diff --git a/bin/assets/ui/breeze.css b/bin/assets/ui/breeze.css index 2b7b16631..02e53a2f7 100644 --- a/bin/assets/ui/breeze.css +++ b/bin/assets/ui/breeze.css @@ -761,11 +761,10 @@ table::header::column:hover { table::row { background-color: var(--list-back); - transition: all 0.125s; } table::row:hover { - background-color: var(--back); + background-color: var(--tab-hover); } table::row:selected { @@ -778,11 +777,13 @@ TreeView::cell { padding-right: 6dp; } -TreeView { +TreeView, +TableView { background-color: var(--list-back); } -TreeView > ScrollBar { +TreeView > ScrollBar, +TableView > ScrollBar { background-color: var(--list-back); } diff --git a/include/eepp/scene/node.hpp b/include/eepp/scene/node.hpp index 9f4eb5b13..093de3cf5 100644 --- a/include/eepp/scene/node.hpp +++ b/include/eepp/scene/node.hpp @@ -394,6 +394,12 @@ class EE_API Node : public Transformable { virtual void nodeDraw(); + void forceKeyDown( const KeyEvent& event ); + + void foceKeyUp( const KeyEvent& event ); + + void forceTextInput( const TextInputEvent& Event ); + protected: typedef std::map> EventsMap; friend class EventDispatcher; diff --git a/include/eepp/system/luapattern.hpp b/include/eepp/system/luapattern.hpp index 77a8136df..1f94697e3 100644 --- a/include/eepp/system/luapattern.hpp +++ b/include/eepp/system/luapattern.hpp @@ -97,19 +97,21 @@ class EE_API LuaPattern { LuaPattern( const std::string& pattern ); bool matches( const char* stringSearch, int stringStartOffset, LuaPattern::Range* matchList, - size_t stringLength ); + size_t stringLength ) const; - bool matches( const std::string& str, LuaPattern::Range* matchList, int stringStartOffset = 0 ); + bool matches( const std::string& str, LuaPattern::Range* matchList = nullptr, + int stringStartOffset = 0 ) const; bool find( const char* stringSearch, int& startMatch, int& endMatch, int stringStartOffset = 0, - int stringLength = 0, int returnMatchIndex = 0 ); + int stringLength = 0, int returnMatchIndex = 0 ) const; bool find( const std::string& s, int& startMatch, int& endMatch, int offset = 0, - int returnedMatchIndex = 0 ); + int returnedMatchIndex = 0 ) const; - const size_t& getNumMatches(); + const size_t& getNumMatches() const; - bool range( int indexGet, int& startMatch, int& endMatch, LuaPattern::Range* returnedMatched ); + bool range( int indexGet, int& startMatch, int& endMatch, + LuaPattern::Range* returnedMatched ) const; const std::string& getPatern() const { return mPattern; } @@ -126,9 +128,9 @@ class EE_API LuaPattern { std::string gsub( const std::string& text, const std::string& replace ); protected: - std::string mErr; + mutable std::string mErr; std::string mPattern; - size_t mMatchNum; + mutable size_t mMatchNum; }; }} // namespace EE::System diff --git a/include/eepp/ui.hpp b/include/eepp/ui.hpp index 83ae4830b..fa8dd947e 100644 --- a/include/eepp/ui.hpp +++ b/include/eepp/ui.hpp @@ -39,6 +39,7 @@ #include #include #include +#include #include #include #include diff --git a/include/eepp/ui/abstract/uiabstracttableview.hpp b/include/eepp/ui/abstract/uiabstracttableview.hpp index c46b5a9d0..ef3696fd5 100644 --- a/include/eepp/ui/abstract/uiabstracttableview.hpp +++ b/include/eepp/ui/abstract/uiabstracttableview.hpp @@ -4,6 +4,7 @@ #include #include #include +#include using namespace EE::Math; @@ -64,24 +65,30 @@ class EE_API UIAbstractTableView : public UIAbstractView { Float getContentSpaceWidth() const; + void moveSelection( int steps ); + protected: friend class EE::UI::UITableHeaderColumn; - virtual ~UIAbstractTableView(); - - UIAbstractTableView( const std::string& tag ); - - Float mRowHeight{0}; - struct ColumnData { Float width{0}; bool visible{true}; UIPushButton* widget{nullptr}; }; - ColumnData& columnData( const size_t& column ) const; - + Float mRowHeight{0}; + mutable std::vector mRows; mutable std::vector mColumn; + mutable std::vector> mWidgets; + UILinearLayout* mHeader; + Float mDragBorderDistance{8}; + bool mAutoExpandOnSingleColumn{false}; + + virtual ~UIAbstractTableView(); + + UIAbstractTableView( const std::string& tag ); + + ColumnData& columnData( const size_t& column ) const; virtual size_t getItemCount() const; @@ -97,13 +104,23 @@ class EE_API UIAbstractTableView : public UIAbstractView { virtual void updateColumnsWidth(); + virtual UITableRow* createRow(); + + virtual UITableRow* updateRow( const int& rowIndex, const ModelIndex& index, + const Float& yOffset ); + + virtual UIWidget* updateCell( const int& rowIndex, const ModelIndex& index, + const size_t& indentLevel, const Float& yOffset ); + + virtual UIWidget* createCell( UIWidget* rowWidget, const ModelIndex& index ); + + virtual void onScrollChange(); + + virtual void onOpenModelIndex( const ModelIndex& index ); + void updateHeaderSize(); int visibleColumn(); - - UILinearLayout* mHeader; - Float mDragBorderDistance{8}; - bool mAutoExpandOnSingleColumn{false}; }; }}} // namespace EE::UI::Abstract diff --git a/include/eepp/ui/doc/syntaxdefinitionmanager.hpp b/include/eepp/ui/doc/syntaxdefinitionmanager.hpp index 69e5200fd..7393fcbb3 100644 --- a/include/eepp/ui/doc/syntaxdefinitionmanager.hpp +++ b/include/eepp/ui/doc/syntaxdefinitionmanager.hpp @@ -29,6 +29,8 @@ class EE_API SyntaxDefinitionManager { std::vector getLanguageNames() const; + std::vector getExtensionsPatternsSupported() const; + protected: SyntaxDefinitionManager(); diff --git a/include/eepp/ui/models/itemlistmodel.hpp b/include/eepp/ui/models/itemlistmodel.hpp new file mode 100644 index 000000000..5e9258fcc --- /dev/null +++ b/include/eepp/ui/models/itemlistmodel.hpp @@ -0,0 +1,40 @@ +#ifndef EE_UI_MODELS_ITEMLISTMODEL_HPP +#define EE_UI_MODELS_ITEMLISTMODEL_HPP + +#include +#include +#include + +namespace EE { namespace UI { namespace Models { + +template class ItemListModel final : public Model { + public: + static std::shared_ptr create( std::vector& data ) { + return std::make_shared( *new ItemListModel( data ) ); + } + + virtual ~ItemListModel() {} + + virtual size_t rowCount( const ModelIndex& ) const { return mData.size(); } + + virtual size_t columnCount( const ModelIndex& ) const { return 1; } + + virtual std::string columnName( const size_t& ) const { return "Data"; } + + virtual Variant data( const ModelIndex& index, Role role = Role::Display ) const { + if ( role == Role::Display ) + return Variant( mData[ index.row() ] ); + return {}; + } + + virtual void update() { onModelUpdate(); } + + private: + explicit ItemListModel( std::vector& data ) : mData( data ) {} + + std::vector& mData; +}; + +}}} // namespace EE::UI::Models + +#endif // EE_UI_MODELS_ITEMLISTMODEL_HPP diff --git a/include/eepp/ui/models/model.hpp b/include/eepp/ui/models/model.hpp index a1793b6ea..4e4741447 100644 --- a/include/eepp/ui/models/model.hpp +++ b/include/eepp/ui/models/model.hpp @@ -1,5 +1,5 @@ -#ifndef EE_UI_MODEL_MODEL_HPP -#define EE_UI_MODEL_MODEL_HPP +#ifndef EE_UI_MODELS_MODEL_HPP +#define EE_UI_MODELS_MODEL_HPP #include #include @@ -102,6 +102,6 @@ inline ModelIndex ModelIndex::parent() const { return mModel ? mModel->parentIndex( *this ) : ModelIndex(); } -}}} // namespace EE::UI::Model +}}} // namespace EE::UI::Models -#endif // EE_UI_MODEL_MODEL_HPP +#endif // EE_UI_MODELS_MODEL_HPP diff --git a/include/eepp/ui/uihelper.hpp b/include/eepp/ui/uihelper.hpp index 64c0a56c6..0a502eb3c 100644 --- a/include/eepp/ui/uihelper.hpp +++ b/include/eepp/ui/uihelper.hpp @@ -94,6 +94,7 @@ enum UINodeType { UI_TYPE_TREEVIEW, UI_TYPE_SCROLLABLEWIDGET, UI_TYPE_TREEVIEW_CELL, + UI_TYPE_TABLEVIEW, UI_TYPE_USER = 10000 }; diff --git a/include/eepp/ui/uilayout.hpp b/include/eepp/ui/uilayout.hpp index baa0f9476..0d007fcd4 100644 --- a/include/eepp/ui/uilayout.hpp +++ b/include/eepp/ui/uilayout.hpp @@ -17,6 +17,8 @@ class EE_API UILayout : public UIWidget { virtual const Sizef& getSize() const; + virtual void updateLayout(); + protected: friend class UISceneNode; @@ -34,8 +36,6 @@ class EE_API UILayout : public UIWidget { virtual void tryUpdateLayout(); - virtual void updateLayout(); - virtual void updateLayoutTree(); void setLayoutDirty(); diff --git a/include/eepp/ui/uipushbutton.hpp b/include/eepp/ui/uipushbutton.hpp index f0a26d531..21ece7e0f 100644 --- a/include/eepp/ui/uipushbutton.hpp +++ b/include/eepp/ui/uipushbutton.hpp @@ -56,6 +56,8 @@ class EE_API UIPushButton : public UIWidget { UIWidget* getFirstInnerItem() const; + virtual void updateLayout(); + protected: UIImage* mIcon; UITextView* mTextBox; @@ -64,8 +66,6 @@ class EE_API UIPushButton : public UIWidget { explicit UIPushButton( const std::string& tag ); - virtual void updateLayout(); - virtual void onSizeChange(); virtual void onAlphaChange(); diff --git a/include/eepp/ui/uisplitter.hpp b/include/eepp/ui/uisplitter.hpp index 7a624c67e..0d8f1be39 100644 --- a/include/eepp/ui/uisplitter.hpp +++ b/include/eepp/ui/uisplitter.hpp @@ -42,6 +42,8 @@ class EE_API UISplitter : public UILayout { virtual std::string getPropertyString( const PropertyDefinition* propertyDef, const Uint32& propertyIndex = 0 ); + virtual void updateLayout(); + protected: UIOrientation mOrientation; bool mAlwaysShowSplitter; @@ -54,8 +56,6 @@ class EE_API UISplitter : public UILayout { virtual void onChildCountChange( Node* child, const bool& removed ); - virtual void updateLayout(); - virtual Uint32 onMessage( const NodeMessage* Msg ); void updateFromDrag(); diff --git a/include/eepp/ui/uitableview.hpp b/include/eepp/ui/uitableview.hpp new file mode 100644 index 000000000..0a0a743a0 --- /dev/null +++ b/include/eepp/ui/uitableview.hpp @@ -0,0 +1,41 @@ +#ifndef EE_UI_UITABLEVIEW_HPP +#define EE_UI_UITABLEVIEW_HPP + +#include + +using namespace EE::UI::Abstract; + +namespace EE { namespace UI { + +class EE_API UITableView : public UIAbstractTableView { + public: + static UITableView* New(); + + Uint32 getType() const; + + bool isType( const Uint32& type ) const; + + virtual void drawChilds(); + + virtual Node* overFind( const Vector2f& point ); + + Float getMaxColumnContentWidth( const size_t& colIndex ); + + protected: + Sizef mContentSize; + + UITableView(); + + virtual void createOrUpdateColumns(); + + void updateContentSize(); + + void onColumnSizeChange( const size_t& ); + + virtual Uint32 onKeyDown( const KeyEvent& event ); + +}; + +}} // namespace EE::UI + +#endif // EE_UI_UITABLEVIEW_HPP diff --git a/include/eepp/ui/uitreeview.hpp b/include/eepp/ui/uitreeview.hpp index c17ae3d55..a569191b7 100644 --- a/include/eepp/ui/uitreeview.hpp +++ b/include/eepp/ui/uitreeview.hpp @@ -70,8 +70,6 @@ class EE_API UITreeView : public UIAbstractTableView { template void traverseTree( Callback ) const; mutable std::map mViewMetadata; - mutable std::vector> mWidgets; - mutable std::vector mRows; virtual size_t getItemCount() const; @@ -79,24 +77,13 @@ class EE_API UITreeView : public UIAbstractTableView { virtual void onColumnSizeChange( const size_t& colIndex ); - virtual UITableRow* createRow(); - - virtual UITableRow* updateRow( const int& rowIndex, const ModelIndex& index, - const Float& yOffset ); - virtual UIWidget* updateCell( const int& rowIndex, const ModelIndex& index, const size_t& indentLevel, const Float& yOffset ); - virtual UIWidget* createCell( UIWidget* rowWidget, const ModelIndex& index, const size_t& col ); - - virtual void onScrollChange(); - - virtual void onModelSelectionChange(); + virtual UIWidget* createCell( UIWidget* rowWidget, const ModelIndex& index ); virtual Uint32 onKeyDown( const KeyEvent& event ); - virtual void onOpenModelIndex( const ModelIndex& index ); - virtual void onOpenTreeModelIndex( const ModelIndex& index, bool open ); void updateContentSize(); diff --git a/projects/linux/ee.files b/projects/linux/ee.files index 429a16f6e..abb79976f 100644 --- a/projects/linux/ee.files +++ b/projects/linux/ee.files @@ -339,6 +339,7 @@ ../../include/eepp/ui/keyboardshortcut.hpp ../../include/eepp/ui/marginmove/scale.hpp ../../include/eepp/ui/models/filesystemmodel.hpp +../../include/eepp/ui/models/itemlistmodel.hpp ../../include/eepp/ui/models/model.hpp ../../include/eepp/ui/models/modelindex.hpp ../../include/eepp/ui/models/modelselection.hpp @@ -400,6 +401,7 @@ ../../include/eepp/ui/uitable.hpp ../../include/eepp/ui/uitableheadercolumn.hpp ../../include/eepp/ui/uitablerow.hpp +../../include/eepp/ui/uitableview.hpp ../../include/eepp/ui/uitabwidget.hpp ../../include/eepp/ui/uitextedit.hpp ../../include/eepp/ui/uitextinput.hpp @@ -867,6 +869,7 @@ ../../src/eepp/ui/uitablecell.cpp ../../src/eepp/ui/uitable.cpp ../../src/eepp/ui/uitableheadercolumn.cpp +../../src/eepp/ui/uitableview.cpp ../../src/eepp/ui/uitabwidget.cpp ../../src/eepp/ui/uitextedit.cpp ../../src/eepp/ui/uitextinput.cpp @@ -1005,6 +1008,8 @@ ../../src/tools/codeeditor/autocompletemodule.hpp ../../src/tools/codeeditor/codeeditor.cpp ../../src/tools/codeeditor/codeeditor.hpp +../../src/tools/codeeditor/projectdirectorytree.cpp +../../src/tools/codeeditor/projectdirectorytree.hpp ../../src/tools/codeeditor/uicodeeditorsplitter.cpp ../../src/tools/codeeditor/uicodeeditorsplitter.hpp ../../src/tools/mapeditor/mapeditor.cpp diff --git a/src/eepp/graphics/textureregion.cpp b/src/eepp/graphics/textureregion.cpp index 871c725c2..53eb12de1 100644 --- a/src/eepp/graphics/textureregion.cpp +++ b/src/eepp/graphics/textureregion.cpp @@ -51,8 +51,7 @@ TextureRegion::TextureRegion( const Uint32& TexId, const std::string& name ) : mTexture( TextureFactory::instance()->getTexture( TexId ) ), mSrcRect( Rect( 0, 0, NULL != mTexture ? mTexture->getImageWidth() : 0, NULL != mTexture ? mTexture->getImageHeight() : 0 ) ), - mOriDestSize( PixelDensity::dpToPx( - Sizef( (Float)mSrcRect.getSize().getWidth(), (Float)mSrcRect.getSize().getHeight() ) ) ), + mOriDestSize( PixelDensity::dpToPx( mSrcRect.getSize().asFloat() ) ), mDestSize( mOriDestSize ), mOffset( 0, 0 ), mPixelDensity( 1 ) {} diff --git a/src/eepp/scene/node.cpp b/src/eepp/scene/node.cpp index f16cf6724..20937c368 100644 --- a/src/eepp/scene/node.cpp +++ b/src/eepp/scene/node.cpp @@ -526,6 +526,18 @@ void Node::nodeDraw() { } } +void Node::forceKeyDown( const KeyEvent& event ) { + onKeyDown( event ); +} + +void Node::foceKeyUp( const KeyEvent& event ) { + onKeyUp( event ); +} + +void Node::forceTextInput( const TextInputEvent& event ) { + onTextInput( event ); +} + void Node::clipStart() { if ( mVisible && isClipped() ) { clipSmartEnable( mScreenPos.x, mScreenPos.y, mSize.getWidth(), mSize.getHeight() ); diff --git a/src/eepp/system/fileinfo.cpp b/src/eepp/system/fileinfo.cpp index 3c5969716..6bae8532f 100644 --- a/src/eepp/system/fileinfo.cpp +++ b/src/eepp/system/fileinfo.cpp @@ -121,8 +121,7 @@ void FileInfo::getInfo() { } void FileInfo::getRealInfo() { - if ( isDirectory() ) - FileSystem::dirRemoveSlashAtEnd( mFilepath ); + FileSystem::dirRemoveSlashAtEnd( mFilepath ); #if EE_PLATFORM != EE_PLATFORM_WIN struct stat st; diff --git a/src/eepp/system/luapattern.cpp b/src/eepp/system/luapattern.cpp index ce21468ba..365595e60 100644 --- a/src/eepp/system/luapattern.cpp +++ b/src/eepp/system/luapattern.cpp @@ -36,7 +36,7 @@ LuaPattern::LuaPattern( const std::string& pattern ) : mPattern( pattern ) { } bool LuaPattern::matches( const char* stringSearch, int stringStartOffset, - LuaPattern::Range* matchList, size_t stringLength ) { + LuaPattern::Range* matchList, size_t stringLength ) const { LuaPattern::Range matchesBuffer[MAX_DEFAULT_MATCHES]; if ( matchList == nullptr ) matchList = matchesBuffer; @@ -53,12 +53,12 @@ bool LuaPattern::matches( const char* stringSearch, int stringStartOffset, } bool LuaPattern::matches( const std::string& str, LuaPattern::Range* matchList, - int stringStartOffset ) { + int stringStartOffset ) const { return matches( str.c_str(), stringStartOffset, matchList, str.size() ); } bool LuaPattern::find( const char* stringSearch, int& startMatch, int& endMatch, - int stringStartOffset, int stringLength, int returnMatchIndex ) { + int stringStartOffset, int stringLength, int returnMatchIndex ) const { LuaPattern::Range matchesBuffer[MAX_DEFAULT_MATCHES]; if ( matches( stringSearch, stringStartOffset, matchesBuffer, stringLength ) ) { range( returnMatchIndex, startMatch, endMatch, matchesBuffer ); @@ -71,12 +71,12 @@ bool LuaPattern::find( const char* stringSearch, int& startMatch, int& endMatch, } bool LuaPattern::find( const std::string& s, int& startMatch, int& endMatch, int offset, - int returnedMatchIndex ) { + int returnedMatchIndex ) const { return find( s.c_str(), startMatch, endMatch, offset, s.size(), returnedMatchIndex ); } bool LuaPattern::range( int indexGet, int& startMatch, int& endMatch, - LuaPattern::Range* returnedMatched ) { + LuaPattern::Range* returnedMatched ) const { if ( indexGet == -1 ) indexGet = getNumMatches() > 1 ? 1 : 0; if ( indexGet >= 0 && indexGet < (int)getNumMatches() ) { @@ -87,7 +87,7 @@ bool LuaPattern::range( int indexGet, int& startMatch, int& endMatch, return false; } -const size_t& LuaPattern::getNumMatches() { +const size_t& LuaPattern::getNumMatches() const { return mMatchNum; } diff --git a/src/eepp/ui/abstract/uiabstracttableview.cpp b/src/eepp/ui/abstract/uiabstracttableview.cpp index 483bfc710..54e15eff6 100644 --- a/src/eepp/ui/abstract/uiabstracttableview.cpp +++ b/src/eepp/ui/abstract/uiabstracttableview.cpp @@ -44,6 +44,7 @@ void UIAbstractTableView::setColumnWidth( const size_t& colIndex, const Float& w columnData( colIndex ).width = width; updateHeaderSize(); onColumnSizeChange( colIndex ); + createOrUpdateColumns(); } } @@ -125,6 +126,8 @@ Float UIAbstractTableView::getHeaderHeight() const { } Sizef UIAbstractTableView::getContentSize() const { + if ( !getModel() ) + return {}; size_t count = getModel()->columnCount(); Sizef size; for ( size_t i = 0; i < count; i++ ) @@ -160,6 +163,8 @@ void UIAbstractTableView::onColumnResizeToContent( const size_t& colIndex ) { } void UIAbstractTableView::updateHeaderSize() { + if ( !getModel() ) + return; size_t count = getModel()->columnCount(); Float totalWidth = 0; for ( size_t i = 0; i < count; i++ ) { @@ -256,4 +261,125 @@ void UIAbstractTableView::setColumnsHidden( const std::vector columns, b createOrUpdateColumns(); } +UITableRow* UIAbstractTableView::createRow() { + mUISceneNode->invalidateStyle( this ); + mUISceneNode->invalidateStyleState( this, true ); + UITableRow* rowWidget = UITableRow::New( "table::row" ); + 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() ); + } + } ); + return rowWidget; +} + +UITableRow* UIAbstractTableView::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 = createRow(); + mRows[rowIndex] = rowWidget; + } else { + rowWidget = mRows[rowIndex]; + } + rowWidget->setCurIndex( index ); + rowWidget->setPixelsSize( getContentSize().getWidth(), getRowHeight() ); + rowWidget->setPixelsPosition( {-mScrollOffset.x, yOffset - mScrollOffset.y} ); + if ( getSelection().contains( index ) ) { + rowWidget->pushState( UIState::StateSelected ); + } else { + rowWidget->popState( UIState::StateSelected ); + } + return rowWidget; +} + +void UIAbstractTableView::onScrollChange() { + mHeader->setPixelsPosition( -mScrollOffset.x, 0 ); + invalidateDraw(); +} + +UIWidget* UIAbstractTableView::createCell( UIWidget* rowWidget, const ModelIndex& ) { + UIPushButton* widget = UIPushButton::NewWithTag( "table::cell" ); + widget->setParent( rowWidget ); + widget->unsetFlags( UI_AUTO_SIZE ); + widget->clipEnable(); + widget->setLayoutSizePolicy( SizePolicy::Fixed, SizePolicy::Fixed ); + widget->setTextAlign( UI_HALIGN_LEFT ); + 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 ) { + onOpenModelIndex( idx ); + } + } ); + return widget; +} + +UIWidget* UIAbstractTableView::updateCell( const int& rowIndex, const ModelIndex& index, + const size_t&, const Float& yOffset ) { + if ( rowIndex >= (int)mWidgets.size() ) + mWidgets.resize( rowIndex + 1 ); + auto* widget = mWidgets[rowIndex][index.column()]; + if ( !widget ) { + UIWidget* rowWidget = updateRow( rowIndex, index, yOffset ); + widget = createCell( rowWidget, index ); + mWidgets[rowIndex][index.column()] = widget; + widget->reloadStyle( true, true, true ); + } + widget->setPixelsSize( columnData( index.column() ).width, getRowHeight() ); + widget->setPixelsPosition( {getColumnPosition( index.column() ).x, 0} ); + + if ( widget->isType( UI_TYPE_PUSHBUTTON ) ) { + UIPushButton* pushButton = widget->asType(); + + Variant txt( getModel()->data( index, Model::Role::Display ) ); + if ( txt.isValid() ) { + if ( txt.is( Variant::Type::String ) ) + pushButton->setText( txt.asString() ); + else if ( txt.is( Variant::Type::cstr ) ) + pushButton->setText( txt.asCStr() ); + } + + bool isVisible = false; + Variant icon( getModel()->data( index, Model::Role::Icon ) ); + if ( icon.is( Variant::Type::Drawable ) && icon.asDrawable() ) { + isVisible = true; + pushButton->setIcon( icon.asDrawable() ); + } + pushButton->getIcon()->setVisible( isVisible ); + } + + return widget; +} + +void UIAbstractTableView::moveSelection( int steps ) { + if ( !getModel() ) + return; + auto& model = *this->getModel(); + ModelIndex newIndex; + if ( !getSelection().isEmpty() ) { + auto oldIndex = getSelection().first(); + newIndex = model.index( oldIndex.row() + steps, oldIndex.column() ); + } else { + newIndex = model.index( 0, 0 ); + } + if ( model.isValid( newIndex ) ) { + getSelection().set( newIndex ); + scrollToPosition( {mScrollOffset.x, getHeaderHeight() + newIndex.row() * getRowHeight()} ); + } +} + +void UIAbstractTableView::onOpenModelIndex( const ModelIndex& index ) { + ModelEvent event( getModel(), index, this ); + sendEvent( &event ); +} + }}} // namespace EE::UI::Abstract diff --git a/src/eepp/ui/abstract/uiabstractview.cpp b/src/eepp/ui/abstract/uiabstractview.cpp index 12e8e311b..f6e9633a1 100644 --- a/src/eepp/ui/abstract/uiabstractview.cpp +++ b/src/eepp/ui/abstract/uiabstractview.cpp @@ -54,6 +54,7 @@ void UIAbstractView::onModelUpdate( unsigned flags ) { void UIAbstractView::onModelSelectionChange() { if ( getModel() && mOnSelection && getSelection().first().isValid() ) mOnSelection( getSelection().first() ); + invalidateDraw(); } void UIAbstractView::notifySelectionChange() { diff --git a/src/eepp/ui/doc/syntaxdefinitionmanager.cpp b/src/eepp/ui/doc/syntaxdefinitionmanager.cpp index ed88624cd..d5edd2e64 100644 --- a/src/eepp/ui/doc/syntaxdefinitionmanager.cpp +++ b/src/eepp/ui/doc/syntaxdefinitionmanager.cpp @@ -1788,6 +1788,14 @@ std::vector SyntaxDefinitionManager::getLanguageNames() const { return names; } +std::vector SyntaxDefinitionManager::getExtensionsPatternsSupported() const { + std::vector exts; + for ( auto& style : mStyles ) + for ( auto& pattern : style.getFiles() ) + exts.emplace_back( pattern ); + return exts; +} + const SyntaxDefinition& SyntaxDefinitionManager::getStyleByExtension( const std::string& filePath ) const { std::string extension( FileSystem::fileExtension( filePath ) ); diff --git a/src/eepp/ui/uinode.cpp b/src/eepp/ui/uinode.cpp index 6e89d15b9..8f7c809cc 100644 --- a/src/eepp/ui/uinode.cpp +++ b/src/eepp/ui/uinode.cpp @@ -343,8 +343,8 @@ void UINode::updateDebugData() { text += "Classes: " + String::join( widget->getStyleSheetClasses(), ' ' ) + "\n"; } - text += String::format( "X: %.2f Y: %.2f\nW: %.2f H: %.2f", mDpPos.x, mDpPos.y, mDpSize.x, - mDpSize.y ); + text += String::format( "X: %.2f Y: %.2f\nW: %.2f (%.2f) H: %.2f (%.2f)", mSize.x, mDpPos.x, + mSize.y, mDpPos.y, mDpSize.x, mDpSize.y ); if ( widget->getPadding() != Rectf( 0, 0, 0, 0 ) ) { Rectf p( widget->getPadding() ); @@ -1179,10 +1179,10 @@ Float UINode::getPropertyRelativeTargetContainerLength( Float containerLength = defaultValue; switch ( relativeTarget ) { case PropertyRelativeTarget::ContainingBlockWidth: - containerLength = getParent()->getPixelsSize().getWidth(); + containerLength = getParent() ? getParent()->getPixelsSize().getWidth() : 0; break; case PropertyRelativeTarget::ContainingBlockHeight: - containerLength = getParent()->getPixelsSize().getHeight(); + containerLength = getParent() ? getParent()->getPixelsSize().getHeight() : 0; break; case PropertyRelativeTarget::LocalBlockWidth: containerLength = getPixelsSize().getWidth(); diff --git a/src/eepp/ui/uipushbutton.cpp b/src/eepp/ui/uipushbutton.cpp index 1938d1d50..4a6eb8ce1 100644 --- a/src/eepp/ui/uipushbutton.cpp +++ b/src/eepp/ui/uipushbutton.cpp @@ -221,7 +221,7 @@ void UIPushButton::onThemeLoaded() { UIPushButton* UIPushButton::setIcon( Drawable* icon ) { if ( mIcon->getDrawable() != icon ) { - mIcon->setSize( icon->getSize() ); + mIcon->setPixelsSize( icon->getPixelsSize() ); mIcon->setDrawable( icon ); updateLayout(); } diff --git a/src/eepp/ui/uitableview.cpp b/src/eepp/ui/uitableview.cpp new file mode 100644 index 000000000..2c662f1b8 --- /dev/null +++ b/src/eepp/ui/uitableview.cpp @@ -0,0 +1,189 @@ +#include +#include +#include +#include +#include + +namespace EE { namespace UI { + +UITableView* UITableView::New() { + return eeNew( UITableView, () ); +} + +UITableView::UITableView() : UIAbstractTableView( "tableview" ) { + clipEnable(); +} + +Uint32 UITableView::getType() const { + return UI_TYPE_TABLEVIEW; +} + +bool UITableView::isType( const Uint32& type ) const { + return UITableView::getType() == type ? true : UIAbstractTableView::isType( type ); +} + +void UITableView::drawChilds() { + int realIndex = 0; + + size_t start = mScrollOffset.y / getRowHeight(); + size_t end = + eemin( (size_t)eeceil( ( mScrollOffset.y + mSize.getHeight() ) / getRowHeight() ), + getItemCount() ); + Float yOffset; + for ( size_t i = start; i < end; i++ ) { + yOffset = getHeaderHeight() + i * getRowHeight(); + ModelIndex index( getModel()->index( i ) ); + if ( yOffset - mScrollOffset.y > mSize.getHeight() ) + break; + if ( yOffset - mScrollOffset.y + getRowHeight() < 0 ) + continue; + for ( size_t colIndex = 0; colIndex < getModel()->columnCount(); colIndex++ ) { + if ( columnData( colIndex ).visible ) { + updateCell( realIndex, getModel()->index( index.row(), colIndex, index.parent() ), + 0, yOffset ); + } + } + updateRow( realIndex, index, yOffset )->nodeDraw(); + realIndex++; + } + + if ( mHeader && mHeader->isVisible() ) + mHeader->nodeDraw(); + if ( mHScroll->isVisible() ) + mHScroll->nodeDraw(); + if ( mVScroll->isVisible() ) + mVScroll->nodeDraw(); +} + +Node* UITableView::overFind( const Vector2f& point ) { + mUISceneNode->setIsLoading( true ); + + 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; + int realIndex = 0; + Float yOffset; + size_t start = mScrollOffset.y / getRowHeight(); + size_t end = eemin( + (size_t)eeceil( ( mScrollOffset.y + mSize.getHeight() ) / getRowHeight() ), + getItemCount() ); + for ( size_t i = start; i < end; i++ ) { + yOffset = getHeaderHeight() + i * getRowHeight(); + ModelIndex index( getModel()->index( i ) ); + if ( yOffset - mScrollOffset.y > mSize.getHeight() ) + break; + if ( yOffset - mScrollOffset.y + getRowHeight() < 0 ) + continue; + for ( size_t colIndex = 0; colIndex < getModel()->columnCount(); colIndex++ ) { + if ( columnData( colIndex ).visible ) { + updateCell( realIndex, + getModel()->index( index.row(), colIndex, index.parent() ), 0, + yOffset ); + } + } + pOver = updateRow( realIndex, index, yOffset )->overFind( point ); + if ( pOver ) + break; + realIndex++; + } + if ( !pOver ) + pOver = this; + } + } + + mUISceneNode->setIsLoading( false ); + return pOver; +} + +Float UITableView::getMaxColumnContentWidth( const size_t& colIndex ) { + Float lWidth = 0; + getUISceneNode()->setIsLoading( true ); + Float yOffset = getHeaderHeight(); + for ( size_t i = 0; i < getItemCount(); i++ ) { + ModelIndex index( getModel()->index( i, colIndex ) ); + UIWidget* widget = updateCell( 0, index, 0, yOffset ); + if ( widget->isType( UI_TYPE_PUSHBUTTON ) ) { + Float w = widget->asType()->getContentSize().getWidth(); + if ( w > lWidth ) + lWidth = w; + } + yOffset += getRowHeight(); + } + getUISceneNode()->setIsLoading( false ); + return lWidth; +} + +void UITableView::createOrUpdateColumns() { + if ( !getModel() ) + return; + UIAbstractTableView::createOrUpdateColumns(); + updateContentSize(); +} + +void UITableView::updateContentSize() { + Sizef oldSize( mContentSize ); + mContentSize = UIAbstractTableView::getContentSize(); + if ( oldSize != mContentSize ) + onContentSizeChange(); +} + +void UITableView::onColumnSizeChange( const size_t& ) { + updateContentSize(); +} + +Uint32 UITableView::onKeyDown( const KeyEvent& event ) { + if ( event.getMod() != 0 ) + return 0; + + auto curIndex = getSelection().first(); + int pageSize = eefloor( getVisibleArea().getHeight() / getRowHeight() ) - 1; + + switch ( event.getKeyCode() ) { + case KEY_PAGEUP: { + moveSelection( -pageSize ); + break; + } + case KEY_PAGEDOWN: { + moveSelection( pageSize ); + break; + } + case KEY_UP: { + moveSelection( -1 ); + break; + } + case KEY_DOWN: { + moveSelection( 1 ); + break; + } + case KEY_END: { + scrollToBottom(); + getSelection().set( getModel()->index( getItemCount() - 1 ) ); + break; + } + case KEY_HOME: { + scrollToTop(); + getSelection().set( getModel()->index( 0, 0 ) ); + break; + } + case KEY_RETURN: + case KEY_SPACE: { + if ( curIndex.isValid() ) + onOpenModelIndex( curIndex ); + break; + } + default: + break; + } + return 0; +} + +}} // namespace EE::UI diff --git a/src/eepp/ui/uitextinput.cpp b/src/eepp/ui/uitextinput.cpp index 8cc44970b..1baffa075 100644 --- a/src/eepp/ui/uitextinput.cpp +++ b/src/eepp/ui/uitextinput.cpp @@ -729,7 +729,7 @@ Uint32 UITextInput::onKeyDown( const KeyEvent& event ) { return 1; } } - return 0; + return UITextView::onKeyDown( event ); } Uint32 UITextInput::onTextInput( const TextInputEvent& event ) { diff --git a/src/eepp/ui/uitreeview.cpp b/src/eepp/ui/uitreeview.cpp index f0b718e28..431c15162 100644 --- a/src/eepp/ui/uitreeview.cpp +++ b/src/eepp/ui/uitreeview.cpp @@ -98,43 +98,11 @@ void UITreeView::onColumnSizeChange( const size_t& ) { updateContentSize(); } -UITableRow* UITreeView::createRow() { - UITableRow* rowWidget = UITableRow::New( "table::row" ); - 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() ); - } - } ); - return rowWidget; -} - -UITableRow* 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 = createRow(); - 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->clearActions(); - rowWidget->pushState( UIState::StateSelected ); - } else { - rowWidget->popState( UIState::StateSelected ); - } - return rowWidget; +void UITreeView::updateContentSize() { + Sizef oldSize( mContentSize ); + mContentSize = UIAbstractTableView::getContentSize(); + if ( oldSize != mContentSize ) + onContentSizeChange(); } class UITreeViewCell : public UIPushButton { @@ -198,7 +166,7 @@ class UITreeViewCell : public UIPushButton { auto cb = [&]( const Event* ) { updateLayout(); }; mImage = UIImage::NewWithTag( mTag + "::expander" ); mImage->setScaleType( UIScaleType::FitInside ) - ->setLayoutSizePolicy( SizePolicy::WrapContent, SizePolicy::WrapContent ) + ->setLayoutSizePolicy( SizePolicy::Fixed, SizePolicy::Fixed ) ->setFlags( UI_VALIGN_CENTER | UI_HALIGN_CENTER ) ->setParent( const_cast( this ) ) ->setVisible( false ) @@ -212,16 +180,16 @@ class UITreeViewCell : public UIPushButton { virtual UIWidget* getExtraInnerWidget() const { return mImage; } }; -UIWidget* UITreeView::createCell( UIWidget* rowWidget, const ModelIndex&, const size_t& col ) { - UIPushButton* widget = col == getModel()->treeColumn() +UIWidget* UITreeView::createCell( UIWidget* rowWidget, const ModelIndex& index ) { + UIPushButton* widget = index.column() == (Int64)getModel()->treeColumn() ? UITreeViewCell::New() : 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->setTextAlign( UI_HALIGN_LEFT ); + if ( index.column() == (Int64)getModel()->treeColumn() ) { widget->addEventListener( Event::MouseDoubleClick, [&]( const Event* event ) { auto mouseEvent = static_cast( event ); auto idx = mouseEvent->getNode()->getParent()->asType()->getCurIndex(); @@ -257,18 +225,6 @@ UIWidget* UITreeView::createCell( UIWidget* rowWidget, const ModelIndex&, const 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& indentLevel, const Float& yOffset ) { if ( rowIndex >= (int)mWidgets.size() ) @@ -276,7 +232,7 @@ UIWidget* UITreeView::updateCell( const int& rowIndex, const ModelIndex& index, auto* widget = mWidgets[rowIndex][index.column()]; if ( !widget ) { UIWidget* rowWidget = updateRow( rowIndex, index, yOffset ); - widget = createCell( rowWidget, index, index.column() ); + widget = createCell( rowWidget, index ); mWidgets[rowIndex][index.column()] = widget; widget->reloadStyle( true, true, true ); } @@ -315,6 +271,7 @@ UIWidget* UITreeView::updateCell( const int& rowIndex, const ModelIndex& index, Drawable* icon = getIndexMetadata( index ).open ? mExpandIcon : mContractIcon; image->setVisible( true ); + image->setPixelsSize( icon->getPixelsSize() ); image->setDrawable( icon ); if ( !mExpandersAsIcons ) { cell->setIndentation( cell->getIndentation() - @@ -361,8 +318,6 @@ Sizef UITreeView::getContentSize() const { void UITreeView::drawChilds() { int realIndex = 0; - mUISceneNode->setIsLoading( true ); - traverseTree( [&]( const int&, const ModelIndex& index, const size_t& indentLevel, const Float& yOffset ) { if ( yOffset - mScrollOffset.y > mSize.getHeight() ) @@ -385,8 +340,6 @@ void UITreeView::drawChilds() { return IterationDecision::Continue; } ); - mUISceneNode->setIsLoading( false ); - if ( mHeader && mHeader->isVisible() ) mHeader->nodeDraw(); if ( mHScroll->isVisible() ) @@ -396,6 +349,8 @@ void UITreeView::drawChilds() { } Node* UITreeView::overFind( const Vector2f& point ) { + mUISceneNode->setIsLoading( true ); + Node* pOver = NULL; if ( mEnabled && mVisible ) { updateWorldPolygon(); @@ -425,6 +380,9 @@ Node* UITreeView::overFind( const Vector2f& point ) { pOver = this; } } + + mUISceneNode->setIsLoading( false ); + return pOver; } @@ -480,11 +438,6 @@ Float UITreeView::getMaxColumnContentWidth( const size_t& colIndex ) { return lWidth; } -void UITreeView::onModelSelectionChange() { - UIAbstractTableView::onModelSelectionChange(); - invalidateDraw(); -} - Uint32 UITreeView::onKeyDown( const KeyEvent& event ) { if ( event.getMod() != 0 ) return 0; @@ -596,8 +549,8 @@ Uint32 UITreeView::onKeyDown( const KeyEvent& event ) { mVScroll->setValue( eemin( 1.f, curY / getScrollableArea().getHeight() ) ); } - break; } + break; } case KEY_END: { scrollToBottom(); @@ -660,11 +613,6 @@ Uint32 UITreeView::onKeyDown( const KeyEvent& event ) { return 0; } -void UITreeView::onOpenModelIndex( const ModelIndex& index ) { - ModelEvent event( getModel(), index, this ); - sendEvent( &event ); -} - void UITreeView::onOpenTreeModelIndex( const ModelIndex& index, bool open ) { ModelEvent event( getModel(), index, this, open ? ModelEventType::OpenTree : ModelEventType::CloseTree ); diff --git a/src/eepp/ui/uiwidgetcreator.cpp b/src/eepp/ui/uiwidgetcreator.cpp index 2f1aa0e9a..b75f56118 100644 --- a/src/eepp/ui/uiwidgetcreator.cpp +++ b/src/eepp/ui/uiwidgetcreator.cpp @@ -22,6 +22,7 @@ #include #include #include +#include #include #include #include @@ -82,6 +83,7 @@ void UIWidgetCreator::createBaseWidgetList() { registeredWidget["codeeditor"] = UICodeEditor::New; registeredWidget["splitter"] = UISplitter::New; registeredWidget["treeview"] = UITreeView::New; + registeredWidget["tableview"] = UITableView::New; registeredWidget["hbox"] = UILinearLayout::NewHorizontal; registeredWidget["vbox"] = UILinearLayout::NewVertical; diff --git a/src/tests/ui_perf_test/ui_perf_test.cpp b/src/tests/ui_perf_test/ui_perf_test.cpp index bbf91c97b..683ca8015 100644 --- a/src/tests/ui_perf_test/ui_perf_test.cpp +++ b/src/tests/ui_perf_test/ui_perf_test.cpp @@ -1,8 +1,4 @@ #include -#include -#include -#include -#include using namespace EE::UI::Abstract; @@ -196,7 +192,8 @@ EE_MAIN_FUNC int main( int argc, char* argv[] ) { Clock clock; auto model = FileSystemModel::New( "." ); // std::make_shared(); - UITreeView* view = UITreeView::New(); + //UITreeView* view = UITreeView::New(); + UITableView* view = UITableView::New(); view->setId( "treeview" ); /*view->setExpandedIcon( open ); view->setContractedIcon( closed );*/ diff --git a/src/tools/codeeditor/codeeditor.cpp b/src/tools/codeeditor/codeeditor.cpp index b1440f038..dace65553 100644 --- a/src/tools/codeeditor/codeeditor.cpp +++ b/src/tools/codeeditor/codeeditor.cpp @@ -362,6 +362,95 @@ std::string App::getKeybind( const std::string& command ) { return ""; } +static int MAX_VISIBLE_ITEMS = 18; +static int MAX_RESULT_ITEMS = 100; + +void App::initLocateBar() { + auto addClickListener = [&]( UIWidget* widget, std::string cmd ) { + widget->addEventListener( Event::MouseClick, [this, cmd]( const Event* event ) { + const MouseEvent* mouseEvent = static_cast( event ); + if ( mouseEvent->getFlags() & EE_BUTTON_LMASK ) + mLocateBarLayout->execute( cmd ); + } ); + }; + mLocateTable = UITableView::New(); + mLocateTable->setParent( mUISceneNode->getRoot() ); + mLocateTable->setHeadersVisible( false ); + mLocateTable->setVisible( false ); + mLocateInput->addEventListener( Event::OnTextChanged, [&]( const Event* ) { + Vector2f pos( mLocateBarLayout->convertToWorldSpace( {0, 0} ) ); + pos.y -= mLocateTable->getPixelsSize().getHeight(); + mLocateTable->setPixelsPosition( pos ); + if ( !mDirTreeReady ) + return; + if ( !mLocateInput->getText().empty() ) { + mLocateTable->setModel( + mDirTree->fuzzyMatchTree( mLocateInput->getText(), MAX_RESULT_ITEMS ) ); + } else { + mLocateTable->setModel( mDirTree->asModel( MAX_RESULT_ITEMS ) ); + } + mLocateTable->getSelection().set( mLocateTable->getModel()->index( 0 ) ); + } ); + mLocateInput->addEventListener( Event::OnPressEnter, [&]( const Event* ) { + KeyEvent keyEvent( mLocateTable, Event::KeyDown, KEY_RETURN, 0, 0 ); + mLocateTable->forceKeyDown( keyEvent ); + } ); + mLocateInput->addEventListener( Event::KeyDown, [&]( const Event* event ) { + const KeyEvent* keyEvent = reinterpret_cast( event ); + mLocateTable->forceKeyDown( *keyEvent ); + } ); + mLocateBarLayout->addCommand( "close-locatebar", [&] { + mLocateBarLayout->setVisible( false ); + mLocateTable->setVisible( false ); + mEditorSplitter->getCurEditor()->setFocus(); + } ); + mLocateBarLayout->getKeyBindings().addKeybindsString( { + {"escape", "close-locatebar"}, + } ); + addClickListener( mLocateBarLayout->find( "locatebar_close" ), "close-locatebar" ); + mLocateTable->addEventListener( Event::OnModelEvent, [&]( const Event* event ) { + const ModelEvent* modelEvent = static_cast( event ); + if ( modelEvent->getModelEventType() == ModelEventType::Open ) { + Variant vPath( modelEvent->getModel()->data( + modelEvent->getModel()->index( modelEvent->getModelIndex().row(), 1 ), + Model::Role::Display ) ); + if ( vPath.isValid() && vPath.is( Variant::Type::cstr ) ) { + std::string path( vPath.asCStr() ); + UITab* tab = mEditorSplitter->isDocumentOpen( path ); + if ( !tab ) { + FileInfo fileInfo( path ); + if ( fileInfo.exists() && fileInfo.isRegularFile() ) + mEditorSplitter->loadFileFromPathInNewTab( path ); + } else { + tab->getTabWidget()->setTabSelected( tab ); + } + mLocateBarLayout->execute( "close-locatebar" ); + } + } + } ); +} + +void App::showLocateBar() { + mLocateBarLayout->setVisible( true ); + mLocateInput->setFocus(); + mLocateTable->setVisible( true ); + if ( !mLocateTable->getModel() ) { + mLocateTable->setModel( mDirTree->asModel( MAX_RESULT_ITEMS ) ); + mLocateTable->getSelection().set( mLocateTable->getModel()->index( 0 ) ); + } + mLocateBarLayout->runOnMainThread( [&] { + Float width = eeceil( mLocateInput->getPixelsSize().getWidth() ); + mLocateTable->setPixelsSize( width, mLocateTable->getRowHeight() * MAX_VISIBLE_ITEMS ); + mLocateTable->setColumnWidth( 0, width * 0.5f ); + mLocateTable->setColumnWidth( + 1, + width * 0.5f - mLocateTable->getVerticalScrollBar()->getPixelsSize().getWidth() - 4 ); + Vector2f pos( mLocateBarLayout->convertToWorldSpace( {0, 0} ) ); + pos.y -= mLocateTable->getPixelsSize().getHeight(); + mLocateTable->setPixelsPosition( pos ); + } ); +} + void App::initSearchBar() { auto addClickListener = [&]( UIWidget* widget, std::string cmd ) { widget->addEventListener( Event::MouseClick, [this, cmd]( const Event* event ) { @@ -390,6 +479,7 @@ void App::initSearchBar() { findInput->removeClass( "error" ); } } else { + findInput->removeClass( "error" ); mSearchState.editor->getDocument().setSelection( mSearchState.editor->getDocument().getSelection().start() ); } @@ -527,7 +617,7 @@ void App::onTextDropped( String text ) { } } -App::App() {} +App::App() : mThreadPool( ThreadPool::createShared( eemin( 4, Sys::getCPUCount() ) ) ) {} App::~App() { saveConfig(); @@ -1131,6 +1221,7 @@ std::map App::getLocalKeybindings() { {{KEY_F6, KEYMOD_NONE}, "debug-draw-highlight-toggle"}, {{KEY_F7, KEYMOD_NONE}, "debug-draw-boxes-toggle"}, {{KEY_F8, KEYMOD_NONE}, "debug-draw-debug-data"}, + {{KEY_K, KEYMOD_CTRL}, "locate"}, }; } @@ -1166,6 +1257,7 @@ void App::onCodeEditorCreated( UICodeEditor* editor, TextDocument& doc ) { doc.setCommand( "save-doc", [&] { saveDoc(); } ); doc.setCommand( "save-as-doc", [&] { saveFileDialog(); } ); doc.setCommand( "find-replace", [&] { showFindView(); } ); + doc.setCommand( "locate", [&] { showLocateBar(); } ); doc.setCommand( "repeat-find", [&] { findNextText( mSearchState ); } ); doc.setCommand( "close-app", [&] { closeApp(); } ); doc.setCommand( "fullscreen-toggle", [&]() { @@ -1239,7 +1331,7 @@ void App::onCodeEditorCreated( UICodeEditor* editor, TextDocument& doc ) { bool App::setAutoComplete( bool enable ) { mConfig.editor.autoComplete = enable; if ( enable && !mAutoCompleteModule ) { - mAutoCompleteModule = eeNew( AutoCompleteModule, () ); + mAutoCompleteModule = eeNew( AutoCompleteModule, ( mThreadPool ) ); mEditorSplitter->forEachEditor( [&]( UICodeEditor* editor ) { editor->registerModule( mAutoCompleteModule ); } ); return true; @@ -1444,11 +1536,13 @@ void App::init( const std::string& file, const Float& pidelDensity ) { const std::string baseUI = R"xml( - + @@ -1532,6 +1626,10 @@ void App::init( const std::string& file, const Float& pidelDensity ) { + + + + @@ -1583,13 +1681,17 @@ void App::init( const std::string& file, const Float& pidelDensity ) { mUISceneNode->getUIIconThemeManager()->setCurrentTheme( iconTheme ); UIWidgetCreator::registerWidget( "searchbar", [] { return UISearchBar::New(); } ); + UIWidgetCreator::registerWidget( "locatebar", [] { return UILocateBar::New(); } ); mUISceneNode->loadLayoutFromString( baseUI ); + mUISceneNode->bind( "main_layout", mMainLayout ); mUISceneNode->bind( "code_container", mBaseLayout ); mUISceneNode->bind( "search_bar", mSearchBarLayout ); + mUISceneNode->bind( "locate_bar", mLocateBarLayout ); mUISceneNode->bind( "doc_info", mDocInfo ); mUISceneNode->bind( "doc_info_text", mDocInfoText ); mUISceneNode->bind( "panel", mSidePanel ); mUISceneNode->bind( "project_splitter", mProjectSplitter ); + mUISceneNode->bind( "locate_find", mLocateInput ); mDocInfo->setVisible( mConfig.editor.showDocInfo ); mSearchBarLayout->setVisible( false )->setEnabled( false ); mProjectSplitter->setSplitPartition( StyleSheetLength( mConfig.window.panelPartition ) ); @@ -1603,22 +1705,24 @@ void App::init( const std::string& file, const Float& pidelDensity ) { initSearchBar(); + initLocateBar(); + createSettingsMenu(); mEditorSplitter->createEditorWithTabWidget( mBaseLayout ); mConsole = eeNew( Console, ( fontMono, true, true, 1024 * 1000, 0, mWindow ) ); - UITreeView* tree = mUISceneNode->find( "project_view" ); - tree->setColumnsHidden( {FileSystemModel::Icon, FileSystemModel::Size, - FileSystemModel::Group, FileSystemModel::Inode, - FileSystemModel::Owner, FileSystemModel::SymlinkTarget, - FileSystemModel::Permissions, FileSystemModel::ModificationTime, - FileSystemModel::Path}, - true ); - tree->setHeadersVisible( false ); - tree->setExpandersAsIcons( true ); - tree->addEventListener( Event::OnModelEvent, [&]( const Event* event ) { + mProjectTreeView = mUISceneNode->find( "project_view" ); + mProjectTreeView->setColumnsHidden( + {FileSystemModel::Icon, FileSystemModel::Size, FileSystemModel::Group, + FileSystemModel::Inode, FileSystemModel::Owner, FileSystemModel::SymlinkTarget, + FileSystemModel::Permissions, FileSystemModel::ModificationTime, + FileSystemModel::Path}, + true ); + mProjectTreeView->setHeadersVisible( false ); + mProjectTreeView->setExpandersAsIcons( true ); + mProjectTreeView->addEventListener( Event::OnModelEvent, [&]( const Event* event ) { const ModelEvent* modelEvent = static_cast( event ); if ( modelEvent->getModelEventType() == ModelEventType::Open ) { Variant vPath( modelEvent->getModel()->data( modelEvent->getModelIndex(), @@ -1627,7 +1731,9 @@ void App::init( const std::string& file, const Float& pidelDensity ) { std::string path( vPath.asCStr() ); UITab* tab = mEditorSplitter->isDocumentOpen( path ); if ( !tab ) { - mEditorSplitter->loadFileFromPathInNewTab( path ); + FileInfo fileInfo( path ); + if ( fileInfo.exists() && fileInfo.isRegularFile() ) + mEditorSplitter->loadFileFromPathInNewTab( path ); } else { tab->getTabWidget()->setTabSelected( tab ); } @@ -1637,22 +1743,39 @@ void App::init( const std::string& file, const Float& pidelDensity ) { if ( !file.empty() && FileSystem::fileExists( file ) ) { if ( FileSystem::isDirectory( file ) ) { - tree->setModel( FileSystemModel::New( file ) ); + loadDirTree( FileSystem::getRealPath( file ) ); + mProjectTreeView->setModel( FileSystemModel::New( file ) ); } else { std::string rpath( FileSystem::getRealPath( file ) ); - tree->setModel( FileSystemModel::New( FileSystem::fileRemoveFileName( rpath ) ) ); + mProjectTreeView->setModel( + FileSystemModel::New( FileSystem::fileRemoveFileName( rpath ) ) ); mEditorSplitter->loadFileFromPath( rpath ); } } else { - tree->setModel( FileSystemModel::New( "." ) ); + loadDirTree( FileSystem::getRealPath( "." ) ); + mProjectTreeView->setModel( FileSystemModel::New( "." ) ); } - tree->setAutoExpandOnSingleColumn( true ); + mProjectTreeView->setAutoExpandOnSingleColumn( true ); mWindow->runMainLoop( &appLoop ); } } +void App::loadDirTree( const std::string& path ) { + Clock* clock = eeNew( Clock, () ); + mDirTree = std::make_unique( path, mThreadPool ); + eePRINTL( "Loading DirTree: %s", path.c_str() ); + mDirTree->scan( + [&, clock]( ProjectDirectoryTree& dirTree ) { + eePRINTL( "DirTree read in: %.2fms. Found %ld files.", + clock->getElapsedTime().asMilliseconds(), dirTree.getFilesCount() ); + eeDelete( clock ); + mDirTreeReady = true; + }, + SyntaxDefinitionManager::instance()->getExtensionsPatternsSupported() ); +} + EE_MAIN_FUNC int main( int argc, char* argv[] ) { args::ArgumentParser parser( "ecode" ); args::HelpFlag help( parser, "help", "Display this help menu", {'h', "help"} ); diff --git a/src/tools/codeeditor/codeeditor.hpp b/src/tools/codeeditor/codeeditor.hpp index f263b0d8a..416d7e662 100644 --- a/src/tools/codeeditor/codeeditor.hpp +++ b/src/tools/codeeditor/codeeditor.hpp @@ -1,6 +1,7 @@ #ifndef EE_TOOLS_CODEEDITOR_HPP #define EE_TOOLS_CODEEDITOR_HPP +#include "projectdirectorytree.hpp" #include class UISearchBar : public UILinearLayout { @@ -35,6 +36,38 @@ class UISearchBar : public UILinearLayout { } }; +class UILocateBar : public UILinearLayout { + public: + typedef std::function CommandCallback; + static UILocateBar* New() { return eeNew( UILocateBar, () ); } + UILocateBar() : + UILinearLayout( "locatebar", UIOrientation::Horizontal ), + mKeyBindings( getUISceneNode()->getWindow()->getInput() ) {} + void addCommand( const std::string& name, const CommandCallback& cb ) { mCommands[name] = cb; } + void execute( const std::string& command ) { + auto cmdIt = mCommands.find( command ); + if ( cmdIt != mCommands.end() ) + cmdIt->second(); + } + KeyBindings& getKeyBindings() { return mKeyBindings; } + + protected: + KeyBindings mKeyBindings; + std::unordered_map> mCommands; + Uint32 onKeyDown( const KeyEvent& event ) { + std::string cmd = + mKeyBindings.getCommandFromKeyBind( {event.getKeyCode(), event.getMod()} ); + if ( !cmd.empty() ) { + auto cmdIt = mCommands.find( cmd ); + if ( cmdIt != mCommands.end() ) { + cmdIt->second(); + return 0; + } + } + return 1; + } +}; + struct UIConfig { StyleSheetLength fontSize{12, StyleSheetLength::Dp}; bool showSidePanel{true}; @@ -116,6 +149,8 @@ class App : public UICodeEditorSplitter::Client { void showFindView(); + void showLocateBar(); + bool replaceSelection( SearchState& search, const String& replacement ); int replaceAll( SearchState& search, const String& replace ); @@ -136,8 +171,10 @@ class App : public UICodeEditorSplitter::Client { Console* mConsole{nullptr}; std::string mWindowTitle{"ecode"}; String mLastSearch; + UILayout* mMainLayout{nullptr}; UILayout* mBaseLayout{nullptr}; UISearchBar* mSearchBarLayout{nullptr}; + UILocateBar* mLocateBarLayout{nullptr}; UIPopUpMenu* mSettingsMenu{nullptr}; UITextView* mSettingsButton{nullptr}; UIPopUpMenu* mColorSchemeMenu{nullptr}; @@ -163,6 +200,16 @@ class App : public UICodeEditorSplitter::Client { Float mDisplayDPI; std::string mResPath; AutoCompleteModule* mAutoCompleteModule{nullptr}; + std::shared_ptr mThreadPool; + std::unique_ptr mDirTree; + UITreeView* mProjectTreeView{nullptr}; + UITableView* mLocateTable{nullptr}; + UITextInput* mLocateInput{nullptr}; + bool mDirTreeReady{false}; + + void initLocateBar(); + + void loadDirTree( const std::string& path ); void showSidePanel( bool show ); diff --git a/src/tools/codeeditor/projectdirectorytree.cpp b/src/tools/codeeditor/projectdirectorytree.cpp new file mode 100644 index 000000000..772338985 --- /dev/null +++ b/src/tools/codeeditor/projectdirectorytree.cpp @@ -0,0 +1,133 @@ +#include "projectdirectorytree.hpp" +#include +#include + +ProjectDirectoryTree::ProjectDirectoryTree( const std::string& path, + std::shared_ptr threadPool ) : + mPath( path ), mPool( threadPool ), mIsReady( false ) { + FileSystem::dirAddSlashAtEnd( mPath ); +} + +void ProjectDirectoryTree::scan( const ProjectDirectoryTree::ScanCompleteEvent& scanComplete, + const std::vector& acceptedPattern ) { +#if EE_PLATFORM != EE_PLATFORM_EMSCRIPTEN + mPool->run( + [&, acceptedPattern] { +#endif + Lock l( mFilesMutex ); + if ( !acceptedPattern.empty() ) { + std::vector files; + std::vector names; + std::vector patterns; + for ( auto& strPattern : acceptedPattern ) + patterns.emplace_back( LuaPattern( strPattern ) ); + std::set info; + getDirectoryFiles( files, names, mPath, info ); + size_t namesCount = names.size(); + bool found; + for ( size_t i = 0; i < namesCount; i++ ) { + found = false; + for ( auto& pattern : patterns ) { + if ( pattern.matches( names[i] ) ) { + found = true; + break; + } + } + if ( found ) { + mFiles.emplace_back( std::move( files[i] ) ); + mNames.emplace_back( std::move( names[i] ) ); + } + } + } else { + std::set info; + getDirectoryFiles( mFiles, mNames, mPath, info ); + } + mIsReady = true; +#if EE_PLATFORM == EE_PLATFORM_EMSCRIPTEN + if ( scanComplete ) + scanComplete( *this ); +#endif +#if EE_PLATFORM != EE_PLATFORM_EMSCRIPTEN + }, + [scanComplete, this] { + if ( scanComplete ) + scanComplete( *this ); + } ); +#endif +} + +std::shared_ptr ProjectDirectoryTree::fuzzyMatchTree( const std::string& match, + const size_t& max ) const { + std::multimap> matchesMap; + std::vector files; + std::vector names; + int score; + for ( size_t i = 0; i < mNames.size(); i++ ) { + if ( ( score = String::fuzzyMatch( mNames[i], match ) ) > 0 ) { + matchesMap.insert( {score, i} ); + } + } + for ( auto& res : matchesMap ) { + if ( names.size() < max ) { + names.emplace_back( mNames[res.second] ); + files.emplace_back( mFiles[res.second] ); + } + } + return std::make_shared( files, names ); +} + +std::shared_ptr ProjectDirectoryTree::matchTree( const std::string& match, + const size_t& max ) const { + std::vector files; + std::vector names; + std::string lowerMatch( String::toLower( match ) ); + for ( size_t i = 0; i < mNames.size(); i++ ) { + if ( String::toLower( mNames[i] ).find( lowerMatch ) != std::string::npos ) { + names.emplace_back( mNames[i] ); + files.emplace_back( mFiles[i] ); + if ( max == names.size() ) + return std::make_shared( files, names ); + } + } + return std::make_shared( files, names ); +} + +std::shared_ptr ProjectDirectoryTree::asModel( const size_t& max ) const { + size_t rmax = eemin( mNames.size(), max ); + std::vector files( rmax ); + std::vector names( rmax ); + for ( size_t i = 0; i < rmax; i++ ) { + files[i] = mFiles[i]; + names[i] = mNames[i]; + } + return std::make_shared( files, names ); +} + +size_t ProjectDirectoryTree::getFilesCount() const { + return mFiles.size(); +} + +void ProjectDirectoryTree::getDirectoryFiles( std::vector& files, + std::vector& names, + std::string directory, + std::set currentDirs ) { + currentDirs.insert( directory ); + std::vector pathFiles = FileSystem::filesGetInPath( directory ); + for ( auto& file : pathFiles ) { + std::string fullpath( directory + file ); + if ( FileSystem::isDirectory( fullpath ) ) { + fullpath += FileSystem::getOSSlash(); + FileInfo dirInfo( fullpath, true ); + if ( dirInfo.isLink() ) { + fullpath = dirInfo.linksTo(); + FileSystem::dirAddSlashAtEnd( fullpath ); + if ( currentDirs.find( fullpath ) == currentDirs.end() ) + continue; + } + getDirectoryFiles( files, names, fullpath, currentDirs ); + } else { + files.emplace_back( fullpath ); + names.emplace_back( file ); + } + } +} diff --git a/src/tools/codeeditor/projectdirectorytree.hpp b/src/tools/codeeditor/projectdirectorytree.hpp new file mode 100644 index 000000000..e8fa09b5e --- /dev/null +++ b/src/tools/codeeditor/projectdirectorytree.hpp @@ -0,0 +1,76 @@ +#ifndef EE_TOOLS_PROJECTDIRECTORYTREE_HPP +#define EE_TOOLS_PROJECTDIRECTORYTREE_HPP + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace EE; +using namespace EE::System; +using namespace EE::UI::Models; + +class FileListModel : public Model { + public: + FileListModel( const std::vector& files, const std::vector& names ) : + mFiles( files ), mNames( names ) {} + + virtual size_t rowCount( const ModelIndex& ) const { return mNames.size(); } + + virtual size_t columnCount( const ModelIndex& ) const { return 2; } + + virtual std::string columnName( const size_t& index ) const { + return index == 0 ? "Name" : "Path"; + } + + virtual Variant data( const ModelIndex& index, Role role = Role::Display ) const { + if ( role == Role::Display ) { + return Variant( index.column() == 0 ? mNames[index.row()].c_str() + : mFiles[index.row()].c_str() ); + } + return {}; + } + + virtual void update() { onModelUpdate(); } + + protected: + std::vector mFiles; + std::vector mNames; +}; + +class ProjectDirectoryTree { + public: + typedef std::function ScanCompleteEvent; + + ProjectDirectoryTree( const std::string& path, std::shared_ptr threadPool ); + + void scan( const ScanCompleteEvent& scanComplete, + const std::vector& acceptedPattern = {} ); + + std::shared_ptr fuzzyMatchTree( const std::string& match, + const size_t& max ) const; + + std::shared_ptr matchTree( const std::string& match, const size_t& max ) const; + + std::shared_ptr asModel( const size_t& max ) const; + + size_t getFilesCount() const; + + protected: + std::string mPath; + std::shared_ptr mPool; + std::vector mFiles; + std::vector mNames; + bool mIsReady; + Mutex mFilesMutex; + + void getDirectoryFiles( std::vector& files, std::vector& names, + std::string directory, std::set currentDirs ); +}; + +#endif // EE_TOOLS_PROJECTDIRECTORYTREE_HPP