diff --git a/bin/assets/layouts/test.css b/bin/assets/layouts/test.css index b537684f9..331697e2c 100644 --- a/bin/assets/layouts/test.css +++ b/bin/assets/layouts/test.css @@ -22,7 +22,7 @@ TabWidget { tab-separation: -1dp; } -TabWidget::TabContainer { +TabWidget::TabBar { background-image: rectangle(solid, #2f3030); background-size: 100% 1dp; background-position-y: bottom; diff --git a/bin/assets/ui/breeze.css b/bin/assets/ui/breeze.css index d4bb66581..4186e44ac 100644 --- a/bin/assets/ui/breeze.css +++ b/bin/assets/ui/breeze.css @@ -399,7 +399,7 @@ TabWidget { gravity: left|bottom; } -TabWidget::TabContainer { +TabWidget::TabBar { background-image: rectangle(solid, var(--tab-line)); background-size: 100% 1px; background-position-y: bottom; diff --git a/bin/assets/ui/uitheme.css b/bin/assets/ui/uitheme.css index 4eb3e6ba8..3e148fe4f 100644 --- a/bin/assets/ui/uitheme.css +++ b/bin/assets/ui/uitheme.css @@ -10,7 +10,7 @@ TabWidget { tab-separation: -1dp; } -TabWidget::TabContainer { +TabWidget::TabBar { background-image: rectangle(solid, #2f3030); background-size: 100% 1dp; background-position-y: bottom; diff --git a/bin/assets/ui/uitheme.eta b/bin/assets/ui/uitheme.eta index a304dc837..6513e854b 100644 Binary files a/bin/assets/ui/uitheme.eta and b/bin/assets/ui/uitheme.eta differ diff --git a/bin/assets/ui/uitheme/uitheme_tabcontainer_normal.3_0_3_3.9.png b/bin/assets/ui/uitheme/uitheme_tabbar_normal.3_0_3_3.9.png similarity index 100% rename from bin/assets/ui/uitheme/uitheme_tabcontainer_normal.3_0_3_3.9.png rename to bin/assets/ui/uitheme/uitheme_tabbar_normal.3_0_3_3.9.png diff --git a/bin/assets/ui/uitheme1.5x.eta b/bin/assets/ui/uitheme1.5x.eta index 07bc7f827..a9b028c5b 100644 Binary files a/bin/assets/ui/uitheme1.5x.eta and b/bin/assets/ui/uitheme1.5x.eta differ diff --git a/bin/assets/ui/uitheme1.5x/uitheme1.5x_tabcontainer_normal.4_0_4_4.9.png b/bin/assets/ui/uitheme1.5x/uitheme1.5x_tabbar_normal.4_0_4_4.9.png similarity index 100% rename from bin/assets/ui/uitheme1.5x/uitheme1.5x_tabcontainer_normal.4_0_4_4.9.png rename to bin/assets/ui/uitheme1.5x/uitheme1.5x_tabbar_normal.4_0_4_4.9.png diff --git a/bin/assets/ui/uitheme2x.eta b/bin/assets/ui/uitheme2x.eta index f2a802fff..125b16552 100644 Binary files a/bin/assets/ui/uitheme2x.eta and b/bin/assets/ui/uitheme2x.eta differ diff --git a/bin/assets/ui/uitheme2x/uitheme2x_tabcontainer_normal.6_0_6_6.9.png b/bin/assets/ui/uitheme2x/uitheme2x_tabbar_normal.6_0_6_6.9.png similarity index 100% rename from bin/assets/ui/uitheme2x/uitheme2x_tabcontainer_normal.6_0_6_6.9.png rename to bin/assets/ui/uitheme2x/uitheme2x_tabbar_normal.6_0_6_6.9.png diff --git a/include/eepp/scene/node.hpp b/include/eepp/scene/node.hpp index 1c62ec970..6cf38cc70 100644 --- a/include/eepp/scene/node.hpp +++ b/include/eepp/scene/node.hpp @@ -229,6 +229,19 @@ class EE_API Node : public Transformable { template T* asType() { return reinterpret_cast( this ); } + Node* findByType( const Uint32& type ) const; + + template T* findByType( const Uint32& type ) const { + return reinterpret_cast( findByType( type ) ); + } + + template T* bindByType( const Uint32& type, T*& ctrl ) { + ctrl = findByType( type ); + return ctrl; + } + + bool inNodeTree( Node* node ) const; + bool isReverseDraw() const; void setReverseDraw( bool reverseDraw ); @@ -372,6 +385,9 @@ class EE_API Node : public Transformable { virtual Node* overFind( const Vector2f& Point ); + /** This removes the node from its parent. Never use this unless you know what you are doing. */ + void detach(); + protected: typedef std::map> EventsMap; friend class EventDispatcher; diff --git a/include/eepp/ui/uisplitter.hpp b/include/eepp/ui/uisplitter.hpp index fd3d612c5..321c6d638 100644 --- a/include/eepp/ui/uisplitter.hpp +++ b/include/eepp/ui/uisplitter.hpp @@ -29,6 +29,16 @@ class EE_API UISplitter : public UILayout { void setDivisionSplit( const Float& divisionSplit ); + void swap(); + + bool isEmpty(); + + bool isFull(); + + UIWidget* getFirstWidget() const; + + UIWidget* getLastWidget() const; + protected: UIOrientation mOrientation; bool mSplitOnlyWhenNeeded; diff --git a/include/eepp/ui/uistyle.hpp b/include/eepp/ui/uistyle.hpp index 64bacf3f0..f167fa8c6 100644 --- a/include/eepp/ui/uistyle.hpp +++ b/include/eepp/ui/uistyle.hpp @@ -88,6 +88,7 @@ class EE_API UIStyle : public UIState { bool mChangingState; bool mForceReapplyProperties; bool mDisableAnimations; + bool mFirstState; void applyVarValues( CSS::StyleSheetProperty* style ); diff --git a/include/eepp/ui/uitabwidget.hpp b/include/eepp/ui/uitabwidget.hpp index 52d9ae6f7..aff994196 100644 --- a/include/eepp/ui/uitabwidget.hpp +++ b/include/eepp/ui/uitabwidget.hpp @@ -71,7 +71,7 @@ class EE_API UITabWidget : public UIWidget { Uint32 getSelectedTabIndex() const; - UIWidget* getTabContainer() const; + UIWidget* getTabBar() const; UIWidget* getControlContainer() const; @@ -124,15 +124,20 @@ class EE_API UITabWidget : public UIWidget { UITab* setTabSelected( const Uint32& tabIndex ); + const bool& getHideWhenNotNeeded() const; + + void setHideWhenNotNeeded( const bool& hideWhenNotNeeded ); + protected: friend class UITab; UIWidget* mCtrlContainer; - UIWidget* mTabContainer; + UIWidget* mTabBar; StyleConfig mStyleConfig; std::deque mTabs; UITab* mTabSelected; Uint32 mTabSelectedIndex; + bool mHideWhenNotNeeded; void onThemeLoaded(); diff --git a/src/eepp/scene/node.cpp b/src/eepp/scene/node.cpp index 539659735..9029119fc 100644 --- a/src/eepp/scene/node.cpp +++ b/src/eepp/scene/node.cpp @@ -582,6 +582,8 @@ void Node::childAdd( Node* node ) { if ( NULL == mChild ) { mChild = node; mChildLast = node; + mChild->mPrev = NULL; + mChild->mNext = NULL; } else { mChildLast->mNext = node; node->mPrev = mChildLast; @@ -713,7 +715,7 @@ const String::HashType& Node::getIdHash() const { } Node* Node::findIdHash( const String::HashType& idHash ) const { - if ( mIdHash == idHash ) { + if ( !isClosing() && mIdHash == idHash ) { return const_cast( this ); } else { Node* child = mChild; @@ -735,6 +737,37 @@ Node* Node::find( const std::string& id ) const { return findIdHash( String::hash( id ) ); } +Node* Node::findByType( const Uint32& type ) const { + if ( !isClosing() && isType( type ) ) { + return const_cast( this ); + } else { + Node* child = mChild; + while ( NULL != child ) { + Node* foundNode = child->findByType( type ); + if ( NULL != foundNode ) + return foundNode; + child = child->mNext; + } + } + + return NULL; +} + +bool Node::inNodeTree( Node* node ) const { + if ( this == node ) { + return true; + } else { + Node* child = mChild; + while ( NULL != child ) { + if ( child->inNodeTree( node ) ) + return true; + child = child->mNext; + } + } + + return false; +} + bool Node::isChild( Node* child ) const { Node* childLoop = mChild; @@ -876,6 +909,13 @@ Node* Node::overFind( const Vector2f& Point ) { return pOver; } +void Node::detach() { + if ( mParentCtrl ) { + mParentCtrl->childRemove( this ); + mParentCtrl = NULL; + } +} + void Node::onSceneChange() { mSceneNode = findSceneNode(); diff --git a/src/eepp/system/filesystem.cpp b/src/eepp/system/filesystem.cpp index 9c87f4e3a..448669002 100644 --- a/src/eepp/system/filesystem.cpp +++ b/src/eepp/system/filesystem.cpp @@ -113,7 +113,9 @@ bool FileSystem::fileCopy( const std::string& src, const std::string& dst ) { } std::string FileSystem::fileExtension( const std::string& filepath, const bool& lowerExt ) { - std::string tstr( filepath.substr( filepath.find_last_of( "." ) + 1 ) ); + size_t dotPos = filepath.find_last_of( "." ); + std::string tstr( dotPos != std::string::npos ? filepath.substr( dotPos + 1 ) + : fileNameFromPath( filepath ) ); if ( lowerExt ) String::toLowerInPlace( tstr ); diff --git a/src/eepp/ui/doc/syntaxdefinitionmanager.cpp b/src/eepp/ui/doc/syntaxdefinitionmanager.cpp index 353e6affd..57756f906 100644 --- a/src/eepp/ui/doc/syntaxdefinitionmanager.cpp +++ b/src/eepp/ui/doc/syntaxdefinitionmanager.cpp @@ -843,6 +843,22 @@ SyntaxDefinitionManager::SyntaxDefinitionManager() { {{"[=]"}, "operator"}, {{"https?://%S+"}, "function"}, }} ); + + // Makefile + add( {{"Makefile", "makefile", "%.mk$", "%.make$"}, + { + {{"#.*\n"}, "comment"}, + {{"[[.]]}"}, "normal"}, + {{"$[@^<%%?+|*]"}, "keyword2"}, + {{"$%(", "%)"}, "keyword"}, + {{"%f[%w_][%d%.]+%f[^%w_]"}, "number"}, + {{"%..*:"}, "keyword2"}, + {{".*:="}, "function"}, + {{".*+="}, "function"}, + {{".*%s="}, "function"}, + }, + {}, + "#"} ); } SyntaxDefinition& SyntaxDefinitionManager::add( SyntaxDefinition&& syntaxStyle ) { @@ -878,5 +894,4 @@ SyntaxDefinitionManager::getStyleByExtension( const std::string& filePath ) cons } return mEmptyDefinition; } - }}} // namespace EE::UI::Doc diff --git a/src/eepp/ui/uisplitter.cpp b/src/eepp/ui/uisplitter.cpp index 0371d1ebe..85787274f 100644 --- a/src/eepp/ui/uisplitter.cpp +++ b/src/eepp/ui/uisplitter.cpp @@ -129,6 +129,31 @@ void UISplitter::setDivisionSplit( const Float& divisionSplit ) { } } +void UISplitter::swap() { + if ( isFull() ) { + UIWidget* tmp = mFirstWidget; + mFirstWidget = mLastWidget; + mLastWidget = tmp; + setLayoutDirty(); + } +} + +bool UISplitter::isEmpty() { + return !mFirstWidget && !mLastWidget; +} + +bool UISplitter::isFull() { + return mFirstWidget && mLastWidget; +} + +UIWidget* UISplitter::getFirstWidget() const { + return mFirstWidget; +} + +UIWidget* UISplitter::getLastWidget() const { + return mLastWidget; +} + void UISplitter::onChildCountChange( Node* child, const bool& removed ) { if ( child != mSplitter ) { if ( !removed ) { diff --git a/src/eepp/ui/uistyle.cpp b/src/eepp/ui/uistyle.cpp index 801039c6e..b5bdae3b5 100644 --- a/src/eepp/ui/uistyle.cpp +++ b/src/eepp/ui/uistyle.cpp @@ -25,7 +25,8 @@ UIStyle::UIStyle( UIWidget* widget ) : mDefinition( nullptr ), mChangingState( false ), mForceReapplyProperties( false ), - mDisableAnimations( false ) {} + mDisableAnimations( false ), + mFirstState( true ) {} UIStyle::~UIStyle() { removeStructurallyVolatileWidgetFromParent(); @@ -291,6 +292,7 @@ void UIStyle::onStateChange() { } mChangingState = false; + mFirstState = false; } } @@ -401,7 +403,8 @@ void UIStyle::applyStyleSheetProperty( const StyleSheetProperty& property, } } - if ( !mDisableAnimations && !mWidget->isSceneNodeLoading() && NULL != propertyDefinition && + if ( !mDisableAnimations && !mFirstState && !mWidget->isSceneNodeLoading() && + NULL != propertyDefinition && StyleSheetPropertyAnimation::animationSupported( propertyDefinition->getType() ) && hasTransition( property.getName() ) && !hasAnimation( property.getPropertyDefinition() ) ) { diff --git a/src/eepp/ui/uitabwidget.cpp b/src/eepp/ui/uitabwidget.cpp index caa38c6b1..7cda247c2 100644 --- a/src/eepp/ui/uitabwidget.cpp +++ b/src/eepp/ui/uitabwidget.cpp @@ -16,16 +16,17 @@ UITabWidget* UITabWidget::New() { UITabWidget::UITabWidget() : UIWidget( "tabwidget" ), mCtrlContainer( NULL ), - mTabContainer( NULL ), + mTabBar( NULL ), mTabSelected( NULL ), - mTabSelectedIndex( eeINDEX_NOT_FOUND ) { + mTabSelectedIndex( eeINDEX_NOT_FOUND ), + mHideWhenNotNeeded( false ) { setHorizontalAlign( UI_HALIGN_CENTER ); - mTabContainer = UIWidget::NewWithTag( "tabwidget::tabcontainer" ); - mTabContainer->setPixelsSize( mSize.getWidth(), mStyleConfig.TabHeight ) + mTabBar = UIWidget::NewWithTag( "tabwidget::tabbar" ); + mTabBar->setPixelsSize( mSize.getWidth(), mStyleConfig.TabHeight ) ->setParent( this ) ->setPosition( 0, 0 ); - mTabContainer->clipEnable(); + mTabBar->clipEnable(); mCtrlContainer = UIWidget::NewWithTag( "tabwidget::container" ); mCtrlContainer @@ -53,9 +54,9 @@ bool UITabWidget::isType( const Uint32& type ) const { void UITabWidget::setTheme( UITheme* Theme ) { UIWidget::setTheme( Theme ); - mTabContainer->setThemeSkin( Theme, "tabwidget" ); + mTabBar->setThemeSkin( Theme, "tabwidget" ); - mCtrlContainer->setThemeSkin( Theme, "tabcontainer" ); + mCtrlContainer->setThemeSkin( Theme, "tabbar" ); if ( 0 == mStyleConfig.TabHeight ) { UISkin* tSkin = Theme->getSkin( "tab" ); @@ -81,18 +82,35 @@ void UITabWidget::onThemeLoaded() { } void UITabWidget::setContainerSize() { - mTabContainer->setPixelsSize( mSize.getWidth() - mRealPadding.Left - mRealPadding.Right, - PixelDensity::dpToPx( mStyleConfig.TabHeight ) ); - mTabContainer->setPosition( mPadding.Left, mPadding.Top ); - mCtrlContainer->setPosition( mPadding.Left, mPadding.Top + mStyleConfig.TabHeight ); - Sizef s( mSize.getWidth() - mRealPadding.Left - mRealPadding.Right, - mSize.getHeight() - PixelDensity::dpToPx( mStyleConfig.TabHeight ) - mRealPadding.Top - - mRealPadding.Bottom ); - if ( s != mCtrlContainer->getPixelsSize() ) { - mCtrlContainer->setPixelsSize( s ); + if ( getTabCount() < 2 && mHideWhenNotNeeded ) { + mTabBar->setVisible( false ); + mTabBar->setEnabled( false ); + mCtrlContainer->setPosition( mPadding.Left, mPadding.Top ); + Sizef s( mSize.getWidth() - mRealPadding.Left - mRealPadding.Right, + mSize.getHeight() - mRealPadding.Top - mRealPadding.Bottom ); + if ( s != mCtrlContainer->getPixelsSize() ) { + mCtrlContainer->setPixelsSize( s ); - for ( auto& tab : mTabs ) { - refreshOwnedWidget( tab ); + for ( auto& tab : mTabs ) { + refreshOwnedWidget( tab ); + } + } + } else { + mTabBar->setVisible( true ); + mTabBar->setEnabled( true ); + mTabBar->setPixelsSize( mSize.getWidth() - mRealPadding.Left - mRealPadding.Right, + PixelDensity::dpToPx( mStyleConfig.TabHeight ) ); + mTabBar->setPosition( mPadding.Left, mPadding.Top ); + mCtrlContainer->setPosition( mPadding.Left, mPadding.Top + mStyleConfig.TabHeight ); + Sizef s( mSize.getWidth() - mRealPadding.Left - mRealPadding.Right, + mSize.getHeight() - PixelDensity::dpToPx( mStyleConfig.TabHeight ) - + mRealPadding.Top - mRealPadding.Bottom ); + if ( s != mCtrlContainer->getPixelsSize() ) { + mCtrlContainer->setPixelsSize( s ); + + for ( auto& tab : mTabs ) { + refreshOwnedWidget( tab ); + } } } } @@ -105,7 +123,6 @@ void UITabWidget::setStyleConfig( const StyleConfig& styleConfig ) { Uint32 tabWidgetHeight = mStyleConfig.TabHeight; mStyleConfig = styleConfig; mStyleConfig.TabHeight = tabWidgetHeight; - setContainerSize(); orderTabs(); } @@ -141,8 +158,8 @@ bool UITabWidget::isDrawInvalidator() const { void UITabWidget::invalidate( Node* invalidator ) { // Only invalidate if the invalidator is actually visible in the current active tab. if ( NULL != invalidator ) { - if ( invalidator == mCtrlContainer || invalidator == mTabContainer || - mTabContainer->isChild( invalidator ) ) { + if ( invalidator == mCtrlContainer || invalidator == mTabBar || + mTabBar->isChild( invalidator ) ) { mNodeDrawInvalidator->invalidate( mCtrlContainer ); } else if ( invalidator->getParent() == mCtrlContainer ) { if ( invalidator->isVisible() ) { @@ -339,6 +356,8 @@ void UITabWidget::zorderTabs() { void UITabWidget::orderTabs() { applyThemeToTabs(); + setContainerSize(); + zorderTabs(); posTabs(); @@ -354,7 +373,7 @@ void UITabWidget::updateTabs() { UITab* UITabWidget::createTab( const String& Text, UINode* ownedWidget, Drawable* Icon ) { UITab* tab = UITab::New(); - tab->setParent( mTabContainer ); + tab->setParent( mTabBar ); tab->setFlags( UI_VALIGN_CENTER | UI_HALIGN_CENTER | UI_AUTO_SIZE ); tab->setIcon( Icon ); tab->setText( Text ); @@ -381,7 +400,7 @@ UITab* UITabWidget::add( const String& Text, UINode* CtrlOwned, Drawable* Icon ) } UITabWidget* UITabWidget::add( UITab* Tab ) { - Tab->setParent( mTabContainer ); + Tab->setParent( mTabBar ); mTabs.push_back( Tab ); @@ -558,6 +577,17 @@ UITab* UITabWidget::setTabSelected( const Uint32& tabIndex ) { return NULL; } +const bool& UITabWidget::getHideWhenNotNeeded() const { + return mHideWhenNotNeeded; +} + +void UITabWidget::setHideWhenNotNeeded( const bool& hideWhenNotNeeded ) { + if ( mHideWhenNotNeeded != hideWhenNotNeeded ) { + mHideWhenNotNeeded = hideWhenNotNeeded; + setContainerSize(); + } +} + void UITabWidget::refreshOwnedWidget( UITab* tab ) { if ( NULL != tab && NULL != tab->getOwnedWidget() ) { tab->getOwnedWidget()->setParent( mCtrlContainer ); @@ -607,11 +637,11 @@ void UITabWidget::onSizeChange() { } void UITabWidget::onChildCountChange( Node* child, const bool& removed ) { - if ( !removed && child != mTabContainer && child != mCtrlContainer ) { + if ( !removed && child != mTabBar && child != mCtrlContainer ) { if ( child->isType( UI_TYPE_TAB ) ) { UITab* Tab = static_cast( child ); - Tab->setParent( mTabContainer ); + Tab->setParent( mTabBar ); mTabs.push_back( Tab ); @@ -644,8 +674,8 @@ void UITabWidget::applyThemeToTabs() { } } -UIWidget* UITabWidget::getTabContainer() const { - return mTabContainer; +UIWidget* UITabWidget::getTabBar() const { + return mTabBar; } UIWidget* UITabWidget::getControlContainer() const { diff --git a/src/eepp/ui/uiwidget.cpp b/src/eepp/ui/uiwidget.cpp index 56654ac0a..2843df7ce 100644 --- a/src/eepp/ui/uiwidget.cpp +++ b/src/eepp/ui/uiwidget.cpp @@ -1063,7 +1063,7 @@ const Uint32& UIWidget::getStylePreviousState() const { std::vector UIWidget::findAllByClass( const std::string& className ) { std::vector widgets; - if ( hasClass( className ) ) { + if ( !isClosing() && hasClass( className ) ) { widgets.push_back( this ); } @@ -1087,7 +1087,7 @@ std::vector UIWidget::findAllByClass( const std::string& className ) std::vector UIWidget::findAllByTag( const std::string& tag ) { std::vector widgets; - if ( getElementTag() == tag ) { + if ( !isClosing() && getElementTag() == tag ) { widgets.push_back( this ); } @@ -1108,7 +1108,7 @@ std::vector UIWidget::findAllByTag( const std::string& tag ) { } UIWidget* UIWidget::findByClass( const std::string& className ) { - if ( hasClass( className ) ) { + if ( !isClosing() && hasClass( className ) ) { return this; } else { Node* child = mChild; @@ -1129,7 +1129,7 @@ UIWidget* UIWidget::findByClass( const std::string& className ) { } UIWidget* UIWidget::findByTag( const std::string& tag ) { - if ( getElementTag() == tag ) { + if ( !isClosing() && getElementTag() == tag ) { return this; } else { Node* child = mChild; @@ -1150,7 +1150,7 @@ UIWidget* UIWidget::findByTag( const std::string& tag ) { } UIWidget* UIWidget::querySelector( const CSS::StyleSheetSelector& selector ) { - if ( selector.select( this ) ) { + if ( !isClosing() && selector.select( this ) ) { return this; } else { Node* child = mChild; @@ -1173,7 +1173,7 @@ UIWidget* UIWidget::querySelector( const CSS::StyleSheetSelector& selector ) { std::vector UIWidget::querySelectorAll( const CSS::StyleSheetSelector& selector ) { std::vector widgets; - if ( selector.select( this ) ) { + if ( !isClosing() && selector.select( this ) ) { widgets.push_back( this ); } diff --git a/src/tools/codeeditor/codeeditor.cpp b/src/tools/codeeditor/codeeditor.cpp index ee98a2f78..5bc6c45fc 100644 --- a/src/tools/codeeditor/codeeditor.cpp +++ b/src/tools/codeeditor/codeeditor.cpp @@ -49,30 +49,68 @@ void App::closeCurrrentTab() { if ( mCurEditor ) { UITabWidget* tabWidget = tabWidgetFromEditor( mCurEditor ); if ( tabWidget ) { - tabWidget->removeTab( (UITab*)mCurEditor->getData() ); - if ( tabWidget->getTabCount() > 0 ) { - tabWidget->setTabSelected( tabWidget->getTabCount() - 1 ); - tabWidget->getSelectedTab()->getOwnedWidget()->setFocus(); + if ( !( mCurEditor->getDocument().isEmpty() && + !tabWidget->getParent()->isType( UI_TYPE_SPLITTER ) ) ) { + tabWidget->removeTab( (UITab*)mCurEditor->getData() ); } } } } -void App::splitEditor( const UIOrientation& orientation ) { - UITabWidget* tabWidget = tabWidgetFromEditor( mCurEditor ); - if ( tabWidget ) { - UISplitter* splitter = (UISplitter*)tabWidget->getParent(); - if ( splitter->getChildCount() < 3 ) { - splitter->setOrientation( orientation ); - createEditorWithSplitter( splitter ); +void App::splitEditor( const SplitDirection& direction, UICodeEditor* editor ) { + if ( !editor ) + return; + UIOrientation orientation = + direction == SplitDirection::Left || direction == SplitDirection::Right + ? UIOrientation::Horizontal + : UIOrientation::Vertical; + UITabWidget* tabWidget = tabWidgetFromEditor( editor ); + if ( !tabWidget ) + return; + Node* parent = tabWidget->getParent(); + UISplitter* parentSplitter = NULL; + bool wasFirst = true; + + if ( parent->isType( UI_TYPE_SPLITTER ) ) { + parentSplitter = parent->asType(); + wasFirst = parentSplitter->getFirstWidget() == tabWidget; + if ( !parentSplitter->isFull() ) { + parentSplitter->setOrientation( orientation ); + createEditorWithTabWidget( parentSplitter ); + if ( direction == SplitDirection::Left || direction == SplitDirection::Top ) + parentSplitter->swap(); + return; } } + + UISplitter* splitter = UISplitter::New(); + splitter->setLayoutSizePolicy( SizePolicy::MatchParent, SizePolicy::MatchParent ); + splitter->setOrientation( orientation ); + tabWidget->detach(); + splitter->setParent( parent ); + tabWidget->setParent( splitter ); + createEditorWithTabWidget( splitter ); + if ( direction == SplitDirection::Left || direction == SplitDirection::Top ) + splitter->swap(); + + if ( parentSplitter ) { + if ( wasFirst && parentSplitter->getFirstWidget() != splitter ) { + parentSplitter->swap(); + } else if ( !wasFirst && parentSplitter->getLastWidget() != splitter ) { + parentSplitter->swap(); + } + } + eeASSERT( mBaseLayout->getChildCount() <= 1 ); } UICodeEditor* App::createCodeEditor() { UICodeEditor* codeEditor = UICodeEditor::New(); codeEditor->setFontSize( 11 ); - codeEditor->getDocument().setCommand( "find", [&]() { findTextMessageBox(); } ); + codeEditor->getDocument().setCommand( "save-doc", [&, codeEditor] { + if ( codeEditor->save() ) + updateEditorTitle( codeEditor ); + } ); + codeEditor->getDocument().setCommand( "find", [&] { findTextMessageBox(); } ); codeEditor->getDocument().setCommand( "repeat-find", [&] { findText(); } ); codeEditor->getDocument().setCommand( "close-app", [&] { closeApp(); } ); codeEditor->getDocument().setCommand( "fullscreen-toggle", @@ -117,16 +155,24 @@ UICodeEditor* App::createCodeEditor() { tab->getOwnedWidget()->setFocus(); } } ); - codeEditor->getDocument().setCommand( "split-horizontal", - [&] { splitEditor( UIOrientation::Horizontal ); } ); - codeEditor->getDocument().setCommand( "split-vertical", - [&] { splitEditor( UIOrientation::Vertical ); } ); + codeEditor->getDocument().setCommand( + "split-right", [&] { splitEditor( SplitDirection::Right, mCurEditor ); } ); + codeEditor->getDocument().setCommand( + "split-bottom", [&] { splitEditor( SplitDirection::Bottom, mCurEditor ); } ); + codeEditor->getDocument().setCommand( + "split-left", [&] { splitEditor( SplitDirection::Left, mCurEditor ); } ); + codeEditor->getDocument().setCommand( "split-top", + [&] { splitEditor( SplitDirection::Top, mCurEditor ); } ); + codeEditor->getDocument().setCommand( "split-swap", [&] { + if ( UISplitter* splitter = splitterFromEditor( mCurEditor ) ) + splitter->swap(); + } ); codeEditor->addKeyBindingString( "escape", "close-app", true ); codeEditor->addKeyBindingString( "f2", "open-file", true ); codeEditor->addKeyBindingString( "f3", "repeat-find", false ); codeEditor->addKeyBindingString( "f12", "console-toggle", true ); codeEditor->addKeyBindingString( "alt+return", "fullscreen-toggle", true ); - codeEditor->addKeyBindingString( "ctrl+s", "save", false ); + codeEditor->addKeyBindingString( "ctrl+s", "save-doc", false ); codeEditor->addKeyBindingString( "ctrl+f", "find", false ); codeEditor->addKeyBindingString( "ctrl+q", "close-app", true ); codeEditor->addKeyBindingString( "ctrl+o", "open-file", true ); @@ -135,8 +181,11 @@ UICodeEditor* App::createCodeEditor() { codeEditor->addKeyBindingString( "ctrl+w", "close-doc", true ); codeEditor->addKeyBindingString( "ctrl+tab", "next-doc", true ); codeEditor->addKeyBindingString( "ctrl+shift+tab", "previous-doc", true ); - codeEditor->addKeyBindingString( "ctrl+shift+l", "split-horizontal", true ); - codeEditor->addKeyBindingString( "ctrl+shift+k", "split-vertical", true ); + codeEditor->addKeyBindingString( "ctrl+shift+j", "split-left", true ); + codeEditor->addKeyBindingString( "ctrl+shift+l", "split-right", true ); + codeEditor->addKeyBindingString( "ctrl+shift+i", "split-top", true ); + codeEditor->addKeyBindingString( "ctrl+shift+k", "split-bottom", true ); + codeEditor->addKeyBindingString( "ctrl+shift+s", "split-swap", true ); codeEditor->addEventListener( Event::OnFocus, [&]( const Event* event ) { mCurEditor = event->getNode()->asType(); updateEditorTitle( mCurEditor ); @@ -167,6 +216,76 @@ void App::updateEditorTitle( UICodeEditor* editor ) { setAppTitle( title ); } +void App::focusSomeEditor( Node* searchFrom ) { + UICodeEditor* editor = + searchFrom ? searchFrom->findByType( UI_TYPE_CODEEDITOR ) + : mUISceneNode->getRoot()->findByType( UI_TYPE_CODEEDITOR ); + if ( searchFrom && !editor ) + editor = mUISceneNode->getRoot()->findByType( UI_TYPE_CODEEDITOR ); + if ( editor && tabWidgetFromEditor( editor ) && !tabWidgetFromEditor( editor )->isClosing() ) { + editor->setFocus(); + } else { + UITabWidget* tabW = mUISceneNode->getRoot()->findByType( UI_TYPE_TABWIDGET ); + if ( tabW && tabW->getTabCount() > 0 ) { + tabW->setTabSelected( tabW->getTabCount() - 1 ); + } + } +} + +void App::onTabClosed( const TabEvent* tabEvent ) { + UICodeEditor* editor = mCurEditor; + if ( tabEvent->getTab()->getOwnedWidget() == mCurEditor ) { + mCurEditor = NULL; + } + UITabWidget* tabWidget = tabEvent->getTab()->getTabWidget(); + if ( tabWidget->getTabCount() == 0 ) { + UISplitter* splitter = splitterFromEditor( editor ); + if ( splitter ) { + if ( splitter->isFull() ) { + tabWidget->close(); + // Remove splitter if it's redundant + Node* parent = splitter->getParent(); + if ( parent->isType( UI_TYPE_SPLITTER ) ) { + UISplitter* parentSplitter = parent->asType(); + Node* remainingNode = tabWidget == splitter->getFirstWidget() + ? splitter->getLastWidget() + : splitter->getFirstWidget(); + bool wasFirst = parentSplitter->getFirstWidget() == splitter; + remainingNode->detach(); + splitter->setParent( mUISceneNode->getRoot() ); + splitter->setVisible( false ); + splitter->setEnabled( false ); + splitter->close(); + remainingNode->setParent( parentSplitter ); + if ( wasFirst ) + parentSplitter->swap(); + focusSomeEditor( parentSplitter ); + } else { + // Then this is the main splitter + Node* remainingNode = tabWidget == splitter->getFirstWidget() + ? splitter->getLastWidget() + : splitter->getFirstWidget(); + splitter->setParent( mUISceneNode->getRoot() ); + splitter->setVisible( false ); + splitter->setEnabled( false ); + splitter->close(); + eeASSERT( parent->getChildCount() == 0 ); + remainingNode->setParent( parent ); + focusSomeEditor( NULL ); + } + eeASSERT( mBaseLayout->getChildCount() == 1 ); + return; + } + } + auto d = createCodeEditorInTabWidget( tabWidget ); + d.first->getTabWidget()->setTabSelected( d.first ); + d.second->setFocus(); + } else { + tabWidget->setTabSelected( tabWidget->getTabCount() - 1 ); + tabWidget->getSelectedTab()->getOwnedWidget()->setFocus(); + } +} + std::pair App::createCodeEditorInTabWidget( UITabWidget* tabWidget ) { if ( NULL == tabWidget ) return std::make_pair( (UITab*)NULL, (UICodeEditor*)NULL ); @@ -174,24 +293,17 @@ std::pair App::createCodeEditorInTabWidget( UITabWidget* UITab* tab = tabWidget->add( editor->getDocument().getFilename(), editor ); editor->setData( (UintPtr)tab ); tabWidget->addEventListener( Event::OnTabClosed, [&]( const Event* event ) { - const TabEvent* tabEvent = static_cast( event ); - if ( tabEvent->getTab()->getOwnedWidget() == mCurEditor ) { - mCurEditor = NULL; - } - UITabWidget* tabWidget = tabEvent->getTab()->getTabWidget(); - if ( tabWidget->getTabCount() == 0 ) { - auto d = createCodeEditorInTabWidget( tabWidget ); - d.first->getTabWidget()->setTabSelected( d.first ); - d.second->setFocus(); - } + onTabClosed( static_cast( event ) ); } ); return std::make_pair( tab, editor ); } -UITabWidget* App::createEditorWithTabWidget( UISplitter* splitter ) { +UITabWidget* App::createEditorWithTabWidget( Node* parent ) { UITabWidget* tabWidget = UITabWidget::New(); - tabWidget->setParent( splitter ); + tabWidget->setLayoutSizePolicy( SizePolicy::MatchParent, SizePolicy::MatchParent ); + tabWidget->setParent( parent ); tabWidget->setTabsClosable( true ); + tabWidget->setHideWhenNotNeeded( true ); tabWidget->addEventListener( Event::OnTabSelected, [&]( const Event* event ) { UITabWidget* tabWidget = event->getNode()->asType(); mCurEditor = tabWidget->getSelectedTab()->getOwnedWidget()->asType(); @@ -202,23 +314,18 @@ UITabWidget* App::createEditorWithTabWidget( UISplitter* splitter ) { return tabWidget; } -UISplitter* App::createEditorWithSplitter( Node* parent ) { - if ( NULL == parent ) - return NULL; - UISplitter* splitter = UISplitter::New(); - splitter->setLayoutSizePolicy( SizePolicy::MatchParent, SizePolicy::MatchParent ); - splitter->setParent( parent ); - auto d = createEditorWithTabWidget( splitter ); - d->getSelectedTab()->getOwnedWidget()->setFocus(); - return splitter; -} - UITabWidget* App::tabWidgetFromEditor( UICodeEditor* editor ) { if ( editor ) return ( (UITab*)editor->getData() )->getTabWidget(); return NULL; } +UISplitter* App::splitterFromEditor( UICodeEditor* editor ) { + if ( editor && editor->getParent()->getParent()->getParent()->isType( UI_TYPE_SPLITTER ) ) + return editor->getParent()->getParent()->getParent()->asType(); + return NULL; +} + void App::setAppTitle( const std::string& title ) { mWindow->setTitle( mWindowTitle + String( title.empty() ? "" : " - " + title ) ); } @@ -260,7 +367,7 @@ void App::openFileDialog() { void App::findText( String text ) { if ( text.empty() ) text = mLastSearch; - if ( text.empty() ) + if ( !mCurEditor || text.empty() ) return; mLastSearch = text; TextDocument& doc = mCurEditor->getDocument(); @@ -276,6 +383,8 @@ void App::findText( String text ) { } void App::findTextMessageBox() { + if ( !mCurEditor ) + return; UIMessageBox* inputSearch = UIMessageBox::New( UIMessageBox::INPUT, "Find text..." ); inputSearch->getTextInput()->setHint( "Find text..." ); String text = mCurEditor->getDocument().getSelectedText(); @@ -408,13 +517,10 @@ void App::init( const std::string& file ) { mUISceneNode->getRoot()->addClass( "appbackground" ); - mBaseLayout = UILinearLayout::NewVertical(); + mBaseLayout = UIRelativeLayout::New(); mBaseLayout->setLayoutSizePolicy( SizePolicy::MatchParent, SizePolicy::MatchParent ); - UISplitter* splitter = UISplitter::New(); - splitter->setLayoutSizePolicy( SizePolicy::MatchParent, SizePolicy::MatchParent ); - splitter->setParent( mBaseLayout ); - createEditorWithSplitter( splitter ); + createEditorWithTabWidget( mBaseLayout ); if ( !file.empty() ) { loadFileFromPath( file ); diff --git a/src/tools/codeeditor/codeeditor.hpp b/src/tools/codeeditor/codeeditor.hpp index a49eec361..f2ee2ac75 100644 --- a/src/tools/codeeditor/codeeditor.hpp +++ b/src/tools/codeeditor/codeeditor.hpp @@ -5,6 +5,8 @@ class App { public: + enum class SplitDirection { Left, Right, Top, Bottom }; + ~App(); void init( const std::string& file = "" ); @@ -25,15 +27,17 @@ class App { UICodeEditor* createCodeEditor(); - UISplitter* createEditorWithSplitter( Node* parent ); - UITabWidget* tabWidgetFromEditor( UICodeEditor* editor ); + UISplitter* splitterFromEditor( UICodeEditor* editor ); + std::pair createCodeEditorInTabWidget( UITabWidget* tabWidget ); - UITabWidget* createEditorWithTabWidget( UISplitter* splitter ); + UITabWidget* createEditorWithTabWidget( Node* parent ); - void splitEditor( const UIOrientation& orientation ); + void splitEditor( const SplitDirection& direction, UICodeEditor* editor ); + + void focusSomeEditor( Node* searchFrom = NULL ); protected: EE::Window::Window* mWindow{NULL}; @@ -58,6 +62,8 @@ class App { bool onCloseRequestCallback( EE::Window::Window* ); void closeCurrrentTab(); + + void onTabClosed( const TabEvent* tabEvent ); }; #endif // EE_TOOLS_CODEEDITOR_HPP