diff --git a/IrrExtensions/Incrementor.h b/IrrExtensions/Incrementor.h new file mode 100644 index 0000000..f4b91f1 --- /dev/null +++ b/IrrExtensions/Incrementor.h @@ -0,0 +1,402 @@ +/* +(c) 2012-2013 Nicolaus Anderson +Created: Dec 24, 2012 +Modified Nov 11, 2019 + +Incrementor - A simple incrementing class for keeping track of changes in a value +with fewer keystrokes. +*/ + +//#include "stdtypes.h" +#include +typedef irr::s32 stds32; +typedef irr::u32 stdu32; + +#ifndef __INCREMENTOR__ +#define __INCREMENTOR__ + +class Inc +{ +public: + enum ECycleType + { + /* This incrementor is to act like a boolean value, switching back and forth + between 0 and 1. */ + CYC_BOOLEAN = 0, + + /* This incrementor must be set to the min value after reaching the max value + or must be set to the max value if decremented from the min value. */ + CYC_REPEAT, + + /* This incrementor counts up and down like an ordinary integer. */ + CYC_DEFAULT + }; + +private: + stds32 value; + stds32 step; + stds32 min; + stds32 max; + + ECycleType cycle; + +public: + + //! Constructor + Inc( + ECycleType type = CYC_DEFAULT, + stds32 start_val=0, stds32 start_step=1, stds32 start_min=0, stds32 start_max=1 + ) + : value( start_val ), step( start_step ), min(start_min), max( start_max ), cycle( type ) + {} + + + //! Deconstructor + ~Inc() {} + + + //! Get type of cycle + ECycleType getCycleType() + { + return cycle; + } + + //! Set the cycle type directly + void setCycleType( ECycleType type ) + { + cycle = type; + } + + //! Set the cycle type to the default + void cycleDefault() + { + cycle = CYC_DEFAULT; + } + + //! Set the cycle type to boolean + void cycleBoolean() + { + cycle = CYC_BOOLEAN; + } + + //! Set the cycle type to repeat + void cycleRepeat() + { + cycle = CYC_REPEAT; + } + + //! Checks if out of bounds - fixes if necessary + bool outOfBounds() + { + switch ( cycle ) + { + case CYC_BOOLEAN: + if ( value > 1 ) + { + value = 0; + return true; + } else if ( value < 0 ) + { + value = 1; + return true; + } + break; + + case CYC_REPEAT: + if ( value > max ) + { + // Wrap around if greater than the max + value = (value - max) + min; + return true; + } else if ( value < min ) + { + // Wrap around if greater than the max + value = (value - max) + min; + return true; + } + + default: + if ( value < min || value > max ) + return true; + break; + } + + return false; + } + + //******* Getters and setters ********** + + stds32& Val() + { + return value; + } + + stdu32 uInt() + { + return (stdu32)value; + } + + stds32& getStep() + { + return step; + } + + stds32& getMin() + { + return min; + } + + stds32& getMax() + { + return max; + } + + void setVal( stds32 new_val ) + { + value = new_val; + } + + void setStep( stds32 new_step ) + { + step = new_step; + } + + void setMin( stds32 new_min ) + { + min = new_min; + clampVal(); + } + + void setMax( stds32 new_max ) + { + max = new_max; + clampVal(); + } + + void setMinShiftMax( stds32 new_min ) + { + max += new_min - min; + min = new_min; + clampVal(); + } + + void setMaxShiftMin( stds32 new_max ) + { + min += new_max - max; + max = new_max; + clampVal(); + } + + void setRange( stds32 new_min, stds32 new_max ) + { + min = new_min; + max = new_max; + clampVal(); + } + + // Range modifiers +#ifdef __RANGE_CLASS__ + Range getRange() + { + return Range( min, max ); + } + + void setRange ( Range& range ) + { + min = range.start; + max = range.end; + clampVal(); + } + + Range& operator= ( Range& range ) + { + min = range.start; + max = range.end; + clampVal(); + return *this; + } +#endif + + void restart() + { + value = min; + } + + void clampVal() + { + if ( min > max ) + { + stds32 m = min; + min = max; + max = m; + } + + if ( value < min ) + value = min; + + else if ( max < value ) + value = max; + } + + // ********* Shortcut operators *********** + + bool operator++ () + { + switch( cycle ) + { + case CYC_BOOLEAN: + value++; + break; + + case CYC_REPEAT: + if ( value == max ) + { + value = min; + return true; + } else { + value += step; + } + break; + + default: + value += step; + } + + return outOfBounds(); + } + + bool operator++ ( stds32 ) + { + return ++(*this); + } + + bool operator-- () + { + switch( cycle ) + { + case CYC_BOOLEAN: + value--; + break; + + case CYC_REPEAT: + if ( value == min ) + { + value = max; + return true; + } else { + value -= step; + } + break; + + default: + value -= step; + } + + return outOfBounds() + } + + bool operator-- ( stds32 ) + { + return --(*this); + } + + bool operator==( Inc& other ) + { + return value == other.value; + } + + bool operator>( Inc& other ) + { + return value > other.value; + } + + bool operator>=( Inc& other ) + { + return value >= other.value; + } + + bool operator<( Inc& other ) + { + return value < other.value; + } + + bool operator<=( Inc& other ) + { + return value <= other.value; + } + + Inc& operator=( Inc& other ) + { + return copy(other); + } + + Inc& copy( Inc& other ) + { + value = other.value; + step = other.step; + min = other.min; + max = other.max; + cycle = other.cycle; + + return *this; + } + + //****** assignment operators ******* + + Inc& operator+=( Inc& other ) + { + value += other.value; + + return *this; + } + + Inc& operator-=( Inc& other ) + { + value -= other.value; + + return *this; + } + + Inc& operator=( stds32 val ) + { + value = val; + + return *this; + } + + Inc& operator+=( stds32 val ) + { + value += val; + + return *this; + } + Inc& operator-=( stds32 val ) + { + value -= val; + + return *this; + } + + Inc& operator=( stdu32 val ) + { + value = (stds32)val; + + return *this; + } + + Inc& operator+=( stdu32 val ) + { + value += (stds32)val; + + return *this; + } + + Inc& operator-=( stdu32 val ) + { + value -= (stds32)val; + + return *this; + } +}; + +#endif // define __INCREMENTOR__ diff --git a/IrrExtensions/IncrementorT.h b/IrrExtensions/IncrementorT.h new file mode 100644 index 0000000..08970fc --- /dev/null +++ b/IrrExtensions/IncrementorT.h @@ -0,0 +1,362 @@ +/* +(c) 2012-2013 Nicolaus Anderson +Generated from Incrementer.h on: Jan 1, 2012 +Modified Nov 11, 2019 + +Incrementor - A simple incrementing class for keeping track of changes in a value +with fewer keystrokes. +*/ + +#ifndef __INCREMENTOR__ +#define __INCREMENTOR__ + +template +class Inc +{ +public: + enum ECycleType + { + /* This incrementor is to act like a boolean value, switching back and forth + between 0 and 1. */ + CYC_BOOLEAN = 0, + + /* This incrementor must be set to the min value after reaching the max value + or must be set to the max value if decremented from the min value. */ + CYC_REPEAT, + + /* This incrementor counts up and down like an ordinary integer. */ + CYC_DEFAULT + }; + +private: + Num value; + Num step; + Num min; + Num max; + + ECycleType cycle; + +public: + + //! Constructor + Inc( + ECycleType type = CYC_DEFAULT, + Num start_val=0, Num start_step=1, Num start_min=0, Num start_max=1 + ) + : value( start_val ), step( start_step ), min(start_min), max( start_max ), cycle( type ) + {} + + + //! Deconstructor + ~Inc() {} + + + //! Get type of cycle + ECycleType getCycleType() + { + return cycle; + } + + //! Set the cycle type directly + void setCycleType( ECycleType type ) + { + cycle = type; + } + + //! Set the cycle type to the default + void cycleDefault() + { + cycle = CYC_DEFAULT; + } + + //! Set the cycle type to boolean + void cycleBoolean() + { + cycle = CYC_BOOLEAN; + } + + //! Set the cycle type to repeat + void cycleRepeat() + { + cycle = CYC_REPEAT; + } + + //! Checks if out of bounds - fixes if necessary + bool outOfBounds() + { + switch ( cycle ) + { + case CYC_BOOLEAN: + if ( value > 1 ) + { + value = 0; + return true; + } else if ( value < 0 ) + { + value = 1; + return true; + } + break; + + case CYC_REPEAT: + if ( value > max ) + { + // Wrap around if greater than the max + value = (value - max) + min; + return true; + } else if ( value < min ) + { + // Wrap around if greater than the max + value = (value - max) + min; + return true; + } + + default: + if ( value < min || value > max ) + return true; + break; + } + + return false; + } + + //******* Getters and setters ********** + + Num& Val() + { + return value; + } + + Num& getStep() + { + return step; + } + + Num& getMin() + { + return min; + } + + Num& getMax() + { + return max; + } + + void setVal( Num new_val ) + { + value = new_val; + } + + void setStep( Num new_step ) + { + step = new_step; + } + + void setMin( Num new_min ) + { + min = new_min; + clampVal(); + } + + void setMax( Num new_max ) + { + max = new_max; + clampVal(); + } + + void setMinShiftMax( Num new_min ) + { + max += new_min - min; + min = new_min; + clampVal(); + } + + void setMaxShiftMin( Num new_max ) + { + min += new_max - max; + max = new_max; + clampVal(); + } + + void setRange( Num new_min, Num new_max ) + { + min = new_min; + max = new_max; + clampVal(); + } + + // Range modifiers +#ifdef __RANGE_CLASS__ + Range getRange() + { + return Range( min, max ); + } + + void setRange( const Range& range ) + { + min = range.start; + max = range.end; + clampVal(); + } + + Range& operator= ( const Range& range ) + { + min = range.start; + max = range.end; + clampVal(); + return *this; + } +#endif + + void restart() + { + value = min; + } + + void clampVal() + { + if ( min > max ) + { + Num m = min; + min = max; + max = m; + } + + if ( value < min ) + value = min; + + else if ( max < value ) + value = max; + } + + // ********* Shortcut operators *********** + + bool operator++ () + { + switch( cycle ) + { + case CYC_BOOLEAN: + value++; + break; + + case CYC_REPEAT: + if ( value == max ) + { + value = min; + return true; + } else { + value += step; + } + break; + + default: + value += step; + } + + return outOfBounds(); + } + + bool operator-- () + { + switch( cycle ) + { + case CYC_BOOLEAN: + value--; + break; + + case CYC_REPEAT: + if ( value == min ) + { + value = max; + return true; + } else { + value -= step; + } + break; + + default: + value -= step; + } + + return outOfBounds(); + } + + bool operator==( Inc& other ) + { + return value == other.value; + } + + bool operator>( Inc& other ) + { + return value > other.value; + } + + bool operator>=( Inc& other ) + { + return value >= other.value; + } + + bool operator<( Inc& other ) + { + return value < other.value; + } + + bool operator<=( Inc& other ) + { + return value <= other.value; + } + + Inc& operator=( Inc& other ) + { + return copy(other); + } + + Inc& copy( Inc& other ) + { + value = other.value; + step = other.step; + min = other.min; + max = other.max; + cycle = other.cycle; + + return *this; + } + + //****** assignment operators ******* + + Inc& operator+=( Inc& other ) + { + value += other.value; + + return *this; + } + + Inc& operator-=( Inc& other ) + { + value -= other.value; + + return *this; + } + + Inc& operator=( Num val ) + { + value = val; + + return *this; + } + + Inc& operator+=( Num val ) + { + value += val; + + return *this; + } + Inc& operator-=( Num val ) + { + value -= val; + + return *this; + } +}; + +#endif // define __INCREMENTOR__ diff --git a/IrrExtensions/Range.h b/IrrExtensions/Range.h new file mode 100644 index 0000000..d05f847 --- /dev/null +++ b/IrrExtensions/Range.h @@ -0,0 +1,222 @@ +/* +Range class +(c) Nicolaus Anderson +Created Jan 10, 2013 +Modified Nov 8, 2019 + +License: zlib +*/ + +#ifndef __RANGE_CLASS__ +#define __RANGE_CLASS__ + +template +class Range +{ +public: + T start; + T end; + + Range() + { + start = 0; + end = 0; + } + + Range( T new_start, T new_end ) + { + start = new_start; + end = new_end; + } + + inline T Length() + { + return end - start; + } + + operator T () + { + return Length(); + } + + //------- division + + T operator/ ( T& value ) + { + if ( value != 0 ) + return Length()/value; + + return 0; + } + + Range& operator/= ( T& value ) + { + end = start + Length()/value; + return *this; + } + + //------- multiplication + + T operator* ( Range& other ) + { + return Length()*other.Length(); + } + + T operator* ( T& value ) + { + return Length()*value; + } + + Range& operator*= ( T& value ) + { + end = start + Length()*value; + return *this; + } + + //------- addition + + //! Add ranges + /* Returns the union of two ranges */ + Range operator+ ( Range& other ) + { + return Range( + (start < other.start)? start : other.start, + (end > other.end)? end : other.end + ); + } + + //! Add value + /* Returns the length of the range plus the value */ + T operator+ ( T& value ) + { + return Length() + value; + } + + //------- other + + //! Extend range + /* Extends the range by the given amount while + returning the range for inline usage. */ + Range& extend( T& value ) + { + end += value; + return *this; + } + + //! In range + /* Indicates if a given value is in the range. + \param value - The number in question. + \param bound_inclusive - Whether the bounds should be + included in the range. */ + bool inRange( T& value, bool bound_inclusive=true ) + { + if ( bound_inclusive ) + return start <= value && value <= end; + + else return start < value && value < end; + } + + // ***************** With other types ******************* + + template + inline T2 Length() + { + return (T2)(end - start); + } + + template + operator T2 () + { + return (T2)Length(); + } + + //-------- division + + template + T operator/ ( T2& value ) + { + if ( value != 0 ) + return Length()/(T)value; + + return 0; + } + + template + Range& operator/= ( T2& value ) + { + end = start + Length()/(T)value; + return *this; + } + + //------- multiplication + + template + T operator* ( Range& other ) + { + return Length()*(T)other.Length(); + } + + template + T operator* ( T2& value ) + { + return Length()*(T)value; + } + + template + Range& operator*= ( T2& value ) + { + end = start + Length()*(T)value; + return *this; + } + + //------- addition + + //! Add ranges + /* Returns the union of two ranges */ + template + Range operator+ ( Range& other ) + { + return Range( + (start < other.start)? start : (T)other.start, + (end > other.end)? end : (T)other.end + ); + } + + //! Add value + /* Returns the length of the range plus the value */ + template + T operator+ ( T2& value ) + { + return Length() + (T)value; + } + + //------- + + //! Extend range + /* Extends the range by the given amount while + returning the range for inline usage. */ + template + Range& extend( T2& value ) + { + end += (T)value; + return *this; + } + + //! In range + /* Indicates if a given value is in the range. + \param value - The number in question. + \param bound_inclusive - Whether the bounds should be + included in the range. */ + template + bool inRange( T2& value, bool bound_inclusive=true ) + { + if ( (bound_inclusive? start<=value : start + +#ifndef _CIRCLE2D_ +#define _CIRCLE2D_ + +namespace irr +{ +namespace core +{ + +//! Class circle 2D +/* +A 2D circle. +*/ + +template +class circle2d +{ +public: + vector2d center; + T radius; + + circle2d() + : radius( (T)0 ) + {} + + circle2d( T x, T y, T r ) + : center( vector2d(x,y) ) + , radius( r ) + {} + + circle2d( vector2d origin, T r ) + : center( origin ) + , radius( r ) + {} + + //! Set + void set( T x, T y, T r ) + { + center = vector2d(x,y); + radius = r; + } + + //! Set + void set( vector2d origin, T r ) + { + center = origin; + radius = r; + } + + //! Top point + /* Returns a vector that gives the top-most point on the circle. */ + vector2d topPoint() + { + return vector2d( center.X, center.Y + radius); + } + + //! Get area + T getArea() + { + return PI * radius * radius; + } + + //! Circumference + inline T getCircumference() + { + return 2 * PI * radius; + } + + //! Point inside? + bool isPointInside( vector2d point ) + { + return ( point - center ).getLengthSQ() <= radius * radius; + } + + //! Get horizon + /* Returns the horizontal secant line of the circle at the given + distance from the top. + The line goes from the left side of the circle to the right side. */ + line2d getHorizon( T distDown ) + { + line2d ret; + + ret.start.Y = center.Y + radius - distDown; + ret.end.Y = ret.start.Y; + + ret.end.X = (T) squareroot( (irr::f64) + ( radius * radius ) - ret.start.Y + ); + + ret.start.X = - ret.end.X; // get the negative of the sqrt + + // correct origin offset + ret.start.X += center.X; + ret.end.X += center.X; + + return ret; + } +}; + +typedef circle2d circle2di; +typedef circle2d circle2df; + +} // end namespace core +} // end namespace irr + +#endif // #ifndef _CIRCLE2D_ diff --git a/IrrExtensions/core/irrptr.h b/IrrExtensions/core/irrptr.h new file mode 100755 index 0000000..a6090c4 --- /dev/null +++ b/IrrExtensions/core/irrptr.h @@ -0,0 +1,152 @@ +// By Nicolaus Anderson, 2018 +// Based on code posted by Ethon for free on Irrlicht +// Thanks Ethon! + +#include +#include + +#ifndef __IRRPTR_H__ +#define __IRRPTR_H__ + +// Uncomment to enable +//#define IRRPTR_CHECK_ACCESS + +#ifdef IRRPTR_CHECK_ACCESS +class IrrPtrAccessNullException {}; +#endif + +namespace irr { +namespace tools { + +template +class irrptr : std::enable_if< std::is_base_of< irr::IReferenceCounted, ObjectType >::value > +{ +public: + typedef typename std::remove_pointer< ObjectType >::type element_type; + typedef element_type* pointer; + typedef element_type const* const_pointer; + + irrptr() + : dataptr(nullptr) + {} + + irrptr( const irrptr& other ) + : dataptr( other.dataptr ) + { + grab(); + } + + irrptr& + operator= ( irrptr& other ) { + set(other.dataptr); + return *this; + } + + ~irrptr() { + drop(); + } + + void + set( pointer data ) { + if ( dataptr == data ) + return; + drop(); + if ( data == 0 ) // Force nullptr usage + dataptr = nullptr; + else + dataptr = data; + grab(); + } + + void + share( irrptr& other ) { + if ( dataptr == other.dataptr ) + return; + drop(); + dataptr = other.dataptr; + grab(); + } + + void + swap( irrptr& other ) { + pointer p = other.dataptr; + other.dataptr = dataptr; + dataptr = p; + } + + void + dump() { + drop(); + } + + void + quiet_dump() { + // No drop + dataptr = nullptr; + } + + pointer + get() { + return dataptr; + } + + const_pointer + get() const + { + return dataptr; + } + + element_type& + access() + { +#ifdef IRRPTR_CHECK_ACCESS + if ( !dataptr ) + throw IrrPtrAccessNullException(); +#endif + return *dataptr; + } + + element_type const& + access() const + { +#ifdef IRRPTR_CHECK_ACCESS + if ( !dataptr ) + throw IrrPtrAccessNullException(); +#endif + return *dataptr; + } + + explicit operator bool() const + { + return (dataptr != nullptr); + } + + +protected: + void grab() { + if ( dataptr ) + dataptr->grab(); + } + + void drop() { + if ( dataptr ) + dataptr->drop(); + } + +private: + pointer dataptr; +}; + +}} + +namespace std +{ + template + void swap( ::irr::tools::irrptr& p1, + ::irr::tools::irrptr& p2) + { + p1.swap(p2); + } +} + +#endif // _IRRPTR_HPP__ diff --git a/IrrExtensions/core/triangle2d.h b/IrrExtensions/core/triangle2d.h new file mode 100755 index 0000000..3e4c4f5 --- /dev/null +++ b/IrrExtensions/core/triangle2d.h @@ -0,0 +1,398 @@ +/* +(c) 2013 Nicolaus Anderson +*/ + +#include + +#ifndef _TRIANGLE2D_ +#define _TRIANGLE2D_ + +namespace irr +{ +namespace core +{ + +//! Class triangle 2D +/* +A 2D triangle. +*/ + +template +class triangle2d +{ +public: + + vector2d pt1; + vector2d pt2; + vector2d pt3; + + triangle2d() + {} + + triangle2d( + vector2d point1, + vector2d point2, + vector2d point3 + ) + : pt1( point1 ) + , pt2( point2 ) + , pt3( point3 ) + {} + + triangle2d ( + T p1x, T p1y, + T p2x, T p2y, + T p3x, T p3y + ) + : pt1( vector2d( p1x, p1y ) ) + , pt2( vector2d( p2x, p2y ) ) + , pt3( vector2d( p3x, p3y ) ) + {} + + //! Set + /* Sets the triangle. */ + void set( + vector2d point1, + vector2d point2, + vector2d point3 + ) + { + pt1 = point1; + pt2 = point2; + pt3 = point3; + } + + //! Set + /* Sets the triangle. */ + void set( + T p1x, T p1y, + T p2x, T p2y, + T p3x, T p3y + ) + { + pt1 = vector2d(p1x,p1y); + pt2 = vector2d(p2x,p2y); + pt3 = vector2d(p3x,p3y); + } + + //! Top point + /* Returns the address of the top point. + Default return is point 1. */ + vector2d& topPoint() + { + if ( pt3.Y > pt2.Y ) + { + if ( pt3.Y > pt1.Y ) + return pt3; + } else { + if ( pt2.Y > pt1.Y ) + return pt2; + } + + return pt1; + } + + //! Top point index + /* Returns which of the 3 points is the highest + (default return is 1). */ + irr::u32 topPointIndex() + { + if ( pt3.Y > pt2.Y ) + { + if ( pt3.Y > pt1.Y ) + return 3; + } else { + if ( pt2.Y > pt1.Y ) + return 2; + } + + return 1; + } + + //! Set highest point to first + /* Sets the highest point to be the first of the three points. */ + void setHighestToFirst() + { + vector2d temp; + + if ( pt3.Y > pt2.Y ) + { + if ( pt3.Y > pt1.Y ) + swap( pt1, pt3 ); + } else { + if ( pt2.Y > pt1.Y ) + swap( pt1, pt2 ); + } + + // Else: no change + } + + //! Make triangle clockwise + /* Restore the triangle to a clockwise triangle. */ + void makeCW() + { + //irr::core::vector2d v12; + //irr::core::vector2d v13; + //irr::core::vector2d v13d; + + // Check for equality + if ( pt1 == pt2 || pt1 == pt3 || pt2 == pt3 + || pt1.isBetweenPoints(pt2,pt3) + || pt2.isBetweenPoints(pt1,pt3) + || pt3.isBetweenPoints(pt1,pt2) + ) + return; + + // Set the highest vector to the first one + setHighestToFirst(); + + // This means only points 2 and 3 are variables + /* For clockwise rotation, 2 must be right of 3. + Switch them if this is not the case. */ + + if ( + (pt3.Y - pt1.Y)*(pt2.X - pt1.X) - (pt2.Y - pt1.Y)*(pt3.X - pt1.X) + < 0 + ) + { + swap( pt2, pt3 ); + } + + //v12 = pt2 - pt1; + //v13 = pt3 - pt1; + + //// Vertical line - easy comparison + //if ( v13.X == 0 ) + //{ + // if ( v12.X < 0 ) + // swap( pt2, pt3 ); + + // return; + //} + + ///* Construct a vector the length of v13 along the x axis. */ + //v13d = irr::core::vector2d( + // v13.getLength(), + // 0 + // ); + + ///* Subtract this vector from the vector between points 1 and 2. + //This will give us the same situation as above. */ + + //if ( (v12 - v13d).X < 0 ) + //{ + // swap( pt2, pt3 ); + //} + } + + //! Make triangle counter clockwise + /* Restore the triangle to a counter-clockwise triangle. */ + void makeCCW() + { + makeCW(); + + vector2d temp = pt3; + pt3 = pt2; + pt2 = temp; + } + + //! Get altitude + /* */ + // To do + + //! Get area + /* Returns the area of the triangle. */ + T getArea() + { + // Can't do this with 2D vectors: + //return + // irr::core::abs_( + // ( (pt2 - pt1).crossProduct(pt3 - pt2) ).getLength() + // ) /(T)2; + + return + ( + (pt2 - pt1).getLength() * (pt3 - pt2).getLength() + ) / (T)2; + } + + //! Point inside? + /* Indicates if the point is within the triangle. + Assumes a clockwise triangle. + If you want to keep the original triangle, make a copy of it + and call makeCW() on the copy. */ + bool isPointInside( vector2d point ) + { + /* Points are automatically disqualified if they are + higher than all of the points or lower than all of them. */ + if ( + // higher + ( point.Y > pt1.Y && point.Y > pt2.Y && point.Y > pt3.Y ) + || // lower + ( point.Y < pt1.Y && point.Y < pt2.Y && point.Y < pt3.Y ) + ) + { + return false; + } + + /* If the triangle is flat, then the point need merely be + between any two of the points. */ + if ( pt1 == pt2 || pt1 == pt3 || pt2 == pt3 ) + { + if ( + point.isBetweenPoints(pt2,pt3) + || point.isBetweenPoints(pt1,pt3) + || point.isBetweenPoints(pt1,pt2) + ) + { + return true; + } + return false; + } + + /* For the point to be inside, it must be right of the + vector between pt1 and pt3 and left of the one between + pt1 and pt2. */ + + // What is commented out is too time-consuming + + //line2d line12( pt1, pt2 ); + //line2d line13( pt2, pt3 ); + + //vector2d near12 + // = line12.getClosestPoint( point ); + + //if ( near12.X < point.X ) + // return false; + + //vector2d near13 + // = line13.getClosestPoint( point ); + + //if ( near13.X > point.X ) + // return false; + + // Must be inside + //return true; + + /* Point must be between the ends of the horizon. */ + line2d horiz = getHorizon( point.Y ); + + if ( horiz.start.X =< point.X + && point.X <= horiz.end.X ) + { + return true; + } + + return false; + } + + //! Get horizon + /* Returns the horizontal line between the vectors that go between + points 1 and 2 and the points 1 and 3. The vector always starts from + the left side of the triangle and goes to the right side. + Assumes the triangle has been made clockwise. + \param distDown - The distance from the top to form the line. */ + line2d getHorizon( T distDown ) + { + line2d ret( pt1, pt1 ); + + /* Don't bother if the distance down is out of the triagle. */ + if ( pt1.Y - distDown < (pt2.Y < pt3.Y)? pt2.Y : pt3.Y ) + return ret; + + /* Situations: + 1) Horizon is above point 2 and 3. + 2) Horizon is below point 2 which is above point 3. + 3) Horizon is below point 3 which is above point 2. + */ + + ret.start.Y = pt1.Y - distDown; + ret.end.Y = pt1.Y - distDown; + + // Situation 1? + if ( pt1.Y - distDown >= pt2.Y && pt1.Y - distDown >= pt3.Y ) + { + if ( pt3.Y == pt1.Y ) + { + ret.start = (pt1.X < pt3.X)? pt1 : pt3; + } else { + ret.start.X = + // delta y * 1/m + (pt1.Y - distDown) * ( pt3.X - pt1.X ) / ( pt3.Y - pt1.Y ) + + pt1.X; // offset to location + } + + if ( pt2.Y == pt1.Y ) + { + ret.end = (pt1.X > pt2.X)? pt1 : pt2; + } else { + ret.end.X = + // delta y * 1/m + (pt1.Y - distDown) * ( pt2.X - pt1.X ) / ( pt2.Y - pt1.Y ) + + pt1.X; // offset to location + } + + return ret; + } + + // Situation 2? + if ( pt2.Y >= pt3.Y ) + { + if ( pt2.Y == pt3.Y ) + { + ret.start = pt3; + ret.end = pt2; + return ret; + } + + // pt2.Y != pt3.Y + + ret.start.X = + // delta y * 1/m + (pt1.Y - distDown) * ( pt3.X - pt1.X ) / ( pt3.Y - pt1.Y ) + + pt1.X; // offset to location + + ret.end.X = + // delta y * 1/m + (pt1.Y - distDown) * ( pt3.X - pt2.X ) / ( pt3.Y - pt2.Y ) + + pt2.X; // offset to location + + return ret; + } + + // Situation 3 + + ret.start.X = + // delta y * 1/m + (pt1.Y - distDown) * ( pt2.X - pt3.X ) / ( pt2.Y - pt3.Y ) + + pt3.X; // offset to location + + ret.end.X = + // delta y * 1/m + (pt1.Y - distDown) * ( pt2.X - pt1.X ) / ( pt2.Y - pt1.Y ) + + pt1.X; // offset to location + + return ret; + } + + //! Swap points + /* Swaps the values of the two points. */ + inline void swap( + vector2d& point1; + vector2d& point2; + ) + { + vector2d temp = point1; + point1 = point2; + point2 = temp; + } + +}; + +typedef triangle2d triangle2di; +typedef triangle2d triangle2df; + +} // end namespace core +} // end namespace irr + +#endif // #ifndef _TRIANGLE2D_ \ No newline at end of file diff --git a/IrrExtensions/gui/Animators/IAnimGUIAnimator.h b/IrrExtensions/gui/Animators/IAnimGUIAnimator.h new file mode 100644 index 0000000..f07acf1 --- /dev/null +++ b/IrrExtensions/gui/Animators/IAnimGUIAnimator.h @@ -0,0 +1,36 @@ +/* +(c) 2013 Nicolaus Anderson +*/ + +#include + +#ifndef _IANIMATED_GUI_ANIMATOR_ +#define _IANIMATED_GUI_ANIMATOR_ + +namespace irr +{ +namespace gui +{ + +//! class Animated GUI Animator +/* Designed to be used with IAnimGUIElement. +This class is meant to be used during OnPostRender() for changing +the GUI element. */ +class IAnimGUIAnimator : virtual public irr::IReferenceCounted +{ +public: + //! On Post-Render / Animate + /* This is effectively the same as OnAnimate for ISceneNode + but is so named because it is meant to be called on post-rendering. */ + virtual void OnPostRender( irr::u32 timeMs, IGUIElement* element )=0; + + //! Cast operator + operator IAnimGUIAnimator*() + { + return (IAnimGUIAnimator*)this; + } +}; + +}} // end namespaces gui and irr + +#endif // #ifndef _IANIMATED_GUI_ANIMATOR_ \ No newline at end of file diff --git a/IrrExtensions/gui/Animators/IAnimGUIElement.h b/IrrExtensions/gui/Animators/IAnimGUIElement.h new file mode 100644 index 0000000..5015bbe --- /dev/null +++ b/IrrExtensions/gui/Animators/IAnimGUIElement.h @@ -0,0 +1,89 @@ +/* +(c) 2013 Nicolaus Anderson +*/ + +#include +#include "IAnimGUIAnimator.h" + +#ifndef _IANIMATED_GUI_ELEMENT_ +#define _IANIMATED_GUI_ELEMENT_ + +namespace irr +{ +namespace gui +{ + +//! Class Animated GUI Element +/* Allows GUI elements to be animated. */ +class IAnimGUIElement : public IGUIElement +{ +protected: + irr::core::list Animators; + +public: + //! Constructor + /* Merely a callback for the IGUIElement constructor. */ + IAnimGUIElement( + EGUI_ELEMENT_TYPE type, + IGUIEnvironment* environment, + IGUIElement* parent, + s32 id, + const core::recti& rectangle + ) + : IGUIElement( type, environment, parent, id, rectangle ) + {} + + //! Add animator + void addAnimator( IAnimGUIAnimator* animator ) + { + if ( animator ) + { + animator->grab(); + Animators.push_back( animator ); + } + } + + //! Remove animator + void removeAnimator( IAnimGUIAnimator* animator ) + { + irr::core::list::Iterator it = Animators.begin(); + for ( ; it != Animators.end(); ++it ) + { + if ( *it == animator ) + { + (*it)->drop(); + Animators.erase(it); + return; + } + } + } + + //! Remove all animators + void clearAnimators() + { + irr::core::list::Iterator it = Animators.begin(); + while ( it != Animators.end() ) + { + (*it)->drop(); + Animators.erase(it); + it = Animators.begin(); + } + } + + //! On Post Render + /* Applies the animators */ + virtual void OnPostRender( irr::u32 timeMs ) + { + irr::core::list::Iterator it; + for ( ; it != Animators.end(); ++it ) + { + (*it)->OnPostRender( timeMs, this ); + } + + IGUIElement::OnPostRender(timeMs); + } +}; + +}} // end namespaces gui and irr + +#endif // #ifndef _IANIMATED_GUI_ELEMENT_ diff --git a/IrrExtensions/gui/Animators/MoveGUIAnimator.cpp b/IrrExtensions/gui/Animators/MoveGUIAnimator.cpp new file mode 100644 index 0000000..e8188f1 --- /dev/null +++ b/IrrExtensions/gui/Animators/MoveGUIAnimator.cpp @@ -0,0 +1,94 @@ +/* +(c) 2013 Nicolaus Anderson +*/ + +#include "MoveGUIAnimator.h" + +#ifndef _MOVE_GUI_ANIMATOR_CPP_ +#define _MOVE_GUI_ANIMATOR_CPP_ + +namespace irr +{ +namespace gui +{ + +MoveGUIAnimator::MoveGUIAnimator( + core::recti* rectangle, + f64 speed, + u32 startTimeMs + ) + : ElemRect( rectangle ) + , OrigRect( *rectangle ) + , TargetRect( *rectangle ) + , transitionSpeed( speed ) + , startTime( startTimeMs ) +{ +} + +MoveGUIAnimator::~MoveGUIAnimator() +{} + +void MoveGUIAnimator::setStartTime( u32 timeMs ) +{ + if ( inMotion ) + return; + + startTime = timeMs; +} + +void MoveGUIAnimator::OnPostRender( u32 timeMs, IGUIElement* element ) +{ + // This animator must be started first. + if ( startTime > timeMs ) + { + inMotion = false; + return; + } + + if ( *ElemRect == TargetRect ) + { + inMotion = false; + OrigRect = TargetRect; + return; + } + + inMotion = true; + + /* Obtain the transition delta for the rectangle based on start time. + Note that the direction of interpolate() is backwards, so the transition + distance is subtracted from 1.0 rather than being used on its own. */ + irr::f64 delta = + 1.0 - irr::core::clamp( + ((irr::f64)(timeMs - startTime)*transitionSpeed), + 0.0, 1.0 + ); + + ElemRect->UpperLeftCorner.interpolate( + OrigRect.UpperLeftCorner, + TargetRect.UpperLeftCorner, + delta + ); + + ElemRect->LowerRightCorner.interpolate( + OrigRect.LowerRightCorner, + TargetRect.LowerRightCorner, + delta + ); +} + +void MoveGUIAnimator::setTargetRect( core::rect newTarget ) +{ + OrigRect = *ElemRect; + TargetRect = newTarget; +} + +void MoveGUIAnimator::setImmediateTarget( core::rect newTarget, u32 timeMs ) +{ + OrigRect = *ElemRect; + TargetRect = newTarget; + startTime = timeMs; +} + +}} // end namespaces gui and irr + +#endif // #ifndef _MOVE_GUI_ANIMATOR_CPP_ diff --git a/IrrExtensions/gui/Animators/MoveGUIAnimator.h b/IrrExtensions/gui/Animators/MoveGUIAnimator.h new file mode 100644 index 0000000..152a407 --- /dev/null +++ b/IrrExtensions/gui/Animators/MoveGUIAnimator.h @@ -0,0 +1,73 @@ +/* +(c) 2013 Nicolaus Anderson +*/ + +#include +#include "IAnimGUIAnimator.h" + +#ifndef _MOVE_GUI_ANIMATOR_H_ +#define _MOVE_GUI_ANIMATOR_H_ + +namespace irr +{ +namespace gui +{ + +class MoveGUIAnimator : public IAnimGUIAnimator +{ +protected: + //! The element rectangle to be changed. + /* Notice that this is a pointer, allowing us to change any rectangle + that is passed to this animator by the GUI element / creator of this + animator. */ + core::rect* ElemRect; + + //! Original rectangle. + /* This is the initial rectangle that is to be changed. + Once the animation is complete or its course has been altered, + this is set to the element's current rectangle. */ + core::rect OrigRect; + + //! Target rectangle + /* This is the final destination rectangle. */ + core::rect TargetRect; + + /* The starting time of this element in milliseconds. */ + u32 startTime; + + /* Speed of the transition. */ + f64 transitionSpeed; + + /* In motion. + When this is set to true, the start time cannot be changed + without starting a new animation. */ + bool inMotion; + +public: + //! Constructor and destructor + MoveGUIAnimator( + core::recti* rectangle, + f64 speed, + u32 startTimeMs + ); + + ~MoveGUIAnimator(); + + //! Set start time. + /* Sets the animator's start time. Note that this does nothing if + there is an animation that has not been completed yet. */ + void setStartTime( u32 timeMs ); + + //! On Post Render / Animate + virtual void OnPostRender( u32 timeMs, IGUIElement* element ); + + //! Set target rectangle + void setTargetRect( core::rect newTarget ); + + //! Set immediate target (for interrupting a current transition) + void setImmediateTarget( core::rect newTarget, u32 timeMs ); +}; + +}} // end namespaces gui and irr + +#endif // #ifndef _MOVE_GUI_ANIMATOR_H_ \ No newline at end of file diff --git a/IrrExtensions/gui/GUIColorEditBox.cpp b/IrrExtensions/gui/GUIColorEditBox.cpp new file mode 100644 index 0000000..655ed59 --- /dev/null +++ b/IrrExtensions/gui/GUIColorEditBox.cpp @@ -0,0 +1,516 @@ +// (c) 2014 Nicolaus Anderson + +#include "GUIColorEditBox.h" +#include +#include + +namespace irr { +namespace gui { + +GUIColorEditBox::GUIColorEditBox( + IGUIEnvironment* pEnvironment, IGUIElement* pParent, recti pRect, s32 id + ) + : IGUIElement( EGUIET_ELEMENT, pEnvironment, pParent, id, pRect ) + , color(0xff000000) + , cursorColor(0x0000ddff) + , cursorIdx(0) + , cursorRect(0,0,0,0) + , focusTime(0) +{ + Text = L"ff000000"; +} + +GUIColorEditBox::~GUIColorEditBox() +{} + +SColor GUIColorEditBox::getColor() +{ + return color; +} + +void GUIColorEditBox::setColor( SColor pColor, bool notifyParent ) +{ + color = pColor; + convertColorToText(); + + if ( Parent && notifyParent ) + { + SEvent newEvent; + newEvent.EventType = EET_GUI_EVENT; + newEvent.GUIEvent.Caller = this; + newEvent.GUIEvent.Element = 0; + newEvent.GUIEvent.EventType = EGET_EDITBOX_CHANGED; + Parent->OnEvent(newEvent); + } +} + +void +GUIColorEditBox::setText(const wchar_t* text) +{ + Text = text; + if ( Text.size() > 6 ) { + Text = Text.subString(0,6); + } + u32 i=0; + for (; i < 6; ++i) { + if ( Text[i] < '0' ) + Text[i] = '0'; + + else if ( Text[i] > 'f' ) + Text[i] = 'f'; + } + convertTextToColor(); +} + +bool GUIColorEditBox::OnEvent( const SEvent& event ) +{ + SEvent newEvent; + + if ( isEnabled() && isVisible() ) + switch( event.EventType ) + { + case EET_MOUSE_INPUT_EVENT: + if ( event.MouseInput.isLeftPressed() ) + return setCursorIndexFromPosition( + vector2di(event.MouseInput.X, event.MouseInput.Y) + ); + return false; + + case EET_KEY_INPUT_EVENT: + if ( event.KeyInput.PressedDown ) + if ( insertChar( event.KeyInput.Key, event.KeyInput.Char ) ) + { + if ( Parent ) + { + + newEvent.EventType = EET_GUI_EVENT; + newEvent.GUIEvent.Caller = this; + newEvent.GUIEvent.Element = 0; + newEvent.GUIEvent.EventType = EGET_EDITBOX_CHANGED; + Parent->OnEvent(newEvent); + } + return true; + } + break; + + case EET_GUI_EVENT: + switch ( event.GUIEvent.EventType ) + { + case EGET_ELEMENT_FOCUSED: + updateCursorRect(); + break; + + case EGET_ELEMENT_FOCUS_LOST: + break; + + default: break; + } + + default: break; + } + + return IGUIElement::OnEvent(event); +} + +void GUIColorEditBox::draw() +{ + if ( !isVisible() ) + return; + + IGUISkin* skin = Environment->getSkin(); + + if ( Environment->getFocus() == this ) + { + skin->draw3DSunkenPane( + this, + skin->getColor(EGDC_EDITABLE), + true, true, + AbsoluteRect, &AbsoluteClippingRect ); + + skin->getFont(irr::gui::EGDF_DEFAULT)->draw( + Text, + AbsoluteRect, + skin->getColor(EGDC_BUTTON_TEXT), + true, true, // center + &AbsoluteClippingRect + ); + + // Draw transitioning-color cursor + u32 ft = focusTime % 1000; + if ( ft > 500 ) + { + ft = 1000 - ft; + } + ft /= 4; + cursorColor.setAlpha( core::clamp( ft, (u32)0, (u32)255 ) ); + + recti cR = cursorRect + AbsoluteRect.UpperLeftCorner; + skin->draw2DRectangle( + this, + cursorColor, + cR, + &AbsoluteClippingRect + ); + } else { + skin->draw3DSunkenPane( + this, + skin->getColor(EGDC_GRAY_EDITABLE), + true, true, + AbsoluteRect, &AbsoluteClippingRect ); + + // Draw grayed text + skin->getFont()->draw( + Text, + AbsoluteRect, + skin->getColor(EGDC_GRAY_TEXT), + true, true, // center + &AbsoluteClippingRect + ); + } +} + +void GUIColorEditBox::OnPostRender(u32 timeMs) +{ + focusTime = timeMs; +} + +bool GUIColorEditBox::setCursorIndexFromPosition( vector2di pPos ) +{ + // Actual location + vector2di cursorPos = pPos - AbsoluteRect.UpperLeftCorner; + if ( ! AbsoluteClippingRect.isPointInside( pPos ) ) + return false; + + // Calculate by starting with the start position + dimension2du textSize = Environment->getSkin()->getFont()->getDimension(Text.c_str()); + cursorPos.X -= (AbsoluteRect.getWidth() - textSize.Width)/2; + cursorPos.Y -= (AbsoluteRect.getHeight() - textSize.Height)/2; + + // Cursor must be in text area + if ( cursorPos.X < 0 || (u32)cursorPos.X >= textSize.Width + || cursorPos.Y < 0 || (u32)cursorPos.Y >= textSize.Height) + { + return false; + } + + // KEEP for FAST version + //cursorIdx = u32( (cursorPos.X * 8) / textSize.Width ); + + cursorIdx = Environment->getSkin()->getFont()->getCharacterFromPos(Text.c_str(), cursorPos.X); + + updateCursorRect(); + + return true; +} + +void GUIColorEditBox::updateCursorRect() +{ + dimension2du textSize = Environment->getSkin()->getFont()->getDimension(Text.c_str()); + + vector2di center( RelativeRect.getWidth(), RelativeRect.getHeight() ); + center /= 2; + vector2di start = center - vector2di( textSize.Width/2, textSize.Height/2 ); + + // KEEP THIS FOR FASTER CODE + //cursorRect.UpperLeftCorner.X = start.X + (textSize.Width * cursorIdx)/8 - 1; + //cursorRect.UpperLeftCorner.Y = start.Y; + //cursorRect.LowerRightCorner.X = start.X + (textSize.Width * (cursorIdx+1))/8; + //cursorRect.LowerRightCorner.Y = start.Y + textSize.Height; + + s32 indexCoord = 0; + s32 lastIndexCoord = 0; + core::stringw shortText; + if ( cursorIdx > 0 ) + { + shortText = Text.subString(0,cursorIdx); + indexCoord = Environment->getSkin()->getFont()->getDimension( shortText.c_str() ).Width; + } + shortText = Text.subString(cursorIdx,1); + lastIndexCoord = Environment->getSkin()->getFont()->getDimension( shortText.c_str() ).Width; + + cursorRect.UpperLeftCorner.X = start.X + indexCoord; + cursorRect.UpperLeftCorner.Y = start.Y; + cursorRect.LowerRightCorner.X = start.X + indexCoord + lastIndexCoord; + cursorRect.LowerRightCorner.Y = start.Y + textSize.Height; + + //cursorRect.repair(); // Causes (a bug:) box to not appear when cursoxIdx>5 +} + +bool GUIColorEditBox::insertChar( EKEY_CODE pKeyCode, wchar_t pKey ) +{ + u32 cursorByte = (7-cursorIdx)*4; + //u32 savedColor = color.color & ( 0xffffffff ^ (0xf0000000 >> cursorByte) ); + u32 savedColor = color.color & ( ~ ( 0x0000000f << cursorByte ) ); + + // Some systems (e.g. X11) fill event.KeyCode.Char correctly for NumPad but still pass the same KeyCode + // as if NumLock were off. + if ( pKey == 0 ) { + switch ( pKeyCode ) + { + // Cursor movement + case KEY_HOME: + cursorIdx = 0; + updateCursorRect(); + return true; + + case KEY_END: + cursorIdx = 7; + updateCursorRect(); + return true; + + case KEY_DELETE: + clear(); + cursorIdx = 0; + updateCursorRect(); + return true; + + case KEY_LEFT: + if ( cursorIdx > 0 ) + --cursorIdx; + updateCursorRect(); + return true; + + case KEY_RIGHT: + if ( cursorIdx < 7 ) + ++cursorIdx; + updateCursorRect(); + return true; + + default: + return false; + } + } + + u32 c_bit; + if ( pKey >= L'0' && pKey <= '9' ) { + c_bit = (u32) pKey - L'0'; + color.color = (c_bit << cursorByte) | savedColor; + Text[cursorIdx] = pKey; + ++cursorIdx; + if ( cursorIdx > 7 ) cursorIdx = 0; + updateCursorRect(); + return true; + } + + switch ( pKeyCode ) + { +/* + // Inserting characters + case KEY_KEY_0: + case KEY_NUMPAD0: + color.color = savedColor; + Text[cursorIdx] = '0'; + ++cursorIdx; + if ( cursorIdx > 7 ) cursorIdx = 0; + updateCursorRect(); + return true; + + case KEY_KEY_1: + case KEY_NUMPAD1: + color.color = (1 << cursorByte) | savedColor; + Text[cursorIdx] = '1'; + ++cursorIdx; + if ( cursorIdx > 7 ) cursorIdx = 0; + updateCursorRect(); + return true; + + case KEY_KEY_2: + case KEY_NUMPAD2: + color.color = (2 << cursorByte) | savedColor; + Text[cursorIdx] = '2'; + ++cursorIdx; + if ( cursorIdx > 7 ) cursorIdx = 0; + updateCursorRect(); + return true; + + case KEY_KEY_3: + case KEY_NUMPAD3: + color.color = (3 << cursorByte) | savedColor; + Text[cursorIdx] = '3'; + ++cursorIdx; + if ( cursorIdx > 7 ) cursorIdx = 0; + updateCursorRect(); + return true; + + case KEY_KEY_4: + case KEY_NUMPAD4: + color.color = (4 << cursorByte) | savedColor; + Text[cursorIdx] = '4'; + ++cursorIdx; + if ( cursorIdx > 7 ) cursorIdx = 0; + updateCursorRect(); + return true; + + case KEY_KEY_5: + case KEY_NUMPAD5: + color.color = (5 << cursorByte) | savedColor; + Text[cursorIdx] = '5'; + ++cursorIdx; + if ( cursorIdx > 7 ) cursorIdx = 0; + updateCursorRect(); + return true; + + case KEY_KEY_6: + case KEY_NUMPAD6: + color.color = (6 << cursorByte) | savedColor; + Text[cursorIdx] = '6'; + ++cursorIdx; + if ( cursorIdx > 7 ) cursorIdx = 0; + updateCursorRect(); + return true; + + case KEY_KEY_7: + case KEY_NUMPAD7: + color.color = (7 << cursorByte) | savedColor; + Text[cursorIdx] = '7'; + ++cursorIdx; + if ( cursorIdx > 7 ) cursorIdx = 0; + updateCursorRect(); + return true; + + case KEY_KEY_8: + case KEY_NUMPAD8: + color.color = (8 << cursorByte) | savedColor; + Text[cursorIdx] = '8'; + ++cursorIdx; + if ( cursorIdx > 7 ) cursorIdx = 0; + updateCursorRect(); + return true; + + case KEY_KEY_9: + case KEY_NUMPAD9: + color.color = (9 << cursorByte) | savedColor; + Text[cursorIdx] = '9'; + ++cursorIdx; + if ( cursorIdx > 7 ) cursorIdx = 0; + updateCursorRect(); + return true; +*/ + + case KEY_KEY_A: + // same as: color.color = (0xa0 << cursorByte) | savedColor; + color.color = (10 << cursorByte) | savedColor; + Text[cursorIdx] = 'a'; + ++cursorIdx; + if ( cursorIdx > 7 ) cursorIdx = 0; + updateCursorRect(); + return true; + + case KEY_KEY_B: + color.color = (11 << cursorByte) | savedColor; + Text[cursorIdx] = 'b'; + ++cursorIdx; + if ( cursorIdx > 7 ) cursorIdx = 0; + updateCursorRect(); + return true; + + case KEY_KEY_C: + color.color = (12 << cursorByte) | savedColor; + Text[cursorIdx] = 'c'; + ++cursorIdx; + if ( cursorIdx > 7 ) cursorIdx = 0; + updateCursorRect(); + return true; + + case KEY_KEY_D: + color.color = (13 << cursorByte) | savedColor; + Text[cursorIdx] = 'd'; + ++cursorIdx; + if ( cursorIdx > 7 ) cursorIdx = 0; + updateCursorRect(); + return true; + + case KEY_KEY_E: + color.color = (14 << cursorByte) | savedColor; + Text[cursorIdx] = 'e'; + ++cursorIdx; + if ( cursorIdx > 7 ) cursorIdx = 0; + updateCursorRect(); + return true; + + case KEY_KEY_F: + color.color = (15 << cursorByte) | savedColor; + Text[cursorIdx] = 'f'; + ++cursorIdx; + if ( cursorIdx > 7 ) cursorIdx = 0; + updateCursorRect(); + return true; + + default: break; + } + + return false; +} + +void GUIColorEditBox::convertTextToColor() +{ + color.color = 0; + u32 i; + if ( Text.size() != 8 ) Text = L"ff000000"; // User used setText(), so fix it. + for ( i=0; i < 8; i++ ) + { + if ( Text[i] <= 'f' && Text[i] >= 'a' ) + color.color |= (Text[i] - 'a' + 10) >> (i*4); + + else if ( Text[i] <= '9' && Text[i] >= '0' ) + color.color |= (Text[i] - '0') >> (i*4); + + // else + // User must have used setText() + } +} + +void GUIColorEditBox::convertColorToText() +{ + u32 i; + u8 chr; + if ( Text.size() != 8 ) Text = L"ff000000"; // User used setText(), so fix it. + for ( i=0; i < 8; i++ ) + { + /* On this computer, when going FROM u32 TO u8, you have to shift the + bits to the right. I will probably need to have this check for endianess + if this is going to be portable code. */ + chr = u8( ( color.color & (0xf0000000 >> (i*4)) ) >> ((7-i)*4) ); + if ( chr > 9 ) + Text[i] = c8( (chr-10) + 'a' ); + else + Text[i] = c8( chr + '0' ); + } +} + +void GUIColorEditBox::clear() +{ + color.color = 0; + Text = L"00000000"; +} + +void GUIColorEditBox::serializeAttributes( + irr::io::IAttributes* out, + irr::io::SAttributeReadWriteOptions* options + ) +{ + IGUIElement::serializeAttributes(out,options); + + out->addColor("Color", color); +} + +void GUIColorEditBox::deserializeAttributes( + irr::io::IAttributes* in, + irr::io::SAttributeReadWriteOptions* options + ) +{ + IGUIElement::deserializeAttributes(in,options); + + bool notifyParentOfEvent = false; + if ( in->existsAttribute("NotifyParentOfColorChange") ) { + notifyParentOfEvent = in->getAttributeAsBool("NotifyParentOfColorChange"); + } + + // Override setText() + if ( in->existsAttribute("Color") ) { + setColor( in->getAttributeAsColor("Color", color), notifyParentOfEvent ); + } +} + +} +} diff --git a/IrrExtensions/gui/GUIColorEditBox.h b/IrrExtensions/gui/GUIColorEditBox.h new file mode 100644 index 0000000..c2e52c8 --- /dev/null +++ b/IrrExtensions/gui/GUIColorEditBox.h @@ -0,0 +1,95 @@ +// (c) 2014 Nicolaus Anderson + +#include +#include +#include + +#ifndef GUI_COLOR_EDITBOX_H +#define GUI_COLOR_EDITBOX_H + +/* Class GUI Color Edit Box + +A GUI Element that accepts only certain user input +(e.g. the character range '0'-'9', 'a'-'f', as well as some +useful editing keys). +The box always contains a valid, HTML-style color, and the +user input is such that it is overwriting, and that way, +8 characters are always present in the edit box. +*/ + +namespace irr +{ +namespace gui +{ + + using irr::video::SColor; + using irr::core::recti; + using irr::core::vector2di; + using irr::core::dimension2du; + +class GUIColorEditBox : public IGUIElement +{ +protected: + SColor color, cursorColor; + s32 cursorIdx; // index of the cursor in the text + recti cursorRect; + u32 focusTime; + +public: + GUIColorEditBox( IGUIEnvironment* pEnvironment, IGUIElement* pParent, recti pRect, s32 id=-1 ); + ~GUIColorEditBox(); + + SColor getColor(); + void setColor( SColor pColor, bool notifyParent=true ); + + // Set the numeric text + virtual void setText(const wchar_t* text); + + /* Handles user events. + Because of how this element refreshes, the data/color should NOT + be taken from this element until this element has lost focus, + upon which it will call its parent. */ + virtual bool OnEvent( const SEvent& event ); + + // Draws to the screen + virtual void draw(); + + // Post rendering (for cursor time) + virtual void OnPostRender(u32 timeMs); + + virtual const c8* getTypeName() const { return staticTypeName(); } + static const c8* staticTypeName() { return "colorEditbox"; } + + // For completeness, we allow both setText and setColor to set the color + virtual void serializeAttributes( + irr::io::IAttributes* out, + irr::io::SAttributeReadWriteOptions* options=0 + ); + + virtual void deserializeAttributes( + irr::io::IAttributes* in, + irr::io::SAttributeReadWriteOptions* options=0 + ); + +protected: + // Attempts to set the cursor position - Returns whether or not it could + bool setCursorIndexFromPosition( vector2di pPos ); + + // Sets the cursor rectangle to the cursor position + void updateCursorRect(); + + // Attempts to insert a character + bool insertChar( EKEY_CODE pKeyCode, wchar_t pKey ); + + // Internal conversion functions + void convertTextToColor(); + void convertColorToText(); + + // Erase data + void clear(); +}; + +} +} + +#endif diff --git a/IrrExtensions/gui/GUIColorSample.cpp b/IrrExtensions/gui/GUIColorSample.cpp new file mode 100644 index 0000000..ac7f345 --- /dev/null +++ b/IrrExtensions/gui/GUIColorSample.cpp @@ -0,0 +1,157 @@ +// (c) 2014 Nicolaus Anderson + +#ifndef GUI_COLOR_SAMPLE_CPP +#define GUI_COLOR_SAMPLE_CPP + +#include "GUIColorSample.h" +#include + +namespace irr { +namespace gui { + +using irr::core::vector2d; +using irr::video::IVideoDriver; + +GUIColorSample::GUIColorSample( IGUIEnvironment* pEnvironment, IGUIElement* pParent, recti pRect, s32 id ) + : IGUIElement( EGUIET_ELEMENT, pEnvironment, pParent, id, pRect ) + , upperLeft(0) + , upperRight(0) + , lowerLeft(0) + , lowerRight(0) + , drawBorder(false) + , borderWidth(3) +// , colorArea(pRect) +{ + colorArea = AbsoluteClippingRect; +} + +void GUIColorSample::setDrawBorder(bool yes) +{ + drawBorder = yes; + + colorArea = AbsoluteClippingRect; + + if ( drawBorder ) + { + colorArea.UpperLeftCorner += vector2d(borderWidth); + colorArea.LowerRightCorner -= vector2d(borderWidth); + } +} + +void GUIColorSample::updateAbsolutePosition() +{ + IGUIElement::updateAbsolutePosition(); + + colorArea = AbsoluteClippingRect; + + if ( drawBorder ) + { + colorArea.UpperLeftCorner += vector2d(borderWidth); + colorArea.LowerRightCorner -= vector2d(borderWidth); + } +} + +void GUIColorSample::showOneColor( SColor pColor ) +{ + upperLeft = pColor; + upperRight = pColor; + lowerLeft = pColor; + lowerRight = pColor; +} + +void GUIColorSample::showTwoColors( SColor pTopLeft, SColor pBottomRight, bool horizontal ) +{ + upperLeft = pTopLeft; + lowerRight = pBottomRight; + if ( horizontal ) + { + upperRight = pTopLeft; + lowerRight = pBottomRight; + } else { + upperRight = pBottomRight; + lowerRight = pTopLeft; + } +} + +void GUIColorSample::showFourColors( SColor pUpperLeft, SColor pUpperRight, SColor pLowerLeft, SColor pLowerRight ) +{ + upperLeft = pUpperLeft; + upperRight = pUpperRight; + lowerLeft = pLowerLeft; + lowerRight = pLowerRight; +} + +bool GUIColorSample::OnEvent( const SEvent& event ) +{ + /* This element cannot be set in focus. */ + if ( !isVisible() || !isEnabled() ) + if ( event.EventType == EET_GUI_EVENT + && event.GUIEvent.Caller == this + && event.GUIEvent.EventType == EGET_ELEMENT_FOCUSED ) + { + if ( Parent ) + Environment->setFocus( Parent ); + else + Environment->setFocus(0); + return true; + } + + return IGUIElement::OnEvent(event); +} + +void GUIColorSample::draw() +{ + if ( isVisible() ) + { + if ( drawBorder ) + { + Environment->getSkin()->draw3DSunkenPane( + this, + Environment->getSkin()->getColor(EGDC_3D_FACE), + true, false, + AbsoluteRect, + &AbsoluteClippingRect + ); + } + + Environment->getVideoDriver()->draw2DRectangle( + colorArea, + upperLeft, upperRight, lowerLeft, lowerRight, + &AbsoluteClippingRect + ); + } +} + +void GUIColorSample::serializeAttributes( + irr::io::IAttributes* out, + irr::io::SAttributeReadWriteOptions* options + ) +{ + IGUIElement::serializeAttributes(out,options); + + out->addColor("UpperLeftColor", upperLeft); + out->addColor("UpperRightColor", upperRight); + out->addColor("LowerLeftColor", lowerLeft); + out->addColor("LowerRightColor", lowerRight); + out->addBool("DrawBorder", drawBorder); + out->addInt("BorderWidth", borderWidth); +} + +void GUIColorSample::deserializeAttributes( + irr::io::IAttributes* in, + irr::io::SAttributeReadWriteOptions* options + ) +{ + IGUIElement::deserializeAttributes(in,options); + + upperLeft = in->getAttributeAsColor("UpperLeftColor", upperLeft); + upperRight = in->getAttributeAsColor("UpperRightColor", upperRight); + lowerLeft = in->getAttributeAsColor("LowerLeftColor", lowerLeft); + lowerRight = in->getAttributeAsColor("LowerRightColor", lowerRight); + borderWidth = in->getAttributeAsInt("BorderWidth", borderWidth); + setDrawBorder( in->getAttributeAsBool("DrawBorder", drawBorder) ); +} + +}} + +#endif // #ifndef GUI_COLOR_SAMPLE_CPP diff --git a/IrrExtensions/gui/GUIColorSample.h b/IrrExtensions/gui/GUIColorSample.h new file mode 100644 index 0000000..5993454 --- /dev/null +++ b/IrrExtensions/gui/GUIColorSample.h @@ -0,0 +1,60 @@ +// (c) 2014 Nicolaus Anderson + +#ifndef GUI_COLOR_SAMPLE_H +#define GUI_COLOR_SAMPLE_H + +#include +#include + +namespace irr { +namespace gui { + +using core::recti; +using video::SColor; + +/* Just a simple square element that shows one or more +colors, depending on its setting. */ +class GUIColorSample : public IGUIElement +{ + SColor upperLeft; + SColor upperRight; + SColor lowerLeft; + SColor lowerRight; + + bool drawBorder; + s32 borderWidth; + recti colorArea; + + /* Note: set enabled to "true" if you want to be able to set this element + to the focus. */ + +public: + GUIColorSample( IGUIEnvironment* pEnvironment, IGUIElement* pParent, recti pRect, s32 id=-1 ); + + void setDrawBorder(bool yes); + virtual void updateAbsolutePosition(); + + void showOneColor( SColor pColor ); + void showTwoColors( SColor pTopLeft, SColor pBottomRight, bool horizontal ); + void showFourColors( SColor pUpperLeft, SColor pUpperRight, SColor pLowerLeft, SColor pLowerRight ); + + virtual bool OnEvent( const SEvent& event ); + virtual void draw(); + + virtual const c8* getTypeName() const { return staticTypeName(); } + static const c8* staticTypeName() { return "colorSample"; } + + virtual void serializeAttributes( + irr::io::IAttributes* out, + irr::io::SAttributeReadWriteOptions* options=0 + ); + + virtual void deserializeAttributes( + irr::io::IAttributes* in, + irr::io::SAttributeReadWriteOptions* options=0 + ); +}; + +}} + +#endif // #ifndef GUI_COLOR_SAMPLE_H diff --git a/IrrExtensions/gui/GUIDropdownSelector.cpp b/IrrExtensions/gui/GUIDropdownSelector.cpp new file mode 100644 index 0000000..16bc4bc --- /dev/null +++ b/IrrExtensions/gui/GUIDropdownSelector.cpp @@ -0,0 +1,293 @@ +// (c) 2015 Nicolaus Anderson +// zlib license + +#ifndef GUI_DROPDOWN_SELECTOR_CPP +#define GUI_DROPDOWN_SELECTOR_CPP + +#include "GUIDropdownSelector.h" +#include +#include +#include +#include +#include + +namespace irr { +namespace gui { + +using video::SColor; + +GUIDropdownSelector::GUIDropdownSelector( IGUIEnvironment* pEnvironment, IGUIElement* pParent, rect pRect, s32 id ) + : IGUIElement( EGUIET_ELEMENT, pEnvironment, pParent, id, pRect ) + , dirty( true ) + , wasMenuFocus( false ) + , iconRect() +{ + button = pEnvironment->addButton( rect(pRect.getSize()), this, -1, L"", L"Click to select" ); + button->setAlignment(EGUIA_UPPERLEFT, EGUIA_LOWERRIGHT, EGUIA_UPPERLEFT, EGUIA_LOWERRIGHT); + button->setDrawBorder(true); + recalculateAbsolutePosition(false); + menu = pEnvironment->addContextMenu( + AbsoluteRect + core::vector2d(0, pRect.getHeight() ), + pEnvironment->getRootGUIElement() + ); + menu->grab(); + menu->setVisible(false); + menu->setCloseHandling( ECMC_HIDE ); + menu->setEventParent(this); + + s32 h = arrowHeight; + s32 hspace = (AbsoluteRect.getHeight() - h) / 2; + iconRect = rect( + AbsoluteRect.LowerRightCorner.X - (h + hspace), // upper left x + AbsoluteRect.UpperLeftCorner.Y + hspace, // upper left y + AbsoluteRect.LowerRightCorner.X - hspace, // lower right x + AbsoluteRect.LowerRightCorner.Y - hspace // lower right y + ); +} + +GUIDropdownSelector::~GUIDropdownSelector() +{ + menu->remove(); // Removes from environment + menu->drop(); +} + +void GUIDropdownSelector::setText( const wchar_t* pText ) +{ + dirty = true; + button->setText( pText ); +} + +void GUIDropdownSelector::setToolTipText(const wchar_t* text) +{ + button->setToolTipText(text); +} + +s32 GUIDropdownSelector::getSelected() const +{ + return menu->getSelectedItem(); +} + +const wchar_t* GUIDropdownSelector::getSelectedText() const +{ + s32 i = menu->getSelectedItem(); + if ( i != -1 && !dirty ) + { + return menu->getItemText( i ); + } + return L""; +} + +const wchar_t* GUIDropdownSelector::getItemText( u32 pIndex ) const +{ + return menu->getItemText( pIndex ); +} + +void GUIDropdownSelector::setItemText( u32 pIndex, const wchar_t* pText ) +{ + menu->setItemText( pIndex, pText ); +} + +void GUIDropdownSelector::setItemEnabled( u32 pIndex, bool pEnable ) +{ + menu->setItemEnabled( pIndex, pEnable ); +} + +bool GUIDropdownSelector::isItemEnabled( u32 pIndex ) const +{ + return menu->isItemEnabled( pIndex ); +} + +u32 GUIDropdownSelector::getItemCount() const { + return menu->getItemCount(); +} + +void GUIDropdownSelector::removeItem( u32 pIndex ) +{ + menu->removeItem( pIndex ); +} + +void GUIDropdownSelector::removeAllItems() +{ + menu->removeAllItems(); + button->setText(L""); +} + +void GUIDropdownSelector::addItem( const wchar_t* pText, bool pEnabled ) +{ + menu->addItem( pText, -1, pEnabled, false, false, false ); + + if ( menu->getItemCount() == 1 ) // first item + { + button->setText( pText ); + } +} + +void GUIDropdownSelector::updateAbsolutePosition() +{ + IGUIElement::updateAbsolutePosition(); + + rect menuRect = menu->getRelativePosition(); + s32 mW = menuRect.getWidth(); + s32 mH = menuRect.getHeight(); + + /* Recall, the menu is added to the root GUI element, so its relative position is relative + to the root, and consequently at the NEW absolute position of this element. */ + menu->setRelativePosition( + rect(0,0,mW,mH) + AbsoluteRect.UpperLeftCorner + core::vector2d( 0, AbsoluteRect.getHeight() ) + ); + + s32 h = arrowHeight; + s32 hspace = (AbsoluteRect.getHeight() - h) / 2; + iconRect = rect( + AbsoluteRect.LowerRightCorner.X - (h + hspace), // upper left x + AbsoluteRect.UpperLeftCorner.Y + hspace, // upper left y + AbsoluteRect.LowerRightCorner.X - hspace, // lower right x + AbsoluteRect.LowerRightCorner.Y - hspace // lower right y + ); +} + +bool GUIDropdownSelector::OnEvent( const SEvent& event ) +{ + if ( ! isVisible() || ! isEnabled() ) + return false; + + if ( event.EventType != EET_GUI_EVENT ) + return false; + + switch ( event.GUIEvent.EventType ) + { + case EGET_ELEMENT_FOCUSED: + if ( event.GUIEvent.Caller == button ) + { + /* Irrlicht's CGUIEnvironment maintains the last GUI focus until the end of its setFocus() method. */ + wasMenuFocus = ( Environment->getFocus() == menu ); + } + break; + + case EGET_BUTTON_CLICKED: + if ( event.GUIEvent.Caller == button ) + { + /* Avoid opening the menu if it was already open. */ + if ( ! wasMenuFocus ) + { + //Parent->bringToFront(this); + menu->setVisible( true ); + Environment->getRootGUIElement()->bringToFront(menu); + Environment->setFocus(menu); + return true; + } + wasMenuFocus = false; + return true; + } + break; + + case EGET_MENU_ITEM_SELECTED: + if ( event.GUIEvent.Caller == menu ) + { + dirty = false; + button->setText( getSelectedText() ); + sendGUIEvent( EGET_COMBO_BOX_CHANGED ); + return true; + } + + default: break; + } + + return false; +} + +void GUIDropdownSelector::draw() +{ + if ( ! isVisible() ) + { + menu->setVisible(false); // Ensure an accidentally-opened menu is hidden + return; + } + + //IGUIElement::draw(); + button->draw(); + menu->draw(); + + // Drawing a down-arrow + u32 dropDownIcon = Environment->getSkin()->getIcon( EGDI_CURSOR_DOWN /*EGDI_DROP_DOWN*/ ); + //video::SColor iconColor = Environment->getSkin()->getColor( isEnabled() ? EGDC_WINDOW_SYMBOL : EGDC_GRAY_WINDOW_SYMBOL ); + + // if too small to draw + if ( iconRect.getWidth() < 0 || iconRect.getHeight() < 0 ) + return; + Environment->getSkin()->getSpriteBank()->draw2DSprite( dropDownIcon, iconRect, &AbsoluteClippingRect ); +} + +void GUIDropdownSelector::sendGUIEvent( EGUI_EVENT_TYPE pEventType, IGUIElement* pElement ) +{ + if ( ! Parent ) return; + + SEvent event; + event.EventType = EET_GUI_EVENT; + event.GUIEvent.Caller = this; + event.GUIEvent.Element = pElement; + event.GUIEvent.EventType = pEventType; + + Parent->OnEvent( event ); +} + +void GUIDropdownSelector::serializeAttributes( io::IAttributes* out, io::SAttributeReadWriteOptions* options ) const { + IGUIElement::serializeAttributes(out, options); + + out->addInt("Selected", getSelected()); + out->addString("SelectedText", getSelectedText()); + + out->addInt("ItemCount", menu->getItemCount()); + u32 i = 0; + for (; i < getItemCount(); ++i) { + core::stringc name("Item"); + name += i; + name += "Text"; + out->addString(name.c_str(), getItemText(i)); + core::stringc label("Item"); + label += i; + label += "Enabled"; + out->addBool(label.c_str(), isItemEnabled(i)); + } +} + +void GUIDropdownSelector::deserializeAttributes( io::IAttributes* in, io::SAttributeReadWriteOptions* options ) { + IGUIElement::deserializeAttributes(in, options); + + dirty = true; + + u32 selected = -1; + s32 itemCount = 0; + s32 i=0; + bool enabled; + + //selected = in->getAttributeAsInt("Selected", selected); + // UNAVAILABLE: IContextMenu doesn't allowing setting the Highlighted menu item. + + removeAllItems(); + + itemCount = in->getAttributeAsInt("ItemCount", itemCount); + + if ( itemCount > 0 ) { + for (; i < itemCount; ++i) { + core::stringc label("Item"); + label += i; + label += "Enabled"; + core::stringc name("Item"); + name += i; + name += "Text"; + enabled = in->getAttributeAsBool(label.c_str(), false); + addItem( in->getAttributeAsStringW(name.c_str()).c_str(), enabled ); + } + } + + updateAbsolutePosition(); + menu->setRelativePosition(AbsoluteRect + core::vector2d(0, RelativeRect.getHeight() )); + wasMenuFocus = ( Environment->getFocus() == menu ); + if ( wasMenuFocus ) + Environment->setFocus(0); +} + +}} + +#endif // #ifndef GUI_DROPDOWN_SELECTOR_CPP diff --git a/IrrExtensions/gui/GUIDropdownSelector.h b/IrrExtensions/gui/GUIDropdownSelector.h new file mode 100644 index 0000000..0ec1daf --- /dev/null +++ b/IrrExtensions/gui/GUIDropdownSelector.h @@ -0,0 +1,71 @@ +// (c) 2015 Nicolaus Anderson +// zlib license + +#ifndef GUI_DROPDOWN_SELECTOR_H +#define GUI_DROPDOWN_SELECTOR_H + +#include + +namespace irr { +namespace gui { + +class IGUIButton; +class IGUIContextMenu; + +using core::rect; + +//! GUI Drop-down selector +/* +This is merely a controller for a button and a menu. +The menu is used to select what is displayed on the button. +*/ +class GUIDropdownSelector : public IGUIElement +{ + IGUIButton* button; + IGUIContextMenu* menu; + bool dirty; + bool wasMenuFocus; + rect iconRect; + static const s32 arrowHeight = 8; +public: + GUIDropdownSelector( IGUIEnvironment* pEnvironment, IGUIElement* pParent, rect pRect, s32 id=-1 ); + + ~GUIDropdownSelector(); + + /* Sets the current text (which may not be in the menu). + Note that this invalidates the current selection. */ + virtual void setText( const wchar_t* pText ); + virtual void setToolTipText(const wchar_t* text); + + /* Returns the index of the selected item. */ + s32 getSelected() const; + + /* Returns the text of the selected item. */ + const wchar_t* getSelectedText() const; + + const wchar_t* getItemText( u32 pIndex ) const; + void setItemText( u32 pIndex, const wchar_t* pText ); + void setItemEnabled( u32 pIndex, bool pEnable ); + bool isItemEnabled( u32 pIndex ) const; + u32 getItemCount() const; + + void removeItem( u32 pIndex ); + void removeAllItems(); + + void addItem( const wchar_t* pText, bool pEnabled=true ); + + virtual void updateAbsolutePosition() _IRR_OVERRIDE_; + virtual bool OnEvent( const SEvent& event ) _IRR_OVERRIDE_; + virtual void draw() _IRR_OVERRIDE_; + virtual const c8* getTypeName() const { return staticTypeName(); } + static const c8* staticTypeName() { return "dropdownSelector"; } + virtual void serializeAttributes( io::IAttributes* out, io::SAttributeReadWriteOptions* options ) const _IRR_OVERRIDE_; + virtual void deserializeAttributes( io::IAttributes* in, io::SAttributeReadWriteOptions* options ) _IRR_OVERRIDE_; + +protected: + void sendGUIEvent( EGUI_EVENT_TYPE pEventType, IGUIElement* pElement=0 ); +}; + +}} + +#endif // #ifndef GUI_DROPDOWN_SELECTOR_H diff --git a/IrrExtensions/gui/GUIDropdownSelectorFactory.h b/IrrExtensions/gui/GUIDropdownSelectorFactory.h new file mode 100644 index 0000000..dbd9201 --- /dev/null +++ b/IrrExtensions/gui/GUIDropdownSelectorFactory.h @@ -0,0 +1,79 @@ +// Copyright 2018 Nicolaus Anderson + +#ifndef GUI_DROPDOWN_SELECTOR_FACTORY_H +#define GUI_DROPDOWN_SELECTOR_FACTORY_H + +#include "GUIDropdownSelector.h" +#include + +namespace irr { +namespace gui { + +class IGUIEnvironment; + + +class GUIDropdownSelectorFactory : public IGUIElementFactory { + + IGUIEnvironment* Environment; + const core::stringc Label; + +public: + GUIDropdownSelectorFactory( IGUIEnvironment* environment ) + : Environment(environment) + , Label("dropdown") + {} + + IGUIElement* addGUIElement(EGUI_ELEMENT_TYPE type, IGUIElement* parent) { + if ( type != EGUIET_ELEMENT ) + return 0; + + IGUIElement* e = new GUIDropdownSelector( + Environment, + parent? parent : Environment->getRootGUIElement(), + core::recti(0,0,100,100), -1); + e->drop(); + return e; + } + + IGUIElement* addGUIElement(const c8* typeName, IGUIElement* parent) { + if ( Label != typeName ) + return 0; + + IGUIElement* e = new GUIDropdownSelector( + Environment, + parent? parent : Environment->getRootGUIElement(), + core::recti(0,0,100,100), -1); + e->drop(); + return e; + } + + s32 getCreatableGUIElementTypeCount() const { + return 1; + } + + EGUI_ELEMENT_TYPE getCreateableGUIElementType(s32 idx) const { + return EGUIET_ELEMENT; + } + + const c8* getCreateableGUIElementTypeName(s32 idx) const { + if ( idx == 0 ) + return Label.c_str(); + + return 0; + } + + const c8* getCreateableGUIElementTypeName(EGUI_ELEMENT_TYPE type) const { + if ( type == EGUIET_ELEMENT ) + return Label.c_str(); + + return 0; + } + + EGUI_ELEMENT_TYPE getTypeFromName(const c8* name) const { + return EGUIET_ELEMENT; + } +}; + +}} + +#endif diff --git a/IrrExtensions/gui/GUIDualSection.cpp b/IrrExtensions/gui/GUIDualSection.cpp new file mode 100644 index 0000000..0e5f82b --- /dev/null +++ b/IrrExtensions/gui/GUIDualSection.cpp @@ -0,0 +1,291 @@ +// (c) 2015 Nicolaus Anderson + +#ifndef GUI_DUAL_SECTION_CPP +#define GUI_DUAL_SECTION_CPP + +#include "GUIDualSection.h" +#include +#include +#include + +namespace irr { +namespace gui { + +using core::recti; + +GUIDualSection::GUIDualSection( bool pVertical, f32 pShift, IGUIEnvironment* pEnvironment, IGUIElement* pParent, recti pRect, s32 id ) + : IGUIElement( EGUIET_ELEMENT, pEnvironment, pParent, id, pRect ) + , sectionTopLeft(0) + , sectionBottomRight(0) + , vertical( pVertical ) + , dragBarSize(10) + , currShift(pShift) // Normal is 0.5f + , dragBarRect() + , lastMousePos() + , currMousePos() + , dragging(false) +{ + sectionTopLeft = new IGUIElement( EGUIET_ELEMENT, Environment, this, -1, pRect ); + sectionBottomRight = new IGUIElement( EGUIET_ELEMENT, Environment, this, -1, pRect ); + sectionTopLeft->setSubElement(true); + sectionBottomRight->setSubElement(true); + + // set left, right, top, bottom alignments + setVerticalNoUpdateAbs( vertical ); + + cramSections(); +} + +GUIDualSection::~GUIDualSection() +{ + sectionTopLeft->drop(); + sectionBottomRight->drop(); +} + +IGUIElement* GUIDualSection::getSectionTopLeft() +{ + return sectionTopLeft; +} + +IGUIElement* GUIDualSection::getSectionBottomRight() +{ + return sectionBottomRight; +} + +void GUIDualSection::addChild( IGUIElement* child) +{ + if ( ! sectionTopLeft && ! sectionBottomRight ) + IGUIElement::addChild(child); +} + +void GUIDualSection::removeChild( IGUIElement* child ) +{ + if ( ! sectionTopLeft && ! sectionBottomRight ) + IGUIElement::removeChild(child); +} + +void GUIDualSection::updateAbsolutePosition() +{ + IGUIElement::updateAbsolutePosition(); + updateDragBar(); +} + +bool GUIDualSection::OnEvent( const SEvent& event ) +{ + if ( !isVisible() || !isEnabled() ) + return false; + + s32 topLeftGap, + bottomRightGap, + deltaPos; + + switch( event.EventType ) + { + case EET_MOUSE_INPUT_EVENT: + // Don't forget to account for events where the sections reach or are at 0 in width/height + // I'd also like to show arrows when the mouse is over the drag bar + currMousePos.set(event.MouseInput.X, event.MouseInput.Y); + + switch( event.MouseInput.Event ) + { + case EMIE_LMOUSE_PRESSED_DOWN: + if ( dragBarRect.isPointInside( currMousePos ) ) + { + lastMousePos = currMousePos; + dragging = true; + return true; + } + break; + + case EMIE_LMOUSE_LEFT_UP: + if ( dragging ) + { + recti topLeftRect = sectionTopLeft->getRelativePosition(); + recti bottomRightRect = sectionBottomRight->getRelativePosition(); + + if ( vertical ) + { + topLeftGap = topLeftRect.getHeight(); + bottomRightGap = bottomRightRect.getHeight(); + deltaPos = currMousePos.Y - lastMousePos.Y; + //deltaPos = core::clamp(deltaPos, -topLeftGap, bottomRightGap); + topLeftRect.LowerRightCorner.Y += deltaPos; + bottomRightRect.UpperLeftCorner.Y += deltaPos; + currShift += (f32)deltaPos / (f32)RelativeRect.getHeight(); + } else { + topLeftGap = topLeftRect.getWidth(); + bottomRightGap = bottomRightRect.getWidth(); + deltaPos = currMousePos.X - lastMousePos.X; + //deltaPos = core::clamp(deltaPos, -topLeftGap, bottomRightGap); + topLeftRect.LowerRightCorner.X += deltaPos; + bottomRightRect.UpperLeftCorner.X += deltaPos; + currShift += (f32)deltaPos / (f32)RelativeRect.getWidth(); + } + //if ( deltaPos != 0 ) + { + sectionTopLeft->setRelativePosition( topLeftRect ); + sectionTopLeft->updateAbsolutePosition(); + sectionBottomRight->setRelativePosition( bottomRightRect ); + sectionBottomRight->updateAbsolutePosition(); + updateDragBar(); + } + dragging = false; + return true; + } + break; + + default: break; + } + break; + + case EET_GUI_EVENT: + if ( event.GUIEvent.EventType == EGET_ELEMENT_FOCUS_LOST + && event.GUIEvent.Caller == this ) + { + dragging = false; + } + break; + + default: break; + } + + return IGUIElement::OnEvent(event); +} + +void GUIDualSection::draw() +{ + IGUIElement::draw(); + Environment->getSkin()->draw3DButtonPaneStandard( this, dragBarRect, &AbsoluteClippingRect ); + + if ( !dragging ) return; + + // Draw drag bar potential position + recti dbpp; + s32 w = AbsoluteRect.getWidth(); + s32 h = AbsoluteRect.getHeight(); + s32 left; // top/left - near bound + s32 right; // bottom/right - far bound + s32 halfDragBarSize = dragBarSize / 2; + core::vector2di ulc = AbsoluteRect.UpperLeftCorner; + + if ( vertical ) + { + left = core::clamp( currMousePos.Y - halfDragBarSize, 0, h ); + right = core::clamp( currMousePos.Y + halfDragBarSize, 0, h ); + dbpp = recti( ulc.X, left, ulc.X + w, right ); + } else { + left = core::clamp( currMousePos.X - halfDragBarSize, 0, w ); + right = core::clamp( currMousePos.X + halfDragBarSize, 0, w ); + dbpp = recti( left, ulc.Y, right, ulc.Y + h ); + } + + Environment->getSkin()->draw2DRectangle( this, video::SColor(0x88888888), dbpp, &AbsoluteClippingRect ); +} + +void GUIDualSection::setVertical( bool yes ) { + setVerticalNoUpdateAbs(yes); + updateAbsolutePosition(); +} + +void GUIDualSection::setShiftPercentage( f32 pShift ) +{ + currShift = pShift; + cramSections(); +} + +void GUIDualSection::cramSections() +{ + s32 open = 0; // open/available area + s32 w = RelativeRect.getWidth(); + s32 h = RelativeRect.getHeight(); + + // Calculate sizes of the sections + if ( vertical ) + { + open = h - dragBarSize; + } else { + open = w - dragBarSize; + } + f32 topLeftPerc = currShift * (f32)open; + f32 bottomRightPerc = (f32)open - topLeftPerc; + + recti topLeftRect; // GUI area devoted to the top/left section + recti bottomRightRect; // GUI area devoted to the bottom/right section + + if ( vertical ) + { + topLeftRect = recti(0, 0, w, s32(h*topLeftPerc) ); + bottomRightRect = recti(0, s32(h*bottomRightPerc + dragBarSize), w, h ); + + dragBarRect = recti( 0, topLeftRect.LowerRightCorner.Y, w, bottomRightRect.UpperLeftCorner.Y ); + } else { + topLeftRect = recti(0, 0, s32(w*topLeftPerc), h ); + bottomRightRect = recti(s32(w*bottomRightPerc + dragBarSize), w, 0, h ); + + dragBarRect = recti( topLeftRect.LowerRightCorner.X, 0, bottomRightRect.UpperLeftCorner.X, h ); + } + sectionTopLeft->setRelativePosition( topLeftRect ); + sectionBottomRight->setRelativePosition( bottomRightRect ); + sectionTopLeft->updateAbsolutePosition(); + sectionBottomRight->updateAbsolutePosition(); + + // Put drag rectangle in absolute coordinate space + dragBarRect += AbsoluteRect.UpperLeftCorner; +} + +void GUIDualSection::updateDragBar() +{ + s32 halfDragBarSize = dragBarSize / 2; + s32 pos, w, h; + w = RelativeRect.getWidth(); + h = RelativeRect.getHeight(); + + if ( vertical ) + { + pos = (s32)((f32)h * currShift); + dragBarRect = recti( 0, pos - halfDragBarSize, w, pos + halfDragBarSize ); + } else { + pos = (s32)((f32)w * currShift); + dragBarRect = recti( pos - halfDragBarSize, 0, pos + halfDragBarSize, h ); + } + dragBarRect += AbsoluteRect.UpperLeftCorner; +} + +void +GUIDualSection::setVerticalNoUpdateAbs( bool yes ) +{ + vertical = yes; + // set left, right, top, bottom alignments + if ( vertical ) // one section above the other + { + sectionTopLeft->setAlignment( EGUIA_UPPERLEFT, EGUIA_LOWERRIGHT, EGUIA_UPPERLEFT, EGUIA_UPPERLEFT ); + sectionBottomRight->setAlignment( EGUIA_UPPERLEFT, EGUIA_LOWERRIGHT, EGUIA_UPPERLEFT, EGUIA_LOWERRIGHT ); + } else { // sections side by side + sectionTopLeft->setAlignment( EGUIA_UPPERLEFT, EGUIA_UPPERLEFT, EGUIA_UPPERLEFT, EGUIA_LOWERRIGHT ); + sectionBottomRight->setAlignment( EGUIA_UPPERLEFT, EGUIA_LOWERRIGHT, EGUIA_UPPERLEFT, EGUIA_LOWERRIGHT ); + } +} + +void +GUIDualSection::serializeAttributes(io::IAttributes* out, io::SAttributeReadWriteOptions* options) const +{ + IGUIElement::serializeAttributes(out, options); + + out->addBool( "IsVertical", vertical ); + out->addInt( "DragBarSize", dragBarSize ); + out->addFloat( "Shift", currShift ); +} + +void +GUIDualSection::deserializeAttributes(io::IAttributes* in, io::SAttributeReadWriteOptions* options) +{ + IGUIElement::deserializeAttributes(in, options); + + setVertical( in->getAttributeAsBool( "IsVertical", vertical ) ); + dragBarSize = in->getAttributeAsInt( "DragBarSize", dragBarSize ); + setShiftPercentage( in->getAttributeAsFloat( "Shift", currShift ) ); +} + +}} + +#endif // #ifndef GUI_DUAL_SECTION_CPP diff --git a/IrrExtensions/gui/GUIDualSection.h b/IrrExtensions/gui/GUIDualSection.h new file mode 100644 index 0000000..f90bf3a --- /dev/null +++ b/IrrExtensions/gui/GUIDualSection.h @@ -0,0 +1,74 @@ +// (c) 2015 Nicolaus Anderson + +#ifndef GUI_DUAL_SECTION_H +#define GUI_DUAL_SECTION_H + +#include + +namespace irr { +namespace gui { + +//! Class Dual Section +/* Creates and controls two child GUI elements side by side and forced to share the same space. +It also creates a draggable bar that allows the user to resize these elements. +Add children to these child GUI elements via getSectionTopLeft() and getSectionBottomRight(). +Other children can be added to this element which are not controlled. Use the normal addChild method. */ +class GUIDualSection : public IGUIElement +{ + IGUIElement* sectionTopLeft; + IGUIElement* sectionBottomRight; + bool vertical; + s32 dragBarSize; // Size of the drag bar (currently, cannot be changed) + f32 currShift; // Current % each section shares of the space in the parent GUI element + core::rect dragBarRect; + core::vector2d lastMousePos; + core::vector2d currMousePos; + bool dragging; + +public: + //! Constructor + /* \param pVertical - Indicates if the sections should be split vertically, one on top and + one on the bottom. Otherwise, they will be split horizontally. + \param pShift - The percentage of the available GUI space occupied by the top/left section. + */ + GUIDualSection( bool pVertical, f32 pShift, IGUIEnvironment* pEnvironment, IGUIElement* pParent, core::rect pRect, s32 id=-1 ); + + ~GUIDualSection(); + + // Grab these to add children to the controlled elements + IGUIElement* getSectionTopLeft(); + IGUIElement* getSectionBottomRight(); + + // Prevent re-adding controlled children + virtual void addChild( IGUIElement* child); + + // Prevent removal of controlled children + virtual void removeChild( IGUIElement* child ); + + virtual void updateAbsolutePosition(); + + virtual bool OnEvent( const SEvent& event ); + + virtual void draw(); + + void setVertical( bool yes ); + + void setShiftPercentage( f32 pShift ); + + virtual const c8* getTypeName() const { return staticTypeName(); } + static const c8* staticTypeName() { return "dualSection"; } + + virtual void serializeAttributes(io::IAttributes*, io::SAttributeReadWriteOptions*) const; + virtual void deserializeAttributes(io::IAttributes* in, io::SAttributeReadWriteOptions* options=0); + +protected: + // Fits the elements into the window + // Cannot be in updateAbsolutePosition() because it is needed in the constructor + void cramSections(); // only used for strict alignment + void updateDragBar(); + void setVerticalNoUpdateAbs( bool ); +}; + +}} + +#endif // #ifndef GUI_DUAL_SECTION_H diff --git a/IrrExtensions/gui/GUIFileSelectPanel.cpp b/IrrExtensions/gui/GUIFileSelectPanel.cpp new file mode 100644 index 0000000..ed09dff --- /dev/null +++ b/IrrExtensions/gui/GUIFileSelectPanel.cpp @@ -0,0 +1,432 @@ +// (c) 2015-2019 Nicolaus Anderson + +#ifndef GUI_FILE_SELECT_PANEL_CPP +#define GUI_MATERIAL_PANEL_CPP + +#include "GUIFileSelectPanel.h" +#include +#include +#include +#include +#include +//#include // needed for locale +#include +#include +#include + + +namespace irr { +namespace gui { + +using core::string; +using core::vector2di; +using core::dimension2di; + +GUIFileSelectPanel::GUIFileSelectPanel( + IGUIEnvironment* pEnvironment, + IGUIElement* pParent, + recti pRect, + io::path pStartDirectory, + s32 id ) + : IGUIElement( EGUIET_ELEMENT, pEnvironment, pParent, id, pRect ) + , fileSystem(0) + , fileList(0) + , filesIndex() + , selectButton(0) + , cancelButton(0) + , fileListBox(0) + , fileNameEditBox(0) + , isFileSelectedFromList(false) + , lastFileSelectPanelEvent(EGFSPE_NONE) + , notifyWhenEditBoxChanges(false) + , restoreDirWhenDone(true) + , restoreDirWhenCancelled(true) + , initialWorkingDir() + , currentWorkingDir() + , drawBack(true) +{ + fileSystem = Environment->getFileSystem(); + fileSystem->grab(); + + recti selectButtonRect( vector2di(pRect.getWidth()-130, pRect.getHeight()-25), dimension2di(60,20) ); + selectButton = Environment->addButton( selectButtonRect, this, -1, L"Select", L"Confirm file name" ); + selectButton->setAlignment( EGUIA_LOWERRIGHT, EGUIA_LOWERRIGHT, EGUIA_LOWERRIGHT, EGUIA_LOWERRIGHT ); + selectButton->setSubElement(true); + selectButton->grab(); + + recti cancelButtonRect( vector2di(pRect.getWidth()-65, pRect.getHeight()-25), dimension2di(60,20) ); + cancelButton = Environment->addButton( cancelButtonRect, this, -1, L"Cancel" ); + cancelButton->setAlignment( EGUIA_LOWERRIGHT, EGUIA_LOWERRIGHT, EGUIA_LOWERRIGHT, EGUIA_LOWERRIGHT ); + cancelButton->setSubElement(true); + cancelButton->grab(); + + recti fileListRect( 5, 5, pRect.getWidth()-5, pRect.getHeight()-55 ); + fileListBox = Environment->addListBox( fileListRect, this, -1, true ); + fileListBox->setAlignment( EGUIA_UPPERLEFT, EGUIA_LOWERRIGHT, EGUIA_UPPERLEFT, EGUIA_LOWERRIGHT ); + fileListBox->setSubElement(true); + fileListBox->grab(); + + recti fileNameEditRect( 5, pRect.getHeight()-50, pRect.getWidth()-5, pRect.getHeight()-30 ); + fileNameEditBox = Environment->addEditBox( L"", fileNameEditRect, true, this, -1 ); + fileNameEditBox->setAlignment( EGUIA_UPPERLEFT, EGUIA_LOWERRIGHT, EGUIA_LOWERRIGHT, EGUIA_LOWERRIGHT ); + fileNameEditBox->setSubElement(true); + fileNameEditBox->grab(); + + initialWorkingDir = fileSystem->getAbsolutePath( fileSystem->getWorkingDirectory() ); + if ( pStartDirectory.size() ) + { + fileSystem->changeWorkingDirectoryTo( pStartDirectory ); + currentWorkingDir = pStartDirectory; + } else { + currentWorkingDir = initialWorkingDir; + } + fillFileList(); +} + +GUIFileSelectPanel::~GUIFileSelectPanel() +{ + //if ( restoreDirWhenDone ) + // fileSystem->changeWorkingDirectoryTo( initialWorkingDir ); + + if ( selectButton ) + selectButton->drop(); + if ( cancelButton ) + cancelButton->drop(); + if ( fileListBox ) + fileListBox->drop(); + if ( fileNameEditBox ) + fileNameEditBox->drop(); + + fileSystem->drop(); +} + +IGUIButton* GUIFileSelectPanel::getSelectButton() +{ + return selectButton; +} + +IGUIButton* GUIFileSelectPanel::getCancelButton() +{ + return cancelButton; +} + +bool GUIFileSelectPanel::OnEvent( const SEvent& event ) +{ + if ( event.EventType != EET_GUI_EVENT ) + return false; + + /* + How this should work: + Typing in an edit-box filters the list to show only matches unless + the editbox value is "", in which case, everything is shown. + The file selected is set to the file name. + + isSelectedFileReal() indicates if the selected file is taken from the list. + */ + + /* + DO NOT RESTORE THE DIRECTORY AFTER SELECTION. + It doesn't make sense to restore the directory before the file path has been obtained by the user. + EDIT: + I can change this if I use getFullFileName() from the fileList, which saves the directory. + */ + + switch ( event.GUIEvent.EventType ) + { + case EGET_BUTTON_CLICKED: + if ( event.GUIEvent.Caller == selectButton ) + { + lastFileSelectPanelEvent = EGFSPE_FILE_CONFIRMED; + sendGUIEvent( EGET_FILE_SELECTED ); + if ( restoreDirWhenDone ) + fileSystem->changeWorkingDirectoryTo(initialWorkingDir); + return true; + } + else if ( event.GUIEvent.Caller == cancelButton ) + { + lastFileSelectPanelEvent = EGFSPE_CANCEL; + sendGUIEvent( EGET_FILE_CHOOSE_DIALOG_CANCELLED ); + if ( restoreDirWhenCancelled ) + fileSystem->changeWorkingDirectoryTo(initialWorkingDir); + return true; + } + break; + + case EGET_EDITBOX_CHANGED: + if ( event.GUIEvent.Caller == fileNameEditBox ) + { + isFileSelectedFromList = false; + fillFileList(); + if ( notifyWhenEditBoxChanges ) + sendGUIEvent( EGET_EDITBOX_CHANGED, fileNameEditBox ); + return true; + } + break; + + case EGET_EDITBOX_ENTER: // another means of selection + if ( event.GUIEvent.Caller == fileNameEditBox ) + { + // confirm file selection + lastFileSelectPanelEvent = EGFSPE_FILE_CONFIRMED; + //sendGUIEvent( EGET_EDITBOX_ENTER, fileNameEditBox ); // Doesn't allow file_selected filtering in parent OnEvent + sendGUIEvent( EGET_FILE_SELECTED ); + if ( restoreDirWhenDone ) + fileSystem->changeWorkingDirectoryTo(initialWorkingDir); + return true; + } + break; + + case EGET_LISTBOX_CHANGED: + if ( event.GUIEvent.Caller == fileListBox ) + { + isFileSelectedFromList = true; + lastFileSelectPanelEvent = EGFSPE_REAL_FILE_SELECTED; + //sendGUIEvent( EGET_LISTBOX_CHANGED, fileListBox ); // Doesn't allow file_selected filtering in parent OnEvent + sendGUIEvent( EGET_FILE_SELECTED ); + return true; + } + break; + + case EGET_LISTBOX_SELECTED_AGAIN: + if ( event.GUIEvent.Caller == fileListBox ) + { + // confirm file selection + lastFileSelectPanelEvent = EGFSPE_FILE_CONFIRMED; + if ( isSelectedFileReal() ) + { + //sendGUIEvent( EGET_LISTBOX_SELECTED_AGAIN, fileListBox ); // Doesn't allow file_selected filtering in parent OnEvent + sendGUIEvent( EGET_FILE_SELECTED ); + if ( restoreDirWhenDone ) + fileSystem->changeWorkingDirectoryTo(initialWorkingDir); + return true; + } else { + // Selected is real (since it's in the list), but it is a directory + openSelectedDirectory(); + } + } + break; + + default: break; + } + + return false; +} + +EGUIFileSelectPanelEvent GUIFileSelectPanel::getLastEvent() +{ + return lastFileSelectPanelEvent; +} + +void GUIFileSelectPanel::sendGUIEvent( EGUI_EVENT_TYPE pEventType, IGUIElement* pElement ) +{ + SEvent event; + event.EventType = EET_GUI_EVENT; + event.GUIEvent.Caller = this; + event.GUIEvent.Element = pElement; + event.GUIEvent.EventType = pEventType; + + if ( Parent ) + Parent->OnEvent(event); +} + +void GUIFileSelectPanel::setNotifyWhenEditBoxChanges(bool yes) +{ + notifyWhenEditBoxChanges = yes; +} + +io::path GUIFileSelectPanel::getCurrentWorkingDirectory() +{ + fileSystem->flattenFilename(currentWorkingDir); + return currentWorkingDir; +} + +bool GUIFileSelectPanel::isSelectedReal() +{ + return isFileSelectedFromList; +} + +bool GUIFileSelectPanel::isSelectedFileReal() +{ + // Must be selected from list and not be a directory + if ( isFileSelectedFromList ) + { + for ( u32 i=0; i < fileList->getFileCount(); i++ ) + { + if ( fileList->getFileName(i) == fileListBox->getListItem( fileListBox->getSelected() ) ) + { + return ! ( fileList->isDirectory(i) ); + } + } + } + return false; +} + +io::path GUIFileSelectPanel::getSelectedFile() +{ + if ( isFileSelectedFromList ) + { + return fileList->getFileName( getSelectedFileIndex() ); + } + io::path filename = fileNameEditBox->getText(); + fileSystem->flattenFilename( filename ); + return filename; +} + +io::path GUIFileSelectPanel::getSelectedFilePath() +{ + if ( isFileSelectedFromList ) + { + return fileList->getFullFileName( getSelectedFileIndex() ); + } + + return fileSystem->getAbsolutePath( getSelectedFile() ); +} + +io::path GUIFileSelectPanel::getSelectedFileRelativePath() +{ + return fileSystem->getRelativeFilename( getSelectedFilePath(), initialWorkingDir ); +} + +//! Restore the working directory to the initial directory when a file is selected +void GUIFileSelectPanel::setRestoreDirectoryWhenDone(bool yes) +{ + restoreDirWhenDone = yes; +} + +//! Restore the working directory to the initial directory when cancelling +void GUIFileSelectPanel::setRestoreDirectoryWhenCancelled(bool yes) +{ + restoreDirWhenCancelled = yes; +} + +void GUIFileSelectPanel::reactivate() +{ + fileSystem->changeWorkingDirectoryTo( currentWorkingDir ); +} + +void GUIFileSelectPanel::deactivate() +{ + fileSystem->changeWorkingDirectoryTo( initialWorkingDir ); +} + +s32 GUIFileSelectPanel::getSelectedFileIndex() +{ + return filesIndex[ fileListBox->getSelected() ]; +} + +void GUIFileSelectPanel::openSelectedDirectory() +{ + if ( ! fileList ) + return; + + //const io::path entry = fileList->getFullFileName( getSelectedFileIndex() ); + const io::path entry = fileList->getFileName( getSelectedFileIndex() ); + + if ( entry.size() > 0 ) + { + fileSystem->changeWorkingDirectoryTo( entry ); + currentWorkingDir = fileSystem->getWorkingDirectory(); + fillFileList(); + } +} + +void GUIFileSelectPanel::pathToStringW(irr::core::stringw& result, const irr::io::path& p) +{ + // Taken from Irrlicht trunk 5823, probably added by CuteAlien. +#ifndef _IRR_WCHAR_FILESYSTEM + char* oldLocale = setlocale(LC_CTYPE, NULL); + setlocale(LC_CTYPE,""); // multibyteToWString is affected by LC_CTYPE. Filenames seem to need the system-locale. + core::multibyteToWString(result, p); + setlocale(LC_CTYPE, oldLocale); +#else + result = p.c_str(); +#endif +} + +void GUIFileSelectPanel::fillFileList() +{ +#if !defined(_IRR_WINDOWS_CE_PLATFORM_) + setlocale(LC_ALL,""); +#endif + + if ( fileList ) { + fileList->drop(); + fileList = 0; + } + + fileList = fileSystem->createFileList(); + + if ( !fileList ) // FIXME: Should throw, but this should also never happen + return; + + filesIndex.clear(); + fileListBox->clear(); + s32 folderIcon = Environment->getSkin()->getIcon(EGDI_DIRECTORY); + s32 fileIcon = Environment->getSkin()->getIcon(EGDI_FILE); + stringw filterText(fileNameEditBox->getText()); + stringw fileNameTemp; + + for ( u32 i=0; i < fileList->getFileCount(); i++ ) + { + pathToStringW(fileNameTemp, fileList->getFileName(i)); + + if ( filterText.size() == 0 + || ( filterText.size() > 0 && fileNameTemp.subString(0,filterText.size()) == filterText ) + ) + { + fileListBox->addItem( + fileNameTemp.c_str(), + (fileList->isDirectory(i)?folderIcon:0) + ); + filesIndex.push_back(i); + } + } +} + +void GUIFileSelectPanel::draw() +{ + if ( drawBack ) + { + Environment->getSkin()->draw3DSunkenPane(this, + Environment->getSkin()->getColor(EGDC_3D_FACE), true, true, + AbsoluteRect, &AbsoluteClippingRect ); + } + + IGUIElement::draw(); +} + +void +GUIFileSelectPanel::serializeAttributes( + irr::io::IAttributes* out, + irr::io::SAttributeReadWriteOptions* options +){ + out->addBool("RestoreStartDirectoryWhenDone", restoreDirWhenDone); + out->addBool("RestoreStartDirectoryWhenCancelled", restoreDirWhenCancelled); +#ifndef _IRR_WCHAR_FILESYSTEM + out->addString("InitialWorkingDirectory", initialWorkingDir.c_str()); +#else + out->addStringW("InitialWorkingDirectory", initialWorkingDir.c_str()); +#endif + out->addBool("DrawBack", drawBack); +} + +void +GUIFileSelectPanel::deserializeAttributes( + irr::io::IAttributes* in, + irr::io::SAttributeReadWriteOptions* options +){ + restoreDirWhenDone = in->getAttributeAsBool("RestoreStartDirectoryWhenDone", restoreDirWhenDone); + restoreDirWhenCancelled = in->getAttributeAsBool("RestoreStartDirectoryWhenCancelled", restoreDirWhenCancelled); + if ( in->existsAttribute("InitialWorkingDirectory") ) { +#ifndef _IRR_WCHAR_FILESYSTEM + initialWorkingDir = in->getAttributeAsString("InitialWorkingDirectory", initialWorkingDir.c_str()); +#else + initialWorkingDir = in->getAttributeAsStringW("InitialWorkingDirectory", initialWorkingDir.c_str()); +#endif + } + drawBack = in->getAttributeAsBool("DrawBack", drawBack); +} + +}} + +#endif diff --git a/IrrExtensions/gui/GUIFileSelectPanel.h b/IrrExtensions/gui/GUIFileSelectPanel.h new file mode 100644 index 0000000..3860c1c --- /dev/null +++ b/IrrExtensions/gui/GUIFileSelectPanel.h @@ -0,0 +1,196 @@ +// (c) 2015-2019 Nicolaus Anderson + +#ifndef GUI_FILE_SELECT_PANEL_H +#define GUI_FILE_SELECT_PANEL_H + +#include + +namespace irr { + +namespace io { + class IFileSystem; + class IFileList; +} + +namespace gui { + +using core::stringw; +using core::recti; +using core::array; +using io::IFileSystem; +using io::IFileList; + +class IGUIButton; +class IGUIListBox; +class IGUIEditBox; + +enum EGUIFileSelectPanelEvent +{ + /* No action or undefined action */ + EGFSPE_NONE=0, + + /* File selected. + This does not mean a file has been confirmed for selection. + It only means that a file has been clicked on in the list of files. + It DOES refer to EXISTING files. */ + EGFSPE_REAL_FILE_SELECTED, + + /* File confirmed. + This does not mean the file exists. + It means that the "Select" button has been clicked, or the enter + button has been pressed, or that the user has double-clicked on a file. + When no real file has been selected, isSelectedReal() and isSelectedFileReal() + should return false but getSelectedFile() should return a filename. + Furthermore, getSelectedFilePath() will return getSelectedFile() with + the current directory appended to the front. */ + EGFSPE_FILE_CONFIRMED, + + /* Action canceled. + This is primarily intended for windows, where the cancel action indicates + the window should close. + It indicates that the "Cancel" button has been pressed. */ + EGFSPE_CANCEL, + + /* The number of events */ + EGFSPE_COUNT +}; + +//! GUI File Select Panel +/* +This is a panel that can be embedded, allowing it to be used in windows or +as the opening screen of a program. It is used for file and directory selection, +but the user can also enter in a file name. To prevent selecting non-existent +files, the existence of the selected file can be checked by isSelectedFileReal(). +To check for real selection (regardless of it is a directory or not), use +isSelectedReal(). These two functions can be used in tandem if one wishes to +allow for directory selection and not file selection via +(isSelectedReal() && !isSelectedFileReal()) +*/ +class GUIFileSelectPanel : public IGUIElement +{ +protected: + IFileSystem* fileSystem; + IFileList* fileList; + array filesIndex; + IGUIButton* selectButton; + IGUIButton* cancelButton; + IGUIListBox* fileListBox; + IGUIEditBox* fileNameEditBox; + bool isFileSelectedFromList; + EGUIFileSelectPanelEvent lastFileSelectPanelEvent; + bool notifyWhenEditBoxChanges; + bool restoreDirWhenDone; + bool restoreDirWhenCancelled; + + io::path initialWorkingDir; + io::path currentWorkingDir; + + bool drawBack; + +public: + GUIFileSelectPanel( + IGUIEnvironment* pEnvironment, + IGUIElement* pParent, + recti pRect, + io::path pStartDirectory, + s32 id=-1 + ); + + ~GUIFileSelectPanel(); + + //! Get panel button + IGUIButton* getSelectButton(); + IGUIButton* getCancelButton(); + + //! On event + /* Handles events, such as from the GUI */ + virtual bool OnEvent( const SEvent& event ); + + //! Get panel event + /* Returns the last event from the panel. + Parent GUI elements should check this when receiving events from this element. */ + EGUIFileSelectPanelEvent getLastEvent(); + +protected: + void sendGUIEvent( EGUI_EVENT_TYPE pEventType, IGUIElement* pElement=0 ); + +public: + //! Notify when edit box changes + /* Sets whether this element should attempt to notify the parent + whenever the editbox changes. This can slow things down, but it allows for + the parent to perhaps update some file-related information being displayed. */ + void setNotifyWhenEditBoxChanges(bool yes); + + //! + io::path getCurrentWorkingDirectory(); + //! Is the selected file or folder real + bool isSelectedReal(); + //! Is the selected file a real file? + bool isSelectedFileReal(); + //! Selected file name + io::path getSelectedFile(); // See important note below + //! Absolute path of the selected file + io::path getSelectedFilePath(); + //! Relative path from initial directory + io::path getSelectedFileRelativePath(); + + //! Restore the working directory to the initial directory when a file is selected + void setRestoreDirectoryWhenDone(bool); + + //! Restore the working directory to the initial directory when cancelling + void setRestoreDirectoryWhenCancelled(bool); + + //! Activate + /* Restores this instance's current working directory. */ + void reactivate(); + + //! Done + /* This should be called when the element is done being used but won't be deleted. + It restores the initial directory. */ + void deactivate(); + + /* IMPORTANT NOTE: + If ever IGUIFileOpenDialog is to be inherited (unlikely), + existing function names should be available. + Hence, getFileName() and getDirectory() are not overridden here, + especially since they return const wchar_t and const io::path& + respectively. + */ + +protected: + //! Returns the saved file index of the visible file + s32 getSelectedFileIndex(); + + //! Open selected directory + /* Attempts to open the selected directory. */ + void openSelectedDirectory(); + + //! Fill file list + /* Fills the file list. */ + void fillFileList(); + + //! Path conversion + /* Converts a path from multibyte to widechar. */ + void pathToStringW(irr::core::stringw&, const irr::io::path&); + +public: + //! Draw + virtual void draw(); + + virtual const c8* getTypeName() const { return staticTypeName(); } + static const c8* staticTypeName() { return "fileSelectPanel"; } + + virtual void serializeAttributes( + irr::io::IAttributes* out, + irr::io::SAttributeReadWriteOptions* options=0 + ); + + virtual void deserializeAttributes( + irr::io::IAttributes* in, + irr::io::SAttributeReadWriteOptions* options=0 + ); +}; + +}} + +#endif diff --git a/IrrExtensions/gui/GUIGroupingPanel.cpp b/IrrExtensions/gui/GUIGroupingPanel.cpp new file mode 100644 index 0000000..07d55bf --- /dev/null +++ b/IrrExtensions/gui/GUIGroupingPanel.cpp @@ -0,0 +1,206 @@ +// (c) 2015 Nicolaus Anderson + +#ifndef GUI_GROUPING_PANEL_CPP +#define GUI_GROUPING_PANEL_CPP + +#include "GUIGroupingPanel.h" +#include +#include +#include +#include +#include + +namespace irr { +namespace gui { + +using video::IVideoDriver; +using video::SColor; +using core::vector2di; + +GUIGroupingPanel::GUIGroupingPanel( const wchar_t* pText, IGUIEnvironment* pEnvironment, IGUIElement* pParent, const recti& pRect, s32 id ) + : IGUIElement( EGUIET_ELEMENT, pEnvironment, pParent, id, pRect ) + , showBorder( true ) + , borderRadius( 6 ) +{ + textSize = pEnvironment->getSkin()->getFont()->getDimension( pText ); + Text = pText; + updateImageCache(); + setTabGroup(true); +} + +void GUIGroupingPanel::setText(const wchar_t* text) +{ + Text = text; + textSize = Environment->getSkin()->getFont()->getDimension( text ); +} + +void GUIGroupingPanel::setShowBorder( bool pShow ) +{ + showBorder = pShow; +} + +void GUIGroupingPanel::setBorderEdgeRadius( f32 pRadius ) +{ + borderRadius = pRadius; + updateImageCache(); +} + +recti GUIGroupingPanel::getClientArea() +{ + return recti( borderRadius, borderRadius + textSize.Height, RelativeRect.getWidth() - borderRadius, RelativeRect.getHeight() - borderRadius ); +} + +//void GUIGroupingPanel::updateAbsolutePosition() +//{ +//} + +void GUIGroupingPanel::draw() +{ + IVideoDriver* vid = Environment->getVideoDriver(); + IGUISkin* skin = Environment->getSkin(); + SColor color = skin->getColor( EGDC_3D_LIGHT ); + SColor shadowColor = skin->getColor( EGDC_3D_SHADOW ); + SColor textColor = skin->getColor( EGDC_BUTTON_TEXT ); + s32 textPad = ( textSize.Width > 0 ) ? 3 : 0; // Padding is only needed when there is text + + // Draw border corners + vector2di textPush(0, textSize.Height/2); + recti cornerSource( ULtexture->getSize() ); // Source rectangle for the images + vid->draw2DImage( ULtexture, AbsoluteRect.UpperLeftCorner + textPush, cornerSource, &AbsoluteClippingRect, SColor(-1), true ); + + vector2di corner2( AbsoluteRect.getWidth() - borderRadius, 0 ); + vid->draw2DImage( URtexture, corner2 + AbsoluteRect.UpperLeftCorner + textPush, cornerSource, &AbsoluteClippingRect, SColor(-1), true ); + + vector2di corner( 0, AbsoluteRect.getHeight() - borderRadius ); + vid->draw2DImage( LLtexture, corner + AbsoluteRect.UpperLeftCorner, cornerSource, &AbsoluteClippingRect, SColor(-1), true ); + + corner2 += vector2di( 0, AbsoluteRect.getHeight() - borderRadius ); // Use .move() in custom Irrlicht + vid->draw2DImage( LRtexture, corner2 + AbsoluteRect.UpperLeftCorner, cornerSource, &AbsoluteClippingRect, SColor(-1), true ); + + // Draw border walls + // Left side + vector2di vline( AbsoluteRect.UpperLeftCorner ); + vline.Y += borderRadius + (textSize.Height / 2); + vector2di vlineEnd( AbsoluteRect.UpperLeftCorner.X, AbsoluteRect.LowerRightCorner.Y - borderRadius ); + + vid->draw2DLine( vline, vlineEnd, color ); + vline.X += 1; + vlineEnd.X += 1; + vid->draw2DLine( vline, vlineEnd, shadowColor ); // Shadow + + // Top side + vector2di hline( AbsoluteRect.UpperLeftCorner ); + hline += vector2di( borderRadius + textPad*2 + textSize.Width, textSize.Height / 2 ); // Use .move() in custom Irrlicht + vector2di hlineEnd( AbsoluteRect.LowerRightCorner.X - borderRadius, AbsoluteRect.UpperLeftCorner.Y + (textSize.Height / 2) ); + + vid->draw2DLine( hline, hlineEnd, color ); + hline.Y += 1; + hlineEnd.Y += 1; + vid->draw2DLine( hline, hlineEnd, shadowColor ); // Shadow + + // Right side + vline.set( AbsoluteRect.LowerRightCorner.X, AbsoluteRect.UpperLeftCorner.Y + borderRadius + (textSize.Height / 2) ); + vlineEnd.set( AbsoluteRect.LowerRightCorner ); + vlineEnd.Y -= borderRadius; + + vid->draw2DLine( vline, vlineEnd, color ); + vline.X -= 1; + vlineEnd.X -= 1; + vid->draw2DLine( vline, vlineEnd, shadowColor ); // Shadow + + // Bottom side + hline.set( AbsoluteRect.UpperLeftCorner.X + borderRadius, AbsoluteRect.LowerRightCorner.Y ); + hlineEnd.set( AbsoluteRect.LowerRightCorner ); + hlineEnd.X -= borderRadius; + + vid->draw2DLine( hline, hlineEnd, color ); + hline.Y -= 1; + hlineEnd.Y -= 1; + vid->draw2DLine( hline, hlineEnd, shadowColor ); // Shadow + + // Draw label text + recti labelRect( borderRadius + 3, 0, borderRadius + textPad, textSize.Height ); + Environment->getSkin()->getFont()->draw( Text.c_str(), labelRect + AbsoluteRect.UpperLeftCorner, textColor, false, false, &AbsoluteClippingRect ); + + // Draw child elements + IGUIElement::draw(); +} + +void GUIGroupingPanel::updateImageCache() +{ + IVideoDriver* vid = Environment->getVideoDriver(); + SColor color = Environment->getSkin()->getColor( EGDC_3D_LIGHT ); + SColor shadowColor = Environment->getSkin()->getColor( EGDC_3D_SHADOW ); + + dimension2du imageSize( borderRadius, borderRadius ); + + // Produces cropped circles used as end-caps / corners + + ULtexture = vid->addRenderTargetTexture( imageSize, "GUI_GROUPING_PANEL_CORNER_UL" ); + if ( ULtexture ) + { + // Irrlicht 5104 + //vid->setRenderTarget( ULtexture ); + // Irrlicht 5589 + vid->setRenderTarget( ULtexture, u16(irr::video::ECBF_COLOR), SColor(0,0,0,0) ); + vid->draw2DPolygon( vector2di( borderRadius+1 ), borderRadius, shadowColor, 40 ); + vid->draw2DPolygon( vector2di( borderRadius, borderRadius ), borderRadius, color, 40 ); + } + + URtexture = vid->addRenderTargetTexture( imageSize, "GUI_GROUPING_PANEL_CORNER_UR" ); + if ( URtexture ) + { + // Irrlicht 5104 + //vid->setRenderTarget( URtexture ); + // Irrlicht 5589 + vid->setRenderTarget( URtexture, u16(irr::video::ECBF_COLOR), SColor(0,0,0,0) ); + vid->draw2DPolygon( vector2di( -1, borderRadius+1 ), borderRadius, shadowColor, 40 ); + vid->draw2DPolygon( vector2di( 0, borderRadius ), borderRadius, color, 40 ); + } + + LLtexture = vid->addRenderTargetTexture( imageSize, "GUI_GROUPING_PANEL_CORNER_LL" ); + if ( LLtexture ) + { + // Irrlicht 5104 + //vid->setRenderTarget( LLtexture ); + // Irrlicht 5589 + vid->setRenderTarget( LLtexture, u16(irr::video::ECBF_COLOR), SColor(0,0,0,0) ); + vid->draw2DPolygon( vector2di( borderRadius+1, -1 ), borderRadius, shadowColor, 40 ); + vid->draw2DPolygon( vector2di( borderRadius, 0 ), borderRadius, color, 40 ); + } + + LRtexture = vid->addRenderTargetTexture( imageSize, "GUI_GROUPING_PANEL_CORNER_LR" ); + if ( LRtexture ) + { + // Irrlicht 5104 + //vid->setRenderTarget( LRtexture ); + // Irrlicht 5589 + vid->setRenderTarget( LRtexture, u16(irr::video::ECBF_COLOR), SColor(0,0,0,0) ); + vid->draw2DPolygon( vector2di( -1 ), borderRadius, shadowColor, 40 ); + vid->draw2DPolygon( vector2di( 0 ), borderRadius, color, 40 ); + } + + vid->setRenderTarget(0); // Should restore old target +} + +void +GUIGroupingPanel::serializeAttributes(io::IAttributes* out, io::SAttributeReadWriteOptions* options) const { + IGUIElement::serializeAttributes(out, options); + + out->addBool("ShowBorder", showBorder ); + out->addFloat("BorderRadius", borderRadius ); +} + +void +GUIGroupingPanel::deserializeAttributes(io::IAttributes* in, io::SAttributeReadWriteOptions* options) { + IGUIElement::deserializeAttributes(in, options); + + // Required so that the text size can be precalculated - NO LONGER NEEDED + //setText( in->getAttributeAsString("Caption", L"") ); + showBorder = in->getAttributeAsBool("ShowBorder", showBorder); + borderRadius = in->getAttributeAsFloat("BorderRadius", borderRadius); +} + +}} + +#endif // #ifndef GUI_GROUPING_PANEL_CPP diff --git a/IrrExtensions/gui/GUIGroupingPanel.h b/IrrExtensions/gui/GUIGroupingPanel.h new file mode 100644 index 0000000..62f5d70 --- /dev/null +++ b/IrrExtensions/gui/GUIGroupingPanel.h @@ -0,0 +1,73 @@ +// (c) 2015 Nicolaus Anderson + +#ifndef GUI_GROUPING_PANEL_H +#define GUI_GROUPING_PANEL_H + +#include + +namespace irr { + +namespace video { + class ITexture; +} + +namespace gui { + +using core::dimension2du; +using core::recti; +using video::ITexture; + +//! Class GUI Grouping Panel +/* The sole purpose of this class is to allow for easy grouping of elements, both visibly +and programmatically. */ +class GUIGroupingPanel : public IGUIElement +{ + dimension2du textSize; + bool showBorder; + f32 borderRadius; + + ITexture* ULtexture; + ITexture* URtexture; + ITexture* LLtexture; + ITexture* LRtexture; +public: + GUIGroupingPanel( const wchar_t* pText, IGUIEnvironment* pEnvironment, IGUIElement* pParent, const recti& pRect, s32 id=-1 ); + + //! Set the text + /* This function both sets the text and peforms some calculations for composing the client area. */ + virtual void setText(const wchar_t* text) _IRR_OVERRIDE_; + + //! Set border visible + /* Sets if the border is visible. */ + void setShowBorder( bool pShow ); + + //! Set border edge radius + /* Sets the radius of the edge border. Note, this will effect the client area. + The larger the radius, the smaller the client area will be. */ + void setBorderEdgeRadius( f32 pRadius ); + + /* Returns the area available to the child elements. Note that, if this area is to change, + child GUI elements should be set to retain their positions relative to the walls, NOT using EGUIA_SCALE. */ + recti getClientArea(); + + /* Updates the absolute rectangle and the client area rectangle. */ + //virtual void updateAbsolutePosition(); + + /* Draws the border of this element. */ + virtual void draw() _IRR_OVERRIDE_; + + virtual const c8* getTypeName() const { return staticTypeName(); } + static const c8* staticTypeName() { return "groupingPanel"; } + + virtual void serializeAttributes(io::IAttributes*, io::SAttributeReadWriteOptions*) const _IRR_OVERRIDE_; + virtual void deserializeAttributes(io::IAttributes* in, io::SAttributeReadWriteOptions* options=0) _IRR_OVERRIDE_; + +protected: + //! Update image cache + /* Creates images to be drawn around the border. These are the corners. */ + void updateImageCache(); +}; + +}} + +#endif // #ifndef GUI_GROUPING_PANEL_H diff --git a/IrrExtensions/gui/GUIMarkedSlider.cpp b/IrrExtensions/gui/GUIMarkedSlider.cpp new file mode 100644 index 0000000..58ffc8a --- /dev/null +++ b/IrrExtensions/gui/GUIMarkedSlider.cpp @@ -0,0 +1,385 @@ +// Copyright 2018 Nicolaus Anderson + +#include "GUIMarkedSlider.h" +#include +#include +#include +#include +#include +#include + +namespace irr { +namespace gui { + +GUIMarkedSlider::GUIMarkedSlider( + bool vertical, + f32 sliderSize, + core::rect rectangle, + IGUIEnvironment* environment, + IGUIElement* parent, + s32 id +) + : IGUIElement( /*EGUIET_SCROLLBAR*/ EGUIET_ELEMENT , environment, parent, id, rectangle ) + , MinValue(0) + , MaxValue(100.f) + , CurrentValue(0) + , DrawFrame(0) + , DrawTicks(false) + , DrawNumbers(false) + , NumberSpacing(10.f) + , IsVertical( vertical ) + , SliderRadius( sliderSize / 2 ) + , SliderSelected(false) + , StartMousePos(0) + , SliderTexture(0) +{ + updateImageCache(); + Environment->getVideoDriver(); +} + +GUIMarkedSlider::~GUIMarkedSlider() +{ +} + +bool GUIMarkedSlider::OnEvent( const SEvent& event ) { + if ( event.EventType == EET_MOUSE_INPUT_EVENT ) { + switch ( event.MouseInput.Event ) { + case EMIE_LMOUSE_PRESSED_DOWN: + if ( isInSliderArea( event.MouseInput.X, event.MouseInput.Y ) ) { + SliderSelected = true; + StartMousePos.set( event.MouseInput.X, event.MouseInput.Y ); + } else { + if ( IsVertical ) { + setValue( event.MouseInput.Y - AbsoluteRect.UpperLeftCorner.Y - SliderRadius*2 ); + } else { + setValue( event.MouseInput.X - AbsoluteRect.UpperLeftCorner.X - SliderRadius*2 ); + } + } + return true; + + case EMIE_LMOUSE_LEFT_UP: + if ( Environment->getFocus() == this ) { + SliderSelected = false; + return true; + } + break; + + case EMIE_MOUSE_MOVED: + if ( SliderSelected ) { + if ( IsVertical ) { + setValue( event.MouseInput.Y - StartMousePos.Y ); + } else { + setValue( event.MouseInput.X - StartMousePos.X ); + } + return true; + } + break; + + default: break; + } + } + return false; +} + +void GUIMarkedSlider::setMinValue( f32 value ) { + MinValue = value; + if ( MinValue > MaxValue ) + MaxValue = MinValue; +} + +f32 GUIMarkedSlider::getMinValue() { + return MinValue; +} + +void GUIMarkedSlider::setMaxValue( f32 value ) { + MaxValue = value; + if ( MaxValue < MinValue ) + MinValue = MaxValue; +} + +f32 GUIMarkedSlider::getMaxValue() { + return MaxValue; +} + +void GUIMarkedSlider::setValue( f32 value ) { + // Odd as it is, to save a headache in drawing, we simply treat the CurrentValue + // as the negative of whatever the user wants + CurrentValue = core::clamp( MaxValue - value, MinValue, MaxValue ); +} + +f32 GUIMarkedSlider::getValue() { + // Odd as it is, to save a headache in drawing, we simply treat the CurrentValue + // as the negative of whatever the user wants + return -CurrentValue; +} + +void GUIMarkedSlider::setDrawFrame( bool yes ) { + DrawFrame = yes; +} + +void GUIMarkedSlider::setDrawTicks( bool yes ) { + DrawTicks = yes; +} + +void GUIMarkedSlider::setDrawNumbers( bool yes ) { + DrawNumbers = yes; +} + +void GUIMarkedSlider::setNumberSpacing( f32 distance ) { + NumberSpacing = distance; +} + +void GUIMarkedSlider::setSliderSize( f32 sliderSize ) { + SliderRadius = sliderSize / 2; + updateImageCache(); +} + +bool GUIMarkedSlider::isInSliderArea( s32 x, s32 y ) { + // Create a rectangle formed by the upper left corner of the absolute position of this element + // and having a size that is the same as the slider radius times 2 + core::recti r( + AbsoluteRect.UpperLeftCorner, + core::dimension2du(SliderRadius*2,SliderRadius*2) + ); + return r.isPointInside( core::vector2di(x,y) ); +} + +void GUIMarkedSlider::updateImageCache() { + + video::IVideoDriver* vid = Environment->getVideoDriver(); + video::SColor lightColor = Environment->getSkin()->getColor( EGDC_3D_LIGHT ); + video::SColor shadowColor = Environment->getSkin()->getColor( EGDC_3D_SHADOW ); + video::SColor textColor = Environment->getSkin()->getColor( EGDC_BUTTON_TEXT ); + core::dimension2du imageSize( (u32)SliderRadius * 2, (u32)SliderRadius * 2 ); + core::vector2di sliderSpot( (s32)SliderRadius ); + + if ( ! SliderTexture ) + SliderTexture = vid->addRenderTargetTexture( imageSize, "GUI_MARKED_SLIDER_TEXTURE" ); + + if ( SliderTexture ) { + vid->setRenderTarget( SliderTexture ); + vid->draw2DPolygon( sliderSpot, (s32)SliderRadius, shadowColor, 40 ); + vid->draw2DPolygon( core::vector2di( (s32)SliderRadius - 1 ), (s32)SliderRadius, lightColor, 40 ); + vid->draw2DPolygon( core::vector2di( (s32)SliderRadius - 2 ), (s32)SliderRadius, shadowColor, 40 ); + sliderSpot.Y = 3; // Avoid drawing over the other bands + vid->draw2DLine( sliderSpot, core::vector2di(sliderSpot.X, (s32)imageSize.Height - 3), textColor ); + } + vid->setRenderTarget(0); +} + +void GUIMarkedSlider::draw() { + if ( ! SliderTexture ) { + // ERROR, so just draw children + IGUIElement::draw(); + return; + } + + // Note to self: + // It might be faster to precalculate components to be drawn to the screen and then + // go through a list of them and use a switch to decide how to draw them. + + video::IVideoDriver* vid = Environment->getVideoDriver(); + IGUISkin* skin = Environment->getSkin(); + video::SColor lightColor = skin->getColor( EGDC_3D_LIGHT ); + video::SColor shadowColor = skin->getColor( EGDC_3D_SHADOW ); + video::SColor textColor = skin->getColor( EGDC_BUTTON_TEXT ); + video::SColor faceColor = skin->getColor( EGDC_3D_FACE ); + s32 halfTextHeight = skin->getFont()->getKerningHeight() / 2; + + core::vector2di drawStart, drawEnd, drawSave, drawSave2; // drawSaves are temporary storage variables + + s32 halfElemWidth = AbsoluteRect.getWidth() / 2; + s32 halfElemHeight = AbsoluteRect.getHeight() / 2; + core::recti SliderTextureRect( SliderTexture->getSize() ); + s32 sSliderRadius = (s32)SliderRadius; + f32 drawValue; + f32 drawSpacing; + f32 lineIndex; + core::recti numberTextRect; + + if ( DrawFrame ) { + skin->draw3DSunkenPane(this, faceColor, false, true, AbsoluteRect, &AbsoluteClippingRect); + } + + if ( IsVertical ) { + drawSpacing = NumberSpacing * ((halfElemWidth - SliderRadius) * 2) / (MaxValue - MinValue + 0.00001); + + drawStart.X = AbsoluteRect.UpperLeftCorner.X + halfElemWidth; + drawStart.Y = AbsoluteRect.UpperLeftCorner.Y + sSliderRadius; + drawEnd.X = AbsoluteRect.UpperLeftCorner.X + halfElemWidth; + drawEnd.Y = AbsoluteRect.LowerRightCorner.Y - sSliderRadius; + + vid->draw2DLine( drawStart, drawEnd, lightColor ); + drawStart.X += 1; + drawEnd.X += 1; + vid->draw2DLine( drawStart, drawEnd, shadowColor ); + + if ( DrawTicks ) { + // Ticks on the ends + drawStart.X -= sSliderRadius + 1; + drawEnd.X += sSliderRadius + 1; + drawSave.Y = drawEnd.Y; + drawEnd.Y = drawStart.Y; + vid->draw2DLine( drawStart, drawEnd, lightColor ); + + drawStart.Y = drawSave.Y; + drawEnd.Y = drawSave.Y; + vid->draw2DLine( drawStart, drawEnd, lightColor ); + + // Draw in reverse-numeric order (since we're always working upside-down) + drawSave.X = drawStart.X; + drawSave2.X = drawEnd.X; + drawSave.Y = drawSave2.Y = drawEnd.Y - (s32)drawSpacing; + for ( lineIndex = 0; drawSave.Y > drawStart.Y; ++lineIndex ) { + vid->draw2DLine( drawSave, drawSave2, textColor ); + drawSave.Y = drawSave2.Y = drawEnd.Y - (s32)(drawSpacing * lineIndex); + } + } + + if ( DrawNumbers ) { + // Draw in bottom->up (since we're always working upside-down) + drawValue = MinValue; + drawStart.Y = AbsoluteRect.UpperLeftCorner.Y + sSliderRadius; + drawEnd.Y = AbsoluteRect.LowerRightCorner.Y - sSliderRadius; + drawSave.X = AbsoluteRect.LowerRightCorner.X - halfElemWidth + 1; // Numbers go to the right of the line + drawSave.Y = drawEnd.Y - halfTextHeight; + drawSave2.X = AbsoluteRect.LowerRightCorner.X; + drawSave2.Y = drawEnd.Y + halfTextHeight; + + numberTextRect = core::recti( drawSave, drawSave2 ); + + for ( lineIndex = 0; drawSave.Y > drawStart.Y; ++lineIndex ) { + const core::stringw numberText( drawValue ); + skin->getFont()->draw( numberText.c_str(), numberTextRect, textColor, false, false, &AbsoluteClippingRect ); + drawValue - NumberSpacing; + numberTextRect = core::recti( drawSave, drawSave2 ); + numberTextRect += core::vector2di(0, - (s32)( drawSpacing * lineIndex ) ); + } + + // Draw the last number separately so that it's correctly lined up + drawSave.Y = drawStart.Y - halfTextHeight; + drawSave2.Y = drawStart.Y + halfTextHeight; + numberTextRect = core::recti(drawSave, drawSave2); + const core::stringw numberTextFinal( MinValue ); + skin->getFont()->draw( numberTextFinal.c_str(), numberTextRect, textColor, false, false, &AbsoluteClippingRect ); + } + + // Draw the slider marker last + drawStart.set( halfElemWidth - sSliderRadius, CurrentValue - sSliderRadius ); + vid->draw2DImage( SliderTexture, AbsoluteRect.UpperLeftCorner + drawStart, SliderTextureRect, &AbsoluteClippingRect, video::SColor(-1), true ); + } else { + // Horizontal Slider + + drawSpacing = NumberSpacing * ((halfElemHeight - SliderRadius) * 2) / (MaxValue - MinValue + 0.00001); + + drawStart.X = AbsoluteRect.UpperLeftCorner.X + sSliderRadius; + drawStart.Y = AbsoluteRect.UpperLeftCorner.Y + halfElemHeight; + drawEnd.X = AbsoluteRect.LowerRightCorner.X - sSliderRadius; + drawEnd.Y = AbsoluteRect.UpperLeftCorner.Y + halfElemHeight; + + vid->draw2DLine( drawStart, drawEnd, lightColor ); + drawStart.Y += 1; + drawEnd.Y += 1; + vid->draw2DLine( drawStart, drawEnd, shadowColor ); + + if ( DrawTicks ) { + // Ticks on the ends + drawStart.Y -= sSliderRadius + 1; + drawEnd.Y += sSliderRadius + 1; + drawSave.X = drawEnd.X; + drawEnd.X = drawStart.X; + vid->draw2DLine( drawStart, drawEnd, lightColor ); + + drawStart.X = drawSave.X; + drawEnd.X = drawSave.X; + vid->draw2DLine( drawStart, drawEnd, lightColor ); + + // Draw in reverse-numeric order (since we're always working upside-down) + drawSave.Y = drawStart.Y; + drawSave2.Y = drawEnd.Y; + drawSave.X = drawSave2.X = drawEnd.X - drawSpacing; + for ( lineIndex = 0; drawSave.X > drawStart.X; ++lineIndex ) { + vid->draw2DLine( drawSave, drawSave2, lightColor ); + drawSave.X = drawSave2.X = drawEnd.X - drawSpacing * lineIndex; + } + } + + if ( DrawNumbers ) { + // Draw in left->right + drawValue = MinValue; + drawEnd.X = AbsoluteRect.LowerRightCorner.X - sSliderRadius; + drawSave.X = AbsoluteRect.UpperLeftCorner.Y + sSliderRadius; + drawSave.Y = AbsoluteRect.LowerRightCorner.Y + halfElemHeight + sSliderRadius * 2 + 1; + drawSave2.X = drawStart.X + drawSpacing; + drawSave2.Y = drawSave.Y + halfTextHeight * 2; // We'll let the compiler optimize *2 to <<2 + + numberTextRect = core::recti( drawSave, drawSave2 ); + + for ( lineIndex = 0; drawSave.X < drawEnd.X; ++lineIndex ) { + const core::stringw numberText( drawValue ); + skin->getFont()->draw( numberText.c_str(), numberTextRect, textColor, false, false, &AbsoluteClippingRect ); + drawValue + NumberSpacing; + numberTextRect = core::recti( drawSave, drawSave2 ); + numberTextRect += core::vector2di( (s32)( drawSpacing * lineIndex ), 0 ); + } + + // Draw the last number separately so that it's correctly lined up + drawSave.X = drawEnd.X; + drawSave2.X = AbsoluteRect.LowerRightCorner.X; + numberTextRect = core::recti(drawSave, drawSave2); + const core::stringw numberTextFinal( MinValue ); + skin->getFont()->draw( numberTextFinal.c_str(), numberTextRect, textColor, false, false, &AbsoluteClippingRect ); + } + + // Draw the slider marker last + drawStart.set( CurrentValue - sSliderRadius, halfElemHeight ); + vid->draw2DImage( SliderTexture, AbsoluteRect.UpperLeftCorner + drawStart, SliderTextureRect, &AbsoluteClippingRect, video::SColor(-1), true ); + } + + IGUIElement::draw(); // Children +} + +void GUIMarkedSlider::serializeAttributes(io::IAttributes* out, io::SAttributeReadWriteOptions* options) const +{ + IGUIElement::serializeAttributes(out,options); + + f32 MinValue; + f32 MaxValue; + f32 CurrentValue; + bool DrawFrame; + bool DrawTicks; + bool DrawNumbers; + f32 NumberSpacing; + bool IsVertical; + f32 SliderRadius; + + out->addFloat("MinValue", MinValue); + out->addFloat("MaxValue", MaxValue); + out->addFloat("CurrentValue", CurrentValue); + out->addBool("DrawFrame", DrawFrame); + out->addBool("DrawTicks", DrawTicks); + out->addBool("DrawNumbers", DrawNumbers); + out->addFloat("NumberSpacing", NumberSpacing); + out->addBool("IsVertical", IsVertical); + out->addFloat("SliderRadius", SliderRadius); +} + +void GUIMarkedSlider::deserializeAttributes(io::IAttributes* in, io::SAttributeReadWriteOptions* options) +{ + IGUIElement::deserializeAttributes(in, options); + + MinValue = in->getAttributeAsFloat("MinValue", MinValue); + MaxValue = in->getAttributeAsFloat("MaxValue", MaxValue); + CurrentValue = in->getAttributeAsFloat("CurrentValue", CurrentValue); + DrawFrame = in->getAttributeAsBool("DrawFrame", DrawFrame); + DrawTicks = in->getAttributeAsBool("DrawTicks", DrawTicks); + DrawNumbers = in->getAttributeAsBool("DrawNumbers", DrawNumbers); + NumberSpacing = in->getAttributeAsFloat("NumberSpacing", NumberSpacing); + IsVertical = in->getAttributeAsBool("IsVertical", IsVertical); + + f32 sliderRadius = in->getAttributeAsFloat("SliderRadius", SliderRadius); + if ( sliderRadius != SliderRadius ) + setSliderSize(SliderRadius*2); // Updates image cache +} + +}} diff --git a/IrrExtensions/gui/GUIMarkedSlider.h b/IrrExtensions/gui/GUIMarkedSlider.h new file mode 100644 index 0000000..024e726 --- /dev/null +++ b/IrrExtensions/gui/GUIMarkedSlider.h @@ -0,0 +1,68 @@ +// Copyright 2018 Nicolaus Anderson + +#include "IGUIElement.h" + +#ifndef __GUI_MARKED_SLIDER_H__ +#define __GUI_MARKED_SLIDER_H__ + +namespace irr { +namespace gui { + +//! Marked Slider class +/* + Draws a long line (the number line) with marked values (below it if the line is vertical or to the right of it if the line is horizontal). + It draws value marker that marks the current value of the slider. It can have different appearances. + - A circle + - (Not yet) Two arrows pointing at a short line in between them which is perpendicular to the number line. + Greyed out when disabled. +*/ +class GUIMarkedSlider : public IGUIElement { + +public: + GUIMarkedSlider(bool vertical, f32 sliderSize, core::rect rectangle, IGUIEnvironment* environment, IGUIElement* parent=0, s32 id=-1 ); + ~GUIMarkedSlider(); + + virtual bool OnEvent( const SEvent& event ) _IRR_OVERRIDE_; + virtual void draw() _IRR_OVERRIDE_; + + void setMinValue( f32 ); + f32 getMinValue(); + void setMaxValue( f32 ); + f32 getMaxValue(); + void setValue( f32 ); + f32 getValue(); + + void setDrawFrame( bool ); + void setDrawTicks( bool ); + void setDrawNumbers( bool ); + void setNumberSpacing( f32 ); // Distance between drawn numbers + void setSliderSize( f32 ); + + bool isInSliderArea( s32 x, s32 y ); + + virtual const c8* getTypeName() const { return staticTypeName(); } + static const c8* staticTypeName() { return "markedSlider"; } + + virtual void serializeAttributes(io::IAttributes*, io::SAttributeReadWriteOptions*) const _IRR_OVERRIDE_; + virtual void deserializeAttributes(io::IAttributes* in, io::SAttributeReadWriteOptions* options=0) _IRR_OVERRIDE_; + +private: + void updateImageCache(); + + f32 MinValue; + f32 MaxValue; + f32 CurrentValue; + bool DrawFrame; + bool DrawTicks; + bool DrawNumbers; + f32 NumberSpacing; + bool IsVertical; + f32 SliderRadius; + bool SliderSelected; + core::vector2di StartMousePos; + video::ITexture* SliderTexture; +}; + +}} + +#endif diff --git a/IrrExtensions/gui/GUIMaterialPanel.cpp b/IrrExtensions/gui/GUIMaterialPanel.cpp new file mode 100644 index 0000000..39fc1f7 --- /dev/null +++ b/IrrExtensions/gui/GUIMaterialPanel.cpp @@ -0,0 +1,848 @@ +// (c) 2014 Nicolaus Anderson + +#include "GUIMaterialPanel.h" +#include +#include +#include +#include +#include +#include "GUIColorEditBox.h" +#include "GUIColorSample.h" +#include "GUISColorSelect.h" +#include +#include +#include "scene/GUIScene.h" +#include "GUIWindow2.h" +#include "GUIFileSelectPanel.h" + +namespace irr { +namespace gui { + +using core::vector2di; +using core::recti; +using core::dimension2d; +using video::SMaterial; + +GUIMaterialPanel::GUIMaterialPanel( + IGUIEnvironment* pEnvironment, + ISceneManager* pSceneManager, + IGUIElement* pParent, + recti pRect, + s32 id + ) + : IGUIElement( EGUIET_ELEMENT, pEnvironment, pParent, id, pRect ) + , material(0) + + , meshMaterialScene(0) + + , ambientColorEditBox(0) + , ambientPanelSample(0) + , ambientColorButton(0) + , ambientColorSelectWindow(0) + , ambientColorSelect(0) + , ambientColorSample(0) + + , diffuseColorEditBox(0) + , diffusePanelSample(0) + , diffuseColorButton(0) + , diffuseColorSelectWindow(0) + , diffuseColorSelect(0) + , diffuseColorSample(0) + + , emissiveColorEditBox(0) + , emissivePanelSample(0) + , emissiveColorButton(0) + , emissiveColorSelectWindow(0) + , emissiveColorSelect(0) + , emissiveColorSample(0) + + , specularColorEditBox(0) + , specularPanelSample(0) + , specularColorButton(0) + , specularColorSelectWindow(0) + , specularColorSelect(0) + , specularColorSample(0) + + , wireframeCheckbox(0) + , gourandShadingCheckbox(0) + , lightingCheckbox(0) + , mipmapsCheckbox(0) + , colorSelectWindowRect(0, 0, 210, 210) + + , textureNameEditBox(0) + , textureSelectButton(0) + , textureRemoveButton(0) + + , selectTextureWindow(0) + , selectTextureFilePanel(0) +{ + // Create the user interface + setTabGroup(true); + + /* When designing a mesh that must become transparent or fade, + you can use this setting: */ + // EMT_TRANSPARENT_VERTEX_ALPHA + /* This setting (which applies to the material; see EMaterialTypes.h) + sets the transparency based on the vertex color alpha. + It would be useful to add a checkbox that allows the user to + select use-the-alpha-value so that the mesh can either be transparent + or solid (for faster rendering). + NOTE: When doing this, if you want to combine both vertex-alpha (to fade + the whole thing) with texture alpha, you will have to write a shader. */ + + s32 startY = 15; // In case the mesh scene is added + + // create GUI Mesh Scene with sphere with material + //meshMaterialScene = new GUIScene( Environment, pSceneManager, this, recti() ); + //materialSceneNode = meshMaterialScene->GetScene()->addSphereSceneNode(); + //meshMaterialScene->SetOptimalCamDistToNode( materialSceneNode ); + //updateMeshScene(); + + s32 boxW, boxH; + boxW = 65; boxH = 20; + recti textRect(0,startY,boxW+5,boxH+15); + recti valueRect(textRect); + valueRect += vector2di( boxW+15, 0 ); + vector2di shiftY(0,boxH+5); + recti sampleRect( 0, 0, boxH, boxH ); + sampleRect += vector2di( valueRect.LowerRightCorner.X + 15, valueRect.UpperLeftCorner.Y ); + + // ********* Ambient color + ambientColorButton = Environment->addButton( + textRect, + this, -1, + L"Ambient", + L"Open ambient color selection window" ); + //ambientColorButton->setTabStop(true); + //ambientColorButton->setTabOrder(0); + + ambientColorEditBox = new GUIColorEditBox( Environment, this, valueRect ); + ambientColorEditBox->setTabStop(true); + ambientColorEditBox->setTabOrder(-1); + + ambientPanelSample = new GUIColorSample( pEnvironment, this, sampleRect ); + ambientPanelSample->setDrawBorder(true); + ambientPanelSample->showOneColor( 0xffffffff ); + + // ********* Diffuse color + textRect += shiftY; + diffuseColorButton = Environment->addButton( + textRect, + this, -1, + L"Diffuse", + L"Open diffuse color selection window" ); + //diffuseColorButton->setTabStop(true); + //diffuseColorButton->setTabOrder(2); + + valueRect += shiftY; + diffuseColorEditBox = new GUIColorEditBox( Environment, this, valueRect ); + diffuseColorEditBox->setTabStop(true); + diffuseColorEditBox->setTabOrder(-1); + + sampleRect += shiftY; + diffusePanelSample = new GUIColorSample( pEnvironment, this, sampleRect ); + diffusePanelSample->setDrawBorder(true); + diffusePanelSample->showOneColor( 0xffffffff ); + + // ********* Emissive color + textRect += shiftY; + emissiveColorButton = Environment->addButton( + textRect, + this, -1, + L"Emissize", + L"Open emissive color selection window" ); + //emissiveColorButton->setTabStop(true); + //emissiveColorButton->setTabOrder(4); + + valueRect += shiftY; + emissiveColorEditBox = new GUIColorEditBox( Environment, this, valueRect ); + emissiveColorEditBox->setTabStop(true); + emissiveColorEditBox->setTabOrder(-1); + + sampleRect += shiftY; + emissivePanelSample = new GUIColorSample( pEnvironment, this, sampleRect ); + emissivePanelSample->setDrawBorder(true); + emissivePanelSample->showOneColor( 0xffffffff ); + + // ********* Specular color + textRect += shiftY; + specularColorButton = Environment->addButton( + textRect, + this, -1, + L"Specular", + L"Open specular color selection window" ); + //specularColorButton->setTabStop(true); + //specularColorButton->setTabOrder(6); + + valueRect += shiftY; + specularColorEditBox = new GUIColorEditBox( Environment, this, valueRect ); + specularColorEditBox->setTabStop(true); + specularColorEditBox->setTabOrder(-1); + + sampleRect += shiftY; + specularPanelSample = new GUIColorSample( pEnvironment, this, sampleRect ); + specularPanelSample->setDrawBorder(true); + specularPanelSample->showOneColor( 0xffffffff ); + + + // Enlargen the text box to fit the text and checkbox + textRect.LowerRightCorner = valueRect.LowerRightCorner; + + // ********* Wireframe + textRect += shiftY; + wireframeCheckbox = Environment->addCheckBox( + false, // checked? + textRect, + this, -1, + L"Wireframe" ); + wireframeCheckbox->setTabStop(true); + wireframeCheckbox->setTabOrder(-1); + + // ********* Gourand Shading + textRect += shiftY; + gourandShadingCheckbox = Environment->addCheckBox( + false, + textRect, + this, -1, + L"Gourand Shading" ); + gourandShadingCheckbox->setTabStop(true); + gourandShadingCheckbox->setTabOrder(-1); + + // ********* Lighting + textRect += shiftY; + lightingCheckbox = Environment->addCheckBox( + false, + textRect, + this, -1, + L"Lit by Lights" ); + lightingCheckbox->setTabStop(true); + lightingCheckbox->setTabOrder(-1); + + // ********* Mipmaps + textRect += shiftY; + mipmapsCheckbox = Environment->addCheckBox( + false, + textRect, + this, -1, + L"Use Mipmaps" ); + mipmapsCheckbox->setTabStop(true); + mipmapsCheckbox->setTabOrder(-1); + + // ********* Texture + textRect += shiftY + vector2di(0,5); + Environment->addStaticText(L"Texture...", textRect, false, false, this ); + + textRect += shiftY; + textureNameEditBox = Environment->addEditBox( L"", textRect, true, this, -1); + textureNameEditBox->setEnabled(false); + + recti textureButtonRect( vector2di(0,0), dimension2d(textRect.getHeight(), textRect.getHeight()) ); + textureButtonRect += vector2di( textRect.LowerRightCorner.X + 5, textRect.UpperLeftCorner.Y ); + textureSelectButton = Environment->addButton( textureButtonRect, this, -1, L"...", L"Select texture." ); + + textureButtonRect += vector2di( textureButtonRect.getWidth() + 5, 0 ); + textureRemoveButton = Environment->addButton( textureButtonRect, this, -1, L"", L"Remove texture." ); + textureRemoveButton->setSpriteBank( Environment->getSkin()->getSpriteBank() ); + textureRemoveButton->setSprite( + EGBS_BUTTON_NOT_FOCUSED, + Environment->getSkin()->getIcon( EGDI_WINDOW_CLOSE ), + Environment->getSkin()->getColor( EGDC_WINDOW_SYMBOL ) + ); + textureRemoveButton->setSprite( + EGBS_BUTTON_FOCUSED, + Environment->getSkin()->getIcon( EGDI_WINDOW_CLOSE ), + Environment->getSkin()->getColor( EGDC_WINDOW_SYMBOL ) + ); + + // ********* Mesh scene + // create GUI Mesh Scene with sphere with material + core::dimension2di sceneSize; + sceneSize.Width = pRect.getWidth() * 2 / 3; + sceneSize.Height = sceneSize.Width; + meshMaterialScene = new GUIScene( + Environment, + pSceneManager, + this, + recti( + vector2di(0, textRect.LowerRightCorner.Y + 15), + sceneSize + ) + ); + + materialSceneNode = meshMaterialScene->GetScene()->addCubeSceneNode(); + meshMaterialScene->SetOptimalCamDistToNode( materialSceneNode ); + meshMaterialScene->enableCamControl(true); + meshMaterialScene->setExtraCamDistance(5); + meshMaterialScene->setDrawBorder(true); +} + +GUIMaterialPanel::~GUIMaterialPanel() +{ + closeWindows(); + + if ( ambientColorEditBox ) + ambientColorEditBox->drop(); + if ( diffuseColorEditBox ) + diffuseColorEditBox->drop(); + if ( emissiveColorEditBox ) + emissiveColorEditBox->drop(); + if ( specularColorEditBox ) + specularColorEditBox->drop(); +} + +void GUIMaterialPanel::closeWindows() +{ + if ( ambientColorSelectWindow ) + { + ambientColorSelectWindow->remove(); + ambientColorSelectWindow = 0; + ambientColorSelect = 0; + } + if ( diffuseColorSelectWindow ) + { + diffuseColorSelectWindow->remove(); + diffuseColorSelectWindow = 0; + diffuseColorSelect = 0; + } + if ( emissiveColorSelectWindow ) + { + emissiveColorSelectWindow->remove(); + emissiveColorSelectWindow = 0; + emissiveColorSelect = 0; + } + if ( specularColorSelectWindow ) + { + specularColorSelectWindow->remove(); + specularColorSelectWindow = 0; + specularColorSelect = 0; + } +} + +void GUIMaterialPanel::setMaterial( SMaterial& pMaterial ) +{ + material = &pMaterial; + + // Update everything + + ambientColorEditBox->setColor( pMaterial.AmbientColor, false ); + ambientPanelSample->showOneColor( pMaterial.AmbientColor ); + diffuseColorEditBox->setColor( pMaterial.DiffuseColor, false ); + diffusePanelSample->showOneColor( pMaterial.DiffuseColor ); + emissiveColorEditBox->setColor( pMaterial.EmissiveColor, false ); + emissivePanelSample->showOneColor( pMaterial.EmissiveColor ); + specularColorEditBox->setColor( pMaterial.SpecularColor, false ); + specularPanelSample->showOneColor( pMaterial.SpecularColor ); + + if ( ambientColorSelectWindow ) + { + ambientColorSelect->setColor( pMaterial.AmbientColor, false ); + ambientColorSample->showOneColor( pMaterial.AmbientColor ); + } + if ( diffuseColorSelectWindow ) + { + diffuseColorSelect->setColor( pMaterial.DiffuseColor, false ); + diffuseColorSample->showOneColor( pMaterial.DiffuseColor ); + } + if ( emissiveColorSelectWindow ) + { + emissiveColorSelect->setColor( pMaterial.EmissiveColor, false ); + emissiveColorSample->showOneColor( pMaterial.EmissiveColor ); + } + if ( specularColorSelectWindow ) + { + specularColorSelect->setColor( pMaterial.SpecularColor, false ); + specularColorSample->showOneColor( pMaterial.SpecularColor ); + } + + wireframeCheckbox->setChecked( pMaterial.Wireframe ); + gourandShadingCheckbox->setChecked( pMaterial.GouraudShading ); + lightingCheckbox->setChecked( pMaterial.Lighting ); + mipmapsCheckbox->setChecked( pMaterial.UseMipMaps ); + + if ( pMaterial.getTexture(0) ) + { + textureNameEditBox->setText( stringw(pMaterial.getTexture(0)->getName().getPath()).c_str() ); + } else { + textureNameEditBox->setText(L""); + } + + meshMaterialScene->setDrawBackground( ! pMaterial.Wireframe ); + updateMeshScene(); +} + +void GUIMaterialPanel::setAmbientLight( irr::video::SColor pColor ) +{ + meshMaterialScene->GetScene()->setAmbientLight( pColor ); +} + +bool GUIMaterialPanel::OnEvent( const SEvent& event ) +{ + // Only respond to GUI events + if ( isEnabled() && isVisible() ) + switch ( event.EventType ) + { + case EET_GUI_EVENT: + if ( OnGuiEvent(event) ) + return true; + break; + + default: break; + } + + return IGUIElement::OnEvent(event); +} + +bool GUIMaterialPanel::OnGuiEvent( const SEvent& event ) +{ + switch ( event.GUIEvent.EventType ) + { + case EGET_EDITBOX_CHANGED: + if ( event.GUIEvent.Caller == ambientColorEditBox ) + { + if ( ambientColorSelectWindow ) + { + ambientColorSelect->setColor( ambientColorEditBox->getColor(), false ); + ambientColorSample->showOneColor( ambientColorEditBox->getColor() ); + } + if ( material ) + material->AmbientColor = ambientColorEditBox->getColor(); + ambientPanelSample->showOneColor( ambientColorEditBox->getColor() ); + + sendGUIEvent( EGET_EDITBOX_CHANGED, ambientColorEditBox ); + return true; + } + else if ( event.GUIEvent.Caller == ambientColorSelect ) + { + ambientColorEditBox->setColor( ambientColorSelect->getColor(), false ); + ambientPanelSample->showOneColor( ambientColorSelect->getColor() ); + ambientColorSample->showOneColor( ambientColorSelect->getColor() ); + if ( material ) + material->AmbientColor = ambientColorSelect->getColor(); + + sendGUIEvent( EGET_EDITBOX_CHANGED, ambientColorSelect ); + return true; + } + else if ( event.GUIEvent.Caller == diffuseColorEditBox ) + { + if ( diffuseColorSelectWindow ) + { + diffuseColorSelect->setColor( diffuseColorEditBox->getColor(), false ); + diffuseColorSample->showOneColor( diffuseColorEditBox->getColor() ); + } + if ( material ) + material->DiffuseColor = diffuseColorEditBox->getColor(); + diffusePanelSample->showOneColor( diffuseColorEditBox->getColor() ); + + sendGUIEvent( EGET_EDITBOX_CHANGED, diffuseColorEditBox ); + return true; + } + else if ( event.GUIEvent.Caller == diffuseColorSelect ) + { + diffuseColorEditBox->setColor( diffuseColorSelect->getColor(), false ); + diffusePanelSample->showOneColor( diffuseColorSelect->getColor() ); + diffuseColorSample->showOneColor( diffuseColorSelect->getColor() ); + if ( material ) + material->DiffuseColor = diffuseColorSelect->getColor(); + + sendGUIEvent( EGET_EDITBOX_CHANGED, diffuseColorSelect ); + return true; + } + else if ( event.GUIEvent.Caller == emissiveColorEditBox ) + { + if ( emissiveColorSelectWindow ) + { + emissiveColorSelect->setColor( emissiveColorEditBox->getColor(), false ); + emissiveColorSample->showOneColor( emissiveColorEditBox->getColor() ); + } + if ( material ) + material->EmissiveColor = emissiveColorEditBox->getColor(); + emissivePanelSample->showOneColor( emissiveColorEditBox->getColor() ); + + sendGUIEvent( EGET_EDITBOX_CHANGED, emissiveColorEditBox ); + return true; + } + else if ( event.GUIEvent.Caller == emissiveColorSelect ) + { + emissiveColorEditBox->setColor( emissiveColorSelect->getColor(), false ); + emissivePanelSample->showOneColor( emissiveColorSelect->getColor() ); + emissiveColorSample->showOneColor( emissiveColorSelect->getColor() ); + if ( material ) + material->EmissiveColor = emissiveColorSelect->getColor(); + + sendGUIEvent( EGET_EDITBOX_CHANGED, emissiveColorSelect ); + return true; + } + else if ( event.GUIEvent.Caller == specularColorEditBox ) + { + if ( specularColorSelectWindow ) + { + specularColorSelect->setColor( specularColorEditBox->getColor(), false ); + specularColorSample->showOneColor( specularColorEditBox->getColor() ); + } + if ( material ) + material->SpecularColor = specularColorEditBox->getColor(); + specularPanelSample->showOneColor( specularColorEditBox->getColor() ); + + sendGUIEvent( EGET_EDITBOX_CHANGED, specularColorEditBox ); + return true; + } + else if ( event.GUIEvent.Caller == specularColorSelect ) + { + specularColorEditBox->setColor( specularColorSelect->getColor(), false ); + specularPanelSample->showOneColor( specularColorSelect->getColor() ); + specularColorSample->showOneColor( specularColorSelect->getColor() ); + if ( material ) + material->SpecularColor = specularColorSelect->getColor(); + + sendGUIEvent( EGET_EDITBOX_CHANGED, specularColorSelect ); + return true; + } + break; //end case EGET_EDITBOX_CHANGED + + case EGET_BUTTON_CLICKED: + if ( event.GUIEvent.Caller == ambientColorButton ) + { + closeWindows(); // Prevent windows from tying into eachother + if ( !ambientColorSelectWindow ) + { + recti r (colorSelectWindowRect); + r.UpperLeftCorner += vector2di(5, 25); + r.LowerRightCorner -= vector2di(5); + // Room for the color sample + r.LowerRightCorner.X -= 50; + + ambientColorSelectWindow = createColorSelectWindow(); + + ambientColorSelect = new GUISColorSelect( + Environment, + ambientColorSelectWindow, + r); + ambientColorSelect->drop(); // balance reference counting + + r.UpperLeftCorner.X = r.LowerRightCorner.X + 20; + r.LowerRightCorner.X = colorSelectWindowRect.LowerRightCorner.X - 10; + ambientColorSample = new GUIColorSample( + Environment, + ambientColorSelectWindow, + r); + ambientColorSample->setDrawBorder(true); + ambientColorSample->drop(); // balance reference counting + } + ambientColorSelect->setColor( ambientColorEditBox->getColor(), false ); + ambientColorSample->showOneColor( ambientColorEditBox->getColor() ); + Environment->getRootGUIElement()->bringToFront( ambientColorSelectWindow ); + Environment->setFocus( ambientColorSelectWindow ); + return true; + } + else if ( event.GUIEvent.Caller == diffuseColorButton ) + { + closeWindows(); // Prevent windows from tying into eachother + if ( !diffuseColorSelectWindow ) + { + recti r (colorSelectWindowRect); + r.UpperLeftCorner += vector2di(5, 25); + r.LowerRightCorner -= vector2di(5); + // Room for the color sample + r.LowerRightCorner.X -= 50; + + diffuseColorSelectWindow = createColorSelectWindow(); + + diffuseColorSelect = new GUISColorSelect( + Environment, + diffuseColorSelectWindow, + r); + diffuseColorSelect->drop(); // balance reference counting + + r.UpperLeftCorner.X = r.LowerRightCorner.X + 20; + r.LowerRightCorner.X = colorSelectWindowRect.LowerRightCorner.X - 10; + diffuseColorSample = new GUIColorSample( + Environment, + diffuseColorSelectWindow, + r); + diffuseColorSample->setDrawBorder(true); + diffuseColorSample->drop(); // balance reference counting + } + diffuseColorSelect->setColor( diffuseColorEditBox->getColor(), false ); + diffuseColorSample->showOneColor( diffuseColorEditBox->getColor() ); + Environment->getRootGUIElement()->bringToFront( diffuseColorSelectWindow ); + Environment->setFocus( diffuseColorSelectWindow ); + return true; + } + else if ( event.GUIEvent.Caller == emissiveColorButton ) + { + closeWindows(); // Prevent windows from tying into eachother + if ( !emissiveColorSelectWindow ) + { + recti r (colorSelectWindowRect); + r.UpperLeftCorner += vector2di(5, 25); + r.LowerRightCorner -= vector2di(5); + // Room for the color sample + r.LowerRightCorner.X -= 50; + + emissiveColorSelectWindow = createColorSelectWindow(); + + emissiveColorSelect = new GUISColorSelect( + Environment, + emissiveColorSelectWindow, + r); + emissiveColorSelect->drop(); // balance reference counting + + r.UpperLeftCorner.X = r.LowerRightCorner.X + 20; + r.LowerRightCorner.X = colorSelectWindowRect.LowerRightCorner.X - 10; + emissiveColorSample = new GUIColorSample( + Environment, + emissiveColorSelectWindow, + r); + emissiveColorSample->setDrawBorder(true); + emissiveColorSample->drop(); // balance reference counting + } + emissiveColorSelect->setColor( emissiveColorEditBox->getColor(), false ); + emissiveColorSample->showOneColor( emissiveColorEditBox->getColor() ); + Environment->getRootGUIElement()->bringToFront( emissiveColorSelectWindow ); + Environment->setFocus( emissiveColorSelectWindow ); + return true; + } + else if ( event.GUIEvent.Caller == specularColorButton ) + { + closeWindows(); // Prevent windows from tying into eachother + if ( !specularColorSelectWindow ) + { + recti r (colorSelectWindowRect); + r.UpperLeftCorner += vector2di(5, 25); + r.LowerRightCorner -= vector2di(5); + // Room for the color sample + r.LowerRightCorner.X -= 50; + + specularColorSelectWindow = createColorSelectWindow(); + + specularColorSelect = new GUISColorSelect( + Environment, + specularColorSelectWindow, + r); + specularColorSelect->drop(); // balance reference counting + + r.UpperLeftCorner.X = r.LowerRightCorner.X + 20; + r.LowerRightCorner.X = colorSelectWindowRect.LowerRightCorner.X - 10; + specularColorSample = new GUIColorSample( + Environment, + specularColorSelectWindow, + r); + specularColorSample->setDrawBorder(true); + specularColorSample->drop(); // balance reference counting + } + specularColorSelect->setColor( specularColorEditBox->getColor(), false ); + specularColorSample->showOneColor( specularColorEditBox->getColor() ); + Environment->getRootGUIElement()->bringToFront( specularColorSelectWindow ); + Environment->setFocus( specularColorSelectWindow ); + return true; + } + else if ( event.GUIEvent.Caller == textureSelectButton ) + { + createSelectTextureDialog(); + return true; + } + else if ( event.GUIEvent.Caller == textureRemoveButton ) + { + if ( material ) + { + material->setTexture(0,0); + } + updateMeshScene(); + textureNameEditBox->setText(L""); + return true; + } + break; // end case EGET_BUTTON_CLICKED + + case EGET_CHECKBOX_CHANGED: + if ( event.GUIEvent.Caller == wireframeCheckbox ) + { + if ( material ) + material->Wireframe = wireframeCheckbox->isChecked(); + + meshMaterialScene->setDrawBackground( ! wireframeCheckbox->isChecked() ); + + sendGUIEvent( EGET_CHECKBOX_CHANGED, wireframeCheckbox ); + return true; + } + else if ( event.GUIEvent.Caller == gourandShadingCheckbox ) + { + if ( material ) + material->GouraudShading = gourandShadingCheckbox->isChecked(); + + sendGUIEvent( EGET_CHECKBOX_CHANGED, gourandShadingCheckbox ); + return true; + } + else if ( event.GUIEvent.Caller == lightingCheckbox ) + { + if ( material ) + material->Lighting = lightingCheckbox->isChecked(); + + sendGUIEvent( EGET_CHECKBOX_CHANGED, lightingCheckbox ); + return true; + } + else if ( event.GUIEvent.Caller == mipmapsCheckbox ) + { + if ( material ) + material->UseMipMaps = mipmapsCheckbox->isChecked(); + + sendGUIEvent( EGET_CHECKBOX_CHANGED, mipmapsCheckbox ); + return true; + } + break; // end case EGET_CHECKBOX_CHANGED + + case EGET_ELEMENT_CLOSED: + // And we hope this doesn't cause memory leaks + if ( event.GUIEvent.Caller == ambientColorSelectWindow ) + { + ambientColorSelectWindow = 0; + ambientColorSelect = 0; + } + else if ( event.GUIEvent.Caller == diffuseColorSelectWindow ) + { + diffuseColorSelectWindow = 0; + diffuseColorSelect = 0; + } + else if ( event.GUIEvent.Caller == emissiveColorSelectWindow ) + { + emissiveColorSelectWindow = 0; + emissiveColorSelect = 0; + } + else if ( event.GUIEvent.Caller == specularColorSelectWindow ) + { + specularColorSelectWindow = 0; + specularColorSelect = 0; + } + break; // end case EGET_ELEMENT_CLOSED + + case EGET_FILE_SELECTED: + if ( event.GUIEvent.Caller == selectTextureFilePanel ) + { + switch ( selectTextureFilePanel->getLastEvent() ) + { + case EGFSPE_FILE_CONFIRMED: + if ( material ) + { + material->setTexture(0, + Environment->getVideoDriver()->getTexture( + selectTextureFilePanel->getSelectedFile() + ) + ); + + if ( material->getTexture(0) ) + { + textureNameEditBox->setText( stringw(selectTextureFilePanel->getSelectedFile()).c_str() ); + updateMeshScene(); + } + } + selectTextureWindow->remove(); + selectTextureWindow = 0; + selectTextureFilePanel = 0; + break; + + default: + break; + } + } + break; + + case EGET_FILE_CHOOSE_DIALOG_CANCELLED: + if ( event.GUIEvent.Caller == selectTextureFilePanel ) + { + selectTextureWindow->remove(); + selectTextureWindow = 0; + selectTextureFilePanel = 0; + } + break; + + default: break; + } + + return false; +} + +void GUIMaterialPanel::sendGUIEvent( EGUI_EVENT_TYPE pEventType, IGUIElement* pElement ) +{ + updateMeshScene(); + + if ( ! Parent ) return; + + SEvent event; + event.EventType = EET_GUI_EVENT; + event.GUIEvent.Caller = this; + event.GUIEvent.Element = pElement; + event.GUIEvent.EventType = pEventType; + + Parent->OnEvent(event); +} + +void GUIMaterialPanel::updateMeshScene() +{ + if ( !material ) + return; + + // There should be at least one + if ( materialSceneNode->getMaterialCount() ) + { + materialSceneNode->getMaterial(0) = *material; + } +} + +IGUIWindow* GUIMaterialPanel::createColorSelectWindow() +{ + s32 bumpX = 5, bumpY = 5; + recti r(colorSelectWindowRect); + r.UpperLeftCorner -= vector2di( bumpX, bumpY ); + //r.LowerRightCorner += vector2di( 50, 0 ); + + // Center in root window + dimension2d ss = Environment->getRootGUIElement()->getAbsolutePosition().getSize() / 2; + dimension2d rs = r.getSize() / 2; + vector2di startPos( ss.Width - rs.Width, ss.Height - rs.Height ); + + //IGUIWindow* window = Environment->addWindow( r, false, 0, this, -1 ); + GUIWindow2* window = new GUIWindow2( Environment, Environment->getRootGUIElement(), r ); + window->setEventParent( this ); + window->setNotClipped(true); // Does not allow full roaming + window->setDraggable(true); + window->setDrawBackground(true); + window->setDrawTitlebar(true); // space for close button + window->getMinimizeButton()->remove(); + window->getMaximizeButton()->remove(); + + window->move( startPos ); + + window->drop(); // balance reference counting + + return window; +} + +void GUIMaterialPanel::createSelectTextureDialog() +{ + dimension2di windowSize(400,300); + dimension2di rootGUISize = Environment->getRootGUIElement()->getAbsolutePosition().getSize(); + recti windowRect( + vector2di( rootGUISize.Width - windowSize.Width, + rootGUISize.Height - windowSize.Height )/2, + windowSize + ); + + IGUIElement* modalScreen = Environment->addModalScreen(Environment->getRootGUIElement()); + + selectTextureWindow = new GUIWindow2( Environment, 0, windowRect ); + selectTextureWindow->setEventParent(this); + modalScreen->addChild( selectTextureWindow ); + selectTextureWindow->getMinimizeButton()->remove(); + + selectTextureFilePanel = new GUIFileSelectPanel( + Environment, + selectTextureWindow, + selectTextureWindow->getClientRect(), + io::path(".") + ); + + selectTextureWindow->drop(); + selectTextureFilePanel->drop(); +} + +} +} diff --git a/IrrExtensions/gui/GUIMaterialPanel.h b/IrrExtensions/gui/GUIMaterialPanel.h new file mode 100644 index 0000000..27aaddf --- /dev/null +++ b/IrrExtensions/gui/GUIMaterialPanel.h @@ -0,0 +1,136 @@ +// (c) 2014 Nicolaus Anderson + +#include + +#ifndef GUI_MATERIAL_PANEL_H_ +#define GUI_MATERIAL_PANEL_H_ + +namespace irr { + +namespace scene { + class ISceneManager; + class IMeshSceneNode; +} + +namespace video { + class SColor; + class SMaterial; +} + +namespace gui { + +using core::vector2di; +using core::recti; +using video::SMaterial; +using scene::ISceneManager; +using scene::IMeshSceneNode; + +class IGUIButton; +class IGUICheckBox; +class IGUIWindow; +class IGUIEditBox; +class GUIColorEditBox; +class GUIColorSample; +class GUISColorSelect; +class GUIScene; +class GUIWindow2; +class GUIFileSelectPanel; + +/* Class : GUI Material Panel + +This creates a panel with buttons for opening color-selection windows, +editboxes for editing the colors (ambient, diffuse, emissive, and specular), +and checkboxes for turning on the engine-features of wireframe drawing, +gouraud shading, making the material lit by lights, and having the +engine generate mipmaps for the material textures. +It is not possible to remove any of these items. +In the future, the buttons, editboxes, and checkboxes should be removable, +but this requires grabbing them in the constructor after they have been +created and dropping them in the deconstructor. + +The design of this class is such that it can handle all of the material +editing without the programmer having to monitor its events and update +changes to the material accordingly. Pass a pointer to the material +via setMaterial(). + +Optionally, this panel should have a 3D mesh scene that shows a sphere, +indicating what the material looks like when applied to a scene node. +*/ +class GUIMaterialPanel : public IGUIElement +{ +protected: + SMaterial* material; + + GUIScene* meshMaterialScene; + IMeshSceneNode* materialSceneNode; + + GUIColorEditBox* ambientColorEditBox; + GUIColorSample* ambientPanelSample; + IGUIButton* ambientColorButton; + IGUIWindow* ambientColorSelectWindow; + GUISColorSelect* ambientColorSelect; + GUIColorSample* ambientColorSample; + + GUIColorEditBox* diffuseColorEditBox; + GUIColorSample* diffusePanelSample; + IGUIButton* diffuseColorButton; + IGUIWindow* diffuseColorSelectWindow; + GUISColorSelect* diffuseColorSelect; + GUIColorSample* diffuseColorSample; + + GUIColorEditBox* emissiveColorEditBox; + GUIColorSample* emissivePanelSample; + IGUIButton* emissiveColorButton; + IGUIWindow* emissiveColorSelectWindow; + GUISColorSelect* emissiveColorSelect; + GUIColorSample* emissiveColorSample; + + GUIColorEditBox* specularColorEditBox; + GUIColorSample* specularPanelSample; + IGUIButton* specularColorButton; + IGUIWindow* specularColorSelectWindow; + GUISColorSelect* specularColorSelect; + GUIColorSample* specularColorSample; + + IGUICheckBox* wireframeCheckbox; + IGUICheckBox* gourandShadingCheckbox; + IGUICheckBox* lightingCheckbox; + IGUICheckBox* mipmapsCheckbox; + + recti colorSelectWindowRect; + + IGUIEditBox* textureNameEditBox; + IGUIButton* textureSelectButton; + IGUIButton* textureRemoveButton; + + GUIWindow2* selectTextureWindow; + GUIFileSelectPanel* selectTextureFilePanel; + + +public: + GUIMaterialPanel( IGUIEnvironment* pEnvironment, ISceneManager* pSceneManager, IGUIElement* pParent, recti pRect, s32 id=-1 ); + ~GUIMaterialPanel(); + + void setMaterial( SMaterial& pMaterial ); + void closeWindows(); + void setAmbientLight( irr::video::SColor pColor ); + + virtual bool OnEvent( const SEvent& event ); + + virtual const c8* getTypeName() const { return staticTypeName(); } + static const c8* staticTypeName() { return "materialPanel"; } +protected: + bool OnGuiEvent( const SEvent& event ); + void sendGUIEvent( EGUI_EVENT_TYPE pEventType, IGUIElement* pElement=0 ); + + // Set the mesh material to a copy of the one pointed to by "material". + void updateMeshScene(); + + IGUIWindow* createColorSelectWindow(); + void createSelectTextureDialog(); +}; + +} +} + +#endif diff --git a/IrrExtensions/gui/GUIMatrixPanel.cpp b/IrrExtensions/gui/GUIMatrixPanel.cpp new file mode 100644 index 0000000..c5e6299 --- /dev/null +++ b/IrrExtensions/gui/GUIMatrixPanel.cpp @@ -0,0 +1,244 @@ +// (c) 2015 Nicolaus Anderson + +#ifndef GUI_MATRIX_PANEL_CPP +#define GUI_MATRIX_PANEL_CPP + +#include "GUIMatrixPanel.h" +#include "GUIVectorPanel.h" +#include +#include +#include +#include + +namespace irr { +namespace gui { + +using core::dimension2d; +using core::vector2di; + +GUIMatrixPanel::GUIMatrixPanel( bool pRotateInDegrees, IGUIEnvironment* pEnvironment, IGUIElement* pParent, rect pRect, s32 id ) + : IGUIElement( EGUIET_ELEMENT, pEnvironment, pParent, id, pRect ) + , translationVectorPanel(0) + , rotationVectorPanel(0) + , scaleVectorPanel(0) + , rotInDeg( pRotateInDegrees ) +{ + // labelSpan = for "Translation", "Rotation", and "Scale" text + // xyzSpan = for "X", "Y", and "Z" text + + /* Intended appearance: + + labelSpan -pad- xyzSpan xyzSpan xyzSpan + labelSpan -pad- vectorPanel + labelSpan -pad- vectorPanel + labelSpan -pad- vectorPanel + */ + + s32 pad = 3; // padding between elements + dimension2d labelSpan = pEnvironment->getSkin()->getFont()->getDimension(L"Translate"); + s32 boxH = (pRect.getHeight() - (3*pad)) / 4; // used regardless of the text height + s32 xyzSpan = ( pRect.getWidth() - (s32)labelSpan.Width - (pad*2) ) / 3; + rect labelRect( 0, boxH, labelSpan.Width, boxH*2 ); // for "Translate", "Rotate", and "Scale" + rect textRect( 0, 0, boxH, boxH ); // for "X", "Y", and "Z" + textRect += vector2di( xyzSpan/2 - 3, boxH/2 - 3 ); // text centering + + // Add labels of X, Y, and Z to the top + // Note the first is indented so it doesn't hover over the labels + textRect += vector2di( labelSpan.Width + pad, 0 ); // Use .move() with custom Irrlicht + IGUIStaticText* textElem = + pEnvironment->addStaticText( L"X", textRect, false, false, this, -1, false ); // no border, wordwrap, nor fill + textElem->setAlignment( EGUIA_UPPERLEFT, EGUIA_SCALE, EGUIA_SCALE, EGUIA_SCALE ); + + textRect += vector2di( xyzSpan, 0 ); // Use .move() with custom Irrlicht + textElem = + pEnvironment->addStaticText( L"Y", textRect, false, false, this, -1, false ); // no border, wordwrap, nor fill + textElem->setAlignment( EGUIA_SCALE, EGUIA_SCALE, EGUIA_SCALE, EGUIA_SCALE ); + + textRect += vector2di( xyzSpan, 0 ); // Use .move() with custom Irrlicht + textElem = pEnvironment->addStaticText( L"Z", textRect, false, false, this, -1, false ); // no border, wordwrap, nor fill + textElem->setAlignment( EGUIA_SCALE, EGUIA_SCALE, EGUIA_SCALE, EGUIA_SCALE ); + + // Add the labels of "Translate", "Rotate", and "Scale" + textElem = pEnvironment->addStaticText( L"Translate", labelRect, false, false, this, -1, false ); + textElem->setAlignment( EGUIA_UPPERLEFT, EGUIA_UPPERLEFT, EGUIA_SCALE, EGUIA_SCALE ); + + labelRect += vector2di( 0, boxH + pad ); // Use .move() with custom Irrlicht + textElem = pEnvironment->addStaticText( L"Rotate", labelRect, false, false, this, -1, false ); + textElem->setAlignment( EGUIA_UPPERLEFT, EGUIA_UPPERLEFT, EGUIA_SCALE, EGUIA_SCALE ); + + labelRect += vector2di( 0, boxH + pad ); // Use .move() with custom Irrlicht + textElem = pEnvironment->addStaticText( L"Scale", labelRect, false, false, this, -1, false ); + textElem->setAlignment( EGUIA_UPPERLEFT, EGUIA_UPPERLEFT, EGUIA_SCALE, EGUIA_SCALE ); + + // Add the vector panels + rect vectorPanelRect( labelSpan.Width + pad, boxH + pad, pRect.getWidth(), boxH*2 + pad ); + + translationVectorPanel = new GUIVectorPanel( pEnvironment, this, vectorPanelRect, true ); + //translationVectorPanel->setAlignment( EGUIA_UPPERLEFT, EGUIA_SCALE, EGUIA_SCALE, EGUIA_SCALE ); + + vectorPanelRect += vector2di( 0, boxH + pad ); // Use .move() with custom Irrlicht + + rotationVectorPanel = new GUIVectorPanel( pEnvironment, this, vectorPanelRect, true ); + //rotationVectorPanel->setAlignment( EGUIA_UPPERLEFT, EGUIA_SCALE, EGUIA_SCALE, EGUIA_SCALE ); + + vectorPanelRect += vector2di( 0, boxH + pad ); // Use .move() with custom Irrlicht + + scaleVectorPanel = new GUIVectorPanel( pEnvironment, this, vectorPanelRect, true ); + //scaleVectorPanel->setAlignment( EGUIA_UPPERLEFT, EGUIA_SCALE, EGUIA_SCALE, EGUIA_SCALE ); +} + +/* +matrix4 GUIMatrixPanel::getMatrix() +{ + // The problem is that setScale and setRotation conflict, causing one to override the other + matrix4 m; + m.setScale( getScale() ); + m.setRotation( getRotation() ); + m.setTranslation( getTranslation() ); + return m; +} +*/ + +void GUIMatrixPanel::setSpaceMatrix( SpaceMatrix& pMatrix ) +{ + //spaceMatrix = pMatrix; + translationVectorPanel->set3DVectorValue( pMatrix.getTranslation() ); + scaleVectorPanel->set3DVectorValue( pMatrix.getScale() ); + if ( rotInDeg ) + rotationVectorPanel->set3DVectorValue( pMatrix.getRotationDegrees() ); + else + rotationVectorPanel->set3DVectorValue( pMatrix.getRotationRadians() ); +} + +void GUIMatrixPanel::setTranslation( vector3d pTranslation ) +{ + //spaceMatrix->setTranslation( pTranslation ); + translationVectorPanel->set3DVectorValue( pTranslation ); +} + +void GUIMatrixPanel::setRotation( vector3d pRotation ) +{ + rotationVectorPanel->set3DVectorValue( pRotation ); +} + +void GUIMatrixPanel::setScale( vector3d pScale ) +{ + scaleVectorPanel->set3DVectorValue( pScale ); +} + +SpaceMatrix GUIMatrixPanel::getSpaceMatrix() +{ + if ( !rotInDeg ) + return SpaceMatrix( + translationVectorPanel->getVector3D(), + rotationVectorPanel->getVector3D(), + scaleVectorPanel->getVector3D() + ); + + // else, must convert from degrees + SpaceMatrix m = SpaceMatrix( + translationVectorPanel->getVector3D(), + vector3d(), + scaleVectorPanel->getVector3D() + ); + + m.setRotationDegrees( rotationVectorPanel->getVector3D() ); + return m; +} + +vector3d GUIMatrixPanel::getTranslation() +{ + return translationVectorPanel->getVector3D(); +} + +vector3d GUIMatrixPanel::getRotation() +{ + return rotationVectorPanel->getVector3D(); +} + +vector3d GUIMatrixPanel::getScale() +{ + return scaleVectorPanel->getVector3D(); +} + +void GUIMatrixPanel::reset() +{ + translationVectorPanel->reset(); + rotationVectorPanel->reset(); + scaleVectorPanel->set3DValue(1.f, 1.f, 1.f); +} + +bool GUIMatrixPanel::OnEvent( const SEvent& event ) +{ + if ( !isVisible() || !isEnabled() || event.EventType != EET_GUI_EVENT ) + return false; + + if ( event.GUIEvent.Caller == translationVectorPanel ) + { + lastAction = EGUIMTXPA_TranslationChanged; + //spaceMatrix->setTranslation( translationVectorPanel->getVector3D() ); + sendGUIEvent( EGET_EDITBOX_CHANGED, translationVectorPanel ); + return true; + } else if ( event.GUIEvent.Caller == rotationVectorPanel ) + { + lastAction = EGUIMTXPA_RotationChanged; + //spaceMatrix->setRotationDegrees( rotationVectorPanel->getVector3D() ); + sendGUIEvent( EGET_EDITBOX_CHANGED, rotationVectorPanel ); + return true; + } else if ( event.GUIEvent.Caller == scaleVectorPanel ) + { + lastAction = EGUIMTXPA_ScaleChanged; + //spaceMatrix->setScale( scaleVectorPanel->getVector3D() ); + sendGUIEvent( EGET_EDITBOX_CHANGED, scaleVectorPanel ); + return true; + } + lastAction = EGUIMTXPA_None; + return false; +} + +void GUIMatrixPanel::sendGUIEvent( EGUI_EVENT_TYPE pEventType, IGUIElement* pElement ) +{ + if ( ! Parent ) return; + + SEvent event; + event.EventType = EET_GUI_EVENT; + event.GUIEvent.Caller = this; + event.GUIEvent.Element = pElement; + event.GUIEvent.EventType = pEventType; + + Parent->OnEvent(event); +} + +EGUIMatrixPanelAction GUIMatrixPanel::getLastAction() +{ + return lastAction; +} + +bool GUIMatrixPanel::rotatesInDegrees() +{ + return rotInDeg; +} + +void GUIMatrixPanel::serializeAttributes( + irr::io::IAttributes* out, + irr::io::SAttributeReadWriteOptions* options + ) +{ + IGUIElement::serializeAttributes(out,options); + + out->addBool("RotateInDegrees", rotInDeg); +} + +void GUIMatrixPanel::deserializeAttributes( + irr::io::IAttributes* in, + irr::io::SAttributeReadWriteOptions* options + ) +{ + IGUIElement::deserializeAttributes(in,options); + + rotInDeg = in->getAttributeAsBool("RotateInDegrees", rotInDeg); +} + +}} + +#endif // #ifndef GUI_MATRIX_PANEL_CPP diff --git a/IrrExtensions/gui/GUIMatrixPanel.h b/IrrExtensions/gui/GUIMatrixPanel.h new file mode 100644 index 0000000..8a97494 --- /dev/null +++ b/IrrExtensions/gui/GUIMatrixPanel.h @@ -0,0 +1,88 @@ +// (c) 2015 Nicolaus Anderson + +#ifndef GUI_MATRIX_PANEL_H +#define GUI_MATRIX_PANEL_H + +#include +#include +// #include +#include "../math/SpaceMatrix.h" + +namespace irr { +namespace gui { + +using core::vector3d; +using core::rect; +using core::matrix4; + +class GUIVectorPanel; + +//! Enum GUI Matrix Panel Action +/* Used for indicating the last action of the matrix. +It is also designed to save time in calculating the full space matrix (something that may never need to be done). */ +enum EGUIMatrixPanelAction +{ + EGUIMTXPA_None = 0, + EGUIMTXPA_TranslationChanged, + EGUIMTXPA_RotationChanged, + EGUIMTXPA_ScaleChanged, + EGUIMTXPA_COUNT +}; + +//! Class GUI Matrix Panel +/* Allows for the easy editing of a matrix. */ +class GUIMatrixPanel : public IGUIElement +{ +protected: + GUIVectorPanel* translationVectorPanel; + GUIVectorPanel* rotationVectorPanel; + GUIVectorPanel* scaleVectorPanel; + //SpaceMatrix spaceMatrix; + +private: + EGUIMatrixPanelAction lastAction; + bool rotInDeg; + +public: + GUIMatrixPanel( bool pRotateInDegrees, IGUIEnvironment* pEnvironment, IGUIElement* pParent, rect pRect, s32 id=-1 ); + + //matrix4 getMatrix(); + void setSpaceMatrix( SpaceMatrix& pMatrix ); + void setTranslation( vector3d pTranslation ); + void setRotation( vector3d pRotation ); + void setScale( vector3d pScale ); + SpaceMatrix getSpaceMatrix(); + vector3d getTranslation(); + vector3d getRotation(); + vector3d getScale(); + void reset(); + + virtual bool OnEvent( const SEvent& event ); + EGUIMatrixPanelAction getLastAction(); + + // Matrix uses degrees for rotation + /* Indicates if this panel keeps its rotation in degrees instead of radians. + The returned matrix from getSpaceMatrix() will be converted to radians, but + getRotation() will be returned with degrees. */ + bool rotatesInDegrees(); + + virtual void serializeAttributes( + irr::io::IAttributes* out, + irr::io::SAttributeReadWriteOptions* options=0 + ); + + virtual void deserializeAttributes( + irr::io::IAttributes* in, + irr::io::SAttributeReadWriteOptions* options=0 + ); + + virtual const c8* getTypeName() const { return staticTypeName(); } + static const c8* staticTypeName() { return "matrixPanel"; } + +private: + void sendGUIEvent( EGUI_EVENT_TYPE pEventType, IGUIElement* pElement ); +}; + +}} + +#endif // #ifndef GUI_MATRIX_PANEL_H diff --git a/IrrExtensions/gui/GUIPanel.cpp b/IrrExtensions/gui/GUIPanel.cpp new file mode 100644 index 0000000..24c8da5 --- /dev/null +++ b/IrrExtensions/gui/GUIPanel.cpp @@ -0,0 +1,74 @@ +// (C) 2020 Nicolaus Anderson + +#include "GUIPanel.h" + +namespace irr { +namespace gui { + +GUIPanel::GUIPanel( IGUIEnvironment* environment, IGUIElement* parent, const core::recti& position, s32 id ) + : IGUIElement(EGUIET_ELEMENT, environment, parent, id, position) + , DrawBackground(true) + , BackgroundType(EGUIDT_SUNKENPANE) + , BackgroundColor(0xff707070) + , DrawTitleBar(false) + , TitleBarColor(0xffc0c0c0) +{} + +void GUIPanel::setDrawBackground( bool yes ) +{ + DrawBackground = yes; +} + +void GUIPanel::setBackgroundType( EGUIDrawType type ) +{ + BackgroundType = type; +} + +void GUIPanel::draw() +{ + switch(BackgroundType) { + case EGUIDT_BUTTON: + Environment->getSkin()->draw3DButtonPaneStandard(this, AbsoluteRect, &AbsoluteClippingRect); + break; + + case EGUIDT_BUTTON_PRESSED: + Environment->getSkin()->draw3DButtonPanePressed(this, AbsoluteRect, &AbsoluteClippingRect); + break; + + case EGUIDT_SUNKENPANE: + Environment->getSkin()->draw3DSunkenPane(this, BackgroundColor, false, true, AbsoluteRect, &AbsoluteClippingRect); + break; + + case EGUIDT_WINDOW: + Environment->getSkin()->draw3DWindowBackground(this, DrawTitleBar, TitleBarColor, AbsoluteRect, &AbsoluteClippingRect); + break; + + default: + break; + } +} + +void GUIPanel::serializeAttributes(io::IAttributes* out, io::SAttributeReadWriteOptions* options) const +{ + IGUIElement::serializeAttributes(out, options); + + out->addBool( "DrawBackground", DrawBackground ); + out->addEnum( "BackgroundType", BackgroundType, GUIDrawTypeNames ); + out->addColor( "BackgroundColor", BackgroundColor ); + out->addBool( "DrawTitleBar", DrawTitleBar ); + out->addColor( "TitleBarColor", TitleBarColor); +} + +void GUIPanel::deserializeAttributes(io::IAttributes* in, io::SAttributeReadWriteOptions* options) +{ + IGUIElement::deserializeAttributes(in, options); + + DrawBackground = in->getAttributeAsBool( "DrawBackground", DrawBackground ); + BackgroundType = (EGUIDrawType)in->getAttributeAsEnumeration( "BackgroundType", GUIDrawTypeNames, BackgroundType ); + BackgroundColor = in->getAttributeAsColor( "BackgroundColor", BackgroundColor ); + DrawTitleBar = in->getAttributeAsBool( "DrawTitleBar", DrawTitleBar ); + TitleBarColor = in->getAttributeAsColor( "TitleBarColor", TitleBarColor ); +} + +} +} diff --git a/IrrExtensions/gui/GUIPanel.h b/IrrExtensions/gui/GUIPanel.h new file mode 100644 index 0000000..0fb8b6e --- /dev/null +++ b/IrrExtensions/gui/GUIPanel.h @@ -0,0 +1,56 @@ +// (C) 2020 Nicolaus Anderson +/* + A convenient GUI element whose standard appearance can be changed to mimic that of other elements. +*/ + +#ifndef GUI_PANEL_H +#define GUI_PANEL_H + +#include +#include +#include + +namespace irr { +namespace gui { + +enum EGUIDrawType { + EGUIDT_BUTTON, + EGUIDT_BUTTON_PRESSED, + EGUIDT_SUNKENPANE, + EGUIDT_WINDOW, +}; + +const c8* const GUIDrawTypeNames[] = { + "button", + "button pressed", + "sunken pane", + "window", + 0 +}; + +struct GUIPanel : public IGUIElement +{ + GUIPanel( IGUIEnvironment* environment, IGUIElement* parent, const core::recti& position, s32 id=-1 ); + + void setDrawBackground( bool yes ); + void setBackgroundType( EGUIDrawType ); + void draw(); + + virtual const c8* getTypeName() const { return staticTypeName(); } + static const c8* staticTypeName() { return "panel"; } + + void serializeAttributes(io::IAttributes* out, io::SAttributeReadWriteOptions* options) const; + void deserializeAttributes(io::IAttributes* in, io::SAttributeReadWriteOptions* options); + +protected: + bool DrawBackground; + EGUIDrawType BackgroundType; + video::SColor BackgroundColor; + bool DrawTitleBar; // Only applies when drawing as as Window + video::SColor TitleBarColor; +}; + +} +} + +#endif diff --git a/IrrExtensions/gui/GUISColorSelect.cpp b/IrrExtensions/gui/GUISColorSelect.cpp new file mode 100644 index 0000000..599aa2c --- /dev/null +++ b/IrrExtensions/gui/GUISColorSelect.cpp @@ -0,0 +1,593 @@ +// (c) 2014 Nicolaus Anderson + +#ifndef GUISCOLORSELECT_CPP +#define GUISCOLORSELECT_CPP + +#include "GUISColorSelect.h" +#include +#include + +namespace irr { +namespace gui { + +using core::clamp; +using core::vector2d; +using core::floor32; +using core::ceil32; +using core::dimension2d; +using video::IVideoDriver; + +GUISColorSelect::GUISColorSelect( IGUIEnvironment* pEnvironment, IGUIElement* pParent, recti pRect, s32 id ) + : IGUISColorSelect( pEnvironment, pParent, pRect, id ) + , color(SOLID_BLACK) // solid black for HSL constructor + , fieldAlpha(0) + , HUE_RANGE( 360.f ) + , LUMINANCE_RANGE( 100.f ) + , SATURATION_RANGE( 100.f ) + , lastEvent( EGSCSE_None ) + , mouseStateFlags(0) + , colorFieldRect( + vector2di(padding), + pRect.getSize() - dimension2d(2*padding, 30 + 2*padding) + ) + , hueFieldRect( + vector2di(padding, pRect.getHeight()-30+(2*padding)), + vector2di(pRect.getWidth()-padding, pRect.getHeight()-padding) + ) + , firstMousePos(0) + , startHue(0) + , lastColorFieldMousePos(0) +{ + colorFieldRect.repair(); + hueFieldRect.repair(); + + updateHueFieldRectangles(); +} + +SColor GUISColorSelect::getColor() +{ + SColorf outf; + color.toRGB(outf); + return outf.toSColor(); +} + +SColor GUISColorSelect::getHueAsColor() +{ + SColorHSL outHSL(color.Hue, SATURATION_RANGE, LUMINANCE_RANGE); + SColorf outf; + outHSL.toRGB(outf); + return outf.toSColor(); +} + +void GUISColorSelect::setColor( SColor pColor, bool notifyParent ) +{ + SColorf cf(pColor); + color.fromRGB(cf); + fieldAlpha = u8(pColor.getAlpha()); + + // Calculate color coordinates for the last color-field mouse position + lastColorFieldMousePos.X = + colorFieldRect.LowerRightCorner.X + - (s32)( (color.Saturation / SATURATION_RANGE) * (f32)(colorFieldRect.getWidth()-1.f) ); + + lastColorFieldMousePos.Y = + colorFieldRect.LowerRightCorner.Y + - (s32) ( (color.Luminance / LUMINANCE_RANGE) * (f32)(colorFieldRect.getHeight()-1) ); + + // Notify parent + if ( Parent && notifyParent ) + sendGUIEvent(EGET_EDITBOX_CHANGED); +} + +void GUISColorSelect::setHue( SColor pColor, bool notifyParent ) +{ + SColorf cf( pColor ); + SColorHSL chsl; + chsl.fromRGB(cf); + color.Hue = chsl.Hue; + + if ( Parent && notifyParent ) + sendGUIEvent(EGET_EDITBOX_CHANGED); +} + +void GUISColorSelect::setAlpha( u8 pAlpha ) +{ + fieldAlpha = pAlpha; +} + +void GUISColorSelect::updateAbsolutePosition() +{ + IGUIElement::updateAbsolutePosition(); + + colorFieldRect.UpperLeftCorner.set(padding,padding); + colorFieldRect.LowerRightCorner.set( + vector2di(RelativeRect.getSize()) - vector2di(padding,30+2*padding) + ); + + hueFieldRect.UpperLeftCorner.set( padding, RelativeRect.getHeight()-30+(2*padding) ); + hueFieldRect.LowerRightCorner.set( + RelativeRect.getWidth()-padding, + RelativeRect.getHeight()-padding + ); + + updateHueFieldRectangles(); +} + +EGUISColorSelectEvent GUISColorSelect::getEvent() +{ + return lastEvent; +} + +bool GUISColorSelect::OnEvent( const SEvent& event ) +{ + if ( isEnabled() && isVisible() ) + switch ( event.EventType ) + { + case EET_MOUSE_INPUT_EVENT: + return onMouseEvent(event); + + case EET_KEY_INPUT_EVENT: + return onKeyEvent(event); + + default: break; + } + return IGUIElement::OnEvent(event); +} + +bool GUISColorSelect::onMouseEvent( const SEvent& event ) +{ + vector2di lastMousePos( event.MouseInput.X, event.MouseInput.Y ); + + /* Note: this GUI Element may respond even when not in focus. + However, forcing it to respond when in focus forces the user to + click on it, which can cause the color values to change, messing + up the value. */ + switch ( event.MouseInput.Event ) + { + case EMIE_LMOUSE_PRESSED_DOWN: + setMouseState( MOUSE_LEFT_PRESSED, true ); + /* Check to see if the mouse is in the correct areas + for modifying the color. */ + if ( colorFieldRect.isPointInside( lastMousePos - AbsoluteRect.UpperLeftCorner ) ) + { + setMouseState( MOUSE_IN_COLOR_FIELD ); + } + else if ( hueFieldRect.isPointInside( lastMousePos - AbsoluteRect.UpperLeftCorner ) ) + { + setMouseState( MOUSE_IN_HUE_FIELD ); + } + return true; + + case EMIE_LMOUSE_LEFT_UP: + if ( getMouseState( MOUSE_LEFT_PRESSED ) ) + { + resetMouseState( MOUSE_LEFT_PRESSED ); + resetMouseState( MOUSE_IN_COLOR_FIELD ); + resetMouseState( MOUSE_IN_HUE_FIELD ); + sendGUIEvent(EGET_EDITBOX_CHANGED); + } + return true; + + case EMIE_MMOUSE_PRESSED_DOWN: + setMouseState( MOUSE_MIDDLE_PRESSED, true ); + firstMousePos = lastMousePos; + startHue = color.Hue; + return true; + + case EMIE_MMOUSE_LEFT_UP: + resetMouseState( MOUSE_MIDDLE_PRESSED ); + sendGUIEvent(EGET_EDITBOX_CHANGED); + return true; + + case EMIE_MOUSE_MOVED: + if ( getMouseState( MOUSE_LEFT_PRESSED ) ) + { + if ( getMouseState( MOUSE_IN_COLOR_FIELD ) ) + { + setColorFieldFromPosition( lastMousePos - AbsoluteRect.UpperLeftCorner ); + sendGUIEvent(EGET_EDITBOX_CHANGED); + } + else if ( getMouseState( MOUSE_IN_HUE_FIELD ) ) + { + setHueFieldFromPosition( + (lastMousePos - AbsoluteRect.UpperLeftCorner - padding).X + ); + sendGUIEvent(EGET_EDITBOX_CHANGED); + } + return true; // absorb anyways + } + else if ( getMouseState( MOUSE_MIDDLE_PRESSED ) ) + { + // Change the hue and update the hue bar + setHueFieldFromShift( lastMousePos.X - firstMousePos.X ); + + sendGUIEvent(EGET_EDITBOX_CHANGED); + return true; + } + return false; + + default: break; + } + + return false; +} + +void GUISColorSelect::sendGUIEvent( EGUI_EVENT_TYPE pEventType, IGUIElement* pElement ) +{ + SEvent event; + event.EventType = EET_GUI_EVENT; + event.GUIEvent.Caller = this; + event.GUIEvent.Element = pElement; + event.GUIEvent.EventType = pEventType; + + if ( Parent ) + Parent->OnEvent(event); +} + +bool GUISColorSelect::onKeyEvent( const SEvent& event ) +{ + switch ( event.KeyInput.Key ) + { + case KEY_KEY_R: + setColor( SOLID_RED ); + return true; + + case KEY_KEY_Y: + setColor( SOLID_YELLOW ); + return true; + + case KEY_KEY_G: + setColor( SOLID_GREEN ); + return true; + + case KEY_KEY_T: + setColor( SOLID_TEAL ); + return true; + + case KEY_KEY_B: + setColor( SOLID_BLUE ); + return true; + + case KEY_KEY_P: + setColor( SOLID_PURPLE ); + return true; + + default: break; + } + return false; +} + +void GUISColorSelect::setMouseState( c8 pState, bool pResetOthers ) +{ + if ( !pResetOthers ) + { + mouseStateFlags |= pState; + return; + } + mouseStateFlags = pState; +} + +void GUISColorSelect::resetMouseState( c8 pState ) +{ + mouseStateFlags &= ~pState; +} + +bool GUISColorSelect::getMouseState( c8 pState ) +{ + return (mouseStateFlags & pState) == pState; +} + +void GUISColorSelect::setColorFieldFromPosition( vector2di pPos ) +{ + lastColorFieldMousePos = pPos; + + /* NOTE: Padding to the left has already been accounted for. */ + /* Move the position until it is in the field. */ + lastColorFieldMousePos.X = clamp( pPos.X, + colorFieldRect.UpperLeftCorner.X, + colorFieldRect.LowerRightCorner.X ); + + lastColorFieldMousePos.Y = clamp( pPos.Y, + colorFieldRect.UpperLeftCorner.Y, + colorFieldRect.LowerRightCorner.Y ); + + /* Color field appearance: + white -------------------- white + | | + | | + full sat color unsaturated color + | | + | | + black -------------------- black + + Meaning that the x position correlates with saturation + and the y position correlates with lightness. + */ + + f32 sat = SATURATION_RANGE * ( + (f32)((colorFieldRect.LowerRightCorner.X) - lastColorFieldMousePos.X) + / (f32)(colorFieldRect.getWidth()-1) + ); + f32 lum = LUMINANCE_RANGE * ( + (f32)((colorFieldRect.LowerRightCorner.Y) - lastColorFieldMousePos.Y) + / (f32)(colorFieldRect.getHeight()-1) + ); + + color.Saturation = clamp( sat, 0.f, SATURATION_RANGE ); + color.Luminance = clamp( lum, 0.f, LUMINANCE_RANGE ); +} + +//============================= +// Wrong: It sets the color along the diagonal +// Preserved for reference + +//void GUISColorSelect::setColorFieldFromPosition( vector2di pPos ) +//{ +// lastColorFieldMousePos = pPos; +// +// /* NOTE: Padding to the left has already been accounted for. */ +// /* Move the position until it is in the field. */ +// pPos.X = min_( +// colorFieldRect.LowerRightCorner.X, +// max_( colorFieldRect.UpperLeftCorner.X, pPos.X ) +// ); +// pPos.Y = min_( +// colorFieldRect.LowerRightCorner.Y, +// max_( colorFieldRect.UpperLeftCorner.Y, pPos.Y ) +// ); +// +// /* Color field appearance: +// full sat color ----------- white +// | | +// | | +// | | +// | | +// | | +// black -------------- unsaturated color +// +// Note: (0,0) is the upper left corner, meaning the fully saturated +// color lies on the rectangle's diagonal [(0,0),(w,h)] and the black +// and white lies on the diagonal [(0,h),(w,0)] +// */ +// +// vector2d pos(pPos); // useful cast +// +// /* Lightness depends only on the projection of the vector +// onto the black-white diagonal. */ +// vector2di bw(0-colorFieldRect.getWidth(), colorFieldRect.getHeight()-0); +// vector2d bw_f(bw); +// f32 bw_length = bw_f.getLength(); +// f32 bw_projection = pos.dotProduct( bw_f.normalize() ); +// +// color.Luminance = (bw_projection / bw_length) * LUMINANCE_RANGE; +// +// /* Saturation depends on the diagonal [(w,0),(0,h)] */ +// vector2di sat(colorFieldRect.getWidth(), colorFieldRect.getHeight()); +// vector2d sat_f(sat); +// f32 sat_length = sat_f.getLength(); +// f32 sat_projection = pos.dotProduct( sat_f.normalize() ); +// +// // 1 - projection because progression along the diagonal is toward unsaturated +// color.Saturation = (1.0f - sat_projection) * SATURATION_RANGE; +//} + +void GUISColorSelect::setHueFieldFromPosition( s32 x ) +{ + if ( x < 0 ) + x = 0; + if ( x >= hueFieldRect.getWidth() ) + x = hueFieldRect.getWidth() - 1; + + /* NOTE: Padding to the left has already been accounted for. */ + f32 i = f32(x) * HUE_RANGE/(f32)(hueFieldRect.getWidth()-1); + color.Hue = clamp( i, 0.f, HUE_RANGE ); +} + +void GUISColorSelect::setHueFieldFromShift( s32 x ) +{ + color.Hue = startHue + (f32)x; + // cancel overshoots + while ( color.Hue < 0 ) + { + color.Hue += HUE_RANGE; + } + while ( color.Hue >= HUE_RANGE ) + { + color.Hue -= HUE_RANGE; + } +} + +void GUISColorSelect::updateHueFieldRectangles() +{ + f32 spacing = hueFieldRect.getWidth() / 6.f; + /* Spacing, in terms of hue, is 60, but there are six color segments + over a space of the hue field rectangle width. + red = 0 + yellow = 60 + green = 120 + teal = 180 + blue = 240 + purple = 300 + red top = 360 + */ + + redYellowRect = recti(0, 0, s32(spacing), hueFieldRect.getHeight()), + yellowGreenRect = recti(s32(spacing), 0, s32(spacing*2), hueFieldRect.getHeight()), + greenTealRect = recti(s32(spacing*2), 0, s32(spacing*3), hueFieldRect.getHeight()), + tealBlueRect = recti(s32(spacing*3), 0, s32(spacing*4), hueFieldRect.getHeight()), + bluePurpleRect = recti(s32(spacing*4), 0, s32(spacing*5), hueFieldRect.getHeight()), + purpleRedRect = recti(s32(spacing*5), 0, s32(spacing*6), hueFieldRect.getHeight()); + + redYellowRect += AbsoluteRect.UpperLeftCorner + hueFieldRect.UpperLeftCorner; + yellowGreenRect += AbsoluteRect.UpperLeftCorner + hueFieldRect.UpperLeftCorner; + greenTealRect += AbsoluteRect.UpperLeftCorner + hueFieldRect.UpperLeftCorner; + tealBlueRect += AbsoluteRect.UpperLeftCorner + hueFieldRect.UpperLeftCorner; + bluePurpleRect += AbsoluteRect.UpperLeftCorner + hueFieldRect.UpperLeftCorner; + purpleRedRect += AbsoluteRect.UpperLeftCorner + hueFieldRect.UpperLeftCorner; +} + +void GUISColorSelect::draw() +{ + if ( !isVisible() ) + return; + + IGUISkin* skin = Environment->getSkin(); + + // Draw color field border ----- (5-pixel padding is for this border) + recti colorFieldBorder( + vector2di(0), + colorFieldRect.getSize() + dimension2d(2*padding, 2*padding) + ); + // move to absolute location + colorFieldBorder += AbsoluteRect.UpperLeftCorner; + // draw + skin->draw3DButtonPaneStandard(this, colorFieldBorder, &AbsoluteClippingRect); + + + // Draw color field --------- + SColorf fieldColorf; + SColorHSL hsl = color; + hsl.Luminance = 50; + hsl.Saturation = 100; + hsl.toRGB( fieldColorf ); + + recti colorFieldRectAbs = colorFieldRect + AbsoluteRect.UpperLeftCorner; + recti colorFieldRectAbs2 = colorFieldRectAbs; + colorFieldRectAbs2.UpperLeftCorner.Y += colorFieldRect.getHeight()/2; + colorFieldRectAbs.LowerRightCorner.Y = colorFieldRectAbs2.UpperLeftCorner.Y; + + /* Problem: Burning's isn't coloring the rectangles correctly, so + to fix this, I need to change either + a) Burnings (a hassle) or + b) Create images that I draw myself (slow for drawing and chews resources) */ + + // top rectangle + Environment->getVideoDriver()->draw2DRectangle( + colorFieldRectAbs, + SOLID_WHITE, SOLID_WHITE, fieldColorf.toSColor(), SOLID_GREY, + &AbsoluteClippingRect + ); + + // bottom rectangle + Environment->getVideoDriver()->draw2DRectangle( + colorFieldRectAbs2, + fieldColorf.toSColor(), SOLID_GREY, SOLID_BLACK, SOLID_BLACK, + &AbsoluteClippingRect + ); + + // should be ((SOLID_BLACK & 0x00ffffff) | (fieldAlpha<<24 & 0xff000000)) + + // TO DO!! + + /* ^ Note above, the current problem is that there is no checkered pattern + in the background in the case of transparent values. Furthermore, there is + no handling of transparent values. That's on the to-do list. + */ + + + // Draw the color tracker ------- + recti colorTrackerRect( + lastColorFieldMousePos - vector2di(2), + lastColorFieldMousePos + vector2di(2) + ); + // move to absolute location + colorTrackerRect += AbsoluteRect.UpperLeftCorner; + // draw + Environment->getVideoDriver()->draw2DRectangleOutline( colorTrackerRect, SOLID_GREY ); + + + // Draw hue field border ----- (5-pixel padding is for this border also) + recti hueFieldBorder( + hueFieldRect.UpperLeftCorner - vector2di(padding), + hueFieldRect.LowerRightCorner + vector2di(padding) + ); + // move to absolute location + hueFieldBorder += AbsoluteRect.UpperLeftCorner; + // draw + skin->draw3DButtonPaneStandard(this, hueFieldBorder, &AbsoluteClippingRect); + + + // Draw hue field ---------- + /* Note: While it would be nice to use createGradientTexture(), + the engine resizes textures to be squares, needlessly chewing up + memory. However, it might be faster on the processor and would + be more convenient for programming. */ + + Environment->getVideoDriver()->draw2DRectangle( + redYellowRect, + SOLID_RED, SOLID_YELLOW, + SOLID_RED, SOLID_YELLOW, + &AbsoluteClippingRect + ); + + Environment->getVideoDriver()->draw2DRectangle( + yellowGreenRect, + SOLID_YELLOW, SOLID_GREEN, + SOLID_YELLOW, SOLID_GREEN, + &AbsoluteClippingRect + ); + + Environment->getVideoDriver()->draw2DRectangle( + greenTealRect, + SOLID_GREEN, SOLID_TEAL, + SOLID_GREEN, SOLID_TEAL, + &AbsoluteClippingRect + ); + + Environment->getVideoDriver()->draw2DRectangle( + tealBlueRect, + SOLID_TEAL, SOLID_BLUE, + SOLID_TEAL, SOLID_BLUE, + &AbsoluteClippingRect + ); + + Environment->getVideoDriver()->draw2DRectangle( + bluePurpleRect, + SOLID_BLUE, SOLID_PURPLE, + SOLID_BLUE, SOLID_PURPLE, + &AbsoluteClippingRect + ); + + Environment->getVideoDriver()->draw2DRectangle( + purpleRedRect, + SOLID_PURPLE, SOLID_RED, + SOLID_PURPLE, SOLID_RED, + &AbsoluteClippingRect + ); + + + // Draw the hue field bar ------ + s32 hueBarX = (s32)( (f32)(hueFieldRect.getWidth()) * color.Hue / HUE_RANGE ); + recti hueFieldBar( hueBarX - 2, -2, hueBarX + 2, hueFieldRect.getHeight() + 2 ); + // Move to draw location + hueFieldBar += hueFieldRect.UpperLeftCorner + AbsoluteRect.UpperLeftCorner; + // draw + Environment->getVideoDriver()->draw2DRectangleOutline( + hueFieldBar, SOLID_GREY + ); +} + +void GUISColorSelect::serializeAttributes( + irr::io::IAttributes* out, + irr::io::SAttributeReadWriteOptions* options + ) +{ + IGUIElement::serializeAttributes(out,options); + + out->addColor("Color", getColor()); +} + +void GUISColorSelect::deserializeAttributes( + irr::io::IAttributes* in, + irr::io::SAttributeReadWriteOptions* options + ) +{ + IGUIElement::deserializeAttributes(in,options); + + setColor(in->getAttributeAsColor("Color", getColor())); +} + +} +} + +#endif // #ifndef GUISCOLORSELECT_CPP diff --git a/IrrExtensions/gui/GUISColorSelect.h b/IrrExtensions/gui/GUISColorSelect.h new file mode 100644 index 0000000..516b760 --- /dev/null +++ b/IrrExtensions/gui/GUISColorSelect.h @@ -0,0 +1,132 @@ +// (c) 2014 Nicolaus Anderson + +#ifndef GUISCOLORSELECT_H +#define GUISCOLORSELECT_H + +#include "IGUISColorSelect.h" +#include "../video/Colors.h" + +/* + +NOTE TO SELF: + +If you create an alpha-bar, it should be either to the right of the +color field or directly below the hue bar. + +I could have made this of sub-elements +- one for the color field, which +would be merely a selection field returning values between 0 and 1 along +whatever horizontal, vertical, or diagonal you wanted +- two sliders for the hue and alpha bars, noting that the background of a +slider need not be drawn (so I could draw a hue gradient below it), +in which the sliders limit the selected range as well as both respond to +the middle mouse movement (if in focus) +(While it might annoy the user, the middle mouse button + direction in +the same direction as the orientation of the slider would matter, allowing +the user to control both hue and alpha (if on different bars), but not +if both bars are oriented the same. This could be very problematic.) + +This level of abstraction would allow me to more easily control how +all of the pieces are placed on the screen. + +*/ + +namespace irr { +namespace gui { + +using core::vector2di; +using core::recti; +using video::SColor; +using video::SColorf; +using video::SColorHSL; + +class GUISColorSelect : public IGUISColorSelect +{ +protected: + SColorHSL color; + u8 fieldAlpha; + + const f32 HUE_RANGE;// = 360.f; // 60 between each color and 1st and last red + const f32 LUMINANCE_RANGE;// = 100.f; + const f32 SATURATION_RANGE;// = 100.f; + + EGUISColorSelectEvent lastEvent; + + u8 mouseStateFlags; + static const u8 MOUSE_LEFT_PRESSED = 1; + static const u8 MOUSE_MIDDLE_PRESSED = 2; + static const u8 MOUSE_RIGHT_PRESSED = 4; + /* Indicates the mouse at least WAS in + the color fields upon the start of the + click, even if not now. */ + static const u8 MOUSE_IN_COLOR_FIELD = 8; + static const u8 MOUSE_IN_HUE_FIELD = 16; + +private: + static const s32 padding = 5; // padding for the color field rectangles + recti colorFieldRect; + recti hueFieldRect; + vector2di firstMousePos; + f32 startHue; + vector2di lastColorFieldMousePos; + + // color rectangles for the hue field + recti redYellowRect, + yellowGreenRect, + greenTealRect, + tealBlueRect, + bluePurpleRect, + purpleRedRect; + + +public: + GUISColorSelect( IGUIEnvironment* pEnvironment, IGUIElement* pParent, recti pRect, s32 id=-1 ); + + virtual SColor getColor(); + SColor getHueAsColor(); + + void setColor( SColor pColor, bool notifyParent=true ); + void setHue( SColor pColor, bool notifyParent=true ); + void setAlpha( u8 pAlpha ); + + /* To do... + Add overrides for updating the Relative rectangle. + This can be done by overridding only updateAbsolutePosition() */ + virtual void updateAbsolutePosition(); + + virtual EGUISColorSelectEvent getEvent(); + virtual bool OnEvent( const SEvent& event ); +private: + bool onMouseEvent( const SEvent& event ); + void sendGUIEvent( EGUI_EVENT_TYPE pEventType, IGUIElement* pElement=0 ); + bool onKeyEvent( const SEvent& event ); + inline void setMouseState( c8 pState, bool pResetOthers=false ); + inline void resetMouseState( c8 pState ); + inline bool getMouseState( c8 pState ); + void setColorFieldFromPosition( vector2di pPos ); + void setHueFieldFromPosition( s32 x ); + void setHueFieldFromShift( s32 x ); + + void updateHueFieldRectangles(); + +public: + virtual void draw(); + + virtual const c8* getTypeName() const { return staticTypeName(); } + static const c8* staticTypeName() { return "sColorSelect"; } + + virtual void serializeAttributes( + irr::io::IAttributes* out, + irr::io::SAttributeReadWriteOptions* options=0 + ); + + virtual void deserializeAttributes( + irr::io::IAttributes* in, + irr::io::SAttributeReadWriteOptions* options=0 + ); +}; + +} +} + +#endif // #ifndef GUISCOLORSELECT_H diff --git a/IrrExtensions/gui/GUISProgressBar.h b/IrrExtensions/gui/GUISProgressBar.h new file mode 100644 index 0000000..2e9a922 --- /dev/null +++ b/IrrExtensions/gui/GUISProgressBar.h @@ -0,0 +1,71 @@ +// (c) 2015 Nicolaus Anderson + +#ifndef GUI_S_PROGRESS_BAR_H +#define GUI_S_PROGRESS_BAR_H + +#include +#include + +namespace irr { +namespace gui { + +class GUISProgressBar : public IGUIElement +{ + f32 value; + bool horiz; + video::SColor backColor; + +public: + GUISProgressBar( IGUIEnvironment* pEnv, IGUIElement* pParent, core::recti pRect, s32 id=-1, + f32 pStartValue=0, bool pHorizontal=true ) + : IGUIElement( EGUIET_ELEMENT, pEnv, pParent, id, pRect ) + , value( pStartValue ) + , horiz( pHorizontal ) + , backColor( 0xff222222 ) + {} + + void setProgress( f32 pPercent ) + { + value = pPercent; + } + + void setBackgroundColor( video::SColor pBackColor ) + { + backColor = pBackColor; + } + + virtual void draw() + { + Environment->getSkin()->draw3DSunkenPane(this, backColor, true, true, AbsoluteRect, &AbsoluteClippingRect ); + core::recti bar(AbsoluteRect); + if ( horiz ) + { + bar.LowerRightCorner.X = AbsoluteRect.UpperLeftCorner.X + (s32)(value * RelativeRect.getWidth()); + } else { + bar.LowerRightCorner.Y = AbsoluteRect.UpperLeftCorner.Y + (s32)(value * RelativeRect.getHeight()); + } + Environment->getSkin()->draw3DButtonPaneStandard(this, bar, &AbsoluteClippingRect); + } + + virtual const c8* getTypeName() const { return staticTypeName(); } + static const c8* staticTypeName() { return "sProgressBar"; } + + virtual void serializeAttributes( irr::io::IAttributes* out, irr::io::SAttributeReadWriteOptions* options ) + { + IGUIElement::serializeAttributes(out,options); + out->addFloat("Progress", value); + out->addColor("BackgroundColor", backColor); + } + + virtual void deserializeAttributes( irr::io::IAttributes* in, irr::io::SAttributeReadWriteOptions* options ) + { + IGUIElement::serializeAttributes(in,options); + setProgress( in->getAttributeAsFloat("Progress",value) ); + setBackgroundColor( in->getAttributeAsColor("BackgroundColor") ); + } +}; + +}} + + +#endif // #ifndef GUI_S_PROGRESS_BAR_H diff --git a/IrrExtensions/gui/GUIScrollPane.cpp b/IrrExtensions/gui/GUIScrollPane.cpp new file mode 100644 index 0000000..1b0893d --- /dev/null +++ b/IrrExtensions/gui/GUIScrollPane.cpp @@ -0,0 +1,258 @@ +// Copyright 2016 Nic Anderson +#include "GUIScrollPane.h" +#include +#include + +namespace irr { +namespace gui { + +GUIScrollPane::GUIScrollPane( IGUIEnvironment* pGUIEnvironment, IGUIElement* pParent, recti pRect, irr::s32 id ) + : IGUIElement( EGUIET_ELEMENT, pGUIEnvironment, pParent, id, pRect ) + , horizontalScrollBar(0) + , verticalScrollBar(0) + , barWidth(20) + , childWrapper(0) +{ + constructChildWrapper(pGUIEnvironment, pRect); + + recti r = recti(0, pRect.getHeight()-barWidth, pRect.getWidth(), pRect.getHeight()); + horizontalScrollBar = pGUIEnvironment->addScrollBar(true, r, this); + horizontalScrollBar->setAlignment(EGUIA_UPPERLEFT,EGUIA_LOWERRIGHT,EGUIA_LOWERRIGHT,EGUIA_LOWERRIGHT); + horizontalScrollBar->setSubElement(true); + horizontalScrollBar->setSmallStep(5); + horizontalScrollBar->setLargeStep(20); + horizontalScrollBar->setPos(0); + + r = recti(pRect.getWidth()-barWidth, 0, pRect.getWidth(), pRect.getHeight()-barWidth); + verticalScrollBar = pGUIEnvironment->addScrollBar(false, r, this); + verticalScrollBar->setAlignment(EGUIA_LOWERRIGHT, EGUIA_UPPERLEFT, EGUIA_LOWERRIGHT, EGUIA_LOWERRIGHT); + verticalScrollBar->setSubElement(true); + verticalScrollBar->setSmallStep(5); + verticalScrollBar->setLargeStep(20); + verticalScrollBar->setPos(0); +} + +GUIScrollPane::~GUIScrollPane() +{ +/* if ( horizontalScrollBar ) + horizontalScrollBar->drop(); + + if ( verticalScrollBar ) + verticalScrollBar->drop();*/ +} + +void GUIScrollPane::constructChildWrapper(IGUIEnvironment* pGUIEnvironment, recti position) +{ + recti r = position; + r.LowerRightCorner.X -= barWidth; + r.LowerRightCorner.Y -= barWidth; + childWrapper = new IGUIElement( EGUIET_ELEMENT, pGUIEnvironment, this, -1, r ); + childWrapper->drop(); // This element will handle the reference counting + childWrapper->setTabGroup(true); +} + +IGUIElement* GUIScrollPane::getChildWrapper() const +{ + return childWrapper; +} + +void GUIScrollPane::showHorizontalScrollBar(bool yes) +{ + recti r; + if ( yes && horizontalScrollBar->isVisible() == false ) + { + r = childWrapper->getRelativePosition(); + r.LowerRightCorner.X -= barWidth; + childWrapper->setRelativePosition(r); + + r = verticalScrollBar->getRelativePosition(); + r.LowerRightCorner.Y -= barWidth; + verticalScrollBar->setRelativePosition(r); + } else if ( !yes && horizontalScrollBar->isVisible() ) { + r = childWrapper->getRelativePosition(); + r.LowerRightCorner.X += barWidth; + childWrapper->setRelativePosition(r); + + r = verticalScrollBar->getRelativePosition(); + r.LowerRightCorner.Y += barWidth; + verticalScrollBar->setRelativePosition(r); + } + horizontalScrollBar->setVisible(yes); + horizontalScrollBar->setEnabled(yes); + recalculateChildBounds(); +} + +void GUIScrollPane::showVerticalScrollBar(bool yes) +{ + recti r; + if ( yes && verticalScrollBar->isVisible() == false ) + { + r = childWrapper->getRelativePosition(); + r.LowerRightCorner.Y -= barWidth; + childWrapper->setRelativePosition(r); + } else if ( !yes && verticalScrollBar->isVisible() ) { + r = childWrapper->getRelativePosition(); + r.LowerRightCorner.Y += barWidth; + childWrapper->setRelativePosition(r); + } + verticalScrollBar->setVisible(yes); + verticalScrollBar->setEnabled(yes); + recalculateChildBounds(); +} + +IGUIScrollBar* GUIScrollPane::getHorizontalBar() const +{ + return horizontalScrollBar; +} + +IGUIScrollBar* GUIScrollPane::getVerticalBar() const +{ + return verticalScrollBar; +} + +void GUIScrollPane::setRelativePosition( recti pPosition ) +{ + IGUIElement::setRelativePosition(pPosition); + updateAbsolutePosition(); +} + +void GUIScrollPane::addChild( IGUIElement* pElement ) +{ + // IGUIElement::addChild(pElement); + childWrapper->addChild(pElement); + childWrapper->updateAbsolutePosition(); + childStartPositions.push_back( + GUIElementPosition( pElement, pElement->getRelativePosition().UpperLeftCorner ) + ); + recalculateChildBounds(); + // Should I have added an offset? Chat clients would not benefit from it but other programs would. +} + +void GUIScrollPane::removeChild( IGUIElement* pElement ) +{ + u32 index = 0; + for ( ; index < childStartPositions.size(); index++ ) + { + if ( childStartPositions[index].element == pElement ) + { + childStartPositions.erase(index); + break; + } + } + childWrapper->removeChild(pElement); + recalculateChildBounds(); +} +void GUIScrollPane::clearChildren() +{ + core::list::ConstIterator kid = childWrapper->getChildren().begin(); + while ( kid != childWrapper->getChildren().end() ) { + childWrapper->removeChild(*kid); + kid = childWrapper->getChildren().begin(); + } +} + +void GUIScrollPane::updateAbsolutePosition() +{ + IGUIElement::updateAbsolutePosition(); + recalculateChildBounds(); +} + +bool GUIScrollPane::OnEvent(const SEvent& event) +{ + if ( !IsEnabled ) + return false; + + switch ( event.EventType ) + { + case EET_MOUSE_INPUT_EVENT: + break; + + case EET_KEY_INPUT_EVENT: + // Scroll on arrow keys? + // Remember to set the scroll bars and get the position from them + break; + + case EET_GUI_EVENT: + if ( event.GUIEvent.EventType == EGET_SCROLL_BAR_CHANGED ) + { + shiftChildrenToPosition( + horizontalScrollBar->getPos(), + verticalScrollBar->getPos() + ); + } + break; + + default: break; + } + + return IGUIElement::OnEvent( event ); +} + +void +GUIScrollPane::serializeAttributes(io::IAttributes* out, io::SAttributeReadWriteOptions* options) const { + IGUIElement::serializeAttributes(out, options); + + out->addBool("ShowHorizontalBar", horizontalScrollBar->isVisible() ); + out->addBool("ShowVerticalBar", verticalScrollBar->isVisible() ); +} + +void +GUIScrollPane::deserializeAttributes(io::IAttributes* in, io::SAttributeReadWriteOptions* options) { + IGUIElement::deserializeAttributes(in, options); + + showHorizontalScrollBar( in->getAttributeAsBool("ShowHorizontalBar", horizontalScrollBar->isVisible()) ); + showVerticalScrollBar( in->getAttributeAsBool("ShowVerticalBar", verticalScrollBar->isVisible()) ); +} + +void GUIScrollPane::recalculateChildBounds() +{ + if ( childWrapper->getChildren().size() == 0 ) { + childBounds = core::rect(); + return; + } + core::list::ConstIterator iter = childWrapper->getChildren().begin(); + childBounds = (*iter)->getRelativePosition(); + core::rect bound; + for ( ; iter != childWrapper->getChildren().end(); ++iter ) + { + bound = (*iter)->getRelativePosition(); + childBounds.addInternalPoint( bound.UpperLeftCorner ); + childBounds.addInternalPoint( bound.LowerRightCorner ); + } + + // Update the scroll bars + horizontalScrollBar->setMax( childBounds.getWidth() ); + verticalScrollBar->setMax( childBounds.getHeight() ); +} + +void GUIScrollPane::shiftChildrenToPosition( s32 x, s32 y ) +{ + if ( childWrapper->getChildren().size() == 0 ) + return; + // Since all children shift the same, only one start position is needed to find the shift + core::list::ConstIterator iter = childWrapper->getChildren().begin(); + core::rect bound = (*iter)->getRelativePosition(); + u32 i=0; + core::vector2d startPos, + currShift, + newShift; + + for ( ; i < childStartPositions.size(); ++i ) + { + if ( childStartPositions[i].element == *iter ) + { + startPos = childStartPositions[i].position; + break; + } + } + currShift = startPos - bound.UpperLeftCorner; + //newShift.set( currShift.X - x, currShift.Y - y ); // TODO: Uncomment and comment out the next line?? What was wrong? + newShift.set( 0, currShift.Y - y ); + + for ( ; iter != childWrapper->getChildren().end(); ++iter ) + { + (*iter)->move(newShift); + } +} + +}} diff --git a/IrrExtensions/gui/GUIScrollPane.h b/IrrExtensions/gui/GUIScrollPane.h new file mode 100644 index 0000000..debb004 --- /dev/null +++ b/IrrExtensions/gui/GUIScrollPane.h @@ -0,0 +1,66 @@ +// Copyright 2016 Nic Anderson +#ifndef GUIScrollPane_H +#define GUIScrollPane_H + +#include + +namespace irr { +namespace gui { + +class IGUIScrollBar; + +using irr::core::recti; + +class GUIScrollPane : public IGUIElement +{ +protected: + struct GUIElementPosition + { + IGUIElement* element; + core::vector2d position; + + GUIElementPosition( IGUIElement* e, core::vector2d p ) + : element(e), position(p) + {} + }; + + IGUIScrollBar* horizontalScrollBar; + IGUIScrollBar* verticalScrollBar; + u32 barWidth; // Width of a scroll width across its short side + IGUIElement* childWrapper; + core::array childStartPositions; // (not in same order as Children) + core::rect childBounds; // rectangle around all children + +public: + GUIScrollPane( IGUIEnvironment* pGUIEnvironment, IGUIElement* pParent, recti pRect, irr::s32 id=-1 ); + ~GUIScrollPane(); + + void constructChildWrapper(IGUIEnvironment* pGUIEnvironment, recti position); + IGUIElement* getChildWrapper() const; + + void showHorizontalScrollBar(bool yes); + void showVerticalScrollBar(bool yes); + IGUIScrollBar* getHorizontalBar() const; + IGUIScrollBar* getVerticalBar() const; + + virtual void setRelativePosition( recti pPosition ); + virtual void addChild( IGUIElement* pElement ); + virtual void removeChild( IGUIElement* pElement ); + void clearChildren(); + virtual void updateAbsolutePosition(); + virtual bool OnEvent(const SEvent& event); + + virtual void serializeAttributes(io::IAttributes*, io::SAttributeReadWriteOptions*) const; + virtual void deserializeAttributes(io::IAttributes* in, io::SAttributeReadWriteOptions* options=0); + + virtual const c8* getTypeName() const { return staticTypeName(); } + static const c8* staticTypeName() { return "scrollPane"; } + +protected: + void recalculateChildBounds(); + void shiftChildrenToPosition( s32 x, s32 y ); +}; + +}} + +#endif diff --git a/IrrExtensions/gui/GUITextureView.h b/IrrExtensions/gui/GUITextureView.h new file mode 100644 index 0000000..9df364d --- /dev/null +++ b/IrrExtensions/gui/GUITextureView.h @@ -0,0 +1,156 @@ +// (c) 2015 Nicolaus Anderson +/* + TODO: This may be removed in the future! +*/ + +#ifndef GUI_TEXTURE_H +#define GUI_TEXTURE_H + +#include +#include +//#include +#include + +namespace irr { +namespace gui { + +using core::recti; +using core::stringc; +using video::ITexture; +using video::IRenderTarget; +using video::IVideoDriver; + +// GUI element for displaying a texture, one that can be rendered to as a render target. +class GUITextureView : public IGUIElement +{ + ITexture* texture; + ITexture* depthStencil; + IRenderTarget* renderTarget; + io::path internalName; + bool useAlphaChannel; + bool removeTexturesOnDestroy; + +public: + // Texture-display version (no IImage version at the moment) + GUITextureView( ITexture* pTexture, IGUIEnvironment* pEnv, IGUIElement* pParent, recti pRect, s32 id=-1, bool pRemoveTexturesOnDestroy=true ) + : IGUIElement( EGUIET_ELEMENT, pEnv, pParent, id, pRect ) + , texture( pTexture ) + , depthStencil(0) + , renderTarget(0) + , useAlphaChannel(false) + , removeTexturesOnDestroy(pRemoveTexturesOnDestroy) + { + internalName = io::path(stringc("GUI_TEXTURE_") + stringc(id)); + } + + ~GUITextureView() + { + IVideoDriver* videoDriver = Environment->getVideoDriver(); + + if ( renderTarget ) + videoDriver->removeRenderTarget( renderTarget ); + + if ( removeTexturesOnDestroy ) + { + videoDriver->removeTexture( texture ); + videoDriver->removeTexture( depthStencil ); + } + } + + //! Set the texture + /* Sets the texture displayed by this GUI element. + Note: It is the responsibility of the programmer to ensure this texture is removed when + no longer needed. */ + void setTexture( ITexture* pTexture ) + { + texture = pTexture; + } + + ITexture* getTexture() + { + return texture; + } + +/* + // Commented out until I know how the depth stencil works + void setDepthStencil( ITexture* pDepthStencil ) + { + depthStencil = pDepthStencil; + } +*/ + + void setUseAlphaChannelOnDraw( bool yes ) + { + useAlphaChannel = yes; + } + + //! Get the render target + /* Creates and returns a render target. + Note: This will erase the pointer to any existing texture, so it is important + that you remove the texture yourself prior to calling this function. */ + IRenderTarget* getRenderTarget( bool pUseCurrentTargetSize=true ) // core::dimension2du pOverrideSize = core::dimension2du() ) + { + IVideoDriver* videoDriver; + dimension2du targetSize; + if ( !renderTarget ) + { + AbsoluteRect.repair(); // Ensure it is positive + videoDriver = Environment->getVideoDriver(); + // Using the AbsoluteRect size could be problematic since the texture size must be both: + // 1) Equal to or smaller than the screen size (since it shares the z-buffer) AND + // 2) A power of 2 in both width and height (which the screen size might not be) + + if ( pUseCurrentTargetSize ) + targetSize = videoDriver->getCurrentRenderTargetSize(); + else + targetSize = dimension2du( AbsoluteRect.getSize() ); + + texture = videoDriver->addRenderTargetTexture( targetSize, internalName, video::ECF_A8R8G8B8 ); + depthStencil = videoDriver->addRenderTargetTexture( targetSize, internalName + "Depth", video::ECF_G16R16F ); + // ^ What color format should the stencil buffer be for OpenGL? A8R8G8B8? + // User named Entity uses ECF_G16R16F, but I suppose it doesn't matter as long as its 32-bits. + + renderTarget = videoDriver->addRenderTarget(); + renderTarget->setTexture(texture, depthStencil); + } + return renderTarget; + } + + void removeRenderTarget() + { + if ( renderTarget ) + { + Environment->getVideoDriver()->removeRenderTarget(renderTarget); + renderTarget = 0; + } + } + + void setUseAlphaChannel( bool yes ) + { + useAlphaChannel = yes; + } + + virtual void draw() + { + if ( !texture ) return; + recti sourceRect( texture->getOriginalSize() ); + if ( isVisible() && isEnabled() ) + { + //Environment->getVideoDriver()->draw2DRectangle( video::SColor(0xffff0000), AbsoluteRect, &AbsoluteClippingRect ); + + // Yes, it is drawing, but the background is black, even though the lighting is working. + Environment->getVideoDriver()->draw2DImage( texture, AbsoluteRect.UpperLeftCorner, + sourceRect, &AbsoluteClippingRect, + video::SColor(0xffffffff), useAlphaChannel ); + } + } + + virtual const c8* getTypeName() const { return staticTypeName(); } + static const c8* staticTypeName() { return "textureView"; } + + // TODO: Serialization +}; + +}} + +#endif // #ifndef GUI_TEXTURE_H diff --git a/IrrExtensions/gui/GUITreeTable.cpp b/IrrExtensions/gui/GUITreeTable.cpp new file mode 100644 index 0000000..6a23c7f --- /dev/null +++ b/IrrExtensions/gui/GUITreeTable.cpp @@ -0,0 +1,1105 @@ +// (c) 2014 Nic Anderson + +#ifndef GUITREETABLE_CPP +#define GUITREETABLE_CPP + +#include "GUITreeTable.h" +#include + +using core::clamp; + +namespace irr { +namespace gui { + +GUITreeTableNode::GUITreeTableNode( IGUIWindow* pWindow, irrTreeNode* pNode ) + : window( pWindow ) + , node( pNode ) +{ + // Creates a bug for some reason + //if ( window ) + // window->grab(); +} + +GUITreeTableNode::GUITreeTableNode( const GUITreeTableNode& pOther ) + : window( pOther.window ) + , node( pOther.node ) +{ + // Is this needed or did it not matter? + //if ( window ) + // window->grab(); +} + +GUITreeTableNode::~GUITreeTableNode() +{ + // Creates a bug for some reason + //if ( window ) + // window->drop(); +} + +IGUIWindow* GUITreeTableNode::getWindow() +{ + return window; +} + +irrTreeNode* GUITreeTableNode::getTreeNode() +{ + return node; +} + +irrTreeElement* GUITreeTableNode::getTreeNodeElement() +{ + if ( node ) + return node->element; + return 0; +} + +void GUITreeTableNode::setWindow( IGUIWindow* pWindow ) +{ + // Creates a bug for some reason + //if ( pWindow ) + // pWindow->grab(); + + //if ( window ) + // window->drop(); + + window = pWindow; +} + +void GUITreeTableNode::setTreeNode( irrTreeNode* pNode ) +{ + node = pNode; +} + +void GUITreeTableNode::setTreeNodeElem( irrTreeElement* pElement ) +{ + if ( node ) + node->element = pElement; +} + +bool GUITreeTableNode::isAncestorOf( GUITreeTableNode* pNode ) +{ + irrTreeNode* childNode = pNode->getTreeNode(); + while ( childNode->getParent() ) + { + childNode = childNode->getParent(); + if ( childNode == node ) + return true; + } + return false; +} + +bool GUITreeTableNode::operator== ( const GUITreeTableNode& other ) +{ + return ( node == other.node ) && ( window == other.window ); +} + + +/* &&&&&&&&&&&&&&& Tree Table &&&&&&&&&&&&&& */ + +GUITreeTable::GUITreeTable( IGUIEnvironment* pEnvironment, + IGUIElement* pParent, recti pRect, s32 id ) + : IGUIElement( EGUIET_ELEMENT, pEnvironment, pParent, id, pRect ) + , treeRoot(0) + , elementFactory(0) + , selectedNode(0) + , nodeMenu(0) + , activity( EGTTABLEA_None ) + , lastEvent( EGTTE_None ) + , focusOnHover( false ) + , allowLinkingToParent( false ) + , canSelectedNodeLoseFocus( false ) + , lineThickness(2) + , nodeWindowSize(0,0,150,100) + , linkColor1(0xffcdcdcd) + , linkColor2(0xffdddddd) + , selectedHighlightColor(0x9900ffde) +{ +#ifdef _COMPILE_GUI_TREE_TABLE_NICE_ + backgroundColor2 = Environment->getSkin()->getColor(EGDC_WINDOW); + // Darken + backgroundColor2.setRed( clamp(backgroundColor2.getRed() - 20, (u32)0, (u32)255) ); + backgroundColor2.setGreen( clamp(backgroundColor2.getGreen() - 20, (u32)0, (u32)255) ); + backgroundColor2.setBlue( clamp(backgroundColor2.getBlue() - 20, (u32)0, (u32)255) ); +#endif +} + +GUITreeTable::~GUITreeTable() +{ + // Kill the node menu grabbed on its construction + nodeMenu->drop(); +} + +IGUIWindow* GUITreeTable::addNode( irrTreeElement* pElement ) +{ + vector2di winHalfSize = nodeWindowSize.LowerRightCorner/2; + IGUIWindow* window = Environment->addWindow( + //recti(lastMousePos-winHalfSize, + // lastMousePos+winHalfSize), + recti(firstMousePos-winHalfSize, + firstMousePos+winHalfSize), + false, // modal? + 0, // text? + this//, // parent + //++windowIds + ); + + window->grab(); /* Bugfix? Requires more looking into. + The program crashes when the window is not grabbed here. */ + window->setDraggable(true); + window->getMaximizeButton()->remove(); + window->getMinimizeButton()->remove(); + window->getCloseButton()->remove(); + + /* Forces the parent to respond first to window selection, + but also forces the table to send GUI events to the windows + by returning false for every unhandled event. */ + window->setSubElement(true); + + if ( elementFactory ) + { + if ( !pElement ) + pElement = elementFactory->buildElementOfWindow( window ); + else + elementFactory->buildElementOfWindow( window ); + } + irrTreeNode* node = new irrTreeNode( /*windowIds*/ 0, 0 ); + if ( pElement ) + node->setElem( pElement ); + + nodeList.push_back( GUITreeTableNode( window, node ) ); + + Environment->setFocus(window); + + return window; +} + +void GUITreeTable::removeSelectedNode() +{ + if ( ! selectedNode ) return; + + activity = EGTTABLEA_None; + canSelectedNodeLoseFocus = true; + + list::Iterator i = nodeList.begin(); + irrTreeNode* removableNode; + for ( ; i != nodeList.end(); ++i ) + { + if ( *selectedNode == *i ) + { + removableNode = i->getTreeNode(); + i->setTreeNode(0); + delete removableNode; + removeChild( i->getWindow() ); + i = nodeList.erase(i); + selectedNode = 0; + + Environment->setFocus(this); + + break; + } + } +} + +void GUITreeTable::removeNodeWithTreeElem( irrTreeElement* pElement ) +{ + activity = EGTTABLEA_None; + canSelectedNodeLoseFocus = true; + + list::Iterator i = nodeList.begin(); + irrTreeNode* removableNode; + for ( ; i != nodeList.end(); ++i ) + { + if ( (*i).getTreeNodeElement() == pElement ) + { + removableNode = i->getTreeNode(); + i->setTreeNode(0); + delete removableNode; + removeChild( i->getWindow() ); + i = nodeList.erase(i); + selectedNode = 0; + break; + } + } +} + +IGUIWindow* GUITreeTable::duplicateSelectedNode() +{ + GUITreeTableNode* node = getSelectedNode(); + IGUIWindow* returnWindow = 0; + if ( node ) + { + if ( elementFactory ) + { + returnWindow = addNode( elementFactory->buildElementDuplicateOf( node->getTreeNode()->getElem() ) ); + elementFactory->amendDuplicateWindow( returnWindow ); + } + } + return returnWindow; +} + +void GUITreeTable::clearAll(bool pClearWindows) +{ + activity = EGTTABLEA_None; + canSelectedNodeLoseFocus = true; + + list::Iterator i = nodeList.begin(); + if ( pClearWindows ) + { + for ( ; i != nodeList.end(); ++i ) + { + i->getWindow()->remove(); + } + } + nodeList.clear(); + treeRoot = 0; + selectedNode = 0; +} + +void GUITreeTable::clearAllTrees() +{ + activity = EGTTABLEA_None; + canSelectedNodeLoseFocus = true; + + irr::core::array* kids; + list::Iterator i = nodeList.begin(); + for ( ; i != nodeList.end(); ++i ) + { + kids = &( (*i).getTreeNode()->children ); + while ( kids->size() ) + kids->erase( kids->size() - 1 ); + } +} + +void GUITreeTable::setElementFactory( GUITreeTableElementFactory* pFactory ) +{ + elementFactory = pFactory; +} + +void GUITreeTable::setFocusOnHover( bool pFocus ) +{ + focusOnHover = pFocus; +} + +void GUITreeTable::setLinkLineThickness( s32 pThickness ) +{ + lineThickness = pThickness; +} + +void GUITreeTable::setNodeWindowRect( recti pRect ) +{ + nodeWindowSize = pRect; + nodeWindowSize.repair(); +} + +recti GUITreeTable::getNodeWindowRect() +{ + return nodeWindowSize; +} + +void GUITreeTable::setLinkColor1( irr::video::SColor pColor ) +{ + linkColor1 = pColor; +} + +void GUITreeTable::setLinkColor2( irr::video::SColor pColor ) +{ + linkColor2 = pColor; +} + +void GUITreeTable::setSelectedHighlightColor( irr::video::SColor pColor ) +{ + selectedHighlightColor = pColor; +} + +EGUITreeTableEvent GUITreeTable::getLastEventState() +{ + return lastEvent; +} + +IGUIWindow* GUITreeTable::getSelectedNodeWindow() +{ + if ( selectedNode ) + return selectedNode->getWindow(); + return 0; +} + +GUITreeTableNode* GUITreeTable::getSelectedNode() +{ + return selectedNode; +} + +GUITreeTableNode* GUITreeTable::getTreeRootNode() +{ + list::Iterator i = nodeList.begin(); + for ( ; i != nodeList.end(); ++i ) + { + if ( i->getTreeNode() == treeRoot ) + return &(*i); + } + return 0; +} + +void GUITreeTable::setTreeRootNode( GUITreeTableNode& pNewRoot ) +{ + /* Verify that the node is in the node list (before we blindly set it) */ + list::Iterator i = nodeList.begin(); + for ( ; i != nodeList.end(); ++i ) + { + if ( *i == pNewRoot ) + { + treeRoot = pNewRoot.getTreeNode(); + break; + } + } +} + +void GUITreeTable::setTreeRoot( irrTreeNode* pNewRoot ) +{ + /* Verify that the node is in the node list (before we blindly set it) */ + list::Iterator i = nodeList.begin(); + for ( ; i != nodeList.end(); ++i ) + { + if ( i->getTreeNode() == pNewRoot ) + { + treeRoot = pNewRoot; + break; + } + } +} + +list& GUITreeTable::getNodeList() +{ + return nodeList; +} + +IGUIContextMenu* GUITreeTable::getNodeMenu( bool pUpdatePosition ) +{ + if ( !nodeMenu ) + { + nodeMenu = Environment->addContextMenu( + //recti(0,0,175,175), + recti(0,0,1,1), + this, + -1 ); + + // Set the settings for the menu + nodeMenu->setCloseHandling( ECMC_REMOVE ); + + // Add items to the menu + nodeMenu->addItem( L"Add node", (s32)EGTTABLE_NMCI_Add, false ); + nodeMenu->addItem( L"Delete", (s32)EGTTABLE_NMCI_Delete, false ); + nodeMenu->addItem( L"Duplicate", (s32)EGTTABLE_NMCI_Duplicate, false ); + nodeMenu->addItem( L"Link to Child", (s32)EGTTABLE_NMCI_LinkToChild, false ); + nodeMenu->addItem( L"Link to Parent", (s32)EGTTABLE_NMCI_LinkToParent, false ); + nodeMenu->addItem( L"Break from Child", (s32)EGTTABLE_NMCI_BreakFromChild, false ); + nodeMenu->addItem( L"Break from Parent", (s32)EGTTABLE_NMCI_BreakFromParent, false ); + nodeMenu->addItem( L"Make root", (s32)EGTTABLE_NMCI_MakeRoot, false ); + + // Debug test + //assert( nodeMenu->findItemWithCommandId( EGTTABLE_NMCI_Add ) + // == EGTTABLE_NMCI_Add ); + + // Cancel out the removal process later (when the node calls IGUIElement::remove() ) + nodeMenu->grab(); + } + if ( pUpdatePosition ) + nodeMenu->setRelativePosition( + lastMousePos + - vector2di(0, nodeMenu->getAbsoluteClippingRect().getHeight()/2) + ); + + return nodeMenu; +} + +/* NOTE: You need to be careful with how you are using this thing. +The context menu itself will already close when it loses focus. +You don't have to set the focus for it. */ +void GUITreeTable::hideNodeMenu() +{ + nodeMenu->remove(); + //canSelectedNodeLoseFocus = true; +} + +bool GUITreeTable::OnEvent( const SEvent& event ) +{ + if ( isEnabled() && isVisible() ) + switch ( event.EventType ) + { + case irr::EET_MOUSE_INPUT_EVENT: + if ( handleMouseEvent( event ) ) + return true; + break; + case EET_KEY_INPUT_EVENT: + if ( handleKeyEvent( event ) ) + return true; + break; + case EET_GUI_EVENT: + if ( handleGuiEvent( event ) ) + return true; + break; + default: break; + } + + // Apparently no recent event, so clear event flag + lastEvent = EGTTE_None; + + return IGUIElement::OnEvent(event); +} + +void GUITreeTable::sendEvent() +{ + if ( !Parent ) return; + + SEvent newEvent; + newEvent.EventType = EET_GUI_EVENT; + newEvent.GUIEvent.Caller = this; + newEvent.GUIEvent.EventType = (EGUI_EVENT_TYPE)(EGET_COUNT+1); + Parent->OnEvent(newEvent); +} + +bool GUITreeTable::handleMouseEvent( const SEvent& event ) +{ + lastMousePos.set( event.MouseInput.X, event.MouseInput.Y ); + + /* Note: The check on the AbsoluteClippingRect has been removed since + this is done by the engine(??). It can be added again if need be. */ + + switch ( event.MouseInput.Event ) + { + /* Node selection is done regardless of the window focus. */ + case EMIE_LMOUSE_PRESSED_DOWN: + if ( activity == EGTTABLEA_Scroll ) // Was accidentally an assignment + { + activity = EGTTABLEA_None; + return true; + } + break; + + case EMIE_MMOUSE_PRESSED_DOWN: + if ( isPointInside( lastMousePos ) ) + { + // Steal focus + if ( Environment->getFocus() != this + && ! isMyChild(Environment->getFocus()) ) + { + Environment->setFocus(this); + lastEvent = EGTTE_NoSelectNode; + sendEvent(); + } + // Node menu automatically closes + activity = EGTTABLEA_Scroll; + firstMousePos = lastMousePos; + return true; + } + break; + + case EMIE_MMOUSE_LEFT_UP: + if ( activity == EGTTABLEA_Scroll ) + { + activity = EGTTABLEA_None; + return true; + } + break; + + case EMIE_MOUSE_MOVED: + if ( activity == EGTTABLEA_Scroll ) + { + moveChildrenBy(lastMousePos - firstMousePos); + firstMousePos = lastMousePos; + return true; + } + break; + + case EMIE_RMOUSE_PRESSED_DOWN: + activity = EGTTABLEA_None; + break; + + case EMIE_RMOUSE_LEFT_UP: + if ( activity == EGTTABLEA_None + && ( Environment->getFocus() == this || isMyChild( Environment->getFocus() ) ) + ) + { + activity = EGTTABLEA_UsingContextMenu; // VERY important + + if ( getNodeMenu()->getParent() != this ) + addChild( getNodeMenu() ); + + //canSelectedNodeLoseFocus = false; // MUST come before focus setting + Environment->setFocus(getNodeMenu(true)); + + if ( selectedNode ) + { + // Set the node menu properties for this specific node + nodeMenu->setItemEnabled( EGTTABLE_NMCI_Add, false ); + nodeMenu->setItemEnabled( EGTTABLE_NMCI_Delete, true ); + nodeMenu->setItemEnabled( EGTTABLE_NMCI_Duplicate, true ); + nodeMenu->setItemEnabled( EGTTABLE_NMCI_LinkToChild, true ); + nodeMenu->setItemEnabled( EGTTABLE_NMCI_LinkToParent, true ); + nodeMenu->setItemEnabled( EGTTABLE_NMCI_BreakFromChild, + (selectedNode->getTreeNode()->children.size() != 0 ) ); + nodeMenu->setItemEnabled( EGTTABLE_NMCI_BreakFromParent, + (selectedNode->getTreeNode()->parent != 0 ) + ); + nodeMenu->setItemEnabled( EGTTABLE_NMCI_MakeRoot, true ); + + } else { + // Set the node menu properties for this specific node + nodeMenu->setItemEnabled( EGTTABLE_NMCI_Add, true ); + nodeMenu->setItemEnabled( EGTTABLE_NMCI_Delete, false ); + nodeMenu->setItemEnabled( EGTTABLE_NMCI_Duplicate, false ); + nodeMenu->setItemEnabled( EGTTABLE_NMCI_LinkToChild, false ); + nodeMenu->setItemEnabled( EGTTABLE_NMCI_LinkToParent, false ); + nodeMenu->setItemEnabled( EGTTABLE_NMCI_BreakFromChild, false ); + nodeMenu->setItemEnabled( EGTTABLE_NMCI_BreakFromParent, false ); + nodeMenu->setItemEnabled( EGTTABLE_NMCI_MakeRoot, false ); + } + lastEvent = EGTTE_OpenNodeMenu; + sendEvent(); + return true; + } + else { + // Kill all activity + activity = EGTTABLEA_None; + if ( isPointInside( lastMousePos ) ) + { + selectedNode = getNodeAt( lastMousePos ); + // Don't care about the window + if ( selectedNode ) + Environment->setFocus( selectedNode->getWindow() ); + return true; + } + } + break; + + default: + // Make position correction for allowing adding nodes here + firstMousePos = lastMousePos; + break; + } + + return false; +} + +bool GUITreeTable::handleKeyEvent( const SEvent& event ) +{ + // Don't handle events when the key is not pressed + if ( ! event.KeyInput.PressedDown ) + return false; + + /* Selected-node-specific events */ + if ( selectedNode && Environment->getFocus() == selectedNode->getWindow() ) + { + switch ( event.KeyInput.Key ) + { + case KEY_DELETE: + lastEvent = EGTTE_RemoveNode; + sendEvent(); + removeSelectedNode(); + return true; + + case KEY_KEY_D: + lastEvent = EGTTE_DuplicateNode; + sendEvent(); + duplicateSelectedNode(); + return true; + + case KEY_UP: + selectedNode->getWindow()->move( vector2di(0,-30) ); + return true; + + case KEY_DOWN: + selectedNode->getWindow()->move( vector2di(0,30) ); + return true; + + case KEY_RIGHT: + selectedNode->getWindow()->move( vector2di(30,0) ); + return true; + + case KEY_LEFT: + selectedNode->getWindow()->move( vector2di(-30,0) ); + return true; + + default: break; + } + } + + if ( Environment->getFocus() == this ) + switch( event.KeyInput.Key ) + { + case KEY_ADD: // by the Numpad + case KEY_PLUS: // near the backspace + lastEvent = EGTTE_AddNode; + sendEvent(); + addNode(); + return true; + + case KEY_UP: + moveChildrenBy( vector2di(0,100) ); + return true; + + case KEY_DOWN: + moveChildrenBy( vector2di(0,-100) ); + return true; + + case KEY_RIGHT: + moveChildrenBy( vector2di(-100,0) ); + return true; + + case KEY_LEFT: + moveChildrenBy( vector2di(100,0) ); + return true; + + default: break; + } + return false; +} + +bool GUITreeTable::handleGuiEvent( const SEvent& event ) +{ + GUITreeTableNode* tableNode; + + switch ( event.GUIEvent.EventType ) + { + case EGET_ELEMENT_FOCUS_LOST: + /* Lose the selection only if the mouse is outside of the element. */ + /* Note that the menu passes back the focus-lost event BEFORE it removes + itself from the parent. */ + if ( selectedNode && selectedNode->getWindow() == event.GUIEvent.Caller ) + { + switch ( activity ) + { + case EGTTABLEA_UsingContextMenu: // requires selection be kept + case EGTTABLEA_LinkingNodeToChild: + case EGTTABLEA_LinkingNodeToParent: + case EGTTABLEA_BreakFromChild: + break; + default: + // Don't deselect if the mouse isn't even inside the table area + if ( isPointInside( lastMousePos ) ) + { + if ( ! selectedNode->getWindow()->isPointInside(lastMousePos) ) + { + lastEvent = EGTTE_NoSelectNode; + sendEvent(); + activity = EGTTABLEA_None; + selectedNode = 0; + } + } + break; + } + } + else if ( event.GUIEvent.Caller == this ) + { + activity = EGTTABLEA_None; + canSelectedNodeLoseFocus = true; + } + // Caller is not this OR a child OR the menu (must be outside click) + return false; // returning "true" prevents environment from changing focus + + case EGET_ELEMENT_FOCUSED: + /* Focus events are called before mouse events. But rather than + relying on the focus also being the selection, it is merely + a means to make a selection. */ + { + if ( event.GUIEvent.Caller == this && activity == EGTTABLEA_None ) + { + if ( Parent ) + Parent->bringToFront(this); + + lastEvent = EGTTE_NoSelectNode; + sendEvent(); + if ( selectedNode ) + { + // Drop activity + activity = EGTTABLEA_None; + canSelectedNodeLoseFocus = true; + selectedNode = 0; + } + break; + } + + // Intercept selection + if ( isMyChild( event.GUIEvent.Caller ) + && event.GUIEvent.Caller->getType() == EGUIET_WINDOW ) + { + switch( activity ) + { + case EGTTABLEA_Scroll: + activity = EGTTABLEA_None; + break; + + case EGTTABLEA_LinkingNodeToChild: + tableNode = getNodeWithWindow( (IGUIWindow*) event.GUIEvent.Caller ); + if ( tableNode && (tableNode != selectedNode) + && (allowLinkingToParent || !tableNode->isAncestorOf(selectedNode)) ) + { + selectedNode->getTreeNode()->stealNode( *(tableNode->getTreeNode()) ); + } + activity = EGTTABLEA_None; + lastEvent = EGTTE_LinkToChild; + canSelectedNodeLoseFocus = true; + sendEvent(); + return true; // Prevent changing focus to clicked node + + case EGTTABLEA_LinkingNodeToParent: + tableNode = getNodeWithWindow( (IGUIWindow*) event.GUIEvent.Caller ); + if ( tableNode && (tableNode != selectedNode) + && (allowLinkingToParent || !selectedNode->isAncestorOf(tableNode)) ) + { + tableNode->getTreeNode()->stealNode( *(selectedNode->getTreeNode()) ); + } + activity = EGTTABLEA_None; + lastEvent = EGTTE_LinkToParent; + canSelectedNodeLoseFocus = true; + sendEvent(); + return true; // Prevent changing focus to clicked node + + case EGTTABLEA_BreakFromChild: + tableNode = getNodeWithWindow( (IGUIWindow*) event.GUIEvent.Caller ); + if ( tableNode && (tableNode != selectedNode) ) + { + if ( tableNode->getTreeNode()->parent == selectedNode->getTreeNode() ) + { + tableNode->getTreeNode()->parent = 0; + selectedNode->getTreeNode()->removeChild( tableNode->getTreeNode() ); + } + } + activity = EGTTABLEA_None; + lastEvent = EGTTE_BreakFromChild; + canSelectedNodeLoseFocus = true; + sendEvent(); + return true; // Prevent changing focus to clicked node + + case EGTTABLEA_UsingContextMenu: + default: + // Select node with this window + selectNodeWithWindow( (IGUIWindow*) event.GUIEvent.Caller ); + if ( selectedNode ) + { + lastEvent = EGTTE_SelectNode; + } else { + lastEvent = EGTTE_NoSelectNode; + } + sendEvent(); + break; + } + + } + } + break; + + case EGET_ELEMENT_HOVERED: + if ( focusOnHover ) + if ( event.GUIEvent.Caller == this ) + { + if ( Environment->getFocus() != this && !isMyChild(Environment->getFocus()) ) + Environment->setFocus(this); + return true; /* Prevent hovering bugs from a parent setting this to focus + by handling the event. */ + } + break; + + //case EGET_ELEMENT_CLOSED: + // /* Called by the menu when it loses focus! */ + // break; + + case EGET_MENU_ITEM_SELECTED: + if ( event.GUIEvent.Caller == nodeMenu ) + { + // Find out what the menu selected + return sendMenuEvent( (EGUITreeTableNodeMenuCommandId) nodeMenu->getItemCommandId( nodeMenu->getSelectedItem() ) ); + } + break; + + default: + break; + } + + return false; +} + +bool GUITreeTable::sendMenuEvent( EGUITreeTableNodeMenuCommandId pCommand ) +{ + switch( pCommand ) + { + case EGTTABLE_NMCI_Add: + addNode(); + lastEvent = EGTTE_AddNode; + sendEvent(); + activity = EGTTABLEA_None; + return true; + + case EGTTABLE_NMCI_Delete: + lastEvent = EGTTE_RemoveNode; + sendEvent(); + removeSelectedNode(); + // OPTIMIZATION: refreshLinkCache(); + //activity = EGTTABLEA_None; // unnecessary + return true; + + case EGTTABLE_NMCI_Duplicate: + lastEvent = EGTTE_DuplicateNode; + sendEvent(); + duplicateSelectedNode(); + activity = EGTTABLEA_None; + return true; + + case EGTTABLE_NMCI_LinkToChild: + activity = EGTTABLEA_LinkingNodeToChild; + // OPTIMIZATION: refreshLinkCache(); + return true; + + case EGTTABLE_NMCI_LinkToParent: + activity = EGTTABLEA_LinkingNodeToParent; + // OPTIMIZATION: refreshLinkCache(); + return true; + + case EGTTABLE_NMCI_BreakFromChild: + activity = EGTTABLEA_BreakFromChild; + // OPTIMIZATION: refreshLinkCache(); + return true; + + case EGTTABLE_NMCI_BreakFromParent: + if ( selectedNode ) + { + if ( selectedNode->getTreeNode()->parent ) + { + selectedNode->getTreeNode()->parent->removeChild( + selectedNode->getTreeNode() + ); + // OPTIMIZATION: refreshLinkCache(); + } + selectedNode->getTreeNode()->parent = 0; + } + activity = EGTTABLEA_None; + return true; + + case EGTTABLE_NMCI_MakeRoot: + if ( selectedNode ) + { + lastEvent = EGTTE_PreMakeRoot; + sendEvent(); + treeRoot = selectedNode->getTreeNode(); + lastEvent = EGTTE_PostMakeRoot; + sendEvent(); + } + activity = EGTTABLEA_None; + return true; + + default: + activity = EGTTABLEA_None; + break; + } + + return false; +} + +void GUITreeTable::draw() +{ + if ( !isVisible() ) + return; + + /* Children of this element are not drawn because that would require + checking to see if such children were windows in the list of nodes. + This slows the drawing process. Furthermore, there shouldn't be + other types of GUI elements as direct children of this element anyways. */ + + // Draw a background +#ifdef _COMPILE_GUI_TREE_TABLE_NICE_ + Environment->getVideoDriver()->draw2DRectangle( AbsoluteRect, + Environment->getSkin()->getColor(EGDC_WINDOW), + Environment->getSkin()->getColor(EGDC_WINDOW), + backgroundColor2, + backgroundColor2, + &AbsoluteClippingRect + ); +#else + Environment->getSkin()->draw2DRectangle( + this, + Environment->getSkin()->getColor(EGDC_WINDOW), + AbsoluteRect, + &AbsoluteClippingRect); +#endif + + /* Go through the list and draw all of the links between nodes + for the tree. + OPTIMIZATION: + The links should be cached and drawn. Then the following process + would only be done when the links change. + The cached links can be moved (rather than rebuilt) when the middle + mouse button is pressed and the mouse moves, thereby saving time + in reconstruction. + With all of that, the draw process is reduced to a single for-loop, + which may not need to go through all of the nodes (since some may + not even be connected). */ + u32 c; + recti r; + core::list::Iterator nodeIter = nodeList.begin(); + GUITreeTableNode* ttnode; + core::array* children; + //for ( u32 n=0; n < nodeList.size(); n++ ) + for ( ; nodeIter != nodeList.end(); ++nodeIter ) + { + if ( selectedNode && (nodeIter->getWindow() == selectedNode->getWindow()) ) + { + // Draw a box around the selected node + r = nodeIter->getWindow()->getAbsolutePosition(); + r.UpperLeftCorner -= vector2di(2,2); + r.LowerRightCorner += vector2di(2,2); + Environment->getSkin()->draw2DRectangle( + this, + selectedHighlightColor, + r, + &AbsoluteClippingRect + ); + } + + children = &(nodeIter->getTreeNode()->children); + for ( c=0; c < children->size(); c++ ) + { + ttnode = findListNodeWithTreeNode( (*children)[c] ); + if ( ttnode ) + { + drawLink( nodeIter->getWindow(), ttnode->getWindow() ); + } + } + } + + // Draw the windows + IGUIElement::draw(); +} + +GUITreeTableNode* GUITreeTable::findListNodeWithTreeNode( irrTreeNode* pNode ) +{ + list::Iterator i = nodeList.begin(); + for ( ; i != nodeList.end(); ++i ) + { + if ( i->getTreeNode() == pNode ) + return &(*i); + } + return 0; +} + +void GUITreeTable::drawLink( IGUIElement* pFromElement, IGUIElement* pToElement ) +{ + vector2di pt1 = pFromElement->getAbsolutePosition().getCenter(); + vector2di pt3 = pToElement->getAbsolutePosition().getCenter(); + + s32 hf = (pt1.X < pt3.X) ? 1 : -1; + s32 vf = (pt1.Y < pt3.Y) ? 1 : -1; + recti line1( pt1.X, pt1.Y, pt3.X, pt1.Y+lineThickness*hf); + recti line2( pt3.X-lineThickness*vf, pt1.Y+lineThickness*hf, pt3.X, pt3.Y ); + line1.repair(); + line2.repair(); + + if ( hf > 0 ) + { + Environment->getVideoDriver()->draw2DRectangle( + line1, + linkColor1, // left up + linkColor2, // right up + linkColor1, // left down + linkColor2, // right down + &AbsoluteClippingRect + ); + } else { + Environment->getVideoDriver()->draw2DRectangle( + line1, + linkColor2, // left up + linkColor1, // right up + linkColor2, // left down + linkColor1, // right down + &AbsoluteClippingRect + ); + } + + if ( vf > 0 ) + { + Environment->getVideoDriver()->draw2DRectangle( + line2, + linkColor1, // left up + linkColor1, // right up + linkColor2, // left down + linkColor2, // right down + &AbsoluteClippingRect + ); + } else { + Environment->getVideoDriver()->draw2DRectangle( + line2, + linkColor2, // left up + linkColor2, // right up + linkColor1, // left down + linkColor1, // right down + &AbsoluteClippingRect + ); + } +} + +GUITreeTableNode* GUITreeTable::getNodeAt( vector2di pPos ) +{ + list::Iterator i = nodeList.begin(); + for ( ; i != nodeList.end(); ++i ) + { + if ( i->getWindow()->getAbsolutePosition().isPointInside(pPos) ) + return &(*i); + } + return 0; +} + +void GUITreeTable::selectNodeAt( vector2di pPos ) +{ + selectedNode = getNodeAt( pPos ); +} + +GUITreeTableNode* GUITreeTable::getNodeWithWindow( IGUIWindow* pWindow ) +{ + list::Iterator i = nodeList.begin(); + for ( ; i != nodeList.end(); ++i ) + { + if ( i->getWindow() == pWindow ) + { + return &(*i); + } + } + return 0; +} + +void GUITreeTable::selectNodeWithWindow( IGUIWindow* pWindow ) +{ + selectedNode = getNodeWithWindow( pWindow ); +} + +void GUITreeTable::moveChildrenBy( vector2di pDistance ) +{ + core::list::Iterator c = Children.begin(); + for ( ; c != Children.end(); ++c ) + { + (*c)->move( pDistance ); + } + + // OPTIMIZATION: refreshLinkCache(); +} + +recti GUITreeTable::getEnclosingRect() +{ + recti border, winRect; + bool set = false; + list::Iterator i = nodeList.begin(); + for ( ; i != nodeList.end(); ++i ) + { + winRect = i->getWindow()->getRelativePosition(); + if ( set ) + { + border.addInternalPoint( winRect.UpperLeftCorner ); + border.addInternalPoint( winRect.LowerRightCorner ); + } else { + border = winRect; + set = true; + } + } + return border; +} + +}} + +#endif diff --git a/IrrExtensions/gui/GUITreeTable.h b/IrrExtensions/gui/GUITreeTable.h new file mode 100644 index 0000000..4d6198f --- /dev/null +++ b/IrrExtensions/gui/GUITreeTable.h @@ -0,0 +1,289 @@ +// (c) 2014 Nic Anderson + +#ifndef GUITREETABLE_H +#define GUITREETABLE_H + +#include +#include "../util/irrTree/irrTree.h" +#include + +/* Compile option: _COMPILE_GUI_TREE_TABLE_NICE_ +Creates a gradient-background, but does not allow the skin +to control the drawing of the background. +It is also costly for rendering. +*/ + +namespace irr { +namespace gui { + +using irr::core::clamp; +using irr::core::list; +using irr::core::vector2di; +using irr::core::recti; + +enum EGUITreeTableActivity +{ + // No activity + EGTTABLEA_None, + + /* When the user uses their middle mouse button, they can move all of + the windows. However, they are not allowed to do other activities. */ + EGTTABLEA_Scroll, + + /* When a context menu is open, the user's mouse is monitored for + whether or not the menu is closed. When the context menu is in use, + other activities (such as node linking) cannot be done. However, those + activites can be a consequence of using the menu. + + Since the context menu can close itself, this is used only + to indicate if the activity should be monitored. + If removed, the buttons of this menu will not be + clickable. However, I will have to either + a) reconstruct the menu every time (takes time) + b) remove the main table GUI from being the parent until it is needed, + which means I have to monitor the GUI event of the context menu losing + focus, which shouldn't be too hard. */ + EGTTABLEA_UsingContextMenu, + + /* When a user has clicked a node and chosen to make it the parent + of the next selected node. Note that, in order to make this succeed, + the soon-to-be-child must be delinked from any previous parent. */ + EGTTABLEA_LinkingNodeToChild, + + /* When a user has clicked a node and chosen to make it the child + of the next selected node. Note that, in order to make this succeed, + the child must be delinked from any previous parent. */ + EGTTABLEA_LinkingNodeToParent, + + /* When a user has clicked a node and chosen to separate it from its + child. Then the child must be selected. */ + EGTTABLEA_BreakFromChild, + + /* When the user decides to disconnect this node from its parent node. + NOTE: This action occurs immediately and therefore does not exist as + a regular action but is listed here for reference. */ + //EGTTABLEA_BreakFromParent, + + // Number of activities + EGTTABLEA_COUNT +}; + +enum EGUITreeTableNodeMenuCommandId +{ + // Add a new node to the list + EGTTABLE_NMCI_Add=0, + + // Delete the selected node + EGTTABLE_NMCI_Delete, + + // Duplicate the selected node + EGTTABLE_NMCI_Duplicate, + + // Link the selected node to a child + EGTTABLE_NMCI_LinkToChild, + + // Link the selected node to a parent + EGTTABLE_NMCI_LinkToParent, + + // Break link from the selected node to a selected child + EGTTABLE_NMCI_BreakFromChild, + + // Break link from the selected node to its parent and vice versa + EGTTABLE_NMCI_BreakFromParent, + + // Make this node the root node + EGTTABLE_NMCI_MakeRoot, + + // Number of nodes + EGTTABLE_NMCI_COUNT +}; + +/* The GUI event states, which can be checked with +GUITreeTable::getLastEventState(), and that way, we never +have to worry about extending EGUI_EVENT_TYPE for this +generic element. */ +enum EGUITreeTableEvent +{ + EGTTE_None, + EGTTE_SelectNode, + EGTTE_NoSelectNode, // No node selected (or current selection deselected) + //EGTTE_MoveNodes, // Not used - it would slow things down + EGTTE_OpenNodeMenu, + EGTTE_AddNode, + EGTTE_DuplicateNode, + EGTTE_RemoveNode, + EGTTE_LinkToChild, + EGTTE_LinkToParent, + EGTTE_BreakFromChild, + EGTTE_BreakFromParent, + EGTTE_PreMakeRoot, + EGTTE_PostMakeRoot, + EGTTE_COUNT +}; + +/* Class GUI Tree Table Element Factory +Inherit this class and pass it to GUITreeTable to allow for creating +the nodes that go in the tree table. +*/ +class GUITreeTableElementFactory +{ +public: + /* Meant for the user to take the window in the tree table and + modify it as they please, returning the tree element that is + represented by the window. */ + virtual irrTreeElement* buildElementOfWindow( IGUIWindow* pWindow )=0; + + /* Meant for duplicating the given tree element, especially + when 1) there is no copy-constructor for the element or + 2) the programmer does not want the element to be directy copied. */ + virtual irrTreeElement* buildElementDuplicateOf( irrTreeElement* pElement )=0; + + /* Meant for when duplicating the selected node. + If something extra needs to be done to the duplicate (or something needs + to be updated concerning it), it can be done via this function. + \param pWindow - The window of the duplicate node. */ + virtual void amendDuplicateWindow( IGUIWindow* pWindow )=0; +}; + +/* Class GUI Tree Table Node +Contains a pointer to the node itself and to the window representing it. */ +class GUITreeTableNode +{ + IGUIWindow* window; + irrTreeNode* node; + +public: + GUITreeTableNode( IGUIWindow* pWindow, irrTreeNode* pNode ); + GUITreeTableNode( const GUITreeTableNode& pOther ); + ~GUITreeTableNode(); + + IGUIWindow* getWindow(); + irrTreeNode* getTreeNode(); + irrTreeElement* getTreeNodeElement(); + void setWindow( IGUIWindow* pWindow ); + void setTreeNode( irrTreeNode* pNode ); + void setTreeNodeElem( irrTreeElement* pElement ); + bool isAncestorOf( GUITreeTableNode* pNode ); + + bool operator== ( const GUITreeTableNode& other ); +}; + +/* Class GUI Tree Table + +This class is a table containing a tree and a list of nodes. +All of the nodes are in the list, including the root (which cannot be +deleted), but, with the exception of the root, none of the nodes need +to be in the tree. +The table is merely a control for a bunch of windows that compose the +tree. These windows can be used by the user to connect or disconnect +the tree. The windows also contain whatever the user wants. +*/ +class GUITreeTable : public IGUIElement +{ + list nodeList; + irrTreeNode* treeRoot; + GUITreeTableElementFactory* elementFactory; + + vector2di lastMousePos, firstMousePos; + GUITreeTableNode* selectedNode; + IGUIContextMenu* nodeMenu; + + EGUITreeTableActivity activity; + EGUITreeTableEvent lastEvent; + + bool focusOnHover; + bool allowLinkingToParent; + + bool canSelectedNodeLoseFocus; + s32 lineThickness; + recti nodeWindowSize; + + irr::video::SColor + linkColor1, + linkColor2, + selectedHighlightColor; + +#ifdef _COMPILE_GUI_TREE_TABLE_NICE_ + irr::video::SColor backgroundColor2; +#endif + +public: + GUITreeTable( IGUIEnvironment* pEnvironment, + IGUIElement* pParent, + recti pRect, s32 id=-1 ); + ~GUITreeTable(); + + /* Add a node at the last mouse location. + \param pElement - The element to be contained in this node. + \return - Window of the new node. */ + IGUIWindow* addNode( irrTreeElement* pElement=0 ); + + void removeSelectedNode(); + void removeNodeWithTreeElem( irrTreeElement* pElement ); + + /* Duplicate the selected node and add the duplicate at the last mouse location. + \param pElement - The element to be contained in this node. + \return - Window of the new node. */ + IGUIWindow* duplicateSelectedNode(); + + void clearAll(bool pClearWindows=true); // kills everything + void clearAllTrees(); // no clearList() because then all would have to die + + void setElementFactory( GUITreeTableElementFactory* pFactory ); + void setFocusOnHover( bool pFocus ); // (sets the Environment focus to this when hovering over it) + void setLinkLineThickness( s32 pThickness ); + void setNodeWindowRect( recti pRect ); + recti getNodeWindowRect(); + void setLinkColor1( irr::video::SColor pColor ); + void setLinkColor2( irr::video::SColor pColor ); + void setSelectedHighlightColor( irr::video::SColor pColor ); + + EGUITreeTableEvent getLastEventState(); + IGUIWindow* getSelectedNodeWindow(); + GUITreeTableNode* getSelectedNode(); + GUITreeTableNode* getTreeRootNode(); + + /* Set the tree root by giving the node. */ + void setTreeRootNode( GUITreeTableNode& pNewRoot ); + void setTreeRoot( irrTreeNode* pNewRoot ); + list& getNodeList(); + +protected: + IGUIContextMenu* getNodeMenu( bool pUpdatePosition=false ); + inline void hideNodeMenu(); + +public: + virtual bool OnEvent( const SEvent& event ); + bool sendMenuEvent( EGUITreeTableNodeMenuCommandId pCommand ); + +protected: + void sendEvent(); + +public: + virtual void draw(); + + virtual const c8* getTypeName() const { return staticTypeName(); } + static const c8* staticTypeName() { return "treeTable"; } + +protected: + GUITreeTableNode* findListNodeWithTreeNode( irrTreeNode* pNode ); + void drawLink( IGUIElement* pFromElement, IGUIElement* pToElement ); + bool handleMouseEvent( const SEvent& event ); + bool handleKeyEvent( const SEvent& event ); + bool handleGuiEvent( const SEvent& event ); + + GUITreeTableNode* getNodeAt( vector2di pPos ); + GUITreeTableNode* getNodeWithWindow( IGUIWindow* pWindow ); + + //u32 getSelectedNodeIndex(); + void selectNodeAt( vector2di pPos ); + void selectNodeWithWindow( IGUIWindow* pWindow ); + +public: + void moveChildrenBy( vector2di pDistance ); + recti getEnclosingRect(); // Relative (to parent) rectangle that wraps around all node windows +}; + +}} + +#endif diff --git a/IrrExtensions/gui/GUIVectorPanel.cpp b/IrrExtensions/gui/GUIVectorPanel.cpp new file mode 100644 index 0000000..376e55b --- /dev/null +++ b/IrrExtensions/gui/GUIVectorPanel.cpp @@ -0,0 +1,248 @@ +// (c) 2015 Nicolaus Anderson + +#ifndef GUI_VECTOR_PANEL_CPP +#define GUI_VECTOR_PANEL_CPP + +#include "GUIVectorPanel.h" +#include +#include + +namespace irr { +namespace gui { + +GUIVectorPanel::GUIVectorPanel( IGUIEnvironment* pEnvironment, IGUIElement* pParent, rect pRect, bool pEdit3D, s32 id ) + : IGUIElement( EGUIET_ELEMENT, pEnvironment, pParent, id, pRect ) + , X(0) + , Y(0) + , Z(0) + , edit3D( pEdit3D ) + , dirtyRect( false ) + , xEdit( 0 ) + , yEdit( 0 ) + , zEdit( 0 ) +{ + s32 coordBoxWidth; + s32 pad = 5; + if ( edit3D ) + { + coordBoxWidth = (pRect.getWidth() - 2*pad) / 3; + } else { + coordBoxWidth = (pRect.getWidth() - pad) / 2; + } + rect valueRect( 0, 0, coordBoxWidth, pRect.getHeight() ); + core::vector2di valueRectShift( coordBoxWidth + pad, 0 ); + + xEdit = pEnvironment->addSpinBox( L"0.0", valueRect, true, this ); + xEdit->setAlignment( EGUIA_UPPERLEFT, EGUIA_LOWERRIGHT, EGUIA_UPPERLEFT, EGUIA_LOWERRIGHT ); + xEdit->setStepSize(0.1f); + xEdit->setValidateOn(EGUI_SBV_CHANGE); // Requirement for irrlicht 1.9 to update on any change to the spin box + + //valueRect.move( coordBoxWidth + pad, 0 ); // custom Irrlicht + valueRect.UpperLeftCorner += valueRectShift; + valueRect.LowerRightCorner += valueRectShift; + yEdit = pEnvironment->addSpinBox( L"0.0", valueRect, true, this ); + yEdit->setAlignment( EGUIA_UPPERLEFT, EGUIA_LOWERRIGHT, EGUIA_UPPERLEFT, EGUIA_LOWERRIGHT ); + yEdit->setStepSize(0.1f); + yEdit->setValidateOn(EGUI_SBV_CHANGE); // Requirement for irrlicht 1.9 to update on any change to the spin box + + //valueRect.move( coordBoxWidth + pad, 0 ); // custom Irrlicht + valueRect.UpperLeftCorner += valueRectShift; + valueRect.LowerRightCorner += valueRectShift; + zEdit = pEnvironment->addSpinBox( L"0.0", valueRect, true, this ); + zEdit->setAlignment( EGUIA_UPPERLEFT, EGUIA_LOWERRIGHT, EGUIA_UPPERLEFT, EGUIA_LOWERRIGHT ); + zEdit->setStepSize(0.1f); + zEdit->setValidateOn(EGUI_SBV_CHANGE); // Requirement for irrlicht 1.9 to update on any change to the spin box + + if ( ! edit3D ) + { + zEdit->setVisible(false); + } +} + +void GUIVectorPanel::set2DValue( f32 pX, f32 pY ) +{ + X = pX; + Y = pY; + xEdit->setValue( X ); + yEdit->setValue( Y ); +} + +void GUIVectorPanel::set2DVectorValue( vector2d pValue ) +{ + X = pValue.X; + Y = pValue.Y; + xEdit->setValue( X ); + yEdit->setValue( Y ); +} + +void GUIVectorPanel::set3DValue( f32 pX, f32 pY, f32 pZ ) +{ + X = pX; + Y = pY; + Z = pZ; + xEdit->setValue( X ); + yEdit->setValue( Y ); + zEdit->setValue( Z ); +} + +void GUIVectorPanel::set3DVectorValue( vector3d pValue ) +{ + X = pValue.X; + Y = pValue.Y; + Z = pValue.Z; + xEdit->setValue( X ); + yEdit->setValue( Y ); + zEdit->setValue( Z ); +} + +void GUIVectorPanel::reset() +{ + X = 0; + Y = 0; + Z = 0; + xEdit->setValue( X ); + yEdit->setValue( Y ); + zEdit->setValue( Z ); +} + +void GUIVectorPanel::setEdit2DVector() // Force editing a 2D vector +{ + edit3D = false; + dirtyRect = true; + updateAbsolutePosition(); +} + +void GUIVectorPanel::setEdit3DVector() // Force editing a 3D vector +{ + edit3D = true; + dirtyRect = true; + updateAbsolutePosition(); +} + +bool GUIVectorPanel::isEditing3D() +{ + return edit3D; +} + +vector2d GUIVectorPanel::getVector2D() +{ + return vector2d( X, Y ); +} + +vector3d GUIVectorPanel::getVector3D() +{ + return vector3d( X, Y, Z ); +} + +void GUIVectorPanel::updateAbsolutePosition() +{ + // Needs to be fixed? + s32 coordBoxScale; + f32 fpad = 5.0f / (f32) RelativeRect.getWidth(); + if ( dirtyRect ) + { + if ( edit3D ) + { + coordBoxScale = 1.f/3.f - fpad*2; + } else { + coordBoxScale = 0.5f - fpad; + } + rect valueRect( 0, 0, coordBoxScale, 1.0f ); + + xEdit->setRelativePositionProportional( valueRect ); + + core::vector2df valueRectShift( coordBoxScale + fpad, 0.f ); + + //valueRect.move( coordBoxScale + fpad, 0.f ); // custom Irrlicht + valueRect.UpperLeftCorner += valueRectShift; + valueRect.LowerRightCorner += valueRectShift; + yEdit->setRelativePositionProportional( valueRect ); + + if ( edit3D ) + { + //valueRect.move( coordBoxScale + fpad, 0.f ); // custom Irrlicht + valueRect.UpperLeftCorner += valueRectShift; + valueRect.LowerRightCorner += valueRectShift; + zEdit->setRelativePositionProportional( valueRect ); + } else { + zEdit->setVisible(false); + } + dirtyRect = false; + } + + IGUIElement::updateAbsolutePosition(); +} + +bool GUIVectorPanel::OnEvent( const SEvent& event ) +{ + if ( !isVisible() || !isEnabled() || event.EventType != EET_GUI_EVENT ) + return false; + + if ( event.GUIEvent.Caller == xEdit ) + { + X = xEdit->getValue(); + sendGUIEvent( EGET_EDITBOX_CHANGED, xEdit ); + return true; + } + else if ( event.GUIEvent.Caller == yEdit ) + { + Y = yEdit->getValue(); + sendGUIEvent( EGET_EDITBOX_CHANGED, yEdit ); + return true; + } + else if ( event.GUIEvent.Caller == zEdit ) + { + Z = zEdit->getValue(); + sendGUIEvent( EGET_EDITBOX_CHANGED, zEdit ); + return true; + } + return false; +} + +void GUIVectorPanel::sendGUIEvent( EGUI_EVENT_TYPE pEventType, IGUIElement* pElement ) +{ + if ( ! Parent ) return; + + SEvent event; + event.EventType = EET_GUI_EVENT; + event.GUIEvent.Caller = this; + event.GUIEvent.Element = pElement; + event.GUIEvent.EventType = pEventType; + + Parent->OnEvent(event); +} + +void GUIVectorPanel::serializeAttributes( + irr::io::IAttributes* out, + irr::io::SAttributeReadWriteOptions* options + ) +{ + IGUIElement::serializeAttributes(out,options); + + out->addBool("Edit3D", edit3D); + out->addFloat("X", X); + out->addFloat("Y", X); + out->addFloat("Z", X); +} + +void GUIVectorPanel::deserializeAttributes( + irr::io::IAttributes* in, + irr::io::SAttributeReadWriteOptions* options + ) +{ + IGUIElement::deserializeAttributes(in,options); + + edit3D = in->getAttributeAsBool("Edit3D", edit3D); + if ( edit3D ) + setEdit3DVector(); + else + setEdit2DVector(); + + X = in->getAttributeAsFloat("X"); + Y = in->getAttributeAsFloat("Y"); + Z = in->getAttributeAsFloat("Z"); +} + +}} + +#endif // #ifndef GUI_VECTOR_PANEL_CPP diff --git a/IrrExtensions/gui/GUIVectorPanel.h b/IrrExtensions/gui/GUIVectorPanel.h new file mode 100644 index 0000000..d51ae35 --- /dev/null +++ b/IrrExtensions/gui/GUIVectorPanel.h @@ -0,0 +1,72 @@ +// (c) 2015 Nicolaus Anderson + +#ifndef GUI_VECTOR_PANEL_H +#define GUI_VECTOR_PANEL_H + +#include +#include + +namespace irr { +namespace gui { + +using core::vector2d; +using core::vector3d; +using core::rect; + +class IGUISpinBox; + +//! GUI Vector Panel +/* This class is a GUI element that contains three edit-boxes (spin boxes, at the moment) for modifying a vector. +Optionally, this class can edit a 3D vector instead of a 2D one. */ +class GUIVectorPanel : public IGUIElement +{ + // Dimensions + f32 X, Y, Z; + bool edit3D; + bool dirtyRect; + + IGUISpinBox* xEdit; + IGUISpinBox* yEdit; + IGUISpinBox* zEdit; + +public: + GUIVectorPanel( IGUIEnvironment* pEnvironment, IGUIElement* pParent, rect pRect, bool pEdit3D=false, s32 id=-1 ); + + void set2DValue( f32 pX, f32 pY ); + void set2DVectorValue( vector2d pValue ); + void set3DValue( f32 pX, f32 pY, f32 pZ ); + void set3DVectorValue( vector3d pValue ); + void reset(); + + /* Set editing a vector. + DO NOT CHANGE! In the future, this may be set to allowing editing 4D vectors. */ + void setEdit2DVector(); // Force editing a 2D vector + void setEdit3DVector(); // Force editing a 3D vector + bool isEditing3D(); + + vector2d getVector2D(); + vector3d getVector3D(); + + virtual void updateAbsolutePosition(); // changes the sizes of the child elements + virtual bool OnEvent( const SEvent& event ); + + virtual const c8* getTypeName() const { return staticTypeName(); } + static const c8* staticTypeName() { return "vectorPanel"; } + + virtual void serializeAttributes( + irr::io::IAttributes* out, + irr::io::SAttributeReadWriteOptions* options=0 + ); + + virtual void deserializeAttributes( + irr::io::IAttributes* in, + irr::io::SAttributeReadWriteOptions* options=0 + ); + +private: + void sendGUIEvent( EGUI_EVENT_TYPE pEventType, IGUIElement* pElement ); +}; + +}} + +#endif // #ifndef GUI_VECTOR_PANEL_H diff --git a/IrrExtensions/gui/GUIWindow2.cpp b/IrrExtensions/gui/GUIWindow2.cpp new file mode 100644 index 0000000..ec05cf5 --- /dev/null +++ b/IrrExtensions/gui/GUIWindow2.cpp @@ -0,0 +1,413 @@ +/* Taken from the irrlicht engine +and slightly modified by adding +an event parent and changing names. +Modifications by Nic Anderson. */ + +#ifndef _GUIWINDOW2_CPP_ +#define _GUIWINDOW2_CPP_ + +#include "GUIWindow2.h" +#include "IGUISkin.h" +#include "IGUIEnvironment.h" +#include "IVideoDriver.h" +#include "IGUIButton.h" +#include "IGUIFont.h" +#include "IGUIFontBitmap.h" + +namespace irr +{ +namespace gui +{ + +//! constructor +GUIWindow2::GUIWindow2( IGUIEnvironment* pEnvironment, IGUIElement* pParent, rect pRect, s32 id ) + : IGUIWindow(pEnvironment, pParent, id, pRect) + , CloseButton(0) + , MinButton(0) + , RestoreButton(0) + , ClientRect() + , CurrentIconColor() + , EventParent(0) + , Dragging(false) + , IsDraggable(true) + , DrawBackground(true) + , DrawTitlebar(true) + , IsActive(false) +{ + #ifdef _DEBUG + setDebugName("GUIWindow2"); + #endif + + IGUISkin* skin = 0; + if (Environment) + skin = Environment->getSkin(); + + CurrentIconColor = video::SColor(255,255,255,255); + + s32 buttonw = 15; + if (skin) + { + buttonw = skin->getSize(EGDS_WINDOW_BUTTON_WIDTH); + } + s32 posx = RelativeRect.getWidth() - buttonw - 4; + + CloseButton = Environment->addButton(core::rect(posx, 3, posx + buttonw, 3 + buttonw), this, -1, + L"", skin ? skin->getDefaultText(EGDT_WINDOW_CLOSE) : L"Close" ); + CloseButton->setSubElement(true); + CloseButton->setTabStop(false); + CloseButton->setAlignment(EGUIA_LOWERRIGHT, EGUIA_LOWERRIGHT, EGUIA_UPPERLEFT, EGUIA_UPPERLEFT); + posx -= buttonw + 2; + + RestoreButton = Environment->addButton(core::rect(posx, 3, posx + buttonw, 3 + buttonw), this, -1, + L"", skin ? skin->getDefaultText(EGDT_WINDOW_RESTORE) : L"Restore" ); + RestoreButton->setVisible(false); + RestoreButton->setSubElement(true); + RestoreButton->setTabStop(false); + RestoreButton->setAlignment(EGUIA_LOWERRIGHT, EGUIA_LOWERRIGHT, EGUIA_UPPERLEFT, EGUIA_UPPERLEFT); + posx -= buttonw + 2; + + MinButton = Environment->addButton(core::rect(posx, 3, posx + buttonw, 3 + buttonw), this, -1, + L"", skin ? skin->getDefaultText(EGDT_WINDOW_MINIMIZE) : L"Minimize" ); + MinButton->setVisible(false); + MinButton->setSubElement(true); + MinButton->setTabStop(false); + MinButton->setAlignment(EGUIA_LOWERRIGHT, EGUIA_LOWERRIGHT, EGUIA_UPPERLEFT, EGUIA_UPPERLEFT); + + MinButton->grab(); + RestoreButton->grab(); + CloseButton->grab(); + + // this element is a tab group + setTabGroup(true); + setTabStop(true); + setTabOrder(-1); + + refreshSprites(); + updateClientRect(); +} + + +//! destructor +GUIWindow2::~GUIWindow2() +{ + if (MinButton) + MinButton->drop(); + + if (RestoreButton) + RestoreButton->drop(); + + if (CloseButton) + CloseButton->drop(); +} + +void GUIWindow2::refreshSprites() +{ + if (!Environment) + return; + IGUISkin* skin = Environment->getSkin(); + if ( !skin ) + return; + + IGUISpriteBank* sprites = skin->getSpriteBank(); + if ( !sprites ) + return; + + CurrentIconColor = skin->getColor(isEnabled() ? EGDC_WINDOW_SYMBOL : EGDC_GRAY_WINDOW_SYMBOL); + + if (sprites) + { + CloseButton->setSpriteBank(sprites); + CloseButton->setSprite(EGBS_BUTTON_UP, skin->getIcon(EGDI_WINDOW_CLOSE), CurrentIconColor); + CloseButton->setSprite(EGBS_BUTTON_DOWN, skin->getIcon(EGDI_WINDOW_CLOSE), CurrentIconColor); + + RestoreButton->setSpriteBank(sprites); + RestoreButton->setSprite(EGBS_BUTTON_UP, skin->getIcon(EGDI_WINDOW_RESTORE), CurrentIconColor); + RestoreButton->setSprite(EGBS_BUTTON_DOWN, skin->getIcon(EGDI_WINDOW_RESTORE), CurrentIconColor); + + MinButton->setSpriteBank(sprites); + MinButton->setSprite(EGBS_BUTTON_UP, skin->getIcon(EGDI_WINDOW_MINIMIZE), CurrentIconColor); + MinButton->setSprite(EGBS_BUTTON_DOWN, skin->getIcon(EGDI_WINDOW_MINIMIZE), CurrentIconColor); + } +} + +//! called if an event happened. +bool GUIWindow2::OnEvent(const SEvent& event) +{ + if (isEnabled()) + { + switch(event.EventType) + { + case EET_GUI_EVENT: + /* Note that this only handles GUI events. Mouse events for child elements + are not handled because focus is determined by the environment. */ + if ( event.GUIEvent.Caller != this ) + if ( EventParent ) + if ( EventParent->OnEvent(event) ) + return true; + + if (event.GUIEvent.EventType == EGET_ELEMENT_FOCUS_LOST) + { + Dragging = false; + IsActive = false; + + if ( EventParent ) + EventParent->OnEvent(event); + } + else + if (event.GUIEvent.EventType == EGET_ELEMENT_FOCUSED) + { + if (Parent && ((event.GUIEvent.Caller == this) || isMyChild(event.GUIEvent.Caller))) + { + Parent->bringToFront(this); + IsActive = true; + + if ( EventParent ) + EventParent->OnEvent(event); + } + else + { + IsActive = false; + } + } + else + if (event.GUIEvent.EventType == EGET_BUTTON_CLICKED) + { + if (event.GUIEvent.Caller == CloseButton) + { + if ( EventParent ) + { + SEvent e; + e.EventType = EET_GUI_EVENT; + e.GUIEvent.Caller = this; + e.GUIEvent.Element = 0; + e.GUIEvent.EventType = EGET_ELEMENT_CLOSED; + + // event parent may catch this event + if ( EventParent->OnEvent(e) ) + return true; + } + + if (Parent) + { + // send close event to parent + SEvent e; + e.EventType = EET_GUI_EVENT; + e.GUIEvent.Caller = this; + e.GUIEvent.Element = 0; + e.GUIEvent.EventType = EGET_ELEMENT_CLOSED; + + // if the event was not absorbed + if (!Parent->OnEvent(e)) + remove(); + + return true; + + } + else + { + remove(); + return true; + } + } + } + break; + case EET_MOUSE_INPUT_EVENT: + switch(event.MouseInput.Event) + { + case EMIE_LMOUSE_PRESSED_DOWN: + DragStart.X = event.MouseInput.X; + DragStart.Y = event.MouseInput.Y; + Dragging = IsDraggable; + if (Parent) + Parent->bringToFront(this); + return true; + case EMIE_LMOUSE_LEFT_UP: + Dragging = false; + return true; + case EMIE_MOUSE_MOVED: + if (!event.MouseInput.isLeftPressed()) + Dragging = false; + + if (Dragging) + { + // gui window should not be dragged outside its parent + if (Parent && + (event.MouseInput.X < Parent->getAbsolutePosition().UpperLeftCorner.X +1 || + event.MouseInput.Y < Parent->getAbsolutePosition().UpperLeftCorner.Y +1 || + event.MouseInput.X > Parent->getAbsolutePosition().LowerRightCorner.X -1 || + event.MouseInput.Y > Parent->getAbsolutePosition().LowerRightCorner.Y -1)) + return true; + + move(core::position2d(event.MouseInput.X - DragStart.X, event.MouseInput.Y - DragStart.Y)); + DragStart.X = event.MouseInput.X; + DragStart.Y = event.MouseInput.Y; + return true; + } + break; + default: + break; + } + default: + break; + } + } + + if ( EventParent ) + if ( EventParent->OnEvent(event) ) + return true; + + return IGUIElement::OnEvent(event); +} + +//! set event parent +/* Nic Anderson mod */ +void GUIWindow2::setEventParent( IEventReceiver* pEventParent ) +{ + EventParent = pEventParent; +} + + +//! Updates the absolute position. +void GUIWindow2::updateAbsolutePosition() +{ + IGUIElement::updateAbsolutePosition(); +} + + +//! draws the element and its children +void GUIWindow2::draw() +{ + if (IsVisible) + { + IGUISkin* skin = Environment->getSkin(); + + + // update each time because the skin is allowed to change this always. + updateClientRect(); + + if ( CurrentIconColor != skin->getColor(isEnabled() ? EGDC_WINDOW_SYMBOL : EGDC_GRAY_WINDOW_SYMBOL) ) + refreshSprites(); + + core::rect rect = AbsoluteRect; + + // draw body fast + if (DrawBackground) + { + rect = skin->draw3DWindowBackground(this, DrawTitlebar, + skin->getColor(IsActive ? EGDC_ACTIVE_BORDER : EGDC_INACTIVE_BORDER), + AbsoluteRect, &AbsoluteClippingRect); + + if (DrawTitlebar && Text.size()) + { + rect.UpperLeftCorner.X += skin->getSize(EGDS_TITLEBARTEXT_DISTANCE_X); + rect.UpperLeftCorner.Y += skin->getSize(EGDS_TITLEBARTEXT_DISTANCE_Y); + rect.LowerRightCorner.X -= skin->getSize(EGDS_WINDOW_BUTTON_WIDTH) + 5; + + IGUIFont* font = skin->getFont(EGDF_WINDOW); + if (font) + { + font->draw(Text.c_str(), rect, + skin->getColor(IsActive ? EGDC_ACTIVE_CAPTION:EGDC_INACTIVE_CAPTION), + false, true, &AbsoluteClippingRect); + } + } + } + } + + IGUIElement::draw(); +} + + +//! Returns pointer to the close button +IGUIButton* GUIWindow2::getCloseButton() const +{ + return CloseButton; +} + + +//! Returns pointer to the minimize button +IGUIButton* GUIWindow2::getMinimizeButton() const +{ + return MinButton; +} + + +//! Returns pointer to the maximize button +IGUIButton* GUIWindow2::getMaximizeButton() const +{ + return RestoreButton; +} + + +//! Returns true if the window is draggable, false if not +bool GUIWindow2::isDraggable() const +{ + return IsDraggable; +} + + +//! Sets whether the window is draggable +void GUIWindow2::setDraggable(bool draggable) +{ + IsDraggable = draggable; + + if (Dragging && !IsDraggable) + Dragging = false; +} + + +//! Set if the window background will be drawn +void GUIWindow2::setDrawBackground(bool draw) +{ + DrawBackground = draw; +} + + +//! Get if the window background will be drawn +bool GUIWindow2::getDrawBackground() const +{ + return DrawBackground; +} + + +//! Set if the window titlebar will be drawn +void GUIWindow2::setDrawTitlebar(bool draw) +{ + DrawTitlebar = draw; +} + + +//! Get if the window titlebar will be drawn +bool GUIWindow2::getDrawTitlebar() const +{ + return DrawTitlebar; +} + + +void GUIWindow2::updateClientRect() +{ + if (! DrawBackground ) + { + ClientRect = core::rect(0,0, AbsoluteRect.getWidth(), AbsoluteRect.getHeight()); + return; + } + IGUISkin* skin = Environment->getSkin(); + skin->draw3DWindowBackground(this, DrawTitlebar, + skin->getColor(IsActive ? EGDC_ACTIVE_BORDER : EGDC_INACTIVE_BORDER), + AbsoluteRect, &AbsoluteClippingRect, &ClientRect); + ClientRect -= AbsoluteRect.UpperLeftCorner; +} + + +//! Returns the rectangle of the drawable area (without border, without titlebar and without scrollbars) +core::rect GUIWindow2::getClientRect() const +{ + return ClientRect; +} + + +} // end namespace gui +} // end namespace irr + + +#endif // #ifndef _GUIWINDOW2_CPP_ diff --git a/IrrExtensions/gui/GUIWindow2.h b/IrrExtensions/gui/GUIWindow2.h new file mode 100644 index 0000000..44598a1 --- /dev/null +++ b/IrrExtensions/gui/GUIWindow2.h @@ -0,0 +1,89 @@ +/* Taken from the irrlicht engine +and slightly modified by adding +an event parent and changing names. +Modifications by Nic Anderson. */ + +#ifndef _GUIWINDOW2_H_ +#define _GUIWINDOW2_H_ + +#include + +namespace irr { +namespace gui { + +using core::rect; + +class GUIWindow2 : public IGUIWindow +{ +public: + GUIWindow2( IGUIEnvironment* pEnvironment, IGUIElement* pParent, rect pRect, s32 id=-1 ); + + ~GUIWindow2(); + + //! called if an event happened. + virtual bool OnEvent(const SEvent& event); + + //! set event parent + /* Nic Anderson mod */ + void setEventParent( IEventReceiver* pEventParent ); + + //! update absolute position + virtual void updateAbsolutePosition(); + + //! draws the element and its children + virtual void draw(); + + //! Returns pointer to the close button + virtual IGUIButton* getCloseButton() const; + + //! Returns pointer to the minimize button + virtual IGUIButton* getMinimizeButton() const; + + //! Returns pointer to the maximize button + virtual IGUIButton* getMaximizeButton() const; + + //! Returns true if the window is draggable, false if not + virtual bool isDraggable() const; + + //! Sets whether the window is draggable + virtual void setDraggable(bool draggable); + + //! Set if the window background will be drawn + virtual void setDrawBackground(bool draw); + + //! Get if the window background will be drawn + virtual bool getDrawBackground() const; + + //! Set if the window titlebar will be drawn + //! Note: If the background is not drawn, then the titlebar is automatically also not drawn + virtual void setDrawTitlebar(bool draw); + + //! Get if the window titlebar will be drawn + virtual bool getDrawTitlebar() const; + + //! Returns the rectangle of the drawable area (without border and without titlebar) + virtual core::rect getClientRect() const; + +protected: + + void updateClientRect(); + void refreshSprites(); + + IGUIButton* CloseButton; + IGUIButton* MinButton; + IGUIButton* RestoreButton; + core::rect ClientRect; + video::SColor CurrentIconColor; + + IEventReceiver* EventParent; // Nic Anderson mod + + core::position2d DragStart; + bool Dragging, IsDraggable; + bool DrawBackground; + bool DrawTitlebar; + bool IsActive; +}; + +}} // end namespaces + +#endif // #ifndef _GUIWINDOW2_H_ diff --git a/IrrExtensions/gui/IGUISColorSelect.h b/IrrExtensions/gui/IGUISColorSelect.h new file mode 100644 index 0000000..a285e66 --- /dev/null +++ b/IrrExtensions/gui/IGUISColorSelect.h @@ -0,0 +1,47 @@ +// (c) 2014 Nicolaus Anderson + +/* Color selection dialog. Notably, it allows selecting and returning +an SColor specifically (as opposed to other color types, such as SColorf. +This element should be able to be embedded in a window but does not need to be. */ + +#ifndef IGUISCOLORSELECT_H +#define IGUISCOLORSELECT_H + +#include +#include + +namespace irr { + +using core::recti; +using video::SColor; + +namespace gui { + +// For the window containing the color select dialog +enum EGUISColorSelectEvent +{ + EGSCSE_None, + EGSCSE_ColorChanged, + EGSCSE_COUNT +}; + +class IGUISColorSelect : public IGUIElement +{ +public: + IGUISColorSelect( IGUIEnvironment* pEnvironment, IGUIElement* pParent, recti pRect, s32 id=-1 ) + : IGUIElement( EGUIET_ELEMENT, pEnvironment, pParent, id, pRect ) + {} + + //! Get the color + /* Returns the ARGB color of this dialog, regardless of if it stores HSV natively. */ + virtual SColor getColor()=0; + + //! Get event + /* Returns the event that occurs if, for example, the user interacts with it. */ + virtual EGUISColorSelectEvent getEvent()=0; +}; + +} +} + +#endif // #ifndef IGUISCOLORSELECT_H diff --git a/IrrExtensions/gui/IrrExtGUIElementFactory.cpp b/IrrExtensions/gui/IrrExtGUIElementFactory.cpp new file mode 100644 index 0000000..fadd3c7 --- /dev/null +++ b/IrrExtensions/gui/IrrExtGUIElementFactory.cpp @@ -0,0 +1,140 @@ +// (C) 2020 Nicolaus Anderson + +#include "IrrExtGUIElementFactory.h" +#include +// EXCLUDE: GUIWindow2 +#include "SimpleGraph/SGraph2D.h" +#include "GUIColorEditBox.h" +#include "GUIColorSample.h" +#include "GUIDualSection.h" +#include "GUIDropdownSelector.h" +#include "GUIFileSelectPanel.h" +#include "GUIGroupingPanel.h" +#include "GUIMarkedSlider.h" +#include "GUIMaterialPanel.h" +#include "GUIMatrixPanel.h" +#include "GUISProgressBar.h" +#include "GUISColorSelect.h" +#include "GUIScrollPane.h" +#include "GUITextureView.h" +#include "GUITreeTable.h" // Requires irrTree: util/irrTree/irrTree/irrTree.h +#include "GUIVectorPanel.h" + + +namespace irr { +namespace gui { + +IrrExtGUIElementFactory::IrrExtGUIElementFactory( IGUIEnvironment* environment, scene::ISceneManager* manager ) + : Environment(environment) + , SceneManager(manager) +{} + +IGUIElement* +IrrExtGUIElementFactory::addGUIElement(EGUI_ELEMENT_TYPE type, IGUIElement* parent) { + return 0; +} + +IGUIElement* +IrrExtGUIElementFactory::addGUIElement(const c8* typeName, IGUIElement* parent) { + core::stringc elemname(typeName); + + if ( elemname == SGraph2D::staticTypeName() ) { + return new SGraph2D( Environment, parent, -1, core::recti(), core::rectf(-1,-1,1,1) ); + } + else if ( elemname == GUIColorEditBox::staticTypeName() ) { + return new GUIColorEditBox( Environment, parent, core::recti() ); + } + else if ( elemname == GUIColorSample::staticTypeName() ) { + return new GUIColorSample( Environment, parent, core::recti() ); + } + else if ( elemname == GUIDualSection::staticTypeName() ) { + // Default: horizontal/left-and-right sections + return new GUIDualSection( false, 0.5f, Environment, parent, core::recti() ); + } + else if ( elemname == GUIDropdownSelector::staticTypeName() ) { + return new GUIDropdownSelector( Environment, parent, core::recti() ); + } + else if ( elemname == GUIFileSelectPanel::staticTypeName() ) { + return new GUIFileSelectPanel( Environment, parent, core::recti(), "." ); + } + else if ( elemname == GUIGroupingPanel::staticTypeName() ) { + /* Could be saved for elsewhere because we might want access to getClientArea(). + Alternatively, add it to items set by serializeAttributes(). */ + return new GUIGroupingPanel( L"", Environment, parent, core::recti() ); + } + else if ( elemname == GUIMarkedSlider::staticTypeName() ) { + // Default: vertical + return new GUIMarkedSlider( true, 100, core::recti(), Environment, parent ); + } + else if ( elemname == GUIMaterialPanel::staticTypeName() ) { + return new GUIMaterialPanel( Environment, SceneManager, parent, core::recti() ); + } + else if ( elemname == GUIMatrixPanel::staticTypeName() ) { + // Default: rotate in degrees + return new GUIMatrixPanel( true, Environment, parent, core::recti() ); + } + else if ( elemname == GUISProgressBar::staticTypeName() ) { + return new GUISProgressBar( Environment, parent, core::recti() ); + } + else if ( elemname == GUISColorSelect::staticTypeName() ) { + return new GUISColorSelect( Environment, parent, core::recti() ); + } + else if ( elemname == GUIScrollPane::staticTypeName() ) { + return new GUIScrollPane( Environment, parent, core::recti() ); + } + else if ( elemname == GUITextureView::staticTypeName() ) { + // FIXME: GUITextureView requires a unique ID so it can give a unique name to render targets + static u32 i = 1; ++i; + return new GUITextureView(0, Environment, parent, core::recti(), i); + } + else if ( elemname == GUITreeTable::staticTypeName() ) { + return new GUITreeTable( Environment, parent, core::recti() ); + } + else if ( elemname == GUIVectorPanel::staticTypeName() ) { + return new GUIVectorPanel( Environment, parent, core::recti() ); + } + + return 0; +} + +s32 +IrrExtGUIElementFactory::getCreatableGUIElementTypeCount() const { + return 16; +} + +EGUI_ELEMENT_TYPE +IrrExtGUIElementFactory::getCreateableGUIElementType(s32 idx) const { + return EGUIET_ELEMENT; +} + +const c8* +IrrExtGUIElementFactory::getCreateableGUIElementTypeName(s32 idx) const { + switch(idx) + { + case 0: return SGraph2D::staticTypeName(); + case 1: return GUIColorEditBox::staticTypeName(); + case 2: return GUIColorSample::staticTypeName(); + case 3: return GUIDualSection::staticTypeName(); + case 4: return GUIDropdownSelector::staticTypeName(); + case 5: return GUIFileSelectPanel::staticTypeName(); + case 6: return GUIGroupingPanel::staticTypeName(); + case 7: return GUIMarkedSlider::staticTypeName(); + case 8: return GUIMaterialPanel::staticTypeName(); + case 9: return GUIMatrixPanel::staticTypeName(); + case 10: return GUISProgressBar::staticTypeName(); + case 11: return GUISColorSelect::staticTypeName(); + case 12: return GUIScrollPane::staticTypeName(); + case 13: return GUITextureView::staticTypeName(); + case 14: return GUITreeTable::staticTypeName(); + case 15: return GUIVectorPanel::staticTypeName(); + default: return ""; + } +} + +const c8* +IrrExtGUIElementFactory::getCreateableGUIElementTypeName(EGUI_ELEMENT_TYPE type) const { + return ""; +} + +} // namespace gui +} // namespace irr diff --git a/IrrExtensions/gui/IrrExtGUIElementFactory.h b/IrrExtensions/gui/IrrExtGUIElementFactory.h new file mode 100644 index 0000000..89fcba1 --- /dev/null +++ b/IrrExtensions/gui/IrrExtGUIElementFactory.h @@ -0,0 +1,41 @@ +// (C) 2020 Nicolaus Anderson + +#ifndef IRR_EXT_GUI_ELEMENT_FACTORY_H +#define IRR_EXT_GUI_ELEMENT_FACTORY_H + +#include + +namespace irr { +namespace scene { + class ISceneManager; +} +namespace gui { + +class IGUIElement; +class IGUIEnvironment; + +//! IrrEXT GUI Element Factory +/* + Produces the GUI elements of the IrrExt project by chronologicaldot (Nic Anderson). + See Irrlicht file IGUIElementFactory.h for documentation. +*/ +class IrrExtGUIElementFactory + : public irr::gui::IGUIElementFactory +{ + IGUIEnvironment* Environment; + scene::ISceneManager* SceneManager; + +public: + IrrExtGUIElementFactory( IGUIEnvironment*, scene::ISceneManager* ); + virtual IGUIElement* addGUIElement(EGUI_ELEMENT_TYPE type, IGUIElement* parent=0); + virtual IGUIElement* addGUIElement(const c8* typeName, IGUIElement* parent=0); + virtual s32 getCreatableGUIElementTypeCount() const; + virtual EGUI_ELEMENT_TYPE getCreateableGUIElementType(s32 idx) const; + virtual const c8* getCreateableGUIElementTypeName(s32 idx) const; + virtual const c8* getCreateableGUIElementTypeName(EGUI_ELEMENT_TYPE type) const; +}; + +} // namespace gui +} // namespace irr + +#endif // IRR_EXT_GUI_ELEMENT_FACTORY_H diff --git a/IrrExtensions/gui/SimpleGraph/IGraph2D.h b/IrrExtensions/gui/SimpleGraph/IGraph2D.h new file mode 100644 index 0000000..53cf450 --- /dev/null +++ b/IrrExtensions/gui/SimpleGraph/IGraph2D.h @@ -0,0 +1,110 @@ +/* +Simple Graph +(c) Nicolaus Anderson +Created Jan 7, 2013 + +License: Same terms as irrlicht +*/ + +#include + +#ifndef _IGRAPH2D_ +#define _IGRAPH2D_ + +namespace irr +{ +namespace gui +{ + +//! Class Graph 2D +/* +Purpose: To plot 2D math functions. +*/ +class IGraph2D : public IGUIElement +{ +public: + + IGraph2D( IGUIEnvironment* envir, IGUIElement* parent, irr::s32 id, const irr::core::recti& rectangle ) + : IGUIElement( EGUIET_ELEMENT, envir, parent, id, rectangle ) + { + } + + virtual ~IGraph2D() + { + } + + //! Set graph size + /* NOT the same as scaling. + This function changes the ranges of values in the graph without changing + the size of the graph as a GUI element. + */ + virtual void setGraphSize( irr::core::rectf& size )=0; + + //! Set graph size along one dimension + /* NOT the same as scaling. + This function changes the ranges of values of ONE AXIS in the graph without + changing the size of the graph as a GUI element. + \param size - New axis min or max + \param isMax - If the value given is for the maximum + */ + virtual void setGraphSizeX( irr::f32 size, bool isMax=true )=0; + virtual void setGraphSizeY( irr::f32 size, bool isMax=true )=0; + + //! Set scale + /* Changes the scale of the ranges of values in the graph without changing + the size of the graph itself. + NOTE: Since this is a scaling, the rectangle being passed in should be the + percentage change (i.e. multiplier for the current values). */ + virtual void setGraphScale( irr::core::rectf& scale )=0; + + //! Get graph size + /* Returns the size of the graph. */ + virtual irr::core::rectf getGraphSize()=0; + + //! Draw + /* Draws the GUI element. */ + virtual void draw()=0; + + //! Clear graph + /* Erases everything in the graph. */ + virtual void clearGraph()=0; + + //------------------------------- + // Drawing area functions / Spaz + + //! Set point color + /* Sets the color of the points to be displayed. */ + virtual void setPointColor( irr::video::SColor color )=0; + + //! Set x-axis color + /* Sets the color that will be used in drawing the line representing + the x-axis. */ + virtual void setXAxisColor( irr::video::SColor color )=0; + + //! Set y-axis color + /* Sets the color that will be used in drawing the line representing + the y-axis. */ + virtual void setYAxisColor( irr::video::SColor color )=0; + + //----------------------------- + + //! Serialize attributes + virtual void serializeAttributes( + irr::io::IAttributes* out, + irr::io::SAttributeReadWriteOptions* options=0 + )=0; + + //! Deserialize attributes + virtual void deserializeAttributes( + irr::io::IAttributes* in, + irr::io::SAttributeReadWriteOptions* options=0 + )=0; + + virtual const c8* getTypeName() const { return staticTypeName(); } + static const c8* staticTypeName() { return "iGraph2D"; } +}; + +} // end namespace gui +} // end namespace irr + +#endif // define _IGRAPH2D_ diff --git a/IrrExtensions/gui/SimpleGraph/SGraph2D.cpp b/IrrExtensions/gui/SimpleGraph/SGraph2D.cpp new file mode 100644 index 0000000..3c7e79d --- /dev/null +++ b/IrrExtensions/gui/SimpleGraph/SGraph2D.cpp @@ -0,0 +1,912 @@ +/* +Simple Graph +(c) Nicolaus Anderson +Created Jan 7, 2013 + +License: Same terms as irrlicht +*/ + +#include "SGraph2D.h" + + +#include + +#ifndef __SIMPLEGRAPH2D_CPP__ +#define __SIMPLEGRAPH2D_CPP__ + +void irr::gui::SGraph2D::setGraphSize( irr::core::rectf& size ) +{ + // Change the points so they can be drawn correctly on the new window + changeGraphWindow( size ); + ReallocateGraphSpace(); +} + +void irr::gui::SGraph2D::setGraphSizeX( irr::f32 size, bool isMax ) +{ + irr::core::rectf new_window(window); + + // Note that LowerRightCorner is actually the top + if ( isMax ) + { + new_window.LowerRightCorner.X = size; + } else { + new_window.UpperLeftCorner.X = size; + } + new_window.repair(); + // Change the points so they can be drawn correctly on the new window + changeGraphWindow( new_window ); + ReallocateGraphSpace(); +} + +void irr::gui::SGraph2D::setGraphSizeY( irr::f32 size, bool isMax ) +{ + irr::core::rectf new_window(window); + // Note that LowerRightCorner is actually the top + if ( isMax ) + { + new_window.LowerRightCorner.Y = size; + } else { + new_window.UpperLeftCorner.Y = size; + } + new_window.repair(); + // Change the points so they can be drawn correctly on the new window + changeGraphWindow( new_window ); + ReallocateGraphSpace(); +} + +void irr::gui::SGraph2D::setGraphScale( irr::core::rectf& scale ) +{ + irr::core::rectf new_window(window); + + new_window.UpperLeftCorner.X *= scale.UpperLeftCorner.X; + new_window.UpperLeftCorner.Y *= scale.UpperLeftCorner.Y; + new_window.LowerRightCorner.X *= scale.LowerRightCorner.X; + new_window.LowerRightCorner.Y *= scale.LowerRightCorner.Y; + + new_window.repair(); + + // Change the points so they can be drawn correctly on the new window + changeGraphWindow( new_window ); + + ReallocateGraphSpace(); +} + +void irr::gui::SGraph2D::ReallocateGraphSpace() +{ + graphImage.reallocate( + (irr::u32)AbsoluteRect.getWidth(), + false + ); +} + +void irr::gui::SGraph2D::changeGraphWindow( irr::core::rectf new_win ) +{ + irr::core::position2df point; + + // Save the ratios - for convenience + irr::f32 winARx = window.getWidth() / (f32)AbsoluteRect.getWidth(); + irr::f32 winARy = window.getHeight() / (f32)AbsoluteRect.getHeight(); + irr::f32 ARnwinx = (f32)AbsoluteRect.getWidth() / new_win.getWidth(); + irr::f32 ARnwiny = (f32)AbsoluteRect.getHeight() / new_win.getHeight(); + + // Restore all points with new values in the new window + for ( irr::u32 p=0; p < graphImage.size(); p++ ) + { + point.X = + ( + // shift from the old graph window (same as in first loop) + graphImage[p].Pos.X * winARx + + window.UpperLeftCorner.X + + -new_win.UpperLeftCorner.X // shift to new origin + ) + * ARnwinx; // change to irrlicht coordinate scale + + point.Y = + ( + // shift from the old graph window (same as in first loop) + graphImage[p].Pos.Y * winARy + + window.UpperLeftCorner.Y + + -new_win.UpperLeftCorner.Y // shift to new origin + ) + * ARnwiny; // change to irrlicht coordinate scale + + // Overwrite the old point + graphImage[p].Pos.X = point.X; + graphImage[p].Pos.Y = point.Y; + } + + // Overwrite the original window + window = new_win; +} + +irr::core::rectf irr::gui::SGraph2D::getGraphSize() +{ + return window; +} + +Range irr::gui::SGraph2D::getXAxisRange() +{ + return Range( + window.UpperLeftCorner.X, + window.LowerRightCorner.X + ); +} + +Range irr::gui::SGraph2D::getYAxisRange() +{ + return Range( + window.UpperLeftCorner.Y, + window.LowerRightCorner.Y + ); +} + +Inc irr::gui::SGraph2D::getIterableXRange() +{ + Inc inc(Inc::CYC_REPEAT); + + inc.setRange( getXAxisRange() ); + inc.setStep( window.getWidth() / ((irr::f32)AbsoluteRect.getWidth()) ); + inc.restart(); + + return inc; +} + +Inc irr::gui::SGraph2D::getIterableYRange() +{ + Inc inc(Inc::CYC_REPEAT); + + inc.setRange( getYAxisRange() ); + inc.setStep( window.getHeight() / ((irr::f32)AbsoluteRect.getHeight()) ); + inc.restart(); + + return inc; +} + +void irr::gui::SGraph2D::markerSpacingX( irr::f32 gap ) +{ + markXgap = gap; +} + +void irr::gui::SGraph2D::markerSpacingY( irr::f32 gap ) +{ + markYgap = gap; +} + +void irr::gui::SGraph2D::draw() +{ + // Don't bother doing anything if this isn't visible + if ( !IsVisible || AbsoluteRect.getArea() == 0 || window.getArea() == 0.0f ) + return; + + // variables... + + irr::core::vector2di point; /* point to be drawn on screen representing + a point from the MathFunc */ + + /* Marker offset + - used to ensure the markers are drawn from the center outward. */ + Inc markerIter; /* no wrapping because we want + out-of-bounds checking */ + // Marker line - drawn on the GUI graph screen + irr::core::line2df marker; + + // For marker labels (drawn if desired) + irr::core::stringw label; + irr::core::position2df label_pos; + + + // operations... + + // Draw the background if there is one + if ( hasBackground ) + { + viddriver->draw2DRectangle( + background_color, + AbsoluteRect, + &AbsoluteClippingRect + ); + } + + // Draw the axis if desired + + if ( showXaxis ) + { + drawToGUI( + irr::core::line2df( + window.UpperLeftCorner.X, + 0.0f, + window.LowerRightCorner.X, + 0.0f + ), + xaxis_color + ); + } + + if ( showYaxis ) + { + drawToGUI( + irr::core::line2df( + 0.0f, + window.UpperLeftCorner.Y, + 0.0f, + window.LowerRightCorner.Y + ), + yaxis_color + ); + } + + // Draw the markers/lines if desired + if ( UseMarkers ) + { + // Y-axis + if ( showYaxisMarks ) + { + /* Set the offset for making the lines appear to be drawn + from the center outward */ + markerIter.setMin( window.UpperLeftCorner.Y ); + markerIter.setMax( window.LowerRightCorner.Y ); + markerIter.setStep( markYgap ); + markerIter.setVal( 0.0f ); // start in the center + + // Set up the marker line + + // Left side of the line + if ( UseTicks ) + marker.start.X = -window.getWidth() / 40.0f; // for 5% window width + else + marker.start.X = window.UpperLeftCorner.X; + + // Right side of the line + if ( UseTicks ) + marker.end.X = window.getWidth() / 40.0f; // for 5% window width + else + marker.end.X = window.LowerRightCorner.X; + + // Draw each marker + while ( !++markerIter ) // go until past the max + { + // Assign the position to the line to draw + marker.start.Y = marker.end.Y = markerIter.Val(); + + // Draw the marker to the GUI + drawToGUI( marker, ymark_color ); + + // Write the axis value if desired + if ( UseYTickLabels ) + { + label = irr::core::stringw( (irr::s32)markerIter.Val() ); + + label_pos.X = marker.end.X + window.getWidth()/50; + label_pos.Y = markerIter.Val(); + + drawToGUI( label, label_pos, ymark_color, false, true ); + } + } + + // Restart at the center + markerIter = 0.0f; + + // Draw each marker + while ( !--markerIter ) // go until past the min + { + // Assign the position to the line to draw + marker.start.Y = markerIter.Val(); + marker.end.Y = markerIter.Val(); + + // Draw the marker to the GUI + drawToGUI( marker, ymark_color ); + + // Write the axis value if desired + if ( UseYTickLabels ) + { + label = irr::core::stringw( (irr::s32)markerIter.Val() ); + + label_pos.X = marker.end.X + window.getWidth()/50; + label_pos.Y = markerIter.Val(); + + drawToGUI( label, label_pos, ymark_color, false, true ); + } + } + } + + // X-axis + if ( showXaxisMarks ) + { + /* Set the offset for making the lines appear to be drawn + from the center outward */ + markerIter.setMin( window.UpperLeftCorner.X ); + markerIter.setMax( window.LowerRightCorner.X ); + markerIter.setStep( markXgap ); + markerIter.setVal( 0.0f ); // start in the center + + // Set up the marker line + + // Top of the line - Below the x-axis in terms of irrlicht drawing + if ( UseTicks ) + marker.start.Y = window.getHeight() / 40.0f; // for 5% window height + else + marker.start.Y = window.UpperLeftCorner.Y; + + // Bottom of the line - Above the x-axis in terms of irrlicht drawing + if ( UseTicks ) + marker.end.Y = -window.getHeight() / 40.0f; // for 5% window height + else + marker.end.Y = window.LowerRightCorner.Y; + + // Draw each marker + while ( !++markerIter ) // go until past the max + { + // Assign the position to the line to draw + marker.start.X = marker.end.X = markerIter.Val(); + + // Draw the marker to the GUI + drawToGUI( marker, xmark_color ); + + // Write the axis value if desired + if ( UseXTickLabels ) + { + label = irr::core::stringw( (irr::s32)markerIter.Val() ); + + label_pos.X = markerIter.Val(); + label_pos.Y = marker.start.Y + window.getHeight()/50; + + drawToGUI( label, label_pos, xmark_color, false, true ); + } + } + + // Restart at the center + markerIter = 0.0f; + + // Draw each marker + while ( !--markerIter ) // go until past the min + { + // Assign the position to the line to draw + marker.start.X = markerIter.Val(); + marker.end.X = markerIter.Val(); + + // Draw the marker to the GUI + drawToGUI( marker, xmark_color ); + + // Write the axis value if desired + if ( UseXTickLabels ) + { + label = irr::core::stringw( (irr::s32)markerIter.Val() ); + + label_pos.X = markerIter.Val(); + label_pos.Y = marker.start.Y + window.getHeight()/50; + + drawToGUI( label, label_pos, xmark_color, false, true ); + } + } + } + } + + // Display each points on screen + for ( + irr::s32 pt = 0; + pt < (irr::s32)graphImage.size(); + pt++ + ) + { + point.X = (irr::s32)graphImage[pt].Pos.X; + point.Y = (irr::s32)graphImage[pt].Pos.Y; + + drawToGUI( point, graphImage[pt].Color ); + } +} + +void irr::gui::SGraph2D::clearGraph() +{ + graphImage.clear(); +} + +void irr::gui::SGraph2D::autoAdjust() +{ + /* Since all of the data points were stored in irrlicht coordinates + system SCALE (in order to draw them faster) (NOT including offset) + they must be converted back to the graph window coordinate system + for the graph to be rescaled to fit them. This can be a costly operation, + (depending on the number of points saved to the graph) so this function + should be called only once - after all of the points have been added. */ + + /* Operations require that both windows - GUI and graph - exist, + otherwise we get a 1/0 errore + Furthermore, there must be points to graph. */ + if ( window.getArea() == 0 || AbsoluteRect.getArea() == 0 + || graphImage.size() == 0 ) + return; + + // Temporary vertex for passing data + irr::core::position2df point; + + // Temporary rectangle - for creating the new window + irr::core::rectf new_window; + + + // Save the ratios - for convenience + irr::f32 winARx = window.getWidth() / (f32)AbsoluteRect.getWidth(); + irr::f32 winARy = window.getHeight() / (f32)AbsoluteRect.getHeight(); + + // First point determines start + /* This is done because the points may not actually reside anywhere + near the origin. */ + new_window.UpperLeftCorner.X = + graphImage[0].Pos.X * winARx // change scale + + window.UpperLeftCorner.X; // shift from corner to origin + new_window.LowerRightCorner.X = + graphImage[0].Pos.X * winARx // change scale + + window.UpperLeftCorner.X; // shift from corner to origin + new_window.UpperLeftCorner.Y = + graphImage[0].Pos.Y * winARy // change scale + + window.UpperLeftCorner.Y; // shift from corner to origin + new_window.LowerRightCorner.Y = + graphImage[0].Pos.Y * winARy // change scale + + window.UpperLeftCorner.Y; // shift from corner to origin + + // Generate the new window + for ( irr::u32 p=1; p < graphImage.size(); p++ ) + { + point.X = + graphImage[p].Pos.X * winARx // change scale + + window.UpperLeftCorner.X; // shift from corner to origin + point.Y = + graphImage[p].Pos.Y * winARy // change scale + + window.UpperLeftCorner.Y; // shift from corner to origin + + new_window.addInternalPoint( point ); + } + + changeGraphWindow( new_window ); +} + +void irr::gui::SGraph2D::setPointClipping( bool yes ) +{ + clipPoints = yes; +} + +void irr::gui::SGraph2D::useBackground( bool use ) +{ + hasBackground = use; +} + +void irr::gui::SGraph2D::setBackgroundColor( irr::video::SColor color ) +{ + background_color = color; +} + +void irr::gui::SGraph2D::setPointColor( irr::video::SColor color ) +{ + point_color = color; +} + +void irr::gui::SGraph2D::setXAxisColor( irr::video::SColor color ) +{ + xaxis_color = color; +} + +void irr::gui::SGraph2D::setYAxisColor( irr::video::SColor color ) +{ + yaxis_color = color; +} + +void irr::gui::SGraph2D::setShowXAxis( bool show ) +{ + showXaxis = show; +} + +void irr::gui::SGraph2D::setShowYAxis( bool show ) +{ + showYaxis = show; +} + +void irr::gui::SGraph2D::usePolygons( irr::f32 radius, irr::s32 corners ) +{ + usePolyPts = true; + polyRadius = radius; + polyPts = corners; +} + +void irr::gui::SGraph2D::useNoPolygons() +{ + usePolyPts = false; +} + +void irr::gui::SGraph2D::showXAxisLabels( bool yes ) +{ + UseXTickLabels = yes; +} + +void irr::gui::SGraph2D::showYAxisLabels( bool yes ) +{ + UseYTickLabels = yes; +} + +void irr::gui::SGraph2D::useTicks() +{ + UseMarkers = true; + UseTicks = true; + + showXaxisMarks = true; + showYaxisMarks = true; +} + +void irr::gui::SGraph2D::useLines() +{ + UseMarkers = true; + UseTicks = false; + + showXaxisMarks = true; + showYaxisMarks = true; +} + +void irr::gui::SGraph2D::useNoMarkers() +{ + UseMarkers = false; +} + +void irr::gui::SGraph2D::showXAxisMarkers( bool show ) +{ + showXaxisMarks = show; + if (show) UseMarkers = true; +} + +void irr::gui::SGraph2D::showYAxisMarkers( bool show ) +{ + showYaxisMarks = show; + if (show) UseMarkers = true; +} + +void irr::gui::SGraph2D::setXAxisMarkerColor( irr::video::SColor color ) +{ + xmark_color = color; +} + +void irr::gui::SGraph2D::setYAxisMarkerColor( irr::video::SColor color ) +{ + ymark_color = color; +} + +void irr::gui::SGraph2D::serializeAttributes( + irr::io::IAttributes *out, + irr::io::SAttributeReadWriteOptions *options + ) +{ + IGUIElement::serializeAttributes( out, options ); + + irr::core::recti win; + win.UpperLeftCorner.X = (irr::s32)(window.UpperLeftCorner.X); + win.UpperLeftCorner.Y = (irr::s32)(window.UpperLeftCorner.Y); + win.LowerRightCorner.X = (irr::s32)(window.LowerRightCorner.X); + win.LowerRightCorner.Y = (irr::s32)(window.LowerRightCorner.Y); + + out->addRect( "Window", win ); + + out->addBool( "FillBackground", hasBackground ); + out->addColor( "BGColor", background_color ); + out->addColor( "XAxisColor", xaxis_color ); + out->addColor( "YAxisColor", yaxis_color ); + out->addColor( "PointColor", point_color ); + out->addColor( "XMarkColor", xmark_color ); + out->addColor( "YMarkColor", ymark_color ); + out->addColor( "XAxisTickColor", xmark_color ); + out->addColor( "YAxisTickColor", ymark_color ); + + out->addBool( "UseMarkers", UseMarkers ); + out->addBool( "UseTicks", UseTicks ); + out->addFloat( "MarkerXSpacing", markXgap ); + out->addFloat( "MarkerYSpacing", markYgap ); + out->addBool( "ShowXAxisMarks", showXaxisMarks ); + out->addBool( "ShowYAxisMarks", showYaxisMarks ); + + out->addBool( "ShowXAxis", showXaxis ); + out->addBool( "ShowYAxis", showYaxis ); + + out->addBool( "ShowXAxisLabels", UseXTickLabels ); + out->addBool( "ShowYAxisLabels", UseYTickLabels ); + + out->addBool( "ClipDrawingRegion", clipPoints ); + + out->addBool( "UsePolygons", usePolyPts ); + out->addFloat( "PolygonRadius", polyRadius ); + out->addInt( "PolygonVertices", polyPts ); +} + +void irr::gui::SGraph2D::deserializeAttributes( + irr::io::IAttributes *in, + irr::io::SAttributeReadWriteOptions *options + ) +{ + IGUIElement::deserializeAttributes( in, options ); + + irr::core::recti win; + irr::core::rectf winf; + if ( in->existsAttribute("Window") ) { + win = in->getAttributeAsRect( "Window" ); + winf.UpperLeftCorner.X = (irr::f32)(win.UpperLeftCorner.X); + winf.UpperLeftCorner.Y = (irr::f32)(win.UpperLeftCorner.Y); + winf.LowerRightCorner.X = (irr::f32)(win.LowerRightCorner.X); + winf.LowerRightCorner.Y = (irr::f32)(win.LowerRightCorner.Y); + } + changeGraphWindow(winf); + + hasBackground = in->getAttributeAsBool( "FillBackground", hasBackground ); + background_color = in->getAttributeAsColor( "BGColor", background_color ); + xaxis_color = in->getAttributeAsColor( "XAxisColor", xaxis_color ); + yaxis_color = in->getAttributeAsColor( "YAxisColor", yaxis_color ); + point_color = in->getAttributeAsColor( "PointColor", point_color ); + xmark_color = in->getAttributeAsColor( "XMarkColor", xmark_color ); + ymark_color = in->getAttributeAsColor( "YMarkColor", ymark_color ); + xmark_color = in->getAttributeAsColor( "XAxisTickColor", xmark_color ); + ymark_color = in->getAttributeAsColor( "YAxisTickColor", ymark_color ); + + UseMarkers = in->getAttributeAsBool( "UseMarkers", UseMarkers ); + UseTicks = in->getAttributeAsBool( "UseTicks", UseTicks ); + markXgap = in->getAttributeAsFloat( "MarkerXSpacing", markXgap ); + markYgap = in->getAttributeAsFloat( "MarkerYSpacing", markXgap ); + showXaxisMarks = in->getAttributeAsBool( "ShowXAxisMarks", showXaxisMarks ); + showYaxisMarks = in->getAttributeAsBool( "ShowYAxisMarks", showYaxisMarks ); + + showXaxis = in->getAttributeAsBool( "ShowXAxis", showXaxis ); + showYaxis = in->getAttributeAsBool( "ShowYAxis", showYaxis ); + + UseXTickLabels = in->getAttributeAsBool( "ShowXAxisLabels", UseXTickLabels ); + UseYTickLabels = in->getAttributeAsBool( "ShowYAxisLabels", UseYTickLabels ); + + clipPoints = in->getAttributeAsBool( "ClipDrawingRegion", clipPoints ); + + usePolyPts = in->getAttributeAsBool( "UsePolygons", usePolyPts ); + polyRadius = in->getAttributeAsFloat( "PolygonRadius", polyRadius ); + polyPts = in->getAttributeAsInt( "PolygonVertices", polyPts ); + + ReallocateGraphSpace(); +} + +void irr::gui::SGraph2D::drawOnGraph( + irr::core::vector2df point, irr::video::SColor color + ) +{ + // Cannot save if the window does not exist + if ( window.getArea() == 0 ) + return; + + // Shift the origin to the corner + point.X -= window.UpperLeftCorner.X; + point.Y -= window.UpperLeftCorner.Y; + + // Convert to the actual GUI window's coordinate system + point.X *= ((irr::f32)AbsoluteRect.getWidth()) / window.getWidth(); + point.Y *= ((irr::f32)AbsoluteRect.getHeight()) / window.getHeight(); + + if ( color.getAlpha() == 0 ) + color = point_color; + + // Save + graphImage.push_back( + irr::video::S3DVertex( point.X, point.Y, 0,0,0,0, color, 0,0) + ); +} + +void irr::gui::SGraph2D::drawOnGraph( + irr::f32 x, + irr::f32 y, + irr::video::SColor color + ) +{ + drawOnGraph( irr::core::vector2df(x,y), color ); +} + +void irr::gui::SGraph2D::drawOnGraphPolar( + irr::core::vector2df point, + irr::video::SColor color + ) +{ + // cartesian coordinates + irr::core::vector2df cartesian; + + cartesian.X = point.X // radius + * cos( point.Y * irr::core::DEGTORAD ); // cos(angle) + + cartesian.Y = point.X // radius + * sin( point.Y * irr::core::DEGTORAD ); // sin(angle) + + if ( color.getAlpha() == 0 ) + color = point_color; + + drawOnGraph( cartesian, color ); +} + +void irr::gui::SGraph2D::drawOnGraphPolar( + irr::f32 radius, + irr::f32 angle, + irr::video::SColor color + ) +{ + drawOnGraphPolar( irr::core::vector2df( radius, angle ), color ); +} + +void irr::gui::SGraph2D::drawRawPoint( + irr::core::vector2df point, + irr::video::SColor color + ) +{ + // Shift the origin to the corner + point.X -= window.UpperLeftCorner.X + // conversion of shift to GUI element size + * ((irr::f32)AbsoluteRect.getWidth()) / window.getWidth(); + + point.Y -= window.UpperLeftCorner.Y + // conversion of shift to GUI element size + * ((irr::f32)AbsoluteRect.getHeight()) / window.getHeight(); + + if ( color.getAlpha() == 0 ) + color = point_color; + + // Save + graphImage.push_back( + irr::video::S3DVertex( point.X, point.Y, 0,0,0,0, color, 0,0) + ); +} + +void irr::gui::SGraph2D::drawToGUI( + irr::core::vector2di point, + irr::video::SColor color + ) +{ + // Do nothing if the window isn't even visible + if ( window.getArea() == 0.0f ) + return; + + // Flip for drawing on the GUI + point.Y = AbsoluteRect.getHeight() - point.Y; + + /* This function has been passed a value that is already prepared to + be drawn to screen and simply needs to be offset. */ + point.X += AbsoluteRect.UpperLeftCorner.X; + point.Y += AbsoluteRect.UpperLeftCorner.Y; + + // Don't draw points outside the window + if ( clipPoints && !AbsoluteClippingRect.isPointInside( point ) ) + return; + + // Draw the point as a pixel - might be very small and hard to see + /* - Drawn faster than a polygon and may create a smooth line */ + viddriver->drawPixel( point.X, point.Y, color ); + + // Attempt to draw the point as a polygon (to make it more visible) + if ( usePolyPts && polyRadius >= 1.0f ) + viddriver->draw2DPolygon( + point, // position + polyRadius, // radius + color, // color + polyPts // roundness of the point + ); + +} + +void irr::gui::SGraph2D::drawToGUI( + irr::core::line2df line, + irr::video::SColor color + ) +{ + // Do nothing if the window isn't even visible + // - saves time and prevents divide-by-zero errors + if ( window.getArea() == 0.0f ) + return; + + if ( AbsoluteClippingRect.getWidth() == 0 || AbsoluteClippingRect.getHeight() == 0 ) + return; + + /* This function has been passed a value that needs to be prepared + to fit in the window in addition to it being offset. */ + + // Line that will be drawn - initialized to begin within the GUI element + irr::core::line2di drawline( + AbsoluteRect.UpperLeftCorner.X, + AbsoluteRect.UpperLeftCorner.Y, + AbsoluteRect.UpperLeftCorner.X, + AbsoluteRect.UpperLeftCorner.Y + ); + + // Tranform the line from graph coordinates. + line.start.X -= window.UpperLeftCorner.X; + line.end.X -= window.UpperLeftCorner.X; + + // Flip the y-axis for drawing in irrlicht coordinates + line.start.Y = window.LowerRightCorner.Y - line.start.Y; + line.end.Y = window.LowerRightCorner.Y - line.end.Y; + + // starting x + drawline.start.X += (irr::s32) ( line.start.X + * AbsoluteRect.getWidth() / window.getWidth() // coordinate system conversion + ); + + // starting y + drawline.start.Y += (irr::s32) ( line.start.Y + * AbsoluteRect.getHeight() / window.getHeight() // coordinate system conversion + ); + + // ending x + drawline.end.X += (irr::s32) ( line.end.X + * AbsoluteRect.getWidth() / window.getWidth() // coordinate system conversion + ); + + // ending y + drawline.end.Y += (irr::s32) ( line.end.Y + * AbsoluteRect.getHeight() / window.getHeight() // coordinate system conversion + ); + + // Shorten to fit in the clipping rectangle + if ( drawline.start.X == drawline.end.X ) // Vertical Line + if ( drawline.start.X < AbsoluteClippingRect.UpperLeftCorner.X + || drawline.start.X > AbsoluteClippingRect.LowerRightCorner.X ) + return; // Don't draw + + if ( drawline.start.Y == drawline.end.Y ) // Horizontal Line + if ( drawline.start.Y < AbsoluteClippingRect.UpperLeftCorner.Y + || drawline.start.Y > AbsoluteClippingRect.LowerRightCorner.Y ) + return; // Don't draw + + // FIXME: We assume only vertical and horizontal lines. Angled lines will be messed up. + drawline.start.X = irr::core::clamp( + drawline.start.X, + AbsoluteClippingRect.UpperLeftCorner.X, + AbsoluteClippingRect.LowerRightCorner.X + ); + drawline.start.Y = irr::core::clamp( + drawline.start.Y, + AbsoluteClippingRect.UpperLeftCorner.Y, + AbsoluteClippingRect.LowerRightCorner.Y + ); + drawline.end.X = irr::core::clamp( + drawline.end.X, + AbsoluteClippingRect.UpperLeftCorner.X, + AbsoluteClippingRect.LowerRightCorner.X + ); + drawline.end.Y = irr::core::clamp( + drawline.end.Y, + AbsoluteClippingRect.UpperLeftCorner.Y, + AbsoluteClippingRect.LowerRightCorner.Y + ); + + // Draw the line + viddriver->draw2DLine( drawline.start, drawline.end, color ); +} + + +void irr::gui::SGraph2D::drawToGUI( + irr::core::stringw text, + irr::core::position2df pos, + irr::video::SColor color, + bool horiz_centering, + bool vert_centering + ) +{ + irr::core::recti fin_pos(AbsoluteRect); + fin_pos.LowerRightCorner = fin_pos.UpperLeftCorner; + + // Shift center + pos.X -= window.UpperLeftCorner.X; + pos.Y = window.UpperLeftCorner.Y - pos.Y + window.getHeight(); // Flip the y-axis + + // coordinate system conversion + pos.X *= (irr::f32)AbsoluteRect.getWidth() / window.getWidth(); + pos.Y *= (irr::f32)AbsoluteRect.getHeight() / window.getHeight(); + + // Move onto the graph (in irrlicht) + fin_pos += irr::core::position2di( (irr::s32)pos.X, (irr::s32)pos.Y ); + + // Write the text + Environment->getSkin()->getFont()->draw( + text, + fin_pos, + color, + horiz_centering, + vert_centering, + &AbsoluteClippingRect + ); +} + + +#endif // define __SIMPLEGRAPH2D_CPP__ diff --git a/IrrExtensions/gui/SimpleGraph/SGraph2D.h b/IrrExtensions/gui/SimpleGraph/SGraph2D.h new file mode 100644 index 0000000..d4172b3 --- /dev/null +++ b/IrrExtensions/gui/SimpleGraph/SGraph2D.h @@ -0,0 +1,380 @@ +/* +Simple Graph +(c) Nicolaus Anderson +Created Jan 7, 2013 + +License: Same terms as irrlicht +*/ + +#include "IGraph2D.h" +#include +#include + +#ifndef __SIMPLEGRAPH2D_H__ +#define __SIMPLEGRAPH2D_H__ + +namespace irr +{ +namespace gui +{ + +//! Class Graph 2D +/* +Purpose: To plot 2D math functions. +*/ +class SGraph2D : public IGraph2D +{ + //! Graph Image + /* The actual graph itself: a chain of points that need to be + drawn. */ + irr::core::array graphImage; + + //! Graph window + /* This is the range of values that the graphed values are forced + to fit into. It is a user-set range. */ + irr::core::rectf window; + + //! Markers (e.g. tick marks) + bool UseMarkers; // whether to use markers or not + bool UseTicks; // whether to use ticks or lines (if markers are in use) + bool UseXTickLabels; // whether to write labels for the x-axis tick marks + bool UseYTickLabels; // whether to write labels for the y-axis tick marks + irr::f32 markXgap; // spacing between markings on the x-axis + irr::f32 markYgap; // spacing between markings on the y-axis + bool showXaxisMarks; // whether the x-axis marks should be displayed + bool showYaxisMarks; // whether the y-axis marks should be displayed + + //! Axis displaying flags + bool showXaxis; + bool showYaxis; + + //! Clip points - i.e. Don't draw points outside the graph window + bool clipPoints; + + // Polygon point attributes + bool usePolyPts; // Use polygon points + irr::f32 polyRadius; // Radius of the polygon points + irr::s32 polyPts; // Number of points in the polygon + + + //! Colors + + // Background color + irr::video::SColor background_color; + bool hasBackground; + + // Axis colors + irr::video::SColor xaxis_color; + irr::video::SColor yaxis_color; + + // Point color + irr::video::SColor point_color; + + // Tick marks / lines perpendicular to the axis + irr::video::SColor xmark_color; + irr::video::SColor ymark_color; + + + // The video driver for drawing on screen + irr::video::IVideoDriver* viddriver; + +public: + + //! Constructor + SGraph2D( + // GUI element parameters + IGUIEnvironment* envir, + IGUIElement* parent, + irr::s32 id, + const irr::core::recti& rectangle, + // Graph parameters + const irr::core::rectf& graphWindow, + bool marks = false, + bool ticks = false, + bool x_labels = true, + bool y_labels = true + ) + : IGraph2D( envir, parent, id, rectangle ), + window( graphWindow ), + UseMarkers( marks ), + showXaxisMarks( marks ), + showYaxisMarks( marks ), + UseTicks( ticks ), + UseXTickLabels( x_labels ), + UseYTickLabels( y_labels ), + markXgap(1.0f), + markYgap(1.0f), + showXaxis( true ), + showYaxis( true ), + clipPoints( true ), + usePolyPts( false ), + background_color( irr::video::SColor( 255, 0, 0, 0 ) ), + hasBackground( true ), + xaxis_color( irr::video::SColor( 255, 0, 125, 0 ) ), + yaxis_color( irr::video::SColor( 255, 0, 125, 0 ) ), + point_color( irr::video::SColor( 255, 255, 0, 0 ) ), + xmark_color( irr::video::SColor( 255, 0, 125, 0 ) ), + ymark_color( irr::video::SColor( 255, 0, 125, 0 ) ) + { + viddriver = Environment->getVideoDriver(); + } + + //! Destructor + ~SGraph2D() + { + } + + //! Set graph size + /* NOT the same as scaling. + This function changes the ranges of values in the graph without changing + the size of the graph as a GUI element. + */ + virtual void setGraphSize( irr::core::rectf& size ); + + //! Set graph size along one dimension + /* NOT the same as scaling. + This function changes the ranges of values of ONE AXIS in the graph without + changing the size of the graph as a GUI element. + \param size - New axis min or max + \param isMax - If the value given is for the maximum + */ + virtual void setGraphSizeX( irr::f32 size, bool isMax ); + virtual void setGraphSizeY( irr::f32 size, bool isMax ); + + //! Set scale + /* Changes the scale of the ranges of values in the graph without changing + the size of the graph itself. + NOTE: Since this is a scaling, the rectangle being passed in should be the + percentage change (i.e. multiplier for the current values). */ + virtual void setGraphScale( irr::core::rectf& scale ); + + //! Reallocate Memory for Graph + /* In order to allow the graph to draw points quickly, this function is + available to reallocate storage space for the data points. + Old data points will not be destroyed and thus must be overwritten. */ + void ReallocateGraphSpace(); + +protected: + + //! Change graph window + /* Corrects the graph points to be drawn correctly in the new graph + window. */ + void changeGraphWindow( irr::core::rectf new_win ); + +public: + + //! Get graph size + /* Returns the size of the graph. */ + virtual irr::core::rectf getGraphSize(); + + // Graphing region + + //! Get Graph X-Axis Range + /* Returns the range of values that will be visible when the graph is + drawn. */ + Range getXAxisRange(); + + //! Get Graph Y-Axis Range + /* Returns the range of values that will be visible when the graph is + drawn. */ + Range getYAxisRange(); + + //! Get Iterable Range for X-Axis + /* Returns an incrementor whose range spans the visible x-axis of the + graph, whose step yields one point per pixel across the screen, and whose + starting position is the one necessary for all points drawn with this as + a guide to cross the entire graph window. */ + Inc getIterableXRange(); + + //! Get Iterable Range for Y-Axis + /* Returns an incrementor whose range spans the visible y-axis of the + graph, whose step yields one point per pixel across the screen, and whose + starting position is the one necessary for all points drawn with this as + a guide to cross the entire graph window. */ + Inc getIterableYRange(); + + + // Other ---- + + //! Set step size + /* Sets the spacing between tick marks (if any). */ + void markerSpacingX( irr::f32 gap=1.0f ); + void markerSpacingY( irr::f32 gap=1.0f ); + + //! Draw + /* Draws the GUI element. */ + virtual void draw(); + + //! Clear graph + /* Erases everything in the graph. */ + virtual void clearGraph(); + + //! Auto-adjust + /* Corrects the graph window so that all of the points will fit. */ + void autoAdjust(); + + //! Clip points + /* Sets whether points outside the graphing area should be drawn. */ + void setPointClipping( bool yes=true); + + + //------------------------------- + // Drawing area functions / Spaz + + //! Set if there is a background + void useBackground( bool use=true ); + + //! Set background color + /* Sets the color to be displayed behind the graph. */ + void setBackgroundColor( irr::video::SColor color ); + + //! Set point color + /* Sets the color of the points to be displayed. */ + virtual void setPointColor( irr::video::SColor color ); + + //! Set x-axis color + /* Sets the color that will be used in drawing the line representing + the x-axis. */ + virtual void setXAxisColor( irr::video::SColor color ); + + //! Set y-axis color + /* Sets the color that will be used in drawing the line representing + the y-axis. */ + virtual void setYAxisColor( irr::video::SColor color ); + + //! Show x-axis + /* Shows the x-xais if desired. */ + void setShowXAxis( bool show=true ); + + //! Show y-axis + /* Shows the y-axis if desired. */ + void setShowYAxis( bool show=true ); + + //! Draw with polygons + /* Draw the points with polygons. */ + void usePolygons( irr::f32 radius=1.5f, irr::s32 corners=4 ); + + //! Stop drawing with polygons + /* Stops drawing using polygons to draw the points. + Note that you will need to reset the radius if you do this. */ + void useNoPolygons(); + + //! Show X-axis labels + /* Writes the values of the tick marks on the x-axis next to them. + Note: The marks for that axis must actually be visible for the numbers + to appear. */ + void showXAxisLabels( bool yes=true ); + + //! Show Y-axis labels + /* Writes the values of the tick marks on the x-axis next to them. + Note: The marks for that axis must actually be visible for the numbers + to appear. */ + void showYAxisLabels( bool yes=true ); + + + //----- Tick marks vs lines + /* Either tick marks (three pixels crossing the line) or perpendicular + lines can be used to show the step size. */ + + //! Set to use tick marks instead of lines + void useTicks(); + + //! Set to use perpendicular lines instead of tick marks + void useLines(); + + //! Set to use no markers + void useNoMarkers(); + + //! Set to show the x-axis markers + void showXAxisMarkers( bool show=true ); + + //! Set to show the y-axis markers + void showYAxisMarkers( bool show=true ); + + //! Set x-axis tick color / perpendicular line color + void setXAxisMarkerColor( irr::video::SColor color ); + + //! Set y-axis tick color / perpendicular line color + void setYAxisMarkerColor( irr::video::SColor color ); + + //----------------------------- + + //! Serialize attributes + virtual void serializeAttributes( + irr::io::IAttributes* out, + irr::io::SAttributeReadWriteOptions* options=0 + ); + + //! Deserialize attributes + virtual void deserializeAttributes( + irr::io::IAttributes* in, + irr::io::SAttributeReadWriteOptions* options=0 + ); + + //----------------------------- + + virtual const c8* getTypeName() const { return staticTypeName(); } + static const c8* staticTypeName() { return "sGraph2D"; } + + + // ------------ Drawing functions ------------ + + //! Draw to the graph + /* Draws on the graph a single point whose coordinates are prepared + according to the graph's coordinate system. + You should use this function for standard operations. */ + void drawOnGraph( + irr::core::vector2df point, + irr::video::SColor color=irr::video::SColor(0) + ); + + void drawOnGraph( + irr::f32 x, + irr::f32 y, + irr::video::SColor color=irr::video::SColor(0) + ); + + //! Draw on the graph - Polar coord + /* Draws on the graph a single point whose coordinates are prepared + according to the graph's coordinate system but are polar corrdinates. + \param point - A radius (X value) and an angle (Y value). + You should use this function for standard operations. */ + void drawOnGraphPolar( + irr::core::vector2df point, + irr::video::SColor color=irr::video::SColor(0) + ); + + void drawOnGraphPolar( + irr::f32 radius, + irr::f32 angle, + irr::video::SColor color=irr::video::SColor(0) + ); + + //! Draw to the graph + /* Draws on the graph a single point whose coordinates are preppared for + the AbsoluteRect and need merely be shifted. */ + void drawRawPoint( + irr::core::vector2df point, + irr::video::SColor color=irr::video::SColor(0) + ); + +protected: + + //! Draw directly to the GUI element + /* Draws to the GUI element by handling the relative offset + of the GUI element from the upperleft corner of the screen. */ + void drawToGUI( irr::core::vector2di point, irr::video::SColor color ); + void drawToGUI( irr::core::line2df line, irr::video::SColor color ); + void drawToGUI( + irr::core::stringw text, + irr::core::position2df pos, + irr::video::SColor color, + bool horiz_centering, + bool vert_centering + ); +}; + +} // end namespace gui +} // end namespace irr + +#endif // define __SIMPLEGRAPH2D_H__ diff --git a/IrrExtensions/gui/SimpleGraph/SGraph2D_sample.jpg b/IrrExtensions/gui/SimpleGraph/SGraph2D_sample.jpg new file mode 100755 index 0000000..df38b83 Binary files /dev/null and b/IrrExtensions/gui/SimpleGraph/SGraph2D_sample.jpg differ diff --git a/IrrExtensions/gui/SimpleGraph/SGraph2D_sample2.jpg b/IrrExtensions/gui/SimpleGraph/SGraph2D_sample2.jpg new file mode 100755 index 0000000..3df8418 Binary files /dev/null and b/IrrExtensions/gui/SimpleGraph/SGraph2D_sample2.jpg differ diff --git a/IrrExtensions/gui/SimpleGraph/driver.cpp b/IrrExtensions/gui/SimpleGraph/driver.cpp new file mode 100644 index 0000000..21ebed5 --- /dev/null +++ b/IrrExtensions/gui/SimpleGraph/driver.cpp @@ -0,0 +1,182 @@ +/* +By Nic Anderson +Used for debugging SGraph2D, the simple implementation of Graph2D. +*/ + +#include + +using std::cout; + +#include + +#ifdef _MSC_VER +#pragma comment(lib, "Irrlicht.lib") +#endif + + +#include "SGraph2D.h" + +using namespace irr; +using namespace gui; + + +void plotsine( SGraph2D* ); +void plotline( SGraph2D* ); +void plotspin( SGraph2D* ); +void plotspintrail( SGraph2D* ); + +void plotsine( SGraph2D* win ) +{ + Inc dis; + core::vector2df pt; + + dis.copy( win->getIterableXRange() ); + dis.setStep( 1.0f / 1000.0f ); + + do { + pt.X = dis.Val(); + pt.Y = 10 * sin( dis.Val() ); + + win->drawOnGraph( pt, video::SColor(255,255,0,0) ); + } while ( !++dis ); +} + +void plotline( SGraph2D* win ) +{ + Inc dis; + core::vector2df pt; + + dis.copy( win->getIterableXRange() ); + dis.setStep( 1.0f / 1000.0f ); + + do { + pt.X = dis.Val(); + pt.Y = dis.Val(); + + win->drawOnGraph( pt, video::SColor(255,255,0,0) ); + } while ( !++dis ); +} + +void plotspin( SGraph2D* win ) +{ + Inc twirl( CYC_DEFAULT, 0.0f, 0.1f, 0.0f, 360.0f ); + + do { + win->drawOnGraphPolar( + 5*cos( 5*twirl.Val() ), // alternating radius + twirl.Val() / core::DEGTORAD, + video::SColor( + 255, + 0,//(((s32)twirl.Val()*5)%255), + (((s32)twirl.Val()*5+100)%255), + (((s32)twirl.Val()*5+200)%255) + ) + ); + } while ( !++twirl ); +} + +void plotspintrail( SGraph2D* win ) +{ + static s32 loops=6; // # loops per flower + static f32 sizeup; + static f32 spacing = 1.2f; + static f32 shiftup = 2; + static f32 resolution = 200; + + Inc twirl( CYC_DEFAULT, 0.0f, 1.0f, 0.0f, (f32)loops ); + Inc innertwirl( CYC_DEFAULT, 0.0f, 1.0f, 0.0f, 360.0f ); + core::vector2df pt; + + do { + innertwirl.setStep( 360.0f / (twirl.Val() + 1) / resolution ); + + do { + sizeup = twirl.Val() + shiftup; + + // Create point in polar coordinates + // radius + pt.X = sizeup // increasing size + // alternating radius + * cos( loops * innertwirl.Val() * core::DEGTORAD ); + + // Convert to cartessian + pt.Y = pt.X * sin( innertwirl.Val() * core::DEGTORAD ); + pt.X = pt.X * cos( innertwirl.Val() * core::DEGTORAD ); + + // Offset + pt.X += //(loops - sizeup + shiftup) + spacing * sizeup * cos( twirl.Val() * 360 / loops * core::DEGTORAD ); + pt.Y += //(loops - sizeup + shiftup) + spacing * sizeup * sin( twirl.Val() * 360 / loops * core::DEGTORAD ); + + // Draw + win->drawOnGraph( + pt, + video::SColor( + 255, + 0,//(((s32)twirl.Val()*360*5)%255), + (((s32)twirl.Val()*360*5+100)%255), + (((s32)twirl.Val()*360*5+200)%255) + ) + ); + + } while ( !++innertwirl ); + + innertwirl.restart(); + + } while ( !++twirl ); + +} + + + +void main() +{ + IrrlichtDevice* device = createDevice( + video::EDT_BURNINGSVIDEO, + irr::core::dimension2du( 1000,700 ) + ); + video::IVideoDriver* vid = device->getVideoDriver(); + IGUIEnvironment* envir = device->getGUIEnvironment(); + + SGraph2D* graph = new SGraph2D( + envir, // environment + envir->getRootGUIElement(), // parent + 0, // id + irr::core::recti(50,50,800,600), // element boundaries + irr::core::rectf(-10.0f,-15.0f,15.0f,10.0f), // graph window + true, // marks? + true // ticks? + ); + + graph->setBackgroundColor( video::SColor(255,200,200,200) ); + graph->usePolygons( 2, 4 ); + graph->setPointClipping( false ); + + graph->markerSpacingX( 1.5 ); + + //plotsine( graph ); + //plotline( graph ); + //plotspin( graph ); + plotspintrail( graph ); + + graph->autoAdjust(); + + graph->setGraphScale( irr::core::rectf( 1.1f, 1.1f, 1.1f, 1.2f ) ); + + graph->drop(); + graph = 0; + + // Change the font + //envir->getSkin()->getFont()->setKerningHeight(14); + //envir->getSkin()->getFont()->setKerningWidth(14); + + while ( device->run() ) + { + vid->beginScene(); + envir->drawAll(); + vid->endScene(); + } + + device->drop(); +} diff --git a/IrrExtensions/gui/UltraGUI/EnumCentralWindowMenuSizing.h b/IrrExtensions/gui/UltraGUI/EnumCentralWindowMenuSizing.h new file mode 100644 index 0000000..d2fb945 --- /dev/null +++ b/IrrExtensions/gui/UltraGUI/EnumCentralWindowMenuSizing.h @@ -0,0 +1,36 @@ +/* +(c) Nicolaus Anderson +*/ + +#pragma once + +/* +Enum for changing the size settings of the central window menu. +*/ +enum ECWMSize +{ + /* Default settings (450px wide, 400px tall). */ + ECWMSize_default =0, + + /* Attempts to size the window to neatly fit the available + program window size. + Applies to both the width and height. */ + ECWMSize_optimize, + + /* Makes the size constant. + Applies to both the width and height. */ + ECWMSize_constant, + + // specifically the width + ECWMSize_width_default, + ECWMSize_width_optimize, + ECWMSize_width_constant, + + // specifically the height + ECWMSize_height_default, + ECWMSize_height_optimize, + ECWMSize_height_constant, + + // count + ECWMSize_COUNT +}; \ No newline at end of file diff --git a/IrrExtensions/gui/UltraGUI/EnumUltraMenuState.h b/IrrExtensions/gui/UltraGUI/EnumUltraMenuState.h new file mode 100644 index 0000000..c5ab9a4 --- /dev/null +++ b/IrrExtensions/gui/UltraGUI/EnumUltraMenuState.h @@ -0,0 +1,73 @@ +/* +(c) Nicolaus Anderson +*/ + +#pragma once + +/* +Enumeration for giving the location of a menu +(for the Ultra GUI Menu). +*/ +enum EUltraMenuLocation +{ + // Left side menu + EUGUIMLoc_Left=0, + + // Central window menu + EUGUIMLoc_Center, + + // Right side menu + EUGUIMLoc_Right, + + // Menu bar + EUGUIMLoc_Bar, + + // Total available locations + EUGUIMLoc_COUNT +}; + +enum EUltraMenuState +{ + // hidden completely + EUGUIMState_Closed=0, + + // closing + EUGUIMState_Closing, + + // open + EUGUIMState_Open, + + // opening + EUGUIMState_Opening, + + // becoming left-side menu + EUGUIMState_ToLeft_Open, + EUGUIMState_ToLeft_Closed, + + // becoming right-side menu + EUGUIMState_ToRight_Open, + EUGUIMState_ToRight_Closed, + + // becoming central window menu + EUGUIMState_ToCenter_Open, + EUGUIMState_ToCenter_Closed, + + // becoming bar menu icon + EUGUIMState_ToBar, + + // Total number of states + EUGUIMState_COUNT +}; + +// What to do when told to close +enum EUltraMenuClose +{ + // Default - hide to the side + EUGUIMClose_Hide=0, + + // Minimize + EUGUIMClose_Minimize, + + // Number of options + EUGUIMClose_COUNT +}; \ No newline at end of file diff --git a/IrrExtensions/gui/UltraGUI/Searchable.cpp b/IrrExtensions/gui/UltraGUI/Searchable.cpp new file mode 100644 index 0000000..d53260b --- /dev/null +++ b/IrrExtensions/gui/UltraGUI/Searchable.cpp @@ -0,0 +1,92 @@ +/* +(c) Nicolaus Anderson +*/ + +#include "Searchable.h" + +Searchable::Searchable() +{ +} + +Searchable::~Searchable() +{ + tags.clear(); +} + +void Searchable::addTag(const wchar_t* tag) +{ + tags.push_back(tag); +} + +void Searchable::addTag( const irr::core::stringw& tag ) +{ + tags.push_back(tag); +} + +void Searchable::addTags( const irr::core::stringw& list ) +{ + // parse the string + irr::core::stringw next; + + irr::u32 s=0; + for ( ; s < list.size(); s++ ) + { + if ( list[s] == ',' ) + { + if ( next.size() > 0 ) + tags.push_back(next); + + next.clear(); + } + else { + if ( list[s] != ' ' && list[s] != '\n' && list[s] != '\r' && list[s] != '\t' ) + { + next.push_back(list[s]); + } + } + } +} + +irr::core::stringw Searchable::getTags() +{ + irr::core::stringw out; + + irr::u32 t=0; + for ( ; t < tags.size(); t++ ) + { + out.append( tags[t] ); + + if ( t != tags.size() - 1 ) + out.append( irr::core::stringw(", ") ); + } + + return out; +} + +bool Searchable::hasMatchingTagExactly( irr::core::stringw& tag ) +{ + irr::u32 t=0; + for ( ; t < tags.size(); t++ ) + { + if ( tags[t].size() == tag.size() && tags[t].equalsn(tag, tag.size()) ) + return true; + } + + return false; +} + +bool Searchable::hasMatchingTagPartial( irr::core::stringw& tag ) +{ + irr::u32 t=0; + for ( ; t < tags.size(); t++ ) + { + if ( tags[t].equalsn(tag, tag.size()) ) + return true; + } + + return false; +} + +void Searchable::shouldShow( bool setting ) +{ +} \ No newline at end of file diff --git a/IrrExtensions/gui/UltraGUI/Searchable.h b/IrrExtensions/gui/UltraGUI/Searchable.h new file mode 100644 index 0000000..c011588 --- /dev/null +++ b/IrrExtensions/gui/UltraGUI/Searchable.h @@ -0,0 +1,42 @@ +/* +(c) Nicolaus Anderson +*/ + +#include + +#pragma once + +//! Class Searchable +/* +An object that can be searched. +*/ +class Searchable +{ +protected: + irr::core::array tags; + +public: + Searchable(); + ~Searchable(); + + virtual void addTag( const wchar_t* tag ); + + virtual void addTag( const irr::core::stringw tag ); + + /* Add comma-separated tags. */ + virtual void addTags( const irr::core::stringw list ); + + /* Get tags */ + virtual irr::core::stringw getTags(); + + /* Has a tag that matches exactly the given one. */ + virtual bool hasMatchingTagExactly( irr::core::stringw& tag ); + + /* Has a partially matching tag. */ + virtual bool hasMatchingTagPartial( irr::core::stringw& tag ); + + /* Called by search engines to indicate whether this object + should show up in the search results. For listed GUI elements, + this may mean being excluded from the drawing order. */ + virtual void shouldShow( bool setting ); +}; \ No newline at end of file diff --git a/IrrExtensions/gui/UltraGUI/UltraGUIConstants.h b/IrrExtensions/gui/UltraGUI/UltraGUIConstants.h new file mode 100644 index 0000000..2e67b65 --- /dev/null +++ b/IrrExtensions/gui/UltraGUI/UltraGUIConstants.h @@ -0,0 +1,74 @@ +/* +(c) Nicolaus Anderson +*/ + +/* A list of constants used for UltraGUI. +Feel free to modify these constants to suit your purposes. */ + +/* Resouce path function. +This is the default resource path option. It can be changed here +to change the default, or you can pass to the constructor a resource +path. */ +#ifndef ULTRAGUI_RESOURCE_PATH +#define ULTRAGUI_RESOURCE_PATH irr::io::path("../resources/art/") +#endif + +/* Button imagery. Appended to the resource path in order to obtain +the imagery for display on the buttons. */ +#define ULTRAGUI_RESOURCE_CLOSEBUTTON "closeButton.png" +#define ULTRAGUI_RESOURCE_TOLEFTBUTTON "toLeftButton.png" +#define ULTRAGUI_RESOURCE_TORIGHTBUTTON "toRightButton.png" +#define ULTRAGUI_RESOURCE_TOCENTERBUTTON "toCenterButton.png" +#define ULTRAGUI_RESOURCE_MINIMIZEBUTTON "minimizeButton.png" + +#define ULTRAGUI_RESOURCE_NOHOVERTEXTURE "" +#define ULTRAGUI_RESOURCE_HOVERTEXTURE_0 "hoverTex0.png" +#define ULTRAGUI_RESOURCE_HOVERTEXTURE_1 "hoverTex1.png" +#define ULTRAGUI_RESOURCE_HOVERTEXTURE_2 "hoverTex2.png" +#define ULTRAGUI_RESOURCE_HOVERTEXTURE_3 "hoverTex3.png" + +/* Size of an UltraGUIMenu as it appears on the menu bar +of the UltraGUIFrame: */ +#define ULTRAGUI_DEFAULT_MENUBAR_BUTTON_WIDTH 80 +#define ULTRAGUI_DEFAULT_MENUBAR_BUTTON_HEIGHT 30 + +/* Spacing between a UltraGUIMenu bar and the edge of the menu bar +of UltraGUIFrame: */ +#define ULTRAGUI_MENUBAR_PADDING 4 + +#define ULTRAGUI_DEFAULT_CENTRAL_MENU_WIDTH 500 +#define ULTRAGUI_DEFAULT_CENTRAL_MENU_HEIGHT 400 + +#define ULTRAGUI_DEFAULT_SIDE_MENU_WIDTH 250 +#define ULTRAGUI_DEFAULT_SIDE_MENU_HEIGHT 500 + +#define ULTRAGUI_DEFAULT_MOVE_SPEED 0.005f + +/* Default header information: */ +#define ULTRAGUI_DEFAULT_MENU_HEADER_HEIGHT 30 + +// Plain header colors (easier to read text on) +//#define ULTRAGUI_DEFAULT_HEADER_COLOR_TOP irr::video::SColor(255,0,100,155) +//#define ULTRAGUI_DEFAULT_HEADER_COLOR_MID irr::video::SColor(255,0,50,95) +//#define ULTRAGUI_DEFAULT_HEADER_COLOR_BOTTOM irr::video::SColor(255,0,70,135) + +// Slick header colors +#define ULTRAGUI_DEFAULT_HEADER_COLOR_TOP irr::video::SColor(255,140,180,205) +#define ULTRAGUI_DEFAULT_HEADER_COLOR_MID irr::video::SColor(255,0,50,95) +#define ULTRAGUI_DEFAULT_HEADER_COLOR_BOTTOM irr::video::SColor(255,20,110,165) + +/* Size of the width or height of a button on an UltraGUIMenu. +For now, set to match small texture size (16x16) to load without image +manipulation or engine modification. */ +#define ULTRAGUI_DEFAULT_MENU_BUTTON_SIZE 16 + +// Button hover color +#define ULTRAGUI_DEFAULT_MENU_BUTTON_HOVERCOLOR irr::video::SColor( 255,255,255,255 ) +//irr::video::SColor( 155,40,200,255 ) + +/* The spacing between the first button of an UltraGUIMenu +and the right side of the menu. */ +#define ULTRAGUI_BUTTON_RIGHT_PADDING 5 + +/* The time (in milliseconds) that the menus are highlighted. */ +#define ULTRAGUI_HIGHLIGHT_TIME 1000 \ No newline at end of file diff --git a/IrrExtensions/gui/UltraGUI/UltraGUIFrame.cpp b/IrrExtensions/gui/UltraGUI/UltraGUIFrame.cpp new file mode 100644 index 0000000..480ded5 --- /dev/null +++ b/IrrExtensions/gui/UltraGUI/UltraGUIFrame.cpp @@ -0,0 +1,672 @@ +#include "UltraGUIFrame.h" + +namespace irr +{ +namespace gui +{ + +UltraGUIFrame::UltraGUIFrame( + IGUIEnvironment* environment, + ICursorControl* cursorc, /* Not implemented yet: Change cursor to arrow when menu is movable */ + irr::s32 id, + IGUIElement* parent, + recti rectangle + ) + : IGUIElement( + EGUIET_ELEMENT, + environment, + (parent? parent : environment->getRootGUIElement()), + id, + (rectangle!=recti()? rectangle : (parent? parent->getAbsolutePosition() : environment->getRootGUIElement()->getAbsolutePosition())) + ) + , cursor( cursorc ) + , menuBarRows( 1 ) + , lastOpenBarX( 0 ) + , lastOpenRow( 0 ) + , leftSideSize( + dimension2du( + ULTRAGUI_DEFAULT_SIDE_MENU_WIDTH, + ULTRAGUI_DEFAULT_SIDE_MENU_HEIGHT + ) + ) + , rightSideSize( + dimension2du( + ULTRAGUI_DEFAULT_SIDE_MENU_WIDTH, + ULTRAGUI_DEFAULT_SIDE_MENU_HEIGHT + ) + ) + , centerSize( + dimension2du( + ULTRAGUI_DEFAULT_CENTRAL_MENU_WIDTH, + ULTRAGUI_DEFAULT_CENTRAL_MENU_HEIGHT + ) + ) + , barSlotSize( + dimension2du( + ULTRAGUI_DEFAULT_MENUBAR_BUTTON_WIDTH, + ULTRAGUI_DEFAULT_MENUBAR_BUTTON_HEIGHT + ) + ) + , headerHeight( ULTRAGUI_DEFAULT_MENU_HEADER_HEIGHT ) + , spriteBank( 0 ) +{ + /* Save the size for use in scaling the menus if this frame + is ever resized. */ + oldRect = (rectangle!=recti()? rectangle : (parent? parent->getAbsolutePosition() : environment->getRootGUIElement()->getAbsolutePosition())); + + /* Set the size of the bar on which everything is drawn. */ + menuBarRect = recti( + oldRect.UpperLeftCorner.X, + oldRect.LowerRightCorner.Y - ULTRAGUI_DEFAULT_MENUBAR_BUTTON_HEIGHT, + oldRect.LowerRightCorner.X, + oldRect.LowerRightCorner.Y + ); +} + +UltraGUIFrame::~UltraGUIFrame() +{ + if ( spriteBank ) + spriteBank->drop(); + + // The IGUIElement destructor will release the children +} + +UltraGUIMenu* UltraGUIFrame::addMenu( irr::s32 menuID, irr::video::ITexture* icon ) +{ + // Rectangle of the menu (left-side shape) + //recti shape( + // AbsoluteRect.UpperLeftCorner.X - (irr::s32)leftSideSize.Width, // Set off screen + // menuBarRect.UpperLeftCorner.Y - (irr::s32)leftSideSize.Height, // Set above the menu bar + // AbsoluteRect.UpperLeftCorner.X, // Set off screen + // menuBarRect.UpperLeftCorner.Y // Set above the menu bar + // ); + + recti shape( + AbsoluteRect.UpperLeftCorner.X, + menuBarRect.UpperLeftCorner.Y - (irr::s32)leftSideSize.Height, + AbsoluteRect.UpperLeftCorner.X + (irr::s32)leftSideSize.Width, + menuBarRect.UpperLeftCorner.Y + ); + + // Create the menu + // This step also makes the menu a child of this element + UltraGUIMenu* menu = new UltraGUIMenu( this, menuID, shape ); + + menu->setLeftSize( dimension2du(shape.getSize()) ); + menu->setRightSize( dimension2du(shape.getSize()) ); + menu->setCenterSize( + dimension2du( + ULTRAGUI_DEFAULT_CENTRAL_MENU_WIDTH, + ULTRAGUI_DEFAULT_CENTRAL_MENU_HEIGHT + ) + ); + menu->setBarSize( + dimension2du( + ULTRAGUI_DEFAULT_MENUBAR_BUTTON_WIDTH, + ULTRAGUI_DEFAULT_MENUBAR_BUTTON_HEIGHT + ) + ); + + if ( icon ) + menu->setMenuBarSprite( icon ); + + // Now have the menu locate it in the correct place + menu->setMyState( EUGUIMState_ToLeft_Open, true ); + menu->snapState(); + + // Make sure the buttons appear + menu->refreshButtons(); + // unnecessary when there is an updateAbsolutePosition() call + + return menu; +} + +void UltraGUIFrame::removeMenu( irr::s32 menuID ) +{ + // Search the children for the menu + irr::core::list::Iterator kid; + for ( ; kid != Children.end(); ++kid ) + { + if ( (*kid)->getID() == menuID ) + { + /* Menu bar menus need to be removed to deallocate space on the bar, + allowing other menus to occupy the slot once occupied by this menu. */ + if ( ((UltraGUIMenu*)(*kid))->getMenuLocation() == EUGUIMLoc_Bar ) + { + leavingBar( (UltraGUIMenu*)(*kid) ); + } + + removeChild(*kid); + } + } +} + +void UltraGUIFrame::closeMenu( irr::s32 menuID ) +{ + UltraGUIMenu* result=0; + + if ( getMenu( menuID, result ) ) + result->setMyState( EUGUIMState_Closed ); +} + +void UltraGUIFrame::closeOpenMenuAtLocation( EUltraMenuLocation location ) +{ + irr::core::list::Iterator kid = Children.begin(); + for ( ; kid != Children.end(); ++kid ) + { + if ( ((UltraGUIMenu*)(*kid))->getMenuLocation() == location ) + { + ((UltraGUIMenu*)(*kid))->setMyState( EUGUIMState_Closed ); + } + } +} + +void UltraGUIFrame::openMenu( irr::s32 menuID ) +{ + UltraGUIMenu* result=0; + irr::core::list::Iterator kid = Children.begin(); + + if ( !getMenu( menuID, result ) ) + return; + + for ( ; kid != Children.end(); ++kid ) + { + if ( ((UltraGUIMenu*)(*kid))->getMenuLocation() == result->getMenuLocation() ) + { + if ( (*kid) == result ) + { + ((UltraGUIMenu*)(*kid))->setMyState( EUGUIMState_Open); + } else { + ((UltraGUIMenu*)(*kid))->setMyState( EUGUIMState_Closed ); + } + } + } +} + +void UltraGUIFrame::moveMenu( irr::s32 menuID, EUltraMenuLocation location ) +{ + UltraGUIMenu* result=0; + + if ( !getMenu( menuID, result ) ) + return; + + switch ( result->getMenuState() ) + { + case EUGUIMState_Open: + switch ( location ) + { + case EUGUIMLoc_Right: + result->setMyState( EUGUIMState_ToRight_Open ); + break; + case EUGUIMLoc_Center: + result->setMyState( EUGUIMState_ToCenter_Open); + break; + case EUGUIMLoc_Bar: + result->setMyState( EUGUIMState_ToBar ); + break; + default: // Move to left + result->setMyState( EUGUIMState_ToLeft_Open ); + break; + } + break; + + default: + // Assume closed state + switch ( location ) + { + case EUGUIMLoc_Right: + result->setMyState( EUGUIMState_ToRight_Closed ); + break; + case EUGUIMLoc_Center: + result->setMyState( EUGUIMState_ToCenter_Closed ); + break; + case EUGUIMLoc_Bar: + result->setMyState( EUGUIMState_ToBar ); + break; + default: // Move to left + result->setMyState( EUGUIMState_ToLeft_Closed ); + break; + } + break; + } + + //updateAbsolutePosition(); +} + +void UltraGUIFrame::setMenuState( irr::s32 menuID, EUltraMenuState state ) +{ + /* Note that the opening of any menu requires the closing of + another unless that menu is a bar menu (in which case it can't close). */ + + // Find the menu + UltraGUIMenu* menu =0; + + // Menu not found, so terminate processing + if ( !getMenu( menuID, menu ) ) + return; + + /* Both the state and location of the menu determine the next + course of action. */ + switch ( state ) + { + case EUGUIMState_Opening: + case EUGUIMState_Open: + closeOpenMenuAtLocation( menu->menuLocation ); + menu->setMyState( EUGUIMState_Open ); + bringToFront( menu ); + break; + + case EUGUIMState_Closing: + case EUGUIMState_Closed: + menu->setMyState( EUGUIMState_Closed ); + break; + + default: + menu->setMyState( state ); + + if ( state == EUGUIMState_ToLeft_Open + || state == EUGUIMState_ToRight_Open + || state == EUGUIMState_ToCenter_Open + ) + { + bringToFront(menu); + } + + break; + } +} + +bool UltraGUIFrame::getMenu( irr::s32 menuID, UltraGUIMenu*& result ) +{ + irr::core::list::Iterator it = Children.begin(); + for ( ; it != Children.end(); ++it ) + { + if ( (*it)->getID() == menuID ) + { + result = (UltraGUIMenu*)(*it); + return true; + } + } + + // Not found + return false; +} + +void UltraGUIFrame::requestBarSpace( UltraGUIMenu* menu ) +{ + /* Calculate if a new row needs to be added for the menu. */ + + if ( lastOpenBarX + (irr::s32)menu->getBarSize().Width + > menuBarRect.getWidth() ) // + ULTRAGUI_MENUBAR_PADDING ) + { + // Add new row + menuBarRows++; + menuBarRect.UpperLeftCorner.Y = AbsoluteRect.LowerRightCorner.Y - (menuBarRows * ULTRAGUI_DEFAULT_MENUBAR_BUTTON_HEIGHT); + + // Set the new last opening + lastOpenBarX = 0; // ULTRAGUI_MENUBAR_PADDING; + occupiedBarX = menu->getBarSize().Width; // ULTRAGUI_MENUBAR_PADDING; + lastOpenRow++; + } else { + occupiedBarX = lastOpenBarX + menu->getBarSize().Width; + } + + /* Since minimized bars should not have the focus, search + for the first open menu that is not on the bar. */ + irr::core::list::Iterator iter = Children.begin(); + for ( ; iter != Children.end(); ++iter ) + { + if ( *iter != menu + && ((UltraGUIMenu*)*iter)->isTrulyOpen() ) + { + bringToFront( *iter ); + break; + } + } +} + +void UltraGUIFrame::leavingBar( UltraGUIMenu* menu ) +{ + // Reset and reprepare + lastOpenBarX = 0; // ULTRAGUI_MENUBAR_PADDING; + occupiedBarX = 0; // ULTRAGUI_MENUBAR_PADDING; + menuBarRows = 1; + menuBarRect.UpperLeftCorner.Y = AbsoluteRect.LowerRightCorner.Y - (menuBarRows * ULTRAGUI_DEFAULT_MENUBAR_BUTTON_HEIGHT); + + irr::core::list::Iterator iter = Children.begin(); + for ( ; iter != Children.end(); ++iter ) + { + if ( *iter != menu + && ((UltraGUIMenu*)*iter)->getMenuLocation() == EUGUIMLoc_Bar ) + { + ((UltraGUIMenu*)*iter)->refresh(); + } + } +} + +IGUISpriteBank* UltraGUIFrame::getMenuSpriteBank() +{ + // Has the sprite bank already been created? + if ( spriteBank ) + { + return spriteBank; + } + + /* Create the sprite bank for the button highlighters. + One to two sprites will be created: + 1) An on-mouse-hover sprite, which is blue. + 2) An on-click sprite, which is white. */ + spriteBank = getEnvironment()->addEmptySpriteBank( ULTRAGUI_RESOURCE_PATH + "ultraMenuSpriteBank" ); + + /* Create the frames for the hover sprite. Note that all of them have + the same size. */ + SGUISpriteFrame noHoverFrame; + noHoverFrame.rectNumber = 0; + noHoverFrame.textureNumber = 0; + + SGUISpriteFrame hoverFrame0; + hoverFrame0.rectNumber = 0; + hoverFrame0.textureNumber = 1; + + SGUISpriteFrame hoverFrame1; + hoverFrame1.rectNumber = 0; + hoverFrame1.textureNumber = 2; + + SGUISpriteFrame hoverFrame2; + hoverFrame2.rectNumber = 0; + hoverFrame2.textureNumber = 3; + + SGUISpriteFrame hoverFrame3; + hoverFrame3.rectNumber = 0; + hoverFrame3.textureNumber = 4; + + // sprite for when the mouse hovers over the button + SGUISprite hoverSprite; + hoverSprite.frameTime = 100; // 100 ms + hoverSprite.Frames.push_back( noHoverFrame ); + hoverSprite.Frames.push_back( hoverFrame0 ); + hoverSprite.Frames.push_back( hoverFrame1 ); + hoverSprite.Frames.push_back( hoverFrame2 ); + hoverSprite.Frames.push_back( hoverFrame3 ); + + // sprite for when the mouse moves off the button + // just a simple reverse of the first sprite + SGUISprite hoverOffSprite; + hoverOffSprite.frameTime = 100; // 100 ms + hoverOffSprite.Frames.push_back( hoverFrame2 ); + hoverOffSprite.Frames.push_back( hoverFrame1 ); + hoverOffSprite.Frames.push_back( hoverFrame0 ); + hoverOffSprite.Frames.push_back( noHoverFrame ); + + // add the sprites to the bank + spriteBank->getSprites().push_back( hoverSprite ); + spriteBank->getSprites().push_back( hoverOffSprite ); + + // add the button area rectangle to the bank + spriteBank->getPositions().push_back( + irr::core::recti( + 0,0, + ULTRAGUI_DEFAULT_MENU_BUTTON_SIZE, + ULTRAGUI_DEFAULT_MENU_BUTTON_SIZE + ) + ); + + // add the on-hover textures to the bank + spriteBank->addTexture( + getEnvironment()->getVideoDriver()->getTexture( + ULTRAGUI_RESOURCE_PATH + ULTRAGUI_RESOURCE_NOHOVERTEXTURE + ) ); + + spriteBank->addTexture( + getEnvironment()->getVideoDriver()->getTexture( + ULTRAGUI_RESOURCE_PATH + ULTRAGUI_RESOURCE_HOVERTEXTURE_0 + ) ); + + spriteBank->addTexture( + getEnvironment()->getVideoDriver()->getTexture( + ULTRAGUI_RESOURCE_PATH + ULTRAGUI_RESOURCE_HOVERTEXTURE_1 + ) ); + + spriteBank->addTexture( + getEnvironment()->getVideoDriver()->getTexture( + ULTRAGUI_RESOURCE_PATH + ULTRAGUI_RESOURCE_HOVERTEXTURE_2 + ) ); + + spriteBank->addTexture( + getEnvironment()->getVideoDriver()->getTexture( + ULTRAGUI_RESOURCE_PATH + ULTRAGUI_RESOURCE_HOVERTEXTURE_3 + ) ); + + + return spriteBank; +} + +bool UltraGUIFrame::OnEvent( const irr::SEvent& event ) +{ + // For now, unless features are added + return IGUIElement::OnEvent(event); +} + +void UltraGUIFrame::updateAbsolutePosition() +{ + // Reset the menu bar count and rows + lastOpenBarX = ULTRAGUI_MENUBAR_PADDING; + lastOpenRow = 0; + + // Save the old rectangle + // It will be needed by the menus for repositioning and resizing + oldRect = AbsoluteRect; + + // Do NOT do: IGUIElement::updateAbsolutePosition(); + // since the children should be updated last. + recalculateAbsolutePosition(false); + /* Recalculating here updates the AbsoluteRect, making its size different + from the previous size (in oldSize) if in fact the size has been changed. */ + + // Update the child sizes stored here (in ultra frame) + // By this, I mean update the sizes in case the frame itself has changed. + // This is probably unnecessary. + // To do?? + + // Update the menu bar rectangle + /* This is necessary in the event that another slot on the + menu bar is requested. */ + menuBarRect.UpperLeftCorner.X = AbsoluteRect.UpperLeftCorner.X; + menuBarRect.LowerRightCorner.X = AbsoluteRect.LowerRightCorner.X; + menuBarRect.LowerRightCorner.Y = AbsoluteRect.LowerRightCorner.Y; + menuBarRect.UpperLeftCorner.Y = + AbsoluteRect.LowerRightCorner.Y + - menuBarRows * ULTRAGUI_DEFAULT_MENUBAR_BUTTON_HEIGHT; + /* Eventually, I may add a spacer between the top of the bar and the + button. However, it would require considering such spacing in the + function getBarSlotPosition(). */ + + // Now update the child elements + irr::core::list::Iterator it = Children.begin(); + for (; it != Children.end(); ++it) + { + (*it)->updateAbsolutePosition(); + } +} + +void UltraGUIFrame::draw() +{ + if ( !isVisible() ) + return; + + // Draw the menu bar + Environment->getSkin()->draw3DMenuPane(this, menuBarRect); + + // Draw Everything else + IGUIElement::draw(); +} + +void UltraGUIFrame::OnPostRender(irr::u32 timeMs) +{ + IGUIElement::OnPostRender(timeMs); +} + +bool UltraGUIFrame::isPointInside( const irr::core::vector2di& point ) const +{ + /* Normally, the clipping rectangle is used. + Whether it should be used here is unknown, but AbsoluteRect + should work. */ + return AbsoluteRect.isPointInside(point); +} + +vector2di UltraGUIFrame::getLeftSidePosition() +{ + // Place on the left side just above the menu bar + return vector2di( + AbsoluteRect.UpperLeftCorner.X, + AbsoluteRect.LowerRightCorner.Y - menuBarRect.getHeight() + ); +} + +vector2di UltraGUIFrame::getRightSidePosition() +{ + // Place on the right size just above the menu bar + return vector2di( + AbsoluteRect.LowerRightCorner.X, + AbsoluteRect.LowerRightCorner.Y - menuBarRect.getHeight() + ); +} + +vector2di UltraGUIFrame::getCenterCenterPosition() +{ + // Place directly in the center of the screen (for now) + return (AbsoluteRect.UpperLeftCorner + AbsoluteRect.LowerRightCorner)/2; + // same as AR.ULC + (AR.LRC - AR.ULC)/2 +} + +vector2di UltraGUIFrame::getBarSlotPosition() +{ + /* This requires that the first position be reset by + updateAbsolutePosition of this class. */ + + vector2di slot( lastOpenBarX, menuBarRect.LowerRightCorner.Y - menuBarRect.getHeight() ); + + /* Indicate the slot has now been occupied by setting + the next open slot to the location after this one. */ + lastOpenBarX = occupiedBarX; + + return slot; +} + +dimension2du UltraGUIFrame::getCenterWindowSize() +{ + /* Calculate the size based on the defaults and the + available screen size. */ + + // Initialize the window size to the set size. + dimension2du windowSize = centerSize; + + /* In the future, the window may be resized according to a + user-selected option. */ + + /* The window is supposed to be 90% of the width and 80% of + the height of the available space AT MAXIMUM. + If it is smaller than that, no change is made to its size. */ + irr::u32 widthMax = (irr::u32) ( 0.9f * (irr::f32)AbsoluteRect.getWidth() ); + irr::u32 heightMax = (irr::u32) ( 0.8f * (irr::f32)AbsoluteRect.getHeight() ); + + if ( windowSize.Width > widthMax ) + windowSize.Width = widthMax; + + if ( windowSize.Height > heightMax ) + windowSize.Height = heightMax; + + return windowSize; +} + +irr::s32 UltraGUIFrame::getBarHeight() +{ + return menuBarRect.getHeight(); +} + +bool UltraGUIFrame::isInLeftDragRegion( irr::s32 x, irr::s32 y ) +{ + // Left side of the region + if ( x < AbsoluteRect.UpperLeftCorner.X ) + return false; + + // Bottom of the region + if ( y > AbsoluteRect.LowerRightCorner.Y - menuBarRect.getHeight() ) + return false; + + // Right side of the region = 15% frame width (as opposed to leftSideSize.Width) + if ( x > AbsoluteRect.UpperLeftCorner.X + (AbsoluteRect.getWidth()*0.15) ) + return false; + + // Top of the region = 15% frame height (as opposed to leftSideSize.Height) + if ( y < AbsoluteRect.LowerRightCorner.Y - (AbsoluteRect.getHeight()*0.85) - menuBarRect.getHeight() ) + return false; + + // Must be inside + return true; +} + +bool UltraGUIFrame::isInRightDragRegion( irr::s32 x, irr::s32 y ) +{ + // Right side of the region + if ( x > AbsoluteRect.LowerRightCorner.X ) + return false; + + // Bottom of the region + if ( y > AbsoluteRect.LowerRightCorner.Y - menuBarRect.getHeight() ) + return false; + + // Left side of the region = 15% frame width (as opposed to rightSideSize.Width) + if ( x < AbsoluteRect.LowerRightCorner.X - (AbsoluteRect.getWidth()*0.15) ) + return false; + + // Top of the region = 15% frame height (as opposed to rightSideSize.Height) + if ( y < AbsoluteRect.LowerRightCorner.Y - (AbsoluteRect.getHeight()*0.85) - menuBarRect.getHeight() ) + return false; + + // Must be inside + return true; +} + +bool UltraGUIFrame::isInCenterDragRegion( irr::s32 x, irr::s32 y ) +{ + // Left side of the region = 35% frame width + if ( x < AbsoluteRect.UpperLeftCorner.X + (AbsoluteRect.getWidth()*0.35) ) + return false; + + // Right side of the region = (100-35)% frame width + if ( x > AbsoluteRect.LowerRightCorner.X - (AbsoluteRect.getWidth()*0.35) ) + return false; + + // Top of the region = 10% frame height + if ( y < AbsoluteRect.UpperLeftCorner.X + (AbsoluteRect.getHeight()*0.1) ) + return false; + + // Bottom of the region = (100-35)% frame height + // Note, this may cause collisions with the menu bar for very tiny screens, haha + if ( y > AbsoluteRect.LowerRightCorner.Y - (AbsoluteRect.getHeight()*0.35) ) + return false; + + // Must be inside + return true; +} + +bool UltraGUIFrame::isInBarDragRegion( irr::s32 x, irr::s32 y ) +{ + irr::core::vector2di pt(x,y); + + if ( menuBarRect.isPointInside( pt ) ) + return true; + + return false; +} + +recti UltraGUIFrame::getFrameRectChange() +{ + vector2di ulc( AbsoluteRect.UpperLeftCorner - oldRect.UpperLeftCorner ); + vector2di lrc( AbsoluteRect.LowerRightCorner - oldRect.LowerRightCorner ); + + return recti( ulc, lrc ); +} + +}} // end namespace gui and irr \ No newline at end of file diff --git a/IrrExtensions/gui/UltraGUI/UltraGUIFrame.h b/IrrExtensions/gui/UltraGUI/UltraGUIFrame.h new file mode 100644 index 0000000..9902622 --- /dev/null +++ b/IrrExtensions/gui/UltraGUI/UltraGUIFrame.h @@ -0,0 +1,265 @@ +/* +(c) Nicolaus Anderson +*/ + +#include +#include "UltraGUIConstants.h" +//#include "EnumCentralWindowMenuSizing.h" +#include "EnumUltraMenuState.h" +#include "UltraGUIMenu.h" + +using irr::core::vector2di; +using irr::core::dimension2du; +using irr::core::recti; + + +#ifndef _ULTRAGUIFRAME_ +#define _ULTRAGUIFRAME_ + +namespace irr +{ +namespace gui +{ + +class UltraGUIFrame : public IGUIElement +{ + friend UltraGUIMenu; + +protected: + // Handles mouse input + // NOTE: This may be removed since it seems mostly unnecessary + ICursorControl* cursor; + + /* Old size - The previous rectangle of the frame before it is resized + (assuming it is resized) or moved. This is used for resizing and + repositioning the menus. */ + recti oldRect; + + // Rectangle of the menu bar + recti menuBarRect; + + // Rows on the menu bar + irr::u32 menuBarRows; + + // Last open x on bar + irr::s32 lastOpenBarX; + + // Last occupied location on the bar + // See requestBarSpace and getBarSlotPosition for details + irr::s32 occupiedBarX; + + // Last open row + /* This is the first row with a slot available. + NOTE: This is a transient number - it is reset and updated every time + updateAbsolutePosition() is called. */ + irr::u32 lastOpenRow; + + // ---- For the UltraGUIMenu elements ---- + + // Left-side menu size + dimension2du leftSideSize; + // Right-side menu size + dimension2du rightSideSize; + // Central window size + dimension2du centerSize; + // Bar icon size + dimension2du barSlotSize; + + /* Header bar size - The height of the bar that contains the title + of a menu plus the buttons that control its activity. */ + irr::s32 headerHeight; + + /* Sprite bank containing the sprites for the menus. + These sprites will be used for when the buttons are hovered over. + The bank is created and stored here (rather than in the menus) + to conserve space. */ + IGUISpriteBank* spriteBank; + +public: + UltraGUIFrame( + IGUIEnvironment* environment, + ICursorControl* cursorc, + irr::s32 id=-1, + IGUIElement* parent=0, + recti rectangle=recti(0,0,0,0) + ); + + ~UltraGUIFrame(void); + + /* Returns a pointer to the environment */ + IGUIEnvironment* getEnvironment() const + { + return Environment; + } + + /* + Adds a menu to the total. + \param menuID - The ID of the menu being added. + */ + UltraGUIMenu* addMenu( + irr::s32 menuID, + irr::video::ITexture* icon=0 + ); + + /* + Removes a menu whose ID is given. If -1 is given, all menus + are removed. + \param menuID - The ID of the menu being removed. + */ + void removeMenu( irr::s32 menuID ); + + /* + Closes the menu whose ID is given. The reaction depends on what menu + this is and where it is located. + >> left side menu - Slides left off the screen. + >> right side menu - Slides right off the screen. + >> menu bar menu - Does nothing. + >> central window menu - Disappears. + */ + void closeMenu( irr::s32 menuID ); + + /* + Closes the menu at the given location + */ + void closeOpenMenuAtLocation( EUltraMenuLocation location ); + + /* + Opens the menu whose ID is given. The reaction depends on what menu + this is and where it is located. + >> left side menu - Slides right if not open. Otherwise, it is highlighted. + >> right side menu - Slides left if not open. Otherwise, it is highlighted. + >> menu bar menu - Appears as the central window menu, minimizing + any menu that was there already. + >> central window menu - Blinks with a highlighter. + */ + void openMenu( irr::s32 menuID ); + + /* + Move the menu to the specific location. + */ + void moveMenu( irr::s32 menuID, EUltraMenuLocation location ); + + /* + Set the menu state. This does everything that the functions + openMenu(), closeMenu(), and moveMenu() do and more. + */ + void setMenuState( irr::s32 menuID, EUltraMenuState state ); + + /* + Saves a pointer to the requested menu. + Returns true if the menu could be found. + */ + bool getMenu( irr::s32 menuID, UltraGUIMenu*& result ); + + /* + \param size - The universal size of the left and right side menus. + */ + void setSideMenuSize( dimension2du size ); + + /* + Sets the bounds for the central window menu. + These are treated as maximum/normal dimensions. + If you wish to treat them as constant, call setCentralWindowMenuSizing() + */ + void setCentralWindowMenuBounds( dimension2du size ); + + /* + \param size_setting - The new size setting. + Setting one of these may override other, previously-set settings. + */ + //void setCentralWindowMenuSizing( ECWMSize size_setting ); + + /* + Requests a slot on the menu bar. + This is to ensure that there is available space on the menu bar. + It may require adding another row. + */ + void requestBarSpace( UltraGUIMenu* menu ); + + /* + Accounts for the given menu leaving the bar. + This allows for other menus to occupy its former location. + */ + void leavingBar( UltraGUIMenu* menu ); + + /* + Get sprite bank for menus. + This sprite bank contains textures for the menus that will be + used when the menu is hovered over. + */ + IGUISpriteBank* getMenuSpriteBank(); + + + // ---------- Engine stuff ------------ + + virtual bool OnEvent( const irr::SEvent& event ); + + virtual void updateAbsolutePosition(); + + virtual void draw(); + + /* For any animation of the frame */ + virtual void OnPostRender( irr::u32 timeMs ); + + /* This GUI element is actually determined by what is visible to + the user. Hence, at any given time, whether or not a point is actually + hovering over this frame is determined by the position of the mouse. + For the left, right, and center, whether or not there is a menu being + displayed determines if the mouse is in the frame. On the bottom, there + is a menu bar that always exists. It is this that is checked first. */ + virtual bool isPointInside( const vector2di& point ) const; + + + // ---------- Calculating destinations + + /* Returns the position where the lower left corner of the + menu should reside when it is the left-side menu. */ + vector2di getLeftSidePosition(); + + /* Returns the position where the lower right corner of the + menu should reside when it is the right-side menu. */ + vector2di getRightSidePosition(); + + /* Returns the position where the exact center of the menu + should reside when it is the center menu. */ + vector2di getCenterCenterPosition(); + + /* Returns the next open location on the bar where the upper left + corner of the menu should reside when it is a bar icon. + IMPORTANT NOT: This increments the position, and thus it should + not be called by anything but menus that are children of this frame. */ + vector2di getBarSlotPosition(); + + + /* Returns the actual available size for the central window menu, + working in cooperation with getCenterCenterPosition() */ + dimension2du getCenterWindowSize(); + + + /* Returns the height of the menu bar. + Useful if the implementation of the menu bar ever changes. */ + irr::s32 getBarHeight(); + + + // ---------- Drag-to regions + + /* Functions that indicate if the mouse is in a region + where a menu can be dragged. The menus themselves will + determine their own sizing, but this ensures that every menu + is just as easy to drag around the screen as every other. + Furthermore, it ensures that screen re-sizing and small screens + can be accounted for. */ + bool isInLeftDragRegion( irr::s32 x, irr::s32 y ); + bool isInRightDragRegion( irr::s32 x, irr::s32 y ); + bool isInCenterDragRegion( irr::s32 x, irr::s32 y ); + bool isInBarDragRegion( irr::s32 x, irr::s32 y ); + + + // ---------- Internal stuff ----------- + + recti getFrameRectChange(); +}; + +}} // end namespace gui and irr + +#endif // #ifndef _ULTRAGUIFRAME_ \ No newline at end of file diff --git a/IrrExtensions/gui/UltraGUI/UltraGUIMenu.cpp b/IrrExtensions/gui/UltraGUI/UltraGUIMenu.cpp new file mode 100644 index 0000000..e0afe88 --- /dev/null +++ b/IrrExtensions/gui/UltraGUI/UltraGUIMenu.cpp @@ -0,0 +1,1415 @@ +/* +(c) 2013 Nicolaus Anderson +*/ + +#include "UltraGUIMenu.h" +#include "UltraGUIFrame.h" +#include + +namespace irr +{ +namespace gui +{ + +UltraGUIMenu::UltraGUIMenu( + UltraGUIFrame* frame, + irr::s32 id, + irr::core::recti rectangle, + irr::io::path resource_path + ) + : IAnimGUIElement( + EGUIET_ELEMENT, + frame->getEnvironment(), + frame,// was frame->getParent() + id, + irr::core::recti()//rectangle + ) + , ultraframe( frame ) + , menuState( EUGUIMState_ToLeft_Open ) + , menuLocation( EUGUIMLoc_Left ) + , menuCloseSetting( EUGUIMClose_Minimize ) + , queuedState( EUGUIMState_ToLeft_Open ) + , inTransition( false ) + , refreshState( false ) + , transitionSpeed( ULTRAGUI_DEFAULT_MOVE_SPEED ) + , transitionColor( frame->getEnvironment()->getSkin()->getColor(EGDC_WINDOW) ) //( 50, 150, 150, 150 ) // transparent gray + , lastTime( 0 ) + , currRect( rectangle ) + , destinyRect( rectangle ) + , leftSideSize( irr::core::dimension2du(250,400) ) + , rightSideSize( irr::core::dimension2du(250,400) ) + , centerSize( irr::core::dimension2du(600,400) ) + , barSlotSize( irr::core::dimension2du(100,40) ) + , barSprite( 0 ) + , headerColorTop( ULTRAGUI_DEFAULT_HEADER_COLOR_TOP ) + , headerColorMid( ULTRAGUI_DEFAULT_HEADER_COLOR_MID ) + , headerColorBottom( ULTRAGUI_DEFAULT_HEADER_COLOR_BOTTOM ) + , hasBarSpriteBorder( false ) + , buttonSize( + ULTRAGUI_DEFAULT_MENU_BUTTON_SIZE, + ULTRAGUI_DEFAULT_MENU_BUTTON_SIZE + ) + , isSelected( false ) + , isDragging( false ) + , resetTime( 0 ) + , IsHighlighted( false ) + , highlightTime( 0 ) + , highlightColor( irr::video::SColor(0,125,125,255) ) + , loadHighlight( false ) +{ + ultraframe->grab(); + + /* Animator to be used in moving this element. */ + moveAnimator = new MoveGUIAnimator( &currRect, transitionSpeed, 0 ); + addAnimator( moveAnimator ); + + /* Create the title text. + This is updated with the buttons. + Note that in bar mode, this is invisible. */ + titleTextBox = rectangle; + titleTextBox.LowerRightCorner.Y = titleTextBox.UpperLeftCorner.Y + ultraframe->headerHeight; + + + /* Prepare the buttons. + Notice, the buttons are NOT children of this element. + This is to keep them separate from the other children of this + element because the buttons must be handled differently. */ + irr::core::recti btn_rect(0,0, (irr::s32)buttonSize.Width, (irr::s32)buttonSize.Height); + + closeButton = frame->getEnvironment()->addButton( btn_rect, this ); //, -1, 0, L"Close" ); + toLeftButton = frame->getEnvironment()->addButton( btn_rect, this ); //, -1, 0, L"Move to Left" ); + toRightButton = frame->getEnvironment()->addButton( btn_rect, this ); //, -1, 0, L"Move to Right" ); + toCenterButton = frame->getEnvironment()->addButton( btn_rect, this ); //, -1, 0, L"Move to Center" ); + minimizeButton = frame->getEnvironment()->addButton( btn_rect, this ); //, -1, 0, L"Minimize" ); + + /* Make the buttons sub-elements so that their events can be handled + by this GUI element. Note that this GUI element must be the direct + parent. */ + closeButton->setSubElement(true); + toLeftButton->setSubElement(true); + toRightButton->setSubElement(true); + toCenterButton->setSubElement(true); + minimizeButton->setSubElement(true); + + /* Remove the default border around the buttons (since the button + textures drawn by this element take up the full button. */ + closeButton->setDrawBorder(false); + toLeftButton->setDrawBorder(false); + toRightButton->setDrawBorder(false); + toCenterButton->setDrawBorder(false); + minimizeButton->setDrawBorder(false); + + // Close button image + closeButton->setImage( + frame->getEnvironment()->getVideoDriver()->getTexture( + resource_path + ULTRAGUI_RESOURCE_CLOSEBUTTON + ) + ); + closeButton->setUseAlphaChannel(true); + + // To-Left button image + toLeftButton->setImage( + frame->getEnvironment()->getVideoDriver()->getTexture( + resource_path + ULTRAGUI_RESOURCE_TOLEFTBUTTON + ) + ); + toLeftButton->setUseAlphaChannel(true); + + // To-Right button image + toRightButton->setImage( + frame->getEnvironment()->getVideoDriver()->getTexture( + resource_path + ULTRAGUI_RESOURCE_TORIGHTBUTTON + ) + ); + toRightButton->setUseAlphaChannel(true); + + // To-center button image + toCenterButton->setImage( + frame->getEnvironment()->getVideoDriver()->getTexture( + resource_path + ULTRAGUI_RESOURCE_TOCENTERBUTTON + ) + ); + toCenterButton->setUseAlphaChannel(true); + + // Minimize button image + minimizeButton->setImage( + frame->getEnvironment()->getVideoDriver()->getTexture( + resource_path + ULTRAGUI_RESOURCE_MINIMIZEBUTTON + ) + ); + minimizeButton->setUseAlphaChannel(true); + + // Get the sprite bank + IGUISpriteBank* spritebank = ultraframe->getMenuSpriteBank(); + + if ( !spritebank ) + return; + + /* Assign the sprite bank to all of the buttons. + All we want is for them to have the same effect on hover + (and maybe mouse-pressed-down). */ + closeButton->setSpriteBank( spritebank ); + toLeftButton->setSpriteBank( spritebank ); + toRightButton->setSpriteBank( spritebank ); + toCenterButton->setSpriteBank( spritebank ); + minimizeButton->setSpriteBank( spritebank ); + + /* Set the button to use the first sprite (hoverSprite) as the + sprite for the hovering state and the second sprite (hoverOffSprite) + as the sprite for the state when the mouse moves away. */ + closeButton->setSprite( + EGBS_BUTTON_MOUSE_OVER, + 0, // hover sprite index in the sprite bank + ULTRAGUI_DEFAULT_MENU_BUTTON_HOVERCOLOR, + //irr::video::SColor(200,0,80,250), // blue used for coloring + false // loop frames + ); + closeButton->setSprite( + EGBS_BUTTON_MOUSE_OFF, + 1, // hover off sprite index in the sprite bank + ULTRAGUI_DEFAULT_MENU_BUTTON_HOVERCOLOR, + false // loop frames + ); + + toLeftButton->setSprite( + EGBS_BUTTON_MOUSE_OVER, + 0, // hover sprite index in the sprite bank + ULTRAGUI_DEFAULT_MENU_BUTTON_HOVERCOLOR, + false // loop frames + ); + toLeftButton->setSprite( + EGBS_BUTTON_MOUSE_OFF, + 1, // hover off sprite index in the sprite bank + ULTRAGUI_DEFAULT_MENU_BUTTON_HOVERCOLOR, + false // loop frames + ); + + toRightButton->setSprite( + EGBS_BUTTON_MOUSE_OVER, + 0, // hover sprite index in the sprite bank + ULTRAGUI_DEFAULT_MENU_BUTTON_HOVERCOLOR, + false // loop frames + ); + toRightButton->setSprite( + EGBS_BUTTON_MOUSE_OFF, + 1, // hover off sprite index in the sprite bank + ULTRAGUI_DEFAULT_MENU_BUTTON_HOVERCOLOR, + false // loop frames + ); + + toCenterButton->setSprite( + EGBS_BUTTON_MOUSE_OVER, + 0, // hover sprite index in the sprite bank + ULTRAGUI_DEFAULT_MENU_BUTTON_HOVERCOLOR, + false // loop frames + ); + toCenterButton->setSprite( + EGBS_BUTTON_MOUSE_OFF, + 1, // hover off sprite index in the sprite bank + ULTRAGUI_DEFAULT_MENU_BUTTON_HOVERCOLOR, + false // loop frames + ); + + minimizeButton->setSprite( + EGBS_BUTTON_MOUSE_OVER, + 0, // hover sprite index in the sprite bank + ULTRAGUI_DEFAULT_MENU_BUTTON_HOVERCOLOR, + false // loop frames + ); + minimizeButton->setSprite( + EGBS_BUTTON_MOUSE_OFF, + 1, // hover off sprite index in the sprite bank + ULTRAGUI_DEFAULT_MENU_BUTTON_HOVERCOLOR, + false // loop frames + ); + + /* It is expected that UltraGUIFrame will call refreshButtons() + since 1) it can't be done here (it isn't a constant function) + and 2) other things will have to be done to this menu anyways + (such as setting the dimensions). */ + +} // END FREAKING HUGE CONSTRUCTOR + +UltraGUIMenu::~UltraGUIMenu() +{ + /* Because it was grabbed in the constructor, the frame + is dropped here. */ + ultraframe->drop(); + + if ( barSprite ) + barSprite->drop(); + + closeButton->drop(); + toLeftButton->drop(); + toRightButton->drop(); + toCenterButton->drop(); + minimizeButton->drop(); + + // Remove all animators + clearAnimators(); +} + +void UltraGUIMenu::setMyState( EUltraMenuState state, bool noHighlight ) +{ + // Logic: Request the menu state be changed by queuing a new state. + + // Please remove the following if these states are removed!!! + /* Ignore states that are only partials (ex: opening and closing). */ + if ( state == EUGUIMState_Opening || state == EUGUIMState_Closing ) + { + return; // Cannot set the state + } + + // Please remove the following if these states are removed!!! + if ( state == EUGUIMState_Open ) + { + switch ( menuLocation ) + { + case EUGUIMLoc_Right: + queuedState = EUGUIMState_ToRight_Open; + break; + + case EUGUIMLoc_Center: + queuedState = EUGUIMState_ToCenter_Open; + break; + + case EUGUIMLoc_Bar: + queuedState = EUGUIMState_ToBar; + break; + + default: + queuedState = EUGUIMState_ToLeft_Open; + break; + } + + // Please remove the following if these states are removed!!! + } else if ( state == EUGUIMState_Closed ) + { + switch ( menuLocation ) + { + case EUGUIMLoc_Right: + queuedState = EUGUIMState_ToRight_Closed; + break; + + case EUGUIMLoc_Center: + queuedState = EUGUIMState_ToCenter_Closed; + break; + + case EUGUIMLoc_Bar: + queuedState = EUGUIMState_ToBar; + break; + + default: + queuedState = EUGUIMState_ToLeft_Closed; + break; + } + } else { + queuedState = state; + } + + /* Account for moving to and from the bar. */ + + if ( menuLocation == EUGUIMLoc_Bar + && queuedState != EUGUIMState_ToBar ) + { + ultraframe->leavingBar( this ); + setChildrenOn(true); + } + + if ( menuLocation != EUGUIMLoc_Bar + && queuedState == EUGUIMState_ToBar ) + { + ultraframe->requestBarSpace(this); + setChildrenOn(false); + } + + // Already open? + if ( !noHighlight ) + if ( + ( menuState == EUGUIMState_ToLeft_Open + || menuState == EUGUIMState_ToRight_Open + || menuState == EUGUIMState_ToCenter_Open + ) && ( + menuState == queuedState + ) + ) + { + loadHighlight = true; + } + + // Set the destination/target rectangle + setDestinyByQueue(); +} + +void UltraGUIMenu::snapState() +{ + inTransition = false; + + // Assume the menu is leaving the bar + if ( menuLocation == EUGUIMLoc_Bar ) + { + ultraframe->leavingBar( this ); + } + + switch( queuedState ) + { + case EUGUIMState_ToLeft_Open: + menuLocation = EUGUIMLoc_Left; + currRect = getLeftRectangle(); + break; + + case EUGUIMState_ToLeft_Closed: + menuLocation = EUGUIMLoc_Left; + currRect = getClosedLeftRectangle(); + break; + + case EUGUIMState_ToRight_Open: + menuLocation = EUGUIMLoc_Right; + currRect = getRightRectangle(); + break; + + case EUGUIMState_ToRight_Closed: + menuLocation = EUGUIMLoc_Right; + currRect = getClosedRightRectangle(); + break; + + case EUGUIMState_ToCenter_Open: + menuLocation = EUGUIMLoc_Center; + currRect = getCenterRectangle(); + break; + + case EUGUIMState_ToCenter_Closed: + menuLocation = EUGUIMLoc_Center; + currRect = getClosedCenterRectangle(); + break; + + case EUGUIMState_ToBar: + // Add the menu back onto the bar + ultraframe->requestBarSpace(this); + menuLocation = EUGUIMLoc_Bar; + currRect = getBarRectangle(); + break; + + default: + break; + } + + menuState = queuedState; + + destinyRect = currRect; +} + +EUltraMenuLocation UltraGUIMenu::locationFromState( EUltraMenuState state ) +{ + switch( state ) + { + case EUGUIMState_ToRight_Open: + case EUGUIMState_ToRight_Closed: + return EUGUIMLoc_Right; + + case EUGUIMState_ToCenter_Open: + case EUGUIMState_ToCenter_Closed: + return EUGUIMLoc_Center; + + case EUGUIMState_ToBar: + return EUGUIMLoc_Bar; + + default: + return EUGUIMLoc_Left; + } +} + +void UltraGUIMenu::setMenuBarSprite( irr::video::ITexture* sprite ) +{ + if ( barSprite ) + barSprite->drop(); + + barSprite = 0; /* So it isn't referenced again. */ + + if ( sprite ) + { + barSprite = sprite; + barSprite->grab(); + } +} + +bool UltraGUIMenu::OnEvent( const irr::SEvent& event ) +{ + if ( !isEnabled() || !isVisible() || inTransition ) + return IGUIElement::OnEvent(event); + + /* irrlicht 1.7.2 does not have the subElement feature, + so you will have to separate buttons and check them yourself + if you wish to use the earlier version. */ + + // This is an open menu, so it can receive event activity + // e.g. click and drag to new designated region + switch ( event.EventType ) + { + case EET_MOUSE_INPUT_EVENT: + + switch( event.MouseInput.Event ) + { + case EMIE_LMOUSE_PRESSED_DOWN: + if ( currRect.isPointInside( + irr::core::vector2di( event.MouseInput.X, event.MouseInput.Y ) + ) ) + { + ultraframe->bringToFront(this); + + // Set to selected + isSelected = true; + + // Prevent other elements from processing + return true; + } + break; + + case EMIE_MOUSE_MOVED: + if ( isSelected ) + { + isDragging = true; + /* Does NOT return true - allows other things to process + with mouse movement. */ + return false; + } + break; + + case EMIE_LMOUSE_LEFT_UP: + + // Environment Focus is irrelevant at this point + + if ( isSelected ) + { + isSelected = false; + + if ( isDragging ) + { + isDragging = false; + + // Drag to left + if ( + ultraframe->isInLeftDragRegion( + event.MouseInput.X, + event.MouseInput.Y ) + ) + { + setMyState( EUGUIMState_ToLeft_Open, true ); + return true; + } + + // Drag to right + else if ( + ultraframe->isInRightDragRegion( + event.MouseInput.X, + event.MouseInput.Y ) + ) + { + setMyState( EUGUIMState_ToRight_Open, true ); + return true; + } + + // Drag to bar + else if ( + ultraframe->isInBarDragRegion( + event.MouseInput.X, + event.MouseInput.Y ) + ) + { + setMyState( EUGUIMState_ToBar, true ); + return true; + } + + // Drag to center + else if ( + ultraframe->isInCenterDragRegion( + event.MouseInput.X, + event.MouseInput.Y ) + ) + { + setMyState( EUGUIMState_ToCenter_Open, true ); + return true; + } + } + + /* The bar state acts different from other states. + When you click on the bar without hitting a button, + it causes the menu to move to the center. + NOTE: This is AFTER dragging because dragging must + take precedence. + If not dependent on isSelected, the environment focus may + be required because if this menu is NOT the focus, it + could mean the user is dragging a menu to the bar. */ + if ( isPointInside( + irr::core::position2di( event.MouseInput.X, event.MouseInput.Y ) + ) + && menuLocation == EUGUIMLoc_Bar + //&& Environment->getFocus() == this + // ^ removed because the menu is selected (in isSelected condition) + ) + { + // Move to center + setMyState( EUGUIMState_ToCenter_Open ); + + // Make the active GUI element + ultraframe->bringToFront(this); + + // Prevent further processing + return true; + } + + } // end isSelected condition + + /* Does NOT return true by default because nothing + has happened to this element. In fact, it may not have + even been selected. */ + break; + + default: + break; + } + + break; // mouse input event + + // ========== GUI Event =========== + case EET_GUI_EVENT: + + switch ( event.GUIEvent.EventType ) + { + case EGET_ELEMENT_FOCUS_LOST: + isSelected = false; + isDragging = false; + break; + + case EGET_ELEMENT_HOVERED: + break; + + /* If this is just a GUI focus event, then the buttons will + call the parent (this element) for click events. + If a button has been clicked, then find which button has + been clicked and act accordingly. */ + + case EGET_ELEMENT_FOCUSED: + break; + + case EGET_BUTTON_CLICKED: + ultraframe->bringToFront(this); + + if ( event.GUIEvent.Caller == closeButton ) + { + // If only told to minimize, go to the bar + if ( menuCloseSetting == EUGUIMClose_Minimize ) + { setMyState( EUGUIMState_ToBar ); + } else { + switch ( menuLocation ) + { + case EUGUIMLoc_Left: + setMyState( EUGUIMState_ToLeft_Closed ); + break; + case EUGUIMLoc_Right: + setMyState( EUGUIMState_ToRight_Closed ); + break; + case EUGUIMLoc_Center: + setMyState( EUGUIMState_ToCenter_Closed ); + break; + default: // Do nothing with bar + break; + } + //setMyState( EUGUIMState_Closed ); + } + return true; + } + else if ( event.GUIEvent.Caller == toLeftButton ) + { + setMyState( EUGUIMState_ToLeft_Open ); + ultraframe->bringToFront(this); + return true; + } + else if ( event.GUIEvent.Caller == toRightButton ) + { + setMyState( EUGUIMState_ToRight_Open ); + ultraframe->bringToFront(this); + return true; + } + else if ( event.GUIEvent.Caller == toCenterButton ) + { + setMyState( EUGUIMState_ToCenter_Open ); + ultraframe->bringToFront(this); + return true; + } + else if ( event.GUIEvent.Caller == minimizeButton ) + { + setMyState( EUGUIMState_ToBar ); + return true; + } + + break; + } + break; // GUI event + + default: + break; + } + + return IGUIElement::OnEvent(event); +} + +void UltraGUIMenu::draw() +{ + // Don't bother drawing if not visible or off-screen + if ( !isVisible() || menuState == EUGUIMState_Closed ) + return; + + irr::core::recti drawRect = currRect; + drawRect.clipAgainst( ultraframe->getAbsoluteClippingRect() ); + + if ( inTransition ) + { + Environment->getSkin()->draw2DRectangle(this, transitionColor, drawRect); + return; + } + + // For drawing the header + // Rectangle used for determining the drawing area + irr::core::recti headerDrawRect( + currRect.UpperLeftCorner.X + 1, + currRect.UpperLeftCorner.Y + 1, + currRect.LowerRightCorner.X - 1, + currRect.UpperLeftCorner.Y + (ultraframe->headerHeight / 3) + ); + // Colors of the header + irr::video::SColor headerColor1 = headerColorTop; + irr::video::SColor headerColor2 = headerColorMid; + irr::video::SColor headerShadow = irr::video::SColor(155,0,0,0); + + // Sprite border box (may be removed or made optional) + irr::core::recti spriteBorder; + + + // Only draw the children and other aspects if not in transition + + if ( menuLocation == EUGUIMLoc_Bar ) // on the menu bar + { + // Draw as a button + Environment->getSkin()->draw3DButtonPaneStandard(this,drawRect); + + // Draw the icon (just after the toLeftButton) + if ( barSprite ) + { + // Location of the bar sprite = center of the bar + spriteBorder.UpperLeftCorner.X = currRect.getCenter().X - barSprite->getSize().Width/2; + spriteBorder.UpperLeftCorner.Y = currRect.getCenter().Y - barSprite->getSize().Height/2; + // lower right corner is the image dimension away from upper left + spriteBorder.LowerRightCorner.X = spriteBorder.UpperLeftCorner.X + (s32)barSprite->getSize().Width; + spriteBorder.LowerRightCorner.Y = spriteBorder.UpperLeftCorner.Y + (s32)barSprite->getSize().Height; + + // Draw the sprite + Environment->getVideoDriver()->draw2DImage( + barSprite, + irr::core::vector2di( + currRect.getCenter().X - barSprite->getSize().Width/2, + currRect.getCenter().Y - barSprite->getSize().Height/2 + ), + irr::core::recti(0,0, (s32)barSprite->getSize().Width, (s32)barSprite->getSize().Height), + &spriteBorder, + irr::video::SColor(255,255,255,255), + true + ); + + // ---- Draw the border of the icon ---- + if ( hasBarSpriteBorder ) + { + spriteBorder.UpperLeftCorner.X = currRect.getCenter().X + barSprite->getSize().Width/2 + 1; + spriteBorder.UpperLeftCorner.Y = currRect.getCenter().Y - barSprite->getSize().Height/2 - 1; + spriteBorder.LowerRightCorner.X = spriteBorder.UpperLeftCorner.X + 1; + spriteBorder.LowerRightCorner.Y = spriteBorder.UpperLeftCorner.Y + barSprite->getSize().Height + 2; + + Environment->getSkin()->draw2DRectangle( this, + Environment->getSkin()->getColor( EGDC_3D_DARK_SHADOW ), + spriteBorder ); + } + + } + + // Draw the buttons + toLeftButton->draw(); + toRightButton->draw(); + /* Not needed since the center of the bar brings the menu + to the center. */ + + /* Notice that the other two need not be drawn because the + bar won't fit them. */ + + } else { + // Don't draw if closed + if ( menuState == EUGUIMState_ToLeft_Closed + || menuState == EUGUIMState_ToRight_Closed + || menuState == EUGUIMState_ToCenter_Closed ) + { return; } + + // Standard drawing + Environment->getSkin()->draw3DMenuPane(this, drawRect); + + // Draw the header box for pizaz + if ( Environment->getFocus() == this ) + { + // Draw top part + Environment->getVideoDriver()->draw2DRectangle( + headerDrawRect, + headerColor1, headerColor1, + headerColor2, headerColor2, + &drawRect ); + + // Draw the mid-section + headerDrawRect.UpperLeftCorner.Y = currRect.UpperLeftCorner.Y + (ultraframe->headerHeight / 3); + headerDrawRect.LowerRightCorner.Y = currRect.UpperLeftCorner.Y + (ultraframe->headerHeight / 3 + 2); + + Environment->getVideoDriver()->draw2DRectangle( + headerDrawRect, + headerColor2, headerColor2, + headerColor2, headerColor2, + &drawRect ); + + // Draw the bottom section + headerDrawRect.UpperLeftCorner.Y = currRect.UpperLeftCorner.Y + (ultraframe->headerHeight / 3 + 2); + headerDrawRect.LowerRightCorner.Y = currRect.UpperLeftCorner.Y + ultraframe->headerHeight - 1; + + headerColor1 = headerColorBottom; + + Environment->getVideoDriver()->draw2DRectangle( + headerDrawRect, + headerColor2, headerColor2, + headerColor1, headerColor1, + &drawRect ); + + // Draw the bottom line + headerDrawRect.UpperLeftCorner.Y = currRect.UpperLeftCorner.Y + ultraframe->headerHeight - 1; + headerDrawRect.LowerRightCorner.Y = currRect.UpperLeftCorner.Y + ultraframe->headerHeight; + + Environment->getVideoDriver()->draw2DRectangle( + headerDrawRect, + headerShadow, headerShadow, + headerShadow, headerShadow, + &drawRect ); + + } else { + // Not in focus + + Environment->getVideoDriver()->draw2DRectangle( + irr::core::recti( + currRect.UpperLeftCorner.X + 1, + currRect.UpperLeftCorner.Y + 1, + currRect.LowerRightCorner.X - 1, + currRect.UpperLeftCorner.Y + ultraframe->headerHeight - 1 + ), + irr::video::SColor(255,0,0,0), irr::video::SColor(255,0,0,0), + irr::video::SColor(255,100,100,100), irr::video::SColor(255,100,100,100), + &drawRect ); + + // Draw the underline bar (for definition of the header box) + Environment->getVideoDriver()->draw2DRectangle( + irr::core::recti( + currRect.UpperLeftCorner.X + 1, + currRect.UpperLeftCorner.Y + ultraframe->headerHeight - 1, + currRect.LowerRightCorner.X - 1, + currRect.UpperLeftCorner.Y + ultraframe->headerHeight + ), + headerShadow, headerShadow, + headerShadow, headerShadow, + &drawRect ); + } + + // Draw the icon of this element + if ( barSprite ) + { + spriteBorder.UpperLeftCorner = currRect.UpperLeftCorner + 5; + spriteBorder.LowerRightCorner.X = currRect.UpperLeftCorner.X + (s32)barSprite->getSize().Width + 5; + spriteBorder.LowerRightCorner.Y = currRect.UpperLeftCorner.Y + (s32)barSprite->getSize().Height + 5; + + Environment->getVideoDriver()->draw2DImage( + barSprite, + currRect.UpperLeftCorner + 5, // ULC.x+5, ULC.y+5 + irr::core::recti(0,0, (s32)barSprite->getSize().Width, (s32)barSprite->getSize().Height), + &spriteBorder, + irr::video::SColor(255,255,255,255), + true + ); + + // ---- Draw the border of the icon ---- + if ( hasBarSpriteBorder ) + { + + // ulc to urc + spriteBorder.UpperLeftCorner = currRect.UpperLeftCorner + 4; + spriteBorder.LowerRightCorner.Y = spriteBorder.UpperLeftCorner.Y + 1; + spriteBorder.LowerRightCorner.X = spriteBorder.UpperLeftCorner.X + barSprite->getSize().Width + 2; + + Environment->getSkin()->draw2DRectangle( this, + Environment->getSkin()->getColor( EGDC_3D_LIGHT ), + spriteBorder ); + + // urc to lrc + spriteBorder.UpperLeftCorner = spriteBorder.LowerRightCorner - 1; + spriteBorder.LowerRightCorner.Y += barSprite->getSize().Height + 1; + + Environment->getSkin()->draw2DRectangle( this, + Environment->getSkin()->getColor( EGDC_3D_SHADOW ), + spriteBorder ); + + // lrc to llc + spriteBorder.UpperLeftCorner.X = spriteBorder.LowerRightCorner.X - (barSprite->getSize().Width + 2); + spriteBorder.UpperLeftCorner.Y = spriteBorder.LowerRightCorner.Y - 1; + + Environment->getSkin()->draw2DRectangle( this, + Environment->getSkin()->getColor( EGDC_3D_DARK_SHADOW ), + spriteBorder ); + + // llc to ulc + spriteBorder.LowerRightCorner = spriteBorder.UpperLeftCorner + 1; + spriteBorder.UpperLeftCorner.Y -= barSprite->getSize().Height; + + Environment->getSkin()->draw2DRectangle( this, + Environment->getSkin()->getColor( EGDC_3D_LIGHT ), + spriteBorder ); + + } + // End draw border for icon + } + + // Draw the child elements if this is not a bar + if ( menuLocation != EUGUIMLoc_Bar ) + IGUIElement::draw(); + + // Header text shadow + titleTextBox += irr::core::vector2di(1,1); + Environment->getSkin()->getFont()->draw( + Text, titleTextBox, // irr::video::SColor(255,255,255,255), + Environment->getSkin()->getColor( EGDC_3D_DARK_SHADOW ), + false, false, + &drawRect ); + + // Header text + titleTextBox -= irr::core::vector2di(1,1); + Environment->getSkin()->getFont()->draw( + Text, titleTextBox, // irr::video::SColor(255,255,255,255), + Environment->getSkin()->getColor( EGDC_HIGH_LIGHT_TEXT ), + false, false, + &drawRect ); + + // Drawing the highlight + if ( isHighlighted() ) + { + Environment->getSkin()->draw2DRectangle(this, highlightColor, drawRect); + } + } // else not on the menu bar +} + +void UltraGUIMenu::OnPostRender( irr::u32 timeMs ) +{ + if ( isHighlighted() ) + { + highlightTime = timeMs - resetTime; + + if ( highlightTime < ULTRAGUI_HIGHLIGHT_TIME ) + { + highlightColor.setAlpha( + irr::core::clamp( + 255*highlightTime/(ULTRAGUI_HIGHLIGHT_TIME*2), + (irr::u32)0, + (irr::u32)255 ) + ); + } else { + highlightColor.setAlpha( + irr::core::clamp( + 255 - 255*highlightTime/(ULTRAGUI_HIGHLIGHT_TIME*2), + (irr::u32)0, + (irr::u32)255 ) + ); + } + + if ( highlightTime >= ULTRAGUI_HIGHLIGHT_TIME*2 ) + { + resetDrawHighlight(); + } + } + + if ( menuState == queuedState ) + { + if ( loadHighlight ) + { + loadHighlight = false; + resetDrawHighlight(); + queueDrawHighlight(timeMs); // must be called second + } + } + + moveToQueuedLocation(timeMs); + + // Save the last time run + lastTime = timeMs; + + IGUIElement::OnPostRender(timeMs); +} + +bool UltraGUIMenu::isPointInside(const irr::core::position2di& point) const +{ + return currRect.isPointInside(point); +} + +void UltraGUIMenu::updateAbsolutePosition() +{ + /* If ultraframe has been resized, resize this for the new frame. + Note that this also accounts for relocation of the frame with or + without size changes. */ + irr::core::recti frameSizeDif = ultraframe->getFrameRectChange(); + + + // ********** To uncomment and finish implementing later *********** + + //if ( menuResize == EUGUIMResize_ToScale ) + //{ + // currRect.UpperLeftCorner += frameSizeDif.UpperLeftCorner; + // currRect.LowerRightCorner += frameSizeDif.LowerRightCorner; + //} else { + // No scaling, just location shift + currRect += frameSizeDif.UpperLeftCorner; + //} + + // Update Absolute Rectangle + /* Note that the rectangle is only for the elements within the menu. */ + + AbsoluteRect = currRect; + AbsoluteClippingRect = AbsoluteRect; + + // Update buttons before changing the Absolute Rectangle + refreshButtons(); + + // Change the Absolute Rectangle for the other elements + AbsoluteRect.UpperLeftCorner.Y += ultraframe->headerHeight; + AbsoluteClippingRect = AbsoluteRect; + + // update all children + irr::core::list::Iterator it = Children.begin(); + for (; it != Children.end(); ++it) + { + (*it)->updateAbsolutePosition(); + } +} + +irr::core::recti UltraGUIMenu::getLeftRectangle() +{ + // lower left corner + irr::core::vector2di leftSpot = ultraframe->getLeftSidePosition(); + leftSpot.Y -= leftSideSize.Height; + + // lower right corner + irr::core::vector2di lowerSpot = leftSpot + irr::core::dimension2di(leftSideSize); + + return irr::core::recti( leftSpot, lowerSpot ); +} + +irr::core::recti UltraGUIMenu::getRightRectangle() +{ + // lower right corner + irr::core::vector2di rightSpot = ultraframe->getRightSidePosition(); + + // upper left corner + irr::core::vector2di upperSpot = rightSpot - irr::core::dimension2di(rightSideSize); + + return irr::core::recti( upperSpot, rightSpot ); +} + +irr::core::recti UltraGUIMenu::getCenterRectangle() +{ + irr::core::vector2di centerSpot = ultraframe->getCenterCenterPosition(); + + irr::core::dimension2di actualSize( (irr::s32)centerSize.Width, (irr::s32)centerSize.Height ); + // Calculate actual center size based on center window settings + irr::core::dimension2di available = irr::core::dimension2di(ultraframe->getCenterWindowSize()); + + if ( available.Width < actualSize.Width ) + actualSize.Width = available.Width; + + if ( available.Height < actualSize.Height ) + actualSize.Height = available.Height; + + // Construct the window bounds and return them + return irr::core::recti( + centerSpot - actualSize / 2, + centerSpot + actualSize / 2 + ); +} + +irr::core::recti UltraGUIMenu::getBarRectangle() +{ + // upper left corner + irr::core::vector2di upperSpot = ultraframe->getBarSlotPosition(); + + return irr::core::recti( + upperSpot, + upperSpot + irr::core::dimension2di(barSlotSize) + ); +} + +irr::core::recti UltraGUIMenu::getClosedLeftRectangle() +{ + // get lower left corner + irr::core::vector2di leftSpot = ultraframe->getLeftSidePosition(); + + // move to upper left corner and off screen + leftSpot -= irr::core::dimension2di( leftSideSize ); + + // fully hidden but full size + return irr::core::recti( leftSpot, leftSideSize ); +} + +irr::core::recti UltraGUIMenu::getClosedRightRectangle() +{ + // get lower left corner + irr::core::vector2di rightSpot = ultraframe->getRightSidePosition(); + + // move to upper left corner + rightSpot.Y -= rightSideSize.Height; + + // now move off screen to the right + rightSpot.X += rightSideSize.Width; + + // fully hidden but full size + return irr::core::recti( rightSpot, rightSideSize ); +} + +irr::core::recti UltraGUIMenu::getClosedCenterRectangle() +{ + // This is convenient - no fancy calculations necessary :D + return irr::core::recti(); +} + +irr::core::recti UltraGUIMenu::getClosedBarRectangle() +{ + return getBarRectangle(); +} + +void UltraGUIMenu::moveToQueuedLocation( irr::u32 timeMs ) +{ + // Move the rectangle in the direction of the destiny rectangle + //interpolateCurrToRect( destinyRect, timeMs ); + + if ( + ( !inTransition && menuState != queuedState ) + || refreshState + ) + { + // Target state has been refreshed + refreshState = false; + + ((MoveGUIAnimator*)moveAnimator)->setImmediateTarget( destinyRect, timeMs ); + inTransition = true; + } + moveAnimator->OnPostRender( timeMs, this ); + + /* Check for a matching destiny rectangle and current rectangle. + This means that the GUI menu has reached its destination and the + state can change. */ + if ( currRect == destinyRect ) + { + menuLocation = locationFromState( queuedState ); + menuState = queuedState; + inTransition = false; + + // Update Absolute Rectangle + AbsoluteRect = currRect; + AbsoluteClippingRect = AbsoluteRect; + + // Update buttons before changing the Absolute Rectangle + refreshButtons(); + + // Change the Absolute Rectangle for the other elements + AbsoluteRect.UpperLeftCorner.Y += ultraframe->headerHeight; + AbsoluteClippingRect = AbsoluteRect; + + // update all children + irr::core::list::Iterator it = Children.begin(); + for (; it != Children.end(); ++it) + { + if ( *it != closeButton && *it != toLeftButton && *it != toRightButton + && *it != toCenterButton && *it != minimizeButton ) + { + (*it)->updateAbsolutePosition(); + } + } + } +} + +void UltraGUIMenu::refresh() +{ + ultraframe->requestBarSpace(this); + setDestinyByQueue(); + refreshState = true; +} + +void UltraGUIMenu::setDestinyByQueue() +{ + // Obtain the destiny rectangle + switch ( queuedState ) + { + // left side + case EUGUIMState_ToLeft_Open: + destinyRect = getLeftRectangle(); + break; + + case EUGUIMState_ToLeft_Closed: + destinyRect = getClosedLeftRectangle(); + break; + + // right side + case EUGUIMState_ToRight_Open: + destinyRect = getRightRectangle(); + break; + + case EUGUIMState_ToRight_Closed: + destinyRect = getClosedRightRectangle(); + break; + + // to center + case EUGUIMState_ToCenter_Open: + destinyRect = getCenterRectangle(); + break; + + case EUGUIMState_ToCenter_Closed: + destinyRect = getClosedCenterRectangle(); + break; + + // to bar + case EUGUIMState_ToBar: + destinyRect = getBarRectangle(); + break; + + default: + break; + } + + destinyRect.repair(); +} + +void UltraGUIMenu::refreshButtons() +{ + irr::core::recti buttonLoc; + irr::core::dimension2di tTB_size; + + /* Update the buttons based on the current state and location of + the menus */ + + switch( menuState ) + { + /* Only time the buttons are visible is when the menu is + open. */ + case EUGUIMState_ToLeft_Open: + case EUGUIMState_ToRight_Open: + case EUGUIMState_ToCenter_Open: + case EUGUIMState_ToBar: + // Calculate first position ( lower right corner ) + buttonLoc.LowerRightCorner = + //currRect.UpperLeftCorner + //+ + irr::core::vector2di( + currRect.getWidth() - ULTRAGUI_BUTTON_RIGHT_PADDING, + (( menuLocation == EUGUIMLoc_Bar )? barSlotSize.Height : ultraframe->headerHeight - 3) + ); + + buttonLoc.UpperLeftCorner = buttonLoc.LowerRightCorner - irr::core::dimension2di( buttonSize ); + + /* For anywhere EXCEPT the bar menu, apply the following */ + if ( menuLocation != EUGUIMLoc_Bar ) + { + + // first button + assignRectToButton( buttonLoc, 1 ); + // second button + buttonLoc -= irr::core::vector2di( (irr::s32)buttonSize.Width, 0 ); + assignRectToButton( buttonLoc, 2 ); + // third button + buttonLoc -= irr::core::vector2di( (irr::s32)buttonSize.Width, 0 ); + assignRectToButton( buttonLoc, 3 ); + // fourth button + buttonLoc -= irr::core::vector2di( (irr::s32)buttonSize.Width, 0 ); + assignRectToButton( buttonLoc, 4 ); + } + else { // bar + + // Move to center of the bar + buttonLoc += irr::core::position2di( + 0, // no change in x + (0 - currRect.getHeight()/2) + buttonSize.Height/2 + ); + + // first button + assignRectToButton( buttonLoc, 1 ); + /* Move the button to the other side of the bar */ + buttonLoc -= irr::core::vector2di( + (irr::s32) (barSlotSize.Width - buttonSize.Width - 2*ULTRAGUI_BUTTON_RIGHT_PADDING), + 0 + ); + // second button + assignRectToButton( buttonLoc, 2 ); + } + + // Show and enable all buttons for the initial seeting + closeButton->setVisible(true); + toLeftButton->setVisible(true); + toRightButton->setVisible(true); + toCenterButton->setVisible(true); + minimizeButton->setVisible(true); + + closeButton->setEnabled(true); + toLeftButton->setEnabled(true); + toRightButton->setEnabled(true); + toCenterButton->setEnabled(true); + minimizeButton->setEnabled(true); + + // Based on location, turn buttons off + if ( menuLocation == EUGUIMLoc_Left ) + { + toLeftButton->setVisible(false); + toLeftButton->setEnabled(false); + } + + if ( menuLocation == EUGUIMLoc_Right ) + { + toRightButton->setVisible(false); + toRightButton->setEnabled(false); + } + + if ( menuLocation == EUGUIMLoc_Center ) + { + toCenterButton->setVisible(false); + toCenterButton->setEnabled(false); + } + + if ( menuLocation == EUGUIMLoc_Bar ) + { + minimizeButton->setVisible(false); + minimizeButton->setEnabled(false); + toCenterButton->setVisible(false); + toCenterButton->setEnabled(false); + // recall the bar icon is a button for sending the menu to center + } + + // Finally, update the title text + tTB_size = titleTextBox.getSize(); + + titleTextBox.UpperLeftCorner = irr::core::position2di( + ( barSprite? barSprite->getSize().Width + 12 : 12 ), + 5 + ); + titleTextBox.LowerRightCorner = titleTextBox.UpperLeftCorner + tTB_size; + + titleTextBox += currRect.UpperLeftCorner; + titleTextBox.clipAgainst( currRect ); + + break; + + /* Hide and disable buttons for every other state of this menu + since the menu is in transition or closed. */ + default: + closeButton->setVisible(false); + toLeftButton->setVisible(false); + toRightButton->setVisible(false); + toCenterButton->setVisible(false); + minimizeButton->setVisible(false); + + closeButton->setEnabled(false); + toLeftButton->setEnabled(false); + toRightButton->setEnabled(false); + toCenterButton->setEnabled(false); + minimizeButton->setEnabled(false); + break; + } +} + +void UltraGUIMenu::assignRectToButton( + irr::core::recti rectangle, + irr::u32 whichButton + ) +{ + // First slot + if ( whichButton == 1 ) + { + // On the bar, the first slot belongs to the toRight button + if ( menuLocation == EUGUIMLoc_Bar ) + { + toRightButton->setRelativePosition( rectangle ); + return; + } + + // Otherwise, it belongs to the close button + closeButton->setRelativePosition( rectangle ); + return; + } + + // Second slot + if ( whichButton == 2 ) + { + // On the bar, the second slot belongs to the to-left button + if ( menuLocation == EUGUIMLoc_Bar ) + { + toLeftButton->setRelativePosition( rectangle ); + return; + } + + // Check if this is not the center + // If not, this slot belongs to the to-center button + if ( menuLocation != EUGUIMLoc_Center ) + { + toCenterButton->setRelativePosition( rectangle ); + return; + } + + // Otherwise, this slot belongs to the minimize button + minimizeButton->setRelativePosition( rectangle ); + return; + } + + // Third slot + /* NOTE: This is never requested when the menu is on the bar, + so that location is not considered. */ + if ( whichButton == 3 ) + { + if ( menuLocation != EUGUIMLoc_Center ) + { + minimizeButton->setRelativePosition( rectangle ); + return; + } + + toRightButton->setRelativePosition( rectangle ); + return; + } + + // Fourth slot + if ( whichButton == 4 ) + { + if ( menuLocation == EUGUIMLoc_Left ) + { + toRightButton->setRelativePosition( rectangle ); + return; + } + + toLeftButton->setRelativePosition( rectangle ); + return; + } +} + +void UltraGUIMenu::setChildrenOn( bool setting ) +{ + irr::core::list::Iterator iter = Children.begin(); + for ( ; iter != Children.end(); ++iter ) + { + if ( *iter != closeButton && *iter != toLeftButton && *iter != toRightButton + && *iter != toCenterButton && *iter != minimizeButton ) + { + (*iter)->setVisible(setting); + (*iter)->setEnabled(setting); + } + } +} + +}} // end namespace gui and irr \ No newline at end of file diff --git a/IrrExtensions/gui/UltraGUI/UltraGUIMenu.h b/IrrExtensions/gui/UltraGUI/UltraGUIMenu.h new file mode 100644 index 0000000..97e1c17 --- /dev/null +++ b/IrrExtensions/gui/UltraGUI/UltraGUIMenu.h @@ -0,0 +1,369 @@ +/* +(c) Nicolaus Anderson +*/ + +#include +#include +#include "UltraGUIConstants.h" +//#include "EnumCentralWindowMenuSizing.h" +#include "EnumUltraMenuState.h" + +#ifndef _ULTRAGUIMENU_ +#define _ULTRAGUIMENU_ + +namespace irr +{ +namespace gui +{ + +class UltraGUIFrame; + +//! Class Ultra GUI Menu +/* Container of user-defined GUI elements. */ +class UltraGUIMenu : public IAnimGUIElement // IGUIElement +{ + friend UltraGUIFrame; + +protected: + UltraGUIFrame* ultraframe; + EUltraMenuState menuState; + EUltraMenuLocation menuLocation; + EUltraMenuClose menuCloseSetting; + + // Indicates if this object is in transition between two locations + bool inTransition; + // Requested state - what to transition to + EUltraMenuState queuedState; + + // Indicates if the state/location/animation should be refreshed. + bool refreshState; + + /* Transition speed - The speed at which the menu moves when changing + positions/states. */ + irr::f32 transitionSpeed; + /* Transition color - the color of the box representing the menu when + the menu is being relocated. */ + irr::video::SColor transitionColor; + + // Animator in control of moving this GUI element + IAnimGUIAnimator* moveAnimator; + + /* Last time + - The last time OnPostRender was called. This is necessary for finding + the change for the rectangle. */ + irr::u32 lastTime; + + // Current rectangle of this menu (what is actually drawn) + irr::core::recti currRect; + /* NOTE: The rectangle used by the occupants of this element is + the standard rectangle: AbsoluteRect. AbsoluteRect is NOT used + for the drawing, so we override isPointInside to ensure this + GUI element is brought to the front when clicked. + + NOTE: AbsoluteRect MUST be updated after a transition is completed. + */ + + /* Rectangle that the menu is trying to become. + This is used for optimization of performance. */ + irr::core::recti destinyRect; + + // Left-side menu size + irr::core::dimension2du leftSideSize; + // Right-side menu size + irr::core::dimension2du rightSideSize; + // Central window size + irr::core::dimension2du centerSize; + // Bar icon size + irr::core::dimension2du barSlotSize; + + + // Title text + /* This is displayed on the header bar. */ + irr::core::recti titleTextBox; + + // Icon to display when the menu is on the bar + irr::video::ITexture* barSprite; + + // Should show the bar sprite border + bool hasBarSpriteBorder; + + // Colors for the header + irr::video::SColor headerColorTop; + irr::video::SColor headerColorMid; + irr::video::SColor headerColorBottom; + + + // ---- Buttons ---- + IGUIButton* closeButton; /* "Closes" the menu. */ + IGUIButton* toLeftButton; /* Sends the menu to the left side. + Not shown when menu is on the left side. */ + IGUIButton* toRightButton; /* Sends the menu to the right side. + Not shown when menu is on the right side. */ + IGUIButton* toCenterButton; /* Sends the menu to the center area. + Not shown when menu is in the center. */ + IGUIButton* minimizeButton; /* Sends the menu to the bar. + Not shown when menu is on the bar. */ + + irr::core::dimension2du buttonSize; + + + // ---- OnEvent aspects + bool isSelected; + bool isDragging; + +public: + UltraGUIMenu( + UltraGUIFrame* frame, + irr::s32 id, + irr::core::recti rectangle, + irr::io::path resource_path = ULTRAGUI_RESOURCE_PATH + ); + ~UltraGUIMenu(); + + /* Returns a pointer to the ultra frame of this menu. */ + UltraGUIFrame* getUltraFrame() + { + return ultraframe; + } + + /* Returns the menu state of this menu. */ + EUltraMenuState getMenuState() + { + return menuState; + } + + /* Indicates if the menu is open (not just on the bar). */ + bool isTrulyOpen() + { + switch ( menuState ) + { + case EUGUIMState_Open: // to be removed? + case EUGUIMState_ToLeft_Open: + case EUGUIMState_ToRight_Open: + case EUGUIMState_ToCenter_Open: + return true; + default: + return false; + } + } + + /* Returns the location state of this menu. */ + EUltraMenuLocation getMenuLocation() + { + return menuLocation; + } + + /* Set the activity for what happens when this menu is told + to close by its button. */ + void setCloseButtonAction( EUltraMenuClose action ) + { + menuCloseSetting = action; + } + + /* Sets the size of the menu rectangle at the appropriate position. + After you set all of the sizes you wish, calld updateAbsolutePosition(). */ + void setLeftSize( irr::core::dimension2du size ) + { + leftSideSize = size; + } + + void setRightSize( irr::core::dimension2du size ) + { + rightSideSize = size; + } + + void setCenterSize( irr::core::dimension2du size ) + { + centerSize = size; + } + + /* PLEASE DO NOT USE THIS. + All bars should be kept the same size. This function may be removed + in the near future. */ + void setBarSize( irr::core::dimension2du size ) + { + barSlotSize = size; + } + + /* Gets the size of the menu rectangle at the appropriate position. */ + irr::core::dimension2du getLeftSize() + { + return leftSideSize; + } + + irr::core::dimension2du getRightSize() + { + return rightSideSize; + } + + irr::core::dimension2du getCenterSize() + { + return centerSize; + } + + irr::core::dimension2du getBarSize() + { + return barSlotSize; + } + + /* + Set the state of the menu. + This determines its open or close state as well as its final location. + It should not matter if you call this function while a menu is in + transition from one location to another. + NOTE: The states of EUGUIMState_Opening and EUGUIMState_Closing are + ignored. + */ + void setMyState( EUltraMenuState state, bool noHighlight=false ); + + /* Snap-to-state + Immediately brings the menu to the queued state. */ + void snapState(); + + /* Location-from-state + A useful function that converts the state to a location. + */ + EUltraMenuLocation locationFromState( EUltraMenuState state ); + + /* + Sets the menu bar button sprite. + */ + void setMenuBarSprite( irr::video::ITexture* sprite ); + + /* + Sets whether there should be a border around the menu bar sprite. + */ + void setBorderOnBarSprite( bool setting ) + { + hasBarSpriteBorder = setting; + } + + /* + Set menu bar header bar colors for gradient + */ + void setHeaderGradient( irr::video::SColor top, irr::video::SColor mid, irr::video::SColor bottom ) + { + headerColorTop = top; + headerColorMid = mid; + headerColorBottom = bottom; + } + + + // ---------- Engine stuff ------------ + + virtual bool OnEvent( const irr::SEvent& event ); + + virtual void draw(); + + /* For animation of the menu */ + virtual void OnPostRender( irr::u32 timeMs ); + + virtual bool isPointInside( const irr::core::position2di& point ) const; + + virtual void updateAbsolutePosition(); + + /* Overridden to avoid error in the rectangles. + Note that the locations of the rectangles will be determined + by the UltraGUIFrame. */ + virtual void setRelativePosition( const irr::core::recti& r ) {} + virtual void setRelativePosition( const irr::core::position2di& position ) {} + + // Alias for setting the left and right sizes + virtual void setRelativePositionProportional(const irr::core::rect& r) + { + //leftSideSize = r.getSize() * ultraframe->getAbsolutePosition().getSize(); + //rightSideSize = r.getSize() * ultraframe->getAbsolutePosition().getSize(); + } + + /* Overridden to avoid error in the rectangles. + Note that the locations of the rectangles will be determined + by the UltraGUIFrame. */ + virtual void setAlignment( + EGUI_ALIGNMENT left, + EGUI_ALIGNMENT right, + EGUI_ALIGNMENT top, + EGUI_ALIGNMENT bottom + ) + {} + + + // ----- Internal assistance functions ----- +protected: + + /* Function that create rectangles based on the proper locations + (as given by UltraGUIFrame) and the designated dimensions. */ + irr::core::recti getLeftRectangle(); + irr::core::recti getRightRectangle(); + irr::core::recti getCenterRectangle(); + irr::core::recti getBarRectangle(); /* Should only be called during an update of position. */ + + irr::core::recti getClosedLeftRectangle(); + irr::core::recti getClosedRightRectangle(); + irr::core::recti getClosedCenterRectangle(); + irr::core::recti getClosedBarRectangle(); + + /* Function that moves the current rectangle towards a particular + location. */ + void moveToQueuedLocation( irr::u32 timeMs ); + + /* Refreshes the state/location/animation of the menu. */ + void refresh(); + + /* Sets the destination rectangle based on what is queued. */ + void setDestinyByQueue(); + +public: + /* Sets the position and visibility of buttons based on the location + of the menu. */ + void refreshButtons(); + +protected: + /* Sets the relative position of the requested button to the given + rectangle. + \param whichButton - A value between 1 and 4 (inclusive) that gives + the button slot. */ + void assignRectToButton( irr::core::recti rectangle, irr::u32 whichButton ); + + /* Sets the children to visible/enabled */ + inline void setChildrenOn( bool setting ); + + + /* For drawing a highlight around the menu. + The menu should glow for a moment and then return to normal. + This requires keeping track of time. */ + + bool IsHighlighted; + irr::u32 highlightTime; + irr::u32 resetTime; // Time to reset the timer to if the highlighting is reset + irr::video::SColor highlightColor; + + // Indicates if the highlighter should be loaded + bool loadHighlight; + + // Queues a highlight + inline void queueDrawHighlight( irr::u32 timeMs ) + { + resetTime = timeMs; + IsHighlighted = true; + } + + inline void resetDrawHighlight() + { + IsHighlighted = false; + highlightTime = 0; + highlightColor.setAlpha(0); + } + +public: + //! Is highlighted + /* Indicates if this GUI element will be highlighted if it is + open and the user wants it open. */ + bool isHighlighted() + { + return IsHighlighted; + } +}; + +}} // end namespace gui and irr + +#endif // #ifndef _ULTRAGUIMENU_ diff --git a/IrrExtensions/gui/UltraGUI/UltraIcon.cpp b/IrrExtensions/gui/UltraGUI/UltraIcon.cpp new file mode 100644 index 0000000..320a6f7 --- /dev/null +++ b/IrrExtensions/gui/UltraGUI/UltraIcon.cpp @@ -0,0 +1,31 @@ +/* +(c) Nicolaus Anderson +*/ + +#include "UltraIcon.h" + +namespace irr +{ +namespace gui +{ + +UltraIcon::UltraIcon( + IGUIEnvironment* environment, + IGUIElement* parent, + irr::s32 id, + irr::core::recti rectangle + ) + : IGUIElement( EGUIET_ELEMENT, environment, parent, id, rectangle ) +{ +} + +UltraIcon::~UltraIcon() +{ +} + +UltraIcon::shouldShow( bool setting ) +{ + setVisible( setting ); +} + +}} // end namespace gui and irr \ No newline at end of file diff --git a/IrrExtensions/gui/UltraGUI/UltraIcon.h b/IrrExtensions/gui/UltraGUI/UltraIcon.h new file mode 100644 index 0000000..2cba966 --- /dev/null +++ b/IrrExtensions/gui/UltraGUI/UltraIcon.h @@ -0,0 +1,53 @@ +/* +(c) Nicolaus Anderson +*/ + +#include + +#pragma once + +//! Class UltraIcon +/* +This is like a desktop icon. +It should handle the displaying of its text and icon based on the area it is allotted. +*/ +class UltraIcon : public IGUIElement, public Searchable +{ +public: + UltraIcon( + IGUIEnvironment* environment, + IGUIElement* parent, + irr::s32 id, + irr::core::recti rectangle + ); + + ~UltraIcon(); + + void setFontSize( irr::s32 size ); + + void setTextArea( irr::core::dimension2du area ); + + void setIconArea( irr::core::dimension2du area ); + + void setText( const wchar_t* text ); + + void setIconSprite( irr::video::ITexture* sprite ); + + /* Sets whether the text should be visible or not. + If the text is not visible, the size of the sprite + should be used to determine the bounding box of the element. */ + void setTextVisible( bool setting ); + + /* Sets whether the icon should be visible or not. + If the icon is not visible, the text area should be used + to determine the bounding box of the element. */ + void setIconVisible( bool setting ); + + //! Should show + /* Indicates whether this element should even show up in lists. */ + virtual void shouldShow( bool setting ); + + // ------- Engine stuff --------- + virtual void OnEvent( const irr::SEvent& event ); + virtual void draw(); +}; diff --git a/IrrExtensions/gui/UltraGUI/UltraList.cpp b/IrrExtensions/gui/UltraGUI/UltraList.cpp new file mode 100644 index 0000000..a6dc920 --- /dev/null +++ b/IrrExtensions/gui/UltraGUI/UltraList.cpp @@ -0,0 +1,77 @@ +/* +(c) Nicolaus Anderson +*/ + +#include "UltraList.h" + +namespace irr +{ +namespace gui +{ + +UltraList::UltraList( + IGUIEnvironment* environment, + IGUIElement* parent, + irr::s32 id, + irr::core::recti rectangle + ) + : IGUIElement( EGUIET_ELEMENT, environment, parent, id, rectangle ) +{ +} + +UltraList::~UltraList() +{ +} + +UltraIcon* UltraList::addListItem( irr::s32 id ) +{ + UltraIcon* icon = new UltraIcon(EGUIET_ELEMENT,Environment,this,id,iconRect); + + Children.push_back((IGUIElement*)icon); + + return (IGUIElement*)icon; +} + +void UltraList::removeListItem( irr::s32 id ) +{ + irr::core::list::Iterator i = Children.begin(); + for ( ; i != Children.end(); ++i ) + { + if ( (*i)->getID() == id ) + { + Children.erase(i); + break; + } + } +} + +void UltraList::setHorizontalSpacing(irr::s32 gap) +{ + xSpacing = gap; +} + +void UltraList::setVerticalSpacing(irr::s32 gap) +{ + ySpacing = gap; +} + +void UltraList::setUniversalSpacing(irr::s32 gap) +{ + xSpacing = gap; + ySpacing = gap; +} + +void UltraList::OnEvent(const irr::SEvent& event) +{ +} + +void UltraList::draw() +{ + /* Arrange the icons on the basis of what is visible + and how much space there is. */ + + // Now draw the children + IGUIElement::draw(); +} + +}} // end namespace gui and irr \ No newline at end of file diff --git a/IrrExtensions/gui/UltraGUI/UltraList.h b/IrrExtensions/gui/UltraGUI/UltraList.h new file mode 100644 index 0000000..d557bdc --- /dev/null +++ b/IrrExtensions/gui/UltraGUI/UltraList.h @@ -0,0 +1,64 @@ +/* +(c) Nicolaus Anderson +*/ + +#include + +#include "UltraIcon.h" + +#pragma once + +namespace irr +{ +namespace gui +{ + +//! Class Ultra List +/* +A list of Ultra Icon GUI elements. +>> It takes into account the sizes of the GUI elements and tries +to list them in such a way that they will fit within either +a specified width (or height) or the parent rectangle. +>> It also allows for searching through elements and displaying +only visible ones. +*/ +class UltraList : public IGUIElement +{ +protected: + irr::s32 xSpacing; + irr::s32 ySpacing; + +public: + UltraList( + IGUIEnvironment* environment, + IGUIElement* parent, + irr::s32 id, + irr::core::recti rectangle + ); + + ~UltraList(); + + /* Adds an Ultra Icon to the list. + \param id - The ID of the element being added. */ + UltraIcon* addListItem( irr::s32 id ); + + /* Remove an Ultra Icon. + \param id - The ID of the element being added. */ + void removeListItem( irr::s32 id ); + + // Spacing between icons + void setHorizontalSpacing( irr::s32 gap ); + void setVerticalSpacing( irr::s32 gap ); + void setUniversalSpacing( irr::s32 gap ); // sets both horizontal and vertical + + // ------- Engine stuff --------- + virtual void OnEvent( const irr::SEvent& event ); + virtual void draw(); + + /* Hijacking this function - The given child is NOT added because + the only elements wanted in this list are of the type UltraIcon, + and the only way to add them is via addListItem(). */ + virtual void addChild( IGUIElement* child ) {} +}; + +}} // end namespace gui and irr \ No newline at end of file diff --git a/IrrExtensions/gui/UltraGUI/resources/arial00.png b/IrrExtensions/gui/UltraGUI/resources/arial00.png new file mode 100755 index 0000000..b6fc0e6 Binary files /dev/null and b/IrrExtensions/gui/UltraGUI/resources/arial00.png differ diff --git a/IrrExtensions/gui/UltraGUI/resources/arial00.xml b/IrrExtensions/gui/UltraGUI/resources/arial00.xml new file mode 100755 index 0000000..7cc7df8 Binary files /dev/null and b/IrrExtensions/gui/UltraGUI/resources/arial00.xml differ diff --git a/IrrExtensions/gui/UltraGUI/resources/art/UltraGUI_button.xcf b/IrrExtensions/gui/UltraGUI/resources/art/UltraGUI_button.xcf new file mode 100755 index 0000000..046ac56 Binary files /dev/null and b/IrrExtensions/gui/UltraGUI/resources/art/UltraGUI_button.xcf differ diff --git a/IrrExtensions/gui/UltraGUI/resources/art/button.svg b/IrrExtensions/gui/UltraGUI/resources/art/button.svg new file mode 100755 index 0000000..5d52ff6 --- /dev/null +++ b/IrrExtensions/gui/UltraGUI/resources/art/button.svg @@ -0,0 +1,390 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + diff --git a/IrrExtensions/gui/UltraGUI/resources/art/closeButton.png b/IrrExtensions/gui/UltraGUI/resources/art/closeButton.png new file mode 100755 index 0000000..b141c5a Binary files /dev/null and b/IrrExtensions/gui/UltraGUI/resources/art/closeButton.png differ diff --git a/IrrExtensions/gui/UltraGUI/resources/art/hover 2/hoverTex0.png b/IrrExtensions/gui/UltraGUI/resources/art/hover 2/hoverTex0.png new file mode 100755 index 0000000..49f9b4c Binary files /dev/null and b/IrrExtensions/gui/UltraGUI/resources/art/hover 2/hoverTex0.png differ diff --git a/IrrExtensions/gui/UltraGUI/resources/art/hover 2/hoverTex1.png b/IrrExtensions/gui/UltraGUI/resources/art/hover 2/hoverTex1.png new file mode 100755 index 0000000..92bbd71 Binary files /dev/null and b/IrrExtensions/gui/UltraGUI/resources/art/hover 2/hoverTex1.png differ diff --git a/IrrExtensions/gui/UltraGUI/resources/art/hover 2/hoverTex2.png b/IrrExtensions/gui/UltraGUI/resources/art/hover 2/hoverTex2.png new file mode 100755 index 0000000..b2f1acf Binary files /dev/null and b/IrrExtensions/gui/UltraGUI/resources/art/hover 2/hoverTex2.png differ diff --git a/IrrExtensions/gui/UltraGUI/resources/art/hover 2/hoverTex3.png b/IrrExtensions/gui/UltraGUI/resources/art/hover 2/hoverTex3.png new file mode 100755 index 0000000..f45a76a Binary files /dev/null and b/IrrExtensions/gui/UltraGUI/resources/art/hover 2/hoverTex3.png differ diff --git a/IrrExtensions/gui/UltraGUI/resources/art/hoverTex0.png b/IrrExtensions/gui/UltraGUI/resources/art/hoverTex0.png new file mode 100755 index 0000000..9b0995f Binary files /dev/null and b/IrrExtensions/gui/UltraGUI/resources/art/hoverTex0.png differ diff --git a/IrrExtensions/gui/UltraGUI/resources/art/hoverTex1.png b/IrrExtensions/gui/UltraGUI/resources/art/hoverTex1.png new file mode 100755 index 0000000..c548bca Binary files /dev/null and b/IrrExtensions/gui/UltraGUI/resources/art/hoverTex1.png differ diff --git a/IrrExtensions/gui/UltraGUI/resources/art/hoverTex2.png b/IrrExtensions/gui/UltraGUI/resources/art/hoverTex2.png new file mode 100755 index 0000000..a1c29bb Binary files /dev/null and b/IrrExtensions/gui/UltraGUI/resources/art/hoverTex2.png differ diff --git a/IrrExtensions/gui/UltraGUI/resources/art/hoverTex3.png b/IrrExtensions/gui/UltraGUI/resources/art/hoverTex3.png new file mode 100755 index 0000000..8b0af71 Binary files /dev/null and b/IrrExtensions/gui/UltraGUI/resources/art/hoverTex3.png differ diff --git a/IrrExtensions/gui/UltraGUI/resources/art/minimizeButton.png b/IrrExtensions/gui/UltraGUI/resources/art/minimizeButton.png new file mode 100755 index 0000000..51da817 Binary files /dev/null and b/IrrExtensions/gui/UltraGUI/resources/art/minimizeButton.png differ diff --git a/IrrExtensions/gui/UltraGUI/resources/art/newfile.png b/IrrExtensions/gui/UltraGUI/resources/art/newfile.png new file mode 100755 index 0000000..2680a72 Binary files /dev/null and b/IrrExtensions/gui/UltraGUI/resources/art/newfile.png differ diff --git a/IrrExtensions/gui/UltraGUI/resources/art/newfile.svg b/IrrExtensions/gui/UltraGUI/resources/art/newfile.svg new file mode 100755 index 0000000..4d7ff7f --- /dev/null +++ b/IrrExtensions/gui/UltraGUI/resources/art/newfile.svg @@ -0,0 +1,149 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + diff --git a/IrrExtensions/gui/UltraGUI/resources/art/sample1.png b/IrrExtensions/gui/UltraGUI/resources/art/sample1.png new file mode 100755 index 0000000..5663ffa Binary files /dev/null and b/IrrExtensions/gui/UltraGUI/resources/art/sample1.png differ diff --git a/IrrExtensions/gui/UltraGUI/resources/art/sample2.png b/IrrExtensions/gui/UltraGUI/resources/art/sample2.png new file mode 100755 index 0000000..aad61b4 Binary files /dev/null and b/IrrExtensions/gui/UltraGUI/resources/art/sample2.png differ diff --git a/IrrExtensions/gui/UltraGUI/resources/art/set 1/closeButton.png b/IrrExtensions/gui/UltraGUI/resources/art/set 1/closeButton.png new file mode 100755 index 0000000..b22f730 Binary files /dev/null and b/IrrExtensions/gui/UltraGUI/resources/art/set 1/closeButton.png differ diff --git a/IrrExtensions/gui/UltraGUI/resources/art/set 1/minimizeButton.png b/IrrExtensions/gui/UltraGUI/resources/art/set 1/minimizeButton.png new file mode 100755 index 0000000..844e445 Binary files /dev/null and b/IrrExtensions/gui/UltraGUI/resources/art/set 1/minimizeButton.png differ diff --git a/IrrExtensions/gui/UltraGUI/resources/art/set 1/toCenterButton.png b/IrrExtensions/gui/UltraGUI/resources/art/set 1/toCenterButton.png new file mode 100755 index 0000000..5eaec25 Binary files /dev/null and b/IrrExtensions/gui/UltraGUI/resources/art/set 1/toCenterButton.png differ diff --git a/IrrExtensions/gui/UltraGUI/resources/art/set 1/toLeftButton.png b/IrrExtensions/gui/UltraGUI/resources/art/set 1/toLeftButton.png new file mode 100755 index 0000000..4a475f7 Binary files /dev/null and b/IrrExtensions/gui/UltraGUI/resources/art/set 1/toLeftButton.png differ diff --git a/IrrExtensions/gui/UltraGUI/resources/art/set 1/toRightButton.png b/IrrExtensions/gui/UltraGUI/resources/art/set 1/toRightButton.png new file mode 100755 index 0000000..60ced19 Binary files /dev/null and b/IrrExtensions/gui/UltraGUI/resources/art/set 1/toRightButton.png differ diff --git a/IrrExtensions/gui/UltraGUI/resources/art/set 2/closeButton.png b/IrrExtensions/gui/UltraGUI/resources/art/set 2/closeButton.png new file mode 100755 index 0000000..a53d221 Binary files /dev/null and b/IrrExtensions/gui/UltraGUI/resources/art/set 2/closeButton.png differ diff --git a/IrrExtensions/gui/UltraGUI/resources/art/set 2/minimizeButton.png b/IrrExtensions/gui/UltraGUI/resources/art/set 2/minimizeButton.png new file mode 100755 index 0000000..3be417c Binary files /dev/null and b/IrrExtensions/gui/UltraGUI/resources/art/set 2/minimizeButton.png differ diff --git a/IrrExtensions/gui/UltraGUI/resources/art/set 2/toCenterButton.png b/IrrExtensions/gui/UltraGUI/resources/art/set 2/toCenterButton.png new file mode 100755 index 0000000..d9484b3 Binary files /dev/null and b/IrrExtensions/gui/UltraGUI/resources/art/set 2/toCenterButton.png differ diff --git a/IrrExtensions/gui/UltraGUI/resources/art/set 2/toLeftButton.png b/IrrExtensions/gui/UltraGUI/resources/art/set 2/toLeftButton.png new file mode 100755 index 0000000..e370906 Binary files /dev/null and b/IrrExtensions/gui/UltraGUI/resources/art/set 2/toLeftButton.png differ diff --git a/IrrExtensions/gui/UltraGUI/resources/art/set 2/toRightButton.png b/IrrExtensions/gui/UltraGUI/resources/art/set 2/toRightButton.png new file mode 100755 index 0000000..5715f12 Binary files /dev/null and b/IrrExtensions/gui/UltraGUI/resources/art/set 2/toRightButton.png differ diff --git a/IrrExtensions/gui/UltraGUI/resources/art/toCenterButton.png b/IrrExtensions/gui/UltraGUI/resources/art/toCenterButton.png new file mode 100755 index 0000000..b4cbdb1 Binary files /dev/null and b/IrrExtensions/gui/UltraGUI/resources/art/toCenterButton.png differ diff --git a/IrrExtensions/gui/UltraGUI/resources/art/toLeftButton.png b/IrrExtensions/gui/UltraGUI/resources/art/toLeftButton.png new file mode 100755 index 0000000..919d8e4 Binary files /dev/null and b/IrrExtensions/gui/UltraGUI/resources/art/toLeftButton.png differ diff --git a/IrrExtensions/gui/UltraGUI/resources/art/toRightButton.png b/IrrExtensions/gui/UltraGUI/resources/art/toRightButton.png new file mode 100755 index 0000000..5e0bc38 Binary files /dev/null and b/IrrExtensions/gui/UltraGUI/resources/art/toRightButton.png differ diff --git a/IrrExtensions/gui/UltraGUI/resources/sys10.xml b/IrrExtensions/gui/UltraGUI/resources/sys10.xml new file mode 100755 index 0000000..1784eb4 Binary files /dev/null and b/IrrExtensions/gui/UltraGUI/resources/sys10.xml differ diff --git a/IrrExtensions/gui/UltraGUI/resources/sys100.bmp b/IrrExtensions/gui/UltraGUI/resources/sys100.bmp new file mode 100755 index 0000000..32f90b5 Binary files /dev/null and b/IrrExtensions/gui/UltraGUI/resources/sys100.bmp differ diff --git a/IrrExtensions/gui/UltraGUI/resources/sys101.bmp b/IrrExtensions/gui/UltraGUI/resources/sys101.bmp new file mode 100755 index 0000000..1e24509 Binary files /dev/null and b/IrrExtensions/gui/UltraGUI/resources/sys101.bmp differ diff --git a/IrrExtensions/gui/UltraGUI/resources/sys102.bmp b/IrrExtensions/gui/UltraGUI/resources/sys102.bmp new file mode 100755 index 0000000..14b15bc Binary files /dev/null and b/IrrExtensions/gui/UltraGUI/resources/sys102.bmp differ diff --git a/IrrExtensions/gui/abstract/TGUIElement.cpp b/IrrExtensions/gui/abstract/TGUIElement.cpp new file mode 100644 index 0000000..186c87c --- /dev/null +++ b/IrrExtensions/gui/abstract/TGUIElement.cpp @@ -0,0 +1,53 @@ +// Copyright 2018 Nicolaus Anderson + +#include "TGUIElement.h" + +namespace irr { +namespace gui { + +TGUIElement::TGUIElement( EGUI_ELEMENT_TYPE type, IGUIEnvironment* environment, IGUIElement* parent, + s32 id, const core::rect& rectangle ) + : IGUIElement( type, environment, parent, id, rectangle ) + , CallbackReceiver(0) +{} + +void TGUIElement::setCallbackReceiver( IEventReceiver* receiver ) { + CallbackReceiver = receiver; +}; + +bool TGUIElement::OnEvent( const SEvent& event ) { + + if ( !IsVisible || !IsEnabled ) + return false; + + switch( event.EventType ) { + case EET_GUI_EVENT: + return OnGUIEvent(event.GUIEvent); + + case EET_MOUSE_INPUT_EVENT: + return OnMouseInput(event.MouseInput); + + case EET_KEY_INPUT_EVENT: + return OnKeyInput(event.KeyInput); + + case EET_JOYSTICK_INPUT_EVENT: + return OnJoystickEvent(event.JoystickEvent); + + default: + break; + } + return IGUIElement::OnEvent(event); +} + +void makeEvent( EGUI_EVENT_TYPE eventType ) { + SEvent e; + e.EventType = EET_GUI_EVENT; + e.GUIEvent.EventType = eventType; + e.GUIEvent.Element = this; + if ( CallbackReceiver ) { + CallbackReceiver->OnEvent(e); + } + Environment->OnEvent(e); +} + +}} diff --git a/IrrExtensions/gui/abstract/TGUIElement.h b/IrrExtensions/gui/abstract/TGUIElement.h new file mode 100644 index 0000000..ab8e94d --- /dev/null +++ b/IrrExtensions/gui/abstract/TGUIElement.h @@ -0,0 +1,39 @@ +// Copyright 2018 Nicolaus Anderson + +#include + +#ifndef __TGUIELEMENT_H__ +#define __TGUIELEMENT_H__ + +namespace irr { +namespace gui { + +//! Template GUI Element class +/* + This class provides a base for other classes to have event parents and simplified handling of events such as mouse input. +*/ +class TGUIElement { +public: + TGUIElement( EGUI_ELEMENT_TYPE type, IGUIEnvironment* environment, IGUIElement* parent, + s32 id, const core::rect& rectangle ); + + virtual ~TGUIElement() {} + + void setCallbackReceiver( IEventReceiver* receiver ); + virtual bool OnEvent( const SEvent& event ); + +protected: + virtual bool OnMouseInput( const SMouseInput& event ) { return false; } + virtual bool OnKeyInput( const SKeyInput& event ) { return false; } + virtual bool OnGUIEvent( const SGUIEvent& event ) { return false; } + virtual bool OnJoystickEvent( const SJoystickEvent& event ) { return false; } + + void makeEvent( EGUI_EVENT_TYPE eventType ); + +private: + IEventReceiver* CallbackReceiver; +}; + +}} + +#endif diff --git a/IrrExtensions/gui/layout/FlowLayout.h b/IrrExtensions/gui/layout/FlowLayout.h new file mode 100644 index 0000000..38eebe3 --- /dev/null +++ b/IrrExtensions/gui/layout/FlowLayout.h @@ -0,0 +1,147 @@ +// (C) 2018 Nicolaus Anderson + +#include + +namespace irr { +namespace gui { + +//! Flow alignment Layout class +/* + A simple, temporal class for automatically aligning GUI elements. Meant for quick usage and destruction. + It can be destroyed at any time. + Example usage: + + IGUIButton *b = guiEnvironment->createButton(...), + *b2 = guiEnvironment->createButton(...); + FlowLayout flow(300, 0,0, 3,3); + flow.add(b); + flow.add(b2); + flow.setSpanByParent(true); + flow.alignAsRow(true); +*/ +class FlowLayout { + typename list_type core::list; + typename list_iterator list_type::Iterator; + + u32 span; + bool setSpanByParentSize; + s32 startX; + s32 startY; + s32 gapX; + s32 gapY; + list_type queue; + const IGUIElement* firstElement; + +public: + //! cstor + /* + @param span - The max width (or height) before wrapping. If zero, items are always placed on a newline. + @param startingX - The relative X position of the upper left corner of the first element in the layout. + @param startingY - The relative Y position of the upper left corner of the first element in the layout. + @param spacingX - The x-distance between elements in the layout. + @param spacingY - The y-distance between elements in the layout. + */ + FlowLayout( u32 span, s32 startingX, s32 startingY, s32 spacingX, s32 spacingY ) + : span(span) + , setSpanByParentSize(false) + , startX(startingX) + , startY(startingY) + , gapX(spacingX) + , gapY(spacingY) + , queue() + , firstElement(0) + { + } + + //! dstor + ~FlowLayout() { + list_iterator li = queue.begin(); + for (; li != queue.end(); ++li) { + (*li)->drop(); + } + } + + //! Add an element + //! Does not allow adding elements with different parents. + void add( IGUIElement* e ) { + if ( e ) { + if ( firstElement ) { + if ( e->getParent() != firstElement->getParent() ) { + return; + } + } + e->grab(); + queue.push_back(e); + } + } + + //! Remove an element + void remove( IGUIElement* e ) { + list_iterator li = queue.begin(); + if ( e ) { + for (; li != queue.end(); ++li) { + if ( *li == e ) { + queue.erase(li); + e->drop(); + return; + } + } + } + } + + //! Set span by parent size + /* + Uses the size of the parent to determine the value for wrapping. + */ + void setSpanByParent(bool yes) { + setSpanByParentSize = yes; + } + + //! Align elements in a row + /* Elements are aligned according to the left side and from left-to-right. + Wrapping occurs when the next element's starting position would be outside of the row width (given as "span"). + */ + void alignAsRow(bool wrap=false) { + core::recti itemRect; + s32 currX = 0; + s32 currY = 0; + s32 currMaxItemHeight = 0; + list_iterator li = queue.begin(); + if ( setSpanByParentSize ) { + if ( li != queue.end() ) { + if ( (*li)->getParent() ) { + span = (*li)->getParent()->getRelativeRect().getWidth(); + } + } + } + for (; li != queue.end(); ++li) { + (*li)->setAlignment(EGUIA_UPPERLEFT,EGUIA_UPPERLEFT,EGUIA_UPPERLEFT,EGUIA_UPPERLEFT); + (*li)->setRelativePosition(core::vector2di(startX + currX, startY + currY)); + itemRect = (*li)->getRelativePosition(); + currX += itemRect.getWidth() + gapX; + currMaxItemHeight = core::max_( currMaxItemHeight, itemRect.getHeight() ); + // Row wrapping + if ( wrap && currX > (s32)span ) { + currX = 0; + currY += currMaxItemHeight + gapY; + currMaxItemHeight = 0; + } + } + } + + //! Align elements in a column with no wrapping + // All elements will be the full width of the parent element + void alignAsOneFullWidthColumn() { + core::recti itemRect; + s32 currY = 0; + list_iterator li = queue.begin(); + for (; li != queue.end(); ++li) { + (*li)->setAlignment(EGUIA_UPPERLEFT,EGUIA_LOWERRIGHT,EGUIA_UPPERLEFT,EGUIA_UPPERLEFT); + (*li)->setRelativePosition(core::vector2di(startX, startY + currY)); + itemRect = (*li)->getRelativeRect(); + currY += itemRect.getHeight() + gapY; + } + } +}; + +}} diff --git a/IrrExtensions/gui/listbox/CGUIListBox2.cpp b/IrrExtensions/gui/listbox/CGUIListBox2.cpp new file mode 100644 index 0000000..8838543 --- /dev/null +++ b/IrrExtensions/gui/listbox/CGUIListBox2.cpp @@ -0,0 +1,1073 @@ +// by tom_gamer +// changed to CGUIListBox22 by Nic Anderson +// Modified to store data associated with text. + +// Copyright (C) 2002-2012 Nikolaus Gebhardt +// This file is part of the "Irrlicht Engine". +// For conditions of distribution and use, see copyright notice in irrlicht.h + +//#ifdef _IRR_COMPILE_WITH_GUI_ // booted from standalone version + +// Changed includes to be library <> +#include "CGUIListBox2.h" +#include +#include +#include +#include +#include +#include +#include // changed to +//#include +#include + +namespace irr +{ +namespace gui +{ + +//! constructor +// timer param added by Nic Anderson to avoid os.h dependency +CGUIListBox2::CGUIListBox2(IGUIEnvironment* environment, ITimer* timer, IGUIElement* parent, + s32 id, core::rect rectangle, bool clip, + bool drawBack, bool moveOverSelect) +: IGUIListBox2(environment, parent, id, rectangle), Selected(-1), + ItemHeight(0),ItemHeightOverride(0), + TotalItemHeight(0), ItemsIconWidth(0), Font(0), IconBank(0), + ScrollBar(0), selectTime(0), LastKeyTime(0), Selecting(false), DrawBack(drawBack), + MoveOverSelect(moveOverSelect), AutoScroll(true), HighlightWhenNotFocused(true), + MultiSelect(false), DidMouseMove(false), LastSelected(-1) + , Timer(timer) +{ + #ifdef _DEBUG + setDebugName("CGUIListBox2"); + #endif + + IGUISkin* skin = Environment->getSkin(); + const s32 s = skin->getSize(EGDS_SCROLLBAR_SIZE); + + //ScrollBar = new CGUIScrollBar(false, Environment, this, -1, + // core::rect(RelativeRect.getWidth() - s, 0, RelativeRect.getWidth(), RelativeRect.getHeight()), + // !clip); + // Changed by Nic Anderson + ScrollBar = Environment->addScrollBar( false, + core::rect(RelativeRect.getWidth() - s, 0, RelativeRect.getWidth(), RelativeRect.getHeight()), + this, -1 ); + + ScrollBar->setSubElement(true); + ScrollBar->setTabStop(false); + ScrollBar->setAlignment(EGUIA_LOWERRIGHT, EGUIA_LOWERRIGHT, EGUIA_UPPERLEFT, EGUIA_LOWERRIGHT); + ScrollBar->setVisible(false); + ScrollBar->setPos(0); + + setNotClipped(!clip); + + // this element can be tabbed to + setTabStop(true); + setTabOrder(-1); + + updateAbsolutePosition(); +} + + +//! destructor +CGUIListBox2::~CGUIListBox2() +{ + if (ScrollBar) + ScrollBar->drop(); + + if (Font) + Font->drop(); + + if (IconBank) + IconBank->drop(); +} + + +//! returns amount of list items +u32 CGUIListBox2::getItemCount() const +{ + return Items.size(); +} + + +//! returns string of a list item. the may be a value from 0 to itemCount-1 +const wchar_t* CGUIListBox2::getListItem(u32 id) const +{ + if (id>=Items.size()) + return 0; + + return Items[id].text.c_str(); +} + +//! returns data associated with the selected list item +IReferenceCounted* CGUIListBox2::getListItemData(u32 id) const // added by Nic Anderson +{ + if (id>=Items.size()) + return 0; + + return Items[id].data; +} + + +//! Returns the icon of an item +s32 CGUIListBox2::getIcon(u32 id) const +{ + if (id>=Items.size()) + return -1; + + return Items[id].icon; +} + + +//! adds a list item, returns id of item +u32 CGUIListBox2::addItem(const wchar_t* text, IReferenceCounted* data) // changed by Nic anderson +{ + return addItem(text, -1, data); +} + + +//! removes a list item whose id is given +void CGUIListBox2::removeItem(u32 id) +{ + if (id >= Items.size()) + return; + + if ((u32)Selected==id) + { + Selected = -1; + } + else if ((u32)Selected > id) + { + Selected -= 1; + //selectTime = os::Timer::getTime(); + selectTime = Timer->getTime(); // change by Nic Anderson + } + + Items.erase(id); + + recalculateItemHeight(); +} + + +s32 CGUIListBox2::getItemAt(s32 xpos, s32 ypos) const +{ + if ( xpos < AbsoluteRect.UpperLeftCorner.X || xpos >= AbsoluteRect.LowerRightCorner.X + || ypos < AbsoluteRect.UpperLeftCorner.Y || ypos >= AbsoluteRect.LowerRightCorner.Y + ) + return -1; + + if ( ItemHeight == 0 ) + return -1; + + s32 item = ((ypos - AbsoluteRect.UpperLeftCorner.Y - 1) + ScrollBar->getPos()) / ItemHeight; + if ( item < 0 || item >= (s32)Items.size()) + return -1; + + return item; +} + +//! clears the list +void CGUIListBox2::clear() +{ + Items.clear(); + ItemsIconWidth = 0; + Selected = -1; + + if (ScrollBar) + ScrollBar->setPos(0); + + recalculateItemHeight(); +} + + +void CGUIListBox2::recalculateItemHeight() +{ + IGUISkin* skin = Environment->getSkin(); + + if (Font != skin->getFont()) + { + if (Font) + Font->drop(); + + Font = skin->getFont(); + if ( 0 == ItemHeightOverride ) + ItemHeight = 0; + + if (Font) + { + if ( 0 == ItemHeightOverride ) + ItemHeight = Font->getDimension(L"A").Height + 4; + + Font->grab(); + } + } + + TotalItemHeight = ItemHeight * Items.size(); + ScrollBar->setMax( core::max_(0, TotalItemHeight - AbsoluteRect.getHeight()) ); + s32 minItemHeight = ItemHeight > 0 ? ItemHeight : 1; + ScrollBar->setSmallStep ( minItemHeight ); + ScrollBar->setLargeStep ( 2*minItemHeight ); + + if ( TotalItemHeight <= AbsoluteRect.getHeight() ) + ScrollBar->setVisible(false); + else + ScrollBar->setVisible(true); +} + + +//! returns id of selected item. returns -1 if no item is selected. +s32 CGUIListBox2::getSelected() const +{ + if (MultiSelect) + { + for (u32 i=0; i=Items.size()) + Selected = -1; + else + Selected = id; + LastSelected = Selected; + + //selectTime = os::Timer::getTime(); + selectTime = Timer->getTime(); // change by Nic Anderson + + recalculateScrollPos(); +} + +//! sets the selected item. Set this to -1 if no item should be selected +void CGUIListBox2::setSelected(const wchar_t *item) +{ + s32 index = -1; + + if ( item ) + { + for ( index = 0; index < (s32) Items.size(); ++index ) + { + if ( Items[index].text == item ) + break; + } + } + setSelected ( index ); +} + +//! called if an event happened. +bool CGUIListBox2::OnEvent(const SEvent& event) +{ + if (isEnabled()) + { + switch(event.EventType) + { + case EET_KEY_INPUT_EVENT: + if (MultiSelect) // ignore key inputs in multiselect mode + break; + + if (event.KeyInput.PressedDown && + (event.KeyInput.Key == KEY_DOWN || + event.KeyInput.Key == KEY_UP || + event.KeyInput.Key == KEY_HOME || + event.KeyInput.Key == KEY_END || + event.KeyInput.Key == KEY_NEXT || + event.KeyInput.Key == KEY_PRIOR ) ) + { + s32 oldSelected = Selected; + switch (event.KeyInput.Key) + { + case KEY_DOWN: + Selected += 1; + break; + case KEY_UP: + Selected -= 1; + break; + case KEY_HOME: + Selected = 0; + break; + case KEY_END: + Selected = (s32)Items.size()-1; + break; + case KEY_NEXT: + Selected += AbsoluteRect.getHeight() / ItemHeight; + break; + case KEY_PRIOR: + Selected -= AbsoluteRect.getHeight() / ItemHeight; + break; + default: + break; + } + if (Selected >= (s32)Items.size()) + Selected = Items.size() - 1; + else + if (Selected<0) + Selected = 0; + LastSelected = Selected; + + recalculateScrollPos(); + + // post the news + + if (oldSelected != Selected && Parent && !Selecting && !MoveOverSelect) + { + SEvent e; + e.EventType = EET_GUI_EVENT; + e.GUIEvent.Caller = this; + e.GUIEvent.Element = 0; + e.GUIEvent.EventType = EGET_LISTBOX_CHANGED; + Parent->OnEvent(e); + } + + return true; + } + else + if (!event.KeyInput.PressedDown && ( event.KeyInput.Key == KEY_RETURN || event.KeyInput.Key == KEY_SPACE ) ) + { + if (Parent) + { + SEvent e; + e.EventType = EET_GUI_EVENT; + e.GUIEvent.Caller = this; + e.GUIEvent.Element = 0; + e.GUIEvent.EventType = EGET_LISTBOX_SELECTED_AGAIN; + Parent->OnEvent(e); + } + return true; + } + else if (event.KeyInput.PressedDown && event.KeyInput.Char) + { + // change selection based on text as it is typed. + //u32 now = os::Timer::getTime(); + u32 now = Timer->getTime(); // change by Nic Anderson + + if (now - LastKeyTime < 500) + { + // add to key buffer if it isn't a key repeat + if (!(KeyBuffer.size() == 1 && KeyBuffer[0] == event.KeyInput.Char)) + { + KeyBuffer += L" "; + KeyBuffer[KeyBuffer.size()-1] = event.KeyInput.Char; + } + } + else + { + KeyBuffer = L" "; + KeyBuffer[0] = event.KeyInput.Char; + } + LastKeyTime = now; + + // find the selected item, starting at the current selection + s32 start = Selected; + // dont change selection if the key buffer matches the current item + if (Selected > -1 && KeyBuffer.size() > 1) + { + if (Items[Selected].text.size() >= KeyBuffer.size() && + KeyBuffer.equals_ignore_case(Items[Selected].text.subString(0,KeyBuffer.size()))) + return true; + } + + s32 current; + for (current = start+1; current < (s32)Items.size(); ++current) + { + if (Items[current].text.size() >= KeyBuffer.size()) + { + if (KeyBuffer.equals_ignore_case(Items[current].text.subString(0,KeyBuffer.size()))) + { + if (Parent && Selected != current && !Selecting && !MoveOverSelect) + { + SEvent e; + e.EventType = EET_GUI_EVENT; + e.GUIEvent.Caller = this; + e.GUIEvent.Element = 0; + e.GUIEvent.EventType = EGET_LISTBOX_CHANGED; + Parent->OnEvent(e); + } + setSelected(current); + return true; + } + } + } + for (current = 0; current <= start; ++current) + { + if (Items[current].text.size() >= KeyBuffer.size()) + { + if (KeyBuffer.equals_ignore_case(Items[current].text.subString(0,KeyBuffer.size()))) + { + if (Parent && Selected != current && !Selecting && !MoveOverSelect) + { + Selected = current; + SEvent e; + e.EventType = EET_GUI_EVENT; + e.GUIEvent.Caller = this; + e.GUIEvent.Element = 0; + e.GUIEvent.EventType = EGET_LISTBOX_CHANGED; + Parent->OnEvent(e); + } + setSelected(current); + return true; + } + } + } + + return true; + } + break; + + case EET_GUI_EVENT: + switch(event.GUIEvent.EventType) + { + case gui::EGET_SCROLL_BAR_CHANGED: + if (event.GUIEvent.Caller == ScrollBar) + return true; + break; + case gui::EGET_ELEMENT_FOCUS_LOST: + { + if (event.GUIEvent.Caller == this) + Selecting = false; + } + default: + break; + } + break; + + case EET_MOUSE_INPUT_EVENT: + { + core::position2d p(event.MouseInput.X, event.MouseInput.Y); + + switch(event.MouseInput.Event) + { + case EMIE_MOUSE_WHEEL: + ScrollBar->setPos(ScrollBar->getPos() + (event.MouseInput.Wheel < 0 ? -1 : 1) * (-ItemHeight)/2); // Nic added parentheses around -ItemHeight + return true; + + case EMIE_LMOUSE_PRESSED_DOWN: + { + Selecting = true; + DidMouseMove = false; + if (MultiSelect && !event.MouseInput.Control && !event.MouseInput.Shift) + for (u32 i=0; igetTime(); // change by Nic Anderson + s32 oldSelected = Selected; + s32 newSelected; + + + newSelected = getItemAt(AbsoluteRect.UpperLeftCorner.X, ypos); + + if (newSelected<0) + return; + + if (MultiSelect) + { + if (onlyHover) + { + if (!Selecting) + { + for (u32 i=0; i check ctrl and shift state */ + if (ctrlState) + Items[newSelected].selected = !Items[newSelected].selected; + else + if (shftState) + { + if (LastSelected < 0) + Items[newSelected].selected = true; + else + { + u32 mi, ma; + mi = core::min_(newSelected, LastSelected); + ma = core::max_(newSelected, LastSelected); + for (u32 i=mi; i<=ma; i++) + Items[i].selected = true; + } + } + else + { + for (u32 i=0; iOnEvent(event); + } +} + + +//! Update the position and size of the listbox, and update the scrollbar +void CGUIListBox2::updateAbsolutePosition() +{ + IGUIElement::updateAbsolutePosition(); + + recalculateItemHeight(); +} + + +//! draws the element and its children +void CGUIListBox2::draw() +{ + if (!IsVisible) + return; + + recalculateItemHeight(); // if the font changed + + IGUISkin* skin = Environment->getSkin(); + + core::rect* clipRect = 0; + + // draw background + core::rect frameRect(AbsoluteRect); + + // draw items + + core::rect clientClip(AbsoluteRect); + clientClip.UpperLeftCorner.Y += 1; + clientClip.UpperLeftCorner.X += 1; + if (ScrollBar->isVisible()) + clientClip.LowerRightCorner.X = AbsoluteRect.LowerRightCorner.X - skin->getSize(EGDS_SCROLLBAR_SIZE); + clientClip.LowerRightCorner.Y -= 1; + clientClip.clipAgainst(AbsoluteClippingRect); + + skin->draw3DSunkenPane(this, skin->getColor(EGDC_3D_HIGH_LIGHT), true, + DrawBack, frameRect, &clientClip); + + if (clipRect) + clientClip.clipAgainst(*clipRect); + + frameRect = AbsoluteRect; + frameRect.UpperLeftCorner.X += 1; + if (ScrollBar->isVisible()) + frameRect.LowerRightCorner.X = AbsoluteRect.LowerRightCorner.X - skin->getSize(EGDS_SCROLLBAR_SIZE); + + frameRect.LowerRightCorner.Y = AbsoluteRect.UpperLeftCorner.Y + ItemHeight; + + frameRect.UpperLeftCorner.Y -= ScrollBar->getPos(); + frameRect.LowerRightCorner.Y -= ScrollBar->getPos(); + + bool hl = (HighlightWhenNotFocused || Environment->hasFocus(this) || Environment->hasFocus(ScrollBar)); + + for (s32 i=0; i<(s32)Items.size(); ++i) + { + if (frameRect.LowerRightCorner.Y >= AbsoluteRect.UpperLeftCorner.Y && + frameRect.UpperLeftCorner.Y <= AbsoluteRect.LowerRightCorner.Y) + { + if (MultiSelect) + { + if (Items[i].selected) + skin->draw2DRectangle(this, skin->getColor(EGDC_HIGH_LIGHT), frameRect, &clientClip); + } + else + if (i == Selected && hl) + skin->draw2DRectangle(this, skin->getColor(EGDC_HIGH_LIGHT), frameRect, &clientClip); + + core::rect textRect = frameRect; + textRect.UpperLeftCorner.X += 3; + + if (Font) + { + if (IconBank && (Items[i].icon > -1)) + { + core::position2di iconPos = textRect.UpperLeftCorner; + iconPos.Y += textRect.getHeight() / 2; + iconPos.X += ItemsIconWidth/2; + + if ( i==Selected && hl ) + { + IconBank->draw2DSprite( (u32)Items[i].icon, iconPos, &clientClip, + hasItemOverrideColor(i, EGUI_LBC_ICON_HIGHLIGHT) ? + getItemOverrideColor(i, EGUI_LBC_ICON_HIGHLIGHT) : getItemDefaultColor(EGUI_LBC_ICON_HIGHLIGHT), + //selectTime, os::Timer::getTime(), false, true); + selectTime, Timer->getTime(), false, true); // change by Nic Anderson + } + else + { + IconBank->draw2DSprite( (u32)Items[i].icon, iconPos, &clientClip, + hasItemOverrideColor(i, EGUI_LBC_ICON) ? getItemOverrideColor(i, EGUI_LBC_ICON) : getItemDefaultColor(EGUI_LBC_ICON), + 0 , + //(i==Selected) ? os::Timer::getTime() : 0, + (i==Selected)? Timer->getTime() : 0, // change by Nic Anderson + false, true); + } + } + + textRect.UpperLeftCorner.X += ItemsIconWidth+3; + + if ( i==Selected && hl ) + { + Font->draw(Items[i].text.c_str(), textRect, + hasItemOverrideColor(i, EGUI_LBC_TEXT_HIGHLIGHT) ? + getItemOverrideColor(i, EGUI_LBC_TEXT_HIGHLIGHT) : getItemDefaultColor(EGUI_LBC_TEXT_HIGHLIGHT), + false, true, &clientClip); + } + else + { + Font->draw(Items[i].text.c_str(), textRect, + hasItemOverrideColor(i, EGUI_LBC_TEXT) ? getItemOverrideColor(i, EGUI_LBC_TEXT) : getItemDefaultColor(EGUI_LBC_TEXT), + false, true, &clientClip); + } + + textRect.UpperLeftCorner.X -= ItemsIconWidth+3; + } + } + + frameRect.UpperLeftCorner.Y += ItemHeight; + frameRect.LowerRightCorner.Y += ItemHeight; + } + + IGUIElement::draw(); +} + + +//! adds an list item with an icon +u32 CGUIListBox2::addItem(const wchar_t* text, s32 icon, IReferenceCounted* data) // changed by Nic anderson +{ + ListItem i; + i.text = text; + i.icon = icon; + i.setData(data); + + Items.push_back(i); + recalculateItemHeight(); + recalculateItemWidth(icon); + + return Items.size() - 1; +} + + +void CGUIListBox2::setSpriteBank(IGUISpriteBank* bank) +{ + if ( bank == IconBank ) + return; + if (IconBank) + IconBank->drop(); + + IconBank = bank; + if (IconBank) + IconBank->grab(); +} + + +void CGUIListBox2::recalculateScrollPos() +{ + if (!AutoScroll) + return; + + const s32 selPos = (LastSelected == -1 ? TotalItemHeight : LastSelected * ItemHeight) - ScrollBar->getPos(); + + if (selPos < 0) + { + ScrollBar->setPos(ScrollBar->getPos() + selPos); + } + else + if (selPos > AbsoluteRect.getHeight() - ItemHeight) + { + ScrollBar->setPos(ScrollBar->getPos() + selPos - AbsoluteRect.getHeight() + ItemHeight); + } +} + + +void CGUIListBox2::setAutoScrollEnabled(bool scroll) +{ + AutoScroll = scroll; +} + + +bool CGUIListBox2::isAutoScrollEnabled() const +{ + //_IRR_IMPLEMENT_MANAGED_MARSHALLING_BUGFIX; // removed by Nic. I don't care about .Net. + return AutoScroll; +} + + +bool CGUIListBox2::getSerializationLabels(EGUI_LISTBOX_COLOR colorType, core::stringc & useColorLabel, core::stringc & colorLabel) const +{ + switch ( colorType ) + { + case EGUI_LBC_TEXT: + useColorLabel = "UseColText"; + colorLabel = "ColText"; + break; + case EGUI_LBC_TEXT_HIGHLIGHT: + useColorLabel = "UseColTextHl"; + colorLabel = "ColTextHl"; + break; + case EGUI_LBC_ICON: + useColorLabel = "UseColIcon"; + colorLabel = "ColIcon"; + break; + case EGUI_LBC_ICON_HIGHLIGHT: + useColorLabel = "UseColIconHl"; + colorLabel = "ColIconHl"; + break; + default: + return false; + } + return true; +} + + +//! Writes attributes of the element. +void CGUIListBox2::serializeAttributes(io::IAttributes* out, io::SAttributeReadWriteOptions* options=0) const +{ + IGUIListBox2::serializeAttributes(out,options); + + // todo: out->addString ("IconBank", IconBank->getName?); + out->addBool ("MultiSelect", MultiSelect); + out->addBool ("DrawBack", DrawBack); + out->addBool ("MoveOverSelect", MoveOverSelect); + out->addBool ("AutoScroll", AutoScroll); + + out->addInt("ItemCount", Items.size()); + for (u32 i=0;iaddString(label.c_str(), Items[i].text.c_str() ); + + for ( s32 c=0; c < (s32)EGUI_LBC_COUNT; ++c ) + { + core::stringc useColorLabel, colorLabel; + if ( !getSerializationLabels((EGUI_LISTBOX_COLOR)c, useColorLabel, colorLabel) ) + return; + label = useColorLabel; label += i; + if ( Items[i].OverrideColors[c].Use ) + { + out->addBool(label.c_str(), true ); + label = colorLabel; label += i; + out->addColor(label.c_str(), Items[i].OverrideColors[c].Color); + } + else + { + out->addBool(label.c_str(), false ); + } + } + } +} + + +//! Reads attributes of the element +void CGUIListBox2::deserializeAttributes(io::IAttributes* in, io::SAttributeReadWriteOptions* options=0) +{ + clear(); + + MultiSelect = in->getAttributeAsBool("MultiSelect"); + DrawBack = in->getAttributeAsBool("DrawBack"); + MoveOverSelect = in->getAttributeAsBool("MoveOverSelect"); + AutoScroll = in->getAttributeAsBool("AutoScroll"); + + IGUIListBox2::deserializeAttributes(in,options); + + const s32 count = in->getAttributeAsInt("ItemCount"); + for (s32 i=0; igetAttributeAsStringW(label.c_str()); + + // changed by Nic anderson + addItem(item.text.c_str(), item.icon, 0); // Should actually use a data-creator class + + for ( u32 c=0; c < EGUI_LBC_COUNT; ++c ) + { + core::stringc useColorLabel, colorLabel; + if ( !getSerializationLabels((EGUI_LISTBOX_COLOR)c, useColorLabel, colorLabel) ) + return; + label = useColorLabel; label += i; + Items[i].OverrideColors[c].Use = in->getAttributeAsBool(label.c_str()); + if ( Items[i].OverrideColors[c].Use ) + { + label = colorLabel; label += i; + Items[i].OverrideColors[c].Color = in->getAttributeAsColor(label.c_str()); + } + } + } +} + + +void CGUIListBox2::recalculateItemWidth(s32 icon) +{ + if (IconBank && icon > -1 && + IconBank->getSprites().size() > (u32)icon && + IconBank->getSprites()[(u32)icon].Frames.size()) + { + u32 rno = IconBank->getSprites()[(u32)icon].Frames[0].rectNumber; + if (IconBank->getPositions().size() > rno) + { + const s32 w = IconBank->getPositions()[rno].getWidth(); + if (w > ItemsIconWidth) + ItemsIconWidth = w; + } + } +} + + +void CGUIListBox2::setItem(u32 index, const wchar_t* text, s32 icon, IReferenceCounted* data) // changed by Nic anderson +{ + if ( index >= Items.size() ) + return; + + Items[index].text = text; + Items[index].icon = icon; + Items[index].setData(data); + + recalculateItemHeight(); + recalculateItemWidth(icon); +} + + +//! Insert the item at the given index +//! Return the index on success or -1 on failure. +s32 CGUIListBox2::insertItem(u32 index, const wchar_t* text, s32 icon, IReferenceCounted* data) // changed by Nic anderson +{ + ListItem i; + i.text = text; + i.icon = icon; + i.setData(data); + + Items.insert(i, index); + recalculateItemHeight(); + recalculateItemWidth(icon); + + return index; +} + + +void CGUIListBox2::swapItems(u32 index1, u32 index2) +{ + if ( index1 >= Items.size() || index2 >= Items.size() ) + return; + + ListItem dummmy = Items[index1]; + Items[index1] = Items[index2]; + Items[index2] = dummmy; +} + + +void CGUIListBox2::setItemOverrideColor(u32 index, video::SColor color) +{ + for ( u32 c=0; c < EGUI_LBC_COUNT; ++c ) + { + Items[index].OverrideColors[c].Use = true; + Items[index].OverrideColors[c].Color = color; + } +} + + +void CGUIListBox2::setItemOverrideColor(u32 index, EGUI_LISTBOX_COLOR colorType, video::SColor color) +{ + if ( index >= Items.size() || colorType < 0 || colorType >= EGUI_LBC_COUNT ) + return; + + Items[index].OverrideColors[colorType].Use = true; + Items[index].OverrideColors[colorType].Color = color; +} + + +void CGUIListBox2::clearItemOverrideColor(u32 index) +{ + for (u32 c=0; c < (u32)EGUI_LBC_COUNT; ++c ) + { + Items[index].OverrideColors[c].Use = false; + } +} + + +void CGUIListBox2::clearItemOverrideColor(u32 index, EGUI_LISTBOX_COLOR colorType) +{ + if ( index >= Items.size() || colorType < 0 || colorType >= EGUI_LBC_COUNT ) + return; + + Items[index].OverrideColors[colorType].Use = false; +} + + +bool CGUIListBox2::hasItemOverrideColor(u32 index, EGUI_LISTBOX_COLOR colorType) const +{ + if ( index >= Items.size() || colorType < 0 || colorType >= EGUI_LBC_COUNT ) + return false; + + return Items[index].OverrideColors[colorType].Use; +} + + +video::SColor CGUIListBox2::getItemOverrideColor(u32 index, EGUI_LISTBOX_COLOR colorType) const +{ + if ( (u32)index >= Items.size() || colorType < 0 || colorType >= EGUI_LBC_COUNT ) + return video::SColor(); + + return Items[index].OverrideColors[colorType].Color; +} + + +video::SColor CGUIListBox2::getItemDefaultColor(EGUI_LISTBOX_COLOR colorType) const +{ + IGUISkin* skin = Environment->getSkin(); + if ( !skin ) + return video::SColor(); + + switch ( colorType ) + { + case EGUI_LBC_TEXT: + return skin->getColor(EGDC_BUTTON_TEXT); + case EGUI_LBC_TEXT_HIGHLIGHT: + return skin->getColor(EGDC_HIGH_LIGHT_TEXT); + case EGUI_LBC_ICON: + return skin->getColor(EGDC_ICON); + case EGUI_LBC_ICON_HIGHLIGHT: + return skin->getColor(EGDC_ICON_HIGH_LIGHT); + default: + return video::SColor(); + } +} + +//! set global itemHeight +void CGUIListBox2::setItemHeight( s32 height ) +{ + ItemHeight = height; + ItemHeightOverride = 1; +} + + +//! Sets whether to draw the background +void CGUIListBox2::setDrawBackground(bool draw) +{ + DrawBack = draw; +} + +//! Sets whether this is a multi-select list box +void CGUIListBox2::setMultiSelect(bool multiSelect) +{ + MultiSelect = multiSelect; + if (multiSelect) /* switch to multiselect */ + Selected = -1; + else /* switch to singleselect */ + { + for (u32 i=0; i &sel) +{ + sel.clear(); + if (!MultiSelect) + { + if (Selected >= 0) + sel.push_back(Selected); + return; + } + for (u32 i=0; i sel) +{ + setMultiSelect(true); + for (u32 i=0; igetTime(); // change by Nic Anderson + if (sel.size() == 0) + LastSelected = -1; + else + LastSelected = sel[0]; + + recalculateScrollPos(); +} + +//! sets the select-state of an item. +void CGUIListBox2::setItemSelection(u32 id, bool selected) +{ + if (!MultiSelect) + setSelected(id); + else + { + if (id < Items.size()) + Items[id].selected = true; + } +} + + +} // end namespace gui +} // end namespace irr + +//#endif // _IRR_COMPILE_WITH_GUI_ diff --git a/IrrExtensions/gui/listbox/CGUIListBox2.h b/IrrExtensions/gui/listbox/CGUIListBox2.h new file mode 100644 index 0000000..099d943 --- /dev/null +++ b/IrrExtensions/gui/listbox/CGUIListBox2.h @@ -0,0 +1,236 @@ +// by tom_gamer +// changed to CGUIListBox2 by Nic Anderson +// Modified to store data associated with text. + +// Copyright (C) 2002-2012 Nikolaus Gebhardt +// This file is part of the "Irrlicht Engine". +// For conditions of distribution and use, see copyright notice in irrlicht.h + +#ifndef __C_GUI_LIST_BOX2_H_INCLUDED__ +#define __C_GUI_LIST_BOX2_H_INCLUDED__ + +// booted from standalone version +//#include "IrrCompileConfig.h" +//#ifdef _IRR_COMPILE_WITH_GUI_ + +#include "IGUIListBox2.h" +#include + +namespace irr +{ + class ITimer; // added by Nic Anderson to avoid os.h dependency + +namespace gui +{ + + class IGUIFont; + class IGUIScrollBar; + + class CGUIListBox2 : public IGUIListBox2 + { + public: + //! constructor + // timer param added by Nic Anderson to avoid os.h dependency + CGUIListBox2(IGUIEnvironment* environment, ITimer* timer, IGUIElement* parent, + s32 id, core::rect rectangle, bool clip=true, + bool drawBack=false, bool moveOverSelect=false); + + //! destructor + virtual ~CGUIListBox2(); + + //! returns amount of list items + virtual u32 getItemCount() const; + + //! returns string of a list item. the id may be a value from 0 to itemCount-1 + virtual const wchar_t* getListItem(u32 id) const; + + //! returns data associated with the selected list item + virtual IReferenceCounted* getListItemData(u32 id) const; // added by Nic Anderson + + //! adds an list item, returns id of item + virtual u32 addItem(const wchar_t* text, IReferenceCounted* data); // changed by Nic Anderson + + //! clears the list + virtual void clear(); + + //! returns id of first selected item. returns -1 if no item is selected. + virtual s32 getSelected() const; + + //! returns ids of selected items. + virtual void getSelected(core::array &sel); + + //! sets the selected item. Set this to -1 if no item should be selected + virtual void setSelected(s32 id); + + //! sets the selected items. + virtual void setSelected(core::array sel); + + //! sets the selected item. Set this to -1 if no item should be selected + virtual void setSelected(const wchar_t *item); + + //! called if an event happened. + virtual bool OnEvent(const SEvent& event); + + //! draws the element and its children + virtual void draw(); + + //! adds an list item with an icon + //! \param text Text of list entry + //! \param icon Sprite index of the Icon within the current sprite bank. Set it to -1 if you want no icon + //! \return + //! returns the id of the new created item + virtual u32 addItem(const wchar_t* text, s32 icon, IReferenceCounted* data); // changed by Nic anderson + + //! Returns the icon of an item + virtual s32 getIcon(u32 id) const; + + //! removes an item from the list + virtual void removeItem(u32 id); + + //! get the the id of the item at the given absolute coordinates + virtual s32 getItemAt(s32 xpos, s32 ypos) const; + + //! Sets the sprite bank which should be used to draw list icons. This font is set to the sprite bank of + //! the built-in-font by default. A sprite can be displayed in front of every list item. + //! An icon is an index within the icon sprite bank. Several default icons are available in the + //! skin through getIcon + virtual void setSpriteBank(IGUISpriteBank* bank); + + //! set whether the listbox should scroll to newly selected items + virtual void setAutoScrollEnabled(bool scroll); + + //! returns true if automatic scrolling is enabled, false if not. + virtual bool isAutoScrollEnabled() const; + + //! Update the position and size of the listbox, and update the scrollbar + virtual void updateAbsolutePosition(); + + //! Writes attributes of the element. + virtual void serializeAttributes(io::IAttributes* out, io::SAttributeReadWriteOptions* options) const; + + //! Reads attributes of the element + virtual void deserializeAttributes(io::IAttributes* in, io::SAttributeReadWriteOptions* options); + + //! set all item colors at given index to color + virtual void setItemOverrideColor(u32 index, video::SColor color); + + //! set all item colors of specified type at given index to color + virtual void setItemOverrideColor(u32 index, EGUI_LISTBOX_COLOR colorType, video::SColor color); + + //! clear all item colors at index + virtual void clearItemOverrideColor(u32 index); + + //! clear item color at index for given colortype + virtual void clearItemOverrideColor(u32 index, EGUI_LISTBOX_COLOR colorType); + + //! has the item at index its color overwritten? + virtual bool hasItemOverrideColor(u32 index, EGUI_LISTBOX_COLOR colorType) const; + + //! return the overwrite color at given item index. + virtual video::SColor getItemOverrideColor(u32 index, EGUI_LISTBOX_COLOR colorType) const; + + //! return the default color which is used for the given colorType + virtual video::SColor getItemDefaultColor(EGUI_LISTBOX_COLOR colorType) const; + + //! set the item at the given index + virtual void setItem(u32 index, const wchar_t* text, s32 icon, IReferenceCounted* data); // changed by Nic anderson + + //! Insert the item at the given index + //! Return the index on success or -1 on failure. + virtual s32 insertItem(u32 index, const wchar_t* text, s32 icon, IReferenceCounted* data); // changed by Nic anderson + + //! Swap the items at the given indices + virtual void swapItems(u32 index1, u32 index2); + + //! set global itemHeight + virtual void setItemHeight( s32 height ); + + //! Sets whether to draw the background + virtual void setDrawBackground(bool draw); + + //! Sets whether this is a multi-select list box + virtual void setMultiSelect(bool multiSelect); + + private: + + struct ListItem + { + core::stringw text; + s32 icon; + IReferenceCounted* data; // added by Nic Anderson + bool selected; + + // changed by Nic Anderson + ListItem() : icon(-1), data(0), selected(false) + {} + + // added by Nic Anderson + void setData( IReferenceCounted* pData ) + { + if ( data ) data->drop(); + data = pData; + if ( data ) data->grab(); + } + + // added by Nic Anderson + ~ListItem() + { + if ( data ) data->drop(); + } + + // A multicolor extension + struct ListItemOverrideColor + { + ListItemOverrideColor() : Use(false) {} + bool Use; + video::SColor Color; + }; + ListItemOverrideColor OverrideColors[EGUI_LBC_COUNT]; + }; + + void recalculateItemHeight(); + void selectNew(s32 ypos, bool onlyHover, bool ctrlState=false, bool shftState=false); + void recalculateScrollPos(); + + // extracted that function to avoid copy&paste code + void recalculateItemWidth(s32 icon); + + // get labels used for serialization + bool getSerializationLabels(EGUI_LISTBOX_COLOR colorType, core::stringc & useColorLabel, core::stringc & colorLabel) const; + + //! sets the select-state of an item. + void setItemSelection(u32 id, bool selected); + + + core::array< ListItem > Items; + s32 Selected; + s32 ItemHeight; + s32 ItemHeightOverride; + s32 TotalItemHeight; + s32 ItemsIconWidth; + gui::IGUIFont* Font; + gui::IGUISpriteBank* IconBank; + gui::IGUIScrollBar* ScrollBar; + u32 selectTime; + u32 LastKeyTime; + core::stringw KeyBuffer; + bool Selecting; + bool DrawBack; + bool MoveOverSelect; + bool AutoScroll; + bool HighlightWhenNotFocused; + bool MultiSelect; + bool DidMouseMove; + s32 LastSelected; + + // timer added by Nic Anderson to avoid os.h dependency + ITimer* Timer; + }; + + +} // end namespace gui +} // end namespace irr + +//#endif // _IRR_COMPILE_WITH_GUI_ + +#endif diff --git a/IrrExtensions/gui/listbox/IGUIListBox2.h b/IrrExtensions/gui/listbox/IGUIListBox2.h new file mode 100644 index 0000000..8986341 --- /dev/null +++ b/IrrExtensions/gui/listbox/IGUIListBox2.h @@ -0,0 +1,153 @@ +// by tom_gamer +// changed to IGUIListBox2 by Nic Anderson +// Modified to store data associated with text. + +// Copyright (C) 2002-2012 Nikolaus Gebhardt +// This file is part of the "Irrlicht Engine". +// For conditions of distribution and use, see copyright notice in irrlicht.h + +#ifndef __I_GUI_LIST_BOX2_H_INCLUDED__ +#define __I_GUI_LIST_BOX2_H_INCLUDED__ + +#include +#include + +namespace irr +{ +namespace gui +{ + class IGUISpriteBank; + + //! Enumeration for listbox colors + enum EGUI_LISTBOX_COLOR + { + //! Color of text + EGUI_LBC_TEXT=0, + //! Color of selected text + EGUI_LBC_TEXT_HIGHLIGHT, + //! Color of icon + EGUI_LBC_ICON, + //! Color of selected icon + EGUI_LBC_ICON_HIGHLIGHT, + //! Not used, just counts the number of available colors + EGUI_LBC_COUNT + }; + + + //! Default list box GUI element. + /** \par This element can create the following events of type EGUI_EVENT_TYPE: + \li EGET_LISTBOX_CHANGED + \li EGET_LISTBOX_SELECTED_AGAIN + */ + class IGUIListBox2 : public IGUIElement + { + public: + //! constructor + IGUIListBox2(IGUIEnvironment* environment, IGUIElement* parent, s32 id, core::rect rectangle) + : IGUIElement(EGUIET_LIST_BOX, environment, parent, id, rectangle) {} + + //! returns amount of list items + virtual u32 getItemCount() const = 0; + + //! returns string of a list item. the may id be a value from 0 to itemCount-1 + virtual const wchar_t* getListItem(u32 id) const = 0; + + //! returns data associated with the selected list item + virtual IReferenceCounted* getListItemData(u32 id) const = 0; // added by Nic Anderson + + //! adds an list item, returns id of item + virtual u32 addItem(const wchar_t* text, IReferenceCounted* data) = 0; // changed by Nic Anderson + + //! adds an list item with an icon + /** \param text Text of list entry + \param icon Sprite index of the Icon within the current sprite bank. Set it to -1 if you want no icon + \return The id of the new created item */ + virtual u32 addItem(const wchar_t* text, s32 icon, IReferenceCounted* data) = 0; // changed by Nic Anderson + + //! Removes an item from the list + virtual void removeItem(u32 index) = 0; + + //! get the the id of the item at the given absolute coordinates + /** \return The id of the listitem or -1 when no item is at those coordinates*/ + virtual s32 getItemAt(s32 xpos, s32 ypos) const = 0; + + //! Returns the icon of an item + virtual s32 getIcon(u32 index) const = 0; + + //! Sets the sprite bank which should be used to draw list icons. + /** This font is set to the sprite bank of the built-in-font by + default. A sprite can be displayed in front of every list item. + An icon is an index within the icon sprite bank. Several + default icons are available in the skin through getIcon. */ + virtual void setSpriteBank(IGUISpriteBank* bank) = 0; + + //! clears the list, deletes all items in the listbox + virtual void clear() = 0; + + //! returns id of selected item. returns -1 if no item is selected. + virtual s32 getSelected() const = 0; + + //! returns ids of selected items. + virtual void getSelected(core::array &sel) = 0; + + //! sets the selected item. Set this to -1 if no item should be selected + virtual void setSelected(s32 index) = 0; + + //! sets the selected items. + virtual void setSelected(core::array sel) = 0; + + //! sets the selected item. Set this to 0 if no item should be selected + virtual void setSelected(const wchar_t *item) = 0; + + //! set whether the listbox should scroll to newly selected items + virtual void setAutoScrollEnabled(bool scroll) = 0; + + //! returns true if automatic scrolling is enabled, false if not. + virtual bool isAutoScrollEnabled() const = 0; + + //! set all item colors at given index to color + virtual void setItemOverrideColor(u32 index, video::SColor color) = 0; + + //! set all item colors of specified type at given index to color + virtual void setItemOverrideColor(u32 index, EGUI_LISTBOX_COLOR colorType, video::SColor color) = 0; + + //! clear all item colors at index + virtual void clearItemOverrideColor(u32 index) = 0; + + //! clear item color at index for given colortype + virtual void clearItemOverrideColor(u32 index, EGUI_LISTBOX_COLOR colorType) = 0; + + //! has the item at index its color overwritten? + virtual bool hasItemOverrideColor(u32 index, EGUI_LISTBOX_COLOR colorType) const = 0; + + //! return the overwrite color at given item index. + virtual video::SColor getItemOverrideColor(u32 index, EGUI_LISTBOX_COLOR colorType) const = 0; + + //! return the default color which is used for the given colorType + virtual video::SColor getItemDefaultColor(EGUI_LISTBOX_COLOR colorType) const = 0; + + //! set the item at the given index + virtual void setItem(u32 index, const wchar_t* text, s32 icon, IReferenceCounted* data) = 0; // changed by Nic Anderson + + //! Insert the item at the given index + /** \return The index on success or -1 on failure. */ + virtual s32 insertItem(u32 index, const wchar_t* text, s32 icon, IReferenceCounted* data) = 0; // changed by Nic Anderson + + //! Swap the items at the given indices + virtual void swapItems(u32 index1, u32 index2) = 0; + + //! set global itemHeight + virtual void setItemHeight( s32 height ) = 0; + + //! Sets whether to draw the background + virtual void setDrawBackground(bool draw) = 0; + + //! Sets whether this is a multi-select list box + virtual void setMultiSelect(bool multiSelect) = 0; +}; + + +} // end namespace gui +} // end namespace irr + +#endif diff --git a/IrrExtensions/gui/scene/CamController.cpp b/IrrExtensions/gui/scene/CamController.cpp new file mode 100644 index 0000000..4dcbbda --- /dev/null +++ b/IrrExtensions/gui/scene/CamController.cpp @@ -0,0 +1,338 @@ +// Copyright (C) 2013 Matthias Walden +// This file is currently not part of the "Irrlicht Engine". +// CamController.cpp +// This is non-restricted usage code. +// Changes by Nic Anderson: +// > Changed prototype and function heador of LoopProc (return value now "void") + +#include "CamController.h" + +// namespace by Nic Anderson +namespace irr { +namespace scene { + +using core::matrix4; + +//--------------------------------------------------------------------------- +CamController::CamController(ICameraSceneNode* NewCameraNode, ISceneNode *NewTarget) +{ + // For if cam controller inherits IReferenceCounted + //#ifdef _DEBUG + //setDebugName("CamController"); + //#endif + + Target = 0; + Camera = 0; + UpdateCam = false; + FollowTargetDir = true; + FollowTargetDirX = true; + + DistToTarget = 0; + Distance = 50.0f; + DistChanged = false; + + XRotBoundary.X = 60.0f; + XRotBoundary.Y = 300.0f; + + SetTarget(NewTarget); + SetCamera(NewCameraNode); + + RotateCamera(vector3df(0)); + Camera->bindTargetAndRotation(true); + + //XRotBoundary +} +//--------------------------------------------------------------------------- +//CamController::~CamController(void) +//{ +// +//} +//--------------------------------------------------------------------------- +void CamController::UpdateCamPos() +{ + CamPos = Camera->getPosition(); +} +//--------------------------------------------------------------------------- +void CamController::UpdateTargetPos() +{ + LastTargetPos = TargetPos; + Target->updateAbsolutePosition(); + TargetPos = Target->getAbsolutePosition() + TargetCenter; +} +//--------------------------------------------------------------------------- +void CamController::SetTarget (ISceneNode *NewTarget, vector3df NewTargetCenter) +{ + Target = NewTarget; + TargetCenter = NewTargetCenter; + UpdateTargetPos(); + LastTargetPos = TargetPos; + + if (Target && Camera) + UpdateCam = true; +} +//--------------------------------------------------------------------------- +void CamController::SetCamera (ICameraSceneNode* NewCameraNode) +{ + Camera = NewCameraNode; + UpdateCamPos(); + + if (Target && Camera) + UpdateCam = true; +} +//--------------------------------------------------------------------------- +void CamController::CalcCameraAlign (void) +{ + //vector3df NewCamPos; + + if(FollowTargetDir) + { + if (!FollowTargetDirX) + { + CamPos = TargetPos + (CamPos - TargetPos).normalize() * Distance; + + vector3df CurrCamRot; + CurrCamRot = GetCameraRot(); + CurrCamRot.X = RotX; + + SetCameraRot(CurrCamRot); + } + else + { + CamPos = TargetPos + (CamPos - TargetPos).normalize() * Distance; + CheckBoundaries(); + } + } + else + { + if(DistChanged) + { + CamPos = CamPos + (TargetPos - LastTargetPos); + + matrix4 mat; + mat.setRotationDegrees( (CamPos - TargetPos).getHorizontalAngle()); + + CamPos = vector3df(0,0,Distance); + mat.rotateVect(CamPos); + + CamPos += TargetPos; + DistChanged = false; + } + else + { + CamPos += (TargetPos - LastTargetPos); + } + } + + Camera->setPosition(CamPos); + Camera->setTarget(TargetPos); +} +//--------------------------------------------------------------------------- +void CamController::CheckBoundaries (void) +{ + //Kontrolle Der X-Grenzwerte + vector3df CurrCamAngle = GetCameraRot(); + if (CurrCamAngle.X < 90.0f) + { + if(CurrCamAngle.X > XRotBoundary.X) + { + CurrCamAngle.X = XRotBoundary.X; + SetCameraRot(CurrCamAngle); + } + } + else + { + if(CurrCamAngle.X < XRotBoundary.Y) + { + CurrCamAngle.X = XRotBoundary.Y; + SetCameraRot(CurrCamAngle); + } + } +} +//--------------------------------------------------------------------------- +vector2df CamController::GetXRotBoundaries (void) +{ + return XRotBoundary; +} +//--------------------------------------------------------------------------- +void CamController::SetXRotBoundaries (vector2df aXRotBoundary) +{ + XRotBoundary = aXRotBoundary; +} +//--------------------------------------------------------------------------- +vector3df CamController::GetCameraRot(void) +{ + return (CamPos - TargetPos).getHorizontalAngle(); +} +//--------------------------------------------------------------------------- +vector3df CamController::GetCameraDirection (void) +{ + vector3df CurrCamDir = vector3df (TargetPos - CamPos); + CurrCamDir.normalize(); + + return CurrCamDir; +} +//--------------------------------------------------------------------------- +vector3df CamController::GetCameraDirection (vector3df aRotationDelta) +{ + vector3df CurrCamDir = vector3df (TargetPos - CamPos); + + vector3df AdjustedCamDir(0,0,1.f); + + matrix4 m; + m.setRotationDegrees(CurrCamDir.getHorizontalAngle() + aRotationDelta); + m.rotateVect(AdjustedCamDir); + + AdjustedCamDir.normalize(); + + return AdjustedCamDir; +} +//--------------------------------------------------------------------------- +vector3df CamController::GetCameraDirectionH (void) +{ + vector3df CurrCamDir = vector3df (TargetPos - CamPos); + CurrCamDir.Y=0; + CurrCamDir.normalize(); + + return CurrCamDir; +} +//--------------------------------------------------------------------------- +vector3df CamController::GetCameraDirectionH (vector3df aRotationDelta) +{ + vector3df CurrCamDir = vector3df (TargetPos - CamPos); + CurrCamDir.Y=0; + + vector3df AdjustedCamDir(0,0,1.f); + + matrix4 m; + m.setRotationDegrees(CurrCamDir.getHorizontalAngle() + aRotationDelta); + m.rotateVect(AdjustedCamDir); + + AdjustedCamDir.normalize(); + + return AdjustedCamDir; +} +//--------------------------------------------------------------------------- +void CamController::SetCameraRot(vector3df aRotation) +{ + //Kontrolle Der X-Grenzwerte + if (aRotation.X < 90.0f) + { + if(aRotation.X > XRotBoundary.X) + { + aRotation.X = XRotBoundary.X; + } + } + else + { + if(aRotation.X < XRotBoundary.Y) + { + aRotation.X = XRotBoundary.Y; + } + } + + if (!FollowTargetDirX) + { + RotX = aRotation.X; + } + + matrix4 mat; + mat.setRotationDegrees(aRotation); + + CamPos = vector3df(0,0,Distance); + mat.rotateVect(CamPos); + + CamPos += TargetPos; + Camera->setPosition(CamPos); + UpdateCam = false; +} +//--------------------------------------------------------------------------- +void CamController::RotateCamera(vector3df aRotationDelta) +{ + SetCameraRot(GetCameraRot() + aRotationDelta); +} +//--------------------------------------------------------------------------- +f32 CamController::GetDistance(void) +{ + return Distance; +} +//--------------------------------------------------------------------------- +void CamController::SetDistance(f32 aDistance) +{ + Distance = aDistance; + UpdateCam = true; + DistChanged = true; +} +//--------------------------------------------------------------------------- +void CamController::ChangeDistance(f32 aDiff) +{ + SetDistance(Distance + aDiff); +} +//--------------------------------------------------------------------------- +bool CamController::GetFollowTargetDir (void) +{ + return FollowTargetDir; +} +//--------------------------------------------------------------------------- +void CamController::SetFollowTargetDir (bool aFollow) +{ + FollowTargetDir = aFollow; +} +//--------------------------------------------------------------------------- +void CamController::SwitchFollowTargetDir(void) +{ + FollowTargetDir = !FollowTargetDir; +} +//--------------------------------------------------------------------------- +bool CamController::GetFollowTargetDirX (void) +{ + return FollowTargetDirX; +} +//--------------------------------------------------------------------------- +void CamController::SetFollowTargetDirX (bool aFollow) +{ + FollowTargetDirX = aFollow; + + if(!FollowTargetDirX) + { + RotX = GetCameraRot().X; + } +} +//--------------------------------------------------------------------------- +void CamController::SwitchFollowTargetDirX(void) +{ + FollowTargetDirX = !FollowTargetDirX; + + if(!FollowTargetDirX) + { + RotX = GetCameraRot().X; + } +} +//--------------------------------------------------------------------------- +f32 CamController::CalcDistToTarget(void) +{ + UpdateCamPos(); + UpdateTargetPos(); + + DistToTarget = CamPos.getDistanceFrom(TargetPos); + return DistToTarget; +} +//--------------------------------------------------------------------------- +// return set to void by Nic Anderson +void CamController::LoopProc () +{ + if (Target && Camera) + { + CalcDistToTarget(); + + if (TargetPos != LastTargetPos) + UpdateCam = true; + + if (UpdateCam) + { + CalcCameraAlign(); + UpdateCam = false; + } + } +} + +}} // end namespaces diff --git a/IrrExtensions/gui/scene/CamController.h b/IrrExtensions/gui/scene/CamController.h new file mode 100644 index 0000000..fdf733c --- /dev/null +++ b/IrrExtensions/gui/scene/CamController.h @@ -0,0 +1,97 @@ +// Copyright (C) 2013 Matthias Walden +// This file is currently not part of the "Irrlicht Engine". +// CamController.h +// This is non-restricted usage code. +// Changes by Nic Anderson: +// > Changed prototype and function heador of LoopProc (return value now "void") + +#ifndef __CAMCONTROLLER_H_INCLUDED__ +#define __CAMCONTROLLER_H_INCLUDED__ + +#include "irrArray.h" +#include "IVideoDriver.h" +#include "ISceneManager.h" +#include "ICameraSceneNode.h" + +using namespace irr; +using namespace core; +using namespace scene; +using namespace video; +using namespace io; +using namespace gui; + +// namespace by Nic Anderson +namespace irr { +namespace scene { + +using core::vector3df; + +class CamController +{ + public: + + CamController(ICameraSceneNode* NewCameraNode, ISceneNode *NewTarget); + //~CamController(void); + + void SetTarget (ISceneNode *NewTarget, vector3df NewTargetCenter = vector3df (0.f)); + void SetCamera (ICameraSceneNode* NewCameraNode); + + void CalcCameraAlign (void); + + vector2df GetXRotBoundaries (void); + void SetXRotBoundaries (vector2df aXRotBoundary); + + vector3df GetCameraRot(void); + vector3df GetCameraDirection (void); + vector3df GetCameraDirection (vector3df aRotationDelta); + vector3df GetCameraDirectionH (void); //H = horizontal! + vector3df GetCameraDirectionH (vector3df aRotationDelta); //H = horizontal! + void SetCameraRot(vector3df aRotation); + void RotateCamera(vector3df aRotationDelta); + + f32 GetDistance(void); + void SetDistance(f32 aDistance); + void ChangeDistance(f32 aDiff); + + bool GetFollowTargetDir (void); + void SetFollowTargetDir (bool aFollow); + void SwitchFollowTargetDir (void); + + bool GetFollowTargetDirX (void); + void SetFollowTargetDirX (bool aFollow); + void SwitchFollowTargetDirX (void); + + // return set to void by Nic Anderson + void LoopProc (); + + private: + + f32 CalcDistToTarget(void); + void UpdateCamPos(); + void UpdateTargetPos(); + + void CheckBoundaries (void); + + ISceneNode *Target; + ICameraSceneNode* Camera; + + vector3df CamPos; + vector3df TargetPos; + vector3df TargetCenter; + vector3df LastTargetPos; + + vector2df XRotBoundary; + f32 RotX; + + f32 DistToTarget; + bool UpdateCam; + bool FollowTargetDir; + bool FollowTargetDirX; + + f32 Distance; + bool DistChanged; +}; + +}} // end namespaces + +#endif // __CAMCONTROLLER_H_INCLUDED__ diff --git a/IrrExtensions/gui/scene/GUIScene.cpp b/IrrExtensions/gui/scene/GUIScene.cpp new file mode 100644 index 0000000..fabc831 --- /dev/null +++ b/IrrExtensions/gui/scene/GUIScene.cpp @@ -0,0 +1,311 @@ +// Copyright (C) 2013 Matthias Walden +// This file is currently not part of the "Irrlicht Engine". +// A Gui-Scene for rendering inside a IrrlichtGui... +// This is non-restricted usage code. +// Changes by Nic Anderson: +// > Casting variables in some places +// > Replaced IrrlichtDevice references by using IGUIEnvironment and ISceneManager +// > Changed constructor function header + + +#include "GUIScene.h" +// includes changed by Nic Anderson +#include +#include +#include +#include +#include +#include +#include +#include + +// namespace by Nic Anderson +namespace irr { +namespace gui { + +//--------------------------------------------------------------------------- +GUIScene::GUIScene(IGUIEnvironment* pEnvironment, scene::ISceneManager* pSceneManager, IGUIElement* pParent, recti pRect, s32 id, video::E_DRIVER_TYPE pDriverType) + : IGUIElement(EGUIET_ELEMENT, pEnvironment, pParent, id, pRect) + // initializations added by Nic Anderson + , FocusedNode(0) + , IDriver(0) + , IScene(0) + , Cam(0) + , Light(0) + , Center(0) + , MeshNode(0) + , CamCTRL(0) + , CurrX(0) + , CurrY(0) + , LastX(0) + , LastY(0) + , driverType(pDriverType) + , drawBack(false) + , drawBorder(false) + , ExtraCamDist(0) // not added by Nic Anderson +{ + //setRelativePositionProportional(rectf(0,0,1,1)); + //recti Pos = getRelativePosition(); + //Pos.UpperLeftCorner.X +=1; + //Pos.UpperLeftCorner.Y +=1; + //Pos.LowerRightCorner.X -=1; + //Pos.LowerRightCorner.Y -=1; + //setRelativePosition(Pos); + + // changes by Nic Anderson + IDriver = pEnvironment->getVideoDriver(); + IScene = pSceneManager->createNewSceneManager(false); + + Cam = IScene->addCameraSceneNode(0, vector3df(0,0,-100.f), vector3df(0,0,0)); + // Cam->setFOV(30 * DEGTORAD); + + Light = IScene->addLightSceneNode(Cam); + Light->setRadius(1000.f); + + Center = IScene->addEmptySceneNode(); + + IScene->getParameters()->setAttribute(ALLOW_ZWRITE_ON_TRANSPARENT, true); +} +//--------------------------------------------------------------------------- +GUIScene::~GUIScene(void) +{ + IScene->drop(); +} +//--------------------------------------------------------------------------- +ISceneManager *GUIScene::GetScene(void) +{ + return IScene; +} +//--------------------------------------------------------------------------- +ICameraSceneNode *GUIScene::GetCamera(void) +{ + return Cam; +} +//--------------------------------------------------------------------------- +void GUIScene::SetOptimalCamDistToNode (ISceneNode *aNode) +{ + FocusedNode = aNode; +} +//--------------------------------------------------------------------------- +void GUIScene::CalcOptimalCamDistToNode (ISceneNode *aNode) +{ + //Node + aabbox3df NodeBBox = aNode->getBoundingBox(); + f32 NodeWidth = NodeBBox.MaxEdge.X - NodeBBox.MinEdge.X; + f32 NodeHeight = NodeBBox.MaxEdge.Y - NodeBBox.MinEdge.Y; + f32 NodeDepth = NodeBBox.MaxEdge.Z - NodeBBox.MinEdge.Z; + + f32 MaxWidth; + + if(NodeWidth > NodeDepth) + { + MaxWidth = NodeWidth; + } + else + { + MaxWidth = NodeDepth; + } + + + //Far + const SViewFrustum* f = Cam->getViewFrustum(); + f32 Far = Cam->getFarValue(); + f32 FarWidth = line3df(f->getFarLeftUp(), f->getFarRightUp()).getLength(); + f32 FarHeight = line3df(f->getFarLeftUp(), f->getFarLeftDown()).getLength(); + + //GuiElement + recti CurrRect = getAbsolutePosition(); + + //Zoom + f32 ZoomX = CurrRect.getWidth() / MaxWidth; + f32 ZoomY = CurrRect.getHeight() / NodeHeight; + + f32 CamDist; + + if (ZoomX < ZoomY) + { + CamDist =1.0f * ((MaxWidth / (FarWidth / Far)) + (0.5f * MaxWidth) ); + } + else + { + CamDist =1.0f * ((NodeHeight / (FarHeight / Far)) + (0.5f * MaxWidth) ); + } + + if(!CamCTRL) + { + Cam->setPosition(vector3df(0,0,CamDist*-1)); + } + else + { + CamCTRL->SetDistance(CamDist + ExtraCamDist); // modified by Nic Anderson for extra cam distance + } + + /*cout << "Far: " << Far << endl; + cout << "Far Width: " << FarWidth << endl; + cout << "Far Height: " << FarHeight << endl; + cout << "Node Width: " << NodeWidth << endl; + cout << "Cam Dist: " << CamDist << endl; + cout << "FOV: " << Cam->getFOV() << endl; + cout << "NodeBBox.MinEdge.Z :" << NodeBBox.MinEdge.Z << endl;*/ +} +//--------------------------------------------------------------------------- +IAnimatedMeshSceneNode* GUIScene::setMesh (IAnimatedMesh *aMesh, bool updateOptimalCamDist) +{ + if(!aMesh) + { + if(MeshNode) + { + MeshNode->remove(); + MeshNode = 0; + } + return MeshNode; + } + else + if(MeshNode) + { + MeshNode->remove(); + MeshNode = 0; + } + + //if(!MeshNode) + //{ + MeshNode = IScene->addAnimatedMeshSceneNode(aMesh); + MeshNode->setPosition(MeshNode->getBoundingBox().getCenter()*-1); + + if(updateOptimalCamDist) + { + SetOptimalCamDistToNode(MeshNode); + } + //} + + return MeshNode; +} +//--------------------------------------------------------------------------- +IAnimatedMeshSceneNode* GUIScene::setMesh (stringw aFileName, bool updateOptimalCamDist) +{ + return setMesh(IScene->getMesh(aFileName), updateOptimalCamDist); +} +//--------------------------------------------------------------------------- +void GUIScene::enableCamControl (bool aEnable) // Nic Anderson renamed +{ + if(!CamCTRL) + { + if(aEnable) + { + CamCTRL = new CamController(Cam, Center); + } + } + else + { + if(!aEnable) + { + delete CamCTRL; + CamCTRL = 0; + } + } +} +//--------------------------------------------------------------------------- +void GUIScene::setDrawBackground( bool pDrawBack ) // by Nic Anderson +{ + drawBack = pDrawBack; +} + +void GUIScene::setDrawBorder( bool pDrawBorder ) // by Nic Anderson +{ + drawBorder = pDrawBorder; +} + +void GUIScene::setExtraCamDistance( f32 pExtraDist ) // by Nic Anderson +{ + ExtraCamDist = pExtraDist; +} +//--------------------------------------------------------------------------- +void GUIScene::draw (void) +{ + rect OldViewPort = IDriver->getViewPort(); + + IDriver->setViewPort(getAbsoluteClippingRect()); + + //IDriver->clearZBuffer(); // function depreciated, but I'm not sure why + + // border setting added by Nic Anderson + recti borderRect; + if ( driverType == video::EDT_BURNINGSVIDEO ) // Burning's doesn't set the viewport for 2D drawing + { + borderRect = getAbsoluteClippingRect(); + borderRect.UpperLeftCorner.X -= 2; + borderRect.UpperLeftCorner.Y -= 2; + borderRect.LowerRightCorner.X += 2; + borderRect.LowerRightCorner.Y += 2; + } else { + borderRect = Environment->getRootGUIElement()->getAbsoluteClippingRect(); + } + if ( drawBorder ) + { + Environment->getSkin()->draw3DSunkenPane( + this, + drawBack? 0xff000000:0, // black + false, + true, + borderRect, + 0 ); + } + + dimension2d Size = getAbsoluteClippingRect().getSize(); + IScene->getActiveCamera()->setAspectRatio((f32)Size.Width / (f32)Size.Height); + + if(CamCTRL) + CamCTRL->LoopProc(); + + IScene->drawAll(); + + if(FocusedNode) + { + CalcOptimalCamDistToNode(FocusedNode); + FocusedNode = 0; + } + + IDriver->setViewPort(OldViewPort); +} +//--------------------------------------------------------------------------- +bool GUIScene::OnEvent (const SEvent &event) +{ + if(CamCTRL) + { + switch(event.EventType) + { + case EET_MOUSE_INPUT_EVENT: + { + if(event.MouseInput.Event==EMIE_LMOUSE_PRESSED_DOWN) + { + LastX = (f32)event.MouseInput.X; + LastY = (f32)event.MouseInput.Y; + return true; + } + else + if(event.MouseInput.Event==EMIE_MOUSE_MOVED) + { + if(event.MouseInput.isLeftPressed()) + { + CurrX = (f32)event.MouseInput.X; + CurrY = (f32)event.MouseInput.Y; + + CamCTRL->RotateCamera(vector3df(LastY-CurrY,CurrX-LastX,0)); + + LastX = CurrX; + LastY = CurrY; + return true; + } + } + } + break; + + // added by Nic Anderson (to remove -Wall error) + default: break; + } + } + + return IGUIElement::OnEvent(event); +} + +}} // end namespaces diff --git a/IrrExtensions/gui/scene/GUIScene.h b/IrrExtensions/gui/scene/GUIScene.h new file mode 100644 index 0000000..a9fbabb --- /dev/null +++ b/IrrExtensions/gui/scene/GUIScene.h @@ -0,0 +1,101 @@ +// Copyright (C) 2013 Matthias Walden +// This file is currently not part of the "Irrlicht Engine". +// A Gui-Scene for rendering inside a IrrlichtGui... +// This is non-restricted usage code. +// Changes by Nic Anderson: +// > Casting variables in some places +// > Set in namespaces +// > Replaced IrrlichtDevice references by using IGUIEnvironment and ISceneManager +// > Changed constructor function header +// and added rectangle + +#ifndef __GUIScene_H_INCLUDED__ +#define __GUIScene_H_INCLUDED__ + +#include "CamController.h" +#include + + +// namespaces and using by Nic Anderson +namespace irr { + +namespace scene { + class ISceneManager; + class ISceneNode; + class ICameraSceneNode; + class ILightSceneNode; + class IAnimatedMesh; + class IAnimatedMeshSceneNode; +} + +namespace video { + class IVideoDriver; +} + +namespace gui { + +using core::recti; +using scene::CamController; +using video::IVideoDriver; +using scene::ISceneManager; +using scene::ISceneNode; +using scene::ICameraSceneNode; +using scene::ILightSceneNode; +using scene::IAnimatedMesh; +using scene::IAnimatedMeshSceneNode; + +class IGUIWindow; + +//--------------------------------------------------------------------------- +class GUIScene : public IGUIElement +{ + public: + // + GUIScene(IGUIEnvironment* pEnvironment, scene::ISceneManager* pSceneManager, IGUIElement* pParent, recti pRect, s32 id=-1, + video::E_DRIVER_TYPE pDriverType=EDT_NULL /* added by Nic Anderson */ ); + ~GUIScene(void); + + ISceneManager *GetScene(void); + ICameraSceneNode *GetCamera(void); + void SetOptimalCamDistToNode (ISceneNode *aNode); + + IAnimatedMeshSceneNode* setMesh (IAnimatedMesh *aMesh = 0, bool updateOptimalCamDist = true); + IAnimatedMeshSceneNode* setMesh (stringw aFileName, bool updateOptimalCamDist = true); + void enableCamControl(bool aEnable); // Nic Anderson renamed + void setDrawBackground( bool pDrawBack ); // by Nic Anderson + void setDrawBorder( bool pDrawBorder ); // by Nic Anderson + void setExtraCamDistance( f32 pExtraDist ); // by Nic Anderson + + virtual void draw (void); + virtual bool OnEvent (const SEvent &event); + + CamController *getCamCTRL(void){return CamCTRL;}; + + private: + ISceneNode * FocusedNode; + void CalcOptimalCamDistToNode (ISceneNode *aNode); + + IVideoDriver* IDriver; + ISceneManager* IScene; + ICameraSceneNode *Cam; + ILightSceneNode *Light; + + ISceneNode* Center; + IAnimatedMeshSceneNode* MeshNode; + CamController* CamCTRL; + + f32 CurrX; + f32 CurrY; + f32 LastX; + f32 LastY; + + // added by Nic Anderson + video::E_DRIVER_TYPE driverType; + bool drawBack; // Indicates the background should be drawn + bool drawBorder; // Indicates the border should be drawn + f32 ExtraCamDist; +}; + +}} // close namespaces + +#endif // __GUIScene_H_INCLUDED__ diff --git a/IrrExtensions/gui/scene/IGUIScene.h b/IrrExtensions/gui/scene/IGUIScene.h new file mode 100644 index 0000000..0221739 --- /dev/null +++ b/IrrExtensions/gui/scene/IGUIScene.h @@ -0,0 +1,48 @@ +// (c) 2015 Nicolaus Anderson +// Based on code by vitek: +// http://irrlicht.sourceforge.net/forum/viewtopic.php?f=9&t=13220 + +#ifndef I_GUI_SCENE_H +#define I_GUI_SCENE_H + +#include + +namespace irr { + +namespace scene { + class ISceneManager; +} + +namespace gui { + +using scene::ISceneManager; + +class IGUIScene : public IGUIElement +{ +protected: + ISceneManager* sceneManager; + +public: + IGUIScene(IGUIEnvironment* pEnvironment, IGUIElement* pParent, s32 id, core::rect pRect) + //: IGUIElement(EGUIET_MESH_VIEWER, pEnvironment, pParent, id, pRect) + ~IGUIScene(); + + void setSceneManager( ISceneManager* pSceneManager ) + { + sceneManager = pSceneManager; + } + + ISceneManager* getSceneManager() + { + return sceneManager; + } + + virtual void draw(); + + virtual const c8* getTypeName() const { return staticTypeName(); } + static const c8* staticTypeName() const { return "iScene"; } +}; + +}} + +#endif // #ifndef I_GUI_SCENE_H diff --git a/IrrExtensions/gui/slickgui/SlickGUISkin.cpp b/IrrExtensions/gui/slickgui/SlickGUISkin.cpp new file mode 100644 index 0000000..754e9f2 --- /dev/null +++ b/IrrExtensions/gui/slickgui/SlickGUISkin.cpp @@ -0,0 +1,589 @@ +// (c) 2020 Nicolaus Anderson + +#include "SlickGUISkin.h" +#include +#include +#include +#include +#include + +namespace irr { +namespace gui { + +SlickGUISkin::SlickGUISkin( IGUIEnvironment* environment, bool startIconsFromZero, bool drawMoreRound ) + : DrawMoreRound(drawMoreRound) + , Environment(environment) + , VideoDriver(environment->getVideoDriver()) +{ + Colors[EGDC_3D_DARK_SHADOW] = 0xff000000; + Colors[EGDC_3D_SHADOW] = 0xff020202; + Colors[EGDC_3D_FACE] = 0xff363636; + Colors[EGDC_3D_HIGH_LIGHT] = 0xffb1b1b1; + Colors[EGDC_3D_LIGHT] = 0xff6e6e6e; + Colors[EGDC_ACTIVE_BORDER] = 0xffb1b1b1; + Colors[EGDC_ACTIVE_CAPTION] = 0xfffdfdfd; + Colors[EGDC_APP_WORKSPACE] = 0xff111315; + Colors[EGDC_BUTTON_TEXT] = 0xffc5c5c5; //0xffa6a6a6; + Colors[EGDC_GRAY_TEXT] = 0xff6e6e6e; + Colors[EGDC_HIGH_LIGHT] = 0xff000000; + Colors[EGDC_HIGH_LIGHT_TEXT] = 0xfff1f1f1; //0xffc5c5c5; + Colors[EGDC_INACTIVE_BORDER] = 0xff6e6e6e; + Colors[EGDC_INACTIVE_CAPTION] = 0xffc0c0c0; + Colors[EGDC_TOOLTIP] = 0xffffffff; + Colors[EGDC_TOOLTIP_BACKGROUND] = 0xff15587b; + Colors[EGDC_SCROLLBAR] = 0xff1b1f24; + Colors[EGDC_WINDOW] = 0xff1f1f1f; + Colors[EGDC_WINDOW_SYMBOL] = 0xfffcfcfc; + Colors[EGDC_ICON] = 0xffb2bac3; + Colors[EGDC_ICON_HIGH_LIGHT] = 0xfffcfcfc; + Colors[EGDC_GRAY_WINDOW_SYMBOL] = 0xff767b82; + Colors[EGDC_EDITABLE] = 0xff051720; + Colors[EGDC_GRAY_EDITABLE] = 0xff15252c; + Colors[EGDC_FOCUSED_EDITABLE] = 0xff051720; + + InactiveHeaderColor = 0xff475760; + ActiveHeaderColor = 0xff006078; + SpareBackgroundColor = 0xff051720; //0xff273740; // 0xff003344; + SpareBackgroundActiveColor = 0xff15252c; //0xff274848; //0xff275950; + + Sizes[EGDS_SCROLLBAR_SIZE] = 20; + Sizes[EGDS_MENU_HEIGHT] = 40; + Sizes[EGDS_WINDOW_BUTTON_WIDTH] = 20; + Sizes[EGDS_CHECK_BOX_WIDTH] = 10; + Sizes[EGDS_BUTTON_WIDTH] = 100; + //Sizes[EGDS_BUTTON_HEIGHT] = 40; + Sizes[EGDS_BUTTON_HEIGHT] = 30; + Sizes[EGDS_TEXT_DISTANCE_X] = 5; + Sizes[EGDS_TEXT_DISTANCE_Y] = 4; + Sizes[EGDS_TITLEBARTEXT_DISTANCE_X] = 5; + Sizes[EGDS_TITLEBARTEXT_DISTANCE_Y] = 1; + Sizes[EGDS_MESSAGE_BOX_GAP_SPACE] = 17; + Sizes[EGDS_MESSAGE_BOX_MIN_TEXT_WIDTH] = 0; + Sizes[EGDS_MESSAGE_BOX_MAX_TEXT_WIDTH] = 500; + Sizes[EGDS_MESSAGE_BOX_MIN_TEXT_HEIGHT] = 0; + Sizes[EGDS_MESSAGE_BOX_MAX_TEXT_HEIGHT] = 99999; + Sizes[EGDS_BUTTON_PRESSED_IMAGE_OFFSET_X] = 1; + Sizes[EGDS_BUTTON_PRESSED_IMAGE_OFFSET_Y] = 1; + Sizes[EGDS_BUTTON_PRESSED_TEXT_OFFSET_X] = 1; + Sizes[EGDS_BUTTON_PRESSED_TEXT_OFFSET_Y] = 1; + Sizes[EGDS_BUTTON_PRESSED_SPRITE_OFFSET_X] = 1; + Sizes[EGDS_BUTTON_PRESSED_SPRITE_OFFSET_Y] = 1; + + if ( startIconsFromZero ) { + Icons[EGDI_WINDOW_MAXIMIZE] = 0; + Icons[EGDI_WINDOW_RESTORE] = 1; + Icons[EGDI_WINDOW_CLOSE] = 2; + Icons[EGDI_WINDOW_MINIMIZE] = 3; + Icons[EGDI_WINDOW_RESIZE] = 4; + Icons[EGDI_CURSOR_UP] = 5; + Icons[EGDI_CURSOR_DOWN] = 6; + Icons[EGDI_CURSOR_LEFT] = 7; + Icons[EGDI_CURSOR_RIGHT] = 8; + Icons[EGDI_MENU_MORE] = 9; + Icons[EGDI_CHECK_BOX_CHECKED] = 10; + Icons[EGDI_DROP_DOWN] = 11; + Icons[EGDI_SMALL_CURSOR_UP] = 12; + Icons[EGDI_SMALL_CURSOR_DOWN] = 13; + Icons[EGDI_RADIO_BUTTON_CHECKED] = 14; + Icons[EGDI_MORE_LEFT] = 15; + Icons[EGDI_MORE_RIGHT] = 16; + Icons[EGDI_MORE_UP] = 17; + Icons[EGDI_MORE_DOWN] = 18; + Icons[EGDI_EXPAND] = 19; + Icons[EGDI_COLLAPSE] = 20; + Icons[EGDI_FILE] = 21; + Icons[EGDI_DIRECTORY] = 22; + } else { + Icons[EGDI_WINDOW_MAXIMIZE] = 225; + Icons[EGDI_WINDOW_RESTORE] = 226; + Icons[EGDI_WINDOW_CLOSE] = 227; + Icons[EGDI_WINDOW_MINIMIZE] = 228; + Icons[EGDI_CURSOR_UP] = 229; + Icons[EGDI_CURSOR_DOWN] = 230; + Icons[EGDI_CURSOR_LEFT] = 231; + Icons[EGDI_CURSOR_RIGHT] = 232; + Icons[EGDI_MENU_MORE] = 232; + Icons[EGDI_CHECK_BOX_CHECKED] = 233; + Icons[EGDI_DROP_DOWN] = 234; + Icons[EGDI_SMALL_CURSOR_UP] = 235; + Icons[EGDI_SMALL_CURSOR_DOWN] = 236; + Icons[EGDI_RADIO_BUTTON_CHECKED] = 237; + Icons[EGDI_MORE_LEFT] = 238; + Icons[EGDI_MORE_RIGHT] = 239; + Icons[EGDI_MORE_UP] = 240; + Icons[EGDI_MORE_DOWN] = 241; + Icons[EGDI_WINDOW_RESIZE] = 242; + Icons[EGDI_EXPAND] = 243; + Icons[EGDI_COLLAPSE] = 244; + Icons[EGDI_FILE] = 245; + Icons[EGDI_DIRECTORY] = 246; + } + + Sprites = 0; + + for(u32 i=0; idrop(); + } + + if (Sprites) + Sprites->drop(); +} + +video::SColor SlickGUISkin::getColor(EGUI_DEFAULT_COLOR color) const +{ + if ( color < EGDC_COUNT ) + return Colors[color]; + + return video::SColor(); +} + +void SlickGUISkin::setColor(EGUI_DEFAULT_COLOR which, video::SColor newColor) +{ + Colors[which] = newColor; +} + +s32 SlickGUISkin::getSize(EGUI_DEFAULT_SIZE size) const +{ + if (size < EGDS_COUNT) + return Sizes[size]; + + return 0; +} + +void SlickGUISkin::setSize(EGUI_DEFAULT_SIZE which, s32 size) +{ + if (which < EGDS_COUNT) + Sizes[which] = size; +} + +const wchar_t* SlickGUISkin::getDefaultText(EGUI_DEFAULT_TEXT which) const +{ + if (which < EGDT_COUNT) + return Texts[which].c_str(); + return 0; +} + +void SlickGUISkin::setDefaultText(EGUI_DEFAULT_TEXT which, const wchar_t* newText) +{ + if ((u32)which < EGDT_COUNT) + Texts[which] = newText; +} + +IGUIFont* SlickGUISkin::getFont(EGUI_DEFAULT_FONT which) const +{ + IGUIFont* font = Fonts[which]; + if ( !font ) + return Fonts[EGDF_DEFAULT]; + return font; +} + +void SlickGUISkin::setFont(IGUIFont* font, EGUI_DEFAULT_FONT which) +{ + if ((u32)which >= EGDF_COUNT) + return; + + if (font) + { + font->grab(); + if (Fonts[which]) + Fonts[which]->drop(); + + Fonts[which] = font; + } +} + +IGUISpriteBank* SlickGUISkin::getSpriteBank() const +{ + return Sprites; +} + +void SlickGUISkin::setSpriteBank(IGUISpriteBank* bank) +{ + if (Sprites) + Sprites->drop(); + + Sprites = bank; + + if (Sprites) + Sprites->grab(); +} + +u32 SlickGUISkin::getIcon(EGUI_DEFAULT_ICON icon) const +{ + if ((u32)icon < EGDI_COUNT) + return Icons[icon]; + + return 0; +} + +void SlickGUISkin::setIcon(EGUI_DEFAULT_ICON icon, u32 index) +{ + if ((u32)icon < EGDI_COUNT) + Icons[icon] = index; +} + +void SlickGUISkin::draw3DButtonPaneStandard( + IGUIElement* element, + const core::rect& rect, + const core::rect* clip) +{ + core::rect backgroundArea( rect.UpperLeftCorner + core::vector2di(1), + rect.LowerRightCorner - core::vector2di(1) ); + + draw2DRectangle( element, getColor(EGDC_3D_FACE), backgroundArea, clip ); + + core::vector2di shadowRight( rect.LowerRightCorner.X-1, rect.UpperLeftCorner.Y+1 ); + core::vector2di shadowBottom( rect.UpperLeftCorner.X+1, rect.LowerRightCorner.Y-1 ); + draw2DVerticalLine( shadowRight, rect.getHeight()-2, getColor(EGDC_3D_SHADOW), clip ); + draw2DHorizontalLine( shadowBottom, rect.getWidth()-2, getColor(EGDC_3D_SHADOW), clip ); + + const video::SColor regularColor = getColor(EGDC_3D_LIGHT); + const video::SColor leftTopColor = Environment->hasFocus(element) ? + getColor(EGDC_3D_HIGH_LIGHT) : getColor(EGDC_3D_LIGHT); + + drawRoundOutline( rect, clip, leftTopColor, regularColor, leftTopColor, regularColor ); +} + +void SlickGUISkin::draw3DButtonPanePressed( + IGUIElement* element, + const core::rect& rect, + const core::rect* clip) +{ + core::rect backgroundArea( rect.UpperLeftCorner + core::vector2di(1), + rect.LowerRightCorner - core::vector2di(1) ); + + draw2DRectangle( element, getColor(EGDC_WINDOW), backgroundArea, clip ); + + core::vector2di shadowLeft( rect.UpperLeftCorner.X+1, rect.UpperLeftCorner.Y+1 ); + core::vector2di shadowTop( rect.UpperLeftCorner.X+1, rect.UpperLeftCorner.Y+1 ); + draw2DVerticalLine( shadowLeft, rect.getHeight()-2, getColor(EGDC_3D_SHADOW), clip ); + draw2DHorizontalLine( shadowTop, rect.getWidth()-2, getColor(EGDC_3D_SHADOW), clip ); + + const video::SColor regularColor = getColor(EGDC_3D_LIGHT); + drawRoundOutline( rect, clip, regularColor, regularColor, regularColor, regularColor ); +} + +void SlickGUISkin::draw3DSunkenPane( + IGUIElement* element, + video::SColor bgcolor, + bool flat, + bool fillBackGround, + const core::rect& rect, + const core::rect* clip) +{ + core::rect backgroundArea( rect.UpperLeftCorner + core::vector2di(1), + rect.LowerRightCorner - core::vector2di(1) ); + + // Ignore "flat" setting + // Ignore suggested background fill color + if ( fillBackGround ) { + //draw2DRectangle( element, getColor(EGDC_3D_DARK_SHADOW), backgroundArea, clip ); + if ( Environment->getFocus() == element ) + draw2DRectangle( element, SpareBackgroundActiveColor, backgroundArea, clip ); + else { + draw2DRectangle( element, SpareBackgroundColor, backgroundArea, clip ); + } + } + + const video::SColor outerBorderColor = getColor(EGDC_3D_LIGHT); + drawSquareOutline(backgroundArea, clip, getColor(EGDC_3D_SHADOW)); + drawRoundOutline(rect, clip, outerBorderColor, outerBorderColor, outerBorderColor, outerBorderColor); +} + +core::rect SlickGUISkin::draw3DWindowBackground( + IGUIElement* element, + bool drawTitleBar, + video::SColor titleBarColor, + const core::rect& rect, + const core::rect* clip, + core::rect* checkClientArea) +{ + core::rect backgroundArea( rect.UpperLeftCorner + core::vector2di(1), + rect.LowerRightCorner - core::vector2di(1) ); + + core::rect titlebarArea( rect.UpperLeftCorner + core::vector2di(2), + core::vector2di( + rect.LowerRightCorner.X - 2, + rect.UpperLeftCorner.Y + getSize(EGDS_WINDOW_BUTTON_WIDTH) + 5 + )); + + if ( checkClientArea ) { + if ( drawTitleBar ) { + checkClientArea->UpperLeftCorner = + rect.UpperLeftCorner + core::vector2di(2, 2+getSize(EGDS_WINDOW_BUTTON_WIDTH)+5); + } else { + checkClientArea->UpperLeftCorner = rect.UpperLeftCorner + core::vector2di(2); + } + checkClientArea->LowerRightCorner = rect.LowerRightCorner - core::vector2di(2); + return titlebarArea; + } + + // Draw background + draw2DRectangle( element, getColor(EGDC_WINDOW), backgroundArea, clip ); + + // Draw titlebar + draw2DRectangle( element, Environment->hasFocus(element)? + ActiveHeaderColor : InactiveHeaderColor, + titlebarArea, clip ); + + const video::SColor focusColor = Environment->hasFocus(element)? + getColor(EGDC_3D_HIGH_LIGHT) : getColor(EGDC_3D_LIGHT); + drawSquareOutline(backgroundArea, clip, getColor(EGDC_3D_SHADOW)); + drawRoundOutline(rect, clip, focusColor, getColor(EGDC_3D_LIGHT), focusColor, getColor(EGDC_3D_LIGHT)); + + return titlebarArea; +} + +void SlickGUISkin::draw3DMenuPane( + IGUIElement* element, + const core::rect& rect, + const core::rect* clip) +{ + core::rect backgroundArea( rect.UpperLeftCorner + core::vector2di(1), + rect.LowerRightCorner - core::vector2di(1) ); + + draw2DRectangle( element, getColor(EGDC_3D_FACE), backgroundArea, clip ); + //const video::SColor outerBorderColor = getColor(EGDC_3D_LIGHT); + drawSquareOutline(backgroundArea, clip, getColor(EGDC_3D_SHADOW)); + drawSquareOutline(rect, clip, getColor(EGDC_3D_LIGHT)); +} + +void SlickGUISkin::draw3DToolBar( + IGUIElement* element, + const core::rect& rect, + const core::rect* clip) +{ + core::rect backgroundArea( rect.UpperLeftCorner + core::vector2di(1), + rect.LowerRightCorner - core::vector2di(1) ); + + draw2DRectangle( element, getColor(EGDC_3D_FACE), backgroundArea, clip ); + //const video::SColor outerBorderColor = getColor(EGDC_3D_LIGHT); + drawSquareOutline(backgroundArea, clip, getColor(EGDC_3D_SHADOW)); + VideoDriver->draw2DRectangle( getColor(EGDC_3D_LIGHT), rect, clip ); +} + +void SlickGUISkin::draw3DTabButton( + IGUIElement* element, + bool active, + const core::rect& rect, + const core::rect* clip, + gui::EGUI_ALIGNMENT alignment) +{ + core::rect backgroundArea( rect.UpperLeftCorner + core::vector2di(1), + rect.LowerRightCorner - core::vector2di(1) ); + VideoDriver->draw2DRectangle( getColor(EGDC_3D_FACE), backgroundArea, clip ); + + video::SColor shadowColor = getColor(EGDC_3D_SHADOW); + draw2DVerticalLine(backgroundArea.UpperLeftCorner, rect.getHeight()-1, shadowColor, clip); + core::vector2di upperRightCorner( backgroundArea.LowerRightCorner.X, backgroundArea.UpperLeftCorner.Y ); + draw2DVerticalLine(upperRightCorner, rect.getHeight()-1, shadowColor, clip); + + video::SColor borderColor = active ? getColor(EGDC_3D_HIGH_LIGHT) : getColor(EGDC_3D_LIGHT); + core::rect trueRect = rect; + core::vector2di lowerLeftCorner( backgroundArea.UpperLeftCorner.X, backgroundArea.LowerRightCorner.Y ); + if ( alignment == EGUIA_UPPERLEFT ) { + trueRect.LowerRightCorner.Y += 1; + draw2DHorizontalLine(lowerLeftCorner, rect.getWidth() - 2, shadowColor, clip); + drawRoundOutline(trueRect, clip, borderColor, borderColor, borderColor, getColor(EGDC_3D_FACE)); + } else { + trueRect.LowerRightCorner.Y -= 1; + draw2DHorizontalLine(backgroundArea.UpperLeftCorner, rect.getWidth() - 2, shadowColor, clip); + drawRoundOutline(trueRect, clip, borderColor, borderColor, getColor(EGDC_3D_FACE), borderColor); + } +} + +void SlickGUISkin::draw3DTabBody( + IGUIElement* element, + bool border, + bool background, + const core::rect& rect, + const core::rect* clip, + s32 tabHeight, + gui::EGUI_ALIGNMENT alignment ) +{ + core::rect backgroundArea( rect.UpperLeftCorner + core::vector2di(1), + rect.LowerRightCorner - core::vector2di(1) ); + + if ( background ) { + if ( alignment == EGUIA_UPPERLEFT ) { + backgroundArea.UpperLeftCorner.Y += tabHeight+1; + } else { + backgroundArea.LowerRightCorner.Y -= tabHeight+1; + } + VideoDriver->draw2DRectangle(getColor(EGDC_3D_FACE), backgroundArea, clip); + } + core::rect outerArea( backgroundArea.UpperLeftCorner - core::vector2di(1), + backgroundArea.LowerRightCorner + core::vector2di(1) ); + if ( border ) { + drawSquareOutline(backgroundArea, clip, getColor(EGDC_3D_SHADOW)); + drawSquareOutline(outerArea, clip, getColor(EGDC_3D_LIGHT)); + } +} + +void SlickGUISkin::drawIcon( + IGUIElement* element, + EGUI_DEFAULT_ICON icon, + const core::position2di position, + u32 starttime, + u32 currenttime, + bool loop, + const core::rect* clip) +{ + if (!Sprites) + return; + + bool gray = element && !element->isEnabled(); + Sprites->draw2DSprite(Icons[icon], position, clip, + Colors[gray? EGDC_GRAY_WINDOW_SYMBOL : EGDC_WINDOW_SYMBOL], + starttime, currenttime, loop, true); +} + +void SlickGUISkin::draw2DRectangle( + IGUIElement* element, + const video::SColor &color, + const core::rect& pos, + const core::rect* clip) +{ + VideoDriver->draw2DRectangle(color, pos, clip); +} + +void SlickGUISkin::drawPixel( u32 x, u32 y, const video::SColor& color, const core::rect* clip ) +{ + if ( clip ) { + if ( !clip->isPointInside(core::vector2di(x,y)) ) + return; + } + VideoDriver->drawPixel(x,y,color); +} + +void SlickGUISkin::draw2DVerticalLine( core::vector2d start, u32 length, + const video::SColor& color, const core::rect* clip ) +{ + core::vector2d finish( start.X, start.Y + length ); + + if ( clip ) { + if ( clip->getWidth() == 0 || clip->getHeight() == 0 ) + return; + + if ( clip->UpperLeftCorner.X > start.X ) + return; // Clipped horizontally + + if ( clip->UpperLeftCorner.Y > start.Y ) + start.Y = clip->UpperLeftCorner.Y; + if ( clip->LowerRightCorner.Y < finish.Y ) + finish.Y = clip->LowerRightCorner.Y; + + if ( start.Y > finish.Y ) // Maybe should be start.Y >= finish.Y + return; + } + + VideoDriver->draw2DLine(start, finish, color); +} + +void SlickGUISkin::draw2DHorizontalLine( core::vector2d start, u32 length, + const video::SColor& color, const core::rect* clip ) +{ + core::vector2d finish( start.X + length, start.Y ); + + if ( clip ) { + if ( clip->getWidth() == 0 || clip->getHeight() == 0 ) + return; + + if ( clip->UpperLeftCorner.Y > start.Y ) + return; // Clipped vertically + + if ( clip->UpperLeftCorner.X > start.X ) + start.X = clip->UpperLeftCorner.X; + if ( clip->LowerRightCorner.X < finish.X ) + finish.X = clip->LowerRightCorner.X; + + if ( start.X > finish.X ) // Maybe should be start.X >= finish.X + return; + } + + VideoDriver->draw2DLine(start, finish, color); +} + +void SlickGUISkin::drawRoundOutline( + const core::rect& rect, const core::rect* clip, + const video::SColor& colorLeft, const video::SColor& colorRight, + const video::SColor& colorTop, const video::SColor& colorBottom + ) +{ + const s32 left = rect.UpperLeftCorner.X; + const s32 right = rect.LowerRightCorner.X; + const s32 top = rect.UpperLeftCorner.Y; + const s32 bottom = rect.LowerRightCorner.Y; + + if ( DrawMoreRound ) { + draw2DVerticalLine( core::vector2di(left, top+2), bottom-top-3, colorLeft ); + draw2DHorizontalLine( core::vector2di(left+2, top), right-left-3, colorTop ); + draw2DVerticalLine( core::vector2di(right, top+2), bottom-top-3, colorRight ); + draw2DHorizontalLine( core::vector2di(left+2, bottom), right-left-3, colorBottom ); + + drawPixel( left+1, top+1, colorTop, clip ); + drawPixel( right-1, top+1, colorTop, clip ); + drawPixel( left+1, bottom-1, colorBottom, clip ); + drawPixel( right-1, bottom-1, colorBottom, clip ); + } else { + draw2DVerticalLine( core::vector2di(left, top+1), bottom-top-1, colorLeft ); + draw2DHorizontalLine( core::vector2di(left+1, top), right-left-1, colorTop ); + draw2DVerticalLine( core::vector2di(right, top+1), bottom-top-1, colorRight ); + draw2DHorizontalLine( core::vector2di(left+1, bottom), right-left-1, colorBottom ); + } +} + +/* +void SlickGUISkin::drawSquareOutline( + const core::rect& rect, const core::rect* clip, + const video::SColor& colorLeft, const video::SColor& colorRight, + const video::SColor& colorTop, const video::SColor& colorBottom + ) +{ + const s32 left = pos.UpperLeftCorner.X; + const s32 right = pos.LowerRightCorner.X; + const s32 top = pos.UpperLeftCorner.Y; + const s32 bottom = pos.LowerRightCorner.Y; + + draw2DVerticalLine( vector2di(left, top+1), bottom-top, colorLeft, clip ); + draw2DHorizontalLine( vector2di(left, top), right-left, colorTop, clip); + draw2DVerticalLine( vector2di(right, top+1), bottom-top, colorRight, clip ); + draw2DHorizontalLine( vector2di(left, bottom), right-left, colorBottom, clip); +} +*/ + +void SlickGUISkin::drawSquareOutline( + const core::rect& rect, const core::rect* clip, + const video::SColor& color ) +{ + const s32 left = rect.UpperLeftCorner.X; + const s32 right = rect.LowerRightCorner.X; + const s32 top = rect.UpperLeftCorner.Y; + const s32 bottom = rect.LowerRightCorner.Y; + + draw2DVerticalLine( core::vector2di(left, top+1), bottom-top, color, clip ); + draw2DHorizontalLine( core::vector2di(left, top), right-left, color, clip); + draw2DVerticalLine( core::vector2di(right, top+1), bottom-top, color, clip ); + draw2DHorizontalLine( core::vector2di(left, bottom), right-left, color, clip); +} + +} // namespace gui +} // namespace irr diff --git a/IrrExtensions/gui/slickgui/SlickGUISkin.h b/IrrExtensions/gui/slickgui/SlickGUISkin.h new file mode 100644 index 0000000..05e1b6b --- /dev/null +++ b/IrrExtensions/gui/slickgui/SlickGUISkin.h @@ -0,0 +1,146 @@ +// (C) 2020 Nicolaus Anderson + +#ifndef IRR_SLICK_GUI_SKIN +#define IRR_SLICK_GUI_SKIN + +#include +#include + +namespace irr { + +namespace video { + class IVideoDriver; +} + +namespace gui { + +class IGUISpriteBank; +class IGUIEnvironment; + + +class SlickGUISkin : public IGUISkin +{ + bool DrawMoreRound; + IGUIEnvironment* Environment; + irr::video::IVideoDriver* VideoDriver; + video::SColor Colors[EGDC_COUNT]; + video::SColor InactiveHeaderColor; + video::SColor ActiveHeaderColor; + video::SColor SpareBackgroundColor; + video::SColor SpareBackgroundActiveColor; + s32 Sizes[EGDS_COUNT]; + u32 Icons[EGDI_COUNT]; + IGUIFont* Fonts[EGDF_COUNT]; + IGUISpriteBank* Sprites; + core::stringw Texts[EGDT_COUNT]; + +public: + /* startIconsFromZero is for when you create your own sprite bank that only holds sprites + for this skin rather than using Irrlicht's default sprite bank. */ + SlickGUISkin( IGUIEnvironment*, bool startIconsFromZero, bool drawMoreRound ); + ~SlickGUISkin(); + + virtual video::SColor getColor(EGUI_DEFAULT_COLOR color) const override; + + virtual void setColor(EGUI_DEFAULT_COLOR which, video::SColor newColor) override; + + virtual s32 getSize(EGUI_DEFAULT_SIZE size) const override; + + virtual void setSize(EGUI_DEFAULT_SIZE which, s32 size) override; + + virtual const wchar_t* getDefaultText(EGUI_DEFAULT_TEXT which) const override; + + virtual void setDefaultText(EGUI_DEFAULT_TEXT which, const wchar_t* newText) override; + + virtual IGUIFont* getFont(EGUI_DEFAULT_FONT which=EGDF_DEFAULT) const override; + + virtual void setFont(IGUIFont* font, EGUI_DEFAULT_FONT which=EGDF_DEFAULT) override; + + virtual IGUISpriteBank* getSpriteBank() const override; + + virtual void setSpriteBank(IGUISpriteBank* bank) override; + + virtual u32 getIcon(EGUI_DEFAULT_ICON icon) const override; + + virtual void setIcon(EGUI_DEFAULT_ICON icon, u32 index) override; + + virtual void draw3DButtonPaneStandard(IGUIElement* element, + const core::rect& rect, + const core::rect* clip=0) override; + + virtual void draw3DButtonPanePressed(IGUIElement* element, + const core::rect& rect, + const core::rect* clip=0) override; + + virtual void draw3DSunkenPane(IGUIElement* element, + video::SColor bgcolor, bool flat, bool fillBackGround, + const core::rect& rect, + const core::rect* clip=0) override; + + virtual core::rect draw3DWindowBackground(IGUIElement* element, + bool drawTitleBar, video::SColor titleBarColor, + const core::rect& rect, + const core::rect* clip=0, + core::rect* checkClientArea=0) override; + + virtual void draw3DMenuPane(IGUIElement* element, + const core::rect& rect, + const core::rect* clip=0) override; + + virtual void draw3DToolBar(IGUIElement* element, + const core::rect& rect, + const core::rect* clip=0) override; + + virtual void draw3DTabButton(IGUIElement* element, bool active, + const core::rect& rect, const core::rect* clip=0, gui::EGUI_ALIGNMENT alignment=EGUIA_UPPERLEFT) override; + + virtual void draw3DTabBody(IGUIElement* element, bool border, bool background, + const core::rect& rect, const core::rect* clip=0, s32 tabHeight=-1, gui::EGUI_ALIGNMENT alignment=EGUIA_UPPERLEFT ) override; + + virtual void drawIcon(IGUIElement* element, EGUI_DEFAULT_ICON icon, + const core::position2di position, u32 starttime=0, u32 currenttime=0, + bool loop=false, const core::rect* clip=0) override; + + virtual void draw2DRectangle(IGUIElement* element, const video::SColor &color, + const core::rect& pos, const core::rect* clip = 0) override; + + //! get the type of this skin + virtual EGUI_SKIN_TYPE getType() const { return EGST_UNKNOWN; } + + //! Draw a pixel clipped + void drawPixel( u32 x, u32 y, const video::SColor&, const core::rect* clip=0 ); + + //! Draw a line clipped + void draw2DVerticalLine( core::vector2d start, u32 length, + const video::SColor&, const core::rect* clip=0 ); + + void draw2DHorizontalLine( core::vector2d start, u32 length, + const video::SColor&, const core::rect* clip=0 ); + + //! Draw a rectangle outline with rounded edges + void drawRoundOutline( + const core::rect& rect, const core::rect* clip, + const video::SColor& colorLeft, const video::SColor& colorRight, + const video::SColor& colorTop, const video::SColor& colorBottom + ); + + //! Draw a rectangle outline + /* NOTE: IVideoDriver has alternative ways of drawing 2D rectangles */ + //void drawSquareOutline( + // const core::rect& rect, const core::rect* clip, + // const video::SColor& colorLeft, const video::SColor& colorRight, + // const video::SColor& colorTop, const video::SColor& colorBottom + //); + + //! Draw a rectangle outline + /* NOTE: IVideoDriver has alternative ways of drawing 2D rectangles */ + void drawSquareOutline( + const core::rect& rect, const core::rect* clip, + const video::SColor& color + ); +}; + +} // namespace gui +} // namespace irr + +#endif // IRR_SLICK_GUI_SKIN diff --git a/IrrExtensions/gui/slickgui/sample_screenshot.png b/IrrExtensions/gui/slickgui/sample_screenshot.png new file mode 100755 index 0000000..8f9cfb5 Binary files /dev/null and b/IrrExtensions/gui/slickgui/sample_screenshot.png differ diff --git a/IrrExtensions/gui/slickgui/samples.png b/IrrExtensions/gui/slickgui/samples.png new file mode 100755 index 0000000..9b033f7 Binary files /dev/null and b/IrrExtensions/gui/slickgui/samples.png differ diff --git a/IrrExtensions/gui/slickgui/samples.xcf b/IrrExtensions/gui/slickgui/samples.xcf new file mode 100755 index 0000000..ee5d147 Binary files /dev/null and b/IrrExtensions/gui/slickgui/samples.xcf differ diff --git a/IrrExtensions/gui/slickgui/sprites/check_box_checked.png b/IrrExtensions/gui/slickgui/sprites/check_box_checked.png new file mode 100755 index 0000000..fa14492 Binary files /dev/null and b/IrrExtensions/gui/slickgui/sprites/check_box_checked.png differ diff --git a/IrrExtensions/gui/slickgui/sprites/check_box_checked.svg b/IrrExtensions/gui/slickgui/sprites/check_box_checked.svg new file mode 100755 index 0000000..c311579 --- /dev/null +++ b/IrrExtensions/gui/slickgui/sprites/check_box_checked.svg @@ -0,0 +1,67 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + diff --git a/IrrExtensions/gui/slickgui/sprites/collapse.png b/IrrExtensions/gui/slickgui/sprites/collapse.png new file mode 100755 index 0000000..10e11f2 Binary files /dev/null and b/IrrExtensions/gui/slickgui/sprites/collapse.png differ diff --git a/IrrExtensions/gui/slickgui/sprites/cursor_down.png b/IrrExtensions/gui/slickgui/sprites/cursor_down.png new file mode 100755 index 0000000..e174636 Binary files /dev/null and b/IrrExtensions/gui/slickgui/sprites/cursor_down.png differ diff --git a/IrrExtensions/gui/slickgui/sprites/cursor_left.png b/IrrExtensions/gui/slickgui/sprites/cursor_left.png new file mode 100755 index 0000000..e35c91e Binary files /dev/null and b/IrrExtensions/gui/slickgui/sprites/cursor_left.png differ diff --git a/IrrExtensions/gui/slickgui/sprites/cursor_right.png b/IrrExtensions/gui/slickgui/sprites/cursor_right.png new file mode 100755 index 0000000..44d000e Binary files /dev/null and b/IrrExtensions/gui/slickgui/sprites/cursor_right.png differ diff --git a/IrrExtensions/gui/slickgui/sprites/cursor_up.png b/IrrExtensions/gui/slickgui/sprites/cursor_up.png new file mode 100755 index 0000000..44b6b7b Binary files /dev/null and b/IrrExtensions/gui/slickgui/sprites/cursor_up.png differ diff --git a/IrrExtensions/gui/slickgui/sprites/cursor_up.svg b/IrrExtensions/gui/slickgui/sprites/cursor_up.svg new file mode 100755 index 0000000..e9e4239 --- /dev/null +++ b/IrrExtensions/gui/slickgui/sprites/cursor_up.svg @@ -0,0 +1,75 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + + diff --git a/IrrExtensions/gui/slickgui/sprites/directory.png b/IrrExtensions/gui/slickgui/sprites/directory.png new file mode 100755 index 0000000..451a6da Binary files /dev/null and b/IrrExtensions/gui/slickgui/sprites/directory.png differ diff --git a/IrrExtensions/gui/slickgui/sprites/drop_down.png b/IrrExtensions/gui/slickgui/sprites/drop_down.png new file mode 100755 index 0000000..f00863b Binary files /dev/null and b/IrrExtensions/gui/slickgui/sprites/drop_down.png differ diff --git a/IrrExtensions/gui/slickgui/sprites/drop_down.svg b/IrrExtensions/gui/slickgui/sprites/drop_down.svg new file mode 100755 index 0000000..f7f35bb --- /dev/null +++ b/IrrExtensions/gui/slickgui/sprites/drop_down.svg @@ -0,0 +1,67 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + diff --git a/IrrExtensions/gui/slickgui/sprites/expand.png b/IrrExtensions/gui/slickgui/sprites/expand.png new file mode 100755 index 0000000..d2196a6 Binary files /dev/null and b/IrrExtensions/gui/slickgui/sprites/expand.png differ diff --git a/IrrExtensions/gui/slickgui/sprites/expand.svg b/IrrExtensions/gui/slickgui/sprites/expand.svg new file mode 100755 index 0000000..fd05c11 --- /dev/null +++ b/IrrExtensions/gui/slickgui/sprites/expand.svg @@ -0,0 +1,75 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + + + + diff --git a/IrrExtensions/gui/slickgui/sprites/file.png b/IrrExtensions/gui/slickgui/sprites/file.png new file mode 100755 index 0000000..b72da6f Binary files /dev/null and b/IrrExtensions/gui/slickgui/sprites/file.png differ diff --git a/IrrExtensions/gui/slickgui/sprites/menu_more.png b/IrrExtensions/gui/slickgui/sprites/menu_more.png new file mode 100755 index 0000000..58a2004 Binary files /dev/null and b/IrrExtensions/gui/slickgui/sprites/menu_more.png differ diff --git a/IrrExtensions/gui/slickgui/sprites/menu_more.svg b/IrrExtensions/gui/slickgui/sprites/menu_more.svg new file mode 100755 index 0000000..a8ebe30 --- /dev/null +++ b/IrrExtensions/gui/slickgui/sprites/menu_more.svg @@ -0,0 +1,116 @@ + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + diff --git a/IrrExtensions/gui/slickgui/sprites/mini_button.svg b/IrrExtensions/gui/slickgui/sprites/mini_button.svg new file mode 100755 index 0000000..c17c732 --- /dev/null +++ b/IrrExtensions/gui/slickgui/sprites/mini_button.svg @@ -0,0 +1,251 @@ + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/IrrExtensions/gui/slickgui/sprites/more_down.png b/IrrExtensions/gui/slickgui/sprites/more_down.png new file mode 100755 index 0000000..eaf5c0d Binary files /dev/null and b/IrrExtensions/gui/slickgui/sprites/more_down.png differ diff --git a/IrrExtensions/gui/slickgui/sprites/more_left.png b/IrrExtensions/gui/slickgui/sprites/more_left.png new file mode 100755 index 0000000..f8a3e9f Binary files /dev/null and b/IrrExtensions/gui/slickgui/sprites/more_left.png differ diff --git a/IrrExtensions/gui/slickgui/sprites/more_right.png b/IrrExtensions/gui/slickgui/sprites/more_right.png new file mode 100755 index 0000000..96b6d18 Binary files /dev/null and b/IrrExtensions/gui/slickgui/sprites/more_right.png differ diff --git a/IrrExtensions/gui/slickgui/sprites/more_up.png b/IrrExtensions/gui/slickgui/sprites/more_up.png new file mode 100755 index 0000000..390ca15 Binary files /dev/null and b/IrrExtensions/gui/slickgui/sprites/more_up.png differ diff --git a/IrrExtensions/gui/slickgui/sprites/more_up.svg b/IrrExtensions/gui/slickgui/sprites/more_up.svg new file mode 100755 index 0000000..712c75d --- /dev/null +++ b/IrrExtensions/gui/slickgui/sprites/more_up.svg @@ -0,0 +1,77 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + + + + diff --git a/IrrExtensions/gui/slickgui/sprites/radio_button_checked.png b/IrrExtensions/gui/slickgui/sprites/radio_button_checked.png new file mode 100755 index 0000000..8d1e4f7 Binary files /dev/null and b/IrrExtensions/gui/slickgui/sprites/radio_button_checked.png differ diff --git a/IrrExtensions/gui/slickgui/sprites/radio_button_checked.svg b/IrrExtensions/gui/slickgui/sprites/radio_button_checked.svg new file mode 100755 index 0000000..17be1fe --- /dev/null +++ b/IrrExtensions/gui/slickgui/sprites/radio_button_checked.svg @@ -0,0 +1,74 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + + diff --git a/IrrExtensions/gui/slickgui/sprites/small_cursor_down.png b/IrrExtensions/gui/slickgui/sprites/small_cursor_down.png new file mode 100755 index 0000000..76c74c7 Binary files /dev/null and b/IrrExtensions/gui/slickgui/sprites/small_cursor_down.png differ diff --git a/IrrExtensions/gui/slickgui/sprites/small_cursor_up.png b/IrrExtensions/gui/slickgui/sprites/small_cursor_up.png new file mode 100755 index 0000000..f6f2318 Binary files /dev/null and b/IrrExtensions/gui/slickgui/sprites/small_cursor_up.png differ diff --git a/IrrExtensions/gui/slickgui/sprites/small_cursor_up.svg b/IrrExtensions/gui/slickgui/sprites/small_cursor_up.svg new file mode 100755 index 0000000..7e63d23 --- /dev/null +++ b/IrrExtensions/gui/slickgui/sprites/small_cursor_up.svg @@ -0,0 +1,67 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + diff --git a/IrrExtensions/gui/slickgui/sprites/window_close.png b/IrrExtensions/gui/slickgui/sprites/window_close.png new file mode 100755 index 0000000..c65b0cb Binary files /dev/null and b/IrrExtensions/gui/slickgui/sprites/window_close.png differ diff --git a/IrrExtensions/gui/slickgui/sprites/window_maximize.png b/IrrExtensions/gui/slickgui/sprites/window_maximize.png new file mode 100755 index 0000000..7adf922 Binary files /dev/null and b/IrrExtensions/gui/slickgui/sprites/window_maximize.png differ diff --git a/IrrExtensions/gui/slickgui/sprites/window_minimize.png b/IrrExtensions/gui/slickgui/sprites/window_minimize.png new file mode 100755 index 0000000..6517aac Binary files /dev/null and b/IrrExtensions/gui/slickgui/sprites/window_minimize.png differ diff --git a/IrrExtensions/gui/slickgui/sprites/window_resize.png b/IrrExtensions/gui/slickgui/sprites/window_resize.png new file mode 100755 index 0000000..67a647e Binary files /dev/null and b/IrrExtensions/gui/slickgui/sprites/window_resize.png differ diff --git a/IrrExtensions/gui/slickgui/sprites/window_restore.png b/IrrExtensions/gui/slickgui/sprites/window_restore.png new file mode 100755 index 0000000..c92a015 Binary files /dev/null and b/IrrExtensions/gui/slickgui/sprites/window_restore.png differ diff --git a/IrrExtensions/math/ComplexNumber.h b/IrrExtensions/math/ComplexNumber.h new file mode 100755 index 0000000..fcbf3bd --- /dev/null +++ b/IrrExtensions/math/ComplexNumber.h @@ -0,0 +1,170 @@ +// Copyright 2014-2016 Nicolaus Anderson + +template +struct ComplexNumber +{ + T real; + T imag; // imaginary component + + + ComplexNumber() + : real( 0 ) + , imag( 0 ) + {} + + ComplexNumber( const ComplexNumber& other ) + : real( other.real ) + , imag( other.imag ) + {} + + ComplexNumber( T realT, T imagT ) + : real( realT ) + , imag( imagT ) + {} + + void set( T realT, T imagT ) + { + real = realT; + imag = imagT; + } + + ComplexNumber& operator = ( const ComplexNumber& other ) + { + real = other.real; + imag = other.imag; + return *this; + } + + ComplexNumber& operator = ( T& value ) + { + real = value; + imag = T(0); + return *this; + } + + bool operator == ( ComplexNumber& other ) + { + return ( real == other.real && imag == other.imag ); + } + + bool operator == ( T& value ) + { + return ( real == value && imag == T(0) ); + } + + // Basic operators for other == ComplexNumber + + ComplexNumber& operator += ( ComplexNumber& other ) + { + real += other.real; + imag += other.imag; + return *this; + } + + ComplexNumber operator + ( ComplexNumber& other ) + { + ComplexNumber temp(*this); + return (temp += other); + } + + ComplexNumber& operator -= ( ComplexNumber& other ) + { + real -= other.real; + imag -= other.imag; + return *this; + } + + ComplexNumber operator - ( ComplexNumber& other ) + { + ComplexNumber temp(*this); + return (temp -= other); + } + + ComplexNumber& operator *= ( ComplexNumber& other ) + { + T tempReal = real; + real = tempReal * other.real - imag * other.imag; + imag = tempReal * other.imag + imag * other.real; + return *this; + } + + ComplexNumber operator * ( ComplexNumber& other ) + { + ComplexNumber temp(*this); + return (temp *= other); + } + + ComplexNumber& operator /= ( ComplexNumber& other ) + { + /* Technically what's happening... (Multiply top and bottom by the complex conjugate of the divisor)*/ + // ComplexNumber cconj = other; // complex conjugate + // cconj.imag *= -1; + // ComplexNumber oc = other*cconj; // eliminate imag from divisor + // real *= cconj.real / oc.real; + // imag *= cconj.imag / oc.real; + + // Shorthand + T tReal = other.real * other.real + other.imag * other.imag; + real *= other.real / tReal; + imag *= -other.imag / tReal; + return *this; + } + + ComplexNumber operator / ( ComplexNumber& other ) + { + ComplexNumber temp(*this); + return (temp /= other); + } + + // Basic operators for other == T + + ComplexNumber& operator += ( T& other ) + { + real += other; + return *this; + } + + ComplexNumber operator + ( T& other ) + { + ComplexNumber temp(*this); + return (temp += other); + } + + ComplexNumber& operator -= ( T& other ) + { + real -= other; + return *this; + } + + ComplexNumber operator - ( T& other ) + { + ComplexNumber temp(*this); + return (temp -= other); + } + + ComplexNumber& operator *= ( T& other ) + { + real *= other; + imag *= other; + return *this; + } + + ComplexNumber operator * ( T& other ) + { + ComplexNumber temp(*this); + return (temp *= other); + } + + ComplexNumber& operator /= ( T& other ) + { + real /= other; + imag /= other; + return *this; + } + + ComplexNumber operator / ( T& other ) + { + ComplexNumber temp(*this); + return (temp /= other); + } +}; diff --git a/IrrExtensions/math/Fraction.h b/IrrExtensions/math/Fraction.h new file mode 100755 index 0000000..029285f --- /dev/null +++ b/IrrExtensions/math/Fraction.h @@ -0,0 +1,134 @@ +// Copyright 2016-2018 Nicolaus Anderson + +namespace math { + +class Fraction +{ + //const char INFINITY = 0x01; + //char flags; + int num; // Numerator + int denom; // Denominator + +public: + Fraction() : num(0), denom(1) {} + + Fraction(int pNumerator, int pDenominator) : /*flags(0),*/ num(pNumerator), denom(pDenominator) + { + //flags &= (!denom & INFINITY); // Set infinity bit if denominator is zero + } + + Fraction(const Fraction& p) + { + *this = p; + } + + Fraction& operator = (const Fraction& p) + { + num = p.num; + denom = p.denom; + } + + int numerator() const { return num; } + + void numerator( int value ) { num = value; } + + int denominator() const { return denom; } + + void denominator( int value ) { denom = value; } + + void reduce() + { + if ( num < 0 && denom < 0 ) + { + num = -num; + denom = -denom; + } + if ( denom != 0 ) { + const int factor = commonFactor(num,denom); + num /= factor; + denom /= factor; + } else { + if ( num != 0 ) + num = 1; + } + } + + Fraction& operator += (const Fraction& p) + { + if ( denom == p.denom ) + { + num += p.num; + return *this; + } + num = (num * p.denom) + (p.num * denom); + denom *= p.denom; + reduce(); + return *this; + } + + Fraction& operator + (const Fraction& p) + { + return (Fraction(*this) += p); + } + + Fraction& operator -= (const Fraction& p) + { + return (*this += Fraction( -p.num, p.denom )); + } + + Fraction& operator - (const Fraction& p) + { + return (Fraction(*this) -= p); + } + + Fraction& operator *= (const Fraction& p) + { + num *= p.num; + denom *= p.denom; + //flags |= p.flags & INFINITY; // Faster? Better? + //flags &= (!denom & INFINITY); + reduce(); + return *this; + } + + Fraction& operator * (const Fraction& p) + { + return (Fraction(*this) -= p); + } + + Fraction& operator /= (const Fraction& p) + { + num *= p.denom; + denom *= p.num; + //flags &= (!denom & INFINITY); + reduce(); + return *this; + } + + Fraction& operator / (const Fraction& p) + { + return (Fraction(*this) /= p); + } + +protected: + int commonFactor(int p, int q) + { + int big, small; + if ( p >= q ) { + big = p; + small = q; + } else { + big = q; + small = p; + } + int mod_bg = big % small; + if ( mod_bg == 1 ) + return 1; + if ( mod_bg == 0 ) + return small; + return commonFactor(small,mod_bg); + } +}; + + +} diff --git a/IrrExtensions/math/SpaceMatrix.h b/IrrExtensions/math/SpaceMatrix.h new file mode 100755 index 0000000..c6c3def --- /dev/null +++ b/IrrExtensions/math/SpaceMatrix.h @@ -0,0 +1,378 @@ +// Copyright 2014-2016 Nicolaus Anderson + +#ifndef SPACEMATRIX_H +#define SPACEMATRIX_H + +#include +#include +#include +#include + +using irr::f32; +using irr::core::abs_; +using irr::core::radToDeg; +using irr::core::degToRad; +using irr::core::vector3df; +using irr::core::matrix4; +using irr::core::quaternion; + +class SpaceMatrix +{ + vector3df translation; + vector3df rotation; + vector3df scale; + bool dirty; + + // Rotation matrix + f32 RM[3][3]; + +public: + + SpaceMatrix() + : translation(0.f,0.f,0.f) + , rotation(0.f,0.f,0.f) + , scale(1.f,1.f,1.f) + , dirty(false) + { + makeRotationIdentity(); + } + + SpaceMatrix( const SpaceMatrix& pOther ) + : translation( pOther.translation ) + , rotation( pOther.rotation ) // radians + , scale( pOther.scale ) + , dirty( pOther.dirty ) + { + if ( ! dirty ) + { + RM[0][0] = pOther.RM[0][0]; + RM[0][1] = pOther.RM[0][1]; + RM[0][2] = pOther.RM[0][2]; + RM[1][0] = pOther.RM[1][0]; + RM[1][1] = pOther.RM[1][1]; + RM[1][2] = pOther.RM[1][2]; + RM[2][0] = pOther.RM[2][0]; + RM[2][1] = pOther.RM[2][1]; + RM[2][2] = pOther.RM[2][2]; + } + } + + SpaceMatrix( const vector3df pTranslation, const vector3df pRotation, const vector3df pScale ) + : translation( pTranslation ) + , rotation( pRotation ) + , scale( pScale ) + , dirty(true) + { + makeRotationIdentity(); + } + + SpaceMatrix& operator= ( const SpaceMatrix& pOther ) + { + translation = pOther.translation; + rotation = pOther.rotation; + scale = pOther.scale; + dirty = pOther.dirty; + if ( ! dirty ) + { + RM[0][0] = pOther.RM[0][0]; + RM[0][1] = pOther.RM[0][1]; + RM[0][2] = pOther.RM[0][2]; + RM[1][0] = pOther.RM[1][0]; + RM[1][1] = pOther.RM[1][1]; + RM[1][2] = pOther.RM[1][2]; + RM[2][0] = pOther.RM[2][0]; + RM[2][1] = pOther.RM[2][1]; + RM[2][2] = pOther.RM[2][2]; + } + return *this; + } + + SpaceMatrix& operator+= ( const SpaceMatrix& pOther ) + { + translation += pOther.translation; + rotation += pOther.rotation; + scale *= pOther.scale; + dirty = true; + return *this; + } + + SpaceMatrix& operator+ ( const SpaceMatrix& pOther ) + { + return SpaceMatrix(*this) += pOther; + } + + SpaceMatrix& operator-= ( const SpaceMatrix& pOther ) + { + translation -= pOther.translation; + rotation -= pOther.rotation; + scale /= pOther.scale; // Causes failure if any value of pOther.scale is zero + dirty = true; + return *this; + } + + SpaceMatrix& operator- ( const SpaceMatrix& pOther ) + { + return SpaceMatrix(*this) -= pOther; + } + + SpaceMatrix& operator+= ( const matrix4 pMatrix ) + { + translation += pMatrix.getTranslation(); + setRotationDegrees( getRotationDegrees() + pMatrix.getRotationDegrees() ); + scale *= pMatrix.getScale(); + dirty = true; + return *this; + } + + SpaceMatrix operator+ ( const matrix4 pMatrix ) + { + return SpaceMatrix(*this) += pMatrix; + } + + void reset() + { + translation.set(0.f,0.f,0.f); + rotation.set(0.f,0.f,0.f); + scale.set(1.f,1.f,1.f); + makeRotationIdentity(); + dirty = false; + } + + void makeRotationIdentity() + { + RM[0][0] = 1; RM[0][1] = 0; RM[0][2] = 0; + RM[1][0] = 0; RM[1][1] = 1; RM[1][2] = 0; + RM[2][0] = 0; RM[2][1] = 0; RM[2][2] = 1; + } + + void setTranslation( vector3df pTranslation ) + { + translation = pTranslation; + } + + void setRotationRadians( vector3df pRotation ) + { + rotation = pRotation; + dirty = true; + } + + void setRotationDegrees( vector3df pRotation ) + { + rotation.X = degToRad( pRotation.X ); + rotation.Y = degToRad( pRotation.Y ); + rotation.Z = degToRad( pRotation.Z ); + dirty = true; + } + + void setScale( vector3df pScale ) + { + scale = pScale; +#ifdef PREVENT_NEGATIVE_MATRIX_SCALE + // Prevent scale sizes < 0 + scale.X = abs_( scale.X ); + scale.Y = abs_( scale.Y ); + scale.Z = abs_( scale.Z ); +#endif + } + + void addTranslation( vector3df pTranslation ) + { + translation += pTranslation; + } + + void addRotationRadians( vector3df pRotationRadians ) + { + rotation += pRotationRadians; + dirty = true; + } + + void amplifyScale( vector3df pScale ) + { + scale *= pScale; +#ifdef PREVENT_NEGATIVE_MATRIX_SCALE + // Prevent scale sizes < 0 + scale.X = abs_( scale.X ); + scale.Y = abs_( scale.Y ); + scale.Z = abs_( scale.Z ); +#endif + } + + vector3df getTranslation() const + { + return translation; + } + + vector3df getRotationRadians() const + { + return rotation; + } + + vector3df getRotationDegrees() + { + return vector3df( radToDeg(rotation.X), radToDeg(rotation.Y), radToDeg(rotation.Z) ); + } + + vector3df getScale() const + { + return scale; + } + + SpaceMatrix getInverse() + { + return SpaceMatrix( - translation, - rotation, vector3df(1) / scale ); + } + + SpaceMatrix& invert() + { + translation *= -1; + rotation *= -1; + scale = vector3df(1) / scale; + dirty = true; + return *this; + } + + // Needed function for MeshUnit::applyLocalizedSpaceTransform() + SpaceMatrix& localizeWith( const SpaceMatrix& pMatrix ) + { + pMatrix.constScaleRotateVect( translation ); + return *this; + } + + void scaleTranslation( const vector3df& vec ) + { + translation *= vec; + } + + void translateVect( vector3df& vec ) const + { + vec += translation; + } + + inline void rotateVect( vector3df& vec ) + { + if ( dirty ) + calcRotation(); + vector3df v = vec; + vec.X = v.X*RM[0][0] + v.Y*RM[0][1] + v.Z*RM[0][2]; + vec.Y = v.X*RM[1][0] + v.Y*RM[1][1] + v.Z*RM[1][2]; + vec.Z = v.X*RM[2][0] + v.Y*RM[2][1] + v.Z*RM[2][2]; + } + + inline void inverseRotateVect( vector3df& vec ) + { + if ( dirty ) + calcRotation(); + vector3df v = vec; + vec.X = v.X*RM[0][0] + v.Y*RM[1][0] + v.Z*RM[2][0]; + vec.Y = v.X*RM[0][1] + v.Y*RM[1][1] + v.Z*RM[2][1]; + vec.Z = v.X*RM[0][2] + v.Y*RM[1][2] + v.Z*RM[2][2]; + } + + // Warning: Must call calcRotation() in non-const zone for this to be correct + void constRotateVect( vector3df& vec ) const + { + vector3df v = vec; + vec.X = v.X*RM[0][0] + v.Y*RM[0][1] + v.Z*RM[0][2]; + vec.Y = v.X*RM[1][0] + v.Y*RM[1][1] + v.Z*RM[1][2]; + vec.Z = v.X*RM[2][0] + v.Y*RM[2][1] + v.Z*RM[2][2]; + } + + // Warning: Must call calcRotation() in non-const zone for this to be correct + void constInverseRotateVect( vector3df& vec ) const + { + vector3df v = vec; + vec.X = v.X*RM[0][0] + v.Y*RM[1][0] + v.Z*RM[2][0]; + vec.Y = v.X*RM[0][1] + v.Y*RM[1][1] + v.Z*RM[2][1]; + vec.Z = v.X*RM[0][2] + v.Y*RM[1][2] + v.Z*RM[2][2]; + } + + inline void scaleVect( vector3df& vec ) const + { + vec *= scale; + } + + + void scaleRotateVect( vector3df& vec ) + { + vec *= scale; + rotateVect(vec); + } + + void inverseScaleRotateVect( vector3df& vec ) + { + inverseRotateVect(vec); + vec /= scale; + } + + // Warning: Must call calcRotation() in non-const zone for this to be correct + void constScaleRotateVect( vector3df& vec ) const + { + vec *= scale; + constRotateVect(vec); + } + + // Warning: Must call calcRotation() in non-const zone for this to be correct + void constInverseScaleRotateVect( vector3df& vec ) const + { + constInverseRotateVect(vec); + vec /= scale; + } + + void transformVect( vector3df& vec ) + { + if ( dirty ) + { + calcRotation(); + } + + vec *= scale; + + // Apply rotation + vector3df v = vec; + vec.X = v.X*RM[0][0] + v.Y*RM[0][1] + v.Z*RM[0][2]; + vec.Y = v.X*RM[1][0] + v.Y*RM[1][1] + v.Z*RM[1][2]; + vec.Z = v.X*RM[2][0] + v.Y*RM[2][1] + v.Z*RM[2][2]; + + vec += translation; + } + + void inverseTransformVect( vector3df& vec ) + { + vec -= translation; + + if ( dirty ) + { + calcRotation(); + } + + // Apply rotation + vector3df v = vec; + vec.X = v.X*RM[0][0] + v.Y*RM[1][0] + v.Z*RM[2][0]; + vec.Y = v.X*RM[0][1] + v.Y*RM[1][1] + v.Z*RM[2][1]; + vec.Z = v.X*RM[0][2] + v.Y*RM[1][2] + v.Z*RM[2][2]; + + vec /= scale; + } + + void calcRotation() + { + quaternion q; + matrix4 rm = q.set( rotation ).getMatrix(); + + RM[0][0] = rm[0]; + RM[1][0] = rm[1]; + RM[2][0] = rm[2]; + + RM[0][1] = rm[4]; + RM[1][1] = rm[5]; + RM[2][1] = rm[6]; + + RM[0][2] = rm[8]; + RM[1][2] = rm[9]; + RM[2][2] = rm[10]; + + dirty = false; + } +}; + +#endif // #ifndef SPACEMATRIX_H diff --git a/IrrExtensions/math/fraction reduction notes.txt b/IrrExtensions/math/fraction reduction notes.txt new file mode 100755 index 0000000..72dae2d --- /dev/null +++ b/IrrExtensions/math/fraction reduction notes.txt @@ -0,0 +1,159 @@ +16/10 = (2*8)/(2*5) + +big = 16 +small = 10 +mod_bg = 6 +factor = 10 - 6 = 4 (should be 2) +big % factor == 0 +small % factor != 0 + + +18/10 = (2*9)/(2*5) + +big = 18 +small = 10 +mod_bg = 8 +factor = 10 - 8 = 2 +big % factor == 0 +small % factor == 0 + + +21/28 = (7*4)/(7*3) + +big = 28 +small = 21 +mod_bg = 7 +factor = 14 (should be 7) +big % factor == 0 +small % factor != 0 + + +13/4 + +big = 13 +small = 4 +mod_bg = 1 +factor = 4 - 1 = 3 +big % factor != 0 +small % factor != 0 + + +16/4 + +big = 16 +small = 4 +mod_bg = 0 +factor = 4 +big % factor == 0 +small % factor == 0 + +The mod_bg contains the common factor between the big and small. That is, the factor is one of the multiples of the mod_bg. + + +121/22 = (11*11)/(11*2) + +big = 121 +small = 22 +mod_bg = 121 - (22*5) = 121 - 110 = 11 + + +550/220 = (5*10*11)/(2*10*11) = (5*5*2*11)/(2*5*2*11) + +big = 550 +small = 220 +mod_bg = 550 - 2*220 = 550 - 440 = 110 + +When small < big/2, mod_bg is the factor it seems. + + +550/440 + +big = 550 +small = 440 +mod_bg = 110 + + + +Let B be big, S be small, M be mod_bg, and all calculated the same as before. If there is a common factor D, then: +D = B/b = S/s = M/m, where b, s, and m are the unique other factors such that: B = b*D, S = s*D, and M = m*D. + +m = M/D = M*s/S +m = M*b/B + +We also know that, when m < s: +m + s = b +if and only if D exists. +Thus: +m = M*(m+s)/B +s = m*S/M +Thus: +m = M*(m+(m*S/M))/B += (M*m + S*m)/B +=> 1 = (M + S)/B +=> B = M + S +But that's an identity. + +m = M*(m+s)/B +=> 1 = (M + s/m)/B +=> B = M + s/m + + +Chinese number theorem? +16/10 -> +16 - 10 = 6 +10 - 6 = 4 +6 - 4 = 2 = factor +4 - 2 = 2 + +18/10 -> +18 - 10 = 8 +10 - 8 = 2 = factor +8 - 2 = 6 (answer is greater than second number) +2 - 6 = -4 (invalid) + +550/220 -> +550 - 220 = 330 +220 - 330 = -110 (invalid) + +Maybe use mods? +16/10 -> +16 % 10 = 6 (big % small) +10 % 6 = 4 (small % mod_bg = mod_mod) +6 % 4 = 2 (mod_bg % mod_mod) +4 % 2 = 2 (same answer) +2 % 2 = 0 (common denominator is 2) + +18/10 -> +18 % 10 = 8 (big % small) +10 % 8 = 2 (small % mod_bg = mod_mod) +8 % 2 = 0 (mod_bg % mod_mod) (common denominator is 2) + +550/220 -> +550 % 220 = 110 (big % small) +220 % 110 = 0 (small % mod_bg = mod_mod) (fast convergence: mod_mod = 0) + + +(7*3*2)/(5*3) = 42/15 -> +42 % 15 = 12 +15 % 12 = 3 +12 % 3 = 0 + + +1598/272 -> +1598 % 272 = 238 +272 % 238 = 34 +238 % 34 = 0 + +So it looks like: +If you mod the big by the small and repeat the process over and over, you will eventually reach the common factor, which is the last value before zero. + + +1987/451 -> +1987 % 451 = 183 +451 % 183 = 85 +183 % 85 = 13 +85 % 13 = 7 +13 % 7 = 6 +7 % 6 = 1 + +Since the result here is 1, there is no common factor. diff --git a/IrrExtensions/misc/BigLinusHood.cpp b/IrrExtensions/misc/BigLinusHood.cpp new file mode 100755 index 0000000..11eb098 --- /dev/null +++ b/IrrExtensions/misc/BigLinusHood.cpp @@ -0,0 +1,123 @@ +/* +(c) 2012 Nicolaus Anderson + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + Since this uses the irrlicht engine, see irrlicht.h for more details. +*/ + +#include + +#pragma once + +using namespace Linus; + + +template +class BigLinusHood : public BigListInterface +{ + BigLListix list; + +public: + BigLinusHood() : BigListInterface() + { + } + + ~BigLinusHood() + { + list.clear(); + } + + //! Get list size + /* By convention, irrlicht objects return a u32 for size. */ + stdu64 size() + { + return (stdu64)list.Size(); + } + + //! Add to end + /* Adds an object to the end of the list. */ + void push_back( T item ) + { + list.push_back( item ); + } + + //! Add to front + /* Adds an object to the front of the list. */ + void push_front( T item ) + { + list.push_front( item ); + } + + //! Obtain item + /* Returns the object. */ + T& operator[] ( stdu64 index ) + { + return list[(stds64)index]; + } + + //! Obtain item + /* Returns the object. */ + T& get( stdu64 index ) + { + return list[(stds64)index]; + } + + //! Set item + /* Sets the item at the given index. */ + void set( stdu64 index, T item ) + { + list.replace( (stds64)index, item ); + } + + //! Insert + /* Adds an item to the list at the given index. */ + void insert( stdu64 index, T item ) + { + list.insert( item, (stds64)index ); + } + + //! Erase + /* Removes an item from the list. */ + void erase( stdu64 index ) + { + list.deleteAt( (stds64)index ); + } + + //! Clear + /* Empties the list of all of its items. */ + void clear() + { + list.clear(); + } + + //! Sort list + /* While irrlicht engine objects do not always have sort, + lists in general have a sort function. */ + virtual void sort() {} + + //! Expose data + /* Returns a void pointer to the actual data. */ + virtual void* exposeData() + { + return (void*)&list; + } + + //! to string + /* Returns the constant type of this object. Useful for type-checking. */ + const stdc8* toString() { return "LinusHood"; } +}; diff --git a/IrrExtensions/misc/BigListInterface.h b/IrrExtensions/misc/BigListInterface.h new file mode 100755 index 0000000..d971f83 --- /dev/null +++ b/IrrExtensions/misc/BigListInterface.h @@ -0,0 +1,96 @@ +/* +(c) 2012 Nicolaus Anderson + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + +*/ + +#include + +#pragma once + + +/* +There are many situations in which lists and arrays are used. The choice of +which is used may be dependent on the implementation within the cpp file and +not the header. Rather than force a specific implementation within the header, +it may be better to provide a partially abstract class header that allows the +person who creates the final implementation to decide which is best. + +This class provides a constant interface for adding, removing, and accessing +items in a list. + +While this class is intended to be inherited from by a class that provides a +list implementation, it can be used as a container of a list or array class, +especially if the names of the storage class do not follow the general convention +of the program. +*/ + +template +class BigListInterface +{ +public: + + //! Get list size + /* For completeness, we return a u64 for size. */ + virtual stdu64 size()=0; + + //! Add to end + /* Adds an object to the end of the list. */ + virtual void push_back( T item )=0; + + //! Add to front + /* Adds an object to the front of the list. */ + virtual void push_front( T item )=0; + + //! Obtain item + /* Returns the object. */ + virtual T& operator[] ( stdu64 index )=0; + + //! Obtain item + /* Returns the object. */ + virtual T& get( stdu64 index )=0; + + //! Set item + /* Sets the item at the given index. */ + virtual void set( stdu64 index, T item )=0; + + //! Insert + /* Adds an item to the list at the given index. */ + virtual void insert( stdu64 index, T item )=0; + + //! Erase + /* Removes an item from the list. */ + virtual void erase( stdu64 index )=0; + + //! Clear + /* Empties the list of all of its items. */ + virtual void clear()=0; + + //! Sort list + /* While irrlicht engine objects do not always have sort, + lists in general have a sort function. */ + virtual void sort()=0; + + //! Expose data + /* Returns a void pointer to the actual data. */ + virtual void* exposeData()=0; + + //! to string + /* Returns the constant type of this object. Useful for type-checking. */ + virtual const stdc8* toString() { return "ListInterface"; } +}; \ No newline at end of file diff --git a/IrrExtensions/misc/FunctionContainer/FunctionContainer.h b/IrrExtensions/misc/FunctionContainer/FunctionContainer.h new file mode 100755 index 0000000..2efc894 --- /dev/null +++ b/IrrExtensions/misc/FunctionContainer/FunctionContainer.h @@ -0,0 +1,75 @@ +/* +(c) 2012 Nicolaus Anderson + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + Since this uses the irrlicht engine, see irrlicht.h for more details. +*/ + +#pragma once + +//! FunctionContainer +/* Holds the function to be used when a key is pressed. */ +class FunctionContainer +{ + //! Function to use + bool (*function) (); + + //! If the function is available + bool hasFunc; + +public: + //! Constructor + FunctionContainer() + { + hasFunc = false; + } + + //! Constructor with passed function + FunctionContainer( bool newFunction() ) + { + // Save a pointer to the passed function + function = newFunction; + + // Indicate that a function can be used + hasFunc = true; + } + + //! If there is a function that can be used + bool isUsable() + { + return hasFunc; + } + + //! Assign a new function + void set( bool newFunction() ) + { + // Save a pointer to the passed function + function = newFunction; + + // Indicate that a function can be used + hasFunc = true; + } + + //! Use the assigned function + bool use() + { + if ( hasFunc ) + return function(); + else return false; + } +}; // end FunctionContainer \ No newline at end of file diff --git a/IrrExtensions/misc/LinusHood.cpp b/IrrExtensions/misc/LinusHood.cpp new file mode 100755 index 0000000..6f2059a --- /dev/null +++ b/IrrExtensions/misc/LinusHood.cpp @@ -0,0 +1,124 @@ +/* +(c) 2012 Nicolaus Anderson + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + Since this uses the irrlicht engine, see irrlicht.h for more details. +*/ + +#include +#include + +#pragma once + +using namespace Linus; + + +template +class LinusHood : public ListInterface +{ + LListix list; + +public: + LinusHood() : ListInterface() + { + } + + ~LinusHood() + { + list.clear(); + } + + //! Get list size + /* By convention, irrlicht objects return a u32 for size. */ + stdu32 size() + { + return (stdu32)list.Size(); + } + + //! Add to end + /* Adds an object to the end of the list. */ + void push_back( T item ) + { + list.push_back( item ); + } + + //! Add to front + /* Adds an object to the front of the list. */ + void push_front( T item ) + { + list.push_front( item ); + } + + //! Obtain item + /* Returns the object. */ + T& operator[] ( stds32 index ) + { + return list[index]; + } + + //! Obtain item + /* Returns the object. */ + T& get( stds32 index ) + { + return list[index]; + } + + //! Set item + /* Sets the item at the given index. */ + void set( stds32 index, T item ) + { + list.replace( index, item ); + } + + //! Insert + /* Adds an item to the list at the given index. */ + void insert( stds32 index, T item ) + { + list.insert( item, index ); + } + + //! Erase + /* Removes an item from the list. */ + void erase( stds32 index ) + { + list.deleteAt( index ); + } + + //! Clear + /* Empties the list of all of its items. */ + void clear() + { + list.clear(); + } + + //! Sort list + /* While irrlicht engine objects do not always have sort, + lists in general have a sort function. */ + virtual void sort() {} + + //! Expose data + /* Returns a void pointer to the actual data. */ + virtual void* exposeData() + { + return (void*)&list; + } + + //! to string + /* Returns the constant type of this object. Useful for type-checking. */ + const stdc8* toString() { return "LinusHood"; } +}; \ No newline at end of file diff --git a/IrrExtensions/misc/ListInterface.h b/IrrExtensions/misc/ListInterface.h new file mode 100755 index 0000000..3b8a613 --- /dev/null +++ b/IrrExtensions/misc/ListInterface.h @@ -0,0 +1,96 @@ +/* +(c) 2012 Nicolaus Anderson + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + +*/ + +#include + +#pragma once + + +/* +There are many situations in which lists and arrays are used. The choice of +which is used may be dependent on the implementation within the cpp file and +not the header. Rather than force a specific implementation within the header, +it may be better to provide a partially abstract class header that allows the +person who creates the final implementation to decide which is best. + +This class provides a constant interface for adding, removing, and accessing +items in a list. + +While this class is intended to be inherited from by a class that provides a +list implementation, it can be used as a container of a list or array class, +especially if the names of the storage class do not follow the general convention +of the program. +*/ + +template +class ListInterface +{ +public: + + //! Get list size + /* By convention, irrlicht objects return a u32 for size. */ + virtual stdu32 size()=0; + + //! Add to end + /* Adds an object to the end of the list. */ + virtual void push_back( T item )=0; + + //! Add to front + /* Adds an object to the front of the list. */ + virtual void push_front( T item )=0; + + //! Obtain item + /* Returns the object. */ + virtual T& operator[] ( stds32 index )=0; + + //! Obtain item + /* Returns the object. */ + virtual T& get( stds32 index )=0; + + //! Set item + /* Sets the item at the given index. */ + virtual void set( stds32 index, T item )=0; + + //! Insert + /* Adds an item to the list at the given index. */ + virtual void insert( stds32 index, T item )=0; + + //! Erase + /* Removes an item from the list. */ + virtual void erase( stds32 index )=0; + + //! Clear + /* Empties the list of all of its items. */ + virtual void clear()=0; + + //! Sort list + /* While irrlicht engine objects do not always have sort, + lists in general have a sort function. */ + virtual void sort(); + + //! Expose data + /* Returns a void pointer to the actual data. */ + virtual void* exposeData(); + + //! to string + /* Returns the constant type of this object. Useful for type-checking. */ + virtual const stdc8* toString() { return "ListInterface"; } +}; \ No newline at end of file diff --git a/IrrExtensions/misc/MegaList.h b/IrrExtensions/misc/MegaList.h new file mode 100755 index 0000000..b3ef15a --- /dev/null +++ b/IrrExtensions/misc/MegaList.h @@ -0,0 +1,488 @@ +/* +(c) 2013 Nicolaus Anderson + +This is a doubly-linked list structure for HUGE lists in which it is helpful to start from a pointer saved near the nodes desiring to be accessed at random. Imagine, within your list, having a waypoint pointer placed every 10 nodes (change "spacing" value to see what works best for you). When accessing a node, the selector pointer jumps to the nearest waypoint pointer before iterating over to the node of the desired index. +*/ + +#include + +#ifndef _MEGA_LISTIX_H_ +#define _MEGA_LISTIX_H_ + +namespace irr +{ +namespace core +{ + + template + class MListixNode + { + public: + MListixNode* Prior; // the node preceding this one + MListixNode* Post; // the node after this one + + Unk ThisItem; // the item stored here + + //! constructor + MListixNode( Unk item ) + { + ThisItem = item; + Prior = 0; + Post = 0; + } + + //! deconstructor + void deleteMe() { delete this; } + }; + + template + class MListixIter + { + irr::s64 pos; + MListixNode* node; + + public: + MListixIter() + { + pos = 0; + node = 0; + } + + bool hasNode() + { + return !(node == 0); + } + + irr::s64 getPos() + { + return pos; + } + + //! Set Node + /* Sets the node to the given one. You shouldn't need to use + this. Note: The old node is not deleted in the process. */ + void setNode( MListixNode* new_node ) + { + node = new_node; + } + + MListixNode* getNode() + { + return node; + } + + //! Goto Node + /* Moves this iterator to the node whose index is given. + \param index - The index to go to. + \param relative - The iterator whose node is the starting point for + this iterator. + \return - True if the node was reached. */ + bool goTo( irr::s64 index, MListixIter* relative ) + { + if ( !relative ) + return false; + + pos = relative->getPos(); + node = relative->getNode(); + + if ( !node ) + return false; + + if ( index > pos ) + { + while ( node && index > pos ) + { + if ( node->Post ) + { + node = node->Post; + pos++; + } else { + return false; + } + } + + } else if ( index < pos ) + { + while ( node && index < pos ) + { + if ( node->Prior ) + { + node = node->Prior; + pos--; + } else { + return false; + } + } + } + + return true; + } + + //! Adjust position + /* Adjusts this iterator's position based on the index of the + most recently inserted node, given by the parameter. + YOU SHOULD NOT USE THIS FUNCTION! + \return - True if the adjustment could be made. */ + bool adjust( irr::s64 insert, bool removal=false ) + { + if ( !node ) + return false; + + if ( insert <= pos ) + { + if ( !removal ) + { + pos++; + } + else { + pos--; + + if ( !node ) + return false; + } + } + + return true; + } + + //! Adjust Position Retain + /* Same as adjust() but it attempts to retain the position of the + iterator in the list. + \param insert - The location of the new node. + \param removal - If this was actually the removal of a node. + \return - True if the node was reached. */ + bool adjustPosRetain( irr::s64 insert, bool removal=false ) + { + if ( !node ) + return false; + + bool safe = true; + + if ( insert <= pos ) + { + if ( !removal ) + pos++; + else + pos--; + + safe = goTo( (pos - 1), this ); + } + + return safe; + } + }; + + template + class MegaListix + { + private: + irr::s64 size; // list size + + // Accessor spacing - keep constant for optimization purposes + /* You need to optimize this for your machine, but that requires + speed testing runs. */ + static const irr::s64 spacing=10; + + // List of accessors points + irr::core::array*> waypoints; + + // The main accessor + MListixIter* selector; + + protected: + void adjustIterators( irr::s64 insert, bool removal=false ) + { + bool keep = true; + + for ( irr::s32 i=(irr::s32)waypoints.size()-1; (irr::s32)i >= 0; i-- ) + { + keep = waypoints[(irr::u32)i]->adjustPosRetain( insert, removal ); + + if ( !keep ) + { + waypoints.erase(i); + } + + keep = true; + } + } + + public: + MegaListix() + { + size = 0; + selector = new MListixIter(); + waypoints.push_back( new MListixIter() ); + } + + ~MegaListix() + { + clear(); + } + + irr::s64 Size() + { + return size; + } + + void spawnIterators() + { + // Correct for a missing first one + if ( waypoints[0]->getPos() != 0 ) + { + // Create this first one + waypoints.push_front( new MListixIter() ); + + // Assign it its rightful position + waypoints[0]->goTo( 0, selector ); + } + + while ( + size > waypoints[ waypoints.size()-1 ]->getPos() + spacing + ) + { + // create a new accessor + waypoints.push_back( new MListixIter() ); + + // Set it to the new position + waypoints[ waypoints.size()-1 ]->goTo( + waypoints[ waypoints.size()-2 ]->getPos() + spacing, + waypoints[ waypoints.size()-2 ] + ); + } + } + + //! Goto Node + /* Attempts to bring the main iterator (selector) to the node whose + index is given. + \return - True if the node could be reached. */ + bool goTo( irr::s64 index ) + { + if ( size == 0 || index > size || waypoints.size() == 0 ) + return false; + + if ( index < 0 ) + index += size; + + if ( index < 0 ) + return false; + + /* Shortcut - Use the current node if it is closer than the + nearest of the waypoints. */ + if ( selector->getPos() % spacing <= spacing / 2 ) + return selector->goTo( index, selector ); + + + MListixIter* lastIter; + + if ( size >= 1 ) + lastIter = waypoints[0]; + + // find the nearest + for ( irr::u32 s=1; s < waypoints.size(); s++ ) + { + /* Go to the range within which the desired point lies. */ + if ( s+1 < waypoints.size() && index > waypoints[s]->getPos() ) + { + lastIter = waypoints[s]; + continue; + } + + /* If the index comes before this node, find which node + is closest to it. */ + if ( index <= waypoints[s]->getPos() ) + { + // closer than the node at the start of this range? + if ( + irr::core::abs_( selector->getPos() - index ) + <= + irr::core::abs_( waypoints[s-1]->getPos() - index ) + ) + { + // closer than the node at the end of this range? + if ( + irr::core::abs_( selector->getPos() - index ) + <= + irr::core::abs_( waypoints[s]->getPos() - index ) + ) + { + } else { + /* selector not closer than the node at the end, + so use the one at the end */ + return selector->goTo( index, waypoints[s] ); + } + + } else { + /* selector not closer than the node at the start, + so use the one at the start */ + return selector->goTo( index, waypoints[s-1] ); + } + } + + lastIter = waypoints[s]; + } + + // last resort + return selector->goTo( index, lastIter ); + } + + //! Insert + /* Inserts the object at the given position. + \return - True if the object could be inserted. */ + bool insert( irr::s64 index, Unk item ) + { + // two causes of failure - index is to negative or > size + if ( index < 0 ) + index += size; + + // First node + if ( size == 0 ) + { + waypoints[0]->setNode( new MListixNode( item ) ); + selector->setNode( waypoints[0]->getNode() ); + size++; // account for the newly added node + + return true; + } + + if ( index < 0 || index > size ) // still too small or too big + return false; + + // Now go to the node + goTo( index ); + + MListixNode* postNode; + + if ( index == 0 ) // insertion at the beginning + { + selector->getNode()->Prior = new MListixNode( item ); + + // tie new prior node to this node + selector->getNode()->Prior->Post = selector->getNode(); + + } else { + if ( index == size ) // insertion at the end + { + selector->getNode()->Post = new MListixNode( item ); + + // tie new post node to this node + selector->getNode()->Post->Prior = selector->getNode(); + } + else + { + // save the next node (it will come after the new one) + postNode = selector->getNode()->Post; + + // create the new node as the next one + selector->getNode()->Post = new MListixNode( item ); + + // tie the new next node back to this one + selector->getNode()->Post->Prior = selector->getNode(); + + // tie the new next node to the old post node + selector->getNode()->Post->Post = postNode; + postNode->Prior = selector->getNode()->Post; + } + } + + size++; // account for the newly added node + + + adjustIterators( index ); + spawnIterators(); + + return true; + } + + void push_back( Unk item ) + { + insert( size, item ); + } + + void push_front( Unk item ) + { + insert( 0, item ); + } + + Unk& get( irr::s64 index ) + { + bool can = goTo(index); + + _IRR_DEBUG_BREAK_IF( !can ); + + return selector->getNode()->ThisItem; + } + + Unk& operator[] ( irr::s64 index ) + { + return get( index ); + } + + Unk copyOf( irr::s64 index ) + { + if ( !goTo(index) ) + return (Unk)0; + + return selector->getNode()->ThisItem; + } + + void erase( irr::s64 index ) + { + if ( !goTo( index ) ) + return; + + /* For this operation, there are three key scenarios: + 1) the node is at one of the waypoints, + 2) the node is before one of the waypoints, + 3) the node is after one of the waypoints. + For the last two cases, a safe deletion can occur without adjusting + the affected selector. For the first case, we have to reset it. */ + + // Variables + MListixNode* mid; + MListixIter** affected; + + // For convenience of code + mid = selector->getNode(); + + if ( mid->Prior ) + { + if ( mid->Post ) + { + mid->Prior->Post = mid->Post; + mid->Post->Prior = mid->Prior; + } else { + mid->Prior->Post = 0; + } + } + + if ( index % spacing == 0 ) // on an iterator + { + // Find the affected iterator + affected = &(waypoints[ (irr::u32) index/spacing ]); + + // Set its new node + if ( mid->Post ) + { + (*affected)->setNode( mid->Post ); + } + } + + adjustIterators( index, true ); + spawnIterators(); + } + + void clear() + { + for ( irr::s64 i = size-1; i >= 0; i-- ) + { + erase(i); + } + } + + }; + +} // end namespace core +} // end namespace irr + +#endif // #ifndef _MEGA_LLISTIX_H_ diff --git a/IrrExtensions/misc/README.md b/IrrExtensions/misc/README.md new file mode 100755 index 0000000..354eab9 --- /dev/null +++ b/IrrExtensions/misc/README.md @@ -0,0 +1 @@ +Files in this folder may not be very useful, but they may serve as a basis for inspiration on what to do... or what NOT to do. diff --git a/IrrExtensions/misc/XPLOsion/Readme.md b/IrrExtensions/misc/XPLOsion/Readme.md new file mode 100755 index 0000000..6b1ab69 --- /dev/null +++ b/IrrExtensions/misc/XPLOsion/Readme.md @@ -0,0 +1,3 @@ +XPLOsion is a GUI layout loader that reads an XML and CSS files as if it were like HTML and CSS for websites. The node names and attributes are the same as the serializable names and attributes of the Irrlicht GUI elements. + +Unfortunately, this loader only works for Irrlicht 1.7.3. There are too many changes that need to be made to fix it. Besides, [CupricBridge](https://github.com/chronologicaldot/CupricBridge ) is faster and easier. diff --git a/IrrExtensions/misc/XPLOsion/XPLOsion/CSSLibrary.h b/IrrExtensions/misc/XPLOsion/XPLOsion/CSSLibrary.h new file mode 100755 index 0000000..822ebf3 --- /dev/null +++ b/IrrExtensions/misc/XPLOsion/XPLOsion/CSSLibrary.h @@ -0,0 +1,222 @@ +/* +Project name: +CSS Library + +Created by Nicolaus Anderson, 2012 + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + Since this uses the irrlicht engine, see irrlicht.h for more details. + +Purpose: +To allow you to design your program using a type of xml format +similar to html and style it like CSS. +*/ + +#include +#include +#include "WebTypes.h" +#include "CSSTagData.h" +#include + +#pragma once + + +//! Class CSS Library +/* Capable of loading and interpreting CSS data in properly formatted files. +Note that not all data will apply to the GUI interface based on certain +restrictions on the GUI elements themselves. */ +class CSSLibrary : public IReferenceCounted +{ + //! Irrlicht Device pointer + /* Necessary for certain things, and also needed if I decide to have this + also handle 3D elements and such and not just GUI things. */ + irr::IrrlichtDevice * irrdev; + + //! CSS tag list + /* Contains the tags that hold the CSS information. */ + Linus::LListix tags; + + //! System characters + /* These are the types of characters that are interpreted as being + individual tokens. */ + irr::core::stringc syschars; + irr::c8 * system_chars; + irr::s32 _maxsystemchars; // total number of system characters + + irr::core::stringc bodysyschars; /* Because the body cares about spaces */ + +public: + + //! Constructor + /**/ + CSSLibrary( irr::IrrlichtDevice * device ) + { + irrdev = device; + + syschars = irr::core::stringc( " \t\n\r.#{}:;/*\"" ); + bodysyschars = irr::core::stringc( "\n\r.#{}:;/*\"" ); + + system_chars = (irr::c8 *)(syschars.c_str()); + _maxsystemchars = (irr::s32)(syschars.size()); + } + + //! Destructor + /**/ + ~CSSLibrary() + { + tags.clear(); + } + + //! Add style + /* Adds an empty style to the list of available styles. */ + void addStyle() + { + tags.push_back( new CSSTagData( irrdev->getVideoDriver() ) ); + (*(tags.itemAt(-1)))->setTagName( irr::core::stringc(tags.Size()) ); + } + + //! Add style + /* Adds a style to the list of available styles. */ + //void addStyle( CSSTagData style ) + //{ + // tags.push_back( style ); + //} + + //! Add style from string + void addStyle( + irr::core::stringc styletext, + irr::core::stringc stylename="", + CSSTagType styletype=CSSTYPE_NEITHER + ) + { + tags.push_back( interpretStyling(styletext) ); + + if ( stylename.size() == 0 ) + { + stylename = irr::core::stringc(tags.Size()); + } + (*(tags.itemAt(-1)))->setTagName( stylename ); + + (*(tags.itemAt(-1)))->setType( styletype ); + } + + //! Add style and return + /* Adds a style to the list of available styles and returns a + pointer to it. */ + CSSTagData* addStyleAndGet() + { + tags.push_back( new CSSTagData( irrdev->getVideoDriver() ) ); + return (*(tags.itemAt(-1))); + } + + //! Load and Parse CSS File + /* Attempts to load a CSS file into the memory bank and parse it. + Returns true for a successful loading and parsing. */ + bool ParseCSSFile( irr::io::path filename ); + + //! Load and Parse CSS File + /* Attempts to load a CSS file into the memory bank and parse it. + Returns true for a successful loading and parsing. */ + bool ParseCSSFile( irr::io::IReadFile * file ); + + //! Get CSS Style + /* Returns a pointer to the CSS style in the list based on the + given index. NOTE: There is no out-of-bounds checking, but + negative indices are allowed. */ + CSSTagData* getCSSStyle( irr::s32 index ) + { + return (CSSTagData*)(*(tags.itemAt( (si32)index ))); + } + + //! Find CSS class + /* Searches for a CSS class whose name matches the given. + If a match is found, it returns the index of that CSS class. */ + irr::s32 findCSSClass( irr::core::stringc name ); + + //! Find CSS ID + /* Searches for a CSS ID whose name matches the given. + If a match is found, it returns the index of that CSS ID. */ + irr::s32 findCSSID( irr::core::stringc name ); + + //! Find CSS random + /* Searches for a CSS tag whose name matches the given. */ + irr::s32 findCSSRandom( irr::core::stringc name ); + + //! Apply styling to a GUI element + /* Attempts to apply a particular CSS style to the GUI element. */ + void applyStyleToGUIElement( + irr::gui::IGUIElement * element, + CSSTagData * css + ); + + //! Apply styling to a GUI element + /* Attempts to apply a style type whose name matches the given and whose + type is either "class", "id", or "random" (any). Default is random. */ + void applyStyleToGUIElement( + irr::gui::IGUIElement * element, + irr::core::stringc stylename, + irr::core::stringc styletype="random" + ); + + //! Build style from GUI element + /* Attempts to create a style from the given GUI element. + NOTE: It is assumed the element has had its absolute position calculated. */ + CSSTagData* buildStyleFromGUIElement( + irr::gui::IGUIElement * element, irr::core::stringc stylename="" + ); + + //! Interpret styling + /* Takes a string that contains the interior of a CSS tag body + and creates a CSS tag out of it. It then returns the tag so it + can either be stored or used once (for example, applied to a + single division). */ + CSSTagData* interpretStyling( irr::core::stringc styletext ); + + //! Apply inline styling + /* Takes a string containing styling information, generates the + style, and applies the style to the passed in GUI element. */ + void applyInlineStyling( + irr::gui::IGUIElement * element, + irr::core::stringc styletext + ); + +protected: + + //! Translate string into color + /* Interprets a string as a color. Accepted English words that give + color values are: "white", "black", "gray", "red", "blue", "green". */ + irr::video::SColor interpretColor( irr::core::stringc word ); + + //! Interpret color value + /* Accepts two characters that indicate a color value (red, green, or blue) + for an HTML color. Returns the resulting color value. Should be used to set + red, green, or blue in SColor. */ + irr::u32 getColorValue( irr::c8 val1, irr::c8 val2 ); + + //! Get rectangle from string + /* Separates the given string into four values and attempts to make a + rectangle out of it. The values must be in the following order and + separated by spaces: left, top, right, bottom. */ + irr::core::recti getRectFromString( irr::core::stringc word ); + + //! Get position from string + /* Separates a string into two values and attempts to make a position2d + out of it. The values must be in the following order and separated by + spaces: left, top. */ + irr::core::position2di getPositionFromString( irr::core::stringc word ); +}; \ No newline at end of file diff --git a/IrrExtensions/misc/XPLOsion/XPLOsion/CSSLibraryImpl.cpp b/IrrExtensions/misc/XPLOsion/XPLOsion/CSSLibraryImpl.cpp new file mode 100755 index 0000000..bedc53f --- /dev/null +++ b/IrrExtensions/misc/XPLOsion/XPLOsion/CSSLibraryImpl.cpp @@ -0,0 +1,608 @@ +/* +Project name: +CSS Library + +Created by Nicolaus Anderson, 2012 +see CSSLibrary.h for terms and conditions. + +Implementation of the functions. +*/ + +#include "CSSLibrary.h" + + +void CSSLibrary::applyStyleToGUIElement( + irr::gui::IGUIElement * element, + CSSTagData * css + ) +{ + // Checking for non-absolute positioning + HTMLPos pos = (HTMLPos) css->getAttributeAsEnumeration( "position", HTMLPosTypes ); + + // Obtain the immediate parent based on type + irr::gui::IGUIElement * immediate_parent; + + if ( pos == HTM_POS_fixed || element->getParent() == 0 ) + { + immediate_parent = irrdev->getGUIEnvironment()->getRootGUIElement(); + } else { + immediate_parent = element->getParent(); + } + + /* Form the CSS data for it to apply to this GUI element based on its positioning*/ + css->finalizeParameters( immediate_parent ); + + + css->applyToGUIElement( element ); +} + +void CSSLibrary::applyStyleToGUIElement( + irr::gui::IGUIElement * element, + irr::core::stringc stylename, + irr::core::stringc styletype + ) +{ + // cannot apply a style with no name + if ( stylename == "" ) + return; + + irr::s32 index; // index of the style in the list of styles + + /* The type name string is made lower so that capitalization is + irrelevant in checking the type. */ + styletype.make_lower(); + + // decide how te get the index based on the type of style wanted + if ( styletype == "class" ) + index = findCSSClass( stylename ); + else if ( styletype == "id" ) + index = findCSSID( stylename ); + else + index = findCSSRandom( stylename ); + + // break if the style is unavailable + if ( index == -1 ) + return; + + // apply the style + applyStyleToGUIElement( element, getCSSStyle( index ) ); +} + + +CSSTagData* CSSLibrary::buildStyleFromGUIElement( + irr::gui::IGUIElement * element, + irr::core::stringc stylename + ) +{ + CSSTagData * style = new CSSTagData( irrdev->getVideoDriver() ); + + style->setTagName( stylename ); + + style->buildStyleFromGUIElement( element ); + + return style; +} + + + +CSSTagData* CSSLibrary::interpretStyling(irr::core::stringc styletext ) +{ + // return tag + CSSTagData* tag = new CSSTagData( irrdev->getVideoDriver() ); + + // Last position in the string to be read from + irr::s32 pos = 0; + + // Last character to be grabbed from the string + irr::c8 chr; + + // Last word seen in the string + irr::core::stringc word; + + // Last seen attribute name + irr::core::stringc attr; + + // flag indicating whether an attribute or its value is being searched for + // "true" == attribute + bool attrORval = true; + + + // While the entirety of the string has not been searched + while ( pos != (irr::s32)styletext.size() ) + { + chr = styletext[pos]; + + + if ( styletext.subString(pos,2) == "/*" ) // comment found + { + do { + pos++; + } while ( styletext.subString(pos,2) != "*/" ); + pos++; + } + else if ( chr == ':' ) // break point, leading to a value + { + word.trim(); + + attr = word; // saving the word as the name of the attribute + + attrORval = false; // now searching for a value instead of an attribute name + word= ""; + } + else if ( chr == ';' ) + { + word.trim(); + + // ADD ATTRIBUTE BEGIN *************************** + + + /* Integer attributes followed by "px" */ + if ( + attr == "left" + || attr == "right" + || attr == "top" + || attr == "bottom" + || attr == "height" + || attr == "width" + + || attr == "SliderWidth" + ) + { + word.remove( "px" ); + tag->setAttribute( attr.c_str(), (irr::s32)atoi(word.c_str()) ); + } + + + /* Integer attributes */ + else if ( + attr == "Id" + || attr == "ActiveTab" + || attr == "CloseHandling" + || attr == "DecimalPlaces" + || attr == "ItemCount" + || attr == "LargeStep" + || attr == "Max" + || attr == "MaxChars" + || attr == "Min" + || attr == "ParentItem" + || attr == "SmallStep" + || attr == "Selected" + || attr == "TabHeight" + || attr == "TabMaxWidth" + || attr == "TabNumber" + || attr == "TabOrder" + || attr == "Value" + + || attr.equalsn( irr::core::stringc("CommandID"), 9 ) + ) + { + tag->setAttribute( attr.c_str(), atoi( word.c_str() ) ); + } + + + /* Floating point number attributes */ + else if ( + attr == "Step" + ) + { + tag->setAttribute( attr.c_str(), (irr::f32)atof( word.c_str() ) ); + } + + + /* Boolean attributes */ + else if ( + attr == "AutoScroll" + || attr == "Background" + || attr == "Border" + || attr == "CancelButton" + || attr == "DrawBackground" + || attr == "DrawBack" + || attr == "DrawTitlebar" + || attr == "FillBackground" + || attr == "Horizontal" + || attr == "IsCloseVisible" + || attr == "IsDraggable" + || attr == "IsMinVisible" + || attr == "IsRestoreVisible" + || attr == "MoveOverSelect" + || attr == "MultiLine" + || attr == "NoButton" + || attr == "NoClip" + || attr == "OkayButton" + || attr == "OverrideBGColorEnabled" + || attr == "OverrideColorEnabled" + || attr == "OverrideTextColorEnabled" + || attr == "PasswordBox" + || attr == "Pressed" + || attr == "PushButton" + || attr == "ScaleImage" + || attr == "ShowSmallTicks" + || attr == "ShowLargeTicks" + || attr == "TabStop" + || attr == "TabGroup" + || attr == "UseAlphaChannel" + || attr == "Visible" + || attr == "WordWrap" + || attr == "YesButton" + + || attr.equalsn( irr::core::stringc("AutoChecking"), 11 ) + || attr.equalsn( irr::core::stringc("Checked"), 7 ) + || attr.equalsn( irr::core::stringc("Enabled"), 7 ) + || attr.equalsn( irr::core::stringc("IsSeparator"), 11 ) + || attr.equalsn( irr::core::stringc("UseColText"), 10 ) + || attr.equalsn( irr::core::stringc("UseColIcon"), 10 ) + ) + { + tag->setAttribute( attr.c_str(), (word == "true") ); + } + + + /* String attributes */ + else if ( + attr == "Caption" + || attr == "PasswordChar" + || attr == "MessageText" + || attr == "Name" // Added with irrlicht 1.8 + + || attr.equalsn( irr::core::stringc("Text"), 4 ) + || attr.equalsn( irr::core::stringc("text"), 4 ) + + || ( attr.equalsn( irr::core::stringc("Item"), 4 ) + && attr.find( "Text", 0 ) != -1 ) + ) + { + //word.trim("\""); // in case someone thought it was necessary to add quotes + tag->setAttribute( attr.c_str(), word.c_str() ); + } + + + /* Color attributes */ + else if ( + attr == "background-color" + || attr == "BackColor" + || attr == "BGColor" + || attr == "FullColor" + || attr == "OverrideColor" + || attr == "TransColor" + || attr == "TextColor" + || attr == "TickColor" + + || attr.equalsn( irr::core::stringc("ColIcon"), 7 ) + || attr.equalsn( irr::core::stringc("ColText"), 7 ) + ) + { + tag->setAttribute( attr.c_str(), interpretColor( word ) ); + } + + + /* Texture attributes */ + else if ( + attr == "background-image" + || attr == "Image" + || attr == "PressedImage" + || attr == "Texture" + ) + { + /* In case someone thought quotation marks were necessary, + remove them. */ + word.trim( "\"" ); + + tag->setAttribute( attr.c_str(), irrdev->getVideoDriver()->getTexture( word ) ); + + // We need this rectangle if it isn't provided + if (( attr == "Image" || attr == "PressedImage" ) + && !tag->existsAttribute( (attr+"Rect").c_str() ) + ) + { + /* We're going to cheat and pass in an invalid rectangle + so that the GUI element won't use it. */ + tag->addRect( (attr+"Rect").c_str(), + core::recti(0,0,-1,1) + ); + } + } + + + /* Ordinary rectangles */ + else if ( + attr == "ImageRect" + || attr == "PressedImageRect" + ) + { + tag->setAttribute( attr.c_str(), getRectFromString(word) ); + } + + + /* Enumerations */ + else if ( attr == "position" ) + { + tag->setAttribute( attr.c_str(), word.c_str(), HTMLPosTypes ); + } + else if ( attr == "background-repeat" ) + { + tag->setAttribute( attr.c_str(), word.c_str(), HTMLBackgroundImageRepeatTypes ); + } + else if ( attr == "overflow" ) + { + if ( word == "show" ) // For backwards compatibility + word = "visible"; + + tag->setAttribute( attr.c_str(), word.c_str(), WebOverflowTypes ); + } + else if ( + attr == "HTextAlign" + || attr == "VTextAlign" + || attr == "TabVerticalAlignment" + ) + { + tag->setAttribute( attr.c_str(), word.c_str(), irr::gui::GUIAlignmentNames ); + } + + + /* Miscellaneous */ + + else if ( attr == "Rect" ) + { + /* For this, we split the rectangle into left, right, top, and bottom + attributes so as not to create overriding conflicts when we call + CSSTagData::finalizeParameters() (because we won't be checking for + an existing rectangle which may or may not need to be overridden). */ + + irr::core::recti temp = getRectFromString(word); + + tag->setAttribute( "left", temp.UpperLeftCorner.X ); + tag->setAttribute( "right", temp.LowerRightCorner.X ); + tag->setAttribute( "top", temp.UpperLeftCorner.Y ); + tag->setAttribute( "bottom", temp.LowerRightCorner.Y ); + } + + else if ( attr == "Position" ) + { + tag->setAttribute( attr.c_str(), getPositionFromString(word) ); + } + + + /*********** OTHER TYPES ********** + Here you can add attribute loaders for other types of GUI elements. + How this works: CSS attributes are stored in CSS tags. In this function, + we are creaing a tag, which is stored in the pointer "tag". + Typical design for loading an attribute is tag->setAttribute(). + The function has several overloads for handling different types. + + // for integers: + tag->setAttribute( c8* name, irr::s32 value ); + + // for colors: + tag->setAttribute( c8* name, irr::video::SColor value ); + // Note: To obtain colors from a string, we use the function + // CSSLibrary::interpretColor( irr::core::stringc ); + + // for enumerations: + tag->setAttribute( c8* name, c8* value, irr::s32 enumeration_literals ); + // See examples above for how to do this. + + // for rectangles: + tag->setAttribute( c8* name, irr::core::recti value ); + // Note: To obtain a rectangle from a string, we use the function + // CSSLibrary::getRectFromString( irr::core::stringc ); + */ + + // else if ( attr == my_css_attribute_name ) + //{ + + //} + + + // ADD ATTRIBUTE END ************************************* + + attrORval = true; // now looking for attribute name instead of its value + word = ""; + } + else + { + word.append( chr ); + } + + pos++; + } + + return tag; +} + + +void CSSLibrary::applyInlineStyling( + irr::gui::IGUIElement * box, + irr::core::stringc styletext + ) +{ + CSSTagData * style; + + style = interpretStyling( styletext ); + + applyStyleToGUIElement( box, style ); + + delete style; +} + + +irr::video::SColor CSSLibrary::interpretColor( irr::core::stringc word ) +{ + // Check first if this is an HTML color - this should be the fastest color input + if ( word[0] == '#' ) + { + irr::video::SColor color( 255, 0, 0, 0); + + // programmer forgot to provide all of the needed color values + if ( word.size() < 7 ) + return color; + + // Each two letters indicate a value for the color + color.setRed( getColorValue( word[1], word[2] ) ); + color.setGreen( getColorValue( word[3], word[4] ) ); + color.setBlue( getColorValue( word[5], word[6] ) ); + + return color; + } + + // Check second to see if four numeric values were given for the color + irr::core::array components; + word.split( components, " ", 1 ); + + if ( components.size() == 4 && atoi(components[0].c_str()) != 0 ) + { + return irr::video::SColor( + atoi( components[0].c_str() ), + atoi( components[1].c_str() ), + atoi( components[2].c_str() ), + atoi( components[3].c_str() ) + ); + } + + // Check for word descriptions of colors + if ( word == "clear" || word == "transparent" ) + { + return irr::video::SColor( 0, 0, 0, 0 ); + } + else if ( word == "white" ) + { + return irr::video::SColor( 255, 255, 255, 255 ); + } + else if ( word == "black" ) + { + return irr::video::SColor( 255, 0, 0, 0 ); + } + else if ( word == "gray" ) + { + return irr::video::SColor( 255, 125, 125, 125 ); + } + else if ( word == "red" ) + { + return irr::video::SColor( 255, 255, 0, 0 ); + } + else if ( word == "blue" ) + { + return irr::video::SColor( 255, 0, 0, 255 ); + } + else if ( word == "green" ) + { + return irr::video::SColor( 255, 0, 200, 0 ); + } + else if ( word == "pink" ) + { + return irr::video::SColor( 255, 255, 0, 255 ); + } + else if ( word == "yellow" ) + { + return irr::video::SColor( 255, 255, 255, 0 ); + } + else if ( word == "orange" ) + { + return irr::video::SColor( 255, 255, 142, 0 ); + } + else if ( word == "purple" ) + { + return irr::video::SColor( 255, 125, 0, 255 ); + } + else if ( word == "maroon" ) + { + return irr::video::SColor( 255, 136, 30, 156 ); + } + else if ( word == "light pink" ) + { + return irr::video::SColor( 255, 255, 125, 255 ); + } + else if ( word == "sky blue" ) + { + return irr::video::SColor( 255, 0, 220, 255 ); + } + else if ( word == "light blue" ) + { + return irr::video::SColor( 255, 255, 125, 125 ); + } + else if ( word == "light green" || word == "lime" ) + { + return irr::video::SColor( 255, 0, 255, 0 ); + } + else if ( word == "medium blue" ) + { + return irr::video::SColor( 255, 0, 118, 255 ); + } + //else + //{ + // ? + //} + + // default return transparent white + return irr::video::SColor( 0, 255, 255, 255 ); +} + + +irr::u32 CSSLibrary::getColorValue(irr::c8 val1, irr::c8 val2) +{ + irr::s32 n1 = (irr::s32) (val1 - 'a'); + irr::s32 n2 = (irr::s32) (val2 - 'a'); + + if ( n1 >= 0 ) + n1 += 10; + else + if ( (irr::s32) val1 == '0' ) + n1 = 0; + else + n1 = (irr::s32)(val1 - '1') + 1; + + if ( n2 >= 0 ) + n2 += 10; + else + if ( (irr::s32) val2 == '0' ) + n2 = 0; + else + n2 = (irr::s32)(val2 - '1') + 1; + + n1 *= 16; + + return (irr::u32)(n1 + n2); +} + + +irr::core::recti CSSLibrary::getRectFromString(irr::core::stringc word) +{ + irr::core::array components(4); + word.split( components, " ", 1 ); + + if ( components.size() == 4 ) + { + return irr::core::recti( + atoi( components[0].c_str() ), // left + atoi( components[1].c_str() ), // top + atoi( components[2].c_str() ), // right (or left + width) + atoi( components[3].c_str() ) // bottom (or top + height) + ); + } + + return irr::core::recti( 0, 0, 100, 100 ); +} + + +irr::core::position2di CSSLibrary::getPositionFromString( irr::core::stringc word ) +{ + irr::core::array components(2); + word.split( components, " ", 1 ); + + if ( components.size() == 2 ) + { + return + irr::core::position2di( + atoi( components[0].c_str() ), // left + atoi( components[1].c_str() ) // top + ); + } + else if ( components.size() == 1 ) + { + return + irr::core::position2di( + atoi( components[0].c_str() ) // left and top + ); + } + + return irr::core::position2di( 0 ); +} diff --git a/IrrExtensions/misc/XPLOsion/XPLOsion/CSSLibraryNess.cpp b/IrrExtensions/misc/XPLOsion/XPLOsion/CSSLibraryNess.cpp new file mode 100755 index 0000000..ec6f760 --- /dev/null +++ b/IrrExtensions/misc/XPLOsion/XPLOsion/CSSLibraryNess.cpp @@ -0,0 +1,200 @@ +/* +Project name: +CSS Library + +Created by Nicolaus Anderson, 2012 +see CSSLibrary.h for terms and conditions. + +Necessary functions. +*/ + +#include "CSSLibrary.h" + + +irr::s32 CSSLibrary::findCSSClass(irr::core::stringc name) +{ + if ( tags.Size() == 0 ) + return -1; + + CSSTagData * data = tags[0]; + + tags.resetMidix(); + + do { + // the data must be a class and have the correct name + if ( (data)->isClass() && (data)->getTagName() == name ) + { + return (irr::s32)( tags.getCurrLoc() ); + } + } while ( !tags.iter(true,&data) ); + + return -1; +} + + +irr::s32 CSSLibrary::findCSSID( irr::core::stringc name ) +{ + if ( tags.Size() == 0 ) + return -1; + + CSSTagData * data = tags[0]; + + tags.resetMidix(); + + do { + // the data must be an ID and have the correct name + if ( (data)->isID() && (data)->getTagName() == name ) + { + return (irr::s32)( tags.getCurrLoc() ); + } + } while ( !tags.iter(true,&data) ); + + return -1; +} + +irr::s32 CSSLibrary::findCSSRandom( irr::core::stringc name ) +{ + if ( tags.Size() == 0 ) + return -1; + + CSSTagData * data = tags[0]; + + tags.resetMidix(); + + do { + // the data merely must have the correct name + if ( (data)->getTagName() == name ) + { + return (irr::s32)( tags.getCurrLoc() ); + } + } while ( !tags.iter(true,&data) ); + + return -1; +} + +bool CSSLibrary::ParseCSSFile( irr::io::path filename ) +{ + irr::io::IReadFile* file = irrdev->getFileSystem()->createAndOpenFile( filename ); + + bool result = ParseCSSFile( file ); + + if ( file != 0 ) + file->drop(); + + return result; +} + + +bool CSSLibrary::ParseCSSFile(irr::io::IReadFile * file) +{ + if ( file == 0 ) return false; + + // Create a reader for reading from the file + irrVFreader reader( file ) ; + + // String for collecting what was returned from file + irr::core::stringc token = " "; + + // Contents of a tag to be passed to the interpreting function + irr::core::stringc contents; + + // Last tag data + CSSTagType tag_type = CSSTYPE_NEITHER; + irr::core::stringc tag_name; + + // Editable flag - if the tag has a body already + bool editable = false; + + + + // Continue while the file returns something + while ( token != "end_main" ) + { + token = reader.readFromFile_sys_nse( _maxsystemchars, system_chars ); + + // Escape from the loop if the next token indicates the end of the file + if ( token == "end_main" ) break; + + + // Determine what to do based on the type found in file + if ( token == "." ) + { + editable = true; + + // Assume this is a class + tag_type = CSSTYPE_CLASS; + + // Next token should be the name + tag_name = reader.readFromFile_sys_nse( _maxsystemchars, system_chars ); + } + else if ( token == "#" ) + { + editable = true; + + // Assume this is an ID + tag_type = CSSTYPE_ID; + + // Next token should be the name + tag_name = reader.readFromFile_sys_nse( _maxsystemchars, system_chars ); + } + else if ( token == "{" ) + { + // Destroy the old string + contents = ""; + + // CSS tag body - search for the rest + while ( token != "end_main" && token != "}" ) + { + if ( token != "{" ) // ignore the first character + contents.append( token ); + token = reader.readFromFile( + bodysyschars.size(), + (irr::c8*)bodysyschars.c_str() + ); + //token = reader.readFromFile_sys_nse( _maxsystemchars, system_chars ); + } + + // Should this be added to the current tag (or was it an accident) + if ( editable ) + { + editable = false; + + addStyle( contents, tag_name, tag_type ); + } + } + else if ( token == "/" ) + { + /* This might need to change because it wastes a word to check + if this is a comment. Then again, what else could it be? */ + + // Check for * to indicate this is a comment + token = reader.readFromFile_sys_nse( _maxsystemchars, system_chars ); + + if ( token == "*" ) + { + // Destroy the old string + token = ""; + + do { + if ( token != "*" ) + { + token = ""; + } + token.append( + reader.readFromFile_sys_nse( _maxsystemchars, system_chars ) + ); + } while ( token != "*/" ); + } + } + else if ( !editable ) + { + // Unknown tag type + tag_type = CSSTYPE_NEITHER; + + // Assume the token was the name + tag_name = token; + } + } + + return true; +} diff --git a/IrrExtensions/misc/XPLOsion/XPLOsion/CSSTagData.h b/IrrExtensions/misc/XPLOsion/XPLOsion/CSSTagData.h new file mode 100755 index 0000000..c71d536 --- /dev/null +++ b/IrrExtensions/misc/XPLOsion/XPLOsion/CSSTagData.h @@ -0,0 +1,187 @@ +/* +Project name: +CSS Library + +Created by Nicolaus Anderson, 2012 +see CSSLibrary.h for terms and conditions. +*/ + +#include +#include "WebTypes.h" +#include +//#include +#include "sdiv.h" + +#pragma once + + +//! CSS tag type +/* Whether the is CSS data belongs to a class, an id, or neither */ +enum CSSTagType +{ + CSSTYPE_NEITHER=0, + CSSTYPE_CLASS, + CSSTYPE_ID, + CSSTYPE_COUNT // indicates how many types there are +}; + + +//! Class CSS Tag Data +/* Contains data concerning a CSS tag. + +The intention here is to take advantage of the engine's built-in +serialization and deserialization aspects that happen to expose +and change the internals of GUI elements. + +We can then modify these elements in a CSS manner that maintains simplicity +in assigning parameters while allowing us to organize our programs using +an XML format similar to HTML. +*/ +class CSSTagData : public irr::io::CAttributes +{ + // Typical CSS data -------------------------------- + CSSTagType css_type; + irr::core::stringc name; + + CSSTagData * parent_tag; /* Only global for simplification of code. + Applicable only in relative cases: when the + element has been nested. + e.g. If only "left" is given in this tag + and "right", "width", and "height" are given + in the tag of the parent GUI element. */ + + // end typical CSS data ---------------------------- + +public: + + // exposed CSS data + + irr::gui::IGUIFont * font; + + + //! Constructor + /* Note that this assigns default parameters to the CSS style. */ + CSSTagData( irr::video::IVideoDriver * driver=0 ) : CAttributes( driver ) + { + css_type = CSSTYPE_NEITHER; + name = ""; + + parent_tag = 0; + } + + //! Set tag name + /* The name of this tag, NOT the name of an attribute. */ + void setTagName( irr::core::stringc tag_name ) + { name = tag_name; } + + //! Get tag name + /* The name of this tag, NOT the name of an attribute. */ + irr::core::stringc getTagName() + { return name; } + + void setType( CSSTagType type ) + { css_type = type; } + + CSSTagType getType() + { return css_type; } + + bool isNothing() + { return ( CSSTYPE_NEITHER == css_type ); } + + bool isClass() + { return ( CSSTYPE_CLASS == css_type ); } + + bool isID() + { return ( CSSTYPE_ID == css_type ); } + + //! Set the parent tag + /* Sets the tag from which data will be inherited if it cannot + be provided by this tag. */ + void setParentTag( CSSTagData * tag ) + { + parent_tag = 0; + parent_tag = tag; + } + + //! Get parent tag + /* Gets a pointer to the parent CSS tag. */ + CSSTagData* getParentTag() + { + return parent_tag; + } + + //! Create bounded rectangle + /* Creates a rectangle that obeys this style and is placed with + respect to the given parent. + \param parent - The IGUIElement parent of this element. + */ + irr::core::recti createBoundedRect( irr::gui::IGUIElement * parent ); + + //! Finalize creation + /* Uses the given information to generate the necessary different types + of parameters. USE AFTER ASSIGNING DATA TO A CLASS INSTANCE OF THIS TYPE! + \param parent - The parent GUI element of the element the CSS data is going + to be applied to. This is necessary in oredr to fit the element within its + parent's borders using "left", "right", "top", and "bottom". + \param position_shift - Offset placement of the GUI element despite its + designed location based on "left", "right", "top", and "bottom". */ + void finalizeParameters( + irr::gui::IGUIElement * parent + ); + + + /* ******************************************************* + The following functions are meant to be called for applying + the style to the GUI element or creating a style from it. + */ + + //! Apply to GUI element + /* Accepts an element and attempts to apply the style to it. + USE AFTER finalizeParameters() */ + void applyToGUIElement( irr::gui::IGUIElement * element ) + { + element->deserializeAttributes(this); + } + + //! Create a style from the GUI element + /* Creates a style from the parameters of the given GUI element, + trying to create a style that mimics its behavior. */ + void buildStyleFromGUIElement( irr::gui::IGUIElement * element ); + + + /* ******************************************************* + OVERRIDE FUNCTIONS + These allow us to use the parent's attributes if this CSS tag cannot + provide them. + */ + virtual bool existsAttribute( const irr::c8* attributeName ) const + { + return getAttributeP( attributeName ) != 0; + } + virtual irr::io::IAttribute* getAttributeP(const irr::c8* attributeName) const; + + virtual irr::core::stringc getAttributeAsString(const irr::c8* attributeName) const; + virtual void getAttributeAsString(const irr::c8* attributeName, char* target) const; + virtual void getAttributeAsStringW(const irr::c8* attributeName, wchar_t* target) const; + virtual irr::core::array getAttributeAsArray(const irr::c8* attributeName) const; + virtual bool getAttributeAsBool(const irr::c8* attributeName) const; + virtual irr::s32 getAttributeAsInt(const irr::c8* attributeName) const; + virtual irr::f32 getAttributeAsFloat(const irr::c8* attributeName) const; + virtual irr::video::SColor getAttributeAsColor(const irr::c8* attributeName) const; + virtual irr::video::SColorf getAttributeAsColorf(const irr::c8* attributeName) const; + + virtual const char* getAttributeAsEnumeration(const irr::c8* attributeName) const; + + virtual irr::s32 getAttributeAsEnumeration( + const irr::c8* attributeName, const char* const* enumerationLiteralsToUse + ) const; + + virtual void getAttributeEnumerationLiteralsOfEnumeration( + const irr::c8* attributeName, irr::core::array& outLiterals + ) const; + + virtual irr::core::rect getAttributeAsRect(const irr::c8* attributeName) const; + virtual irr::core::position2di getAttributeAsPosition2d(const irr::c8* attributeName) const; + + virtual irr::video::ITexture* getAttributeAsTexture(const irr::c8* attributeName) const; +}; diff --git a/IrrExtensions/misc/XPLOsion/XPLOsion/CSSTagDataImpl.cpp b/IrrExtensions/misc/XPLOsion/XPLOsion/CSSTagDataImpl.cpp new file mode 100755 index 0000000..7bd05f6 --- /dev/null +++ b/IrrExtensions/misc/XPLOsion/XPLOsion/CSSTagDataImpl.cpp @@ -0,0 +1,117 @@ +/* +Project name: +CSS Library + +Created by Nicolaus Anderson, 2012 +see CSSLibrary.h for terms and conditions. + +Implementation of some important functions. +*/ + +#include "CSSTagData.h" + + +void CSSTagData::finalizeParameters( + irr::gui::IGUIElement * parent + ) +{ + // default = showing the element + if ( !existsAttribute( "Visible" ) ) + addBool( "Visible", true ); + + + // ensure that this parent's parameters are applied appropriately for this element + if ( parent_tag != 0 ) + parent_tag->finalizeParameters( parent ); + + + // position ***************************************** + + + irr::core::recti frame = createBoundedRect( parent ); + irr::core::vector2di position_shift; + HTMLPos position = (HTMLPos) getAttributeAsEnumeration( + "position", HTMLPosTypes + ); + + /* Check if this has a static or relative position, which require the tedious + task of finding the positions of the last added child GUI element (to this + element's parent) that is positioned likewise. */ + if ( position == HTM_POS_relative || position == HTM_POS_static ) + { + /* Find the last child of the parent element positioned by "relative" or "static". + This child must be of type HTMLPositioned for its position to be identified. */ + irr::core::list::ConstIterator it = parent->getChildren().getLast(); + --it; // skip this gui element - we only care about the other children + + for ( irr::u32 index = 0; index < parent->getChildren().size() - 1; ++index) + { + if ( + ((divBox*)*it)->getPosition() == HTM_POS_static + || ((divBox*)*it)->getPosition() == HTM_POS_relative + ) + { + position_shift = + (*it)->getAbsolutePosition().LowerRightCorner + - + parent->getAbsolutePosition().UpperLeftCorner; + + // now determine the shift type based on the position type of this element + if ( position == HTM_POS_static ) + { + frame += irr::core::vector2di( 0, position_shift.Y ); + } else { + frame += position_shift; + } + + break; + } + + --it; // examine the next child + } + } + + // finalize the position + setAttribute( "Rect", frame ); + + if ( existsAttribute("left") ) + setAttribute( "LeftAlign", "upperLeft", irr::gui::GUIAlignmentNames ); + else + setAttribute( "LeftAlign", "lowerRight", irr::gui::GUIAlignmentNames ); + + if ( existsAttribute("right") ) + setAttribute( "RightAlign", "lowerRight", irr::gui::GUIAlignmentNames ); + else + setAttribute( "RightAlign", "upperLeft", irr::gui::GUIAlignmentNames ); + + if ( existsAttribute("top") ) + setAttribute( "TopAlign", "upperLeft", irr::gui::GUIAlignmentNames ); + else + setAttribute( "TopAlign", "lowerRight", irr::gui::GUIAlignmentNames ); + + if ( existsAttribute("bottom") ) + setAttribute( "BottomAlign", "lowerRight", irr::gui::GUIAlignmentNames ); + else + setAttribute( "BottomAlign", "upperLeft", irr::gui::GUIAlignmentNames ); +} + + +void CSSTagData::buildStyleFromGUIElement( irr::gui::IGUIElement * element ) +{ + // serialize only attributes common to all GUI elements + // (automatically done because the element has been casted to the basic type) + element->serializeAttributes( this ); + + /* NOTE: Given that AlignLeft, AlignRight, AlignTop, and AlignBottom may be + set to upperLEFT, lowerRIGHT, center, or scale, all four of these must be + taken into account. + + Currently, only upperLeft is taken into account. */ + + irr::core::recti boundbox = element->getAbsolutePosition(); + + setAttribute( "left", boundbox.UpperLeftCorner.X ); + setAttribute( "right", boundbox.LowerRightCorner.X ); + setAttribute( "top", boundbox.UpperLeftCorner.Y ); + setAttribute( "bottom", boundbox.LowerRightCorner.Y ); +} \ No newline at end of file diff --git a/IrrExtensions/misc/XPLOsion/XPLOsion/CSSTagDataNess.cpp b/IrrExtensions/misc/XPLOsion/XPLOsion/CSSTagDataNess.cpp new file mode 100755 index 0000000..df492b7 --- /dev/null +++ b/IrrExtensions/misc/XPLOsion/XPLOsion/CSSTagDataNess.cpp @@ -0,0 +1,87 @@ +/* +Project name: +CSS Library + +Created by Nicolaus Anderson, 2012 +see CSSLibrary.h for terms and conditions. + +Necessities +*/ + +#include "CSSTagData.h" + + +irr::core::recti CSSTagData::createBoundedRect( + irr::gui::IGUIElement * parent + ) +{ + irr::s32 left =0; + irr::s32 right =0; + irr::s32 top = 0; + irr::s32 bottom = 0; + irr::s32 height = 0; + irr::s32 width = 0; + bool hasLeft, hasRight, hasTop, hasBottom, hasHeight, hasWidth; + + // obtain information about the walls + hasLeft = existsAttribute( "left" ); + hasRight = existsAttribute( "right" ); + hasTop = existsAttribute( "top" ); + hasBottom = existsAttribute( "bottom" ); + hasHeight = existsAttribute( "height" ); + hasWidth = existsAttribute( "width" ); + + if ( hasLeft ) + left = getAttributeAsInt( "left" ); + + if ( hasRight ) + right = getAttributeAsInt( "right" ); + + if ( hasTop ) + top = getAttributeAsInt( "top" ); + + if ( hasBottom ) + bottom = getAttributeAsInt( "bottom" ); + + if ( hasHeight ) + height = getAttributeAsInt( "height" ); + + if ( hasWidth ) + width = getAttributeAsInt( "width" ); + + + // Save the current parent parameters for use in the alignment process + irr::core::recti temp_parent_pos = parent->getAbsolutePosition(); + + // Create the rectangle to be changed + irr::core::recti r = irr::core::recti( + left, + top, + temp_parent_pos.getWidth() - right, + temp_parent_pos.getHeight() - bottom + ); + + + /* Alter rectangle if one of the needed boundary parameters is missing + AND there is a parameter to replace it. */ + if ( (!hasLeft || !hasRight) && hasWidth ) + { + if ( hasRight ) + { + r.UpperLeftCorner.X = temp_parent_pos.getWidth() - right - width; + } else { + r.LowerRightCorner.X = left + width; + } + } + if ( (!hasTop || !hasBottom) && hasHeight ) + { + if ( hasBottom ) + { + r.UpperLeftCorner.Y = temp_parent_pos.getHeight() - bottom - height; + } else { + r.LowerRightCorner.Y = top + height; + } + } + + return r; +} diff --git a/IrrExtensions/misc/XPLOsion/XPLOsion/CSSTagDataOverrides.cpp b/IrrExtensions/misc/XPLOsion/XPLOsion/CSSTagDataOverrides.cpp new file mode 100755 index 0000000..8c46725 --- /dev/null +++ b/IrrExtensions/misc/XPLOsion/XPLOsion/CSSTagDataOverrides.cpp @@ -0,0 +1,261 @@ +/* +Project name: +CSS Library + +Functions based on those (C) Nikolaus Gebhardt +Edited by Nicolaus Anderson, 2012 +see irrlicht.h for terms and conditions. + +Overriding the usual IAttributes functions with duplicates +that contain slight modifications for our purposes. +that call our getAttributeP() function. +*/ + +#include "CSSTagData.h" +#include "CAttributeImpl.h" + + +using namespace irr; +using namespace io; + + +IAttribute* CSSTagData::getAttributeP(const c8* attributeName) const +{ + for (irr::u32 i=0; iName == attributeName) + return Attributes[i]; + + // Added for our purposes: + // Now search the parent's attributes if the parent exists + if ( parent_tag != 0 ) + return parent_tag->getAttributeP( attributeName ); + + return 0; +} + +//! Gets a string attribute. +//! \param attributeName: Name of the attribute to get. +//! \return Returns value of the attribute previously set by setStringAttribute() +//! or 0 if attribute is not set. +core::stringc CSSTagData::getAttributeAsString(const c8* attributeName) const +{ + core::stringc str; + + IAttribute* att = getAttributeP(attributeName); + if (att) + return att->getString(); + else + return str; +} + +//! Gets a string attribute. +//! \param attributeName: Name of the attribute to get. +//! \param target: Buffer where the string is copied to. +void CSSTagData::getAttributeAsString(const c8* attributeName, char* target) const +{ + IAttribute* att = getAttributeP(attributeName); + if (att) + { + core::stringc str = att->getString(); + strcpy(target,str.c_str()); + } + else + target[0] = 0; +} + +//! Gets a string attribute. +//! \param attributeName: Name of the attribute to get. +//! \param target: Buffer where the string is copied to. +void CSSTagData::getAttributeAsStringW(const c8* attributeName, wchar_t* target) const +{ + IAttribute* att = getAttributeP(attributeName); + if (att) + { + core::stringw str = att->getStringW(); + wcscpy(target,str.c_str()); + } + else + target[0] = 0; +} + +//! Gets an attribute as an array of wide strings. +core::array CSSTagData::getAttributeAsArray(const c8* attributeName) const +{ + IAttribute* att = getAttributeP(attributeName); + if (att) + return att->getArray(); + else + return core::array(); +} + +//! Gets a attribute as boolean value +//! \param attributeName: Name of the attribute to get. +//! \return Returns value of the attribute previously set by setAttribute() as bool +//! or 0 if attribute is not set. +bool CSSTagData::getAttributeAsBool(const c8* attributeName) const +{ + bool ret = false; + + IAttribute* att = getAttributeP(attributeName); + if (att) + ret = att->getBool(); + + return ret; +} + +//! Gets a attribute as integer value +//! \param attributeName: Name of the attribute to get. +//! \return Returns value of the attribute previously set by setAttribute() as integer +//! or 0 if attribute is not set. +s32 CSSTagData::getAttributeAsInt(const c8* attributeName) const +{ + IAttribute* att = getAttributeP(attributeName); + if (att) + return att->getInt(); + else + return 0; +} + +//! Gets a attribute as integer value +//! \param attributeName: Name of the attribute to get. +//! \return Returns value of the attribute previously set by setAttribute() as float value +//! or 0 if attribute is not set. +f32 CSSTagData::getAttributeAsFloat(const c8* attributeName) const +{ + IAttribute* att = getAttributeP(attributeName); + if (att) + return att->getFloat(); + + return 0.f; +} + +//! Gets an attribute as color +//! \param attributeName: Name of the attribute to get. +//! \return Returns value of the attribute previously set by setAttribute() +video::SColor CSSTagData::getAttributeAsColor(const c8* attributeName) const +{ + video::SColor ret(0); + + IAttribute* att = getAttributeP(attributeName); + if (att) + ret = att->getColor(); + + return ret; +} + +//! Gets an attribute as floating point color +//! \param attributeName: Name of the attribute to get. +//! \return Returns value of the attribute previously set by setAttribute() +video::SColorf CSSTagData::getAttributeAsColorf(const c8* attributeName) const +{ + IAttribute* att = getAttributeP(attributeName); + if (att) + return att->getColorf(); + else + return video::SColorf(); +} + +//! Gets an attribute as enumeration +//! \param attributeName: Name of the attribute to get. +//! \return Returns value of the attribute previously set by setAttribute() +const char* CSSTagData::getAttributeAsEnumeration(const c8* attributeName) const +{ + IAttribute* att = getAttributeP(attributeName); + if (att) + return att->getEnum(); + else + return 0; +} + +//! Gets an attribute as enumeration +s32 CSSTagData::getAttributeAsEnumeration(const c8* attributeName, const char* const* enumerationLiteralsToUse) const +{ + IAttribute* att = getAttributeP(attributeName); + + if (enumerationLiteralsToUse && att) + { + const char* value = att->getEnum(); + if (value) + { + for (s32 i=0; enumerationLiteralsToUse[i]; ++i) + if (!strcmp(value, enumerationLiteralsToUse[i])) + return i; + } + } + + return -1; +} + +//! Gets the list of enumeration literals of an enumeration attribute +//! \param attributeName: Name of the attribute to get. +void CSSTagData::getAttributeEnumerationLiteralsOfEnumeration(const c8* attributeName, core::array& outLiterals) const +{ + IAttribute* att = getAttributeP(attributeName); + + if (att && att->getType() == EAT_ENUM) + outLiterals = ((irr::io::CEnumAttribute*)att)->EnumLiterals; +} + +//! Gets an attribute as rectangle +//! \param attributeName: Name of the attribute to get. +//! \return Returns value of the attribute previously set by setAttribute() +core::rect CSSTagData::getAttributeAsRect(const c8* attributeName) const +{ + IAttribute* att = getAttributeP(attributeName); + if (att) + return att->getRect(); + else + return core::rect(); +} + +//! Gets an attribute as 2d position +//! \param attributeName: Name of the attribute to get. +//! \return Returns value of the attribute previously set by setAttribute() +core::position2di CSSTagData::getAttributeAsPosition2d(const c8* attributeName) const +{ + IAttribute* att = getAttributeP(attributeName); + if (att) + return att->getPosition(); + else + return core::position2di(); +} + +//! Gets an attribute as texture reference +//! \param attributeName: Name of the attribute to get. +video::ITexture* CSSTagData::getAttributeAsTexture(const c8* attributeName) const +{ + IAttribute* att = getAttributeP(attributeName); + if (att) + return att->getTexture(); + else + return 0; +} + + +/* NOTE: + +For those wishing to implement this for loading models (etc.) in a CSS manner: +The following functions need to be appended to this list: + +(beginning on line 432 of CAttributes.cpp) + +core::vector3df CSSTagData::getAttributeAsVector3d(const c8* attributeName) +void CSSTagData::getAttributeAsBinaryData(const c8* attributeName, void* outData, s32 maxSizeInBytes) +E_ATTRIBUTE_TYPE CSSTagData::getAttributeType(const c8* attributeName) +const wchar_t* CSSTagData::getAttributeTypeString(const c8* attributeName) + +(beginning on line 946 of CAttributes.cpp) + +core::matrix4 CSSTagData::getAttributeAsMatrix(const c8* attributeName) +core::quaternion CSSTagData::getAttributeAsQuaternion(const c8* attributeName) +core::aabbox3df CSSTagData::getAttributeAsBox3d(const c8* attributeName) +core::plane3df CSSTagData::getAttributeAsPlane3d(const c8* attributeName) +core::triangle3df CSSTagData::getAttributeAsTriangle3d(const c8* attributeName) +core::line2df CSSTagData::getAttributeAsLine2d(const c8* attributeName) +core::line3df CSSTagData::getAttributeAsLine3d(const c8* attributeName) +void* CSSTagData::getAttributeAsUserPointer(const c8* attributeName) + + +All that is required is copying and pasting these functions (and the aforemented, +slightly-modified headers) into this file and the prototypes into CSSTagData.h +*/ diff --git a/IrrExtensions/misc/XPLOsion/XPLOsion/CSlider.cpp b/IrrExtensions/misc/XPLOsion/XPLOsion/CSlider.cpp new file mode 100755 index 0000000..a4ba564 --- /dev/null +++ b/IrrExtensions/misc/XPLOsion/XPLOsion/CSlider.cpp @@ -0,0 +1,347 @@ +/* +Created by Nicolaus Anderson, 2012 + +For license terms, see CSlider.h +*/ + +#include "CSlider.h" + +namespace irr +{ +namespace gui +{ + +void CSlider::draw() +{ + if ( !IsVisible ) return; + + bool DrawWithDriver = false; + video::SColor barColor = video::SColor(255,125,125,125); + core::recti tickMark( + AbsoluteRect.UpperLeftCorner.X, + AbsoluteRect.UpperLeftCorner.Y, + AbsoluteRect.UpperLeftCorner.X + (Horizontal?0:AbsoluteRect.getWidth()), + AbsoluteRect.UpperLeftCorner.Y + (Horizontal?AbsoluteRect.getHeight():0) + ); + core::vector2di direction( (Horizontal?1:0), (Horizontal?0:1) ); + + + IGUISkin* skin = Environment->getSkin(); + if ( !skin ) DrawWithDriver = true; + + video::IVideoDriver* vid = Environment->getVideoDriver(); + + // Shape the slider rectangle based on its orientation + if ( Horizontal ) + { + SliderRect.UpperLeftCorner.X = AbsoluteRect.UpperLeftCorner.X + ActualPos; + SliderRect.LowerRightCorner.X = SliderRect.UpperLeftCorner.X + ConstantSliderWidth; + + SliderRect.UpperLeftCorner.Y = AbsoluteRect.UpperLeftCorner.Y; + SliderRect.LowerRightCorner.Y = AbsoluteRect.LowerRightCorner.Y; + } else { + SliderRect.UpperLeftCorner.Y = AbsoluteRect.UpperLeftCorner.Y + ActualPos; + SliderRect.LowerRightCorner.Y = SliderRect.UpperLeftCorner.Y + ConstantSliderWidth; + + SliderRect.UpperLeftCorner.X = AbsoluteRect.UpperLeftCorner.X; + SliderRect.LowerRightCorner.X = AbsoluteRect.LowerRightCorner.X; + } + + + // Draw the background + vid->draw2DRectangle( BackgroundColor, AbsoluteRect, &AbsoluteClippingRect); + + + // Draw the tick marks ******************** + + s32 tkp = 0; // tick point + + // Large ticks first + if ( ShowLargeTicks ) + for ( tkp = 0 ; tkp <= (Max-Min)/LargeStep; tkp++ ) + { + vid->draw2DLine( tickMark.UpperLeftCorner, tickMark.LowerRightCorner, TickColor); + + if ( Horizontal ) + tickMark += direction*LargeStep*(RelativeRect.getWidth() - ConstantSliderWidth)/(Max-Min); + else + tickMark += direction*LargeStep*(RelativeRect.getHeight() - ConstantSliderWidth)/(Max-Min); + } + + // Reshape the tick mark - squeeze it together + if ( Horizontal ) + { + // squeeze + tickMark.UpperLeftCorner.Y += AbsoluteRect.getHeight()/4; + tickMark.LowerRightCorner.Y -= AbsoluteRect.getHeight()/4; + + // reset position + tickMark.UpperLeftCorner.X = AbsoluteRect.UpperLeftCorner.X; + tickMark.LowerRightCorner.X = AbsoluteRect.UpperLeftCorner.X; + } else { + // squeeze + tickMark.UpperLeftCorner.X += AbsoluteRect.getWidth()/4; + tickMark.LowerRightCorner.X -= AbsoluteRect.getWidth()/4; + + // reset position + tickMark.UpperLeftCorner.Y = AbsoluteRect.UpperLeftCorner.Y; + tickMark.LowerRightCorner.Y = AbsoluteRect.UpperLeftCorner.Y; + } + + // Small tick marks next + if ( ShowSmallTicks ) + for ( tkp = 0 ; tkp <= (Max-Min)/SmallStep; tkp++ ) + { + vid->draw2DLine( tickMark.UpperLeftCorner, tickMark.LowerRightCorner, TickColor); + + if ( Horizontal ) + tickMark += direction*SmallStep*(RelativeRect.getWidth() - ConstantSliderWidth)/(Max-Min); + else + tickMark += direction*SmallStep*(RelativeRect.getHeight() - ConstantSliderWidth)/(Max-Min); + } + + + // Draw the slider bar *********************** + + // Use the driver directly if there is no skin + if ( DrawWithDriver ) + { + vid->draw2DRectangle( barColor, SliderRect, &AbsoluteClippingRect); + } else { + skin->draw2DRectangle(this, skin->getColor(EGDC_SCROLLBAR), SliderRect, &AbsoluteClippingRect); + } +} + + +// Function contents (c) Nikolaus Gebhardt +// Altered here and there by Nicolaus Anderson +//! called if an event happened. +bool CSlider::OnEvent(const SEvent& event) +{ + if (IsEnabled) + { + + switch(event.EventType) + { + case EET_KEY_INPUT_EVENT: + if (event.KeyInput.PressedDown) + { + const s32 oldPos = Pos; + bool absorb = true; + switch (event.KeyInput.Key) + { + case KEY_LEFT: + case KEY_UP: + setPos(Pos-SmallStep); + break; + case KEY_RIGHT: + case KEY_DOWN: + setPos(Pos+SmallStep); + break; + case KEY_HOME: + setPos(Min); + break; + case KEY_PRIOR: + setPos(Pos-LargeStep); + break; + case KEY_END: + setPos(Max); + break; + case KEY_NEXT: + setPos(Pos+LargeStep); + break; + default: + absorb = false; + } + + if (Pos != oldPos) + { + SEvent newEvent; + newEvent.EventType = EET_GUI_EVENT; + newEvent.GUIEvent.Caller = this; + newEvent.GUIEvent.Element = 0; + newEvent.GUIEvent.EventType = EGET_SCROLL_BAR_CHANGED; + Parent->OnEvent(newEvent); + } + if (absorb) + return true; + } + break; + case EET_GUI_EVENT: + // Note: button section erased + if (event.GUIEvent.EventType == EGET_ELEMENT_FOCUS_LOST) + { + if (event.GUIEvent.Caller == this) + Dragging = false; + } + break; + case EET_MOUSE_INPUT_EVENT: + { + const core::position2di p(event.MouseInput.X, event.MouseInput.Y); + bool isInside = isPointInside ( p ); + switch(event.MouseInput.Event) + { + case EMIE_MOUSE_WHEEL: + if (Environment->hasFocus(this)) + { + // thanks to a bug report by REAPER + // thanks to tommi by tommi for another bugfix + // everybody needs a little thanking. hallo niko!;-) + setPos( getPos() + + ( (event.MouseInput.Wheel < 0 ? -1 : 1) * SmallStep * (Horizontal ? 1 : -1 ) ) + ); + + SEvent newEvent; + newEvent.EventType = EET_GUI_EVENT; + newEvent.GUIEvent.Caller = this; + newEvent.GUIEvent.Element = 0; + newEvent.GUIEvent.EventType = EGET_SCROLL_BAR_CHANGED; + Parent->OnEvent(newEvent); + return true; + } + break; + case EMIE_LMOUSE_PRESSED_DOWN: + { + if (isInside) + { + Dragging = true; + DraggedBySlider = SliderRect.isPointInside(p); + /* This is the offset from the top of the slider itself + that I (Anderson) use for identifying change in position. */ + OffsetPos = getPosFromMousePos(p) - ActualPos; + Environment->setFocus ( this ); + return true; + } + break; + } + case EMIE_LMOUSE_LEFT_UP: + case EMIE_MOUSE_MOVED: + { + if ( !event.MouseInput.isLeftPressed () ) + Dragging = false; + + if ( !Dragging ) + { + if ( event.MouseInput.Event == EMIE_MOUSE_MOVED ) + break; + return isInside; + } + + if ( event.MouseInput.Event == EMIE_LMOUSE_LEFT_UP ) + Dragging = false; + + // Exact positions, not a percentage as for scrollbars + const s32 newPos = getPosFromMousePos(p); + const s32 oldPos = ActualPos; // Changed for using exact position + + if (!DraggedBySlider) + { + if ( isInside ) + { + DraggedBySlider = SliderRect.isPointInside(p); + } + + if (DraggedBySlider) + { + // Altered for my purposes - I (Anderson) only want the shift, not a snap + setActualPos( newPos - OffsetPos ); + } + else + { + if (event.MouseInput.Event == EMIE_MOUSE_MOVED) + return isInside; + } + } + + if (DraggedBySlider) + { + // Altered for my purposes - I (Anderson) only want the shift, not a snap + setActualPos( newPos - OffsetPos ); + } + + if (ActualPos != oldPos && Parent) + { + SEvent newEvent; + newEvent.EventType = EET_GUI_EVENT; + newEvent.GUIEvent.Caller = this; + newEvent.GUIEvent.Element = 0; + newEvent.GUIEvent.EventType = EGET_SCROLL_BAR_CHANGED; + Parent->OnEvent(newEvent); + } + return isInside; + } break; + + default: + break; + } + } break; + default: + break; + } + } + + return IGUIElement::OnEvent(event); +} + + +s32 CSlider::getPosFromMousePos(const core::position2di &pos) const +{ + if ( Horizontal ) + return pos.X - AbsoluteRect.UpperLeftCorner.X; + else + return pos.Y - AbsoluteRect.UpperLeftCorner.Y; +} + + +void CSlider::serializeAttributes( + irr::io::IAttributes* out, irr::io::SAttributeReadWriteOptions* options + ) const +{ + IGUIElement::serializeAttributes( out, options ); + + // Usual attributes + out->addBool("Horizontal", Horizontal); + out->addInt ("Value", Pos); + out->addInt ("Min", Min); + out->addInt ("Max", Max); + out->addInt ("SmallStep", SmallStep); + out->addInt ("LargeStep", LargeStep); + + // Unique attributes + out->addInt( "SliderWidth", ConstantSliderWidth ); + out->addColor( "BGColor", BackgroundColor ); + out->addColor( "TickColor", TickColor ); + out->addBool( "ShowSmallTicks", ShowSmallTicks ); + out->addBool( "ShowLargeTicks", ShowLargeTicks ); +} + + +void CSlider::deserializeAttributes( + irr::io::IAttributes *in, + irr::io::SAttributeReadWriteOptions *options + ) +{ + IGUIElement::deserializeAttributes( in, options ); + + // Unique attributes + ConstantSliderWidth = in->getAttributeAsInt( "SliderWidth" ); // MUST PRECEDE "Value" + + if ( ConstantSliderWidth <= 0 ) // It needs to exist + ConstantSliderWidth = 10; + + BackgroundColor = in->getAttributeAsColor( "BGColor" ); + TickColor = in->getAttributeAsColor( "TickColor" ); + ShowSmallTicks = in->getAttributeAsBool( "ShowSmallTicks" ); + ShowLargeTicks = in->getAttributeAsBool( "ShowLargeTicks" ); + + // Usual attributes + Horizontal = in->getAttributeAsBool("Horizontal"); + setMin(in->getAttributeAsInt("Min")); + setMax(in->getAttributeAsInt("Max")); + setPos(in->getAttributeAsInt("Value")); + setSmallStep(in->getAttributeAsInt("SmallStep")); + setLargeStep(in->getAttributeAsInt("LargeStep")); +} + +} // end namespace gui +} // end namespace irr diff --git a/IrrExtensions/misc/XPLOsion/XPLOsion/CSlider.h b/IrrExtensions/misc/XPLOsion/XPLOsion/CSlider.h new file mode 100755 index 0000000..8baec7e --- /dev/null +++ b/IrrExtensions/misc/XPLOsion/XPLOsion/CSlider.h @@ -0,0 +1,227 @@ +/* +Project name: CSlider + +Created by Nicolaus Anderson, 2012 + +Code copied from Nikolaus Gebhardt is copyrighted by him and subject to +the license of the irrlicht engine. + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ + +#include "irrMath.h" +#include "IGUIScrollBar.h" +#include "IGUISkin.h" +#include "IVideoDriver.h" +#include "IGUIEnvironment.h" + +#pragma once + +namespace irr +{ +namespace gui +{ + +//! Class CSlider +/* This is merely an implementation of IGUIScrollBar with a different +face for interaction. We reuse much of Gebhardt's code to save time. */ +class CSlider : public IGUIScrollBar +{ +private: + core::rect SliderRect; + s32 ConstantSliderWidth; + + bool Dragging; + bool Horizontal; + bool DraggedBySlider; + + s32 Pos; + s32 ActualPos; // Left side / Top of the slider + s32 OffsetPos; + s32 SmallStep; + s32 LargeStep; + s32 Min; + s32 Max; + u32 LastChange; + + bool ShowSmallTicks; + bool ShowLargeTicks; + f32 SmallTickSpacing; + f32 LargeTickSpacing; + + video::SColor TickColor; + video::SColor BackgroundColor; + +public: + //! constructor + CSlider( + bool horiz, // if horizontal + IGUIEnvironment* environment, + IGUIElement* parent, + s32 id, + irr::core::rect rectangle, // shape + bool noclip + ) + : IGUIScrollBar( environment, parent, id, rectangle ), + Pos(0), ActualPos(0), OffsetPos(0), + SmallStep(-1), LargeStep(-1), Min(0), Max(10), + LastChange(0), Dragging(false), Horizontal(horiz), DraggedBySlider(false), + ShowSmallTicks(false), ShowLargeTicks(false), + SmallTickSpacing(0), LargeTickSpacing(0), ConstantSliderWidth(10), + TickColor( video::SColor(255,0,0,0) ) + { +#ifdef _DEBUG + setDebugName("CSlider"); +#endif + setNotClipped(noclip); + } + + //! destructor + ~CSlider() + { + } + + //! sets the maximum value of the slider. + void setMax(s32 max) + { + Max = max; + } + //! gets the maximum value of the slider. + s32 getMax() const + { + return Max; + } + + //! sets the minimum value of the slider. + void setMin(s32 min) + { + Min = min; + } + //! gets the minimum value of the slider. + s32 getMin() const + { + return Min; + } + + //! gets the small step value + s32 getSmallStep() const + { + return SmallStep; + } + + //! Sets the small step + /** That is the amount that the value changes by when clicking + on the bar near the current slider position. + Default is -1, meaning exact snap. + Set the step if you want the slider to move in integer values only. */ + void setSmallStep(s32 step) + { + SmallStep = step; + } + + //! gets the large step value + s32 getLargeStep() const + { + return LargeStep; + } + + //! Sets the large step + /** That is the amount that the value changes by when clicking + on the bar far away from the current slider position. + Default is -1, meaning exact snap. + Set the step if you want the slider to move in integer values only. */ + void setLargeStep(s32 step) + { + LargeStep = step; + } + + //! gets the current position of the slider + s32 getPos() const + { + return Pos; + } + + //! sets the current position of the slider + void setPos(s32 pos) + { + Pos = core::s32_clamp(pos,Min,Max); + + if ( Horizontal ) + ActualPos = (Max>Min?(Pos-Min)*(RelativeRect.getWidth()-ConstantSliderWidth)/(Max-Min):0); + else + ActualPos = (Max>Min?(Pos-Min)*(RelativeRect.getHeight()-ConstantSliderWidth)/(Max-Min):0); + } + + void setActualPos(s32 pos) + { + if ( Horizontal ) + { + ActualPos = core::s32_clamp(pos,0,RelativeRect.getWidth()-ConstantSliderWidth); + Pos = (Max-Min)*ActualPos/(RelativeRect.getWidth()-ConstantSliderWidth) + Min; + } else { + ActualPos = core::s32_clamp(pos,0,RelativeRect.getHeight()-ConstantSliderWidth); + Pos = (Max-Min)*ActualPos/(RelativeRect.getHeight()-ConstantSliderWidth) + Min; + } + } + + //! Get the actual position + s32 getActualPos() + { + return ActualPos; + } + + //! Get percentage/100 + /* Returns the fraction of the distance the slider has traveled along + the available pathway. + This can be more useful to have than the position %, which is always between + 0 and 100. */ + f32 getPercentTraveled() + { + if ( Horizontal ) + return (f32)ActualPos/(f32)(RelativeRect.getWidth()-ConstantSliderWidth); + + return (f32)ActualPos/(f32)(RelativeRect.getHeight()-ConstantSliderWidth); + } + + //! Draw + /* For drawing the division. This is called by the engine. */ + void draw(); + + //! On Event + /* Response to user input events. */ + bool OnEvent( const irr::SEvent& event ); + + //! Get position from mouse position + /* Returns the position of the slider region that the mouse is + currently hovering over. */ + s32 getPosFromMousePos(const core::position2di &pos) const; + + //! Serialize + /* For obtaining the key attributes of this GUI element. */ + void serializeAttributes( + irr::io::IAttributes* out, irr::io::SAttributeReadWriteOptions* options=0 + ) const; + + //! Deserialize + /* For defining this GUI element from key attributes. */ + void deserializeAttributes( + irr::io::IAttributes* in, irr::io::SAttributeReadWriteOptions* options=0 + ); +}; + +} // end namespace gui +} // end namespace irr \ No newline at end of file diff --git a/IrrExtensions/misc/XPLOsion/XPLOsion/GUIElemAcceptor.h b/IrrExtensions/misc/XPLOsion/XPLOsion/GUIElemAcceptor.h new file mode 100755 index 0000000..4b4387d --- /dev/null +++ b/IrrExtensions/misc/XPLOsion/XPLOsion/GUIElemAcceptor.h @@ -0,0 +1,51 @@ +/* +Created by Nicolaus Anderson, 2012 + +For license terms, see xplo.h +*/ + +#include + +#ifndef __INCLUDE_GUI_ELEM_ACCEPTOR__ +#define __INCLUDE_GUI_ELEM_ACCEPTOR__ + +//! Class GUI Element Acceptor +/* In order to completely separate the GUI setup from functional classes +(that is, those whose function is independent of the way the GUI elements are +styled), this class should be inherited. +It is an interface by which a class can receive a GUI element and decide if +it wants to become dependent on / respond to the activity of that GUI element. + +Reasoning: +In programming, it is hard to save enumeration data type data in a file, +whereas if the GUI was entirely built within the code, it would be simple +enough to use: +[code] +enum GUI_IDs { GI_1 = 1 }; + +IGUIElement* elem = envir->addButton(); + +elem->setID( GI_1 ); +[/code] +... and inside of the functional class, associate activity with the GUI +element with the ID "GI_1". + +When loading a GUI element from a file, we don't have this option, so +this class gives a psuedo-similar option, using a string (the "BindingHint") +instead of an enumeration. +*/ +class GUIElemAcceptor +{ +public: + + //! Receive and Accept + /* Accepts the given GUI element and determines if the class (inheriting + this one) should save a pointer to the GUI element or drop it. + \param elem - The GUI element to be examined. + \param BindingHints - Information to help the function decide if a pointer + to the GUI element should be saved. + \return - "true" if it accepted the GUI element and saved a pointer to it. */ + virtual bool bind( irr::gui::IGUIElement* elem, const irr::c8* BindingHint )=0; +}; + +#endif // #ifndef __INCLUDE_GUI_ELEM_ACCEPTOR__ diff --git a/IrrExtensions/misc/XPLOsion/XPLOsion/GUIElemAttributes.h b/IrrExtensions/misc/XPLOsion/XPLOsion/GUIElemAttributes.h new file mode 100755 index 0000000..e80f6e8 --- /dev/null +++ b/IrrExtensions/misc/XPLOsion/XPLOsion/GUIElemAttributes.h @@ -0,0 +1,31 @@ +/* +Project name: +CSS Library + +Created by Nicolaus Anderson, 2012 +see CSSLibrary.h for terms and conditions. +*/ + +#include + +using namespace irr::io; + +class GUIElemAttributes +{ +public: + + irr::core::stringc name; + irr::core::stringc value; + + E_ATTRIBUTE_TYPE type; + + irr::core::stringw type_string; + + GUIElemAttributes( irr::core::stringc new_name ) + { + name = new_name; + type = EAT_UNKNOWN; + + type_string = L"unknown"; + } +}; \ No newline at end of file diff --git a/IrrExtensions/misc/XPLOsion/XPLOsion/GUIFunctionFire.cpp b/IrrExtensions/misc/XPLOsion/XPLOsion/GUIFunctionFire.cpp new file mode 100755 index 0000000..c4941d3 --- /dev/null +++ b/IrrExtensions/misc/XPLOsion/XPLOsion/GUIFunctionFire.cpp @@ -0,0 +1,83 @@ +/* +Project name: +GUI Function Fire + +Created by Nicolaus Anderson, 2012 +For terms and conditions, see GUIfunctionFire.h +*/ + +#include "GUIFunctionFire.h" + +irr::s32 GUIFunctionFire::findFunctionByName( irr::core::stringc funcName ) +{ + for ( funcs.resetMidix(); !funcs.iter(true,0) ; ) + { + if ( ((IDedFunc *)(funcs.getCurrItem()))->matches( funcName ) ) + return funcs.getCurrLoc(); + } + + return -1; +} + +void GUIFunctionFire::registerFunctionClass( IDedFunc * funcclass ) +{ + /* First, find if the function class with that name exists already. + If so, replace it. */ + if ( findFunctionByName( funcclass->getName() ) != -1 ) + { + IDedFunc* f = *(funcs.getCurrItem()); + funcs.replace( funcs.getCurrLoc(), funcclass ); + delete f; + } else { + funcs.push_back( funcclass ); + } +} + +void GUIFunctionFire::giveToFunction( irr::core::stringc funcName, irr::s32 gui_elem_id ) +{ + irr::s32 index; + + index = findFunctionByName( funcName ); + + /* First, find if function class with that name exists. */ + if ( index == -1 ) + { + // Create a dummy function + funcs.push_back( new IDedFunc(funcName) ); + } + + ((IDedFunc*)funcs[index])->addRespondant( gui_elem_id ); +} + +bool GUIFunctionFire::callFunctionWithRespondant( irr::s32 id ) +{ + irr::s32 func_loc = 0; + + for ( ; func_loc < funcs.Size(); func_loc++ ) + { + if ( funcs[func_loc]->hasRespondant( id ) ) + { + /* Activate the call function. If it returned false, it may be + deferring action to another call function. */ + if ( funcs[func_loc]->call() ) + return true; + } + } + + return false; +} + +bool GUIFunctionFire::OnEvent( const irr::SEvent &event ) +{ + switch( event.EventType ) + { + case irr::EET_GUI_EVENT: + + // The caller's function will be identified based on their ID + return callFunctionWithRespondant( event.GUIEvent.Caller->getID() ); + + default: break; + } + + return false; +} \ No newline at end of file diff --git a/IrrExtensions/misc/XPLOsion/XPLOsion/GUIFunctionFire.h b/IrrExtensions/misc/XPLOsion/XPLOsion/GUIFunctionFire.h new file mode 100755 index 0000000..b8265e3 --- /dev/null +++ b/IrrExtensions/misc/XPLOsion/XPLOsion/GUIFunctionFire.h @@ -0,0 +1,91 @@ +/* +Project name: +GUI Function Fire + +Created by Nicolaus Anderson, 2012 + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + Since this uses the irrlicht engine, see irrlicht.h for more details. + +Purpose: +Sometimes it is necessary to associate functions with certain GUI elements, +especially those that are interacted with by the user via clicking but do not fire. +This class is aimed handling clicking on seemingly non-responsive items such as +divisions and buttons by associating them with functions. +In essence, it acts as an event receiver for buttons and divisions. +*/ + +#include +#include +#include "IDedFunc.h" + + +#pragma once + +class GUIFunctionFire : public irr::IReferenceCounted, public irr::IEventReceiver +{ + /* Pointer to the GUI environment in order to interact with the buttons. */ + irr::gui::IGUIEnvironment* environ; + + /* List containing all of the functions associated with GUI interaction. */ + Linus::LListix funcs; + +public: + + //! Constructor + GUIFunctionFire( + irr::gui::IGUIEnvironment* environment + ) + { + environ = environment; + } + + //! Deconstructor + ~GUIFunctionFire() + { + funcs.clear(); + } + + //! Find function with name + /* Returns the index of the function whose name matches the given. + If no function is found, it returns -1. */ + irr::s32 findFunctionByName( irr::core::stringc funcName ); + + //! Register function class + /* Registers a class that responds to a function so that + the function can be used by the GUI buttons. */ + void registerFunctionClass( IDedFunc* funcclass ); + + //! Assign to function + /* Assigns a function to a respondant. If the respondant isn't available, + a dummy function will be made. */ + void giveToFunction( irr::core::stringc funcName, irr::s32 gui_elem_id ); + + // Call function with respondant + /* Attempts to find a function that is set to respond when the GUI element + possessing the given ID is interacted with. If the function is found, it + is called. + Returns true only if the function was found AND returns true itself. + By not returning true, the function could be deferring processing to + another function. */ + bool callFunctionWithRespondant( irr::s32 id ); + + //! On event + /* Responds to interaction with the GUI environment by the user. */ + virtual bool OnEvent( const irr::SEvent& event ); +}; \ No newline at end of file diff --git a/IrrExtensions/misc/XPLOsion/XPLOsion/IDedFunc.h b/IrrExtensions/misc/XPLOsion/XPLOsion/IDedFunc.h new file mode 100755 index 0000000..eb47bca --- /dev/null +++ b/IrrExtensions/misc/XPLOsion/XPLOsion/IDedFunc.h @@ -0,0 +1,116 @@ +/* +Created by Nicolaus Anderson, 2012 + +For license terms, see GUIFunctionFire.h +*/ + +#include +#include + +#pragma once + +//! Function class +/* +This class is designed for housing a function that is to respond to a GUI +operation. +It has two special functions: +matches() - Called to see if the ID matches this one's ID string. +call() - Called when the function is used by a GUI object. +*/ +class IDedFunc +{ +protected: + //irr::s32 ID; + + irr::core::stringc my_name; + + /* GUI elements that are to respond by using this function */ + irr::core::array respondants; + +public: + IDedFunc() + {} + + IDedFunc( irr::core::stringc name ) + { + my_name = name; + } + + ~IDedFunc() + { + respondants.clear(); + } + + /* + void setID( irr::s32 id ) + { + ID = id; + } + + irr::s32 getID() + { + return ID; + } + + bool matches( irr::s32 id ) + { + return ID == id; + } + */ + + void setName( irr::core::stringc name ) + { + my_name = name; + } + + irr::core::stringc getName() + { + return my_name; + } + + bool matches( irr::core::stringc name ) + { + return my_name == name; + } + + bool hasRespondant( irr::s32 id ) + { + return (respondants.linear_search( id ) != -1); + } + + void addRespondant( irr::s32 id ) + { + // Don't add if already in the list + if (respondants.linear_search( id ) != -1) + respondants.push_back( id ); + } + + void removeRespondant( irr::s32 id ) + { + irr::s32 index; + + index = respondants.linear_search( id ); + + // Don't try to remove if it isn't even there + if ( index != -1 ) + respondants.erase( index ); + } + + //! Call function + /* This is called when the class in to respond to a GUI + event. */ + virtual bool call() { return false; } +}; + + +//! Class Simple Func Class +/* Simple implementation of IdedFunc that allows the storage of a +function. */ +class SimpleIdedFunc : public FunctionContainer, IDedFunc +{ +public: + virtual bool call() + { + return this->use(); + } +}; \ No newline at end of file diff --git a/IrrExtensions/misc/XPLOsion/XPLOsion/WebTypes.h b/IrrExtensions/misc/XPLOsion/XPLOsion/WebTypes.h new file mode 100755 index 0000000..2609979 --- /dev/null +++ b/IrrExtensions/misc/XPLOsion/XPLOsion/WebTypes.h @@ -0,0 +1,92 @@ +/* +Created by Nicolaus Anderson, 2012 + +For license terms, see xplo.h +*/ + +#include + +#pragma once + +//! HTML Position type +/* Determines where an element will be drawn on screen. */ +enum HTMLPos +{ + HTM_POS_absolute=0, + + HTM_POS_relative, + + HTM_POS_static, + + HTM_POS_fixed +}; + +//! HTML Position type +/* Determines where an element will be drawn on screen. */ +const irr::c8* const HTMLPosTypes[] = +{ + "absolute", + "relative", + "static", + "fixed", + 0 +}; + +//! HTML Overflow +/* Determines if children should be cropped to stay in the element. */ +enum HTMLOverflow +{ + HTM_OVF_show = 0, // don't crop children + HTM_OVF_hide, // crop children + HTM_OVF_auto // create a scroll bar +}; + +//! HTML Overflow +/* Determines if children should be cropped to stay in the element. */ +const irr::c8* const WebOverflowTypes[] = +{ + "visible", + "hidden", + "auto", + 0 +}; + +//! HTML Positioned-element +/* All such elements that are meant to be positioned in an HTML-style manner +need to implement this. */ +class HTMLPositioned +{ +protected: + /* Contains the type of position that determines where this + element will be drawn. */ + HTMLPos position_type; + +public: + void setPosition( HTMLPos pos_type ) { position_type = pos_type; } + + HTMLPos getPosition() { return position_type; } +}; + + +//! HTML Background image repeat +/* Determines how the background image should be repeated over the +entire object. */ +enum HTMLBackgroundImageRepeat +{ + HTM_BIR_no_repeat=0, // no repeat + HTM_BIR_repeat_x, // only repeat along the x-axis + HTM_BIR_repeat_y, // only repeat along the y-axis + HTM_BIR_repeat, // repeat along both the x-axis and the y-axis +}; + +//! HTML Background image repeat +/* Determines how the background image should be repeated over the +entire object. */ +const irr::c8* const HTMLBackgroundImageRepeatTypes[] = +{ + "no-repeat", + "repeat-x", + "repeat-y", + "repeat", + 0 +}; \ No newline at end of file diff --git a/IrrExtensions/misc/XPLOsion/XPLOsion/debugger.cpp b/IrrExtensions/misc/XPLOsion/XPLOsion/debugger.cpp new file mode 100755 index 0000000..9a7939a Binary files /dev/null and b/IrrExtensions/misc/XPLOsion/XPLOsion/debugger.cpp differ diff --git a/IrrExtensions/misc/XPLOsion/XPLOsion/divClass.cpp b/IrrExtensions/misc/XPLOsion/XPLOsion/divClass.cpp new file mode 100755 index 0000000..b01f1b5 --- /dev/null +++ b/IrrExtensions/misc/XPLOsion/XPLOsion/divClass.cpp @@ -0,0 +1,145 @@ +/* +Created by Nicolaus Anderson, 2012 + +For license terms, see xplo.h +*/ + +#include "divClass.h" + +void divBox::setClipped( bool clip ) +{ + this->NoClip = !clip; +} + +bool divBox::hidesOverflow() +{ + return ( overflow == HTM_OVF_hide ); +} + +void divBox::overflowShow() { overflow = HTM_OVF_show; } +void divBox::overflowHide() { overflow = HTM_OVF_hide; } +void divBox::overflowAuto() { overflow = HTM_OVF_auto; } + +//void divBox::setOnPressFunction( bool NewFunction() ) +//{ +// onPressFunction->set( NewFunction ); +//} + +bool divBox::isLeftAlignedLeft() +{ + return ( AlignLeft == irr::gui::EGUIA_UPPERLEFT ); +} + +bool divBox::isTopAlignedTop() +{ + return ( AlignTop == irr::gui::EGUIA_UPPERLEFT ); +} + +void divBox::setPosition( HTMLPosType pos_type ) +{ + position_type = pos_type; +} + +void divBox::setBackgroundColor( irr::video::SColor color ) +{ + background_color = color; +} + +void divBox::setImage( irr::io::path filename ) +{ + background_image = irrdev->getVideoDriver()->getTexture( filename ); +} + +void divBox::draw() +{ + irr::video::IVideoDriver * vid = irrdev->getVideoDriver(); + + // Draw the background color (default is transparent) + vid->draw2DRectangle( background_color, AbsoluteRect, &AbsoluteClippingRect); + + if ( background_image != 0 ) + { + + // Box in which to draw the image + image_box = new irr::core::recti( + irr::core::vector2di(0,0), + background_image->getOriginalSize() + ); + + vid->draw2DImage( + background_image, + irr::core::vector2di(0,0), + *image_box, + &AbsoluteClippingRect, + irr::video::SColor(0,0,0,0), true + ); + } + + if ( ifDrawBorder ) + vid->draw2DRectangleOutline( AbsoluteRect, outline_color ); + + //irr::gui::IGUIElement::draw(); + irr::core::list::Iterator it = Children.begin(); + for ( ; it != Children.end(); it++ ) + { + (*it)->draw(); + } +} + +bool divBox::OnEvent( const irr::SEvent& event ) +{ + if ( isClickable && isVisible() && event.EventType == irr::EET_MOUSE_INPUT_EVENT ) + { + if ( event.MouseInput.isLeftPressed() ) + { + //if ( onPressFunction != 0 ) + // return onPressFunction->use(); + //else + // return false; + + irr::SEvent newEvent; + newEvent.eventType = irr::EET_GUI_EVENT; + nevEvent.GUIEvent.Caller = this; + nevEvent.GUIEvent.Element = 0; + nevEvent.GUIEvent.EventType = EGET_BUTTON_PRESSED; + if ( Parent ) + Parent->OnEvent(event); + } + return false; + } + + // else + return irr::gui::IGUIElement::OnEvent(event); +} + +void divBox::TransferParameters( irr::gui::IGUIElement * elem, bool dumpMe ) +{ + // change the parent + irr::gui::IGUIElement * prt = elem->getParent(); + *prt = *(this->Parent); + Parent->addChild(elem); + + if ( dumpMe ) + { + this->grab(); + this->Parent->remove(); + Parent = 0; + } + + // transfer boundary + elem->setRelativePosition( this->RelativeRect ); + //elem->AbsoluteRect = this->AbsoluteRect; + //elem->AbsoluteClippingRect = this->AbsoluteClippingRect; + + elem->setAlignment( + this->AlignLeft, this->AlignRight, this->AlignTop, this->AlignBottom + ); + + elem->setVisible( !NoClip ); + + // transfer text contents + elem->setText( this->getText() ); + + if ( dumpMe ) + this->drop(); +} \ No newline at end of file diff --git a/IrrExtensions/misc/XPLOsion/XPLOsion/divClass.h b/IrrExtensions/misc/XPLOsion/XPLOsion/divClass.h new file mode 100755 index 0000000..23cf449 --- /dev/null +++ b/IrrExtensions/misc/XPLOsion/XPLOsion/divClass.h @@ -0,0 +1,133 @@ +/* +Created by Nicolaus Anderson, 2012 + +For license terms, see xplo.h +*/ + +#include +#include "WebTypes.h" +//#include "FuncClass.h" + +#include + +#pragma once + +//! Div class +/* +This class is a box that can contain children and text. +The box is as wide as the objects inside it make it +unless it has been given strict parameters. + +In the latter case, the function clipAgainst() is used. +*/ +class divBox : public irr::gui::IGUIElement +{ +protected: + /* Reference to the engine. */ + irr::IrrlichtDevice * irrdev; + + /* Contains the type of position that determines where this + element will be drawn. */ + HTMLPosType position_type; + + irr::video::ITexture * background_image; + irr::core::recti * image_box; + + /* Overflow */ + HTMLOverflow overflow; + + bool leftPressed; + +public: + /* Background colors */ + irr::video::SColor background_color; + irr::video::SColor outline_color; + bool ifDrawBorder; + + /* Responsiveness of the box */ + bool isClickable; + + /* For if this division can be pressed like a button. */ + //SimpleFuncClass * onPressFunction; + + //! Constructor + /**/ + divBox( irr::IrrlichtDevice * device, irr::gui::IGUIElement * parent = 0, irr::s32 id=-1 + , WriteableInterface * writer=0 ) + : IGUIElement( irr::gui::EGUIET_ELEMENT, device->getGUIEnvironment(), parent, id, + irr::core::recti(0,0,0,0) ) + , leftPressed(false) + { + irrdev = device; + + background_image = 0; + background_color = irr::video::SColor( 0,0,0,0 ); + outline_color = irr::video::SColor( 255,0,0,0 ); + ifDrawBorder = false; + image_box = new irr::core::recti( 0,0,0,0 ); + + position_type = HTM_POS_static; + overflow = HTM_OVF_show; + + if ( this->getParent() ) + { + writer->print( + irr::core::stringc(" pcs =") + + irr::core::stringc( (irr::u32) this->getParent()->getChildren().size() ) + + irr::core::stringc( " " ) + ); + } + } + + //! Destructor + /**/ + ~divBox() + { + } + + //! Set clipped + /* Determines if this division should be clipped if its bounding box + extends outside that of its parent's. */ + void setClipped( bool clip ); + + //! Hides overflow + /* Indicates if the overflow of this division is hidden. */ + bool hidesOverflow(); + + //! Show the overflow + /* Allows that which extends outside of this division's bounding box to be shown. */ + void overflowShow(); + + //! Hide the overflow + /* Hides that which extends outside of this division's bounding box. */ + void overflowHide(); + + //! Automatically handle the overflow + /* Creates scroll bars to display this division's contents if it extends outside + of this division's bounding box. */ + void overflowAuto(); + + //void setOnPressFunction( bool NewFunction() ); + + bool isLeftAlignedLeft(); + + bool isTopAlignedTop(); + + void setPosition( HTMLPosType pos_type ); + + void setBackgroundColor( irr::video::SColor color ); + + void setImage( irr::io::path filename ); + + //! Draw + /* For drawing the division. This is called by the engine. */ + virtual void draw(); + + //! On Event + /* Response to user input events. */ + virtual bool OnEvent( const irr::SEvent& event ); + + //! Transfer parameters + /* Applies GUI element size and display information to the given GUI element. */ + void TransferParameters( irr::gui::IGUIElement * elem, bool dumpMe ); +}; \ No newline at end of file diff --git a/IrrExtensions/misc/XPLOsion/XPLOsion/sdiv.cpp b/IrrExtensions/misc/XPLOsion/XPLOsion/sdiv.cpp new file mode 100755 index 0000000..fb0000c --- /dev/null +++ b/IrrExtensions/misc/XPLOsion/XPLOsion/sdiv.cpp @@ -0,0 +1,828 @@ +/* +Created by Nicolaus Anderson, 2012 + +For license terms, see xplo.h +*/ + +#include "sdiv.h" + +using namespace irr::gui; + + +bool divBox::hidesOverflow() +{ + return (overflow == HTM_OVF_hide) || (overflow == HTM_OVF_auto); +} + +void divBox::overflowShow() { overflow = HTM_OVF_show; } +void divBox::overflowHide() { overflow = HTM_OVF_hide; } +void divBox::overflowAuto() { overflow = HTM_OVF_auto; } + + +HTMLOverflow divBox::getOverflow() { return overflow; } +void divBox::setOverflow( HTMLOverflow new_overflow ) { overflow = new_overflow; } + + +irr::core::stringc divBox::convertOverFlowToString() const +{ + if ( overflow == HTM_OVF_show ) + return irr::core::stringc( "visible" ); + + if ( overflow == HTM_OVF_hide ) + return irr::core::stringc( "hidden" ); + + if ( overflow == HTM_OVF_auto ) + return irr::core::stringc( "auto" ); + + return " "; +} + +void divBox::setOverflowFromString( const irr::c8* overflow_val ) +{ + irr::core::stringc ovrf = overflow_val; + + if ( ovrf == "visible" || ovrf == "show" ) + { + overflow = HTM_OVF_show; + return; + } + + if ( ovrf == "hidden" ) + { + overflow = HTM_OVF_hide; + return; + } + + if ( ovrf == "auto" ) + { + overflow = HTM_OVF_auto; + return; + } +} + + +void divBox::setImage( irr::io::path filename ) +{ + if ( image_box != 0 ) + { + delete image_box; + image_box = 0; + } + + //background_image = Environment->getVideoDriver()->getTexture( filename ); + background_image = Environment->getVideoDriver()->createImageFromFile( filename ); + + background_image_name = (irr::core::stringc) filename; +} + + +void divBox::setDrawBorder( bool draw ) +{ + ifDrawBorder = draw; +} + + +void divBox::setBorderColor( irr::video::SColor color ) +{ + border_color = color; +} + +void divBox::setScrollBarWidth( irr::s32 width ) +{ + SCROLLBAR_WIDTH = (width>0? width:20); +} + +irr::core::vector2di divBox::getScrollBorPositions() +{ + return scroll_position; +} + +void divBox::addChild(irr::gui::IGUIElement* child) +{ + /* Resets the absolute position of the children to their + original positions so we can add a new one in the appropriate + location and update the scrollbars. */ + resetChildPositions(); + + IGUIElement::addChild(child); + + child_bounds = getChildBounds(); + + updateScrollBars(true); + + // default position for scrollbars is in front + bringScrollsToFront(); +} + +void divBox::addChildToEnd(irr::gui::IGUIElement* child) +{ + /* Resets the absolute position of the children to their + original positions so we can add a new one in the appropriate + location and update the scrollbars. */ + resetChildPositions(); + + IGUIElement::addChildToEnd(child); + + child_bounds = getChildBounds(); + + updateScrollBars(true); + + // default position for scrollbars is in front + bringScrollsToFront(); +} + + +void divBox::removeChild(irr::gui::IGUIElement* child) +{ + /* Resets the absolute position of the children to their + original positions so we can add a new one in the appropriate + location and update the scrollbars. */ + resetChildPositions(); + + IGUIElement::removeChild(child); + + child_bounds = getChildBounds(); + + updateScrollBars(true); +} + +void divBox::resetChildBounds() +{ + child_bounds = getChildBounds(); +} + +void divBox::resetChildPositions() +{ + irr::core::list::Iterator kid; + + kid = Children.begin(); + for ( ; kid != Children.end(); kid++ ) + { + // skip the scrollbars + if ( scrollbar_horiz != 0 && *kid == scrollbar_horiz ) + continue; + if ( scrollbar_vert != 0 && *kid == scrollbar_vert ) + continue; + + (*kid)->move( scroll_position ); + } + + scroll_position.set( 0, 0 ); + + if ( scrollbar_horiz != 0 ) + scrollbar_horiz->setPos(0); + + if ( scrollbar_vert != 0 ) + scrollbar_vert->setPos(0); +} + +void divBox::bringScrollsToFront() +{ + if ( scrollbar_horiz ) + bringToFront( scrollbar_horiz ); + + if ( scrollbar_vert ) + bringToFront( scrollbar_vert ); +} + + +void divBox::draw() +{ + if ( !IsVisible ) return; + + irr::video::IVideoDriver * vid = Environment->getVideoDriver(); + + irr::core::vector2di image_shift; // location for drawing the background image + + // Draw the background color (default is transparent) + vid->draw2DRectangle( background_color, AbsoluteRect, &AbsoluteClippingRect); + + // If there is an image, draw it + if ( background_image != 0 ) + { + // Box in which to draw the image + /* For textures + if ( image_box == 0 ) + image_box = new irr::core::recti( + irr::core::vector2di(0,0), + background_image->getOriginalSize() + ); + + vid->draw2DImage( + background_image, + AbsoluteRect.UpperLeftCorner, + *image_box, + &AbsoluteClippingRect + ); + */ + + if ( image_box == 0 ) + image_box = new irr::core::recti( + AbsoluteClippingRect.UpperLeftCorner, // upper left corner + + AbsoluteClippingRect.UpperLeftCorner // lower right corner + + + // similar to casting the image dimension + irr::core::vector2di( + background_image->getDimension().Width, + background_image->getDimension().Height + ) + ); + + /* Prepare to draw the image across both the x and y axis, + only stopping if not supposed to continue. */ + + // set the starting location for drawing the image + image_shift = AbsoluteClippingRect.UpperLeftCorner; + + // across the width + while ( image_shift.X < AbsoluteClippingRect.LowerRightCorner.X ) + { + while ( image_shift.Y < AbsoluteClippingRect.LowerRightCorner.Y ) + { + for ( + irr::u32 plx = 0; + plx < (irr::u32)image_box->getWidth() + && + (irr::s32)plx + image_shift.X < AbsoluteClippingRect.LowerRightCorner.X; + plx++ + ) + { + for ( + irr::u32 ply = 0; + ply < (irr::u32)image_box->getHeight() + && + (irr::s32)ply + image_shift.Y < AbsoluteClippingRect.LowerRightCorner.Y; + ply++ ) + { + vid->drawPixel( + plx + (irr::u32)image_shift.X, + ply + (irr::u32)image_shift.Y, + background_image->getPixel( plx, ply ) + ); + } + } + + /* stop drawing if this isn't supposed to be repeated + along the y-axis or over all */ + if ( background_image_repeat != HTM_BIR_repeat + && background_image_repeat != HTM_BIR_repeat_y ) + break; + + // perform shift to start drawing further along + image_shift.Y += background_image->getDimension().Height; + + + } // end going across the height + + /* stop drawing if this isn't supposed to be repeated + along the x-axis or over all */ + if ( background_image_repeat != HTM_BIR_repeat + && background_image_repeat != HTM_BIR_repeat_x ) + break; + + // perform shift to start drawing further along + image_shift.X += background_image->getDimension().Width; + + // reset + image_shift.Y = AbsoluteClippingRect.UpperLeftCorner.Y; + + } // end going across the width + } + + // If it has a border, draw that too + if ( ifDrawBorder ) + vid->draw2DRectangleOutline( AbsoluteClippingRect, border_color ); + + + // Draw the children and possibly scrollbars. + drawChildren(); +} + + +void divBox::drawChildren() +{ + // Convenient iterator + irr::core::list::Iterator kid; + + // Normal drawing + if ( overflow != HTM_OVF_auto ) + { + kid = Children.begin(); + for ( ; kid != Children.end(); kid++ ) + { + // skip the scrollbars + if ( scrollbar_horiz != 0 && *kid == scrollbar_horiz ) + continue; + if ( scrollbar_vert != 0 && *kid == scrollbar_vert ) + continue; + + (*kid)->draw(); + } + } + + // Automatic drawing + else { + + kid = Children.begin(); + for ( ; kid != Children.end(); kid++ ) + { + // skip the scrollbars + if ( scrollbar_horiz != 0 && *kid == scrollbar_horiz ) + continue; + if ( scrollbar_vert != 0 && *kid == scrollbar_vert ) + continue; + + (*kid)->draw(); + } + + updateScrollBars(false); + + if ( scrollbar_horiz != 0 ) + { + // Draw the scrollbar + scrollbar_horiz->draw(); + } + + if ( scrollbar_vert != 0 ) + { + // Draw the scrollbar + scrollbar_vert->draw(); + } + + } + +} + + +irr::core::recti divBox::getChildBounds() +{ + // Convenient iterator + irr::core::list::Iterator kid; + + kid = Children.begin(); + + // skip the scrollbars + if ( scrollbar_horiz != 0 && *kid == scrollbar_horiz ) + kid++; + if ( scrollbar_vert != 0 && *kid == scrollbar_vert ) + kid++; + + /* Reset the region occupied by the child GUI elements. */ + if ( kid != Children.end() ) + { + child_bounds.constrainTo( + (*kid)->getRelativePosition() + ); + } + + // Find out the bounding area of the children + for ( ; kid != Children.end(); kid++ ) + { + // skip the scrollbars + if ( scrollbar_horiz != 0 && *kid == scrollbar_horiz ) + continue; + if ( scrollbar_vert != 0 && *kid == scrollbar_vert ) + continue; + + child_bounds.addInternalPoint( + (*kid)->getRelativePosition().UpperLeftCorner + ); + + child_bounds.addInternalPoint( + (*kid)->getRelativePosition().LowerRightCorner + ); + } + + return child_bounds; +} + + +void divBox::updateScrollBars( bool rescale ) +{ + /* Pre-update check for vertical overflow - allows us to account for it + when making the horizontal scroll bar. */ + if ( child_bounds.getHeight() > AbsoluteRect.getHeight() ) + has_overflow_vert = true; + + updateScrollBarHoriz( rescale ); + updateScrollBarVert( rescale ); +} + + +void divBox::updateScrollBarHoriz( bool rescale ) +{ + // Is a scroll bar needed? + if ( + child_bounds.UpperLeftCorner.X < 0 + || + child_bounds.LowerRightCorner.X + ( has_overflow_vert?SCROLLBAR_WIDTH:0 ) + > + AbsoluteRect.getWidth() + ) + { + // Does a scroll bar need to be made? + if ( scrollbar_horiz == 0 ) + { + scrollbar_horiz = + Environment->addScrollBar( + true, // = horizontal bar + irr::core::recti( + 0, + AbsoluteRect.getHeight() - SCROLLBAR_WIDTH, + AbsoluteRect.getWidth(), + AbsoluteRect.getHeight() + ), + this + /* parent GUI element + - Set to a sub-element because we're going to be drawing + this from an absolute frame of reference + (and we don't want it moved with the other children). + */ + ); + + scrollbar_horiz->setSmallStep( 5 ); + scrollbar_horiz->setLargeStep( 20 ); + + scrollbar_horiz->setSubElement( false ); + + } else { + scrollbar_horiz->setVisible( true ); + + // Re-scale if desired + if ( rescale ) + scrollbar_horiz->setRelativePosition( + irr::core::recti( + 0, + AbsoluteRect.getHeight() - SCROLLBAR_WIDTH, + AbsoluteRect.getWidth(), + AbsoluteRect.getHeight() + ) + ); + } + + has_overflow_horiz = true; + + irr::s32 gap_x; + + /* Checking to see if the scroll bar's maximum will allow us to + show all of the child GUI elements. + Note: we leave room for the vertical bar if it exists. */ + gap_x = child_bounds.LowerRightCorner.X + - AbsoluteRect.getWidth(); + + if ( gap_x > 0 ) + { + /* Setting the maximum value for the scroll bar to be + positioned to. */ + scrollbar_horiz->setMax( + gap_x + ( has_overflow_vert?SCROLLBAR_WIDTH:0 ) + ); + } else { + scrollbar_horiz->setMax( ( has_overflow_vert?SCROLLBAR_WIDTH:0 ) ); + } + + /* Checking to see if the scroll bar's minimum will allow us to + show all of the child GUI elements. */ + gap_x = child_bounds.UpperLeftCorner.X; + + if ( gap_x < 0 ) + { + /* Setting the minimum value for the scroll bar to be + positioned to. */ + scrollbar_horiz->setMin( gap_x ); + } else { + scrollbar_horiz->setMin( 0 ); + } + } + else if ( + child_bounds.UpperLeftCorner.X > 0 + && + child_bounds.LowerRightCorner.X + ( has_overflow_vert?SCROLLBAR_WIDTH:0 ) + < + AbsoluteRect.getWidth() + ) + { + // NO HORIZONTAL SCROLL BAR - elements fit + has_overflow_horiz = false; + + if ( scrollbar_horiz != 0 ) + scrollbar_horiz->setVisible( false ); + } +} + + +void divBox::updateScrollBarVert( bool rescale ) +{ + // Is a scroll bar needed? + if ( + child_bounds.UpperLeftCorner.Y < 0 + || + child_bounds.LowerRightCorner.Y + ( has_overflow_horiz?SCROLLBAR_WIDTH:0 ) + > + AbsoluteRect.getHeight() + ) + { + // Create the vertical scrollbar if it doesn't exist + if ( scrollbar_vert == 0 ) + { + scrollbar_vert = + Environment->addScrollBar( + false, // = vertical bar + irr::core::recti( + AbsoluteRect.getWidth() - SCROLLBAR_WIDTH, + 0, + AbsoluteRect.getWidth(), + AbsoluteRect.getHeight() + + (has_overflow_horiz? -SCROLLBAR_WIDTH:0) + ), + this + /* parent GUI element + - Set to a sub-element because we're going to be drawing + this from an absolute frame of reference + (and we don't want it moved with the other children). + */ + ); + + scrollbar_vert->setSmallStep( 5 ); + scrollbar_vert->setLargeStep( 20 ); + + scrollbar_vert->setSubElement( false ); + + } else { + scrollbar_vert->setVisible( true ); + + // Re-scale if desired + if ( rescale ) + scrollbar_vert->setRelativePosition( + irr::core::recti( + AbsoluteRect.getWidth() - SCROLLBAR_WIDTH, + 0, + AbsoluteRect.getWidth(), + AbsoluteRect.getHeight() + + (has_overflow_horiz? -SCROLLBAR_WIDTH:0) + ) + ); + } + + has_overflow_vert = true; + + irr::s32 gap_y; + + /* Checking to see if the scroll bar's maximum will allow us to + show all of the child GUI elements. + Note: we leave room for the horizontal bar if it exists. */ + gap_y = child_bounds.LowerRightCorner.Y + - AbsoluteRect.getHeight(); + + if ( gap_y > 0 ) + { + /* Setting the maximum value for the scroll bar to be + positioned to. */ + scrollbar_vert->setMax( + gap_y + ( has_overflow_horiz?SCROLLBAR_WIDTH:0 ) + ); + } else { + scrollbar_vert->setMax( ( has_overflow_horiz?SCROLLBAR_WIDTH:0 ) ); + } + + /* Checking to see if the scroll bar's minimum will allow us to + show all of the child GUI elements. */ + gap_y = child_bounds.UpperLeftCorner.Y; + + if ( gap_y < 0 ) + { + /* Setting the minimum value for the scroll bar to be + positioned to. */ + scrollbar_vert->setMin( gap_y ); + } else { + scrollbar_vert->setMin( 0 ); + } + } + else if ( + child_bounds.UpperLeftCorner.Y > 0 + && + child_bounds.LowerRightCorner.Y + ( has_overflow_horiz?SCROLLBAR_WIDTH:0 ) + < + AbsoluteRect.getHeight() + ) + { + // NO VERTICAL SCROLL BAR - elements fit + has_overflow_vert = false; + + if ( scrollbar_vert != 0 ) + scrollbar_vert->setVisible( false ); + } +} + + +/* ****** Overrides of setRelative Position */ + +void divBox::setRelativePosition(const irr::core::recti &r) +{ + IGUIElement::setRelativePosition(r); + + updateScrollBars(true); +} + +void divBox::setRelativePosition(const irr::core::position2di &position) +{ + IGUIElement::setRelativePosition(position); + + updateScrollBars(true); +} + +void divBox::setRelativePositionProportional(const irr::core::rectf &r) +{ + IGUIElement::setRelativePositionProportional(r); + + updateScrollBars(true); +} + +/* ****** END Overrides of setRelative Position */ + + +/* In response to user interaction, divisions do nothing more than +focus the activity on them, indicating that something interacted +with them. It is up to the programmer to decide on their response. +Response can be handled using xplo and GUIfunctionFire. */ +bool divBox::OnEvent( const irr::SEvent& event ) +{ + switch( event.EventType ) + { + case irr::EET_GUI_EVENT: + + switch ( event.GUIEvent.EventType ) + { + case irr::gui::EGET_ELEMENT_FOCUS_LOST: + + if ( event.GUIEvent.Caller == this ) + { + // Bring the scrollbar elements to the front by default + bringScrollsToFront(); + + return false; + } + + break; // end case irr::gui::EGET_ELEMENT_FOCUS_LOST + + case irr::gui::EGET_SCROLL_BAR_CHANGED: + + /* For shifting the location of children when using automatic overflow setting. */ + irr::core::vector2di child_shift; + + /* Identify the shift in location of the GUI element children based + on the scrollbar's bar location. */ + child_shift.X = + (scrollbar_horiz? + scroll_position.X - scrollbar_horiz->getPos() + : 0 ); + + child_shift.Y = + (scrollbar_vert? + scroll_position.Y - scrollbar_vert->getPos() + : 0 ); + + + // Update the scroll position + + if ( scrollbar_horiz ) + scroll_position.X = scrollbar_horiz->getPos(); + + if ( scrollbar_vert ) + scroll_position.Y = scrollbar_vert->getPos(); + + + /* Shift all of the child GUI elements according to the difference + in current scrollbar position from original position. */ + irr::core::list::Iterator kid = Children.begin(); + for ( ; kid != Children.end(); kid++ ) + { + // skip the scrollbars + if ( scrollbar_horiz != 0 && *kid == scrollbar_horiz ) + continue; + if ( scrollbar_vert != 0 && *kid == scrollbar_vert ) + continue; + + (*kid)->move( child_shift ); + } + + break; // end case irr::gui::EGET_SCROLL_BAR_CHANGED + + } + + break; + + case irr::EET_MOUSE_INPUT_EVENT: + + if (event.MouseInput.Event == irr::EMIE_LMOUSE_PRESSED_DOWN) + { + if ( + !AbsoluteClippingRect.isPointInside( + irr::core::position2d( + event.MouseInput.X, event.MouseInput.Y + ) + ) + ) + { + Environment->setFocus(this); + return true; + } + } + break; + + default: break; + } + + return IGUIElement::OnEvent( event ); +} + + +void divBox::serializeAttributes( + irr::io::IAttributes* out, irr::io::SAttributeReadWriteOptions* options + ) const +{ + // ensure the usual details are also accounted for + IGUIElement::serializeAttributes( out, options ); + + out->addEnum( "position", position_type, HTMLPosTypes ); + + /* + if ( background_image != 0 ) + out->addTexture("background-image", background_image ); + */ + if ( background_image != 0 ) + out->addString( "background-image", background_image_name.c_str() ); + + out->addEnum( "background-repeat",background_image_repeat, + HTMLBackgroundImageRepeatTypes); + + + out->addColor( "background-color", background_color ); + out->addColor( "outline-color", border_color ); + out->addBool( "Border", ifDrawBorder ); + + out->addEnum( "overflow", overflow, WebOverflowTypes ); + + out->addInt( "scrollbar-width", SCROLLBAR_WIDTH ); +} + + +void divBox::deserializeAttributes( + irr::io::IAttributes *in, + irr::io::SAttributeReadWriteOptions *options + ) +{ + // Have the superclass handle the rest of the deserialization + IGUIElement::deserializeAttributes( in, options ); + + // Set the position/layout type + position_type = (HTMLPos) in->getAttributeAsEnumeration( "position", HTMLPosTypes ); + + // Get the background image + //background_image = in->getAttributeAsTexture( "background-image" ); + setImage( (irr::io::path) in->getAttributeAsString( "background-image" ) ); + + // Get the background image repetition style + background_image_repeat = + (HTMLBackgroundImageRepeat) + in->getAttributeAsEnumeration( "background-repeat", HTMLBackgroundImageRepeatTypes ); + + // Get the background color + irr::s32 color_attr = in->findAttribute( "background-color" ); + if ( color_attr != -1 ) + background_color = in->getAttributeAsColor( color_attr ); + + // Identify if there should be an outline + if ( in->existsAttribute( "Border" ) && in->getAttributeAsBool( "Border" ) ) + { + ifDrawBorder = true; + border_color = in->getAttributeAsColor( "outline-color" ); + } + + // Set the overflow + overflow = (HTMLOverflow) in->getAttributeAsEnumeration( "overflow", WebOverflowTypes ); + + if ( in->existsAttribute( "scrollbar-width" ) ) + SCROLLBAR_WIDTH = in->getAttributeAsInt( "scrollbar-width" ); +} + + + +irr::gui::IGUIElement* addDivBox( + irr::gui::IGUIEnvironment * env, + irr::gui::IGUIElement * parent = 0, + irr::s32 id=-1 + ) +{ + divBox* box; + + if ( parent == 0 ) + box = new divBox( env, env->getRootGUIElement(), id ); + else + box = new divBox( env, parent, id ); + + box->drop(); + + return (irr::gui::IGUIElement*)box; +} diff --git a/IrrExtensions/misc/XPLOsion/XPLOsion/sdiv.h b/IrrExtensions/misc/XPLOsion/XPLOsion/sdiv.h new file mode 100755 index 0000000..25a818f --- /dev/null +++ b/IrrExtensions/misc/XPLOsion/XPLOsion/sdiv.h @@ -0,0 +1,267 @@ +/* +Created by Nicolaus Anderson, 2012 + +For license terms, see xplo.h +*/ + +#include +#include "WebTypes.h" + +#ifndef _INCLUDE_SDIV_H_ +#define _INCLUDE_SDIV_H_ + +//! Div class +/* +This class is a GUI box that acts like an HTML division. +It contains information for hiding its child GUI elements. +If its overflow is set to auto, scrollbars are generated +for moving the content within the clipping rectangle of this box. +*/ +class divBox : public irr::gui::IGUIElement, public HTMLPositioned +{ +protected: + //irr::video::ITexture * background_image; + irr::video::IImage* background_image; + irr::core::recti* image_box; + irr::core::stringc background_image_name; + HTMLBackgroundImageRepeat background_image_repeat; + + /* Overflow */ + HTMLOverflow overflow; + + /* Background colors */ + irr::video::SColor background_color; + irr::video::SColor border_color; + bool ifDrawBorder; + + /* For handling scrolling when overflow is set to auto. */ + bool has_overflow_horiz; + bool has_overflow_vert; + irr::core::vector2di scroll_position; // last position of the scroll bar + irr::gui::IGUIScrollBar* scrollbar_horiz; + irr::gui::IGUIScrollBar* scrollbar_vert; + + irr::s32 SCROLLBAR_WIDTH; + + /* Region occupied by the child GUI elements. */ + irr::core::recti child_bounds; + +public: + + //! Constructor + /**/ + divBox( + irr::gui::IGUIEnvironment * env, + irr::gui::IGUIElement * parent = 0, + irr::s32 id=-1 ) + : IGUIElement( + irr::gui::EGUIET_ELEMENT, + env, + parent, id, + irr::core::recti(0,0,0,0) + ) + { +#ifdef _DEBUG + setDebugName( "divBox" ); +#endif + + background_image = 0; + background_color = irr::video::SColor( 0,0,0,0 ); + border_color = irr::video::SColor( 255,0,0,0 ); + ifDrawBorder = false; + image_box = 0; + + position_type = HTM_POS_static; + overflow = HTM_OVF_show; + + has_overflow_horiz = false; + has_overflow_vert = false; + scrollbar_horiz = 0; + scrollbar_vert = 0; + + SCROLLBAR_WIDTH = 20; + } + + //! Destructor + /**/ + ~divBox() + { + if ( scrollbar_horiz != 0 ) + scrollbar_horiz->drop(); + + if ( scrollbar_vert != 0 ) + scrollbar_vert->drop(); + + if ( background_image != 0 ) + background_image->drop(); + } + + //! Get GUI element type name + /* Override of the IGUIElement function getTypeName(). */ + const irr::c8* getTypeName() const + { + return "divBox"; + } + + //! Hides overflow + /* Indicates if the overflow of this division is hidden. */ + bool hidesOverflow(); + + //! Show the overflow + /* Allows that which extends outside of this division's bounding box to be shown. */ + void overflowShow(); + + //! Hide the overflow + /* Hides that which extends outside of this division's bounding box. */ + void overflowHide(); + + //! Automatically handle the overflow + /* Creates scroll bars to display this division's contents if it extends outside + of this division's bounding box. */ + void overflowAuto(); + + //! Convert overflow parameter to string + /* Converts this div's current HTMLOverflow value to a string and returns it. */ + irr::core::stringc convertOverFlowToString() const; + + //! Get overflow + /* Returns the HTMLOverflow value of this div */ + HTMLOverflow getOverflow(); + + //! Set overflow + /* Saves the overflow value to this div */ + void setOverflow( HTMLOverflow new_overflow ); + + //! Set overflow + /* Converts a string to an HTMLOverflow value */ + void setOverflowFromString( const irr::c8* overflow_val ); + + //! Set background color + /* Sets the default color that will be drawn for this GUI element when it + is displayed. */ + void setBackgroundColor( irr::video::SColor color ) { background_color = color; } + + //! Set background image + /* Loads the image from the given path and assigns it to this GUI element + as its background image. */ + void setImage( irr::io::path filename ); + + //! Set draw outline + /* Determines if an outline should be draw around the division. + Note: Current implementation calls for outlines to be drawn under + anything attached to the edge rather than pushing it inward. */ + void setDrawBorder( bool draw=false ); + + //! Set border color + /* Assigns the color of the border to draw. */ + void setBorderColor( irr::video::SColor color ); + + //! Set scrollbar width + /* Sets the width of the scroll bars, vertical or horizontal. */ + void setScrollBarWidth( irr::s32 width ); + + //! Get scroll position + /* Useful for outside this class if the programmer wants to draw + other things based on this element's scrollbars' positions. */ + irr::core::vector2di getScrollBorPositions(); + + //! Draw + /* For drawing the division. This is called by the engine. */ + virtual void draw(); + + //! Set relative position + /* Simple override of IGUIElement::setRelativePosition but also calls for + the updating of the scroll bars. */ + void setRelativePosition(const irr::core::recti &r); + void setRelativePosition(const irr::core::position2di &position); + + //! Set relative position + /* Simple override of IGUIElement::setRelativePosition but also calls for + the updating of the scroll bars. */ + void setRelativePositionProportional(const irr::core::rectf &r); + + //! On Event + /* Response to user input events. */ + virtual bool OnEvent( const irr::SEvent& event ); + + //! Serialize + /* For obtaining the key attributes of this GUI element. */ + virtual void serializeAttributes( + irr::io::IAttributes* out, irr::io::SAttributeReadWriteOptions* options=0 + ) const; + + //! Deserialize + /* For defining this GUI element from key attributes. */ + virtual void deserializeAttributes( + irr::io::IAttributes* in, irr::io::SAttributeReadWriteOptions* options=0 + ); + + + /* Override methods from IGUIElement and methods for keeping the scroll bars + properly adjusted. */ + + //! Add Child + /* Handles adding a child to this GUI element. */ + void addChild(irr::gui::IGUIElement* child); + + //! Add Child + /* Handles adding a child to this GUI element. */ + void addChildToEnd(IGUIElement* child); + + //! Remove Child + /* Handles removing a child from this GUI element. */ + void removeChild(irr::gui::IGUIElement* child); + + //! Reset child bounds + /* Resets the child bounds in case changes were made (e.g. GUI elements + were made the children of this without it knowing). */ + void resetChildBounds(); + + //! Reset child positions + /* Resets the position of the children and the scrollbars. */ + void resetChildPositions(); + + //! Bring scrollbars to front + /* Make the scroll bars the first thing that will respond on user + interaction. */ + void bringScrollsToFront(); + +protected: + + //! Draw children + /* Handles drawing he children and generating scrollbars if necessary. */ + virtual void drawChildren(); + + //! Get child GUI element boundaries + /* Returns a rectangle that encompasses all of the child GUI elements. */ + // Recommend this be added to the irrlicht engine. + irr::core::recti getChildBounds(); + + //! Update scroll bars + /* Updates the scroll bar positions. */ + void updateScrollBars( bool rescale ); + + //! Update horizontal scroll bar + /* Creates the scroll bar if necessary. Otherwise it simply udates its + size and location information. This is necessary if the division is + going to be resized. */ + void updateScrollBarHoriz( bool rescale ); + + //! Update vertical scroll bar + /* Creates the scroll bar if necessary. Otherwise it simply udates its + size and location information. This is necessary if the division is + going to be resized. */ + void updateScrollBarVert( bool rescale ); +}; + + +//! Div Generator +/* Creates an instance of divBox, assigning it to the root GUI +element if no parent was given. */ +irr::gui::IGUIElement* addDivBox( + irr::gui::IGUIEnvironment*, + irr::gui::IGUIElement*, + irr::s32 + ); + +#endif // ifndef _INCLUDE_SDIV_H_ \ No newline at end of file diff --git a/IrrExtensions/misc/XPLOsion/XPLOsion/xplo.cpp b/IrrExtensions/misc/XPLOsion/XPLOsion/xplo.cpp new file mode 100755 index 0000000..708ec76 --- /dev/null +++ b/IrrExtensions/misc/XPLOsion/XPLOsion/xplo.cpp @@ -0,0 +1,894 @@ +/* +Created by Nicolaus Anderson, 2012 + +For license terms, see xplo.h +*/ + +#include "xplo.h" + +#pragma once + + +void XPLOsion::TranslateNode( + irrXMLTreeNode* node, irr::gui::IGUIElement* parent, bool clipped + ) +{ + irrXMLTreeElement* elem = (irrXMLTreeElement*) node->element; + irr::gui::IGUIElement* gui_element; + + // empty default bounding box + irr::core::recti box; + + + // text division ========================== + if ( elem->getNodeName() == "text" ) + { + // GUI element creation + gui_element = + irrdev->getGUIEnvironment()->addStaticText( + // default values are irrelevant + L" ", box, false, true, + parent, // parent GUI element - NOT irrelevant + 0, true + ); + + // Interpret the parameters of this xml node and apply them + applyAttributesToElement( node, gui_element ); + + // Clipping necessary? + gui_element->setNotClipped( !clipped ); + + // Append the real text if it exists + elem->getNodeText().trim(); + if ( elem->getNodeText().size() != 0 ) + { + gui_element->setText( + irr::core::stringw( elem->getNodeText() ).c_str() + ); + } + + /* For now, text boxes don't have children, so these operations are done. + This may change if I decide I want fancy trim around my text boxes and + I can get that to even work. */ + + return; + } + + // regular division ========================= + else if ( elem->getNodeName() == "div" ) + { + gui_element = new divBox( irrdev->getGUIEnvironment(), parent, 0 ); + + // Interpret the parameters of this xml node and apply them + applyAttributesToElement( node, gui_element, true ); + + // Clipping necessary? + gui_element->setNotClipped( !clipped ); + + // Translate the children of this node since divs can contain stuff + for ( irr::s32 c = 0; c < (s32)(node->children.size()); c++ ) + { + TranslateNode( + (irrXMLTreeNode *)node->get(c), + gui_element, + ((divBox*)gui_element)->hidesOverflow() + ); + } + + ((divBox*)gui_element)->resetChildBounds(); + + // Ensure that the scroll bars start out on top + ((divBox*)gui_element)->bringScrollsToFront(); + + return; + } + + // empty element ========================== + else if ( elem->getNodeName() == "basic" ) { + gui_element = new IGUIElement( irrdev->getGUIEnvironment(), irrdev->getGUIEnvironment()->getRootGUIElement() ); + + // Interpret the parameters of this xml node and apply them + applyAttributesToElement( node, gui_element ); + + // Clipping necessary? + gui_element->setNotClipped( !clipped ); + + /* Translate the child nodes. */ + for ( irr::s32 c = 0; c < (s32)(node->children.size()); c++ ) + { + TranslateNode( + (irrXMLTreeNode*)node->get(c), + gui_element, + true // hides overflow + ); + } + + return; + } + + // button ================================== + else if ( elem->getNodeName() == "button" ) + { + gui_element = + irrdev->getGUIEnvironment()->addButton( + box, // bounding box - irrelevant + parent, // parent GUI element + 0 + ); + + // Interpret the parameters of this xml node and apply them + applyAttributesToElement( node, gui_element, true ); + + // Clipping necessary? + gui_element->setNotClipped( !clipped ); + + /* Translate the children of this node since buttons can contain stuff. + Like what? - Perhaps we want multiple images on a button or some kind + of animated trim. */ + for ( irr::s32 c = 0; c < (s32)(node->children.size()); c++ ) + { + TranslateNode( + (irrXMLTreeNode *)node->get(c), + gui_element, + true // hides overflow + ); + } + + return; + } + + // text edit box ========================== + else if ( elem->getNodeName() == "editbox" ) + { + gui_element = + irrdev->getGUIEnvironment()->addEditBox( + L" ", box, true, // irrelevant + parent, // parent GUI element - NOT irrelevant + 0 + ); + + // Interpret the parameters of this xml node and apply them + applyAttributesToElement( node, gui_element ); + + // Clipping necessary? + gui_element->setNotClipped( !clipped ); + + /* Translate the children of this node since editboxes can contain stuff. + Like what? - Perhaps we want a marker to follow the mouse around + whenever it moves within the text box. */ + for ( irr::s32 c = 0; c < (s32)(node->children.size()); c++ ) + { + TranslateNode( + (irrXMLTreeNode *)node->get(c), + gui_element, + true // hides overflow + ); + } + + return; + } + + // window within the main window ========================== + else if ( elem->getNodeName() == "window" ) + { + gui_element = + irrdev->getGUIEnvironment()->addWindow( + box, false, 0, + parent, // parent GUI element - NOT irrelevant + 0 + ); + + // Interpret the parameters of this xml node and apply them + applyAttributesToElement( node, gui_element ); + + // Clipping necessary? + gui_element->setNotClipped( !clipped ); + + // Translate the children of this node since windows can contain stuff + for ( irr::s32 c = 0; c < (s32)(node->children.size()); c++ ) + { + TranslateNode( + (irrXMLTreeNode *)node->get(c), + gui_element, + true // hides overflow + ); + } + + return; + } + + // context menu ========================== + else if ( elem->getNodeName() == "menu" ) + { + gui_element = + irrdev->getGUIEnvironment()->addContextMenu( + box, // bounding box - irrelevant + parent, // parent GUI element - NOT irrelevant + 0 + ); + + // Interpret the parameters of this xml node and apply them + applyAttributesToElement( node, gui_element ); + + // Clipping necessary? + gui_element->setNotClipped( !clipped ); + + // Translate the children of this node since menus can contain stuff + for ( irr::s32 c = 0; c < (s32)(node->children.size()); c++ ) + { + TranslateNode( + (irrXMLTreeNode*)node->get(c), + gui_element, + true // hides overflow + ); + } + + return; + } + + // menu bar ========================== + else if ( elem->getNodeName() == "menubar" ) + { + gui_element = + irrdev->getGUIEnvironment()->addMenu( + parent, // parent GUI element - NOT irrelevant + 0 + ); + + // Interpret the parameters of this xml node and apply them + applyAttributesToElement( node, gui_element ); + + // Clipping necessary? + gui_element->setNotClipped( !clipped ); + + // Translate the children of this node since menubars can contain stuff + for ( irr::s32 c = 0; c < (s32)(node->children.size()); c++ ) + { + TranslateNode( + (irrXMLTreeNode*)node->get(c), + gui_element, + true // hides overflow + ); + } + + return; + } + + // message box ========================== + else if ( elem->getNodeName() == "messagebox" ) + { + gui_element = + irrdev->getGUIEnvironment()->addMessageBox( + L" ", L" ", false, 0, // irrelevant + parent, // parent GUI element - NOT irrelevant + 0, 0 + ); + + // Interpret the parameters of this xml node and apply them + applyAttributesToElement( node, gui_element ); + + // Clipping necessary? + gui_element->setNotClipped( !clipped ); + + // Translate the children of this node since menubars can contain stuff + for ( irr::s32 c = 0; c < (s32)(node->children.size()); c++ ) + { + TranslateNode( + (irrXMLTreeNode*)node->get(c), + gui_element, + true // hides overflow + ); + } + + return; + } + + // modal screen ========================== + else if ( elem->getNodeName() == "modalscreen" ) + { + gui_element = + irrdev->getGUIEnvironment()->addModalScreen( parent ); + + // Interpret the parameters of this xml node and apply them + applyAttributesToElement( node, gui_element ); + + // Clipping necessary? + gui_element->setNotClipped( !clipped ); + + // Translate the children of this node since modalscreens can contain stuff + for ( irr::s32 c = 0; c < (s32)(node->children.size()); c++ ) + { + TranslateNode( + (irrXMLTreeNode*)node->get(c), + gui_element, + true // hides overflow + ); + } + + return; + } + + // mesh viewer ========================== + else if ( elem->getNodeName() == "meshviewer" ) + { + gui_element = + irrdev->getGUIEnvironment()->addMeshViewer( + box, // irrelevant + parent, // parent GUI element - NOT irrelevant + 0, 0 + ); + + // Interpret the parameters of this xml node and apply them + applyAttributesToElement( node, gui_element ); + + // Clipping necessary? + gui_element->setNotClipped( !clipped ); + + /* Translate the children of this node since mesh viewers can contain stuff. + Contain what? - Toolbars, perhaps for modelling and animation. + */ + for ( irr::s32 c = 0; c < (s32)(node->children.size()); c++ ) + { + TranslateNode( + (irrXMLTreeNode*)node->get(c), + gui_element, + true // hides overflow + ); + } + + return; + } + + // checkbox ========================== + else if ( elem->getNodeName() == "checkbox" ) + { + gui_element = + irrdev->getGUIEnvironment()->addCheckBox( + false, box, // irrelevant + parent, // parent GUI element - NOT irrelevant + 0, 0 + ); + + // Interpret the parameters of this xml node and apply them + applyAttributesToElement( node, gui_element ); + + // Clipping necessary? + gui_element->setNotClipped( !clipped ); + + // Check boxes shouldn't have children, so these operations are done + // Why would you give a checkbox child GUI elements? + + return; + } + + // combobox ========================== + else if ( elem->getNodeName() == "combobox" ) + { + gui_element = + irrdev->getGUIEnvironment()->addComboBox( + box, // irrelevant + parent, // parent GUI element - NOT irrelevant + 0 + ); + + // Interpret the parameters of this xml node and apply them + applyAttributesToElement( node, gui_element ); + + // Clipping necessary? + gui_element->setNotClipped( !clipped ); + + /* Translate the children of this node since mesh viewers can contain stuff. + Contain what? - Toolbars, perhaps for modelling and animation. + */ + for ( irr::s32 c = 0; c < (s32)(node->children.size()); c++ ) + { + TranslateNode( + (irrXMLTreeNode*)node->get(c), + gui_element, + true // hides overflow + ); + } + + return; + } + + // file open dialog box ========================== + else if ( elem->getNodeName() == "fileopendialog" ) + { + gui_element = + irrdev->getGUIEnvironment()->addFileOpenDialog( + L"Open file", // title + true, + parent, // parent GUI element - NOT irrelevant + 0 + ); + + /* File open dialog boxes establish their own boundaries, + hence it isn't worth loading a bounding box for them. + + Furthermore, file open dialog boxes do not draw children, + so it isn't necessary to try to add any. */ + + return; + } + + // list box ========================== + else if ( elem->getNodeName() == "listbox" ) + { + gui_element = + irrdev->getGUIEnvironment()->addListBox( + box, + parent, // parent GUI element - NOT irrelevant + 0, false + ); + + // Interpret the parameters of this xml node and apply them + applyAttributesToElement( node, gui_element ); + + // Clipping necessary? + gui_element->setNotClipped( !clipped ); + + /* Translate the children of this node since listboxs can contain stuff. + Like what? - Perhaps something to follow the mouse around while it is + in the box. */ + for ( irr::s32 c = 0; c < (s32)(node->children.size()); c++ ) + { + TranslateNode( + (irrXMLTreeNode*)node->get(c), + gui_element, + true // hides overflow + ); + } + + return; + } + + // color select dialog ========================== + else if ( elem->getNodeName() == "colorselectdialog" ) + { + gui_element = + irrdev->getGUIEnvironment()->addColorSelectDialog( + L"Select color", // title + true, + parent, // parent GUI element - NOT irrelevant + 0 + ); + + /* Color select dialog boxes establish their own boundaries + by trying to fit within their parent GUI element, + hence it isn't worth loading a bounding box for them. + + Furthermore, file open dialog boxes do not draw children, + so it isn't necessary to try to add any. */ + + return; + } + + // in out fader ========================== + else if ( elem->getNodeName() == "inoutfader" ) + { + gui_element = + irrdev->getGUIEnvironment()->addInOutFader( + &box, + parent, // parent GUI element - NOT irrelevant + 0 + ); + + // Interpret the parameters of this xml node and apply them + applyAttributesToElement( node, gui_element ); + + // Clipping necessary? + gui_element->setNotClipped( !clipped ); + + /* Translate the children of this node since in out faders can contain + stuff. + Like what? - Perhaps something to follow the mouse around while it is + in the box. */ + for ( irr::s32 c = 0; c < (s32)(node->children.size()); c++ ) + { + TranslateNode( + (irrXMLTreeNode*)node->get(c), + gui_element, + true // hides overflow + ); + } + + return; + } + + // scroll bars ========================== + else if ( elem->getNodeName() == "scrollbar" ) + { + gui_element = + irrdev->getGUIEnvironment()->addScrollBar( + true, box, + parent, // parent GUI element - NOT irrelevant + 0 + ); + + // Interpret the parameters of this xml node and apply them + applyAttributesToElement( node, gui_element ); + + // Clipping necessary? + gui_element->setNotClipped( !clipped ); + + /* Translate the child nodes. */ + for ( irr::s32 c = 0; c < (s32)(node->children.size()); c++ ) + { + TranslateNode( + (irrXMLTreeNode*)node->get(c), + gui_element, + true // hides overflow + ); + } + + return; + } + + // sliders ========================== + else if ( elem->getNodeName() == "slider" ) + { + gui_element = + new irr::gui::CSlider( + false, + irrdev->getGUIEnvironment(), + parent, // parent GUI element - NOT irrelevant + 0, box, false + ); + + // Interpret the parameters of this xml node and apply them + applyAttributesToElement( node, gui_element ); + + // Clipping necessary? + gui_element->setNotClipped( !clipped ); + + /* Translate the child nodes. */ + for ( irr::s32 c = 0; c < (s32)(node->children.size()); c++ ) + { + TranslateNode( + (irrXMLTreeNode*)node->get(c), + gui_element, + true // hides overflow + ); + } + + return; + } + + // tab control ========================== + else if ( elem->getNodeName() == "tabcontrol" ) + { + gui_element = + irrdev->getGUIEnvironment()->addTabControl( + box, + parent, // parent GUI element - NOT irrelevant + false, true, 0 ); + + // Interpret the parameters of this xml node and apply them + applyAttributesToElement( node, gui_element ); + + // Clipping necessary? + gui_element->setNotClipped( !clipped ); + + /* Translate the child nodes. */ + for ( irr::s32 c = 0; c < (s32)(node->children.size()); c++ ) + { + TranslateNode( + (irrXMLTreeNode*)node->get(c), + gui_element, + true // hides overflow + ); + } + + return; + } + + // tab ========================== + else if ( elem->getNodeName() == "tab" ) + { + if ( parent->getType() == irr::gui::EGUIET_TAB_CONTROL ) + { + //gui_element = + //irrdev->getGUIEnvironment()->addTab( box, 0, 0 ); + gui_element = ((irr::gui::IGUITabControl*)parent)->addTab( L" " ); + } else { + gui_element = + irrdev->getGUIEnvironment()->addTab( + box, + parent, // parent GUI element - NOT irrelevant + 0 ); + } + + // Interpret the parameters of this xml node and apply them + applyAttributesToElement( node, gui_element ); + + // Clipping necessary? + gui_element->setNotClipped( !clipped ); + + /* Translate the child nodes. */ + for ( irr::s32 c = 0; c < (s32)(node->children.size()); c++ ) + { + TranslateNode( + (irrXMLTreeNode*)node->get(c), + gui_element, + true // hides overflow + ); + } + + return; + } + + // spinbox ========================== + else if ( elem->getNodeName() == "spinbox" ) + { + gui_element = + irrdev->getGUIEnvironment()->addSpinBox( + L"", box, true, + parent, // parent GUI element - NOT irrelevant + 0 ); + + // Interpret the parameters of this xml node and apply them + applyAttributesToElement( node, gui_element ); + + // Clipping necessary? + gui_element->setNotClipped( !clipped ); + + /* Translate the child nodes. */ + for ( irr::s32 c = 0; c < (s32)(node->children.size()); c++ ) + { + TranslateNode( + (irrXMLTreeNode*)node->get(c), + gui_element, + true // hides overflow + ); + } + + return; + } + + /*************** OTHER TYPES *************** + Define other GUI elements here, perhaps your own custom GUI element. + Be sure to add the necessary attribute loaders to the CSSLibraryImpl.cpp + function CSSLibrary::interpretStyling(). + */ + + // if ( elem->getNodeName() == my_element_name ) + //{ + + // return; + //} + + // Registered GUI Factories ========================== + /* Use the registered GUI Factories to create the element. */ + //bool inFactory = false; + + u32 factory_count = + irrdev->getGUIEnvironment()->getRegisteredGUIElementFactoryCount(); + + gui::IGUIElementFactory* factory; + + /* Brute-force trying to create the GUI element by testing out + every GUI element factory. */ + u32 fac = 0; + for (; fac < factory_count; fac++ ) + { + factory = irrdev->getGUIEnvironment()->getGUIElementFactory(fac); + + gui_element = + factory->addGUIElement( elem->getNodeName().c_str(), parent ); + + if ( gui_element == 0 ) + continue; + + // Interpret the parameters of this xml node and apply them + applyAttributesToElement( node, gui_element ); + + // Clipping necessary? + gui_element->setNotClipped( !clipped ); + + // This factory was able to create the GUI element + if ( gui_element ) + { + // Translate the children + for ( irr::s32 c = 0; c < (s32)(node->children.size()); c++ ) + { + TranslateNode( + (irrXMLTreeNode*)node->get(c), + parent, + false + ); + } + + return; + } + } + + // unknown type =========================== + //if ( !inFactory ) + //{ + // Translate the children + for ( irr::s32 c = 0; c < (s32)(node->children.size()); c++ ) + { + TranslateNode( + (irrXMLTreeNode*)node->get(c), + parent, + false + ); + } + //} +} + + +void XPLOsion::applyAttributesToElement( + irrXMLTreeNode* node, + irr::gui::IGUIElement* gui_element, + bool checkIfPressable + ) +{ + Linus::LListix applied_styles; + //irr::core::array applied_styles; + + /* A hint for the GUIElemAcceptor to identify if they should save + a pointer to the specific GUI element. */ + irr::core::stringc BindHint; + + + obtainAttributesForElement( node, &applied_styles, checkIfPressable ); + + /* Remove duplicate styles */ + for ( irr::s32 i1 = 0; i1 < applied_styles.Size() - 1; i1++ ) + { + for ( irr::s32 i2 = i1 + 1; i2 < applied_styles.Size(); i2++ ) + { + if ( applied_styles[i1] == applied_styles[i2] ) + //applied_styles.erase( i2 ); + applied_styles.deleteAt( i2 ); + } + } + + // Stacking CSS styles ********************** + + if ( applied_styles.Size() > 0 ) + { + applied_styles[0]->setParentTag( 0 ); + + for ( irr::s32 index = 1; index < applied_styles.Size(); index++ ) + { + applied_styles[index]->setParentTag( applied_styles[index-1] ); + } + + csslib->applyStyleToGUIElement( + gui_element, + applied_styles[applied_styles.Size()-1] + ); + + /* Obtain binding hints + - Allows us to link GUI elements with classes that use their + values but aren't dependent on their styling. */ + for ( irr::s32 st = applied_styles.Size()-1; st >= 0; st-- ) + { + BindHint = applied_styles[st]->getAttributeAsString( "BindHint" ); + + if ( BindHint.size() > 0 ) + { + for ( irr::u32 gea = 0; gea < acceptors.size(); gea++ ) + { + /* Offer the GUI element to the class to bind it. + The element is offered to all classes, regardless if one + accepts it, since multiple classes may use the same + GUI element. */ + acceptors[gea]->bind( gui_element, BindHint.c_str() ); + } + + break; + } + } + } + + /* Override blank ID if one was not acquired from the styling. */ + if( gui_element->getID() == 0 ) + { + gui_element->setID( autoID++ ); + } +} + + +void XPLOsion::obtainAttributesForElement( + irrXMLTreeNode* node, + Linus::LListix* applied_styles, + bool checkIfPressable + ) +{ + irrXMLTreeNode *node_b; + irrXMLTreeElement * elem; + irr::s32 csstag_index = 0; // used for finding if the tag exists + + irr::s32 num_attrs = 0; + + + /* We're going to use node_b for checking parent nodes for CSS data, + and that way we don't lose track of what's in "node". */ + node_b = node; + + + /* styleInheritance will determine whether or not to inherit parent + GUI element's CSS data, and thus determine if this loop of grabbing + XML data will continue. */ + do { + elem = (irrXMLTreeElement *) node_b->element; + + /* Error: incorrect type */ + if ( elem->getNodeType() != irr::io::EXN_ELEMENT ) + break; + + /* Determine the attributes of the GUI element based on what's in the node's + attributes list. */ + num_attrs = elem->numAttributes() - 1; + for( irr::s32 a = num_attrs; a >= 0; a-- ) + { + if ( elem->getAttrName(a) == "class" ) + { + // Set the shape of this element based on its class style + csstag_index = csslib->findCSSClass( elem->getAttrValue(a) ); + + if ( csstag_index != -1 ) + { + applied_styles->push_front( + csslib->getCSSStyle(csstag_index) + ); + } + } + else if ( elem->getAttrName(a) == "id" ) + { + // Set the shape of this element based on its ID style + csstag_index = csslib->findCSSID( elem->getAttrValue(a) ); + + if ( csstag_index != -1 ) + { + applied_styles->push_front( + csslib->getCSSStyle(csstag_index) + ); + } + } + else if ( elem->getAttrName(a) == "style" ) + { + // Request that the style be interpreted in-place + applied_styles->push_front( + csslib->interpretStyling( elem->getAttrValue(a) ) + ); + } + else if ( + elem->getAttrName(a) == "width" + || elem->getAttrName(a) == "height" + ) + { + applied_styles->push_front( + csslib->interpretStyling( + elem->getAttrName(a) + ":" + elem->getAttrValue(a) + ";" + ) + ); + } else // get a random CSS node + { + // Set the shape of this element based on a random style whose name matches + csstag_index = csslib->findCSSRandom( elem->getAttrValue(a) ); + + if ( csstag_index != -1 ) + { + applied_styles->push_front( + csslib->getCSSStyle(csstag_index) + ); + } + } + } + + /* if styleInheritance is true, this will be relevant */ + if ( node_b->getParent() != 0 ) + { + node_b = (irrXMLTreeNode*)node_b->getParent(); + } else { + break; // Normal loop termination when inheritance desired - no parent + } + + // Normal loop termination when inheritance not desired + } while ( styleInheritance ); +} diff --git a/IrrExtensions/misc/XPLOsion/XPLOsion/xplo.h b/IrrExtensions/misc/XPLOsion/XPLOsion/xplo.h new file mode 100755 index 0000000..54b00b5 --- /dev/null +++ b/IrrExtensions/misc/XPLOsion/XPLOsion/xplo.h @@ -0,0 +1,332 @@ +/* +Project name: +XML Program Layout arranger ("XPLOsion") + +Created by Nicolaus Anderson, 2012 + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + Since this uses the irrlicht engine, see irrlicht.h for more details. + +Purpose: +To allow you to design your program using a type of xml format +similar to html. +The layout of the program's interface is to be done in an XML format. +The unique attributes of the GUI elements are to be done in a CSS-style format. + +List of some recognized tags: + +
+ + + + + +
+ + + + + + + + + +
diff --git a/IrrExtensions/misc/XPLOsion/test files/testgui2.xml b/IrrExtensions/misc/XPLOsion/test files/testgui2.xml new file mode 100755 index 0000000..e2f56d1 --- /dev/null +++ b/IrrExtensions/misc/XPLOsion/test files/testgui2.xml @@ -0,0 +1,47 @@ + +
+ +
+
+
+ +
+
+ + +
+
+
+
+
+
+
+ +
+ + + + \ No newline at end of file diff --git a/IrrExtensions/misc/XPLOsion/test files/testgui3.xml b/IrrExtensions/misc/XPLOsion/test files/testgui3.xml new file mode 100755 index 0000000..da9cb14 --- /dev/null +++ b/IrrExtensions/misc/XPLOsion/test files/testgui3.xml @@ -0,0 +1,18 @@ + +
+ + + + + + + + + + + + + +
+ + \ No newline at end of file diff --git a/IrrExtensions/misc/XPLOsion/test files/testgui4.xml b/IrrExtensions/misc/XPLOsion/test files/testgui4.xml new file mode 100755 index 0000000..eceb3e1 --- /dev/null +++ b/IrrExtensions/misc/XPLOsion/test files/testgui4.xml @@ -0,0 +1,12 @@ + + +
+ + + This is some text + + + More text + + +
\ No newline at end of file diff --git a/IrrExtensions/misc/XPLOsion/test files/testgui5.xml b/IrrExtensions/misc/XPLOsion/test files/testgui5.xml new file mode 100755 index 0000000..74a9e5c --- /dev/null +++ b/IrrExtensions/misc/XPLOsion/test files/testgui5.xml @@ -0,0 +1 @@ +
\ No newline at end of file diff --git a/IrrExtensions/misc/XPLOsion/utilities/LinusListix - original.h b/IrrExtensions/misc/XPLOsion/utilities/LinusListix - original.h new file mode 100755 index 0000000..0aefe7e --- /dev/null +++ b/IrrExtensions/misc/XPLOsion/utilities/LinusListix - original.h @@ -0,0 +1,1049 @@ +/* +This is the header file for LinusListix +(C) Nic Anderson +Date created: Oct 2, 2011 +Updated: +Mar 21 & 27, 2012 +April 19, 2012 +July 2, 2012 +Aug 17, 2013 +Aug 14, 2014 + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + +*/ + +// don't double define this + // if "_LinusListix_INCLUDED" is not defined... +#ifndef _LinusListix_INCLUDED_ + // ... define it (and define the namespace as well) +#define _LinusListix_INCLUDED_ + + +// simple definitions for usage with this list +#ifdef _MSC_VER +typedef __int32 si32; +#else +typedef signed int si32; +#endif + + +// namespace containing the list +namespace Linus +{ + template + class LListixNode + { + public: + LListixNode * Prior; // the node preceding this one + LListixNode * Post; // the node after this one + + Unk ThisItem; // the item stored here + + //! constructor + LListixNode( Unk item ) + { + ThisItem = item; + } + + //! deconstructor + void deleteMe() { delete this; } + }; + + template + class LListix + { + private: + si32 size; // list size + LListixNode * HeadNode; // leading node + LListixNode * TailNode; // last node + + /* Node between the leading and the last node + - in the region being used the most - i.e., the grabber. */ + LListixNode * Midix; + si32 midixL; // location of Midix - the node being pointed to by Midix + + /* For setIterableMethod function - capability of applying a + function to the list objects. */ + /* Pointer to a function whose operations are applied to + list items. */ + bool (*Function)(Unk * listItem, si32 index, void* external); + bool hasFunc; // Indicates if a function has been saved to the pointer + si32 FunctionID; // In case you wanted to identify the function you saved here + + public: + + //! Constructor + LListix() + : size(0) + , midixL(0) + , hasFunc(false) + { + } + + //! Constructor + LListix(LListix& other) + : size(0) + , midixL(0) + , hasFunc(false) + { + Unk item; + other.resetMidix(); + if ( other.Size() > 0 ) { + item = other[0]; + push_back(item); + } + while ( !other.iter(true,&item) ) + { + push_back(item); + } + if ( other.hasFunc ) + { + Function = other.Function; + FunctionID = other.FunctionID; + hasFunc = true; + } + } + + //! Destructor + ~LListix() + { this->clear(); } + + // Operator = + LListix& operator= ( LListix& other ) + { + Unk item; + other.resetMidix(); + if ( other.Size() > 0 ) { + item = other[0]; + push_back(item); + } + while ( !other.iter(true,&item) ) + { + push_back(item); + } + if ( other.hasFunc ) + { + Function = other.Function; + FunctionID = other.FunctionID; + hasFunc = true; + } + return *this; + } + + //! Midix location + /* Returns the location of Midix. + Same as "getCurrLoc". Retained for compatibility purposes. */ + si32 midixLocation() + { return midixL; } + + //! Get current node location + /* Returns the index value of the last node normally accessed. */ + si32 getCurrLoc() + { return midixL; } + + //! Get current node + /* Returns a pointer to the last node normally accessed. */ + LListixNode * getCurrNode() + { return Midix; } + + //! Get current node item + /* Returns a pointer to the item of the last node normally accessed. */ + Unk * getCurrItem() + { return &(Midix->ThisItem); } + + //! Check bounds + /* Check if the given index is within the bounds of the list. + \param index - The index to be checked. + \param alter - If the index should be altered to ensure it is in range. + Returns false if the index is NOT out of bounds. Otherwise it returns true. */ + bool CheckBound( si32 * index, bool alter=false) + { + // This index cannot be used + if ( size == 0 ) return true; + + if ( *index >= size || *index < 0 ) + { + // Is this index fixable? + if ( alter ) + { + // Don't worry about corrections if there are no items + // - let the calling function handle this scenario + if ( size == 0 ) return false; + + // Correction for low values + while ( *index < 0 ) *index = *index + size; + + // Correction for high values + while ( *index >= size ) *index = *index - size; + + // This index can be used + return false; + } else { + // Indicate this index should not be used + return true; + } + } else { + // This index can be used + return false; + } + } + + //! Return pointer-to + /* Find and return the pointer to a node at this particular location */ + LListixNode * at( si32 loc ) + { + /* if there is no item in the list yet, + or the desired location is larger than the list size: + return a blank */ + if ( size == 0 ) return 0; + + // Ensure the given index is within the boundaries + if ( CheckBound( &loc, true ) ) return 0; + + /* If the location is closer to the last link than either the + the first one or the Midix, start from the last link to obtain it. */ + if ( loc > (size + midixL)/2 ) + { + Midix = TailNode; // set the Midix to the tail + midixL = size - 1; // set the location to the end + } else { + /* If the location is closer to the first link than the + Midix, start from the first link to obtain it. */ + if ( loc < midixL/2 ) + { + Midix = HeadNode; // set the Midix to the head + midixL = 0; // set the location to the start + } + } + + while ( loc > midixL ) + { + Midix = Midix->Post; + midixL = midixL + 1; + } + while ( loc < midixL ) + { + Midix = Midix->Prior; + midixL = midixL - 1; + } + + return Midix; + } + + //! Return address of + /* Find and return the address of the object at this particular location */ + Unk& operator[] ( si32 loc ) + { + return (this->at(loc))->ThisItem; + } + + //! Return copy-of + /* Find and return a copy of the object at this particular location */ + Unk copyOf ( si32 loc ) + { + return (this->at(loc))->ThisItem; + } + + //! Item at + /* Get a pointer to the item at the given location. */ + Unk * itemAt( si32 loc ) + { + return &((this->at(loc))->ThisItem); + } + + //! Is this the beginning? + /* Returns true if the given index equals 0. */ + bool atStart( si32 index ) + { + return (index == 0); + } + + //! Is this the end? + /* Returns true if the given index equals the size - 1 */ + bool atEnd( si32 index ) + { + return (index == (size-1)); + } + + //! Is the end reached? + /* Returns true if the midix is at the end. */ + bool end() + { + return (midixL == (size-1)); + } + + //! Size + /* Returns the size of this list */ + si32 Size() { return size; } + + //! Set size + /* Sets the size of the list if altered by some function outside of + this class. */ + void setSize( si32 newSize ) { this->size = newSize; } + + //! Reset Midix + /* Resets the pointer for grabbing items back to the head node */ + void resetMidix() + { + Midix = HeadNode; + midixL = 0; + } + + //! Set head node + /* Sets the head node of the list if altered by some function outside + of this class. */ + void setHeadNode( LListixNode * newHeadNode ) + { + this->HeadNode = newHeadNode; + } + + //! Set tail node + /* Sets the tail node of the list if altered by some function outside + of this class. */ + void setTailNode( LListixNode * newTailNode ) + { + this->TailNode = newTailNode; + } + + //! Replace item + /* Find and return the node at this particular location */ + void replace( si32 loc, Unk item ) + { + if ( CheckBound( &loc, true ) ) return; + + (this->at(loc))->ThisItem = item; + } + + //! Replace this list + /* Replace this list's contents with the one given */ + void replaceAll( LListix * other ) + { + si32 index = 0; + + /* Replace the individual items without deleting the nodes + since doing so is costly. */ + for (; index < size; index++ ) + { + if ( index < other->Size() ) + { + this->at(index)->ThisItem = other->copyOf(index); + } else { + cutOff( index, false, false ); + break; + } + } + + // Add extras to this list from the other if available + for (; index < other->Size(); index++ ) + { + push_back( other->copyOf(index) ); + } + } + + //! Replace this list + /* Replace this list's contents with the one given */ + void operator=( LListix * other ) + { + replaceAll( other ); + } + + //! Insert + /* Insert a link at a specific location in the chain. The new link + will possess the location index of the location parameter given. */ + void insert( Unk item, si32 loc ) + { + // Ensure non-negative index + if ( size > 0 ) + while ( loc < 0 ) loc = size + loc; + + // if this is not one of the first links to be added + if ( size >= 2 ) + { + // go to this location if it is not the last one + if ( loc < size ) + { + // if this is to be the first link + if ( loc == 0 ) + { + HeadNode->Prior = new LListixNode( item ); + HeadNode->Prior->Post = HeadNode; // connect the links + HeadNode = HeadNode->Prior; // set the new link as the head + midixL = midixL + 1; // account for a new link placed before Midix + } else { + // go to this location + this->at(loc); + + // save another pointer + LListixNode * preceder = Midix->Prior; + + // create a new node + Midix->Prior = new LListixNode( item ); + + // connect the new link to the... + Midix->Prior->Post = Midix; // ... one following it + Midix->Prior->Prior = preceder; // ... one preceding it + + // connect the link following the new link to the new link + preceder->Post = Midix->Prior; + + // account for the new link + midixL = midixL + 1; + } + + } else { // insertion after the last link + + // tack on a new link + TailNode->Post = new LListixNode( item ); + + // set the tail link's preceding link to this one + TailNode->Post->Prior = TailNode; + + // set the tail link to the new tail + TailNode = TailNode->Post; + } + + size = size + 1; // account for the newly added link + } else { + // if this is the first link + if ( size == 0 ) + { + HeadNode = new LListixNode ( item ); + TailNode = HeadNode; + Midix = HeadNode; + midixL = 0; + } else { // there is at least one link, so add a new one + if ( loc == 0 ) // new head + { + TailNode->Prior = new LListixNode ( item ); + TailNode->Prior->Post = TailNode; // connect the links + HeadNode = TailNode->Prior; // save this new link to the head + Midix = TailNode; + midixL = 1; // the Midix has moved up one + } else { + HeadNode->Post = new LListixNode ( item ); + HeadNode->Post->Prior = HeadNode; // connect the links + TailNode = HeadNode->Post; // save this new link to the head + Midix = HeadNode; + midixL = 0; // the Midix starts from the beginning + } + } + + size = size + 1; // account for the newly added link + } + } + + //! Add to back + /* Add an item to the end of the chain */ + void push_back( Unk item ) + { + insert( item, this->size ); + } + + //! Add to front + /* Add an item to the beginning of the chain */ + void push_front( Unk item ) + { + insert( item, 0 ); + } + + //! Add to back successively + /* Adds an item to the end of the chain */ + void operator << ( Unk item ) + { + push_back( item ); + } + + //! Delete + /* Remove an item from somewhere in the list */ + void deleteAt( si32 loc ) + { + // if there are any links + if ( size > 0 ) + { + // account for indexes that are out of bounds + if ( CheckBound( &loc, true ) ) return; + + // if this is the only link in the chain + if ( size == 1 ) + { + HeadNode->deleteMe(); // delete the only item + size = 0; + midixL = 0; + } else { + // if the head + if ( loc == 0 ) + { + // the Midix may need to move + if ( Midix == HeadNode ) + { + Midix = Midix->Post; + } else + midixL -= 1; // account for the position change + + // set the head link to the next link after the first one + HeadNode = HeadNode->Post; + + // delete the old first link + HeadNode->Prior->deleteMe(); + + // Remove access to it + HeadNode->Prior = 0; + } else { + // if the tail + if ( loc == size - 1 ) + { + // the Midix may need to move + if ( Midix == TailNode ) + { + Midix = Midix->Prior; + midixL = size - 2; + } + + // set the tail link to the link prior to the last one + TailNode = TailNode->Prior; + + // delete the old tail link + TailNode->Post->deleteMe(); + + // Remove access to it + TailNode->Post = 0; + } else { + // find the location just prior to this + this->at(loc-1); + + // create a pointer to the following one + LListixNode * follower = Midix->Post->Post; + + // delete the middle node + Midix->Post->deleteMe(); + + // connect the two parts of the chain + Midix->Post = follower; + follower->Prior = Midix; + } + } + + size = size - 1; // account for the loss of a link + } + } + } + + //! Empty + /* Clear the contents of the list */ + void clear() + { + while ( size > 0 ) + { + deleteAt(-1); + } + } + + //! Find + /* Attempts to locate an item in the list that matches the given + item. Returns -1 if the item could not be found. */ + si32 find( Unk match ) + { + for ( si32 i = 0; i < size; i++ ) + { + if ( (this->at(loc))->ThisItem == match ) + return i; + } + return -1; + } + + + //! Swap + /* Exchange two items in the list. */ + void swap( si32 item1_loc, si32 item2_loc ) + { + // Account for indexes that are out of bounds + if ( CheckBound( &item1_loc, true ) || CheckBound( &item2_loc, true ) ) + return; + + // save the first item to be moved + Unk item = *( itemAt(item1_loc) ); + + // relocate the other item to be moved + *( itemAt( item1_loc ) ) = *( itemAt( item2_loc ) ); + + // resave the item in temporary storage + *( itemAt( item2_loc ) ) = item; + } + + //! Relocate + /* Moves the link at the given location to the new location. + \param loc - Current index of the link. + \param newloc - Index where to move the link. + Returns true if the relocation was successful. */ + bool relocate( si32 loc, si32 newloc ) + { + // Account for indexes that are out of bounds + if ( CheckBound( &loc, true ) || CheckBound( &newloc, true ) ) + return false; + + // Insert the item at the specific slot + if ( loc < newloc ) + insert( *(itemAt( loc )), newloc+1 ); + else + this->insert( *(itemAt( loc )), newloc ); + + /* Delete the old link, depending on its location relative + to the new one, since the index of the old one will change + if the new link is inserted before it. */ + if ( newloc < loc ) + deleteAt( loc + 1 ); + else + deleteAt( loc ); + + // Successful relocation of the link + return true; + } + + //! Continue + /* Continue to extract items following the immediate location after the + last normally-accessed item. If an end point of the list is reached, + iteration restarts from the other end. + (See function "next" if stopping at the last item is desired.) + \param foward - indicates whether to iterate towards the ending or + towards the beginning. */ + Unk cont(bool forward=true) + { + // iterate forwards through the list + if ( forward ) + { + // wrapping + if ( midixL + 1 == size ) + return this->copyOf(0); // NOTE: midixL will be set to zero by at() + else + return this->copyOf(midixL + 1); + } else { + // iterate backwards through the list + if ( midixL == 0 ) + return this->copyOf(size - 1); // NOTE: midixL will be set to (size - 1) by at() + else + return this->copyOf(midixL - 1); + } + } + + //! Next + /* Continue to extract items following the immediate location of the + last normally-accessed item. If an end point of the list is reached, the + function only returns the last-reached item. + \param foward - indicates whether to iterate towards the ending or + towards the beginning. */ + Unk next(bool forward=true) + { + // iterate forwards through the list + if ( forward ) + { + if ( midixL + 1 == size ) + return this->copyOf(-1); + else + return this->copyOf(midixL + 1); + } else { + // iterate backwards through the list + if ( midixL == 0 ) + return this->copyOf(0); + else + return this->copyOf(midixL - 1); + } + } + + //! Iterate + /* DOES NOT RETURN FIRST OBJECT! + Designed for for-loops and while loops. Calls the cont() function + but returns true when the iteration has made a complete cycle. Pass + in a pointer to the variable to receive the value in the list at each + iteration. */ + bool iter( bool forward=true, Unk * val=0 ) + { + if ( size == 0 ) + return true; + + bool isCycle = false; + + if ( midixL == size - 1 && forward ) + { isCycle = true; } + else { + if ( midixL == 0 && forward == false ) + { isCycle = true; } + } + + *val = cont(forward); + + /*if ( size != 0 ) + *val = cont(forward); + else + return true;*/ + + return isCycle; + } + + + //********** SPECIAL OPERATION FUNCTIONS *********** + /* The following functions define procedures for setting up and using + a function whose operations are to be applied to the items in the + linked list in an iterative manner. */ + + //! Iterable method + /* Set a function whose operations are to be applied to items + stored in this list. + *function contains: + \param listItem - A pointer to the item in the linked list. + \param index - The index of the item in the linked list. + \param external - For your own use. + The function must return a boolean value to indicate whether or not + iteration should continue. + \param functionID - The ID of the function if you want to give it one. + */ + void setIterableMethod( + bool (*function)( Unk * listItem, si32 index, void* external ), + si32 functionID=0 + ) + { + this->Function = function; + hasFunc = true; + this->FunctionID = functionID; + } + + //! Has iterable method + /* Indicates if a function has been saved using + setIterableMethod. */ + bool hasIterableMethod() + { + return hasFunc; + } + + //! Use function + /* Uses the function passed to this list via setIterableMethod + by passing to it each item in the list between (and including) the items + given by start and end. + \param start - The starting point of iteration. + \param end - The final point of iteration. + \param iter - The iteration amount / the skip. + \param external - For your own use. + Returns false if the loop broke by failure within the function + (as signaled by the user function return). */ + bool iterationWithMethod( si32 start=0, si32 end=-1, + si32 iter=1, void* external=0 ) + { + // Don't bother doing anything if there is no function for use + if ( !hasFunc ) return 0; + + bool cont_flag = true; // indicates if the loop should continue + + // Account for indexes that are out of bounds + if ( CheckBound(&start, true) || CheckBound(&end, true) ) return false; + + // Don't bother doing anything if there are no items + if ( size == 0 ) return 0; + + // Account for the flip + if ( start > end ) + { + end -= 2; + iter = -1; + } + + for ( si32 i = start; i != end+1; i += iter ) + { + // Continue only if the user says + if ( cont_flag ) + { + // Use the function + cont_flag = Function( itemAt(i), i, external ); + } else + break; + } + + // Return success or internal termination + return cont_flag; + } + + + //********** COPY, COMBINE, OR BREAK LIST FUNCTIONS *********** + /* The following functions define procedures merging lists or parts + of lists or breaking lists into pieces. */ + + //! Copy list + /* Copies a specified section of the list to a new list and returns + a pointer to that list. + \param start - First index to copy. + \param end - Last index to copy. */ + LListix * replicate( si32 start=0, si32 end=this->size-1 ) + { + // new list to be returned + LListix * return_list = new LListix(); + + // Don't bother doing anything if there are no items + if ( size == 0 ) return return_list; + + // Account for indexes that are out of bounds + if ( CheckBound(&start,false) || CheckBound(&end,false) ) + return return_list; + + // Append to the new list this data from indexes between start and end + for ( si32 i = start; i <= end; i++ ) + { + return_list->push_back( this->copyOf(i) ); + } + + return return_list; + } + + //! Append list + /* Appends the data from the given list to this one. */ + void append( LListix * list ) + { + for ( si32 i = 0; i < list->Size(); i++ ) + { + this->push_back( list->copyOf(i) ); + } + } + + //! Merge lists + /* Merges the lists to where the given list to follow this list. Both + list instantiations will then reference the same list. + + ATTN: For a program to terminate properly after having used this function, + the sizes of all but one list tied into one must be set to zero. */ + void merge( LListix * list ) + { + // Set the tail and head nodes + if ( this->size > 0 && list->Size() > 0 ) + { + // Link the middle + list->at(0)->Prior = this->TailNode; + this->TailNode->Post = list->at(0); + + // Link the ends + this->TailNode = list->at(-1); + list->setHeadNode( this->HeadNode ); + } else { + // First list is the one with items + if ( this->size > 0 ) + { + list->setHeadNode( this->HeadNode ); + list->setTailNode( this->TailNode ); + } else { + // Second list is the one with items + this->HeadNode = list->at(0); + this->TailNode = list->at(0); + } + } + + // Set the new size + this->size += list->Size(); + list->setSize( this->size ); + + // Reset the midix locations + this->resetMidix(); + list->resetMidix(); + } + + //! Cut off list + /* Cuts off the list at a certain index and either returns a pointer + to a new list carrying the cut off values or deletes these values. + \param index - Index of the first node to cut off. + \param retPtr - If the cut part of the list should return a pointer + to a new list. Otherwise, the nodes will be deleted. + \param front - If the front of the list should be cut. */ + LListix * cutOff( si32 index, bool retPtr=false, bool front=false ) + { + LListix * return_list; + + // Account for indexes that are out of bounds + if ( CheckBound( &index, true ) ) return 0; + + // Don't bother doing anything if there are no items + if ( this->size == 0 ) return 0; + + if ( retPtr ) + { + // Generate a new list to save to + return_list = new LListix(); + + // Transfer contents without copying + + // Simple case + if ( size == index && front || index == 0 && !front ) + { + // Prepare to take the list + return_list->setHeadNode( this->HeadNode ); + return_list->setTailNode( this->HeadNode ); + + // Drop the list + this->HeadNode = 0; + this->TailNode = 0; + + } else { + + // Complex case + if ( front ) + { + // Prepare to hold onto the front end of the list + return_list->setHeadNode( this->HeadNode ); + return_list->setTailNode( this->at(index) ); + + // Prepare to let go of the front of the list + this->HeadNode = this->at(index+1); + + // Drop the front of the list + this->HeadNode->Prior->Post = 0; + this->HeadNode->Prior = 0; + } else { + // Prepare to hold onto the back end of the list + return_list->setHeadNode( this->at(index) ); + return_list->setTailNode( this->TailNode ); + + // Prepare to let go of the back of the list + this->TailNode = this->at(index-1); + + // Drop the front of the list + this->TailNode->Post->Prior = 0; + this->TailNode->Post = 0; + } + + } + + // Account for list size change + this->size -= (index+1); + resetMidix(); + + // Prepare the returnable list for use + return_list->setSize( index+1 ); + return_list->resetMidix(); + + // Remove connections to the old nodes + return_list->at(0)->Prior = 0; + return_list->at(index)->Post = 0; + + } else { + // For not returning the cut: + + // If deletion is for the front nodes + if ( front ) { + for ( si32 i = 0 ; i < index; i++ ) + { + deleteAt(0); + } + } else { + for ( si32 i = size ; i > index; i-- ) + { + deleteAt(-1); + } + } + + return 0; + } + + return return_list; + } + + //! Cut out list + /* Cuts out the specified section of the list between the given + indexes and either returns a pointer to a new list carrying the cut + out values or deletes these values. + \param start - Index of the first node to cut out. + \param end - Index of the last node to cut out. + \param retPtr - If the cut part of the list should return a pointer + to a new list. Otherwise, the nodes will be deleted. */ + LListix * cutOut( si32 start, si32 end, bool retPtr ) + { + LListix * return_list; + + // Account for indexes that are out of bounds + if ( CheckBound( &start, true ) || CheckBound( &end, true ) ) + return 0; + + // Don't bother doing anything if there are no items + if ( size == 0 ) return 0; + + // Flip the indexes if need be + si32 temp; + if ( start > end ) + { + temp = start; + start = end; + end = temp; + } + + // Prepare to cut out part of the list + return_list = new LListix(); + + // Link up the places to cut out + return_list->setHeadNode( this->at( start ) ); + return_list->setTailNode( this->at( end ) ); + + // If all of the link are included in the range, just give them away + if ( end - start + 1 == size ) + { + this->HeadNode = 0; + this->TailNode = 0; + this->size = 0; + } else { + // If the head was included + if ( start == 0 ) + { + // Set the head to the location after the last to be cut out + this->HeadNode = this->at(end+1); + } else { + // If the tail was included + if ( end == size - 1 ) + { + // Set the tail to the location after the first to be cut out + this->TailNode = this->at(start-1); + } else { + + // The cut is somewhere between the head node and the tail node + + // Tie together the two ends of the list + this->at(end)->Post->Prior = this->at(start)->Prior; + this->at(start)->Prior->Post = this->at(end)->Post; + } + } + + // Cut the size down by the amount that was lost + this->size = this->size - (end - start + 1); + } + + // Account for the size change in the new list + return_list->setSize( end - start + 1 ); + return_list->resetMidix(); + + // Cut off the ends + return_list->at(0)->Prior = 0; + return_list->at( end - start )->Post = 0; + + // Reset the location of the midix to the head node + resetMidix(); + + // If the cut out part of this list should be returned + if ( retPtr ) + { + return return_list; + } else { + return_list->clear(); + return 0; + } + } + + }; +} + +#endif \ No newline at end of file diff --git a/IrrExtensions/misc/XPLOsion/utilities/LinusListix.h b/IrrExtensions/misc/XPLOsion/utilities/LinusListix.h new file mode 100755 index 0000000..fe53f18 --- /dev/null +++ b/IrrExtensions/misc/XPLOsion/utilities/LinusListix.h @@ -0,0 +1,1166 @@ +/* +This is the header file for LinusListix +(C) Nic Anderson +Date created: Oct 2, 2011 +Updated: +Mar 21 & 27, 2012 +April 19, 2012 +July 2, 2012 +Aug 17, 2013 +Aug 14, 2014 +other 2015 - minor fixes +Apr 21, 2015 (defines added) +Jan 6, 2016 (index size fix) + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + +*/ + +#ifndef _LinusListix_INCLUDED_ +#define _LinusListix_INCLUDED_ + +/* For compatibility purposes with older versions, uncomment the following line. */ +//#define LLISTIX_2014 + +/* +To do: +For the for-loop tools (cont(), next(), and iter()), uncomment the following line. */ +//#define LLISTIX_FORLOOP_TOOLS + + +// simple definitions for usage with this list +#ifndef LLISTIX_SI32 +#ifdef _MSC_VER +typedef __int32 si32; +#else +typedef signed int si32; +#endif +#define LLISTIX_SI32 +#endif // #ifndef LLISTIX_SI32 + + +// namespace containing the list +namespace Linus +{ + template // Should probably be template + class LListixNode + { + public: + LListixNode* Prior; // the node preceding this one + LListixNode* Post; // the node after this one + + Unk ThisItem; // the item stored here + + //! constructor + LListixNode( Unk item ) + { + ThisItem = item; + } + + //! deconstructor + void deleteMe() { delete this; } + }; + + template // Should probably be template + class LListix + { + private: + si32 size; // list size + LListixNode* HeadNode; // leading node + LListixNode* TailNode; // last node + + /* Node between the leading and the last node + - in the region being used the most - i.e., the grabber. */ + LListixNode* Midix; + si32 midixL; // location of Midix - the node being pointed to by Midix + +#ifdef LLISTIX_2014 + /* For setIterableMethod function - capability of applying a + function to the list objects. */ + /* Pointer to a function whose operations are applied to + list items. */ + bool (*Function)(Unk * listItem, si32 index, void* external); + bool hasFunc; // Indicates if a function has been saved to the pointer + si32 FunctionID; // In case you wanted to identify the function you saved here +#endif + + public: + + //! Constructor + LListix() + : size(0) + , midixL(0) +#ifdef LLISTIX_2014 + , hasFunc(false) +#endif + { + } + + //! Constructor + LListix(LListix& other) + : size(0) + , midixL(0) +#ifdef LLISTIX_2014 + , hasFunc(false) +#endif + { + si32 i=0; + for ( ; i < other.Size(); i++ ) + { + push_back( other.copyOf(i) ); + } +/* + Unk item; + other.resetMidix(); + if ( other.Size() > 0 ) { + item = other[0]; + push_back(item); + } +#ifdef LLISTIX_2014 + while ( !other.iter(true,&item) ) +#else + while ( !other.iter(&item) ) +#endif + { + push_back(item); + } +*/ +#ifdef LLISTIX_2014 + if ( other.hasFunc ) + { + Function = other.Function; + FunctionID = other.FunctionID; + hasFunc = true; + } +#endif + } + + //! Destructor + ~LListix() + { this->clear(); } + + // Operator = + LListix& operator= ( LListix& other ) + { + si32 i=0; + for ( ; i < other.Size(); i++ ) + { + push_back( other.copyOf(i) ); + } +/* + Unk item; + other.resetMidix(); + if ( other.Size() > 0 ) { + item = other[0]; + push_back(item); + } +#ifdef LLISTIX_2014 + while ( !other.iter(true,&item) ) +#else + while ( !other.iter(&item) ) +#endif + { + push_back(item); + } +*/ +#ifdef LLISTIX_2014 + if ( other.hasFunc ) + { + Function = other.Function; + FunctionID = other.FunctionID; + hasFunc = true; + } +#endif + return *this; + } + +#ifdef LLISTIX_2014 + //! Midix location + /* Returns the location of Midix. + Same as "getCurrLoc". Retained for compatibility purposes. */ + si32 midixLocation() + { return midixL; } +#endif + + //! Get current node location + /* Returns the index value of the last node normally accessed. */ + si32 getCurrLoc() + { return midixL; } + + //! Get current node + /* Returns a pointer to the last node normally accessed. */ + LListixNode* getCurrNode() + { return Midix; } + + //! Get current node item + /* Returns a pointer to the item of the last node normally accessed. */ + Unk* getCurrItem() + { return &(Midix->ThisItem); } + + //! Check bounds + /* Check if the given index is within the bounds of the list. + \param index - The index to be checked. + \param alter - If the index should be altered to ensure it is in range. + Returns false if the index is NOT out of bounds. Otherwise it returns true. + NOTE: When LLISTIX_SIZE_T is NOT defined, this can be used with negative indices. + */ + bool CheckBound( si32* index, bool alter=false) + { + // This index cannot be used + if ( size == 0 ) return true; + + if ( *index >= size +#if !(defined LLISTIX_SIZE_T) + || *index < 0 +#endif + ) + { + // Is this index fixable? + if ( alter ) + { + // Don't worry about corrections if there are no items + // - let the calling function handle this scenario + if ( size == 0 ) return false; + +#if !(defined LLISTIX_SIZE_T) + // Correction for low values + while ( *index < 0 ) *index = *index + size; +#endif + + // Correction for high values + while ( *index >= size ) *index = *index - size; + + // This index can be used + return false; + } else { + // Indicate this index should not be used + return true; + } + } else { + // This index can be used + return false; + } + } + + //! Return pointer-to + /* Find and return the pointer to a node at this particular location */ + LListixNode * at( si32 loc ) + { + /* if there is no item in the list yet, + or the desired location is larger than the list size: + return a blank */ + if ( size == 0 ) return 0; + + // Ensure the given index is within the boundaries + if ( CheckBound( &loc, true ) ) return 0; + + /* If the location is closer to the last link than either the + the first one or the Midix, start from the last link to obtain it. */ + if ( loc > (size + midixL)/2 ) + { + Midix = TailNode; // set the Midix to the tail + midixL = size - 1; // set the location to the end + } else { + /* If the location is closer to the first link than the + Midix, start from the first link to obtain it. */ + if ( loc < midixL/2 ) + { + Midix = HeadNode; // set the Midix to the head + midixL = 0; // set the location to the start + } + } + + while ( loc > midixL ) + { + Midix = Midix->Post; + midixL = midixL + 1; + } + while ( loc < midixL ) + { + Midix = Midix->Prior; + midixL = midixL - 1; + } + + return Midix; + } + + //! Return address of + /* Find and return the address of the object at this particular location */ + Unk& operator[] ( si32 loc ) + { + return (this->at(loc))->ThisItem; + } + + //! Return copy-of + /* Find and return a copy of the object at this particular location */ + Unk copyOf ( si32 loc ) + { + return (this->at(loc))->ThisItem; + } + + //! Item at + /* Get a pointer to the item at the given location. */ + Unk * itemAt( si32 loc ) + { + return &((this->at(loc))->ThisItem); + } + + //! Is this the beginning? + /* Returns true if the given index equals 0. */ + bool atStart( si32 index ) + { + return (index == 0); + } + + //! Is this the end? + /* Returns true if the given index equals the size - 1 */ + bool atEnd( si32 index ) + { + return (index == (size-1)); + } + + //! Is the end reached? + /* Returns true if the midix is at the end. */ + bool end() + { + return (midixL == (size-1)); + } + + //! Size + /* Returns the size of this list */ + si32 Size() { return size; } + + //! Set size + /* Sets the size of the list if altered by some function outside of + this class. */ + void setSize( si32 newSize ) { this->size = newSize; } + + //! Reset Midix + /* Resets the pointer for grabbing items back to the head node */ + void resetMidix() + { + Midix = HeadNode; + midixL = 0; + } + + //! Set head node + /* Sets the head node of the list if altered by some function outside + of this class. */ + void setHeadNode( LListixNode * newHeadNode ) + { + this->HeadNode = newHeadNode; + } + + //! Set tail node + /* Sets the tail node of the list if altered by some function outside + of this class. */ + void setTailNode( LListixNode * newTailNode ) + { + this->TailNode = newTailNode; + } + + //! Replace item + /* Find and return the node at this particular location */ + void replace( si32 loc, Unk item ) + { + if ( CheckBound( &loc, true ) ) return; + + (this->at(loc))->ThisItem = item; + } + + //! Replace this list + /* Replace this list's contents with the one given */ + void replaceAll( LListix * other ) + { + si32 index = 0; + + /* Replace the individual items without deleting the nodes + since doing so is costly. */ + for (; index < size; index++ ) + { + if ( index < other->Size() ) + { + this->at(index)->ThisItem = other->copyOf(index); + } else { + cutOff( index, false, false ); + break; + } + } + + // Add extras to this list from the other if available + for (; index < other->Size(); index++ ) + { + push_back( other->copyOf(index) ); + } + } + + //! Replace this list + /* Replace this list's contents with the one given */ + void operator=( LListix * other ) + { + replaceAll( other ); + } + + //! Insert + /* Insert a link at a specific location in the chain. The new link + will possess the location index of the location parameter given. */ + void insert( Unk item, si32 loc ) + { + // Ensure non-negative index + if ( size > 0 ) + while ( loc < 0 ) loc = size + loc; + + // if this is not one of the first links to be added + if ( size >= 2 ) + { + // go to this location if it is not the last one + if ( loc < size ) + { + // if this is to be the first link + if ( loc == 0 ) + { + HeadNode->Prior = new LListixNode( item ); + HeadNode->Prior->Post = HeadNode; // connect the links + HeadNode = HeadNode->Prior; // set the new link as the head + midixL = midixL + 1; // account for a new link placed before Midix + } else { + // go to this location + this->at(loc); + + // save another pointer + LListixNode * preceder = Midix->Prior; + + // create a new node + Midix->Prior = new LListixNode( item ); + + // connect the new link to the... + Midix->Prior->Post = Midix; // ... one following it + Midix->Prior->Prior = preceder; // ... one preceding it + + // connect the link following the new link to the new link + preceder->Post = Midix->Prior; + + // account for the new link + midixL = midixL + 1; + } + + } else { // insertion after the last link + + // tack on a new link + TailNode->Post = new LListixNode( item ); + + // set the tail link's preceding link to this one + TailNode->Post->Prior = TailNode; + + // set the tail link to the new tail + TailNode = TailNode->Post; + } + + size = size + 1; // account for the newly added link + } else { + // if this is the first link + if ( size == 0 ) + { + HeadNode = new LListixNode ( item ); + TailNode = HeadNode; + Midix = HeadNode; + midixL = 0; + } else { // there is at least one link, so add a new one + if ( loc == 0 ) // new head + { + TailNode->Prior = new LListixNode ( item ); + TailNode->Prior->Post = TailNode; // connect the links + HeadNode = TailNode->Prior; // save this new link to the head + Midix = TailNode; + midixL = 1; // the Midix has moved up one + } else { + HeadNode->Post = new LListixNode ( item ); + HeadNode->Post->Prior = HeadNode; // connect the links + TailNode = HeadNode->Post; // save this new link to the head + Midix = HeadNode; + midixL = 0; // the Midix starts from the beginning + } + } + + size = size + 1; // account for the newly added link + } + } + + //! Add to back + /* Add an item to the end of the chain */ + void push_back( Unk item ) + { + insert( item, this->size ); + } + + //! Add to front + /* Add an item to the beginning of the chain */ + void push_front( Unk item ) + { + insert( item, 0 ); + } + + //! Add to back successively + /* Adds an item to the end of the chain */ + void operator << ( Unk item ) + { + push_back( item ); + } + + //! Delete + /* Remove an item from somewhere in the list */ + void deleteAt( si32 loc ) + { + // if there are any links + if ( size > 0 ) + { + // account for indexes that are out of bounds + if ( CheckBound( &loc, true ) ) return; + + // if this is the only link in the chain + if ( size == 1 ) + { + HeadNode->deleteMe(); // delete the only item + size = 0; + midixL = 0; + } else { + // if the head + if ( loc == 0 ) + { + // the Midix may need to move + if ( Midix == HeadNode ) + { + Midix = Midix->Post; + } else + midixL -= 1; // account for the position change + + // set the head link to the next link after the first one + HeadNode = HeadNode->Post; + + // delete the old first link + HeadNode->Prior->deleteMe(); + + // Remove access to it + HeadNode->Prior = 0; + } else { + // if the tail + if ( loc == size - 1 ) + { + // the Midix may need to move + if ( Midix == TailNode ) + { + Midix = Midix->Prior; + midixL = size - 2; + } + + // set the tail link to the link prior to the last one + TailNode = TailNode->Prior; + + // delete the old tail link + TailNode->Post->deleteMe(); + + // Remove access to it + TailNode->Post = 0; + } else { + // find the location just prior to this + this->at(loc-1); + + // create a pointer to the following one + LListixNode * follower = Midix->Post->Post; + + // delete the middle node + Midix->Post->deleteMe(); + + // connect the two parts of the chain + Midix->Post = follower; + follower->Prior = Midix; + } + } + + size = size - 1; // account for the loss of a link + } + } + } + + //! Empty + /* Clear the contents of the list */ + void clear() + { + while ( size > 0 ) + { + deleteAt(-1); + } + } + + //! Find + /* Attempts to locate an item in the list that matches the given + item. Returns -1 if the item could not be found. */ + si32 find( Unk& match ) + { + for ( si32 i = 0; i < size; i++ ) + { + if ( (this->at(i))->ThisItem == match ) + return i; + } + return -1; + } + + + //! Swap + /* Exchange two items in the list. */ + void swap( si32 item1_loc, si32 item2_loc ) + { + // Account for indexes that are out of bounds + if ( CheckBound( &item1_loc, true ) || CheckBound( &item2_loc, true ) ) + return; + + // save the first item to be moved + Unk item = *( itemAt(item1_loc) ); + + // relocate the other item to be moved + *( itemAt( item1_loc ) ) = *( itemAt( item2_loc ) ); + + // resave the item in temporary storage + *( itemAt( item2_loc ) ) = item; + } + + //! Relocate + /* Moves the link at the given location to the new location. + \param loc - Current index of the link. + \param newloc - Index where to move the link. + Returns true if the relocation was successful. */ + bool relocate( si32 loc, si32 newloc ) + { + // Account for indexes that are out of bounds + if ( CheckBound( &loc, true ) || CheckBound( &newloc, true ) ) + return false; + + // Insert the item at the specific slot + if ( loc < newloc ) + insert( *(itemAt( loc )), newloc+1 ); + else + this->insert( *(itemAt( loc )), newloc ); + + /* Delete the old link, depending on its location relative + to the new one, since the index of the old one will change + if the new link is inserted before it. */ + if ( newloc < loc ) + deleteAt( loc + 1 ); + else + deleteAt( loc ); + + // Successful relocation of the link + return true; + } + + //! Continue + /* Continue to extract items following the immediate location after the + last normally-accessed item. If an end point of the list is reached, + iteration restarts from the other end. + (See function "next" if stopping at the last item is desired.) + \param foward - indicates whether to iterate towards the ending or + towards the beginning. */ + Unk cont(bool forward=true) + { + // iterate forwards through the list + if ( forward ) + { + // wrapping + if ( midixL + 1 == size ) + return this->copyOf(0); // NOTE: midixL will be set to zero by at() + else + return this->copyOf(midixL + 1); + } else { + // iterate backwards through the list + if ( midixL == 0 ) + return this->copyOf(size - 1); // NOTE: midixL will be set to (size - 1) by at() + else + return this->copyOf(midixL - 1); + } + } + + //! Next + /* Continue to extract items following the immediate location of the + last normally-accessed item. If an end point of the list is reached, the + function only returns the last-reached item. + \param foward - indicates whether to iterate towards the ending or + towards the beginning. */ + Unk next(bool forward=true) + { + // iterate forwards through the list + if ( forward ) + { + if ( midixL + 1 == size ) + return this->copyOf(-1); + else + return this->copyOf(midixL + 1); + } else { + // iterate backwards through the list + if ( midixL == 0 ) + return this->copyOf(0); + else + return this->copyOf(midixL - 1); + } + } + + //! Iterate + /* DOES NOT RETURN FIRST OBJECT! + Designed for for-loops and while loops. Calls the cont() function + but returns true when the iteration has made a complete cycle. Pass + in a pointer to the variable to receive the value in the list at each + iteration. */ +#ifdef LLISTIX_2014 + /* Poor implementation, since it implies allowing for trying to use a null pointer, + which would crash the function. */ + bool iter( bool forward=true, Unk* val=0 ) +#else + bool iter( Unk* val, bool forward=true ) +#endif + { + if ( size == 0 ) + return true; + + bool isCycle = false; + + if ( midixL == size - 1 && forward ) + { isCycle = true; } + else { + if ( midixL == 0 && forward == false ) + { isCycle = true; } + } + + *val = cont(forward); + + /*if ( size != 0 ) + *val = cont(forward); + else + return true;*/ + + return isCycle; + } + + +#ifdef LLISTIX_2014 + /* Note the removal of the following functions: + setIterableMethod() + hasIterableMethod() + iterationWithMethod() + */ + + //********** SPECIAL OPERATION FUNCTIONS *********** + /* The following functions define procedures for setting up and using + a function whose operations are to be applied to the items in the + linked list in an iterative manner. */ + + //! Iterable method + /* Set a function whose operations are to be applied to items + stored in this list. + *function contains: + \param listItem - A pointer to the item in the linked list. + \param index - The index of the item in the linked list. + \param external - For your own use. + The function must return a boolean value to indicate whether or not + iteration should continue. + \param functionID - The ID of the function if you want to give it one. + */ + void setIterableMethod( + bool (*function)( Unk* listItem, si32 index, void* external ), + si32 functionID=0 + ) + { + this->Function = function; + hasFunc = true; + this->FunctionID = functionID; + } + + //! Has iterable method + /* Indicates if a function has been saved using + setIterableMethod. */ + bool hasIterableMethod() + { + return hasFunc; + } + + //! Use function + /* Uses the function passed to this list via setIterableMethod + by passing to it each item in the list between (and including) the items + given by start and end. + \param start - The starting point of iteration. + \param end - The final point of iteration. + \param iter - The iteration amount / the skip. + \param external - For your own use. + Returns false if the loop broke by failure within the function + (as signaled by the user function return). */ + bool iterationWithMethod( si32 start=0, si32 end=-1, + si32 iter=1, void* external=0 ) + { + // Don't bother doing anything if there is no function for use + if ( !hasFunc ) return 0; + + bool cont_flag = true; // indicates if the loop should continue + + // Account for indexes that are out of bounds + if ( CheckBound(&start, true) || CheckBound(&end, true) ) return false; + + // Don't bother doing anything if there are no items + if ( size == 0 ) return 0; + + // Account for the flip + if ( start > end ) + { + end -= 2; + iter = -1; + } + + for ( si32 i = start; i != end+1; i += iter ) + { + // Continue only if the user says + if ( cont_flag ) + { + // Use the function + cont_flag = Function( itemAt(i), i, external ); + } else + break; + } + + // Return success or internal termination + return cont_flag; + } +#endif + + + //********** COPY, COMBINE, OR BREAK LIST FUNCTIONS *********** + /* The following functions define procedures merging lists or parts + of lists or breaking lists into pieces. */ + +#if (defined LLISTIX_2014) && !(defined __GNUG__) + //! Copy list + /* Copies a specified section of the list to a new list and returns + a pointer to that list. + \param start - First index to copy. + \param end - Last index to copy. */ + LListix* replicate( si32 start=0, si32 end=this->size-1 ) + { + // new list to be returned + LListix * return_list = new LListix(); + + // Don't bother doing anything if there are no items + if ( size == 0 ) return return_list; + + // Account for indexes that are out of bounds + if ( CheckBound(&start,false) || CheckBound(&end,false) ) + return return_list; + + // Append to the new list this data from indexes between start and end + for ( si32 i = start; i <= end; i++ ) + { + return_list->push_back( this->copyOf(i) ); + } + + return return_list; + } +#endif + + //! Append list + /* Appends the data from the given list to this one. */ + void append( LListix* list ) + { + for ( si32 i = 0; i < list->Size(); i++ ) + { + this->push_back( list->copyOf(i) ); + } + } + + //! Merge lists + /* Merges the lists to where the given list to follow this list. Both + list instantiations will then reference the same list. + + ATTN: For a program to terminate properly after having used this function, + the sizes of all but one list tied into one must be set to zero. */ + void merge( LListix* list ) + { + // Set the tail and head nodes + if ( this->size > 0 && list->Size() > 0 ) + { + // Link the middle + list->at(0)->Prior = this->TailNode; + this->TailNode->Post = list->at(0); + + // Link the ends + this->TailNode = list->at(-1); + list->setHeadNode( this->HeadNode ); + } else { + // First list is the one with items + if ( this->size > 0 ) + { + list->setHeadNode( this->HeadNode ); + list->setTailNode( this->TailNode ); + } else { + // Second list is the one with items + this->HeadNode = list->at(0); + this->TailNode = list->at(0); + } + } + + // Set the new size + + + this->size += list->Size(); + list->setSize( this->size ); + + // Reset the midix locations + this->resetMidix(); + list->resetMidix(); + } + + //! Cut off list + /* Cuts off the list at a certain index and either returns a pointer + to a new list carrying the cut off values or deletes these values. + \param index - Index of the first node to cut off. + \param retPtr - If the cut part of the list should return a pointer + to a new list. Otherwise, the nodes will be deleted. + \param front - If the front of the list should be cut. */ + LListix* cutOff( si32 index, bool retPtr=false, bool front=false ) + { + LListix* return_list; + + // Account for indexes that are out of bounds + if ( CheckBound( &index, true ) ) return 0; + + // Don't bother doing anything if there are no items + if ( this->size == 0 ) return 0; + + if ( retPtr ) + { + // Generate a new list to save to + return_list = new LListix(); + + // Transfer contents without copying + + // Simple case + if ( size == index && front || index == 0 && !front ) + { + // Prepare to take the list + return_list->setHeadNode( this->HeadNode ); + return_list->setTailNode( this->HeadNode ); + + // Drop the list + this->HeadNode = 0; + this->TailNode = 0; + + } else { + + // Complex case + if ( front ) + { + // Prepare to hold onto the front end of the list + return_list->setHeadNode( this->HeadNode ); + return_list->setTailNode( this->at(index) ); + + // Prepare to let go of the front of the list + this->HeadNode = this->at(index+1); + + // Drop the front of the list + this->HeadNode->Prior->Post = 0; + this->HeadNode->Prior = 0; + } else { + // Prepare to hold onto the back end of the list + return_list->setHeadNode( this->at(index) ); + return_list->setTailNode( this->TailNode ); + + // Prepare to let go of the back of the list + this->TailNode = this->at(index-1); + + // Drop the front of the list + this->TailNode->Post->Prior = 0; + this->TailNode->Post = 0; + } + + } + + // Account for list size change + this->size -= (index+1); + resetMidix(); + + // Prepare the returnable list for use + return_list->setSize( index+1 ); + return_list->resetMidix(); + + // Remove connections to the old nodes + return_list->at(0)->Prior = 0; + return_list->at(index)->Post = 0; + + } else { + // For not returning the cut: + + // If deletion is for the front nodes + if ( front ) { + for ( si32 i = 0 ; i < index; i++ ) + { + deleteAt(0); + } + } else { + for ( si32 i = size ; i > index; i-- ) + { + deleteAt(-1); + } + } + + return 0; + } + + return return_list; + } + + //! Cut out list + /* Cuts out the specified section of the list between the given + indexes and either returns a pointer to a new list carrying the cut + out values or deletes these values. + \param start - Index of the first node to cut out. + \param end - Index of the last node to cut out. + \param retPtr - If the cut part of the list should return a pointer + to a new list. Otherwise, the nodes will be deleted. */ + LListix* cutOut( si32 start, si32 end, bool retPtr ) + { + LListix * return_list; + + // Account for indexes that are out of bounds + if ( CheckBound( &start, true ) || CheckBound( &end, true ) ) + return 0; + + // Don't bother doing anything if there are no items + if ( size == 0 ) return 0; + + // Flip the indexes if need be + si32 temp; + if ( start > end ) + { + temp = start; + start = end; + end = temp; + } + + // Prepare to cut out part of the list + return_list = new LListix(); + + // Link up the places to cut out + return_list->setHeadNode( this->at( start ) ); + return_list->setTailNode( this->at( end ) ); + + // If all of the link are included in the range, just give them away + if ( end - start + 1 == size ) + { + this->HeadNode = 0; + this->TailNode = 0; + this->size = 0; + } else { + // If the head was included + if ( start == 0 ) + { + // Set the head to the location after the last to be cut out + this->HeadNode = this->at(end+1); + } else { + // If the tail was included + if ( end == size - 1 ) + { + // Set the tail to the location after the first to be cut out + this->TailNode = this->at(start-1); + } else { + + // The cut is somewhere between the head node and the tail node + + // Tie together the two ends of the list + this->at(end)->Post->Prior = this->at(start)->Prior; + this->at(start)->Prior->Post = this->at(end)->Post; + } + } + + // Cut the size down by the amount that was lost + this->size = this->size - (end - start + 1); + } + + // Account for the size change in the new list + return_list->setSize( end - start + 1 ); + return_list->resetMidix(); + + // Cut off the ends + return_list->at(0)->Prior = 0; + return_list->at( end - start )->Post = 0; + + // Reset the location of the midix to the head node + resetMidix(); + + // If the cut out part of this list should be returned + if ( retPtr ) + { + return return_list; + } else { + return_list->clear(); + return 0; + } + } + +/* + +To do: Function for inserting list 1 into list 2: + +There are 2 sets of 3 cases: + +list 1 size = 0 +list 1 size = 1 +list 1 size > 1 + +list 2 size = 0 +list 2 size = 1 +list 2 size > 1 + +For a total of 9 cases: + +00 +01 +0>1 +10 +11 +1>1 +>10 +>11 +>1>1 + +Five of the cases are identical: Whenever list size 1 = 0 or list size 2 = 0, and also trivial. +These can all be handled with a single if-statement. + +Two other cases are also trivial: Whenever list 1 size = 1. +This can be handled by a simple insertion. + +One other case is somewhat trivial: Whenever list 2 size = 1. +The only thing that matters here is whether the node in list 2 is attached to the beginning or to the end of the new list. + +The final case - whenever both lists have a size greater than 1 - is non-trivial. +*/ + +/* + void insert( LListix* other ) + { + if ( size == 0 ) + HeadNode = other.HeadNode + } +*/ + + }; +} + +#endif diff --git a/IrrExtensions/misc/directions.cpp b/IrrExtensions/misc/directions.cpp new file mode 100755 index 0000000..b688fb3 --- /dev/null +++ b/IrrExtensions/misc/directions.cpp @@ -0,0 +1,61 @@ +/* +(c) Nicolaus Anderson +Created: Jan 10, 2013 + +License: zlib +*/ + +#include "directions.cpp" + +#pragma once + +#ifdef __ESTDDIRECTION__ + +EStdDirection std_directions::findDirectionEnumValue( stdc8* str ) +{ + if ( str == "left" ) + return ESDIR_LEFT; + + if ( str == "right" ) + return ESDIR_RIGHT; + + if ( str == "up" ) + return ESDIR_UP; + + if ( str == "down" ) + return ESDIR_DOWN; + + if ( str == "forward" ) + return ESDIR_FORWARD; + + if ( str == "backward" ) + return ESDIR_BACKWARD; + + // default return + return ESDIR_LEFT; +} + +#endif + + +#ifndef __ESTDDIMENSION__ + +EStdDimension std_dimensions::findDimensionEnumValue( stdc8* str ) +{ + if ( str == "x-axis" ) + return ESDIM_X; + + if ( str == "y-axis" ) + return ESDIM_Y; + + if ( str == "z-axis" ) + return ESDIM_Z; + + if ( str == "time" ) + return ESDIM_T; + + // default return + return ESDIM_X; +} + +#endif \ No newline at end of file diff --git a/IrrExtensions/misc/directions.h b/IrrExtensions/misc/directions.h new file mode 100755 index 0000000..1ad496e --- /dev/null +++ b/IrrExtensions/misc/directions.h @@ -0,0 +1,77 @@ +/* +(c) Nicolaus Anderson +Created: Jan 10, 2013 + +License: zlib +*/ + +#include "stdtypes.h" + +#pragma once + +namespace std_directions +{ + // can be overridden if necessary +#ifndef __ESTDDIRECTION__ +#define __ESTDDIRECTION__ + +/* Enumeration: Standard directions */ +enum EStdDirection +{ + ESDIR_LEFT = 0, + ESDIR_RIGHT, + ESDIR_UP, + ESDIR_DOWN, + ESDIR_FORWARD, + ESDIR_BACKWARD +}; + +const stdc8* const EStdDirLiterals[] = +{ + "left", + "right", + "up", + "down", + "forward", + "backward", + 0 +}; + +// prototype +EStdDirection findDirectionEnumValue( stdc8* str ); + +#endif + +} // end namespace std_directions + + +namespace std_dimensions +{ + // can be overridden if necessary +#ifndef __ESTDDIMENSION__ +#define __ESTDDIMENSION__ + +/* Enumeration: Standard dimensions */ +enum EStdDimension +{ + ESDIM_X = 0, + ESDIM_Y, + ESDIM_Z, + ESDIM_T +}; + +const stdc8* const EStdDimLiterals[] = +{ + "x-axis", + "y-axis", + "z-axis", + "time", + 0 +}; + +// prototype +EStdDimension findDimensionEnumValue( stdc8* str ); + +#endif + +} // end namespace std_dimensions \ No newline at end of file diff --git a/IrrExtensions/misc/irrArrayIter.h b/IrrExtensions/misc/irrArrayIter.h new file mode 100755 index 0000000..8c1ba2f --- /dev/null +++ b/IrrExtensions/misc/irrArrayIter.h @@ -0,0 +1,56 @@ +#include + +namespace irr { +namespace core { + +template +class aItr +{ + array* a; + u32 i; + +public: + aItr( array& pArray ) + { + a = pArray; + } + + T& operator++ () + { + if ( i < a.Size()-1 ) + i++; + return (*a)[i]; + } + + T& operator-- () + { + if ( i > 0 ) + i--; + return (*a)[i]; + } + + T& start() + { + i = 0; + return (*a)[i]; + } + + T& end() + { + i = a.size() - 1; + return (*a)[i]; + } + + T& goTo( u32 pIndex ) + { + i = clamp(pIndex,0,a.Size()-1); + return (*a)[i]; + } + + T& operator* () + { + return (*a)[i]; + } +} + +}} \ No newline at end of file diff --git a/IrrExtensions/misc/irrMultiEventReciever.h b/IrrExtensions/misc/irrMultiEventReciever.h new file mode 100755 index 0000000..6c77d6e --- /dev/null +++ b/IrrExtensions/misc/irrMultiEventReciever.h @@ -0,0 +1,88 @@ +/* +Created by Nicolaus Anderson, 2012 + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + Since this uses the irrlicht engine, see irrlicht.h for more details. +*/ + +#ifndef IRR_MULTI_EVENT_RECEIVER_DISPATCHER_H +#define IRR_MULTI_EVENT_RECEIVER_DISPATCHER_H + +#include + +namespace irr { + +class irrMultiEventReceiver : public irr::IEventReceiver +{ +protected: + irr::core::list receivers; + irr::core::list::Iterator iter; + +public: + + irrMultiEventReceiver() + { + } + + bool OnEvent( const irr::SEvent& event ) + { + for ( iter = receivers.begin(); iter != receivers.end(); iter++ ) + { + if ( (*iter)->OnEvent(event) ) + break; + } + + return finished; + } + + void registerReceiver( irr::IEventReceiver* rec ) + { + receivers.push_back( rec ); + } + + bool removeReceiver( irr::IEventReceiver* rec ) + { + for ( iter = receivers.begin(); iter != receivers.end(); iter++ ) + { + if ( *iter == rec ) + { + receivers.erase( iter ); + return true; + } + } + + return false; + } + + irr::IEventReceiver* getReceiver( irr::u32 index ) + { + if ( index > receivers.size() ) + return 0; + + irr::u32 i = 0; + iter = receivers.begin(); + for ( ; i < index; iter++, i++ ); + + return *iter; + } + +}; + +} + +#endif diff --git a/IrrExtensions/misc/irrWriteableInterface/StdTextContainer.h b/IrrExtensions/misc/irrWriteableInterface/StdTextContainer.h new file mode 100755 index 0000000..cb1e926 --- /dev/null +++ b/IrrExtensions/misc/irrWriteableInterface/StdTextContainer.h @@ -0,0 +1,143 @@ +// (C) 2012 Nicolaus Anderson + +#ifndef IRR_STD_TEXT_CONTAINER_H +#define IRR_STD_TEXT_CONTAINER_H + +#include + +/* Standard text container */ +class irrStdTextContainer : public TextContainer +{ +public: + core::stringc text; + + irrStdTextContainer() + { + } + + virtual u32 size() + { + return text.size(); + } + + //! Check bounds + /* Check if the given index is within the bounds of the list. + \param index - The index to be checked. + \param alter - If the index should be altered to ensure it is in range. + Returns false if the index is NOT out of bounds. Otherwise it returns true. */ + bool CheckBound( s32 * index, bool alter=false) + { + // This index cannot be used + if ( size() == 0 ) return true; + + if ( *index >= (s32)size() || *index < 0 ) + { + // Is this index fixable? + if ( alter ) + { + // Don't worry about corrections if there are no items + // - let the calling function handle this scenario + if ( size() == 0 ) return false; + + // Correction for low values + while ( *index < 0 ) *index = *index + size(); + + // Correction for high values + while ( *index >= (s32)size() ) *index = *index - (s32)size(); + + // This index can be used + return false; + } else { + // Indicate this index should not be used + return true; + } + } else { + // This index can be used + return false; + } + } + + virtual void append( core::stringc newtext ) + { + text += newtext; + } + + virtual void edit( s32 index, bool overwrite, core::stringc str ) + { + if ( !CheckBound( &index, true ) ) + { + if ( overwrite ) + { + if ( str.size() < text.size() ) + { + for ( u32 c = text.size()-1; c >= str.size(); text.erase( c ) ); + } else { + if ( text.size() < str.size() ) + { + for ( u32 c = text.size(); c < str.size(); text.append(str[c]) ); + } + } + } else { + core::stringc temp = text.subString( (u32)index, text.size()-1-index ); + + text.reserve( index ); // reallocate the space in the string + text.append( str + temp ); + } + } + } + + virtual void deleteChars( s32 index, s32 quantity ) + { + for ( s32 i = index; i < quantity && i < (s32)size(); i++ ) + { + text.erase( (u32)i ); + } + } + + virtual void clear() + { + for ( u32 i = size()-1; size() != 0; i-- ) + { + text.erase( i ); + } + } + + virtual void set( core::stringc newtext ) + { + this->text = newtext; + } + + virtual void set( core::stringw newtext ) + { + this->text = core::stringc( newtext ); + } + + virtual c8 get( s32 index ) + { + if ( !CheckBound( &index, true ) ) + { + return text[index]; + } else { + return ' '; + } + } + + virtual c8& operator[] ( s32 index ) + { + return text[index]; + } + + //virtual core::stringc getText(); + + virtual core::stringc toStringc() + { + return text; + } + + virtual core::stringw toStringw() + { + return core::stringw( text ); + } +}; + +#endif // IRR_STD_TEXT_CONTAINER_H diff --git a/IrrExtensions/misc/irrWriteableInterface/TextContainer.h b/IrrExtensions/misc/irrWriteableInterface/TextContainer.h new file mode 100755 index 0000000..a51ef7c --- /dev/null +++ b/IrrExtensions/misc/irrWriteableInterface/TextContainer.h @@ -0,0 +1,168 @@ +/* +(c) 2012 Nicolaus Anderson + +See irrWriteableInterface.h for copyright details. +*/ + +#include + +using namespace irr; + +#pragma once + + +/* Text container interface for reliable return. */ + +class TextContainer +{ +public: + virtual u32 size()=0; + virtual void append( core::stringc text )=0; + virtual void edit( s32 index, bool overwrite, core::stringc text )=0; + virtual void deleteChars( s32 index, s32 quantity )=0; + virtual void clear()=0; + virtual void set( core::stringc newtext )=0; + virtual void set( core::stringw newtext )=0; + virtual c8 get( s32 index )=0; + //virtual core::stringc getText()=0; + virtual core::stringc toStringc()=0; + virtual core::stringw toStringw()=0; + + virtual c8& operator[] ( s32 index )=0; +}; + + +/* Standard text container */ +class irrStdTextContainer : public TextContainer +{ +public: + core::stringc text; + + irrStdTextContainer() + { + } + + virtual u32 size() + { + return text.size(); + } + + //! Check bounds + /* Check if the given index is within the bounds of the list. + \param index - The index to be checked. + \param alter - If the index should be altered to ensure it is in range. + Returns false if the index is NOT out of bounds. Otherwise it returns true. */ + bool CheckBound( s32 * index, bool alter=false) + { + // This index cannot be used + if ( size() == 0 ) return true; + + if ( *index >= (s32)size() || *index < 0 ) + { + // Is this index fixable? + if ( alter ) + { + // Don't worry about corrections if there are no items + // - let the calling function handle this scenario + if ( size() == 0 ) return false; + + // Correction for low values + while ( *index < 0 ) *index = *index + size(); + + // Correction for high values + while ( *index >= (s32)size() ) *index = *index - (s32)size(); + + // This index can be used + return false; + } else { + // Indicate this index should not be used + return true; + } + } else { + // This index can be used + return false; + } + } + + virtual void append( core::stringc newtext ) + { + text += newtext; + } + + virtual void edit( s32 index, bool overwrite, core::stringc str ) + { + if ( !CheckBound( &index, true ) ) + { + if ( overwrite ) + { + if ( str.size() < text.size() ) + { + for ( u32 c = text.size()-1; c >= str.size(); text.erase( c ) ); + } else { + if ( text.size() < str.size() ) + { + for ( u32 c = text.size(); c < str.size(); text.append(str[c]) ); + } + } + } else { + core::stringc temp = text.subString( (u32)index, text.size()-1-index ); + + text.reserve( index ); // reallocate the space in the string + text.append( str + temp ); + } + } + } + + virtual void deleteChars( s32 index, s32 quantity ) + { + for ( s32 i = index; i < quantity && i < (s32)size(); i++ ) + { + text.erase( (u32)i ); + } + } + + virtual void clear() + { + for ( u32 i = size()-1; size() != 0; i-- ) + { + text.erase( i ); + } + } + + virtual void set( core::stringc newtext ) + { + this->text = newtext; + } + + virtual void set( core::stringw newtext ) + { + this->text = core::stringc( newtext ); + } + + virtual c8 get( s32 index ) + { + if ( !CheckBound( &index, true ) ) + { + return text[index]; + } else { + return ' '; + } + } + + virtual c8& operator[] ( s32 index ) + { + return text[index]; + } + + //virtual core::stringc getText(); + + virtual core::stringc toStringc() + { + return text; + } + + virtual core::stringw toStringw() + { + return core::stringw( text ); + } +}; \ No newline at end of file diff --git a/IrrExtensions/misc/irrWriteableInterface/debugger.cpp b/IrrExtensions/misc/irrWriteableInterface/debugger.cpp new file mode 100755 index 0000000..608059d --- /dev/null +++ b/IrrExtensions/misc/irrWriteableInterface/debugger.cpp @@ -0,0 +1,26 @@ + +#include +#include +#include "TextContainer.h" +#include "irrWinWriter.h" + +using std::cin; + +using namespace irr; + + +void main() +{ + + // + irrWinWriter writer; + + writer.print( "Is this working?" ); + + writer.finalize(); + + writer.writeConsoleInputToStdOut(); + + char stopper; + cin >> stopper; +} \ No newline at end of file diff --git a/IrrExtensions/misc/irrWriteableInterface/irrStdWriteable.h b/IrrExtensions/misc/irrWriteableInterface/irrStdWriteable.h new file mode 100755 index 0000000..f3bf23a --- /dev/null +++ b/IrrExtensions/misc/irrWriteableInterface/irrStdWriteable.h @@ -0,0 +1,225 @@ +/* +(c) 2012 Nicolaus Anderson + +See irrWriteableInterface.h for copyright details. +*/ + +#ifndef IRR_STD_WRITEABLE_H +#define IRR_STD_WRITEABLE_H + +#include +#include "TextContainer.h" +#include "irrWriteableInterface.h" + +using namespace irr; + + +//! Class irrStdWriteable +/* This class is a cross-platform writer for the irrlicht engine. +It can write to files or to irrlicht GUI text boxes. */ +class irrStdWriteable : public WriteableInterface +{ +protected: + // GUI box + gui::IGUIEditBox * box; + bool hasGUIBox; + core::stringw textbox_text; + + // file + io::IWriteFile * output_file; + bool hasFile; + +public: + + //! Constructor + /**/ + irrStdWriteable() : WriteableInterface() + { + hasGUIBox = false; + hasFile = false; + } + + //! Deconstructor + /* Drops the output file. */ + ~irrStdWriteable() + { + if ( hasFile ) + output_file->drop(); + } + + //! Flag = Standard out exists + /* Indicates if there is something to output to. */ + virtual bool hasRealStdOut() + { + switch( mode ) + { + case STDOUT_FILE: + if ( hasFile ) { + return true; + } else { + return false; + } + case STDOUT_BOX: + if ( hasGUIBox ) { + return true; + } else { + return false; + } + default: break; + } + + return false; + } + + //! Create file + /* Creates a new file to write to. + \param path - String or path containing where to put this new file. + Path includes file name. + \param append - Whether to append to a file with this name that has + already been opened by the program. + */ + void createFile( IrrlichtDevice * device, io::path path, bool append ) + { + output_file = + device->getFileSystem()->createAndWriteFile( path, append ); + + if ( output_file != 0 ) // if the file exists + { + hasFile = true; + } + } + + //! Set file + /* Sets the file to be written to. */ + void setFile( io::IWriteFile * file ) + { + output_file = file; + output_file->grab(); + } + + //! Write to file + /* Writes the text to the file. */ + void writeToFile( core::stringc text ) + { + if ( hasFile ) + output_file->write( (void*)text.c_str(), text.size()*sizeof(c8) ); + } + + //! Write to file + /* Writes the text to the file. */ + void writeToFile() + { + if ( hasFile ) + { + output_file->write( + (void*)(container->toStringc().c_str()), + (container->size())*sizeof(c8) + ); + + container->clear(); + } + } + + //! Create GUI text box + /* Creates a GUI text box to be used. */ + gui::IGUIEditBox& createGUIBox( + gui::IGUIEnvironment * envir, + gui::IGUIElement * parent, + s32 id=-1, + core::rect shape = core::rect(0,0,400,400) + ) + { + box = envir->addEditBox( L"", shape, true, parent, id ); + hasGUIBox = true; + } + + //! Set GUI box + /* Takes a pointer to the IGUIEditBox that this writer + will now write to. */ + void setGUIBox( gui::IGUIEditBox * new_box ) + { + if ( new_box != 0 ) + { + box = new_box; + hasGUIBox = true; + } + } + + //! Write to irrlight GUI edit box + /* Writes the given text to the GUI box */ + bool writeToGUIBox( core::stringc text ) + { + // Convert + textbox_text = text; + + // Write to the box + if ( hasGUIBox ) + { + box->setText( textbox_text.c_str() ); + return true; + } else { + return false; + } + } + + //! Write to irrlight GUI edit box + /* Writes the given text to the GUI box */ + bool writeToGUIBox() + { + // Write to the box + if ( hasGUIBox ) + { + box->setText( container->toStringw().c_str() ); + container->clear(); + return true; + } else { + return false; + } + } + + //! Write to file from GUI box + /* Writes the contents of the GUI box to the given file if possible. + Returns false if unable to do so. */ + bool writeFromGUIBoxToFile() + { + // Check if there is both a GUI box and a file + if ( !hasGUIBox || !hasFile ) + return false; + + // Convert the text in the GUI box to standard string text + container->set( textbox_text ); + + // Write this text to file + writeToFile( container->toStringc() ); + } + + //! Write to standard out + /* Writes the contents to the standard output. */ + virtual void writeToStdOut( core::stringc str ) + { + switch( mode ) + { + case STDOUT_FILE: + writeToFile( str ); container->clear(); break; + case STDOUT_BOX: + writeToGUIBox( str ); container->clear(); break; + default: break; + } + } + + //! Write to standard out + /* Writes the contents to the standard output. */ + virtual void writeToStdOut() + { + switch( mode ) + { + case STDOUT_FILE: + writeToFile(); break; + case STDOUT_BOX: + writeToGUIBox(); break; + default: break; + } + } +}; + +#endif diff --git a/IrrExtensions/misc/irrWriteableInterface/irrWinWriter.h b/IrrExtensions/misc/irrWriteableInterface/irrWinWriter.h new file mode 100755 index 0000000..0c70479 --- /dev/null +++ b/IrrExtensions/misc/irrWriteableInterface/irrWinWriter.h @@ -0,0 +1,132 @@ +/* +(c) 2012 Nicolaus Anderson + +See irrWriteableInterface.h for copyright details. +*/ + +#ifndef IRR_WIN_WRITER_H +#define IRR_WIN_WRITER_H + +#include + +#include +#include "TextContainer.h" +#include "irrWriteableInterface.h" +#include "irrStdWriteable.h" + +using std::cout; +using std::cin; + +using namespace irr; + + +//! Standard Windows Console Writer for irrlicht +class irrWinWriter : public irrStdWriteable +{ +private: + +public: + + //! Constructor + /**/ + irrWinWriter( STDOUT_MODE initial_mode = STDOUT_CONSOLE ) + : irrStdWriteable() + { + mode = initial_mode; + } + + //! Flag = Standard out exists + /* Indicates if there is something to output to. */ + virtual bool hasRealStdOut() + { + switch( mode ) + { + case STDOUT_FILE: + if ( hasFile ) + return true; + else + return false; + case STDOUT_BOX: + if ( hasGUIBox ) + return true; + case STDOUT_CONSOLE: + return true; + default: break; + } + + return false; + } + + //! Sets writer + /* Identifies if a writer can be written to and, if so, allows + for content to be writen there via standard out. */ + bool setWriter( STDOUT_MODE which ) + { + mode = which; + } + + //! Override writeToStdOut + /* Writes to the console based on the current write setting. */ + virtual void writeToStdOut( core::stringc str ) + { + switch( mode ) + { + case STDOUT_FILE: + writeToFile( str ); break; + case STDOUT_BOX: + writeToGUIBox( str ); break; + case STDOUT_CONSOLE: + cout << str.c_str(); + break; + default: break; + } + } + + //! Override writeToStdOut + /* Writes to the console based on the current write setting. */ + virtual void writeToStdOut() + { + switch( mode ) + { + case STDOUT_FILE: + writeToFile(); break; + case STDOUT_BOX: + writeToGUIBox(); break; + case STDOUT_CONSOLE: + cout << container->toStringc().c_str(); + container->clear(); + break; + default: break; + } + } + + //! Get console input + /* Gets the input from the console. */ + core::stringc getInputFromConsole() + { + char c; + core::stringc text; + do { + cin >> c; + text.append( c ); + } while ( cin.peek() != '\n' ); + return text; + } + + //! Print console input + /* Gets the input from the console and stores it with print(). */ + void printConsoleInput() + { + print( getInputFromConsole() ); + } + + //! Write console input to standard out + /* Gets the console input and writes it to standard out. + Note this bypasses the saving of this information to the container. */ + void writeConsoleInputToStdOut() + { + writeToStdOut( getInputFromConsole() ); + } +}; + +#endif diff --git a/IrrExtensions/misc/irrWriteableInterface/irrWriteableInterface.h b/IrrExtensions/misc/irrWriteableInterface/irrWriteableInterface.h new file mode 100755 index 0000000..86176e3 --- /dev/null +++ b/IrrExtensions/misc/irrWriteableInterface/irrWriteableInterface.h @@ -0,0 +1,163 @@ +/* +(c) 2012 Nicolaus Anderson + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + Since this uses the irrlicht engine, see irrlicht.h for more details. +*/ + +#ifndef IRR_WRITEABLE_INTERFACE_H +#define IRR_WRITEABLE_INTERFACE_H + +#include +#include "TextContainer.h" + +using namespace irr; + +// Mode for writing +enum STDOUT_MODE +{ + // nothing to output to + STDOUT_NONE = 0, + + // output to the given file + STDOUT_FILE, + + // output to the given text box + STDOUT_BOX, + + // output to the standard Windows console + STDOUT_CONSOLE +}; + +//! Class Writeable Interface +/* This is a class used for allowing things to write to an imaginary console or file. +It can contain any number of characters that can be edited before the writing is +finalized (output to file or console). */ +class WriteableInterface +{ +protected: + //! Container + /* Container used for holding all of the characters before they + are output to file or console. */ + TextContainer* container; + + //! Output mode + /* Indicates to what the information should be written. + Need not be implemented. */ + STDOUT_MODE mode; + +public: + + //! Constructor + /* Template that takes the type TextContainer. */ + WriteableInterface() + { + container = (TextContainer *) new irrStdTextContainer(); + mode = STDOUT_NONE; + } + + //! Deconstructor + /**/ + ~WriteableInterface() + { + container->clear(); + } + + //! Flag = Standard out exists + /* Indicates if there is something to output to. */ + virtual bool hasRealStdOut() { + return false; + } + + //! Set new container + /* Establishes a new type of container to hold the text. */ + void setNewContainer( TextContainer * newcontainer ) + { + container = newcontainer; + } + + //! Print + /* Saves text to the container to be written. */ + void print( core::stringc text ) + { + container->append( text ); + } + + //! Edit + /* Allows for changes to be made at a specific point in the text + before it is output. + \param index - The starting position for changes to be made. + Negative values will be assumed to begin at the end of the text. + \param overwrite - Indicates whether existing text should be overwritten. + \param text - The text to save to the container. */ + void edit( s32 index, bool overwrite, core::stringc text ) + { + container->edit( index, overwrite, text ); + } + + //! Erase + /* Deletes the text at a specific point. + \param index - The starting position for changes to be made. + Negative values will be assumed to begin at the end of the text. + \param quantity - The number of characters to be deleted. */ + void erase( s32 index, s32 quantity ) + { + container->deleteChars( index, quantity ); + } + + //! Return container text + /* Returns all of the text currently being held in the container + as a stringc. */ + core::stringc getText() + { + return container->toStringc(); + } + + //! Finalize + /* Outputs everything to the console or file and clears the container + holding all of the letters to be output. + Returns whether or not this could be done. (There may be no real file or console.) + \param permanent - Empties the container regardless of whether its contents were + output to the console or file. */ + bool finalize( bool permanent=true ) + { + if ( !hasRealStdOut() ) + { + // Should the contents of the container be erased anyways? + if ( permanent ) + container->clear(); + + return false; + } + + // Assume an existing Standard out + writeToStdOut(); + + return true; + } + + //! Write to standard out + /* Direct write to the output console. + Function must be determined by programmer of WriteableInterface. */ + virtual void writeToStdOut( core::stringc str ) {} + + //! Write to standard out + /* Direct write to the output console. + Function must be determined by programmer of WriteableInterface. */ + virtual void writeToStdOut() { container->clear(); } +}; diff --git a/IrrExtensions/misc/irrlichtKeyController/irrlichtKeyController/KeyController_driver.cpp b/IrrExtensions/misc/irrlichtKeyController/irrlichtKeyController/KeyController_driver.cpp new file mode 100755 index 0000000..b13c8b2 --- /dev/null +++ b/IrrExtensions/misc/irrlichtKeyController/irrlichtKeyController/KeyController_driver.cpp @@ -0,0 +1,91 @@ +/* +Driver for irrlichtKeyController class +(c) 2012 Nicolaus Anderson + +Current progress: +irrlichtKeyController functions as expected. + +Limitations: +You cannot pass functions from classes directly to the key +controller (since accessing member functions in classes in C++ +is illegal). Thus, you must assign an independent function to +a key's function and have the independent function call the +class function you wish to use. +*/ + +#include +#include +#include "irrlichtKeyController.h" + +#ifdef _MSC_VER +#pragma comment(lib, "Irrlicht.lib") +#endif + +using namespace irr; +using namespace core; +using namespace video; + + +// the number of the key being pressed +int key; + +bool pressed() +{ printf( "\nKey pressed (%i)", key ); return true; } + +bool released() +{ printf ( "\nKey released (%i)", key ); return true; } + + +class MyReceiver : public IEventReceiver +{ +public: + KeyController key_controller; + + MyReceiver() + { + key_controller.isActive = true; + + for ( s32 k = 0; k < KEY_KEY_CODES_COUNT; k++ ) + { + key_controller.assignToKey( pressed, (EKEY_CODE)k, true ); + key_controller.assignToKey( released, (EKEY_CODE)k, false ); + } + } + + virtual bool OnEvent( const SEvent& event ) + { + if ( event.EventType == EET_KEY_INPUT_EVENT ) + { + key = (int) event.KeyInput.Key; + } else key = 0; + + return this->key_controller.OnEvent( event ); + } +}; + +void main() +{ + // Create a receiver + MyReceiver receiver; + + // Create an engine + IrrlichtDevice * dev; + + dev = createDevice( + EDT_BURNINGSVIDEO, dimension2d (640,480), 16, false, false, false, + &receiver // using test receiver + ); + + // Test presence of all functions for pressed keys + printf( "\nDry run:\n" ); + for ( s32 k = 0; k < KEY_KEY_CODES_COUNT; k++ ) + { + receiver.key_controller.OnKeyChange( (EKEY_CODE)k, true ); + receiver.key_controller.OnKeyChange( (EKEY_CODE)k, false ); + } + + printf( "\nDry run completed.\n\n" ); + + // Loop + while ( dev->run() ); +} diff --git a/IrrExtensions/misc/irrlichtKeyController/irrlichtKeyController/irrlichtKeyController.cpp b/IrrExtensions/misc/irrlichtKeyController/irrlichtKeyController/irrlichtKeyController.cpp new file mode 100755 index 0000000..39f5b7d --- /dev/null +++ b/IrrExtensions/misc/irrlichtKeyController/irrlichtKeyController/irrlichtKeyController.cpp @@ -0,0 +1,66 @@ +/* +(c) Nicolaus Anderson +Irrlicht engine (c) by Nikolaus Gebhardt + +See irrlichtKeyController.h for copyright details. + +Date created: April 26, 2012 + +Description: This is key controller class for handling when keys +are pressed. It allows for changes to the button configuration. +*/ + +#include +#include +#include "irrlichtKeyController.h" + +#ifndef _IRR_KEY_CONTROLLER_C_ +#define _IRR_KEY_CONTROLLER_C_ + + +//******** MAIN CLASS FUNCTIONS *********** + + +//! Assign a function to a key +bool KeyController::assignToKey( bool newFunction(), EKEY_CODE key, + bool keyState ) +{ + if ( key < KEY_KEY_CODES_COUNT ) + { + key_attr[ (s32)key ].set( newFunction, keyState ); + + return true; + } else + return false; +} // end assignToKey + + +//***************************************** +//! Set active state (allow this to be used) +void KeyController::setActiveState( bool state ) +{ + isActive = state; +} // end setActiveState + + +//***************************************** +//! Switch active state from previous +void KeyController::switchActiveState() +{ + // Is the flag "true"? - Then set it to false. Otherwise set it to true. + isActive = isActive ? false : true; +} // end switchActiveState + + +//***************************************** +//! Respond to an event with the keyboard +bool KeyController::OnKeyChange( EKEY_CODE key, bool keyState ) +{ + // Do something only if this is active + if ( isActive ) + { + return key_attr[ (s32) key ].use( keyState ); + } +} // end OnKeyChange + +#endif \ No newline at end of file diff --git a/IrrExtensions/misc/irrlichtKeyController/irrlichtKeyController/irrlichtKeyController.h b/IrrExtensions/misc/irrlichtKeyController/irrlichtKeyController/irrlichtKeyController.h new file mode 100755 index 0000000..17ca544 --- /dev/null +++ b/IrrExtensions/misc/irrlichtKeyController/irrlichtKeyController/irrlichtKeyController.h @@ -0,0 +1,166 @@ +/* +(c) Nicolaus Anderson +Irrlicht engine (c) by Nikolaus Gebhardt + +Date created: April 26, 2012 + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + Since this uses the irrlicht engine, see irrlicht.h for more details. + +Description: This is key controller for handling when keys +are pressed. It allows for changes to the button configuration. +*/ + +#include +#include + +using namespace irr; +using namespace core; +using namespace gui; + +#ifndef _IRR_KEY_CONTROLLER_H_ +#define _IRR_KEY_CONTROLLER_H_ + +// total keys: +// KEY_KEY_CODES_COUNT + +// Constants +#define ISKEY_PRESSED true +#define ISKEY_RELEASED false + + +//******** SUPPORTING CLASSES *********** + + +//! Key Functions Container +/* Class that holds the pair of functions associated with +the pressed and released states of a key. */ +class KeyFunctionsContainer +{ + // Function for being pressed + FunctionContainer Pressed; + FunctionContainer Released; + +public: + //! Assign a function to a key state + void set( bool newFunction(), bool keyState ) + { + if ( keyState == ISKEY_PRESSED ) + Pressed.set( newFunction ); + else + Released.set( newFunction ); + } + + //! See if a function can be used + bool isUsable( bool keyState ) + { + if ( keyState == ISKEY_PRESSED ) + return Pressed.isUsable(); + else + return Released.isUsable(); + } + + //! Use the function associated with the key + bool use( bool keyState ) + { + if ( keyState == ISKEY_PRESSED ) + return Pressed.use(); + else + return Released.use(); + } + +}; // end KeyFunctionsContainer + + +//*************** MAIN CLASS ****************** + +class KeyController +{ +private: + // Identification tag of this instance + s32 ID; + + // List of attributes assigned to keys, including their functions + KeyFunctionsContainer key_attr[ KEY_KEY_CODES_COUNT ]; + +public: + //! is active + /* Determines if the key controller should respond when keys + are pressed. Set this to false when you wish to use either + another key controller or are having the user change the functions + of the keys during runtime. */ + bool isActive; + + + //! Constructor + KeyController( s32 id=0 ) + { + ID = id; + isActive = false; // assumer the programmer doesn't want this active at first + } + + //! Get the ID of this instance + s32 getID() const + { + return ID; + } + + //! Set the ID of this instance + void setID( s32 newID ) + { + ID = newID; + } + + //! Assign a function to a key + /* Assigns a function to respond to key input. The type of input + (pressed down or released) is determined by keyState. Note that a key + can have a response to either being pressed down or being released + OR BOTH. + \param newFunction - The function to assign. + \param key - The key to assign (of type EKEY_CODE, which can be casted to + from s32 since it's just an integer. + \param keyState - The key state, either pressed down (true) or released (false) + that this function responds to. */ + bool assignToKey( bool newFunction(), EKEY_CODE key, bool keyState=ISKEY_PRESSED ); + + //! Set active state (allow the functions in this class to be used) + void setActiveState( bool state ); + + //! Switch active state from previous + void switchActiveState(); + + //! Respond to an event with the keyboard + /* Call this function inside your class that inherits IEventReceiver + if you already have the key code ( type == irr::EKEY_CODE ) and know + the state of the key. */ + bool OnKeyChange( EKEY_CODE key, bool keyState ); + + //! Respond to user interaction + /* Reacts to a key input event. Returns true if it reacted. */ + bool OnEvent( const SEvent & event ) + { + // Respond only if this is a key input event + if ( event.EventType == EET_KEY_INPUT_EVENT ) + { + return OnKeyChange( event.KeyInput.Key, event.KeyInput.PressedDown ); + } else + return false; + } +}; + +#endif \ No newline at end of file diff --git a/IrrExtensions/misc/irrvFreader/debugger.cpp b/IrrExtensions/misc/irrvFreader/debugger.cpp new file mode 100755 index 0000000..4856f48 --- /dev/null +++ b/IrrExtensions/misc/irrvFreader/debugger.cpp @@ -0,0 +1,42 @@ + + +#include +#include "irrVFreader.cpp" + +#include +using std::cin; +using std::cout; + +#ifdef _MSC_VER +#pragma comment( lib, "irrlicht.lib" ) +#endif + + + +void main() +{ + irr::IrrlichtDevice* dev; + + dev = createDevice(); + + irrVFreader reader( + dev->getFileSystem()->createAndOpenFile( "test.txt" ) + ); + + irr::core::stringc text = " "; + irr::core::stringc schars = " \n\r/*\"\""; + irr::c8* chars = (irr::c8*)schars.c_str(); + + while ( text != "end_main" ) + { + text = reader.readFromFile_sys_nse( 6, chars ); + + //if ( text == "\"" ) + // cout << "\n(Q)"; + //else + cout << "\n(" << (char*)text.c_str() << ")"; + } + + char h; + cin >> h; +} \ No newline at end of file diff --git a/IrrExtensions/misc/irrvFreader/irrVFreader.cpp b/IrrExtensions/misc/irrvFreader/irrVFreader.cpp new file mode 100755 index 0000000..63e0604 --- /dev/null +++ b/IrrExtensions/misc/irrvFreader/irrVFreader.cpp @@ -0,0 +1,331 @@ +/* +(C) 2011-2012 Nicolaus Anderson +This file subject to license as given in PES_Plot_main.cpp +*/ + +#pragma once + +#include "IReadFile.h" +#include "irrString.h" + +using namespace irr; + + +class irrVFreader +{ + +s32 read_position; +io::IReadFile * theFile; + + class infile + { + bool usable; + c8 * filePtr; + long filesize; + c8* faPtrEnd; // the last location the file pointer points to + + public: + infile() + { + usable = false; + } + + void infileInst( io::IReadFile * file ) + { + filesize = file->getSize(); + filePtr = new c8[filesize]; + memset( filePtr, 0, filesize ); + faPtrEnd = filePtr + filesize; // final pointer location = curr + assoc. items + + file->read( (void*)filePtr, filesize ); // copy contents into the array + + usable = true; + } + + long Size() { return filesize; } + + c8 Read( s32 readpos ) + { + if (usable) + return filePtr[ readpos ]; + else return ' '; + } + + } infiler; + +public: + +// constructor +irrVFreader(io::IReadFile * file) +{ + theFile = file; // save a pointer to the file to read + + infiler.infileInst(file); // the file in c8 characters + + read_position = 0; // start from the beginning +} + + +/* Functions used to call readFromFile functions repeatedly when spaces are not +desired. */ +core::string readFromFile_ns(bool delim) +{ + core::string ret; + + c8 * s_chars; + s32 numchars = 1; + + if ( delim ) { + s_chars = new c8[3]; + numchars = 3; + s_chars[1] = (c8)'\n'; + s_chars[2] = (c8)'\r'; + } else + s_chars = new c8[1]; + + s_chars[0] = (c8)' '; + + do { + ret = readFromFile( numchars, s_chars ); + } while ( ret == " " ); + + return ret; +} + +core::string readFromFile_sys_ns(s32 _MaxSystemChars, c8 * system_chars) +{ + core::string ret; + + do { + ret = readFromFile( _MaxSystemChars, system_chars ); + } while ( ret == " " ); + + return ret; +} + +core::string readFromFile_sys_nse(s32 _MaxSystemChars, c8 * system_chars) +{ + core::string ret; + + do { + ret = readFromFile( _MaxSystemChars, system_chars ); + } while ( ret == " " || ret == "\n" || ret == "\r" ); + + return ret; +} + +//==================================================== + +/* Function used to obtain objects (tokens) from files +considering special characters. */ +core::string readFromFile(s32 _MaxSystemChars, c8 * system_chars) +{ + /* read individual characters from the file */ + + // prep: create necessary variables + c8 character = ' '; /* This stores the character to be placed in a + core::string and returned to the function that called this + function */ + c8 peek; /* For looking at the next character in file. */ + bool set = false; /* Indicates whether or not the loop that collects + characters from file (and puts them in a core::string) should + be stopped. */ + bool set_fail = false; /* Indicates whether or not to reset the set variable so + that the character-collecting loop can continue. */ + s32 count = 0; /* Used in for-loop to compare system characters with + each new character extracted from file. */ + + // initialize a core::string to return to the function that called this one + core::string strng; + + //********************************** + + + // get characters from file + do + { + // WHAT TO DO IF AT THE END OF MAIN + if ( read_position >= infiler.Size() ) + { + /* assign "end_main" to the return core::string to indicate the + end of the file has been reached */ + strng = "end_main"; + + // end the readFromFile() use here + return strng; + } // endif WHAT TO DO IF AT THE END OF MAIN + + // get a character from the file + character = infiler.Read( read_position ); + read_position++; + + /* Get rid of any tabs. Turn them into spaces. */ + if ( character == ' ' ) + { character = ' '; } + + /* check to make sure that the character is not a system command + character */ + for (count = 0; count < _MaxSystemChars; count++) + { + /* If the character is a system character, plan to get rid of it. */ + if ( character == system_chars[count] ) + { + set = true; break; + } + } + + // Add the character if it is going to be the only one in the word. + if ( set == false || strng.size() == 0 ) + { + // Add the character to the word + strng.append( character ); + + /* Consider adding another character to the word ONLY if it + is a number, since this might be a floating point decimal + number. */ + if ( character == '.' ) + { + // peek at the next character from the file + peek = infiler.Read( read_position ); + + //if ( peek == '0' || peek == '1' + // || peek == '2' || peek == '3' + // || peek == '4' || peek == '5' + // || peek == '6' || peek == '7' + // || peek == '8' || peek == '9' ) + if ( peek >= '0' && peek <= '9' ) + { + set = false; + } + } + } else { + // Apparently, set was true, so a system character was found + /* Only let a period be added to the word as long as there + is no other period in the word already. */ + //if ( character == '.' + // && ( strng[0] == '0' || strng[0] == '1' + // || strng[0] == '2' || strng[0] == '3' + // || strng[0] == '4' || strng[0] == '5' + // || strng[0] == '6' || strng[0] == '7' + // || strng[0] == '8' || strng[0] == '9' + // ) ) + if ( character == '.' && (strng[0] >= '0' && strng[0] <= '9') ) + /* NOTE: Only the first character in the word needs + to be looked at, since the period will be added if it + is going to be the first character in the word anyways. + A number at the beginning of the word automatically + indicates a floating decimal number. + + Minus signs are to be handled under the '-' data manipulator. */ + { + // The system character is a period. + // Check for a period already in the word + set_fail = false; // assume there is no period in the word + // start looking for a period in the word + for (u32 w = 0; w < strng.size(); w++) + { + if (strng[w] == '.') + { set_fail = true; break; } + } + if ( set_fail == true ) + { + /* If there is a period in the word already, allow + the second period to be picked up next time. */ + read_position--; + } else { + // Add the period to the end of the word + strng.append( character ); + + // peek at the next character from the file + peek = infiler.Read( read_position ); + + /* only consider adding another character to the core::string + if the character is a decimal place or a number */ + //if ( peek == '0' || peek == '1' + // || peek == '2' || peek == '3' + // || peek == '4' || peek == '5' + // || peek == '6' || peek == '7' + // || peek == '8' || peek == '9' ) + if ( peek >= '0' && peek <= '9' ) + { + set = false; + } + } + } else { + /* The core::string length was not zero, or the character was + not a period. */ + + /* The system character was not added to the end of a word, + so allow it to be extracted next time. */ + read_position--; + } + } + + /* Don't collect more characters if the file is at its end. + Simply return what has been collected already. */ + if ( read_position >= infiler.Size() && strng.size() > 0 ) + { + /* Return what has been acquired. */ + return strng; + } + + } while ( set == false && read_position < infiler.Size() ); + + // return the core::string + return strng; +} + + +//============================================================== + +/* Function for getting a select number of characters from file */ +core::string readFileChars( s32 obtain ) +{ + /* read individual characters from the file */ + + // prep: create necessary variables + c8 character = ' '; /* This stores the character to be placed in a + core::string and returned to the function that called this + function */ + s32 count = 0; /* Used in while loop to compare the number of characters + stored in the core::string with the number of characters + desired in the core::string. */ + + // initialize a core::string to return to the function that called this one + core::string strng; + + + //********************************** + + // get characters from file + do + { + // WHAT TO DO IF AT THE END OF MAIN + if ( read_position >= infiler.Size() ) + { + /* assign "end_main" to the return core::string to indicate the + end of the file has been reached */ + strng = "end_main"; + + // end the readFromFile() use here + return strng; + } // endif WHAT TO DO IF AT THE END OF MAIN + + + // get a character from the file + character = infiler.Read( read_position ); + read_position++; + + // store the character in the core::string + strng.append( character ); + + // indicate another character has been put in the core::string + count++; + + } while ( read_position < infiler.Size() && count < obtain ); + + // return the core::string + return strng; +} + +}; diff --git a/IrrExtensions/misc/irrvFreader/test.txt b/IrrExtensions/misc/irrvFreader/test.txt new file mode 100755 index 0000000..429976c --- /dev/null +++ b/IrrExtensions/misc/irrvFreader/test.txt @@ -0,0 +1,11 @@ + + +/*This is some test text. */ + +"Maybe quotes will be seen." + +0.32 + +21.3 + +51 \ No newline at end of file diff --git a/IrrExtensions/misc/stdtypes.h b/IrrExtensions/misc/stdtypes.h new file mode 100755 index 0000000..f89e20c --- /dev/null +++ b/IrrExtensions/misc/stdtypes.h @@ -0,0 +1,135 @@ +/* +(c) 2013 Nicolaus Anderson +License: zlib + +Number data type templates + +Purpose: +Sometimes you may not want to use irrlicht types but you still want +your classes to be usable with or without irrlicht types. +Hence, this is available. + +*/ + +#pragma once + +#if defined(__IRR_TYPES_H_INCLUDED__) //&& defined(__USE_IRRLICHT_NUMBERS__) + +typedef irr::c8 stdc8; +typedef wchar_t stdwc; +typedef irr::s8 stds8; +typedef irr::s16 stds16; +typedef irr::s32 stds32; +typedef irr::s64 stds64; +typedef irr::u8 stdu8; +typedef irr::u16 stdu16; +typedef irr::u32 stdu32; +typedef irr::u64 stdu64; +typedef irr::f32 stdf32; +typedef irr::f64 stdf64; + +typedef long double stdf128; + +#ifdef __IRR_STRING_H_INCLUDED__ +typedef irr::core::stringc stdstr; +typedef irr::core::stringw stdstrw; +#endif + +#else // built-in types + +typedef char stdc8; +typedef wchar_t stdwc; +typedef signed char stds8; +typedef short stds16; +typedef int stds32; +typedef long int stds64; +typedef unsigned char stdu8; +typedef unsigned short stdu16; +typedef unsigned int stdu32; +typedef unsigned long stdu64; +typedef float stdf32; +typedef double stdf64; +typedef long double stdf128; + +#if defined( _INC_STRING ) && defined( __USE_MSC_STRING__ ) +typedef string stdstr; +#endif +#if defined( _INC_WCHAR ) && defined( __USE_MSC_STRING_W__ ) +typedef wstring stdstrw; +#endif + +/* Note the type definitions in xstring, lines 2209 to 2212 + +typedef basic_string, allocator > + string; +typedef basic_string, + allocator > wstring; +*/ + +#endif + +// Unicode character +#ifdef( _MSC_VER ) +#define __int8 utf8 // UTF-8 character +#define wchar_t utf16 // UTF-16 character +#define __int32 utf32 // UTF-32 character +#else +#define char utf8 // linux handles unicode in char as UTF-8 +#define short utf16 +#define int utf32 +#endif + + +#ifndef __ENUMTYPE__ +#define __ENUMTYPE__ + +//! Enumeration "Number Type" +/* Used to indicate the type of number stored in a particular location. */ +enum ENumType +{ + ENT_null=0, + ENT_bool, + ENT_c8, + ENT_wc, + ENT_s16, + ENT_s32, + ENT_s64, + ENT_u8, + ENT_u16, + ENT_u32, + ENT_u64, + ENT_f32, + ENT_f64, + ENT_f128, + ENT_string, + ENT_other +}; + +#endif + + +#define BOOLMAX 1 +#define STDC8MAX 255 +#define STDU8MAX 255 +#define STDS16MAX 32767 +#define STDS32MAX 2147483647 +#define STDS64MAX 9223372036854775807 +#define STDU16MAX 65536 +#define STDU32MAX 4294967296 +//#define STDU64MAX 18446744073709551616 // constant too big +#define STDU64 0xffffffffffffffffU +#define STDF32MAX 2147483646 +#define STDF64MAX 9223372036854775806 + + +#define BOOLMIN 0 +#define STDC8MIN 0 +#define STDU8MIN 0 +#define STDS16MIN -32767 +#define STDS32MIN -2147483647 +#define STDS64MIN -9223372036854775807 +#define STDU16MIN -65536 +#define STDU32MIN 0 +#define STDU64MIN 0 +#define STDF32MIN -2147483646 +#define STDF64MIN -9223372036854775806 diff --git a/IrrExtensions/misc/streams/InputStream.h b/IrrExtensions/misc/streams/InputStream.h new file mode 100755 index 0000000..c0b58dc --- /dev/null +++ b/IrrExtensions/misc/streams/InputStream.h @@ -0,0 +1,110 @@ +/* +(c) 2013 Nicolaus Anderson +Licence: zlib +*/ + +#ifdef _USE_IRRLICHT_ +# include +#else +# include +# include +#endif + +//#include + +#ifndef __INPUT_STREAM__ +#define __INPUT_STREAM__ + +using irr::s32; +using irr::core::stringc; +using irr::core::stringw; + +enum EReadPosition +{ + // Indicates offset from the current token + EPOS_CURRENT=0, + + // Indicates offset from the beginning + EPOS_BEGIN, + + // Indicates offset from the end + EPOS_END, + + // Count + EPOS_COUNT +}; + +class InputStream +{ +public: + //! End of stream? + /* + \return - "true" if the end of the input stream (such as the end of a file) + has been reached. */ + virtual bool EOS(); + + //! Peek at the next token + /* Returns the next token from the stream but does NOT increment the + reading position. + NOTE: On reaching the end of the stream, it returns an empty string. */ + virtual stringc peek()=0; + + //! Get the next token + /* Saves the next token from the stream and increments the reading + position. Note that numbers with decimal places are considered as single + tokens. + \return - "true" if it is possible to continue reading the stream. + "false" if the end of the stream has been reached. */ + virtual bool next( stringc& token )=0; + virtual bool next( stringw& token )=0; + + //! Set read position + /* Sets the position within the port's stream that will be next read + from. This could be a node in an xml file or a simple string from a + text file. + \param offset - The number of tokens from the current token + or the beginning or end of the stream to begin reading from. + \param epos - Indicates how to position the offset. + Returns true if the offset was made. */ + virtual bool seek( s32 offset, EReadPosition epos=EPOS_CURRENT )=0; + + //! Get reading position + /* Returns the current position in the stream. This is required for + function-style objects that need to return reading to the original + location (prior to when reading was shifted to the function body). */ + virtual s32 streamPos()=0; + + //! Get token at + /* Saves the token from a particular position in the stream. + \return - "true" if it is possible to continue reading the stream. + "false" if the end of the stream has been reached. */ + virtual bool at( s32 position, stringc& token )=0; + virtual bool at( s32 position, stringw& token )=0; + + //! Find token + /* Searches the stream for the given token and returns its location + in the stream if it was found. + \param token - The token whose position to find. + \param cycle - Searching starts from the current reading position and + goes to the end of the file. If "cycle" is set to true, the reading + will wrap back around to the beginning and the search will continue until + the current reading position in the stream has been reached again. + \return - The position of the token in the stream or -1 if the function + failed to find it. + */ + virtual s32 find( stringc token, bool cycle=true ); + virtual s32 find( stringw token, bool cycle=true ); + + //! Find token sequence + /* Searches the stream for the given token sequence and returns the + location of the first token in that stream if the sequence was found. */ + //virtual s32 findTokenSequence( ListInterface>* sequence ); + + //! Move past token + /* Slides the current reading position to the position AFTER the given + token. Returns true if successful. */ + virtual bool movePast( stringc token ); + virtual bool movePast( stringw token ); +}; + +#endif // #ifndef __INPUT_STREAM__ \ No newline at end of file diff --git a/IrrExtensions/misc/streams/InputStreamFactory.h b/IrrExtensions/misc/streams/InputStreamFactory.h new file mode 100755 index 0000000..5f780ed --- /dev/null +++ b/IrrExtensions/misc/streams/InputStreamFactory.h @@ -0,0 +1,27 @@ +/* +(c) 2013 Nicolaus Anderson +Licence: zlib +*/ + +#include "InputStream.h" + +#ifndef __INPUT_STREAM_FACTORY__ +#define __INPUT_STREAM_FACTORY__ + +//! class InputPort Factory +/* This is an abstract class designed for generating and destroying +objects of the type InputStream. */ +class InputStreamFactory +{ +public: + //! Creator + /* Generates InputPort instances */ + InputStream* create()=0; + + //! Destroyer + /* Handles the object when it is no longer needed, possibly + destroying it. */ + void destroy( InputStream* port )=0; +}; + +#endif // #ifndef __INPUT_STREAM_FACTORY__ \ No newline at end of file diff --git a/IrrExtensions/misc/streams/IrrFileInputStream.h b/IrrExtensions/misc/streams/IrrFileInputStream.h new file mode 100755 index 0000000..1ab6dbe --- /dev/null +++ b/IrrExtensions/misc/streams/IrrFileInputStream.h @@ -0,0 +1,106 @@ +/* +(c) 2013 Nicolaus Anderson +Licence: zlib +*/ + +#include +#include "InputStream.h" + +#ifndef __IRR_FILE_INPUT_STREAM__ +#define __IRR_FILE_INPUT_STREAM__ + +//! Cass Irr File Input Stream +/* This type of stream is specifically designed to convert a text +file to standard characters and then to a list of accessible tokens. */ +class IrrFileInputStream, public InputStream +{ +public: + //! constructor + IrrFileInputStream() + { + } + + //! destructor + ~IrrFileInputStream() + { + } + + // ******** Functions from InputPort ********** + + //! End of stream? + /* + \return - "true" if the end of the input stream (such as the end of a file) + has been reached. */ + virtual bool EOS(); + + //! Peek at the next token + /* Returns the next token from the stream but does NOT increment the + reading position. + NOTE: On reaching the end of the stream, it returns an empty string. */ + virtual stringc peek()=0; + + //! Get the next token + /* Saves the next token from the stream and increments the reading + position. Note that numbers with decimal places are considered as single + tokens. + \return - "true" if it is possible to continue reading the stream. + "false" if the end of the stream has been reached. */ + virtual bool next( stringc& token )=0; + + //! Set read position + /* Sets the position within the port's stream that will be next read + from. This could be a node in an xml file or a simple string from a + text file. + \param offset - The number of tokens from the current token + or the beginning or end of the stream to begin reading from. + \param epos - Indicates how to position the offset. + Returns true if the offset was made. */ + virtual bool seek( s32 offset, EReadPosition epos=EPOS_CURRENT )=0; + + //! Get reading position + /* Returns the current position in the stream. This is required for + function-style objects that need to return reading to the original + location (prior to when reading was shifted to the function body). */ + virtual s32 streamPos()=0; + + //! Get token at + /* Saves the token from a particular position in the stream. + \return - "true" if it is possible to continue reading the stream. + "false" if the end of the stream has been reached. */ + virtual bool at( s32 position, stringc& token )=0; + + //! Find token + /* Searches the stream for the given token and returns its location + in the stream if it was found. + \param token - The token whose position to find. + \param cycle - Searching starts from the current reading position and + goes to the end of the file. If "cycle" is set to true, the reading + will wrap back around to the beginning and the search will continue until + the current reading position in the stream has been reached again. + \return - The position of the token in the stream or -1 if the function + failed to find it. + */ + virtual s32 find( stringc token, bool cycle=true ); + + //! Find token sequence + /* Searches the stream for the given token sequence and returns the + location of the first token in that stream if the sequence was found. */ + //virtual s32 findTokenSequence( ListInterface>* sequence ); + + //! Move past token + /* Slides the current reading position to the position AFTER the given + token. Returns true if successful. */ + virtual bool movePast( stringc token ); + + + // ******** Unique functions ********** + + //! Set file and read + /* This sets the file to read from and then proceeds to read it. + Note that calling this function removes all data from the previous + file stored here. */ + void setFileAndRead( irr::io::IReadFile* file ); + +}; + +#endif // #define __IRR_FILE_INPUT_STREAM__ \ No newline at end of file diff --git a/IrrExtensions/misc/streams/StreamManager.h b/IrrExtensions/misc/streams/StreamManager.h new file mode 100755 index 0000000..7c6ced3 --- /dev/null +++ b/IrrExtensions/misc/streams/StreamManager.h @@ -0,0 +1,28 @@ +/* +(c) 2013 Nicolaus Anderson +Licence: zlib +*/ + +#include "InputStream.h" +#include "OutputStream.h" +#include "irrTypes.h" + +#ifndef __STREAM_MANAGER__ +#define __STREAM_MANAGER__ + +//! class Stream Manager +/* This is an abstract class designed for managing the accessing of objects +of the types InputStream and OutputStream. */ +class StreamManager +{ +public: + //! Accesser for input streams + /* Attempts to load the input stream whose name is given. */ + InputStream* getInputStream( irr::c8* name )=0; + + //! Accessor for output streams + /* Attempts to load the output stream whose name is given. */ + OutputStream* getOutputStream( irr::c8* name )=0; +}; + +#endif // #ifndef __STREAM_MANAGER__ \ No newline at end of file diff --git a/IrrExtensions/modded/Readme.md b/IrrExtensions/modded/Readme.md new file mode 100755 index 0000000..fb135d5 --- /dev/null +++ b/IrrExtensions/modded/Readme.md @@ -0,0 +1,26 @@ +This folder includes files that are part of the Irrlicht Engine but have been modified by Nic Anderson for specific purposes. +Files are based on revisions 5589 and 5823. Most of the official files have remained relatively the same over the years, so you can drop these in as replacements. + +The following methods have been added: + +vector2D::move() +rect::set() +rect::move() +rect::delta() // and fixed it +rect::makeDelta() +IRenderTarget::getTextureCount() +IRenderTarget::getTexture(u32) +IRenderTarget::getDepthStencil() + +matrix4::getInverse() now uses "T d" instead of "f32 d" and "iszero(d)" instead of "iszero(d, FLT_MIN)" so that matrices of doubles will be accurate. + +quaternion::slerp() now has a call to normalize() at the end of the method (prior to the return) in order to compensate for calculation rounding errors. Problem originally reported by porcus: +http://irrlicht.sourceforge.net/forum/viewtopic.php?f=7&t=49597 + +I had to change IGUIElement so that it would correctly assign default values to the element in deserializeAttributes() (which is now being done in the latest Irrlicht). I copied my IGUIElement::deserializeAttributes() from my modified Irrlicht 5589, but I had to leave out the part for using the often-checked-attributes enum value check. I also changed the part for setAlignment so that it'd use the new defaultNotFound value. + +I added AlignmentInfo to EGUIAlignment.h as well as the following to IGUIElement.h: + void setAlignment( AlignmentInfo info ) + AlignmentInfo getAlignment( u32 i ) + +I added IrrlichtDevice::setApplicationClass(const char*) and added an implementation to CIrrDeviceLinux. diff --git a/IrrExtensions/modded/include/EGUIAlignment.h b/IrrExtensions/modded/include/EGUIAlignment.h new file mode 100644 index 0000000..f800e65 --- /dev/null +++ b/IrrExtensions/modded/include/EGUIAlignment.h @@ -0,0 +1,50 @@ +// Copyright (C) 2002-2012 Nikolaus Gebhardt +// This file is part of the "Irrlicht Engine". +// For conditions of distribution and use, see copyright notice in irrlicht.h + +#ifndef __E_GUI_ALIGNMENT_H_INCLUDED__ +#define __E_GUI_ALIGNMENT_H_INCLUDED__ + +namespace irr +{ +namespace gui +{ +enum EGUI_ALIGNMENT +{ + //! Aligned to parent's top or left side (default) + EGUIA_UPPERLEFT=0, + //! Aligned to parent's bottom or right side + EGUIA_LOWERRIGHT, + //! Aligned to the center of parent + EGUIA_CENTER, + //! Stretched to fit parent + EGUIA_SCALE +}; + +//! Names for alignments +const c8* const GUIAlignmentNames[] = +{ + "upperLeft", + "lowerRight", + "center", + "scale", + 0 +}; + +//! Convenient packaging of alignment info, added by Nic Anderson +struct AlignmentInfo +{ + EGUI_ALIGNMENT left; + EGUI_ALIGNMENT right; + EGUI_ALIGNMENT top; + EGUI_ALIGNMENT bottom; + + AlignmentInfo() : left( EGUIA_UPPERLEFT ), right( EGUIA_UPPERLEFT ), top( EGUIA_UPPERLEFT ), bottom( EGUIA_UPPERLEFT ) {} + AlignmentInfo( EGUI_ALIGNMENT l, EGUI_ALIGNMENT r, EGUI_ALIGNMENT t, EGUI_ALIGNMENT b ) : left(l), right(r), top(t), bottom(b) {} +}; + +} // namespace gui +} // namespace irr + +#endif // __E_GUI_ALIGNMENT_H_INCLUDED__ + diff --git a/IrrExtensions/modded/include/IGUIElement.h b/IrrExtensions/modded/include/IGUIElement.h new file mode 100644 index 0000000..b3f03a5 --- /dev/null +++ b/IrrExtensions/modded/include/IGUIElement.h @@ -0,0 +1,1062 @@ +// Copyright (C) 2002-2012 Nikolaus Gebhardt +// This file is part of the "Irrlicht Engine". +// For conditions of distribution and use, see copyright notice in irrlicht.h + +#ifndef __I_GUI_ELEMENT_H_INCLUDED__ +#define __I_GUI_ELEMENT_H_INCLUDED__ + +#include "IAttributeExchangingObject.h" +#include "irrList.h" +#include "rect.h" +#include "irrString.h" +#include "IEventReceiver.h" +#include "EGUIElementTypes.h" +#include "EGUIAlignment.h" +#include "IAttributes.h" +#include "IGUIEnvironment.h" + +namespace irr +{ +namespace gui +{ +//! Base class of all GUI elements. +class IGUIElement : public virtual io::IAttributeExchangingObject, public IEventReceiver +{ +public: + + //! Constructor + IGUIElement(EGUI_ELEMENT_TYPE type, IGUIEnvironment* environment, IGUIElement* parent, + s32 id, const core::rect& rectangle) + : Parent(0), RelativeRect(rectangle), AbsoluteRect(rectangle), + AbsoluteClippingRect(rectangle), DesiredRect(rectangle), + MaxSize(0,0), MinSize(1,1), IsVisible(true), IsEnabled(true), + IsSubElement(false), NoClip(false), ID(id), IsTabStop(false), TabOrder(-1), IsTabGroup(false), + AlignLeft(EGUIA_UPPERLEFT), AlignRight(EGUIA_UPPERLEFT), AlignTop(EGUIA_UPPERLEFT), AlignBottom(EGUIA_UPPERLEFT), + Environment(environment), Type(type) + { + #ifdef _DEBUG + setDebugName("IGUIElement"); + #endif + + // if we were given a parent to attach to + if (parent) + { + parent->addChildToEnd(this); + recalculateAbsolutePosition(true); + } + } + + + //! Destructor + virtual ~IGUIElement() + { + // delete all children + core::list::Iterator it = Children.begin(); + for (; it != Children.end(); ++it) + { + (*it)->Parent = 0; + (*it)->drop(); + } + } + + + //! Returns parent of this element. + IGUIElement* getParent() const + { + return Parent; + } + + //! Returns the relative rectangle of this element. + core::rect getRelativePosition() const + { + return RelativeRect; + } + + + //! Sets the relative rectangle of this element. + /** \param r The absolute position to set */ + void setRelativePosition(const core::rect& r) + { + if (Parent) + { + const core::rect& r2 = Parent->getAbsolutePosition(); + + core::dimension2df d((f32)(r2.getSize().Width), (f32)(r2.getSize().Height)); + + if (AlignLeft == EGUIA_SCALE) + ScaleRect.UpperLeftCorner.X = (f32)r.UpperLeftCorner.X / d.Width; + if (AlignRight == EGUIA_SCALE) + ScaleRect.LowerRightCorner.X = (f32)r.LowerRightCorner.X / d.Width; + if (AlignTop == EGUIA_SCALE) + ScaleRect.UpperLeftCorner.Y = (f32)r.UpperLeftCorner.Y / d.Height; + if (AlignBottom == EGUIA_SCALE) + ScaleRect.LowerRightCorner.Y = (f32)r.LowerRightCorner.Y / d.Height; + } + + DesiredRect = r; + updateAbsolutePosition(); + } + + //! Sets the relative rectangle of this element, maintaining its current width and height + /** \param position The new relative position to set. Width and height will not be changed. */ + void setRelativePosition(const core::position2di & position) + { + const core::dimension2di mySize = RelativeRect.getSize(); + const core::rect rectangle(position.X, position.Y, + position.X + mySize.Width, position.Y + mySize.Height); + setRelativePosition(rectangle); + } + + + //! Sets the relative rectangle of this element as a proportion of its parent's area. + /** \note This method used to be 'void setRelativePosition(const core::rect& r)' + \param r The rectangle to set, interpreted as a proportion of the parent's area. + Meaningful values are in the range [0...1], unless you intend this element to spill + outside its parent. */ + void setRelativePositionProportional(const core::rect& r) + { + if (!Parent) + return; + + const core::dimension2di& d = Parent->getAbsolutePosition().getSize(); + + DesiredRect = core::rect( + core::floor32((f32)d.Width * r.UpperLeftCorner.X), + core::floor32((f32)d.Height * r.UpperLeftCorner.Y), + core::floor32((f32)d.Width * r.LowerRightCorner.X), + core::floor32((f32)d.Height * r.LowerRightCorner.Y)); + + ScaleRect = r; + + updateAbsolutePosition(); + } + + + //! Gets the absolute rectangle of this element + core::rect getAbsolutePosition() const + { + return AbsoluteRect; + } + + + //! Returns the visible area of the element. + core::rect getAbsoluteClippingRect() const + { + return AbsoluteClippingRect; + } + + + //! Sets whether the element will ignore its parent's clipping rectangle + /** \param noClip If true, the element will not be clipped by its parent's clipping rectangle. */ + void setNotClipped(bool noClip) + { + NoClip = noClip; + updateAbsolutePosition(); + } + + + //! Gets whether the element will ignore its parent's clipping rectangle + /** \return true if the element is not clipped by its parent's clipping rectangle. */ + bool isNotClipped() const + { + return NoClip; + } + + + //! Sets the maximum size allowed for this element + /** If set to 0,0, there is no maximum size */ + void setMaxSize(core::dimension2du size) + { + MaxSize = size; + updateAbsolutePosition(); + } + + + //! Sets the minimum size allowed for this element + void setMinSize(core::dimension2du size) + { + MinSize = size; + if (MinSize.Width < 1) + MinSize.Width = 1; + if (MinSize.Height < 1) + MinSize.Height = 1; + updateAbsolutePosition(); + } + + + //! The alignment defines how the borders of this element will be positioned when the parent element is resized. + void setAlignment(EGUI_ALIGNMENT left, EGUI_ALIGNMENT right, EGUI_ALIGNMENT top, EGUI_ALIGNMENT bottom) + { + AlignLeft = left; + AlignRight = right; + AlignTop = top; + AlignBottom = bottom; + + if (Parent) + { + core::rect r(Parent->getAbsolutePosition()); + + core::dimension2df d((f32)r.getSize().Width, (f32)r.getSize().Height); + + if (AlignLeft == EGUIA_SCALE) + ScaleRect.UpperLeftCorner.X = (f32)DesiredRect.UpperLeftCorner.X / d.Width; + if (AlignRight == EGUIA_SCALE) + ScaleRect.LowerRightCorner.X = (f32)DesiredRect.LowerRightCorner.X / d.Width; + if (AlignTop == EGUIA_SCALE) + ScaleRect.UpperLeftCorner.Y = (f32)DesiredRect.UpperLeftCorner.Y / d.Height; + if (AlignBottom == EGUIA_SCALE) + ScaleRect.LowerRightCorner.Y = (f32)DesiredRect.LowerRightCorner.Y / d.Height; + } + } + + + //! Set the alignment with an info struct + void setAlignment( AlignmentInfo info ) + { + setAlignment( info.left, info.right, info.top, info.bottom ); + } + + + //! Retrieve the alignment. + AlignmentInfo getAlignment() const + { + return AlignmentInfo( AlignLeft, AlignRight, AlignTop, AlignBottom ); + } + + + //! Updates the absolute position. + virtual void updateAbsolutePosition() + { + recalculateAbsolutePosition(false); + + // update all children + core::list::Iterator it = Children.begin(); + for (; it != Children.end(); ++it) + { + (*it)->updateAbsolutePosition(); + } + } + + + //! Returns the topmost GUI element at the specific position. + /** + This will check this GUI element and all of its descendants, so it + may return this GUI element. To check all GUI elements, call this + function on device->getGUIEnvironment()->getRootGUIElement(). Note + that the root element is the size of the screen, so doing so (with + an on-screen point) will always return the root element if no other + element is above it at that point. + \param point: The point at which to find a GUI element. + \return The topmost GUI element at that point, or 0 if there are + no candidate elements at this point. + */ + virtual IGUIElement* getElementFromPoint(const core::position2d& point) + { + IGUIElement* target = 0; + + // we have to search from back to front, because later children + // might be drawn over the top of earlier ones. + + core::list::ConstIterator it = Children.getLast(); + + if (isVisible()) + { + while(it != Children.end()) + { + target = (*it)->getElementFromPoint(point); + if (target) + return target; + + --it; + } + } + + if (isVisible() && isPointInside(point)) + target = this; + + return target; + } + + + //! Returns true if a point is within this element. + /** Elements with a shape other than a rectangle should override this method */ + virtual bool isPointInside(const core::position2d& point) const + { + return AbsoluteClippingRect.isPointInside(point); + } + + + //! Adds a GUI element as new child of this element. + virtual void addChild(IGUIElement* child) + { + if ( child && child != this ) + { + addChildToEnd(child); + child->updateAbsolutePosition(); + } + } + + //! Removes a child. + virtual void removeChild(IGUIElement* child) + { + core::list::Iterator it = Children.begin(); + for (; it != Children.end(); ++it) + if ((*it) == child) + { + (*it)->Parent = 0; + (*it)->drop(); + Children.erase(it); + return; + } + } + + + //! Removes this element from its parent. + virtual void remove() + { + if (Parent) + Parent->removeChild(this); + } + + + //! Draws the element and its children. + virtual void draw() + { + if ( isVisible() ) + { + core::list::Iterator it = Children.begin(); + for (; it != Children.end(); ++it) + (*it)->draw(); + } + } + + + //! animate the element and its children. + virtual void OnPostRender(u32 timeMs) + { + if ( isVisible() ) + { + core::list::Iterator it = Children.begin(); + for (; it != Children.end(); ++it) + (*it)->OnPostRender( timeMs ); + } + } + + + //! Moves this element. + virtual void move(core::position2d absoluteMovement) + { + setRelativePosition(DesiredRect + absoluteMovement); + } + + + //! Returns true if element is visible. + virtual bool isVisible() const + { + return IsVisible; + } + + //! Check whether the element is truly visible, taking into accounts its parents' visibility + /** \return true if the element and all its parents are visible, + false if this or any parent element is invisible. */ + virtual bool isTrulyVisible() const + { + if(!IsVisible) + return false; + + if(!Parent) + return true; + + return Parent->isTrulyVisible(); + } + + //! Sets the visible state of this element. + virtual void setVisible(bool visible) + { + IsVisible = visible; + } + + + //! Returns true if this element was created as part of its parent control + virtual bool isSubElement() const + { + return IsSubElement; + } + + + //! Sets whether this control was created as part of its parent. + /** For example, it is true when a scrollbar is part of a listbox. + SubElements are not saved to disk when calling guiEnvironment->saveGUI() */ + virtual void setSubElement(bool subElement) + { + IsSubElement = subElement; + } + + + //! If set to true, the focus will visit this element when using the tab key to cycle through elements. + /** If this element is a tab group (see isTabGroup/setTabGroup) then + ctrl+tab will be used instead. */ + void setTabStop(bool enable) + { + IsTabStop = enable; + } + + + //! Returns true if this element can be focused by navigating with the tab key + bool isTabStop() const + { + return IsTabStop; + } + + + //! Sets the priority of focus when using the tab key to navigate between a group of elements. + /** See setTabGroup, isTabGroup and getTabGroup for information on tab groups. + Elements with a lower number are focused first */ + void setTabOrder(s32 index) + { + // negative = autonumber + if (index < 0) + { + TabOrder = 0; + IGUIElement *el = getTabGroup(); + while (IsTabGroup && el && el->Parent) + el = el->Parent; + + IGUIElement *first=0, *closest=0; + if (el) + { + // find the highest element number + el->getNextElement(-1, true, IsTabGroup, first, closest, true); + if (first) + { + TabOrder = first->getTabOrder() + 1; + } + } + + } + else + TabOrder = index; + } + + + //! Returns the number in the tab order sequence + s32 getTabOrder() const + { + return TabOrder; + } + + + //! Sets whether this element is a container for a group of elements which can be navigated using the tab key. + /** For example, windows are tab groups. + Groups can be navigated using ctrl+tab, providing isTabStop is true. */ + void setTabGroup(bool isGroup) + { + IsTabGroup = isGroup; + } + + + //! Returns true if this element is a tab group. + bool isTabGroup() const + { + return IsTabGroup; + } + + + //! Returns the container element which holds all elements in this element's tab group. + IGUIElement* getTabGroup() + { + IGUIElement *ret=this; + + while (ret && !ret->isTabGroup()) + ret = ret->getParent(); + + return ret; + } + + + //! Returns true if element is enabled + /** Currently elements do _not_ care about parent-states. + So if you want to affect children you have to enable/disable them all. + The only exception to this are sub-elements which also check their parent. + */ + virtual bool isEnabled() const + { + if ( isSubElement() && IsEnabled && getParent() ) + return getParent()->isEnabled(); + + return IsEnabled; + } + + + //! Sets the enabled state of this element. + virtual void setEnabled(bool enabled) + { + IsEnabled = enabled; + } + + + //! Sets the new caption of this element. + virtual void setText(const wchar_t* text) + { + Text = text; + } + + + //! Returns caption of this element. + virtual const wchar_t* getText() const + { + return Text.c_str(); + } + + + //! Sets the new caption of this element. + virtual void setToolTipText(const wchar_t* text) + { + ToolTipText = text; + } + + + //! Returns caption of this element. + virtual const core::stringw& getToolTipText() const + { + return ToolTipText; + } + + + //! Returns id. Can be used to identify the element. + virtual s32 getID() const + { + return ID; + } + + + //! Sets the id of this element + virtual void setID(s32 id) + { + ID = id; + } + + + //! Called if an event happened. + virtual bool OnEvent(const SEvent& event) + { + return Parent ? Parent->OnEvent(event) : false; + } + + + //! Brings a child to front + /** \return True if successful, false if not. */ + virtual bool bringToFront(IGUIElement* element) + { + core::list::Iterator it = Children.begin(); + for (; it != Children.end(); ++it) + { + if (element == (*it)) + { + Children.erase(it); + Children.push_back(element); + return true; + } + } + + return false; + } + + + //! Moves a child to the back, so it's siblings are drawn on top of it + /** \return True if successful, false if not. */ + virtual bool sendToBack(IGUIElement* child) + { + core::list::Iterator it = Children.begin(); + if (child == (*it)) // already there + return true; + for (; it != Children.end(); ++it) + { + if (child == (*it)) + { + Children.erase(it); + Children.push_front(child); + return true; + } + } + + return false; + } + + //! Returns list with children of this element + virtual const core::list& getChildren() const + { + return Children; + } + + + //! Finds the first element with the given id. + /** \param id: Id to search for. + \param searchchildren: Set this to true, if also children of this + element may contain the element with the searched id and they + should be searched too. + \return Returns the first element with the given id. If no element + with this id was found, 0 is returned. */ + virtual IGUIElement* getElementFromId(s32 id, bool searchchildren=false) const + { + IGUIElement* e = 0; + + core::list::ConstIterator it = Children.begin(); + for (; it != Children.end(); ++it) + { + if ((*it)->getID() == id) + return (*it); + + if (searchchildren) + e = (*it)->getElementFromId(id, true); + + if (e) + return e; + } + + return e; + } + + + //! returns true if the given element is a child of this one. + //! \param child: The child element to check + bool isMyChild(IGUIElement* child) const + { + if (!child) + return false; + do + { + if (child->Parent) + child = child->Parent; + + } while (child->Parent && child != this); + + + return child == this; + } + + + //! searches elements to find the closest next element to tab to + /** \param startOrder: The TabOrder of the current element, -1 if none + \param reverse: true if searching for a lower number + \param group: true if searching for a higher one + \param first: element with the highest/lowest known tab order depending on search direction + \param closest: the closest match, depending on tab order and direction + \param includeInvisible: includes invisible elements in the search (default=false) + \param includeDisabled: includes disabled elements in the search (default=false) + \return true if successfully found an element, false to continue searching/fail */ + bool getNextElement(s32 startOrder, bool reverse, bool group, + IGUIElement*& first, IGUIElement*& closest, bool includeInvisible=false, + bool includeDisabled=false) const + { + // we'll stop searching if we find this number + s32 wanted = startOrder + ( reverse ? -1 : 1 ); + if (wanted==-2) + wanted = 1073741824; // maximum s32 + + core::list::ConstIterator it = Children.begin(); + + s32 closestOrder, currentOrder; + + while(it != Children.end()) + { + // ignore invisible elements and their children + if ( ( (*it)->isVisible() || includeInvisible ) && + (group == true || (*it)->isTabGroup() == false) ) + { + // ignore disabled, but children are checked (disabled is currently per element ignoring parent states) + if ( (*it)->isEnabled() || includeDisabled ) + { + // only check tab stops and those with the same group status + if ((*it)->isTabStop() && ((*it)->isTabGroup() == group)) + { + currentOrder = (*it)->getTabOrder(); + + // is this what we're looking for? + if (currentOrder == wanted) + { + closest = *it; + return true; + } + + // is it closer than the current closest? + if (closest) + { + closestOrder = closest->getTabOrder(); + if ( ( reverse && currentOrder > closestOrder && currentOrder < startOrder) + ||(!reverse && currentOrder < closestOrder && currentOrder > startOrder)) + { + closest = *it; + } + } + else + if ( (reverse && currentOrder < startOrder) || (!reverse && currentOrder > startOrder) ) + { + closest = *it; + } + + // is it before the current first? + if (first) + { + closestOrder = first->getTabOrder(); + + if ( (reverse && closestOrder < currentOrder) || (!reverse && closestOrder > currentOrder) ) + { + first = *it; + } + } + else + { + first = *it; + } + } + } + // search within children + if ((*it)->getNextElement(startOrder, reverse, group, first, closest)) + { + return true; + } + } + ++it; + } + return false; + } + + + //! Returns the type of the gui element. + /** This is needed for the .NET wrapper but will be used + later for serializing and deserializing. + If you wrote your own GUIElements, you need to set the type for your element as first parameter + in the constructor of IGUIElement. For own (=unknown) elements, simply use EGUIET_ELEMENT as type */ + EGUI_ELEMENT_TYPE getType() const + { + return Type; + } + + //! Returns true if the gui element supports the given type. + /** This is mostly used to check if you can cast a gui element to the class that goes with the type. + Most gui elements will only support their own type, but if you derive your own classes from interfaces + you can overload this function and add a check for the type of the base-class additionally. + This allows for checks comparable to the dynamic_cast of c++ with enabled rtti. + Note that you can't do that by calling BaseClass::hasType(type), but you have to do an explicit + comparison check, because otherwise the base class usually just checks for the member variable + Type which contains the type of your derived class. + */ + virtual bool hasType(EGUI_ELEMENT_TYPE type) const + { + return type == Type; + } + + + //! Returns the type name of the gui element. + /** This is needed serializing elements. For serializing your own elements, override this function + and return your own type name which is created by your IGUIElementFactory */ + virtual const c8* getTypeName() const + { + return GUIElementTypeNames[Type]; + } + + //! Returns the name of the element. + /** \return Name as character string. */ + virtual const c8* getName() const + { + return Name.c_str(); + } + + + //! Sets the name of the element. + /** \param name New name of the gui element. */ + virtual void setName(const c8* name) + { + Name = name; + } + + + //! Sets the name of the element. + /** \param name New name of the gui element. */ + virtual void setName(const core::stringc& name) + { + Name = name; + } + + + //! Writes attributes of the scene node. + /** Implement this to expose the attributes of your scene node for + scripting languages, editors, debuggers or xml serialization purposes. */ + virtual void serializeAttributes(io::IAttributes* out, io::SAttributeReadWriteOptions* options=0) const + { + out->addString("Name", Name.c_str()); + out->addInt("Id", ID ); + out->addString("Caption", getText()); + out->addString("ToolTip", getToolTipText().c_str()); + out->addRect("Rect", DesiredRect); + out->addPosition2d("MinSize", core::position2di(MinSize.Width, MinSize.Height)); + out->addPosition2d("MaxSize", core::position2di(MaxSize.Width, MaxSize.Height)); + out->addEnum("LeftAlign", AlignLeft, GUIAlignmentNames); + out->addEnum("RightAlign", AlignRight, GUIAlignmentNames); + out->addEnum("TopAlign", AlignTop, GUIAlignmentNames); + out->addEnum("BottomAlign", AlignBottom, GUIAlignmentNames); + out->addBool("Visible", IsVisible); + out->addBool("Enabled", IsEnabled); + out->addBool("TabStop", IsTabStop); + out->addBool("TabGroup", IsTabGroup); + out->addInt("TabOrder", TabOrder); + out->addBool("NoClip", NoClip); + } + + + //! Reads attributes of the scene node. + /** Implement this to set the attributes of your scene node for + scripting languages, editors, debuggers or xml deserialization purposes. */ + virtual void deserializeAttributes(io::IAttributes* in, io::SAttributeReadWriteOptions* options=0) + { + setID(in->getAttributeAsInt("Id", ID)); + setVisible(in->getAttributeAsBool("Visible", IsVisible)); + setEnabled(in->getAttributeAsBool("Enabled", IsEnabled)); + setName(in->getAttributeAsString("Name", Name)); + setText(in->getAttributeAsStringW("Caption", Text.c_str()).c_str()); + setToolTipText(in->getAttributeAsStringW("ToolTip", ToolTipText.c_str()).c_str()); + IsTabStop = in->getAttributeAsBool("TabStop", IsTabStop); + IsTabGroup = in->getAttributeAsBool("TabGroup", IsTabGroup); + TabOrder = in->getAttributeAsInt("TabOrder", TabOrder); + + core::position2di p = in->getAttributeAsPosition2d("MaxSize", core::position2di(MaxSize.Width, MaxSize.Height)); + setMaxSize(core::dimension2du(p.X,p.Y)); + + p = in->getAttributeAsPosition2d("MinSize", core::position2di(MinSize.Width, MinSize.Height)); + setMinSize(core::dimension2du(p.X,p.Y)); + + setAlignment( + (EGUI_ALIGNMENT) in->getAttributeAsEnumeration("LeftAlign", GUIAlignmentNames, (s32)AlignLeft), + (EGUI_ALIGNMENT) in->getAttributeAsEnumeration("RightAlign", GUIAlignmentNames, (s32)AlignRight), + (EGUI_ALIGNMENT) in->getAttributeAsEnumeration("TopAlign", GUIAlignmentNames, (s32)AlignTop), + (EGUI_ALIGNMENT) in->getAttributeAsEnumeration("BottomAlign", GUIAlignmentNames, (s32)AlignBottom) + ); + + setRelativePosition(in->getAttributeAsRect("Rect", DesiredRect)); + + setNotClipped(in->getAttributeAsBool("NoClip", NoClip)); + } + +protected: + // not virtual because needed in constructor + void addChildToEnd(IGUIElement* child) + { + if (child) + { + child->grab(); // prevent destruction when removed + child->remove(); // remove from old parent + child->LastParentRect = getAbsolutePosition(); + child->Parent = this; + Children.push_back(child); + } + } + + // not virtual because needed in constructor + void recalculateAbsolutePosition(bool recursive) + { + core::rect parentAbsolute(0,0,0,0); + core::rect parentAbsoluteClip; + f32 fw=0.f, fh=0.f; + + if (Parent) + { + parentAbsolute = Parent->AbsoluteRect; + + if (NoClip) + { + IGUIElement* p=this; + while (p->Parent) + p = p->Parent; + parentAbsoluteClip = p->AbsoluteClippingRect; + } + else + parentAbsoluteClip = Parent->AbsoluteClippingRect; + } + + const s32 diffx = parentAbsolute.getWidth() - LastParentRect.getWidth(); + const s32 diffy = parentAbsolute.getHeight() - LastParentRect.getHeight(); + + if (AlignLeft == EGUIA_SCALE || AlignRight == EGUIA_SCALE) + fw = (f32)parentAbsolute.getWidth(); + + if (AlignTop == EGUIA_SCALE || AlignBottom == EGUIA_SCALE) + fh = (f32)parentAbsolute.getHeight(); + + switch (AlignLeft) + { + case EGUIA_UPPERLEFT: + break; + case EGUIA_LOWERRIGHT: + DesiredRect.UpperLeftCorner.X += diffx; + break; + case EGUIA_CENTER: + DesiredRect.UpperLeftCorner.X += diffx/2; + break; + case EGUIA_SCALE: + DesiredRect.UpperLeftCorner.X = core::round32(ScaleRect.UpperLeftCorner.X * fw); + break; + } + + switch (AlignRight) + { + case EGUIA_UPPERLEFT: + break; + case EGUIA_LOWERRIGHT: + DesiredRect.LowerRightCorner.X += diffx; + break; + case EGUIA_CENTER: + DesiredRect.LowerRightCorner.X += diffx/2; + break; + case EGUIA_SCALE: + DesiredRect.LowerRightCorner.X = core::round32(ScaleRect.LowerRightCorner.X * fw); + break; + } + + switch (AlignTop) + { + case EGUIA_UPPERLEFT: + break; + case EGUIA_LOWERRIGHT: + DesiredRect.UpperLeftCorner.Y += diffy; + break; + case EGUIA_CENTER: + DesiredRect.UpperLeftCorner.Y += diffy/2; + break; + case EGUIA_SCALE: + DesiredRect.UpperLeftCorner.Y = core::round32(ScaleRect.UpperLeftCorner.Y * fh); + break; + } + + switch (AlignBottom) + { + case EGUIA_UPPERLEFT: + break; + case EGUIA_LOWERRIGHT: + DesiredRect.LowerRightCorner.Y += diffy; + break; + case EGUIA_CENTER: + DesiredRect.LowerRightCorner.Y += diffy/2; + break; + case EGUIA_SCALE: + DesiredRect.LowerRightCorner.Y = core::round32(ScaleRect.LowerRightCorner.Y * fh); + break; + } + + RelativeRect = DesiredRect; + + const s32 w = RelativeRect.getWidth(); + const s32 h = RelativeRect.getHeight(); + + // make sure the desired rectangle is allowed + if (w < (s32)MinSize.Width) + RelativeRect.LowerRightCorner.X = RelativeRect.UpperLeftCorner.X + MinSize.Width; + if (h < (s32)MinSize.Height) + RelativeRect.LowerRightCorner.Y = RelativeRect.UpperLeftCorner.Y + MinSize.Height; + if (MaxSize.Width && w > (s32)MaxSize.Width) + RelativeRect.LowerRightCorner.X = RelativeRect.UpperLeftCorner.X + MaxSize.Width; + if (MaxSize.Height && h > (s32)MaxSize.Height) + RelativeRect.LowerRightCorner.Y = RelativeRect.UpperLeftCorner.Y + MaxSize.Height; + + RelativeRect.repair(); + + AbsoluteRect = RelativeRect + parentAbsolute.UpperLeftCorner; + + if (!Parent) + parentAbsoluteClip = AbsoluteRect; + + AbsoluteClippingRect = AbsoluteRect; + AbsoluteClippingRect.clipAgainst(parentAbsoluteClip); + + LastParentRect = parentAbsolute; + + if ( recursive ) + { + // update all children + core::list::Iterator it = Children.begin(); + for (; it != Children.end(); ++it) + { + (*it)->recalculateAbsolutePosition(recursive); + } + } + } + +protected: + + //! List of all children of this element + core::list Children; + + //! Pointer to the parent + IGUIElement* Parent; + + //! relative rect of element + core::rect RelativeRect; + + //! absolute rect of element + core::rect AbsoluteRect; + + //! absolute clipping rect of element + core::rect AbsoluteClippingRect; + + //! the rectangle the element would prefer to be, + //! if it was not constrained by parent or max/min size + core::rect DesiredRect; + + //! for calculating the difference when resizing parent + core::rect LastParentRect; + + //! relative scale of the element inside its parent + core::rect ScaleRect; + + //! maximum and minimum size of the element + core::dimension2du MaxSize, MinSize; + + //! is visible? + bool IsVisible; + + //! is enabled? + bool IsEnabled; + + //! is a part of a larger whole and should not be serialized? + bool IsSubElement; + + //! does this element ignore its parent's clipping rectangle? + bool NoClip; + + //! caption + core::stringw Text; + + //! tooltip + core::stringw ToolTipText; + + //! users can set this for identifying the element by string + core::stringc Name; + + //! users can set this for identifying the element by integer + s32 ID; + + //! tab stop like in windows + bool IsTabStop; + + //! tab order + s32 TabOrder; + + //! tab groups are containers like windows, use ctrl+tab to navigate + bool IsTabGroup; + + //! tells the element how to act when its parent is resized + EGUI_ALIGNMENT AlignLeft, AlignRight, AlignTop, AlignBottom; + + //! GUI Environment + IGUIEnvironment* Environment; + + //! type of element + EGUI_ELEMENT_TYPE Type; +}; + + +} // end namespace gui +} // end namespace irr + +#endif + diff --git a/IrrExtensions/modded/include/IRenderTarget.h b/IrrExtensions/modded/include/IRenderTarget.h new file mode 100644 index 0000000..e7f0e75 --- /dev/null +++ b/IrrExtensions/modded/include/IRenderTarget.h @@ -0,0 +1,106 @@ +// Copyright (C) 2015 Patryk Nadrowski +// This file is part of the "Irrlicht Engine". +// For conditions of distribution and use, see copyright notice in irrlicht.h + +#ifndef __I_RENDER_TARGET_H_INCLUDED__ +#define __I_RENDER_TARGET_H_INCLUDED__ + +#include "IReferenceCounted.h" +#include "EDriverTypes.h" +#include "irrArray.h" + +namespace irr +{ +namespace video +{ + class ITexture; + + //! Interface of a Render Target. + class IRenderTarget : public virtual IReferenceCounted + { + public: + + //! constructor + IRenderTarget() : DepthStencil(0), DriverType(EDT_NULL) + { + } + + //! Returns an array of previously set textures. + const core::array& getTexture() const + { + return Texture; + } + + //! Returns a of previously set depth / depth-stencil texture. + ITexture* getDepthStencil() const + { + return DepthStencil; + } + + //! Set multiple textures. + /** Set multiple textures for the render target. + \param texture Array of texture objects. These textures are used for a color outputs. + \param depthStencil Depth or packed depth-stencil texture. This texture is used as depth + or depth-stencil buffer. */ + virtual void setTexture(const core::array& texture, ITexture* depthStencil) = 0; + + //! Set one texture. + void setTexture(ITexture* texture, ITexture* depthStencil) + { + core::array textureArray(1); + textureArray.push_back(texture); + + setTexture(textureArray, depthStencil); + } + + //! Get driver type of render target. + E_DRIVER_TYPE getDriverType() const + { + return DriverType; + } + + // Method added by Nic Anderson + // Original idea by Entity + //! Returns the number of textures stored here + u32 getTextureCount() + { + return Texture.size(); + } + + // Method added by Nic Anderson + // Original idea by Entity + //! Returns a pointer to a texture stored here by index + ITexture* getTexture( u32 index ) + { + return Texture[index]; + } + + // Method added by Nic Anderson + // Original idea by Entity + // Returns a pointer to the depth stencil + ITexture* getDepthStencil() + { + return DepthStencil; + } + + protected: + + //! Textures assigned to render target. + core::array Texture; + + //! Depth or packed depth-stencil texture assigned to render target. + ITexture* DepthStencil; + + //! Driver type of render target. + E_DRIVER_TYPE DriverType; + + private: + // no copying (IReferenceCounted still allows that for reasons which take some time to work around) + IRenderTarget(const IRenderTarget&); + IRenderTarget& operator=(const IRenderTarget&); + }; + +} +} + +#endif diff --git a/IrrExtensions/modded/include/IrrlichtDevice.h b/IrrExtensions/modded/include/IrrlichtDevice.h new file mode 100644 index 0000000..5195ed1 --- /dev/null +++ b/IrrExtensions/modded/include/IrrlichtDevice.h @@ -0,0 +1,345 @@ +// Copyright (C) 2002-2012 Nikolaus Gebhardt +// This file is part of the "Irrlicht Engine". +// For conditions of distribution and use, see copyright notice in irrlicht.h + +#ifndef __I_IRRLICHT_DEVICE_H_INCLUDED__ +#define __I_IRRLICHT_DEVICE_H_INCLUDED__ + +#include "IReferenceCounted.h" +#include "dimension2d.h" +#include "IVideoDriver.h" +#include "EDriverTypes.h" +#include "EDeviceTypes.h" +#include "IEventReceiver.h" +#include "ICursorControl.h" +#include "IVideoModeList.h" +#include "ITimer.h" +#include "IOSOperator.h" + +namespace irr +{ + class ILogger; + class IEventReceiver; + class IRandomizer; + + namespace io { + class IFileSystem; + } // end namespace io + + namespace gui { + class IGUIEnvironment; + } // end namespace gui + + namespace scene { + class ISceneManager; + } // end namespace scene + + namespace video { + class IContextManager; + } // end namespace video + + //! The Irrlicht device. You can create it with createDevice() or createDeviceEx(). + /** This is the most important class of the Irrlicht Engine. You can + access everything in the engine if you have a pointer to an instance of + this class. There should be only one instance of this class at any + time. + */ + class IrrlichtDevice : public virtual IReferenceCounted + { + public: + + //! Runs the device. + /** Also increments the virtual timer by calling + ITimer::tick();. You can prevent this + by calling ITimer::stop(); before and ITimer::start() after + calling IrrlichtDevice::run(). Returns false if device wants + to be deleted. Use it in this way: + \code + while(device->run()) + { + // draw everything here + } + \endcode + If you want the device to do nothing if the window is inactive + (recommended), use the slightly enhanced code shown at isWindowActive(). + + Note if you are running Irrlicht inside an external, custom + created window: Calling Device->run() will cause Irrlicht to + dispatch windows messages internally. + If you are running Irrlicht in your own custom window, you can + also simply use your own message loop using GetMessage, + DispatchMessage and whatever and simply don't use this method. + But note that Irrlicht will not be able to fetch user input + then. See irr::SIrrlichtCreationParameters::WindowId for more + information and example code. + */ + virtual bool run() = 0; + + //! Cause the device to temporarily pause execution and let other processes run. + /** This should bring down processor usage without major + performance loss for Irrlicht */ + virtual void yield() = 0; + + //! Pause execution and let other processes to run for a specified amount of time. + /** It may not wait the full given time, as sleep may be interrupted + \param timeMs: Time to sleep for in milliseconds. + \param pauseTimer: If true, pauses the device timer while sleeping + */ + virtual void sleep(u32 timeMs, bool pauseTimer=false) = 0; + + //! Provides access to the video driver for drawing 3d and 2d geometry. + /** \return Pointer the video driver. */ + virtual video::IVideoDriver* getVideoDriver() = 0; + + //! Provides access to the virtual file system. + /** \return Pointer to the file system. */ + virtual io::IFileSystem* getFileSystem() = 0; + + //! Provides access to the 2d user interface environment. + /** \return Pointer to the gui environment. */ + virtual gui::IGUIEnvironment* getGUIEnvironment() = 0; + + //! Provides access to the scene manager. + /** \return Pointer to the scene manager. */ + virtual scene::ISceneManager* getSceneManager() = 0; + + //! Provides access to the cursor control. + /** \return Pointer to the mouse cursor control interface. */ + virtual gui::ICursorControl* getCursorControl() = 0; + + //! Provides access to the message logger. + /** \return Pointer to the logger. */ + virtual ILogger* getLogger() = 0; + + //! Gets a list with all video modes available. + /** You only need a null driver (ED_NULL) to access + those video modes. So you can get the available modes + before starting any other video driver. + \return Pointer to a list with all video modes supported + by the gfx adapter. */ + virtual video::IVideoModeList* getVideoModeList() = 0; + + //! Get context manager + virtual video::IContextManager* getContextManager() = 0; + + //! Provides access to the operation system operator object. + /** The OS operator provides methods for + getting system specific information and doing system + specific operations, such as exchanging data with the clipboard + or reading the operation system version. + \return Pointer to the OS operator. */ + virtual IOSOperator* getOSOperator() = 0; + + //! Provides access to the engine's timer. + /** The system time can be retrieved by it as + well as the virtual time, which also can be manipulated. + \return Pointer to the ITimer object. */ + virtual ITimer* getTimer() = 0; + + //! Provides access to the engine's currently set randomizer. + /** \return Pointer to the IRandomizer object. */ + virtual IRandomizer* getRandomizer() const =0; + + //! Sets a new randomizer. + /** \param r Pointer to the new IRandomizer object. This object is + grab()'ed by the engine and will be released upon the next setRandomizer + call or upon device destruction. */ + virtual void setRandomizer(IRandomizer* r) =0; + + //! Creates a new default randomizer. + /** The default randomizer provides the random sequence known from previous + Irrlicht versions and is the initial randomizer set on device creation. + \return Pointer to the default IRandomizer object. */ + virtual IRandomizer* createDefaultRandomizer() const =0; + + //! Sets the caption of the window. + /** \param text: New text of the window caption. */ + virtual void setWindowCaption(const wchar_t* text) = 0; + + //! Sets the application class + virtual void setApplicationClass(const char* className) {} + + //! Returns if the window is active. + /** If the window is inactive, + nothing needs to be drawn. So if you don't want to draw anything + when the window is inactive, create your drawing loop this way: + \code + while(device->run()) + { + if (device->isWindowActive()) + { + // draw everything here + } + else + device->yield(); + } + \endcode + \return True if window is active. */ + virtual bool isWindowActive() const = 0; + + //! Checks if the Irrlicht window has focus + /** \return True if window has focus. */ + virtual bool isWindowFocused() const = 0; + + //! Checks if the Irrlicht window is minimized + /** \return True if window is minimized. */ + virtual bool isWindowMinimized() const = 0; + + //! Checks if the Irrlicht window is running in fullscreen mode + /** \return True if window is fullscreen. */ + virtual bool isFullscreen() const = 0; + + //! Get the current color format of the window + /** \return Color format of the window. */ + virtual video::ECOLOR_FORMAT getColorFormat() const = 0; + + //! Notifies the device that it should close itself. + /** IrrlichtDevice::run() will always return false after closeDevice() was called. */ + virtual void closeDevice() = 0; + + //! Get the version of the engine. + /** The returned string + will look like this: "1.2.3" or this: "1.2". + \return String which contains the version. */ + virtual const c8* getVersion() const = 0; + + //! Sets a new user event receiver which will receive events from the engine. + /** Return true in IEventReceiver::OnEvent to prevent the event from continuing along + the chain of event receivers. The path that an event takes through the system depends + on its type. See irr::EEVENT_TYPE for details. + \param receiver New receiver to be used. */ + virtual void setEventReceiver(IEventReceiver* receiver) = 0; + + //! Provides access to the current event receiver. + /** \return Pointer to the current event receiver. Returns 0 if there is none. */ + virtual IEventReceiver* getEventReceiver() = 0; + + //! Sends a user created event to the engine. + /** Is is usually not necessary to use this. However, if you + are using an own input library for example for doing joystick + input, you can use this to post key or mouse input events to + the engine. Internally, this method only delegates the events + further to the scene manager and the GUI environment. */ + virtual bool postEventFromUser(const SEvent& event) = 0; + + //! Sets the input receiving scene manager. + /** If set to null, the main scene manager (returned by + GetSceneManager()) will receive the input + \param sceneManager New scene manager to be used. */ + virtual void setInputReceivingSceneManager(scene::ISceneManager* sceneManager) = 0; + + //! Sets if the window should be resizable in windowed mode. + /** The default is false. This method only works in windowed + mode. + \param resize Flag whether the window should be resizable. */ + virtual void setResizable(bool resize=false) = 0; + + //! Resize the render window. + /** This will only work in windowed mode and is not yet supported on all systems. + It does set the drawing/clientDC size of the window, the window decorations are added to that. + You get the current window size with IVideoDriver::getScreenSize() (might be unified in future) + */ + virtual void setWindowSize(const irr::core::dimension2d& size) = 0; + + //! Minimizes the window if possible. + virtual void minimizeWindow() =0; + + //! Maximizes the window if possible. + virtual void maximizeWindow() =0; + + //! Restore the window to normal size if possible. + virtual void restoreWindow() =0; + + //! Get the position of the frame on-screen + virtual core::position2di getWindowPosition() = 0; + + //! Activate any joysticks, and generate events for them. + /** Irrlicht contains support for joysticks, but does not generate joystick events by default, + as this would consume joystick info that 3rd party libraries might rely on. Call this method to + activate joystick support in Irrlicht and to receive irr::SJoystickEvent events. + \param joystickInfo On return, this will contain an array of each joystick that was found and activated. + \return true if joysticks are supported on this device and _IRR_COMPILE_WITH_JOYSTICK_EVENTS_ + is defined, false if joysticks are not supported or support is compiled out. + */ + virtual bool activateJoysticks(core::array& joystickInfo) =0; + + //! Set the current Gamma Value for the Display + virtual bool setGammaRamp(f32 red, f32 green, f32 blue, + f32 relativebrightness, f32 relativecontrast) =0; + + //! Get the current Gamma Value for the Display + virtual bool getGammaRamp(f32 &red, f32 &green, f32 &blue, + f32 &brightness, f32 &contrast) =0; + + //! Set the maximal elapsed time between 2 clicks to generate doubleclicks for the mouse. It also affects tripleclick behavior. + /** When set to 0 no double- and tripleclicks will be generated. + \param timeMs maximal time in milliseconds for two consecutive clicks to be recognized as double click + */ + virtual void setDoubleClickTime(u32 timeMs) =0; + + //! Get the maximal elapsed time between 2 clicks to generate double- and tripleclicks for the mouse. + /** When return value is 0 no double- and tripleclicks will be generated. + \return maximal time in milliseconds for two consecutive clicks to be recognized as double click + */ + virtual u32 getDoubleClickTime() const =0; + + //! Remove messages pending in the system message loop + /** This function is usually used after messages have been buffered for a longer time, for example + when loading a large scene. Clearing the message loop prevents that mouse- or buttonclicks which users + have pressed in the meantime will now trigger unexpected actions in the gui.
+ So far the following messages are cleared:
+ Win32: All keyboard and mouse messages
+ Linux: All keyboard and mouse messages
+ All other devices are not yet supported here.
+ The function is still somewhat experimental, as the kind of messages we clear is based on just a few use-cases. + If you think further messages should be cleared, or some messages should not be cleared here, then please tell us. */ + virtual void clearSystemMessages() = 0; + + //! Get the type of the device. + /** This allows the user to check which windowing system is currently being + used. */ + virtual E_DEVICE_TYPE getType() const = 0; + + //! Check if a driver type is supported by the engine. + /** Even if true is returned the driver may not be available + for a configuration requested when creating the device. */ + static bool isDriverSupported(video::E_DRIVER_TYPE driver) + { + switch (driver) + { + case video::EDT_NULL: + return true; + case video::EDT_SOFTWARE: +#ifdef _IRR_COMPILE_WITH_SOFTWARE_ + return true; +#else + return false; +#endif + case video::EDT_BURNINGSVIDEO: +#ifdef _IRR_COMPILE_WITH_BURNINGSVIDEO_ + return true; +#else + return false; +#endif + case video::EDT_DIRECT3D9: +#ifdef _IRR_COMPILE_WITH_DIRECT3D_9_ + return true; +#else + return false; +#endif + case video::EDT_OPENGL: +#ifdef _IRR_COMPILE_WITH_OPENGL_ + return true; +#else + return false; +#endif + default: + return false; + } + } + }; + +} // end namespace irr + +#endif + diff --git a/IrrExtensions/modded/include/matrix4.h b/IrrExtensions/modded/include/matrix4.h new file mode 100644 index 0000000..9004665 --- /dev/null +++ b/IrrExtensions/modded/include/matrix4.h @@ -0,0 +1,2312 @@ +// Copyright (C) 2002-2012 Nikolaus Gebhardt +// This file is part of the "Irrlicht Engine". +// For conditions of distribution and use, see copyright notice in irrlicht.h + +#ifndef __IRR_MATRIX_H_INCLUDED__ +#define __IRR_MATRIX_H_INCLUDED__ + +#include "irrMath.h" +#include "vector3d.h" +#include "vector2d.h" +#include "plane3d.h" +#include "aabbox3d.h" +#include "rect.h" +#include "irrString.h" + +// enable this to keep track of changes to the matrix +// and make simpler identity check for seldom changing matrices +// otherwise identity check will always compare the elements +//#define USE_MATRIX_TEST + +// this is only for debugging purposes +//#define USE_MATRIX_TEST_DEBUG + +#if defined( USE_MATRIX_TEST_DEBUG ) + +struct MatrixTest +{ + MatrixTest () : ID(0), Calls(0) {} + char buf[256]; + int Calls; + int ID; +}; +static MatrixTest MTest; + +#endif + +namespace irr +{ +namespace core +{ + + //! 4x4 matrix. Mostly used as transformation matrix for 3d calculations. + /** The matrix is a D3D style matrix, row major with translations in the 4th row. */ + template + class CMatrix4 + { + public: + + //! Constructor Flags + enum eConstructor + { + EM4CONST_NOTHING = 0, + EM4CONST_COPY, + EM4CONST_IDENTITY, + EM4CONST_TRANSPOSED, + EM4CONST_INVERSE, + EM4CONST_INVERSE_TRANSPOSED + }; + + //! Default constructor + /** \param constructor Choose the initialization style */ + CMatrix4( eConstructor constructor = EM4CONST_IDENTITY ); + + //! Constructor with value initialization + CMatrix4(const T& r0c0, const T& r0c1, const T& r0c2, const T& r0c3, + const T& r1c0, const T& r1c1, const T& r1c2, const T& r1c3, + const T& r2c0, const T& r2c1, const T& r2c2, const T& r2c3, + const T& r3c0, const T& r3c1, const T& r3c2, const T& r3c3) + { + M[0] = r0c0; M[1] = r0c1; M[2] = r0c2; M[3] = r0c3; + M[4] = r1c0; M[5] = r1c1; M[6] = r1c2; M[7] = r1c3; + M[8] = r2c0; M[9] = r2c1; M[10] = r2c2; M[11] = r2c3; + M[12] = r3c0; M[13] = r3c1; M[14] = r3c2; M[15] = r3c3; + } + + //! Copy constructor + /** \param other Other matrix to copy from + \param constructor Choose the initialization style */ + CMatrix4(const CMatrix4& other, eConstructor constructor = EM4CONST_COPY); + + //! Simple operator for directly accessing every element of the matrix. + T& operator()(const s32 row, const s32 col) + { +#if defined ( USE_MATRIX_TEST ) + definitelyIdentityMatrix=false; +#endif + return M[ row * 4 + col ]; + } + + //! Simple operator for directly accessing every element of the matrix. + const T& operator()(const s32 row, const s32 col) const { return M[row * 4 + col]; } + + //! Simple operator for linearly accessing every element of the matrix. + T& operator[](u32 index) + { +#if defined ( USE_MATRIX_TEST ) + definitelyIdentityMatrix=false; +#endif + return M[index]; + } + + //! Simple operator for linearly accessing every element of the matrix. + const T& operator[](u32 index) const { return M[index]; } + + //! Sets this matrix equal to the other matrix. + inline CMatrix4& operator=(const CMatrix4 &other); + + //! Sets all elements of this matrix to the value. + inline CMatrix4& operator=(const T& scalar); + + //! Returns pointer to internal array + const T* pointer() const { return M; } + T* pointer() + { +#if defined ( USE_MATRIX_TEST ) + definitelyIdentityMatrix=false; +#endif + return M; + } + + //! Returns true if other matrix is equal to this matrix. + bool operator==(const CMatrix4 &other) const; + + //! Returns true if other matrix is not equal to this matrix. + bool operator!=(const CMatrix4 &other) const; + + //! Add another matrix. + CMatrix4 operator+(const CMatrix4& other) const; + + //! Add another matrix. + CMatrix4& operator+=(const CMatrix4& other); + + //! Subtract another matrix. + CMatrix4 operator-(const CMatrix4& other) const; + + //! Subtract another matrix. + CMatrix4& operator-=(const CMatrix4& other); + + //! set this matrix to the product of two matrices + /** Calculate b*a */ + inline CMatrix4& setbyproduct(const CMatrix4& other_a,const CMatrix4& other_b ); + + //! Set this matrix to the product of two matrices + /** Calculate b*a, no optimization used, + use it if you know you never have a identity matrix */ + CMatrix4& setbyproduct_nocheck(const CMatrix4& other_a,const CMatrix4& other_b ); + + //! Multiply by another matrix. + /** Calculate other*this */ + CMatrix4 operator*(const CMatrix4& other) const; + + //! Multiply by another matrix. + /** Calculate and return other*this */ + CMatrix4& operator*=(const CMatrix4& other); + + //! Multiply by scalar. + CMatrix4 operator*(const T& scalar) const; + + //! Multiply by scalar. + CMatrix4& operator*=(const T& scalar); + + //! Set matrix to identity. + inline CMatrix4& makeIdentity(); + + //! Returns true if the matrix is the identity matrix + inline bool isIdentity() const; + + //! Returns true if the matrix is orthogonal + inline bool isOrthogonal() const; + + //! Returns true if the matrix is the identity matrix + bool isIdentity_integer_base () const; + + //! Set the translation of the current matrix. Will erase any previous values. + CMatrix4& setTranslation( const vector3d& translation ); + + //! Gets the current translation + vector3d getTranslation() const; + + //! Set the inverse translation of the current matrix. Will erase any previous values. + CMatrix4& setInverseTranslation( const vector3d& translation ); + + //! Make a rotation matrix from Euler angles. The 4th row and column are unmodified. + inline CMatrix4& setRotationRadians( const vector3d& rotation ); + + //! Make a rotation matrix from Euler angles. The 4th row and column are unmodified. + CMatrix4& setRotationDegrees( const vector3d& rotation ); + + //! Get the rotation, as set by setRotation() when you already know the scale. + /** If you already know the scale then this function is faster than the other getRotationDegrees overload. + NOTE: You will have the same end-rotation as used in setRotation, but it might not use the same axis values. + */ + core::vector3d getRotationDegrees(const vector3d& scale) const; + + //! Returns the rotation, as set by setRotation(). + /** NOTE: You will have the same end-rotation as used in setRotation, but it might not use the same axis values. + */ + core::vector3d getRotationDegrees() const; + + //! Make an inverted rotation matrix from Euler angles. + /** The 4th row and column are unmodified. */ + inline CMatrix4& setInverseRotationRadians( const vector3d& rotation ); + + //! Make an inverted rotation matrix from Euler angles. + /** The 4th row and column are unmodified. */ + inline CMatrix4& setInverseRotationDegrees( const vector3d& rotation ); + + //! Make a rotation matrix from angle and axis, assuming left handed rotation. + /** The 4th row and column are unmodified. */ + inline CMatrix4& setRotationAxisRadians(const T& angle, const vector3d& axis); + + //! Set Scale + CMatrix4& setScale( const vector3d& scale ); + + //! Set Scale + CMatrix4& setScale( const T scale ) { return setScale(core::vector3d(scale,scale,scale)); } + + //! Get Scale + core::vector3d getScale() const; + + //! Translate a vector by the inverse of the translation part of this matrix. + void inverseTranslateVect( vector3df& vect ) const; + + //! Rotate a vector by the inverse of the rotation part of this matrix. + void inverseRotateVect( vector3df& vect ) const; + + //! Rotate a vector by the rotation part of this matrix. + void rotateVect( vector3df& vect ) const; + + //! An alternate transform vector method, writing into a second vector + void rotateVect(core::vector3df& out, const core::vector3df& in) const; + + //! An alternate transform vector method, writing into an array of 3 floats + void rotateVect(T *out,const core::vector3df &in) const; + + //! Transforms the vector by this matrix + /** This operation is performed as if the vector was 4d with the 4th component =1 */ + void transformVect( vector3df& vect) const; + + //! Transforms input vector by this matrix and stores result in output vector + /** This operation is performed as if the vector was 4d with the 4th component =1 */ + void transformVect( vector3df& out, const vector3df& in ) const; + + //! An alternate transform vector method, writing into an array of 4 floats + /** This operation is performed as if the vector was 4d with the 4th component =1. + NOTE: out[3] will be written to (4th vector component)*/ + void transformVect(T *out,const core::vector3df &in) const; + + //! An alternate transform vector method, reading from and writing to an array of 3 floats + /** This operation is performed as if the vector was 4d with the 4th component =1 + NOTE: out[3] will be written to (4th vector component)*/ + void transformVec3(T *out, const T * in) const; + + //! Translate a vector by the translation part of this matrix. + /** This operation is performed as if the vector was 4d with the 4th component =1 */ + void translateVect( vector3df& vect ) const; + + //! Transforms a plane by this matrix + void transformPlane( core::plane3d &plane) const; + + //! Transforms a plane by this matrix + void transformPlane( const core::plane3d &in, core::plane3d &out) const; + + //! Transforms a axis aligned bounding box + /** The result box of this operation may not be accurate at all. For + correct results, use transformBoxEx() */ + void transformBox(core::aabbox3d& box) const; + + //! Transforms a axis aligned bounding box + /** The result box of this operation should be accurate, but this operation + is slower than transformBox(). */ + void transformBoxEx(core::aabbox3d& box) const; + + //! Multiplies this matrix by a 1x4 matrix + void multiplyWith1x4Matrix(T* matrix) const; + + //! Calculates inverse of matrix. Slow. + /** \return Returns false if there is no inverse matrix.*/ + bool makeInverse(); + + + //! Inverts a primitive matrix which only contains a translation and a rotation + /** \param out: where result matrix is written to. */ + bool getInversePrimitive ( CMatrix4& out ) const; + + //! Gets the inverse matrix of this one + /** \param out: where result matrix is written to. + \return Returns false if there is no inverse matrix. */ + bool getInverse(CMatrix4& out) const; + + //! Builds a right-handed perspective projection matrix based on a field of view + CMatrix4& buildProjectionMatrixPerspectiveFovRH(f32 fieldOfViewRadians, f32 aspectRatio, f32 zNear, f32 zFar); + + //! Builds a left-handed perspective projection matrix based on a field of view + CMatrix4& buildProjectionMatrixPerspectiveFovLH(f32 fieldOfViewRadians, f32 aspectRatio, f32 zNear, f32 zFar); + + //! Builds a left-handed perspective projection matrix based on a field of view, with far plane at infinity + CMatrix4& buildProjectionMatrixPerspectiveFovInfinityLH(f32 fieldOfViewRadians, f32 aspectRatio, f32 zNear, f32 epsilon=0); + + //! Builds a right-handed perspective projection matrix. + CMatrix4& buildProjectionMatrixPerspectiveRH(f32 widthOfViewVolume, f32 heightOfViewVolume, f32 zNear, f32 zFar); + + //! Builds a left-handed perspective projection matrix. + CMatrix4& buildProjectionMatrixPerspectiveLH(f32 widthOfViewVolume, f32 heightOfViewVolume, f32 zNear, f32 zFar); + + //! Builds a left-handed orthogonal projection matrix. + CMatrix4& buildProjectionMatrixOrthoLH(f32 widthOfViewVolume, f32 heightOfViewVolume, f32 zNear, f32 zFar); + + //! Builds a right-handed orthogonal projection matrix. + CMatrix4& buildProjectionMatrixOrthoRH(f32 widthOfViewVolume, f32 heightOfViewVolume, f32 zNear, f32 zFar); + + //! Builds a left-handed look-at matrix. + CMatrix4& buildCameraLookAtMatrixLH( + const vector3df& position, + const vector3df& target, + const vector3df& upVector); + + //! Builds a right-handed look-at matrix. + CMatrix4& buildCameraLookAtMatrixRH( + const vector3df& position, + const vector3df& target, + const vector3df& upVector); + + //! Builds a matrix that flattens geometry into a plane. + /** \param light: light source + \param plane: plane into which the geometry if flattened into + \param point: value between 0 and 1, describing the light source. + If this is 1, it is a point light, if it is 0, it is a directional light. */ + CMatrix4& buildShadowMatrix(const core::vector3df& light, core::plane3df plane, f32 point=1.0f); + + //! Builds a matrix which transforms a normalized Device Coordinate to Device Coordinates. + /** Used to scale <-1,-1><1,1> to viewport, for example from <-1,-1> <1,1> to the viewport <0,0><0,640> */ + CMatrix4& buildNDCToDCMatrix( const core::rect& area, f32 zScale); + + //! Creates a new matrix as interpolated matrix from two other ones. + /** \param b: other matrix to interpolate with + \param time: Must be a value between 0 and 1. */ + CMatrix4 interpolate(const core::CMatrix4& b, f32 time) const; + + //! Gets transposed matrix + CMatrix4 getTransposed() const; + + //! Gets transposed matrix + inline void getTransposed( CMatrix4& dest ) const; + + //! Builds a matrix that rotates from one vector to another + /** \param from: vector to rotate from + \param to: vector to rotate to + */ + CMatrix4& buildRotateFromTo(const core::vector3df& from, const core::vector3df& to); + + //! Builds a combined matrix which translates to a center before rotation and translates from origin afterwards + /** \param center Position to rotate around + \param translate Translation applied after the rotation + */ + void setRotationCenter(const core::vector3df& center, const core::vector3df& translate); + + //! Builds a matrix which rotates a source vector to a look vector over an arbitrary axis + /** \param camPos: viewer position in world coo + \param center: object position in world-coo and rotation pivot + \param translation: object final translation from center + \param axis: axis to rotate about + \param from: source vector to rotate from + */ + void buildAxisAlignedBillboard(const core::vector3df& camPos, + const core::vector3df& center, + const core::vector3df& translation, + const core::vector3df& axis, + const core::vector3df& from); + + /* + construct 2D Texture transformations + rotate about center, scale, and transform. + */ + //! Set to a texture transformation matrix with the given parameters. + CMatrix4& buildTextureTransform( f32 rotateRad, + const core::vector2df &rotatecenter, + const core::vector2df &translate, + const core::vector2df &scale); + + //! Set texture transformation rotation + /** Rotate about z axis, recenter at (0.5,0.5). + Doesn't clear other elements than those affected + \param radAngle Angle in radians + \return Altered matrix */ + CMatrix4& setTextureRotationCenter( f32 radAngle ); + + //! Set texture transformation translation + /** Doesn't clear other elements than those affected. + \param x Offset on x axis + \param y Offset on y axis + \return Altered matrix */ + CMatrix4& setTextureTranslate( f32 x, f32 y ); + + //! Get texture transformation translation + /** \param x returns offset on x axis + \param y returns offset on y axis */ + void getTextureTranslate( f32& x, f32& y ) const; + + //! Set texture transformation translation, using a transposed representation + /** Doesn't clear other elements than those affected. + \param x Offset on x axis + \param y Offset on y axis + \return Altered matrix */ + CMatrix4& setTextureTranslateTransposed( f32 x, f32 y ); + + //! Set texture transformation scale + /** Doesn't clear other elements than those affected. + \param sx Scale factor on x axis + \param sy Scale factor on y axis + \return Altered matrix. */ + CMatrix4& setTextureScale( f32 sx, f32 sy ); + + //! Get texture transformation scale + /** \param sx Returns x axis scale factor + \param sy Returns y axis scale factor */ + void getTextureScale( f32& sx, f32& sy ) const; + + //! Set texture transformation scale, and recenter at (0.5,0.5) + /** Doesn't clear other elements than those affected. + \param sx Scale factor on x axis + \param sy Scale factor on y axis + \return Altered matrix. */ + CMatrix4& setTextureScaleCenter( f32 sx, f32 sy ); + + //! Sets all matrix data members at once + CMatrix4& setM(const T* data); + + //! Sets if the matrix is definitely identity matrix + void setDefinitelyIdentityMatrix( bool isDefinitelyIdentityMatrix); + + //! Gets if the matrix is definitely identity matrix + bool getDefinitelyIdentityMatrix() const; + + //! Compare two matrices using the equal method + bool equals(const core::CMatrix4& other, const T tolerance=(T)ROUNDING_ERROR_f64) const; + + private: + //! Matrix data, stored in row-major order + T M[16]; +#if defined ( USE_MATRIX_TEST ) + //! Flag is this matrix is identity matrix + mutable u32 definitelyIdentityMatrix; +#endif +#if defined ( USE_MATRIX_TEST_DEBUG ) + u32 id; + mutable u32 calls; +#endif + + }; + + // Default constructor + template + inline CMatrix4::CMatrix4( eConstructor constructor ) +#if defined ( USE_MATRIX_TEST ) + : definitelyIdentityMatrix(BIT_UNTESTED) +#endif +#if defined ( USE_MATRIX_TEST_DEBUG ) + ,id ( MTest.ID++), calls ( 0 ) +#endif + { + switch ( constructor ) + { + case EM4CONST_NOTHING: + case EM4CONST_COPY: + break; + case EM4CONST_IDENTITY: + case EM4CONST_INVERSE: + default: + makeIdentity(); + break; + } + } + + // Copy constructor + template + inline CMatrix4::CMatrix4( const CMatrix4& other, eConstructor constructor) +#if defined ( USE_MATRIX_TEST ) + : definitelyIdentityMatrix(BIT_UNTESTED) +#endif +#if defined ( USE_MATRIX_TEST_DEBUG ) + ,id ( MTest.ID++), calls ( 0 ) +#endif + { + switch ( constructor ) + { + case EM4CONST_IDENTITY: + makeIdentity(); + break; + case EM4CONST_NOTHING: + break; + case EM4CONST_COPY: + *this = other; + break; + case EM4CONST_TRANSPOSED: + other.getTransposed(*this); + break; + case EM4CONST_INVERSE: + if (!other.getInverse(*this)) + memset(M, 0, 16*sizeof(T)); + break; + case EM4CONST_INVERSE_TRANSPOSED: + if (!other.getInverse(*this)) + memset(M, 0, 16*sizeof(T)); + else + *this=getTransposed(); + break; + } + } + + //! Add another matrix. + template + inline CMatrix4 CMatrix4::operator+(const CMatrix4& other) const + { + CMatrix4 temp ( EM4CONST_NOTHING ); + + temp[0] = M[0]+other[0]; + temp[1] = M[1]+other[1]; + temp[2] = M[2]+other[2]; + temp[3] = M[3]+other[3]; + temp[4] = M[4]+other[4]; + temp[5] = M[5]+other[5]; + temp[6] = M[6]+other[6]; + temp[7] = M[7]+other[7]; + temp[8] = M[8]+other[8]; + temp[9] = M[9]+other[9]; + temp[10] = M[10]+other[10]; + temp[11] = M[11]+other[11]; + temp[12] = M[12]+other[12]; + temp[13] = M[13]+other[13]; + temp[14] = M[14]+other[14]; + temp[15] = M[15]+other[15]; + + return temp; + } + + //! Add another matrix. + template + inline CMatrix4& CMatrix4::operator+=(const CMatrix4& other) + { + M[0]+=other[0]; + M[1]+=other[1]; + M[2]+=other[2]; + M[3]+=other[3]; + M[4]+=other[4]; + M[5]+=other[5]; + M[6]+=other[6]; + M[7]+=other[7]; + M[8]+=other[8]; + M[9]+=other[9]; + M[10]+=other[10]; + M[11]+=other[11]; + M[12]+=other[12]; + M[13]+=other[13]; + M[14]+=other[14]; + M[15]+=other[15]; + + return *this; + } + + //! Subtract another matrix. + template + inline CMatrix4 CMatrix4::operator-(const CMatrix4& other) const + { + CMatrix4 temp ( EM4CONST_NOTHING ); + + temp[0] = M[0]-other[0]; + temp[1] = M[1]-other[1]; + temp[2] = M[2]-other[2]; + temp[3] = M[3]-other[3]; + temp[4] = M[4]-other[4]; + temp[5] = M[5]-other[5]; + temp[6] = M[6]-other[6]; + temp[7] = M[7]-other[7]; + temp[8] = M[8]-other[8]; + temp[9] = M[9]-other[9]; + temp[10] = M[10]-other[10]; + temp[11] = M[11]-other[11]; + temp[12] = M[12]-other[12]; + temp[13] = M[13]-other[13]; + temp[14] = M[14]-other[14]; + temp[15] = M[15]-other[15]; + + return temp; + } + + //! Subtract another matrix. + template + inline CMatrix4& CMatrix4::operator-=(const CMatrix4& other) + { + M[0]-=other[0]; + M[1]-=other[1]; + M[2]-=other[2]; + M[3]-=other[3]; + M[4]-=other[4]; + M[5]-=other[5]; + M[6]-=other[6]; + M[7]-=other[7]; + M[8]-=other[8]; + M[9]-=other[9]; + M[10]-=other[10]; + M[11]-=other[11]; + M[12]-=other[12]; + M[13]-=other[13]; + M[14]-=other[14]; + M[15]-=other[15]; + + return *this; + } + + //! Multiply by scalar. + template + inline CMatrix4 CMatrix4::operator*(const T& scalar) const + { + CMatrix4 temp ( EM4CONST_NOTHING ); + + temp[0] = M[0]*scalar; + temp[1] = M[1]*scalar; + temp[2] = M[2]*scalar; + temp[3] = M[3]*scalar; + temp[4] = M[4]*scalar; + temp[5] = M[5]*scalar; + temp[6] = M[6]*scalar; + temp[7] = M[7]*scalar; + temp[8] = M[8]*scalar; + temp[9] = M[9]*scalar; + temp[10] = M[10]*scalar; + temp[11] = M[11]*scalar; + temp[12] = M[12]*scalar; + temp[13] = M[13]*scalar; + temp[14] = M[14]*scalar; + temp[15] = M[15]*scalar; + + return temp; + } + + //! Multiply by scalar. + template + inline CMatrix4& CMatrix4::operator*=(const T& scalar) + { + M[0]*=scalar; + M[1]*=scalar; + M[2]*=scalar; + M[3]*=scalar; + M[4]*=scalar; + M[5]*=scalar; + M[6]*=scalar; + M[7]*=scalar; + M[8]*=scalar; + M[9]*=scalar; + M[10]*=scalar; + M[11]*=scalar; + M[12]*=scalar; + M[13]*=scalar; + M[14]*=scalar; + M[15]*=scalar; + + return *this; + } + + //! Multiply by another matrix. + template + inline CMatrix4& CMatrix4::operator*=(const CMatrix4& other) + { +#if defined ( USE_MATRIX_TEST ) + // do checks on your own in order to avoid copy creation + if ( !other.isIdentity() ) + { + if ( this->isIdentity() ) + { + return (*this = other); + } + else + { + CMatrix4 temp ( *this ); + return setbyproduct_nocheck( temp, other ); + } + } + return *this; +#else + CMatrix4 temp ( *this ); + return setbyproduct_nocheck( temp, other ); +#endif + } + + //! multiply by another matrix + // set this matrix to the product of two other matrices + // goal is to reduce stack use and copy + template + inline CMatrix4& CMatrix4::setbyproduct_nocheck(const CMatrix4& other_a,const CMatrix4& other_b ) + { + const T *m1 = other_a.M; + const T *m2 = other_b.M; + + M[0] = m1[0]*m2[0] + m1[4]*m2[1] + m1[8]*m2[2] + m1[12]*m2[3]; + M[1] = m1[1]*m2[0] + m1[5]*m2[1] + m1[9]*m2[2] + m1[13]*m2[3]; + M[2] = m1[2]*m2[0] + m1[6]*m2[1] + m1[10]*m2[2] + m1[14]*m2[3]; + M[3] = m1[3]*m2[0] + m1[7]*m2[1] + m1[11]*m2[2] + m1[15]*m2[3]; + + M[4] = m1[0]*m2[4] + m1[4]*m2[5] + m1[8]*m2[6] + m1[12]*m2[7]; + M[5] = m1[1]*m2[4] + m1[5]*m2[5] + m1[9]*m2[6] + m1[13]*m2[7]; + M[6] = m1[2]*m2[4] + m1[6]*m2[5] + m1[10]*m2[6] + m1[14]*m2[7]; + M[7] = m1[3]*m2[4] + m1[7]*m2[5] + m1[11]*m2[6] + m1[15]*m2[7]; + + M[8] = m1[0]*m2[8] + m1[4]*m2[9] + m1[8]*m2[10] + m1[12]*m2[11]; + M[9] = m1[1]*m2[8] + m1[5]*m2[9] + m1[9]*m2[10] + m1[13]*m2[11]; + M[10] = m1[2]*m2[8] + m1[6]*m2[9] + m1[10]*m2[10] + m1[14]*m2[11]; + M[11] = m1[3]*m2[8] + m1[7]*m2[9] + m1[11]*m2[10] + m1[15]*m2[11]; + + M[12] = m1[0]*m2[12] + m1[4]*m2[13] + m1[8]*m2[14] + m1[12]*m2[15]; + M[13] = m1[1]*m2[12] + m1[5]*m2[13] + m1[9]*m2[14] + m1[13]*m2[15]; + M[14] = m1[2]*m2[12] + m1[6]*m2[13] + m1[10]*m2[14] + m1[14]*m2[15]; + M[15] = m1[3]*m2[12] + m1[7]*m2[13] + m1[11]*m2[14] + m1[15]*m2[15]; +#if defined ( USE_MATRIX_TEST ) + definitelyIdentityMatrix=false; +#endif + return *this; + } + + + //! multiply by another matrix + // set this matrix to the product of two other matrices + // goal is to reduce stack use and copy + template + inline CMatrix4& CMatrix4::setbyproduct(const CMatrix4& other_a, const CMatrix4& other_b ) + { +#if defined ( USE_MATRIX_TEST ) + if ( other_a.isIdentity () ) + return (*this = other_b); + else + if ( other_b.isIdentity () ) + return (*this = other_a); + else + return setbyproduct_nocheck(other_a,other_b); +#else + return setbyproduct_nocheck(other_a,other_b); +#endif + } + + //! multiply by another matrix + template + inline CMatrix4 CMatrix4::operator*(const CMatrix4& m2) const + { +#if defined ( USE_MATRIX_TEST ) + // Testing purpose.. + if ( this->isIdentity() ) + return m2; + if ( m2.isIdentity() ) + return *this; +#endif + + CMatrix4 m3 ( EM4CONST_NOTHING ); + + const T *m1 = M; + + m3[0] = m1[0]*m2[0] + m1[4]*m2[1] + m1[8]*m2[2] + m1[12]*m2[3]; + m3[1] = m1[1]*m2[0] + m1[5]*m2[1] + m1[9]*m2[2] + m1[13]*m2[3]; + m3[2] = m1[2]*m2[0] + m1[6]*m2[1] + m1[10]*m2[2] + m1[14]*m2[3]; + m3[3] = m1[3]*m2[0] + m1[7]*m2[1] + m1[11]*m2[2] + m1[15]*m2[3]; + + m3[4] = m1[0]*m2[4] + m1[4]*m2[5] + m1[8]*m2[6] + m1[12]*m2[7]; + m3[5] = m1[1]*m2[4] + m1[5]*m2[5] + m1[9]*m2[6] + m1[13]*m2[7]; + m3[6] = m1[2]*m2[4] + m1[6]*m2[5] + m1[10]*m2[6] + m1[14]*m2[7]; + m3[7] = m1[3]*m2[4] + m1[7]*m2[5] + m1[11]*m2[6] + m1[15]*m2[7]; + + m3[8] = m1[0]*m2[8] + m1[4]*m2[9] + m1[8]*m2[10] + m1[12]*m2[11]; + m3[9] = m1[1]*m2[8] + m1[5]*m2[9] + m1[9]*m2[10] + m1[13]*m2[11]; + m3[10] = m1[2]*m2[8] + m1[6]*m2[9] + m1[10]*m2[10] + m1[14]*m2[11]; + m3[11] = m1[3]*m2[8] + m1[7]*m2[9] + m1[11]*m2[10] + m1[15]*m2[11]; + + m3[12] = m1[0]*m2[12] + m1[4]*m2[13] + m1[8]*m2[14] + m1[12]*m2[15]; + m3[13] = m1[1]*m2[12] + m1[5]*m2[13] + m1[9]*m2[14] + m1[13]*m2[15]; + m3[14] = m1[2]*m2[12] + m1[6]*m2[13] + m1[10]*m2[14] + m1[14]*m2[15]; + m3[15] = m1[3]*m2[12] + m1[7]*m2[13] + m1[11]*m2[14] + m1[15]*m2[15]; + return m3; + } + + + + template + inline vector3d CMatrix4::getTranslation() const + { + return vector3d(M[12], M[13], M[14]); + } + + + template + inline CMatrix4& CMatrix4::setTranslation( const vector3d& translation ) + { + M[12] = translation.X; + M[13] = translation.Y; + M[14] = translation.Z; +#if defined ( USE_MATRIX_TEST ) + definitelyIdentityMatrix=false; +#endif + return *this; + } + + template + inline CMatrix4& CMatrix4::setInverseTranslation( const vector3d& translation ) + { + M[12] = -translation.X; + M[13] = -translation.Y; + M[14] = -translation.Z; +#if defined ( USE_MATRIX_TEST ) + definitelyIdentityMatrix=false; +#endif + return *this; + } + + template + inline CMatrix4& CMatrix4::setScale( const vector3d& scale ) + { + M[0] = scale.X; + M[5] = scale.Y; + M[10] = scale.Z; +#if defined ( USE_MATRIX_TEST ) + definitelyIdentityMatrix=false; +#endif + return *this; + } + + //! Returns the absolute values of the scales of the matrix. + /** + Note that this returns the absolute (positive) values unless only scale is set. + Unfortunately it does not appear to be possible to extract any original negative + values. The best that we could do would be to arbitrarily make one scale + negative if one or three of them were negative. + FIXME - return the original values. + */ + template + inline vector3d CMatrix4::getScale() const + { + // See http://www.robertblum.com/articles/2005/02/14/decomposing-matrices + + // Deal with the 0 rotation case first + // Prior to Irrlicht 1.6, we always returned this value. + if(core::iszero(M[1]) && core::iszero(M[2]) && + core::iszero(M[4]) && core::iszero(M[6]) && + core::iszero(M[8]) && core::iszero(M[9])) + return vector3d(M[0], M[5], M[10]); + + // We have to do the full calculation. + return vector3d(sqrtf(M[0] * M[0] + M[1] * M[1] + M[2] * M[2]), + sqrtf(M[4] * M[4] + M[5] * M[5] + M[6] * M[6]), + sqrtf(M[8] * M[8] + M[9] * M[9] + M[10] * M[10])); + } + + template + inline CMatrix4& CMatrix4::setRotationDegrees( const vector3d& rotation ) + { + return setRotationRadians( rotation * core::DEGTORAD ); + } + + template + inline CMatrix4& CMatrix4::setInverseRotationDegrees( const vector3d& rotation ) + { + return setInverseRotationRadians( rotation * core::DEGTORAD ); + } + + template + inline CMatrix4& CMatrix4::setRotationRadians( const vector3d& rotation ) + { + const f64 cr = cos( rotation.X ); + const f64 sr = sin( rotation.X ); + const f64 cp = cos( rotation.Y ); + const f64 sp = sin( rotation.Y ); + const f64 cy = cos( rotation.Z ); + const f64 sy = sin( rotation.Z ); + + M[0] = (T)( cp*cy ); + M[1] = (T)( cp*sy ); + M[2] = (T)( -sp ); + + const f64 srsp = sr*sp; + const f64 crsp = cr*sp; + + M[4] = (T)( srsp*cy-cr*sy ); + M[5] = (T)( srsp*sy+cr*cy ); + M[6] = (T)( sr*cp ); + + M[8] = (T)( crsp*cy+sr*sy ); + M[9] = (T)( crsp*sy-sr*cy ); + M[10] = (T)( cr*cp ); +#if defined ( USE_MATRIX_TEST ) + definitelyIdentityMatrix=false; +#endif + return *this; + } + + + //! Returns a rotation that is equivalent to that set by setRotationDegrees(). + /** This code was sent in by Chev. Note that it does not necessarily return + the *same* Euler angles as those set by setRotationDegrees(), but the rotation will + be equivalent, i.e. will have the same result when used to rotate a vector or node. + This code was originally written by by Chev. + */ + template + inline core::vector3d CMatrix4::getRotationDegrees(const vector3d& scale_) const + { + const CMatrix4 &mat = *this; + core::vector3d scale(scale_); + // we need to check for negative scale on to axes, which would bring up wrong results + if (scale.Y<0 && scale.Z<0) + { + scale.Y =-scale.Y; + scale.Z =-scale.Z; + } + else if (scale.X<0 && scale.Z<0) + { + scale.X =-scale.X; + scale.Z =-scale.Z; + } + else if (scale.X<0 && scale.Y<0) + { + scale.X =-scale.X; + scale.Y =-scale.Y; + } + const core::vector3d invScale(core::reciprocal(scale.X),core::reciprocal(scale.Y),core::reciprocal(scale.Z)); + + f64 Y = -asin(core::clamp(mat[2]*invScale.X, -1.0, 1.0)); + const f64 C = cos(Y); + Y *= RADTODEG64; + + f64 rotx, roty, X, Z; + + if (!core::iszero(C)) + { + const f64 invC = core::reciprocal(C); + rotx = mat[10] * invC * invScale.Z; + roty = mat[6] * invC * invScale.Y; + X = atan2( roty, rotx ) * RADTODEG64; + rotx = mat[0] * invC * invScale.X; + roty = mat[1] * invC * invScale.X; + Z = atan2( roty, rotx ) * RADTODEG64; + } + else + { + X = 0.0; + rotx = mat[5] * invScale.Y; + roty = -mat[4] * invScale.Y; + Z = atan2( roty, rotx ) * RADTODEG64; + } + + // fix values that get below zero + if (X < 0.0) X += 360.0; + if (Y < 0.0) Y += 360.0; + if (Z < 0.0) Z += 360.0; + + return vector3d((T)X,(T)Y,(T)Z); + } + + //! Returns a rotation that is equivalent to that set by setRotationDegrees(). + /** This code was sent in by Chev. Note that it does not necessarily return + the *same* Euler angles as those set by setRotationDegrees(), but the rotation will + be equivalent, i.e. will have the same result when used to rotate a vector or node. + This code was originally written by by Chev. */ + template + inline core::vector3d CMatrix4::getRotationDegrees() const + { + return getRotationDegrees(getScale()); + } + + + //! Sets matrix to rotation matrix of inverse angles given as parameters + template + inline CMatrix4& CMatrix4::setInverseRotationRadians( const vector3d& rotation ) + { + f64 cr = cos( rotation.X ); + f64 sr = sin( rotation.X ); + f64 cp = cos( rotation.Y ); + f64 sp = sin( rotation.Y ); + f64 cy = cos( rotation.Z ); + f64 sy = sin( rotation.Z ); + + M[0] = (T)( cp*cy ); + M[4] = (T)( cp*sy ); + M[8] = (T)( -sp ); + + f64 srsp = sr*sp; + f64 crsp = cr*sp; + + M[1] = (T)( srsp*cy-cr*sy ); + M[5] = (T)( srsp*sy+cr*cy ); + M[9] = (T)( sr*cp ); + + M[2] = (T)( crsp*cy+sr*sy ); + M[6] = (T)( crsp*sy-sr*cy ); + M[10] = (T)( cr*cp ); +#if defined ( USE_MATRIX_TEST ) + definitelyIdentityMatrix=false; +#endif + return *this; + } + + //! Sets matrix to rotation matrix defined by axis and angle, assuming LH rotation + template + + inline CMatrix4& CMatrix4::setRotationAxisRadians( const T& angle, const vector3d& axis ) + { + const f64 c = cos(angle); + const f64 s = sin(angle); + const f64 t = 1.0 - c; + + const f64 tx = t * axis.X; + const f64 ty = t * axis.Y; + const f64 tz = t * axis.Z; + + const f64 sx = s * axis.X; + const f64 sy = s * axis.Y; + const f64 sz = s * axis.Z; + + M[0] = (T)(tx * axis.X + c); + M[1] = (T)(tx * axis.Y + sz); + M[2] = (T)(tx * axis.Z - sy); + + M[4] = (T)(ty * axis.X - sz); + M[5] = (T)(ty * axis.Y + c); + M[6] = (T)(ty * axis.Z + sx); + + M[8] = (T)(tz * axis.X + sy); + M[9] = (T)(tz * axis.Y - sx); + M[10] = (T)(tz * axis.Z + c); + +#if defined ( USE_MATRIX_TEST ) + definitelyIdentityMatrix=false; +#endif + return *this; + } + + + /*! + */ + template + inline CMatrix4& CMatrix4::makeIdentity() + { + memset(M, 0, 16*sizeof(T)); + M[0] = M[5] = M[10] = M[15] = (T)1; +#if defined ( USE_MATRIX_TEST ) + definitelyIdentityMatrix=true; +#endif + return *this; + } + + + /* + check identity with epsilon + solve floating range problems.. + */ + template + inline bool CMatrix4::isIdentity() const + { +#if defined ( USE_MATRIX_TEST ) + if (definitelyIdentityMatrix) + return true; +#endif + if (!core::equals( M[12], (T)0 ) || !core::equals( M[13], (T)0 ) || !core::equals( M[14], (T)0 ) || !core::equals( M[15], (T)1 )) + return false; + + if (!core::equals( M[ 0], (T)1 ) || !core::equals( M[ 1], (T)0 ) || !core::equals( M[ 2], (T)0 ) || !core::equals( M[ 3], (T)0 )) + return false; + + if (!core::equals( M[ 4], (T)0 ) || !core::equals( M[ 5], (T)1 ) || !core::equals( M[ 6], (T)0 ) || !core::equals( M[ 7], (T)0 )) + return false; + + if (!core::equals( M[ 8], (T)0 ) || !core::equals( M[ 9], (T)0 ) || !core::equals( M[10], (T)1 ) || !core::equals( M[11], (T)0 )) + return false; +/* + if (!core::equals( M[ 0], (T)1 ) || + !core::equals( M[ 5], (T)1 ) || + !core::equals( M[10], (T)1 ) || + !core::equals( M[15], (T)1 )) + return false; + + for (s32 i=0; i<4; ++i) + for (s32 j=0; j<4; ++j) + if ((j != i) && (!iszero((*this)(i,j)))) + return false; +*/ +#if defined ( USE_MATRIX_TEST ) + definitelyIdentityMatrix=true; +#endif + return true; + } + + + /* Check orthogonality of matrix. */ + template + inline bool CMatrix4::isOrthogonal() const + { + T dp=M[0] * M[4 ] + M[1] * M[5 ] + M[2 ] * M[6 ] + M[3 ] * M[7 ]; + if (!iszero(dp)) + return false; + dp = M[0] * M[8 ] + M[1] * M[9 ] + M[2 ] * M[10] + M[3 ] * M[11]; + if (!iszero(dp)) + return false; + dp = M[0] * M[12] + M[1] * M[13] + M[2 ] * M[14] + M[3 ] * M[15]; + if (!iszero(dp)) + return false; + dp = M[4] * M[8 ] + M[5] * M[9 ] + M[6 ] * M[10] + M[7 ] * M[11]; + if (!iszero(dp)) + return false; + dp = M[4] * M[12] + M[5] * M[13] + M[6 ] * M[14] + M[7 ] * M[15]; + if (!iszero(dp)) + return false; + dp = M[8] * M[12] + M[9] * M[13] + M[10] * M[14] + M[11] * M[15]; + return (iszero(dp)); + } + + + /* + doesn't solve floating range problems.. + but takes care on +/- 0 on translation because we are changing it.. + reducing floating point branches + but it needs the floats in memory.. + */ + template + inline bool CMatrix4::isIdentity_integer_base() const + { +#if defined ( USE_MATRIX_TEST ) + if (definitelyIdentityMatrix) + return true; +#endif + if(IR(M[0])!=F32_VALUE_1) return false; + if(IR(M[1])!=0) return false; + if(IR(M[2])!=0) return false; + if(IR(M[3])!=0) return false; + + if(IR(M[4])!=0) return false; + if(IR(M[5])!=F32_VALUE_1) return false; + if(IR(M[6])!=0) return false; + if(IR(M[7])!=0) return false; + + if(IR(M[8])!=0) return false; + if(IR(M[9])!=0) return false; + if(IR(M[10])!=F32_VALUE_1) return false; + if(IR(M[11])!=0) return false; + + if(IR(M[12])!=0) return false; + if(IR(M[13])!=0) return false; + if(IR(M[13])!=0) return false; + if(IR(M[15])!=F32_VALUE_1) return false; + +#if defined ( USE_MATRIX_TEST ) + definitelyIdentityMatrix=true; +#endif + return true; + } + + + template + inline void CMatrix4::rotateVect( vector3df& vect ) const + { + vector3df tmp = vect; + vect.X = tmp.X*M[0] + tmp.Y*M[4] + tmp.Z*M[8]; + vect.Y = tmp.X*M[1] + tmp.Y*M[5] + tmp.Z*M[9]; + vect.Z = tmp.X*M[2] + tmp.Y*M[6] + tmp.Z*M[10]; + } + + //! An alternate transform vector method, writing into a second vector + template + inline void CMatrix4::rotateVect(core::vector3df& out, const core::vector3df& in) const + { + out.X = in.X*M[0] + in.Y*M[4] + in.Z*M[8]; + out.Y = in.X*M[1] + in.Y*M[5] + in.Z*M[9]; + out.Z = in.X*M[2] + in.Y*M[6] + in.Z*M[10]; + } + + //! An alternate transform vector method, writing into an array of 3 floats + template + inline void CMatrix4::rotateVect(T *out, const core::vector3df& in) const + { + out[0] = in.X*M[0] + in.Y*M[4] + in.Z*M[8]; + out[1] = in.X*M[1] + in.Y*M[5] + in.Z*M[9]; + out[2] = in.X*M[2] + in.Y*M[6] + in.Z*M[10]; + } + + template + inline void CMatrix4::inverseRotateVect( vector3df& vect ) const + { + vector3df tmp = vect; + vect.X = tmp.X*M[0] + tmp.Y*M[1] + tmp.Z*M[2]; + vect.Y = tmp.X*M[4] + tmp.Y*M[5] + tmp.Z*M[6]; + vect.Z = tmp.X*M[8] + tmp.Y*M[9] + tmp.Z*M[10]; + } + + template + inline void CMatrix4::transformVect( vector3df& vect) const + { + f32 vector[3]; + + vector[0] = vect.X*M[0] + vect.Y*M[4] + vect.Z*M[8] + M[12]; + vector[1] = vect.X*M[1] + vect.Y*M[5] + vect.Z*M[9] + M[13]; + vector[2] = vect.X*M[2] + vect.Y*M[6] + vect.Z*M[10] + M[14]; + + vect.X = vector[0]; + vect.Y = vector[1]; + vect.Z = vector[2]; + } + + template + inline void CMatrix4::transformVect( vector3df& out, const vector3df& in) const + { + out.X = in.X*M[0] + in.Y*M[4] + in.Z*M[8] + M[12]; + out.Y = in.X*M[1] + in.Y*M[5] + in.Z*M[9] + M[13]; + out.Z = in.X*M[2] + in.Y*M[6] + in.Z*M[10] + M[14]; + } + + + template + inline void CMatrix4::transformVect(T *out, const core::vector3df &in) const + { + out[0] = in.X*M[0] + in.Y*M[4] + in.Z*M[8] + M[12]; + out[1] = in.X*M[1] + in.Y*M[5] + in.Z*M[9] + M[13]; + out[2] = in.X*M[2] + in.Y*M[6] + in.Z*M[10] + M[14]; + out[3] = in.X*M[3] + in.Y*M[7] + in.Z*M[11] + M[15]; + } + + template + inline void CMatrix4::transformVec3(T *out, const T * in) const + { + out[0] = in[0]*M[0] + in[1]*M[4] + in[2]*M[8] + M[12]; + out[1] = in[0]*M[1] + in[1]*M[5] + in[2]*M[9] + M[13]; + out[2] = in[0]*M[2] + in[1]*M[6] + in[2]*M[10] + M[14]; + } + + + //! Transforms a plane by this matrix + template + inline void CMatrix4::transformPlane( core::plane3d &plane) const + { + vector3df member; + // Transform the plane member point, i.e. rotate, translate and scale it. + transformVect(member, plane.getMemberPoint()); + + // Transform the normal by the transposed inverse of the matrix + CMatrix4 transposedInverse(*this, EM4CONST_INVERSE_TRANSPOSED); + vector3df normal = plane.Normal; + transposedInverse.transformVect(normal); + + plane.setPlane(member, normal); + } + + //! Transforms a plane by this matrix + template + inline void CMatrix4::transformPlane( const core::plane3d &in, core::plane3d &out) const + { + out = in; + transformPlane( out ); + } + + //! Transforms the edge-points of a bounding box + //! Deprecated as it's usually not what people need (regards only 2 corners, but other corners might be outside the box after transformation) + //! Use transformBoxEx instead. + template + _IRR_DEPRECATED_ inline void CMatrix4::transformBox(core::aabbox3d& box) const + { +#if defined ( USE_MATRIX_TEST ) + if (isIdentity()) + return; +#endif + + transformVect(box.MinEdge); + transformVect(box.MaxEdge); + box.repair(); + } + + //! Transforms a axis aligned bounding box more accurately than transformBox() + template + inline void CMatrix4::transformBoxEx(core::aabbox3d& box) const + { +#if defined ( USE_MATRIX_TEST ) + if (isIdentity()) + return; +#endif + + const f32 Amin[3] = {box.MinEdge.X, box.MinEdge.Y, box.MinEdge.Z}; + const f32 Amax[3] = {box.MaxEdge.X, box.MaxEdge.Y, box.MaxEdge.Z}; + + f32 Bmin[3]; + f32 Bmax[3]; + + Bmin[0] = Bmax[0] = M[12]; + Bmin[1] = Bmax[1] = M[13]; + Bmin[2] = Bmax[2] = M[14]; + + const CMatrix4 &m = *this; + + for (u32 i = 0; i < 3; ++i) + { + for (u32 j = 0; j < 3; ++j) + { + const f32 a = m(j,i) * Amin[j]; + const f32 b = m(j,i) * Amax[j]; + + if (a < b) + { + Bmin[i] += a; + Bmax[i] += b; + } + else + { + Bmin[i] += b; + Bmax[i] += a; + } + } + } + + box.MinEdge.X = Bmin[0]; + box.MinEdge.Y = Bmin[1]; + box.MinEdge.Z = Bmin[2]; + + box.MaxEdge.X = Bmax[0]; + box.MaxEdge.Y = Bmax[1]; + box.MaxEdge.Z = Bmax[2]; + } + + + //! Multiplies this matrix by a 1x4 matrix + template + inline void CMatrix4::multiplyWith1x4Matrix(T* matrix) const + { + /* + 0 1 2 3 + 4 5 6 7 + 8 9 10 11 + 12 13 14 15 + */ + + T mat[4]; + mat[0] = matrix[0]; + mat[1] = matrix[1]; + mat[2] = matrix[2]; + mat[3] = matrix[3]; + + matrix[0] = M[0]*mat[0] + M[4]*mat[1] + M[8]*mat[2] + M[12]*mat[3]; + matrix[1] = M[1]*mat[0] + M[5]*mat[1] + M[9]*mat[2] + M[13]*mat[3]; + matrix[2] = M[2]*mat[0] + M[6]*mat[1] + M[10]*mat[2] + M[14]*mat[3]; + matrix[3] = M[3]*mat[0] + M[7]*mat[1] + M[11]*mat[2] + M[15]*mat[3]; + } + + template + inline void CMatrix4::inverseTranslateVect( vector3df& vect ) const + { + vect.X = vect.X-M[12]; + vect.Y = vect.Y-M[13]; + vect.Z = vect.Z-M[14]; + } + + template + inline void CMatrix4::translateVect( vector3df& vect ) const + { + vect.X = vect.X+M[12]; + vect.Y = vect.Y+M[13]; + vect.Z = vect.Z+M[14]; + } + + + template + inline bool CMatrix4::getInverse(CMatrix4& out) const + { + /// Calculates the inverse of this Matrix + /// The inverse is calculated using Cramers rule. + /// If no inverse exists then 'false' is returned. + +#if defined ( USE_MATRIX_TEST ) + if ( this->isIdentity() ) + { + out=*this; + return true; + } +#endif + const CMatrix4 &m = *this; + + // Nic has set this to use type T for accuracy + T d = (m[0] * m[5] - m[1] * m[4]) * (m[10] * m[15] - m[11] * m[14]) - + (m[0] * m[6] - m[2] * m[4]) * (m[9] * m[15] - m[11] * m[13]) + + (m[0] * m[7] - m[3] * m[4]) * (m[9] * m[14] - m[10] * m[13]) + + (m[1] * m[6] - m[2] * m[5]) * (m[8] * m[15] - m[11] * m[12]) - + (m[1] * m[7] - m[3] * m[5]) * (m[8] * m[14] - m[10] * m[12]) + + (m[2] * m[7] - m[3] * m[6]) * (m[8] * m[13] - m[9] * m[12]); + + if( core::iszero(d) ) + return false; + // ^ epsilon removed by Nic for accuracy with double. + // iszero has an override for doubles. + + d = core::reciprocal(d); + + out[0] = d * (m[5] * (m[10] * m[15] - m[11] * m[14]) + + m[6] * (m[11] * m[13] - m[9] * m[15]) + + m[7] * (m[9] * m[14] - m[10] * m[13])); + out[1] = d * (m[9] * (m[2] * m[15] - m[3] * m[14]) + + m[10] * (m[3] * m[13] - m[1] * m[15]) + + m[11] * (m[1] * m[14] - m[2] * m[13])); + out[2] = d * (m[13] * (m[2] * m[7] - m[3] * m[6]) + + m[14] * (m[3] * m[5] - m[1] * m[7]) + + m[15] * (m[1] * m[6] - m[2] * m[5])); + out[3] = d * (m[1] * (m[7] * m[10] - m[6] * m[11]) + + m[2] * (m[5] * m[11] - m[7] * m[9]) + + m[3] * (m[6] * m[9] - m[5] * m[10])); + out[4] = d * (m[6] * (m[8] * m[15] - m[11] * m[12]) + + m[7] * (m[10] * m[12] - m[8] * m[14]) + + m[4] * (m[11] * m[14] - m[10] * m[15])); + out[5] = d * (m[10] * (m[0] * m[15] - m[3] * m[12]) + + m[11] * (m[2] * m[12] - m[0] * m[14]) + + m[8] * (m[3] * m[14] - m[2] * m[15])); + out[6] = d * (m[14] * (m[0] * m[7] - m[3] * m[4]) + + m[15] * (m[2] * m[4] - m[0] * m[6]) + + m[12] * (m[3] * m[6] - m[2] * m[7])); + out[7] = d * (m[2] * (m[7] * m[8] - m[4] * m[11]) + + m[3] * (m[4] * m[10] - m[6] * m[8]) + + m[0] * (m[6] * m[11] - m[7] * m[10])); + out[8] = d * (m[7] * (m[8] * m[13] - m[9] * m[12]) + + m[4] * (m[9] * m[15] - m[11] * m[13]) + + m[5] * (m[11] * m[12] - m[8] * m[15])); + out[9] = d * (m[11] * (m[0] * m[13] - m[1] * m[12]) + + m[8] * (m[1] * m[15] - m[3] * m[13]) + + m[9] * (m[3] * m[12] - m[0] * m[15])); + out[10] = d * (m[15] * (m[0] * m[5] - m[1] * m[4]) + + m[12] * (m[1] * m[7] - m[3] * m[5]) + + m[13] * (m[3] * m[4] - m[0] * m[7])); + out[11] = d * (m[3] * (m[5] * m[8] - m[4] * m[9]) + + m[0] * (m[7] * m[9] - m[5] * m[11]) + + m[1] * (m[4] * m[11] - m[7] * m[8])); + out[12] = d * (m[4] * (m[10] * m[13] - m[9] * m[14]) + + m[5] * (m[8] * m[14] - m[10] * m[12]) + + m[6] * (m[9] * m[12] - m[8] * m[13])); + out[13] = d * (m[8] * (m[2] * m[13] - m[1] * m[14]) + + m[9] * (m[0] * m[14] - m[2] * m[12]) + + m[10] * (m[1] * m[12] - m[0] * m[13])); + out[14] = d * (m[12] * (m[2] * m[5] - m[1] * m[6]) + + m[13] * (m[0] * m[6] - m[2] * m[4]) + + m[14] * (m[1] * m[4] - m[0] * m[5])); + out[15] = d * (m[0] * (m[5] * m[10] - m[6] * m[9]) + + m[1] * (m[6] * m[8] - m[4] * m[10]) + + m[2] * (m[4] * m[9] - m[5] * m[8])); + +#if defined ( USE_MATRIX_TEST ) + out.definitelyIdentityMatrix = definitelyIdentityMatrix; +#endif + return true; + } + + + //! Inverts a primitive matrix which only contains a translation and a rotation + //! \param out: where result matrix is written to. + template + inline bool CMatrix4::getInversePrimitive ( CMatrix4& out ) const + { + out.M[0 ] = M[0]; + out.M[1 ] = M[4]; + out.M[2 ] = M[8]; + out.M[3 ] = 0; + + out.M[4 ] = M[1]; + out.M[5 ] = M[5]; + out.M[6 ] = M[9]; + out.M[7 ] = 0; + + out.M[8 ] = M[2]; + out.M[9 ] = M[6]; + out.M[10] = M[10]; + out.M[11] = 0; + + out.M[12] = (T)-(M[12]*M[0] + M[13]*M[1] + M[14]*M[2]); + out.M[13] = (T)-(M[12]*M[4] + M[13]*M[5] + M[14]*M[6]); + out.M[14] = (T)-(M[12]*M[8] + M[13]*M[9] + M[14]*M[10]); + out.M[15] = 1; + +#if defined ( USE_MATRIX_TEST ) + out.definitelyIdentityMatrix = definitelyIdentityMatrix; +#endif + return true; + } + + /*! + */ + template + inline bool CMatrix4::makeInverse() + { +#if defined ( USE_MATRIX_TEST ) + if (definitelyIdentityMatrix) + return true; +#endif + CMatrix4 temp ( EM4CONST_NOTHING ); + + if (getInverse(temp)) + { + *this = temp; + return true; + } + + return false; + } + + + template + inline CMatrix4& CMatrix4::operator=(const CMatrix4 &other) + { + if (this==&other) + return *this; + memcpy(M, other.M, 16*sizeof(T)); +#if defined ( USE_MATRIX_TEST ) + definitelyIdentityMatrix=other.definitelyIdentityMatrix; +#endif + return *this; + } + + + template + inline CMatrix4& CMatrix4::operator=(const T& scalar) + { + for (s32 i = 0; i < 16; ++i) + M[i]=scalar; + +#if defined ( USE_MATRIX_TEST ) + definitelyIdentityMatrix=false; +#endif + return *this; + } + + + template + inline bool CMatrix4::operator==(const CMatrix4 &other) const + { +#if defined ( USE_MATRIX_TEST ) + if (definitelyIdentityMatrix && other.definitelyIdentityMatrix) + return true; +#endif + for (s32 i = 0; i < 16; ++i) + if (M[i] != other.M[i]) + return false; + + return true; + } + + + template + inline bool CMatrix4::operator!=(const CMatrix4 &other) const + { + return !(*this == other); + } + + + // Builds a right-handed perspective projection matrix based on a field of view + template + inline CMatrix4& CMatrix4::buildProjectionMatrixPerspectiveFovRH( + f32 fieldOfViewRadians, f32 aspectRatio, f32 zNear, f32 zFar) + { + const f64 h = reciprocal(tan(fieldOfViewRadians*0.5)); + _IRR_DEBUG_BREAK_IF(aspectRatio==0.f); //divide by zero + const T w = static_cast(h / aspectRatio); + + _IRR_DEBUG_BREAK_IF(zNear==zFar); //divide by zero + M[0] = w; + M[1] = 0; + M[2] = 0; + M[3] = 0; + + M[4] = 0; + M[5] = (T)h; + M[6] = 0; + M[7] = 0; + + M[8] = 0; + M[9] = 0; + M[10] = (T)(zFar/(zNear-zFar)); // DirectX version +// M[10] = (T)(zFar+zNear/(zNear-zFar)); // OpenGL version + M[11] = -1; + + M[12] = 0; + M[13] = 0; + M[14] = (T)(zNear*zFar/(zNear-zFar)); // DirectX version +// M[14] = (T)(2.0f*zNear*zFar/(zNear-zFar)); // OpenGL version + M[15] = 0; + +#if defined ( USE_MATRIX_TEST ) + definitelyIdentityMatrix=false; +#endif + return *this; + } + + + // Builds a left-handed perspective projection matrix based on a field of view + template + inline CMatrix4& CMatrix4::buildProjectionMatrixPerspectiveFovLH( + f32 fieldOfViewRadians, f32 aspectRatio, f32 zNear, f32 zFar) + { + const f64 h = reciprocal(tan(fieldOfViewRadians*0.5)); + _IRR_DEBUG_BREAK_IF(aspectRatio==0.f); //divide by zero + const T w = static_cast(h / aspectRatio); + + _IRR_DEBUG_BREAK_IF(zNear==zFar); //divide by zero + M[0] = w; + M[1] = 0; + M[2] = 0; + M[3] = 0; + + M[4] = 0; + M[5] = (T)h; + M[6] = 0; + M[7] = 0; + + M[8] = 0; + M[9] = 0; + M[10] = (T)(zFar/(zFar-zNear)); + M[11] = 1; + + M[12] = 0; + M[13] = 0; + M[14] = (T)(-zNear*zFar/(zFar-zNear)); + M[15] = 0; + +#if defined ( USE_MATRIX_TEST ) + definitelyIdentityMatrix=false; +#endif + return *this; + } + + + // Builds a left-handed perspective projection matrix based on a field of view, with far plane culling at infinity + template + inline CMatrix4& CMatrix4::buildProjectionMatrixPerspectiveFovInfinityLH( + f32 fieldOfViewRadians, f32 aspectRatio, f32 zNear, f32 epsilon) + { + const f64 h = reciprocal(tan(fieldOfViewRadians*0.5)); + _IRR_DEBUG_BREAK_IF(aspectRatio==0.f); //divide by zero + const T w = static_cast(h / aspectRatio); + + M[0] = w; + M[1] = 0; + M[2] = 0; + M[3] = 0; + + M[4] = 0; + M[5] = (T)h; + M[6] = 0; + M[7] = 0; + + M[8] = 0; + M[9] = 0; + M[10] = (T)(1.f-epsilon); + M[11] = 1; + + M[12] = 0; + M[13] = 0; + M[14] = (T)(zNear*(epsilon-1.f)); + M[15] = 0; + +#if defined ( USE_MATRIX_TEST ) + definitelyIdentityMatrix=false; +#endif + return *this; + } + + + // Builds a left-handed orthogonal projection matrix. + template + inline CMatrix4& CMatrix4::buildProjectionMatrixOrthoLH( + f32 widthOfViewVolume, f32 heightOfViewVolume, f32 zNear, f32 zFar) + { + _IRR_DEBUG_BREAK_IF(widthOfViewVolume==0.f); //divide by zero + _IRR_DEBUG_BREAK_IF(heightOfViewVolume==0.f); //divide by zero + _IRR_DEBUG_BREAK_IF(zNear==zFar); //divide by zero + M[0] = (T)(2/widthOfViewVolume); + M[1] = 0; + M[2] = 0; + M[3] = 0; + + M[4] = 0; + M[5] = (T)(2/heightOfViewVolume); + M[6] = 0; + M[7] = 0; + + M[8] = 0; + M[9] = 0; + M[10] = (T)(1/(zFar-zNear)); + M[11] = 0; + + M[12] = 0; + M[13] = 0; + M[14] = (T)(zNear/(zNear-zFar)); + M[15] = 1; + +#if defined ( USE_MATRIX_TEST ) + definitelyIdentityMatrix=false; +#endif + return *this; + } + + + // Builds a right-handed orthogonal projection matrix. + template + inline CMatrix4& CMatrix4::buildProjectionMatrixOrthoRH( + f32 widthOfViewVolume, f32 heightOfViewVolume, f32 zNear, f32 zFar) + { + _IRR_DEBUG_BREAK_IF(widthOfViewVolume==0.f); //divide by zero + _IRR_DEBUG_BREAK_IF(heightOfViewVolume==0.f); //divide by zero + _IRR_DEBUG_BREAK_IF(zNear==zFar); //divide by zero + M[0] = (T)(2/widthOfViewVolume); + M[1] = 0; + M[2] = 0; + M[3] = 0; + + M[4] = 0; + M[5] = (T)(2/heightOfViewVolume); + M[6] = 0; + M[7] = 0; + + M[8] = 0; + M[9] = 0; + M[10] = (T)(1/(zNear-zFar)); + M[11] = 0; + + M[12] = 0; + M[13] = 0; + M[14] = (T)(zNear/(zNear-zFar)); + M[15] = 1; + +#if defined ( USE_MATRIX_TEST ) + definitelyIdentityMatrix=false; +#endif + return *this; + } + + + // Builds a right-handed perspective projection matrix. + template + inline CMatrix4& CMatrix4::buildProjectionMatrixPerspectiveRH( + f32 widthOfViewVolume, f32 heightOfViewVolume, f32 zNear, f32 zFar) + { + _IRR_DEBUG_BREAK_IF(widthOfViewVolume==0.f); //divide by zero + _IRR_DEBUG_BREAK_IF(heightOfViewVolume==0.f); //divide by zero + _IRR_DEBUG_BREAK_IF(zNear==zFar); //divide by zero + M[0] = (T)(2*zNear/widthOfViewVolume); + M[1] = 0; + M[2] = 0; + M[3] = 0; + + M[4] = 0; + M[5] = (T)(2*zNear/heightOfViewVolume); + M[6] = 0; + M[7] = 0; + + M[8] = 0; + M[9] = 0; + M[10] = (T)(zFar/(zNear-zFar)); + M[11] = -1; + + M[12] = 0; + M[13] = 0; + M[14] = (T)(zNear*zFar/(zNear-zFar)); + M[15] = 0; + +#if defined ( USE_MATRIX_TEST ) + definitelyIdentityMatrix=false; +#endif + return *this; + } + + + // Builds a left-handed perspective projection matrix. + template + inline CMatrix4& CMatrix4::buildProjectionMatrixPerspectiveLH( + f32 widthOfViewVolume, f32 heightOfViewVolume, f32 zNear, f32 zFar) + { + _IRR_DEBUG_BREAK_IF(widthOfViewVolume==0.f); //divide by zero + _IRR_DEBUG_BREAK_IF(heightOfViewVolume==0.f); //divide by zero + _IRR_DEBUG_BREAK_IF(zNear==zFar); //divide by zero + M[0] = (T)(2*zNear/widthOfViewVolume); + M[1] = 0; + M[2] = 0; + M[3] = 0; + + M[4] = 0; + M[5] = (T)(2*zNear/heightOfViewVolume); + M[6] = 0; + M[7] = 0; + + M[8] = 0; + M[9] = 0; + M[10] = (T)(zFar/(zFar-zNear)); + M[11] = 1; + + M[12] = 0; + M[13] = 0; + M[14] = (T)(zNear*zFar/(zNear-zFar)); + M[15] = 0; +#if defined ( USE_MATRIX_TEST ) + definitelyIdentityMatrix=false; +#endif + return *this; + } + + + // Builds a matrix that flattens geometry into a plane. + template + inline CMatrix4& CMatrix4::buildShadowMatrix(const core::vector3df& light, core::plane3df plane, f32 point) + { + plane.Normal.normalize(); + const f32 d = plane.Normal.dotProduct(light); + + M[ 0] = (T)(-plane.Normal.X * light.X + d); + M[ 1] = (T)(-plane.Normal.X * light.Y); + M[ 2] = (T)(-plane.Normal.X * light.Z); + M[ 3] = (T)(-plane.Normal.X * point); + + M[ 4] = (T)(-plane.Normal.Y * light.X); + M[ 5] = (T)(-plane.Normal.Y * light.Y + d); + M[ 6] = (T)(-plane.Normal.Y * light.Z); + M[ 7] = (T)(-plane.Normal.Y * point); + + M[ 8] = (T)(-plane.Normal.Z * light.X); + M[ 9] = (T)(-plane.Normal.Z * light.Y); + M[10] = (T)(-plane.Normal.Z * light.Z + d); + M[11] = (T)(-plane.Normal.Z * point); + + M[12] = (T)(-plane.D * light.X); + M[13] = (T)(-plane.D * light.Y); + M[14] = (T)(-plane.D * light.Z); + M[15] = (T)(-plane.D * point + d); +#if defined ( USE_MATRIX_TEST ) + definitelyIdentityMatrix=false; +#endif + return *this; + } + + // Builds a left-handed look-at matrix. + template + inline CMatrix4& CMatrix4::buildCameraLookAtMatrixLH( + const vector3df& position, + const vector3df& target, + const vector3df& upVector) + { + vector3df zaxis = target - position; + zaxis.normalize(); + + vector3df xaxis = upVector.crossProduct(zaxis); + xaxis.normalize(); + + vector3df yaxis = zaxis.crossProduct(xaxis); + + M[0] = (T)xaxis.X; + M[1] = (T)yaxis.X; + M[2] = (T)zaxis.X; + M[3] = 0; + + M[4] = (T)xaxis.Y; + M[5] = (T)yaxis.Y; + M[6] = (T)zaxis.Y; + M[7] = 0; + + M[8] = (T)xaxis.Z; + M[9] = (T)yaxis.Z; + M[10] = (T)zaxis.Z; + M[11] = 0; + + M[12] = (T)-xaxis.dotProduct(position); + M[13] = (T)-yaxis.dotProduct(position); + M[14] = (T)-zaxis.dotProduct(position); + M[15] = 1; +#if defined ( USE_MATRIX_TEST ) + definitelyIdentityMatrix=false; +#endif + return *this; + } + + + // Builds a right-handed look-at matrix. + template + inline CMatrix4& CMatrix4::buildCameraLookAtMatrixRH( + const vector3df& position, + const vector3df& target, + const vector3df& upVector) + { + vector3df zaxis = position - target; + zaxis.normalize(); + + vector3df xaxis = upVector.crossProduct(zaxis); + xaxis.normalize(); + + vector3df yaxis = zaxis.crossProduct(xaxis); + + M[0] = (T)xaxis.X; + M[1] = (T)yaxis.X; + M[2] = (T)zaxis.X; + M[3] = 0; + + M[4] = (T)xaxis.Y; + M[5] = (T)yaxis.Y; + M[6] = (T)zaxis.Y; + M[7] = 0; + + M[8] = (T)xaxis.Z; + M[9] = (T)yaxis.Z; + M[10] = (T)zaxis.Z; + M[11] = 0; + + M[12] = (T)-xaxis.dotProduct(position); + M[13] = (T)-yaxis.dotProduct(position); + M[14] = (T)-zaxis.dotProduct(position); + M[15] = 1; +#if defined ( USE_MATRIX_TEST ) + definitelyIdentityMatrix=false; +#endif + return *this; + } + + + // creates a new matrix as interpolated matrix from this and the passed one. + template + inline CMatrix4 CMatrix4::interpolate(const core::CMatrix4& b, f32 time) const + { + CMatrix4 mat ( EM4CONST_NOTHING ); + + for (u32 i=0; i < 16; i += 4) + { + mat.M[i+0] = (T)(M[i+0] + ( b.M[i+0] - M[i+0] ) * time); + mat.M[i+1] = (T)(M[i+1] + ( b.M[i+1] - M[i+1] ) * time); + mat.M[i+2] = (T)(M[i+2] + ( b.M[i+2] - M[i+2] ) * time); + mat.M[i+3] = (T)(M[i+3] + ( b.M[i+3] - M[i+3] ) * time); + } + return mat; + } + + + // returns transposed matrix + template + inline CMatrix4 CMatrix4::getTransposed() const + { + CMatrix4 t ( EM4CONST_NOTHING ); + getTransposed ( t ); + return t; + } + + + // returns transposed matrix + template + inline void CMatrix4::getTransposed( CMatrix4& o ) const + { + o[ 0] = M[ 0]; + o[ 1] = M[ 4]; + o[ 2] = M[ 8]; + o[ 3] = M[12]; + + o[ 4] = M[ 1]; + o[ 5] = M[ 5]; + o[ 6] = M[ 9]; + o[ 7] = M[13]; + + o[ 8] = M[ 2]; + o[ 9] = M[ 6]; + o[10] = M[10]; + o[11] = M[14]; + + o[12] = M[ 3]; + o[13] = M[ 7]; + o[14] = M[11]; + o[15] = M[15]; +#if defined ( USE_MATRIX_TEST ) + o.definitelyIdentityMatrix=definitelyIdentityMatrix; +#endif + } + + + // used to scale <-1,-1><1,1> to viewport + template + inline CMatrix4& CMatrix4::buildNDCToDCMatrix( const core::rect& viewport, f32 zScale) + { + const f32 scaleX = (viewport.getWidth() - 0.75f ) * 0.5f; + const f32 scaleY = -(viewport.getHeight() - 0.75f ) * 0.5f; + + const f32 dx = -0.5f + ( (viewport.UpperLeftCorner.X + viewport.LowerRightCorner.X ) * 0.5f ); + const f32 dy = -0.5f + ( (viewport.UpperLeftCorner.Y + viewport.LowerRightCorner.Y ) * 0.5f ); + + makeIdentity(); + M[12] = (T)dx; + M[13] = (T)dy; + return setScale(core::vector3d((T)scaleX, (T)scaleY, (T)zScale)); + } + + //! Builds a matrix that rotates from one vector to another + /** \param from: vector to rotate from + \param to: vector to rotate to + + http://www.euclideanspace.com/maths/geometry/rotations/conversions/angleToMatrix/index.htm + */ + template + inline CMatrix4& CMatrix4::buildRotateFromTo(const core::vector3df& from, const core::vector3df& to) + { + // unit vectors + core::vector3df f(from); + core::vector3df t(to); + f.normalize(); + t.normalize(); + + // axis multiplication by sin + core::vector3df vs(t.crossProduct(f)); + + // axis of rotation + core::vector3df v(vs); + v.normalize(); + + // cosinus angle + T ca = f.dotProduct(t); + + core::vector3df vt(v * (1 - ca)); + + M[0] = vt.X * v.X + ca; + M[5] = vt.Y * v.Y + ca; + M[10] = vt.Z * v.Z + ca; + + vt.X *= v.Y; + vt.Z *= v.X; + vt.Y *= v.Z; + + M[1] = vt.X - vs.Z; + M[2] = vt.Z + vs.Y; + M[3] = 0; + + M[4] = vt.X + vs.Z; + M[6] = vt.Y - vs.X; + M[7] = 0; + + M[8] = vt.Z - vs.Y; + M[9] = vt.Y + vs.X; + M[11] = 0; + + M[12] = 0; + M[13] = 0; + M[14] = 0; + M[15] = 1; + + return *this; + } + + //! Builds a matrix which rotates a source vector to a look vector over an arbitrary axis + /** \param camPos: viewer position in world coord + \param center: object position in world-coord, rotation pivot + \param translation: object final translation from center + \param axis: axis to rotate about + \param from: source vector to rotate from + */ + template + inline void CMatrix4::buildAxisAlignedBillboard( + const core::vector3df& camPos, + const core::vector3df& center, + const core::vector3df& translation, + const core::vector3df& axis, + const core::vector3df& from) + { + // axis of rotation + core::vector3df up = axis; + up.normalize(); + const core::vector3df forward = (camPos - center).normalize(); + const core::vector3df right = up.crossProduct(forward).normalize(); + + // correct look vector + const core::vector3df look = right.crossProduct(up); + + // rotate from to + // axis multiplication by sin + const core::vector3df vs = look.crossProduct(from); + + // cosinus angle + const f32 ca = from.dotProduct(look); + + core::vector3df vt(up * (1.f - ca)); + + M[0] = static_cast(vt.X * up.X + ca); + M[5] = static_cast(vt.Y * up.Y + ca); + M[10] = static_cast(vt.Z * up.Z + ca); + + vt.X *= up.Y; + vt.Z *= up.X; + vt.Y *= up.Z; + + M[1] = static_cast(vt.X - vs.Z); + M[2] = static_cast(vt.Z + vs.Y); + M[3] = 0; + + M[4] = static_cast(vt.X + vs.Z); + M[6] = static_cast(vt.Y - vs.X); + M[7] = 0; + + M[8] = static_cast(vt.Z - vs.Y); + M[9] = static_cast(vt.Y + vs.X); + M[11] = 0; + + setRotationCenter(center, translation); + } + + + //! Builds a combined matrix which translate to a center before rotation and translate afterward + template + inline void CMatrix4::setRotationCenter(const core::vector3df& center, const core::vector3df& translation) + { + M[12] = -M[0]*center.X - M[4]*center.Y - M[8]*center.Z + (center.X - translation.X ); + M[13] = -M[1]*center.X - M[5]*center.Y - M[9]*center.Z + (center.Y - translation.Y ); + M[14] = -M[2]*center.X - M[6]*center.Y - M[10]*center.Z + (center.Z - translation.Z ); + M[15] = (T) 1.0; +#if defined ( USE_MATRIX_TEST ) + definitelyIdentityMatrix=false; +#endif + } + + /*! + Generate texture coordinates as linear functions so that: + u = Ux*x + Uy*y + Uz*z + Uw + v = Vx*x + Vy*y + Vz*z + Vw + The matrix M for this case is: + Ux Vx 0 0 + Uy Vy 0 0 + Uz Vz 0 0 + Uw Vw 0 0 + */ + + + template + inline CMatrix4& CMatrix4::buildTextureTransform( f32 rotateRad, + const core::vector2df &rotatecenter, + const core::vector2df &translate, + const core::vector2df &scale) + { + const f32 c = cosf(rotateRad); + const f32 s = sinf(rotateRad); + + M[0] = (T)(c * scale.X); + M[1] = (T)(s * scale.Y); + M[2] = 0; + M[3] = 0; + + M[4] = (T)(-s * scale.X); + M[5] = (T)(c * scale.Y); + M[6] = 0; + M[7] = 0; + + M[8] = (T)(c * scale.X * rotatecenter.X + -s * rotatecenter.Y + translate.X); + M[9] = (T)(s * scale.Y * rotatecenter.X + c * rotatecenter.Y + translate.Y); + M[10] = 1; + M[11] = 0; + + M[12] = 0; + M[13] = 0; + M[14] = 0; + M[15] = 1; +#if defined ( USE_MATRIX_TEST ) + definitelyIdentityMatrix=false; +#endif + return *this; + } + + + // rotate about z axis, center ( 0.5, 0.5 ) + template + inline CMatrix4& CMatrix4::setTextureRotationCenter( f32 rotateRad ) + { + const f32 c = cosf(rotateRad); + const f32 s = sinf(rotateRad); + M[0] = (T)c; + M[1] = (T)s; + + M[4] = (T)-s; + M[5] = (T)c; + + M[8] = (T)(0.5f * ( s - c) + 0.5f); + M[9] = (T)(-0.5f * ( s + c) + 0.5f); + +#if defined ( USE_MATRIX_TEST ) + definitelyIdentityMatrix = definitelyIdentityMatrix && (rotateRad==0.0f); +#endif + return *this; + } + + + template + inline CMatrix4& CMatrix4::setTextureTranslate ( f32 x, f32 y ) + { + M[8] = (T)x; + M[9] = (T)y; + +#if defined ( USE_MATRIX_TEST ) + definitelyIdentityMatrix = definitelyIdentityMatrix && (x==0.0f) && (y==0.0f); +#endif + return *this; + } + + template + inline void CMatrix4::getTextureTranslate(f32& x, f32& y) const + { + x = (f32)M[8]; + y = (f32)M[9]; + } + + template + inline CMatrix4& CMatrix4::setTextureTranslateTransposed ( f32 x, f32 y ) + { + M[2] = (T)x; + M[6] = (T)y; + +#if defined ( USE_MATRIX_TEST ) + definitelyIdentityMatrix = definitelyIdentityMatrix && (x==0.0f) && (y==0.0f); +#endif + return *this; + } + + template + inline CMatrix4& CMatrix4::setTextureScale ( f32 sx, f32 sy ) + { + M[0] = (T)sx; + M[5] = (T)sy; +#if defined ( USE_MATRIX_TEST ) + definitelyIdentityMatrix = definitelyIdentityMatrix && (sx==1.0f) && (sy==1.0f); +#endif + return *this; + } + + template + inline void CMatrix4::getTextureScale ( f32& sx, f32& sy ) const + { + sx = (f32)M[0]; + sy = (f32)M[5]; + } + + template + inline CMatrix4& CMatrix4::setTextureScaleCenter( f32 sx, f32 sy ) + { + M[0] = (T)sx; + M[5] = (T)sy; + M[8] = (T)(0.5f - 0.5f * sx); + M[9] = (T)(0.5f - 0.5f * sy); + +#if defined ( USE_MATRIX_TEST ) + definitelyIdentityMatrix = definitelyIdentityMatrix && (sx==1.0f) && (sy==1.0f); +#endif + return *this; + } + + + // sets all matrix data members at once + template + inline CMatrix4& CMatrix4::setM(const T* data) + { + memcpy(M,data, 16*sizeof(T)); + +#if defined ( USE_MATRIX_TEST ) + definitelyIdentityMatrix=false; +#endif + return *this; + } + + + // sets if the matrix is definitely identity matrix + template + inline void CMatrix4::setDefinitelyIdentityMatrix( bool isDefinitelyIdentityMatrix) + { +#if defined ( USE_MATRIX_TEST ) + definitelyIdentityMatrix = isDefinitelyIdentityMatrix; +#else + (void)isDefinitelyIdentityMatrix; // prevent compiler warning +#endif + } + + + // gets if the matrix is definitely identity matrix + template + inline bool CMatrix4::getDefinitelyIdentityMatrix() const + { +#if defined ( USE_MATRIX_TEST ) + return definitelyIdentityMatrix; +#else + return false; +#endif + } + + + //! Compare two matrices using the equal method + template + inline bool CMatrix4::equals(const core::CMatrix4& other, const T tolerance) const + { +#if defined ( USE_MATRIX_TEST ) + if (definitelyIdentityMatrix && other.definitelyIdentityMatrix) + return true; +#endif + for (s32 i = 0; i < 16; ++i) + if (!core::equals(M[i],other.M[i], tolerance)) + return false; + + return true; + } + + + // Multiply by scalar. + template + inline CMatrix4 operator*(const T scalar, const CMatrix4& mat) + { + return mat*scalar; + } + + + //! Typedef for f32 matrix + typedef CMatrix4 matrix4; + + //! global const identity matrix + IRRLICHT_API extern const matrix4 IdentityMatrix; + +} // end namespace core +} // end namespace irr + +#endif + diff --git a/IrrExtensions/modded/include/quaternion.h b/IrrExtensions/modded/include/quaternion.h new file mode 100644 index 0000000..2796489 --- /dev/null +++ b/IrrExtensions/modded/include/quaternion.h @@ -0,0 +1,755 @@ +// Copyright (C) 2002-2012 Nikolaus Gebhardt +// This file is part of the "Irrlicht Engine". +// For conditions of distribution and use, see copyright notice in irrlicht.h + +#ifndef __IRR_QUATERNION_H_INCLUDED__ +#define __IRR_QUATERNION_H_INCLUDED__ + +#include "irrTypes.h" +#include "irrMath.h" +#include "matrix4.h" +#include "vector3d.h" + +// NOTE: You *only* need this when updating an application from Irrlicht before 1.8 to Irrlicht 1.8 or later. +// Between Irrlicht 1.7 and Irrlicht 1.8 the quaternion-matrix conversions changed. +// Before the fix they had mixed left- and right-handed rotations. +// To test if your code was affected by the change enable IRR_TEST_BROKEN_QUATERNION_USE and try to compile your application. +// This defines removes those functions so you get compile errors anywhere you use them in your code. +// For every line with a compile-errors you have to change the corresponding lines like that: +// - When you pass the matrix to the quaternion constructor then replace the matrix by the transposed matrix. +// - For uses of getMatrix() you have to use quaternion::getMatrix_transposed instead. +// #define IRR_TEST_BROKEN_QUATERNION_USE + +namespace irr +{ +namespace core +{ + +//! Quaternion class for representing rotations. +/** It provides cheap combinations and avoids gimbal locks. +Also useful for interpolations. */ +class quaternion +{ + public: + + //! Default Constructor + quaternion() : X(0.0f), Y(0.0f), Z(0.0f), W(1.0f) {} + + //! Constructor + quaternion(f32 x, f32 y, f32 z, f32 w) : X(x), Y(y), Z(z), W(w) { } + + //! Constructor which converts Euler angles (radians) to a quaternion + quaternion(f32 x, f32 y, f32 z); + + //! Constructor which converts Euler angles (radians) to a quaternion + quaternion(const vector3df& vec); + +#ifndef IRR_TEST_BROKEN_QUATERNION_USE + //! Constructor which converts a matrix to a quaternion + quaternion(const matrix4& mat); +#endif + + //! Equality operator + bool operator==(const quaternion& other) const; + + //! inequality operator + bool operator!=(const quaternion& other) const; + + //! Assignment operator + inline quaternion& operator=(const quaternion& other); + +#ifndef IRR_TEST_BROKEN_QUATERNION_USE + //! Matrix assignment operator + inline quaternion& operator=(const matrix4& other); +#endif + + //! Add operator + quaternion operator+(const quaternion& other) const; + + //! Multiplication operator + //! Be careful, unfortunately the operator order here is opposite of that in CMatrix4::operator* + quaternion operator*(const quaternion& other) const; + + //! Multiplication operator with scalar + quaternion operator*(f32 s) const; + + //! Multiplication operator with scalar + quaternion& operator*=(f32 s); + + //! Multiplication operator + vector3df operator*(const vector3df& v) const; + + //! Multiplication operator + quaternion& operator*=(const quaternion& other); + + //! Calculates the dot product + inline f32 dotProduct(const quaternion& other) const; + + //! Sets new quaternion + inline quaternion& set(f32 x, f32 y, f32 z, f32 w); + + //! Sets new quaternion based on Euler angles (radians) + inline quaternion& set(f32 x, f32 y, f32 z); + + //! Sets new quaternion based on Euler angles (radians) + inline quaternion& set(const core::vector3df& vec); + + //! Sets new quaternion from other quaternion + inline quaternion& set(const core::quaternion& quat); + + //! returns if this quaternion equals the other one, taking floating point rounding errors into account + inline bool equals(const quaternion& other, + const f32 tolerance = ROUNDING_ERROR_f32 ) const; + + //! Normalizes the quaternion + inline quaternion& normalize(); + +#ifndef IRR_TEST_BROKEN_QUATERNION_USE + //! Creates a matrix from this quaternion + matrix4 getMatrix() const; +#endif + //! Faster method to create a rotation matrix, you should normalize the quaternion before! + void getMatrixFast(matrix4 &dest) const; + + //! Creates a matrix from this quaternion + void getMatrix( matrix4 &dest, const core::vector3df &translation=core::vector3df() ) const; + + /*! + Creates a matrix from this quaternion + Rotate about a center point + shortcut for + core::quaternion q; + q.rotationFromTo ( vin[i].Normal, forward ); + q.getMatrixCenter ( lookat, center, newPos ); + + core::matrix4 m2; + m2.setInverseTranslation ( center ); + lookat *= m2; + + core::matrix4 m3; + m2.setTranslation ( newPos ); + lookat *= m3; + + */ + void getMatrixCenter( matrix4 &dest, const core::vector3df ¢er, const core::vector3df &translation ) const; + + //! Creates a matrix from this quaternion + inline void getMatrix_transposed( matrix4 &dest ) const; + + //! Inverts this quaternion + quaternion& makeInverse(); + + //! Set this quaternion to the linear interpolation between two quaternions + /** \param q1 First quaternion to be interpolated. + \param q2 Second quaternion to be interpolated. + \param time Progress of interpolation. For time=0 the result is + q1, for time=1 the result is q2. Otherwise interpolation + between q1 and q2. + */ + quaternion& lerp(quaternion q1, quaternion q2, f32 time); + + //! Set this quaternion to the result of the spherical interpolation between two quaternions + /** \param q1 First quaternion to be interpolated. + \param q2 Second quaternion to be interpolated. + \param time Progress of interpolation. For time=0 the result is + q1, for time=1 the result is q2. Otherwise interpolation + between q1 and q2. + \param threshold To avoid inaccuracies at the end (time=1) the + interpolation switches to linear interpolation at some point. + This value defines how much of the remaining interpolation will + be calculated with lerp. Everything from 1-threshold up will be + linear interpolation. + */ + quaternion& slerp(quaternion q1, quaternion q2, + f32 time, f32 threshold=.05f); + + //! Create quaternion from rotation angle and rotation axis. + /** Axis must be unit length. + The quaternion representing the rotation is + q = cos(A/2)+sin(A/2)*(x*i+y*j+z*k). + \param angle Rotation Angle in radians. + \param axis Rotation axis. */ + quaternion& fromAngleAxis (f32 angle, const vector3df& axis); + + //! Fills an angle (radians) around an axis (unit vector) + void toAngleAxis (f32 &angle, core::vector3df& axis) const; + + //! Output this quaternion to an Euler angle (radians) + void toEuler(vector3df& euler) const; + + //! Set quaternion to identity + quaternion& makeIdentity(); + + //! Set quaternion to represent a rotation from one vector to another. + quaternion& rotationFromTo(const vector3df& from, const vector3df& to); + + //! Quaternion elements. + f32 X; // vectorial (imaginary) part + f32 Y; + f32 Z; + f32 W; // real part +}; + + +// Constructor which converts Euler angles to a quaternion +inline quaternion::quaternion(f32 x, f32 y, f32 z) +{ + set(x,y,z); +} + + +// Constructor which converts Euler angles to a quaternion +inline quaternion::quaternion(const vector3df& vec) +{ + set(vec.X,vec.Y,vec.Z); +} + +#ifndef IRR_TEST_BROKEN_QUATERNION_USE +// Constructor which converts a matrix to a quaternion +inline quaternion::quaternion(const matrix4& mat) +{ + (*this) = mat; +} +#endif + +// equal operator +inline bool quaternion::operator==(const quaternion& other) const +{ + return ((X == other.X) && + (Y == other.Y) && + (Z == other.Z) && + (W == other.W)); +} + +// inequality operator +inline bool quaternion::operator!=(const quaternion& other) const +{ + return !(*this == other); +} + +// assignment operator +inline quaternion& quaternion::operator=(const quaternion& other) +{ + X = other.X; + Y = other.Y; + Z = other.Z; + W = other.W; + return *this; +} + +#ifndef IRR_TEST_BROKEN_QUATERNION_USE +// matrix assignment operator +inline quaternion& quaternion::operator=(const matrix4& m) +{ + const f32 diag = m[0] + m[5] + m[10] + 1; + + if( diag > 0.0f ) + { + const f32 scale = sqrtf(diag) * 2.0f; // get scale from diagonal + + // TODO: speed this up + X = (m[6] - m[9]) / scale; + Y = (m[8] - m[2]) / scale; + Z = (m[1] - m[4]) / scale; + W = 0.25f * scale; + } + else + { + if (m[0]>m[5] && m[0]>m[10]) + { + // 1st element of diag is greatest value + // find scale according to 1st element, and double it + const f32 scale = sqrtf(1.0f + m[0] - m[5] - m[10]) * 2.0f; + + // TODO: speed this up + X = 0.25f * scale; + Y = (m[4] + m[1]) / scale; + Z = (m[2] + m[8]) / scale; + W = (m[6] - m[9]) / scale; + } + else if (m[5]>m[10]) + { + // 2nd element of diag is greatest value + // find scale according to 2nd element, and double it + const f32 scale = sqrtf(1.0f + m[5] - m[0] - m[10]) * 2.0f; + + // TODO: speed this up + X = (m[4] + m[1]) / scale; + Y = 0.25f * scale; + Z = (m[9] + m[6]) / scale; + W = (m[8] - m[2]) / scale; + } + else + { + // 3rd element of diag is greatest value + // find scale according to 3rd element, and double it + const f32 scale = sqrtf(1.0f + m[10] - m[0] - m[5]) * 2.0f; + + // TODO: speed this up + X = (m[8] + m[2]) / scale; + Y = (m[9] + m[6]) / scale; + Z = 0.25f * scale; + W = (m[1] - m[4]) / scale; + } + } + + return normalize(); +} +#endif + + +// multiplication operator +inline quaternion quaternion::operator*(const quaternion& other) const +{ + quaternion tmp; + + tmp.W = (other.W * W) - (other.X * X) - (other.Y * Y) - (other.Z * Z); + tmp.X = (other.W * X) + (other.X * W) + (other.Y * Z) - (other.Z * Y); + tmp.Y = (other.W * Y) + (other.Y * W) + (other.Z * X) - (other.X * Z); + tmp.Z = (other.W * Z) + (other.Z * W) + (other.X * Y) - (other.Y * X); + + return tmp; +} + + +// multiplication operator +inline quaternion quaternion::operator*(f32 s) const +{ + return quaternion(s*X, s*Y, s*Z, s*W); +} + + +// multiplication operator +inline quaternion& quaternion::operator*=(f32 s) +{ + X*=s; + Y*=s; + Z*=s; + W*=s; + return *this; +} + +// multiplication operator +inline quaternion& quaternion::operator*=(const quaternion& other) +{ + return (*this = other * (*this)); +} + +// add operator +inline quaternion quaternion::operator+(const quaternion& b) const +{ + return quaternion(X+b.X, Y+b.Y, Z+b.Z, W+b.W); +} + +#ifndef IRR_TEST_BROKEN_QUATERNION_USE +// Creates a matrix from this quaternion +inline matrix4 quaternion::getMatrix() const +{ + core::matrix4 m; + getMatrix(m); + return m; +} +#endif + +//! Faster method to create a rotation matrix, you should normalize the quaternion before! +inline void quaternion::getMatrixFast( matrix4 &dest) const +{ + // TODO: + // gpu quaternion skinning => fast Bones transform chain O_O YEAH! + // http://www.mrelusive.com/publications/papers/SIMD-From-Quaternion-to-Matrix-and-Back.pdf + dest[0] = 1.0f - 2.0f*Y*Y - 2.0f*Z*Z; + dest[1] = 2.0f*X*Y + 2.0f*Z*W; + dest[2] = 2.0f*X*Z - 2.0f*Y*W; + dest[3] = 0.0f; + + dest[4] = 2.0f*X*Y - 2.0f*Z*W; + dest[5] = 1.0f - 2.0f*X*X - 2.0f*Z*Z; + dest[6] = 2.0f*Z*Y + 2.0f*X*W; + dest[7] = 0.0f; + + dest[8] = 2.0f*X*Z + 2.0f*Y*W; + dest[9] = 2.0f*Z*Y - 2.0f*X*W; + dest[10] = 1.0f - 2.0f*X*X - 2.0f*Y*Y; + dest[11] = 0.0f; + + dest[12] = 0.f; + dest[13] = 0.f; + dest[14] = 0.f; + dest[15] = 1.f; + + dest.setDefinitelyIdentityMatrix(false); +} + +/*! + Creates a matrix from this quaternion +*/ +inline void quaternion::getMatrix(matrix4 &dest, + const core::vector3df ¢er) const +{ + // ok creating a copy may be slower, but at least avoid internal + // state chance (also because otherwise we cannot keep this method "const"). + + quaternion q( *this); + q.normalize(); + f32 X = q.X; + f32 Y = q.Y; + f32 Z = q.Z; + f32 W = q.W; + + dest[0] = 1.0f - 2.0f*Y*Y - 2.0f*Z*Z; + dest[1] = 2.0f*X*Y + 2.0f*Z*W; + dest[2] = 2.0f*X*Z - 2.0f*Y*W; + dest[3] = 0.0f; + + dest[4] = 2.0f*X*Y - 2.0f*Z*W; + dest[5] = 1.0f - 2.0f*X*X - 2.0f*Z*Z; + dest[6] = 2.0f*Z*Y + 2.0f*X*W; + dest[7] = 0.0f; + + dest[8] = 2.0f*X*Z + 2.0f*Y*W; + dest[9] = 2.0f*Z*Y - 2.0f*X*W; + dest[10] = 1.0f - 2.0f*X*X - 2.0f*Y*Y; + dest[11] = 0.0f; + + dest[12] = center.X; + dest[13] = center.Y; + dest[14] = center.Z; + dest[15] = 1.f; + + dest.setDefinitelyIdentityMatrix ( false ); +} + + +/*! + Creates a matrix from this quaternion + Rotate about a center point + shortcut for + core::quaternion q; + q.rotationFromTo(vin[i].Normal, forward); + q.getMatrix(lookat, center); + + core::matrix4 m2; + m2.setInverseTranslation(center); + lookat *= m2; +*/ +inline void quaternion::getMatrixCenter(matrix4 &dest, + const core::vector3df ¢er, + const core::vector3df &translation) const +{ + quaternion q(*this); + q.normalize(); + f32 X = q.X; + f32 Y = q.Y; + f32 Z = q.Z; + f32 W = q.W; + + dest[0] = 1.0f - 2.0f*Y*Y - 2.0f*Z*Z; + dest[1] = 2.0f*X*Y + 2.0f*Z*W; + dest[2] = 2.0f*X*Z - 2.0f*Y*W; + dest[3] = 0.0f; + + dest[4] = 2.0f*X*Y - 2.0f*Z*W; + dest[5] = 1.0f - 2.0f*X*X - 2.0f*Z*Z; + dest[6] = 2.0f*Z*Y + 2.0f*X*W; + dest[7] = 0.0f; + + dest[8] = 2.0f*X*Z + 2.0f*Y*W; + dest[9] = 2.0f*Z*Y - 2.0f*X*W; + dest[10] = 1.0f - 2.0f*X*X - 2.0f*Y*Y; + dest[11] = 0.0f; + + dest.setRotationCenter ( center, translation ); +} + +// Creates a matrix from this quaternion +inline void quaternion::getMatrix_transposed(matrix4 &dest) const +{ + quaternion q(*this); + q.normalize(); + f32 X = q.X; + f32 Y = q.Y; + f32 Z = q.Z; + f32 W = q.W; + + dest[0] = 1.0f - 2.0f*Y*Y - 2.0f*Z*Z; + dest[4] = 2.0f*X*Y + 2.0f*Z*W; + dest[8] = 2.0f*X*Z - 2.0f*Y*W; + dest[12] = 0.0f; + + dest[1] = 2.0f*X*Y - 2.0f*Z*W; + dest[5] = 1.0f - 2.0f*X*X - 2.0f*Z*Z; + dest[9] = 2.0f*Z*Y + 2.0f*X*W; + dest[13] = 0.0f; + + dest[2] = 2.0f*X*Z + 2.0f*Y*W; + dest[6] = 2.0f*Z*Y - 2.0f*X*W; + dest[10] = 1.0f - 2.0f*X*X - 2.0f*Y*Y; + dest[14] = 0.0f; + + dest[3] = 0.f; + dest[7] = 0.f; + dest[11] = 0.f; + dest[15] = 1.f; + + dest.setDefinitelyIdentityMatrix(false); +} + + +// Inverts this quaternion +inline quaternion& quaternion::makeInverse() +{ + X = -X; Y = -Y; Z = -Z; + return *this; +} + + +// sets new quaternion +inline quaternion& quaternion::set(f32 x, f32 y, f32 z, f32 w) +{ + X = x; + Y = y; + Z = z; + W = w; + return *this; +} + + +// sets new quaternion based on Euler angles +inline quaternion& quaternion::set(f32 x, f32 y, f32 z) +{ + f64 angle; + + angle = x * 0.5; + const f64 sr = sin(angle); + const f64 cr = cos(angle); + + angle = y * 0.5; + const f64 sp = sin(angle); + const f64 cp = cos(angle); + + angle = z * 0.5; + const f64 sy = sin(angle); + const f64 cy = cos(angle); + + const f64 cpcy = cp * cy; + const f64 spcy = sp * cy; + const f64 cpsy = cp * sy; + const f64 spsy = sp * sy; + + X = (f32)(sr * cpcy - cr * spsy); + Y = (f32)(cr * spcy + sr * cpsy); + Z = (f32)(cr * cpsy - sr * spcy); + W = (f32)(cr * cpcy + sr * spsy); + + return normalize(); +} + +// sets new quaternion based on Euler angles +inline quaternion& quaternion::set(const core::vector3df& vec) +{ + return set( vec.X, vec.Y, vec.Z); +} + +// sets new quaternion based on other quaternion +inline quaternion& quaternion::set(const core::quaternion& quat) +{ + return (*this=quat); +} + + +//! returns if this quaternion equals the other one, taking floating point rounding errors into account +inline bool quaternion::equals(const quaternion& other, const f32 tolerance) const +{ + return core::equals( X, other.X, tolerance) && + core::equals( Y, other.Y, tolerance) && + core::equals( Z, other.Z, tolerance) && + core::equals( W, other.W, tolerance); +} + + +// normalizes the quaternion +inline quaternion& quaternion::normalize() +{ + // removed conditional branch since it may slow down and anyway the condition was + // false even after normalization in some cases. + return (*this *= reciprocal_squareroot ( X*X + Y*Y + Z*Z + W*W )); +} + +// set this quaternion to the result of the linear interpolation between two quaternions +inline quaternion& quaternion::lerp( quaternion q1, quaternion q2, f32 time) +{ + const f32 scale = 1.0f - time; + return (*this = (q1*scale) + (q2*time)); +} + + +// set this quaternion to the result of the interpolation between two quaternions +inline quaternion& quaternion::slerp( quaternion q1, quaternion q2, f32 time, f32 threshold) +{ + f32 angle = q1.dotProduct(q2); + + // make sure we use the short rotation + if (angle < 0.0f) + { + q1 *= -1.0f; + angle *= -1.0f; + } + + if (angle <= (1-threshold)) // spherical interpolation + { + const f32 theta = acosf(angle); + const f32 invsintheta = reciprocal(sinf(theta)); + const f32 scale = sinf(theta * (1.0f-time)) * invsintheta; + const f32 invscale = sinf(theta * time) * invsintheta; + return (*this = (q1*scale) + (q2*invscale)); + } + else // linear interpolation + lerp(q1,q2,time); + // Fix suggested by porcus, implemented by Nic + normalize(); + return *this; +} + + +// calculates the dot product +inline f32 quaternion::dotProduct(const quaternion& q2) const +{ + return (X * q2.X) + (Y * q2.Y) + (Z * q2.Z) + (W * q2.W); +} + + +//! axis must be unit length, angle in radians +inline quaternion& quaternion::fromAngleAxis(f32 angle, const vector3df& axis) +{ + const f32 fHalfAngle = 0.5f*angle; + const f32 fSin = sinf(fHalfAngle); + W = cosf(fHalfAngle); + X = fSin*axis.X; + Y = fSin*axis.Y; + Z = fSin*axis.Z; + return *this; +} + + +inline void quaternion::toAngleAxis(f32 &angle, core::vector3df &axis) const +{ + const f32 scale = sqrtf(X*X + Y*Y + Z*Z); + + if (core::iszero(scale) || W > 1.0f || W < -1.0f) + { + angle = 0.0f; + axis.X = 0.0f; + axis.Y = 1.0f; + axis.Z = 0.0f; + } + else + { + const f32 invscale = reciprocal(scale); + angle = 2.0f * acosf(W); + axis.X = X * invscale; + axis.Y = Y * invscale; + axis.Z = Z * invscale; + } +} + +inline void quaternion::toEuler(vector3df& euler) const +{ + const f64 sqw = W*W; + const f64 sqx = X*X; + const f64 sqy = Y*Y; + const f64 sqz = Z*Z; + const f64 test = 2.0 * (Y*W - X*Z); + + if (core::equals(test, 1.0, 0.000001)) + { + // heading = rotation about z-axis + euler.Z = (f32) (-2.0*atan2(X, W)); + // bank = rotation about x-axis + euler.X = 0; + // attitude = rotation about y-axis + euler.Y = (f32) (core::PI64/2.0); + } + else if (core::equals(test, -1.0, 0.000001)) + { + // heading = rotation about z-axis + euler.Z = (f32) (2.0*atan2(X, W)); + // bank = rotation about x-axis + euler.X = 0; + // attitude = rotation about y-axis + euler.Y = (f32) (core::PI64/-2.0); + } + else + { + // heading = rotation about z-axis + euler.Z = (f32) atan2(2.0 * (X*Y +Z*W),(sqx - sqy - sqz + sqw)); + // bank = rotation about x-axis + euler.X = (f32) atan2(2.0 * (Y*Z +X*W),(-sqx - sqy + sqz + sqw)); + // attitude = rotation about y-axis + euler.Y = (f32) asin( clamp(test, -1.0, 1.0) ); + } +} + + +inline vector3df quaternion::operator* (const vector3df& v) const +{ + // nVidia SDK implementation + + vector3df uv, uuv; + vector3df qvec(X, Y, Z); + uv = qvec.crossProduct(v); + uuv = qvec.crossProduct(uv); + uv *= (2.0f * W); + uuv *= 2.0f; + + return v + uv + uuv; +} + +// set quaternion to identity +inline core::quaternion& quaternion::makeIdentity() +{ + W = 1.f; + X = 0.f; + Y = 0.f; + Z = 0.f; + return *this; +} + +inline core::quaternion& quaternion::rotationFromTo(const vector3df& from, const vector3df& to) +{ + // Based on Stan Melax's article in Game Programming Gems + // Copy, since cannot modify local + vector3df v0 = from; + vector3df v1 = to; + v0.normalize(); + v1.normalize(); + + const f32 d = v0.dotProduct(v1); + if (d >= 1.0f) // If dot == 1, vectors are the same + { + return makeIdentity(); + } + else if (d <= -1.0f) // exactly opposite + { + core::vector3df axis(1.0f, 0.f, 0.f); + axis = axis.crossProduct(v0); + if (axis.getLength()==0) + { + axis.set(0.f,1.f,0.f); + axis = axis.crossProduct(v0); + } + // same as fromAngleAxis(core::PI, axis).normalize(); + return set(axis.X, axis.Y, axis.Z, 0).normalize(); + } + + const f32 s = sqrtf( (1+d)*2 ); // optimize inv_sqrt + const f32 invs = 1.f / s; + const vector3df c = v0.crossProduct(v1)*invs; + return set(c.X, c.Y, c.Z, s * 0.5f).normalize(); +} + + +} // end namespace core +} // end namespace irr + +#endif + diff --git a/IrrExtensions/modded/include/rect.h b/IrrExtensions/modded/include/rect.h new file mode 100644 index 0000000..fc3a9c4 --- /dev/null +++ b/IrrExtensions/modded/include/rect.h @@ -0,0 +1,326 @@ +// Copyright (C) 2002-2012 Nikolaus Gebhardt +// This file is part of the "Irrlicht Engine". +// For conditions of distribution and use, see copyright notice in irrlicht.h + +#ifndef __IRR_RECT_H_INCLUDED__ +#define __IRR_RECT_H_INCLUDED__ + +#include "irrTypes.h" +#include "dimension2d.h" +#include "position2d.h" + +namespace irr +{ +namespace core +{ + + //! Rectangle template. + /** Mostly used by 2D GUI elements and for 2D drawing methods. + It has 2 positions instead of position and dimension and a fast + method for collision detection with other rectangles and points. + + Coordinates are (0,0) for top-left corner, and increasing to the right + and to the bottom. + */ + template + class rect + { + public: + + //! Default constructor creating empty rectangle at (0,0) + rect() : UpperLeftCorner(0,0), LowerRightCorner(0,0) {} + + //! Constructor with two corners + rect(T x, T y, T x2, T y2) + : UpperLeftCorner(x,y), LowerRightCorner(x2,y2) {} + + //! Constructor with two corners + rect(const position2d& upperLeft, const position2d& lowerRight) + : UpperLeftCorner(upperLeft), LowerRightCorner(lowerRight) {} + + //! Constructor with upper left corner and dimension + template + rect(const position2d& pos, const dimension2d& size) + : UpperLeftCorner(pos), LowerRightCorner(pos.X + size.Width, pos.Y + size.Height) {} + + //! Constructor with upper left at 0,0 and lower right using dimension + template + explicit rect(const dimension2d& size) + : UpperLeftCorner(0,0), LowerRightCorner(size.Width, size.Height) {} + + //! move right by given numbers + rect operator+(const position2d& pos) const + { + rect ret(*this); + return ret+=pos; + } + + //! move right by given numbers + rect& operator+=(const position2d& pos) + { + UpperLeftCorner += pos; + LowerRightCorner += pos; + return *this; + } + + //! move left by given numbers + rect operator-(const position2d& pos) const + { + rect ret(*this); + return ret-=pos; + } + + //! move left by given numbers + rect& operator-=(const position2d& pos) + { + UpperLeftCorner -= pos; + LowerRightCorner -= pos; + return *this; + } + + // Added by Nic Anderson + //! set the rectangle + rect& set(T x, T y, T x2, T y2) { + UpperLeftCorner.set(x,y); + LowerRightCorner.set(x2,y2); + return *this; + } + + // Added by Nic Anderson + //! set the rectangle + template + rect& set( vector2d pos, dimension2d size ) { + UpperLeftCorner = pos; + LowerRightCorner.set( pos.X + (T)size.Width, pos.Y + (T)size.Height ); + return *this; + } + + // Added by Nic Anderson + //! move the entire rectangle by the given shift amounts + rect& move(T x, T y) + { + UpperLeftCorner.move(x,y); + LowerRightCorner.move(x,y); + return *this; + } + + // Added by Nic Anderson + //! return the difference of two rectangles + rect delta(const rect& other) + { + return rect(*this).makeDelta(other); + } + + // Added by Nic Anderson + //! converts the rect into the difference of two rectangles and returns it + rect& makeDelta(const rect& other) + { + UpperLeftCorner -= other.UpperLeftCorner; + LowerRightCorner -= other.LowerRightCorner; + return *this; + } + + //! equality operator + bool operator==(const rect& other) const + { + return (UpperLeftCorner == other.UpperLeftCorner && + LowerRightCorner == other.LowerRightCorner); + } + + //! inequality operator + bool operator!=(const rect& other) const + { + return (UpperLeftCorner != other.UpperLeftCorner || + LowerRightCorner != other.LowerRightCorner); + } + + //! compares size of rectangles + bool operator<(const rect& other) const + { + return getArea() < other.getArea(); + } + + //! Returns size of rectangle + T getArea() const + { + return getWidth() * getHeight(); + } + + //! Returns if a 2d point is within this rectangle. + /** \param pos Position to test if it lies within this rectangle. + \return True if the position is within the rectangle, false if not. */ + bool isPointInside(const position2d& pos) const + { + return (UpperLeftCorner.X <= pos.X && + UpperLeftCorner.Y <= pos.Y && + LowerRightCorner.X >= pos.X && + LowerRightCorner.Y >= pos.Y); + } + + //! Check if the rectangle collides with another rectangle. + /** \param other Rectangle to test collision with + \return True if the rectangles collide. */ + bool isRectCollided(const rect& other) const + { + return (LowerRightCorner.Y > other.UpperLeftCorner.Y && + UpperLeftCorner.Y < other.LowerRightCorner.Y && + LowerRightCorner.X > other.UpperLeftCorner.X && + UpperLeftCorner.X < other.LowerRightCorner.X); + } + + //! Clips this rectangle with another one. + /** \param other Rectangle to clip with */ + void clipAgainst(const rect& other) + { + if (other.LowerRightCorner.X < LowerRightCorner.X) + LowerRightCorner.X = other.LowerRightCorner.X; + if (other.LowerRightCorner.Y < LowerRightCorner.Y) + LowerRightCorner.Y = other.LowerRightCorner.Y; + + if (other.UpperLeftCorner.X > UpperLeftCorner.X) + UpperLeftCorner.X = other.UpperLeftCorner.X; + if (other.UpperLeftCorner.Y > UpperLeftCorner.Y) + UpperLeftCorner.Y = other.UpperLeftCorner.Y; + + // correct possible invalid rect resulting from clipping + if (UpperLeftCorner.Y > LowerRightCorner.Y) + UpperLeftCorner.Y = LowerRightCorner.Y; + if (UpperLeftCorner.X > LowerRightCorner.X) + UpperLeftCorner.X = LowerRightCorner.X; + } + + //! Moves this rectangle to fit inside another one. + /** \return True on success, false if not possible */ + bool constrainTo(const rect& other) + { + if (other.getWidth() < getWidth() || other.getHeight() < getHeight()) + return false; + + T diff = other.LowerRightCorner.X - LowerRightCorner.X; + if (diff < 0) + { + LowerRightCorner.X += diff; + UpperLeftCorner.X += diff; + } + + diff = other.LowerRightCorner.Y - LowerRightCorner.Y; + if (diff < 0) + { + LowerRightCorner.Y += diff; + UpperLeftCorner.Y += diff; + } + + diff = UpperLeftCorner.X - other.UpperLeftCorner.X; + if (diff < 0) + { + UpperLeftCorner.X -= diff; + LowerRightCorner.X -= diff; + } + + diff = UpperLeftCorner.Y - other.UpperLeftCorner.Y; + if (diff < 0) + { + UpperLeftCorner.Y -= diff; + LowerRightCorner.Y -= diff; + } + + return true; + } + + //! Get width of rectangle. + T getWidth() const + { + return LowerRightCorner.X - UpperLeftCorner.X; + } + + //! Get height of rectangle. + T getHeight() const + { + return LowerRightCorner.Y - UpperLeftCorner.Y; + } + + //! If the lower right corner of the rect is smaller then the upper left, the points are swapped. + void repair() + { + if (LowerRightCorner.X < UpperLeftCorner.X) + { + T t = LowerRightCorner.X; + LowerRightCorner.X = UpperLeftCorner.X; + UpperLeftCorner.X = t; + } + + if (LowerRightCorner.Y < UpperLeftCorner.Y) + { + T t = LowerRightCorner.Y; + LowerRightCorner.Y = UpperLeftCorner.Y; + UpperLeftCorner.Y = t; + } + } + + //! Returns if the rect is valid to draw. + /** It would be invalid if the UpperLeftCorner is lower or more + right than the LowerRightCorner. */ + bool isValid() const + { + return ((LowerRightCorner.X >= UpperLeftCorner.X) && + (LowerRightCorner.Y >= UpperLeftCorner.Y)); + } + + //! Get the center of the rectangle + position2d getCenter() const + { + return position2d( + (UpperLeftCorner.X + LowerRightCorner.X) / 2, + (UpperLeftCorner.Y + LowerRightCorner.Y) / 2); + } + + //! Get the dimensions of the rectangle + dimension2d getSize() const + { + return dimension2d(getWidth(), getHeight()); + } + + + //! Adds a point to the rectangle + /** Causes the rectangle to grow bigger if point is outside of + the box + \param p Point to add to the box. */ + void addInternalPoint(const position2d& p) + { + addInternalPoint(p.X, p.Y); + } + + //! Adds a point to the bounding rectangle + /** Causes the rectangle to grow bigger if point is outside of + the box + \param x X-Coordinate of the point to add to this box. + \param y Y-Coordinate of the point to add to this box. */ + void addInternalPoint(T x, T y) + { + if (x>LowerRightCorner.X) + LowerRightCorner.X = x; + if (y>LowerRightCorner.Y) + LowerRightCorner.Y = y; + + if (x UpperLeftCorner; + //! Lower right corner + position2d LowerRightCorner; + }; + + //! Rectangle with float values + typedef rect rectf; + //! Rectangle with int values + typedef rect recti; + +} // end namespace core +} // end namespace irr + +#endif + diff --git a/IrrExtensions/modded/include/vector2d.h b/IrrExtensions/modded/include/vector2d.h new file mode 100644 index 0000000..8e583cc --- /dev/null +++ b/IrrExtensions/modded/include/vector2d.h @@ -0,0 +1,416 @@ +// Copyright (C) 2002-2012 Nikolaus Gebhardt +// This file is part of the "Irrlicht Engine". +// For conditions of distribution and use, see copyright notice in irrlicht.h + +#ifndef __IRR_POINT_2D_H_INCLUDED__ +#define __IRR_POINT_2D_H_INCLUDED__ + +#include "irrMath.h" +#include "dimension2d.h" + +namespace irr +{ +namespace core +{ + + +//! 2d vector template class with lots of operators and methods. +/** As of Irrlicht 1.6, this class supersedes position2d, which should + be considered deprecated. */ +template +class vector2d +{ +public: + //! Default constructor (null vector) + vector2d() : X(0), Y(0) {} + //! Constructor with two different values + vector2d(T nx, T ny) : X(nx), Y(ny) {} + //! Constructor with the same value for both members + explicit vector2d(T n) : X(n), Y(n) {} + //! Copy constructor + vector2d(const vector2d& other) : X(other.X), Y(other.Y) {} + + vector2d(const dimension2d& other) : X(other.Width), Y(other.Height) {} + + // operators + + vector2d operator-() const { return vector2d(-X, -Y); } + + vector2d& operator=(const vector2d& other) { X = other.X; Y = other.Y; return *this; } + + vector2d& operator=(const dimension2d& other) { X = other.Width; Y = other.Height; return *this; } + + vector2d operator+(const vector2d& other) const { return vector2d(X + other.X, Y + other.Y); } + vector2d operator+(const dimension2d& other) const { return vector2d(X + other.Width, Y + other.Height); } + vector2d& operator+=(const vector2d& other) { X+=other.X; Y+=other.Y; return *this; } + vector2d operator+(const T v) const { return vector2d(X + v, Y + v); } + vector2d& operator+=(const T v) { X+=v; Y+=v; return *this; } + vector2d& operator+=(const dimension2d& other) { X += other.Width; Y += other.Height; return *this; } + + vector2d operator-(const vector2d& other) const { return vector2d(X - other.X, Y - other.Y); } + vector2d operator-(const dimension2d& other) const { return vector2d(X - other.Width, Y - other.Height); } + vector2d& operator-=(const vector2d& other) { X-=other.X; Y-=other.Y; return *this; } + vector2d operator-(const T v) const { return vector2d(X - v, Y - v); } + vector2d& operator-=(const T v) { X-=v; Y-=v; return *this; } + vector2d& operator-=(const dimension2d& other) { X -= other.Width; Y -= other.Height; return *this; } + + vector2d operator*(const vector2d& other) const { return vector2d(X * other.X, Y * other.Y); } + vector2d& operator*=(const vector2d& other) { X*=other.X; Y*=other.Y; return *this; } + vector2d operator*(const T v) const { return vector2d(X * v, Y * v); } + vector2d& operator*=(const T v) { X*=v; Y*=v; return *this; } + + vector2d operator/(const vector2d& other) const { return vector2d(X / other.X, Y / other.Y); } + vector2d& operator/=(const vector2d& other) { X/=other.X; Y/=other.Y; return *this; } + vector2d operator/(const T v) const { return vector2d(X / v, Y / v); } + vector2d& operator/=(const T v) { X/=v; Y/=v; return *this; } + + //! sort in order X, Y. Equality with rounding tolerance. + bool operator<=(const vector2d&other) const + { + return (X=(const vector2d&other) const + { + return (X>other.X || core::equals(X, other.X)) || + (core::equals(X, other.X) && (Y>other.Y || core::equals(Y, other.Y))); + } + + //! sort in order X, Y. Difference must be above rounding tolerance. + bool operator<(const vector2d&other) const + { + return (X(const vector2d&other) const + { + return (X>other.X && !core::equals(X, other.X)) || + (core::equals(X, other.X) && Y>other.Y && !core::equals(Y, other.Y)); + } + + bool operator==(const vector2d& other) const { return equals(other); } + bool operator!=(const vector2d& other) const { return !equals(other); } + + // functions + + //! Checks if this vector equals the other one. + /** Takes floating point rounding errors into account. + \param other Vector to compare with. + \param tolerance Epsilon value for both - comparing X and Y. + \return True if the two vector are (almost) equal, else false. */ + bool equals(const vector2d& other, const T tolerance = (T)ROUNDING_ERROR_f32 ) const + { + return core::equals(X, other.X, tolerance) && core::equals(Y, other.Y, tolerance); + } + + vector2d& set(T nx, T ny) {X=nx; Y=ny; return *this; } + vector2d& set(const vector2d& p) { X=p.X; Y=p.Y; return *this; } + + // Added by Nic Anderson + //! Moves the vector by the given amount in each dimension. + void move( T x, T y ) + { + X += x; + Y += y; + } + + //! Gets the length of the vector. + /** \return The length of the vector. */ + T getLength() const { return core::squareroot( X*X + Y*Y ); } + + //! Get the squared length of this vector + /** This is useful because it is much faster than getLength(). + \return The squared length of the vector. */ + T getLengthSQ() const { return X*X + Y*Y; } + + //! Get the dot product of this vector with another. + /** \param other Other vector to take dot product with. + \return The dot product of the two vectors. */ + T dotProduct(const vector2d& other) const + { + return X*other.X + Y*other.Y; + } + + //! check if this vector is parallel to another vector + bool nearlyParallel( const vector2d & other, const T factor = relativeErrorFactor()) const + { + // https://eagergames.wordpress.com/2017/04/01/fast-parallel-lines-and-vectors-test/ + // if a || b then a.x/a.y = b.x/b.y (similiar triangles) + // if a || b then either both x are 0 or both y are 0. + + return equalsRelative( X*other.Y, other.X* Y, factor) + && // a bit counterintuitive, but makes sure that + // only y or only x are 0, and at same time deals + // with the case where one vector is zero vector. + (X*other.X + Y*other.Y) != 0; + } + + //! Gets distance from another point. + /** Here, the vector is interpreted as a point in 2-dimensional space. + \param other Other vector to measure from. + \return Distance from other point. */ + T getDistanceFrom(const vector2d& other) const + { + return vector2d(X - other.X, Y - other.Y).getLength(); + } + + //! Returns squared distance from another point. + /** Here, the vector is interpreted as a point in 2-dimensional space. + \param other Other vector to measure from. + \return Squared distance from other point. */ + T getDistanceFromSQ(const vector2d& other) const + { + return vector2d(X - other.X, Y - other.Y).getLengthSQ(); + } + + //! rotates the point anticlockwise around a center by an amount of degrees. + /** \param degrees Amount of degrees to rotate by, anticlockwise. + \param center Rotation center. + \return This vector after transformation. */ + vector2d& rotateBy(f64 degrees, const vector2d& center=vector2d()) + { + degrees *= DEGTORAD64; + const f64 cs = cos(degrees); + const f64 sn = sin(degrees); + + X -= center.X; + Y -= center.Y; + + set((T)(X*cs - Y*sn), (T)(X*sn + Y*cs)); + + X += center.X; + Y += center.Y; + return *this; + } + + //! Normalize the vector. + /** The null vector is left untouched. + \return Reference to this vector, after normalization. */ + vector2d& normalize() + { + f32 length = (f32)(X*X + Y*Y); + if ( length == 0 ) + return *this; + length = core::reciprocal_squareroot ( length ); + X = (T)(X * length); + Y = (T)(Y * length); + return *this; + } + + //! Calculates the angle of this vector in degrees in the trigonometric sense. + /** 0 is to the right (3 o'clock), values increase counter-clockwise. + This method has been suggested by Pr3t3nd3r. + \return Returns a value between 0 and 360. */ + f64 getAngleTrig() const + { + if (Y == 0) + return X < 0 ? 180 : 0; + else + if (X == 0) + return Y < 0 ? 270 : 90; + + if ( Y > 0) + if (X > 0) + return atan((irr::f64)Y/(irr::f64)X) * RADTODEG64; + else + return 180.0-atan((irr::f64)Y/-(irr::f64)X) * RADTODEG64; + else + if (X > 0) + return 360.0-atan(-(irr::f64)Y/(irr::f64)X) * RADTODEG64; + else + return 180.0+atan(-(irr::f64)Y/-(irr::f64)X) * RADTODEG64; + } + + //! Calculates the angle of this vector in degrees in the counter trigonometric sense. + /** 0 is to the right (3 o'clock), values increase clockwise. + \return Returns a value between 0 and 360. */ + inline f64 getAngle() const + { + if (Y == 0) // corrected thanks to a suggestion by Jox + return X < 0 ? 180 : 0; + else if (X == 0) + return Y < 0 ? 90 : 270; + + // don't use getLength here to avoid precision loss with s32 vectors + // avoid floating-point trouble as sqrt(y*y) is occasionally larger than y, so clamp + const f64 tmp = core::clamp(Y / sqrt((f64)(X*X + Y*Y)), -1.0, 1.0); + const f64 angle = atan( core::squareroot(1 - tmp*tmp) / tmp) * RADTODEG64; + + if (X>0 && Y>0) + return angle + 270; + else + if (X>0 && Y<0) + return angle + 90; + else + if (X<0 && Y<0) + return 90 - angle; + else + if (X<0 && Y>0) + return 270 - angle; + + return angle; + } + + //! Calculates the angle between this vector and another one in degree. + /** \param b Other vector to test with. + \return Returns a value between 0 and 90. */ + inline f64 getAngleWith(const vector2d& b) const + { + f64 tmp = (f64)(X*b.X + Y*b.Y); + + if (tmp == 0.0) + return 90.0; + + tmp = tmp / core::squareroot((f64)((X*X + Y*Y) * (b.X*b.X + b.Y*b.Y))); + if (tmp < 0.0) + tmp = -tmp; + if ( tmp > 1.0 ) // avoid floating-point trouble + tmp = 1.0; + + return atan(sqrt(1 - tmp*tmp) / tmp) * RADTODEG64; + } + + //! Returns if this vector interpreted as a point is on a line between two other points. + /** It is assumed that the point is on the line. + \param begin Beginning vector to compare between. + \param end Ending vector to compare between. + \return True if this vector is between begin and end, false if not. */ + bool isBetweenPoints(const vector2d& begin, const vector2d& end) const + { + // . end + // / + // / + // / + // . begin + // - + // - + // . this point (am I inside or outside)? + // + if (begin.X != end.X) + { + return ((begin.X <= X && X <= end.X) || + (begin.X >= X && X >= end.X)); + } + else + { + return ((begin.Y <= Y && Y <= end.Y) || + (begin.Y >= Y && Y >= end.Y)); + } + } + + //! Creates an interpolated vector between this vector and another vector. + /** \param other The other vector to interpolate with. + \param d Interpolation value between 0.0f (all the other vector) and 1.0f (all this vector). + Note that this is the opposite direction of interpolation to getInterpolated_quadratic() + \return An interpolated vector. This vector is not modified. */ + vector2d getInterpolated(const vector2d& other, f64 d) const + { + f64 inv = 1.0f - d; + return vector2d((T)(other.X*inv + X*d), (T)(other.Y*inv + Y*d)); + } + + //! Creates a quadratically interpolated vector between this and two other vectors. + /** \param v2 Second vector to interpolate with. + \param v3 Third vector to interpolate with (maximum at 1.0f) + \param d Interpolation value between 0.0f (all this vector) and 1.0f (all the 3rd vector). + Note that this is the opposite direction of interpolation to getInterpolated() and interpolate() + \return An interpolated vector. This vector is not modified. */ + vector2d getInterpolated_quadratic(const vector2d& v2, const vector2d& v3, f64 d) const + { + // this*(1-d)*(1-d) + 2 * v2 * (1-d) + v3 * d * d; + const f64 inv = 1.0f - d; + const f64 mul0 = inv * inv; + const f64 mul1 = 2.0f * d * inv; + const f64 mul2 = d * d; + + return vector2d ( (T)(X * mul0 + v2.X * mul1 + v3.X * mul2), + (T)(Y * mul0 + v2.Y * mul1 + v3.Y * mul2)); + } + + /*! Test if this point and another 2 poitns taken as triplet + are colinear, clockwise, anticlockwise. This can be used also + to check winding order in triangles for 2D meshes. + \return 0 if points are colinear, 1 if clockwise, 2 if anticlockwise + */ + s32 checkOrientation( const vector2d & b, const vector2d & c) const + { + // Example of clockwise points + // + // ^ Y + // | A + // | . . + // | . . + // | C.....B + // +---------------> X + + T val = (b.Y - Y) * (c.X - b.X) - + (b.X - X) * (c.Y - b.Y); + + if (val == 0) return 0; // colinear + + return (val > 0) ? 1 : 2; // clock or counterclock wise + } + + /*! Returns true if points (a,b,c) are clockwise on the X,Y plane*/ + inline bool areClockwise( const vector2d & b, const vector2d & c) const + { + T val = (b.Y - Y) * (c.X - b.X) - + (b.X - X) * (c.Y - b.Y); + + return val > 0; + } + + /*! Returns true if points (a,b,c) are counterclockwise on the X,Y plane*/ + inline bool areCounterClockwise( const vector2d & b, const vector2d & c) const + { + T val = (b.Y - Y) * (c.X - b.X) - + (b.X - X) * (c.Y - b.Y); + + return val < 0; + } + + //! Sets this vector to the linearly interpolated vector between a and b. + /** \param a first vector to interpolate with, maximum at 1.0f + \param b second vector to interpolate with, maximum at 0.0f + \param d Interpolation value between 0.0f (all vector b) and 1.0f (all vector a) + Note that this is the opposite direction of interpolation to getInterpolated_quadratic() + */ + vector2d& interpolate( const vector2d& a, const vector2d& b, f64 d) + { + X = (T)((f64)b.X + ( ( a.X - b.X ) * d )); + Y = (T)((f64)b.Y + ( ( a.Y - b.Y ) * d )); + return *this; + } + + //! X coordinate of vector. + T X; + + //! Y coordinate of vector. + T Y; +}; + + //! Typedef for f32 2d vector. + typedef vector2d vector2df; + + //! Typedef for integer 2d vector. + typedef vector2d vector2di; + + template + vector2d operator*(const S scalar, const vector2d& vector) { return vector*scalar; } + + // These methods are declared in dimension2d, but need definitions of vector2d + template + dimension2d::dimension2d(const vector2d& other) : Width(other.X), Height(other.Y) { } + + template + bool dimension2d::operator==(const vector2d& other) const { return Width == other.X && Height == other.Y; } + +} // end namespace core +} // end namespace irr + +#endif + diff --git a/IrrExtensions/modded/source/CFileSystem.cpp b/IrrExtensions/modded/source/CFileSystem.cpp new file mode 100755 index 0000000..666d24c --- /dev/null +++ b/IrrExtensions/modded/source/CFileSystem.cpp @@ -0,0 +1,1102 @@ +// Copyright (C) 2002-2012 Nikolaus Gebhardt +// This file is part of the "Irrlicht Engine". +// For conditions of distribution and use, see copyright notice in irrlicht.h + +#include "IrrCompileConfig.h" + +#include "CFileSystem.h" +#include "IReadFile.h" +#include "IWriteFile.h" +#include "CZipReader.h" +#include "CMountPointReader.h" +#include "CPakReader.h" +#include "CNPKReader.h" +#include "CTarReader.h" +#include "CWADReader.h" +#include "CFileList.h" +#include "CXMLReader.h" +#include "CXMLWriter.h" +#include "stdio.h" +#include "os.h" +#include "CAttributes.h" +#include "CReadFile.h" +#include "CMemoryFile.h" +#include "CLimitReadFile.h" +#include "CWriteFile.h" +#include "irrList.h" + +#if defined (__STRICT_ANSI__) + #error Compiling with __STRICT_ANSI__ not supported. g++ does set this when compiling with -std=c++11 or -std=c++0x. Use instead -std=gnu++11 or -std=gnu++0x. Or use -U__STRICT_ANSI__ to disable strict ansi. +#endif + +#if defined (_IRR_WINDOWS_API_) + #if !defined ( _WIN32_WCE ) + #include // for _chdir + #include // for _access + #include + #endif +#else + #if (defined(_IRR_POSIX_API_) || defined(_IRR_OSX_PLATFORM_)) + #include + #include + #include + #include + #include + #include + #include + #include + #endif +#endif + +namespace irr +{ +namespace io +{ + +//! constructor +CFileSystem::CFileSystem() +{ + #ifdef _DEBUG + setDebugName("CFileSystem"); + #endif + + setFileListSystem(FILESYSTEM_NATIVE); + //! reset current working directory + getWorkingDirectory(); + +#ifdef __IRR_COMPILE_WITH_PAK_ARCHIVE_LOADER_ + ArchiveLoader.push_back(new CArchiveLoaderPAK(this)); +#endif + +#ifdef __IRR_COMPILE_WITH_NPK_ARCHIVE_LOADER_ + ArchiveLoader.push_back(new CArchiveLoaderNPK(this)); +#endif + +#ifdef __IRR_COMPILE_WITH_TAR_ARCHIVE_LOADER_ + ArchiveLoader.push_back(new CArchiveLoaderTAR(this)); +#endif + +#ifdef __IRR_COMPILE_WITH_WAD_ARCHIVE_LOADER_ + ArchiveLoader.push_back(new CArchiveLoaderWAD(this)); +#endif + +#ifdef __IRR_COMPILE_WITH_MOUNT_ARCHIVE_LOADER_ + ArchiveLoader.push_back(new CArchiveLoaderMount(this)); +#endif + +#ifdef __IRR_COMPILE_WITH_ZIP_ARCHIVE_LOADER_ + ArchiveLoader.push_back(new CArchiveLoaderZIP(this)); +#endif + +} + + +//! destructor +CFileSystem::~CFileSystem() +{ + u32 i; + + for ( i=0; i < FileArchives.size(); ++i) + { + FileArchives[i]->drop(); + } + + for ( i=0; i < ArchiveLoader.size(); ++i) + { + ArchiveLoader[i]->drop(); + } +} + + +//! opens a file for read access +IReadFile* CFileSystem::createAndOpenFile(const io::path& filename) +{ + if ( filename.empty() ) + return 0; + + IReadFile* file = 0; + u32 i; + + for (i=0; i< FileArchives.size(); ++i) + { + file = FileArchives[i]->createAndOpenFile(filename); + if (file) + return file; + } + + // Create the file using an absolute path so that it matches + // the scheme used by CNullDriver::getTexture(). + return CReadFile::createReadFile(getAbsolutePath(filename)); +} + + +//! Creates an IReadFile interface for treating memory like a file. +IReadFile* CFileSystem::createMemoryReadFile(const void* memory, s32 len, + const io::path& fileName, bool deleteMemoryWhenDropped) +{ + if (!memory) + return 0; + else + return new CMemoryReadFile(memory, len, fileName, deleteMemoryWhenDropped); +} + + +//! Creates an IReadFile interface for reading files inside files +IReadFile* CFileSystem::createLimitReadFile(const io::path& fileName, + IReadFile* alreadyOpenedFile, long pos, long areaSize) +{ + if (!alreadyOpenedFile) + return 0; + else + return new CLimitReadFile(alreadyOpenedFile, pos, areaSize, fileName); +} + + +//! Creates an IReadFile interface for treating memory like a file. +IWriteFile* CFileSystem::createMemoryWriteFile(void* memory, s32 len, + const io::path& fileName, bool deleteMemoryWhenDropped) +{ + if (!memory) + return 0; + else + return new CMemoryWriteFile(memory, len, fileName, deleteMemoryWhenDropped); +} + + +//! Opens a file for write access. +IWriteFile* CFileSystem::createAndWriteFile(const io::path& filename, bool append) +{ + return CWriteFile::createWriteFile(filename, append); +} + + +//! Adds an external archive loader to the engine. +void CFileSystem::addArchiveLoader(IArchiveLoader* loader) +{ + if (!loader) + return; + + loader->grab(); + ArchiveLoader.push_back(loader); +} + +//! Returns the total number of archive loaders added. +u32 CFileSystem::getArchiveLoaderCount() const +{ + return ArchiveLoader.size(); +} + +//! Gets the archive loader by index. +IArchiveLoader* CFileSystem::getArchiveLoader(u32 index) const +{ + if (index < ArchiveLoader.size()) + return ArchiveLoader[index]; + else + return 0; +} + +//! move the hirarchy of the filesystem. moves sourceIndex relative up or down +bool CFileSystem::moveFileArchive(u32 sourceIndex, s32 relative) +{ + bool r = false; + const s32 dest = (s32) sourceIndex + relative; + const s32 dir = relative < 0 ? -1 : 1; + const s32 sourceEnd = ((s32) FileArchives.size() ) - 1; + IFileArchive *t; + + for (s32 s = (s32) sourceIndex;s != dest; s += dir) + { + if (s < 0 || s > sourceEnd || s + dir < 0 || s + dir > sourceEnd) + continue; + + t = FileArchives[s + dir]; + FileArchives[s + dir] = FileArchives[s]; + FileArchives[s] = t; + r = true; + } + return r; +} + + +//! Adds an archive to the file system. +bool CFileSystem::addFileArchive(const io::path& filename, bool ignoreCase, + bool ignorePaths, E_FILE_ARCHIVE_TYPE archiveType, + const core::stringc& password, + IFileArchive** retArchive) +{ + IFileArchive* archive = 0; + bool ret = false; + + // see if archive is already added + if (changeArchivePassword(filename, password, retArchive)) + return true; + + s32 i; + + // do we know what type it should be? + if (archiveType == EFAT_UNKNOWN || archiveType == EFAT_FOLDER) + { + // try to load archive based on file name + for (i = ArchiveLoader.size()-1; i >=0 ; --i) + { + if (ArchiveLoader[i]->isALoadableFileFormat(filename)) + { + archive = ArchiveLoader[i]->createArchive(filename, ignoreCase, ignorePaths); + if (archive) + break; + } + } + + // try to load archive based on content + if (!archive) + { + io::IReadFile* file = createAndOpenFile(filename); + if (file) + { + for (i = ArchiveLoader.size()-1; i >= 0; --i) + { + file->seek(0); + if (ArchiveLoader[i]->isALoadableFileFormat(file)) + { + file->seek(0); + archive = ArchiveLoader[i]->createArchive(file, ignoreCase, ignorePaths); + if (archive) + break; + } + } + file->drop(); + } + } + } + else + { + // try to open archive based on archive loader type + + io::IReadFile* file = 0; + + for (i = ArchiveLoader.size()-1; i >= 0; --i) + { + if (ArchiveLoader[i]->isALoadableFileFormat(archiveType)) + { + // attempt to open file + if (!file) + file = createAndOpenFile(filename); + + // is the file open? + if (file) + { + // attempt to open archive + file->seek(0); + if (ArchiveLoader[i]->isALoadableFileFormat(file)) + { + file->seek(0); + archive = ArchiveLoader[i]->createArchive(file, ignoreCase, ignorePaths); + if (archive) + break; + } + } + else + { + // couldn't open file + break; + } + } + } + + // if open, close the file + if (file) + file->drop(); + } + + if (archive) + { + FileArchives.push_back(archive); + if (password.size()) + archive->Password=password; + if (retArchive) + *retArchive = archive; + ret = true; + } + else + { + os::Printer::log("Could not create archive for", filename, ELL_ERROR); + } + + return ret; +} + +// don't expose! +bool CFileSystem::changeArchivePassword(const path& filename, + const core::stringc& password, + IFileArchive** archive) +{ + for (s32 idx = 0; idx < (s32)FileArchives.size(); ++idx) + { + // TODO: This should go into a path normalization method + // We need to check for directory names with trailing slash and without + const path absPath = getAbsolutePath(filename); + const path arcPath = FileArchives[idx]->getFileList()->getPath(); + if ((absPath == arcPath) || ((absPath+_IRR_TEXT("/")) == arcPath)) + { + if (password.size()) + FileArchives[idx]->Password=password; + if (archive) + *archive = FileArchives[idx]; + return true; + } + } + + return false; +} + +bool CFileSystem::addFileArchive(IReadFile* file, bool ignoreCase, + bool ignorePaths, E_FILE_ARCHIVE_TYPE archiveType, + const core::stringc& password, IFileArchive** retArchive) +{ + if (!file || archiveType == EFAT_FOLDER) + return false; + + if (file) + { + if (changeArchivePassword(file->getFileName(), password, retArchive)) + return true; + + IFileArchive* archive = 0; + s32 i; + + if (archiveType == EFAT_UNKNOWN) + { + // try to load archive based on file name + for (i = ArchiveLoader.size()-1; i >=0 ; --i) + { + if (ArchiveLoader[i]->isALoadableFileFormat(file->getFileName())) + { + archive = ArchiveLoader[i]->createArchive(file, ignoreCase, ignorePaths); + if (archive) + break; + } + } + + // try to load archive based on content + if (!archive) + { + for (i = ArchiveLoader.size()-1; i >= 0; --i) + { + file->seek(0); + if (ArchiveLoader[i]->isALoadableFileFormat(file)) + { + file->seek(0); + archive = ArchiveLoader[i]->createArchive(file, ignoreCase, ignorePaths); + if (archive) + break; + } + } + } + } + else + { + // try to open archive based on archive loader type + for (i = ArchiveLoader.size()-1; i >= 0; --i) + { + if (ArchiveLoader[i]->isALoadableFileFormat(archiveType)) + { + // attempt to open archive + file->seek(0); + if (ArchiveLoader[i]->isALoadableFileFormat(file)) + { + file->seek(0); + archive = ArchiveLoader[i]->createArchive(file, ignoreCase, ignorePaths); + if (archive) + break; + } + } + } + } + + if (archive) + { + FileArchives.push_back(archive); + if (password.size()) + archive->Password=password; + if (retArchive) + *retArchive = archive; + return true; + } + else + { + os::Printer::log("Could not create archive for", file->getFileName(), ELL_ERROR); + } + } + + return false; +} + + +//! Adds an archive to the file system. +bool CFileSystem::addFileArchive(IFileArchive* archive) +{ + if ( archive ) + { + for (u32 i=0; i < FileArchives.size(); ++i) + { + if (archive == FileArchives[i]) + { + return false; + } + } + FileArchives.push_back(archive); + archive->grab(); + + return true; + } + + return false; +} + + +//! removes an archive from the file system. +bool CFileSystem::removeFileArchive(u32 index) +{ + bool ret = false; + if (index < FileArchives.size()) + { + FileArchives[index]->drop(); + FileArchives.erase(index); + ret = true; + } + return ret; +} + + +//! removes an archive from the file system. +bool CFileSystem::removeFileArchive(const io::path& filename) +{ + const path absPath = getAbsolutePath(filename); + for (u32 i=0; i < FileArchives.size(); ++i) + { + if (absPath == FileArchives[i]->getFileList()->getPath()) + return removeFileArchive(i); + } + return false; +} + + +//! Removes an archive from the file system. +bool CFileSystem::removeFileArchive(const IFileArchive* archive) +{ + for (u32 i=0; i < FileArchives.size(); ++i) + { + if (archive == FileArchives[i]) + { + return removeFileArchive(i); + } + } + return false; +} + + +//! gets an archive +u32 CFileSystem::getFileArchiveCount() const +{ + return FileArchives.size(); +} + + +IFileArchive* CFileSystem::getFileArchive(u32 index) +{ + return index < getFileArchiveCount() ? FileArchives[index] : 0; +} + + +//! Returns the string of the current working directory +const io::path& CFileSystem::getWorkingDirectory() +{ + EFileSystemType type = FileSystemType; + + if (type != FILESYSTEM_NATIVE) + { + type = FILESYSTEM_VIRTUAL; + } + else + { + #if defined(_IRR_WINDOWS_API_) + fschar_t tmp[_MAX_PATH]; + #if defined(_IRR_WCHAR_FILESYSTEM ) + _wgetcwd(tmp, _MAX_PATH); + WorkingDirectory[FILESYSTEM_NATIVE] = tmp; + WorkingDirectory[FILESYSTEM_NATIVE].replace(L'\\', L'/'); + #else + _getcwd(tmp, _MAX_PATH); + WorkingDirectory[FILESYSTEM_NATIVE] = tmp; + WorkingDirectory[FILESYSTEM_NATIVE].replace('\\', '/'); + #endif + #endif + + #if (defined(_IRR_POSIX_API_) || defined(_IRR_OSX_PLATFORM_)) + + // getting the CWD is rather complex as we do not know the size + // so try it until the call was successful + // Note that neither the first nor the second parameter may be 0 according to POSIX + + #if defined(_IRR_WCHAR_FILESYSTEM ) + u32 pathSize=256; + wchar_t *tmpPath = new wchar_t[pathSize]; + while ((pathSize < (1<<16)) && !(wgetcwd(tmpPath,pathSize))) + { + delete [] tmpPath; + pathSize *= 2; + tmpPath = new char[pathSize]; + } + if (tmpPath) + { + WorkingDirectory[FILESYSTEM_NATIVE] = tmpPath; + delete [] tmpPath; + } + #else + u32 pathSize=256; + char *tmpPath = new char[pathSize]; + while ((pathSize < (1<<16)) && !(getcwd(tmpPath,pathSize))) + { + delete [] tmpPath; + pathSize *= 2; + tmpPath = new char[pathSize]; + } + if (tmpPath) + { + WorkingDirectory[FILESYSTEM_NATIVE] = tmpPath; + delete [] tmpPath; + } + #endif + #endif + + WorkingDirectory[type].validate(); + } + + return WorkingDirectory[type]; +} + + +//! Changes the current Working Directory to the given string. +bool CFileSystem::changeWorkingDirectoryTo(const io::path& newDirectory) +{ + bool success=false; + + if (FileSystemType != FILESYSTEM_NATIVE) + { + WorkingDirectory[FILESYSTEM_VIRTUAL] = newDirectory; + // is this empty string constant really intended? + flattenFilename(WorkingDirectory[FILESYSTEM_VIRTUAL], _IRR_TEXT("")); + success = true; + } + else + { + WorkingDirectory[FILESYSTEM_NATIVE] = newDirectory; + +#if defined(_MSC_VER) + #if defined(_IRR_WCHAR_FILESYSTEM) + success = (_wchdir(newDirectory.c_str()) == 0); + #else + success = (_chdir(newDirectory.c_str()) == 0); + #endif +#else + #if defined(_IRR_WCHAR_FILESYSTEM) + success = (_wchdir(newDirectory.c_str()) == 0); + #else + success = (chdir(newDirectory.c_str()) == 0); + #endif +#endif + } + + return success; +} + + +io::path CFileSystem::getAbsolutePath(const io::path& filename) const +{ + if ( filename.empty() ) + return filename; +#if defined(_IRR_WINDOWS_API_) + fschar_t *p=0; + fschar_t fpath[_MAX_PATH]; + #if defined(_IRR_WCHAR_FILESYSTEM ) + p = _wfullpath(fpath, filename.c_str(), _MAX_PATH); + core::stringw tmp(p); + tmp.replace(L'\\', L'/'); + #else + p = _fullpath(fpath, filename.c_str(), _MAX_PATH); + core::stringc tmp(p); + tmp.replace('\\', '/'); + #endif + return tmp; +#elif (defined(_IRR_POSIX_API_) || defined(_IRR_OSX_PLATFORM_)) + c8* p=0; + c8 fpath[4096]; + fpath[0]=0; + p = realpath(filename.c_str(), fpath); + if (!p) + { + // content in fpath is unclear at this point + if (!fpath[0]) // seems like fpath wasn't altered, use our best guess + { + io::path tmp(filename); + return flattenFilename(tmp); + } + else + return io::path(fpath); + } + if (filename[filename.size()-1]=='/') + return io::path(p)+_IRR_TEXT("/"); + else + return io::path(p); +#else + return io::path(filename); +#endif +} + + +//! returns the directory part of a filename, i.e. all until the first +//! slash or backslash, excluding it. If no directory path is prefixed, a '.' +//! is returned. +io::path CFileSystem::getFileDir(const io::path& filename) const +{ + // find last forward or backslash + s32 lastSlash = filename.findLast('/'); + const s32 lastBackSlash = filename.findLast('\\'); + lastSlash = lastSlash > lastBackSlash ? lastSlash : lastBackSlash; + + if ((u32)lastSlash < filename.size()) + return filename.subString(0, lastSlash); + else + return _IRR_TEXT("."); +} + + +//! returns the base part of a filename, i.e. all except for the directory +//! part. If no directory path is prefixed, the full name is returned. +io::path CFileSystem::getFileBasename(const io::path& filename, bool keepExtension) const +{ + // find last forward or backslash + s32 lastSlash = filename.findLast('/'); + const s32 lastBackSlash = filename.findLast('\\'); + lastSlash = core::max_(lastSlash, lastBackSlash); + + // get number of chars after last dot + s32 end = 0; + if (!keepExtension) + { + // take care to search only after last slash to check only for + // dots in the filename + end = filename.findLast('.'); + if (end == -1 || end < lastSlash) + end=0; + else + end = filename.size()-end; + } + + if ((u32)lastSlash < filename.size()) + return filename.subString(lastSlash+1, filename.size()-lastSlash-1-end); + else if (end != 0) + return filename.subString(0, filename.size()-end); + else + return filename; +} + + +//! flatten a path and file name for example: "/you/me/../." becomes "/you" +io::path& CFileSystem::flattenFilename(io::path& directory, const io::path& root) const +{ + directory.replace('\\', '/'); + if (directory.lastChar() != '/') + directory.append('/'); + + io::path dir; + io::path subdir; + + s32 lastpos = 0; + s32 pos = 0; + bool lastWasRealDir=false; + + while ((pos = directory.findNext('/', lastpos)) >= 0) + { + subdir = directory.subString(lastpos, pos - lastpos + 1); + + if (subdir == _IRR_TEXT("../")) + { + if (lastWasRealDir) + { + deletePathFromPath(dir, 2); + lastWasRealDir=(dir.size()!=0); + } + else + { + dir.append(subdir); + lastWasRealDir=false; + } + } + else if (subdir == _IRR_TEXT("/")) + { + dir = root; + } + else if (subdir != _IRR_TEXT("./")) + { + dir.append(subdir); + lastWasRealDir=true; + } + + lastpos = pos + 1; + } + directory = dir; + return directory; +} + + +//! Get the relative filename, relative to the given directory +path CFileSystem::getRelativeFilename(const path& filename, const path& directory) const +{ + if ( filename.empty() || directory.empty() ) + return filename; + + io::path path1, file, ext; + core::splitFilename(getAbsolutePath(filename), &path1, &file, &ext); + io::path path2(getAbsolutePath(directory)); + core::list list1, list2; + path1.split(list1, _IRR_TEXT("/\\"), 2); + path2.split(list2, _IRR_TEXT("/\\"), 2); + u32 i=0; + core::list::ConstIterator it1,it2; + it1=list1.begin(); + it2=list2.begin(); + + #if defined (_IRR_WINDOWS_API_) + fschar_t partition1 = 0, partition2 = 0; + io::path prefix1, prefix2; + if ( it1 != list1.end() ) + prefix1 = *it1; + if ( it2 != list2.end() ) + prefix2 = *it2; + if ( prefix1.size() > 1 && prefix1[1] == _IRR_TEXT(':') ) + partition1 = core::locale_lower(prefix1[0]); + if ( prefix2.size() > 1 && prefix2[1] == _IRR_TEXT(':') ) + partition2 = core::locale_lower(prefix2[0]); + + // must have the same prefix or we can't resolve it to a relative filename + if ( partition1 != partition2 ) + { + return filename; + } + #endif + + + for (; iaddItem(Path + c_file.name, 0, c_file.size, (_A_SUBDIR & c_file.attrib) != 0, 0); + } + while( _tfindnext( hFile, &c_file ) == 0 ); + + _findclose( hFile ); + } + #endif + + //TODO add drives + //entry.Name = "E:\\"; + //entry.isDirectory = true; + //Files.push_back(entry); + #endif + + // -------------------------------------------- + //! Linux version + #if (defined(_IRR_POSIX_API_) || defined(_IRR_OSX_PLATFORM_)) + + + r = new CFileList(Path, false, false); + + r->addItem(Path + _IRR_TEXT(".."), 0, 0, true, 0); + + //! We use the POSIX compliant methods instead of scandir + DIR* dirHandle=opendir(Path.c_str()); + if (dirHandle) + { + struct dirent *dirEntry; + while ((dirEntry=readdir(dirHandle))) + { + u32 size = 0; + bool isDirectory = false; + + if((strcmp(dirEntry->d_name, ".")==0) || + (strcmp(dirEntry->d_name, "..")==0)) + { + continue; + } + struct stat buf; + if (stat(dirEntry->d_name, &buf)==0) + { + size = buf.st_size; + isDirectory = S_ISDIR(buf.st_mode); + } + #if !defined(_IRR_SOLARIS_PLATFORM_) && !defined(__CYGWIN__) + // only available on some systems + else + { + isDirectory = dirEntry->d_type == DT_DIR; + } + #endif + + r->addItem(Path + dirEntry->d_name, 0, size, isDirectory, 0); + } + closedir(dirHandle); + } + #endif + } + else + { + //! create file list for the virtual filesystem + r = new CFileList(Path, false, false); + + //! add relative navigation + SFileListEntry e2; + SFileListEntry e3; + + //! PWD + r->addItem(Path + _IRR_TEXT("."), 0, 0, true, 0); + + //! parent + r->addItem(Path + _IRR_TEXT(".."), 0, 0, true, 0); + + //! merge archives + for (u32 i=0; i < FileArchives.size(); ++i) + { + const IFileList *merge = FileArchives[i]->getFileList(); + + for (u32 j=0; j < merge->getFileCount(); ++j) + { + if (core::isInSameDirectory(Path, merge->getFullFileName(j)) == 0) + { + r->addItem(merge->getFullFileName(j), merge->getFileOffset(j), merge->getFileSize(j), merge->isDirectory(j), 0); + } + } + } + } + + if (r) + r->sort(); + return r; +} + +//! Creates an empty filelist +IFileList* CFileSystem::createEmptyFileList(const io::path& path, bool ignoreCase, bool ignorePaths) +{ + return new CFileList(path, ignoreCase, ignorePaths); +} + + +//! determines if a file exists and would be able to be opened. +bool CFileSystem::existFile(const io::path& filename) const +{ + for (u32 i=0; i < FileArchives.size(); ++i) + if (FileArchives[i]->getFileList()->findFile(filename)!=-1) + return true; + +#if defined(_MSC_VER) + #if defined(_IRR_WCHAR_FILESYSTEM) + return (_waccess(filename.c_str(), 0) != -1); + #else + return (_access(filename.c_str(), 0) != -1); + #endif +#elif defined(F_OK) + #if defined(_IRR_WCHAR_FILESYSTEM) + return (_waccess(filename.c_str(), F_OK) != -1); + #else + return (access(filename.c_str(), F_OK) != -1); + #endif +#else + return (access(filename.c_str(), 0) != -1); +#endif +} + + +//! Creates a XML Reader from a file. +IXMLReader* CFileSystem::createXMLReader(const io::path& filename) +{ +#ifdef _IRR_COMPILE_WITH_XML_ + IReadFile* file = createAndOpenFile(filename); + if (!file) + return 0; + + IXMLReader* reader = createXMLReader(file); + file->drop(); + return reader; +#else + noXML(); + return 0; +#endif +} + + +//! Creates a XML Reader from a file. +IXMLReader* CFileSystem::createXMLReader(IReadFile* file) +{ +#ifdef _IRR_COMPILE_WITH_XML_ + if (!file) + return 0; + + return createIXMLReader(file); +#else + noXML(); + return 0; +#endif +} + + +//! Creates a XML Reader from a file. +IXMLReaderUTF8* CFileSystem::createXMLReaderUTF8(const io::path& filename) +{ +#ifdef _IRR_COMPILE_WITH_XML_ + IReadFile* file = createAndOpenFile(filename); + if (!file) + return 0; + + IXMLReaderUTF8* reader = createIXMLReaderUTF8(file); + file->drop(); + return reader; +#else + noXML(); + + return 0; +#endif +} + + +//! Creates a XML Reader from a file. +IXMLReaderUTF8* CFileSystem::createXMLReaderUTF8(IReadFile* file) +{ +#ifdef _IRR_COMPILE_WITH_XML_ + if (!file) + return 0; + + return createIXMLReaderUTF8(file); +#else + noXML(); + return 0; +#endif +} + + +//! Creates a XML Writer from a file. +IXMLWriter* CFileSystem::createXMLWriter(const io::path& filename) +{ +#ifdef _IRR_COMPILE_WITH_XML_ + IWriteFile* file = createAndWriteFile(filename); + IXMLWriter* writer = 0; + if (file) + { + writer = createXMLWriter(file); + file->drop(); + } + return writer; +#else + noXML(); + return 0; +#endif +} + + +//! Creates a XML Writer from a file. +IXMLWriter* CFileSystem::createXMLWriter(IWriteFile* file) +{ +#ifdef _IRR_COMPILE_WITH_XML_ + return new CXMLWriter(file); +#else + noXML(); + return 0; +#endif +} + + +//! creates a filesystem which is able to open files from the ordinary file system, +//! and out of zipfiles, which are able to be added to the filesystem. +IFileSystem* createFileSystem() +{ + return new CFileSystem(); +} + + +//! Creates a new empty collection of attributes, usable for serialization and more. +IAttributes* CFileSystem::createEmptyAttributes(video::IVideoDriver* driver) +{ + return new CAttributes(driver); +} + + +} // end namespace irr +} // end namespace io diff --git a/IrrExtensions/modded/source/CIrrDeviceLinux.cpp b/IrrExtensions/modded/source/CIrrDeviceLinux.cpp new file mode 100755 index 0000000..a681ec9 --- /dev/null +++ b/IrrExtensions/modded/source/CIrrDeviceLinux.cpp @@ -0,0 +1,2276 @@ +// Copyright (C) 2002-2012 Nikolaus Gebhardt +// This file is part of the "Irrlicht Engine". +// For conditions of distribution and use, see copyright notice in irrlicht.h + +#include "CIrrDeviceLinux.h" + +#ifdef _IRR_COMPILE_WITH_X11_DEVICE_ + +#include +#include +#include +#include +#include +#include "IEventReceiver.h" +#include "ISceneManager.h" +#include "IGUIEnvironment.h" +#include "os.h" +#include "CTimer.h" +#include "irrString.h" +#include "Keycodes.h" +#include "COSOperator.h" +#include "CColorConverter.h" +#include "SIrrCreationParameters.h" +#include "IGUISpriteBank.h" +#include +#include + +#if defined(_IRR_COMPILE_WITH_OPENGL_) +#include "CGLXManager.h" +#endif + +#ifdef _IRR_LINUX_XCURSOR_ +#include +#endif + +#if defined _IRR_COMPILE_WITH_JOYSTICK_EVENTS_ +#include +#include + +#ifdef __FreeBSD__ +#include +#else + +// linux/joystick.h includes linux/input.h, which #defines values for various KEY_FOO keys. +// These override the irr::KEY_FOO equivalents, which stops key handling from working. +// As a workaround, defining _INPUT_H stops linux/input.h from being included; it +// doesn't actually seem to be necessary except to pull in sys/ioctl.h. +#define _INPUT_H +#include // Would normally be included in linux/input.h +#include +#undef _INPUT_H +#endif + +#endif // _IRR_COMPILE_WITH_JOYSTICK_EVENTS_ + +namespace irr +{ + namespace video + { +#ifdef _IRR_COMPILE_WITH_OPENGL_ + IVideoDriver* createOpenGLDriver(const irr::SIrrlichtCreationParameters& params, io::IFileSystem* io, IContextManager* contextManager); +#endif + } +} // end namespace irr + +namespace +{ + Atom X_ATOM_CLIPBOARD; + Atom X_ATOM_TARGETS; + Atom X_ATOM_UTF8_STRING; + Atom X_ATOM_TEXT; + Atom X_ATOM_NETWM_MAXIMIZE_VERT; + Atom X_ATOM_NETWM_MAXIMIZE_HORZ; + Atom X_ATOM_NETWM_STATE; +}; + +namespace irr +{ + +const char wmDeleteWindow[] = "WM_DELETE_WINDOW"; + +//! constructor +CIrrDeviceLinux::CIrrDeviceLinux(const SIrrlichtCreationParameters& param) + : CIrrDeviceStub(param), +#ifdef _IRR_COMPILE_WITH_X11_ + XDisplay(0), VisualInfo(0), Screennr(0), XWindow(0), StdHints(0), SoftwareImage(0), + XInputMethod(0), XInputContext(0), + HasNetWM(false), +#ifdef _IRR_COMPILE_WITH_OPENGL_ + GlxWin(0), + Context(0), +#endif +#endif + Width(param.WindowSize.Width), Height(param.WindowSize.Height), + WindowHasFocus(false), WindowMinimized(false), + UseXVidMode(false), UseXRandR(false), UseGLXWindow(false), + ExternalWindow(false), AutorepeatSupport(0) +{ + #ifdef _DEBUG + setDebugName("CIrrDeviceLinux"); + #endif + + // print version, distribution etc. + // thx to LynxLuna for pointing me to the uname function + core::stringc linuxversion; + struct utsname LinuxInfo; + uname(&LinuxInfo); + + linuxversion += LinuxInfo.sysname; + linuxversion += " "; + linuxversion += LinuxInfo.release; + linuxversion += " "; + linuxversion += LinuxInfo.version; + linuxversion += " "; + linuxversion += LinuxInfo.machine; + + Operator = new COSOperator(linuxversion, this); + os::Printer::log(linuxversion.c_str(), ELL_INFORMATION); + + // create keymap + createKeyMap(); + + // create window + if (CreationParams.DriverType != video::EDT_NULL) + { + // create the window, only if we do not use the null device + if (!createWindow()) + return; + } + + // create cursor control + CursorControl = new CCursorControl(this, CreationParams.DriverType == video::EDT_NULL); + + // create driver + createDriver(); + + if (!VideoDriver) + return; + +#ifdef _IRR_COMPILE_WITH_X11_ + createInputContext(); +#endif + + createGUIAndScene(); +} + + +//! destructor +CIrrDeviceLinux::~CIrrDeviceLinux() +{ +#ifdef _IRR_COMPILE_WITH_X11_ + if (StdHints) + XFree(StdHints); + // Disable cursor (it is drop'ed in stub) + if (CursorControl) + { + CursorControl->setVisible(false); + static_cast(CursorControl)->clearCursors(); + } + + // Must free OpenGL textures etc before destroying context, so can't wait for stub destructor + if ( GUIEnvironment ) + { + GUIEnvironment->drop(); + GUIEnvironment = NULL; + } + if ( SceneManager ) + { + SceneManager->drop(); + SceneManager = NULL; + } + if ( VideoDriver ) + { + VideoDriver->drop(); + VideoDriver = NULL; + } + + destroyInputContext(); + + if (XDisplay) + { + #ifdef _IRR_COMPILE_WITH_OPENGL_ + if (Context) + { + if (GlxWin) + { + if (!glXMakeContextCurrent(XDisplay, None, None, NULL)) + os::Printer::log("Could not release glx context.", ELL_WARNING); + } + else + { + if (!glXMakeCurrent(XDisplay, None, NULL)) + os::Printer::log("Could not release glx context.", ELL_WARNING); + } + glXDestroyContext(XDisplay, Context); + if (GlxWin) + glXDestroyWindow(XDisplay, GlxWin); + } + #endif // #ifdef _IRR_COMPILE_WITH_OPENGL_ + + // Reset fullscreen resolution change + switchToFullscreen(true); + + if (SoftwareImage) + XDestroyImage(SoftwareImage); + + if (!ExternalWindow) + { + XDestroyWindow(XDisplay,XWindow); + XCloseDisplay(XDisplay); + } + } + if (VisualInfo) + XFree(VisualInfo); + +#endif // #ifdef _IRR_COMPILE_WITH_X11_ + +#if defined(_IRR_COMPILE_WITH_JOYSTICK_EVENTS_) + for (u32 joystick = 0; joystick < ActiveJoysticks.size(); ++joystick) + { + if (ActiveJoysticks[joystick].fd >= 0) + { + close(ActiveJoysticks[joystick].fd); + } + } +#endif +} + + +#if defined(_IRR_COMPILE_WITH_X11_) && defined(_DEBUG) +int IrrPrintXError(Display *display, XErrorEvent *event) +{ + char msg[256]; + char msg2[256]; + + snprintf_irr(msg, 256, "%d", event->request_code); + XGetErrorDatabaseText(display, "XRequest", msg, "unknown", msg2, 256); + XGetErrorText(display, event->error_code, msg, 256); + os::Printer::log("X Error", msg, ELL_WARNING); + os::Printer::log("From call ", msg2, ELL_WARNING); + return 0; +} +#endif + + +bool CIrrDeviceLinux::switchToFullscreen(bool reset) +{ + if (!CreationParams.Fullscreen) + return true; + if (reset) + { +#ifdef _IRR_LINUX_X11_VIDMODE_ + if (UseXVidMode && CreationParams.Fullscreen) + { + XF86VidModeSwitchToMode(XDisplay, Screennr, &OldVideoMode); + XF86VidModeSetViewPort(XDisplay, Screennr, 0, 0); + } + #endif + #ifdef _IRR_LINUX_X11_RANDR_ + if (UseXRandR && CreationParams.Fullscreen) + { + XRRScreenConfiguration *config=XRRGetScreenInfo(XDisplay,DefaultRootWindow(XDisplay)); + XRRSetScreenConfig(XDisplay,config,DefaultRootWindow(XDisplay),OldRandrMode,OldRandrRotation,CurrentTime); + XRRFreeScreenConfigInfo(config); + } + #endif + return true; + } + + getVideoModeList(); + #if defined(_IRR_LINUX_X11_VIDMODE_) || defined(_IRR_LINUX_X11_RANDR_) + s32 eventbase, errorbase; + s32 bestMode = -1; + #endif + + #ifdef _IRR_LINUX_X11_VIDMODE_ + if (XF86VidModeQueryExtension(XDisplay, &eventbase, &errorbase)) + { + // enumerate video modes + s32 modeCount; + XF86VidModeModeInfo** modes; + + XF86VidModeGetAllModeLines(XDisplay, Screennr, &modeCount, &modes); + + // find fitting mode + for (s32 i = 0; ihdisplay >= Width && modes[i]->vdisplay >= Height) + bestMode = i; + else if (bestMode!=-1 && + modes[i]->hdisplay >= Width && + modes[i]->vdisplay >= Height && + modes[i]->hdisplay <= modes[bestMode]->hdisplay && + modes[i]->vdisplay <= modes[bestMode]->vdisplay) + bestMode = i; + } + if (bestMode != -1) + { + os::Printer::log("Starting vidmode fullscreen mode...", ELL_INFORMATION); + os::Printer::log("hdisplay: ", core::stringc(modes[bestMode]->hdisplay).c_str(), ELL_INFORMATION); + os::Printer::log("vdisplay: ", core::stringc(modes[bestMode]->vdisplay).c_str(), ELL_INFORMATION); + + XF86VidModeSwitchToMode(XDisplay, Screennr, modes[bestMode]); + XF86VidModeSetViewPort(XDisplay, Screennr, 0, 0); + UseXVidMode=true; + } + else + { + os::Printer::log("Could not find specified video mode, running windowed.", ELL_WARNING); + CreationParams.Fullscreen = false; + } + + XFree(modes); + } + else + #endif + #ifdef _IRR_LINUX_X11_RANDR_ + if (XRRQueryExtension(XDisplay, &eventbase, &errorbase)) + { + s32 modeCount; + XRRScreenConfiguration *config=XRRGetScreenInfo(XDisplay,DefaultRootWindow(XDisplay)); + XRRScreenSize *modes=XRRConfigSizes(config,&modeCount); + for (s32 i = 0; i= Width && (u32)modes[i].height >= Height) + bestMode = i; + else if (bestMode!=-1 && + (u32)modes[i].width >= Width && + (u32)modes[i].height >= Height && + modes[i].width <= modes[bestMode].width && + modes[i].height <= modes[bestMode].height) + bestMode = i; + } + if (bestMode != -1) + { + os::Printer::log("Starting randr fullscreen mode...", ELL_INFORMATION); + os::Printer::log("width: ", core::stringc(modes[bestMode].width).c_str(), ELL_INFORMATION); + os::Printer::log("height: ", core::stringc(modes[bestMode].height).c_str(), ELL_INFORMATION); + + XRRSetScreenConfig(XDisplay,config,DefaultRootWindow(XDisplay),bestMode,OldRandrRotation,CurrentTime); + UseXRandR=true; + } + XRRFreeScreenConfigInfo(config); + } + else + #endif + { + os::Printer::log("VidMode or RandR extension must be installed to allow Irrlicht " + "to switch to fullscreen mode. Running in windowed mode instead.", ELL_WARNING); + CreationParams.Fullscreen = false; + } + return CreationParams.Fullscreen; +} + + +#if defined(_IRR_COMPILE_WITH_X11_) +void IrrPrintXGrabError(int grabResult, const c8 * grabCommand ) +{ + if ( grabResult == GrabSuccess ) + { +// os::Printer::log(grabCommand, ": GrabSuccess", ELL_INFORMATION); + return; + } + + switch ( grabResult ) + { + case AlreadyGrabbed: + os::Printer::log(grabCommand, ": AlreadyGrabbed", ELL_WARNING); + break; + case GrabNotViewable: + os::Printer::log(grabCommand, ": GrabNotViewable", ELL_WARNING); + break; + case GrabFrozen: + os::Printer::log(grabCommand, ": GrabFrozen", ELL_WARNING); + break; + case GrabInvalidTime: + os::Printer::log(grabCommand, ": GrabInvalidTime", ELL_WARNING); + break; + default: + os::Printer::log(grabCommand, ": grab failed with unknown problem", ELL_WARNING); + break; + } +} +#endif + + +bool CIrrDeviceLinux::createWindow() +{ +#ifdef _IRR_COMPILE_WITH_X11_ +#ifdef _DEBUG + os::Printer::log("Creating X window...", ELL_INFORMATION); + XSetErrorHandler(IrrPrintXError); +#endif + + XDisplay = XOpenDisplay(0); + if (!XDisplay) + { + os::Printer::log("Error: Need running XServer to start Irrlicht Engine.", ELL_ERROR); + if (XDisplayName(0)[0]) + os::Printer::log("Could not open display", XDisplayName(0), ELL_ERROR); + else + os::Printer::log("Could not open display, set DISPLAY variable", ELL_ERROR); + return false; + } + + Screennr = DefaultScreen(XDisplay); + + switchToFullscreen(); + +#if defined(_IRR_COMPILE_WITH_OPENGL_) + // don't use the XVisual with OpenGL, because it ignores all requested + // properties of the CreationParams + if (CreationParams.DriverType == video::EDT_OPENGL) + { + video::SExposedVideoData data; + data.OpenGLLinux.X11Display = XDisplay; + ContextManager = new video::CGLXManager(CreationParams, data, Screennr); + VisualInfo = ((video::CGLXManager*)ContextManager)->getVisual(); + } +#endif + + if (!VisualInfo) + { + // create visual with standard X methods + os::Printer::log("Using plain X visual"); + XVisualInfo visTempl; //Template to hold requested values + int visNumber; // Return value of available visuals + + visTempl.screen = Screennr; + // ARGB visuals should be avoided for usual applications + visTempl.depth = CreationParams.WithAlphaChannel?32:24; + while ((!VisualInfo) && (visTempl.depth>=16)) + { + VisualInfo = XGetVisualInfo(XDisplay, VisualScreenMask|VisualDepthMask, + &visTempl, &visNumber); + visTempl.depth -= 8; + } + } + + if (!VisualInfo) + { + os::Printer::log("Fatal error, could not get visual.", ELL_ERROR); + XCloseDisplay(XDisplay); + XDisplay=0; + return false; + } +#ifdef _DEBUG + else + os::Printer::log("Visual chosen: ", core::stringc(static_cast(VisualInfo->visualid)).c_str(), ELL_DEBUG); +#endif + + // create color map + Colormap colormap; + colormap = XCreateColormap(XDisplay, + RootWindow(XDisplay, VisualInfo->screen), + VisualInfo->visual, AllocNone); + + WndAttributes.colormap = colormap; + WndAttributes.border_pixel = 0; + WndAttributes.event_mask = StructureNotifyMask | FocusChangeMask | ExposureMask; + if (!CreationParams.IgnoreInput) + WndAttributes.event_mask |= PointerMotionMask | + ButtonPressMask | KeyPressMask | + ButtonReleaseMask | KeyReleaseMask; + + if (!CreationParams.WindowId) + { + int x = 0; + int y = 0; + + if (!CreationParams.Fullscreen) + { + if (CreationParams.WindowPosition.X > 0) + x = CreationParams.WindowPosition.X; + if (CreationParams.WindowPosition.Y > 0) + y = CreationParams.WindowPosition.Y; + } + + // create new Window + // Remove window manager decoration in fullscreen + WndAttributes.override_redirect = CreationParams.Fullscreen; + XWindow = XCreateWindow(XDisplay, + RootWindow(XDisplay, VisualInfo->screen), + x, y, Width, Height, 0, VisualInfo->depth, + InputOutput, VisualInfo->visual, + CWBorderPixel | CWColormap | CWEventMask | CWOverrideRedirect, + &WndAttributes); + + XMapRaised(XDisplay, XWindow); + CreationParams.WindowId = (void*)XWindow; + Atom wmDelete; + wmDelete = XInternAtom(XDisplay, wmDeleteWindow, True); + XSetWMProtocols(XDisplay, XWindow, &wmDelete, 1); + if (CreationParams.Fullscreen) + { + XSetInputFocus(XDisplay, XWindow, RevertToParent, CurrentTime); + int grabKb = XGrabKeyboard(XDisplay, XWindow, True, GrabModeAsync, + GrabModeAsync, CurrentTime); + IrrPrintXGrabError(grabKb, "XGrabKeyboard"); + int grabPointer = XGrabPointer(XDisplay, XWindow, True, ButtonPressMask, + GrabModeAsync, GrabModeAsync, XWindow, None, CurrentTime); + IrrPrintXGrabError(grabPointer, "XGrabPointer"); + XWarpPointer(XDisplay, None, XWindow, 0, 0, 0, 0, 0, 0); + } + else if (CreationParams.WindowPosition.X >= 0 || CreationParams.WindowPosition.Y >= 0) // default is -1, -1 + { + // Window managers are free to ignore positions above, so give it another shot + XMoveWindow(XDisplay,XWindow,x,y); + } + } + else + { + // attach external window + XWindow = (Window)CreationParams.WindowId; + if (!CreationParams.IgnoreInput) + { + XCreateWindow(XDisplay, + XWindow, + 0, 0, Width, Height, 0, VisualInfo->depth, + InputOutput, VisualInfo->visual, + CWBorderPixel | CWColormap | CWEventMask, + &WndAttributes); + } + XWindowAttributes wa; + XGetWindowAttributes(XDisplay, XWindow, &wa); + CreationParams.WindowSize.Width = wa.width; + CreationParams.WindowSize.Height = wa.height; + CreationParams.Fullscreen = false; + ExternalWindow = true; + } + + WindowMinimized=false; + // Currently broken in X, see Bug ID 2795321 + // XkbSetDetectableAutoRepeat(XDisplay, True, &AutorepeatSupport); + + Window tmp; + u32 borderWidth; + int x,y; + unsigned int bits; + + XGetGeometry(XDisplay, XWindow, &tmp, &x, &y, &Width, &Height, &borderWidth, &bits); + CreationParams.Bits = bits; + CreationParams.WindowSize.Width = Width; + CreationParams.WindowSize.Height = Height; + + StdHints = XAllocSizeHints(); + long num; + XGetWMNormalHints(XDisplay, XWindow, StdHints, &num); + + // create an XImage for the software renderer + //(thx to Nadav for some clues on how to do that!) + + if (CreationParams.DriverType == video::EDT_SOFTWARE || CreationParams.DriverType == video::EDT_BURNINGSVIDEO) + { + SoftwareImage = XCreateImage(XDisplay, + VisualInfo->visual, VisualInfo->depth, + ZPixmap, 0, 0, Width, Height, + BitmapPad(XDisplay), 0); + + // use malloc because X will free it later on + if (SoftwareImage) + SoftwareImage->data = (char*) malloc(SoftwareImage->bytes_per_line * SoftwareImage->height * sizeof(char)); + } + + initXAtoms(); + + // check netwm support + Atom WMCheck = XInternAtom(XDisplay, "_NET_SUPPORTING_WM_CHECK", true); + if (WMCheck != None) + HasNetWM = true; + +#endif // #ifdef _IRR_COMPILE_WITH_X11_ + return true; +} + + +//! create the driver +void CIrrDeviceLinux::createDriver() +{ + switch(CreationParams.DriverType) + { +#ifdef _IRR_COMPILE_WITH_X11_ + case video::EDT_SOFTWARE: +#ifdef _IRR_COMPILE_WITH_SOFTWARE_ + VideoDriver = video::createSoftwareDriver(CreationParams.WindowSize, CreationParams.Fullscreen, FileSystem, this); +#else + os::Printer::log("No Software driver support compiled in.", ELL_ERROR); +#endif + break; + case video::EDT_BURNINGSVIDEO: +#ifdef _IRR_COMPILE_WITH_BURNINGSVIDEO_ + VideoDriver = video::createBurningVideoDriver(CreationParams, FileSystem, this); +#else + os::Printer::log("Burning's video driver was not compiled in.", ELL_ERROR); +#endif + break; + case video::EDT_OPENGL: +#ifdef _IRR_COMPILE_WITH_OPENGL_ + { + video::SExposedVideoData data; + data.OpenGLLinux.X11Window = XWindow; + data.OpenGLLinux.X11Display = XDisplay; + + ContextManager->initialize(CreationParams, data); + + VideoDriver = video::createOpenGLDriver(CreationParams, FileSystem, ContextManager); + } +#else + os::Printer::log("No OpenGL support compiled in.", ELL_ERROR); +#endif + break; + case video::DEPRECATED_EDT_DIRECT3D8_NO_LONGER_EXISTS: + case video::EDT_DIRECT3D9: + os::Printer::log("This driver is not available in Linux. Try OpenGL or Software renderer.", + ELL_ERROR); + break; + case video::EDT_NULL: + VideoDriver = video::createNullDriver(FileSystem, CreationParams.WindowSize); + break; + default: + os::Printer::log("Unable to create video driver of unknown type.", ELL_ERROR); + break; +#else + case video::EDT_NULL: + VideoDriver = video::createNullDriver(FileSystem, CreationParams.WindowSize); + break; + default: + os::Printer::log("No X11 support compiled in. Only Null driver available.", ELL_ERROR); + break; +#endif + } +} + +#ifdef _IRR_COMPILE_WITH_X11_ +bool CIrrDeviceLinux::createInputContext() +{ + // One one side it would be nicer to let users do that - on the other hand + // not setting the environment locale will not work when using i18n X11 functions. + // So users would have to call it always or their input is broken badly. + // We can restore immediately - so won't mess with anything in users apps. + core::stringc oldLocale(setlocale(LC_CTYPE, NULL)); + setlocale(LC_CTYPE, ""); // use environment locale + + if ( !XSupportsLocale() ) + { + os::Printer::log("Locale not supported. Falling back to non-i18n input.", ELL_WARNING); + setlocale(LC_CTYPE, oldLocale.c_str()); + return false; + } + + XInputMethod = XOpenIM(XDisplay, NULL, NULL, NULL); + if ( !XInputMethod ) + { + setlocale(LC_CTYPE, oldLocale.c_str()); + os::Printer::log("XOpenIM failed to create an input method. Falling back to non-i18n input.", ELL_WARNING); + return false; + } + + XIMStyles *im_supported_styles; + XGetIMValues(XInputMethod, XNQueryInputStyle, &im_supported_styles, (char*)NULL); + XIMStyle bestStyle = 0; + // TODO: If we want to support languages like chinese or japanese as well we probably have to work with callbacks here. + XIMStyle supportedStyle = XIMPreeditNone | XIMStatusNone; + for(int i=0; i < im_supported_styles->count_styles; ++i) + { + XIMStyle style = im_supported_styles->supported_styles[i]; + if ((style & supportedStyle) == style) /* if we can handle it */ + { + bestStyle = style; + break; + } + } + XFree(im_supported_styles); + + if ( !bestStyle ) + { + XDestroyIC(XInputContext); + XInputContext = 0; + + os::Printer::log("XInputMethod has no input style we can use. Falling back to non-i18n input.", ELL_WARNING); + setlocale(LC_CTYPE, oldLocale.c_str()); + return false; + } + + XInputContext = XCreateIC(XInputMethod, + XNInputStyle, bestStyle, + XNClientWindow, XWindow, + (char*)NULL); + if (!XInputContext ) + { + os::Printer::log("XInputContext failed to create an input context. Falling back to non-i18n input.", ELL_WARNING); + setlocale(LC_CTYPE, oldLocale.c_str()); + return false; + } + XSetICFocus(XInputContext); + setlocale(LC_CTYPE, oldLocale.c_str()); + return true; +} + +void CIrrDeviceLinux::destroyInputContext() +{ + if ( XInputContext ) + { + XUnsetICFocus(XInputContext); + XDestroyIC(XInputContext); + XInputContext = 0; + } + if ( XInputMethod ) + { + XCloseIM(XInputMethod); + XInputMethod = 0; + } +} + +EKEY_CODE CIrrDeviceLinux::getKeyCode(XEvent &event) +{ + EKEY_CODE keyCode = (EKEY_CODE)0; + + SKeyMap mp; + mp.X11Key = XkbKeycodeToKeysym(XDisplay, event.xkey.keycode, 0, 0); + // mp.X11Key = XKeycodeToKeysym(XDisplay, event.xkey.keycode, 0); // deprecated, if we still find platforms which need that we have to use some define + const s32 idx = KeyMap.binary_search(mp); + if (idx != -1) + { + keyCode = (EKEY_CODE)KeyMap[idx].Win32Key; + } + if (keyCode == 0) + { + // Any value is better than none, that allows at least using the keys. + // Worst case is that some keys will be identical, still better than _all_ + // unknown keys being identical. + if ( !mp.X11Key ) + { + keyCode = (EKEY_CODE)event.xkey.keycode; + os::Printer::log("No such X11Key, using event keycode", core::stringc(event.xkey.keycode).c_str(), ELL_INFORMATION); + } + else if (idx == -1) + { + keyCode = (EKEY_CODE)mp.X11Key; + os::Printer::log("EKEY_CODE not found, using orig. X11 keycode", core::stringc(mp.X11Key).c_str(), ELL_INFORMATION); + } + else + { + keyCode = (EKEY_CODE)mp.X11Key; + os::Printer::log("EKEY_CODE is 0, using orig. X11 keycode", core::stringc(mp.X11Key).c_str(), ELL_INFORMATION); + } + } + return keyCode; +} +#endif + +//! runs the device. Returns false if device wants to be deleted +bool CIrrDeviceLinux::run() +{ + os::Timer::tick(); + +#ifdef _IRR_COMPILE_WITH_X11_ + + if ( CursorControl ) + static_cast(CursorControl)->update(); + + if ((CreationParams.DriverType != video::EDT_NULL) && XDisplay) + { + SEvent irrevent; + irrevent.MouseInput.ButtonStates = 0xffffffff; + + while (XPending(XDisplay) > 0 && !Close) + { + XEvent event; + XNextEvent(XDisplay, &event); + + switch (event.type) + { + case ConfigureNotify: + // check for changed window size + if ((event.xconfigure.width != (int) Width) || + (event.xconfigure.height != (int) Height)) + { + Width = event.xconfigure.width; + Height = event.xconfigure.height; + + // resize image data + if (SoftwareImage) + { + XDestroyImage(SoftwareImage); + + SoftwareImage = XCreateImage(XDisplay, + VisualInfo->visual, VisualInfo->depth, + ZPixmap, 0, 0, Width, Height, + BitmapPad(XDisplay), 0); + + // use malloc because X will free it later on + if (SoftwareImage) + SoftwareImage->data = (char*) malloc(SoftwareImage->bytes_per_line * SoftwareImage->height * sizeof(char)); + } + + if (VideoDriver) + VideoDriver->OnResize(core::dimension2d(Width, Height)); + } + break; + + case MapNotify: + WindowMinimized=false; + break; + + case UnmapNotify: + WindowMinimized=true; + break; + + case FocusIn: + WindowHasFocus=true; + break; + + case FocusOut: + WindowHasFocus=false; + break; + + case MotionNotify: + irrevent.EventType = irr::EET_MOUSE_INPUT_EVENT; + irrevent.MouseInput.Event = irr::EMIE_MOUSE_MOVED; + irrevent.MouseInput.X = event.xbutton.x; + irrevent.MouseInput.Y = event.xbutton.y; + irrevent.MouseInput.Control = (event.xkey.state & ControlMask) != 0; + irrevent.MouseInput.Shift = (event.xkey.state & ShiftMask) != 0; + + // mouse button states + irrevent.MouseInput.ButtonStates = (event.xbutton.state & Button1Mask) ? irr::EMBSM_LEFT : 0; + irrevent.MouseInput.ButtonStates |= (event.xbutton.state & Button3Mask) ? irr::EMBSM_RIGHT : 0; + irrevent.MouseInput.ButtonStates |= (event.xbutton.state & Button2Mask) ? irr::EMBSM_MIDDLE : 0; + + postEventFromUser(irrevent); + break; + + case ButtonPress: + case ButtonRelease: + + irrevent.EventType = irr::EET_MOUSE_INPUT_EVENT; + irrevent.MouseInput.X = event.xbutton.x; + irrevent.MouseInput.Y = event.xbutton.y; + irrevent.MouseInput.Control = (event.xkey.state & ControlMask) != 0; + irrevent.MouseInput.Shift = (event.xkey.state & ShiftMask) != 0; + + // mouse button states + // This sets the state which the buttons had _prior_ to the event. + // So unlike on Windows the button which just got changed has still the old state here. + // We handle that below by flipping the corresponding bit later. + irrevent.MouseInput.ButtonStates = (event.xbutton.state & Button1Mask) ? irr::EMBSM_LEFT : 0; + irrevent.MouseInput.ButtonStates |= (event.xbutton.state & Button3Mask) ? irr::EMBSM_RIGHT : 0; + irrevent.MouseInput.ButtonStates |= (event.xbutton.state & Button2Mask) ? irr::EMBSM_MIDDLE : 0; + + irrevent.MouseInput.Event = irr::EMIE_COUNT; + + switch(event.xbutton.button) + { + case Button1: + irrevent.MouseInput.Event = + (event.type == ButtonPress) ? irr::EMIE_LMOUSE_PRESSED_DOWN : irr::EMIE_LMOUSE_LEFT_UP; + irrevent.MouseInput.ButtonStates ^= irr::EMBSM_LEFT; + break; + + case Button3: + irrevent.MouseInput.Event = + (event.type == ButtonPress) ? irr::EMIE_RMOUSE_PRESSED_DOWN : irr::EMIE_RMOUSE_LEFT_UP; + irrevent.MouseInput.ButtonStates ^= irr::EMBSM_RIGHT; + break; + + case Button2: + irrevent.MouseInput.Event = + (event.type == ButtonPress) ? irr::EMIE_MMOUSE_PRESSED_DOWN : irr::EMIE_MMOUSE_LEFT_UP; + irrevent.MouseInput.ButtonStates ^= irr::EMBSM_MIDDLE; + break; + + case Button4: + if (event.type == ButtonPress) + { + irrevent.MouseInput.Event = EMIE_MOUSE_WHEEL; + irrevent.MouseInput.Wheel = 1.0f; + } + break; + + case Button5: + if (event.type == ButtonPress) + { + irrevent.MouseInput.Event = EMIE_MOUSE_WHEEL; + irrevent.MouseInput.Wheel = -1.0f; + } + break; + } + + if (irrevent.MouseInput.Event != irr::EMIE_COUNT) + { + postEventFromUser(irrevent); + + if ( irrevent.MouseInput.Event >= EMIE_LMOUSE_PRESSED_DOWN && irrevent.MouseInput.Event <= EMIE_MMOUSE_PRESSED_DOWN ) + { + u32 clicks = checkSuccessiveClicks(irrevent.MouseInput.X, irrevent.MouseInput.Y, irrevent.MouseInput.Event); + if ( clicks == 2 ) + { + irrevent.MouseInput.Event = (EMOUSE_INPUT_EVENT)(EMIE_LMOUSE_DOUBLE_CLICK + irrevent.MouseInput.Event-EMIE_LMOUSE_PRESSED_DOWN); + postEventFromUser(irrevent); + } + else if ( clicks == 3 ) + { + irrevent.MouseInput.Event = (EMOUSE_INPUT_EVENT)(EMIE_LMOUSE_TRIPLE_CLICK + irrevent.MouseInput.Event-EMIE_LMOUSE_PRESSED_DOWN); + postEventFromUser(irrevent); + } + } + } + break; + + case MappingNotify: + XRefreshKeyboardMapping (&event.xmapping) ; + break; + + case KeyRelease: + if (0 == AutorepeatSupport && (XPending( XDisplay ) > 0) ) + { + // check for Autorepeat manually + // We'll do the same as Windows does: Only send KeyPressed + // So every KeyRelease is a real release + XEvent next_event; + XPeekEvent (event.xkey.display, &next_event); + if ((next_event.type == KeyPress) && + (next_event.xkey.keycode == event.xkey.keycode) && + (next_event.xkey.time - event.xkey.time) < 2) // usually same time, but on some systems a difference of 1 is possible + { + // Ignore the key release event + break; + } + } + + irrevent.EventType = irr::EET_KEY_INPUT_EVENT; + irrevent.KeyInput.PressedDown = false; + irrevent.KeyInput.Char = 0; // on release that's undefined + irrevent.KeyInput.Control = (event.xkey.state & ControlMask) != 0; + irrevent.KeyInput.Shift = (event.xkey.state & ShiftMask) != 0; + irrevent.KeyInput.Key = getKeyCode(event); + + postEventFromUser(irrevent); + break; + + case KeyPress: + { + SKeyMap mp; + if ( XInputContext ) + { + wchar_t buf[8]={0}; + Status status; + int strLen = XwcLookupString(XInputContext, &event.xkey, buf, sizeof(buf), &mp.X11Key, &status); + if ( status == XBufferOverflow ) + { + os::Printer::log("XwcLookupString needs a larger buffer", ELL_INFORMATION); + } + if ( strLen > 0 && (status == XLookupChars || status == XLookupBoth) ) + { + if ( strLen > 1 ) + os::Printer::log("Additional returned characters dropped", ELL_INFORMATION); + irrevent.KeyInput.Char = buf[0]; + } + else + { +#if 0 // Most of those are fine - but useful to have the info when debugging Irrlicht itself. + if ( status == XLookupNone ) + os::Printer::log("XLookupNone", ELL_INFORMATION); + else if ( status == XLookupKeySym ) + // Getting this also when user did not set setlocale(LC_ALL, ""); and using an unknown locale + // XSupportsLocale doesn't seeem to catch that unfortunately - any other ideas to catch it are welcome. + os::Printer::log("XLookupKeySym", ELL_INFORMATION); + else if ( status == XBufferOverflow ) + os::Printer::log("XBufferOverflow", ELL_INFORMATION); + else if ( strLen == 0 ) + os::Printer::log("no string", ELL_INFORMATION); +#endif + irrevent.KeyInput.Char = 0; + } + } + else // Old version without InputContext. Does not support i18n, but good to have as fallback. + { + union + { + char buf[8]; + wchar_t wbuf[2]; + } tmp = {{0}}; + XLookupString(&event.xkey, tmp.buf, sizeof(tmp.buf), &mp.X11Key, NULL); + irrevent.KeyInput.Char = tmp.wbuf[0]; + } + + irrevent.EventType = irr::EET_KEY_INPUT_EVENT; + irrevent.KeyInput.PressedDown = true; + irrevent.KeyInput.Control = (event.xkey.state & ControlMask) != 0; + irrevent.KeyInput.Shift = (event.xkey.state & ShiftMask) != 0; + irrevent.KeyInput.Key = getKeyCode(event); + + postEventFromUser(irrevent); + } + break; + + case ClientMessage: + { + char *atom = XGetAtomName(XDisplay, event.xclient.message_type); + if (*atom == *wmDeleteWindow) + { + os::Printer::log("Quit message received.", ELL_INFORMATION); + Close = true; + } + else + { + // we assume it's a user message + irrevent.EventType = irr::EET_USER_EVENT; + irrevent.UserEvent.UserData1 = static_cast(event.xclient.data.l[0]); + irrevent.UserEvent.UserData2 = static_cast(event.xclient.data.l[1]); + postEventFromUser(irrevent); + } + XFree(atom); + } + break; + + case SelectionRequest: + { + XEvent respond; + XSelectionRequestEvent *req = &(event.xselectionrequest); + if ( req->target == XA_STRING) + { + XChangeProperty (XDisplay, + req->requestor, + req->property, req->target, + 8, // format + PropModeReplace, + (unsigned char*) Clipboard.c_str(), + Clipboard.size()); + respond.xselection.property = req->property; + } + else if ( req->target == X_ATOM_TARGETS ) + { + long data[2]; + + data[0] = X_ATOM_TEXT; + data[1] = XA_STRING; + + XChangeProperty (XDisplay, req->requestor, + req->property, req->target, + 8, PropModeReplace, + (unsigned char *) &data, + sizeof (data)); + respond.xselection.property = req->property; + } + else + { + respond.xselection.property= None; + } + respond.xselection.type= SelectionNotify; + respond.xselection.display= req->display; + respond.xselection.requestor= req->requestor; + respond.xselection.selection=req->selection; + respond.xselection.target= req->target; + respond.xselection.time = req->time; + XSendEvent (XDisplay, req->requestor,0,0,&respond); + XFlush (XDisplay); + } + break; + + default: + break; + } // end switch + + } // end while + } +#endif //_IRR_COMPILE_WITH_X11_ + + if (!Close) + pollJoysticks(); + + return !Close; +} + + +//! Pause the current process for the minimum time allowed only to allow other processes to execute +void CIrrDeviceLinux::yield() +{ + struct timespec ts = {0,1}; + nanosleep(&ts, NULL); +} + + +//! Pause execution and let other processes to run for a specified amount of time. +void CIrrDeviceLinux::sleep(u32 timeMs, bool pauseTimer=false) +{ + const bool wasStopped = Timer ? Timer->isStopped() : true; + + struct timespec ts; + ts.tv_sec = (time_t) (timeMs / 1000); + ts.tv_nsec = (long) (timeMs % 1000) * 1000000; + + if (pauseTimer && !wasStopped) + Timer->stop(); + + nanosleep(&ts, NULL); + + if (pauseTimer && !wasStopped) + Timer->start(); +} + + +//! sets the caption of the window +void CIrrDeviceLinux::setWindowCaption(const wchar_t* text) +{ +#ifdef _IRR_COMPILE_WITH_X11_ + if (CreationParams.DriverType == video::EDT_NULL) + return; + + XTextProperty txt; + if (Success==XwcTextListToTextProperty(XDisplay, const_cast(&text), + 1, XStdICCTextStyle, &txt)) + { + XSetWMName(XDisplay, XWindow, &txt); + XSetWMIconName(XDisplay, XWindow, &txt); + XFree(txt.value); + } +#endif +} + + +//! Sets the application class +void CIrrDeviceLinux::setApplicationClass(const char* className) +{ +#ifdef _IRR_COMPILE_WITH_X11_ + if (CreationParams.DriverType == video::EDT_NULL) + return; + + XClassHint* hint = XAllocClassHint(); + hint->res_name = const_cast(className); + hint->res_class = const_cast(className); + XSetClassHint(XDisplay, XWindow, hint); + XFree(hint); +#endif +} + + +//! presents a surface in the client area +bool CIrrDeviceLinux::present(video::IImage* image, void* windowId, core::rect* srcRect) +{ +#ifdef _IRR_COMPILE_WITH_X11_ + // this is only necessary for software drivers. + if (!SoftwareImage) + return true; + + // thx to Nadav, who send me some clues of how to display the image + // to the X Server. + + const u32 destwidth = SoftwareImage->width; + const u32 minWidth = core::min_(image->getDimension().Width, destwidth); + const u32 destPitch = SoftwareImage->bytes_per_line; + + video::ECOLOR_FORMAT destColor; + switch (SoftwareImage->bits_per_pixel) + { + case 16: + if (SoftwareImage->depth==16) + destColor = video::ECF_R5G6B5; + else + destColor = video::ECF_A1R5G5B5; + break; + case 24: destColor = video::ECF_R8G8B8; break; + case 32: destColor = video::ECF_A8R8G8B8; break; + default: + os::Printer::log("Unsupported screen depth."); + return false; + } + + u8* srcdata = reinterpret_cast(image->getData()); + u8* destData = reinterpret_cast(SoftwareImage->data); + + const u32 destheight = SoftwareImage->height; + const u32 srcheight = core::min_(image->getDimension().Height, destheight); + const u32 srcPitch = image->getPitch(); + for (u32 y=0; y!=srcheight; ++y) + { + video::CColorConverter::convert_viaFormat(srcdata,image->getColorFormat(), minWidth, destData, destColor); + srcdata+=srcPitch; + destData+=destPitch; + } + + GC gc = DefaultGC(XDisplay, DefaultScreen(XDisplay)); + Window myWindow=XWindow; + if (windowId) + myWindow = reinterpret_cast(windowId); + XPutImage(XDisplay, myWindow, gc, SoftwareImage, 0, 0, 0, 0, destwidth, destheight); +#endif + return true; +} + + +//! notifies the device that it should close itself +void CIrrDeviceLinux::closeDevice() +{ + Close = true; +} + + +//! returns if window is active. if not, nothing need to be drawn +bool CIrrDeviceLinux::isWindowActive() const +{ + return (WindowHasFocus && !WindowMinimized); +} + + +//! returns if window has focus. +bool CIrrDeviceLinux::isWindowFocused() const +{ + return WindowHasFocus; +} + + +//! returns if window is minimized. +bool CIrrDeviceLinux::isWindowMinimized() const +{ + return WindowMinimized; +} + + +//! returns color format of the window. +video::ECOLOR_FORMAT CIrrDeviceLinux::getColorFormat() const +{ +#ifdef _IRR_COMPILE_WITH_X11_ + if (VisualInfo && (VisualInfo->depth != 16)) + return video::ECF_R8G8B8; + else +#endif + return video::ECF_R5G6B5; +} + + +//! Sets if the window should be resizable in windowed mode. +void CIrrDeviceLinux::setResizable(bool resize) +{ +#ifdef _IRR_COMPILE_WITH_X11_ + if (CreationParams.DriverType == video::EDT_NULL || CreationParams.Fullscreen ) + return; + + if ( !resize ) + { + // Must be heap memory because data size depends on X Server + XSizeHints *hints = XAllocSizeHints(); + hints->flags=PSize|PMinSize|PMaxSize; + hints->min_width=hints->max_width=hints->base_width=Width; + hints->min_height=hints->max_height=hints->base_height=Height; + XSetWMNormalHints(XDisplay, XWindow, hints); + XFree(hints); + } + else + { + XSetWMNormalHints(XDisplay, XWindow, StdHints); + } + XFlush(XDisplay); +#endif // #ifdef _IRR_COMPILE_WITH_X11_ +} + +//! Resize the render window. +void CIrrDeviceLinux::setWindowSize(const irr::core::dimension2d& size) +{ +#ifdef _IRR_COMPILE_WITH_X11_ + if (CreationParams.DriverType == video::EDT_NULL || CreationParams.Fullscreen ) + return; + + XWindowChanges values; + values.width = size.Width; + values.height = size.Height; + XConfigureWindow(XDisplay, XWindow, CWWidth | CWHeight, &values); + XFlush(XDisplay); +#endif // #ifdef _IRR_COMPILE_WITH_X11_ +} + +//! Return pointer to a list with all video modes supported by the gfx adapter. +video::IVideoModeList* CIrrDeviceLinux::getVideoModeList() +{ +#ifdef _IRR_COMPILE_WITH_X11_ + if (!VideoModeList->getVideoModeCount()) + { + bool temporaryDisplay = false; + + if (!XDisplay) + { + XDisplay = XOpenDisplay(0); + temporaryDisplay=true; + } + if (XDisplay) + { + #if defined(_IRR_LINUX_X11_VIDMODE_) || defined(_IRR_LINUX_X11_RANDR_) + s32 eventbase, errorbase; + s32 defaultDepth=DefaultDepth(XDisplay,Screennr); + #endif + + #ifdef _IRR_LINUX_X11_VIDMODE_ + if (XF86VidModeQueryExtension(XDisplay, &eventbase, &errorbase)) + { + // enumerate video modes + int modeCount; + XF86VidModeModeInfo** modes; + + XF86VidModeGetAllModeLines(XDisplay, Screennr, &modeCount, &modes); + + // save current video mode + OldVideoMode = *modes[0]; + + // find fitting mode + + VideoModeList->setDesktop(defaultDepth, core::dimension2d( + modes[0]->hdisplay, modes[0]->vdisplay)); + for (int i = 0; iaddMode(core::dimension2d( + modes[i]->hdisplay, modes[i]->vdisplay), defaultDepth); + } + XFree(modes); + } + else + #endif + #ifdef _IRR_LINUX_X11_RANDR_ + if (XRRQueryExtension(XDisplay, &eventbase, &errorbase)) + { + int modeCount; + XRRScreenConfiguration *config=XRRGetScreenInfo(XDisplay,DefaultRootWindow(XDisplay)); + OldRandrMode=XRRConfigCurrentConfiguration(config,&OldRandrRotation); + XRRScreenSize *modes=XRRConfigSizes(config,&modeCount); + VideoModeList->setDesktop(defaultDepth, core::dimension2d( + modes[OldRandrMode].width, modes[OldRandrMode].height)); + for (int i = 0; iaddMode(core::dimension2d( + modes[i].width, modes[i].height), defaultDepth); + } + XRRFreeScreenConfigInfo(config); + } + else + #endif + { + os::Printer::log("VidMode or RandR X11 extension requireed for VideoModeList." , ELL_WARNING); + } + } + if (XDisplay && temporaryDisplay) + { + XCloseDisplay(XDisplay); + XDisplay=0; + } + } +#endif + + return VideoModeList; +} + + +//! Minimize window +void CIrrDeviceLinux::minimizeWindow() +{ +#ifdef _IRR_COMPILE_WITH_X11_ + XIconifyWindow(XDisplay, XWindow, Screennr); +#endif +} + + +//! Maximize window +void CIrrDeviceLinux::maximizeWindow() +{ +#ifdef _IRR_COMPILE_WITH_X11_ + // Maximize is not implemented in bare X, it's a WM construct. + if (HasNetWM) + { + XEvent ev = {0}; + + ev.type = ClientMessage; + ev.xclient.window = XWindow; + ev.xclient.message_type = X_ATOM_NETWM_STATE; + ev.xclient.format = 32; + ev.xclient.data.l[0] = 1; // _NET_WM_STATE_ADD + ev.xclient.data.l[1] = X_ATOM_NETWM_MAXIMIZE_VERT; + ev.xclient.data.l[2] = X_ATOM_NETWM_MAXIMIZE_HORZ; + + XSendEvent(XDisplay, DefaultRootWindow(XDisplay), false, + SubstructureNotifyMask|SubstructureRedirectMask, &ev); + } + + XMapWindow(XDisplay, XWindow); +#endif +} + + +//! Restore original window size +void CIrrDeviceLinux::restoreWindow() +{ +#ifdef _IRR_COMPILE_WITH_X11_ + // Maximize is not implemented in bare X, it's a WM construct. + if (HasNetWM) + { + XEvent ev = {0}; + + ev.type = ClientMessage; + ev.xclient.window = XWindow; + ev.xclient.message_type = X_ATOM_NETWM_STATE; + ev.xclient.format = 32; + ev.xclient.data.l[0] = 0; // _NET_WM_STATE_REMOVE + ev.xclient.data.l[1] = X_ATOM_NETWM_MAXIMIZE_VERT; + ev.xclient.data.l[2] = X_ATOM_NETWM_MAXIMIZE_HORZ; + + XSendEvent(XDisplay, DefaultRootWindow(XDisplay), false, + SubstructureNotifyMask|SubstructureRedirectMask, &ev); + } + + XMapWindow(XDisplay, XWindow); +#endif +} + +core::position2di CIrrDeviceLinux::getWindowPosition() +{ + int wx = 0, wy = 0; +#ifdef _IRR_COMPILE_WITH_X11_ + Window child; + XTranslateCoordinates(XDisplay, XWindow, DefaultRootWindow(XDisplay), 0, 0, &wx, &wy, &child); +#endif + return core::position2di(wx, wy); +} + +void CIrrDeviceLinux::createKeyMap() +{ + // I don't know if this is the best method to create + // the lookuptable, but I'll leave it like that until + // I find a better version. + // Search for missing numbers in keysymdef.h + +#ifdef _IRR_COMPILE_WITH_X11_ + KeyMap.reallocate(190); + KeyMap.push_back(SKeyMap(XK_BackSpace, KEY_BACK)); + KeyMap.push_back(SKeyMap(XK_Tab, KEY_TAB)); + KeyMap.push_back(SKeyMap(XK_ISO_Left_Tab, KEY_TAB)); + KeyMap.push_back(SKeyMap(XK_Linefeed, 0)); // ??? + KeyMap.push_back(SKeyMap(XK_Clear, KEY_CLEAR)); + KeyMap.push_back(SKeyMap(XK_Return, KEY_RETURN)); + KeyMap.push_back(SKeyMap(XK_Pause, KEY_PAUSE)); + KeyMap.push_back(SKeyMap(XK_Scroll_Lock, KEY_SCROLL)); + KeyMap.push_back(SKeyMap(XK_Sys_Req, 0)); // ??? + KeyMap.push_back(SKeyMap(XK_Escape, KEY_ESCAPE)); + KeyMap.push_back(SKeyMap(XK_Insert, KEY_INSERT)); + KeyMap.push_back(SKeyMap(XK_Delete, KEY_DELETE)); + KeyMap.push_back(SKeyMap(XK_Home, KEY_HOME)); + KeyMap.push_back(SKeyMap(XK_Left, KEY_LEFT)); + KeyMap.push_back(SKeyMap(XK_Up, KEY_UP)); + KeyMap.push_back(SKeyMap(XK_Right, KEY_RIGHT)); + KeyMap.push_back(SKeyMap(XK_Down, KEY_DOWN)); + KeyMap.push_back(SKeyMap(XK_Prior, KEY_PRIOR)); + KeyMap.push_back(SKeyMap(XK_Page_Up, KEY_PRIOR)); + KeyMap.push_back(SKeyMap(XK_Next, KEY_NEXT)); + KeyMap.push_back(SKeyMap(XK_Page_Down, KEY_NEXT)); + KeyMap.push_back(SKeyMap(XK_End, KEY_END)); + KeyMap.push_back(SKeyMap(XK_Begin, KEY_HOME)); + KeyMap.push_back(SKeyMap(XK_Num_Lock, KEY_NUMLOCK)); + KeyMap.push_back(SKeyMap(XK_KP_Space, KEY_SPACE)); + KeyMap.push_back(SKeyMap(XK_KP_Tab, KEY_TAB)); + KeyMap.push_back(SKeyMap(XK_KP_Enter, KEY_RETURN)); + KeyMap.push_back(SKeyMap(XK_KP_F1, KEY_F1)); + KeyMap.push_back(SKeyMap(XK_KP_F2, KEY_F2)); + KeyMap.push_back(SKeyMap(XK_KP_F3, KEY_F3)); + KeyMap.push_back(SKeyMap(XK_KP_F4, KEY_F4)); + KeyMap.push_back(SKeyMap(XK_KP_Home, KEY_HOME)); + KeyMap.push_back(SKeyMap(XK_KP_Left, KEY_LEFT)); + KeyMap.push_back(SKeyMap(XK_KP_Up, KEY_UP)); + KeyMap.push_back(SKeyMap(XK_KP_Right, KEY_RIGHT)); + KeyMap.push_back(SKeyMap(XK_KP_Down, KEY_DOWN)); + KeyMap.push_back(SKeyMap(XK_Print, KEY_PRINT)); + KeyMap.push_back(SKeyMap(XK_KP_Prior, KEY_PRIOR)); + KeyMap.push_back(SKeyMap(XK_KP_Page_Up, KEY_PRIOR)); + KeyMap.push_back(SKeyMap(XK_KP_Next, KEY_NEXT)); + KeyMap.push_back(SKeyMap(XK_KP_Page_Down, KEY_NEXT)); + KeyMap.push_back(SKeyMap(XK_KP_End, KEY_END)); + KeyMap.push_back(SKeyMap(XK_KP_Begin, KEY_HOME)); + KeyMap.push_back(SKeyMap(XK_KP_Insert, KEY_INSERT)); + KeyMap.push_back(SKeyMap(XK_KP_Delete, KEY_DELETE)); + KeyMap.push_back(SKeyMap(XK_KP_Equal, 0)); // ??? + KeyMap.push_back(SKeyMap(XK_KP_Multiply, KEY_MULTIPLY)); + KeyMap.push_back(SKeyMap(XK_KP_Add, KEY_ADD)); + KeyMap.push_back(SKeyMap(XK_KP_Separator, KEY_SEPARATOR)); + KeyMap.push_back(SKeyMap(XK_KP_Subtract, KEY_SUBTRACT)); + KeyMap.push_back(SKeyMap(XK_KP_Decimal, KEY_DECIMAL)); + KeyMap.push_back(SKeyMap(XK_KP_Divide, KEY_DIVIDE)); + KeyMap.push_back(SKeyMap(XK_KP_0, KEY_NUMPAD0)); + KeyMap.push_back(SKeyMap(XK_KP_1, KEY_NUMPAD1)); + KeyMap.push_back(SKeyMap(XK_KP_2, KEY_NUMPAD2)); + KeyMap.push_back(SKeyMap(XK_KP_3, KEY_NUMPAD3)); + KeyMap.push_back(SKeyMap(XK_KP_4, KEY_NUMPAD4)); + KeyMap.push_back(SKeyMap(XK_KP_5, KEY_NUMPAD5)); + KeyMap.push_back(SKeyMap(XK_KP_6, KEY_NUMPAD6)); + KeyMap.push_back(SKeyMap(XK_KP_7, KEY_NUMPAD7)); + KeyMap.push_back(SKeyMap(XK_KP_8, KEY_NUMPAD8)); + KeyMap.push_back(SKeyMap(XK_KP_9, KEY_NUMPAD9)); + KeyMap.push_back(SKeyMap(XK_F1, KEY_F1)); + KeyMap.push_back(SKeyMap(XK_F2, KEY_F2)); + KeyMap.push_back(SKeyMap(XK_F3, KEY_F3)); + KeyMap.push_back(SKeyMap(XK_F4, KEY_F4)); + KeyMap.push_back(SKeyMap(XK_F5, KEY_F5)); + KeyMap.push_back(SKeyMap(XK_F6, KEY_F6)); + KeyMap.push_back(SKeyMap(XK_F7, KEY_F7)); + KeyMap.push_back(SKeyMap(XK_F8, KEY_F8)); + KeyMap.push_back(SKeyMap(XK_F9, KEY_F9)); + KeyMap.push_back(SKeyMap(XK_F10, KEY_F10)); + KeyMap.push_back(SKeyMap(XK_F11, KEY_F11)); + KeyMap.push_back(SKeyMap(XK_F12, KEY_F12)); + KeyMap.push_back(SKeyMap(XK_Shift_L, KEY_LSHIFT)); + KeyMap.push_back(SKeyMap(XK_Shift_R, KEY_RSHIFT)); + KeyMap.push_back(SKeyMap(XK_Control_L, KEY_LCONTROL)); + KeyMap.push_back(SKeyMap(XK_Control_R, KEY_RCONTROL)); + KeyMap.push_back(SKeyMap(XK_Caps_Lock, KEY_CAPITAL)); + KeyMap.push_back(SKeyMap(XK_Shift_Lock, KEY_CAPITAL)); + KeyMap.push_back(SKeyMap(XK_Meta_L, KEY_LWIN)); + KeyMap.push_back(SKeyMap(XK_Meta_R, KEY_RWIN)); + KeyMap.push_back(SKeyMap(XK_Alt_L, KEY_LMENU)); + KeyMap.push_back(SKeyMap(XK_Alt_R, KEY_RMENU)); + KeyMap.push_back(SKeyMap(XK_ISO_Level3_Shift, KEY_RMENU)); + KeyMap.push_back(SKeyMap(XK_Menu, KEY_MENU)); + KeyMap.push_back(SKeyMap(XK_space, KEY_SPACE)); + KeyMap.push_back(SKeyMap(XK_exclam, 0)); //? + KeyMap.push_back(SKeyMap(XK_quotedbl, 0)); //? + KeyMap.push_back(SKeyMap(XK_section, 0)); //? + KeyMap.push_back(SKeyMap(XK_numbersign, KEY_OEM_2)); + KeyMap.push_back(SKeyMap(XK_dollar, 0)); //? + KeyMap.push_back(SKeyMap(XK_percent, 0)); //? + KeyMap.push_back(SKeyMap(XK_ampersand, 0)); //? + KeyMap.push_back(SKeyMap(XK_apostrophe, KEY_OEM_7)); + KeyMap.push_back(SKeyMap(XK_parenleft, 0)); //? + KeyMap.push_back(SKeyMap(XK_parenright, 0)); //? + KeyMap.push_back(SKeyMap(XK_asterisk, 0)); //? + KeyMap.push_back(SKeyMap(XK_plus, KEY_PLUS)); //? + KeyMap.push_back(SKeyMap(XK_comma, KEY_COMMA)); //? + KeyMap.push_back(SKeyMap(XK_minus, KEY_MINUS)); //? + KeyMap.push_back(SKeyMap(XK_period, KEY_PERIOD)); //? + KeyMap.push_back(SKeyMap(XK_slash, KEY_OEM_2)); //? + KeyMap.push_back(SKeyMap(XK_0, KEY_KEY_0)); + KeyMap.push_back(SKeyMap(XK_1, KEY_KEY_1)); + KeyMap.push_back(SKeyMap(XK_2, KEY_KEY_2)); + KeyMap.push_back(SKeyMap(XK_3, KEY_KEY_3)); + KeyMap.push_back(SKeyMap(XK_4, KEY_KEY_4)); + KeyMap.push_back(SKeyMap(XK_5, KEY_KEY_5)); + KeyMap.push_back(SKeyMap(XK_6, KEY_KEY_6)); + KeyMap.push_back(SKeyMap(XK_7, KEY_KEY_7)); + KeyMap.push_back(SKeyMap(XK_8, KEY_KEY_8)); + KeyMap.push_back(SKeyMap(XK_9, KEY_KEY_9)); + KeyMap.push_back(SKeyMap(XK_colon, 0)); //? + KeyMap.push_back(SKeyMap(XK_semicolon, KEY_OEM_1)); + KeyMap.push_back(SKeyMap(XK_less, KEY_OEM_102)); + KeyMap.push_back(SKeyMap(XK_equal, KEY_PLUS)); + KeyMap.push_back(SKeyMap(XK_greater, 0)); //? + KeyMap.push_back(SKeyMap(XK_question, 0)); //? + KeyMap.push_back(SKeyMap(XK_at, KEY_KEY_2)); //? + KeyMap.push_back(SKeyMap(XK_mu, 0)); //? + KeyMap.push_back(SKeyMap(XK_EuroSign, 0)); //? + KeyMap.push_back(SKeyMap(XK_A, KEY_KEY_A)); + KeyMap.push_back(SKeyMap(XK_B, KEY_KEY_B)); + KeyMap.push_back(SKeyMap(XK_C, KEY_KEY_C)); + KeyMap.push_back(SKeyMap(XK_D, KEY_KEY_D)); + KeyMap.push_back(SKeyMap(XK_E, KEY_KEY_E)); + KeyMap.push_back(SKeyMap(XK_F, KEY_KEY_F)); + KeyMap.push_back(SKeyMap(XK_G, KEY_KEY_G)); + KeyMap.push_back(SKeyMap(XK_H, KEY_KEY_H)); + KeyMap.push_back(SKeyMap(XK_I, KEY_KEY_I)); + KeyMap.push_back(SKeyMap(XK_J, KEY_KEY_J)); + KeyMap.push_back(SKeyMap(XK_K, KEY_KEY_K)); + KeyMap.push_back(SKeyMap(XK_L, KEY_KEY_L)); + KeyMap.push_back(SKeyMap(XK_M, KEY_KEY_M)); + KeyMap.push_back(SKeyMap(XK_N, KEY_KEY_N)); + KeyMap.push_back(SKeyMap(XK_O, KEY_KEY_O)); + KeyMap.push_back(SKeyMap(XK_P, KEY_KEY_P)); + KeyMap.push_back(SKeyMap(XK_Q, KEY_KEY_Q)); + KeyMap.push_back(SKeyMap(XK_R, KEY_KEY_R)); + KeyMap.push_back(SKeyMap(XK_S, KEY_KEY_S)); + KeyMap.push_back(SKeyMap(XK_T, KEY_KEY_T)); + KeyMap.push_back(SKeyMap(XK_U, KEY_KEY_U)); + KeyMap.push_back(SKeyMap(XK_V, KEY_KEY_V)); + KeyMap.push_back(SKeyMap(XK_W, KEY_KEY_W)); + KeyMap.push_back(SKeyMap(XK_X, KEY_KEY_X)); + KeyMap.push_back(SKeyMap(XK_Y, KEY_KEY_Y)); + KeyMap.push_back(SKeyMap(XK_Z, KEY_KEY_Z)); + KeyMap.push_back(SKeyMap(XK_bracketleft, KEY_OEM_4)); + KeyMap.push_back(SKeyMap(XK_backslash, KEY_OEM_5)); + KeyMap.push_back(SKeyMap(XK_bracketright, KEY_OEM_6)); + KeyMap.push_back(SKeyMap(XK_asciicircum, KEY_OEM_5)); + KeyMap.push_back(SKeyMap(XK_dead_circumflex, KEY_OEM_5)); + KeyMap.push_back(SKeyMap(XK_degree, 0)); //? + KeyMap.push_back(SKeyMap(XK_underscore, KEY_MINUS)); //? + KeyMap.push_back(SKeyMap(XK_grave, KEY_OEM_3)); + KeyMap.push_back(SKeyMap(XK_dead_grave, KEY_OEM_3)); + KeyMap.push_back(SKeyMap(XK_acute, KEY_OEM_6)); + KeyMap.push_back(SKeyMap(XK_dead_acute, KEY_OEM_6)); + KeyMap.push_back(SKeyMap(XK_a, KEY_KEY_A)); + KeyMap.push_back(SKeyMap(XK_b, KEY_KEY_B)); + KeyMap.push_back(SKeyMap(XK_c, KEY_KEY_C)); + KeyMap.push_back(SKeyMap(XK_d, KEY_KEY_D)); + KeyMap.push_back(SKeyMap(XK_e, KEY_KEY_E)); + KeyMap.push_back(SKeyMap(XK_f, KEY_KEY_F)); + KeyMap.push_back(SKeyMap(XK_g, KEY_KEY_G)); + KeyMap.push_back(SKeyMap(XK_h, KEY_KEY_H)); + KeyMap.push_back(SKeyMap(XK_i, KEY_KEY_I)); + KeyMap.push_back(SKeyMap(XK_j, KEY_KEY_J)); + KeyMap.push_back(SKeyMap(XK_k, KEY_KEY_K)); + KeyMap.push_back(SKeyMap(XK_l, KEY_KEY_L)); + KeyMap.push_back(SKeyMap(XK_m, KEY_KEY_M)); + KeyMap.push_back(SKeyMap(XK_n, KEY_KEY_N)); + KeyMap.push_back(SKeyMap(XK_o, KEY_KEY_O)); + KeyMap.push_back(SKeyMap(XK_p, KEY_KEY_P)); + KeyMap.push_back(SKeyMap(XK_q, KEY_KEY_Q)); + KeyMap.push_back(SKeyMap(XK_r, KEY_KEY_R)); + KeyMap.push_back(SKeyMap(XK_s, KEY_KEY_S)); + KeyMap.push_back(SKeyMap(XK_t, KEY_KEY_T)); + KeyMap.push_back(SKeyMap(XK_u, KEY_KEY_U)); + KeyMap.push_back(SKeyMap(XK_v, KEY_KEY_V)); + KeyMap.push_back(SKeyMap(XK_w, KEY_KEY_W)); + KeyMap.push_back(SKeyMap(XK_x, KEY_KEY_X)); + KeyMap.push_back(SKeyMap(XK_y, KEY_KEY_Y)); + KeyMap.push_back(SKeyMap(XK_z, KEY_KEY_Z)); + KeyMap.push_back(SKeyMap(XK_ssharp, KEY_OEM_4)); + KeyMap.push_back(SKeyMap(XK_adiaeresis, KEY_OEM_7)); + KeyMap.push_back(SKeyMap(XK_odiaeresis, KEY_OEM_3)); + KeyMap.push_back(SKeyMap(XK_udiaeresis, KEY_OEM_1)); + KeyMap.push_back(SKeyMap(XK_Super_L, KEY_LWIN)); + KeyMap.push_back(SKeyMap(XK_Super_R, KEY_RWIN)); + + KeyMap.sort(); +#endif +} + +bool CIrrDeviceLinux::activateJoysticks(core::array & joystickInfo) +{ +#if defined (_IRR_COMPILE_WITH_JOYSTICK_EVENTS_) + + joystickInfo.clear(); + + u32 joystick; + for (joystick = 0; joystick < 32; ++joystick) + { + // The joystick device could be here... + core::stringc devName = "/dev/js"; + devName += joystick; + + SJoystickInfo returnInfo; + JoystickInfo info; + + info.fd = open(devName.c_str(), O_RDONLY); + if (-1 == info.fd) + { + // ...but Ubuntu and possibly other distros + // create the devices in /dev/input + devName = "/dev/input/js"; + devName += joystick; + info.fd = open(devName.c_str(), O_RDONLY); + if (-1 == info.fd) + { + // and BSD here + devName = "/dev/joy"; + devName += joystick; + info.fd = open(devName.c_str(), O_RDONLY); + } + } + + if (-1 == info.fd) + continue; + +#ifdef __FreeBSD__ + info.axes=2; + info.buttons=2; +#else + ioctl( info.fd, JSIOCGAXES, &(info.axes) ); + ioctl( info.fd, JSIOCGBUTTONS, &(info.buttons) ); + fcntl( info.fd, F_SETFL, O_NONBLOCK ); +#endif + + (void)memset(&info.persistentData, 0, sizeof(info.persistentData)); + info.persistentData.EventType = irr::EET_JOYSTICK_INPUT_EVENT; + info.persistentData.JoystickEvent.Joystick = ActiveJoysticks.size(); + + // There's no obvious way to determine which (if any) axes represent a POV + // hat, so we'll just set it to "not used" and forget about it. + info.persistentData.JoystickEvent.POV = 65535; + + ActiveJoysticks.push_back(info); + + returnInfo.Joystick = joystick; + returnInfo.PovHat = SJoystickInfo::POV_HAT_UNKNOWN; + returnInfo.Axes = info.axes; + returnInfo.Buttons = info.buttons; + +#ifndef __FreeBSD__ + char name[80]; + ioctl( info.fd, JSIOCGNAME(80), name); + returnInfo.Name = name; +#endif + + joystickInfo.push_back(returnInfo); + } + + for (joystick = 0; joystick < joystickInfo.size(); ++joystick) + { + char logString[256]; + (void)sprintf(logString, "Found joystick %u, %u axes, %u buttons '%s'", + joystick, joystickInfo[joystick].Axes, + joystickInfo[joystick].Buttons, joystickInfo[joystick].Name.c_str()); + os::Printer::log(logString, ELL_INFORMATION); + } + + return true; +#else + return false; +#endif // _IRR_COMPILE_WITH_JOYSTICK_EVENTS_ +} + + +void CIrrDeviceLinux::pollJoysticks() +{ +#if defined (_IRR_COMPILE_WITH_JOYSTICK_EVENTS_) + if (0 == ActiveJoysticks.size()) + return; + + for (u32 j= 0; j< ActiveJoysticks.size(); ++j) + { + JoystickInfo & info = ActiveJoysticks[j]; + +#ifdef __FreeBSD__ + struct joystick js; + if (read(info.fd, &js, sizeof(js)) == sizeof(js)) + { + info.persistentData.JoystickEvent.ButtonStates = js.b1 | (js.b2 << 1); /* should be a two-bit field */ + info.persistentData.JoystickEvent.Axis[0] = js.x; /* X axis */ + info.persistentData.JoystickEvent.Axis[1] = js.y; /* Y axis */ + } +#else + struct js_event event; + while (sizeof(event) == read(info.fd, &event, sizeof(event))) + { + switch(event.type & ~JS_EVENT_INIT) + { + case JS_EVENT_BUTTON: + if (event.value) + info.persistentData.JoystickEvent.ButtonStates |= (1 << event.number); + else + info.persistentData.JoystickEvent.ButtonStates &= ~(1 << event.number); + break; + + case JS_EVENT_AXIS: + if (event.number < SEvent::SJoystickEvent::NUMBER_OF_AXES) + info.persistentData.JoystickEvent.Axis[event.number] = event.value; + break; + + default: + break; + } + } +#endif + + // Send an irrlicht joystick event once per ::run() even if no new data were received. + (void)postEventFromUser(info.persistentData); + } +#endif // _IRR_COMPILE_WITH_JOYSTICK_EVENTS_ +} + + +//! Set the current Gamma Value for the Display +bool CIrrDeviceLinux::setGammaRamp( f32 red, f32 green, f32 blue, f32 brightness, f32 contrast ) +{ + #if defined(_IRR_LINUX_X11_VIDMODE_) || defined(_IRR_LINUX_X11_RANDR_) + s32 eventbase, errorbase; + #ifdef _IRR_LINUX_X11_VIDMODE_ + if (XF86VidModeQueryExtension(XDisplay, &eventbase, &errorbase)) + { + XF86VidModeGamma gamma; + gamma.red=red; + gamma.green=green; + gamma.blue=blue; + XF86VidModeSetGamma(XDisplay, Screennr, &gamma); + return true; + } + #endif + #if defined(_IRR_LINUX_X11_VIDMODE_) && defined(_IRR_LINUX_X11_RANDR_) + else + #endif + #ifdef _IRR_LINUX_X11_RANDR_ + if (XRRQueryExtension(XDisplay, &eventbase, &errorbase)) + { + XRRQueryVersion(XDisplay, &eventbase, &errorbase); // major, minor + if (eventbase>=1 && errorbase>1) + { + #if (RANDR_MAJOR>1 || RANDR_MINOR>1) + XRRCrtcGamma *gamma = XRRGetCrtcGamma(XDisplay, Screennr); + if (gamma) + { + *gamma->red=(u16)red; + *gamma->green=(u16)green; + *gamma->blue=(u16)blue; + XRRSetCrtcGamma(XDisplay, Screennr, gamma); + XRRFreeGamma(gamma); + return true; + } + #endif + } + } + #endif + #endif + return false; +} + + +//! Get the current Gamma Value for the Display +bool CIrrDeviceLinux::getGammaRamp( f32 &red, f32 &green, f32 &blue, f32 &brightness, f32 &contrast ) +{ + brightness = 0.f; + contrast = 0.f; + #if defined(_IRR_LINUX_X11_VIDMODE_) || defined(_IRR_LINUX_X11_RANDR_) + s32 eventbase, errorbase; + #ifdef _IRR_LINUX_X11_VIDMODE_ + if (XF86VidModeQueryExtension(XDisplay, &eventbase, &errorbase)) + { + XF86VidModeGamma gamma; + XF86VidModeGetGamma(XDisplay, Screennr, &gamma); + red = gamma.red; + green = gamma.green; + blue = gamma.blue; + return true; + } + #endif + #if defined(_IRR_LINUX_X11_VIDMODE_) && defined(_IRR_LINUX_X11_RANDR_) + else + #endif + #ifdef _IRR_LINUX_X11_RANDR_ + if (XRRQueryExtension(XDisplay, &eventbase, &errorbase)) + { + XRRQueryVersion(XDisplay, &eventbase, &errorbase); // major, minor + if (eventbase>=1 && errorbase>1) + { + #if (RANDR_MAJOR>1 || RANDR_MINOR>1) + XRRCrtcGamma *gamma = XRRGetCrtcGamma(XDisplay, Screennr); + if (gamma) + { + red = *gamma->red; + green = *gamma->green; + blue= *gamma->blue; + XRRFreeGamma(gamma); + return true; + } + #endif + } + } + #endif + #endif + return false; +} + + +//! gets text from the clipboard +//! \return Returns 0 if no string is in there. +const c8* CIrrDeviceLinux::getTextFromClipboard() const +{ +#if defined(_IRR_COMPILE_WITH_X11_) + Window ownerWindow = XGetSelectionOwner (XDisplay, X_ATOM_CLIPBOARD); + if ( ownerWindow == XWindow ) + { + return Clipboard.c_str(); + } + Clipboard = ""; + if (ownerWindow != None ) + { + XConvertSelection (XDisplay, X_ATOM_CLIPBOARD, XA_STRING, XA_PRIMARY, ownerWindow, CurrentTime); + XFlush (XDisplay); + + // check for data + Atom type; + int format; + unsigned long numItems, bytesLeft, dummy; + unsigned char *data; + XGetWindowProperty (XDisplay, ownerWindow, + XA_PRIMARY, // property name + 0, // offset + 0, // length (we only check for data, so 0) + 0, // Delete 0==false + AnyPropertyType, // AnyPropertyType or property identifier + &type, // return type + &format, // return format + &numItems, // number items + &bytesLeft, // remaining bytes for partial reads + &data); // data + if ( bytesLeft > 0 ) + { + // there is some data to get + int result = XGetWindowProperty (XDisplay, ownerWindow, XA_PRIMARY, 0, + bytesLeft, 0, AnyPropertyType, &type, &format, + &numItems, &dummy, &data); + if (result == Success) + Clipboard = (irr::c8*)data; + XFree (data); + } + } + + return Clipboard.c_str(); + +#else + return 0; +#endif +} + +//! copies text to the clipboard +void CIrrDeviceLinux::copyToClipboard(const c8* text) const +{ +#if defined(_IRR_COMPILE_WITH_X11_) + // Actually there is no clipboard on X but applications just say they own the clipboard and return text when asked. + // Which btw. also means that on X you lose clipboard content when closing applications. + Clipboard = text; + XSetSelectionOwner (XDisplay, X_ATOM_CLIPBOARD, XWindow, CurrentTime); + XFlush (XDisplay); +#endif +} + +#ifdef _IRR_COMPILE_WITH_X11_ +// return true if the passed event has the type passed in parameter arg +Bool PredicateIsEventType(Display *display, XEvent *event, XPointer arg) +{ + if ( event && event->type == *(int*)arg ) + { +// os::Printer::log("remove event:", core::stringc((int)arg).c_str(), ELL_INFORMATION); + return True; + } + return False; +} +#endif //_IRR_COMPILE_WITH_X11_ + +//! Remove all messages pending in the system message loop +void CIrrDeviceLinux::clearSystemMessages() +{ +#ifdef _IRR_COMPILE_WITH_X11_ + if (CreationParams.DriverType != video::EDT_NULL) + { + XEvent event; + int usrArg = ButtonPress; + while ( XCheckIfEvent(XDisplay, &event, PredicateIsEventType, XPointer(&usrArg)) == True ) {} + usrArg = ButtonRelease; + while ( XCheckIfEvent(XDisplay, &event, PredicateIsEventType, XPointer(&usrArg)) == True ) {} + usrArg = MotionNotify; + while ( XCheckIfEvent(XDisplay, &event, PredicateIsEventType, XPointer(&usrArg)) == True ) {} + usrArg = KeyRelease; + while ( XCheckIfEvent(XDisplay, &event, PredicateIsEventType, XPointer(&usrArg)) == True ) {} + usrArg = KeyPress; + while ( XCheckIfEvent(XDisplay, &event, PredicateIsEventType, XPointer(&usrArg)) == True ) {} + } +#endif //_IRR_COMPILE_WITH_X11_ +} + +void CIrrDeviceLinux::initXAtoms() +{ +#ifdef _IRR_COMPILE_WITH_X11_ + X_ATOM_CLIPBOARD = XInternAtom(XDisplay, "CLIPBOARD", False); + X_ATOM_TARGETS = XInternAtom(XDisplay, "TARGETS", False); + X_ATOM_UTF8_STRING = XInternAtom (XDisplay, "UTF8_STRING", False); + X_ATOM_TEXT = XInternAtom (XDisplay, "TEXT", False); + X_ATOM_NETWM_MAXIMIZE_VERT = XInternAtom(XDisplay, "_NET_WM_STATE_MAXIMIZED_VERT", true); + X_ATOM_NETWM_MAXIMIZE_HORZ = XInternAtom(XDisplay, "_NET_WM_STATE_MAXIMIZED_HORZ", true); + X_ATOM_NETWM_STATE = XInternAtom(XDisplay, "_NET_WM_STATE", true); +#endif +} + + +#ifdef _IRR_COMPILE_WITH_X11_ + +Cursor CIrrDeviceLinux::TextureToMonochromeCursor(irr::video::ITexture * tex, const core::rect& sourceRect, const core::position2d &hotspot) +{ + XImage * sourceImage = XCreateImage(XDisplay, VisualInfo->visual, + 1, // depth, + ZPixmap, // XYBitmap (depth=1), ZPixmap(depth=x) + 0, 0, sourceRect.getWidth(), sourceRect.getHeight(), + 32, // bitmap_pad, + 0// bytes_per_line (0 means continuos in memory) + ); + sourceImage->data = new char[sourceImage->height * sourceImage->bytes_per_line]; + XImage * maskImage = XCreateImage(XDisplay, VisualInfo->visual, + 1, // depth, + ZPixmap, + 0, 0, sourceRect.getWidth(), sourceRect.getHeight(), + 32, // bitmap_pad, + 0 // bytes_per_line + ); + maskImage->data = new char[maskImage->height * maskImage->bytes_per_line]; + + // write texture into XImage + video::ECOLOR_FORMAT format = tex->getColorFormat(); + u32 bytesPerPixel = video::IImage::getBitsPerPixelFromFormat(format) / 8; + u32 bytesLeftGap = sourceRect.UpperLeftCorner.X * bytesPerPixel; + u32 bytesRightGap = tex->getPitch() - sourceRect.LowerRightCorner.X * bytesPerPixel; + const u8* data = (const u8*)tex->lock(video::ETLM_READ_ONLY, 0); + data += sourceRect.UpperLeftCorner.Y*tex->getPitch(); + for ( s32 y = 0; y < sourceRect.getHeight(); ++y ) + { + data += bytesLeftGap; + for ( s32 x = 0; x < sourceRect.getWidth(); ++x ) + { + video::SColor pixelCol; + pixelCol.setData((const void*)data, format); + data += bytesPerPixel; + + if ( pixelCol.getAlpha() == 0 ) // transparent + { + XPutPixel(maskImage, x, y, 0); + XPutPixel(sourceImage, x, y, 0); + } + else // color + { + if ( pixelCol.getAverage() >= 127 ) + XPutPixel(sourceImage, x, y, 1); + else + XPutPixel(sourceImage, x, y, 0); + XPutPixel(maskImage, x, y, 1); + } + } + data += bytesRightGap; + } + tex->unlock(); + + Pixmap sourcePixmap = XCreatePixmap(XDisplay, XWindow, sourceImage->width, sourceImage->height, sourceImage->depth); + Pixmap maskPixmap = XCreatePixmap(XDisplay, XWindow, maskImage->width, maskImage->height, maskImage->depth); + + XGCValues values; + values.foreground = 1; + values.background = 1; + GC gc = XCreateGC( XDisplay, sourcePixmap, GCForeground | GCBackground, &values ); + + XPutImage(XDisplay, sourcePixmap, gc, sourceImage, 0, 0, 0, 0, sourceImage->width, sourceImage->height); + XPutImage(XDisplay, maskPixmap, gc, maskImage, 0, 0, 0, 0, maskImage->width, maskImage->height); + + XFreeGC(XDisplay, gc); + XDestroyImage(sourceImage); + XDestroyImage(maskImage); + + Cursor cursorResult = 0; + XColor foreground, background; + foreground.red = 65535; + foreground.green = 65535; + foreground.blue = 65535; + foreground.flags = DoRed | DoGreen | DoBlue; + background.red = 0; + background.green = 0; + background.blue = 0; + background.flags = DoRed | DoGreen | DoBlue; + + cursorResult = XCreatePixmapCursor(XDisplay, sourcePixmap, maskPixmap, &foreground, &background, hotspot.X, hotspot.Y); + + XFreePixmap(XDisplay, sourcePixmap); + XFreePixmap(XDisplay, maskPixmap); + + return cursorResult; +} + +#ifdef _IRR_LINUX_XCURSOR_ +Cursor CIrrDeviceLinux::TextureToARGBCursor(irr::video::ITexture * tex, const core::rect& sourceRect, const core::position2d &hotspot) +{ + XcursorImage * image = XcursorImageCreate (sourceRect.getWidth(), sourceRect.getHeight()); + image->xhot = hotspot.X; + image->yhot = hotspot.Y; + + // write texture into XcursorImage + video::ECOLOR_FORMAT format = tex->getColorFormat(); + u32 bytesPerPixel = video::IImage::getBitsPerPixelFromFormat(format) / 8; + u32 bytesLeftGap = sourceRect.UpperLeftCorner.X * bytesPerPixel; + u32 bytesRightGap = tex->getPitch() - sourceRect.LowerRightCorner.X * bytesPerPixel; + XcursorPixel* target = image->pixels; + const u8* data = (const u8*)tex->lock(video::ETLM_READ_ONLY, 0); + data += sourceRect.UpperLeftCorner.Y*tex->getPitch(); + for ( s32 y = 0; y < sourceRect.getHeight(); ++y ) + { + data += bytesLeftGap; + for ( s32 x = 0; x < sourceRect.getWidth(); ++x ) + { + video::SColor pixelCol; + pixelCol.setData((const void*)data, format); + data += bytesPerPixel; + + *target = (XcursorPixel)pixelCol.color; + ++target; + } + data += bytesRightGap; + } + tex->unlock(); + + Cursor cursorResult=XcursorImageLoadCursor(XDisplay, image); + + XcursorImageDestroy(image); + + + return cursorResult; +} +#endif // #ifdef _IRR_LINUX_XCURSOR_ + +Cursor CIrrDeviceLinux::TextureToCursor(irr::video::ITexture * tex, const core::rect& sourceRect, const core::position2d &hotspot) +{ +#ifdef _IRR_LINUX_XCURSOR_ + return TextureToARGBCursor( tex, sourceRect, hotspot ); +#else + return TextureToMonochromeCursor( tex, sourceRect, hotspot ); +#endif +} +#endif // _IRR_COMPILE_WITH_X11_ + + +CIrrDeviceLinux::CCursorControl::CCursorControl(CIrrDeviceLinux* dev, bool null) + : Device(dev) +#ifdef _IRR_COMPILE_WITH_X11_ + , PlatformBehavior(gui::ECPB_NONE), LastQuery(0) +#endif + , IsVisible(true), Null(null), UseReferenceRect(false) + , ActiveIcon(gui::ECI_NORMAL), ActiveIconStartTime(0) +{ +#ifdef _IRR_COMPILE_WITH_X11_ + if (!Null) + { + XGCValues values; + unsigned long valuemask = 0; + + XColor fg, bg; + + // this code, for making the cursor invisible was sent in by + // Sirshane, thank your very much! + + + Pixmap invisBitmap = XCreatePixmap(Device->XDisplay, Device->XWindow, 32, 32, 1); + Pixmap maskBitmap = XCreatePixmap(Device->XDisplay, Device->XWindow, 32, 32, 1); + Colormap screen_colormap = DefaultColormap( Device->XDisplay, DefaultScreen( Device->XDisplay ) ); + XAllocNamedColor( Device->XDisplay, screen_colormap, "black", &fg, &fg ); + XAllocNamedColor( Device->XDisplay, screen_colormap, "white", &bg, &bg ); + + GC gc = XCreateGC( Device->XDisplay, invisBitmap, valuemask, &values ); + + XSetForeground( Device->XDisplay, gc, BlackPixel( Device->XDisplay, DefaultScreen( Device->XDisplay ) ) ); + XFillRectangle( Device->XDisplay, invisBitmap, gc, 0, 0, 32, 32 ); + XFillRectangle( Device->XDisplay, maskBitmap, gc, 0, 0, 32, 32 ); + + InvisCursor = XCreatePixmapCursor( Device->XDisplay, invisBitmap, maskBitmap, &fg, &bg, 1, 1 ); + XFreeGC(Device->XDisplay, gc); + XFreePixmap(Device->XDisplay, invisBitmap); + XFreePixmap(Device->XDisplay, maskBitmap); + + initCursors(); + } +#endif +} + +CIrrDeviceLinux::CCursorControl::~CCursorControl() +{ + // Do not clearCursors here as the display is already closed + // TODO (cutealien): droping cursorcontrol earlier might work, not sure about reason why that's done in stub currently. +} + +#ifdef _IRR_COMPILE_WITH_X11_ +void CIrrDeviceLinux::CCursorControl::clearCursors() +{ + if (!Null) + XFreeCursor(Device->XDisplay, InvisCursor); + for ( u32 i=0; i < Cursors.size(); ++i ) + { + for ( u32 f=0; f < Cursors[i].Frames.size(); ++f ) + { + XFreeCursor(Device->XDisplay, Cursors[i].Frames[f].IconHW); + } + } +} + +void CIrrDeviceLinux::CCursorControl::initCursors() +{ + Cursors.push_back( CursorX11(XCreateFontCursor(Device->XDisplay, XC_top_left_arrow)) ); // (or XC_arrow?) + Cursors.push_back( CursorX11(XCreateFontCursor(Device->XDisplay, XC_crosshair)) ); + Cursors.push_back( CursorX11(XCreateFontCursor(Device->XDisplay, XC_hand2)) ); // (or XC_hand1? ) + Cursors.push_back( CursorX11(XCreateFontCursor(Device->XDisplay, XC_question_arrow)) ); + Cursors.push_back( CursorX11(XCreateFontCursor(Device->XDisplay, XC_xterm)) ); + Cursors.push_back( CursorX11(XCreateFontCursor(Device->XDisplay, XC_X_cursor)) ); // (or XC_pirate?) + Cursors.push_back( CursorX11(XCreateFontCursor(Device->XDisplay, XC_watch)) ); // (or XC_clock?) + Cursors.push_back( CursorX11(XCreateFontCursor(Device->XDisplay, XC_fleur)) ); + Cursors.push_back( CursorX11(XCreateFontCursor(Device->XDisplay, XC_top_right_corner)) ); // NESW not available in X11 + Cursors.push_back( CursorX11(XCreateFontCursor(Device->XDisplay, XC_top_left_corner)) ); // NWSE not available in X11 + Cursors.push_back( CursorX11(XCreateFontCursor(Device->XDisplay, XC_sb_v_double_arrow)) ); + Cursors.push_back( CursorX11(XCreateFontCursor(Device->XDisplay, XC_sb_h_double_arrow)) ); + Cursors.push_back( CursorX11(XCreateFontCursor(Device->XDisplay, XC_sb_up_arrow)) ); // (or XC_center_ptr?) +} + +void CIrrDeviceLinux::CCursorControl::update() +{ + if ( (u32)ActiveIcon < Cursors.size() && !Cursors[ActiveIcon].Frames.empty() && Cursors[ActiveIcon].FrameTime ) + { + // update animated cursors. This could also be done by X11 in case someone wants to figure that out (this way was just easier to implement) + u32 now = Device->getTimer()->getRealTime(); + u32 frame = ((now - ActiveIconStartTime) / Cursors[ActiveIcon].FrameTime) % Cursors[ActiveIcon].Frames.size(); + XDefineCursor(Device->XDisplay, Device->XWindow, Cursors[ActiveIcon].Frames[frame].IconHW); + } +} +#endif + +//! Sets the active cursor icon +void CIrrDeviceLinux::CCursorControl::setActiveIcon(gui::ECURSOR_ICON iconId) +{ +#ifdef _IRR_COMPILE_WITH_X11_ + if ( iconId >= (s32)Cursors.size() ) + return; + + if ( Cursors[iconId].Frames.size() ) + XDefineCursor(Device->XDisplay, Device->XWindow, Cursors[iconId].Frames[0].IconHW); + + ActiveIconStartTime = Device->getTimer()->getRealTime(); + ActiveIcon = iconId; +#endif +} + + +//! Add a custom sprite as cursor icon. +gui::ECURSOR_ICON CIrrDeviceLinux::CCursorControl::addIcon(const gui::SCursorSprite& icon) +{ +#ifdef _IRR_COMPILE_WITH_X11_ + if ( icon.SpriteId >= 0 ) + { + CursorX11 cX11; + cX11.FrameTime = icon.SpriteBank->getSprites()[icon.SpriteId].frameTime; + for ( u32 i=0; i < icon.SpriteBank->getSprites()[icon.SpriteId].Frames.size(); ++i ) + { + irr::u32 texId = icon.SpriteBank->getSprites()[icon.SpriteId].Frames[i].textureNumber; + irr::u32 rectId = icon.SpriteBank->getSprites()[icon.SpriteId].Frames[i].rectNumber; + irr::core::rect rectIcon = icon.SpriteBank->getPositions()[rectId]; + Cursor cursor = Device->TextureToCursor(icon.SpriteBank->getTexture(texId), rectIcon, icon.HotSpot); + cX11.Frames.push_back( CursorFrameX11(cursor) ); + } + + Cursors.push_back( cX11 ); + + return (gui::ECURSOR_ICON)(Cursors.size() - 1); + } +#endif + return gui::ECI_NORMAL; +} + +//! replace the given cursor icon. +void CIrrDeviceLinux::CCursorControl::changeIcon(gui::ECURSOR_ICON iconId, const gui::SCursorSprite& icon) +{ +#ifdef _IRR_COMPILE_WITH_X11_ + if ( iconId >= (s32)Cursors.size() ) + return; + + for ( u32 i=0; i < Cursors[iconId].Frames.size(); ++i ) + XFreeCursor(Device->XDisplay, Cursors[iconId].Frames[i].IconHW); + + if ( icon.SpriteId >= 0 ) + { + CursorX11 cX11; + cX11.FrameTime = icon.SpriteBank->getSprites()[icon.SpriteId].frameTime; + for ( u32 i=0; i < icon.SpriteBank->getSprites()[icon.SpriteId].Frames.size(); ++i ) + { + irr::u32 texId = icon.SpriteBank->getSprites()[icon.SpriteId].Frames[i].textureNumber; + irr::u32 rectId = icon.SpriteBank->getSprites()[icon.SpriteId].Frames[i].rectNumber; + irr::core::rect rectIcon = icon.SpriteBank->getPositions()[rectId]; + Cursor cursor = Device->TextureToCursor(icon.SpriteBank->getTexture(texId), rectIcon, icon.HotSpot); + cX11.Frames.push_back( CursorFrameX11(cursor) ); + } + + Cursors[iconId] = cX11; + } +#endif +} + +irr::core::dimension2di CIrrDeviceLinux::CCursorControl::getSupportedIconSize() const +{ + // this returns the closest match that is smaller or same size, so we just pass a value which should be large enough for cursors + unsigned int width=0, height=0; +#ifdef _IRR_COMPILE_WITH_X11_ + XQueryBestCursor(Device->XDisplay, Device->XWindow, 64, 64, &width, &height); +#endif + return core::dimension2di(width, height); +} + +} // end namespace + +#endif // _IRR_COMPILE_WITH_X11_DEVICE_ + diff --git a/IrrExtensions/modded/source/CIrrDeviceLinux.h b/IrrExtensions/modded/source/CIrrDeviceLinux.h new file mode 100755 index 0000000..e74f9c0 --- /dev/null +++ b/IrrExtensions/modded/source/CIrrDeviceLinux.h @@ -0,0 +1,461 @@ +// Copyright (C) 2002-2012 Nikolaus Gebhardt +// This file is part of the "Irrlicht Engine". +// For conditions of distribution and use, see copyright notice in irrlicht.h + +#ifndef __C_IRR_DEVICE_LINUX_H_INCLUDED__ +#define __C_IRR_DEVICE_LINUX_H_INCLUDED__ + +#include "IrrCompileConfig.h" + +#ifdef _IRR_COMPILE_WITH_X11_DEVICE_ + +#include "CIrrDeviceStub.h" +#include "IrrlichtDevice.h" +#include "IImagePresenter.h" +#include "ICursorControl.h" +#include "os.h" + +#ifdef _IRR_COMPILE_WITH_X11_ + +#ifdef _IRR_COMPILE_WITH_OPENGL_ +#include +#define GLX_GLXEXT_LEGACY 1 +#include +#ifdef _IRR_OPENGL_USE_EXTPOINTER_ +#include "glxext.h" +#endif +#endif + +#include +#include +#include +#ifdef _IRR_LINUX_X11_VIDMODE_ +#include +#endif +#ifdef _IRR_LINUX_X11_RANDR_ +#include +#endif +#include + +#else +#define KeySym s32 +#endif + +namespace irr +{ + + class CIrrDeviceLinux : public CIrrDeviceStub, public video::IImagePresenter + { + public: + + //! constructor + CIrrDeviceLinux(const SIrrlichtCreationParameters& param); + + //! destructor + virtual ~CIrrDeviceLinux(); + + //! runs the device. Returns false if device wants to be deleted + virtual bool run() _IRR_OVERRIDE_; + + //! Cause the device to temporarily pause execution and let other processes to run + // This should bring down processor usage without major performance loss for Irrlicht + virtual void yield() _IRR_OVERRIDE_; + + //! Pause execution and let other processes to run for a specified amount of time. + virtual void sleep(u32 timeMs, bool pauseTimer) _IRR_OVERRIDE_; + + //! sets the caption of the window + virtual void setWindowCaption(const wchar_t* text) _IRR_OVERRIDE_; + + //! Sets the application class + virtual void setApplicationClass(const char* className) _IRR_OVERRIDE_; + + //! returns if window is active. if not, nothing need to be drawn + virtual bool isWindowActive() const _IRR_OVERRIDE_; + + //! returns if window has focus. + virtual bool isWindowFocused() const _IRR_OVERRIDE_; + + //! returns if window is minimized. + virtual bool isWindowMinimized() const _IRR_OVERRIDE_; + + //! returns color format of the window. + virtual video::ECOLOR_FORMAT getColorFormat() const _IRR_OVERRIDE_; + + //! presents a surface in the client area + virtual bool present(video::IImage* surface, void* windowId=0, core::rect* src=0 ) _IRR_OVERRIDE_; + + //! notifies the device that it should close itself + virtual void closeDevice() _IRR_OVERRIDE_; + + //! \return Returns a pointer to a list with all video modes + //! supported by the gfx adapter. + virtual video::IVideoModeList* getVideoModeList() _IRR_OVERRIDE_; + + //! Sets if the window should be resizable in windowed mode. + virtual void setResizable(bool resize=false) _IRR_OVERRIDE_; + + //! Resize the render window. + virtual void setWindowSize(const irr::core::dimension2d& size) _IRR_OVERRIDE_; + + //! Minimizes the window. + virtual void minimizeWindow() _IRR_OVERRIDE_; + + //! Maximizes the window. + virtual void maximizeWindow() _IRR_OVERRIDE_; + + //! Restores the window size. + virtual void restoreWindow() _IRR_OVERRIDE_; + + //! Get the position of this window on screen + virtual core::position2di getWindowPosition() _IRR_OVERRIDE_; + + //! Activate any joysticks, and generate events for them. + virtual bool activateJoysticks(core::array & joystickInfo) _IRR_OVERRIDE_; + + //! Set the current Gamma Value for the Display + virtual bool setGammaRamp( f32 red, f32 green, f32 blue, f32 brightness, f32 contrast ) _IRR_OVERRIDE_; + + //! Get the current Gamma Value for the Display + virtual bool getGammaRamp( f32 &red, f32 &green, f32 &blue, f32 &brightness, f32 &contrast ) _IRR_OVERRIDE_; + + //! gets text from the clipboard + //! \return Returns 0 if no string is in there. + virtual const c8* getTextFromClipboard() const; + + //! copies text to the clipboard + //! This sets the clipboard selection and _not_ the primary selection which you have on X on the middle mouse button. + virtual void copyToClipboard(const c8* text) const; + + //! Remove all messages pending in the system message loop + virtual void clearSystemMessages() _IRR_OVERRIDE_; + + //! Get the device type + virtual E_DEVICE_TYPE getType() const _IRR_OVERRIDE_ + { + return EIDT_X11; + } + +#ifdef _IRR_COMPILE_WITH_X11_ + // convert an Irrlicht texture to a X11 cursor + Cursor TextureToCursor(irr::video::ITexture * tex, const core::rect& sourceRect, const core::position2d &hotspot); + Cursor TextureToMonochromeCursor(irr::video::ITexture * tex, const core::rect& sourceRect, const core::position2d &hotspot); +#ifdef _IRR_LINUX_XCURSOR_ + Cursor TextureToARGBCursor(irr::video::ITexture * tex, const core::rect& sourceRect, const core::position2d &hotspot); +#endif +#endif + + private: + + //! create the driver + void createDriver(); + + bool createWindow(); + + void createKeyMap(); + + void pollJoysticks(); + + void initXAtoms(); + + bool switchToFullscreen(bool reset=false); + +#ifdef _IRR_COMPILE_WITH_X11_ + bool createInputContext(); + void destroyInputContext(); + EKEY_CODE getKeyCode(XEvent &event); +#endif + + //! Implementation of the linux cursor control + class CCursorControl : public gui::ICursorControl + { + public: + + CCursorControl(CIrrDeviceLinux* dev, bool null); + + ~CCursorControl(); + + //! Changes the visible state of the mouse cursor. + virtual void setVisible(bool visible) _IRR_OVERRIDE_ + { + if (visible==IsVisible) + return; + IsVisible = visible; +#ifdef _IRR_COMPILE_WITH_X11_ + if (!Null) + { + if ( !IsVisible ) + XDefineCursor( Device->XDisplay, Device->XWindow, InvisCursor ); + else + XUndefineCursor( Device->XDisplay, Device->XWindow ); + } +#endif + } + + //! Returns if the cursor is currently visible. + virtual bool isVisible() const _IRR_OVERRIDE_ + { + return IsVisible; + } + + //! Sets the new position of the cursor. + virtual void setPosition(const core::position2d &pos) _IRR_OVERRIDE_ + { + setPosition(pos.X, pos.Y); + } + + //! Sets the new position of the cursor. + virtual void setPosition(f32 x, f32 y) _IRR_OVERRIDE_ + { + setPosition((s32)(x*Device->Width), (s32)(y*Device->Height)); + } + + //! Sets the new position of the cursor. + virtual void setPosition(const core::position2d &pos) _IRR_OVERRIDE_ + { + setPosition(pos.X, pos.Y); + } + + //! Sets the new position of the cursor. + virtual void setPosition(s32 x, s32 y) _IRR_OVERRIDE_ + { +#ifdef _IRR_COMPILE_WITH_X11_ + + if (!Null) + { + if (UseReferenceRect) + { + XWarpPointer(Device->XDisplay, + None, + Device->XWindow, 0, 0, + Device->Width, + Device->Height, + ReferenceRect.UpperLeftCorner.X + x, + ReferenceRect.UpperLeftCorner.Y + y); + + } + else + { + XWarpPointer(Device->XDisplay, + None, + Device->XWindow, 0, 0, + Device->Width, + Device->Height, x, y); + } + XFlush(Device->XDisplay); + } +#endif + CursorPos.X = x; + CursorPos.Y = y; + } + + //! Returns the current position of the mouse cursor. + virtual const core::position2d& getPosition() _IRR_OVERRIDE_ + { + updateCursorPos(); + return CursorPos; + } + + //! Returns the current position of the mouse cursor. + virtual core::position2d getRelativePosition() _IRR_OVERRIDE_ + { + updateCursorPos(); + + if (!UseReferenceRect) + { + return core::position2d(CursorPos.X / (f32)Device->Width, + CursorPos.Y / (f32)Device->Height); + } + + return core::position2d(CursorPos.X / (f32)ReferenceRect.getWidth(), + CursorPos.Y / (f32)ReferenceRect.getHeight()); + } + + virtual void setReferenceRect(core::rect* rect=0) _IRR_OVERRIDE_ + { + if (rect) + { + ReferenceRect = *rect; + UseReferenceRect = true; + + // prevent division through zero and uneven sizes + + if (!ReferenceRect.getHeight() || ReferenceRect.getHeight()%2) + ReferenceRect.LowerRightCorner.Y += 1; + + if (!ReferenceRect.getWidth() || ReferenceRect.getWidth()%2) + ReferenceRect.LowerRightCorner.X += 1; + } + else + UseReferenceRect = false; + } + + //! Sets the active cursor icon + virtual void setActiveIcon(gui::ECURSOR_ICON iconId) _IRR_OVERRIDE_; + + //! Gets the currently active icon + virtual gui::ECURSOR_ICON getActiveIcon() const _IRR_OVERRIDE_ + { + return ActiveIcon; + } + + //! Add a custom sprite as cursor icon. + virtual gui::ECURSOR_ICON addIcon(const gui::SCursorSprite& icon) _IRR_OVERRIDE_; + + //! replace the given cursor icon. + virtual void changeIcon(gui::ECURSOR_ICON iconId, const gui::SCursorSprite& icon) _IRR_OVERRIDE_; + + //! Return a system-specific size which is supported for cursors. Larger icons will fail, smaller icons might work. + virtual core::dimension2di getSupportedIconSize() const _IRR_OVERRIDE_; + +#ifdef _IRR_COMPILE_WITH_X11_ + //! Set platform specific behavior flags. + virtual void setPlatformBehavior(gui::ECURSOR_PLATFORM_BEHAVIOR behavior) _IRR_OVERRIDE_ {PlatformBehavior = behavior; } + + //! Return platform specific behavior. + virtual gui::ECURSOR_PLATFORM_BEHAVIOR getPlatformBehavior() const _IRR_OVERRIDE_ { return PlatformBehavior; } + + void update(); + void clearCursors(); +#endif + private: + + void updateCursorPos() + { +#ifdef _IRR_COMPILE_WITH_X11_ + if (Null) + return; + + if ( PlatformBehavior&gui::ECPB_X11_CACHE_UPDATES && !os::Timer::isStopped() ) + { + u32 now = os::Timer::getTime(); + if (now <= LastQuery) + return; + LastQuery = now; + } + + Window tmp; + int itmp1, itmp2; + unsigned int maskreturn; + XQueryPointer(Device->XDisplay, Device->XWindow, + &tmp, &tmp, + &itmp1, &itmp2, + &CursorPos.X, &CursorPos.Y, &maskreturn); +#endif + } + + CIrrDeviceLinux* Device; + core::position2d CursorPos; + core::rect ReferenceRect; +#ifdef _IRR_COMPILE_WITH_X11_ + gui::ECURSOR_PLATFORM_BEHAVIOR PlatformBehavior; + u32 LastQuery; + Cursor InvisCursor; + + struct CursorFrameX11 + { + CursorFrameX11() : IconHW(0) {} + CursorFrameX11(Cursor icon) : IconHW(icon) {} + + Cursor IconHW; // hardware cursor + }; + + struct CursorX11 + { + CursorX11() {} + explicit CursorX11(Cursor iconHw, u32 frameTime=0) : FrameTime(frameTime) + { + Frames.push_back( CursorFrameX11(iconHw) ); + } + core::array Frames; + u32 FrameTime; + }; + + core::array Cursors; + + void initCursors(); +#endif + bool IsVisible; + bool Null; + bool UseReferenceRect; + gui::ECURSOR_ICON ActiveIcon; + u32 ActiveIconStartTime; + }; + + friend class CCursorControl; + +#ifdef _IRR_COMPILE_WITH_X11_ + friend class COpenGLDriver; + + Display *XDisplay; + XVisualInfo* VisualInfo; + int Screennr; + Window XWindow; + XSetWindowAttributes WndAttributes; + XSizeHints* StdHints; + XImage* SoftwareImage; + XIM XInputMethod; + XIC XInputContext; + bool HasNetWM; + mutable core::stringc Clipboard; + #ifdef _IRR_LINUX_X11_VIDMODE_ + XF86VidModeModeInfo OldVideoMode; + #endif + #ifdef _IRR_LINUX_X11_RANDR_ + SizeID OldRandrMode; + Rotation OldRandrRotation; + #endif + #ifdef _IRR_COMPILE_WITH_OPENGL_ + GLXWindow GlxWin; + GLXContext Context; + #endif +#endif + u32 Width, Height; + bool WindowHasFocus; + bool WindowMinimized; + bool UseXVidMode; + bool UseXRandR; + bool UseGLXWindow; + bool ExternalWindow; + int AutorepeatSupport; + + struct SKeyMap + { + SKeyMap() {} + SKeyMap(s32 x11, s32 win32) + : X11Key(x11), Win32Key(win32) + { + } + + KeySym X11Key; + s32 Win32Key; + + bool operator<(const SKeyMap& o) const + { + return X11Key KeyMap; + +#if defined(_IRR_COMPILE_WITH_JOYSTICK_EVENTS_) + struct JoystickInfo + { + int fd; + int axes; + int buttons; + + SEvent persistentData; + + JoystickInfo() : fd(-1), axes(0), buttons(0) { } + }; + core::array ActiveJoysticks; +#endif + }; + + +} // end namespace irr + +#endif // _IRR_COMPILE_WITH_X11_DEVICE_ +#endif // __C_IRR_DEVICE_LINUX_H_INCLUDED__ + diff --git a/IrrExtensions/modded/source/my first Burnings draw2DRectangle.txt b/IrrExtensions/modded/source/my first Burnings draw2DRectangle.txt new file mode 100755 index 0000000..1cca029 --- /dev/null +++ b/IrrExtensions/modded/source/my first Burnings draw2DRectangle.txt @@ -0,0 +1,107 @@ +/* +This is my OLD version of the draw2DRectangle function for Burnings Renderer. +Use the new one instead. This is here for reference. +*/ + + +//!Draws an 2d rectangle with a gradient. +void CBurningVideoDriver::draw2DRectangle(const core::rect& position, + SColor colorLeftUp, SColor colorRightUp, SColor colorLeftDown, SColor colorRightDown, + const core::rect* clip) +{ +#if 1 // USE_BURNINGS_PRETTY_DRAW2DRECT + if ( !RenderTargetSurface ) + { + setRenderTarget(BackBuffer); + } + + // Done in setRenderTarget + //RenderTargetSize = RenderTargetSurface->getDimension(); + + core::rect realClip( position ); + if ( clip ) + realClip.clipAgainst(*clip); + + if ( !realClip.isValid() ) + return; + + // Rectangle bounds given by % + u32 x0 = (u32) ( realClip.UpperLeftCorner.X < 0 ? 0 : realClip.UpperLeftCorner.X ); + u32 y0 = (u32) ( realClip.UpperLeftCorner.Y < 0 ? 0 : realClip.UpperLeftCorner.Y ); + u32 x100 = (u32)realClip.LowerRightCorner.X < RenderTargetSize.Width ? (u32)realClip.LowerRightCorner.X : RenderTargetSize.Width-1; + u32 y100 = (u32)realClip.LowerRightCorner.Y < RenderTargetSize.Height ? (u32)realClip.LowerRightCorner.Y : RenderTargetSize.Height-1; + + u32 x, y, w, h, rx, ry, rw, rh; + f32 ryh, rxw; + w = x100 - x0; // render area width + h = y100 - y0; // render area height + //s32 flatAlpha, flatRed, flatGreen, flatBlue; + + /* The rectangle colors should be drawn as though the rectangle were the same size, + even if part of it is clipped. */ + rw = position.getWidth(); // real width + rh = position.getHeight(); // real height + + u32 colorLeftUpColor = colorLeftUp.color; + u32 colorLeftDownColor = colorLeftDown.color; + u32 colorRightUpColor = colorRightUp.color; + u32 colorRightDownColor = colorRightDown.color; + + //SColor pixelColor; = colorLeftUp; + u32 pixelColor; + //SColor leftColor, rightColor; + u32 leftColor, rightColor; + + tVideoSample* dst; // render target surface (just a pointer to a u32 array of size RenderTargetSize) + + y=0; + ry = position.UpperLeftCorner.Y < 0 ? (u32)( position.UpperLeftCorner.Y * -1 ) : 0; + for ( ; y < h ; y++ ) + { + ryh = (f32)ry/(f32)rh; + //leftColor = colorLeftDown.getInterpolated( colorLeftUp, (f32)ry/(f32)rh ); + + leftColor = + ( (u32( (colorLeftDownColor>>24)*ryh + (colorLeftUpColor>>24)*(1.f-ryh) )) << 24 ) + + ( (u32( ((colorLeftDownColor>>16) & 0xff)*ryh + ((colorLeftUpColor>>16) & 0xff)*(1.f-ryh) )) << 16 ) + + ( (u32( ((colorLeftDownColor>>8) & 0xff)*ryh + ((colorLeftUpColor>>8) & 0xff)*(1.f-ryh) )) << 8 ) + + ( u32( (colorLeftDownColor & 0xff)*ryh + (colorLeftUpColor & 0xff)*(1.f-ryh) ) ); + + //rightColor = colorRightDown.getInterpolated( colorRightUp, (f32)ry/(f32)rh ); + rightColor = + ( (u32( (colorRightDownColor>>24)*ryh + (colorRightUpColor>>24)*(1.f-ryh) )) << 24 ) + + ( (u32( ((colorRightDownColor>>16) & 0xff)*ryh + ((colorRightUpColor>>16) & 0xff)*(1.f-ryh) )) << 16 ) + + ( (u32( ((colorRightDownColor>>8) & 0xff)*ryh + ((colorRightUpColor>>8) & 0xff)*(1.f-ryh) )) << 8 ) + + ( u32( (colorRightDownColor & 0xff)*ryh + (colorRightUpColor & 0xff)*(1.f-ryh) ) ); + + // left and right side + //RenderTargetSurface->setPixel( x0, y + y0, leftColor, false ); + //RenderTargetSurface->setPixel( x100, y + y0, rightColor, false ); + dst = (tVideoSample*)RenderTargetSurface->lock() + (y+y0)*RenderTargetSize.Width + x0; + dst[0] = (tVideoSample)leftColor; //.color; + dst[w-1] = (tVideoSample)rightColor; //.color; + + x = 1; + rx = position.UpperLeftCorner.X < 0 ? (u32)( position.UpperLeftCorner.X * -1 ) : 0; + for ( ; x < w-1; x++ ) + { + rxw = (f32)x/(f32)rw; + + //pixelColor = rightColor.getInterpolated( leftColor, (f32)rx/(f32)rw ); + pixelColor = + ( (u32( (rightColor>>24)*rxw + (leftColor>>24)*(1.f-rxw) )) << 24 ) + + ( (u32( ((rightColor>>16) & 0xff)*rxw + ((leftColor>>16) & 0xff)*(1.f-rxw) )) << 16 ) + + ( (u32( ((rightColor>>8) & 0xff)*rxw + ((leftColor>>8) & 0xff)*(1.f-rxw) )) << 8 ) + + ( u32( (rightColor & 0xff)*rxw + (leftColor & 0xff)*(1.f-rxw) ) ); + + //RenderTargetSurface->setPixel( x + x0, y + y0, pixelColor, false ); // <-- Reeeally slow + dst[x] = (tVideoSample)pixelColor; //.color; + + rx++; + } + + ry++; + } + //RenderTargetSurface->unlock(); // unnecessary for Burnings +#endif +} diff --git a/IrrExtensions/modded/source/new Burnings draw2DRectangle.txt b/IrrExtensions/modded/source/new Burnings draw2DRectangle.txt new file mode 100755 index 0000000..9ddc433 --- /dev/null +++ b/IrrExtensions/modded/source/new Burnings draw2DRectangle.txt @@ -0,0 +1,146 @@ +/* +This is the new Burnings Renderer draw2DRectangle I've created. +It creates a cleaner, CORRECT color gradient, whereas the original cheated and was wrong (and therefore incorrect when used for GUI color-pickers). +Based on Irrlicht revision 5064. It will work with revision 5589, but it will need to be updated for newer versions (like 5823) because the newer versions have a new render target system. + + +To Do: + +> Change all of the color names. I used the wrong names, and I've flipped them... again. :( +> Clip to screen size. +> Set the realClip to the clip if it exists +*/ + +//!Draws an 2d rectangle with a gradient. +void CBurningVideoDriver::draw2DRectangle(const core::rect& position, + SColor colorLeftUp, SColor colorRightUp, SColor colorLeftDown, SColor colorRightDown, + const core::rect* clip) +{ + +if ( !RenderTargetSurface ) +{ + setRenderTarget(BackBuffer); +} + +core::rect realClip( position ); +if ( clip ) + realClip.clipAgainst(*clip); + +if ( !realClip.isValid() ) + return; + +f64 da, dr, dg, db, lda, ldr, ldg, ldb, rda, rdr, rdg, rdb; + +f32 ula, ulr, ulg, ulb, ura, urr, urg, urb; + +f32 la, lr, lg, lb, ra, rr, rg, rb, a, r, g, b; //, color; +u32 u32a, u32b, u32g, u32b; + +tVideoSample* dst; // destination pixel + +ula = upperLeftColor.getAlpha(); +ulr = upperLeftColor.getRed(); +ulg = upperLeftColor.getGreen(); +ulb = upperLeftColor.getBlue(); +ura = upperRightColor.getAlpha(); +urr = upperRightColor.getRed(); +urg = upperRightColor.getGreen(); +urb = upperRightColor.getBlue(); + +u32 w = (u32) position.getWidth(); +u32 h = (u32) position.getHeight(); +f32 invw = 1.f / (f32)w; +f32 invh = 1.f / (f32)h; + +s32 startX = position.UpperLeftCorner.X; +s32 startY = position.UpperLeftCorner.Y; + +// Perform clipping to clip +if ( w + startX > realClip->LowerRightCorner.X ) +{ + w = realClip->LowerRightCorner.X - startX; +} +if ( h + startY > realClip->LowerRightCorner.Y ) +{ + h = realClip->LowerRightCorner.Y - startY; +} +if ( startX > realClip->UpperLeftCorner.X ) +{ + w -= startX - realClip->UpperLeftCorner.X; // remove however much startX is in excess over clipping X + startX = realClip->UpperLeftCorner.X; +} +if ( startY > realClip->UpperLeftCorner.Y ) +{ + h -= startY - realClip->UpperLeftCorner.Y; // remove however much startY is in excess over clipping Y + startY = realClip->UpperLeftCorner.Y; +} + +// Clip to screen +// ... to do... +/* Note, you may consider doing some of this earlier (i.e. before clipping the startX with the clipping rect) */ + +lda = (lowerLeftColor.getAlpha() - ula) * invh; +ldr = (lowerLeftColor.getRed() - ulr) * invh; +ldg = (lowerLeftColor.getGreen() - ulg) * invh; +ldb = (lowerLeftColor.getBlue() - ulb) * invh; +rda = (lowerRightColor.getAlpha() - ura) * invh; +rdr = (lowerRightColor.getRed() - urr) * invh; +rdg = (lowerRightColor.getGreen() - urg) * invh; +rdb = (lowerRightColor.getBlue() - urb) * invh; + +la = (f32) ula; +lr = (f32) ulr; +lg = (f32) ulg; +lb = (f32) ulb; +ra = (f32) ura; +rr = (f32) urr; +rg = (f32) urg; +rb = (f32) urb; + +f32 dx, dy; +for ( dy = 0; dy < h; dy += 1 ) +{ + //la = ula + (u32)( dy*lda ); + la += lda; + lr += ldr; + lg += ldg; + lb += ldb; + ra += rda; + rr += rdr; + rg += rdg; + rb += rdb; + + da = invw * (ra - la ); + dr = invw * (rr - lr ); + dg = invw * (rg - lg ); + db = invw * (rb - lb ); + + a = la; + r = lr; + g = lg; + b = lb; + + // Get back-buffer point + dst = (tVideoSample*)RenderTargetSurface->lock() + (startY + dy)*renderTargetSize.Width + (startX + dx); + + for ( dx = 0; dx < w; dx += 1 ) + { + //a = la + dx*da; + a += da; + r += dr + g += dg; + b += db; + + u32a = u32( a < 255 ? a : 255 ); + u32r = u32( r < 255 ? r : 255 ); + u32g = u32( g < 255 ? g : 255 ); + u32b = u32( b < 255 ? b : 255 ); + dst[dx] = (u32a << 32) | (u32r << 16) | (u32g << 8) | (u32b); + } +} + +} // end function + + + + diff --git a/IrrExtensions/util/irrJSON/CharFileReader.h b/IrrExtensions/util/irrJSON/CharFileReader.h new file mode 100644 index 0000000..d2ccb8c --- /dev/null +++ b/IrrExtensions/util/irrJSON/CharFileReader.h @@ -0,0 +1,329 @@ +/* (c) 2014 Nicolaus Anderson +License: zlib +*/ + +#include +#include +#include + +// Based on (but not compatible with): InputStream.h + +/* +Note that c8 is used everywhere, representing characters, which are +always supposed to be 8-bits, except on Linux systems. +Even still, that feature of Linux is never taken advantage of here. +Instead, everything is treated as a u8 (unsigned 8-bit), but does +not use u8 in order to keep the compiler happy with irrString. +*/ + +namespace irr { +namespace io { + +using core::string; + +enum ESeekPosition +{ + // Indicates offset from the current token + ESeek_CURRENT=0, + + // Indicates offset from the beginning + ESeek_BEGIN, + + // Indicates offset from the end + ESeek_END, + + // Count + ESeek_COUNT +}; + +class CharFileReader +{ + c8* file; + long fileSize; + long readPos; + + /* NOTE: readPos starts at -1 and ends at fileSize (1 after the last character) + to allow the easy, continuous usage of next() */ + +public: + CharFileReader( IReadFile* pFile=0 ) + : file(0) + , fileSize(0) + , readPos(-1) + { + if ( pFile ) + { + fileSize = pFile->getSize(); + file = new c8[fileSize]; + pFile->read( (void*)file, fileSize ); // copy contents into the array + //fileSize /= sizeof(c8); // Account for data type + } + } + + ~CharFileReader() + { + if ( file ) + { + delete[] file; + } + } + + void setFile( io::IReadFile* pFile ) + { + if ( file ) + { + delete[] file; + } + fileSize = 0; + readPos = -1; + + if ( pFile ) + { + fileSize = pFile->getSize(); + file = new c8[fileSize]; + pFile->read( (void*)file, fileSize ); // copy contents into the array + //fileSize /= sizeof(c8); // Account for data type + } + } + + //! Set the file to a pointer + /* NOTE: Requires that the pointer end in zero. + NOTE: The contents of the pointer are NOT copied, + but the pointer size is at least calculated. */ + void setFile( c8* ptr ) + { + if ( file ) + { + delete[] file; + } + fileSize = 0; + readPos = -1; + + if ( ptr ) + { + while ( ptr[fileSize] != 0 ) + fileSize++; + file = ptr; + } + } + + c8 curr() + { + if ( readPos >= 0 && readPos < fileSize ) // implies fileSize > 0 + return file[readPos]; + return 0; + } + + c8 next() + { + if ( readPos < fileSize ) + { + readPos++; + if ( readPos < fileSize ) + return file[readPos]; + } + return 0; + } + + c8 prev() + { + if ( readPos > 0 ) + { + readPos--; + return file[readPos]; + } + return 0; + } + + void seek( long pPosition, ESeekPosition pSeek=ESeek_CURRENT ) + { + switch( pSeek ) + { + case ESeek_BEGIN: + if ( pPosition > fileSize ) + readPos = fileSize; + else + readPos = pPosition; + break; + + case ESeek_END: + if ( fileSize < pPosition ) + readPos = -1; + else + readPos = fileSize - pPosition; + break; + + default: + if ( readPos + pPosition > -1 ) + { + if ( readPos + pPosition < fileSize ) + { + readPos += pPosition; + } else { + readPos = fileSize; + } + } else { + readPos = -1; + } + } + } + + //! Indicates if the reading position is prior to the first character + bool atPreBegin() + { + return (readPos == -1); + } + + //! Indicates if the reading position is after the last character in file + bool atEOF() + { + // If another character cannot be extracted via next() + return (fileSize == readPos); + } +}; + +class CharFileAccessor +{ + CharFileReader* reader; + + c8 unicode8SizeMark; + c8 unicode8SizeBits; + /* It is assumed that all numbers in the text file are, in fact, + stored as characters and not numbers. */ + + bool bigEndian; /* Flag indicating if the file is big-endian. + This is usually identified via: + 1) The first character in file (which takes precedence + over user setting) or + 2) User setting */ + +protected: + /* Sets the endianness of the accessor based on the Byte Order Mark. + Note that this mark is two characters long for U16. + U16BE = FE FF + U16LE = FF FE */ + void setEndianFromBom( char* bom ) + { + bigEndian = bom[0] == 0xfe; + } + +public: + /* Checks the system's endianness. + Idea thanks to http://www.codeproject.com/Articles/4804/Basic-concepts-on-Endianness + */ + static bool isSystemBigEndian() + { + s16 w = 0x0201; + return ((c8*)&w)[0] != 0x01; + } + + + CharFileAccessor( CharFileReader* pReader=0 ) + : reader( pReader ) + , unicode8SizeMark( (c8)0xc0 ) + /* rather than 80 (1000 0000), check for the existence of a second + byte via c0 (1100 0000) */ + , unicode8SizeBits( (c8)0xf0 ) + , bigEndian( false ) + {} + + void setReader( CharFileReader* pReader ) + { + reader = pReader; + } + + CharFileReader* getReader() + { + return reader; + } + + bool atEOF() + { + if ( reader ) + return reader->atEOF(); + return true; + } + + /* Sets the endianness for U16 and U32 reading. + (Not required for U8) */ + void setEndianness( bool pBigEndian ) + { + bigEndian = pBigEndian; + } + + /* Sets the endianness for U16 and U32 reading. + (Not required for U8) */ + void useSystemEndian() + { + bigEndian = isSystemBigEndian(); + } + + /* Returns the next unicode-8 character as a string. */ + const string getNextU8Char() + { + u8 i = 1; + string out; + + if ( !reader ) + return out; + + u8 c = reader->next(); + + if ( (c & unicode8SizeMark) > 0 ) + { + i = (c & unicode8SizeBits) >> 4; + } + out.append(c); + for ( ; i > 1; i>>=2 ) + { + out.append(reader->next()); + } + //reader->seek(-3, ESeek_CURRENT); + return out; + } + + /* Returns a string deliminated by the given characters. + Skips the characters given as skipChars. */ + const string getToken( const c8* delims, u32 numDelims, const c8* skipChars, u32 numSkipChars ) + { + c8 c; + u32 d; + string out; // may need to be const + bool add; + + if ( !reader ) + return out; + + while ( ! reader->atEOF() ) + { + c = reader->next(); + for ( d = 0; d < numDelims; d++ ) + { + if ( c == delims[d] ) + { + if ( out.size() == 0 ) + out.append(c); + else + reader->seek(-1); // Deliminator should be put in the next string + return out; + } + } + add = true; + for ( d = 0; d < numSkipChars; d++ ) + { + if ( c == skipChars[d] ) + { + add = false; + if ( out.size() ) + return out; + } + } + if ( add ) + out.append(c); + } + return out; + } + +}; + +}} diff --git a/IrrExtensions/util/irrJSON/Readme.md b/IrrExtensions/util/irrJSON/Readme.md new file mode 100644 index 0000000..7d26c0a --- /dev/null +++ b/IrrExtensions/util/irrJSON/Readme.md @@ -0,0 +1,2 @@ +JSON file loader and storage for Irrlicht Engine ( irrlicht.sourceforge.net ). +Requires irrTree. diff --git a/IrrExtensions/util/irrJSON/irrJSON.cpp b/IrrExtensions/util/irrJSON/irrJSON.cpp new file mode 100644 index 0000000..80eb47e --- /dev/null +++ b/IrrExtensions/util/irrJSON/irrJSON.cpp @@ -0,0 +1,291 @@ +/* (c) 2014 Nicolaus Anderson +License: zlib +*/ + +#ifndef IRRJSON_CPP +#define IRRJSON_CPP + +#include "irrJSON.h" + +namespace irr { +namespace io { + +using core::string; + +irrJSON::irrJSON( IFileSystem* pFileSystem ) + : fileSystem( pFileSystem ) + , delimChars( "{}:,;\"\'\\" ) + , skipChars( " \t\r\n" ) + , quoteDelimChars( "\"\'\\" ) + , reader(0) + , accessor(0) +{ +} + +irrJSON::~irrJSON() +{ +} + +bool irrJSON::parseFile(path pPath, irrTreeNode*& pResultTree) +{ + IReadFile* file = fileSystem->createAndOpenFile( pPath ); + + bool result = parseFile( file, pResultTree ); + + if ( file != 0 ) + file->drop(); + + return result; +} + +bool irrJSON::parseFile( IReadFile* pFile, irrTreeNode*& pResultTree ) +{ + if ( !pFile || !pFile->getSize() ) + return false; + + reader = new CharFileReader(pFile); + accessor = new CharFileAccessor(reader); + + /* + IMPORTANT NOTE: + + We want to be able to load JSON files that don't necessarily follow + standard format. For example, they may have multiple root bodies. + In that case, a single root is used that represents the file. + + JSONroot + { + node0{ + info ( is tree root ) + node1, + node2, + node3 + } + node1 { + info + } + node2 { + info + } + node3 { + info + } + } + */ + + pResultTree = new irrTreeNode(); + pResultTree->setElem( new irrJSONElement("root") ); + + while ( ! reader->atEOF() ) + getElements(pResultTree); + //findAndStartBody(pResultTree); + + delete accessor; + delete reader; + + return true; +} + +inline string irrJSON::getToken( bool pInQuotes ) +{ + if ( pInQuotes ) + return accessor->getToken(quoteDelimChars.c_str(), quoteDelimChars.size(), 0, 0 ); + + /* Ironically, it doesn't matter if unicode-8 characters are grabbed + here or ASCII characters are since only ASCII characters are the + deliminators. */ + return accessor->getToken(delimChars.c_str(), delimChars.size(), skipChars.c_str(), skipChars.size()); +} + +void irrJSON::getElements( irrTreeNode* pNode ) +{ + string token; + bool findName = true; + bool add = false; // (flag) append the token to name or value + // quote types 1==' 2==" + bool inQuote1 = false; + bool inQuote2 = false; + bool out; + c8 temp; + + string name; + string value; + irrJSONElement* elem = (irrJSONElement*)(pNode->getElem()); + + token = getToken(); + while ( ! reader->atEOF() && token[0] != '}' ) + { + add = false; + + // Handle quotes situations + if ( inQuote1 || inQuote2 ) + { + switch ( token[0] ) + { + case '\'': + if ( !inQuote2 ) { + inQuote1 = false; + } else { + add = true; + } + break; + + case '\"': + if ( !inQuote1 ) { + inQuote2 = false; + } else { + add = true; + } + break; + + case '\\': // Escape character + { + if ( reader->atEOF() ) + break; + temp = reader->next(); + switch( temp ) + { + case '\\': // backslash + if ( findName ) + name.append('\\'); + else + value.append('\\'); + break; + case 'n': // new line (Windows) + if ( findName ) + name.append('\n'); + else + value.append('\n'); + break; + case 'r': // new line / return (Linux) + if ( findName ) + name.append('\r'); + else + value.append('\r'); + break; + case 't': // tab + if ( findName ) + name.append('\t'); + else + value.append('\t'); + break; + default: break; + } + } + break; + + default: + add = true; + break; + } + // Handle normal situations + } else { + switch ( token[0] ) + { + case ':': + findName = false; + break; + + case '{': + if ( ! findName ) + { + getElements( &(pNode->addNode( new irrJSONElement(name.c_str()) )) ); + } + break; + + case '}': // <- unreachable anyways + case ';': + case ',': + findName = true; + if ( name.size() && name != "" ) + elem->addAttribute(name,value); + name = ""; + value = ""; + break; + + case '\'': + inQuote1 = true; + break; + + case '\"': + inQuote2 = true; + break; + + case '/': // Comments + token = getToken(); + out = false; + if ( ! reader->atEOF() && token[0] == '*' ) + { + token = getToken(); + while ( ! reader->atEOF() && token[0] != '}' ) + { + if ( out ) + { + // Comment ended + if ( token[0] == '/' ) + { + // Continue normal activity + token = getToken(); + + // Collect values terminated by '}' + // Must be looking for value + if ( (token[0] == '}' || reader->atEOF()) && !findName ) + { + elem->addAttribute(name,value); + } + + continue; + } + else + out = false; + } + if ( token[0] == '*' ) + out = true; + token = getToken(); + } + // Continue normal activity + + // Collect values terminated by '}' + // Must be looking for value + if ( (token[0] == '}' || reader->atEOF()) && !findName ) + { + elem->addAttribute(name,value); + } + + continue; + } + add = true; + break; + + default: // regular words + add = true; + break; + } + } + + if ( add ) + { + if ( findName ) + { + name.append(token); + } else { + value.append(token); + } + } + + if ( token[0] != '}' ) + token = getToken( inQuote1 || inQuote2 ); + + // Collect values terminated by '}' + // Must be looking for value + if ( (token[0] == '}' || reader->atEOF()) && !findName ) + { + elem->addAttribute(name,value); + } + } // end while loop +} + + +}} + +#endif diff --git a/IrrExtensions/util/irrJSON/irrJSON.h b/IrrExtensions/util/irrJSON/irrJSON.h new file mode 100644 index 0000000..4d7144b --- /dev/null +++ b/IrrExtensions/util/irrJSON/irrJSON.h @@ -0,0 +1,99 @@ +/* (c) 2014 Nicolaus Anderson +License: zlib +*/ + +#ifndef IRRJSON_H +#define IRRJSON_H + +#include +#include +#include +#include "CharFileReader.h" + +namespace irr { +namespace io { + +using core::string; +using core::list; + +/* NOTE: +JSON is meant to support unicode format, so in this case, the irrJSON +supports Unicode8 by avoiding circumstances that require interpreting characters +outside of the ASCII range of Unicode8. +*/ +class irrJSONElement : public irrTreeElement +{ +protected: + /* Name of the element (includes any '.' or character that + precedes the name in the file. */ + string name; + +public: + struct Attribute + { + string name; + string value; + + Attribute() + : name("") + , value("") + {} + + Attribute(string pName, string pValue) + : name( pName ) + , value( pValue ) + {} + }; + +protected: + list attributes; + +public: + irrJSONElement() + : name("") + {} + + irrJSONElement( const c8* pName ) + : name(pName) + {} + + string& getName() { return name; } + list& getAttributes() { return attributes; } + + void addAttribute(string pName, string pValue) + { + getAttributes().push_back( Attribute(pName,pValue) ); + } +}; + + +class irrJSON +{ + IFileSystem* fileSystem; + string delimChars; + string skipChars; + string quoteDelimChars; + + CharFileReader* reader; + CharFileAccessor* accessor; + +public: + irrJSON( IFileSystem* pFileSystem ); + ~irrJSON(); + + //! Parse file + /* Attempts to parse the file as a JSON-formatted file. + \param pResultTree - The output tree parsed from the JSON. + IMPORTANT NOTE: This should be an empty tree pointer! + \return - true if successful. */ + bool parseFile( path pPath, irrTreeNode*& pResultTree ); + bool parseFile( IReadFile* pFile, irrTreeNode*& pResultTree ); + +protected: + inline string getToken( bool pInQuotes=false ); + void getElements( irrTreeNode* pNode ); +}; + +}} + +#endif diff --git a/IrrExtensions/util/irrJSON/json_test.json b/IrrExtensions/util/irrJSON/json_test.json new file mode 100644 index 0000000..14ce0a8 --- /dev/null +++ b/IrrExtensions/util/irrJSON/json_test.json @@ -0,0 +1,4 @@ +start : { + child1 : { "Hello" : 1 }, + child2 : { World : 2 } +} diff --git a/IrrExtensions/util/irrJSON/main.cpp b/IrrExtensions/util/irrJSON/main.cpp new file mode 100644 index 0000000..ab86c33 --- /dev/null +++ b/IrrExtensions/util/irrJSON/main.cpp @@ -0,0 +1,63 @@ +#include +#include "irrJSON.h" +#include + +#ifdef _MSC_VER +#pragma comment(lib,"Irrlicht.lib") +#endif + +using namespace irr::core; +using namespace irr::io; + +using std::cout; +using std::cin; + +irr::core::stringc out; + +void logNode( /*irr::ILogger* logger,*/ irrTreeNode* node ) +{ + irrJSONElement* elem = (irrJSONElement*)(node->getElem()); + if ( elem ) + { + //logger->log( "Node name", elem->getName().c_str() ); + out.append( "Node name: " ).append( elem->getName().c_str() ); + list::Iterator i = elem->getAttributes().begin(); + for ( ; i != elem->getAttributes().end(); ++i ) + { + //logger->log( " Attribute", (*i).name.c_str() ); + //logger->log( " Value", (*i).value.c_str() ); + out.append( "\nAttribute: " ) + .append( (*i).name.c_str() ) + .append( ", value: " ) + .append( (*i).value.c_str() ); + } + } + out.append("\nCHILDREN {\n"); + for ( u32 c=0; c < node->children.size(); c++ ) + { + logNode( /*logger,*/ node->children[c] ); + } + out.append("}\n"); +} + +int main() +{ + irr::IrrlichtDevice* dev = irr::createDevice(video::EDT_NULL,dimension2du(640,480)); + + irrJSON* json = new irrJSON( dev->getFileSystem() ); + irrTreeNode* tree; + bool done = json->parseFile( io::path("json_test.json"), tree ); + if ( !done ) + return 1; + + // Print everything in the tree + /*irr::ILogger* logger = dev->getLogger();*/ + logNode( /*logger,*/ tree ); + + cout << out.c_str(); + + char k; + cin >> k; + + return 0; +} diff --git a/IrrExtensions/util/irrTree/Readme.md b/IrrExtensions/util/irrTree/Readme.md new file mode 100755 index 0000000..da8cada --- /dev/null +++ b/IrrExtensions/util/irrTree/Readme.md @@ -0,0 +1,4 @@ +# irrTree + +An abstract class used for storing data in a tree format. +Used by irrXMLTree. diff --git a/IrrExtensions/util/irrTree/compile instructions.txt b/IrrExtensions/util/irrTree/compile instructions.txt new file mode 100755 index 0000000..cedd147 --- /dev/null +++ b/IrrExtensions/util/irrTree/compile instructions.txt @@ -0,0 +1,2 @@ +g++ -c irrTree.h irrTree.cpp -I/usr/local/include/irrlicht + diff --git a/IrrExtensions/util/irrTree/debugger.cpp b/IrrExtensions/util/irrTree/debugger.cpp new file mode 100755 index 0000000..9250eea --- /dev/null +++ b/IrrExtensions/util/irrTree/debugger.cpp @@ -0,0 +1,218 @@ +/* +(c) 2012 Nic Anderson +*/ + +#include + +#include +#include "irrTree.h" + +#ifdef _MSC_VER +#pragma comment(lib, "Irrlicht.lib") +#endif + +using std::cout; +using std::cin; +using namespace irr; + +void test1(); +void test2(); +void test3(); +void test4(); + + +class Int : public irrTreeElement +{ +public: + s32 i; + + Int() : irrTreeElement() { i = 0; } + Int( s32 val ) : irrTreeElement() { i = val; } + void set( s32 val ) { i = val; } + s32 get() { return i; } + + virtual bool matches( irrTreeElement * other ) + { + cout << "\nIn TreeElement: fixed: i = " << i << ", other = " << ((Int *)other)->i; + return i == ((Int *)other)->i; + } + virtual bool matches( s32 * val ) { return (i==*val)?true:false; } + virtual bool matches( Int * val ) { return (i==val->i)?true:false; } +}; + + + + +void main() +{ + test4(); +} + + +void test1() +{ + irrTreeNode tree; + + char h; + + for ( s32 i=0; i < 10; i++ ) + { + tree.addNode( i ); + } + + cout << "\nCurrent size 1 = " << tree.Size(); + cin >> h; + + for ( s32 i=0; i < 10; i++ ) + { + tree[0].addNode( i ); + } + + cout << "\nCurrent size 2 = " << tree.Size(); + cin >> h; + + for ( s32 i=0; i < 10; i++ ) + { + tree[0][0].addNode( i ); + } + + cout << "\nCurrent size 3 = " << tree.Size(); + cin >> h; + + cout << "\n\nNow inserting numbers..."; + + tree[2].setElem( new Int(19) ); + tree[4] = new Int(4); + + cout << "\nNow attempting to find them..."; + + cout << "\nFirst number's node id = " << tree.getChildIndexByMatch( &Int(19) ); + cout << "\nSecond number's node id = " << tree.getChildIndexByMatch( &Int(4) ); + + // Doesn't work because native element does not contain field "i": + //((Int *)(TreeElement *)tree[0][0][3].element)->i = (s32)12; + //((Int *)((TreeElement *)tree[0][5].getElem()))->i = (s32)3; + + cout << "\n\n\nNow looking for second set of numbers..."; + + tree[0][0][3] = new Int(12); + ((Int *)tree[4].getElem())->i = 3; + + cout << "\nThird number's node id = " << tree[0][0].getChildIndexByMatch( &Int(12) ); + cout << "\nFourth number's node id = " << tree.getChildIndexByMatch( &Int(3) ); + + cin >> h; + + cout << "\n\n\nNow attempting to find them the long way."; + + irrTreeNode * node = new irrTreeNode(); + node->setElem( new Int() ); + ((Int *)(node->getElem()))->set( 2 ); + + if ( tree.getAnyMatching( &Int(19), node ) ) + { + s32 in = ((Int *)(node->getElem()))->i; + cout << "\nNumber found = " << in; + } else { + cout << "\nCould not find first node."; + } + + cin >> h; +} + +void test2() +{ + irrTreeNode tree; + + char h; + int id = 0; + + cout << "\nNow testing deletion...\nAdding node."; + tree = new Int(id*10); + tree.addNode(++id); + tree[0] = new Int(id*10); + tree[0].addNode(++id); + tree[0][0] = new Int(id*10); + /* note that assigning inline, as in the following, causes id to not increment + for the Int class: + tree[0].addNode(++id) = new Int(id*10); */ + + cout << "\nTree = "; + cout << "\n" << tree.ID << " (" << ((Int*)(tree.getElem()))->get() << ")"; + cout << "\n" << tree[0].ID << " (" << ((Int*)(tree[0].getElem()))->get() << ")"; + cout << "\n" << tree[0][0].ID << " (" << ((Int*)(tree[0][0].getElem()))->get() << ")"; + + irrTreeNode* last = & (tree[0][0]); + + cout << "\n\nRemoving node (middle)..."; + tree[0].~irrTreeNode(); + cout << "\nTree (from root) = "; + cout << "\n" << tree.ID << " (" << ((Int*)tree.getElem())->get() << ")"; + cout << "\n\nHas children? "; + if ( tree.children.size() ) + cout << "Yes"; + else + cout << "No"; + cout << "\n\nDoes last still exist? "; + if ( last ) + { + cout << "Yes"; + cout << "\nand it == " << last->ID << " (" << ((Int*)(last->getElem()))->get() << ")"; + } else + cout << "No"; + + cout << "\n"; + + cin >> h; +} + +void test3() +{ + char h; + + cout << "\nTest begun."; + + irrTreeNode* root = new irrTreeNode(); + root->setElem(new Int(0)); + + root->addNode() = new Int(12); + root->addNode() = new Int(22); + + irrTreeNode other; + other.stealNode( (*root)[0] ); + other.stealNode( (*root)[0] ); + + delete root; + + cout << "\nTest halfway."; + + cout << "\nother[0] == " << ((Int*) other[0].getElem() )->get(); + cout << "\nother[1] == " << ((Int*) other[1].getElem() )->get(); + + cout << "\nTest complete."; + + cin >> h; +} + +void test4() +{ + char h; + + cout << "\nTest begun."; + + irrTreeNode* root = new irrTreeNode(); + root->setElem( new Int(0) ); + + root->addNode() = new Int(11); + root->addNode() = new Int(21); + + root->get(0)->addNode() = new Int(111); + + cout << "\nDeleting..."; + + delete root->get(0); + + cout << "\nDelete complete."; + + cin >> h; +} \ No newline at end of file diff --git a/IrrExtensions/util/irrTree/irrTree.cpp b/IrrExtensions/util/irrTree/irrTree.cpp new file mode 100755 index 0000000..e3ddbda --- /dev/null +++ b/IrrExtensions/util/irrTree/irrTree.cpp @@ -0,0 +1,92 @@ +/* +(c) 2012 Nicolaus Anderson + +See irrTree.h for copyright details. +*/ + +#ifndef IRRTREE_CPP +#define IRRTREE_CPP + +#include "irrTree.h" + +bool irrTreeNode::matches( irrTreeElement* match ) +{ + return element->matches(match); +} + +bool irrTreeNode::getNodeWithID( s32 id, irrTreeNode*& node ) +{ + // First, search self + if ( ID == id ) + { + node = this; + return true; + } + + // Second search just the childrem + for ( s32 c = 0; c < (s32)children.size(); c++ ) + { + if ( ((irrTreeNode*)(children[c]))->ID == id ) + { + node = (irrTreeNode*)(children[c]); + return true; + } + } + + // Now have the children search their children + for ( s32 c = 0; c < (s32)children.size(); c++ ) + { + if ( ((irrTreeNode*)(children[c]))->getNodeWithID( id, node ) ) + return true; + } + + return false; +} + +bool irrTreeNode::getAnyMatching( irrTreeElement* match, irrTreeNode*& node ) +{ + // First, search self + if ( matches(match) ) + { + node = (irrTreeNode*)this; + return true; + } + + // Now have the children search their children + for ( s32 c = 0; c < (s32)children.size(); c++ ) + { + if ( ((irrTreeNode*)(children[c]))->getAnyMatching( match, node ) ) + return true; + } + + return false; +} + +s32 irrTreeNode::getChildIndexByMatch( irrTreeElement* match ) +{ + for ( s32 c = 0; c < (s32)children.size(); c++ ) + { + if ( ((irrTreeNode*)(children[c]))->matches( match ) ) + { + return c; + } + } + + return -1; +} + +bool irrTreeNode::getChildByMatch( irrTreeElement* match, irrTreeNode*& node ) +{ + for ( s32 c = 0; c < (s32)children.size(); c++ ) + { + if ( ((irrTreeNode*)(children[c]))->matches( match ) ) + { + node = (irrTreeNode*)(children[c]); + return true; + } + } + + return false; +} + +#endif \ No newline at end of file diff --git a/IrrExtensions/util/irrTree/irrTree.h b/IrrExtensions/util/irrTree/irrTree.h new file mode 100755 index 0000000..8f2262a --- /dev/null +++ b/IrrExtensions/util/irrTree/irrTree.h @@ -0,0 +1,177 @@ +/* +(c) 2012 Nicolaus Anderson + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + Since this uses the irrlicht engine, see irrlicht.h for more details. +*/ + +#ifndef IRRTREE_H +#define IRRTREE_H + +#include + +using namespace irr; + +class irrTreeElement +{ +public: + /* A virtual deconstructor is required for this class (and consequently for inheriting + from this class) because the "delete" operator is used. */ + virtual ~irrTreeElement() {} + + virtual bool matches( irrTreeElement * other ) + { + return false; + } + + bool operator== ( irrTreeElement * other ) + { + return matches( other ); + } +}; + + +class irrTreeNode +{ +public: + core::array children; + s32 ID; // ID of this node + irrTreeElement* element; // what this node is carrying + irrTreeNode* parent; // parent node of this node + + irrTreeNode( s32 id=0, irrTreeNode* parent_node=0, + irrTreeElement* new_element = new irrTreeElement() ) + : ID(id) + , element( new_element ) + , parent( parent_node ) + { + } + + ~irrTreeNode() + { + u32 c = 0; + for ( ; c < children.size(); c++ ) + { + children[c]->parent = 0; + } + children.clear(); + if ( element ) + delete element; + + if ( parent ) + parent->removeChild(this); + } + + void setElem( irrTreeElement* new_element ) + { + if ( element ) + delete element; + element = new_element; + } + + void operator= ( irrTreeElement* new_element ) + { + setElem( new_element ); + } + + irrTreeElement* getElem() + { + return element; + } + + irrTreeNode& addNode( s32 id=0 ) + { + children.push_back( new irrTreeNode( id, this ) ); + return *(children[children.size()-1]); + } + + irrTreeNode& addNode( irrTreeElement* elem, s32 id=0 ) + { + children.push_back( new irrTreeNode( id, this ) ); + children[children.size()-1]->element = elem; + return *(children[children.size()-1]); + } + + // Takes a node from another tree node + void stealNode( irrTreeNode& pTakeMe ) + { + if ( pTakeMe.parent ) + pTakeMe.parent->removeChild(&pTakeMe); + children.push_back( &pTakeMe ); + pTakeMe.parent = this; + } + + void removeChild( irrTreeNode* child ) + { + u32 c = 0; + for ( ; c < children.size(); c++ ) + { + if ( children[c] == child ) + { + children.erase(c); + break; + } + } + } + + //! Size of tree + /* Returns the size of the entire tree, including all nodes. */ + s32 Size() + { + s32 size = children.size(); + + for ( s32 i=0 ; i < (s32)children.size(); i++ ) + size += getChildByIndex(i)->Size(); + + return size; + } + + //! Get child by index + /* Returns only one of this node's children based on index. */ + irrTreeNode* getChildByIndex( s32 child ) + { + return children[child]; + } + + irrTreeNode& operator[] ( s32 index ) + { + return *(children[index]); + } + + irrTreeNode* get( s32 index ) + { + return getChildByIndex( index ); + } + + irrTreeNode* getParent() + { + return parent; + } + + bool matches( irrTreeElement* match ); + + bool getNodeWithID( s32 id, irrTreeNode*& node ); + + bool getAnyMatching( irrTreeElement* match, irrTreeNode*& node ); + + s32 getChildIndexByMatch( irrTreeElement* match ); + + bool getChildByMatch( irrTreeElement* match, irrTreeNode*& node ); +}; + +#endif diff --git a/IrrExtensions/util/irrXMLStorage/debugger.cpp b/IrrExtensions/util/irrXMLStorage/debugger.cpp new file mode 100755 index 0000000..bef0a29 --- /dev/null +++ b/IrrExtensions/util/irrXMLStorage/debugger.cpp @@ -0,0 +1,44 @@ +/* +(c) 2012 Nic Anderson +*/ + +#include + +#include +#include "irrXMLStorage.h" +#include + +using std::cin; + +#ifdef _MSC_VER +#pragma comment(lib, "Irrlicht.lib") +#endif + +using namespace irr; + +void main() +{ + IrrlichtDevice * irrdevice = createDevice(video::EDT_NULL); + irrXMLStorage * unit = new irrXMLStorage(); + irrWinWriter writer; + + writer.print( "Starting test...\n" ); + + if ( unit->LoadFile( irrdevice->getFileSystem(), "text.html" ) ) + { + writer.print( "\nFile loaded" ); + + if ( unit->ParseFile() ) + { + writer.print( "\nFile parsed\n\n" ); + unit->WriteAll( &writer ); + } + } + + unit[0].get(0)->element; + + //writer.finalize(); the unit already calls finalize + + char c; + cin >> c; +} \ No newline at end of file diff --git a/IrrExtensions/util/irrXMLStorage/irrXMLStorage.cpp b/IrrExtensions/util/irrXMLStorage/irrXMLStorage.cpp new file mode 100755 index 0000000..b747e42 --- /dev/null +++ b/IrrExtensions/util/irrXMLStorage/irrXMLStorage.cpp @@ -0,0 +1,211 @@ +//(C) 2021 Nicolaus Anderson + +#include "irrXMLStorage.h" + + +irrXMLStorage::irrXMLStorage() +{ + hasFile = false; + + root = new irrXMLTreeNode(); +} + +irrXMLStorage::~irrXMLStorage() +{ + // Empty the tree nodes and wipe them from memory + delete root; +} + +//! Clear tree +/* Delete the current XML contents stored here. */ +void irrXMLStorage::ClearTree() +{ + delete root; +} + +//! Load file +/* Attempts to load an XML file (with the given name) into memory. +If it is unable to load a file, this function will return false +and the Parse function will not work. */ +bool irrXMLStorage::LoadFile( io::IFileSystem* file_sys, io::path filename ) +{ + // Load the file and create a reader + reader = file_sys->createXMLReader( filename.c_str() ); + + // Indicate if a file could not be opened + return (hasFile = (reader != 0)); +} + + +//! Parse file +/* Parses the XML file, saving its contents in the tree. +Returns false if unable to parse the tree for some reason. */ +bool irrXMLStorage::ParseFile() +{ + // Stop if there is no file to parse + if ( !hasFile ) return false; + + // necessary variables + // automatic id assigned to the node that indicates order of addition to tree + s32 auto_id = 0; + + // pointer to node for saving data + irrXMLTreeNode* save_node = root; // start at the first node + // element/object to save data to + irrXMLTreeElement* save_elem = (irrXMLTreeElement*)(save_node->getElem()); + + // node data + io::EXML_NODE node_type; + core::stringc node_text = ""; + s32 num_attrs = 0; // number of attributes in the node + core::stringw node_attr_name; + + // Parse while there are nodes + while ( reader->read() ) + { + node_type = reader->getNodeType(); + + // pseudo-switch for what to do based on node type encountered + // if ( node_type == io::EXN_NONE ) + // { + // no node + // } else + if ( node_type == io::EXN_ELEMENT ) // standard node + { + // Save current node text + save_elem->appendNodeText( node_text ); + + // Erase the node text for the next node to use + node_text = ""; + + // Create a new node to store data + save_node->addNode( ++auto_id ); + + // Go to that node + save_node = (irrXMLTreeNode*) save_node->children.getLast(); + + save_elem = (irrXMLTreeElement*) save_node->getElem(); + + // Get the node type and name + save_elem->setNodeType( io::EXN_ELEMENT ); + save_elem->setNodeName( reader->getNodeName() ); + + // Get the attribute information + num_attrs = reader->getAttributeCount(); + for ( s32 attr = 0; attr < num_attrs; attr++ ) + { + node_attr_name = reader->getAttributeName( attr ); + + save_elem->addAttribute( + node_attr_name, + reader->getAttributeValueSafe( node_attr_name.c_str() ) + ); + } + + } else + if ( node_type == io::EXN_ELEMENT_END ) // end of a standard node + { + // Save the internal text, if any + save_elem->appendNodeText( node_text ); + + // Erase the node text for the parent node to continue adding to its own + node_text = ""; + + // Return to the parent of this node, thus "closing" it + save_node = (irrXMLTreeNode*)save_node->getParent(); + + } else + if ( node_type == io::EXN_TEXT ) // text within a node + { + node_text += reader->getNodeData(); + } + } + + return true; // successful run +} + + +//! Get node +/* Direct access to the XML nodes. */ +irrXMLTreeNode& irrXMLStorage::operator[] ( s32 index ) +{ + return (irrXMLTreeNode&)root[ index ]; +} + +//! Get node +/* Direct access to the XML nodes. */ +irrXMLTreeNode* irrXMLStorage::get( s32 index ) +{ + return (irrXMLTreeNode*)root->getChildByIndex( index ); +} + +//! Get the root node +/* Direct access to the root XML node. */ +irrXMLTreeNode* irrXMLStorage::getRoot() +{ + return (irrXMLTreeNode*)root; +} + +//! Root size +/* Returns the number of children at the root. */ +s32 irrXMLStorage::rootSize() +{ + return (s32)(root->children.size()); +} + +#ifdef USE_IRR_WRITEABLE_INTERFACE + +//! Output all contents +/* Writes the contents of the tree to a given WriteableInterface. +Simply put, it calls WriteContentsOf for the root node. */ +void irrXMLStorage::WriteAll( WriteableInterface* writer ) +{ + WriteContentsOf( root, writer ); + writer->finalize(); +} + +//! Output the contents of a specific node +/* Writes the contents of the select node to the given WriteableInterface. */ +void irrXMLStorage::WriteContentsOf( irrXMLTreeNode* node, WriteableInterface* writer ) +{ + writer->finalize(); + + irrXMLTreeElement* elem = (irrXMLTreeElement*)(node->element); + + s32 sizeattr = elem->numAttributes(); + + if ( elem->getNodeType() == io::EXN_ELEMENT ) + { + // Output info about this specific node + writer->print( core::stringc("<") + elem->getNodeName() ); + + for ( s32 i = 0; i < sizeattr; i++ ) + { + writer->print( core::stringc(" ") + elem->getAttrName(i) + "=\"" + + elem->getAttrValue(i) + "\"" ); + } + + writer->print( ">\n" ); + + // Output info about all sub nodes + for ( s32 c = 0; c < (s32)node->children.size(); c++ ) + { + WriteContentsOf( (irrXMLTreeNode*)node->get(c), writer ); + } + + writer->print( elem->getNodeText() ); + + // Appear to terminate this node + writer->print( core::stringc("\ngetNodeName() + ">\n" ); + } else { + if ( elem->getNodeType() == io::EXN_UNKNOWN ) + { + for ( s32 c = 0; c < (s32)node->children.size(); c++ ) + { + WriteContentsOf( (irrXMLTreeNode*)node->get(c), writer ); + } + } + } +} + +#endif // USE_IRR_WRITEABLE_INTERFACE diff --git a/IrrExtensions/util/irrXMLStorage/irrXMLStorage.h b/IrrExtensions/util/irrXMLStorage/irrXMLStorage.h new file mode 100755 index 0000000..11d626f --- /dev/null +++ b/IrrExtensions/util/irrXMLStorage/irrXMLStorage.h @@ -0,0 +1,101 @@ +/* +(c) 2012 Nic Anderson + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + Since this uses the irrlicht engine, see irrlicht.h for more details. +*/ + +#ifndef IRRXMLSTORAGE_H +#define IRRXMLSTORAGE_H + +#include +#include "../irrXMLTree/irrXMLTree.h" + +// TODO: Replace this +#ifdef USE_IRR_WRITEABLE_INTERFACE +#include "../../misc/irrWriteableInterface/irrWriteableInterface.h" +#endif + +using namespace irr; + +//! irr XML Storage +/* This class extracts data from an XML file and saves it in an +tree composed of irrXMLTreeNodes. +*/ +class irrXMLStorage : public IReferenceCounted +{ + // ID # + s32 ID; + + // Tree containing the data + irrXMLTreeNode* root; + + // File data collection + io::IXMLReader* reader; // XML reader for acquiring the data + bool hasFile; // contains a file for reading + +public: + + irrXMLStorage(); + + ~irrXMLStorage(); + + //! Clear tree + /* Delete the current XML contents stored here. */ + void ClearTree(); + + //! Load file + /* Attempts to load an XML file (with the given name) into memory. + If it is unable to load a file, this function will return false + and the Parse function will not work. */ + bool LoadFile( io::IFileSystem* file_sys, io::path filename ); + + //! Parse file + /* Parses the XML file, saving its contents in the tree. + Returns false if unable to parse the tree for some reason. */ + bool ParseFile(); + + //! Get node + /* Direct access to the XML nodes. */ + irrXMLTreeNode& operator[] ( s32 index ); + + //! Get node + /* Direct access to the XML nodes. */ + irrXMLTreeNode* get( s32 index ); + + //! Get the root node + /* Direct access to the root XML node. */ + irrXMLTreeNode* getRoot(); + + //! Root size + /* Returns the number of children at the root. */ + s32 rootSize(); + +#ifdef USE_IRR_WRITEABLE_INTERFACE + //! Output all contents + /* Writes the contents of the tree to a given WriteableInterface. + Simply put, it calls WriteContentsOf for the root node. */ + void WriteAll( WriteableInterface* writer ); + + //! Output the contents of a specific node + /* Writes the contents of the select node to the given WriteableInterface. */ + void WriteContentsOf( irrXMLTreeNode* node, WriteableInterface* writer ); +#endif +}; + +#endif diff --git a/IrrExtensions/util/irrXMLStorage/text.html b/IrrExtensions/util/irrXMLStorage/text.html new file mode 100755 index 0000000..05fd2b9 --- /dev/null +++ b/IrrExtensions/util/irrXMLStorage/text.html @@ -0,0 +1,14 @@ + + + + + +This is something to read. + + + + + + +
+
diff --git a/IrrExtensions/util/irrXMLStorage/text.xml b/IrrExtensions/util/irrXMLStorage/text.xml new file mode 100755 index 0000000..e0551ca --- /dev/null +++ b/IrrExtensions/util/irrXMLStorage/text.xml @@ -0,0 +1,18 @@ + + + + + +farse + + + Some interior text. + + + + + + + + + diff --git a/IrrExtensions/util/irrXMLTree/debugger.cpp b/IrrExtensions/util/irrXMLTree/debugger.cpp new file mode 100644 index 0000000..4a599b0 --- /dev/null +++ b/IrrExtensions/util/irrXMLTree/debugger.cpp @@ -0,0 +1,98 @@ +/* +(c) Nic Anderson +*/ + +#include + +#include "irrXMLTree.h" +#include + +#include +using std::cout; +using std::cin; + +#ifdef _MSC_VER +#pragma comment(lib, "Irrlicht.lib") +#endif + + +void main() +{ + irrXMLTreeNode tree; + + char h; + + for ( s32 i=0; i < 10; i++ ) + { + tree.addNode( i ); + } + + cout << "\nCurrent size 1 = " << tree.Size(); + cin >> h; + + for ( s32 i=0; i < 10; i++ ) + { + tree[0].addNode( i ); + } + + cout << "\nCurrent size 2 = " << tree.Size(); + cin >> h; + + for ( s32 i=0; i < 10; i++ ) + { + tree[0][0].addNode( i ); + } + + cout << "\nCurrent size 3 = " << tree.Size(); + cin >> h; + + cout << "\n\nNow inserting names..."; + + tree[2].setElem( new irrXMLTreeElement( io::EXN_ELEMENT, "t1" ) ); + tree[4] = new irrXMLTreeElement( io::EXN_ELEMENT, "t2" ); + + cout << "\nNow attempting to find them..."; + + cout << "\nFirst name's node id = " + << tree.getChildIndexByMatch( new irrXMLTreeElement( io::EXN_ELEMENT, "t1" ) ); + cout << "\nSecond name's node id = " + << tree.getChildIndexByMatch( new irrXMLTreeElement( io::EXN_ELEMENT, "t2" ) ); + + cin >> h; + + cout << "\n\n\nNow looking for second set of numbers..."; + + tree[0][0][3] = new irrXMLTreeElement( io::EXN_ELEMENT, "t3" ); + ((irrXMLTreeElement*)tree[4].getElem())->setNodeName( "t4" ); + + cout << "\nThird name's node id = " + << tree[0][0].getChildIndexByMatch( new irrXMLTreeElement( io::EXN_ELEMENT, "t3" ) ); + cout << "\nFourth name's node id = " + << tree.getChildIndexByMatch( new irrXMLTreeElement( io::EXN_ELEMENT, "t4" ) ); + + cin >> h; + + cout << "\n\n\nNow attempting to find them the long way."; + + + irrXMLTreeNode* nodexml = new irrXMLTreeNode(); + nodexml->setElem( new irrXMLTreeElement() ); + ((irrXMLTreeElement*)( nodexml->getElem() ))->setNodeName( "lt1" ); + + cout << "..."; + cin >> h; + + irrTreeNode* node; + irrXMLTreeElement* xmlelem = new irrXMLTreeElement( io::EXN_ELEMENT, "t4" ); + if ( tree.getAnyMatching( xmlelem, (irrTreeNode*&)node ) ) + { + core::stringc in = ((irrXMLTreeElement*)(node->getElem()))->getNodeName(); + cout << "\nNode name found = " << in.c_str(); + } else { + cout << "\nCould not find node."; + } + + //delete xmlelem; + + cin >> h; +} \ No newline at end of file diff --git a/IrrExtensions/util/irrXMLTree/irrXMLTree.h b/IrrExtensions/util/irrXMLTree/irrXMLTree.h new file mode 100644 index 0000000..d9adf39 --- /dev/null +++ b/IrrExtensions/util/irrXMLTree/irrXMLTree.h @@ -0,0 +1,267 @@ +/* +(c) 2012 Nic Anderson + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + Since this uses the irrlicht engine, see irrlicht.h for more details. +*/ + +#ifndef IRRXMLTREE_H +#define IRRXMLTREE_H + +#include +#include "../irrTree/irrTree.h" +//#include - include only when debugging irrXMLTree.h standalone + +using namespace irr; + +class irrXMLTreeElement : public irrTreeElement +{ + +private: + // node type + io::EXML_NODE type; + + // node name + core::stringc name; + + // text found inside of the node tags (but not within other tags) if any + core::stringc text; + + // Attributes taken from the XML sheet + class Attribute + { + public: + core::stringc name; + core::stringc value; + + //! + Attribute() + { + name = ""; + value = ""; + } + //! + Attribute( core::stringc new_name, core::stringc new_value ) + { + name = new_name; + value = new_value; + } + }; + core::array attributes; + +public: + //! + irrXMLTreeElement() + { + type = io::EXN_UNKNOWN; + name = "null"; + text = ""; + } + + //! + irrXMLTreeElement( io::EXML_NODE new_type, core::stringc new_name ) + { + type = new_type; + name = new_name; + text = ""; + } + + //! + ~irrXMLTreeElement() + { + attributes.clear(); + } + + // Adding attributes ----------------- + //! + void addAttribute() + { + attributes.push_back( Attribute() ); + } + + //! + void addAttribute( core::stringc name, core::stringc value ) + { + attributes.push_back( Attribute( name, value ) ); + } + + // setters ============================ + //! + void setNodeType( io::EXML_NODE node_type ) { type = node_type; } + //! + void setNodeName( core::stringc node_name ) { name = node_name; } + //! + void setNodeText( core::stringc node_text ) { text = node_text; } + + //! + void appendNodeText( core::stringc node_text ) { text += node_text; } + + //! Set attribute name + /**/ + void setAttrName( s32 which, core::stringc name ) + { + attributes[which].name = name; + } + + //! Set attribute value + /**/ + void setAttrValue( s32 which, core::stringc value ) + { + attributes[which].value = value; + } + + // getters ========================= + //! + io::EXML_NODE getNodeType() { return type; } + //! + core::stringc getNodeName() { return name; } + //! + core::stringc getNodeText() { return text; } + + //! Get attribute name + /**/ + core::stringc getAttrName( s32 which ) { return attributes[which].name; } + + //! Get attribute value + /**/ + core::stringc getAttrValue( s32 which ) { return attributes[which].value; } + + //! Get the total number of attributes + /**/ + s32 numAttributes() { return (s32)attributes.size(); } + + //! Index the given name + /* Searches the list of attributes for one whose name matches the given + one. Returns the index of this attribute or -1 if not found. */ + s32 indexOfName( core::stringc name ) + { + for ( s32 i = 0; i < (s32)attributes.size(); i++ ) + { + if ( attributes[i].name == name ) + return i; + } + + return -1; + } + + + //======== OVERRIDE FUNCTIONS ========== + // (or those with associated functionality) + + //! + virtual bool matches( irrTreeElement * match ) + { + // Conversion necessary + irrXMLTreeElement * XMLmatch = (irrXMLTreeElement *)match; + + // Comparison + return ( this->name == XMLmatch->name ); + } + + + // associated =================== + //! + virtual bool matchesType( irrXMLTreeElement * match ) + { + return ( this->type == match->type ); + } + + //! + virtual bool matchesName( irrXMLTreeElement * match ) + { + return ( this->name == match->name ); + } + + //! + virtual bool matchesText( irrXMLTreeElement * match ) + { + return ( this->text == match->text ); + } + + //! Match all attributes + /* Checks to see if both the given and this element + contain the same attributes and possibly respective values. + \param include_values - indicates as to whether the values should be compared */ + virtual bool matchesAttr( irrXMLTreeElement * match, bool include_values ) + { + // stores index of attribute whose name matches what's being viewed from the given + s32 matching_index = 0; + + for ( s32 i = 0; i < (s32)attributes.size(); i++ ) + { + matching_index = indexOfName( match->attributes[i].name ); + + if ( matching_index == -1 ) + { + return false; + } else { + if ( include_values + && attributes[matching_index].value != match->attributes[i].value + ) + { + return false; + } + } + } + + // All entries identified as matching + return true; + } + + //! Identical + /* Indentifies if two elements are identical in every respect. */ + virtual bool identical( irrXMLTreeElement * match ) + { + if ( type == match->type ) + { + if ( name == match->name ) + { + return matchesAttr( match, true ); + } else return false; + } else return false; + } + + //! implicit cast to irrTreeElement * + /**/ + operator irrTreeElement*() const + { + return (irrTreeElement *)this; + } +}; + + +//************************** irrXMLTreeNode ******************************* + +class irrXMLTreeNode : public irrTreeNode +{ +public: + irrXMLTreeNode( s32 id=0, irrXMLTreeNode * parent_node=0 ) + : irrTreeNode(id, parent_node, new irrXMLTreeElement()) + { + //delete this->element; + //this->element = new irrXMLTreeElement(); + } + + //! + virtual void addNode( s32 id=0 ) + { + this->children.push_back( new irrXMLTreeNode(id,this) ); + } +}; + +#endif diff --git a/IrrExtensions/util/util/NullMacros.h b/IrrExtensions/util/util/NullMacros.h new file mode 100755 index 0000000..dbbe836 --- /dev/null +++ b/IrrExtensions/util/util/NullMacros.h @@ -0,0 +1,26 @@ +// Copyright 2016 Nicolaus Anderson + +#ifndef NULL_DEFINING_HEADER +#define NULL_DEFINING_HEADER + +#ifdef REAL_NULL +#undef REAL_NULL +#endif + +#ifdef USE_CPP_11 +#define REAL_NULL nullptr +#else +#define REAL_NULL 0 +#endif + +#ifdef isNull +#undef isNull +#endif +#define isNull(x) (x == REAL_NULL) + +#ifdef notNull +#undef notNull +#endif +#define notNull(x) (x != REAL_NULL) + +#endif diff --git a/IrrExtensions/util/util/privacy.h b/IrrExtensions/util/util/privacy.h new file mode 100755 index 0000000..578a3cb --- /dev/null +++ b/IrrExtensions/util/util/privacy.h @@ -0,0 +1,52 @@ +// (c) 2019 Nicolaus Anderson +/* + AGREEMENT + 1) This software is provided AS-IS without any expressed guarantee of any kind of usability or fitness. + 2) You may use, modify, and/or distribute this software to your heart's content provided: + a) You do not misrepresent the software as having been created by you. (That would be lying.) + b) You transmit all copies of this software with this original agreement completely intact. +*/ +/* + USAGE + These macros are intended to replace the standard "private" and "protected" labels of classes. + Use them in place of the "private" and "protected" labels. + This allows for accessing and checking the private and protected members of a class for MOMENTARY debugging + purposes. + [code] + class MyClass + { + loose_protected: + int protectedMember; + + loose_private: + int privateMember; + }; + [/code] +*/ + +#ifndef PRIVACY_UTIL_H +#define PRIVACY_UTIL_H + +#define ENFORCE_PRIVATE +#ifdef UNENFORCE_PROTECTED +#undef ENFORCE_PRIVATE +#endif + +#define ENFORCE_PROTECTED +#ifdef UNENFORCE_PROTECTED +#undef ENFORCE_PROTECTED +#endif + +#ifdef ENFORCE_PRIVATE +#define loose_private private +#else +#define loose_private public +#endif + +#ifdef ENFORCE_PROTECTED +#define loose_protected protected +#else +#define loose_protected public +#endif + +#endif // end PRIVACY_UTIL_H diff --git a/IrrExtensions/util/util/readme.txt b/IrrExtensions/util/util/readme.txt new file mode 100755 index 0000000..f8f6ec4 --- /dev/null +++ b/IrrExtensions/util/util/readme.txt @@ -0,0 +1 @@ +This is just a collection of generic tools that can be copied and dropped into your project. Don't feel bad for creating copies of them. diff --git a/IrrExtensions/util/util/std_enable_if.h b/IrrExtensions/util/util/std_enable_if.h new file mode 100755 index 0000000..dd7011c --- /dev/null +++ b/IrrExtensions/util/util/std_enable_if.h @@ -0,0 +1,16 @@ +// From: +// https://code.woboq.org/qt5/include/c++/7.3.0/type_traits.html#std::enable_if + +// C++11 -*- C++ -*- +// Copyright (C) 2007-2017 Free Software Foundation, Inc. +// Distributed under the GNU GPL v3 + + // Primary template. +/// Define a member typedef @c type only if a boolean constant is true. +template +struct enable_if +{ }; +// Partial specialization for true. +template +struct enable_if +{ typedef _Tp type; }; diff --git a/IrrExtensions/util/util/utilPile.h b/IrrExtensions/util/util/utilPile.h new file mode 100755 index 0000000..240d769 --- /dev/null +++ b/IrrExtensions/util/util/utilPile.h @@ -0,0 +1,88 @@ +// Copyright 2018 Nicolaus Anderson +#ifndef UTILITY_DYN_ARRAY +#define UTILITY_DYN_ARRAY + +namespace util { + +typedef unsigned long PileSize_t; + +class BadPileIndexException { + PileSize_t idx; +public: + BadPileIndexException( PileSize_t index ) + : idx(index) + {} + + PileSize_t + getIndex() const { + return idx; + } +}; + +/* + A dynamically-sized array that cannot be shrunk until destroyed. + Unlike other arrays, it occupies only the space that it needs. +*/ +template +class Pile { + T* data; + PileSize_t count; + +public: + + Pile() + : data(0) + , count(0) + {} + + Pile( const Pile& pOther ) + : data(0) + , count(0) + { + data = new T[pOther.size()]; + for (; count < pOther.size(); ++count) { + data[count] = T(pOther.data[count]); + } + } + + ~Pile() { + while( count > 0 ) { + --count; + data[count].~T(); + } + } + + PileSize_t + size() const { + return count; + } + + void + push_back( const T& item ) { + T* temp = data; + data = new T[++count]; + PileSize_t i=0; + for (; i < count - 1; ++i ) { + data[i] = temp[i]; + } + data[count-1] = T(item); + } + + T& + operator[] ( PileSize_t index ) { + if ( index >= count ) + throw BadPileIndexException(index); + return data[index]; + } + + const T& + operator[] ( PileSize_t index ) const { + if ( index >= count ) + throw BadPileIndexException(index); + return data[index]; + } +}; + +} + +#endif diff --git a/IrrExtensions/video/Colors.h b/IrrExtensions/video/Colors.h new file mode 100755 index 0000000..544e2a2 --- /dev/null +++ b/IrrExtensions/video/Colors.h @@ -0,0 +1,20 @@ +// (c) 2014-2015 Nicolaus Anderson + +#ifndef Colors_H +#define Colors_H + +#define SOLID_RED 0xffff0000 +#define SOLID_YELLOW 0xffffff00 +#define SOLID_GREEN 0xff00ff00 +#define SOLID_TEAL 0xff00ffff +#define SOLID_BLUE 0xff0000ff +#define SOLID_PURPLE 0xffff00ff +#define SOLID_WHITE 0xffffffff +#define SOLID_BLACK 0xff000000 + + // non-transitionable colors +#define SOLID_GREY 0xff0f0f0f +#define SOLID_GRAY SOLID_GREY +#define SOLID_PINK 0xffff0f0f + +#endif // ifndef Colors_H diff --git a/IrrExtensions/video/createGradientTexture.cpp b/IrrExtensions/video/createGradientTexture.cpp new file mode 100755 index 0000000..3c93787 --- /dev/null +++ b/IrrExtensions/video/createGradientTexture.cpp @@ -0,0 +1,108 @@ +/* +Created by Nicolaus Anderson, 2012 +See createGradientTexture.h for details. +*/ + +#include "createGradientTexture.h" + +namespace irr +{ +namespace video +{ + +IImage* createGradientImage( + IVideoDriver* videoDriver, + core::dimension2du size, + bool horizontal, + SColor colors[], + u32 colorCount, + f32 stops[] + ) +{ + // We do actually need something to work with + if ( colorCount < 1 ) + return 0; + + // First, we need a blank image to work with + IImage* img = videoDriver->createImage( ECF_A8R8G8B8, size ); + img->fill( 0xFF000000 ); // solid black + + SColor pixel; // - the pixel we're actually going to insert into the image + u32 x = 0; // for iteration along the axis where the color changes + u32 y = 0; // for iteration along the axis where color does not change + u32 stop = 0; // current stop + f32 percent = 0.0f; // percent transition between stopping points + + /* For single-color gradients, we simply create a solid color image + and return it. */ + if ( colorCount == 1 ) + { + img->fill( colors[0] ); + return img; + } + + /* Now we draw the gradient */ + for (; x < (horizontal? size.Width:size.Height); x++ ) + { + /* First, we increment to the next color if we are passed + where it ends. */ + if ( stops[stop] < x && stop + 1 < colorCount ) + stop++; + + // Now, we find how far through the gradient we are + percent = + (f32)x * + // range conversion: actual to gradient + (stops[stop+1] - stops[stop]) + / (f32)(horizontal? size.Width:size.Height); + + /* Next, we creat the pixel on the basis of how far we are through the + gradient */ + pixel = colors[stop].getInterpolated( colors[stop+1], percent ); + + /* Finally, we can apply this color to the image, drawing it + across the image. + This might be faster if there were a method for drawing a solid + line across an image. */ + for (; y < (horizontal? size.Height:size.Width); y++ ) + { + if ( horizontal ) + img->setPixel( x, y, pixel ); + else + img->setPixel( y, x, pixel ); + } + } + + return img; +} + + +ITexture* createGradientTexture( + IVideoDriver* videoDriver, + core::dimension2du size, + bool horizontal, + SColor colors[], + u32 colorCount, + f32 stops[], + core::stringw name + ) +{ + IImage* img = + createGradientImage( + videoDriver, size, horizontal, colors, colorCount, stops + ); + + // The following code is mimicked from CGUIColorSelectDialog.cpp + bool genMip = videoDriver->getTextureCreationFlag(ETCF_CREATE_MIP_MAPS); + videoDriver->setTextureCreationFlag( video::ETCF_CREATE_MIP_MAPS, false); + + ITexture* tex = videoDriver->addTexture( name.c_str(), img ); + img->drop(); + + videoDriver->setTextureCreationFlag(ETCF_CREATE_MIP_MAPS, genMip); + + return tex; +} + +} // end namespace video +} // end namespace irr diff --git a/IrrExtensions/video/createGradientTexture.h b/IrrExtensions/video/createGradientTexture.h new file mode 100755 index 0000000..cd35016 --- /dev/null +++ b/IrrExtensions/video/createGradientTexture.h @@ -0,0 +1,55 @@ +/* +Created by Nicolaus Anderson, 2012 + +Subject to the same terms as in the irrlicht engine. +See irrlicht.h for more details. +*/ + +#include + +#ifndef IRR_CREATE_GRADIENT_IMAGE_TEXTURE_H +#define IRR_CREATE_GRADIENT_IMAGE_TEXTURE_H + +namespace irr +{ +namespace video +{ + +//! Create Gradient Image +/* Creates an image that is a gradient of the given colors. +The locations of complete transitions from one color to the next are +given by the stops, which are percentages of the overall size. +There must be an equal number of stops as there are colors. +NOTE: You will not need to grab the image, but you will need to drop it. +*/ +IImage* createGradientImage( + IVideoDriver* videoDriver, + core::dimension2du size, + bool horizontal, + SColor colors[], + u32 colorCount, + f32 stops[] + ); + +//! Create Gradient Texture +/* Creates a texture that is a gradient of the given colors. +The locations of complete transitions from one color to the next are +given by the stops, which are percentages of the overall size. +The return is a texture created by the video driver from an image generated +by this function. +There must be an equal number of stops as there are colors. +*/ +ITexture* createGradientTexture( + IVideoDriver* videoDriver, + core::dimension2du size, + bool horizontal, + SColor colors[], + u32 colorCount, + f32 stops[], + core::stringw name + ); + +} // end namespace video +} // end namespace irr + +#endif diff --git a/Makefile b/Makefile index ba9e876..0328bb1 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,4 @@ -IRRLICHT_PATH = /home/andrey/irrlicht-1.8.4 +IRRLICHT_PATH = /home/andrey/irrlicht-1.9 CPPFLAGS += -I$(IRRLICHT_PATH)/include -I/usr/X11R6/include @@ -12,7 +12,7 @@ CXXFLAGS += -std=c++17 LIBS += -L$(IRRLICHT_PATH)/lib/Linux -lIrrlicht -L/usr/X11R6/lib64 -lGL -lXxf86vm -lXext -lX11 -SOURCES = main.cpp +SOURCES = ~/balance_wheel/IrrExtensions/gui/GUIScrollPane.cpp main.cpp TARGET = balance_wheel diff --git a/main.cpp b/main.cpp index 742b712..fb1ae2f 100644 --- a/main.cpp +++ b/main.cpp @@ -1,24 +1,36 @@ #include #include #include +#include "./IrrExtensions/gui/GUIScrollPane.h" +#include +#include using namespace std; using namespace irr; +IrrlichtDevice* device = nullptr; +core::dimension2du wnd_size(1920, 1080); +core::recti params_pane_rect( + core::vector2di(wnd_size.Width-wnd_size.Width/4, 0), + core::dimension2di(wnd_size.Width/4, wnd_size.Height*2) +); + struct BalanceWheelParams { - const f32 gravity = -9.8; - f32 original_len = 40.f; - f32 mass = 1.f; - f32 elasticity = 100.f; - f32 friction_coef = 0.1f; - f32 string_friction_coef = 1.0f; - const f32 original_start_ang = 60.f; // in degrees - const core::vector3df pos = core::vector3df(0.0f, 0.0f, 0.0f); - const core::vector3df scale = core::vector3df(0.25f, 1.0f, 1.0f); - const f32 radius = 5.0f; - const f32 original_vel = 0.0f; - const f32 push_force = 500.0f; + f32 gravity = -9.8; + f32 original_len = 4.f; + f32 mass = 10.0f; + f32 elasticity = 500.f; + f32 friction_coef = 1.0f; + f32 string_friction_coef = 50.0f; + f32 original_start_ang = 0.0f; // in degrees + core::vector3df pos = core::vector3df(0.0f, 0.0f, 0.0f); + core::vector3df scale = core::vector3df(1.0f, 1.0f, 1.0f)*0.2; + f32 radius = 5.0f; + f32 original_vel = 0.0f; + f32 push_force = 1000.0f; + + u16 params_num = 12; }; struct CameraParams @@ -27,6 +39,9 @@ struct CameraParams f32 rot_speed = 30.0f; }; +BalanceWheelParams bw_params; +CameraParams c_params; + enum ObjectID { OBJECT_ID_BALANCE_WHEEL = 0, @@ -35,13 +50,34 @@ enum ObjectID OBJECT_ID_CAMERA }; -gui::IGUIWindow *exit_wnd = nullptr; +enum GUIEditBox +{ + GUI_EDITBOX_GRAVITY = 0, + GUI_EDITBOX_ORIGINAL_LENGTH, + GUI_EDITBOX_MASS, + GUI_EDITBOX_ELASTICITY, + GUI_EDITBOX_FRICTION_COEFFICIENT, + GUI_EDITBOX_STRING_FRICTION_COEFFICIENT, + GUI_EDITBOX_ORIGINAL_START_ANGLE, + GUI_EDITBOX_POS_X, + GUI_EDITBOX_POS_Y, + GUI_EDITBOX_POS_Z, + GUI_EDITBOX_SCALE_X, + GUI_EDITBOX_SCALE_Y, + GUI_EDITBOX_SCALE_Z, + GUI_EDITBOX_RADIUS, + GUI_EDITBOX_ORIGINAL_VELOCITY, + GUI_EDITBOX_PUSHING_FORCE, + GUI_EDITBOX_COUNT +}; + +const std::array editbox_ids = {3, 5, 7, 9, 11, 13, 15, 18, 20, 22, 25, 27, 29, 31, 33, 35}; class AppEventReceiver : public IEventReceiver { public: AppEventReceiver() : isEscPressed(false), isMiddleMousePressed(false), wheel(0.0f), mousePosDelta(0.0f, 0.0f, 0.0f), lastMousePos(0.0f, 0.0f, 0.0f), - isUpPressed(false), isDownPressed(false), isLeftPressed(false), isRightPressed(false) + isUpPressed(false), isDownPressed(false), isLeftPressed(false), isRightPressed(false), isResetButtonClicked(false) { } @@ -50,15 +86,16 @@ public: switch (event.EventType) { case EET_KEY_INPUT_EVENT: + { if (event.KeyInput.Key == KEY_ESCAPE) { isEscPressed = true; break; } - if (exit_wnd != nullptr) - return false; - + core::vector2di cursor_pos = device->getCursorControl()->getPosition(); + if (params_pane_rect.isPointInside(cursor_pos)) + break; switch (event.KeyInput.Key) { case KEY_UP: @@ -77,9 +114,12 @@ public: break; } break; + } case EET_MOUSE_INPUT_EVENT: - if (exit_wnd != nullptr) - return false; + { + core::vector2di cursor_pos = device->getCursorControl()->getPosition(); + if (params_pane_rect.isPointInside(cursor_pos)) + break; switch (event.MouseInput.Event) { @@ -102,6 +142,12 @@ public: break; } break; + } + case EET_GUI_EVENT: + { + if (event.GUIEvent.EventType == gui::EGET_BUTTON_CLICKED) + isResetButtonClicked = true; + } default: break; @@ -120,52 +166,226 @@ public: f32 wheel; core::vector3df mousePosDelta; core::vector3df lastMousePos; + bool isResetButtonClicked; }; -void createExitDialogueWindow(gui::IGUIEnvironment* env) +void createParametersPanelWindow(u16 params_num) { - if (env == nullptr) - return; - + gui::IGUIEnvironment* env = device->getGUIEnvironment(); gui::IGUISkin* skin = env->getSkin(); - skin->setColor(gui::EGDC_3D_DARK_SHADOW, video::SColor(255, 255, 255, 255)); - core::recti exit_w_rect(core::position2di(256, 192), core::dimension2di(512, 384)); - exit_wnd = env->addWindow(exit_w_rect, true, 0, 0, 1); - - if (exit_wnd == nullptr) + for (s32 i = 0; i < gui::EGDC_COUNT; i++) { - std::cout << "The exit window fails to be created!" << std::endl; - return; + video::SColor color = skin->getColor((gui::EGUI_DEFAULT_COLOR)i); + color.setAlpha(255); + skin->setColor((gui::EGUI_DEFAULT_COLOR)i, color); } - gui::IGUIButton* cbut = env->addButton(core::recti( - core::position2di(512/2-128, 384/2-384/4-64), core::dimension2di(2*128, 64) - ), exit_wnd, 2, L"Continue"); + gui::GUIScrollPane* params_pane = new gui::GUIScrollPane(env, env->getRootGUIElement(), params_pane_rect, 1); - if (cbut == nullptr) - { - std::cout << "The continue button fails to be created!" << std::endl; - return; - } + params_pane->showVerticalScrollBar(true); + params_pane->showHorizontalScrollBar(false); + params_pane->updateAbsolutePosition(); - cbut->updateAbsolutePosition(); + core::dimension2di params_pane_size = params_pane_rect.getSize(); + s32 width = params_pane_size.Width/6; + s32 height = wnd_size.Height/12; - gui::IGUIButton* exit_but = env->addButton(core::recti( - core::position2di(512/2-128, 384/2+384/4-64), core::dimension2di(2*128, 64) - ), exit_wnd, 3, L"Exit"); + env->addButton(core::recti( + core::vector2di(params_pane_size.Width/2-params_pane_size.Width/2/4, height/2/2), + core::dimension2di(params_pane_size.Width/2/4*2, height/2)), + params_pane, 36, L"Reset"); + gui::IGUIStaticText* gravity_st = env->addStaticText(L"Gravity:", core::recti( + core::vector2di(width, height), + core::dimension2di(params_pane_size.Width, wnd_size.Height/24) + ), false, false, params_pane, 2); - if (exit_but == nullptr) - { - std::cout << "The exit button fails to be created!" << std::endl; - return; - } + height += wnd_size.Height/24; + u32 gravity_text_w = gravity_st->getTextWidth()*4; + core::string gravity_text((double)bw_params.gravity); + gui::IGUIEditBox* gravity_edbox = env->addEditBox(gravity_text.c_str(), core::recti( + core::vector2di(width, height), + core::dimension2di(gravity_text_w, wnd_size.Height/24) + ), true, params_pane, 3); - exit_but->updateAbsolutePosition(); + height += wnd_size.Height/24 + wnd_size.Height/12; + env->addStaticText(L"Original Length:", core::recti( + core::vector2di(width, height), + core::dimension2di(params_pane_size.Width, wnd_size.Height/24) + ), false, false, params_pane, 4); + + height += wnd_size.Height/24; + core::string orig_l_text((double)bw_params.original_len); + env->addEditBox(orig_l_text.c_str(), core::recti( + core::vector2di(width, height), + core::dimension2di(gravity_text_w, wnd_size.Height/24) + ), true, params_pane, 5); + + height += wnd_size.Height/24 + wnd_size.Height/12; + env->addStaticText(L"Mass:", core::recti( + core::vector2di(width, height), + core::dimension2di(params_pane_size.Width, wnd_size.Height/24) + ), false, false, params_pane, 6); + + height += wnd_size.Height/24; + core::string mass_text((double)bw_params.mass); + env->addEditBox(mass_text.c_str(), core::recti( + core::vector2di(width, height), + core::dimension2di(gravity_text_w, wnd_size.Height/24) + ), true, params_pane, 7); + + height += wnd_size.Height/24 + wnd_size.Height/12; + env->addStaticText(L"Elasticity:", core::recti( + core::vector2di(width, height), + core::dimension2di(params_pane_size.Width, wnd_size.Height/24) + ), false, false, params_pane, 8); + + height += wnd_size.Height/24; + core::string elasticity_text((double)bw_params.elasticity); + env->addEditBox(elasticity_text.c_str(), core::recti( + core::vector2di(width, height), + core::dimension2di(gravity_text_w, wnd_size.Height/24) + ), true, params_pane, 9); + + height += wnd_size.Height/24 + wnd_size.Height/12; + env->addStaticText(L"Air resistance coefficient:", core::recti( + core::vector2di(width, height), + core::dimension2di(params_pane_size.Width, wnd_size.Height/24) + ), false, false, params_pane, 10); + + height += wnd_size.Height/24; + core::string air_resist_text((double)bw_params.friction_coef); + env->addEditBox(air_resist_text.c_str(), core::recti( + core::vector2di(width, height), + core::dimension2di(gravity_text_w, wnd_size.Height/24) + ), true, params_pane, 11); + + height += wnd_size.Height/24 + wnd_size.Height/12; + env->addStaticText(L"String friction coefficient:", core::recti( + core::vector2di(width, height), + core::dimension2di(params_pane_size.Width, wnd_size.Height/24) + ), false, false, params_pane, 12); + + height += wnd_size.Height/24; + core::string str_fric_text((double)bw_params.string_friction_coef); + env->addEditBox(str_fric_text.c_str(), core::recti( + core::vector2di(width, height), + core::dimension2di(gravity_text_w, wnd_size.Height/24) + ), true, params_pane, 13); + + width += gravity_text_w + width; + height = wnd_size.Height/12; + env->addStaticText(L"Original start angle:", core::recti( + core::vector2di(width, height), + core::dimension2di(params_pane_size.Width, wnd_size.Height/24) + ), false, false, params_pane, 14); + + height += wnd_size.Height/24; + core::string orig_start_ang_text((double)bw_params.original_start_ang); + env->addEditBox(orig_start_ang_text.c_str(), core::recti( + core::vector2di(width, height), + core::dimension2di(gravity_text_w, wnd_size.Height/24) + ), true, params_pane, 15); + + height += wnd_size.Height/24 + wnd_size.Height/12; + env->addStaticText(L"Position:", core::recti( + core::vector2di(width, height), + core::dimension2di(params_pane_size.Width, wnd_size.Height/24) + ), false, false, params_pane, 16); + + height += wnd_size.Height/24; + + env->addStaticText(L"X:", core::recti(core::vector2di(width, height), core::dimension2di(gravity_text_w/6, wnd_size.Height/24)), false, false, params_pane, 17); + core::string pos_x_text((double)bw_params.pos.X); + env->addEditBox(pos_x_text.c_str(), core::recti( + core::vector2di(width + gravity_text_w/6, height), + core::dimension2di(gravity_text_w/6, wnd_size.Height/24) + ), true, params_pane, 18); + env->addStaticText(L"Y:", core::recti(core::vector2di(width + (gravity_text_w/6)*2, height), core::dimension2di(gravity_text_w/6, wnd_size.Height/24)), false, false, params_pane, 19); + core::string pos_y_text((double)bw_params.pos.Y); + env->addEditBox(pos_y_text.c_str(), core::recti( + core::vector2di(width + (gravity_text_w/6)*3, height), + core::dimension2di(gravity_text_w/6, wnd_size.Height/24) + ), true, params_pane, 20); + env->addStaticText(L"Z:", core::recti(core::vector2di(width + (gravity_text_w/6)*4, height), core::dimension2di(gravity_text_w/6, wnd_size.Height/24)), false, false, params_pane, 21); + core::string pos_z_text((double)bw_params.pos.Z); + env->addEditBox(pos_z_text.c_str(), core::recti( + core::vector2di(width + (gravity_text_w/6)*5, height), + core::dimension2di(gravity_text_w/6, wnd_size.Height/24) + ), true, params_pane, 22); + + height += wnd_size.Height/24 + wnd_size.Height/12; + env->addStaticText(L"Scale:", core::recti( + core::vector2di(width, height), + core::dimension2di(params_pane_size.Width, wnd_size.Height/24) + ), false, false, params_pane, 23); + + height += wnd_size.Height/24; + + env->addStaticText(L"X:", core::recti(core::vector2di(width, height), core::dimension2di(gravity_text_w/6, wnd_size.Height/24)), false, false, params_pane, 24); + core::string scale_x_text((double)bw_params.scale.X); + env->addEditBox(scale_x_text.c_str(), core::recti( + core::vector2di(width + gravity_text_w/6, height), + core::dimension2di(gravity_text_w/6, wnd_size.Height/24) + ), true, params_pane, 25); + env->addStaticText(L"Y:", core::recti(core::vector2di(width + (gravity_text_w/6)*2, height), core::dimension2di(gravity_text_w/6, wnd_size.Height/24)), false, false, params_pane, 26); + core::string scale_y_text((double)bw_params.scale.Y); + env->addEditBox(scale_y_text.c_str(), core::recti( + core::vector2di(width + (gravity_text_w/6)*3, height), + core::dimension2di(gravity_text_w/6, wnd_size.Height/24) + ), true, params_pane, 27); + env->addStaticText(L"Z:", core::recti(core::vector2di(width + (gravity_text_w/6)*4, height), core::dimension2di(gravity_text_w/6, wnd_size.Height/24)), false, false, params_pane, 28); + core::string scale_z_text((double)bw_params.scale.Z); + env->addEditBox(scale_z_text.c_str(), core::recti( + core::vector2di(width + (gravity_text_w/6)*5, height), + core::dimension2di(gravity_text_w/6, wnd_size.Height/24) + ), true, params_pane, 29); + + height += wnd_size.Height/24 + wnd_size.Height/12; + env->addStaticText(L"Radius:", core::recti( + core::vector2di(width, height), + core::dimension2di(params_pane_size.Width, wnd_size.Height/24) + ), false, false, params_pane, 30); + + height += wnd_size.Height/24; + + core::string radius_text((double)bw_params.radius); + env->addEditBox(radius_text.c_str(), core::recti( + core::vector2di(width, height), + core::dimension2di(gravity_text_w, wnd_size.Height/24) + ), true, params_pane, 31); + + height += wnd_size.Height/24 + wnd_size.Height/12; + env->addStaticText(L"Original velocity:", core::recti( + core::vector2di(width, height), + core::dimension2di(params_pane_size.Width, wnd_size.Height/24) + ), false, false, params_pane, 32); + + height += wnd_size.Height/24; + + core::string orig_vel_text((double)bw_params.original_vel); + env->addEditBox(orig_vel_text.c_str(), core::recti( + core::vector2di(width, height), + core::dimension2di(gravity_text_w, wnd_size.Height/24) + ), true, params_pane, 33); + + height += wnd_size.Height/24 + wnd_size.Height/12; + env->addStaticText(L"Pushing force:", core::recti( + core::vector2di(width, height), + core::dimension2di(params_pane_size.Width, wnd_size.Height/24) + ), false, false, params_pane, 34); + + height += wnd_size.Height/24; + + core::string push_f_text((double)bw_params.push_force); + env->addEditBox(push_f_text.c_str(), core::recti( + core::vector2di(width, height), + core::dimension2di(gravity_text_w, wnd_size.Height/24) + ), true, params_pane, 35); } int main() { - IrrlichtDevice* device = createDevice(video::EDT_OPENGL, core::dimension2du(1024, 768), 32, false, true); + device = createDevice(video::EDT_OPENGL, wnd_size, 32, false, true); if (device == nullptr) { @@ -181,8 +401,7 @@ int main() AppEventReceiver receiver; device->setEventReceiver(&receiver); - BalanceWheelParams bw_params; - CameraParams c_params; + createParametersPanelWindow(bw_params.params_num); core::vector3df end( bw_params.original_len*std::sin(core::degToRad(bw_params.original_start_ang)), @@ -244,98 +463,188 @@ int main() return 1; } - smgr->addTextSceneNode(env->getBuiltInFont(), L"X", video::SColor(255, 255, 255, 255), 0, core::vector3df(55.0f, 0.0f, 0.0f), 9); - smgr->addTextSceneNode(env->getBuiltInFont(), L"Y", video::SColor(255, 255, 255, 255), 0, core::vector3df(0.0f, 55.0f, 0.0f), 10); - smgr->addTextSceneNode(env->getBuiltInFont(), L"Z", video::SColor(255, 255, 255, 255), 0, core::vector3df(0.0f, 0.0f, 55.0f), 11); + smgr->addTextSceneNode(env->getBuiltInFont(), L"X", video::SColor(255, 255, 255, 255), 0, core::vector3df(11.0f, 0.0f, 0.0f), 9); + smgr->addTextSceneNode(env->getBuiltInFont(), L"Y", video::SColor(255, 255, 255, 255), 0, core::vector3df(0.0f, 11.0f, 0.0f), 10); + smgr->addTextSceneNode(env->getBuiltInFont(), L"Z", video::SColor(255, 255, 255, 255), 0, core::vector3df(0.0f, 0.0f, 11.0f), 11); bal_wheel->addShadowVolumeSceneNode(0, 12, true); smgr->setShadowColor(video::SColor(150,0,0,0)); platform->addShadowVolumeSceneNode(0, 13, true); + //createParametersPanelWindow(env); + core::vector3df cur_vel(bw_params.original_vel); core::vector3df cur_rel_pos(end); core::vector3df cur_push_force(0.0f, 0.0f, 0.0f); + + u32 needTime = device->getTimer()->getTime() + 10; while(device->run()) - { - if (device->isWindowActive()) + { + cur_push_force = core::vector3df(0.0f, 0.0f, 0.0f); + if (receiver.isEscPressed) { + device->closeDevice(); + /*createExitDialogueWindow(device->getGUIEnvironment()); + receiver.isEscPressed = false;*/ + } + else if (receiver.wheel != 0.0f) + { + core::vector3df cur_target = camera->getTarget(); + const core::vector3df& cur_pos = camera->getPosition(); + core::vector3df rel_target = cur_target - cur_pos; + + s16 sign = receiver.wheel/std::fabs(receiver.wheel); + + if (sign > 0) + std::cout << "Camera has offset forward" << std::endl; + else if (sign < 0) + std::cout << "Camera has offset backward" << std::endl; + + core::vector3df cam_offset = rel_target.normalize()*(f32)sign*c_params.offset; + + core::vector3df new_pos = cur_pos + cam_offset; + camera->setPosition(new_pos); + + receiver.wheel = 0.0f; + } + else if (receiver.isMiddleMousePressed && receiver.mousePosDelta != core::vector3df(0.0f, 0.0f, 0.0f)) + { + const core::vector3df& cur_pos = camera->getPosition(); + core::vector3df cur_target = camera->getTarget(); + core::vector3df new_rot = (cur_pos - cur_target).getHorizontalAngle() + core::vector3df(-receiver.mousePosDelta.Y/10.0f, -receiver.mousePosDelta.X/10.0f, 0.0f); + + core::matrix4 matrix; + matrix.setRotationDegrees(new_rot); + + core::vector3df original_camera_pos(0.0f, 0.0f, (cur_target - cur_pos).getLength()); + matrix.rotateVect(original_camera_pos); + + camera->setPosition(cur_target + original_camera_pos); + receiver.mousePosDelta = core::vector3df(0.0f, 0.0f, 0.0f); + } + else if (receiver.isUpPressed) + { + core::vector3df nmlzed_target = camera->getTarget() - camera->getPosition(); + nmlzed_target.normalize(); + + cur_push_force = nmlzed_target * bw_params.push_force; + receiver.isUpPressed = false; + } + else if (receiver.isDownPressed) + { + core::vector3df nmlzed_target = camera->getTarget() - camera->getPosition(); + nmlzed_target.normalize(); + + cur_push_force = nmlzed_target * -bw_params.push_force; + receiver.isDownPressed = false; + } + else if (receiver.isLeftPressed) + { + core::vector3df nmlzed_target = camera->getTarget() - camera->getPosition(); + nmlzed_target.normalize(); + nmlzed_target.rotateXZBy(90.0f); + + cur_push_force = nmlzed_target * bw_params.push_force; + receiver.isLeftPressed = false; + } + else if (receiver.isRightPressed) + { + core::vector3df nmlzed_target = camera->getTarget() - camera->getPosition(); + nmlzed_target.normalize(); + nmlzed_target.rotateXZBy(-90.0f); + + cur_push_force = nmlzed_target * bw_params.push_force; + receiver.isRightPressed = false; + } + else if (receiver.isResetButtonClicked) + { + for (u32 i = 0; i < (u32)GUI_EDITBOX_COUNT; i++) + { + gui::IGUIElement* editbox = env->getRootGUIElement()->getElementFromId(editbox_ids[i], true); + const wchar_t* param_text = editbox->getText(); + switch ((GUIEditBox)i) + { + case GUI_EDITBOX_GRAVITY: + bw_params.gravity = wcscmp(param_text, L"") != 0 ? wcstof(param_text, 0) : bw_params.gravity; + break; + case GUI_EDITBOX_ORIGINAL_LENGTH: + bw_params.original_len = wcscmp(param_text, L"") != 0 ? wcstof(param_text, 0) : bw_params.original_len; + break; + case GUI_EDITBOX_MASS: + bw_params.mass = wcscmp(param_text, L"") != 0 ? wcstof(param_text, 0) : bw_params.mass; + break; + case GUI_EDITBOX_ELASTICITY: + bw_params.elasticity = wcscmp(param_text, L"") != 0 ? wcstof(param_text, 0) : bw_params.elasticity; + break; + case GUI_EDITBOX_FRICTION_COEFFICIENT: + bw_params.friction_coef = wcscmp(param_text, L"") != 0 ? wcstof(param_text, 0) : bw_params.friction_coef; + break; + case GUI_EDITBOX_STRING_FRICTION_COEFFICIENT: + bw_params.string_friction_coef = wcscmp(param_text, L"") != 0 ? wcstof(param_text, 0) : bw_params.string_friction_coef; + break; + case GUI_EDITBOX_ORIGINAL_START_ANGLE: + bw_params.original_start_ang = wcscmp(param_text, L"") != 0 ? wcstof(param_text, 0) : bw_params.original_start_ang; + break; + case GUI_EDITBOX_POS_X: + bw_params.pos.X = wcscmp(param_text, L"") != 0 ? wcstof(param_text, 0) : bw_params.pos.X; + break; + case GUI_EDITBOX_POS_Y: + bw_params.pos.Y = wcscmp(param_text, L"") != 0 ? wcstof(param_text, 0) : bw_params.pos.Y; + break; + case GUI_EDITBOX_POS_Z: + bw_params.pos.Z = wcscmp(param_text, L"") != 0 ? wcstof(param_text, 0) : bw_params.pos.Z; + break; + case GUI_EDITBOX_SCALE_X: + bw_params.scale.X = wcscmp(param_text, L"") != 0 ? wcstof(param_text, 0) : bw_params.scale.X; + break; + case GUI_EDITBOX_SCALE_Y: + bw_params.scale.Y = wcscmp(param_text, L"") != 0 ? wcstof(param_text, 0) : bw_params.scale.Y; + break; + case GUI_EDITBOX_SCALE_Z: + bw_params.scale.Z = wcscmp(param_text, L"") != 0 ? wcstof(param_text, 0) : bw_params.scale.Z; + break; + case GUI_EDITBOX_RADIUS: + bw_params.radius = wcscmp(param_text, L"") != 0 ? wcstof(param_text, 0) : bw_params.radius; + break; + case GUI_EDITBOX_ORIGINAL_VELOCITY: + bw_params.original_vel = wcscmp(param_text, L"") != 0 ? wcstof(param_text, 0) : bw_params.original_vel; + break; + case GUI_EDITBOX_PUSHING_FORCE: + bw_params.push_force = wcscmp(param_text, L"") != 0 ? wcstof(param_text, 0) : bw_params.push_force; + break; + } + } + end = core::vector3df(bw_params.original_len*std::sin(core::degToRad(bw_params.original_start_ang)), + -bw_params.original_len*std::cos(core::degToRad(bw_params.original_start_ang)), + 0.f); + beam = core::line3df(bw_params.pos, bw_params.pos + end); + bal_wheel->drop(); + bal_wheel = smgr->addSphereSceneNode(bw_params.radius, 32, 0, OBJECT_ID_BALANCE_WHEEL); + up_vec = (end*-1.0f).normalize(); + bal_wheel->setPosition(beam.end); + bal_wheel->setScale(bw_params.scale); + bal_wheel->setRotation(up_vec.getHorizontalAngle()); + + material.MaterialType = video::EMT_LIGHTMAP_LIGHTING; + material.AntiAliasing = true; + material.Lighting = true; + + bal_wheel->getMaterial(0) = material; + bal_wheel->setMaterialFlag(video::EMF_NORMALIZE_NORMALS, true); + bal_wheel->setMaterialTexture(0, vdrv->getTexture(fsys->getAbsolutePath("./textures/wood5.jpeg"))); + bal_wheel->addShadowVolumeSceneNode(0, 12, true); + smgr->setShadowColor(video::SColor(150,0,0,0)); + platform->setPosition(core::vector3df(bw_params.pos.X, bw_params.pos.Y - bw_params.original_len - bw_params.radius - 3.f, 0.f)); + light->setPosition(core::vector3df(bw_params.pos.X + 10.f, bw_params.pos.Y, 0.f)); + camera->setPosition(core::vector3df(0.0f, -bw_params.original_len/2, bw_params.original_len*2)); + + cur_vel = core::vector3df(bw_params.original_vel); + cur_rel_pos = core::vector3df(end); cur_push_force = core::vector3df(0.0f, 0.0f, 0.0f); - if (receiver.isEscPressed) - { - device->closeDevice(); - /*createExitDialogueWindow(device->getGUIEnvironment()); - receiver.isEscPressed = false;*/ - } - else if (receiver.wheel != 0.0f) - { - core::vector3df cur_target = camera->getTarget(); - const core::vector3df& cur_pos = camera->getPosition(); - core::vector3df rel_target = cur_target - cur_pos; - - s16 sign = receiver.wheel/std::fabs(receiver.wheel); - - if (sign > 0) - std::cout << "Camera has offset forward" << std::endl; - else if (sign < 0) - std::cout << "Camera has offset backward" << std::endl; - - core::vector3df cam_offset = rel_target.normalize()*(f32)sign*c_params.offset; - - core::vector3df new_pos = cur_pos + cam_offset; - camera->setPosition(new_pos); - - receiver.wheel = 0.0f; - } - else if (receiver.isMiddleMousePressed && receiver.mousePosDelta != core::vector3df(0.0f, 0.0f, 0.0f)) - { - const core::vector3df& cur_pos = camera->getPosition(); - core::vector3df cur_target = camera->getTarget(); - core::vector3df new_rot = (cur_pos - cur_target).getHorizontalAngle() + core::vector3df(-receiver.mousePosDelta.Y/10.0f, -receiver.mousePosDelta.X/10.0f, 0.0f); - - core::matrix4 matrix; - matrix.setRotationDegrees(new_rot); - - core::vector3df original_camera_pos(0.0f, 0.0f, (cur_target - cur_pos).getLength()); - matrix.rotateVect(original_camera_pos); - - camera->setPosition(cur_target + original_camera_pos); - receiver.mousePosDelta = core::vector3df(0.0f, 0.0f, 0.0f); - } - else if (receiver.isUpPressed) - { - core::vector3df nmlzed_target = -camera->getPosition(); - nmlzed_target.normalize(); - - cur_push_force = nmlzed_target * bw_params.push_force; - receiver.isUpPressed = false; - } - else if (receiver.isDownPressed) - { - core::vector3df nmlzed_target = -camera->getPosition(); - nmlzed_target.normalize(); - - cur_push_force = nmlzed_target * -bw_params.push_force; - receiver.isDownPressed = false; - } - else if (receiver.isLeftPressed) - { - core::vector3df nmlzed_target = -camera->getPosition(); - nmlzed_target.normalize(); - nmlzed_target.rotateXZBy(90.0f); - - cur_push_force = nmlzed_target * bw_params.push_force; - receiver.isLeftPressed = false; - } - else if (receiver.isRightPressed) - { - core::vector3df nmlzed_target = -camera->getPosition(); - nmlzed_target.normalize(); - nmlzed_target.rotateXZBy(-90.0f); - - cur_push_force = nmlzed_target * bw_params.push_force; - receiver.isRightPressed = false; - } + + receiver.isResetButtonClicked = false; + } /*else if (receiver.isMouseWheelChanged) { core::vector3df cur_target = camera->getTarget(); @@ -373,53 +682,66 @@ int main() receiver.pressed_btn = nullptr; }*/ - core::vector3df elastic_force = (cur_rel_pos.getLength() > bw_params.original_len)? cur_rel_pos/(-cur_rel_pos.getLength()) * bw_params.elasticity * (cur_rel_pos.getLength() - bw_params.original_len) : + core::vector3df elastic_force = (cur_rel_pos.getLength() > bw_params.original_len)? cur_rel_pos/(-cur_rel_pos.getLength()) * bw_params.elasticity * (cur_rel_pos.getLength() - bw_params.original_len) : core::vector3df(0.0f, 0.0f, 0.0f); - core::vector3df friction_force = -cur_vel*bw_params.friction_coef; - f32 pos_len_sqr = cur_rel_pos.dotProduct(cur_rel_pos); - core::vector3df string_friction_force; - if (std::fabs(pos_len_sqr) > 0.01) - string_friction_force = -(cur_vel.dotProduct(cur_rel_pos))/(pos_len_sqr)*cur_rel_pos*bw_params.string_friction_coef; - core::vector3df res_force = core::vector3df(0.0f, bw_params.gravity*bw_params.mass, 0.0f) + elastic_force + friction_force + string_friction_force + cur_push_force; + core::vector3df friction_force = -cur_vel*bw_params.friction_coef; + f32 pos_len_sqr = cur_rel_pos.dotProduct(cur_rel_pos); + core::vector3df string_friction_force; + if (std::fabs(pos_len_sqr) > 0.01) + string_friction_force = -(cur_vel.dotProduct(cur_rel_pos))/(pos_len_sqr)*cur_rel_pos*bw_params.string_friction_coef; + core::vector3df res_force = core::vector3df(0.0f, bw_params.gravity*bw_params.mass, 0.0f) + elastic_force + friction_force + string_friction_force + cur_push_force; - cur_rel_pos += cur_vel*0.001; + cur_rel_pos += cur_vel*0.01; - cur_vel += res_force*0.001/bw_params.mass; + cur_vel += res_force*0.01/bw_params.mass; - bal_wheel->setPosition(bw_params.pos + cur_rel_pos); + bal_wheel->setPosition(bw_params.pos + cur_rel_pos); - up_vec = (cur_rel_pos*-1.0f).normalize(); - bal_wheel->setRotation(up_vec.getHorizontalAngle()); + up_vec = (cur_rel_pos*-1.0f).normalize(); + bal_wheel->setRotation(up_vec.getHorizontalAngle()); - vdrv->beginScene(true, true, video::SColor(0, 0, 0, 255)); - vdrv->setTransform(video::ETS_WORLD, core::IdentityMatrix); + vdrv->beginScene(true, true, video::SColor(0, 0, 0, 255)); + vdrv->setTransform(video::ETS_WORLD, core::IdentityMatrix); - video::SMaterial line_material; - line_material.setFlag(video::EMF_NORMALIZE_NORMALS, true); - line_material.Thickness = 1.5; - line_material.Lighting = true; - line_material.MaterialType = video::EMT_LIGHTMAP_LIGHTING; - line_material.NormalizeNormals = true; + video::SMaterial line_material; + line_material.setFlag(video::EMF_NORMALIZE_NORMALS, true); + line_material.Thickness = 1.5; + line_material.Lighting = true; + line_material.MaterialType = video::EMT_LIGHTMAP_LIGHTING; + line_material.NormalizeNormals = true; - vdrv->setMaterial(line_material); - vdrv->draw3DLine(core::vector3df(-50.0f, 0.0f, 0.0f), core::vector3df(50.0f, 0.0f, 0.0f), video::SColor(202,0,16,255));//video::SColor(252, 0, 20, 255)); - vdrv->draw3DLine(core::vector3df(0.0f, -50.0f, 0.0f), core::vector3df(0.0f, 50.0f, 0.0f), video::SColor(0,126,0,255));//video::SColor(10, 0, 234, 255)); - vdrv->draw3DLine(core::vector3df(0.0f, 0.0f, -50.0f), core::vector3df(0.0f, 0.0f, 50.0f), video::SColor(0,0,146,255));//video::SColor(0, 217, 0, 255)); + vdrv->setMaterial(line_material); + vdrv->draw3DLine(core::vector3df(-10.0f, 0.0f, 0.0f), core::vector3df(10.0f, 0.0f, 0.0f), video::SColor(202,0,16,255));//video::SColor(252, 0, 20, 255)); + vdrv->draw3DLine(core::vector3df(0.0f, 0.0f, 0.0f), core::vector3df(0.0f, 10.0f, 0.0f), video::SColor(0,126,0,255));//video::SColor(10, 0, 234, 255)); + vdrv->draw3DLine(core::vector3df(0.0f, 0.0f, -10.0f), core::vector3df(0.0f, 0.0f, 10.0f), video::SColor(0,0,146,255));//video::SColor(0, 217, 0, 255)); - vdrv->draw3DLine(beam.start, bw_params.pos + cur_rel_pos, video::SColor(255, 255, 255, 255)); - smgr->drawAll(); - env->drawAll(); - vdrv->endScene(); + vdrv->draw3DLine(beam.start, bw_params.pos + cur_rel_pos, video::SColor(255, 255, 255, 255)); + + smgr->drawAll(); + gui::IGUISkin* skin = env->getSkin(); + skin->draw2DRectangle(0, skin->getColor(gui::EGDC_3D_FACE), core::recti( + core::vector2di(wnd_size.Width-wnd_size.Width/4, 0), + core::dimension2di(wnd_size.Width/4, wnd_size.Height) + )); + env->drawAll(); + vdrv->endScene(); - core::stringw caption = L"[Irrlicht Engine] Balance Wheel Demo ["; - caption += vdrv->getName(); - caption += "; FPS: "; - caption += vdrv->getFPS(); - caption += "]"; + core::stringw caption = L"[Irrlicht Engine] Balance Wheel Demo ["; + caption += vdrv->getName(); + caption += "]"; + /*caption += "; FPS: "; + caption += vdrv->getFPS(); + caption += "]";*/ - device->setWindowCaption(caption.c_str()); + device->setWindowCaption(caption.c_str()); + + while (device->getTimer()->getTime() < needTime) { + device->getTimer()->tick(); + device->yield(); } + needTime += 10; + std::cout << needTime << std::endl; } device->drop();