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:
Martín Lucas Golini
2026-08-14 00:17:29 -03:00
parent a2c9adca9a
commit bf750761ae
37 changed files with 1579 additions and 131 deletions
@@ -6,6 +6,7 @@
#include <eepp/ui/uitablecell.hpp>
#include <eepp/ui/uitableheadercolumn.hpp>
#include <eepp/ui/uitablerow.hpp>
#include <nlohmann/json_fwd.hpp>
#include <unordered_map>
using namespace EE::Math;
@@ -20,6 +21,8 @@ namespace EE { namespace UI { namespace Abstract {
class EE_API UIAbstractTableView : public UIAbstractView {
public:
enum class ColumnWidthMode : Uint8 { Pixels, Percentage };
enum TableFlags : Uint32 {
TableFlagNone = 0,
TableFlagHeaders = ( 1 << 0 ),
@@ -85,6 +88,30 @@ class EE_API UIAbstractTableView : public UIAbstractView {
const Float& getColumnWidth( const size_t& colIndex ) const;
Float getColumnWidthPercentage( const size_t& colIndex ) const;
std::vector<Float> getColumnsWidthPercentage() const;
ColumnWidthMode getColumnWidthMode() const;
void setColumnWidthMode( ColumnWidthMode mode, bool convertCurrentWidths = true );
bool isColumnWidthModeMenuEnabled() const;
void setColumnWidthModeMenuEnabled( bool enabled );
void setColumnWidthPercentage( const size_t& colIndex, Float percentage );
void setColumnsWidthPercentage( const std::vector<Float>& percentages );
/** Serializes the column width mode and widths. Pixel widths are stored as dp. */
nlohmann::json serializeColumnWidths() const;
/** Restores column widths serialized by serializeColumnWidths(). Legacy percentage arrays are
* also accepted. Percentage values may be partial or contain surplus entries; pixel widths must
* match the model. Returns false if the data is invalid or incompatible with the model. */
bool unserializeColumnWidths( const nlohmann::json& widths );
virtual Float getMaxColumnContentWidth( const size_t& colIndex, bool bestGuess = false );
bool getAutoExpandOnSingleColumn() const;
@@ -180,6 +207,7 @@ class EE_API UIAbstractTableView : public UIAbstractView {
Float minHeight{ 0 };
Float maxWidth{ 0 };
Float width{ 0 };
Float percentage{ 0 };
bool visible{ true };
bool manuallySet{ false };
UIPushButton* widget{ nullptr };
@@ -210,6 +238,10 @@ class EE_API UIAbstractTableView : public UIAbstractView {
std::function<void( UITableCell* )> mSetupCellCb;
Float mRowHeaderWidth{ 0 };
Uint32 mTableFlags{ UITABLE_DEFAULT_FLAGS };
ColumnWidthMode mColumnWidthMode{ ColumnWidthMode::Pixels };
bool mColumnWidthModeMenuEnabled{ false };
bool mUpdatingColumnsForScrollbars{ false };
std::string mPendingSerializedColumnWidths;
virtual ~UIAbstractTableView();
@@ -231,6 +263,8 @@ class EE_API UIAbstractTableView : public UIAbstractView {
virtual void updateColumnsWidth();
void restorePendingColumnWidths();
virtual Uint32 onFocus( NodeFocusReason reason );
virtual Uint32 onFocusLoss();
@@ -252,6 +286,8 @@ class EE_API UIAbstractTableView : public UIAbstractView {
virtual void onScrollChange();
virtual void onContentSizeChange();
virtual void onRowCreated( UITableRow* row );
virtual void onSortColumn( const size_t& colIndex );
@@ -270,6 +306,10 @@ class EE_API UIAbstractTableView : public UIAbstractView {
void resetColumnData();
void updatePercentageColumnWidths();
int adjacentVisibleColumn( size_t column ) const;
void buildRowHeader();
void updateRowHeader( int realRowIndex, const ModelIndex& index, Float yOffset );
+3
View File
@@ -232,6 +232,7 @@ enum class PropertyId : Uint16 {
TabAllowSwitchTabsInEmptySpaces,
SplitterPartition,
SplitterAlwaysShow,
SplitterHideOnEdge,
DroppableHoveringColor,
TextAsFallback,
SelectOnClick,
@@ -304,6 +305,8 @@ enum class PropertyId : Uint16 {
IconSize,
SortIconSize,
MainColumn,
ColumnWidthMode,
ColumnWidthModeMenu,
RowHeaderWidth,
TableFlags,
IndentWidth,
@@ -1,6 +1,8 @@
#ifndef EE_UI_TOOLS_UITABWIDGETSPLITTER_HPP
#define EE_UI_TOOLS_UITABWIDGETSPLITTER_HPP
#include <eepp/core/containers.hpp>
#include <eepp/scene/eventconnection.hpp>
#include <eepp/system/mutex.hpp>
#include <eepp/ui/splitdirection.hpp>
#include <eepp/ui/uiscenenode.hpp>
@@ -130,6 +132,8 @@ class EE_API UITabWidgetSplitter {
void setOnTabWidgetCreateCb( std::function<void( UITabWidget* )> cb );
void setOnTabWidgetCloseCb( std::function<void( UITabWidget* )> cb );
void closeSplitter( UISplitter* splitter );
void addRemainingTabWidgets( Node* widget );
@@ -254,10 +258,13 @@ class EE_API UITabWidgetSplitter {
Float mVisualSplitEdgePercent{ 0.1 };
Mutex mTabWidgetMutex;
std::function<void( UITabWidget* )> mOnTabWidgetCreateCb;
std::function<void( UITabWidget* )> mOnTabWidgetCloseCb;
std::function<bool( SplitDirection direction, UIWidget* widget )> mCanCreateSplitFn;
TabTryCloseCallback mTabTryCloseCb;
mutable Mutex mWidgetTypesMutex;
std::unordered_map<std::string, WidgetTypeCallback> mWidgetTypes;
UnorderedMap<std::string, WidgetTypeCallback> mWidgetTypes;
UnorderedMap<UITabWidget*, Scene::EventConnectionList> mTabWidgetEventConnections;
UnorderedMap<UIWidget*, Scene::EventConnectionList> mWidgetEventConnections;
UITabWidgetSplitter( Client* client, UISceneNode* sceneNode );
+9
View File
@@ -1,6 +1,7 @@
#ifndef EE_UI_UISPLITTER_HPP
#define EE_UI_UISPLITTER_HPP
#include <eepp/scene/eventconnection.hpp>
#include <eepp/ui/uilayout.hpp>
namespace EE { namespace UI {
@@ -23,6 +24,10 @@ class EE_API UISplitter : public UILayout {
void setAlwaysShowSplitter( bool alwaysShowSplitter );
const bool& hideSplitterOnEdge() const;
void setHideSplitterOnEdge( bool hideSplitterOnEdge );
const StyleSheetLength& getSplitPartition() const;
void setSplitPartition( const StyleSheetLength& divisionSplit );
@@ -49,10 +54,12 @@ class EE_API UISplitter : public UILayout {
protected:
UIOrientation mOrientation;
bool mAlwaysShowSplitter;
bool mHideSplitterOnEdge;
StyleSheetLength mSplitPartition;
UIWidget* mSplitter;
UIWidget* mFirstWidget;
UIWidget* mLastWidget;
Scene::EventConnectionList mEventConnections;
UISplitter();
@@ -63,6 +70,8 @@ class EE_API UISplitter : public UILayout {
void updateFromDrag();
void updateSplitterDragFlags();
bool shouldShowSplitter() const;
};
}} // namespace EE::UI
+3 -1
View File
@@ -20,7 +20,7 @@ class EE_API UITableHeaderColumn : public UIPushButton {
protected:
UIAbstractTableView* mView;
size_t mColIndex;
mutable UIImage* mImage{nullptr};
mutable UIImage* mImage{ nullptr };
Uint32 onCalculateDrag( const Vector2f& position, const Uint32& flags );
@@ -34,6 +34,8 @@ class EE_API UITableHeaderColumn : public UIPushButton {
Uint32 onMouseClick( const Vector2i& position, const Uint32& flags );
Uint32 onMouseUp( const Vector2i& position, const Uint32& flags );
Uint32 onMouseDoubleClick( const Vector2i& position, const Uint32& flags );
Uint32 onDragStop( const Vector2i& pos, const Uint32& flags );
+2
View File
@@ -2,6 +2,7 @@
#define EE_UI_UITABWIDGET_HPP
#include <deque>
#include <eepp/scene/eventconnection.hpp>
#include <eepp/ui/splitdirection.hpp>
#include <eepp/ui/uitab.hpp>
#include <eepp/ui/uiwidget.hpp>
@@ -260,6 +261,7 @@ class EE_API UITabWidget : public UIWidget {
UIListView* mTabSwitcher{ nullptr };
TabJumpMode mTabJumpMode{ TabJumpMode::Linear };
std::function<bool( const UIWidget* widget )> mAcceptsDropOfWidgetFn;
Scene::EventConnectionList mEventConnections;
UITabWidget();