diff --git a/bin/assets/ui/breeze.css b/bin/assets/ui/breeze.css index c20684374..71541faf4 100644 --- a/bin/assets/ui/breeze.css +++ b/bin/assets/ui/breeze.css @@ -736,6 +736,48 @@ Splitter::separator:hover { background-color: var(--primary); } +table::header { + background-color: var(--back); +} + +table::header::column { + background-color: var(--back); + border-right-color: var(--tab-line); + border-right-width: 1dp; + border-bottom-color: var(--tab-line); + border-bottom-width: 1dp; + border-type: inside; + color: var(--font); + padding: 2dp 6dp 2dp 6dp; + transition: all 0.125s; + text-align: left; +} + +table::header::column:hover { + background-color: var(--tab-hover); +} + +table::row { + background-color: var(--list-back); + transition: all 0.125s; +} + +table::row:hover { + background-color: var(--back); +} + +table::row:selected { + background-color: var(--primary); +} + +TreeView { + background-color: var(--list-back); +} + +TreeView > ScrollBar { + background-color: var(--back); +} + .appbackground { background-color: var(--back); } diff --git a/include/eepp/scene/event.hpp b/include/eepp/scene/event.hpp index 14b3c3b7d..53dff7265 100644 --- a/include/eepp/scene/event.hpp +++ b/include/eepp/scene/event.hpp @@ -73,6 +73,7 @@ class EE_API Event { OnSelectionChanged, OnNodeDropped, OnSave, + OnModelEvent, UserEvent, NoEvent = eeINDEX_NOT_FOUND }; diff --git a/include/eepp/scene/node.hpp b/include/eepp/scene/node.hpp index d363f2178..9f4eb5b13 100644 --- a/include/eepp/scene/node.hpp +++ b/include/eepp/scene/node.hpp @@ -390,6 +390,10 @@ class EE_API Node : public Transformable { void forEachNode( std::function func ); + void forEachChild( std::function func ); + + virtual void nodeDraw(); + protected: typedef std::map> EventsMap; friend class EventDispatcher; @@ -488,8 +492,6 @@ class EE_API Node : public Transformable { virtual Uint32 onFocusLoss(); - virtual void internalDraw(); - void clipEnd(); void updateScreenPos(); diff --git a/include/eepp/ui/abstract/model.hpp b/include/eepp/ui/abstract/model.hpp new file mode 100644 index 000000000..65ae66ce5 --- /dev/null +++ b/include/eepp/ui/abstract/model.hpp @@ -0,0 +1,184 @@ +#ifndef EE_UI_MODEL_MODEL_HPP +#define EE_UI_MODEL_MODEL_HPP + +#include +#include +#include +#include +#include +#include +#include + +using namespace EE::Graphics; +using namespace EE::Math; + +namespace EE { namespace UI { namespace Abstract { + +enum class SortOrder { None, Ascending, Descending }; + +class UIAbstractView; + +class Variant { + public: + enum class Type { + Invalid, + DataPtr, + String, + Bool, + Float, + Int, + Int64, + Drawable, + Vector2f, + Rectf, + cstr + }; + Variant() : mType( Type::Invalid ) {} + explicit Variant( const std::string& string ) : mType( Type::String ) { + mValue.asString = eeNew( std::string, ( string ) ); + } + Variant( Drawable* drawable, bool ownDrawable = false ) : mType( Type::Drawable ) { + mValue.asDrawable = drawable; + mOwnsObject = ownDrawable; + } + Variant( const Vector2f& v ) : mType( Type::Vector2f ) { + mValue.asVector2f = eeNew( Vector2f, ( v ) ); + } + Variant( void* data ) : mType( Type::DataPtr ) { mValue.asDataPtr = data; } + Variant( const Rectf& r ) : mType( Type::Rectf ) { mValue.asRectf = eeNew( Rectf, ( r ) ); } + Variant( bool val ) : mType( Type::Bool ) { mValue.asBool = val; } + Variant( const Float& val ) : mType( Type::Float ) { mValue.asFloat = val; } + Variant( const int& val ) : mType( Type::Int ) { mValue.asInt = val; } + Variant( const Int64& val ) : mType( Type::Int64 ) { mValue.asInt64 = val; } + explicit Variant( const char* data ) : mType( Type::cstr ) { mValue.asCStr = data; } + ~Variant() { reset(); } + const std::string& asString() const { return *mValue.asString; } + Drawable* asDrawable() const { return mValue.asDrawable; } + const bool& asBool() const { return mValue.asBool; } + const Float& asFloat() const { return mValue.asFloat; } + const int& asInt() const { return mValue.asInt; } + const Int64& asInt64() const { return mValue.asInt64; } + const Vector2f& asVector2f() const { return *mValue.asVector2f; } + const Rectf& asRectf() const { return *mValue.asRectf; } + const char* asCStr() const { return mValue.asCStr; } + bool is( const Type& type ) const { return type == mType; } + void reset() { + switch ( mType ) { + case Type::String: + eeSAFE_DELETE( mValue.asString ); + break; + case Type::Drawable: + if ( mOwnsObject ) + eeSAFE_DELETE( mValue.asDrawable ); + break; + case Type::Vector2f: + eeSAFE_DELETE( mValue.asVector2f ); + break; + case Type::Rectf: + eeSAFE_DELETE( mValue.asRectf ); + break; + default: + break; + } + mType = Type::Invalid; + } + bool isValid() { return mType != Type::Invalid; } + + private: + union { + void* asDataPtr; + Drawable* asDrawable; + std::string* asString; + bool asBool; + Float asFloat; + int asInt; + Int64 asInt64; + Vector2f* asVector2f; + Rectf* asRectf; + const char* asCStr; + } mValue; + Type mType; + bool mOwnsObject{false}; +}; + +class EE_API Model { + public: + enum UpdateFlag { + DontInvalidateIndexes = 0, + InvalidateAllIndexes = 1 << 0, + }; + + enum class Role { + Display, + Icon, + }; + + virtual ~Model(){}; + + virtual size_t rowCount( const ModelIndex& = ModelIndex() ) const = 0; + + virtual size_t columnCount( const ModelIndex& = ModelIndex() ) const = 0; + + virtual std::string columnName( const size_t& /*column*/ ) const { return {}; } + + virtual Variant data( const ModelIndex&, Role = Role::Display ) const = 0; + + virtual void update() = 0; + + virtual ModelIndex parentIndex( const ModelIndex& ) const { return {}; } + + virtual ModelIndex index( int row, int column = 0, const ModelIndex& = ModelIndex() ) const { + return createIndex( row, column ); + } + + virtual ModelIndex sibling( int row, int column, const ModelIndex& parent ) const; + + virtual void setData( const ModelIndex&, const Variant& ) {} + + virtual size_t treeColumn() const { return 0; } + + virtual bool acceptsDrag( const ModelIndex&, const std::string& dataType ); + + virtual bool isColumnSortable( const size_t& /*columnIndex*/ ) const { return true; } + + virtual std::string dragDataType() const { return {}; } + + bool isValid( const ModelIndex& index ) const { + auto parentIndex = this->parentIndex( index ); + return index.row() >= 0 && index.row() < (Int64)rowCount( parentIndex ) && + index.column() >= 0 && index.column() < (Int64)columnCount( parentIndex ); + } + + virtual int keyColumn() const { return -1; } + + virtual SortOrder sortOrder() const { return SortOrder::None; } + + virtual void setKeyColumnAndSortOrder( const size_t& /*column*/, const SortOrder& /*order*/ ) {} + + void registerView( UIAbstractView* ); + + void unregisterView( UIAbstractView* ); + + void setOnUpdate( const std::function& onUpdate ); + + protected: + Model(){}; + + void forEachView( std::function ); + + void onModelUpdate( unsigned flags = UpdateFlag::InvalidateAllIndexes ); + + ModelIndex createIndex( int row, int column, const void* data = nullptr ) const; + + private: + std::unordered_set mViews; + std::function mOnUpdate; +}; + +inline ModelIndex ModelIndex::parent() const { + return mModel ? mModel->parentIndex( *this ) : ModelIndex(); +} + +}}} // namespace EE::UI::Abstract + +#endif // EE_UI_MODEL_MODEL_HPP diff --git a/include/eepp/ui/abstract/modeleditingdelegate.hpp b/include/eepp/ui/abstract/modeleditingdelegate.hpp new file mode 100644 index 000000000..d95e773ec --- /dev/null +++ b/include/eepp/ui/abstract/modeleditingdelegate.hpp @@ -0,0 +1,48 @@ +#ifndef EE_UI_MODELEDITINGDELEGATE_HPP +#define EE_UI_MODELEDITINGDELEGATE_HPP + +#include +#include +#include + +namespace EE { namespace UI { namespace Abstract { + +class EE_API ModelEditingDelegate { + public: + virtual ~ModelEditingDelegate() {} + + void bind( std::shared_ptr model, const ModelIndex& index ) { + if ( mModel.get() == model.get() && mIndex == index ) + return; + mModel = model; + mIndex = index; + mWidget = createWidget(); + } + + UIWidget* getWidget() { return mWidget; } + UIWidget* getWidget() const { return mWidget; } + + std::function onCommit; + + virtual Variant getValue() const = 0; + virtual void setValue( const Variant& ) = 0; + virtual void willBeginEditing() {} + + protected: + ModelEditingDelegate() {} + + virtual UIWidget* createWidget() = 0; + + void commit() { + if ( onCommit ) + onCommit(); + } + + std::shared_ptr mModel; + ModelIndex mIndex; + UIWidget* mWidget; +}; + +}}} // namespace EE::UI::Abstract + +#endif // EE_UI_MODELEDITINGDELEGATE_HPP diff --git a/include/eepp/ui/abstract/modelindex.hpp b/include/eepp/ui/abstract/modelindex.hpp new file mode 100644 index 000000000..36670bb86 --- /dev/null +++ b/include/eepp/ui/abstract/modelindex.hpp @@ -0,0 +1,50 @@ +#ifndef EE_UI_MODEL_MODELINDEX_HPP +#define EE_UI_MODEL_MODELINDEX_HPP + +#include + +namespace EE { namespace UI { namespace Abstract { + +class Model; + +class EE_API ModelIndex { + public: + ModelIndex() {} + + bool isValid() const { return mRow != -1 && mColumn != -1; } + + const Int64& row() const { return mRow; } + + const Int64& column() const { return mColumn; } + + void* data() const { return mData; } + + ModelIndex parent() const; + + bool hasParent() const { return parent().isValid(); } + + bool operator<( const ModelIndex& other ) const { + return mRow != other.mRow || mColumn != other.mColumn; + } + + bool operator==( const ModelIndex& other ) const { + return mModel == other.mModel && mRow == other.mRow && mColumn == other.mColumn && + mData == other.mData; + } + + bool operator!=( const ModelIndex& other ) const { return !( *this == other ); } + + protected: + friend class Model; + const Model* mModel{nullptr}; + Int64 mRow{-1}; + Int64 mColumn{-1}; + void* mData{nullptr}; + + ModelIndex( const Model& model, int row, int column, void* internalData ) : + mModel( &model ), mRow( row ), mColumn( column ), mData( internalData ) {} +}; + +}}} // namespace EE::UI::Abstract + +#endif // EE_UI_MODEL_MODELINDEX_HPP diff --git a/include/eepp/ui/abstract/modelselection.hpp b/include/eepp/ui/abstract/modelselection.hpp new file mode 100644 index 000000000..2153f64a7 --- /dev/null +++ b/include/eepp/ui/abstract/modelselection.hpp @@ -0,0 +1,68 @@ +#ifndef EE_UI_MODEL_MODELSELECTION_HPP +#define EE_UI_MODEL_MODELSELECTION_HPP + +#include +#include +#include +#include + +namespace EE { namespace UI { namespace Abstract { + +class UIAbstractView; + +class EE_API ModelSelection { + public: + ModelSelection( UIAbstractView* view ) : mView( view ) {} + + int size() const { return mIndexes.size(); } + bool isEmpty() const { return mIndexes.empty(); } + bool contains( const ModelIndex& index ) const { + return std::find( mIndexes.begin(), mIndexes.end(), index ) != mIndexes.end(); + } + bool containsRow( int row ) const { + for ( auto& index : mIndexes ) { + if ( index.row() == row ) + return true; + } + return false; + } + + void set( const ModelIndex& ); + void add( const ModelIndex& ); + void toggle( const ModelIndex& ); + bool remove( const ModelIndex& ); + void clear(); + + template void forEachIndex( Callback callback ) { + for ( auto& index : indexes() ) + callback( index ); + } + + template void forEachIndex( Callback callback ) const { + for ( auto& index : indexes() ) + callback( index ); + } + + std::vector indexes() const { + std::vector selectedIndexes; + for ( auto& index : mIndexes ) + selectedIndexes.push_back( index ); + return selectedIndexes; + } + + ModelIndex first() const { + if ( mIndexes.empty() ) + return {}; + return *mIndexes.begin(); + } + + void removeMatching( std::function ); + + protected: + UIAbstractView* mView; + std::vector mIndexes; +}; + +}}} // namespace EE::UI::Abstract + +#endif // EE_UI_MODEL_MODELSELECTION_HPP diff --git a/include/eepp/ui/abstract/uiabstracttableview.hpp b/include/eepp/ui/abstract/uiabstracttableview.hpp new file mode 100644 index 000000000..819890220 --- /dev/null +++ b/include/eepp/ui/abstract/uiabstracttableview.hpp @@ -0,0 +1,82 @@ +#ifndef EE_UI_UIABSTRACTTABLEVIEW_HPP +#define EE_UI_UIABSTRACTTABLEVIEW_HPP + +#include +#include +#include + +using namespace EE::Math; + +namespace EE { namespace UI { +class UIPushButton; +class UILinearLayout; +}} // namespace EE::UI + +namespace EE { namespace UI { namespace Abstract { + +class EE_API UIAbstractTableView : public UIAbstractView { + public: + Uint32 getType() const; + + bool isType( const Uint32& type ) const; + + virtual Float getRowHeight() const { return getHeaderHeight(); } + + virtual Float getHeaderHeight() const; + + virtual Sizef getContentSize() const; + + bool areHeadersVisible() const; + + void setHeadersVisible( bool visible ); + + bool isColumnHidden( const size_t& column ) const; + + void setColumnHidden( const size_t& column, bool hidden ); + + virtual void selectAll(); + + const Float& getDragBorderDistance() const; + + void setDragBorderDistance( const Float& dragBorderDistance ); + + Vector2f getColumnPosition( const size_t& index ); + + protected: + friend class EE::UI::UITableHeaderColumn; + + virtual ~UIAbstractTableView(); + + UIAbstractTableView( const std::string& tag ); + + struct ColumnData { + Float width{0}; + bool visible{true}; + UIPushButton* widget{nullptr}; + }; + + ColumnData& columnData( const size_t& column ) const; + + mutable std::vector mColumn; + + virtual size_t getItemCount() const; + + virtual void onModelUpdate( unsigned flags ); + + virtual void createOrUpdateColumns(); + + virtual void onSizeChange(); + + virtual void onColumnSizeChange( const size_t& colIndex ); + + virtual void onColumnResizeToContent( const size_t& colIndex ); + + void updateHeaderSize(); + + UILinearLayout* mHeader; + Float mDragBorderDistance{8}; +}; + +}}} // namespace EE::UI::Abstract + +#endif // EE_UI_UIABSTRACTTABLEVIEW_HPP diff --git a/include/eepp/ui/abstract/uiabstractview.hpp b/include/eepp/ui/abstract/uiabstractview.hpp new file mode 100644 index 000000000..5739af215 --- /dev/null +++ b/include/eepp/ui/abstract/uiabstractview.hpp @@ -0,0 +1,80 @@ +#ifndef EE_UI_UIABSTRACTVIEW_HPP +#define EE_UI_UIABSTRACTVIEW_HPP + +#include +#include +#include +#include +#include + +namespace EE { namespace UI { namespace Abstract { + +class ModelEvent : public Event { + public: + ModelEvent( Model* model, const ModelIndex& index, Node* node ) : + Event( node, Event::OnModelEvent ), model( model ), index( index ) {} + + const Model* getModel() const { return model; } + + const ModelIndex& getModelIndex() const { return index; } + + protected: + const Model* model; + ModelIndex index; +}; + +class EE_API UIAbstractView : public UIScrollableWidget { + public: + Uint32 getType() const; + + bool isType( const Uint32& type ) const; + + void setModel( std::shared_ptr ); + + Model* getModel() { return mModel.get(); } + + const Model* getModel() const { return mModel.get(); } + + ModelSelection& getSelection() { return mSelection; } + + const ModelSelection& getSelection() const { return mSelection; } + + virtual void selectAll() = 0; + + void notifySelectionChange(); + + std::function getOnSelectionChange() const; + + void setOnSelectionChange( const std::function& onSelectionChange ); + + std::function getOnSelection() const; + + void setOnSelection( const std::function& onSelection ); + + protected: + friend class Model; + + virtual void onModelUpdate( unsigned flags ); + + virtual void onModelSelectionChange(); + + UIAbstractView( const std::string& tag ); + + virtual ~UIAbstractView(); + + bool mEditable{false}; + ModelIndex mEditIndex; + UIWidget* mEditWidget; + Rect mEditWidgetContentRect; + + std::shared_ptr mModel; + std::unique_ptr mEditingDelegate; + ModelSelection mSelection; + + std::function mOnSelectionChange; + std::function mOnSelection; +}; + +}}} // namespace EE::UI::Abstract + +#endif // EE_UI_UIABSTRACTVIEW_HPP diff --git a/include/eepp/ui/uihelper.hpp b/include/eepp/ui/uihelper.hpp index eb2d77298..323602073 100644 --- a/include/eepp/ui/uihelper.hpp +++ b/include/eepp/ui/uihelper.hpp @@ -89,6 +89,10 @@ enum UINodeType { UI_TYPE_ITEMCONTAINER, UI_TYPE_CODEEDITOR, UI_TYPE_SPLITTER, + UI_TYPE_ABSTRACTVIEW, + UI_TYPE_ABSTRACTTABLEVIEW, + UI_TYPE_TREEVIEW, + UI_TYPE_SCROLLABLEWIDGET, UI_TYPE_USER = 10000 }; diff --git a/include/eepp/ui/uiitemcontainer.hpp b/include/eepp/ui/uiitemcontainer.hpp index 4871319b9..a4ee49afe 100644 --- a/include/eepp/ui/uiitemcontainer.hpp +++ b/include/eepp/ui/uiitemcontainer.hpp @@ -59,7 +59,7 @@ template void UIItemContainer::drawChilds() { if ( tParent->mItems.size() ) { for ( Uint32 i = tParent->mVisibleFirst; i <= tParent->mVisibleLast; i++ ) if ( NULL != tParent->mItems[i] ) - tParent->mItems[i]->internalDraw(); + tParent->mItems[i]->nodeDraw(); } } diff --git a/include/eepp/ui/uilinearlayout.hpp b/include/eepp/ui/uilinearlayout.hpp index a863c4df0..919fcba97 100644 --- a/include/eepp/ui/uilinearlayout.hpp +++ b/include/eepp/ui/uilinearlayout.hpp @@ -7,6 +7,8 @@ namespace EE { namespace UI { class EE_API UILinearLayout : public UILayout { public: + static UILinearLayout* NewWithTag( const std::string& tag, const UIOrientation& orientation ); + static UILinearLayout* New(); static UILinearLayout* NewVertical(); diff --git a/include/eepp/ui/uimenuitem.hpp b/include/eepp/ui/uimenuitem.hpp index b6bc86573..7af52df05 100644 --- a/include/eepp/ui/uimenuitem.hpp +++ b/include/eepp/ui/uimenuitem.hpp @@ -36,7 +36,7 @@ class EE_API UIMenuItem : public UIPushButton { virtual Uint32 onMouseClick( const Vector2i& pos, const Uint32& flags ); - virtual UIWidget* getExtraInnerWidget(); + virtual UIWidget* getExtraInnerWidget() const; void createShortcutView(); }; diff --git a/include/eepp/ui/uimenusubmenu.hpp b/include/eepp/ui/uimenusubmenu.hpp index ab5722c78..37ddbb0cd 100644 --- a/include/eepp/ui/uimenusubmenu.hpp +++ b/include/eepp/ui/uimenusubmenu.hpp @@ -51,7 +51,7 @@ class EE_API UIMenuSubMenu : public UIMenuItem { virtual void onAlphaChange(); - virtual UIWidget* getExtraInnerWidget(); + virtual UIWidget* getExtraInnerWidget() const; void onSubMenuFocusLoss( const Event* Event ); diff --git a/include/eepp/ui/uinode.hpp b/include/eepp/ui/uinode.hpp index 213565e67..06e2cb263 100644 --- a/include/eepp/ui/uinode.hpp +++ b/include/eepp/ui/uinode.hpp @@ -257,6 +257,8 @@ class EE_API UINode : public Node { Rectf getLocalDpBounds() const; + virtual void nodeDraw(); + protected: Vector2f mDpPos; Sizef mDpSize; @@ -318,8 +320,6 @@ class EE_API UINode : public Node { void checkClose(); - virtual void internalDraw(); - virtual void onWidgetFocusLoss(); void writeFlag( const Uint32& Flag, const Uint32& Val ); diff --git a/include/eepp/ui/uipushbutton.hpp b/include/eepp/ui/uipushbutton.hpp index 4f0df8af2..99a7b4ccf 100644 --- a/include/eepp/ui/uipushbutton.hpp +++ b/include/eepp/ui/uipushbutton.hpp @@ -11,6 +11,8 @@ class EE_API UIPushButton : public UIWidget { public: static UIPushButton* New(); + static UIPushButton* NewWithTag( const std::string& tag ); + UIPushButton(); virtual ~UIPushButton(); @@ -40,6 +42,10 @@ class EE_API UIPushButton : public UIWidget { virtual std::string getPropertyString( const PropertyDefinition* propertyDef, const Uint32& propertyIndex = 0 ); + void setTextAlign( const Uint32& align ); + + virtual Sizef getContentSize() const; + protected: UIImage* mIcon; UITextView* mTextBox; @@ -65,7 +71,7 @@ class EE_API UIPushButton : public UIWidget { virtual Uint32 onKeyUp( const KeyEvent& Event ); - virtual UIWidget* getExtraInnerWidget(); + virtual UIWidget* getExtraInnerWidget() const; }; }} // namespace EE::UI diff --git a/include/eepp/ui/uiscrollablewidget.hpp b/include/eepp/ui/uiscrollablewidget.hpp new file mode 100644 index 000000000..76e40860e --- /dev/null +++ b/include/eepp/ui/uiscrollablewidget.hpp @@ -0,0 +1,80 @@ +#ifndef EE_UI_UISCROLLABLEWIDGET_HPP +#define EE_UI_UISCROLLABLEWIDGET_HPP + +#include + +namespace EE { namespace UI { + +class UIScrollBar; + +class EE_API UIScrollableWidget : public UIWidget { + public: + enum ScrollViewType { Inclusive, Exclusive }; + + virtual Uint32 getType() const; + + virtual bool isType( const Uint32& type ) const; + + void setVerticalScrollMode( const ScrollBarMode& Mode ); + + const ScrollBarMode& getVerticalScrollMode(); + + void setHorizontalScrollMode( const ScrollBarMode& Mode ); + + const ScrollBarMode& getHorizontalScrollMode(); + + const ScrollViewType& getViewType() const; + + void setViewType( const ScrollViewType& viewType ); + + UIScrollBar* getVerticalScrollBar() const; + + UIScrollBar* getHorizontalScrollBar() const; + + virtual bool applyProperty( const StyleSheetProperty& attribute ); + + virtual std::string getPropertyString( const PropertyDefinition* propertyDef, + const Uint32& propertyIndex = 0 ); + + virtual Sizef getContentSize() const = 0; + + void scrollToTop(); + + void scrollToBottom(); + + Sizef getScrollableArea() const; + + Sizef getVisibleArea() const; + + protected: + ScrollViewType mViewType; + ScrollBarMode mVScrollMode; + ScrollBarMode mHScrollMode; + UIScrollBar* mVScroll; + UIScrollBar* mHScroll; + Uint32 mSizeChangeCb; + Uint32 mPosChangeCb; + Vector2f mScrollOffset; + + UIScrollableWidget( const std::string& tag ); + + virtual Uint32 onMessage( const NodeMessage* Msg ); + + virtual void onSizeChange(); + + virtual void onAlphaChange(); + + virtual void onPaddingChange(); + + void onValueChangeCb( const Event* Event ); + + virtual void onContentSizeChange(); + + virtual void updateScroll(); + + virtual void onScrollChange(); +}; + +}} // namespace EE::UI + +#endif // EE_UI_UISCROLLABLEWIDGET_HPP diff --git a/include/eepp/ui/uitab.hpp b/include/eepp/ui/uitab.hpp index 208acd6e9..ac33626cb 100644 --- a/include/eepp/ui/uitab.hpp +++ b/include/eepp/ui/uitab.hpp @@ -62,7 +62,7 @@ class EE_API UITab : public UISelectButton { virtual void onSizeChange(); - virtual UIWidget* getExtraInnerWidget(); + virtual UIWidget* getExtraInnerWidget() const; void setOwnedNode(); diff --git a/include/eepp/ui/uitableheadercolumn.hpp b/include/eepp/ui/uitableheadercolumn.hpp new file mode 100644 index 000000000..3c83c159e --- /dev/null +++ b/include/eepp/ui/uitableheadercolumn.hpp @@ -0,0 +1,38 @@ +#ifndef EE_UI_UITABLEHEADERCOLUMN_HPP +#define EE_UI_UITABLEHEADERCOLUMN_HPP + +#include + +namespace EE { namespace UI { + +namespace Abstract { +class UIAbstractTableView; +} +using namespace Abstract; + +class UITableHeaderColumn : public UIPushButton { + public: + UITableHeaderColumn( UIAbstractTableView* view, const size_t& colIndex ); + + protected: + UIAbstractTableView* mView; + size_t mColIndex; + + Uint32 onCalculateDrag( const Vector2f& position, const Uint32& flags ); + + Uint32 onMouseDown( const Vector2i& position, const Uint32& flags ); + + Uint32 onDrag( const Vector2f& position, const Uint32&, const Sizef& dragDiff ); + + Uint32 onMouseLeave( const Vector2i& position, const Uint32& flags ); + + Uint32 onMouseMove( const Vector2i& position, const Uint32& flags ); + + Uint32 onMouseDoubleClick( const Vector2i& position, const Uint32& flags ); + + Uint32 onDragStop( const Vector2i& pos, const Uint32& flags ); +}; + +}} + +#endif // EE_UI_UITABLEHEADERCOLUMN_HPP diff --git a/include/eepp/ui/uitextview.hpp b/include/eepp/ui/uitextview.hpp index 94d5eda71..de38eacc1 100644 --- a/include/eepp/ui/uitextview.hpp +++ b/include/eepp/ui/uitextview.hpp @@ -86,6 +86,7 @@ class EE_API UITextView : public UIWidget { virtual std::string getPropertyString( const PropertyDefinition* propertyDef, const Uint32& propertyIndex = 0 ); + void setTextAlign( const Uint32& align ); protected: Text* mTextCache; String mString; diff --git a/include/eepp/ui/uitreeview.hpp b/include/eepp/ui/uitreeview.hpp new file mode 100644 index 000000000..847db8964 --- /dev/null +++ b/include/eepp/ui/uitreeview.hpp @@ -0,0 +1,95 @@ +#ifndef EE_UI_UITREEVIEW_HPP +#define EE_UI_UITREEVIEW_HPP + +#include +#include +#include + +using namespace EE::UI::Abstract; + +namespace EE { namespace UI { + +class UITableRow; + +class EE_API UITreeView : public UIAbstractTableView { + public: + static UITreeView* New(); + + Uint32 getType() const; + + bool isType( const Uint32& type ) const; + + const Float& getIndentWidth() const; + + void setIndentWidth( const Float& indentWidth ); + + virtual Sizef getContentSize() const; + + virtual void drawChilds(); + + virtual Node* overFind( const Vector2f& point ); + + bool isExpanded( const ModelIndex& index ) const; + + Drawable* getExpandIcon() const; + + void setExpandedIcon( Drawable* expandIcon ); + + Drawable* getContractIcon() const; + + void setContractedIcon( Drawable* contractIcon ); + + protected: + enum class IterationDecision { + Continue, + Break, + Stop, + }; + + Float mIndentWidth; + Sizef mContentSize; + Drawable* mExpandIcon{nullptr}; + Drawable* mContractIcon{nullptr}; + + UITreeView(); + + virtual void createOrUpdateColumns(); + + struct MetadataForIndex; + + template void traverseTree( Callback ) const; + + mutable std::map> mViewMetadata; + mutable std::vector> mWidgets; + mutable std::vector mRows; + + virtual size_t getItemCount() const; + + UITreeView::MetadataForIndex& getIndexMetadata( const ModelIndex& index ) const; + + virtual void onColumnSizeChange( const size_t& colIndex ); + + virtual UIWidget* updateCell( const int& rowIndex, const ModelIndex& index, const size_t& col, + const size_t& indentLevel, const Float& yOffset ); + + virtual UIWidget* updateRow( const int& rowIndex, const ModelIndex& index, + const Float& yOffset ); + + virtual UIWidget* createCell( UIWidget* rowWidget, const ModelIndex& index, const size_t& col ); + + virtual void onScrollChange(); + + virtual void onColumnResizeToContent( const size_t& colIndex ); + + virtual void onModelSelectionChange(); + + virtual Uint32 onKeyDown( const KeyEvent& event ); + + virtual void onOpenModelIndex( const ModelIndex& index ); + + void updateContentSize(); +}; + +}} // namespace EE::UI + +#endif // EE_UI_UITREEVIEW_HPP diff --git a/include/eepp/ui/uiwindow.hpp b/include/eepp/ui/uiwindow.hpp index 4460cbdee..90d12940c 100644 --- a/include/eepp/ui/uiwindow.hpp +++ b/include/eepp/ui/uiwindow.hpp @@ -141,7 +141,7 @@ class EE_API UIWindow : public UIWidget { virtual bool applyProperty( const StyleSheetProperty& attribute ); - virtual void internalDraw(); + virtual void nodeDraw(); void invalidate( Node* invalidator ); diff --git a/premake4.lua b/premake4.lua index 595a8ad55..c54c0060e 100644 --- a/premake4.lua +++ b/premake4.lua @@ -433,7 +433,7 @@ function build_link_configuration( package_name, use_ee_icon ) end fix_shared_lib_linking_path( package_name, "libeepp-debug" ) - + if not os.is_real("emscripten") then targetname ( package_name .. "-debug" .. extension ) else @@ -748,7 +748,7 @@ function build_eepp( build_name ) "src/eepp/scene/*.cpp", "src/eepp/scene/actions/*.cpp", "src/eepp/ui/*.cpp", - "src/eepp/ui/actions/*.cpp", + "src/eepp/ui/abstract/*.cpp", "src/eepp/ui/css/*.cpp", "src/eepp/ui/doc/*.cpp", "src/eepp/ui/tools/*.cpp", diff --git a/premake5.lua b/premake5.lua index cebee0a06..484a0abfa 100644 --- a/premake5.lua +++ b/premake5.lua @@ -1,855 +1,855 @@ -newoption { trigger = "with-openssl", description = "Enables OpenSSL support ( and disables mbedtls backend )." } -newoption { trigger = "with-dynamic-freetype", description = "Dynamic link against freetype." } -newoption { trigger = "with-static-eepp", description = "Force to build the demos and tests with eepp compiled statically" } -newoption { trigger = "with-static-backend", description = "It will try to compile the library with a static backend (only for gcc and mingw).\n\t\t\t\tThe backend should be placed in libs/your_platform/libYourBackend.a" } -newoption { trigger = "with-gles2", description = "Compile with GLES2 support" } -newoption { trigger = "with-gles1", description = "Compile with GLES1 support" } -newoption { trigger = "with-mojoal", description = "Compile with mojoAL as OpenAL implementation instead of using openal-soft (requires SDL2 backend)" } -newoption { trigger = "use-frameworks", description = "In macOS it will try to link the external libraries from its frameworks. For example, instead of linking against SDL2 it will link against SDL2.framework." } -newoption { trigger = "windows-vc-build", description = "This is used to build the framework in Visual Studio downloading its external dependencies and making them available to the VS project without having to install them manually." } -newoption { - trigger = "with-backend", - description = "Select the backend to use for window and input handling.\n\t\t\tIf no backend is selected or if the selected is not installed the script will search for a backend present in the system, and will use it.", - allowed = { - { "SDL2", "SDL2" }, - } -} - -function print_table( table_ref ) - for _, value in pairs( table_ref ) do - print(value) - end -end - -function table_length(T) - local count = 0 - for _ in pairs(T) do count = count + 1 end - return count -end - -function multiple_insert( parent_table, insert_table ) - for _, value in pairs( insert_table ) do - table.insert( parent_table, value ) - end -end - -function os_findlib( name ) - if os.istarget("macosx") and ( is_xcode() or _OPTIONS["use-frameworks"] ) then - local path = "/Library/Frameworks/" .. name .. ".framework" - - if os.isdir( path ) then - return path - end - end - - return os.findlib( name ) -end - -function get_backend_link_name( name ) - if os.istarget("macosx") and ( is_xcode() or _OPTIONS["use-frameworks"] ) then - local fname = name .. ".framework" - - if os_findlib( name ) then -- Search for the framework - return fname - end - end - - return name -end - -function string.starts(String,Start) - if ( _ACTION ) then - return string.sub(String,1,string.len(Start))==Start - end - - return false -end - -function is_vs() - return ( string.starts(_ACTION,"vs") ) -end - -function is_xcode() - return ( string.starts(_ACTION,"xcode") ) -end - -function set_kind() - if os.istarget("macosx") then - kind("ConsoleApp") - else - kind("WindowedApp") - end -end - -link_list = { } -os_links = { } -backends = { } -static_backends = { } -backend_selected = false -remote_sdl2_version = "SDL2-2.0.12" -remote_sdl2_devel_src_url = "https://libsdl.org/release/SDL2-2.0.12.zip" -remote_sdl2_devel_vc_url = "https://www.libsdl.org/release/SDL2-devel-2.0.12-VC.zip" - -function incdirs( dirs ) - if is_xcode() then - sysincludedirs { dirs } - end - includedirs { dirs } -end - -function download_and_extract_sdl(sdl_url) - print("Downloading: " .. sdl_url) - local dest_dir = "src/thirdparty/" - local local_file = dest_dir .. remote_sdl2_version .. ".zip" - local result_str, response_code = http.download(sdl_url, local_file) - if response_code == 200 then - print("Downloaded successfully to: " .. local_file) - zip.extract(local_file, dest_dir) - print("Extracted " .. local_file .. " into " .. dest_dir) - else - print("Failed to download: " .. sdl_url) - exit(1) - end -end - -function download_and_extract_dependencies() - if not os.isdir("src/thirdparty/" .. remote_sdl2_version) then - if _OPTIONS["windows-vc-build"] then - download_and_extract_sdl(remote_sdl2_devel_vc_url) - elseif os.istarget("ios") then - download_and_extract_sdl(remote_sdl2_devel_src_url) - end - end -end - -function build_base_configuration( package_name ) - incdirs { "src/thirdparty/zlib" } - - set_ios_config() - set_xcode_config() - - filter "not system:windows" - buildoptions{ "-fPIC" } - - filter "configurations:debug*" - targetname ( package_name .. "-debug" ) - - filter "configurations:release*" - targetname ( package_name ) - - filter "action:not vs*" - cdialect "gnu99" - buildoptions { "-Wall" } - - filter "action:vs*" - incdirs { "src/thirdparty/libzip/vs" } -end - -function build_base_cpp_configuration( package_name ) - if not os.istarget("windows") then - buildoptions{ "-fPIC" } - end - - set_ios_config() - set_xcode_config() - - filter "action:not vs*" - buildoptions { "-Wall" } - - filter "configurations:debug*" - defines { "DEBUG" } - symbols "On" - targetname ( package_name .. "-debug" ) - - filter "configurations:release*" - optimize "Speed" - targetname ( package_name ) -end - -function build_link_configuration( package_name, use_ee_icon ) - incdirs { "include" } - local extension = ""; - - if package_name == "eepp" then - defines { "EE_EXPORTS" } - elseif package_name == "eepp-static" then - defines { "EE_STATIC" } - end - - if package_name ~= "eepp" and package_name ~= "eepp-static" then - if not _OPTIONS["with-static-eepp"] then - links { "eepp-shared" } - else - links { "eepp-static" } - defines { "EE_STATIC" } - add_static_links() - links { link_list } - end - end - - set_ios_config() - set_xcode_config() - - filter { "system:windows", "action:not vs*" } - if ( true == use_ee_icon ) then - linkoptions { "../../bin/assets/icon/ee.res" } - end - - filter { "system:windows", "action:vs*" } - files { "bin/assets/icon/ee.rc", "bin/assets/icon/ee.ico" } - vpaths { ['Resources/*'] = { "ee.rc", "ee.ico" } } - - filter "action:not vs*" - cppdialect "C++14" - buildoptions { "-Wall" } - - filter { "configurations:debug*", "action:not vs*" } - buildoptions{ "-Wno-long-long" } - - filter { "configurations:release*", "action:not vs*" } - buildoptions { "-fno-strict-aliasing -ffast-math" } - - filter { "configurations:release*", "action:not vs*", "system:not macosx" } - buildoptions { "-s" } - - filter "configurations:debug*" - defines { "DEBUG", "EE_DEBUG", "EE_MEMORY_MANAGER" } - targetname ( package_name .. "-debug" .. extension ) - - filter "configurations:release*" - defines { "NDEBUG" } - targetname ( package_name .. extension ) - - filter { "system:windows", "action:not vs*" } - linkoptions { "-static-libgcc", "-static-libstdc++" } - - filter { "system:windows", "action:vs*" } - if table.contains( backends, "SDL2" ) then - links { "SDL2", "SDL2main" } - end - - filter { "options:windows-vc-build", "system:windows", "platforms:x86" } - syslibdirs { "src/thirdparty/" .. remote_sdl2_version .."/lib/x86" } - - filter { "options:windows-vc-build", "system:windows", "platforms:x86_64" } - syslibdirs { "src/thirdparty/" .. remote_sdl2_version .."/lib/x64" } - - filter "system:emscripten" - targetname ( package_name .. extension ) - linkoptions{ "-O2 -s TOTAL_MEMORY=67108864 -s ASM_JS=1 -s VERBOSE=1 -s DISABLE_EXCEPTION_CATCHING=0 -s USE_SDL=2 -s ERROR_ON_UNDEFINED_SYMBOLS=0 -s ERROR_ON_MISSING_LIBRARIES=0 -s FULL_ES3=1 -s \"BINARYEN_TRAP_MODE='clamp'\"" } - buildoptions { "-fno-strict-aliasing -O2 -s USE_SDL=2 -s PRECISE_F32=1 -s ENVIRONMENT=web" } - - if _OPTIONS["with-gles1"] and ( not _OPTIONS["with-gles2"] or _OPTIONS["force-gles1"] ) then - linkoptions{ "-s LEGACY_GL_EMULATION=1" } - end - - if _OPTIONS["with-gles2"] and not _OPTIONS["force-gles1"] then - linkoptions{ "-s FULL_ES2=1" } - end -end - -function generate_os_links() - if os.istarget("linux") then - multiple_insert( os_links, { "rt", "pthread", "X11", "GL", "Xcursor" } ) - - if _OPTIONS["with-static-eepp"] then - table.insert( os_links, "dl" ) - end - elseif os.istarget("windows") then - multiple_insert( os_links, { "opengl32", "glu32", "gdi32", "ws2_32", "winmm", "ole32" } ) - elseif os.istarget("mingw32") then - multiple_insert( os_links, { "opengl32", "glu32", "gdi32", "ws2_32", "winmm", "ole32" } ) - elseif os.istarget("macosx") then - multiple_insert( os_links, { "OpenGL.framework", "CoreFoundation.framework" } ) - elseif os.istarget("bsd") then - multiple_insert( os_links, { "rt", "pthread", "X11", "GL", "Xcursor" } ) - elseif os.istarget("haiku") then - multiple_insert( os_links, { "GL", "network" } ) - elseif os.istarget("ios") then - multiple_insert( os_links, { "OpenGLES.framework", "AudioToolbox.framework", "CoreAudio.framework", "Foundation.framework", "CoreFoundation.framework", "UIKit.framework", "QuartzCore.framework", "CoreGraphics.framework", "CoreMotion.framework", "AVFoundation.framework", "GameController.framework" } ) - elseif os.istarget("android") then - multiple_insert( os_links, { "GLESv1_CM", "GLESv2", "log" } ) - end - - if not _OPTIONS["with-mojoal"] then - if os.istarget("linux") or os.istarget("bsd") or os.istarget("haiku") or os.istarget("emscripten") then - multiple_insert( os_links, { "openal" } ) - elseif os.istarget("windows") or os.istarget("mingw32") then - multiple_insert( os_links, { "OpenAL32" } ) - elseif os.istarget("macosx") or os.istarget("ios") then - multiple_insert( os_links, { "OpenAL.framework" } ) - end - end -end - -function parse_args() - if _OPTIONS["with-gles2"] then - defines { "EE_GLES2", "SOIL_GLES2" } - end - - if _OPTIONS["with-gles1"] then - defines { "EE_GLES1", "SOIL_GLES1" } - end -end - -function add_static_links() - -- The linking order DOES matter - -- Expose the symbols that need one static library AFTER adding that static lib - - -- Add static backends - if next(static_backends) ~= nil then - for _, value in pairs( static_backends ) do - linkoptions { value } - end - end - - if not _OPTIONS["with-dynamic-freetype"] then - links { "freetype-static" } - end - - links { "SOIL2-static", - "chipmunk-static", - "libzip-static", - "jpeg-compressor-static", - "zlib-static", - "imageresampler-static", - "pugixml-static", - "vorbis-static" - } - - if _OPTIONS["with-mojoal"] then - links { "mojoal-static"} - end - - if not _OPTIONS["with-openssl"] then - links { "mbedtls-static" } - end - - if not os.istarget("haiku") and not os.istarget("ios") and not os.istarget("android") and not os.istarget("emscripten") then - links{ "glew-static" } - end -end - -function can_add_static_backend( name ) - if _OPTIONS["with-static-backend"] then - return true - end - return false -end - -function insert_static_backend( name ) - table.insert( static_backends, "../../libs/" .. os.target() .. "/lib" .. name .. ".a" ) -end - -function add_sdl2() - print("Using SDL2 backend"); - if not can_add_static_backend("SDL2") then - table.insert( link_list, get_backend_link_name( "SDL2" ) ) - else - print("Using static backend") - insert_static_backend( "SDL2" ) - end - - table.insert( backends, "SDL2" ) -end - -function set_xcode_config() - if is_xcode() or _OPTIONS["use-frameworks"] then - linkoptions { "-F /Library/Frameworks" } - incdirs { "/Library/Frameworks/SDL2.framework/Headers" } - defines { "EE_SDL2_FROM_ROOTPATH" } - end -end - -function set_ios_config() - if os.istarget("ios") then - local toolchainpath = os.getenv("TOOLCHAINPATH") - local iosversion = os.getenv("IOSVERSION") - local sysroot_path = os.getenv("SYSROOTPATH") - - if nil == os.getenv("TOOLCHAINPATH") then - toolchainpath = os.outputof("xcrun -find -sdk iphonesimulator clang") - end - - if nil == os.getenv("IOSVERSION") then - iosversion = os.outputof("xcrun --sdk iphonesimulator --show-sdk-version") - end - - if nil == os.getenv("SYSROOTPATH") then - local platform_path = os.outputof("xcrun --sdk iphonesimulator --show-sdk-platform-path") - sysroot_path = platform_path .. "/Developer/SDKs/iPhoneSimulator" .. iosversion .. ".sdk/" - end - - local framework_path = sysroot_path .. "/System/Library/Frameworks" - local framework_libs_path = framework_path .. "/usr/lib" - local sysroot_ver = " -miphoneos-version-min=9.0 -isysroot " .. sysroot_path - - buildoptions { sysroot_ver .. " -I" .. sysroot_path .. "/usr/include" } - linkoptions { sysroot_ver } - libdirs { framework_libs_path } - linkoptions { " -F" .. framework_path .. " -L" .. framework_libs_path .. " -isysroot " .. sysroot_path } - includedirs { "src/thirdparty/" .. remote_sdl2_version .. "/include" } - end -end - -function backend_is( name, libname ) - if not _OPTIONS["with-backend"] then - _OPTIONS["with-backend"] = "SDL2" - end - - if next(backends) == nil then - backends = string.explode(_OPTIONS["with-backend"],",") - end - - local backend_sel = table.contains( backends, name ) - - local ret_val = os_findlib( libname ) and backend_sel - - if os.istarget("mingw32") or os.istarget("emscripten") then - ret_val = backend_sel - end - - if ret_val then - backend_selected = true - end - - return ret_val -end - -function select_backend() - if backend_is("SDL2", "SDL2") then - print("Selected SDL2") - add_sdl2() - end - - -- If the selected backend is not present, try to find one present - if not backend_selected then - if os_findlib("SDL2", "SDL2") then - add_sdl2() - else - print("ERROR: Couldnt find any backend. Forced SDL2.") - add_sdl2() - end - end -end - -function check_ssl_support() - if _OPTIONS["with-openssl"] then - if os.istarget("windows") then - table.insert( link_list, get_backend_link_name( "libssl" ) ) - table.insert( link_list, get_backend_link_name( "libcrypto" ) ) - else - table.insert( link_list, get_backend_link_name( "ssl" ) ) - table.insert( link_list, get_backend_link_name( "crypto" ) ) - end - - files { "src/eepp/network/ssl/backend/openssl/*.cpp" } - - defines { "EE_OPENSSL" } - else - files { "src/eepp/network/ssl/backend/mbedtls/*.cpp" } - - defines { "EE_MBEDTLS" } - end - - defines { "EE_SSL_SUPPORT" } -end - -function build_eepp( build_name ) - files { "src/eepp/core/*.cpp", - "src/eepp/math/*.cpp", - "src/eepp/system/*.cpp", - "src/eepp/audio/*.cpp", - "src/eepp/graphics/*.cpp", - "src/eepp/graphics/renderer/*.cpp", - "src/eepp/window/*.cpp", - "src/eepp/network/*.cpp", - "src/eepp/network/ssl/*.cpp", - "src/eepp/network/http/*.cpp", - "src/eepp/scene/*.cpp", - "src/eepp/scene/actions/*.cpp", - "src/eepp/ui/*.cpp", - "src/eepp/ui/actions/*.cpp", - "src/eepp/ui/css/*.cpp", - "src/eepp/ui/doc/*.cpp", - "src/eepp/ui/tools/*.cpp", - "src/eepp/physics/*.cpp", - "src/eepp/physics/constraints/*.cpp", - "src/eepp/maps/*.cpp", - "src/eepp/maps/mapeditor/*.cpp" - } - - incdirs { "include", - "src", - "src/thirdparty", - "include/eepp/thirdparty", - "src/thirdparty/freetype2/include", - "src/thirdparty/zlib", - "src/thirdparty/libogg/include", - "src/thirdparty/libvorbis/include", - "src/thirdparty/mbedtls/include" - } - - add_static_links() - check_ssl_support() - - if table.contains( backends, "SDL2" ) then - files { "src/eepp/window/backend/SDL2/*.cpp" } - defines { "EE_BACKEND_SDL_ACTIVE", "EE_SDL_VERSION_2" } - end - - multiple_insert( link_list, os_links ) - - links { link_list } - - build_link_configuration( build_name ) - - filter "options:use-frameworks" - defines { "EE_USE_FRAMEWORKS" } - - filter { "system:macosx", "action:xcode* or options:use-frameworks" } - libdirs { "/System/Library/Frameworks", "/Library/Frameworks" } - - filter "system:windows" - files { "src/eepp/system/platform/win/*.cpp" } - files { "src/eepp/network/platform/win/*.cpp" } - - filter "system:not windows" - files { "src/eepp/system/platform/posix/*.cpp" } - files { "src/eepp/network/platform/unix/*.cpp" } - - filter "options:with-mojoal" - defines( "AL_LIBTYPE_STATIC" ) - incdirs { "src/thirdparty/mojoAL" } - - filter "options:windows-vc-build" - incdirs { "src/thirdparty/" .. remote_sdl2_version .. "/include" } - - filter "action:vs*" - incdirs { "src/thirdparty/libzip/vs" } - - filter "action:not vs*" - cppdialect "C++14" - - filter "options:with-dynamic-freetype" - if not os.istarget("ios") and os_findlib("freetype") then - table.insert( link_list, get_backend_link_name( "freetype" ) ) - end -end - -workspace "eepp" - targetdir("./bin/") - if os.istarget("ios") then - configurations { "debug-x64", "debug-arm64", "release-x64", "release-arm64" } - platforms { "x86_64", "arm64" } - else - configurations { "debug", "release" } - platforms { "x86_64", "x86" } - end - rtti "On" - download_and_extract_dependencies() - select_backend() - generate_os_links() - parse_args() - location("./make/" .. os.target() .. "/") - objdir("obj/" .. os.target() .. "/") - - filter "platforms:x86" - architecture "x86" - - filter "platforms:arm64 or configurations:debug-arm64 or configurations:release-arm64" - architecture "arm64" - - filter "platforms:x86_64 or configurations:debug-x64 or configurations:release-x64" - architecture "x86_64" - - filter "system:macosx" - defines { "GL_SILENCE_DEPRECATION" } - - filter "configurations:debug*" - defines { "DEBUG" } - symbols "On" - filter "configurations:release*" - optimize "Speed" - - filter { "system:windows", "action:vs*" } - flags { "MultiProcessorCompile" } - - project "SOIL2-static" - kind "StaticLib" - language "C" - targetdir("libs/" .. os.target() .. "/thirdparty/") - files { "src/thirdparty/SOIL2/src/SOIL2/*.c" } - incdirs { "src/thirdparty/SOIL2" } - build_base_configuration( "SOIL2" ) - - project "glew-static" - kind "StaticLib" - language "C" - defines { "GLEW_NO_GLU", "GLEW_STATIC" } - targetdir("libs/" .. os.target() .. "/thirdparty/") - files { "src/thirdparty/glew/*.c" } - incdirs { "include/thirdparty/glew" } - build_base_configuration( "glew" ) - - project "mbedtls-static" - kind "StaticLib" - language "C" - targetdir("libs/" .. os.target() .. "/thirdparty/") - incdirs { "src/thirdparty/mbedtls/include/" } - files { "src/thirdparty/mbedtls/library/*.c" } - build_base_cpp_configuration( "mbedtls" ) - - project "vorbis-static" - kind "StaticLib" - language "C" - targetdir("libs/" .. os.target() .. "/thirdparty/") - incdirs { "src/thirdparty/libvorbis/lib/", "src/thirdparty/libogg/include", "src/thirdparty/libvorbis/include" } - files { "src/thirdparty/libogg/**.c", "src/thirdparty/libvorbis/**.c" } - build_base_cpp_configuration( "vorbis" ) - - project "pugixml-static" - kind "StaticLib" - language "C++" - targetdir("libs/" .. os.target() .. "/thirdparty/") - files { "src/thirdparty/pugixml/*.cpp" } - build_base_cpp_configuration( "pugixml" ) - - project "zlib-static" - kind "StaticLib" - language "C" - targetdir("libs/" .. os.target() .. "/thirdparty/") - files { "src/thirdparty/zlib/*.c" } - build_base_configuration( "zlib" ) - - project "libzip-static" - kind "StaticLib" - language "C" - targetdir("libs/" .. os.target() .. "/thirdparty/") - files { "src/thirdparty/libzip/*.c" } - incdirs { "src/thirdparty/zlib" } - build_base_configuration( "libzip" ) - - project "freetype-static" - kind "StaticLib" - language "C" - targetdir("libs/" .. os.target() .. "/thirdparty/") - defines { "FT2_BUILD_LIBRARY" } - files { "src/thirdparty/freetype2/src/**.c" } - incdirs { "src/thirdparty/freetype2/include" } - build_base_configuration( "freetype" ) - - project "chipmunk-static" - kind "StaticLib" - targetdir("libs/" .. os.target() .. "/thirdparty/") - files { "src/thirdparty/chipmunk/*.c", "src/thirdparty/chipmunk/constraints/*.c" } - incdirs { "include/eepp/thirdparty/chipmunk" } - build_base_configuration( "chipmunk" ) - filter "action:vs*" - language "C++" - buildoptions { "/TP" } - filter "action:not vs*" - language "C" - - project "jpeg-compressor-static" - kind "StaticLib" - language "C++" - targetdir("libs/" .. os.target() .. "/thirdparty/") - files { "src/thirdparty/jpeg-compressor/*.cpp" } - build_base_cpp_configuration( "jpeg-compressor" ) - - project "imageresampler-static" - kind "StaticLib" - language "C++" - targetdir("libs/" .. os.target() .. "/thirdparty/") - files { "src/thirdparty/imageresampler/*.cpp" } - build_base_cpp_configuration( "imageresampler" ) - - project "mojoal-static" - kind "StaticLib" - language "C" - targetdir("libs/" .. os.target() .. "/thirdparty/") - incdirs { "include/eepp/thirdparty/mojoAL" } - defines( "AL_LIBTYPE_STATIC" ) - files { "src/thirdparty/mojoAL/*.c" } - build_base_cpp_configuration( "mojoal" ) - filter "options:windows-vc-build" - incdirs { "src/thirdparty/" .. remote_sdl2_version .. "/include" } - - project "efsw-static" - kind "StaticLib" - language "C++" - targetdir("libs/" .. os.target() .. "/thirdparty/") - incdirs { "src/thirdparty/efsw/include", "src/thirdparty/efsw/src" } - files { "src/thirdparty/efsw/src/efsw/*.cpp" } - build_base_cpp_configuration( "efsw" ) - filter "system:windows" - files { "src/thirdparty/efsw/src/efsw/platform/win/*.cpp" } - excludes { - "src/thirdparty/efsw/src/efsw/WatcherKqueue.cpp", - "src/thirdparty/efsw/src/efsw/WatcherFSEvents.cpp", - "src/thirdparty/efsw/src/efsw/WatcherInotify.cpp", - "src/thirdparty/efsw/src/efsw/FileWatcherKqueue.cpp", - "src/thirdparty/efsw/src/efsw/FileWatcherInotify.cpp", - "src/thirdparty/efsw/src/efsw/FileWatcherFSEvents.cpp" - } - filter "system:linux" - excludes { - "src/thirdparty/efsw/src/efsw/WatcherKqueue.cpp", - "src/thirdparty/efsw/src/efsw/WatcherFSEvents.cpp", - "src/thirdparty/efsw/src/efsw/WatcherWin32.cpp", - "src/thirdparty/efsw/src/efsw/FileWatcherKqueue.cpp", - "src/thirdparty/efsw/src/efsw/FileWatcherWin32.cpp", - "src/thirdparty/efsw/src/efsw/FileWatcherFSEvents.cpp" - } - filter "system:macosx or system:ios" - excludes { - "src/thirdparty/efsw/src/efsw/WatcherInotify.cpp", - "src/thirdparty/efsw/src/efsw/WatcherWin32.cpp", - "src/thirdparty/efsw/src/efsw/FileWatcherInotify.cpp", - "src/thirdparty/efsw/src/efsw/FileWatcherWin32.cpp" - } - filter "system:bsd" - excludes { - "src/thirdparty/efsw/src/efsw/WatcherInotify.cpp", - "src/thirdparty/efsw/src/efsw/WatcherWin32.cpp", - "src/thirdparty/efsw/src/efsw/WatcherFSEvents.cpp", - "src/thirdparty/efsw/src/efsw/FileWatcherInotify.cpp", - "src/thirdparty/efsw/src/efsw/FileWatcherWin32.cpp", - "src/thirdparty/efsw/src/efsw/FileWatcherFSEvents.cpp" - } - filter "system:not windows" - files { "src/thirdparty/efsw/src/efsw/platform/posix/*.cpp" } - - -- Library - project "eepp-main" - kind "StaticLib" - language "C++" - targetdir("libs/" .. os.target() .. "/") - files { "src/eepp/main/eepp_main.cpp" } - - project "eepp-static" - kind "StaticLib" - language "C++" - targetdir("libs/" .. os.target() .. "/") - build_eepp( "eepp-static" ) - - project "eepp-shared" - kind "SharedLib" - language "C++" - targetdir("libs/" .. os.target() .. "/") - build_eepp( "eepp" ) - - -- Examples - project "eepp-external-shader" - set_kind() - language "C++" - files { "src/examples/external_shader/*.cpp" } - build_link_configuration( "eepp-external-shader", true ) - - project "eepp-empty-window" - set_kind() - language "C++" - files { "src/examples/empty_window/*.cpp" } - build_link_configuration( "eepp-empty-window", true ) - - project "eepp-sound" - kind "ConsoleApp" - language "C++" - files { "src/examples/sound/*.cpp" } - build_link_configuration( "eepp-sound", true ) - - project "eepp-sprites" - set_kind() - language "C++" - files { "src/examples/sprites/*.cpp" } - build_link_configuration( "eepp-sprites", true ) - - project "eepp-fonts" - set_kind() - language "C++" - files { "src/examples/fonts/*.cpp" } - build_link_configuration( "eepp-fonts", true ) - - project "eepp-vbo-fbo-batch" - set_kind() - language "C++" - files { "src/examples/vbo_fbo_batch/*.cpp" } - build_link_configuration( "eepp-vbo-fbo-batch", true ) - - project "eepp-physics" - set_kind() - language "C++" - files { "src/examples/physics/*.cpp" } - build_link_configuration( "eepp-physics", true ) - - project "eepp-http-request" - kind "ConsoleApp" - language "C++" - files { "src/examples/http_request/*.cpp" } - incdirs { "src/thirdparty" } - build_link_configuration( "eepp-http-request", true ) - - project "eepp-ui-hello-world" - set_kind() - language "C++" - files { "src/examples/ui_hello_world/*.cpp" } - incdirs { "src/thirdparty" } - build_link_configuration( "eepp-ui-hello-world", true ) - - -- Tools - project "eepp-textureatlaseditor" - set_kind() - language "C++" - files { "src/tools/textureatlaseditor/*.cpp" } - build_link_configuration( "eepp-TextureAtlasEditor", true ) - - project "eepp-mapeditor" - set_kind() - language "C++" - files { "src/tools/mapeditor/*.cpp" } - build_link_configuration( "eepp-MapEditor", true ) - - project "eepp-uieditor" - set_kind() - language "C++" - incdirs { "src/thirdparty/efsw/include", "src/thirdparty" } - links { "efsw-static", "pugixml-static" } - files { "src/tools/uieditor/*.cpp" } - build_link_configuration( "eepp-UIEditor", true ) - filter "system:macosx" - links { "CoreFoundation.framework", "CoreServices.framework" } - filter { "system:not windows", "system:not haiku" } - links { "pthread" } - - project "ecode" - set_kind() - language "C++" - files { "src/tools/codeeditor/*.cpp" } - incdirs { "src/thirdparty" } - build_link_configuration( "ecode", true ) - - project "eepp-texturepacker" - kind "ConsoleApp" - language "C++" - incdirs { "src/thirdparty" } - files { "src/tools/texturepacker/*.cpp" } - build_link_configuration( "eepp-TexturePacker", true ) - - -- Tests - project "eepp-test" - set_kind() - language "C++" - files { "src/tests/test_all/*.cpp" } - build_link_configuration( "eepp-test", true ) - - project "eepp-ui-perf-test" - set_kind() - language "C++" - files { "src/tests/ui_perf_test/*.cpp" } - includedirs { "src/thirdparty" } - build_link_configuration( "eepp-ui-perf-test", true ) - -if os.isfile("external_projects.lua") then - dofile("external_projects.lua") -end +newoption { trigger = "with-openssl", description = "Enables OpenSSL support ( and disables mbedtls backend )." } +newoption { trigger = "with-dynamic-freetype", description = "Dynamic link against freetype." } +newoption { trigger = "with-static-eepp", description = "Force to build the demos and tests with eepp compiled statically" } +newoption { trigger = "with-static-backend", description = "It will try to compile the library with a static backend (only for gcc and mingw).\n\t\t\t\tThe backend should be placed in libs/your_platform/libYourBackend.a" } +newoption { trigger = "with-gles2", description = "Compile with GLES2 support" } +newoption { trigger = "with-gles1", description = "Compile with GLES1 support" } +newoption { trigger = "with-mojoal", description = "Compile with mojoAL as OpenAL implementation instead of using openal-soft (requires SDL2 backend)" } +newoption { trigger = "use-frameworks", description = "In macOS it will try to link the external libraries from its frameworks. For example, instead of linking against SDL2 it will link against SDL2.framework." } +newoption { trigger = "windows-vc-build", description = "This is used to build the framework in Visual Studio downloading its external dependencies and making them available to the VS project without having to install them manually." } +newoption { + trigger = "with-backend", + description = "Select the backend to use for window and input handling.\n\t\t\tIf no backend is selected or if the selected is not installed the script will search for a backend present in the system, and will use it.", + allowed = { + { "SDL2", "SDL2" }, + } +} + +function print_table( table_ref ) + for _, value in pairs( table_ref ) do + print(value) + end +end + +function table_length(T) + local count = 0 + for _ in pairs(T) do count = count + 1 end + return count +end + +function multiple_insert( parent_table, insert_table ) + for _, value in pairs( insert_table ) do + table.insert( parent_table, value ) + end +end + +function os_findlib( name ) + if os.istarget("macosx") and ( is_xcode() or _OPTIONS["use-frameworks"] ) then + local path = "/Library/Frameworks/" .. name .. ".framework" + + if os.isdir( path ) then + return path + end + end + + return os.findlib( name ) +end + +function get_backend_link_name( name ) + if os.istarget("macosx") and ( is_xcode() or _OPTIONS["use-frameworks"] ) then + local fname = name .. ".framework" + + if os_findlib( name ) then -- Search for the framework + return fname + end + end + + return name +end + +function string.starts(String,Start) + if ( _ACTION ) then + return string.sub(String,1,string.len(Start))==Start + end + + return false +end + +function is_vs() + return ( string.starts(_ACTION,"vs") ) +end + +function is_xcode() + return ( string.starts(_ACTION,"xcode") ) +end + +function set_kind() + if os.istarget("macosx") then + kind("ConsoleApp") + else + kind("WindowedApp") + end +end + +link_list = { } +os_links = { } +backends = { } +static_backends = { } +backend_selected = false +remote_sdl2_version = "SDL2-2.0.12" +remote_sdl2_devel_src_url = "https://libsdl.org/release/SDL2-2.0.12.zip" +remote_sdl2_devel_vc_url = "https://www.libsdl.org/release/SDL2-devel-2.0.12-VC.zip" + +function incdirs( dirs ) + if is_xcode() then + sysincludedirs { dirs } + end + includedirs { dirs } +end + +function download_and_extract_sdl(sdl_url) + print("Downloading: " .. sdl_url) + local dest_dir = "src/thirdparty/" + local local_file = dest_dir .. remote_sdl2_version .. ".zip" + local result_str, response_code = http.download(sdl_url, local_file) + if response_code == 200 then + print("Downloaded successfully to: " .. local_file) + zip.extract(local_file, dest_dir) + print("Extracted " .. local_file .. " into " .. dest_dir) + else + print("Failed to download: " .. sdl_url) + exit(1) + end +end + +function download_and_extract_dependencies() + if not os.isdir("src/thirdparty/" .. remote_sdl2_version) then + if _OPTIONS["windows-vc-build"] then + download_and_extract_sdl(remote_sdl2_devel_vc_url) + elseif os.istarget("ios") then + download_and_extract_sdl(remote_sdl2_devel_src_url) + end + end +end + +function build_base_configuration( package_name ) + incdirs { "src/thirdparty/zlib" } + + set_ios_config() + set_xcode_config() + + filter "not system:windows" + buildoptions{ "-fPIC" } + + filter "configurations:debug*" + targetname ( package_name .. "-debug" ) + + filter "configurations:release*" + targetname ( package_name ) + + filter "action:not vs*" + cdialect "gnu99" + buildoptions { "-Wall" } + + filter "action:vs*" + incdirs { "src/thirdparty/libzip/vs" } +end + +function build_base_cpp_configuration( package_name ) + if not os.istarget("windows") then + buildoptions{ "-fPIC" } + end + + set_ios_config() + set_xcode_config() + + filter "action:not vs*" + buildoptions { "-Wall" } + + filter "configurations:debug*" + defines { "DEBUG" } + symbols "On" + targetname ( package_name .. "-debug" ) + + filter "configurations:release*" + optimize "Speed" + targetname ( package_name ) +end + +function build_link_configuration( package_name, use_ee_icon ) + incdirs { "include" } + local extension = ""; + + if package_name == "eepp" then + defines { "EE_EXPORTS" } + elseif package_name == "eepp-static" then + defines { "EE_STATIC" } + end + + if package_name ~= "eepp" and package_name ~= "eepp-static" then + if not _OPTIONS["with-static-eepp"] then + links { "eepp-shared" } + else + links { "eepp-static" } + defines { "EE_STATIC" } + add_static_links() + links { link_list } + end + end + + set_ios_config() + set_xcode_config() + + filter { "system:windows", "action:not vs*" } + if ( true == use_ee_icon ) then + linkoptions { "../../bin/assets/icon/ee.res" } + end + + filter { "system:windows", "action:vs*" } + files { "bin/assets/icon/ee.rc", "bin/assets/icon/ee.ico" } + vpaths { ['Resources/*'] = { "ee.rc", "ee.ico" } } + + filter "action:not vs*" + cppdialect "C++14" + buildoptions { "-Wall" } + + filter { "configurations:debug*", "action:not vs*" } + buildoptions{ "-Wno-long-long" } + + filter { "configurations:release*", "action:not vs*" } + buildoptions { "-fno-strict-aliasing -ffast-math" } + + filter { "configurations:release*", "action:not vs*", "system:not macosx" } + buildoptions { "-s" } + + filter "configurations:debug*" + defines { "DEBUG", "EE_DEBUG", "EE_MEMORY_MANAGER" } + targetname ( package_name .. "-debug" .. extension ) + + filter "configurations:release*" + defines { "NDEBUG" } + targetname ( package_name .. extension ) + + filter { "system:windows", "action:not vs*" } + linkoptions { "-static-libgcc", "-static-libstdc++" } + + filter { "system:windows", "action:vs*" } + if table.contains( backends, "SDL2" ) then + links { "SDL2", "SDL2main" } + end + + filter { "options:windows-vc-build", "system:windows", "platforms:x86" } + syslibdirs { "src/thirdparty/" .. remote_sdl2_version .."/lib/x86" } + + filter { "options:windows-vc-build", "system:windows", "platforms:x86_64" } + syslibdirs { "src/thirdparty/" .. remote_sdl2_version .."/lib/x64" } + + filter "system:emscripten" + targetname ( package_name .. extension ) + linkoptions{ "-O2 -s TOTAL_MEMORY=67108864 -s ASM_JS=1 -s VERBOSE=1 -s DISABLE_EXCEPTION_CATCHING=0 -s USE_SDL=2 -s ERROR_ON_UNDEFINED_SYMBOLS=0 -s ERROR_ON_MISSING_LIBRARIES=0 -s FULL_ES3=1 -s \"BINARYEN_TRAP_MODE='clamp'\"" } + buildoptions { "-fno-strict-aliasing -O2 -s USE_SDL=2 -s PRECISE_F32=1 -s ENVIRONMENT=web" } + + if _OPTIONS["with-gles1"] and ( not _OPTIONS["with-gles2"] or _OPTIONS["force-gles1"] ) then + linkoptions{ "-s LEGACY_GL_EMULATION=1" } + end + + if _OPTIONS["with-gles2"] and not _OPTIONS["force-gles1"] then + linkoptions{ "-s FULL_ES2=1" } + end +end + +function generate_os_links() + if os.istarget("linux") then + multiple_insert( os_links, { "rt", "pthread", "X11", "GL", "Xcursor" } ) + + if _OPTIONS["with-static-eepp"] then + table.insert( os_links, "dl" ) + end + elseif os.istarget("windows") then + multiple_insert( os_links, { "opengl32", "glu32", "gdi32", "ws2_32", "winmm", "ole32" } ) + elseif os.istarget("mingw32") then + multiple_insert( os_links, { "opengl32", "glu32", "gdi32", "ws2_32", "winmm", "ole32" } ) + elseif os.istarget("macosx") then + multiple_insert( os_links, { "OpenGL.framework", "CoreFoundation.framework" } ) + elseif os.istarget("bsd") then + multiple_insert( os_links, { "rt", "pthread", "X11", "GL", "Xcursor" } ) + elseif os.istarget("haiku") then + multiple_insert( os_links, { "GL", "network" } ) + elseif os.istarget("ios") then + multiple_insert( os_links, { "OpenGLES.framework", "AudioToolbox.framework", "CoreAudio.framework", "Foundation.framework", "CoreFoundation.framework", "UIKit.framework", "QuartzCore.framework", "CoreGraphics.framework", "CoreMotion.framework", "AVFoundation.framework", "GameController.framework" } ) + elseif os.istarget("android") then + multiple_insert( os_links, { "GLESv1_CM", "GLESv2", "log" } ) + end + + if not _OPTIONS["with-mojoal"] then + if os.istarget("linux") or os.istarget("bsd") or os.istarget("haiku") or os.istarget("emscripten") then + multiple_insert( os_links, { "openal" } ) + elseif os.istarget("windows") or os.istarget("mingw32") then + multiple_insert( os_links, { "OpenAL32" } ) + elseif os.istarget("macosx") or os.istarget("ios") then + multiple_insert( os_links, { "OpenAL.framework" } ) + end + end +end + +function parse_args() + if _OPTIONS["with-gles2"] then + defines { "EE_GLES2", "SOIL_GLES2" } + end + + if _OPTIONS["with-gles1"] then + defines { "EE_GLES1", "SOIL_GLES1" } + end +end + +function add_static_links() + -- The linking order DOES matter + -- Expose the symbols that need one static library AFTER adding that static lib + + -- Add static backends + if next(static_backends) ~= nil then + for _, value in pairs( static_backends ) do + linkoptions { value } + end + end + + if not _OPTIONS["with-dynamic-freetype"] then + links { "freetype-static" } + end + + links { "SOIL2-static", + "chipmunk-static", + "libzip-static", + "jpeg-compressor-static", + "zlib-static", + "imageresampler-static", + "pugixml-static", + "vorbis-static" + } + + if _OPTIONS["with-mojoal"] then + links { "mojoal-static"} + end + + if not _OPTIONS["with-openssl"] then + links { "mbedtls-static" } + end + + if not os.istarget("haiku") and not os.istarget("ios") and not os.istarget("android") and not os.istarget("emscripten") then + links{ "glew-static" } + end +end + +function can_add_static_backend( name ) + if _OPTIONS["with-static-backend"] then + return true + end + return false +end + +function insert_static_backend( name ) + table.insert( static_backends, "../../libs/" .. os.target() .. "/lib" .. name .. ".a" ) +end + +function add_sdl2() + print("Using SDL2 backend"); + if not can_add_static_backend("SDL2") then + table.insert( link_list, get_backend_link_name( "SDL2" ) ) + else + print("Using static backend") + insert_static_backend( "SDL2" ) + end + + table.insert( backends, "SDL2" ) +end + +function set_xcode_config() + if is_xcode() or _OPTIONS["use-frameworks"] then + linkoptions { "-F /Library/Frameworks" } + incdirs { "/Library/Frameworks/SDL2.framework/Headers" } + defines { "EE_SDL2_FROM_ROOTPATH" } + end +end + +function set_ios_config() + if os.istarget("ios") then + local toolchainpath = os.getenv("TOOLCHAINPATH") + local iosversion = os.getenv("IOSVERSION") + local sysroot_path = os.getenv("SYSROOTPATH") + + if nil == os.getenv("TOOLCHAINPATH") then + toolchainpath = os.outputof("xcrun -find -sdk iphonesimulator clang") + end + + if nil == os.getenv("IOSVERSION") then + iosversion = os.outputof("xcrun --sdk iphonesimulator --show-sdk-version") + end + + if nil == os.getenv("SYSROOTPATH") then + local platform_path = os.outputof("xcrun --sdk iphonesimulator --show-sdk-platform-path") + sysroot_path = platform_path .. "/Developer/SDKs/iPhoneSimulator" .. iosversion .. ".sdk/" + end + + local framework_path = sysroot_path .. "/System/Library/Frameworks" + local framework_libs_path = framework_path .. "/usr/lib" + local sysroot_ver = " -miphoneos-version-min=9.0 -isysroot " .. sysroot_path + + buildoptions { sysroot_ver .. " -I" .. sysroot_path .. "/usr/include" } + linkoptions { sysroot_ver } + libdirs { framework_libs_path } + linkoptions { " -F" .. framework_path .. " -L" .. framework_libs_path .. " -isysroot " .. sysroot_path } + includedirs { "src/thirdparty/" .. remote_sdl2_version .. "/include" } + end +end + +function backend_is( name, libname ) + if not _OPTIONS["with-backend"] then + _OPTIONS["with-backend"] = "SDL2" + end + + if next(backends) == nil then + backends = string.explode(_OPTIONS["with-backend"],",") + end + + local backend_sel = table.contains( backends, name ) + + local ret_val = os_findlib( libname ) and backend_sel + + if os.istarget("mingw32") or os.istarget("emscripten") then + ret_val = backend_sel + end + + if ret_val then + backend_selected = true + end + + return ret_val +end + +function select_backend() + if backend_is("SDL2", "SDL2") then + print("Selected SDL2") + add_sdl2() + end + + -- If the selected backend is not present, try to find one present + if not backend_selected then + if os_findlib("SDL2", "SDL2") then + add_sdl2() + else + print("ERROR: Couldnt find any backend. Forced SDL2.") + add_sdl2() + end + end +end + +function check_ssl_support() + if _OPTIONS["with-openssl"] then + if os.istarget("windows") then + table.insert( link_list, get_backend_link_name( "libssl" ) ) + table.insert( link_list, get_backend_link_name( "libcrypto" ) ) + else + table.insert( link_list, get_backend_link_name( "ssl" ) ) + table.insert( link_list, get_backend_link_name( "crypto" ) ) + end + + files { "src/eepp/network/ssl/backend/openssl/*.cpp" } + + defines { "EE_OPENSSL" } + else + files { "src/eepp/network/ssl/backend/mbedtls/*.cpp" } + + defines { "EE_MBEDTLS" } + end + + defines { "EE_SSL_SUPPORT" } +end + +function build_eepp( build_name ) + files { "src/eepp/core/*.cpp", + "src/eepp/math/*.cpp", + "src/eepp/system/*.cpp", + "src/eepp/audio/*.cpp", + "src/eepp/graphics/*.cpp", + "src/eepp/graphics/renderer/*.cpp", + "src/eepp/window/*.cpp", + "src/eepp/network/*.cpp", + "src/eepp/network/ssl/*.cpp", + "src/eepp/network/http/*.cpp", + "src/eepp/scene/*.cpp", + "src/eepp/scene/actions/*.cpp", + "src/eepp/ui/*.cpp", + "src/eepp/ui/abstract/*.cpp", + "src/eepp/ui/css/*.cpp", + "src/eepp/ui/doc/*.cpp", + "src/eepp/ui/tools/*.cpp", + "src/eepp/physics/*.cpp", + "src/eepp/physics/constraints/*.cpp", + "src/eepp/maps/*.cpp", + "src/eepp/maps/mapeditor/*.cpp" + } + + incdirs { "include", + "src", + "src/thirdparty", + "include/eepp/thirdparty", + "src/thirdparty/freetype2/include", + "src/thirdparty/zlib", + "src/thirdparty/libogg/include", + "src/thirdparty/libvorbis/include", + "src/thirdparty/mbedtls/include" + } + + add_static_links() + check_ssl_support() + + if table.contains( backends, "SDL2" ) then + files { "src/eepp/window/backend/SDL2/*.cpp" } + defines { "EE_BACKEND_SDL_ACTIVE", "EE_SDL_VERSION_2" } + end + + multiple_insert( link_list, os_links ) + + links { link_list } + + build_link_configuration( build_name ) + + filter "options:use-frameworks" + defines { "EE_USE_FRAMEWORKS" } + + filter { "system:macosx", "action:xcode* or options:use-frameworks" } + libdirs { "/System/Library/Frameworks", "/Library/Frameworks" } + + filter "system:windows" + files { "src/eepp/system/platform/win/*.cpp" } + files { "src/eepp/network/platform/win/*.cpp" } + + filter "system:not windows" + files { "src/eepp/system/platform/posix/*.cpp" } + files { "src/eepp/network/platform/unix/*.cpp" } + + filter "options:with-mojoal" + defines( "AL_LIBTYPE_STATIC" ) + incdirs { "src/thirdparty/mojoAL" } + + filter "options:windows-vc-build" + incdirs { "src/thirdparty/" .. remote_sdl2_version .. "/include" } + + filter "action:vs*" + incdirs { "src/thirdparty/libzip/vs" } + + filter "action:not vs*" + cppdialect "C++14" + + filter "options:with-dynamic-freetype" + if not os.istarget("ios") and os_findlib("freetype") then + table.insert( link_list, get_backend_link_name( "freetype" ) ) + end +end + +workspace "eepp" + targetdir("./bin/") + if os.istarget("ios") then + configurations { "debug-x64", "debug-arm64", "release-x64", "release-arm64" } + platforms { "x86_64", "arm64" } + else + configurations { "debug", "release" } + platforms { "x86_64", "x86" } + end + rtti "On" + download_and_extract_dependencies() + select_backend() + generate_os_links() + parse_args() + location("./make/" .. os.target() .. "/") + objdir("obj/" .. os.target() .. "/") + + filter "platforms:x86" + architecture "x86" + + filter "platforms:arm64 or configurations:debug-arm64 or configurations:release-arm64" + architecture "arm64" + + filter "platforms:x86_64 or configurations:debug-x64 or configurations:release-x64" + architecture "x86_64" + + filter "system:macosx" + defines { "GL_SILENCE_DEPRECATION" } + + filter "configurations:debug*" + defines { "DEBUG" } + symbols "On" + filter "configurations:release*" + optimize "Speed" + + filter { "system:windows", "action:vs*" } + flags { "MultiProcessorCompile" } + + project "SOIL2-static" + kind "StaticLib" + language "C" + targetdir("libs/" .. os.target() .. "/thirdparty/") + files { "src/thirdparty/SOIL2/src/SOIL2/*.c" } + incdirs { "src/thirdparty/SOIL2" } + build_base_configuration( "SOIL2" ) + + project "glew-static" + kind "StaticLib" + language "C" + defines { "GLEW_NO_GLU", "GLEW_STATIC" } + targetdir("libs/" .. os.target() .. "/thirdparty/") + files { "src/thirdparty/glew/*.c" } + incdirs { "include/thirdparty/glew" } + build_base_configuration( "glew" ) + + project "mbedtls-static" + kind "StaticLib" + language "C" + targetdir("libs/" .. os.target() .. "/thirdparty/") + incdirs { "src/thirdparty/mbedtls/include/" } + files { "src/thirdparty/mbedtls/library/*.c" } + build_base_cpp_configuration( "mbedtls" ) + + project "vorbis-static" + kind "StaticLib" + language "C" + targetdir("libs/" .. os.target() .. "/thirdparty/") + incdirs { "src/thirdparty/libvorbis/lib/", "src/thirdparty/libogg/include", "src/thirdparty/libvorbis/include" } + files { "src/thirdparty/libogg/**.c", "src/thirdparty/libvorbis/**.c" } + build_base_cpp_configuration( "vorbis" ) + + project "pugixml-static" + kind "StaticLib" + language "C++" + targetdir("libs/" .. os.target() .. "/thirdparty/") + files { "src/thirdparty/pugixml/*.cpp" } + build_base_cpp_configuration( "pugixml" ) + + project "zlib-static" + kind "StaticLib" + language "C" + targetdir("libs/" .. os.target() .. "/thirdparty/") + files { "src/thirdparty/zlib/*.c" } + build_base_configuration( "zlib" ) + + project "libzip-static" + kind "StaticLib" + language "C" + targetdir("libs/" .. os.target() .. "/thirdparty/") + files { "src/thirdparty/libzip/*.c" } + incdirs { "src/thirdparty/zlib" } + build_base_configuration( "libzip" ) + + project "freetype-static" + kind "StaticLib" + language "C" + targetdir("libs/" .. os.target() .. "/thirdparty/") + defines { "FT2_BUILD_LIBRARY" } + files { "src/thirdparty/freetype2/src/**.c" } + incdirs { "src/thirdparty/freetype2/include" } + build_base_configuration( "freetype" ) + + project "chipmunk-static" + kind "StaticLib" + targetdir("libs/" .. os.target() .. "/thirdparty/") + files { "src/thirdparty/chipmunk/*.c", "src/thirdparty/chipmunk/constraints/*.c" } + incdirs { "include/eepp/thirdparty/chipmunk" } + build_base_configuration( "chipmunk" ) + filter "action:vs*" + language "C++" + buildoptions { "/TP" } + filter "action:not vs*" + language "C" + + project "jpeg-compressor-static" + kind "StaticLib" + language "C++" + targetdir("libs/" .. os.target() .. "/thirdparty/") + files { "src/thirdparty/jpeg-compressor/*.cpp" } + build_base_cpp_configuration( "jpeg-compressor" ) + + project "imageresampler-static" + kind "StaticLib" + language "C++" + targetdir("libs/" .. os.target() .. "/thirdparty/") + files { "src/thirdparty/imageresampler/*.cpp" } + build_base_cpp_configuration( "imageresampler" ) + + project "mojoal-static" + kind "StaticLib" + language "C" + targetdir("libs/" .. os.target() .. "/thirdparty/") + incdirs { "include/eepp/thirdparty/mojoAL" } + defines( "AL_LIBTYPE_STATIC" ) + files { "src/thirdparty/mojoAL/*.c" } + build_base_cpp_configuration( "mojoal" ) + filter "options:windows-vc-build" + incdirs { "src/thirdparty/" .. remote_sdl2_version .. "/include" } + + project "efsw-static" + kind "StaticLib" + language "C++" + targetdir("libs/" .. os.target() .. "/thirdparty/") + incdirs { "src/thirdparty/efsw/include", "src/thirdparty/efsw/src" } + files { "src/thirdparty/efsw/src/efsw/*.cpp" } + build_base_cpp_configuration( "efsw" ) + filter "system:windows" + files { "src/thirdparty/efsw/src/efsw/platform/win/*.cpp" } + excludes { + "src/thirdparty/efsw/src/efsw/WatcherKqueue.cpp", + "src/thirdparty/efsw/src/efsw/WatcherFSEvents.cpp", + "src/thirdparty/efsw/src/efsw/WatcherInotify.cpp", + "src/thirdparty/efsw/src/efsw/FileWatcherKqueue.cpp", + "src/thirdparty/efsw/src/efsw/FileWatcherInotify.cpp", + "src/thirdparty/efsw/src/efsw/FileWatcherFSEvents.cpp" + } + filter "system:linux" + excludes { + "src/thirdparty/efsw/src/efsw/WatcherKqueue.cpp", + "src/thirdparty/efsw/src/efsw/WatcherFSEvents.cpp", + "src/thirdparty/efsw/src/efsw/WatcherWin32.cpp", + "src/thirdparty/efsw/src/efsw/FileWatcherKqueue.cpp", + "src/thirdparty/efsw/src/efsw/FileWatcherWin32.cpp", + "src/thirdparty/efsw/src/efsw/FileWatcherFSEvents.cpp" + } + filter "system:macosx or system:ios" + excludes { + "src/thirdparty/efsw/src/efsw/WatcherInotify.cpp", + "src/thirdparty/efsw/src/efsw/WatcherWin32.cpp", + "src/thirdparty/efsw/src/efsw/FileWatcherInotify.cpp", + "src/thirdparty/efsw/src/efsw/FileWatcherWin32.cpp" + } + filter "system:bsd" + excludes { + "src/thirdparty/efsw/src/efsw/WatcherInotify.cpp", + "src/thirdparty/efsw/src/efsw/WatcherWin32.cpp", + "src/thirdparty/efsw/src/efsw/WatcherFSEvents.cpp", + "src/thirdparty/efsw/src/efsw/FileWatcherInotify.cpp", + "src/thirdparty/efsw/src/efsw/FileWatcherWin32.cpp", + "src/thirdparty/efsw/src/efsw/FileWatcherFSEvents.cpp" + } + filter "system:not windows" + files { "src/thirdparty/efsw/src/efsw/platform/posix/*.cpp" } + + -- Library + project "eepp-main" + kind "StaticLib" + language "C++" + targetdir("libs/" .. os.target() .. "/") + files { "src/eepp/main/eepp_main.cpp" } + + project "eepp-static" + kind "StaticLib" + language "C++" + targetdir("libs/" .. os.target() .. "/") + build_eepp( "eepp-static" ) + + project "eepp-shared" + kind "SharedLib" + language "C++" + targetdir("libs/" .. os.target() .. "/") + build_eepp( "eepp" ) + + -- Examples + project "eepp-external-shader" + set_kind() + language "C++" + files { "src/examples/external_shader/*.cpp" } + build_link_configuration( "eepp-external-shader", true ) + + project "eepp-empty-window" + set_kind() + language "C++" + files { "src/examples/empty_window/*.cpp" } + build_link_configuration( "eepp-empty-window", true ) + + project "eepp-sound" + kind "ConsoleApp" + language "C++" + files { "src/examples/sound/*.cpp" } + build_link_configuration( "eepp-sound", true ) + + project "eepp-sprites" + set_kind() + language "C++" + files { "src/examples/sprites/*.cpp" } + build_link_configuration( "eepp-sprites", true ) + + project "eepp-fonts" + set_kind() + language "C++" + files { "src/examples/fonts/*.cpp" } + build_link_configuration( "eepp-fonts", true ) + + project "eepp-vbo-fbo-batch" + set_kind() + language "C++" + files { "src/examples/vbo_fbo_batch/*.cpp" } + build_link_configuration( "eepp-vbo-fbo-batch", true ) + + project "eepp-physics" + set_kind() + language "C++" + files { "src/examples/physics/*.cpp" } + build_link_configuration( "eepp-physics", true ) + + project "eepp-http-request" + kind "ConsoleApp" + language "C++" + files { "src/examples/http_request/*.cpp" } + incdirs { "src/thirdparty" } + build_link_configuration( "eepp-http-request", true ) + + project "eepp-ui-hello-world" + set_kind() + language "C++" + files { "src/examples/ui_hello_world/*.cpp" } + incdirs { "src/thirdparty" } + build_link_configuration( "eepp-ui-hello-world", true ) + + -- Tools + project "eepp-textureatlaseditor" + set_kind() + language "C++" + files { "src/tools/textureatlaseditor/*.cpp" } + build_link_configuration( "eepp-TextureAtlasEditor", true ) + + project "eepp-mapeditor" + set_kind() + language "C++" + files { "src/tools/mapeditor/*.cpp" } + build_link_configuration( "eepp-MapEditor", true ) + + project "eepp-uieditor" + set_kind() + language "C++" + incdirs { "src/thirdparty/efsw/include", "src/thirdparty" } + links { "efsw-static", "pugixml-static" } + files { "src/tools/uieditor/*.cpp" } + build_link_configuration( "eepp-UIEditor", true ) + filter "system:macosx" + links { "CoreFoundation.framework", "CoreServices.framework" } + filter { "system:not windows", "system:not haiku" } + links { "pthread" } + + project "ecode" + set_kind() + language "C++" + files { "src/tools/codeeditor/*.cpp" } + incdirs { "src/thirdparty" } + build_link_configuration( "ecode", true ) + + project "eepp-texturepacker" + kind "ConsoleApp" + language "C++" + incdirs { "src/thirdparty" } + files { "src/tools/texturepacker/*.cpp" } + build_link_configuration( "eepp-TexturePacker", true ) + + -- Tests + project "eepp-test" + set_kind() + language "C++" + files { "src/tests/test_all/*.cpp" } + build_link_configuration( "eepp-test", true ) + + project "eepp-ui-perf-test" + set_kind() + language "C++" + files { "src/tests/ui_perf_test/*.cpp" } + includedirs { "src/thirdparty" } + build_link_configuration( "eepp-ui-perf-test", true ) + +if os.isfile("external_projects.lua") then + dofile("external_projects.lua") +end diff --git a/projects/android-project/app/jni/eepp.mk b/projects/android-project/app/jni/eepp.mk index 793e6d5e0..c59cb45d1 100644 --- a/projects/android-project/app/jni/eepp.mk +++ b/projects/android-project/app/jni/eepp.mk @@ -82,7 +82,7 @@ CODE_SRCS := \ ui/*.cpp \ ui/css/*.cpp \ ui/doc/*.cpp \ - ui/actions/*.cpp \ + ui/model/*.cpp \ ui/tools/*.cpp \ maps/*.cpp \ maps/mapeditor/*.cpp diff --git a/projects/linux/ee.creator.user b/projects/linux/ee.creator.user index 057dfcf17..2f5fb63c6 100644 --- a/projects/linux/ee.creator.user +++ b/projects/linux/ee.creator.user @@ -1,6 +1,6 @@ - + EnvironmentId diff --git a/projects/linux/ee.files b/projects/linux/ee.files index 9cad74458..e5b2e32a5 100644 --- a/projects/linux/ee.files +++ b/projects/linux/ee.files @@ -294,8 +294,14 @@ ../../include/eepp/thirdparty/chipmunk/cpSpatialIndex.h ../../include/eepp/thirdparty/chipmunk/cpVect.h ../../include/eepp/thirdparty/PlusCallback/callback.hpp +../../include/eepp/ui/abstract/model.hpp +../../include/eepp/ui/abstract/uiabstracttableview.hpp +../../include/eepp/ui/abstract/uiabstractview.hpp ../../include/eepp/ui/base.hpp ../../include/eepp/ui.hpp +../../include/eepp/ui/abstract/modeleditingdelegate.hpp +../../include/eepp/ui/abstract/modelindex.hpp +../../include/eepp/ui/abstract/modelselection.hpp ../../include/eepp/ui/border.hpp ../../include/eepp/ui/css/animationdefinition.hpp ../../include/eepp/ui/css/drawableimageparser.hpp @@ -331,9 +337,13 @@ ../../include/eepp/ui/doc/undostack.hpp ../../include/eepp/ui/keyboardshortcut.hpp ../../include/eepp/ui/marginmove/scale.hpp +../../include/eepp/ui/abstract/model.hpp +../../include/eepp/ui/abstract/modelindex.hpp +../../include/eepp/ui/abstract/modelselection.hpp ../../include/eepp/ui/tools/textureatlaseditor.hpp ../../include/eepp/ui/tools/uicodeeditorsplitter.hpp ../../include/eepp/ui/tools/uicolorpicker.hpp +../../include/eepp/ui/uiabstractview.hpp ../../include/eepp/ui/uibackgrounddrawable.hpp ../../include/eepp/ui/uiborderdrawable.hpp ../../include/eepp/ui/uicheckbox.hpp @@ -371,6 +381,7 @@ ../../include/eepp/ui/uiradiobutton.hpp ../../include/eepp/ui/uirelativelayout.hpp ../../include/eepp/ui/uiscenenode.hpp +../../include/eepp/ui/uiscrollablewidget.hpp ../../include/eepp/ui/uiscrollbar.hpp ../../include/eepp/ui/uiscrollview.hpp ../../include/eepp/ui/uiselectbutton.hpp @@ -385,6 +396,7 @@ ../../include/eepp/ui/uitab.hpp ../../include/eepp/ui/uitablecell.hpp ../../include/eepp/ui/uitable.hpp +../../include/eepp/ui/uitableheadercolumn.hpp ../../include/eepp/ui/uitabwidget.hpp ../../include/eepp/ui/uitextedit.hpp ../../include/eepp/ui/uitextinput.hpp @@ -395,6 +407,7 @@ ../../include/eepp/ui/uithememanager.hpp ../../include/eepp/ui/uitooltip.hpp ../../include/eepp/ui/uitouchdraggablewidget.hpp +../../include/eepp/ui/uitreeview.hpp ../../include/eepp/ui/uiviewpager.hpp ../../include/eepp/ui/uiwidgetcreator.hpp ../../include/eepp/ui/uiwidget.hpp @@ -754,6 +767,10 @@ ../../src/eepp/system/translator.cpp ../../src/eepp/system/virtualfilesystem.cpp ../../src/eepp/system/zip.cpp +../../src/eepp/ui/abstract/model.cpp +../../src/eepp/ui/abstract/modelselection.cpp +../../src/eepp/ui/abstract/uiabstracttableview.cpp +../../src/eepp/ui/abstract/uiabstractview.cpp ../../src/eepp/ui/border.cpp ../../src/eepp/ui/css/animationdefinition.cpp ../../src/eepp/ui/css/drawableimageparser.cpp @@ -784,6 +801,8 @@ ../../src/eepp/ui/doc/textdocument.cpp ../../src/eepp/ui/doc/undostack.cpp ../../src/eepp/ui/keyboardshortcut.cpp +../../src/eepp/ui/abstract/model.cpp +../../src/eepp/ui/abstract/modelselection.cpp ../../src/eepp/ui/tools/textureatlaseditor.cpp ../../src/eepp/ui/tools/textureatlasnew.cpp ../../src/eepp/ui/tools/textureatlasnew.hpp @@ -791,6 +810,7 @@ ../../src/eepp/ui/tools/textureatlastextureregioneditor.hpp ../../src/eepp/ui/tools/uicodeeditorsplitter.cpp ../../src/eepp/ui/tools/uicolorpicker.cpp +../../src/eepp/ui/uiabstractview.cpp ../../src/eepp/ui/uibackgrounddrawable.cpp ../../src/eepp/ui/uiborderdrawable.cpp ../../src/eepp/ui/uicheckbox.cpp @@ -825,6 +845,7 @@ ../../src/eepp/ui/uiradiobutton.cpp ../../src/eepp/ui/uirelativelayout.cpp ../../src/eepp/ui/uiscenenode.cpp +../../src/eepp/ui/uiscrollablewidget.cpp ../../src/eepp/ui/uiscrollbar.cpp ../../src/eepp/ui/uiscrollview.cpp ../../src/eepp/ui/uiselectbutton.cpp @@ -839,6 +860,7 @@ ../../src/eepp/ui/uitab.cpp ../../src/eepp/ui/uitablecell.cpp ../../src/eepp/ui/uitable.cpp +../../src/eepp/ui/uitableheadercolumn.cpp ../../src/eepp/ui/uitabwidget.cpp ../../src/eepp/ui/uitextedit.cpp ../../src/eepp/ui/uitextinput.cpp @@ -849,6 +871,7 @@ ../../src/eepp/ui/uithememanager.cpp ../../src/eepp/ui/uitooltip.cpp ../../src/eepp/ui/uitouchdraggablewidget.cpp +../../src/eepp/ui/uitreeview.cpp ../../src/eepp/ui/uiviewpager.cpp ../../src/eepp/ui/uiwidget.cpp ../../src/eepp/ui/uiwidgetcreator.cpp diff --git a/src/eepp/scene/node.cpp b/src/eepp/scene/node.cpp index edd3fbed2..f16cf6724 100644 --- a/src/eepp/scene/node.cpp +++ b/src/eepp/scene/node.cpp @@ -48,7 +48,7 @@ Node::~Node() { if ( NULL != mParentNode ) mParentNode->childRemove( this ); - EventDispatcher* eventDispatcher = NULL != mSceneNode ? mSceneNode->getEventDispatcher() : NULL; + EventDispatcher* eventDispatcher = getEventDispatcher(); if ( NULL != eventDispatcher ) { if ( eventDispatcher->getFocusNode() == this && mSceneNode != this ) { @@ -489,7 +489,7 @@ void Node::drawChilds() { while ( NULL != child ) { if ( child->mVisible ) { - child->internalDraw(); + child->nodeDraw(); } child = child->mPrev; @@ -499,7 +499,7 @@ void Node::drawChilds() { while ( NULL != child ) { if ( child->mVisible ) { - child->internalDraw(); + child->nodeDraw(); } child = child->mNext; @@ -507,7 +507,7 @@ void Node::drawChilds() { } } -void Node::internalDraw() { +void Node::nodeDraw() { if ( mVisible ) { if ( mNodeFlags & NODE_FLAG_POSITION_DIRTY ) updateScreenPos(); @@ -912,6 +912,14 @@ void Node::forEachNode( std::function func ) { } } +void Node::forEachChild( std::function func ) { + Node* node = mChild; + while ( node ) { + func( node ); + node = node->getNextNode(); + } +} + void Node::onSceneChange() { mSceneNode = findSceneNode(); diff --git a/src/eepp/system/color.cpp b/src/eepp/system/color.cpp index 709f90a8e..d5929ed85 100644 --- a/src/eepp/system/color.cpp +++ b/src/eepp/system/color.cpp @@ -231,7 +231,7 @@ std::string Color::toHexString( const bool& prependHashtag ) const { stream << std::setfill( '0' ) << std::setw( sizeof( Color ) * 2 ) << std::hex << getValue(); std::string str = stream.str(); if ( this->a == 255 ) - return str.substr( 0, 6 ); + return str.substr( 0, prependHashtag ? 7 : 6 ); return str; } diff --git a/src/eepp/ui/abstract/model.cpp b/src/eepp/ui/abstract/model.cpp new file mode 100644 index 000000000..bafd03d22 --- /dev/null +++ b/src/eepp/ui/abstract/model.cpp @@ -0,0 +1,46 @@ +#include +#include + +namespace EE { namespace UI { namespace Abstract { + +void Model::onModelUpdate( unsigned flags ) { + if ( mOnUpdate ) + mOnUpdate(); + forEachView( [&]( UIAbstractView* view ) { view->onModelUpdate( flags ); } ); +} + +void Model::forEachView( std::function callback ) { + for ( auto view : mViews ) + callback( view ); +} + +void Model::unregisterView( UIAbstractView* view ) { + mViews.erase( view ); +} + +void Model::registerView( UIAbstractView* view ) { + mViews.insert( view ); +} + +ModelIndex Model::createIndex( int row, int column, const void* data ) const { + return ModelIndex( *this, row, column, const_cast( data ) ); +} + +void Model::setOnUpdate( const std::function& onUpdate ) { + mOnUpdate = onUpdate; +} + +ModelIndex Model::sibling( int row, int column, const ModelIndex& parent ) const { + if ( !parent.isValid() ) + return index( row, column, {} ); + int rowCount = this->rowCount( parent ); + if ( row < 0 || row > rowCount ) + return {}; + return index( row, column, parent ); +} + +bool Model::acceptsDrag( const ModelIndex&, const std::string& ) { + return false; +} + +}}} // namespace EE::UI::Abstract diff --git a/src/eepp/ui/abstract/modelselection.cpp b/src/eepp/ui/abstract/modelselection.cpp new file mode 100644 index 000000000..5faeec7c2 --- /dev/null +++ b/src/eepp/ui/abstract/modelselection.cpp @@ -0,0 +1,62 @@ +#include +#include +#include + +namespace EE { namespace UI { namespace Abstract { + +void ModelSelection::removeMatching( std::function filter ) { + std::vector::iterator> toRemove; + for ( auto it = mIndexes.begin(); it != mIndexes.end(); it++ ) { + if ( filter( *it ) ) + toRemove.push_back( it ); + } + for ( auto& index : toRemove ) + mIndexes.erase( index ); +} + +void ModelSelection::set( const ModelIndex& index ) { + eeASSERT( index.isValid() ); + if ( mIndexes.size() == 1 && contains( index ) ) + return; + mIndexes.clear(); + mIndexes.push_back( index ); + mView->notifySelectionChange(); +} + +void ModelSelection::add( const ModelIndex& index ) { + eeASSERT( index.isValid() ); + auto contains = std::find( mIndexes.begin(), mIndexes.end(), index ); + if ( contains == mIndexes.end() ) + return; + mIndexes.push_back( index ); + mView->notifySelectionChange(); +} + +void ModelSelection::toggle( const ModelIndex& index ) { + eeASSERT( index.isValid() ); + auto contains = std::find( mIndexes.begin(), mIndexes.end(), index ); + if ( contains != mIndexes.end() ) + mIndexes.erase( contains ); + else + mIndexes.push_back( index ); + mView->notifySelectionChange(); +} + +bool ModelSelection::remove( const ModelIndex& index ) { + eeASSERT( index.isValid() ); + auto contains = std::find( mIndexes.begin(), mIndexes.end(), index ); + if ( contains == mIndexes.end() ) + return false; + mIndexes.erase( contains ); + mView->notifySelectionChange(); + return true; +} + +void ModelSelection::clear() { + if ( mIndexes.empty() ) + return; + mIndexes.clear(); + mView->notifySelectionChange(); +} + +}}} // namespace EE::UI::Abstract diff --git a/src/eepp/ui/abstract/uiabstracttableview.cpp b/src/eepp/ui/abstract/uiabstracttableview.cpp new file mode 100644 index 000000000..bf31207d9 --- /dev/null +++ b/src/eepp/ui/abstract/uiabstracttableview.cpp @@ -0,0 +1,165 @@ +#include +#include +#include +#include + +namespace EE { namespace UI { namespace Abstract { + +UIAbstractTableView::UIAbstractTableView( const std::string& tag ) : + UIAbstractView( tag ), mDragBorderDistance( PixelDensity::dpToPx( 4 ) ) { + mHeader = UILinearLayout::NewWithTag( "table::header", UIOrientation::Horizontal ); + mHeader->setLayoutSizePolicy( SizePolicy::Fixed, SizePolicy::Fixed ); + mHeader->setParent( this ); + mHeader->setVisible( true ); + mHeader->setEnabled( true ); +} + +UIAbstractTableView::~UIAbstractTableView() {} + +Uint32 UIAbstractTableView::getType() const { + return UI_TYPE_ABSTRACTTABLEVIEW; +} + +bool UIAbstractTableView::isType( const Uint32& type ) const { + return UIAbstractTableView::getType() == type ? true : UIAbstractView::isType( type ); +} + +void UIAbstractTableView::selectAll() { + getSelection().clear(); + for ( size_t itemIndex = 0; itemIndex < getItemCount(); ++itemIndex ) { + auto index = getModel()->index( itemIndex ); + getSelection().add( index ); + } +} + +size_t UIAbstractTableView::getItemCount() const { + if ( !getModel() ) + return 0; + return getModel()->rowCount(); +} + +void UIAbstractTableView::onModelUpdate( unsigned flags ) { + UIAbstractView::onModelUpdate( flags ); + createOrUpdateColumns(); +} + +void UIAbstractTableView::createOrUpdateColumns() { + Model* model = getModel(); + if ( !model ) + return; + + size_t count = model->columnCount(); + Float totalWidth = 0; + + for ( size_t i = 0; i < count; i++ ) { + ColumnData& col = columnData( i ); + if ( !col.widget ) { + col.widget = eeNew( UITableHeaderColumn, ( this, i ) ); + col.widget->setParent( mHeader ); + col.widget->setEnabled( true ); + col.widget->setVisible( true ); + } + col.visible = !isColumnHidden( i ); + col.widget->setVisible( col.visible ); + if ( !col.visible ) + continue; + col.widget->setLayoutSizePolicy( SizePolicy::WrapContent, SizePolicy::WrapContent ); + col.widget->setText( model->columnName( i ) ); + col.widget->reloadStyle( true, true, true ); + col.width = eeceil( eemax( col.width, col.widget->getPixelsSize().getWidth() ) ); + col.widget->setLayoutSizePolicy( SizePolicy::Fixed, SizePolicy::Fixed ); + col.widget->setPixelsSize( col.width, getHeaderHeight() ); + totalWidth += col.width; + } + + if ( count < mColumn.size() ) { + for ( size_t i = count; i < mColumn.size(); i++ ) { + ColumnData& col = columnData( i ); + col.width = 0; + col.visible = false; + if ( col.widget ) { + col.widget->close(); + col.widget = nullptr; + } + } + } + + mHeader->setPixelsSize( totalWidth, getHeaderHeight() ); + mHeader->updateLayout(); +} + +Float UIAbstractTableView::getHeaderHeight() const { + return areHeadersVisible() ? eeceil( columnData( 0 ).widget + ? columnData( 0 ).widget->getPixelsSize().getHeight() + : 16 ) + : 0; +} + +Sizef UIAbstractTableView::getContentSize() const { + size_t count = getModel()->columnCount(); + Sizef size; + for ( size_t i = 0; i < count; i++ ) + if ( !isColumnHidden( i ) ) + size.x += columnData( i ).width; + size.y = getHeaderHeight(); + size.y += getItemCount() * getRowHeight(); + return size; +} + +bool UIAbstractTableView::areHeadersVisible() const { + return mHeader->isVisible(); +} + +void UIAbstractTableView::setHeadersVisible( bool visible ) { + mHeader->setVisible( visible ); +} + +void UIAbstractTableView::onSizeChange() { + UIAbstractView::onSizeChange(); + createOrUpdateColumns(); +} + +void UIAbstractTableView::onColumnSizeChange( const size_t& ) {} + +void UIAbstractTableView::onColumnResizeToContent( const size_t& ) {} + +void UIAbstractTableView::updateHeaderSize() { + size_t count = getModel()->columnCount(); + Float totalWidth = 0; + for ( size_t i = 0; i < count; i++ ) { + ColumnData& col = columnData( i ); + totalWidth += col.width; + } + mHeader->setPixelsSize( totalWidth, getHeaderHeight() ); +} + +const Float& UIAbstractTableView::getDragBorderDistance() const { + return mDragBorderDistance; +} + +void UIAbstractTableView::setDragBorderDistance( const Float& dragBorderDistance ) { + mDragBorderDistance = dragBorderDistance; +} + +Vector2f UIAbstractTableView::getColumnPosition( const size_t& index ) { + return columnData( index ).widget->getPixelsPosition(); +} + +UIAbstractTableView::ColumnData& UIAbstractTableView::columnData( const size_t& column ) const { + if ( column >= mColumn.size() ) + mColumn.resize( column + 1 ); + return mColumn[column]; +} + +bool UIAbstractTableView::isColumnHidden( const size_t& column ) const { + return !columnData( column ).visible; +} + +void UIAbstractTableView::setColumnHidden( const size_t& column, bool hidden ) { + if ( columnData( column ).visible != !hidden ) { + columnData( column ).visible = !hidden; + createOrUpdateColumns(); + } +} + +}}} // namespace EE::UI::Abstract diff --git a/src/eepp/ui/abstract/uiabstractview.cpp b/src/eepp/ui/abstract/uiabstractview.cpp new file mode 100644 index 000000000..12e8e311b --- /dev/null +++ b/src/eepp/ui/abstract/uiabstractview.cpp @@ -0,0 +1,65 @@ +#include + +namespace EE { namespace UI { namespace Abstract { + +UIAbstractView::UIAbstractView( const std::string& tag ) : + UIScrollableWidget( tag ), mSelection( this ) {} + +UIAbstractView::~UIAbstractView() {} + +std::function UIAbstractView::getOnSelection() const { + return mOnSelection; +} + +void UIAbstractView::setOnSelection( const std::function& onSelection ) { + mOnSelection = onSelection; +} + +std::function UIAbstractView::getOnSelectionChange() const { + return mOnSelectionChange; +} + +void UIAbstractView::setOnSelectionChange( const std::function& onSelectionChange ) { + mOnSelectionChange = onSelectionChange; +} + +Uint32 UIAbstractView::getType() const { + return UI_TYPE_ABSTRACTVIEW; +} + +bool UIAbstractView::isType( const Uint32& type ) const { + return UIAbstractView::getType() == type ? true : UIScrollableWidget::isType( type ); +} + +void UIAbstractView::setModel( std::shared_ptr model ) { + if ( model.get() == mModel.get() ) + return; + if ( mModel ) + mModel->unregisterView( this ); + mModel = model; + if ( mModel ) + mModel->registerView( this ); + onModelUpdate( Model::InvalidateAllIndexes ); +} + +void UIAbstractView::onModelUpdate( unsigned flags ) { + if ( !getModel() || ( flags & Model::InvalidateAllIndexes ) ) { + getSelection().clear(); + } else { + getSelection().removeMatching( + [this]( auto& index ) { return !getModel()->isValid( index ); } ); + } +} + +void UIAbstractView::onModelSelectionChange() { + if ( getModel() && mOnSelection && getSelection().first().isValid() ) + mOnSelection( getSelection().first() ); +} + +void UIAbstractView::notifySelectionChange() { + onModelSelectionChange(); + if ( mOnSelectionChange ) + mOnSelectionChange(); +} + +}}} // namespace EE::UI::Abstract diff --git a/src/eepp/ui/css/stylesheetpropertyanimation.cpp b/src/eepp/ui/css/stylesheetpropertyanimation.cpp index 4f27605bf..7d75a3216 100644 --- a/src/eepp/ui/css/stylesheetpropertyanimation.cpp +++ b/src/eepp/ui/css/stylesheetpropertyanimation.cpp @@ -130,7 +130,7 @@ void StyleSheetPropertyAnimation::tweenProperty( UIWidget* widget, const Float& StyleSheetPropertyAnimation* StyleSheetPropertyAnimation::fromAnimationKeyframes( const AnimationDefinition& animation, const KeyframesDefinition& keyframes, const PropertyDefinition* propertyDef, UIWidget* widget, const Uint32& propertyIndex, - const AnimationOrigin& animationOrigin ) { + const AnimationOrigin& ) { std::vector properties; std::vector times; @@ -314,7 +314,7 @@ void StyleSheetPropertyAnimation::onStart() { } } -void StyleSheetPropertyAnimation::onUpdate( const Time& time ) { +void StyleSheetPropertyAnimation::onUpdate( const Time& ) { if ( NULL != mNode && mNode->isWidget() ) { UIWidget* widget = mNode->asType(); diff --git a/src/eepp/ui/uiimage.cpp b/src/eepp/ui/uiimage.cpp index b1d5356f7..f6d2c2286 100644 --- a/src/eepp/ui/uiimage.cpp +++ b/src/eepp/ui/uiimage.cpp @@ -45,6 +45,9 @@ bool UIImage::isType( const Uint32& type ) const { } UIImage* UIImage::setDrawable( Drawable* drawable, bool ownIt ) { + if ( drawable == mDrawable ) + return this; + safeDeleteDrawable(); mDrawable = drawable; diff --git a/src/eepp/ui/uilinearlayout.cpp b/src/eepp/ui/uilinearlayout.cpp index 82873da02..347497740 100644 --- a/src/eepp/ui/uilinearlayout.cpp +++ b/src/eepp/ui/uilinearlayout.cpp @@ -4,6 +4,11 @@ namespace EE { namespace UI { +UILinearLayout* UILinearLayout::NewWithTag( const std::string& tag, + const UIOrientation& orientation ) { + return eeNew( UILinearLayout, ( tag, orientation ) ); +} + UILinearLayout* UILinearLayout::New() { return eeNew( UILinearLayout, () ); } diff --git a/src/eepp/ui/uimenuitem.cpp b/src/eepp/ui/uimenuitem.cpp index 75fffd49e..f05c86f2b 100644 --- a/src/eepp/ui/uimenuitem.cpp +++ b/src/eepp/ui/uimenuitem.cpp @@ -72,7 +72,7 @@ Uint32 UIMenuItem::onMouseClick( const Vector2i&, const Uint32& flags ) { return 1; } -UIWidget* UIMenuItem::getExtraInnerWidget() { +UIWidget* UIMenuItem::getExtraInnerWidget() const { return mShortcutView; } diff --git a/src/eepp/ui/uimenusubmenu.cpp b/src/eepp/ui/uimenusubmenu.cpp index a4053eaf4..7e5986cc5 100644 --- a/src/eepp/ui/uimenusubmenu.cpp +++ b/src/eepp/ui/uimenusubmenu.cpp @@ -60,7 +60,7 @@ void UIMenuSubMenu::onAlphaChange() { mArrow->setAlpha( mAlpha ); } -UIWidget* UIMenuSubMenu::getExtraInnerWidget() { +UIWidget* UIMenuSubMenu::getExtraInnerWidget() const { return mArrow; } diff --git a/src/eepp/ui/uinode.cpp b/src/eepp/ui/uinode.cpp index d521e7607..bb1e3198d 100644 --- a/src/eepp/ui/uinode.cpp +++ b/src/eepp/ui/uinode.cpp @@ -726,7 +726,7 @@ void UINode::drawBorder() { } } -void UINode::internalDraw() { +void UINode::nodeDraw() { if ( mVisible ) { if ( mNodeFlags & NODE_FLAG_POSITION_DIRTY ) updateScreenPos(); @@ -736,21 +736,21 @@ void UINode::internalDraw() { matrixSet(); - clipStart(); - if ( mWorldBounds.intersect( mSceneNode->getWorldBounds() ) ) { + clipStart(); + draw(); drawChilds(); if ( 0.f != mAlpha ) drawForeground(); + + clipEnd(); } else if ( !isClipped() ) { drawChilds(); } - clipEnd(); - drawBorder(); drawHighlightFocus(); diff --git a/src/eepp/ui/uipushbutton.cpp b/src/eepp/ui/uipushbutton.cpp index 1b5e38cee..9ad12c559 100644 --- a/src/eepp/ui/uipushbutton.cpp +++ b/src/eepp/ui/uipushbutton.cpp @@ -11,6 +11,10 @@ UIPushButton* UIPushButton::New() { return eeNew( UIPushButton, () ); } +UIPushButton* UIPushButton::NewWithTag( const std::string& tag ) { + return eeNew( UIPushButton, ( tag ) ); +} + UIPushButton::UIPushButton( const std::string& tag ) : UIWidget( tag ), mIcon( NULL ), mTextBox( NULL ) { mFlags |= ( UI_AUTO_SIZE | UI_VALIGN_CENTER | UI_HALIGN_CENTER ); @@ -221,6 +225,8 @@ void UIPushButton::onSizeChange() { if ( NULL != eWidget && eWidget->isVisible() ) { eWidget->setPixelsPosition( ePos ); } + + UIWidget::onSizeChange(); } void UIPushButton::setTheme( UITheme* Theme ) { @@ -237,8 +243,10 @@ void UIPushButton::onThemeLoaded() { } UIPushButton* UIPushButton::setIcon( Drawable* Icon ) { - mIcon->setDrawable( Icon ); - onSizeChange(); + if ( mIcon->getDrawable() != Icon ) { + mIcon->setDrawable( Icon ); + onSizeChange(); + } return this; } @@ -318,10 +326,41 @@ const Sizei& UIPushButton::getIconMinimumSize() const { return mIconMinSize; } -UIWidget* UIPushButton::getExtraInnerWidget() { +UIWidget* UIPushButton::getExtraInnerWidget() const { return NULL; } +void UIPushButton::setTextAlign( const Uint32& align ) { + mFlags &= ~( UI_HALIGN_CENTER | UI_HALIGN_RIGHT ); + mFlags |= align; + onAlignChange(); +} + +Sizef UIPushButton::getContentSize() const { + Float sH = getSkinSize().getHeight(); + Float sHS = getSkinSize( UIState::StateFlagSelected ).getHeight(); + Float tH = mTextBox->getPixelsSize().getHeight(); + Float eH = + NULL != getExtraInnerWidget() ? getExtraInnerWidget()->getPixelsSize().getHeight() : 0; + Float minHeight = eeceil( eemax( eemax( PixelDensity::dpToPx( eemax( sH, sHS ) ), tH ), eH ) ); + Int32 txtW = mTextBox->getPixelsSize().getWidth(); + Int32 iconSize = mIcon->getPixelsSize().getWidth() > 0 + ? mIcon->getPixelsSize().getWidth() + + PixelDensity::dpToPxI( mIcon->getLayoutMargin().Left + + mIcon->getLayoutMargin().Right ) + : 0; + UIWidget* eWidget = getExtraInnerWidget(); + Int32 eWidgetSize = NULL != eWidget ? PixelDensity::dpToPxI( eWidget->getSize().getWidth() + + eWidget->getLayoutMargin().Left + + eWidget->getLayoutMargin().Right ) + : 0; + Int32 minWidth = txtW + iconSize + eWidgetSize + mRealPadding.Left + mRealPadding.Right + + ( NULL != getSkin() ? PixelDensity::dpToPxI( getSkin()->getBorderSize().Left + + getSkin()->getBorderSize().Right ) + : 0 ); + return Sizef( minWidth, minHeight ); +} + std::string UIPushButton::getPropertyString( const PropertyDefinition* propertyDef, const Uint32& propertyIndex ) { if ( NULL == propertyDef ) @@ -336,6 +375,11 @@ std::string UIPushButton::getPropertyString( const PropertyDefinition* propertyD case PropertyId::MinIconSize: return String::format( "%ddp", mIconMinSize.getWidth() ) + " " + String::format( "%ddp", mIconMinSize.getHeight() ); + case PropertyId::TextAlign: + return Font::getHorizontalAlign( getFlags() ) == UI_HALIGN_CENTER + ? "center" + : ( Font::getHorizontalAlign( getFlags() ) == UI_HALIGN_RIGHT ? "right" + : "left" ); case PropertyId::Color: case PropertyId::ShadowColor: case PropertyId::SelectionColor: @@ -347,7 +391,6 @@ std::string UIPushButton::getPropertyString( const PropertyDefinition* propertyD case PropertyId::TextStrokeWidth: case PropertyId::TextStrokeColor: case PropertyId::TextSelection: - case PropertyId::TextAlign: return mTextBox->getPropertyString( propertyDef, propertyIndex ); default: return UIWidget::getPropertyString( propertyDef, propertyIndex ); @@ -381,6 +424,16 @@ bool UIPushButton::applyProperty( const StyleSheetProperty& attribute ) { case PropertyId::MinIconSize: setIconMinimumSize( attribute.asSizei() ); break; + case PropertyId::TextAlign: { + std::string align = String::toLower( attribute.value() ); + if ( align == "center" ) + setTextAlign( UI_HALIGN_CENTER ); + else if ( align == "left" ) + setTextAlign( UI_HALIGN_LEFT ); + else if ( align == "right" ) + setTextAlign( UI_HALIGN_RIGHT ); + break; + } case PropertyId::Color: case PropertyId::ShadowColor: case PropertyId::SelectionColor: @@ -392,7 +445,6 @@ bool UIPushButton::applyProperty( const StyleSheetProperty& attribute ) { case PropertyId::TextStrokeWidth: case PropertyId::TextStrokeColor: case PropertyId::TextSelection: - case PropertyId::TextAlign: attributeSet = mTextBox->applyProperty( attribute ); break; default: diff --git a/src/eepp/ui/uiscrollablewidget.cpp b/src/eepp/ui/uiscrollablewidget.cpp new file mode 100644 index 000000000..0a16aef38 --- /dev/null +++ b/src/eepp/ui/uiscrollablewidget.cpp @@ -0,0 +1,303 @@ +#include +#include +#include + +namespace EE { namespace UI { + +UIScrollableWidget::UIScrollableWidget( const std::string& tag ) : + UIWidget( tag ), + mViewType( Exclusive ), + mVScrollMode( ScrollBarMode::Auto ), + mHScrollMode( ScrollBarMode::Auto ), + mVScroll( UIScrollBar::NewVertical() ), + mHScroll( UIScrollBar::NewHorizontal() ), + mSizeChangeCb( 0 ), + mPosChangeCb( 0 ) { + mFlags |= UI_OWNS_CHILDS_POSITION; + + mVScroll->setParent( this ); + mHScroll->setParent( this ); + + mVScroll->addEventListener( Event::OnValueChange, + cb::Make1( this, &UIScrollableWidget::onValueChangeCb ) ); + mHScroll->addEventListener( Event::OnValueChange, + cb::Make1( this, &UIScrollableWidget::onValueChangeCb ) ); + + applyDefaultTheme(); +} + +Uint32 UIScrollableWidget::getType() const { + return UI_TYPE_SCROLLABLEWIDGET; +} + +bool UIScrollableWidget::isType( const Uint32& type ) const { + return UIWidget::getType() == type ? true : UIWidget::isType( type ); +} + +void UIScrollableWidget::onSizeChange() { + onContentSizeChange(); + UIWidget::onSizeChange(); +} + +void UIScrollableWidget::onAlphaChange() { + UIWidget::onAlphaChange(); + mVScroll->setAlpha( mAlpha ); + mHScroll->setAlpha( mAlpha ); +} + +void UIScrollableWidget::onPaddingChange() { + onContentSizeChange(); + UIWidget::onPaddingChange(); +} + +void UIScrollableWidget::setVerticalScrollMode( const ScrollBarMode& Mode ) { + if ( Mode != mVScrollMode ) { + mVScrollMode = Mode; + onContentSizeChange(); + } +} + +const ScrollBarMode& UIScrollableWidget::getVerticalScrollMode() { + return mVScrollMode; +} + +void UIScrollableWidget::setHorizontalScrollMode( const ScrollBarMode& Mode ) { + if ( Mode != mHScrollMode ) { + mHScrollMode = Mode; + onContentSizeChange(); + } +} + +const ScrollBarMode& UIScrollableWidget::getHorizontalScrollMode() { + return mHScrollMode; +} + +const UIScrollableWidget::ScrollViewType& UIScrollableWidget::getViewType() const { + return mViewType; +} + +void UIScrollableWidget::setViewType( const ScrollViewType& viewType ) { + if ( viewType != mViewType ) { + mViewType = viewType; + onContentSizeChange(); + } +} + +UIScrollBar* UIScrollableWidget::getVerticalScrollBar() const { + return mVScroll; +} + +UIScrollBar* UIScrollableWidget::getHorizontalScrollBar() const { + return mHScroll; +} + +void UIScrollableWidget::onContentSizeChange() { + Sizef contentSize( getContentSize() ); + + if ( ScrollBarMode::AlwaysOn == mHScrollMode ) { + mHScroll->setVisible( true ); + mHScroll->setEnabled( true ); + } else if ( ScrollBarMode::AlwaysOff == mHScrollMode ) { + mHScroll->setVisible( false ); + mHScroll->setEnabled( false ); + } else { + bool visible = contentSize.getWidth() > + getPixelsSize().getWidth() - getPixelsPadding().Left - + getPixelsPadding().Right - mVScroll->getPixelsSize().getWidth(); + + mHScroll->setVisible( visible ); + mHScroll->setEnabled( visible ); + } + + if ( ScrollBarMode::AlwaysOn == mVScrollMode ) { + mVScroll->setVisible( true ); + mVScroll->setEnabled( true ); + } else if ( ScrollBarMode::AlwaysOff == mVScrollMode ) { + mVScroll->setVisible( false ); + mVScroll->setEnabled( false ); + } else { + bool visible = contentSize.getHeight() > + getPixelsSize().getHeight() - getPixelsPadding().Top - + getPixelsPadding().Bottom - mHScroll->getPixelsSize().getHeight(); + + mVScroll->setVisible( visible ); + mVScroll->setEnabled( visible ); + } + + Sizef size = getPixelsSize() - mRealPadding; + + if ( Exclusive == mViewType ) { + if ( mVScroll->isVisible() ) + size.x -= mVScroll->getPixelsSize().getWidth(); + + if ( mHScroll->isVisible() ) + size.y -= mHScroll->getPixelsSize().getHeight(); + } + + mVScroll->setPixelsPosition( getPixelsSize().getWidth() - mVScroll->getPixelsSize().getWidth() - + mRealPadding.Right, + mRealPadding.Top ); + mHScroll->setPixelsPosition( mRealPadding.Left, getPixelsSize().getHeight() - + mHScroll->getPixelsSize().getHeight() - + mRealPadding.Bottom ); + + mVScroll->setPixelsSize( mVScroll->getPixelsSize().getWidth(), + getPixelsSize().getHeight() - mRealPadding.Top - mRealPadding.Bottom ); + + mHScroll->setPixelsSize( + getPixelsSize().getWidth() - mRealPadding.Left - mRealPadding.Right - + ( mVScroll->isVisible() ? mVScroll->getPixelsSize().getWidth() : 0 ), + mHScroll->getPixelsSize().getHeight() ); + + if ( size.getWidth() > 0 ) + mHScroll->setPageStep( size.getWidth() / contentSize.getWidth() ); + if ( size.getHeight() > 0 ) + mVScroll->setPageStep( size.getHeight() / contentSize.getHeight() ); + + updateScroll(); +} + +Sizef UIScrollableWidget::getScrollableArea() const { + Sizef contentSize( getContentSize() ); + Sizef size = getVisibleArea(); + return contentSize - size; +} + +Sizef UIScrollableWidget::getVisibleArea() const { + Sizef size = getPixelsSize() - mRealPadding; + if ( mVScroll->isVisible() ) + size.x -= mVScroll->getPixelsSize().getWidth(); + if ( mHScroll->isVisible() ) + size.y -= mHScroll->getPixelsSize().getHeight(); + return size; +} + +void UIScrollableWidget::updateScroll() { + Sizef totalScroll = getScrollableArea(); + Vector2f initScroll( mScrollOffset ); + mScrollOffset = Vector2f::Zero; + + if ( mVScroll->isVisible() && totalScroll.y > 0 ) + mScrollOffset.y = totalScroll.y * mVScroll->getValue(); + + if ( mHScroll->isVisible() && totalScroll.x > 0 ) + mScrollOffset.x = totalScroll.x * mHScroll->getValue(); + + if ( initScroll != mScrollOffset ) + onScrollChange(); +} + +void UIScrollableWidget::onScrollChange() {} + +void UIScrollableWidget::onValueChangeCb( const Event* ) { + updateScroll(); +} + +std::string UIScrollableWidget::getPropertyString( const PropertyDefinition* propertyDef, + const Uint32& propertyIndex ) { + if ( NULL == propertyDef ) + return ""; + + switch ( propertyDef->getPropertyId() ) { + case PropertyId::VScrollMode: + return getVerticalScrollMode() == ScrollBarMode::Auto + ? "auto" + : ( getVerticalScrollMode() == ScrollBarMode::AlwaysOn ? "on" : "off" ); + case PropertyId::HScrollMode: + return getHorizontalScrollMode() == ScrollBarMode::Auto + ? "auto" + : ( getHorizontalScrollMode() == ScrollBarMode::AlwaysOn ? "on" : "off" ); + case PropertyId::ScrollBarStyle: + return mVScroll->getScrollBarType() == UIScrollBar::NoButtons ? "no-buttons" + : "two-buttons"; + case PropertyId::ScrollBarMode: + return getViewType() == Inclusive ? "inclusive" : "exclusive"; + default: + return UIWidget::getPropertyString( propertyDef, propertyIndex ); + } +} + +void UIScrollableWidget::scrollToTop() { + mVScroll->setValue( 0 ); +} + +void UIScrollableWidget::scrollToBottom() { + mVScroll->setValue( 1 ); +} + +bool UIScrollableWidget::applyProperty( const StyleSheetProperty& attribute ) { + if ( !checkPropertyDefinition( attribute ) ) + return false; + + switch ( attribute.getPropertyDefinition()->getPropertyId() ) { + case PropertyId::ScrollBarMode: { + std::string val( attribute.asString() ); + String::toLowerInPlace( val ); + if ( "inclusive" == val || "inside" == val ) + setViewType( Inclusive ); + else if ( "exclusive" == val || "outside" == val ) + setViewType( Exclusive ); + break; + } + case PropertyId::VScrollMode: { + std::string val( attribute.asString() ); + String::toLowerInPlace( val ); + + if ( "on" == val ) + setVerticalScrollMode( ScrollBarMode::AlwaysOn ); + else if ( "off" == val ) + setVerticalScrollMode( ScrollBarMode::AlwaysOn ); + else if ( "auto" == val ) + setVerticalScrollMode( ScrollBarMode::Auto ); + break; + } + case PropertyId::HScrollMode: { + std::string val( attribute.asString() ); + String::toLowerInPlace( val ); + + if ( "on" == val ) + setHorizontalScrollMode( ScrollBarMode::AlwaysOn ); + else if ( "off" == val ) + setHorizontalScrollMode( ScrollBarMode::AlwaysOn ); + else if ( "auto" == val ) + setHorizontalScrollMode( ScrollBarMode::Auto ); + break; + } + case PropertyId::ScrollBarStyle: { + std::string val( attribute.asString() ); + String::toLowerInPlace( val ); + + if ( "no-buttons" == val || "nobuttons" == val ) { + mVScroll->setScrollBarStyle( UIScrollBar::NoButtons ); + mHScroll->setScrollBarStyle( UIScrollBar::NoButtons ); + } else if ( "two-buttons" == val || "twobuttons" == val ) { + mVScroll->setScrollBarStyle( UIScrollBar::TwoButtons ); + mHScroll->setScrollBarStyle( UIScrollBar::NoButtons ); + } + break; + } + default: + return UIWidget::applyProperty( attribute ); + } + + return true; +} + +Uint32 UIScrollableWidget::onMessage( const NodeMessage* Msg ) { + switch ( Msg->getMsg() ) { + case NodeMessage::MouseUp: { + if ( mVScroll->isEnabled() ) { + if ( Msg->getFlags() & EE_BUTTON_WUMASK ) { + mVScroll->setValue( mVScroll->getValue() - mVScroll->getClickStep() ); + return 1; + } else if ( Msg->getFlags() & EE_BUTTON_WDMASK ) { + mVScroll->setValue( mVScroll->getValue() + mVScroll->getClickStep() ); + return 1; + } + } + } + } + return UIWidget::onMessage( Msg ); +} + +}} // namespace EE::UI diff --git a/src/eepp/ui/uitab.cpp b/src/eepp/ui/uitab.cpp index 13a9d18f5..f7e5ce589 100644 --- a/src/eepp/ui/uitab.cpp +++ b/src/eepp/ui/uitab.cpp @@ -125,7 +125,7 @@ void UITab::onSizeChange() { UISelectButton::onSizeChange(); } -UIWidget* UITab::getExtraInnerWidget() { +UIWidget* UITab::getExtraInnerWidget() const { return mCloseButton; } diff --git a/src/eepp/ui/uitableheadercolumn.cpp b/src/eepp/ui/uitableheadercolumn.cpp new file mode 100644 index 000000000..07e3aabe8 --- /dev/null +++ b/src/eepp/ui/uitableheadercolumn.cpp @@ -0,0 +1,90 @@ +#include +#include +#include + +namespace EE { namespace UI { + +UITableHeaderColumn::UITableHeaderColumn( UIAbstractTableView* view, const size_t& colIndex ) : + UIPushButton( "table::header::column" ), mView( view ), mColIndex( colIndex ) { + setDragEnabled( true ); +} + +Uint32 UITableHeaderColumn::onCalculateDrag( const Vector2f& position, const Uint32& flags ) { + if ( isDragEnabled() && isDragging() && NULL != getEventDispatcher() ) { + EventDispatcher* eventDispatcher = getEventDispatcher(); + if ( !( flags /*press trigger*/ & mDragButton ) ) { + setDragging( false ); + eventDispatcher->setNodeDragging( NULL ); + return 1; + } + Vector2f pos( eefloor( position.x ), eefloor( position.y ) ); + if ( mDragPoint != pos && std::abs( mDragPoint.x - pos.x ) > 1.f ) { + Sizef dragDiff( ( Float )( mDragPoint.x - pos.x ), 0 ); + if ( onDrag( pos, flags, dragDiff ) ) { + mDragPoint = pos; + eventDispatcher->setNodeDragging( this ); + } + } + } + return 1; +} + +Uint32 UITableHeaderColumn::onMouseDown( const Vector2i& position, const Uint32& flags ) { + Vector2f localPos( convertToNodeSpace( position.asFloat() ) ); + if ( NULL != getEventDispatcher() && !getEventDispatcher()->isNodeDragging() && + !( getEventDispatcher()->getLastPressTrigger() & mDragButton ) && + ( flags & mDragButton ) && isDragEnabled() && !isDragging() && + localPos.x >= mSize.getWidth() - mView->getDragBorderDistance() ) { + startDragging( position.asFloat() ); + } + pushState( UIState::StatePressed ); + return Node::onMouseDown( position, flags ); +} + +Uint32 UITableHeaderColumn::onDrag( const Vector2f& position, const Uint32&, + const Sizef& dragDiff ) { + Vector2f localPos( convertToNodeSpace( position ) ); + if ( isDragging() || localPos.x >= mSize.getWidth() - mView->getDragBorderDistance() ) { + setPixelsSize( mSize.x - dragDiff.x, mSize.getHeight() ); + if ( mSize.getWidth() != mView->columnData( mColIndex ).width ) { + mView->columnData( mColIndex ).width = mSize.getWidth(); + mView->updateHeaderSize(); + mView->onColumnSizeChange( mColIndex ); + } + return 1; + } + return 0; +} + +Uint32 UITableHeaderColumn::onMouseLeave( const Vector2i& position, const Uint32& flags ) { + if ( !isDragging() ) + getUISceneNode()->setCursor( Cursor::Arrow ); + return UIPushButton::onMouseLeave( position, flags ); +} + +Uint32 UITableHeaderColumn::onMouseMove( const Vector2i& position, const Uint32& flags ) { + Vector2f localPos( convertToNodeSpace( position.asFloat() ) ); + if ( isDragging() || localPos.x >= mSize.getWidth() - mView->getDragBorderDistance() ) { + getUISceneNode()->setCursor( Cursor::SizeWE ); + } else if ( !isDragging() ) { + getUISceneNode()->setCursor( Cursor::Arrow ); + } + return UIPushButton::onMouseMove( position, flags ); +} + +Uint32 UITableHeaderColumn::onMouseDoubleClick( const Vector2i& position, const Uint32& flags ) { + Vector2f localPos( convertToNodeSpace( position.asFloat() ) ); + if ( localPos.x >= mSize.getWidth() - mView->getDragBorderDistance() ) + mView->onColumnResizeToContent( mColIndex ); + return UIPushButton::onMouseDoubleClick( position, flags ); +} + +Uint32 UITableHeaderColumn::onDragStop( const Vector2i& pos, const Uint32& flags ) { + getUISceneNode()->setCursor( Cursor::Arrow ); + mView->columnData( mColIndex ).width = mSize.getWidth(); + mView->updateHeaderSize(); + mView->onColumnSizeChange( mColIndex ); + return UIPushButton::onDragStop( pos, flags ); +} + +}} // namespace EE::UI diff --git a/src/eepp/ui/uitextview.cpp b/src/eepp/ui/uitextview.cpp index 9b6d3c6c8..5254ad421 100644 --- a/src/eepp/ui/uitextview.cpp +++ b/src/eepp/ui/uitextview.cpp @@ -649,11 +649,11 @@ bool UITextView::applyProperty( const StyleSheetProperty& attribute ) { case PropertyId::TextAlign: { std::string align = String::toLower( attribute.value() ); if ( align == "center" ) - setFlags( UI_HALIGN_CENTER ); + setTextAlign( UI_HALIGN_CENTER ); else if ( align == "left" ) - setFlags( UI_HALIGN_LEFT ); + setTextAlign( UI_HALIGN_LEFT ); else if ( align == "right" ) - setFlags( UI_HALIGN_RIGHT ); + setTextAlign( UI_HALIGN_RIGHT ); break; } default: @@ -703,4 +703,10 @@ std::string UITextView::getPropertyString( const PropertyDefinition* propertyDef } } +void UITextView::setTextAlign( const Uint32& align ) { + mFlags &= ~(UI_HALIGN_CENTER|UI_HALIGN_RIGHT); + mFlags |= align; + onAlignChange(); +} + }} // namespace EE::UI diff --git a/src/eepp/ui/uitreeview.cpp b/src/eepp/ui/uitreeview.cpp new file mode 100644 index 000000000..54b4958e6 --- /dev/null +++ b/src/eepp/ui/uitreeview.cpp @@ -0,0 +1,500 @@ +#include +#include +#include +#include +#include +#include + +namespace EE { namespace UI { + +struct UITreeView::MetadataForIndex { + bool open{false}; +}; + +UITreeView* UITreeView::New() { + return eeNew( UITreeView, () ); +} + +UITreeView::UITreeView() : UIAbstractTableView( "treeview" ), mIndentWidth( 16 ) { + mExpandIcon = getUISceneNode()->findIcon( "tree-expanded" ); + mContractIcon = getUISceneNode()->findIcon( "tree-contracted" ); +} + +Uint32 UITreeView::getType() const { + return UI_TYPE_TREEVIEW; +} + +bool UITreeView::isType( const Uint32& type ) const { + return UITreeView::getType() == type ? true : UIAbstractTableView::isType( type ); +} + +UITreeView::MetadataForIndex& UITreeView::getIndexMetadata( const ModelIndex& index ) const { + eeASSERT( index.isValid() ); + auto it = mViewMetadata.find( index.data() ); + if ( it != mViewMetadata.end() ) + return *it->second; + auto newMetadata = std::make_unique(); + auto* ref = newMetadata.get(); + mViewMetadata.insert( {index.data(), std::move( newMetadata )} ); + return *ref; +} + +template void UITreeView::traverseTree( Callback callback ) const { + eeASSERT( getModel() ); + auto& model = *getModel(); + int indentLevel = 1; + Float yOffset = getHeaderHeight(); + int rowIndex = -1; + std::function traverseIndex = + [&]( const ModelIndex& index ) { + if ( index.isValid() ) { + auto& metadata = getIndexMetadata( index ); + rowIndex++; + IterationDecision decision = callback( rowIndex, index, indentLevel, yOffset ); + if ( decision == IterationDecision::Break || decision == IterationDecision::Stop ) + return decision; + yOffset += getRowHeight(); + if ( !metadata.open ) { + return IterationDecision::Continue; + } + } + if ( indentLevel > 0 && !index.isValid() ) + return IterationDecision::Continue; + ++indentLevel; + int rowCount = model.rowCount( index ); + for ( int i = 0; i < rowCount; ++i ) { + IterationDecision decision = + traverseIndex( model.index( i, model.treeColumn(), index ) ); + if ( decision == IterationDecision::Break || decision == IterationDecision::Stop ) + return decision; + } + --indentLevel; + return IterationDecision::Continue; + }; + int rootCount = model.rowCount(); + for ( int rootIndex = 0; rootIndex < rootCount; ++rootIndex ) { + IterationDecision decision = + traverseIndex( model.index( rootIndex, model.treeColumn(), ModelIndex() ) ); + if ( decision == IterationDecision::Break || decision == IterationDecision::Stop ) + break; + } +} + +void UITreeView::createOrUpdateColumns() { + if ( !getModel() ) + return; + UIAbstractTableView::createOrUpdateColumns(); + updateContentSize(); +} + +size_t UITreeView::getItemCount() const { + size_t count = 0; + traverseTree( [&]( const int&, const ModelIndex&, const size_t&, const Float& ) { + count++; + return IterationDecision::Continue; + } ); + return count; +} + +void UITreeView::onColumnSizeChange( const size_t& ) { + updateContentSize(); +} + +class UITableRow : public UIWidget { + public: + UITableRow( const std::string& tag ) : UIWidget( tag ) {} + + ModelIndex getCurIndex() const { return mCurIndex; } + + void setCurIndex( const ModelIndex& curIndex ) { mCurIndex = curIndex; } + + protected: + virtual Uint32 onMessage( const NodeMessage* msg ) { + if ( msg->getMsg() == NodeMessage::MouseDown && ( msg->getFlags() & EE_BUTTON_LMASK ) && + ( !getEventDispatcher()->getMouseDownNode() || + getEventDispatcher()->getMouseDownNode() == this || + isParentOf( getEventDispatcher()->getMouseDownNode() ) ) && + getEventDispatcher()->getNodeDragging() == nullptr ) { + sendMouseEvent( Event::MouseDown, getEventDispatcher()->getMousePos(), + msg->getFlags() ); + } + return 0; + } + + ModelIndex mCurIndex; +}; + +UIWidget* UITreeView::updateRow( const int& rowIndex, const ModelIndex& index, + const Float& yOffset ) { + if ( rowIndex >= (int)mRows.size() ) + mRows.resize( rowIndex + 1, nullptr ); + UITableRow* rowWidget = nullptr; + if ( mRows[rowIndex] == nullptr ) { + rowWidget = eeNew( UITableRow, ( "table::row" ) ); + rowWidget->clipEnable(); + rowWidget->setParent( this ); + rowWidget->setLayoutSizePolicy( SizePolicy::Fixed, SizePolicy::Fixed ); + rowWidget->reloadStyle( true, true, true ); + rowWidget->addEventListener( Event::MouseDown, [&]( const Event* event ) { + if ( ( !getEventDispatcher()->getMouseDownNode() || + getEventDispatcher()->getMouseDownNode() == this || + isParentOf( getEventDispatcher()->getMouseDownNode() ) ) && + getEventDispatcher()->getNodeDragging() == nullptr ) { + getSelection().set( event->getNode()->asType()->getCurIndex() ); + } + } ); + mRows[rowIndex] = rowWidget; + } else { + rowWidget = mRows[rowIndex]; + } + rowWidget->setCurIndex( index ); + rowWidget->setPixelsSize( getContentSize().getWidth(), getRowHeight() ); + rowWidget->setPixelsPosition( {-mScrollOffset.x, yOffset - mScrollOffset.y} ); + if ( getSelection().first() == index ) { + rowWidget->pushState( UIState::StateSelected ); + } else { + rowWidget->popState( UIState::StateSelected ); + } + return rowWidget; +} + +UIWidget* UITreeView::createCell( UIWidget* rowWidget, const ModelIndex&, const size_t& col ) { + UIPushButton* widget = UIPushButton::NewWithTag( "table::cell" ); + widget->setParent( rowWidget ); + widget->unsetFlags( UI_AUTO_SIZE ); + widget->clipEnable(); + widget->setLayoutSizePolicy( SizePolicy::Fixed, SizePolicy::Fixed ); + widget->asType()->setTextAlign( UI_HALIGN_LEFT ); + if ( col == getModel()->treeColumn() ) { + widget->addEventListener( Event::MouseDoubleClick, [&]( const Event* event ) { + auto mouseEvent = static_cast( event ); + auto idx = mouseEvent->getNode()->getParent()->asType()->getCurIndex(); + if ( mouseEvent->getFlags() & EE_BUTTON_LMASK ) { + if ( getModel()->rowCount( idx ) ) { + auto& data = getIndexMetadata( idx ); + data.open = !data.open; + createOrUpdateColumns(); + } else { + onOpenModelIndex( idx ); + } + } + } ); + widget->addEventListener( Event::MouseClick, [&]( const Event* event ) { + auto mouseEvent = static_cast( event ); + UIImage* icon = mouseEvent->getNode()->asType()->getIcon(); + if ( icon ) { + Vector2f pos( icon->convertToNodeSpace( mouseEvent->getPosition().asFloat() ) ); + if ( pos >= Vector2f::Zero && pos <= icon->getPixelsSize() ) { + auto idx = + mouseEvent->getNode()->getParent()->asType()->getCurIndex(); + auto& data = getIndexMetadata( idx ); + if ( getModel()->rowCount( idx ) ) { + data.open = !data.open; + createOrUpdateColumns(); + } + } + } + } ); + } + return widget; +} + +void UITreeView::onScrollChange() { + mHeader->setPixelsPosition( -mScrollOffset.x, 0 ); + invalidateDraw(); +} + +void UITreeView::updateContentSize() { + Sizef oldSize( mContentSize ); + mContentSize = UIAbstractTableView::getContentSize(); + if ( oldSize != mContentSize ) + onContentSizeChange(); +} + +UIWidget* UITreeView::updateCell( const int& rowIndex, const ModelIndex& index, const size_t& col, + const size_t& indentLevel, const Float& yOffset ) { + if ( rowIndex >= (int)mWidgets.size() ) + mWidgets.resize( rowIndex + 1 ); + auto* widget = mWidgets[rowIndex][col]; + if ( !widget ) { + UIWidget* rowWidget = updateRow( rowIndex, index, yOffset ); + widget = createCell( rowWidget, index, col ); + mWidgets[rowIndex][col] = widget; + } + widget->setPixelsSize( columnData( col ).width, getRowHeight() ); + widget->setPixelsPosition( {getColumnPosition( col ).x, 0} ); + + if ( col == getModel()->treeColumn() ) + widget->setPaddingLeft( getIndentWidth() * indentLevel ); + + ModelIndex idx( getModel()->index( index.row(), col, index.parent() ) ); + + Variant txt( getModel()->data( idx, Model::Role::Display ) ); + + if ( widget->isType( UI_TYPE_PUSHBUTTON ) ) { + UIPushButton* pushButton = widget->asType(); + if ( txt.isValid() ) { + if ( txt.is( Variant::Type::String ) ) + pushButton->setText( txt.asString() ); + else if ( txt.is( Variant::Type::cstr ) ) + pushButton->setText( txt.asCStr() ); + } + pushButton->setIcon( nullptr ); + if ( col == getModel()->treeColumn() ) + pushButton->setIcon( getIndexMetadata( index ).open ? mExpandIcon : mContractIcon ); + Variant icon( getModel()->data( idx, Model::Role::Icon ) ); + if ( icon.is( Variant::Type::Drawable ) ) + pushButton->setIcon( icon.asDrawable() ); + } + + return widget; +} + +const Float& UITreeView::getIndentWidth() const { + return mIndentWidth; +} + +void UITreeView::setIndentWidth( const Float& indentWidth ) { + if ( mIndentWidth != indentWidth ) { + mIndentWidth = indentWidth; + createOrUpdateColumns(); + } +} + +Sizef UITreeView::getContentSize() const { + return mContentSize; +} + +void UITreeView::drawChilds() { + traverseTree( [&]( const int& rowIndex, const ModelIndex& index, const size_t& indentLevel, + const Float& yOffset ) { + if ( yOffset - mScrollOffset.y > mSize.getHeight() ) + return IterationDecision::Stop; + if ( yOffset - mScrollOffset.y + getRowHeight() < 0 ) + return IterationDecision::Continue; + for ( size_t colIndex = 0; colIndex < getModel()->columnCount(); colIndex++ ) + updateCell( rowIndex, index, colIndex, indentLevel, yOffset ); + updateRow( rowIndex, index, yOffset )->nodeDraw(); + return IterationDecision::Continue; + } ); + if ( mHeader && mHeader->isVisible() ) + mHeader->nodeDraw(); + if ( mHScroll->isVisible() ) + mHScroll->nodeDraw(); + if ( mVScroll->isVisible() ) + mVScroll->nodeDraw(); +} + +Node* UITreeView::overFind( const Vector2f& point ) { + Node* pOver = NULL; + if ( mEnabled && mVisible ) { + updateWorldPolygon(); + if ( mWorldBounds.contains( point ) && mPoly.pointInside( point ) ) { + writeNodeFlag( NODE_FLAG_MOUSEOVER_ME_OR_CHILD, 1 ); + mSceneNode->addMouseOverNode( this ); + if ( mHScroll->isVisible() && ( pOver = mHScroll->overFind( point ) ) ) + return pOver; + if ( mVScroll->isVisible() && ( pOver = mVScroll->overFind( point ) ) ) + return pOver; + if ( mHeader && ( pOver = mHeader->overFind( point ) ) ) + return pOver; + traverseTree( [&, point]( int rowIndex, const ModelIndex& index, const size_t&, + const Float& yOffset ) { + if ( yOffset - mScrollOffset.y > mSize.getHeight() ) + return IterationDecision::Stop; + if ( yOffset - mScrollOffset.y + getRowHeight() < 0 ) + return IterationDecision::Continue; + pOver = updateRow( rowIndex, index, yOffset )->overFind( point ); + if ( pOver ) + return IterationDecision::Stop; + return IterationDecision::Continue; + } ); + if ( !pOver ) + pOver = this; + } + } + return pOver; +} + +bool UITreeView::isExpanded( const ModelIndex& index ) const { + return getIndexMetadata( index ).open; +} + +Drawable* UITreeView::getExpandIcon() const { + return mExpandIcon; +} + +void UITreeView::setExpandedIcon( Drawable* expandIcon ) { + if ( mExpandIcon != expandIcon ) { + mExpandIcon = expandIcon; + createOrUpdateColumns(); + } +} + +Drawable* UITreeView::getContractIcon() const { + return mContractIcon; +} + +void UITreeView::setContractedIcon( Drawable* contractIcon ) { + if ( mContractIcon != contractIcon ) { + mContractIcon = contractIcon; + createOrUpdateColumns(); + } +} + +void UITreeView::onColumnResizeToContent( const size_t& colIndex ) { + UIWidget* lWidget = nullptr; + Float lWidth = 0; + getUISceneNode()->setIsLoading( true ); + traverseTree( [&, colIndex]( const int& rowIndex, const ModelIndex& index, + const size_t& indentLevel, const Float& yOffset ) { + UIWidget* widget = updateCell( rowIndex, index, colIndex, indentLevel, yOffset ); + if ( widget->isType( UI_TYPE_PUSHBUTTON ) ) { + Float w = widget->asType()->getContentSize().getWidth(); + if ( w > lWidth ) { + lWidget = widget; + lWidth = w; + } + } + return IterationDecision::Continue; + } ); + getUISceneNode()->setIsLoading( false ); + if ( lWidget ) { + columnData( colIndex ).width = lWidth; + createOrUpdateColumns(); + } +} + +void UITreeView::onModelSelectionChange() { + UIAbstractTableView::onModelSelectionChange(); + invalidateDraw(); +} + +Uint32 UITreeView::onKeyDown( const KeyEvent& event ) { + if ( event.getMod() != 0 ) + return 0; + + auto curIndex = getSelection().first(); + + switch ( event.getKeyCode() ) { + case KEY_UP: { + ModelIndex prevIndex; + ModelIndex foundIndex; + Float curY; + traverseTree( + [&]( const int&, const ModelIndex& index, const size_t&, const Float& offsetY ) { + if ( index == curIndex ) { + foundIndex = prevIndex; + curY = offsetY; + return IterationDecision::Break; + } + prevIndex = index; + return IterationDecision::Continue; + } ); + if ( foundIndex.isValid() ) { + getSelection().set( foundIndex ); + if ( curY < mScrollOffset.y + getHeaderHeight() + getRowHeight() || + curY > mScrollOffset.y + getPixelsSize().getHeight() - mRealPadding.Top - + mRealPadding.Bottom - getRowHeight() ) { + curY -= getHeaderHeight() + getRowHeight(); + mVScroll->setValue( eemin( + 1.f, eemax( 0.f, curY / getScrollableArea().getHeight() ) ) ); + } + } + break; + } + case KEY_DOWN: { + ModelIndex prevIndex; + ModelIndex foundIndex; + Float curY; + traverseTree( + [&]( const int&, const ModelIndex& index, const size_t&, const Float& offsetY ) { + if ( prevIndex == curIndex ) { + foundIndex = index; + curY = offsetY; + return IterationDecision::Break; + } + prevIndex = index; + return IterationDecision::Continue; + } ); + if ( foundIndex.isValid() ) { + getSelection().set( foundIndex ); + if ( curY < mScrollOffset.y || + curY > mScrollOffset.y + getPixelsSize().getHeight() - mRealPadding.Top - + mRealPadding.Bottom - getRowHeight() ) { + curY -= + eefloor( getVisibleArea().getHeight() / getRowHeight() ) * getRowHeight() - + getRowHeight(); + mVScroll->setValue( + eemin( 1.f, curY / getScrollableArea().getHeight() ) ); + } + break; + } + } + case KEY_END: { + scrollToBottom(); + ModelIndex lastIndex; + traverseTree( [&]( const int&, const ModelIndex& index, const size_t&, const Float& ) { + lastIndex = index; + return IterationDecision::Continue; + } ); + getSelection().set( lastIndex ); + break; + } + case KEY_HOME: { + scrollToTop(); + getSelection().set( getModel()->index( 0, 0 ) ); + break; + } + case KEY_RIGHT: { + if ( curIndex.isValid() && getModel()->rowCount( curIndex ) ) { + auto& metadata = getIndexMetadata( curIndex ); + if ( !metadata.open ) { + metadata.open = true; + createOrUpdateColumns(); + return 0; + } + getSelection().set( getModel()->index( 0, getModel()->treeColumn(), curIndex ) ); + } + break; + } + case KEY_LEFT: { + if ( curIndex.isValid() && getModel()->rowCount( curIndex ) ) { + auto& metadata = getIndexMetadata( curIndex ); + if ( metadata.open ) { + metadata.open = false; + createOrUpdateColumns(); + return 0; + } + } + if ( curIndex.isValid() && curIndex.parent().isValid() ) { + getSelection().set( curIndex.parent() ); + return 0; + } + break; + } + case KEY_RETURN: + case KEY_SPACE: { + if ( curIndex.isValid() ) { + if ( getModel()->rowCount( curIndex ) ) { + auto& metadata = getIndexMetadata( curIndex ); + metadata.open = !metadata.open; + createOrUpdateColumns(); + } else { + onOpenModelIndex( curIndex ); + } + } + break; + } + default: + break; + } + return 0; +} + +void UITreeView::onOpenModelIndex( const ModelIndex& index ) { + ModelEvent event( getModel(), index, this ); + sendEvent( &event ); +} + +}} // namespace EE::UI diff --git a/src/eepp/ui/uiwidgetcreator.cpp b/src/eepp/ui/uiwidgetcreator.cpp index 933ad7c85..2f1aa0e9a 100644 --- a/src/eepp/ui/uiwidgetcreator.cpp +++ b/src/eepp/ui/uiwidgetcreator.cpp @@ -29,6 +29,7 @@ #include #include #include +#include #include #include #include @@ -80,6 +81,7 @@ void UIWidgetCreator::createBaseWidgetList() { registeredWidget["viewpager"] = UIViewPager::New; registeredWidget["codeeditor"] = UICodeEditor::New; registeredWidget["splitter"] = UISplitter::New; + registeredWidget["treeview"] = UITreeView::New; registeredWidget["hbox"] = UILinearLayout::NewHorizontal; registeredWidget["vbox"] = UILinearLayout::NewVertical; diff --git a/src/eepp/ui/uiwindow.cpp b/src/eepp/ui/uiwindow.cpp index d616ede17..ea86cee55 100644 --- a/src/eepp/ui/uiwindow.cpp +++ b/src/eepp/ui/uiwindow.cpp @@ -60,7 +60,7 @@ UIWindow::UIWindow( UIWindow::WindowBaseContainerType type, const StyleConfig& w switch ( type ) { case LINEAR_LAYOUT: - mContainer = UILinearLayout::NewWithTag( "window::container" ); + mContainer = UILinearLayout::NewWithTag( "window::container", UIOrientation::Vertical ); break; case RELATIVE_LAYOUT: mContainer = UIRelativeLayout::NewWithTag( "window::container" ); @@ -1212,7 +1212,7 @@ Uint32 UIWindow::onMouseDoubleClick( const Vector2i&, const Uint32& Flags ) { return 1; } -void UIWindow::internalDraw() { +void UIWindow::nodeDraw() { if ( mVisible && 0 != mAlpha ) { updateScreenPos(); diff --git a/src/tests/ui_perf_test/ui_perf_test.cpp b/src/tests/ui_perf_test/ui_perf_test.cpp index 41295fdf8..860b01701 100644 --- a/src/tests/ui_perf_test/ui_perf_test.cpp +++ b/src/tests/ui_perf_test/ui_perf_test.cpp @@ -1,4 +1,104 @@ #include +#include +#include +#include + +using namespace EE::UI::Abstract; + +class TestModel : public Model { + public: + struct NodeT { + std::vector children; + NodeT* parent{nullptr}; + + ModelIndex index( const TestModel& model, int column ) const { + if ( !parent ) + return {}; + for ( size_t row = 0; row < parent->children.size(); ++row ) { + if ( parent->children[row] == this ) + return model.createIndex( row, column, const_cast( this ) ); + } + return {}; + } + }; + + size_t getRows() const { return 10000; } + size_t getCols() const { return 4; } + size_t getChilds() const { return 50; } + + TestModel() : Model() { + for ( size_t row = 0; row < getRows(); ++row ) { + NodeT* n = new NodeT(); + n->parent = &mRoot; + for ( size_t i = 0; i < getChilds(); i++ ) { + NodeT* c = new NodeT(); + c->parent = n; + n->children.push_back( c ); + } + mRoot.children.push_back( n ); + } + } + + virtual ModelIndex parentIndex( const ModelIndex& index ) const { + if ( !index.isValid() ) + return {}; + auto& node = this->node( index ); + if ( !node.parent ) { + eeASSERT( &node == &mRoot ); + return {}; + } + return node.parent->index( *this, index.column() ); + } + + virtual size_t rowCount( const ModelIndex& index = ModelIndex() ) const { + auto& node = this->node( index ); + return node.children.size(); + } + + virtual size_t columnCount( const ModelIndex& = ModelIndex() ) const { return getCols(); } + + NodeT mRoot; + const NodeT& node( const ModelIndex& index ) const { + if ( !index.isValid() ) + return mRoot; + return *(NodeT*)index.data(); + } + + ModelIndex index( int row, int column, const ModelIndex& parent ) const { + if ( row < 0 || column < 0 ) + return {}; + auto& node = this->node( parent ); + if ( static_cast( row ) >= node.children.size() ) + return {}; + return createIndex( row, column, node.children[row] ); + } + + virtual std::string columnName( const size_t& column ) const { + return String::format( "Column %ld", column ); + } + + virtual Variant data( const ModelIndex& index, Role role = Role::Display ) const { + switch ( role ) { + case Role::Display: { + return Variant( String::format( "Test %lld-%lld", index.row(), index.column() ) ); + } + case Role::Icon: { + if ( index.column() == 0 && rowCount( index ) == 0 ) { + return Variant( SceneManager::instance() + ->getUISceneNode() + ->getUIIconThemeManager() + ->getCurrentTheme() + ->getIcon( "file" ) ); + } + } + default: { + } + } + return Variant(); + }; + + virtual void update() {} +}; // This file is used to test some UI related stuffs. // It's not a benchmark or a real test suite. @@ -13,6 +113,21 @@ void mainLoop() { win->close(); } + UISceneNode* uiSceneNode = SceneManager::instance()->getUISceneNode(); + + if ( win->getInput()->isKeyUp( KEY_F6 ) ) { + uiSceneNode->setHighlightFocus( !uiSceneNode->getHighlightFocus() ); + uiSceneNode->setHighlightOver( !uiSceneNode->getHighlightOver() ); + } + + if ( win->getInput()->isKeyUp( KEY_F7 ) ) { + uiSceneNode->setDrawBoxes( !uiSceneNode->getDrawBoxes() ); + } + + if ( win->getInput()->isKeyUp( KEY_F8 ) ) { + uiSceneNode->setDrawDebugData( !uiSceneNode->getDrawDebugData() ); + } + // Update the UI scene. SceneManager::instance()->update(); @@ -39,9 +154,23 @@ EE_MAIN_FUNC int main( int argc, char* argv[] ) { Engine::instance()->getDisplayManager()->getDisplayIndex( 0 )->getPixelDensity() ); FontTrueType* font = FontTrueType::New( "NotoSans-Regular", "assets/fonts/NotoSans-Regular.ttf" ); + FontTrueType* iconFont = FontTrueType::New( "icon", "assets/fonts/remixicon.ttf" ); + UIIconTheme* iconTheme = UIIconTheme::New( "remixicon" ); + auto addIcon = [iconTheme, iconFont]( const std::string& name, const Uint32& codePoint, + const Uint32& size ) -> Drawable* { + Drawable* ic = iconFont->getGlyphDrawable( codePoint, size ); + iconTheme->add( name, ic ); + return ic; + }; + Drawable* closed = addIcon( "folder", 0xed6a, 16 ); + Drawable* open = addIcon( "folder-open", 0xed70, 16 ); + addIcon( "tree-expanded", 0xea50, 24 ); + addIcon( "tree-contracted", 0xea54, 24 ); + addIcon( "file", 0xecc3, 24 ); UISceneNode* uiSceneNode = UISceneNode::New(); SceneManager::instance()->add( uiSceneNode ); uiSceneNode->getUIThemeManager()->setDefaultFont( font ); + uiSceneNode->getUIIconThemeManager()->setCurrentTheme( iconTheme ); /*StyleSheetParser styleSheetParser; styleSheetParser.loadFromFile( "assets/ui/breeze.css" ); uiSceneNode->setStyleSheet( styleSheetParser.getStyleSheet() );*/ @@ -50,9 +179,10 @@ EE_MAIN_FUNC int main( int argc, char* argv[] ) { pd = "1.5x"; else if ( PixelDensity::getPixelDensity() >= 2.f ) pd = "2x"; - UITheme* theme = + /*UITheme* theme = UITheme::load( "uitheme" + pd, "uitheme" + pd, "assets/ui/uitheme" + pd + ".eta", font, - "assets/ui/uitheme.css" ); + "assets/ui/uitheme.css" );*/ + UITheme* theme = UITheme::load( "breeze", "breeze", "", font, "assets/ui/breeze.css" ); uiSceneNode->setStyleSheet( theme->getStyleSheet() ); uiSceneNode->getUIThemeManager() ->setDefaultEffectsEnabled( true ) @@ -63,6 +193,17 @@ EE_MAIN_FUNC int main( int argc, char* argv[] ) { auto* vlay = UILinearLayout::NewVertical(); vlay->setLayoutSizePolicy( SizePolicy::MatchParent, SizePolicy::MatchParent ); + Clock clock; + auto model = std::make_shared(); + UITreeView* view = UITreeView::New(); + view->setId( "treeview" ); + view->setExpandedIcon( open ); + view->setContractedIcon( closed ); + view->setLayoutSizePolicy( SizePolicy::MatchParent, SizePolicy::MatchParent ); + view->setParent( vlay ); + view->setModel( model ); + eePRINTL( "Total time: %.2fms", clock.getElapsedTime().asMilliseconds() ); + /* ListBox test */ /* std::vector strings; for ( size_t i = 0; i < 10000; i++ ) @@ -101,7 +242,7 @@ EE_MAIN_FUNC int main( int argc, char* argv[] ) { uiSceneNode->getRoot()->childsCloseAll(); SceneManager::instance()->update();*/ - total.restart(); + /*total.restart(); for ( size_t i = 0; i < 100000; i++ ) { auto* widget = UIWidget::New(); widget->setParent( vlay ); @@ -115,7 +256,7 @@ EE_MAIN_FUNC int main( int argc, char* argv[] ) { widget->setBackgroundColor( Color::fromHsv( col ) ); } std::cout << "Time UIWidget total: " << total.getElapsedTime().asMilliseconds() << " ms" - << std::endl; + << std::endl;*/ /*UIWindow* wind = UIWindow::New(); wind->setSize( 500, 500 );