diff --git a/include/eepp/ui/uinode.hpp b/include/eepp/ui/uinode.hpp index 3da515d86..061ebfa51 100644 --- a/include/eepp/ui/uinode.hpp +++ b/include/eepp/ui/uinode.hpp @@ -217,7 +217,7 @@ class EE_API UINode : public Node { bool isDragging() const; - void setDragging( const bool& dragging ); + void setDragging( bool dragging, bool emitDropEvent = true ); void startDragging( const Vector2f& position ); diff --git a/include/eepp/ui/uipushbutton.hpp b/include/eepp/ui/uipushbutton.hpp index 665e043f6..497d3488b 100644 --- a/include/eepp/ui/uipushbutton.hpp +++ b/include/eepp/ui/uipushbutton.hpp @@ -50,7 +50,7 @@ class EE_API UIPushButton : public UIWidget { virtual const String& getText() const; - UITextView* getTextBox() const; + UITextView* getTextView() const; void setIconMinimumSize( const Sizei& minIconSize ); diff --git a/include/eepp/ui/uitablerow.hpp b/include/eepp/ui/uitablerow.hpp index ca05964fb..53a4ea29d 100644 --- a/include/eepp/ui/uitablerow.hpp +++ b/include/eepp/ui/uitablerow.hpp @@ -50,7 +50,7 @@ class EE_API UITableRow : public UIWidget { auto eventType = isMouseEvent( msg->getMsg() ); if ( eventType != Event::NoEvent && ( mouseDownNode == nullptr || mouseDownNode == this || isParentOf( mouseDownNode ) ) && - draggingNode == nullptr ) { + ( draggingNode == nullptr || mouseDownNode == draggingNode ) ) { sendMouseEvent( eventType, eventDispatcher->getMousePos(), msg->getFlags() ); } return 0; diff --git a/src/eepp/ui/uinode.cpp b/src/eepp/ui/uinode.cpp index fa2ae958d..5e536b668 100644 --- a/src/eepp/ui/uinode.cpp +++ b/src/eepp/ui/uinode.cpp @@ -1506,7 +1506,7 @@ bool UINode::isDragging() const { return 0 != ( mNodeFlags & NODE_FLAG_DRAGGING ); } -void UINode::setDragging( const bool& dragging ) { +void UINode::setDragging( bool dragging, bool emitDropEvent ) { if ( NULL == getEventDispatcher() ) return; @@ -1523,15 +1523,17 @@ void UINode::setDragging( const bool& dragging ) { onDragStop( getEventDispatcher()->getMousePos(), getEventDispatcher()->getPressTrigger() ); - bool enabled = isEnabled(); - mEnabled = false; - Node* found = getUISceneNode()->overFind( getEventDispatcher()->getMousePosf() ); - if ( found && found->isUINode() ) { - NodeDropMessage msg( found, NodeMessage::Drop, this ); - found->messagePost( &msg ); - found->asType()->onDrop( this ); + if ( emitDropEvent ) { + bool enabled = isEnabled(); + mEnabled = false; + Node* found = getUISceneNode()->overFind( getEventDispatcher()->getMousePosf() ); + if ( found && found->isUINode() ) { + NodeDropMessage msg( found, NodeMessage::Drop, this ); + found->messagePost( &msg ); + found->asType()->onDrop( this ); + } + mEnabled = enabled; } - mEnabled = enabled; } } diff --git a/src/eepp/ui/uipushbutton.cpp b/src/eepp/ui/uipushbutton.cpp index 5ab438f18..16decc716 100644 --- a/src/eepp/ui/uipushbutton.cpp +++ b/src/eepp/ui/uipushbutton.cpp @@ -150,10 +150,10 @@ void UIPushButton::onAutoSize() { if ( textW > fsize.getWidth() - nonTextW ) { Float mw = eemax( 0.f, fsize.getWidth() - nonTextW ); - getTextBox()->setMaxWidthEq( String::format( "%.0fdp", mw ) ); + getTextView()->setMaxWidthEq( String::format( "%.0fdp", mw ) ); } - if ( getTextBox()->getTextWidth() > getTextBox()->getSize().getWidth() ) - getTextBox()->setHorizontalAlign( UI_HALIGN_LEFT ); + if ( getTextView()->getTextWidth() > getTextView()->getSize().getWidth() ) + getTextView()->setHorizontalAlign( UI_HALIGN_LEFT ); } setInternalPixelsWidth( size.getWidth() ); @@ -420,7 +420,7 @@ const String& UIPushButton::getText() const { return mTextBox->getText(); } -UITextView* UIPushButton::getTextBox() const { +UITextView* UIPushButton::getTextView() const { return mTextBox; } diff --git a/src/eepp/ui/uitab.cpp b/src/eepp/ui/uitab.cpp index 94bd655bc..da8df60e6 100644 --- a/src/eepp/ui/uitab.cpp +++ b/src/eepp/ui/uitab.cpp @@ -268,7 +268,7 @@ void UITab::onAutoSize() { w = eemin( w, getMaxSize().getWidth() ); if ( textW > w - nonTextW ) - getTextBox()->setMaxWidthEq( String::format( "%.0fdp", w - nonTextW ) ); + getTextView()->setMaxWidthEq( String::format( "%.0fdp", w - nonTextW ) ); } setInternalWidth( w ); @@ -278,8 +278,8 @@ void UITab::onAutoSize() { getTabWidget()->orderTabs(); } - if ( getTextBox()->getTextWidth() > getTextBox()->getSize().getWidth() ) - getTextBox()->setHorizontalAlign( UI_HALIGN_LEFT ); + if ( getTextView()->getTextWidth() > getTextView()->getSize().getWidth() ) + getTextView()->setHorizontalAlign( UI_HALIGN_LEFT ); } std::string UITab::getPropertyString( const PropertyDefinition* propertyDef, diff --git a/src/tools/ecode/applayout.xml.hpp b/src/tools/ecode/applayout.xml.hpp index aa9ef5a95..81876b133 100644 --- a/src/tools/ecode/applayout.xml.hpp +++ b/src/tools/ecode/applayout.xml.hpp @@ -510,6 +510,11 @@ Anchor.error:hover { window::modaldialog.shadowbg { background-color: #00000066; } +textview.dragged_cell { + background-color: var(--button-back); + border-radius: 4dp; + padding: 4dp; +} @media (prefers-color-scheme: light) { @@ -539,7 +544,7 @@ R"html( - + diff --git a/src/tools/ecode/customwidgets.hpp b/src/tools/ecode/customwidgets.hpp new file mode 100644 index 000000000..719d8df72 --- /dev/null +++ b/src/tools/ecode/customwidgets.hpp @@ -0,0 +1,15 @@ +#pragma once + +#include + +using namespace EE; +using namespace EE::UI; + +namespace ecode { + +enum class CustomWidgets : Uint32 { + UI_TYPE_WELCOME_TAB = UI_TYPE_USER + 1, + UI_TYPE_TREEVIEWCELLFS = UI_TYPE_WELCOME_TAB + 1, +}; + +} diff --git a/src/tools/ecode/ecode.cpp b/src/tools/ecode/ecode.cpp index 09e46a797..5c0463e0f 100644 --- a/src/tools/ecode/ecode.cpp +++ b/src/tools/ecode/ecode.cpp @@ -5,6 +5,7 @@ #include "settingsactions.hpp" #include "settingsmenu.hpp" #include "uibuildsettings.hpp" +#include "uitreeviewfs.hpp" #include "uiwelcomescreen.hpp" #include "version.hpp" #include @@ -4035,6 +4036,7 @@ void App::init( const LogLevel& logLevel, std::string file, const Float& pidelDe UIWidgetCreator::registerWidget( "rellayce", UIRelativeLayoutCommandExecuter::New ); UIWidgetCreator::registerWidget( "hboxce", UIHLinearLayoutCommandExecuter::New ); UIWidgetCreator::registerWidget( "vboxce", UIVLinearLayoutCommandExecuter::New ); + UIWidgetCreator::registerWidget( "treeviewfs", UITreeViewFS::New ); mUISceneNode->loadLayoutFromString( baseUI, nullptr, APP_LAYOUT_STYLE_MARKER ); mAppStyleSheet = mUISceneNode->getStyleSheet().getAllWithMarker( APP_LAYOUT_STYLE_MARKER ); diff --git a/src/tools/ecode/ecode.hpp b/src/tools/ecode/ecode.hpp index 33f3342f1..8f90ea6b7 100644 --- a/src/tools/ecode/ecode.hpp +++ b/src/tools/ecode/ecode.hpp @@ -2,6 +2,7 @@ #define ECODE_HPP #include "appconfig.hpp" +#include "customwidgets.hpp" #include "docsearchcontroller.hpp" #include "featureshealth.hpp" #include "filesystemlistener.hpp" @@ -25,10 +26,6 @@ using namespace eterm::UI; -enum class CustomWidgets { - UI_TYPE_WELCOME_TAB = UI_TYPE_USER + 1, -}; - namespace ecode { class AutoCompletePlugin; diff --git a/src/tools/ecode/plugins/debugger/statusdebuggercontroller.cpp b/src/tools/ecode/plugins/debugger/statusdebuggercontroller.cpp index 5269ce426..3284b3987 100644 --- a/src/tools/ecode/plugins/debugger/statusdebuggercontroller.cpp +++ b/src/tools/ecode/plugins/debugger/statusdebuggercontroller.cpp @@ -65,7 +65,7 @@ UIWidget* UIBreakpointsTableView::createCell( UIWidget* rowWidget, const ModelIn if ( index.column() == BreakpointsModel::Enabled ) { UIBreakpointsTableCell* widget = UIBreakpointsTableCell::New( mTag + "::cell", (const BreakpointsModel*)getModel(), index ); - widget->getTextBox()->setEnabled( true ); + widget->getTextView()->setEnabled( true ); widget->setDontAutoHideEmptyTextBox( true ); return setupCell( widget, rowWidget, index ); } else if ( index.column() == BreakpointsModel::Remove ) { diff --git a/src/tools/ecode/plugins/git/gitplugin.cpp b/src/tools/ecode/plugins/git/gitplugin.cpp index c6f9ed884..ce0f46559 100644 --- a/src/tools/ecode/plugins/git/gitplugin.cpp +++ b/src/tools/ecode/plugins/git/gitplugin.cpp @@ -340,7 +340,7 @@ void GitPlugin::updateStatusBarSync() { mStatusButton->setClass( "status_but" ); mStatusButton->setIcon( iconDrawable( "source-control", 10 ) ); mStatusButton->reloadStyle( true, true ); - mStatusButton->getTextBox()->setUsingCustomStyling( true ); + mStatusButton->getTextView()->setUsingCustomStyling( true ); mStatusButton->on( Event::MouseClick, [this]( const Event* event ) { if ( nullptr == mTab ) @@ -397,7 +397,7 @@ void GitPlugin::updateStatusBarSync() { } SyntaxTokenizer::tokenizeText( mStatusCustomTokenizer->def, mStatusCustomTokenizer->scheme, - mStatusButton->getTextBox()->getTextCache() ); + mStatusButton->getTextView()->getTextCache() ); mStatusButton->invalidateDraw(); } @@ -871,8 +871,7 @@ void GitPlugin::commit( const std::string& repoPath ) { txtEdit->getDocument().setCommand( "commit-amend", [chkAmend] { chkAmend->setChecked( !chkAmend->isChecked() ); } ); - txtEdit->getKeyBindings().addKeybind( { KEY_L, KeyMod::getDefaultModifier() }, - "commit-amend" ); + txtEdit->getKeyBindings().addKeybind( { KEY_L, KeyMod::getDefaultModifier() }, "commit-amend" ); txtEdit->getDocument().setCommand( "commit-push", [chkPush] { chkPush->setChecked( !chkPush->isChecked() ); } ); diff --git a/src/tools/ecode/plugins/pluginmanager.cpp b/src/tools/ecode/plugins/pluginmanager.cpp index f19e5b91e..952de4e1f 100644 --- a/src/tools/ecode/plugins/pluginmanager.cpp +++ b/src/tools/ecode/plugins/pluginmanager.cpp @@ -1,6 +1,6 @@ +#include "pluginmanager.hpp" #include "../filesystemlistener.hpp" #include "plugin.hpp" -#include "pluginmanager.hpp" #include #include #include @@ -402,9 +402,9 @@ class UIPluginManagerTable : public UITableView { setOnUpdateCellCb( [this]( UITableCell* cell, Model* model ) { if ( mUpdatingEnabled ) return; - if ( !cell->getTextBox()->isType( UI_TYPE_CHECKBOX ) ) + if ( !cell->getTextView()->isType( UI_TYPE_CHECKBOX ) ) return; - UICheckBox* chk = cell->getTextBox()->asType(); + UICheckBox* chk = cell->getTextView()->asType(); PluginsModel* pModel = static_cast( model ); bool enabled = pModel ->data( model->index( cell->getCurIndex().row(), @@ -449,7 +449,7 @@ class UIPluginManagerTable : public UITableView { if ( index.column() == PluginsModel::Title ) { UITableCell* widget = UITableCell::NewWithOpt( mTag + "::cell", getCheckBoxFn( index, (const PluginsModel*)getModel() ) ); - widget->getTextBox()->setEnabled( true ); + widget->getTextView()->setEnabled( true ); return setupCell( widget, rowWidget, index ); } return UITableView::createCell( rowWidget, index ); diff --git a/src/tools/ecode/uitreeviewfs.cpp b/src/tools/ecode/uitreeviewfs.cpp new file mode 100644 index 000000000..c75daf700 --- /dev/null +++ b/src/tools/ecode/uitreeviewfs.cpp @@ -0,0 +1,189 @@ +#include "uitreeviewfs.hpp" +#include "customwidgets.hpp" + +#include +#include +#include +#include + +#include + +namespace ecode { + +class UITreeViewCellFS : public UITreeViewCell { + public: + static UITextView* sDragTV; + + static UITreeViewCellFS* New() { return eeNew( UITreeViewCellFS, () ); } + + UITreeViewCellFS() : UITreeViewCell() { setDragEnabled( true ); } + + Uint32 getType() const override { + return static_cast( CustomWidgets::UI_TYPE_TREEVIEWCELLFS ); + } + + bool isType( const Uint32& type ) const override { + return getType() == type ? true : UITreeViewCell::isType( type ); + } + + bool acceptsDropOfWidget( const UIWidget* widget ) override { + return widget->isType( static_cast( CustomWidgets::UI_TYPE_TREEVIEWCELLFS ) ); + } + + UITreeViewFS* getTreeView() const { return getParent()->getParent()->asType(); } + + const FileSystemModel* getModel() const { + auto model = getParent()->getParent()->asType()->getModel(); + auto fsm = static_cast( model ); + return fsm; + } + + Uint32 onDrag( const Vector2f& position, const Uint32& flags, const Sizef& dragDiff ) override { + sDragTV->setVisible( true )->setEnabled( false ); + sDragTV->setPixelsPosition( position + PixelDensity::dpToPx( 8 ) ); + getUISceneNode()->getWindow()->getCursorManager()->set( Cursor::Hand ); + + setEnabled( false ); + Node* overFind = getUISceneNode()->overFind( position ); + setEnabled( true ); + + if ( overFind && overFind->isType( UI_TYPE_WIDGET ) ) { + UIWidget* widget = overFind->asType()->acceptsDropOfWidgetInTree( this ); + + if ( mCurDropWidget ) { + mCurDropWidget->writeNodeFlag( NODE_FLAG_DROPPABLE_HOVERING, 0 ); + mCurDropWidget = nullptr; + } + + if ( widget ) { + mCurDropWidget = widget; + mCurDropWidget->writeNodeFlag( NODE_FLAG_DROPPABLE_HOVERING, 1 ); + } + } + + return 1; + } + + Uint32 onDragStart( const Vector2i& position, const Uint32& flags ) override { + if ( sDragTV == nullptr ) { + sDragTV = UITextView::New(); + sDragTV->setClass( "dragged_cell" ); + } + sDragTV->setVisible( false )->setEnabled( false ); + sDragTV->setText( getTextView()->getText() ); + setClass( "dragged" ); + getTreeView()->setSourceDrag( getModel()->node( getCurIndex() ).fullPath() ); + return UITreeViewCell::onDragStart( position, flags ); + } + + Uint32 onDragStop( const Vector2i& position, const Uint32& flags ) override { + sDragTV->setVisible( false ); + removeClass( "dragged" ); + getUISceneNode()->getWindow()->getCursorManager()->set( Cursor::Arrow ); + + if ( mCurDropWidget ) { + mCurDropWidget->writeNodeFlag( NODE_FLAG_DROPPABLE_HOVERING, 0 ); + mCurDropWidget = nullptr; + } + return UITreeViewCell::onDragStop( position, flags ); + } + + Uint32 onKeyDown( const KeyEvent& event ) override { + if ( event.getKeyCode() == KEY_ESCAPE && getEventDispatcher()->isNodeDragging() && + getEventDispatcher()->getNodeDragging()->isUINode() ) { + getEventDispatcher()->getNodeDragging()->asType()->setDragging( false, false ); + getEventDispatcher()->setNodeDragging( nullptr ); + return 1; + } + return 0; + } + + const std::string& getCurrentPath() const { + return getModel()->node( getCurIndex() ).fullPath(); + } + + protected: + UIWidget* mCurDropWidget{ nullptr }; +}; + +UITextView* UITreeViewCellFS::sDragTV = nullptr; + +UITreeViewFS::UITreeViewFS() : UITreeView() {} + +UIWidget* UITreeViewFS::createCell( UIWidget* rowWidget, const ModelIndex& index ) { + auto* widget = UITreeViewCellFS::New(); + return setupCell( widget, rowWidget, index ); +} + +Uint32 UITreeViewFS::onMessage( const NodeMessage* msg ) { + if ( msg->getMsg() == NodeMessage::Drop ) { + const NodeDropMessage* dropMsg = static_cast( msg ); + static constexpr auto expectedType = + static_cast( CustomWidgets::UI_TYPE_TREEVIEWCELLFS ); + if ( dropMsg->getSender()->isType( expectedType ) && + dropMsg->getDroppedNode()->isType( expectedType ) ) { + auto dst = dropMsg->getSender()->asType()->getCurrentPath(); + moveFile( mSrcDrag, dst ); + return 1; + } + } + return UITreeView::onMessage( msg ); +} + +static bool fsRenameFile( const std::string& fpath, const std::string& newFilePath ) { + try { +#if EE_PLATFORM == EE_PLATFORM_WIN + std::filesystem::rename( String( fpath ).toWideString(), + String( newFilePath ).toWideString() ); +#else + std::filesystem::rename( fpath, newFilePath ); +#endif + } catch ( const std::filesystem::filesystem_error& ) { + return false; + } + + return true; +} + +void UITreeViewFS::moveFile( const std::string& src, const std::string& dst ) { + FileInfo srcInfo( src ); + if ( !srcInfo.exists() ) + return; + FileInfo dstInfo( dst ); + if ( !dstInfo.exists() ) + return; + if ( srcInfo.getFilepath() != srcInfo.getRealPath() ) + srcInfo = FileInfo( srcInfo.getRealPath() ); + if ( !dstInfo.isDirectory() ) { + dstInfo = FileInfo( dstInfo.getDirectoryPath() ); + if ( dstInfo.getFilepath() != dstInfo.getRealPath() ) + dstInfo = FileInfo( dstInfo.getRealPath() ); + } + if ( srcInfo.getDirectoryPath() == dstInfo.getFilepath() ) + return; + + std::string srcPath( srcInfo.getFilepath() ); + std::string dstPath( dstInfo.getFilepath() ); + FileSystem::dirAddSlashAtEnd( dstPath ); + dstPath += srcInfo.getFileName(); + + auto fsm = static_cast( getModel() ); + std::string partialSrc( srcPath ); + FileSystem::filePathRemoveBasePath( fsm->getRootPath(), partialSrc ); + std::string partialDst( dstPath ); + FileSystem::filePathRemoveBasePath( fsm->getRootPath(), partialDst ); + + auto confirmMsg( String::format( + i18n( "confirm_move_file_or_dir", "Are you sure you want to move:\n%s\ninto:\n%s" ) + .toUtf8(), + partialSrc, partialDst ) ); + + UIMessageBox* msgBox = UIMessageBox::New( UIMessageBox::OK_CANCEL, confirmMsg ); + msgBox->center(); + msgBox->setCloseShortcut( { KEY_ESCAPE, 0 } ); + msgBox->showWhenReady(); + msgBox->on( Event::OnConfirm, [srcPath = std::move( srcPath ), dstPath = std::move( dstPath )]( + auto ) { fsRenameFile( srcPath, dstPath ); } ); +} + +} // namespace ecode diff --git a/src/tools/ecode/uitreeviewfs.hpp b/src/tools/ecode/uitreeviewfs.hpp new file mode 100644 index 000000000..f2990e0c5 --- /dev/null +++ b/src/tools/ecode/uitreeviewfs.hpp @@ -0,0 +1,27 @@ +#pragma once + +#include + +using namespace EE::UI; + +namespace ecode { + +class UITreeViewFS : public UITreeView { + public: + static UITreeViewFS* New() { return eeNew( UITreeViewFS, () ); } + + UITreeViewFS(); + + UIWidget* createCell( UIWidget* rowWidget, const ModelIndex& index ) override; + + Uint32 onMessage( const NodeMessage* msg ) override; + + void setSourceDrag( const std::string& src ) { mSrcDrag = src; } + + protected: + std::string mSrcDrag; + + void moveFile( const std::string& src, const std::string& dst ); +}; + +} // namespace ecode