mirror of
https://github.com/SpartanJ/eepp.git
synced 2026-10-01 10:40:20 +03:00
ecode: add customizable debugger panel layouts
- add a reusable right-side panel managed at the application level
- migrate debugger tabs to UITabWidgetSplitter instances
- support moving and splitting debugger tabs across bottom and right panels
- restrict debugger tab drops to debugger-owned panel targets
- serialize debugger tab layouts, panel state, and table column widths
- add pixel and percentage column-width modes to UIAbstractTableView
- support XML/CSS configuration and an optional width-mode context menu
- preserve percentage totals across resizing, fit-to-content, minimum widths,
scrollbar changes, and pixel-density rounding
- add splitter edge hiding while preserving splitter-always-show precedence
- use EventConnection ownership for safe widget and controller lifetimes
- validate serialized splitter layouts with structural limits
- display project-relative source paths in debugger stack frames
- add ecode localization strings, CSS documentation, tests, and agent rules
This commit is contained in:
@@ -121,6 +121,7 @@ void AppConfig::load( const std::string& confPath, std::string& keybindingsPath,
|
||||
windowState.winIcon = ini.getValue( "window", "winicon", resPath + "icon/ecode.png" );
|
||||
windowState.panelPartition = iniState.getValue( "window", "panel_partition", "15%" );
|
||||
windowState.statusBarPartition = iniState.getValue( "window", "status_bar_partition", "85%" );
|
||||
windowState.rightPanelPartition = iniState.getValue( "window", "right_panel_partition", "75%" );
|
||||
windowState.displayIndex = iniState.getValueI( "window", "display_index", 0 );
|
||||
windowState.position.x = iniState.getValueI( "window", "x", -1 );
|
||||
windowState.position.y = iniState.getValueI( "window", "y", -1 );
|
||||
@@ -331,6 +332,7 @@ void AppConfig::save( const std::vector<std::string>& recentFiles,
|
||||
iniState.setValueF( "window", "pixeldensity", windowState.pixelDensity );
|
||||
iniState.setValue( "window", "panel_partition", panelPartition );
|
||||
iniState.setValue( "window", "status_bar_partition", statusBarPartition );
|
||||
iniState.setValue( "window", "right_panel_partition", windowState.rightPanelPartition );
|
||||
iniState.setValueI( "window", "display_index", windowState.displayIndex );
|
||||
iniState.setValueI( "window", "x", windowState.position.x );
|
||||
iniState.setValueI( "window", "y", windowState.position.y );
|
||||
|
||||
@@ -90,6 +90,7 @@ struct WindowStateConfig {
|
||||
bool maximized{ false };
|
||||
std::string panelPartition;
|
||||
std::string statusBarPartition;
|
||||
std::string rightPanelPartition{ "75%" };
|
||||
int displayIndex{ 0 };
|
||||
Vector2i position{ -1, -1 };
|
||||
Uint32 lastRunVersion{ 0 };
|
||||
|
||||
@@ -615,6 +615,7 @@ TabWidget::container > ImageViewer > TextView {
|
||||
</RelativeLayout>
|
||||
<Tab id="treeview_tab" text='@string("project", "Project")' owns="project_view_cont" text-as-fallback="true" icon="icon(folder-open, 12dp)" />
|
||||
</TabWidget>
|
||||
<Splitter id="right_panel_splitter" splitter-partition="100%" splitter-always-show="false" splitter-hide-on-edge="true">
|
||||
<vbox>
|
||||
<Splitter id="main_splitter" lw="mp" lh="0" lw8="1" orientation="vertical">
|
||||
<RelativeLayout id="main_splitter_cont">
|
||||
@@ -706,6 +707,8 @@ TabWidget::container > ImageViewer > TextView {
|
||||
<Widget class="status_sep" lw="0" lw8="1" lh="1dp" />
|
||||
</statusbar>
|
||||
</vbox>
|
||||
<RelativeLayout id="right_panel_container"></RelativeLayout>
|
||||
</Splitter>
|
||||
</Splitter>
|
||||
<Image id="settings" lw="16dp" lh="16dp" lg="top|right" gravity="center" />
|
||||
<TextView id="menu_hint" class="app_hint" lw="wc" lh="wc" lg="bottom|right" visible="false"
|
||||
|
||||
@@ -6,10 +6,12 @@
|
||||
#include "fontpickercontroller.hpp"
|
||||
#include "keybindingshelper.hpp"
|
||||
#include "pathhelper.hpp"
|
||||
#include "plugins/debugger/statusdebuggercontroller.hpp"
|
||||
#include "settingsactions.hpp"
|
||||
#include "settingsmenu.hpp"
|
||||
#include "uibuildsettings.hpp"
|
||||
#include "uidownloadwindow.hpp"
|
||||
#include "uirightpanel.hpp"
|
||||
#include "uitreeviewfs.hpp"
|
||||
#include "uiwelcomescreen.hpp"
|
||||
#include "version.hpp"
|
||||
@@ -860,6 +862,8 @@ bool App::loadConfig( const LogLevel& logLevel, const Sizeu& displaySize, bool s
|
||||
void App::saveConfig() {
|
||||
if ( !mCurrentProject.empty() )
|
||||
saveSidePanelTabsOrder();
|
||||
if ( mRightPanel )
|
||||
mRightPanel->saveState();
|
||||
|
||||
mConfig.save(
|
||||
mRecentFiles, mRecentFolders,
|
||||
@@ -3810,6 +3814,17 @@ UISplitter* App::getMainSplitter() const {
|
||||
return mMainSplitter;
|
||||
}
|
||||
|
||||
UIRightPanel* App::getRightPanel() const {
|
||||
return mRightPanel.get();
|
||||
}
|
||||
|
||||
StatusDebuggerController* App::getStatusDebuggerController() const {
|
||||
if ( !mStatusBar )
|
||||
return nullptr;
|
||||
auto element = mStatusBar->getStatusBarElement( "status_app_debugger" );
|
||||
return static_cast<StatusDebuggerController*>( element.get() );
|
||||
}
|
||||
|
||||
StatusTerminalController* App::getStatusTerminalController() const {
|
||||
return mStatusTerminalController.get();
|
||||
}
|
||||
@@ -4979,6 +4994,8 @@ void App::init( InitParameters& params ) {
|
||||
mSplitter->setOpenDocumentsInMainSplit( mConfig.editor.openDocumentsInMainSplit );
|
||||
mSplitter->setRestoreEditorSelectionOnFocus( mConfig.editor.restoreEditorSelectionOnFocus );
|
||||
mSplitter->setOnTabWidgetCreateCb( [this]( UITabWidget* tabWidget ) {
|
||||
tabWidget->setAcceptsDropOfWidgetFn(
|
||||
[]( const UIWidget* widget ) { return !widget->hasClass( "debugger-tab" ); } );
|
||||
tabWidget->getTabBar()->onDoubleClick(
|
||||
[this]( const MouseEvent* ) { mSplitter->createEditorInNewTab(); } );
|
||||
} );
|
||||
@@ -5032,6 +5049,10 @@ void App::init( InitParameters& params ) {
|
||||
mMainSplitter = mUISceneNode->find<UISplitter>( "main_splitter" );
|
||||
mMainSplitter->setSplitPartition(
|
||||
StyleSheetLength( mConfig.windowState.statusBarPartition ) );
|
||||
auto rightPanelSplitter = mUISceneNode->find<UISplitter>( "right_panel_splitter" );
|
||||
auto rightPanelContainer = mUISceneNode->find<UILayout>( "right_panel_container" );
|
||||
mRightPanel =
|
||||
std::make_unique<UIRightPanel>( rightPanelSplitter, rightPanelContainer, &mConfig );
|
||||
mStatusBar = mUISceneNode->find<UIStatusBar>( "status_bar" );
|
||||
mPluginManager->setMainSplitter( mMainSplitter );
|
||||
|
||||
|
||||
@@ -35,6 +35,8 @@ class DateTimeController;
|
||||
class FontPickerController;
|
||||
class SettingsMenu;
|
||||
class UITreeViewFS;
|
||||
class StatusDebuggerController;
|
||||
class UIRightPanel;
|
||||
|
||||
class App : public UICodeEditorSplitter::Client, public PluginContextProvider {
|
||||
public:
|
||||
@@ -599,6 +601,10 @@ class App : public UICodeEditorSplitter::Client, public PluginContextProvider {
|
||||
|
||||
UISplitter* getMainSplitter() const;
|
||||
|
||||
UIRightPanel* getRightPanel() const;
|
||||
|
||||
StatusDebuggerController* getStatusDebuggerController() const;
|
||||
|
||||
StatusTerminalController* getStatusTerminalController() const;
|
||||
|
||||
void hideStatusTerminal();
|
||||
@@ -777,6 +783,7 @@ class App : public UICodeEditorSplitter::Client, public PluginContextProvider {
|
||||
UIThemePtr mTheme;
|
||||
UIStatusBar* mStatusBar{ nullptr };
|
||||
UISplitter* mMainSplitter{ nullptr };
|
||||
std::unique_ptr<UIRightPanel> mRightPanel;
|
||||
UIMessageBox* mCloseMsgBox{ nullptr };
|
||||
UIMenuBar* mMenuBar{ nullptr };
|
||||
std::unique_ptr<SettingsActions> mSettingsActions;
|
||||
|
||||
@@ -140,8 +140,10 @@ void DebuggerClientListener::initUI() {
|
||||
}
|
||||
|
||||
if ( !mStackModel ) {
|
||||
mStackModel = std::make_shared<StackModel>( StackTraceInfo{}, sceneNode );
|
||||
}
|
||||
mStackModel =
|
||||
std::make_shared<StackModel>( StackTraceInfo{}, sceneNode, mPlugin->mProjectPath );
|
||||
} else
|
||||
mStackModel->setProjectPath( mPlugin->mProjectPath );
|
||||
|
||||
UITableView* uiThreads = sdc->getUIThreads();
|
||||
uiThreads->setModel( mThreadsModel );
|
||||
|
||||
@@ -183,6 +183,11 @@ DebuggerPlugin::~DebuggerPlugin() {
|
||||
}
|
||||
}
|
||||
|
||||
void DebuggerPlugin::onSaveState( IniFile* state ) {
|
||||
if ( auto controller = getStatusDebuggerController() )
|
||||
state->setValue( "debugger", "panel_layout", controller->saveLayout() );
|
||||
}
|
||||
|
||||
void DebuggerPlugin::onSaveProject( const std::string& /*projectFolder*/,
|
||||
const std::string& projectStatePath,
|
||||
bool rewriteStateOnlyIfNeeded ) {
|
||||
@@ -579,11 +584,11 @@ void DebuggerPlugin::loadDAPConfig( const std::string& path, bool updateConfigFi
|
||||
mKeyBindings["debugger-step-over"] = "f10";
|
||||
mKeyBindings["debugger-step-into"] = "f11";
|
||||
mKeyBindings["debugger-step-out"] = "shift+f11";
|
||||
#if EE_PLATFORM == EE_PLATFORM_MACOS
|
||||
#if EE_PLATFORM == EE_PLATFORM_MACOS
|
||||
mKeyBindings["toggle-status-app-debugger"] = "mod+6";
|
||||
#else
|
||||
#else
|
||||
mKeyBindings["toggle-status-app-debugger"] = "alt+6";
|
||||
#endif
|
||||
#endif
|
||||
}
|
||||
|
||||
if ( j.contains( "keybindings" ) ) {
|
||||
@@ -768,8 +773,9 @@ void DebuggerPlugin::buildSidePanelTab() {
|
||||
if ( mProjectPath.empty() )
|
||||
return;
|
||||
UIIcon* icon = findIcon( "debug" );
|
||||
mTab = mSidePanel->add( i18n( "debugger", "Debugger" ), mTabContents,
|
||||
icon ? icon->createDrawable( PixelDensity::dpToPx( 12 ) ) : nullptr );
|
||||
mTab =
|
||||
mSidePanel->add( i18n( "debugger", "Debugger" ), mTabContents,
|
||||
icon ? icon->createDrawable( PixelDensity::dpToPx( 12 ) ) : nullptr );
|
||||
mTab->setId( "debugger_tab" );
|
||||
mTab->setTextAsFallback( true );
|
||||
|
||||
@@ -1217,8 +1223,7 @@ bool DebuggerPlugin::replaceInVal( std::string& val,
|
||||
String::replaceAll( val, KEY_PATH_SEPARATOR, FileSystem::getOSSlash() );
|
||||
String::replaceAll( val, KEY_PATH_SEPARATOR_ABBR, FileSystem::getOSSlash() );
|
||||
String::replaceAll( val, KEY_UUID, UUID().toString() );
|
||||
String::replaceAll( val, KEY_TIMESTAMP,
|
||||
std::to_string( Sys::getUnixTimestamp() ) );
|
||||
String::replaceAll( val, KEY_TIMESTAMP, std::to_string( Sys::getUnixTimestamp() ) );
|
||||
|
||||
auto* editor = getPluginContext()->getSplitter()->getCurEditor();
|
||||
if ( getPluginContext()->getSplitter()->getCurEditor() ) {
|
||||
|
||||
@@ -67,6 +67,8 @@ class DebuggerPlugin : public PluginBase {
|
||||
|
||||
std::string getDescription() override { return Definition().description; }
|
||||
|
||||
void onSaveState( IniFile* state ) override;
|
||||
|
||||
void onSaveProject( const std::string& projectFolder, const std::string& projectStatePath,
|
||||
bool rewriteStateOnlyIfNeeded ) override;
|
||||
|
||||
|
||||
@@ -1,10 +1,15 @@
|
||||
#include "stackmodel.hpp"
|
||||
#include <eepp/system/filesystem.hpp>
|
||||
#include <eepp/ui/uiscenenode.hpp>
|
||||
|
||||
namespace ecode {
|
||||
|
||||
StackModel::StackModel( StackTraceInfo&& stack, UISceneNode* sceneNode ) :
|
||||
mStack( std::move( stack ) ), mSceneNode( sceneNode ) {}
|
||||
StackModel::StackModel( StackTraceInfo&& stack, UISceneNode* sceneNode, std::string projectPath ) :
|
||||
mStack( std::move( stack ) ),
|
||||
mProjectPath( std::move( projectPath ) ),
|
||||
mSceneNode( sceneNode ) {
|
||||
FileSystem::dirAddSlashAtEnd( mProjectPath );
|
||||
}
|
||||
|
||||
size_t StackModel::rowCount( const ModelIndex& ) const {
|
||||
Lock l( mResourceLock );
|
||||
@@ -45,7 +50,8 @@ Variant StackModel::data( const ModelIndex& modelIndex, ModelRole role ) const {
|
||||
: Variant();
|
||||
case Columns::SourcePath:
|
||||
return mStack.stackFrames[modelIndex.row()].source
|
||||
? Variant( mStack.stackFrames[modelIndex.row()].source->path )
|
||||
? Variant( displaySourcePath(
|
||||
mStack.stackFrames[modelIndex.row()].source->path ) )
|
||||
: Variant();
|
||||
case Columns::Line:
|
||||
return Variant( String::toString( mStack.stackFrames[modelIndex.row()].line ) );
|
||||
@@ -90,4 +96,23 @@ void StackModel::setCurrentScopeId( int scope ) {
|
||||
}
|
||||
}
|
||||
|
||||
void StackModel::setProjectPath( const std::string& projectPath ) {
|
||||
std::string normalizedProjectPath( projectPath );
|
||||
FileSystem::dirAddSlashAtEnd( normalizedProjectPath );
|
||||
{
|
||||
Lock l( mResourceLock );
|
||||
if ( mProjectPath == normalizedProjectPath )
|
||||
return;
|
||||
mProjectPath = std::move( normalizedProjectPath );
|
||||
}
|
||||
invalidate();
|
||||
}
|
||||
|
||||
const char* StackModel::displaySourcePath( const std::string& sourcePath ) const {
|
||||
if ( mProjectPath.empty() || sourcePath.size() < mProjectPath.size() ||
|
||||
0 != sourcePath.compare( 0, mProjectPath.size(), mProjectPath ) )
|
||||
return sourcePath.c_str();
|
||||
return sourcePath.c_str() + mProjectPath.size();
|
||||
}
|
||||
|
||||
} // namespace ecode
|
||||
|
||||
@@ -19,7 +19,7 @@ class StackModel : public Model {
|
||||
public:
|
||||
enum Columns { ID, Name, SourceName, SourcePath, Line, Column };
|
||||
|
||||
StackModel( StackTraceInfo&& stack, UISceneNode* sceneNode );
|
||||
StackModel( StackTraceInfo&& stack, UISceneNode* sceneNode, std::string projectPath = {} );
|
||||
|
||||
virtual size_t rowCount( const ModelIndex& ) const;
|
||||
|
||||
@@ -37,10 +37,15 @@ class StackModel : public Model {
|
||||
|
||||
void setCurrentScopeId( int scope );
|
||||
|
||||
void setProjectPath( const std::string& projectPath );
|
||||
|
||||
protected:
|
||||
StackTraceInfo mStack;
|
||||
std::string mProjectPath;
|
||||
UISceneNode* mSceneNode{ nullptr };
|
||||
int mCurrentScopeId{ 0 };
|
||||
|
||||
const char* displaySourcePath( const std::string& sourcePath ) const;
|
||||
};
|
||||
|
||||
} // namespace ecode
|
||||
|
||||
@@ -1,11 +1,45 @@
|
||||
#include "statusdebuggercontroller.hpp"
|
||||
#include "../../appconfig.hpp"
|
||||
#include "../../uirightpanel.hpp"
|
||||
#include "../../widgetcommandexecuter.hpp"
|
||||
#include "../plugincontextprovider.hpp"
|
||||
#include "eepp/ui/uiwidgetcreator.hpp"
|
||||
#include <eepp/ui/uicheckbox.hpp>
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
namespace ecode {
|
||||
|
||||
static constexpr size_t DEBUGGER_LAYOUT_MAX_DEPTH = 16;
|
||||
static constexpr size_t DEBUGGER_LAYOUT_MAX_NODES = 64;
|
||||
static constexpr size_t DEBUGGER_WIDGET_ID_PREFIX_LENGTH = 9;
|
||||
|
||||
static bool isValidDebuggerLayoutNode( const nlohmann::json& node,
|
||||
UnorderedSet<std::string>& widgetTypes, size_t depth,
|
||||
size_t& nodeCount ) {
|
||||
if ( depth > DEBUGGER_LAYOUT_MAX_DEPTH || ++nodeCount > DEBUGGER_LAYOUT_MAX_NODES )
|
||||
return false;
|
||||
if ( !node.is_object() || !node.contains( "type" ) || !node["type"].is_string() )
|
||||
return false;
|
||||
if ( node["type"] == "splitter" )
|
||||
return node.contains( "first" ) && node.contains( "last" ) &&
|
||||
isValidDebuggerLayoutNode( node["first"], widgetTypes, depth + 1, nodeCount ) &&
|
||||
isValidDebuggerLayoutNode( node["last"], widgetTypes, depth + 1, nodeCount );
|
||||
if ( node["type"] != "tabwidget" || !node.contains( "files" ) || !node["files"].is_array() )
|
||||
return false;
|
||||
for ( const auto& file : node["files"] ) {
|
||||
if ( !file.is_object() || !file.contains( "type" ) || !file["type"].is_string() ||
|
||||
!widgetTypes.emplace( file["type"].get<std::string>() ).second )
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static void restoreColumnWidths( UIAbstractTableView* view, const nlohmann::json& columns,
|
||||
const char* key ) {
|
||||
if ( view && columns.contains( key ) )
|
||||
view->unserializeColumnWidths( columns[key] );
|
||||
}
|
||||
|
||||
class UIBreakpointsTableCell : public UITableCell {
|
||||
public:
|
||||
static UIBreakpointsTableCell* New( const std::string& tag, const BreakpointsModel* model,
|
||||
@@ -109,6 +143,37 @@ StatusDebuggerController::StatusDebuggerController( UISplitter* mainSplitter,
|
||||
PluginContextProvider* pluginContext ) :
|
||||
StatusBarElement( mainSplitter, uiSceneNode, pluginContext ) {}
|
||||
|
||||
StatusDebuggerController::~StatusDebuggerController() {
|
||||
mEventConnections.clear();
|
||||
mTabWidgetEventConnections.clear();
|
||||
if ( mContainer )
|
||||
mContainer->removeActionsByTag( reinterpret_cast<UintPtr>( this ) );
|
||||
if ( mTabWidgetSplitter ) {
|
||||
mTabWidgetSplitter->setOnTabWidgetCreateCb( nullptr );
|
||||
mTabWidgetSplitter->setOnTabWidgetCloseCb( nullptr );
|
||||
mTabWidgetSplitter->forEachTabWidget( []( UITabWidget* tabWidget ) {
|
||||
tabWidget->setSplitFunction( nullptr );
|
||||
tabWidget->setTabTryCloseCallback( nullptr );
|
||||
tabWidget->setAcceptsDropOfWidgetFn( nullptr );
|
||||
} );
|
||||
eeDelete( mTabWidgetSplitter );
|
||||
}
|
||||
if ( mContext && mContext->getRightPanel() )
|
||||
mContext->getRightPanel()->unregisterPanel( "debugger" );
|
||||
if ( mContainer )
|
||||
mContainer->close();
|
||||
}
|
||||
|
||||
void StatusDebuggerController::show() {
|
||||
StatusBarElement::show();
|
||||
updateRightPanel();
|
||||
}
|
||||
|
||||
void StatusDebuggerController::hide() {
|
||||
StatusBarElement::hide();
|
||||
updateRightPanel();
|
||||
}
|
||||
|
||||
UIWidget* StatusDebuggerController::getWidget() {
|
||||
return mContainer;
|
||||
}
|
||||
@@ -193,6 +258,207 @@ void StatusDebuggerController::clearConsoleBuffer() {
|
||||
} );
|
||||
}
|
||||
|
||||
const std::string& StatusDebuggerController::saveLayout() {
|
||||
saveTabLayout();
|
||||
return mSerializedLayout;
|
||||
}
|
||||
|
||||
void StatusDebuggerController::onTabCreated( UITab* tab, UIWidget* widget ) {
|
||||
tab->setId( "debugger_tab_" + widget->getId().substr( DEBUGGER_WIDGET_ID_PREFIX_LENGTH ) );
|
||||
tab->addClass( "debugger-tab" );
|
||||
tab->setTextAsFallback( true );
|
||||
mEventConnections += tab->connect( Event::OnDragStart,
|
||||
[this]( const Event* ) { beginRightPanelDropPreview(); } );
|
||||
mEventConnections +=
|
||||
tab->connect( Event::OnDragStop, [this]( const Event* ) { endRightPanelDropPreview(); } );
|
||||
}
|
||||
|
||||
void StatusDebuggerController::onWidgetFocusChange( UIWidget* ) {}
|
||||
|
||||
void StatusDebuggerController::createTabWidgets() {
|
||||
mRightPanelContainer = mContext->getRightPanel()
|
||||
? mContext->getRightPanel()->registerPanel( "debugger" )
|
||||
: nullptr;
|
||||
if ( !mDebuggerTabsContainer || !mRightPanelContainer )
|
||||
return;
|
||||
|
||||
mTabWidgetSplitter = UITabWidgetSplitter::New( this, mUISceneNode );
|
||||
mTabWidgetSplitter->setHideTabBarOnSingleTab( false );
|
||||
mTabWidgetSplitter->setOnTabWidgetCreateCb( [this]( UITabWidget* tabWidget ) {
|
||||
tabWidget->setTabsClosable( false );
|
||||
tabWidget->setAcceptsDropOfWidgetFn(
|
||||
[]( const UIWidget* widget ) { return widget->hasClass( "debugger-tab" ); } );
|
||||
tabWidget->setSplitFunction(
|
||||
[this]( SplitDirection direction, UITabWidget* target ) -> UITabWidget* {
|
||||
Node* dragging = mUISceneNode->getEventDispatcher()->getNodeDragging();
|
||||
if ( dragging && dragging->isType( UI_TYPE_TAB ) ) {
|
||||
auto source = dragging->asType<UITab>()->getTabWidget();
|
||||
bool sourceInRight = source && mRightPanelContainer->isParentOf( source );
|
||||
bool targetInRight = mRightPanelContainer->isParentOf( target );
|
||||
if ( source && sourceInRight != targetInRight )
|
||||
return target;
|
||||
}
|
||||
return mTabWidgetSplitter->splitTabWidget( direction, target );
|
||||
},
|
||||
mTabWidgetSplitter->getVisualSplitEdgePercent() );
|
||||
auto& connections = mTabWidgetEventConnections[tabWidget];
|
||||
connections += tabWidget->connect( Event::OnTabAdded, [this]( const Event* ) {
|
||||
if ( !mRestoringLayout ) {
|
||||
saveTabLayout();
|
||||
updateRightPanel();
|
||||
}
|
||||
} );
|
||||
} );
|
||||
mTabWidgetSplitter->setOnTabWidgetCloseCb(
|
||||
[this]( UITabWidget* tabWidget ) { mTabWidgetEventConnections.erase( tabWidget ); } );
|
||||
|
||||
mUITabWidget = mTabWidgetSplitter->createTabWidget( mDebuggerTabsContainer );
|
||||
mUITabWidget->setId( "app_debugger_tab_widget" );
|
||||
mUIRightTabWidget = mTabWidgetSplitter->createTabWidget( mRightPanelContainer );
|
||||
mUIRightTabWidget->setId( "app_debugger_right_tab_widget" );
|
||||
|
||||
const auto registerWidget = [this]( const std::string& type, UIWidget* widget,
|
||||
const std::string& title ) {
|
||||
widget->addClass( type );
|
||||
mTabWidgetSplitter->registerWidgetType(
|
||||
type, { []( UIWidget* ) { return nlohmann::json::object(); },
|
||||
[widget, title]( const nlohmann::json& ) -> WidgetLoadResult {
|
||||
return { widget, nullptr, title };
|
||||
} } );
|
||||
};
|
||||
registerWidget( "debugger-threads-and-stack", mUIThreadsSplitter,
|
||||
mContext->i18n( "threads_and_stack", "Threads & Stack" ).toUtf8() );
|
||||
registerWidget( "debugger-variables", mUIVariables,
|
||||
mContext->i18n( "variables", "Variables" ).toUtf8() );
|
||||
registerWidget( "debugger-expressions", mUIExpressions,
|
||||
mContext->i18n( "expressions", "Expressions" ).toUtf8() );
|
||||
registerWidget( "debugger-breakpoints", mUIBreakpoints,
|
||||
mContext->i18n( "breakpoints", "Breakpoints" ).toUtf8() );
|
||||
registerWidget( "debugger-console", mUIConsole,
|
||||
mContext->i18n( "console_output", "Console Output" ).toUtf8() );
|
||||
|
||||
restoreTabLayout();
|
||||
}
|
||||
|
||||
void StatusDebuggerController::restoreTabLayout() {
|
||||
static const UnorderedSet<std::string> expectedWidgetTypes{
|
||||
"debugger-threads-and-stack", "debugger-variables", "debugger-expressions",
|
||||
"debugger-breakpoints", "debugger-console" };
|
||||
mRestoringLayout = true;
|
||||
bool restored = false;
|
||||
mSerializedLayout = mContext->getConfig().iniState.getValue(
|
||||
"debugger", "panel_layout",
|
||||
mContext->getConfig().iniState.getValue( "ui", "debugger_panel_layout", "" ) );
|
||||
const std::string& saved = mSerializedLayout;
|
||||
if ( !saved.empty() ) {
|
||||
auto layout = nlohmann::json::parse( saved, nullptr, false, true );
|
||||
UnorderedSet<std::string> widgetTypes;
|
||||
size_t nodeCount = 0;
|
||||
const int version = layout.is_discarded() ? 0 : layout.value( "version", 0 );
|
||||
if ( !layout.is_discarded() && ( version == 2 || version == 3 ) &&
|
||||
layout.contains( "bottom" ) && layout.contains( "right" ) &&
|
||||
isValidDebuggerLayoutNode( layout["bottom"], widgetTypes, 0, nodeCount ) &&
|
||||
isValidDebuggerLayoutNode( layout["right"], widgetTypes, 0, nodeCount ) &&
|
||||
widgetTypes == expectedWidgetTypes ) {
|
||||
mTabWidgetSplitter->unserializeNode( layout["bottom"], mUITabWidget );
|
||||
mTabWidgetSplitter->unserializeNode( layout["right"], mUIRightTabWidget );
|
||||
if ( layout.contains( "columns" ) && layout["columns"].is_object() ) {
|
||||
restoreColumnWidths( mUIThreads, layout["columns"], "threads" );
|
||||
restoreColumnWidths( mUIStack, layout["columns"], "stack" );
|
||||
restoreColumnWidths( mUIVariables, layout["columns"], "variables" );
|
||||
restoreColumnWidths( mUIExpressions, layout["columns"], "expressions" );
|
||||
restoreColumnWidths( mUIBreakpoints, layout["columns"], "breakpoints" );
|
||||
}
|
||||
restored = true;
|
||||
}
|
||||
}
|
||||
|
||||
if ( !restored ) {
|
||||
mTabWidgetSplitter->createWidgetInTabWidget(
|
||||
mUITabWidget, mUIThreadsSplitter,
|
||||
mContext->i18n( "threads_and_stack", "Threads & Stack" ).toUtf8(), false );
|
||||
mTabWidgetSplitter->createWidgetInTabWidget(
|
||||
mUITabWidget, mUIBreakpoints, mContext->i18n( "breakpoints", "Breakpoints" ).toUtf8(),
|
||||
false );
|
||||
mTabWidgetSplitter->createWidgetInTabWidget(
|
||||
mUITabWidget, mUIConsole, mContext->i18n( "console_output", "Console Output" ).toUtf8(),
|
||||
false );
|
||||
mTabWidgetSplitter->createWidgetInTabWidget(
|
||||
mUIRightTabWidget, mUIVariables, mContext->i18n( "variables", "Variables" ).toUtf8(),
|
||||
false );
|
||||
mTabWidgetSplitter->createWidgetInTabWidget(
|
||||
mUIRightTabWidget, mUIExpressions,
|
||||
mContext->i18n( "expressions", "Expressions" ).toUtf8(), false );
|
||||
mUITabWidget->setTabSelected( Uint32{ 0 } );
|
||||
mUIRightTabWidget->setTabSelected( Uint32{ 0 } );
|
||||
}
|
||||
if ( mUITabWidget->getTabCount() )
|
||||
mTabWidgetSplitter->setCurrentWidget(
|
||||
mUITabWidget->getTabSelected()->getOwnedWidget()->asType<UIWidget>() );
|
||||
mRestoringLayout = false;
|
||||
saveTabLayout();
|
||||
updateRightPanel();
|
||||
}
|
||||
|
||||
void StatusDebuggerController::saveTabLayout() {
|
||||
if ( mRestoringLayout || !mTabWidgetSplitter || !mDebuggerTabsContainer ||
|
||||
!mRightPanelContainer || !mDebuggerTabsContainer->getFirstChild() ||
|
||||
!mRightPanelContainer->getFirstChild() )
|
||||
return;
|
||||
nlohmann::json layout;
|
||||
layout["version"] = 3;
|
||||
layout["bottom"] = mTabWidgetSplitter->serializeNode( mDebuggerTabsContainer->getFirstChild() );
|
||||
layout["right"] = mTabWidgetSplitter->serializeNode( mRightPanelContainer->getFirstChild() );
|
||||
layout["columns"]["threads"] = mUIThreads->serializeColumnWidths();
|
||||
layout["columns"]["stack"] = mUIStack->serializeColumnWidths();
|
||||
layout["columns"]["variables"] = mUIVariables->serializeColumnWidths();
|
||||
layout["columns"]["expressions"] = mUIExpressions->serializeColumnWidths();
|
||||
layout["columns"]["breakpoints"] = mUIBreakpoints->serializeColumnWidths();
|
||||
mSerializedLayout = layout.dump();
|
||||
}
|
||||
|
||||
bool StatusDebuggerController::rightPanelHasTabs() const {
|
||||
bool hasTabs = false;
|
||||
if ( !mTabWidgetSplitter || !mRightPanelContainer )
|
||||
return false;
|
||||
mTabWidgetSplitter->forEachTabWidgetStoppable( [this, &hasTabs]( UITabWidget* tabWidget ) {
|
||||
hasTabs = mRightPanelContainer->isParentOf( tabWidget ) && tabWidget->getTabCount() > 0;
|
||||
return hasTabs;
|
||||
} );
|
||||
return hasTabs;
|
||||
}
|
||||
|
||||
void StatusDebuggerController::beginRightPanelDropPreview() {
|
||||
if ( !mRightPanelDropPreview && mContainer && mContainer->isVisible() &&
|
||||
!rightPanelHasTabs() ) {
|
||||
mRightPanelDropPreview = true;
|
||||
updateRightPanel();
|
||||
}
|
||||
}
|
||||
|
||||
void StatusDebuggerController::endRightPanelDropPreview() {
|
||||
if ( !mRightPanelDropPreview || !mContainer )
|
||||
return;
|
||||
// The drop target is resolved after OnDragStop. Keep the empty panel alive until the next
|
||||
// update so it can receive the drop, then collapse it if the drag was canceled or rejected.
|
||||
mContainer->removeActionsByTag( reinterpret_cast<UintPtr>( this ) );
|
||||
mContainer->runOnMainThread(
|
||||
[this] {
|
||||
mRightPanelDropPreview = false;
|
||||
updateRightPanel();
|
||||
},
|
||||
Time::Zero, reinterpret_cast<UintPtr>( this ) );
|
||||
}
|
||||
|
||||
void StatusDebuggerController::updateRightPanel() {
|
||||
auto rightPanel = mContext->getRightPanel();
|
||||
if ( !rightPanel || !mRightPanelContainer || !mTabWidgetSplitter )
|
||||
return;
|
||||
rightPanel->setPanelVisible( "debugger",
|
||||
mContainer && mContainer->isVisible() &&
|
||||
( mRightPanelDropPreview || rightPanelHasTabs() ) );
|
||||
}
|
||||
|
||||
void StatusDebuggerController::createContainer() {
|
||||
if ( mContainer )
|
||||
return;
|
||||
@@ -206,21 +472,16 @@ void StatusDebuggerController::createContainer() {
|
||||
}
|
||||
</style>
|
||||
<hboxce id="app_debugger" lw="mp" lh="mp" visible="false">
|
||||
<TabWidget id="app_debugger_tab_widget" lw="0" lw8="1" lh="mp">
|
||||
<vbox id="app_debugger_tabs" lw="0" lw8="1" lh="mp">
|
||||
<Splitter id="debugger_threads_and_stack" layout_width="mp" lh="mp" splitter-partition="15%">
|
||||
<TableView id="debugger_threads" layout_width="mp" layout_height="mp" />
|
||||
<TableView id="debugger_stack" layout_width="mp" layout_height="mp" />
|
||||
</Splitter>
|
||||
<TreeView id="debugger_variables" layout_width="mp" layout_height="mp" />
|
||||
<TreeView id="debugger_expressions" layout_width="mp" layout_height="mp" />
|
||||
<BreakpointsTableView id="debugger_breakpoints" layout_width="mp" layout_height="mp" />
|
||||
<TableView id="debugger_threads" layout_width="mp" layout_height="mp" column-width-mode="percentage" column-width-mode-menu="true" />
|
||||
<TableView id="debugger_stack" layout_width="mp" layout_height="mp" column-width-mode="percentage" column-width-mode-menu="true" />
|
||||
</Splitter>
|
||||
<TreeView id="debugger_variables" layout_width="mp" layout_height="mp" column-width-mode="percentage" column-width-mode-menu="true" />
|
||||
<TreeView id="debugger_expressions" layout_width="mp" layout_height="mp" column-width-mode="percentage" column-width-mode-menu="true" />
|
||||
<BreakpointsTableView id="debugger_breakpoints" layout_width="mp" layout_height="mp" column-width-mode="percentage" column-width-mode-menu="true" />
|
||||
<CodeEditor id="debugger_console" layout_width="mp" layout_height="mp" />
|
||||
<Tab id="debugger_tab_threads_and_stack" text="@string(threads_and_stack, Threads & Stack)" owns="debugger_threads_and_stack" />
|
||||
<Tab id="debugger_tab_variables" text="@string(variables, Variables)" owns="debugger_variables" />
|
||||
<Tab id="debugger_tab_expressions" text="@string(expressions, Expressions)" owns="debugger_expressions" />
|
||||
<Tab id="debugger_tab_breakpoints" text="@string(breakpoints, Breakpoints)" owns="debugger_breakpoints" />
|
||||
<Tab id="debugger_tab_console" text="@string(console_output, Console Output)" owns="debugger_console" />
|
||||
</TabWidget>
|
||||
</vbox>
|
||||
<vbox id="app_debugger_buttons" class="vertical_bar" lw="16dp" lh="mp">
|
||||
<PushButton class="expand_status_bar_panel" lw="mp" tooltip="@string(expand_panel, Expand Panel)" />
|
||||
<PushButton id="app_debugger_start" class="debugger_start" lw="mp" icon="icon(debug-start, 12dp)" tooltip="@string(start, Start)" />
|
||||
@@ -248,7 +509,7 @@ void StatusDebuggerController::createContainer() {
|
||||
|
||||
mContext->getStatusBar()->registerStatusBarPanel( mContainer, mContainer );
|
||||
|
||||
mContainer->bind( "app_debugger_tab_widget", mUITabWidget );
|
||||
mContainer->bind( "app_debugger_tabs", mDebuggerTabsContainer );
|
||||
mContainer->bind( "debugger_threads_and_stack", mUIThreadsSplitter );
|
||||
mContainer->bind( "debugger_threads", mUIThreads );
|
||||
mContainer->bind( "debugger_stack", mUIStack );
|
||||
@@ -256,6 +517,8 @@ void StatusDebuggerController::createContainer() {
|
||||
mContainer->bind( "debugger_variables", mUIVariables );
|
||||
mContainer->bind( "debugger_expressions", mUIExpressions );
|
||||
mContainer->bind( "debugger_console", mUIConsole );
|
||||
mEventConnections += mContainer->connect( Event::OnVisibleChange,
|
||||
[this]( const Event* ) { updateRightPanel(); } );
|
||||
mContainer->bind( "app_debugger_start", mUIButStart );
|
||||
mContainer->bind( "app_debugger_stop", mUIButStop );
|
||||
mContainer->bind( "app_debugger_pause", mUIButPause );
|
||||
@@ -264,25 +527,34 @@ void StatusDebuggerController::createContainer() {
|
||||
mContainer->bind( "app_debugger_step_into", mUIButStepInto );
|
||||
mContainer->bind( "app_debugger_step_out", mUIButStepOut );
|
||||
|
||||
createTabWidgets();
|
||||
|
||||
mContainer->setCommand( "next-tab", [this] {
|
||||
if ( mUITabWidget )
|
||||
mUITabWidget->focusNextTab();
|
||||
if ( mTabWidgetSplitter && mTabWidgetSplitter->getCurWidget() ) {
|
||||
auto tabWidget =
|
||||
mTabWidgetSplitter->tabWidgetFromWidget( mTabWidgetSplitter->getCurWidget() );
|
||||
if ( tabWidget )
|
||||
tabWidget->focusNextTab();
|
||||
}
|
||||
} );
|
||||
|
||||
mContainer->setCommand( "previous-tab", [this] {
|
||||
if ( mUITabWidget )
|
||||
mUITabWidget->focusPreviousTab();
|
||||
if ( mTabWidgetSplitter && mTabWidgetSplitter->getCurWidget() ) {
|
||||
auto tabWidget =
|
||||
mTabWidgetSplitter->tabWidgetFromWidget( mTabWidgetSplitter->getCurWidget() );
|
||||
if ( tabWidget )
|
||||
tabWidget->focusPreviousTab();
|
||||
}
|
||||
} );
|
||||
|
||||
for ( int i = 1; i <= 5; i++ ) {
|
||||
mContainer->setCommand( String::format( "switch-to-tab-%d", i ), [this, i] {
|
||||
if ( mUITabWidget )
|
||||
mUITabWidget->setTabSelected(
|
||||
eeclamp<Int32>( i - 1, 0, mUITabWidget->getTabCount() - 1 ) );
|
||||
if ( mTabWidgetSplitter )
|
||||
mTabWidgetSplitter->switchToTab( i - 1 );
|
||||
} );
|
||||
}
|
||||
|
||||
mContainer->on( Event::KeyDown, [this]( const Event* event ) {
|
||||
mEventConnections += mContainer->connect( Event::KeyDown, [this]( const Event* event ) {
|
||||
auto ke = event->asKeyEvent();
|
||||
if ( ke->getSanitizedMod() == 0 && ke->getKeyCode() == EE::Window::KEY_ESCAPE &&
|
||||
mSplitter->getCurEditor() ) {
|
||||
@@ -313,19 +585,12 @@ void StatusDebuggerController::createContainer() {
|
||||
|
||||
mUIThreads->setAutoExpandOnSingleColumn( true );
|
||||
|
||||
mUIStack->setAutoColumnsWidth( true );
|
||||
mUIStack->setFitAllColumnsToWidget( true );
|
||||
mUIStack->setMainColumn( 1 );
|
||||
|
||||
mUIVariables->setAutoColumnsWidth( true );
|
||||
mUIVariables->setFitAllColumnsToWidget( true );
|
||||
mUIVariables->setMainColumn( 1 );
|
||||
|
||||
mUIBreakpoints->setAutoColumnsWidth( true );
|
||||
mUIBreakpoints->setFitAllColumnsToWidget( true );
|
||||
mUIBreakpoints->setMainColumn( 1 );
|
||||
|
||||
mUIExpressions->setAutoColumnsWidth( true );
|
||||
mUIExpressions->setFitAllColumnsToWidget( true );
|
||||
mUIExpressions->setMainColumn( 1 );
|
||||
|
||||
mUIConsole->setLocked( true );
|
||||
@@ -333,7 +598,7 @@ void StatusDebuggerController::createContainer() {
|
||||
mUIConsole->setShowLineNumber( false );
|
||||
mUIConsole->getDocument().reset();
|
||||
mUIConsole->setScrollY( mUIConsole->getMaxScroll().y );
|
||||
mUIConsole->on( Event::OnScrollChange, [this]( auto ) {
|
||||
mEventConnections += mUIConsole->connect( Event::OnScrollChange, [this]( const Event* ) {
|
||||
mScrollLocked = mUIConsole->getMaxScroll().y == mUIConsole->getScroll().y;
|
||||
} );
|
||||
}
|
||||
|
||||
@@ -3,8 +3,11 @@
|
||||
|
||||
#include "../../uistatusbar.hpp"
|
||||
#include "models/breakpointsmodel.hpp"
|
||||
#include <eepp/core/containers.hpp>
|
||||
#include <eepp/scene/eventconnection.hpp>
|
||||
#include <eepp/system/luapattern.hpp>
|
||||
#include <eepp/ui/tools/uicodeeditorsplitter.hpp>
|
||||
#include <eepp/ui/tools/uitabwidgetsplitter.hpp>
|
||||
#include <eepp/ui/uicodeeditor.hpp>
|
||||
#include <eepp/ui/uirelativelayout.hpp>
|
||||
#include <eepp/ui/uiscenenode.hpp>
|
||||
@@ -34,7 +37,7 @@ class UIBreakpointsTableView : public UITableView {
|
||||
UIWidget* createCell( UIWidget* rowWidget, const ModelIndex& index );
|
||||
};
|
||||
|
||||
class StatusDebuggerController : public StatusBarElement {
|
||||
class StatusDebuggerController : public StatusBarElement, public UITabWidgetSplitter::Client {
|
||||
public:
|
||||
enum class State { NotStarted, Running, Paused };
|
||||
|
||||
@@ -43,7 +46,11 @@ class StatusDebuggerController : public StatusBarElement {
|
||||
StatusDebuggerController( UISplitter* mainSplitter, UISceneNode* uiSceneNode,
|
||||
PluginContextProvider* pluginContext );
|
||||
|
||||
virtual ~StatusDebuggerController() {};
|
||||
virtual ~StatusDebuggerController();
|
||||
|
||||
virtual void show();
|
||||
|
||||
virtual void hide();
|
||||
|
||||
UIWidget* getWidget();
|
||||
|
||||
@@ -71,8 +78,14 @@ class StatusDebuggerController : public StatusBarElement {
|
||||
|
||||
void setDebuggingState( State state );
|
||||
|
||||
const std::string& saveLayout();
|
||||
|
||||
std::function<void( StatusDebuggerController*, UIWidget* )> onWidgetCreated{ nullptr };
|
||||
|
||||
void onTabCreated( UITab* tab, UIWidget* widget );
|
||||
|
||||
void onWidgetFocusChange( UIWidget* widget );
|
||||
|
||||
protected:
|
||||
UIHLinearLayoutCommandExecuter* mContainer{ nullptr };
|
||||
UITableView* mUIThreads{ nullptr };
|
||||
@@ -90,9 +103,32 @@ class StatusDebuggerController : public StatusBarElement {
|
||||
UIPushButton* mUIButStepOver{ nullptr };
|
||||
UIPushButton* mUIButStepOut{ nullptr };
|
||||
UITabWidget* mUITabWidget{ nullptr };
|
||||
UITabWidget* mUIRightTabWidget{ nullptr };
|
||||
UITabWidgetSplitter* mTabWidgetSplitter{ nullptr };
|
||||
UILayout* mDebuggerTabsContainer{ nullptr };
|
||||
UILayout* mRightPanelContainer{ nullptr };
|
||||
Scene::EventConnectionList mEventConnections;
|
||||
UnorderedMap<UITabWidget*, Scene::EventConnectionList> mTabWidgetEventConnections;
|
||||
bool mScrollLocked{ true };
|
||||
bool mRestoringLayout{ false };
|
||||
bool mRightPanelDropPreview{ false };
|
||||
std::string mSerializedLayout;
|
||||
|
||||
void createContainer();
|
||||
|
||||
void createTabWidgets();
|
||||
|
||||
void restoreTabLayout();
|
||||
|
||||
void saveTabLayout();
|
||||
|
||||
bool rightPanelHasTabs() const;
|
||||
|
||||
void beginRightPanelDropPreview();
|
||||
|
||||
void endRightPanelDropPreview();
|
||||
|
||||
void updateRightPanel();
|
||||
};
|
||||
|
||||
} // namespace ecode
|
||||
|
||||
@@ -19,6 +19,7 @@ class Font;
|
||||
namespace UI {
|
||||
|
||||
class UISplitter;
|
||||
class UILayout;
|
||||
class UITabWidget;
|
||||
class UISceneNode;
|
||||
class UICodeEditor;
|
||||
@@ -58,6 +59,8 @@ class ProjectDirectoryTree;
|
||||
struct TerminalConfig;
|
||||
class UIMainLayout;
|
||||
class UITreeViewFS;
|
||||
class StatusDebuggerController;
|
||||
class UIRightPanel;
|
||||
|
||||
class PluginContextProvider {
|
||||
public:
|
||||
@@ -65,6 +68,10 @@ class PluginContextProvider {
|
||||
|
||||
virtual UISplitter* getMainSplitter() const = 0;
|
||||
|
||||
virtual UIRightPanel* getRightPanel() const = 0;
|
||||
|
||||
virtual StatusDebuggerController* getStatusDebuggerController() const = 0;
|
||||
|
||||
virtual UITreeViewFS* getProjectTreeView() const = 0;
|
||||
|
||||
virtual void hideGlobalSearchBar() = 0;
|
||||
|
||||
@@ -0,0 +1,101 @@
|
||||
#include "uirightpanel.hpp"
|
||||
#include "appconfig.hpp"
|
||||
|
||||
namespace ecode {
|
||||
|
||||
using namespace EE;
|
||||
using namespace EE::UI;
|
||||
|
||||
UIRightPanel::UIRightPanel( UISplitter* splitter, UILayout* container, AppConfig* config ) :
|
||||
mSplitter( splitter ), mContainer( container ), mConfig( config ) {}
|
||||
|
||||
UILayout* UIRightPanel::registerPanel( const std::string& id ) {
|
||||
auto found = mPanels.find( id );
|
||||
if ( found != mPanels.end() )
|
||||
return found->second.layout;
|
||||
if ( !mContainer )
|
||||
return nullptr;
|
||||
auto layout = UIRelativeLayout::New();
|
||||
layout->setId( id );
|
||||
layout->setLayoutSizePolicy( SizePolicy::MatchParent, SizePolicy::MatchParent );
|
||||
layout->setVisible( false )->setEnabled( false )->setParent( mContainer );
|
||||
mPanels.emplace( id, Panel{ layout, false } );
|
||||
return layout;
|
||||
}
|
||||
|
||||
void UIRightPanel::unregisterPanel( const std::string& id ) {
|
||||
auto found = mPanels.find( id );
|
||||
if ( found == mPanels.end() )
|
||||
return;
|
||||
if ( found->second.layout )
|
||||
found->second.layout->close();
|
||||
mPanels.erase( found );
|
||||
updateVisibility();
|
||||
}
|
||||
|
||||
void UIRightPanel::setPanelVisible( const std::string& id, bool visible ) {
|
||||
auto found = mPanels.find( id );
|
||||
if ( found == mPanels.end() )
|
||||
return;
|
||||
if ( visible ) {
|
||||
for ( auto& panel : mPanels ) {
|
||||
if ( panel.first != id && panel.second.visible ) {
|
||||
panel.second.visible = false;
|
||||
if ( panel.second.layout )
|
||||
panel.second.layout->setVisible( false )->setEnabled( false );
|
||||
}
|
||||
}
|
||||
}
|
||||
if ( found->second.visible == visible )
|
||||
return;
|
||||
found->second.visible = visible;
|
||||
if ( found->second.layout )
|
||||
found->second.layout->setVisible( visible )->setEnabled( visible );
|
||||
updateVisibility();
|
||||
}
|
||||
|
||||
bool UIRightPanel::isPanelVisible( const std::string& id ) const {
|
||||
auto found = mPanels.find( id );
|
||||
return found != mPanels.end() && found->second.visible;
|
||||
}
|
||||
|
||||
void UIRightPanel::saveState() {
|
||||
if ( mSplitter && mConfig &&
|
||||
mSplitter->getSplitPartition().getUnit() == StyleSheetLength::Percentage &&
|
||||
mSplitter->getSplitPartition().getValue() < 100.f )
|
||||
mConfig->windowState.rightPanelPartition = mSplitter->getSplitPartition().toString();
|
||||
}
|
||||
|
||||
UISplitter* UIRightPanel::getSplitter() const {
|
||||
return mSplitter;
|
||||
}
|
||||
|
||||
UILayout* UIRightPanel::getContainer() const {
|
||||
return mContainer;
|
||||
}
|
||||
|
||||
void UIRightPanel::updateVisibility() {
|
||||
if ( !mSplitter || !mConfig )
|
||||
return;
|
||||
bool visible = false;
|
||||
for ( const auto& panel : mPanels ) {
|
||||
if ( panel.second.visible ) {
|
||||
visible = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if ( visible == mVisible )
|
||||
return;
|
||||
mVisible = visible;
|
||||
if ( visible ) {
|
||||
auto partition = mConfig->windowState.rightPanelPartition;
|
||||
if ( partition.empty() || partition == "100%" )
|
||||
partition = "75%";
|
||||
mSplitter->setSplitPartition( StyleSheetLength( partition ) );
|
||||
} else {
|
||||
saveState();
|
||||
mSplitter->setSplitPartition( StyleSheetLength( "100%" ) );
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace ecode
|
||||
@@ -0,0 +1,50 @@
|
||||
#ifndef ECODE_UIRIGHTPANEL_HPP
|
||||
#define ECODE_UIRIGHTPANEL_HPP
|
||||
|
||||
#include <eepp/core/containers.hpp>
|
||||
#include <eepp/ui/uirelativelayout.hpp>
|
||||
#include <eepp/ui/uisplitter.hpp>
|
||||
|
||||
using namespace EE;
|
||||
using namespace EE::UI;
|
||||
|
||||
namespace ecode {
|
||||
|
||||
class AppConfig;
|
||||
|
||||
class UIRightPanel {
|
||||
public:
|
||||
UIRightPanel( UISplitter* splitter, UILayout* container, AppConfig* config );
|
||||
|
||||
UILayout* registerPanel( const std::string& id );
|
||||
|
||||
void unregisterPanel( const std::string& id );
|
||||
|
||||
void setPanelVisible( const std::string& id, bool visible );
|
||||
|
||||
bool isPanelVisible( const std::string& id ) const;
|
||||
|
||||
void saveState();
|
||||
|
||||
UISplitter* getSplitter() const;
|
||||
|
||||
UILayout* getContainer() const;
|
||||
|
||||
protected:
|
||||
struct Panel {
|
||||
UILayout* layout{ nullptr };
|
||||
bool visible{ false };
|
||||
};
|
||||
|
||||
UISplitter* mSplitter{ nullptr };
|
||||
UILayout* mContainer{ nullptr };
|
||||
AppConfig* mConfig{ nullptr };
|
||||
UnorderedMap<std::string, Panel> mPanels;
|
||||
bool mVisible{ false };
|
||||
|
||||
void updateVisibility();
|
||||
};
|
||||
|
||||
} // namespace ecode
|
||||
|
||||
#endif // ECODE_UIRIGHTPANEL_HPP
|
||||
Reference in New Issue
Block a user