diff --git a/include/eepp/ui/abstract/model.hpp b/include/eepp/ui/abstract/model.hpp index dee50f412..48c9239fd 100644 --- a/include/eepp/ui/abstract/model.hpp +++ b/include/eepp/ui/abstract/model.hpp @@ -51,7 +51,7 @@ class Variant { Variant( const Int64& val ) : mType( Type::Int64 ) { mValue.asInt64 = val; } ~Variant() { reset(); } const std::string& asString() const { return *mValue.asString; } - const Drawable* asDrawable() const { return mValue.asDrawable; } + 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; } diff --git a/include/eepp/ui/abstract/modelindex.hpp b/include/eepp/ui/abstract/modelindex.hpp index 6c40fb692..36670bb86 100644 --- a/include/eepp/ui/abstract/modelindex.hpp +++ b/include/eepp/ui/abstract/modelindex.hpp @@ -14,9 +14,13 @@ class EE_API 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 { diff --git a/include/eepp/ui/abstract/uiabstractview.hpp b/include/eepp/ui/abstract/uiabstractview.hpp index 4736e8ff2..5cd0121c1 100644 --- a/include/eepp/ui/abstract/uiabstractview.hpp +++ b/include/eepp/ui/abstract/uiabstractview.hpp @@ -12,17 +12,23 @@ namespace EE { namespace UI { namespace Abstract { class EE_API UIAbstractView : public UIWidget { public: 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; bool isEditable() const { return mEditable; } + void setEditable( bool editable ) { mEditable = editable; } void setActivatesOnSelection( bool b ) { mActivatesOnSelection = b; } + bool getActivatesOnSelection() const { return mActivatesOnSelection; } void notifySelectionChange(); diff --git a/include/eepp/ui/uitreeview.hpp b/include/eepp/ui/uitreeview.hpp index 3f69aec52..e18f6968c 100644 --- a/include/eepp/ui/uitreeview.hpp +++ b/include/eepp/ui/uitreeview.hpp @@ -23,6 +23,16 @@ class EE_API UITreeView : public UIAbstractTableView { 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, @@ -32,6 +42,8 @@ class EE_API UITreeView : public UIAbstractTableView { Float mIndentWidth; Sizef mContentSize; + Drawable* mExpandIcon{nullptr}; + Drawable* mContractIcon{nullptr}; UITreeView(); 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/uipushbutton.cpp b/src/eepp/ui/uipushbutton.cpp index a42c7f807..63cc81ba7 100644 --- a/src/eepp/ui/uipushbutton.cpp +++ b/src/eepp/ui/uipushbutton.cpp @@ -243,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; } diff --git a/src/eepp/ui/uitreeview.cpp b/src/eepp/ui/uitreeview.cpp index 132af743a..d08063eb8 100644 --- a/src/eepp/ui/uitreeview.cpp +++ b/src/eepp/ui/uitreeview.cpp @@ -1,5 +1,6 @@ #include #include +#include #include namespace EE { namespace UI { @@ -12,7 +13,10 @@ UITreeView* UITreeView::New() { return eeNew( UITreeView, () ); } -UITreeView::UITreeView() : UIAbstractTableView( "treeview" ), mIndentWidth( 16 ) {} +UITreeView::UITreeView() : UIAbstractTableView( "treeview" ), mIndentWidth( 16 ) { + mExpandIcon = getUISceneNode()->findIcon( "tree-expanded" ); + mContractIcon = getUISceneNode()->findIcon( "tree-contracted" ); +} UITreeView::MetadataForIndex& UITreeView::getIndexMetadata( const ModelIndex& index ) const { eeASSERT( index.isValid() ); @@ -72,6 +76,8 @@ template void UITreeView::traverseTree( Callback callback ) } void UITreeView::createOrUpdateColumns() { + if ( !getModel() ) + return; UIAbstractTableView::createOrUpdateColumns(); updateContentSize(); traverseTree( [&]( const ModelIndex& index, const size_t& indentLevel, const Float& yOffset ) { @@ -152,11 +158,13 @@ UIPushButton* UITreeView::updateCell( const ModelIndex& index, const size_t& col if ( col == getModel()->treeColumn() ) widget->setPaddingLeft( getIndentWidth() * indentLevel ); - Variant variant( getModel()->data( getModel()->index( index.row(), col, index.parent() ), - Model::Role::Display ) ); + ModelIndex idx( getModel()->index( index.row(), col, index.parent() ) ); + + Variant variant( getModel()->data( idx, Model::Role::Display ) ); if ( variant.isValid() ) widget->setText( variant.asString() ); - + if ( col == getModel()->treeColumn() && getModel()->rowCount( index ) > 0 ) + widget->setIcon( getIndexMetadata( index ).open ? mExpandIcon : mContractIcon ); return widget; } @@ -165,7 +173,10 @@ const Float& UITreeView::getIndentWidth() const { } void UITreeView::setIndentWidth( const Float& indentWidth ) { - mIndentWidth = indentWidth; + if ( mIndentWidth != indentWidth ) { + mIndentWidth = indentWidth; + createOrUpdateColumns(); + } } Sizef UITreeView::getContentSize() const { @@ -209,4 +220,30 @@ Node* UITreeView::overFind( const Vector2f& point ) { 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(); + } +} + }} // namespace EE::UI diff --git a/src/tests/ui_perf_test/ui_perf_test.cpp b/src/tests/ui_perf_test/ui_perf_test.cpp index 4b484bb24..b71f41dd3 100644 --- a/src/tests/ui_perf_test/ui_perf_test.cpp +++ b/src/tests/ui_perf_test/ui_perf_test.cpp @@ -23,7 +23,7 @@ class TestModel : public Model { }; TestModel() : Model() { - for ( size_t row = 0; row < 4; ++row ) { + for ( size_t row = 0; row < 100; ++row ) { NodeT* n = new NodeT(); n->parent = &mRoot; for ( size_t i = 0; i < 4; i++ ) { @@ -78,6 +78,16 @@ class TestModel : public Model { case Role::Display: { return Variant( String::format( "Test %lld-%lld", index.row(), index.column() ) ); } + case Role::Icon: { + if ( index.column() == 0 ) { + return Variant( + SceneManager::instance() + ->getUISceneNode() + ->getUIIconThemeManager() + ->getCurrentTheme() + ->getIcon( node( index ).children.size() ? "folder-open" : "folder" ) ); + } + } default: { } } @@ -141,9 +151,22 @@ 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 ); 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() );*/ @@ -169,6 +192,8 @@ EE_MAIN_FUNC int main( int argc, char* argv[] ) { 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 );