Allow to split editors views by dragging tabs into the tab widget corners.

This commit is contained in:
Martín Lucas Golini
2024-12-29 23:50:24 -03:00
parent a447da4002
commit 793e87af5c
14 changed files with 252 additions and 51 deletions

View File

@@ -574,7 +574,7 @@ class EE_API Node : public Transformable {
void childRemove( Node* node );
Rectf getScreenBounds();
Rectf getScreenBounds() const;
void setInternalPosition( const Vector2f& Pos );

View File

@@ -0,0 +1,10 @@
#ifndef EE_UI_SPLIT_DIRECTION_HPP
#define EE_UI_SPLIT_DIRECTION_HPP
namespace EE { namespace UI {
enum class SplitDirection { Left, Right, Top, Bottom };
}} // namespace EE::UI
#endif // EE_UI_SPLIT_DIRECTION_HPP

View File

@@ -1,6 +1,7 @@
#ifndef EE_UI_TOOLS_UICODEEDITORSPLITTER_HPP
#define EE_UI_TOOLS_UICODEEDITORSPLITTER_HPP
#include <eepp/ui/splitdirection.hpp>
#include <eepp/ui/uicodeeditor.hpp>
#include <eepp/ui/uimessagebox.hpp>
#include <eepp/ui/uiscenenode.hpp>
@@ -19,8 +20,6 @@ class EE_API UICodeEditorSplitter {
static const std::map<KeyBindings::Shortcut, std::string> getLocalDefaultKeybindings();
enum class SplitDirection { Left, Right, Top, Bottom };
class EE_API Client {
public:
virtual ~Client() {};
@@ -355,6 +354,12 @@ class EE_API UICodeEditorSplitter {
void setOnTabWidgetCreateCb( std::function<void( UITabWidget* )> cb );
bool getVisualSplitting() const;
void setVisualSplitting( bool visualSplitting );
Float getVisualSplitEdgePercent() const;
void setVisualSplitEdgePercent( Float visualSplitEdgePercent );
protected:
UISceneNode* mUISceneNode{ nullptr };
std::shared_ptr<ThreadPool> mThreadPool;
@@ -367,6 +372,7 @@ class EE_API UICodeEditorSplitter {
Client* mClient;
bool mHideTabBarOnSingleTab{ true };
bool mFirstCodeEditor{ true };
bool mVisualSplitting{ true };
UICodeEditor* mAboutToAddEditor{ nullptr };
UIMessageBox* mTryCloseMsgBox{ nullptr };
Mutex mTabWidgetMutex;
@@ -378,6 +384,7 @@ class EE_API UICodeEditorSplitter {
std::vector<NavigationRecord> mNavigationHistory;
size_t mNavigationHistoryPos{ std::numeric_limits<size_t>::max() };
std::function<void( UITabWidget* )> mOnTabWidgetCreateCb;
Float mVisualSplitEdgePercent{ 0.1 };
UICodeEditorSplitter( UICodeEditorSplitter::Client* client, UISceneNode* sceneNode,
std::shared_ptr<ThreadPool> threadPool,
@@ -387,6 +394,12 @@ class EE_API UICodeEditorSplitter {
virtual void onTabClosed( const TabEvent* tabEvent );
void closeAllTabs( std::vector<UITab*> tabs, UITabWidget::FocusTabBehavior focusTabBehavior );
UITabWidget* createTabWidget( Node* parent );
UITabWidget* splitTabWidget( SplitDirection, UITabWidget* );
void updateTabWidgetVisualSplitting();
};
}}} // namespace EE::UI::Tools

View File

@@ -428,6 +428,8 @@ class EE_API UINode : public Node {
void smartClipStart( const ClipType& reqClipType );
void smartClipEnd( const ClipType& reqClipType );
Color getDroppableHoveringColor();
};
}} // namespace EE::UI

View File

@@ -2,8 +2,10 @@
#define EE_UI_UITABWIDGET_HPP
#include <deque>
#include <eepp/ui/splitdirection.hpp>
#include <eepp/ui/uitab.hpp>
#include <eepp/ui/uiwidget.hpp>
#include <optional>
namespace EE { namespace UI {
@@ -26,6 +28,8 @@ class EE_API TabEvent : public Event {
class EE_API UITabWidget : public UIWidget {
public:
using SplitFunctionCb = std::function<UITabWidget*( SplitDirection, UITabWidget* )>;
enum class FocusTabBehavior { Closest, FocusOrder, Default };
class StyleConfig {
@@ -157,7 +161,7 @@ class EE_API UITabWidget : public UIWidget {
void setAllowRearrangeTabs( bool allowRearrangeTabs );
bool getAllowDragAndDropTabs() const;
bool allowDragAndDropTabs() const;
void setAllowDragAndDropTabs( bool allowDragAndDropTabs );
@@ -187,6 +191,8 @@ class EE_API UITabWidget : public UIWidget {
void swapTabs( UITab* left, UITab* right );
void setSplitFunction( SplitFunctionCb cb, Float splitEdgePercent = 0.1 );
protected:
friend class UITab;
@@ -209,6 +215,8 @@ class EE_API UITabWidget : public UIWidget {
FocusTabBehavior mFocusTabBehavior{ FocusTabBehavior::Closest };
std::deque<UITab*> mFocusHistory;
UIPopUpMenu* mCurrentMenu{ nullptr };
SplitFunctionCb mSplitFn;
Float mSplitEdgePercent{ 0.1 };
void onThemeLoaded();
@@ -229,6 +237,8 @@ class EE_API UITabWidget : public UIWidget {
virtual Uint32 onMessage( const NodeMessage* msg );
virtual void drawDroppableHovering();
void setContainerSize();
void posTabs();
@@ -254,6 +264,8 @@ class EE_API UITabWidget : public UIWidget {
void insertFocusHistory( UITab* tab );
void eraseFocusHistory( UITab* tab );
std::optional<SplitDirection> getDropDirection() const;
};
}} // namespace EE::UI

View File

@@ -360,6 +360,7 @@
../../include/eepp/ui/models/persistentmodelindex.hpp
../../include/eepp/ui/models/sortingproxymodel.hpp
../../include/eepp/ui/models/widgettreemodel.hpp
../../include/eepp/ui/splitdirection.hpp
../../include/eepp/ui/tools/textureatlaseditor.hpp
../../include/eepp/ui/tools/uicodeeditorsplitter.hpp
../../include/eepp/ui/tools/uicolorpicker.hpp

View File

@@ -510,7 +510,7 @@ void Node::onSizeChange() {
invalidateDraw();
}
Rectf Node::getScreenBounds() {
Rectf Node::getScreenBounds() const {
return Rectf( Vector2f( mScreenPosi.x, mScreenPosi.y ),
Sizef( (Float)(int)mSize.getWidth(), (Float)(int)mSize.getHeight() ) );
}

View File

@@ -626,11 +626,7 @@ void UICodeEditorSplitter::removeUnusedTab( UITabWidget* tabWidget, bool destroy
}
}
UITabWidget* UICodeEditorSplitter::createEditorWithTabWidget( Node* parent, bool openCurEditor ) {
eeASSERT( curWidgetExists() );
if ( nullptr == mBaseLayout )
mBaseLayout = parent;
UICodeEditor* prevCurEditor = mCurEditor;
UITabWidget* UICodeEditorSplitter::createTabWidget( Node* parent ) {
UITabWidget* tabWidget = UITabWidget::New();
tabWidget->setLayoutSizePolicy( SizePolicy::MatchParent, SizePolicy::MatchParent );
tabWidget->setParent( parent );
@@ -641,6 +637,13 @@ UITabWidget* UICodeEditorSplitter::createEditorWithTabWidget( Node* parent, bool
tabWidget->setAllowSwitchTabsInEmptySpaces( true );
tabWidget->setEnabledCreateContextMenu( true );
tabWidget->setFocusTabBehavior( UITabWidget::FocusTabBehavior::FocusOrder );
if ( mVisualSplitting ) {
tabWidget->setSplitFunction(
[this]( SplitDirection dir, UITabWidget* widget ) -> UITabWidget* {
return splitTabWidget( dir, widget );
},
mVisualSplitEdgePercent );
}
tabWidget->addEventListener( Event::OnTabSelected, [this]( const Event* event ) {
UITabWidget* tabWidget = event->getNode()->asType<UITabWidget>();
if ( tabWidget->getTabSelected()->getOwnedWidget()->isType( UI_TYPE_CODEEDITOR ) ) {
@@ -663,6 +666,17 @@ UITabWidget* UICodeEditorSplitter::createEditorWithTabWidget( Node* parent, bool
} );
if ( mOnTabWidgetCreateCb )
mOnTabWidgetCreateCb( tabWidget );
Lock l( mTabWidgetMutex );
mTabWidgets.push_back( tabWidget );
return tabWidget;
}
UITabWidget* UICodeEditorSplitter::createEditorWithTabWidget( Node* parent, bool openCurEditor ) {
eeASSERT( curWidgetExists() );
if ( nullptr == mBaseLayout )
mBaseLayout = parent;
UICodeEditor* prevCurEditor = mCurEditor;
UITabWidget* tabWidget = createTabWidget( parent );
auto editorData = createCodeEditorInTabWidget( tabWidget );
if ( editorData.first == nullptr || editorData.second == nullptr ) {
if ( !mTabWidgets.empty() && mTabWidgets[0]->getTabCount() > 0 ) {
@@ -684,8 +698,6 @@ UITabWidget* UICodeEditorSplitter::createEditorWithTabWidget( Node* parent, bool
editorData.first->setTooltipText( path );
}
mAboutToAddEditor = nullptr;
Lock l( mTabWidgetMutex );
mTabWidgets.push_back( tabWidget );
return tabWidget;
}
@@ -1205,6 +1217,51 @@ UISplitter* UICodeEditorSplitter::split( const SplitDirection& direction, UIWidg
return splitter;
}
UITabWidget* UICodeEditorSplitter::splitTabWidget( SplitDirection direction,
UITabWidget* tabWidget ) {
if ( !tabWidget )
return nullptr;
UIOrientation orientation =
direction == SplitDirection::Left || direction == SplitDirection::Right
? UIOrientation::Horizontal
: UIOrientation::Vertical;
Node* parent = tabWidget->getParent();
UISplitter* parentSplitter = nullptr;
bool wasFirst = true;
if ( parent->isType( UI_TYPE_SPLITTER ) ) {
parentSplitter = parent->asType<UISplitter>();
wasFirst = parentSplitter->getFirstWidget() == tabWidget;
if ( !parentSplitter->isFull() ) {
parentSplitter->setOrientation( orientation );
UITabWidget* newTabWidget = createTabWidget( parentSplitter );
if ( direction == SplitDirection::Left || direction == SplitDirection::Top )
parentSplitter->swap();
return newTabWidget;
}
}
UISplitter* splitter = UISplitter::New();
splitter->setLayoutSizePolicy( SizePolicy::MatchParent, SizePolicy::MatchParent );
splitter->setOrientation( orientation );
tabWidget->detach();
splitter->setParent( parent );
tabWidget->setParent( splitter );
UITabWidget* newTabWidget = createTabWidget( 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();
}
}
return newTabWidget;
}
void UICodeEditorSplitter::switchToTab( Int32 index ) {
UITabWidget* tabWidget = tabWidgetFromWidget( mCurWidget );
if ( tabWidget ) {
@@ -1665,4 +1722,40 @@ void UICodeEditorSplitter::setOnTabWidgetCreateCb( std::function<void( UITabWidg
mOnTabWidgetCreateCb = std::move( cb );
}
bool UICodeEditorSplitter::getVisualSplitting() const {
return mVisualSplitting;
}
void UICodeEditorSplitter::setVisualSplitting( bool visualSplitting ) {
if ( mVisualSplitting != visualSplitting ) {
mVisualSplitting = visualSplitting;
updateTabWidgetVisualSplitting();
}
}
Float UICodeEditorSplitter::getVisualSplitEdgePercent() const {
return mVisualSplitEdgePercent;
}
void UICodeEditorSplitter::setVisualSplitEdgePercent( Float visualSplitEdgePercent ) {
if ( mVisualSplitEdgePercent != visualSplitEdgePercent ) {
mVisualSplitEdgePercent = visualSplitEdgePercent;
updateTabWidgetVisualSplitting();
}
}
void UICodeEditorSplitter::updateTabWidgetVisualSplitting() {
for ( UITabWidget* tabWidget : mTabWidgets ) {
if ( mVisualSplitting ) {
tabWidget->setSplitFunction(
[this]( SplitDirection dir, UITabWidget* widget ) -> UITabWidget* {
return splitTabWidget( dir, widget );
},
mVisualSplitEdgePercent );
} else {
tabWidget->setSplitFunction( nullptr, mVisualSplitEdgePercent );
}
}
}
}}} // namespace EE::UI::Tools

View File

@@ -567,25 +567,10 @@ void UINode::drawOverNode() {
}
void UINode::drawDroppableHovering() {
const PropertyDefinition* def =
StyleSheetSpecification::instance()->getProperty( "droppable-hovering-color" );
Color color = Color::fromString( def->getDefaultValue() );
if ( isWidget() ) {
UIWidget* widget = asType<UIWidget>();
std::string colorString = widget->getPropertyString( def );
if ( !colorString.empty() ) {
color = Color::fromString( colorString );
} else {
colorString = mUISceneNode->getRoot()->getPropertyString( def );
if ( !colorString.empty() )
color = Color::fromString( colorString );
}
}
Primitives P;
P.setFillMode( DRAW_FILL );
P.setBlendMode( getBlendMode() );
P.setColor( color );
P.setColor( getDroppableHoveringColor() );
P.setLineWidth( PixelDensity::dpToPxI( 1 ) );
P.drawRectangle( getScreenBounds() );
}
@@ -1053,6 +1038,24 @@ void UINode::smartClipEnd( const ClipType& reqClipType ) {
smartClipEnd( reqClipType, isMeOrParentTreeScaledOrRotatedOrFrameBuffer() );
}
Color UINode::getDroppableHoveringColor() {
const PropertyDefinition* def =
StyleSheetSpecification::instance()->getProperty( "droppable-hovering-color" );
Color color = Color::fromString( def->getDefaultValue() );
if ( isWidget() ) {
UIWidget* widget = asType<UIWidget>();
std::string colorString = widget->getPropertyString( def );
if ( !colorString.empty() ) {
color = Color::fromString( colorString );
} else {
colorString = mUISceneNode->getRoot()->getPropertyString( def );
if ( !colorString.empty() )
color = Color::fromString( colorString );
}
}
return color;
}
void UINode::nodeDraw() {
if ( mVisible ) {
if ( mNodeFlags & NODE_FLAG_POSITION_DIRTY )

View File

@@ -76,7 +76,7 @@ Uint32 UITab::onDrag( const Vector2f& pos, const Uint32&, const Sizef& dragDiff
tabW->swapTabs( tab, this );
}
if ( tabW->getAllowDragAndDropTabs() && !( mFlags & UI_DRAG_VERTICAL ) ) {
if ( tabW->allowDragAndDropTabs() && !( mFlags & UI_DRAG_VERTICAL ) ) {
mDragTotalDiff += mDragPoint.y - pos.y;
if ( eeabs( mDragTotalDiff ) >= tabW->getTabVerticalDragResistance() ) {
setFlags( UI_DRAG_VERTICAL );
@@ -90,7 +90,7 @@ Uint32 UITab::onDrag( const Vector2f& pos, const Uint32&, const Sizef& dragDiff
}
}
if ( tabW->getAllowDragAndDropTabs() ) {
if ( tabW->allowDragAndDropTabs() ) {
setEnabled( false );
Node* overFind = getUISceneNode()->overFind( pos );
setEnabled( true );
@@ -435,7 +435,7 @@ void UITab::updateTab() {
} else {
UIPushButton::setText( mText );
}
setDragEnabled( tTabW->getAllowRearrangeTabs() || tTabW->getAllowDragAndDropTabs() );
setDragEnabled( tTabW->getAllowRearrangeTabs() || tTabW->allowDragAndDropTabs() );
onAutoSize();
}
}

View File

@@ -174,7 +174,7 @@ std::string UITabWidget::getPropertyString( const PropertyDefinition* propertyDe
case PropertyId::TabBarAllowRearrange:
return getAllowRearrangeTabs() ? "true" : "false";
case PropertyId::TabBarAllowDragAndDrop:
return getAllowDragAndDropTabs() ? "true" : "false";
return allowDragAndDropTabs() ? "true" : "false";
case PropertyId::TabAllowSwitchTabsInEmptySpaces:
return getAllowSwitchTabsInEmptySpaces() ? "true" : "false";
case PropertyId::TabCloseButtonVisible:
@@ -833,7 +833,7 @@ void UITabWidget::setAllowRearrangeTabs( bool allowRearrangeTabs ) {
}
}
bool UITabWidget::getAllowDragAndDropTabs() const {
bool UITabWidget::allowDragAndDropTabs() const {
return mAllowDragAndDropTabs;
}
@@ -859,7 +859,8 @@ void UITabWidget::setAllowSwitchTabsInEmptySpaces( bool allowSwitchTabsInEmptySp
bool UITabWidget::acceptsDropOfWidget( const UIWidget* widget ) {
return mAllowDragAndDropTabs && widget && UI_TYPE_TAB == widget->getType() &&
!isParentOf( widget ) && widget->asConstType<UITab>()->getTabWidget() != this;
!isParentOf( widget ) &&
( mSplitFn || widget->asConstType<UITab>()->getTabWidget() != this );
}
const Color& UITabWidget::getDroppableHoveringColor() const {
@@ -933,6 +934,11 @@ void UITabWidget::swapTabs( UITab* left, UITab* right ) {
}
}
void UITabWidget::setSplitFunction( SplitFunctionCb cb, Float splitEdgePercent ) {
mSplitFn = std::move( cb );
mSplitEdgePercent = splitEdgePercent;
}
void UITabWidget::selectPreviousTab() {
if ( eeINDEX_NOT_FOUND != mTabSelectedIndex && mTabSelectedIndex > 0 ) {
setTabSelected( getTab( mTabSelectedIndex - 1 ) );
@@ -1009,10 +1015,19 @@ Uint32 UITabWidget::onMessage( const NodeMessage* msg ) {
const NodeDropMessage* dropMsg = static_cast<const NodeDropMessage*>( msg );
if ( dropMsg->getDroppedNode()->isType( UI_TYPE_TAB ) ) {
UITab* tab = dropMsg->getDroppedNode()->asType<UITab>();
if ( tab->getTabWidget() != this && tab->getTabWidget()->getAllowDragAndDropTabs() ) {
auto dir = getDropDirection();
if ( !mSplitFn || !dir ) {
if ( tab->getTabWidget() != this && tab->getTabWidget()->allowDragAndDropTabs() ) {
tab->getTabWidget()->removeTab( tab, false, false, false );
add( tab );
setTabSelected( tab );
return 1;
}
} else if ( dir && tab->getTabWidget()->allowDragAndDropTabs() ) {
tab->getTabWidget()->removeTab( tab, false, false, false );
add( tab );
setTabSelected( tab );
auto tabWidget = mSplitFn( *dir, this );
tabWidget->add( tab );
tabWidget->setTabSelected( tab );
return 1;
}
}
@@ -1030,6 +1045,65 @@ Uint32 UITabWidget::onMessage( const NodeMessage* msg ) {
return 0;
}
std::optional<SplitDirection> UITabWidget::getDropDirection() const {
Vector2f mousePos( getEventDispatcher()->getMousePosf() );
mousePos = convertToNodeSpace( mousePos );
if ( mTabBar->isVisible() && mousePos.y <= mTabBar->getPixelsSize().y )
return {};
if ( mousePos.y <= mSize.y * mSplitEdgePercent ) {
return SplitDirection::Top;
} else if ( mousePos.y >= mSize.y - mSize.y * mSplitEdgePercent ) {
return SplitDirection::Bottom;
} else if ( mousePos.x <= mSize.x * mSplitEdgePercent ) {
return SplitDirection::Left;
} else if ( mousePos.x >= mSize.x - mSize.x * mSplitEdgePercent ) {
return SplitDirection::Right;
}
return {};
}
void UITabWidget::drawDroppableHovering() {
if ( mSplitFn ) {
auto dir = getDropDirection();
auto rect( getScreenBounds() );
if ( mTabBar->isVisible() )
rect.Top += mTabBar->getPixelsSize().getHeight();
if ( dir ) {
constexpr auto splitSize = 0.5f;
switch ( *dir ) {
case SplitDirection::Top:
rect.Bottom = rect.Top + mSize.y * splitSize;
break;
case SplitDirection::Bottom:
rect.Top = rect.Bottom - mSize.y * splitSize;
break;
case SplitDirection::Left:
rect.Right = rect.Left + mSize.x * splitSize;
break;
case SplitDirection::Right:
rect.Left = rect.Right - mSize.x * splitSize;
break;
}
} else if ( std::any_of( mTabs.begin(), mTabs.end(),
[]( const UITab* tab ) { return tab->isDragging(); } ) ) {
return;
}
Primitives P;
P.setFillMode( DRAW_FILL );
P.setBlendMode( getBlendMode() );
P.setColor( UINode::getDroppableHoveringColor() );
P.setLineWidth( PixelDensity::dpToPxI( 1 ) );
P.drawRectangle( rect );
} else {
UIWidget::drawDroppableHovering();
}
}
void UITabWidget::applyThemeToTabs() {
if ( mStyleConfig.TabsEdgesDiffSkins ) {
for ( Uint32 i = 0; i < mTabs.size(); i++ ) {

View File

@@ -651,8 +651,7 @@ void AppConfig::loadDocuments( UICodeEditorSplitter* editorSplitter, json j,
}
} else if ( j["type"] == "splitter" ) {
UISplitter* splitter = editorSplitter->split(
j["orientation"] == "horizontal" ? UICodeEditorSplitter::SplitDirection::Right
: UICodeEditorSplitter::SplitDirection::Bottom,
j["orientation"] == "horizontal" ? SplitDirection::Right : SplitDirection::Bottom,
curTabWidget->getTabSelected()->getOwnedWidget()->asType<UICodeEditor>(), false );
if ( nullptr == splitter )

View File

@@ -201,26 +201,22 @@ class App : public UICodeEditorSplitter::Client {
[this] { mTerminalManager->createTerminalInSplitter(); } );
t.setCommand( "terminal-split-right", [this] {
auto cwd = getCurrentWorkingDir();
mSplitter->split( UICodeEditorSplitter::SplitDirection::Right,
mSplitter->getCurWidget(), false );
mSplitter->split( SplitDirection::Right, mSplitter->getCurWidget(), false );
mTerminalManager->createNewTerminal( "", nullptr, cwd );
} );
t.setCommand( "terminal-split-bottom", [this] {
auto cwd = getCurrentWorkingDir();
mSplitter->split( UICodeEditorSplitter::SplitDirection::Bottom,
mSplitter->getCurWidget(), false );
mSplitter->split( SplitDirection::Bottom, mSplitter->getCurWidget(), false );
mTerminalManager->createNewTerminal( "", nullptr, cwd );
} );
t.setCommand( "terminal-split-left", [this] {
auto cwd = getCurrentWorkingDir();
mSplitter->split( UICodeEditorSplitter::SplitDirection::Left, mSplitter->getCurWidget(),
false );
mSplitter->split( SplitDirection::Left, mSplitter->getCurWidget(), false );
mTerminalManager->createNewTerminal( "", nullptr, cwd );
} );
t.setCommand( "terminal-split-top", [this] {
auto cwd = getCurrentWorkingDir();
mSplitter->split( UICodeEditorSplitter::SplitDirection::Top, mSplitter->getCurWidget(),
false );
mSplitter->split( SplitDirection::Top, mSplitter->getCurWidget(), false );
mTerminalManager->createNewTerminal( "", nullptr, cwd );
} );
t.setCommand( "reopen-closed-tab", [this] { reopenClosedTab(); } );

View File

@@ -42,15 +42,13 @@ UITerminal* TerminalManager::createTerminalInSplitter( const std::string& workin
switch ( config.term.newTerminalOrientation ) {
case NewTerminalOrientation::Vertical: {
auto cwd = workingDir.empty() ? mApp->getCurrentWorkingDir() : workingDir;
splitter->split( UICodeEditorSplitter::SplitDirection::Right,
splitter->getCurWidget(), false );
splitter->split( SplitDirection::Right, splitter->getCurWidget(), false );
term = createNewTerminal( "", nullptr, cwd, "", {}, fallback );
break;
}
case NewTerminalOrientation::Horizontal: {
auto cwd = workingDir.empty() ? mApp->getCurrentWorkingDir() : workingDir;
splitter->split( UICodeEditorSplitter::SplitDirection::Bottom,
splitter->getCurWidget(), false );
splitter->split( SplitDirection::Bottom, splitter->getCurWidget(), false );
term = createNewTerminal( "", nullptr, cwd, "", {}, fallback );
break;
}