From 6885e345725ef2ec0020ce49acd212f4568d33e7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mart=C3=ADn=20Lucas=20Golini?= Date: Thu, 18 Jun 2020 05:33:21 -0300 Subject: [PATCH] Added support to move Tabs from one UITabWidget to another using the onDrop API. --- TODO.md | 4 -- docs/articles/cssspecification.md | 10 +++ include/eepp/scene/event.hpp | 1 + include/eepp/scene/node.hpp | 2 +- include/eepp/ui/css/propertydefinition.hpp | 1 + include/eepp/ui/uitab.hpp | 2 + include/eepp/ui/uitabwidget.hpp | 16 +++++ src/eepp/scene/node.cpp | 13 ++-- src/eepp/scene/nodemessage.cpp | 4 ++ src/eepp/ui/css/stylesheetspecification.cpp | 1 + src/eepp/ui/uinode.cpp | 6 +- src/eepp/ui/uitab.cpp | 30 +++++++-- src/eepp/ui/uitabwidget.cpp | 74 ++++++++++++++++++--- src/tools/codeeditor/codeeditor.cpp | 18 +++-- 14 files changed, 144 insertions(+), 38 deletions(-) diff --git a/TODO.md b/TODO.md index bc9fabfba..6157e2333 100644 --- a/TODO.md +++ b/TODO.md @@ -1,10 +1,6 @@ # TODO - Short and mid term plans. -## UITabWidget - -* Add support to move Tabs from one `UITabWidget` to another using the onDrop API. - ## UIIconTheme and UIIconThemeManager Implement icon themes separated from the `UITheme` and customizable from a CSS file. diff --git a/docs/articles/cssspecification.md b/docs/articles/cssspecification.md index 9362f9522..05e8deab9 100644 --- a/docs/articles/cssspecification.md +++ b/docs/articles/cssspecification.md @@ -1564,6 +1564,16 @@ Enables/disables manually rearraging the tabs in the tab bar. --- +### tabbar-allow-drag-and-drop-tabs + +Enables/disables the hability to move any tab from a TabWidget to another. + +* Applicable to: EE::UI::UITabWidget (TabWidget) +* Data Type: [boolean](#boolean-data-type) +* Default value: `false` + +--- + ### tab-closable Enables/disables tabs to be closable with the middle mouse button click or by clicking on the close diff --git a/include/eepp/scene/event.hpp b/include/eepp/scene/event.hpp index e4cd77e1c..cc9a30365 100644 --- a/include/eepp/scene/event.hpp +++ b/include/eepp/scene/event.hpp @@ -52,6 +52,7 @@ class EE_API Event { MsgBoxConfirmClick, MsgBoxCancelClick, OnTabSelected, + OnTabAdded, OnTabClosed, OnTabNavigate, OnClose, // Warning: Only some nodes will report this event. diff --git a/include/eepp/scene/node.hpp b/include/eepp/scene/node.hpp index 19a99e598..0ff6c74c2 100644 --- a/include/eepp/scene/node.hpp +++ b/include/eepp/scene/node.hpp @@ -371,7 +371,7 @@ class EE_API Node : public Transformable { bool isChild( Node* child ) const; - bool inParentTreeOf( Node* Child ) const; + bool inParentTreeOf( Node* child ) const; void setLoadingState( bool loading ); diff --git a/include/eepp/ui/css/propertydefinition.hpp b/include/eepp/ui/css/propertydefinition.hpp index 824aa4e8c..c8cfb98f7 100644 --- a/include/eepp/ui/css/propertydefinition.hpp +++ b/include/eepp/ui/css/propertydefinition.hpp @@ -190,6 +190,7 @@ enum class PropertyId : Uint32 { BorderBottomRightRadius = String::hash( "border-bottom-right-radius" ), TabBarHideOnSingleTab = String::hash( "tabbar-hide-on-single-tab" ), TabBarAllowRearrange = String::hash( "tabbar-allow-rearrange" ), + TabBarAllowDragAndDrop = String::hash( "tabbar-allow-drag-and-drop-tabs" ), }; enum class PropertyType : Uint32 { diff --git a/include/eepp/ui/uitab.hpp b/include/eepp/ui/uitab.hpp index f50f5d7ae..208acd6e9 100644 --- a/include/eepp/ui/uitab.hpp +++ b/include/eepp/ui/uitab.hpp @@ -43,6 +43,8 @@ class EE_API UITab : public UISelectButton { String mText; std::string mOwnedName; UIWidget* mCloseButton; + Float mDragTotalDiff; + UITabWidget* mTabWidget; Uint32 onDrag( const Vector2f& position, const Uint32& flags, const Sizef& dragDiff ); diff --git a/include/eepp/ui/uitabwidget.hpp b/include/eepp/ui/uitabwidget.hpp index cf542a881..59928d67e 100644 --- a/include/eepp/ui/uitabwidget.hpp +++ b/include/eepp/ui/uitabwidget.hpp @@ -141,6 +141,14 @@ class EE_API UITabWidget : public UIWidget { void setAllowRearrangeTabs( bool allowRearrangeTabs ); + bool getAllowDragAndDropTabs() const; + + void setAllowDragAndDropTabs( bool allowDragAndDropTabs ); + + const Float& getTabVerticalDragResistance() const; + + void setTabVerticalDragResistance( const Float& tabVerticalDragResistance ); + protected: friend class UITab; @@ -153,17 +161,25 @@ class EE_API UITabWidget : public UIWidget { TabTryCloseCallback mTabTryCloseCallback; bool mHideTabBarOnSingleTab; bool mAllowRearrangeTabs; + bool mAllowDragAndDropTabs; + Float mTabVerticalDragResistance; void onThemeLoaded(); UITab* createTab( const String& text, UINode* nodeOwned, Drawable* icon ); + void removeTab( const Uint32& index, bool destroyOwnedNode, bool destroyTab = true ); + + void removeTab( UITab* tab, bool destroyOwnedNode, bool destroyTab ); + virtual void onSizeChange(); virtual void onChildCountChange( Node* child, const bool& removed ); virtual void onPaddingChange(); + virtual Uint32 onMessage( const NodeMessage* msg ); + void setContainerSize(); void posTabs(); diff --git a/src/eepp/scene/node.cpp b/src/eepp/scene/node.cpp index 941f6a9a2..07d083a90 100644 --- a/src/eepp/scene/node.cpp +++ b/src/eepp/scene/node.cpp @@ -773,16 +773,13 @@ bool Node::isChild( Node* child ) const { return false; } -bool Node::inParentTreeOf( Node* Child ) const { - Node* ParentLoop = Child->mParentNode; - - while ( NULL != ParentLoop ) { - if ( ParentLoop == this ) +bool Node::inParentTreeOf( Node* child ) const { + Node* node = child->mParentNode; + while ( NULL != node ) { + if ( node == this ) return true; - - ParentLoop = ParentLoop->mParentNode; + node = node->mParentNode; } - return false; } diff --git a/src/eepp/scene/nodemessage.cpp b/src/eepp/scene/nodemessage.cpp index 5e8e92b08..25a898a22 100644 --- a/src/eepp/scene/nodemessage.cpp +++ b/src/eepp/scene/nodemessage.cpp @@ -23,4 +23,8 @@ const Uint32& NodeMessage::getFlags() const { NodeDropMessage::NodeDropMessage( Node* node, const Uint32& msg, Node* droppedNode ) : NodeMessage( node, msg ), mDroppedNode( droppedNode ) {} +Node* NodeDropMessage::getDroppedNode() const { + return mDroppedNode; +} + }} // namespace EE::Scene diff --git a/src/eepp/ui/css/stylesheetspecification.cpp b/src/eepp/ui/css/stylesheetspecification.cpp index 97231fb8e..b8d8e6269 100644 --- a/src/eepp/ui/css/stylesheetspecification.cpp +++ b/src/eepp/ui/css/stylesheetspecification.cpp @@ -362,6 +362,7 @@ void StyleSheetSpecification::registerDefaultProperties() { registerProperty( "tabbar-hide-on-single-tab", "false" ); registerProperty( "tabbar-allow-rearrange", "false" ); + registerProperty( "tabbar-allow-drag-and-drop-tabs", "false" ); // Shorthands registerShorthand( "margin", {"margin-top", "margin-right", "margin-bottom", "margin-left"}, diff --git a/src/eepp/ui/uinode.cpp b/src/eepp/ui/uinode.cpp index cbf19c690..d521e7607 100644 --- a/src/eepp/ui/uinode.cpp +++ b/src/eepp/ui/uinode.cpp @@ -442,11 +442,7 @@ Uint32 UINode::onCalculateDrag( const Vector2f& position, const Uint32& flags ) if ( onDrag( pos, flags, dragDiff ) ) { setPixelsPosition( mPosition - dragDiff ); - mDragPoint = pos; - - onPositionChange(); - eventDispatcher->setNodeDragging( this ); } } @@ -1137,9 +1133,9 @@ void UINode::setDragging( const bool& dragging ) { mEnabled = false; Node* found = getUISceneNode()->overFind( getEventDispatcher()->getMousePosf() ); if ( found && found->isUINode() ) { - found->asType()->onDrop( this ); NodeDropMessage msg( found, NodeMessage::Drop, this ); found->messagePost( &msg ); + found->asType()->onDrop( this ); } mEnabled = enabled; } diff --git a/src/eepp/ui/uitab.cpp b/src/eepp/ui/uitab.cpp index fe507636a..d4d6b53b8 100644 --- a/src/eepp/ui/uitab.cpp +++ b/src/eepp/ui/uitab.cpp @@ -9,7 +9,8 @@ UITab* UITab::New() { return eeNew( UITab, () ); } -UITab::UITab() : UISelectButton( "tab" ), mOwnedWidget( NULL ) { +UITab::UITab() : + UISelectButton( "tab" ), mOwnedWidget( NULL ), mDragTotalDiff( 0.f ), mTabWidget( NULL ) { mTextBox->setElementTag( mTag + "::text" ); mIcon->setElementTag( mTag + "::icon" ); auto cb = [&]( const Event* ) { onSizeChange(); }; @@ -37,12 +38,13 @@ UITabWidget* UITab::getTabWidget() { if ( NULL != getParent() && NULL != getParent()->getParent() && getParent()->getParent()->isType( UI_TYPE_TABWIDGET ) ) { return getParent()->getParent()->asType(); + } else if ( mTabWidget ) { + return mTabWidget; } - return NULL; } -Uint32 UITab::onDrag( const Vector2f&, const Uint32&, const Sizef& dragDiff ) { +Uint32 UITab::onDrag( const Vector2f& pos, const Uint32&, const Sizef& dragDiff ) { UITabWidget* tabW = getTabWidget(); if ( !tabW ) return 0; @@ -65,11 +67,27 @@ Uint32 UITab::onDrag( const Vector2f&, const Uint32&, const Sizef& dragDiff ) { } } } + if ( tabW->getAllowDragAndDropTabs() && !( mFlags & UI_DRAG_VERTICAL ) ) { + mDragTotalDiff += ( Float )( mDragPoint.y - pos.y ); + if ( eeabs( mDragTotalDiff ) >= tabW->getTabVerticalDragResistance() ) { + setFlags( UI_DRAG_VERTICAL ); + setPixelsPosition( mPosition.x, mPosition.y - mDragTotalDiff ); + mDragPoint = pos; + mTabWidget = getTabWidget(); + Vector2f posDiff( pos ); + worldToNode( posDiff ); + setParent( getUISceneNode()->getRoot() ); + setPixelsPosition( pos - posDiff ); + } + } return 1; } Uint32 UITab::onDragStart( const Vector2i& position, const Uint32& flags ) { UITabWidget* tabW = getTabWidget(); + mTabWidget = NULL; + mDragTotalDiff = 0; + unsetFlags( UI_DRAG_VERTICAL ); if ( tabW ) { for ( size_t i = 0; i < tabW->getTabCount(); i++ ) { UITab* tab = tabW->getTab( i ); @@ -83,6 +101,10 @@ Uint32 UITab::onDragStart( const Vector2i& position, const Uint32& flags ) { Uint32 UITab::onDragStop( const Vector2i& position, const Uint32& flags ) { UITabWidget* tabW = getTabWidget(); + if ( mTabWidget ) { + setParent( tabW->getTabBar() ); + mTabWidget = NULL; + } if ( tabW ) { for ( size_t i = 0; i < tabW->getTabCount(); i++ ) { UITab* tab = tabW->getTab( i ); @@ -303,7 +325,7 @@ void UITab::updateTab() { } else { UIPushButton::setText( mText ); } - setDragEnabled( tTabW->getAllowRearrangeTabs() ); + setDragEnabled( tTabW->getAllowRearrangeTabs() || tTabW->getAllowDragAndDropTabs() ); onAutoSize(); } } diff --git a/src/eepp/ui/uitabwidget.cpp b/src/eepp/ui/uitabwidget.cpp index f6fcf8e98..fcc2166a4 100644 --- a/src/eepp/ui/uitabwidget.cpp +++ b/src/eepp/ui/uitabwidget.cpp @@ -20,7 +20,9 @@ UITabWidget::UITabWidget() : mTabSelected( NULL ), mTabSelectedIndex( eeINDEX_NOT_FOUND ), mHideTabBarOnSingleTab( false ), - mAllowRearrangeTabs( false ) { + mAllowRearrangeTabs( false ), + mAllowDragAndDropTabs( false ), + mTabVerticalDragResistance( PixelDensity::dpToPx( 64 ) ) { setHorizontalAlign( UI_HALIGN_CENTER ); mTabBar = UIWidget::NewWithTag( "tabwidget::tabbar" ); @@ -218,6 +220,9 @@ bool UITabWidget::applyProperty( const StyleSheetProperty& attribute ) { case PropertyId::TabBarAllowRearrange: setAllowRearrangeTabs( attribute.asBool() ); break; + case PropertyId::TabBarAllowDragAndDrop: + setAllowDragAndDropTabs( attribute.asBool() ); + break; default: return UIWidget::applyProperty( attribute ); } @@ -424,6 +429,9 @@ UITabWidget* UITabWidget::add( UITab* tab ) { orderTabs(); } + TabEvent tabEvent( this, tab, getTabIndex( tab ), Event::OnTabAdded ); + sendEvent( &tabEvent ); + return this; } @@ -459,13 +467,19 @@ Uint32 UITabWidget::getTabCount() const { } void UITabWidget::removeTab( const Uint32& index, bool destroyOwnedNode ) { + removeTab( index, destroyOwnedNode, true ); +} + +void UITabWidget::removeTab( const Uint32& index, bool destroyOwnedNode, bool destroyTab ) { eeASSERT( index < mTabs.size() ); UITab* tab = mTabs[index]; - tab->close(); - tab->setVisible( false ); - tab->setEnabled( false ); + if ( destroyTab ) { + tab->close(); + tab->setVisible( false ); + tab->setEnabled( false ); + } if ( destroyOwnedNode ) { tab->getOwnedWidget()->close(); tab->getOwnedWidget()->setVisible( false ); @@ -499,7 +513,11 @@ void UITabWidget::removeTab( const Uint32& index, bool destroyOwnedNode ) { } void UITabWidget::removeTab( UITab* tab, bool destroyOwnedNode ) { - removeTab( getTabIndex( tab ), destroyOwnedNode ); + removeTab( getTabIndex( tab ), destroyOwnedNode, true ); +} + +void UITabWidget::removeTab( UITab* tab, bool destroyOwnedNode, bool destroyTab ) { + removeTab( getTabIndex( tab ), destroyOwnedNode, destroyTab ); } void UITabWidget::removeAllTabs( bool destroyOwnedNode ) { @@ -628,6 +646,22 @@ void UITabWidget::setAllowRearrangeTabs( bool allowRearrangeTabs ) { } } +bool UITabWidget::getAllowDragAndDropTabs() const { + return mAllowDragAndDropTabs; +} + +void UITabWidget::setAllowDragAndDropTabs( bool allowDragAndDropTabs ) { + mAllowDragAndDropTabs = allowDragAndDropTabs; +} + +const Float& UITabWidget::getTabVerticalDragResistance() const { + return mTabVerticalDragResistance; +} + +void UITabWidget::setTabVerticalDragResistance( const Float& tabVerticalDragResistance ) { + mTabVerticalDragResistance = tabVerticalDragResistance; +} + void UITabWidget::refreshOwnedWidget( UITab* tab ) { if ( NULL != tab && NULL != tab->getOwnedWidget() ) { tab->getOwnedWidget()->setParent( mNodeContainer ); @@ -695,17 +729,24 @@ void UITabWidget::onSizeChange() { void UITabWidget::onChildCountChange( Node* child, const bool& removed ) { if ( !removed && child != mTabBar && child != mNodeContainer ) { if ( child->isType( UI_TYPE_TAB ) ) { - UITab* Tab = static_cast( child ); + // This must be a tab that was dragging. + if ( std::find( mTabs.begin(), mTabs.end(), child->asType() ) != mTabs.end() ) + return; - Tab->setParent( mTabBar ); + UITab* tab = static_cast( child ); - mTabs.push_back( Tab ); + tab->setParent( mTabBar ); + + mTabs.push_back( tab ); if ( NULL == mTabSelected ) { - setTabSelected( Tab ); + setTabSelected( tab ); } else { orderTabs(); } + + TabEvent tabEvent( this, tab, getTabIndex( tab ), Event::OnTabAdded ); + sendEvent( &tabEvent ); } else { child->setParent( mNodeContainer ); child->setVisible( false ); @@ -722,6 +763,21 @@ void UITabWidget::onPaddingChange() { UIWidget::onPaddingChange(); } +Uint32 UITabWidget::onMessage( const NodeMessage* msg ) { + if ( msg->getMsg() == NodeMessage::Drop && mAllowDragAndDropTabs ) { + const NodeDropMessage* dropMsg = static_cast( msg ); + if ( dropMsg->getDroppedNode()->isType( UI_TYPE_TAB ) ) { + UITab* tab = dropMsg->getDroppedNode()->asType(); + if ( tab->getTabWidget() != this ) { + tab->getTabWidget()->removeTab( tab, false, false ); + add( tab ); + return 1; + } + } + } + return 0; +} + void UITabWidget::applyThemeToTabs() { if ( mStyleConfig.TabsEdgesDiffSkins ) { for ( Uint32 i = 0; i < mTabs.size(); i++ ) { diff --git a/src/tools/codeeditor/codeeditor.cpp b/src/tools/codeeditor/codeeditor.cpp index fe3657f1a..641845c2a 100644 --- a/src/tools/codeeditor/codeeditor.cpp +++ b/src/tools/codeeditor/codeeditor.cpp @@ -451,6 +451,7 @@ UITabWidget* App::createEditorWithTabWidget( Node* parent ) { tabWidget->setTabsClosable( true ); tabWidget->setHideTabBarOnSingleTab( true ); tabWidget->setAllowRearrangeTabs( true ); + tabWidget->setAllowDragAndDropTabs( true ); tabWidget->addEventListener( Event::OnTabSelected, [&]( const Event* event ) { UITabWidget* tabWidget = event->getNode()->asType(); mCurEditor = tabWidget->getTabSelected()->getOwnedWidget()->asType(); @@ -463,6 +464,16 @@ UITabWidget* App::createEditorWithTabWidget( Node* parent ) { tabWidget->addEventListener( Event::OnTabClosed, [&]( const Event* event ) { onTabClosed( static_cast( event ) ); } ); + tabWidget->addEventListener( Event::OnTabAdded, [&]( const Event* event ) { + UITabWidget* tabWidget = event->getNode()->asType(); + if ( tabWidget->getTabCount() == 2 && tabWidget->getTab( 0 ) + ->getOwnedWidget() + ->asType() + ->getDocument() + .isEmpty() ) { + tabWidget->removeTab( (Uint32)0 ); + } + } ); createCodeEditorInTabWidget( tabWidget ); mTabWidgets.push_back( tabWidget ); return tabWidget; @@ -508,13 +519,6 @@ void App::openFileDialog() { UITab* addedTab = d.first; loadFileFromPath( event->getNode()->asType()->getFullPath(), d.second ); tabWidget->setTabSelected( addedTab ); - UITab* firstTab = tabWidget->getTab( 0 ); - if ( addedTab != firstTab ) { - UICodeEditor* editor = (UICodeEditor*)firstTab->getOwnedWidget(); - if ( editor->getDocument().isEmpty() ) { - tabWidget->removeTab( firstTab ); - } - } } ); dialog->addEventListener( Event::OnWindowClose, [&]( const Event* ) { if ( mCurEditor && !SceneManager::instance()->isShootingDown() )