mirror of
https://github.com/SpartanJ/eepp.git
synced 2026-10-02 11:10:31 +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:
@@ -8,8 +8,19 @@ Your name is Negen (from negentropy: the process of creating order out of chaos)
|
|||||||
1. **Performance & Memory Management:**
|
1. **Performance & Memory Management:**
|
||||||
- Performance is the absolute key in `eepp`.
|
- Performance is the absolute key in `eepp`.
|
||||||
- Favor stack-allocated memory over heap allocations whenever possible.
|
- Favor stack-allocated memory over heap allocations whenever possible.
|
||||||
|
- Prefer eepp's internal container layer when it provides the required semantics. Check
|
||||||
|
`include/eepp/core/containers.hpp`, `small_vector.hpp`, `lrucache.hpp`, and related core
|
||||||
|
containers before introducing standard-library or third-party containers directly.
|
||||||
|
- Also consider `include/eepp/core/small_function.hpp` for frequently stored callbacks with
|
||||||
|
known, bounded capture sizes. It is not a general replacement for `std::function`: use it only
|
||||||
|
when its inline-capacity, callable semantics, and object-size tradeoff fit the concrete use.
|
||||||
- Any heap allocation must be heavily justified.
|
- Any heap allocation must be heavily justified.
|
||||||
- Exercise reason: maximize stack use for speed, but actively calculate boundaries to prevent stack-overflows.
|
- Exercise reason: maximize stack use for speed, but actively calculate boundaries to prevent stack-overflows.
|
||||||
|
- Review the memory layout of every new or materially changed struct and class. Order members
|
||||||
|
and select appropriately sized enum/integer storage to minimize alignment padding, and verify
|
||||||
|
meaningful changes with compiler layout data or `sizeof` instead of guessing. Do not use packed
|
||||||
|
layouts or otherwise force misaligned access, and preserve public ABI unless the change is
|
||||||
|
explicitly authorized.
|
||||||
- Before finalizing C++ changes, perform an explicit allocation audit:
|
- Before finalizing C++ changes, perform an explicit allocation audit:
|
||||||
- Review every heap allocation, string copy, container insertion, `std::function`, lambda capture, and async handoff introduced or touched by the change.
|
- Review every heap allocation, string copy, container insertion, `std::function`, lambda capture, and async handoff introduced or touched by the change.
|
||||||
- Prefer move captures for owned temporary strings, buffers, vectors, and other heap-backed objects passed into lambdas.
|
- Prefer move captures for owned temporary strings, buffers, vectors, and other heap-backed objects passed into lambdas.
|
||||||
|
|||||||
@@ -17,9 +17,24 @@ When working on this project, rely on the following resources to understand exis
|
|||||||
* **Implementation Examples:** A wide variety of examples showing how to use the library are located in `src/examples/`.
|
* **Implementation Examples:** A wide variety of examples showing how to use the library are located in `src/examples/`.
|
||||||
* **General Context:** The `README.md` at the root directory contains deeper project details.
|
* **General Context:** The `README.md` at the root directory contains deeper project details.
|
||||||
|
|
||||||
|
## Localization
|
||||||
|
|
||||||
|
The locale catalogs under `bin/assets/i18n/` belong to **ecode**; the other applications are not
|
||||||
|
currently localized. Whenever an ecode feature introduces or changes a user-facing i18n key, add or
|
||||||
|
update that key in every catalog in this directory. Do not rely only on the fallback string embedded
|
||||||
|
in the source. Keep all locale files structurally valid and verify that every supported catalog
|
||||||
|
contains the new or renamed key.
|
||||||
|
|
||||||
## C++ Virtual Method Style
|
## C++ Virtual Method Style
|
||||||
Follow the convention already used by the class being edited. In particular, when a class declares
|
Follow the convention already used by the class being edited. In particular, when a class declares
|
||||||
virtual methods without the `override` specifier, do not introduce `override` on new methods in that
|
virtual methods without the `override` specifier, do not introduce `override` on new methods in that
|
||||||
class. Mixing the styles can enable Clang's inconsistent-missing-override warnings for the existing
|
class. Mixing the styles can enable Clang's inconsistent-missing-override warnings for the existing
|
||||||
declarations. A class-wide conversion is a separate change and must update all applicable methods
|
declarations. A class-wide conversion is a separate change and must update all applicable methods
|
||||||
together.
|
together.
|
||||||
|
|
||||||
|
## Namespace Style
|
||||||
|
|
||||||
|
Follow eepp's established namespace style: prefer the appropriate `using namespace EE::...`
|
||||||
|
declarations and unqualified eepp type names, such as `UISplitter`, over repeatedly spelling fully
|
||||||
|
qualified names such as `EE::UI::UISplitter`. Keep explicit qualification only where it is required
|
||||||
|
to resolve ambiguity or avoid importing an unusually broad namespace into an unsuitable scope.
|
||||||
|
|||||||
@@ -5,20 +5,22 @@ This project relies on a comprehensive suite of unit tests to prevent regression
|
|||||||
## Running Tests
|
## Running Tests
|
||||||
The test binary manages its own current working directory, so you can execute it from anywhere.
|
The test binary manages its own current working directory, so you can execute it from anywhere.
|
||||||
|
|
||||||
|
* **Prefer the release test binary during normal development:**
|
||||||
|
When AddressSanitizer or other debug-only diagnostics are not required, build and run `bin/unit_tests/eepp-unit_tests`. The optimized release suite is substantially faster and should be the default for iterative testing. Use `bin/unit_tests/eepp-unit_tests-debug` when investigating memory safety, assertions, or other behavior that specifically requires the debug configuration.
|
||||||
* **Default Execution for Agents on Linux & FreeBSD:**
|
* **Default Execution for Agents on Linux & FreeBSD:**
|
||||||
Always run unit tests through the project wrapper unless the user explicitly asks for a different harness:
|
Always run unit tests through the project wrapper unless the user explicitly asks for a different harness:
|
||||||
`projects/scripts/xvfb-run-eepp bin/unit_tests/eepp-unit_tests-debug`
|
`projects/scripts/xvfb-run-eepp bin/unit_tests/eepp-unit_tests`
|
||||||
* **Why the wrapper is required:**
|
* **Why the wrapper is required:**
|
||||||
Tests open ~400 individual windows. The wrapper runs them in an isolated framebuffer, enables race-safe automatic display selection for concurrent agent test runs, sets the default screen to `1280x1024x24`, and injects `ASAN_OPTIONS=detect_leaks=0` automatically.
|
Tests open ~400 individual windows. The wrapper runs them in an isolated framebuffer, enables race-safe automatic display selection for concurrent agent test runs, sets the default screen to `1280x1024x24`, and injects `ASAN_OPTIONS=detect_leaks=0` automatically.
|
||||||
* **Do not skip the wrapper for filtered tests:**
|
* **Do not skip the wrapper for filtered tests:**
|
||||||
A focused test still needs the same wrapper:
|
A focused test still needs the same wrapper:
|
||||||
`projects/scripts/xvfb-run-eepp bin/unit_tests/eepp-unit_tests-debug --filter="FontRendering.*Offset*"`
|
`projects/scripts/xvfb-run-eepp bin/unit_tests/eepp-unit_tests --filter="FontRendering.*Offset*"`
|
||||||
* **Fallback only when the wrapper itself fails:**
|
* **Fallback only when the wrapper itself fails:**
|
||||||
If `projects/scripts/xvfb-run-eepp` fails before launching the test binary, report that wrapper failure and then use this fallback to keep verification moving:
|
If `projects/scripts/xvfb-run-eepp` fails before launching the test binary, report that wrapper failure and then use this fallback to keep verification moving:
|
||||||
`ASAN_OPTIONS=detect_leaks=0 xvfb-run -a -s "-screen 0 1280x1024x24" bin/unit_tests/eepp-unit_tests-debug`
|
`xvfb-run -a -s "-screen 0 1280x1024x24" bin/unit_tests/eepp-unit_tests`
|
||||||
Do not use plain `xvfb-run` as the first attempt for GUI/unit tests.
|
Do not use plain `xvfb-run` as the first attempt for GUI/unit tests.
|
||||||
* **Direct Execution (Only for non-window tests or explicit user requests):**
|
* **Direct Execution (Only for non-window tests or explicit user requests):**
|
||||||
`bin/unit_tests/eepp-unit_tests-debug`
|
`bin/unit_tests/eepp-unit_tests`
|
||||||
* **Filtering Tests:**
|
* **Filtering Tests:**
|
||||||
Use the `--filter` parameter to run specific tests (supports glob patterns).
|
Use the `--filter` parameter to run specific tests (supports glob patterns).
|
||||||
Keep the wrapper in front of the binary unless the test is known not to create windows.
|
Keep the wrapper in front of the binary unless the test is known not to create windows.
|
||||||
|
|||||||
@@ -890,6 +890,8 @@ in der Baumansicht und in Öffen-/Schließdialogen aktivieren.</string>
|
|||||||
<string name="uifiledialog_select_file">Eine Datei auswählen</string>
|
<string name="uifiledialog_select_file">Eine Datei auswählen</string>
|
||||||
<string name="uifiledialog_select_folder">Einen Ordner auswählen</string>
|
<string name="uifiledialog_select_folder">Einen Ordner auswählen</string>
|
||||||
<string name="uifiledialog_table">Tabelle</string>
|
<string name="uifiledialog_table">Tabelle</string>
|
||||||
|
<string name="uitable_fit_columns_to_view">Spalten an Ansicht anpassen</string>
|
||||||
|
<string name="uitable_free_column_widths">Freie Spaltenbreiten</string>
|
||||||
<string name="uiterminal_copy">Kopieren</string>
|
<string name="uiterminal_copy">Kopieren</string>
|
||||||
<string name="uiterminal_open_link">Link öffnen</string>
|
<string name="uiterminal_open_link">Link öffnen</string>
|
||||||
<string name="uiterminal_paste">Einfügen</string>
|
<string name="uiterminal_paste">Einfügen</string>
|
||||||
|
|||||||
@@ -874,6 +874,8 @@ the directory tree and in file dialogs to open a folder or file.</string>
|
|||||||
<string name="uifiledialog_select_file">Select a file</string>
|
<string name="uifiledialog_select_file">Select a file</string>
|
||||||
<string name="uifiledialog_select_folder">Select a folder</string>
|
<string name="uifiledialog_select_folder">Select a folder</string>
|
||||||
<string name="uifiledialog_table">Table</string>
|
<string name="uifiledialog_table">Table</string>
|
||||||
|
<string name="uitable_fit_columns_to_view">Fit Columns to View</string>
|
||||||
|
<string name="uitable_free_column_widths">Free Column Widths</string>
|
||||||
<string name="uiterminal_copy">Copy</string>
|
<string name="uiterminal_copy">Copy</string>
|
||||||
<string name="uiterminal_open_link">Open Link</string>
|
<string name="uiterminal_open_link">Open Link</string>
|
||||||
<string name="uiterminal_paste">Paste</string>
|
<string name="uiterminal_paste">Paste</string>
|
||||||
|
|||||||
@@ -865,6 +865,8 @@ dans l'arborescence de répertoires ainsi que dans les boites de dialogues de s
|
|||||||
<string name="uifiledialog_select_file">Sélectionner un fichier</string>
|
<string name="uifiledialog_select_file">Sélectionner un fichier</string>
|
||||||
<string name="uifiledialog_select_folder">Sélectionner un dossier</string>
|
<string name="uifiledialog_select_folder">Sélectionner un dossier</string>
|
||||||
<string name="uifiledialog_table">Table</string>
|
<string name="uifiledialog_table">Table</string>
|
||||||
|
<string name="uitable_fit_columns_to_view">Ajuster les colonnes à la vue</string>
|
||||||
|
<string name="uitable_free_column_widths">Largeurs de colonnes libres</string>
|
||||||
<string name="uiterminal_copy">Copier</string>
|
<string name="uiterminal_copy">Copier</string>
|
||||||
<string name="uiterminal_open_link">Ouvrir un lien</string>
|
<string name="uiterminal_open_link">Ouvrir un lien</string>
|
||||||
<string name="uiterminal_paste">Coller</string>
|
<string name="uiterminal_paste">Coller</string>
|
||||||
|
|||||||
@@ -663,6 +663,8 @@ file in the directory tree.</string>
|
|||||||
<string name="uifiledialog_select_file">选择文件</string>
|
<string name="uifiledialog_select_file">选择文件</string>
|
||||||
<string name="uifiledialog_select_folder">选择文件夹</string>
|
<string name="uifiledialog_select_folder">选择文件夹</string>
|
||||||
<string name="uifiledialog_table">Table</string>
|
<string name="uifiledialog_table">Table</string>
|
||||||
|
<string name="uitable_fit_columns_to_view">列宽适应视图</string>
|
||||||
|
<string name="uitable_free_column_widths">自由调整列宽</string>
|
||||||
<string name="uiterminal_copy">复制</string>
|
<string name="uiterminal_copy">复制</string>
|
||||||
<string name="uiterminal_open_link">打开链接</string>
|
<string name="uiterminal_open_link">打开链接</string>
|
||||||
<string name="uiterminal_paste">粘贴</string>
|
<string name="uiterminal_paste">粘贴</string>
|
||||||
|
|||||||
@@ -534,6 +534,30 @@ Sets the width of the child elements of a grid layout.
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
### column-width-mode
|
||||||
|
|
||||||
|
Selects how table and tree view column widths are represented and resized.
|
||||||
|
|
||||||
|
* Applicable to: EE::UI::UIAbstractTableView (TableView, TreeView)
|
||||||
|
* Data Type: [string-list](#string-list-data-type)
|
||||||
|
* Value List:
|
||||||
|
* `pixels`: Column widths are stored as fixed pixel lengths and can extend beyond the available content width.
|
||||||
|
* `percentage`: Visible column widths are stored as normalized percentages of the available content width. Resizing a column adjusts an adjacent visible column to preserve the total.
|
||||||
|
* Default value: `pixels`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### column-width-mode-menu
|
||||||
|
|
||||||
|
Enables a context menu on table and tree view column headers for switching between pixel and
|
||||||
|
percentage column width modes.
|
||||||
|
|
||||||
|
* Applicable to: EE::UI::UIAbstractTableView (TableView, TreeView)
|
||||||
|
* Data Type: [boolean](#boolean-data-type)
|
||||||
|
* Default value: `false`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
### cursor
|
### cursor
|
||||||
|
|
||||||
Read [cursor](https://developer.mozilla.org/en-US/docs/Web/CSS/cursor) documentation.
|
Read [cursor](https://developer.mozilla.org/en-US/docs/Web/CSS/cursor) documentation.
|
||||||
@@ -1934,8 +1958,8 @@ Sets the [skin](#skin) tint color.
|
|||||||
|
|
||||||
### splitter-always-show
|
### splitter-always-show
|
||||||
|
|
||||||
Sets if the splitter divisor is always visible. If false it will be shown only if two views are
|
Sets whether the splitter divisor remains visible whenever two views are attached. This property
|
||||||
attached to the splitter.
|
overrides `splitter-hide-on-edge`.
|
||||||
|
|
||||||
* Applicable to: EE::UI::UISplitter (Splitter)
|
* Applicable to: EE::UI::UISplitter (Splitter)
|
||||||
* Data Type: [boolean](#boolean-data-type)
|
* Data Type: [boolean](#boolean-data-type)
|
||||||
@@ -1943,6 +1967,17 @@ attached to the splitter.
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
### splitter-hide-on-edge
|
||||||
|
|
||||||
|
Hides the splitter divisor when its percentage partition is exactly `0%` or `100%`, allowing one
|
||||||
|
view to consume the complete splitter. This has no effect when `splitter-always-show` is `true`.
|
||||||
|
|
||||||
|
* Applicable to: EE::UI::UISplitter (Splitter)
|
||||||
|
* Data Type: [boolean](#boolean-data-type)
|
||||||
|
* Default value: `false`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
### splitter-partition
|
### splitter-partition
|
||||||
|
|
||||||
Sets the space occupied by the first view contained by the splitter.
|
Sets the space occupied by the first view contained by the splitter.
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
#include <eepp/ui/uitablecell.hpp>
|
#include <eepp/ui/uitablecell.hpp>
|
||||||
#include <eepp/ui/uitableheadercolumn.hpp>
|
#include <eepp/ui/uitableheadercolumn.hpp>
|
||||||
#include <eepp/ui/uitablerow.hpp>
|
#include <eepp/ui/uitablerow.hpp>
|
||||||
|
#include <nlohmann/json_fwd.hpp>
|
||||||
#include <unordered_map>
|
#include <unordered_map>
|
||||||
|
|
||||||
using namespace EE::Math;
|
using namespace EE::Math;
|
||||||
@@ -20,6 +21,8 @@ namespace EE { namespace UI { namespace Abstract {
|
|||||||
|
|
||||||
class EE_API UIAbstractTableView : public UIAbstractView {
|
class EE_API UIAbstractTableView : public UIAbstractView {
|
||||||
public:
|
public:
|
||||||
|
enum class ColumnWidthMode : Uint8 { Pixels, Percentage };
|
||||||
|
|
||||||
enum TableFlags : Uint32 {
|
enum TableFlags : Uint32 {
|
||||||
TableFlagNone = 0,
|
TableFlagNone = 0,
|
||||||
TableFlagHeaders = ( 1 << 0 ),
|
TableFlagHeaders = ( 1 << 0 ),
|
||||||
@@ -85,6 +88,30 @@ class EE_API UIAbstractTableView : public UIAbstractView {
|
|||||||
|
|
||||||
const Float& getColumnWidth( const size_t& colIndex ) const;
|
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 );
|
virtual Float getMaxColumnContentWidth( const size_t& colIndex, bool bestGuess = false );
|
||||||
|
|
||||||
bool getAutoExpandOnSingleColumn() const;
|
bool getAutoExpandOnSingleColumn() const;
|
||||||
@@ -180,6 +207,7 @@ class EE_API UIAbstractTableView : public UIAbstractView {
|
|||||||
Float minHeight{ 0 };
|
Float minHeight{ 0 };
|
||||||
Float maxWidth{ 0 };
|
Float maxWidth{ 0 };
|
||||||
Float width{ 0 };
|
Float width{ 0 };
|
||||||
|
Float percentage{ 0 };
|
||||||
bool visible{ true };
|
bool visible{ true };
|
||||||
bool manuallySet{ false };
|
bool manuallySet{ false };
|
||||||
UIPushButton* widget{ nullptr };
|
UIPushButton* widget{ nullptr };
|
||||||
@@ -210,6 +238,10 @@ class EE_API UIAbstractTableView : public UIAbstractView {
|
|||||||
std::function<void( UITableCell* )> mSetupCellCb;
|
std::function<void( UITableCell* )> mSetupCellCb;
|
||||||
Float mRowHeaderWidth{ 0 };
|
Float mRowHeaderWidth{ 0 };
|
||||||
Uint32 mTableFlags{ UITABLE_DEFAULT_FLAGS };
|
Uint32 mTableFlags{ UITABLE_DEFAULT_FLAGS };
|
||||||
|
ColumnWidthMode mColumnWidthMode{ ColumnWidthMode::Pixels };
|
||||||
|
bool mColumnWidthModeMenuEnabled{ false };
|
||||||
|
bool mUpdatingColumnsForScrollbars{ false };
|
||||||
|
std::string mPendingSerializedColumnWidths;
|
||||||
|
|
||||||
virtual ~UIAbstractTableView();
|
virtual ~UIAbstractTableView();
|
||||||
|
|
||||||
@@ -231,6 +263,8 @@ class EE_API UIAbstractTableView : public UIAbstractView {
|
|||||||
|
|
||||||
virtual void updateColumnsWidth();
|
virtual void updateColumnsWidth();
|
||||||
|
|
||||||
|
void restorePendingColumnWidths();
|
||||||
|
|
||||||
virtual Uint32 onFocus( NodeFocusReason reason );
|
virtual Uint32 onFocus( NodeFocusReason reason );
|
||||||
|
|
||||||
virtual Uint32 onFocusLoss();
|
virtual Uint32 onFocusLoss();
|
||||||
@@ -252,6 +286,8 @@ class EE_API UIAbstractTableView : public UIAbstractView {
|
|||||||
|
|
||||||
virtual void onScrollChange();
|
virtual void onScrollChange();
|
||||||
|
|
||||||
|
virtual void onContentSizeChange();
|
||||||
|
|
||||||
virtual void onRowCreated( UITableRow* row );
|
virtual void onRowCreated( UITableRow* row );
|
||||||
|
|
||||||
virtual void onSortColumn( const size_t& colIndex );
|
virtual void onSortColumn( const size_t& colIndex );
|
||||||
@@ -270,6 +306,10 @@ class EE_API UIAbstractTableView : public UIAbstractView {
|
|||||||
|
|
||||||
void resetColumnData();
|
void resetColumnData();
|
||||||
|
|
||||||
|
void updatePercentageColumnWidths();
|
||||||
|
|
||||||
|
int adjacentVisibleColumn( size_t column ) const;
|
||||||
|
|
||||||
void buildRowHeader();
|
void buildRowHeader();
|
||||||
|
|
||||||
void updateRowHeader( int realRowIndex, const ModelIndex& index, Float yOffset );
|
void updateRowHeader( int realRowIndex, const ModelIndex& index, Float yOffset );
|
||||||
|
|||||||
@@ -232,6 +232,7 @@ enum class PropertyId : Uint16 {
|
|||||||
TabAllowSwitchTabsInEmptySpaces,
|
TabAllowSwitchTabsInEmptySpaces,
|
||||||
SplitterPartition,
|
SplitterPartition,
|
||||||
SplitterAlwaysShow,
|
SplitterAlwaysShow,
|
||||||
|
SplitterHideOnEdge,
|
||||||
DroppableHoveringColor,
|
DroppableHoveringColor,
|
||||||
TextAsFallback,
|
TextAsFallback,
|
||||||
SelectOnClick,
|
SelectOnClick,
|
||||||
@@ -304,6 +305,8 @@ enum class PropertyId : Uint16 {
|
|||||||
IconSize,
|
IconSize,
|
||||||
SortIconSize,
|
SortIconSize,
|
||||||
MainColumn,
|
MainColumn,
|
||||||
|
ColumnWidthMode,
|
||||||
|
ColumnWidthModeMenu,
|
||||||
RowHeaderWidth,
|
RowHeaderWidth,
|
||||||
TableFlags,
|
TableFlags,
|
||||||
IndentWidth,
|
IndentWidth,
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
#ifndef EE_UI_TOOLS_UITABWIDGETSPLITTER_HPP
|
#ifndef EE_UI_TOOLS_UITABWIDGETSPLITTER_HPP
|
||||||
#define 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/system/mutex.hpp>
|
||||||
#include <eepp/ui/splitdirection.hpp>
|
#include <eepp/ui/splitdirection.hpp>
|
||||||
#include <eepp/ui/uiscenenode.hpp>
|
#include <eepp/ui/uiscenenode.hpp>
|
||||||
@@ -130,6 +132,8 @@ class EE_API UITabWidgetSplitter {
|
|||||||
|
|
||||||
void setOnTabWidgetCreateCb( std::function<void( UITabWidget* )> cb );
|
void setOnTabWidgetCreateCb( std::function<void( UITabWidget* )> cb );
|
||||||
|
|
||||||
|
void setOnTabWidgetCloseCb( std::function<void( UITabWidget* )> cb );
|
||||||
|
|
||||||
void closeSplitter( UISplitter* splitter );
|
void closeSplitter( UISplitter* splitter );
|
||||||
|
|
||||||
void addRemainingTabWidgets( Node* widget );
|
void addRemainingTabWidgets( Node* widget );
|
||||||
@@ -254,10 +258,13 @@ class EE_API UITabWidgetSplitter {
|
|||||||
Float mVisualSplitEdgePercent{ 0.1 };
|
Float mVisualSplitEdgePercent{ 0.1 };
|
||||||
Mutex mTabWidgetMutex;
|
Mutex mTabWidgetMutex;
|
||||||
std::function<void( UITabWidget* )> mOnTabWidgetCreateCb;
|
std::function<void( UITabWidget* )> mOnTabWidgetCreateCb;
|
||||||
|
std::function<void( UITabWidget* )> mOnTabWidgetCloseCb;
|
||||||
std::function<bool( SplitDirection direction, UIWidget* widget )> mCanCreateSplitFn;
|
std::function<bool( SplitDirection direction, UIWidget* widget )> mCanCreateSplitFn;
|
||||||
TabTryCloseCallback mTabTryCloseCb;
|
TabTryCloseCallback mTabTryCloseCb;
|
||||||
mutable Mutex mWidgetTypesMutex;
|
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 );
|
UITabWidgetSplitter( Client* client, UISceneNode* sceneNode );
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
#ifndef EE_UI_UISPLITTER_HPP
|
#ifndef EE_UI_UISPLITTER_HPP
|
||||||
#define EE_UI_UISPLITTER_HPP
|
#define EE_UI_UISPLITTER_HPP
|
||||||
|
|
||||||
|
#include <eepp/scene/eventconnection.hpp>
|
||||||
#include <eepp/ui/uilayout.hpp>
|
#include <eepp/ui/uilayout.hpp>
|
||||||
|
|
||||||
namespace EE { namespace UI {
|
namespace EE { namespace UI {
|
||||||
@@ -23,6 +24,10 @@ class EE_API UISplitter : public UILayout {
|
|||||||
|
|
||||||
void setAlwaysShowSplitter( bool alwaysShowSplitter );
|
void setAlwaysShowSplitter( bool alwaysShowSplitter );
|
||||||
|
|
||||||
|
const bool& hideSplitterOnEdge() const;
|
||||||
|
|
||||||
|
void setHideSplitterOnEdge( bool hideSplitterOnEdge );
|
||||||
|
|
||||||
const StyleSheetLength& getSplitPartition() const;
|
const StyleSheetLength& getSplitPartition() const;
|
||||||
|
|
||||||
void setSplitPartition( const StyleSheetLength& divisionSplit );
|
void setSplitPartition( const StyleSheetLength& divisionSplit );
|
||||||
@@ -49,10 +54,12 @@ class EE_API UISplitter : public UILayout {
|
|||||||
protected:
|
protected:
|
||||||
UIOrientation mOrientation;
|
UIOrientation mOrientation;
|
||||||
bool mAlwaysShowSplitter;
|
bool mAlwaysShowSplitter;
|
||||||
|
bool mHideSplitterOnEdge;
|
||||||
StyleSheetLength mSplitPartition;
|
StyleSheetLength mSplitPartition;
|
||||||
UIWidget* mSplitter;
|
UIWidget* mSplitter;
|
||||||
UIWidget* mFirstWidget;
|
UIWidget* mFirstWidget;
|
||||||
UIWidget* mLastWidget;
|
UIWidget* mLastWidget;
|
||||||
|
Scene::EventConnectionList mEventConnections;
|
||||||
|
|
||||||
UISplitter();
|
UISplitter();
|
||||||
|
|
||||||
@@ -63,6 +70,8 @@ class EE_API UISplitter : public UILayout {
|
|||||||
void updateFromDrag();
|
void updateFromDrag();
|
||||||
|
|
||||||
void updateSplitterDragFlags();
|
void updateSplitterDragFlags();
|
||||||
|
|
||||||
|
bool shouldShowSplitter() const;
|
||||||
};
|
};
|
||||||
|
|
||||||
}} // namespace EE::UI
|
}} // namespace EE::UI
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ class EE_API UITableHeaderColumn : public UIPushButton {
|
|||||||
protected:
|
protected:
|
||||||
UIAbstractTableView* mView;
|
UIAbstractTableView* mView;
|
||||||
size_t mColIndex;
|
size_t mColIndex;
|
||||||
mutable UIImage* mImage{nullptr};
|
mutable UIImage* mImage{ nullptr };
|
||||||
|
|
||||||
Uint32 onCalculateDrag( const Vector2f& position, const Uint32& flags );
|
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 onMouseClick( const Vector2i& position, const Uint32& flags );
|
||||||
|
|
||||||
|
Uint32 onMouseUp( const Vector2i& position, const Uint32& flags );
|
||||||
|
|
||||||
Uint32 onMouseDoubleClick( const Vector2i& position, const Uint32& flags );
|
Uint32 onMouseDoubleClick( const Vector2i& position, const Uint32& flags );
|
||||||
|
|
||||||
Uint32 onDragStop( const Vector2i& pos, const Uint32& flags );
|
Uint32 onDragStop( const Vector2i& pos, const Uint32& flags );
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
#define EE_UI_UITABWIDGET_HPP
|
#define EE_UI_UITABWIDGET_HPP
|
||||||
|
|
||||||
#include <deque>
|
#include <deque>
|
||||||
|
#include <eepp/scene/eventconnection.hpp>
|
||||||
#include <eepp/ui/splitdirection.hpp>
|
#include <eepp/ui/splitdirection.hpp>
|
||||||
#include <eepp/ui/uitab.hpp>
|
#include <eepp/ui/uitab.hpp>
|
||||||
#include <eepp/ui/uiwidget.hpp>
|
#include <eepp/ui/uiwidget.hpp>
|
||||||
@@ -260,6 +261,7 @@ class EE_API UITabWidget : public UIWidget {
|
|||||||
UIListView* mTabSwitcher{ nullptr };
|
UIListView* mTabSwitcher{ nullptr };
|
||||||
TabJumpMode mTabJumpMode{ TabJumpMode::Linear };
|
TabJumpMode mTabJumpMode{ TabJumpMode::Linear };
|
||||||
std::function<bool( const UIWidget* widget )> mAcceptsDropOfWidgetFn;
|
std::function<bool( const UIWidget* widget )> mAcceptsDropOfWidgetFn;
|
||||||
|
Scene::EventConnectionList mEventConnections;
|
||||||
|
|
||||||
UITabWidget();
|
UITabWidget();
|
||||||
|
|
||||||
|
|||||||
@@ -9,6 +9,7 @@
|
|||||||
#include <eepp/ui/uiscrollbar.hpp>
|
#include <eepp/ui/uiscrollbar.hpp>
|
||||||
#include <eepp/window/engine.hpp>
|
#include <eepp/window/engine.hpp>
|
||||||
#include <eepp/window/input.hpp>
|
#include <eepp/window/input.hpp>
|
||||||
|
#include <nlohmann/json.hpp>
|
||||||
|
|
||||||
namespace EE { namespace UI { namespace Abstract {
|
namespace EE { namespace UI { namespace Abstract {
|
||||||
|
|
||||||
@@ -104,6 +105,166 @@ const Float& UIAbstractTableView::getColumnWidth( const size_t& colIndex ) const
|
|||||||
return columnData( colIndex ).width;
|
return columnData( colIndex ).width;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Float UIAbstractTableView::getColumnWidthPercentage( const size_t& colIndex ) const {
|
||||||
|
return columnData( colIndex ).percentage;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<Float> UIAbstractTableView::getColumnsWidthPercentage() const {
|
||||||
|
std::vector<Float> percentages;
|
||||||
|
size_t count = getModel() ? getModel()->columnCount() : mColumn.size();
|
||||||
|
percentages.reserve( count );
|
||||||
|
for ( size_t i = 0; i < count; ++i )
|
||||||
|
percentages.emplace_back( columnData( i ).percentage );
|
||||||
|
return percentages;
|
||||||
|
}
|
||||||
|
|
||||||
|
UIAbstractTableView::ColumnWidthMode UIAbstractTableView::getColumnWidthMode() const {
|
||||||
|
return mColumnWidthMode;
|
||||||
|
}
|
||||||
|
|
||||||
|
void UIAbstractTableView::setColumnWidthMode( ColumnWidthMode mode, bool convertCurrentWidths ) {
|
||||||
|
if ( mode == mColumnWidthMode )
|
||||||
|
return;
|
||||||
|
if ( mode == ColumnWidthMode::Percentage && getModel() ) {
|
||||||
|
setAutoColumnsWidth( false );
|
||||||
|
if ( convertCurrentWidths ) {
|
||||||
|
Float totalWidth = 0;
|
||||||
|
for ( size_t i = 0; i < getModel()->columnCount(); ++i )
|
||||||
|
if ( !isColumnHidden( i ) )
|
||||||
|
totalWidth += columnData( i ).width;
|
||||||
|
if ( totalWidth > 0 )
|
||||||
|
for ( size_t i = 0; i < getModel()->columnCount(); ++i )
|
||||||
|
if ( !isColumnHidden( i ) )
|
||||||
|
columnData( i ).percentage = columnData( i ).width / totalWidth * 100.f;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
mColumnWidthMode = mode;
|
||||||
|
createOrUpdateColumns( false );
|
||||||
|
}
|
||||||
|
|
||||||
|
bool UIAbstractTableView::isColumnWidthModeMenuEnabled() const {
|
||||||
|
return mColumnWidthModeMenuEnabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
void UIAbstractTableView::setColumnWidthModeMenuEnabled( bool enabled ) {
|
||||||
|
mColumnWidthModeMenuEnabled = enabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
void UIAbstractTableView::setColumnWidthPercentage( const size_t& colIndex, Float percentage ) {
|
||||||
|
if ( mColumnWidthMode != ColumnWidthMode::Percentage )
|
||||||
|
setColumnWidthMode( ColumnWidthMode::Percentage );
|
||||||
|
if ( !getModel() || colIndex >= getModel()->columnCount() )
|
||||||
|
return;
|
||||||
|
auto& column = columnData( colIndex );
|
||||||
|
int adjacent = adjacentVisibleColumn( colIndex );
|
||||||
|
if ( adjacent >= 0 ) {
|
||||||
|
auto& sibling = columnData( adjacent );
|
||||||
|
Float combined = column.percentage + sibling.percentage;
|
||||||
|
column.percentage = eeclamp( percentage, 0.f, combined );
|
||||||
|
sibling.percentage = combined - column.percentage;
|
||||||
|
} else {
|
||||||
|
column.percentage = 100.f;
|
||||||
|
}
|
||||||
|
createOrUpdateColumns( false );
|
||||||
|
}
|
||||||
|
|
||||||
|
void UIAbstractTableView::setColumnsWidthPercentage( const std::vector<Float>& percentages ) {
|
||||||
|
const size_t columnCount = getModel() ? getModel()->columnCount() : percentages.size();
|
||||||
|
if ( mColumn.size() < columnCount )
|
||||||
|
mColumn.resize( columnCount );
|
||||||
|
const size_t suppliedColumnCount = eemin( percentages.size(), columnCount );
|
||||||
|
Float total = 0;
|
||||||
|
for ( size_t i = 0; i < suppliedColumnCount; ++i ) {
|
||||||
|
columnData( i ).percentage = eemax( 0.f, percentages[i] );
|
||||||
|
if ( !getModel() || !isColumnHidden( i ) )
|
||||||
|
total += columnData( i ).percentage;
|
||||||
|
}
|
||||||
|
size_t missingVisibleColumns = 0;
|
||||||
|
for ( size_t i = suppliedColumnCount; i < columnCount; ++i )
|
||||||
|
if ( !getModel() || !isColumnHidden( i ) )
|
||||||
|
++missingVisibleColumns;
|
||||||
|
const Float missingPercentage =
|
||||||
|
missingVisibleColumns > 0 ? eemax( 0.f, 100.f - total ) / missingVisibleColumns : 0.f;
|
||||||
|
for ( size_t i = suppliedColumnCount; i < columnCount; ++i ) {
|
||||||
|
columnData( i ).percentage = !getModel() || !isColumnHidden( i ) ? missingPercentage : 0.f;
|
||||||
|
total += columnData( i ).percentage;
|
||||||
|
}
|
||||||
|
if ( total > 0 )
|
||||||
|
for ( size_t i = 0; i < columnCount; ++i )
|
||||||
|
if ( !getModel() || !isColumnHidden( i ) )
|
||||||
|
columnData( i ).percentage = columnData( i ).percentage / total * 100.f;
|
||||||
|
setAutoColumnsWidth( false );
|
||||||
|
mColumnWidthMode = ColumnWidthMode::Percentage;
|
||||||
|
if ( getModel() )
|
||||||
|
createOrUpdateColumns( false );
|
||||||
|
}
|
||||||
|
|
||||||
|
nlohmann::json UIAbstractTableView::serializeColumnWidths() const {
|
||||||
|
if ( !getModel() && !mPendingSerializedColumnWidths.empty() )
|
||||||
|
return nlohmann::json::parse( mPendingSerializedColumnWidths, nullptr, false, true );
|
||||||
|
nlohmann::json saved;
|
||||||
|
const bool percentage = mColumnWidthMode == ColumnWidthMode::Percentage;
|
||||||
|
saved["mode"] = percentage ? "percentage" : "pixels";
|
||||||
|
if ( percentage ) {
|
||||||
|
saved["widths"] = getColumnsWidthPercentage();
|
||||||
|
return saved;
|
||||||
|
}
|
||||||
|
std::vector<Float> widths;
|
||||||
|
if ( !getModel() )
|
||||||
|
return saved;
|
||||||
|
widths.reserve( getModel()->columnCount() );
|
||||||
|
for ( size_t i = 0; i < getModel()->columnCount(); ++i )
|
||||||
|
widths.emplace_back( PixelDensity::pxToDp( getColumnWidth( i ) ) );
|
||||||
|
saved["widths"] = std::move( widths );
|
||||||
|
return saved;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool UIAbstractTableView::unserializeColumnWidths( const nlohmann::json& saved ) {
|
||||||
|
const nlohmann::json* widths = &saved;
|
||||||
|
ColumnWidthMode mode = ColumnWidthMode::Percentage;
|
||||||
|
if ( saved.is_object() ) {
|
||||||
|
if ( !saved.contains( "widths" ) || !saved["widths"].is_array() )
|
||||||
|
return false;
|
||||||
|
widths = &saved["widths"];
|
||||||
|
if ( saved.value( "mode", "percentage" ) == "pixels" )
|
||||||
|
mode = ColumnWidthMode::Pixels;
|
||||||
|
} else if ( !saved.is_array() ) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
std::vector<Float> values;
|
||||||
|
values.reserve( widths->size() );
|
||||||
|
for ( const auto& value : *widths ) {
|
||||||
|
if ( !value.is_number() )
|
||||||
|
return false;
|
||||||
|
values.emplace_back( value.get<Float>() );
|
||||||
|
}
|
||||||
|
if ( !getModel() ) {
|
||||||
|
mPendingSerializedColumnWidths = saved.dump();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
mPendingSerializedColumnWidths.clear();
|
||||||
|
if ( mode == ColumnWidthMode::Percentage ) {
|
||||||
|
setColumnsWidthPercentage( values );
|
||||||
|
} else {
|
||||||
|
if ( values.size() != getModel()->columnCount() )
|
||||||
|
return false;
|
||||||
|
setColumnWidthMode( mode, false );
|
||||||
|
for ( size_t i = 0; i < values.size(); ++i )
|
||||||
|
setColumnWidth( i, PixelDensity::dpToPx( values[i] ) );
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void UIAbstractTableView::restorePendingColumnWidths() {
|
||||||
|
if ( !getModel() || mPendingSerializedColumnWidths.empty() )
|
||||||
|
return;
|
||||||
|
std::string serialized( std::move( mPendingSerializedColumnWidths ) );
|
||||||
|
mPendingSerializedColumnWidths.clear();
|
||||||
|
auto widths = nlohmann::json::parse( serialized, nullptr, false, true );
|
||||||
|
if ( !widths.is_discarded() )
|
||||||
|
unserializeColumnWidths( widths );
|
||||||
|
}
|
||||||
|
|
||||||
void UIAbstractTableView::selectAll() {
|
void UIAbstractTableView::selectAll() {
|
||||||
getSelection().clear();
|
getSelection().clear();
|
||||||
for ( size_t itemIndex = 0; itemIndex < getItemCount(); ++itemIndex ) {
|
for ( size_t itemIndex = 0; itemIndex < getItemCount(); ++itemIndex ) {
|
||||||
@@ -139,11 +300,13 @@ void UIAbstractTableView::onModelUpdate( unsigned flags ) {
|
|||||||
[this] {
|
[this] {
|
||||||
modelUpdate( mPendingUpdateFlags.exchange( 0 ) );
|
modelUpdate( mPendingUpdateFlags.exchange( 0 ) );
|
||||||
createOrUpdateColumns( true );
|
createOrUpdateColumns( true );
|
||||||
|
restorePendingColumnWidths();
|
||||||
},
|
},
|
||||||
Time::Zero, onModelUpdateTag );
|
Time::Zero, onModelUpdateTag );
|
||||||
} else {
|
} else {
|
||||||
UIAbstractView::onModelUpdate( flags );
|
UIAbstractView::onModelUpdate( flags );
|
||||||
createOrUpdateColumns( true );
|
createOrUpdateColumns( true );
|
||||||
|
restorePendingColumnWidths();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -196,6 +359,9 @@ void UIAbstractTableView::createOrUpdateColumns( bool resetColumnData ) {
|
|||||||
col.widget->setPixelsSize( col.width, getHeaderHeight() );
|
col.widget->setPixelsSize( col.width, getHeaderHeight() );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ( mColumnWidthMode == ColumnWidthMode::Percentage )
|
||||||
|
updatePercentageColumnWidths();
|
||||||
|
|
||||||
if ( mAutoColumnsWidth && visibleColCount > 1 ) {
|
if ( mAutoColumnsWidth && visibleColCount > 1 ) {
|
||||||
Float contentWidth = getContentSpaceWidth();
|
Float contentWidth = getContentSpaceWidth();
|
||||||
bool shouldVScrollBeVisible = shouldVerticalScrollBeVisible();
|
bool shouldVScrollBeVisible = shouldVerticalScrollBeVisible();
|
||||||
@@ -311,9 +477,92 @@ void UIAbstractTableView::onSizeChange() {
|
|||||||
createOrUpdateColumns( false );
|
createOrUpdateColumns( false );
|
||||||
}
|
}
|
||||||
|
|
||||||
void UIAbstractTableView::onColumnSizeChange( const size_t&, bool fromUserInteraction ) {
|
void UIAbstractTableView::onColumnSizeChange( const size_t& colIndex, bool fromUserInteraction ) {
|
||||||
if ( fromUserInteraction && mAutoColumnsWidth )
|
if ( fromUserInteraction && mAutoColumnsWidth )
|
||||||
mAutoColumnsWidth = false;
|
mAutoColumnsWidth = false;
|
||||||
|
if ( fromUserInteraction && mColumnWidthMode == ColumnWidthMode::Percentage && getModel() ) {
|
||||||
|
int adjacent = adjacentVisibleColumn( colIndex );
|
||||||
|
Float contentWidth = getContentSpaceWidth();
|
||||||
|
if ( adjacent >= 0 && contentWidth > 0 ) {
|
||||||
|
auto& column = columnData( colIndex );
|
||||||
|
auto& sibling = columnData( adjacent );
|
||||||
|
Float combined = column.percentage + sibling.percentage;
|
||||||
|
Float minPercentage = column.minWidth / contentWidth * 100.f;
|
||||||
|
Float siblingMinPercentage = sibling.minWidth / contentWidth * 100.f;
|
||||||
|
column.percentage = eeclamp( column.width / contentWidth * 100.f, minPercentage,
|
||||||
|
combined - siblingMinPercentage );
|
||||||
|
sibling.percentage = combined - column.percentage;
|
||||||
|
updatePercentageColumnWidths();
|
||||||
|
updateHeaderSize();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void UIAbstractTableView::updatePercentageColumnWidths() {
|
||||||
|
if ( !getModel() )
|
||||||
|
return;
|
||||||
|
Float contentWidth = getContentSpaceWidth();
|
||||||
|
Float totalPercentage = 0;
|
||||||
|
int visibleColumns = 0;
|
||||||
|
for ( size_t i = 0; i < getModel()->columnCount(); ++i ) {
|
||||||
|
if ( !isColumnHidden( i ) ) {
|
||||||
|
totalPercentage += columnData( i ).percentage;
|
||||||
|
visibleColumns++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ( totalPercentage <= 0 && visibleColumns > 0 ) {
|
||||||
|
for ( size_t i = 0; i < getModel()->columnCount(); ++i )
|
||||||
|
if ( !isColumnHidden( i ) )
|
||||||
|
columnData( i ).percentage = 100.f / visibleColumns;
|
||||||
|
totalPercentage = 100.f;
|
||||||
|
}
|
||||||
|
contentWidth = eefloor( contentWidth );
|
||||||
|
Float cumulativePercentage = 0;
|
||||||
|
Float previousBoundary = 0;
|
||||||
|
Float assignedWidth = 0;
|
||||||
|
for ( size_t i = 0; i < getModel()->columnCount(); ++i ) {
|
||||||
|
if ( isColumnHidden( i ) )
|
||||||
|
continue;
|
||||||
|
auto& column = columnData( i );
|
||||||
|
cumulativePercentage += column.percentage;
|
||||||
|
Float boundary = eefloor( contentWidth * cumulativePercentage / totalPercentage + 0.5f );
|
||||||
|
Float width = boundary - previousBoundary;
|
||||||
|
Float minWidth = eeceil( column.minWidth );
|
||||||
|
width = column.maxWidth != 0
|
||||||
|
? eeclamp( width, minWidth, eemax( minWidth, eefloor( column.maxWidth ) ) )
|
||||||
|
: eemax( width, minWidth );
|
||||||
|
column.setWidth( width, true );
|
||||||
|
assignedWidth += width;
|
||||||
|
previousBoundary = boundary;
|
||||||
|
}
|
||||||
|
Float overflow = assignedWidth - contentWidth;
|
||||||
|
for ( size_t i = getModel()->columnCount(); overflow > 0 && i > 0; --i ) {
|
||||||
|
if ( isColumnHidden( i - 1 ) )
|
||||||
|
continue;
|
||||||
|
auto& column = columnData( i - 1 );
|
||||||
|
Float shrink = eemin( overflow, column.width - eeceil( column.minWidth ) );
|
||||||
|
if ( shrink > 0 ) {
|
||||||
|
column.setWidth( column.width - shrink, true );
|
||||||
|
overflow -= shrink;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for ( size_t i = 0; i < getModel()->columnCount(); ++i ) {
|
||||||
|
auto& column = columnData( i );
|
||||||
|
if ( column.widget && !isColumnHidden( i ) )
|
||||||
|
column.widget->setPixelsSize( column.width, getHeaderHeight() );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int UIAbstractTableView::adjacentVisibleColumn( size_t column ) const {
|
||||||
|
if ( !getModel() )
|
||||||
|
return -1;
|
||||||
|
for ( size_t i = column + 1; i < getModel()->columnCount(); ++i )
|
||||||
|
if ( !isColumnHidden( i ) )
|
||||||
|
return i;
|
||||||
|
for ( size_t i = column; i > 0; --i )
|
||||||
|
if ( !isColumnHidden( i - 1 ) )
|
||||||
|
return i - 1;
|
||||||
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
Float UIAbstractTableView::getMaxColumnContentWidth( const size_t&, bool ) {
|
Float UIAbstractTableView::getMaxColumnContentWidth( const size_t&, bool ) {
|
||||||
@@ -322,6 +571,10 @@ Float UIAbstractTableView::getMaxColumnContentWidth( const size_t&, bool ) {
|
|||||||
|
|
||||||
void UIAbstractTableView::onColumnResizeToContent( const size_t& colIndex ) {
|
void UIAbstractTableView::onColumnResizeToContent( const size_t& colIndex ) {
|
||||||
columnData( colIndex ).setWidth( getMaxColumnContentWidth( colIndex, true ) );
|
columnData( colIndex ).setWidth( getMaxColumnContentWidth( colIndex, true ) );
|
||||||
|
if ( mColumnWidthMode == ColumnWidthMode::Percentage ) {
|
||||||
|
onColumnSizeChange( colIndex, true );
|
||||||
|
return;
|
||||||
|
}
|
||||||
createOrUpdateColumns( false );
|
createOrUpdateColumns( false );
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -582,6 +835,31 @@ void UIAbstractTableView::onScrollChange() {
|
|||||||
mHeader->setPixelsPosition( mRowHeaderWidth + -mScrollOffset.x, 0 );
|
mHeader->setPixelsPosition( mRowHeaderWidth + -mScrollOffset.x, 0 );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void UIAbstractTableView::onContentSizeChange() {
|
||||||
|
if ( mUpdatingColumnsForScrollbars ) {
|
||||||
|
UIScrollableWidget::onContentSizeChange();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool verticalScrollWasVisible = mVScroll->isVisible();
|
||||||
|
UIScrollableWidget::onContentSizeChange();
|
||||||
|
const bool columnsDependOnContentWidth =
|
||||||
|
mColumnWidthMode == ColumnWidthMode::Percentage || mAutoColumnsWidth ||
|
||||||
|
( mAutoExpandOnSingleColumn && visibleColumnCount() == 1 );
|
||||||
|
if ( !columnsDependOnContentWidth || verticalScrollWasVisible == mVScroll->isVisible() )
|
||||||
|
return;
|
||||||
|
|
||||||
|
mUpdatingColumnsForScrollbars = true;
|
||||||
|
for ( int iteration = 0; iteration < 2; ++iteration ) {
|
||||||
|
bool visibilityUsedForColumns = mVScroll->isVisible();
|
||||||
|
createOrUpdateColumns( false );
|
||||||
|
UIScrollableWidget::onContentSizeChange();
|
||||||
|
if ( visibilityUsedForColumns == mVScroll->isVisible() )
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
mUpdatingColumnsForScrollbars = false;
|
||||||
|
}
|
||||||
|
|
||||||
void UIAbstractTableView::bindNavigationClick( UIWidget* widget ) {
|
void UIAbstractTableView::bindNavigationClick( UIWidget* widget ) {
|
||||||
mWidgetsClickCbId[widget].push_back(
|
mWidgetsClickCbId[widget].push_back(
|
||||||
widget->on( Event::MouseDoubleClick, [this]( const Event* event ) {
|
widget->on( Event::MouseDoubleClick, [this]( const Event* event ) {
|
||||||
@@ -1012,6 +1290,14 @@ bool UIAbstractTableView::applyProperty( const StyleSheetProperty& attribute ) {
|
|||||||
case PropertyId::MainColumn:
|
case PropertyId::MainColumn:
|
||||||
setMainColumn( attribute.asInt() );
|
setMainColumn( attribute.asInt() );
|
||||||
break;
|
break;
|
||||||
|
case PropertyId::ColumnWidthMode:
|
||||||
|
setColumnWidthMode( String::iequals( attribute.getValue(), "percentage" )
|
||||||
|
? ColumnWidthMode::Percentage
|
||||||
|
: ColumnWidthMode::Pixels );
|
||||||
|
break;
|
||||||
|
case PropertyId::ColumnWidthModeMenu:
|
||||||
|
setColumnWidthModeMenuEnabled( attribute.asBool() );
|
||||||
|
break;
|
||||||
case PropertyId::RowHeaderWidth:
|
case PropertyId::RowHeaderWidth:
|
||||||
setRowHeaderWidth(
|
setRowHeaderWidth(
|
||||||
lengthFromValue( attribute.getValue(), PropertyRelativeTarget::None ) );
|
lengthFromValue( attribute.getValue(), PropertyRelativeTarget::None ) );
|
||||||
@@ -1063,6 +1349,10 @@ std::string UIAbstractTableView::getPropertyString( const PropertyDefinition* pr
|
|||||||
return String::fromFloat( (Float)getSortIconSize(), "px" );
|
return String::fromFloat( (Float)getSortIconSize(), "px" );
|
||||||
case PropertyId::MainColumn:
|
case PropertyId::MainColumn:
|
||||||
return String::toString( (Int64)getMainColumn() );
|
return String::toString( (Int64)getMainColumn() );
|
||||||
|
case PropertyId::ColumnWidthMode:
|
||||||
|
return getColumnWidthMode() == ColumnWidthMode::Percentage ? "percentage" : "pixels";
|
||||||
|
case PropertyId::ColumnWidthModeMenu:
|
||||||
|
return isColumnWidthModeMenuEnabled() ? "true" : "false";
|
||||||
case PropertyId::RowHeaderWidth:
|
case PropertyId::RowHeaderWidth:
|
||||||
return String::fromFloat( getRowHeaderWidth(), "px" );
|
return String::fromFloat( getRowHeaderWidth(), "px" );
|
||||||
case PropertyId::TableFlags: {
|
case PropertyId::TableFlags: {
|
||||||
@@ -1099,9 +1389,10 @@ std::string UIAbstractTableView::getPropertyString( const PropertyDefinition* pr
|
|||||||
|
|
||||||
std::vector<PropertyId> UIAbstractTableView::getPropertiesImplemented() const {
|
std::vector<PropertyId> UIAbstractTableView::getPropertiesImplemented() const {
|
||||||
auto props = UIAbstractView::getPropertiesImplemented();
|
auto props = UIAbstractView::getPropertiesImplemented();
|
||||||
props.insert( props.end(),
|
props.insert( props.end(), { PropertyId::RowHeight, PropertyId::IconSize,
|
||||||
{ PropertyId::RowHeight, PropertyId::IconSize, PropertyId::SortIconSize,
|
PropertyId::SortIconSize, PropertyId::MainColumn,
|
||||||
PropertyId::MainColumn, PropertyId::RowHeaderWidth, PropertyId::TableFlags } );
|
PropertyId::ColumnWidthMode, PropertyId::ColumnWidthModeMenu,
|
||||||
|
PropertyId::RowHeaderWidth, PropertyId::TableFlags } );
|
||||||
return props;
|
return props;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -336,6 +336,10 @@ void StyleSheetSpecification::registerDefaultProperties() {
|
|||||||
.setType( PropertyType::NumberLength );
|
.setType( PropertyType::NumberLength );
|
||||||
registerProperty( PropertyId::MainColumn, "main-column", "0" )
|
registerProperty( PropertyId::MainColumn, "main-column", "0" )
|
||||||
.setType( PropertyType::NumberInt );
|
.setType( PropertyType::NumberInt );
|
||||||
|
registerProperty( PropertyId::ColumnWidthMode, "column-width-mode", "pixels" )
|
||||||
|
.setType( PropertyType::String );
|
||||||
|
registerProperty( PropertyId::ColumnWidthModeMenu, "column-width-mode-menu", "false" )
|
||||||
|
.setType( PropertyType::Bool );
|
||||||
registerProperty( PropertyId::RowHeaderWidth, "row-header-width", "" )
|
registerProperty( PropertyId::RowHeaderWidth, "row-header-width", "" )
|
||||||
.setType( PropertyType::NumberLength );
|
.setType( PropertyType::NumberLength );
|
||||||
registerProperty( PropertyId::TableFlags, "table-flags", "" )
|
registerProperty( PropertyId::TableFlags, "table-flags", "" )
|
||||||
@@ -590,6 +594,8 @@ void StyleSheetSpecification::registerDefaultProperties() {
|
|||||||
.setRelativeTarget( PropertyRelativeTarget::LocalBlockWidth );
|
.setRelativeTarget( PropertyRelativeTarget::LocalBlockWidth );
|
||||||
registerProperty( PropertyId::SplitterAlwaysShow, "splitter-always-show", "true" )
|
registerProperty( PropertyId::SplitterAlwaysShow, "splitter-always-show", "true" )
|
||||||
.setType( PropertyType::Bool );
|
.setType( PropertyType::Bool );
|
||||||
|
registerProperty( PropertyId::SplitterHideOnEdge, "splitter-hide-on-edge", "false" )
|
||||||
|
.setType( PropertyType::Bool );
|
||||||
|
|
||||||
registerProperty( PropertyId::DroppableHoveringColor, "droppable-hovering-color", "#FFFFFF20" )
|
registerProperty( PropertyId::DroppableHoveringColor, "droppable-hovering-color", "#FFFFFF20" )
|
||||||
.setType( PropertyType::Color );
|
.setType( PropertyType::Color );
|
||||||
|
|||||||
@@ -1926,6 +1926,8 @@ void UICodeEditorSplitter::onTabClosed( const TabEvent* tabEvent ) {
|
|||||||
Node* remainingNode = tabWidget == splitter->getFirstWidget()
|
Node* remainingNode = tabWidget == splitter->getFirstWidget()
|
||||||
? splitter->getLastWidget()
|
? splitter->getLastWidget()
|
||||||
: splitter->getFirstWidget();
|
: splitter->getFirstWidget();
|
||||||
|
// Keep the surviving subtree out of closeSplitter()'s recursive bookkeeping.
|
||||||
|
remainingNode->detach();
|
||||||
closeSplitter( splitter );
|
closeSplitter( splitter );
|
||||||
eeASSERT( parent->getChildCount() == 0 );
|
eeASSERT( parent->getChildCount() == 0 );
|
||||||
remainingNode->setParent( parent );
|
remainingNode->setParent( parent );
|
||||||
|
|||||||
@@ -17,7 +17,13 @@ UITabWidgetSplitter::UITabWidgetSplitter( UITabWidgetSplitter::Client* client,
|
|||||||
UISceneNode* sceneNode ) :
|
UISceneNode* sceneNode ) :
|
||||||
mUISceneNode( sceneNode ), mClient( client ) {}
|
mUISceneNode( sceneNode ), mClient( client ) {}
|
||||||
|
|
||||||
UITabWidgetSplitter::~UITabWidgetSplitter() {}
|
UITabWidgetSplitter::~UITabWidgetSplitter() {
|
||||||
|
mTabWidgetEventConnections.clear();
|
||||||
|
mWidgetEventConnections.clear();
|
||||||
|
mTabWidgets.clear();
|
||||||
|
mCurWidget = nullptr;
|
||||||
|
mClient = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
UITabWidget* UITabWidgetSplitter::tabWidgetFromWidget( UIWidget* widget ) const {
|
UITabWidget* UITabWidgetSplitter::tabWidgetFromWidget( UIWidget* widget ) const {
|
||||||
if ( widget )
|
if ( widget )
|
||||||
@@ -49,15 +55,15 @@ void UITabWidgetSplitter::setCurrentWidget( UIWidget* curWidget ) {
|
|||||||
std::pair<UITab*, UIWidget*>
|
std::pair<UITab*, UIWidget*>
|
||||||
UITabWidgetSplitter::createWidgetInTabWidget( UITabWidget* tabWidget, UIWidget* widget,
|
UITabWidgetSplitter::createWidgetInTabWidget( UITabWidget* tabWidget, UIWidget* widget,
|
||||||
const std::string& tabName, bool focus ) {
|
const std::string& tabName, bool focus ) {
|
||||||
eeASSERT( curWidgetExists() );
|
|
||||||
if ( nullptr == tabWidget )
|
if ( nullptr == tabWidget )
|
||||||
return std::make_pair( (UITab*)nullptr, (UIWidget*)nullptr );
|
return std::make_pair( (UITab*)nullptr, (UIWidget*)nullptr );
|
||||||
UITab* tab = tabWidget->add( tabName, widget );
|
UITab* tab = tabWidget->add( tabName, widget );
|
||||||
widget->setData( (UintPtr)tab );
|
widget->setData( (UintPtr)tab );
|
||||||
widget->on( Event::OnFocusWithin, [this]( const Event* event ) {
|
auto& connections = mWidgetEventConnections[widget];
|
||||||
|
connections += widget->connect( Event::OnFocusWithin, [this]( const Event* event ) {
|
||||||
setCurrentWidget( event->getNode()->asType<UIWidget>() );
|
setCurrentWidget( event->getNode()->asType<UIWidget>() );
|
||||||
} );
|
} );
|
||||||
widget->on( Event::OnTitleChange, [this]( const Event* event ) {
|
connections += widget->connect( Event::OnTitleChange, [this]( const Event* event ) {
|
||||||
const TextEvent* tevent = static_cast<const TextEvent*>( event );
|
const TextEvent* tevent = static_cast<const TextEvent*>( event );
|
||||||
UIWidget* widget = event->getNode()->asType<UIWidget>();
|
UIWidget* widget = event->getNode()->asType<UIWidget>();
|
||||||
UITabWidget* tabWidget = tabWidgetFromWidget( widget );
|
UITabWidget* tabWidget = tabWidgetFromWidget( widget );
|
||||||
@@ -157,7 +163,8 @@ UITabWidget* UITabWidgetSplitter::createTabWidget( Node* parent ) {
|
|||||||
},
|
},
|
||||||
mVisualSplitEdgePercent );
|
mVisualSplitEdgePercent );
|
||||||
}
|
}
|
||||||
tabWidget->on( Event::OnTabSelected, [this]( const Event* event ) {
|
auto& connections = mTabWidgetEventConnections[tabWidget];
|
||||||
|
connections += tabWidget->connect( Event::OnTabSelected, [this]( const Event* event ) {
|
||||||
UITabWidget* tabWidget = event->getNode()->asType<UITabWidget>();
|
UITabWidget* tabWidget = event->getNode()->asType<UITabWidget>();
|
||||||
eeASSERT( nullptr != tabWidget && nullptr != tabWidget->getTabSelected() &&
|
eeASSERT( nullptr != tabWidget && nullptr != tabWidget->getTabSelected() &&
|
||||||
nullptr != tabWidget->getTabSelected()->getOwnedWidget() );
|
nullptr != tabWidget->getTabSelected()->getOwnedWidget() );
|
||||||
@@ -175,7 +182,7 @@ UITabWidget* UITabWidgetSplitter::createTabWidget( Node* parent ) {
|
|||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
} );
|
} );
|
||||||
tabWidget->on( Event::OnTabClosed, [this]( const Event* event ) {
|
connections += tabWidget->connect( Event::OnTabClosed, [this]( const Event* event ) {
|
||||||
onTabClosed( static_cast<const TabEvent*>( event ) );
|
onTabClosed( static_cast<const TabEvent*>( event ) );
|
||||||
} );
|
} );
|
||||||
if ( mOnTabWidgetCreateCb )
|
if ( mOnTabWidgetCreateCb )
|
||||||
@@ -285,6 +292,7 @@ void UITabWidgetSplitter::closeTab( UIWidget* widget,
|
|||||||
UITabWidget::FocusTabBehavior focusTabBehavior ) {
|
UITabWidget::FocusTabBehavior focusTabBehavior ) {
|
||||||
if ( widget ) {
|
if ( widget ) {
|
||||||
UITabWidget* tabWidget = tabWidgetFromWidget( widget );
|
UITabWidget* tabWidget = tabWidgetFromWidget( widget );
|
||||||
|
mWidgetEventConnections.erase( widget );
|
||||||
if ( tabWidget )
|
if ( tabWidget )
|
||||||
tabWidget->removeTab( (UITab*)widget->getData(), true, false, focusTabBehavior );
|
tabWidget->removeTab( (UITab*)widget->getData(), true, false, focusTabBehavior );
|
||||||
if ( mCurWidget == widget )
|
if ( mCurWidget == widget )
|
||||||
@@ -497,9 +505,12 @@ void UITabWidgetSplitter::closeTabWidgets( UISplitter* splitter ) {
|
|||||||
Node* node = splitter->getFirstChild();
|
Node* node = splitter->getFirstChild();
|
||||||
while ( node ) {
|
while ( node ) {
|
||||||
if ( node->isType( UI_TYPE_TABWIDGET ) ) {
|
if ( node->isType( UI_TYPE_TABWIDGET ) ) {
|
||||||
auto it =
|
auto tabWidget = node->asType<UITabWidget>();
|
||||||
std::find( mTabWidgets.begin(), mTabWidgets.end(), node->asType<UITabWidget>() );
|
auto it = std::find( mTabWidgets.begin(), mTabWidgets.end(), tabWidget );
|
||||||
if ( it != mTabWidgets.end() ) {
|
if ( it != mTabWidgets.end() ) {
|
||||||
|
if ( mOnTabWidgetCloseCb )
|
||||||
|
mOnTabWidgetCloseCb( tabWidget );
|
||||||
|
mTabWidgetEventConnections.erase( tabWidget );
|
||||||
Lock l( mTabWidgetMutex );
|
Lock l( mTabWidgetMutex );
|
||||||
mTabWidgets.erase( it );
|
mTabWidgets.erase( it );
|
||||||
}
|
}
|
||||||
@@ -622,6 +633,9 @@ void UITabWidgetSplitter::onTabClosed( const TabEvent* tabEvent ) {
|
|||||||
if ( tabWidget->getTabCount() == 0 ) {
|
if ( tabWidget->getTabCount() == 0 ) {
|
||||||
UISplitter* splitter = splitterFromWidget( widget );
|
UISplitter* splitter = splitterFromWidget( widget );
|
||||||
if ( splitter && splitter->isFull() ) {
|
if ( splitter && splitter->isFull() ) {
|
||||||
|
if ( mOnTabWidgetCloseCb )
|
||||||
|
mOnTabWidgetCloseCb( tabWidget );
|
||||||
|
mTabWidgetEventConnections.erase( tabWidget );
|
||||||
tabWidget->close();
|
tabWidget->close();
|
||||||
auto itWidget = std::find( mTabWidgets.begin(), mTabWidgets.end(), tabWidget );
|
auto itWidget = std::find( mTabWidgets.begin(), mTabWidgets.end(), tabWidget );
|
||||||
if ( itWidget != mTabWidgets.end() ) {
|
if ( itWidget != mTabWidgets.end() ) {
|
||||||
@@ -647,6 +661,7 @@ void UITabWidgetSplitter::onTabClosed( const TabEvent* tabEvent ) {
|
|||||||
Node* remainingNode = tabWidget == splitter->getFirstWidget()
|
Node* remainingNode = tabWidget == splitter->getFirstWidget()
|
||||||
? splitter->getLastWidget()
|
? splitter->getLastWidget()
|
||||||
: splitter->getFirstWidget();
|
: splitter->getFirstWidget();
|
||||||
|
remainingNode->detach();
|
||||||
closeSplitter( splitter );
|
closeSplitter( splitter );
|
||||||
eeASSERT( parent->getChildCount() == 0 );
|
eeASSERT( parent->getChildCount() == 0 );
|
||||||
remainingNode->setParent( parent );
|
remainingNode->setParent( parent );
|
||||||
@@ -676,6 +691,10 @@ void UITabWidgetSplitter::setOnTabWidgetCreateCb( std::function<void( UITabWidge
|
|||||||
mOnTabWidgetCreateCb = std::move( cb );
|
mOnTabWidgetCreateCb = std::move( cb );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void UITabWidgetSplitter::setOnTabWidgetCloseCb( std::function<void( UITabWidget* )> cb ) {
|
||||||
|
mOnTabWidgetCloseCb = std::move( cb );
|
||||||
|
}
|
||||||
|
|
||||||
bool UITabWidgetSplitter::getVisualSplitting() const {
|
bool UITabWidgetSplitter::getVisualSplitting() const {
|
||||||
return mVisualSplitting;
|
return mVisualSplitting;
|
||||||
}
|
}
|
||||||
|
|||||||
+40
-12
@@ -12,29 +12,33 @@ UISplitter::UISplitter() :
|
|||||||
UILayout( "splitter" ),
|
UILayout( "splitter" ),
|
||||||
mOrientation( UIOrientation::Horizontal ),
|
mOrientation( UIOrientation::Horizontal ),
|
||||||
mAlwaysShowSplitter( true ),
|
mAlwaysShowSplitter( true ),
|
||||||
|
mHideSplitterOnEdge( false ),
|
||||||
mSplitPartition( StyleSheetLength( "50%" ) ),
|
mSplitPartition( StyleSheetLength( "50%" ) ),
|
||||||
mFirstWidget( NULL ),
|
mFirstWidget( NULL ),
|
||||||
mLastWidget( NULL ) {
|
mLastWidget( NULL ) {
|
||||||
mFlags |= UI_OWNS_CHILDREN_POSITION;
|
mFlags |= UI_OWNS_CHILDREN_POSITION;
|
||||||
mSplitter = UIWidget::NewWithTag( "splitter::separator" );
|
mSplitter = UIWidget::NewWithTag( "splitter::separator" );
|
||||||
mSplitter->setDragEnabled( true );
|
mSplitter->setDragEnabled( true );
|
||||||
mSplitter->on( Event::OnDragStart,
|
mEventConnections += mSplitter->connect( Event::OnDragStart, [this]( const Event* ) {
|
||||||
[this]( const Event* ) { mSplitter->pushState( UIState::StateSelected ); } );
|
mSplitter->pushState( UIState::StateSelected );
|
||||||
mSplitter->on( Event::OnDragStop,
|
} );
|
||||||
[this]( const Event* ) { mSplitter->popState( UIState::StateSelected ); } );
|
mEventConnections += mSplitter->connect( Event::OnDragStop, [this]( const Event* ) {
|
||||||
|
mSplitter->popState( UIState::StateSelected );
|
||||||
|
} );
|
||||||
mSplitter->setParent( this );
|
mSplitter->setParent( this );
|
||||||
mSplitter->setMinWidth( 4 );
|
mSplitter->setMinWidth( 4 );
|
||||||
mSplitter->setMinHeight( 4 );
|
mSplitter->setMinHeight( 4 );
|
||||||
mSplitter->on( Event::OnSizeChange, [this]( const Event* ) {
|
mEventConnections += mSplitter->connect( Event::OnSizeChange, [this]( const Event* ) {
|
||||||
setLayoutDirty( LayoutInvalidation::ContainerLayout );
|
setLayoutDirty( LayoutInvalidation::ContainerLayout );
|
||||||
} );
|
} );
|
||||||
mSplitter->on( Event::MouseEnter, [this]( const Event* ) {
|
mEventConnections += mSplitter->connect( Event::MouseEnter, [this]( const Event* ) {
|
||||||
getUISceneNode()->setCursor( mOrientation == UIOrientation::Horizontal ? Cursor::SizeWE
|
getUISceneNode()->setCursor( mOrientation == UIOrientation::Horizontal ? Cursor::SizeWE
|
||||||
: Cursor::SizeNS );
|
: Cursor::SizeNS );
|
||||||
} );
|
} );
|
||||||
mSplitter->on( Event::MouseLeave,
|
mEventConnections += mSplitter->connect( Event::MouseLeave, [this]( const Event* ) {
|
||||||
[this]( const Event* ) { getUISceneNode()->setCursor( Cursor::Arrow ); } );
|
getUISceneNode()->setCursor( Cursor::Arrow );
|
||||||
mSplitter->on( Event::OnPositionChange, [this]( const Event* ) {
|
} );
|
||||||
|
mEventConnections += mSplitter->connect( Event::OnPositionChange, [this]( const Event* ) {
|
||||||
if ( mSplitter->isDragging() && !mDirtyLayout )
|
if ( mSplitter->isDragging() && !mDirtyLayout )
|
||||||
updateFromDrag();
|
updateFromDrag();
|
||||||
} );
|
} );
|
||||||
@@ -77,6 +81,17 @@ void UISplitter::setAlwaysShowSplitter( bool alwaysShowSplitter ) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const bool& UISplitter::hideSplitterOnEdge() const {
|
||||||
|
return mHideSplitterOnEdge;
|
||||||
|
}
|
||||||
|
|
||||||
|
void UISplitter::setHideSplitterOnEdge( bool hideSplitterOnEdge ) {
|
||||||
|
if ( hideSplitterOnEdge != mHideSplitterOnEdge ) {
|
||||||
|
mHideSplitterOnEdge = hideSplitterOnEdge;
|
||||||
|
setLayoutDirty( LayoutInvalidation::ContainerLayout );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const StyleSheetLength& UISplitter::getSplitPartition() const {
|
const StyleSheetLength& UISplitter::getSplitPartition() const {
|
||||||
return mSplitPartition;
|
return mSplitPartition;
|
||||||
}
|
}
|
||||||
@@ -127,6 +142,9 @@ bool UISplitter::applyProperty( const StyleSheetProperty& attribute ) {
|
|||||||
case PropertyId::SplitterAlwaysShow:
|
case PropertyId::SplitterAlwaysShow:
|
||||||
setAlwaysShowSplitter( attribute.asBool() );
|
setAlwaysShowSplitter( attribute.asBool() );
|
||||||
break;
|
break;
|
||||||
|
case PropertyId::SplitterHideOnEdge:
|
||||||
|
setHideSplitterOnEdge( attribute.asBool() );
|
||||||
|
break;
|
||||||
case PropertyId::Orientation:
|
case PropertyId::Orientation:
|
||||||
setOrientation( String::iequals( attribute.getValue(), "horizontal" )
|
setOrientation( String::iequals( attribute.getValue(), "horizontal" )
|
||||||
? UIOrientation::Horizontal
|
? UIOrientation::Horizontal
|
||||||
@@ -149,6 +167,8 @@ std::string UISplitter::getPropertyString( const PropertyDefinition* propertyDef
|
|||||||
return getSplitPartition().toString();
|
return getSplitPartition().toString();
|
||||||
case PropertyId::SplitterAlwaysShow:
|
case PropertyId::SplitterAlwaysShow:
|
||||||
return alwaysShowSplitter() ? "true" : "false";
|
return alwaysShowSplitter() ? "true" : "false";
|
||||||
|
case PropertyId::SplitterHideOnEdge:
|
||||||
|
return hideSplitterOnEdge() ? "true" : "false";
|
||||||
case PropertyId::Orientation:
|
case PropertyId::Orientation:
|
||||||
return getOrientation() == UIOrientation::Horizontal ? "horizontal" : "vertical";
|
return getOrientation() == UIOrientation::Horizontal ? "horizontal" : "vertical";
|
||||||
default:
|
default:
|
||||||
@@ -159,7 +179,7 @@ std::string UISplitter::getPropertyString( const PropertyDefinition* propertyDef
|
|||||||
std::vector<PropertyId> UISplitter::getPropertiesImplemented() const {
|
std::vector<PropertyId> UISplitter::getPropertiesImplemented() const {
|
||||||
auto props = UIWidget::getPropertiesImplemented();
|
auto props = UIWidget::getPropertiesImplemented();
|
||||||
auto local = { PropertyId::SplitterPartition, PropertyId::SplitterAlwaysShow,
|
auto local = { PropertyId::SplitterPartition, PropertyId::SplitterAlwaysShow,
|
||||||
PropertyId::Orientation };
|
PropertyId::SplitterHideOnEdge, PropertyId::Orientation };
|
||||||
props.insert( props.end(), local.begin(), local.end() );
|
props.insert( props.end(), local.begin(), local.end() );
|
||||||
return props;
|
return props;
|
||||||
}
|
}
|
||||||
@@ -204,7 +224,7 @@ void UISplitter::onChildCountChange( Node* child, const bool& removed ) {
|
|||||||
|
|
||||||
void UISplitter::updateFromDrag() {
|
void UISplitter::updateFromDrag() {
|
||||||
mDirtyLayout = true;
|
mDirtyLayout = true;
|
||||||
mSplitter->setVisible( !mAlwaysShowSplitter && !mLastWidget ? false : true );
|
mSplitter->setVisible( shouldShowSplitter() );
|
||||||
mSplitter->setEnabled( mSplitter->isVisible() );
|
mSplitter->setEnabled( mSplitter->isVisible() );
|
||||||
|
|
||||||
if ( UIOrientation::Horizontal == mOrientation ) {
|
if ( UIOrientation::Horizontal == mOrientation ) {
|
||||||
@@ -351,7 +371,7 @@ void UISplitter::updateLayout() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
mSplitter->setVisible( !mAlwaysShowSplitter && !mLastWidget ? false : true );
|
mSplitter->setVisible( shouldShowSplitter() );
|
||||||
mSplitter->setEnabled( mSplitter->isVisible() );
|
mSplitter->setEnabled( mSplitter->isVisible() );
|
||||||
Float totalSpace = mOrientation == UIOrientation::Horizontal
|
Float totalSpace = mOrientation == UIOrientation::Horizontal
|
||||||
? mSize.getWidth() - mPaddingPx.Left - mPaddingPx.Right
|
? mSize.getWidth() - mPaddingPx.Left - mPaddingPx.Right
|
||||||
@@ -426,6 +446,14 @@ void UISplitter::updateSplitterDragFlags() {
|
|||||||
: UI_DRAG_HORIZONTAL );
|
: UI_DRAG_HORIZONTAL );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool UISplitter::shouldShowSplitter() const {
|
||||||
|
if ( !mLastWidget )
|
||||||
|
return false;
|
||||||
|
return mAlwaysShowSplitter || !mHideSplitterOnEdge ||
|
||||||
|
mSplitPartition.getUnit() != StyleSheetLength::Percentage ||
|
||||||
|
( mSplitPartition.getValue() > 0.f && mSplitPartition.getValue() < 100.f );
|
||||||
|
}
|
||||||
|
|
||||||
Uint32 UISplitter::onMessage( const NodeMessage* Msg ) {
|
Uint32 UISplitter::onMessage( const NodeMessage* Msg ) {
|
||||||
switch ( Msg->getMsg() ) {
|
switch ( Msg->getMsg() ) {
|
||||||
case NodeMessage::LayoutAttributeChange: {
|
case NodeMessage::LayoutAttributeChange: {
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
#include <eepp/ui/abstract/uiabstracttableview.hpp>
|
#include <eepp/ui/abstract/uiabstracttableview.hpp>
|
||||||
|
#include <eepp/ui/uimenuitem.hpp>
|
||||||
|
#include <eepp/ui/uipopupmenu.hpp>
|
||||||
#include <eepp/ui/uiscenenode.hpp>
|
#include <eepp/ui/uiscenenode.hpp>
|
||||||
#include <eepp/ui/uitableheadercolumn.hpp>
|
#include <eepp/ui/uitableheadercolumn.hpp>
|
||||||
|
|
||||||
@@ -31,7 +33,7 @@ Uint32 UITableHeaderColumn::onCalculateDrag( const Vector2f& position, const Uin
|
|||||||
}
|
}
|
||||||
Vector2f pos( eefloor( position.x ), eefloor( position.y ) );
|
Vector2f pos( eefloor( position.x ), eefloor( position.y ) );
|
||||||
if ( mDragPoint != pos && std::abs( mDragPoint.x - pos.x ) > 1.f ) {
|
if ( mDragPoint != pos && std::abs( mDragPoint.x - pos.x ) > 1.f ) {
|
||||||
Sizef dragDiff( ( Float )( mDragPoint.x - pos.x ), 0 );
|
Sizef dragDiff( (Float)( mDragPoint.x - pos.x ), 0 );
|
||||||
if ( onDrag( pos, flags, dragDiff ) ) {
|
if ( onDrag( pos, flags, dragDiff ) ) {
|
||||||
mDragPoint = pos;
|
mDragPoint = pos;
|
||||||
eventDispatcher->setNodeDragging( this );
|
eventDispatcher->setNodeDragging( this );
|
||||||
@@ -77,6 +79,30 @@ Uint32 UITableHeaderColumn::onMouseClick( const Vector2i& position, const Uint32
|
|||||||
return UIPushButton::onMouseClick( position, flags );
|
return UIPushButton::onMouseClick( position, flags );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Uint32 UITableHeaderColumn::onMouseUp( const Vector2i& position, const Uint32& flags ) {
|
||||||
|
if ( ( flags & EE_BUTTON_RMASK ) && mView->isColumnWidthModeMenuEnabled() ) {
|
||||||
|
auto* menu = UIPopUpMenu::New();
|
||||||
|
menu->addRadioButton( i18n( "uitable_fit_columns_to_view", "Fit Columns to View" ),
|
||||||
|
mView->getColumnWidthMode() ==
|
||||||
|
UIAbstractTableView::ColumnWidthMode::Percentage )
|
||||||
|
->setId( "percentage" );
|
||||||
|
menu->addRadioButton( i18n( "uitable_free_column_widths", "Free Column Widths" ),
|
||||||
|
mView->getColumnWidthMode() ==
|
||||||
|
UIAbstractTableView::ColumnWidthMode::Pixels )
|
||||||
|
->setId( "pixels" );
|
||||||
|
menu->on( Event::OnItemClicked, [view = mView]( const Event* event ) {
|
||||||
|
if ( !event->getNode()->isType( UI_TYPE_MENUITEM ) )
|
||||||
|
return;
|
||||||
|
view->setColumnWidthMode( event->getNode()->getId() == "percentage"
|
||||||
|
? UIAbstractTableView::ColumnWidthMode::Percentage
|
||||||
|
: UIAbstractTableView::ColumnWidthMode::Pixels );
|
||||||
|
} );
|
||||||
|
menu->setCloseOnHide( true );
|
||||||
|
menu->showAtScreenPosition( position.asFloat() );
|
||||||
|
}
|
||||||
|
return UIPushButton::onMouseUp( position, flags );
|
||||||
|
}
|
||||||
|
|
||||||
Uint32 UITableHeaderColumn::onDrag( const Vector2f& position, const Uint32&,
|
Uint32 UITableHeaderColumn::onDrag( const Vector2f& position, const Uint32&,
|
||||||
const Sizef& dragDiff ) {
|
const Sizef& dragDiff ) {
|
||||||
Vector2f localPos( convertToNodeSpace( position ) );
|
Vector2f localPos( convertToNodeSpace( position ) );
|
||||||
|
|||||||
@@ -87,8 +87,10 @@ UITabWidget::UITabWidget() :
|
|||||||
mTabScroll = UIScrollBar::NewHorizontalWithTag( "scrollbarmini" );
|
mTabScroll = UIScrollBar::NewHorizontalWithTag( "scrollbarmini" );
|
||||||
mTabScroll->setParent( mTabBar );
|
mTabScroll->setParent( mTabBar );
|
||||||
mTabScroll->setLayoutSizePolicy( SizePolicy::Fixed, SizePolicy::WrapContent );
|
mTabScroll->setLayoutSizePolicy( SizePolicy::Fixed, SizePolicy::WrapContent );
|
||||||
mTabScroll->on( Event::OnSizeChange, [this]( const Event* ) { updateScrollBar(); } );
|
mEventConnections +=
|
||||||
mTabScroll->on( Event::OnValueChange, [this]( const Event* ) { updateScroll(); } );
|
mTabScroll->connect( Event::OnSizeChange, [this]( const Event* ) { updateScrollBar(); } );
|
||||||
|
mEventConnections +=
|
||||||
|
mTabScroll->connect( Event::OnValueChange, [this]( const Event* ) { updateScroll(); } );
|
||||||
|
|
||||||
onSizeChange();
|
onSizeChange();
|
||||||
|
|
||||||
@@ -560,6 +562,10 @@ UITab* UITabWidget::add( const String& text, UINode* nodeOwned, DrawablePtr icon
|
|||||||
|
|
||||||
UITabWidget* UITabWidget::add( UITab* tab ) {
|
UITabWidget* UITabWidget::add( UITab* tab ) {
|
||||||
tab->setParent( mTabBar );
|
tab->setParent( mTabBar );
|
||||||
|
if ( tab->getOwnedWidget() && tab->getOwnedWidget()->isWidget() )
|
||||||
|
tab->getOwnedWidget()->asType<UIWidget>()->setLayoutSizePolicy( SizePolicy::Fixed,
|
||||||
|
SizePolicy::Fixed );
|
||||||
|
refreshOwnedWidget( tab );
|
||||||
|
|
||||||
mTabs.push_back( tab );
|
mTabs.push_back( tab );
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,8 @@
|
|||||||
#include <eepp/system/sys.hpp>
|
#include <eepp/system/sys.hpp>
|
||||||
#include <eepp/ui/tools/uitabwidgetsplitter.hpp>
|
#include <eepp/ui/tools/uitabwidgetsplitter.hpp>
|
||||||
#include <eepp/ui/uiapplication.hpp>
|
#include <eepp/ui/uiapplication.hpp>
|
||||||
|
#include <eepp/ui/uiscrollbar.hpp>
|
||||||
|
#include <eepp/ui/uitableview.hpp>
|
||||||
#include <eepp/ui/uiwidget.hpp>
|
#include <eepp/ui/uiwidget.hpp>
|
||||||
#include <nlohmann/json.hpp>
|
#include <nlohmann/json.hpp>
|
||||||
|
|
||||||
@@ -21,6 +23,415 @@ class TestClient : public UITabWidgetSplitter::Client {
|
|||||||
void onWidgetFocusChange( UIWidget* ) override { focusChangeCount++; }
|
void onWidgetFocusChange( UIWidget* ) override { focusChangeCount++; }
|
||||||
};
|
};
|
||||||
|
|
||||||
|
class TransferTestTabWidget : public UITabWidget {
|
||||||
|
public:
|
||||||
|
static TransferTestTabWidget* New() { return eeNew( TransferTestTabWidget, () ); }
|
||||||
|
|
||||||
|
void detachTab( UITab* tab ) { removeTab( tab, false, false, false ); }
|
||||||
|
|
||||||
|
const Sizef& getContainerPixelsSize() const { return mNodeContainer->getPixelsSize(); }
|
||||||
|
};
|
||||||
|
|
||||||
|
class ThreeColumnModel : public Model {
|
||||||
|
public:
|
||||||
|
explicit ThreeColumnModel( size_t rows = 1 ) : mRows( rows ) {}
|
||||||
|
|
||||||
|
size_t rowCount( const ModelIndex& = ModelIndex() ) const override { return mRows; }
|
||||||
|
size_t columnCount( const ModelIndex& = ModelIndex() ) const override { return 3; }
|
||||||
|
std::string columnName( const size_t& column ) const override {
|
||||||
|
return String::format( "Column %zu", column );
|
||||||
|
}
|
||||||
|
ModelIndex index( int row, int column,
|
||||||
|
const ModelIndex& parent = ModelIndex() ) const override {
|
||||||
|
return row == 0 && column >= 0 && column < 3 && !parent.isValid()
|
||||||
|
? createIndex( row, column )
|
||||||
|
: ModelIndex{};
|
||||||
|
}
|
||||||
|
Variant data( const ModelIndex&, ModelRole = ModelRole::Display ) const override {
|
||||||
|
return "Value";
|
||||||
|
}
|
||||||
|
|
||||||
|
protected:
|
||||||
|
size_t mRows;
|
||||||
|
};
|
||||||
|
|
||||||
|
class PercentageTestTable : public UITableView {
|
||||||
|
public:
|
||||||
|
static PercentageTestTable* New() { return eeNew( PercentageTestTable, () ); }
|
||||||
|
|
||||||
|
void userResizeColumn( size_t column, Float width ) {
|
||||||
|
columnData( column ).setWidth( width, true );
|
||||||
|
onColumnSizeChange( column, true );
|
||||||
|
}
|
||||||
|
|
||||||
|
void setColumnMinimumWidth( size_t column, Float width ) {
|
||||||
|
columnData( column ).minWidth = width;
|
||||||
|
createOrUpdateColumns( false );
|
||||||
|
}
|
||||||
|
|
||||||
|
void resizeColumnToContent( size_t column, Float width ) {
|
||||||
|
mContentWidth = width;
|
||||||
|
onColumnResizeToContent( column );
|
||||||
|
}
|
||||||
|
|
||||||
|
Float getMaxColumnContentWidth( const size_t&, bool ) override { return mContentWidth; }
|
||||||
|
|
||||||
|
protected:
|
||||||
|
Float mContentWidth{ 0 };
|
||||||
|
};
|
||||||
|
|
||||||
|
UTEST( UISplitter, HideSplitterOnEdge ) {
|
||||||
|
UIApplication app(
|
||||||
|
WindowSettings( 800, 600, "eepp - unit tests" ),
|
||||||
|
UIApplication::Settings( Sys::getProcessPath() + ".." + FileSystem::getOSSlash(), 1 ) );
|
||||||
|
|
||||||
|
auto* splitter = UISplitter::New();
|
||||||
|
splitter->setParent( app.getUI() );
|
||||||
|
splitter->setPixelsSize( 800, 600 );
|
||||||
|
UIWidget::New()->setParent( splitter );
|
||||||
|
UIWidget::New()->setParent( splitter );
|
||||||
|
auto* separator = splitter->findByTag( "splitter::separator" );
|
||||||
|
ASSERT_TRUE( separator != nullptr );
|
||||||
|
|
||||||
|
splitter->setHideSplitterOnEdge( true );
|
||||||
|
splitter->setAlwaysShowSplitter( false );
|
||||||
|
splitter->setSplitPartition( StyleSheetLength( "100%" ) );
|
||||||
|
splitter->updateLayout();
|
||||||
|
EXPECT_FALSE( separator->isVisible() );
|
||||||
|
EXPECT_EQ( splitter->getFirstWidget()->getPixelsSize().getWidth(), 800.f );
|
||||||
|
EXPECT_EQ( splitter->getLastWidget()->getPixelsSize().getWidth(), 0.f );
|
||||||
|
|
||||||
|
splitter->setSplitPartition( StyleSheetLength( "75%" ) );
|
||||||
|
splitter->updateLayout();
|
||||||
|
app.getUI()->update( Milliseconds( 16 ) );
|
||||||
|
EXPECT_TRUE( separator->isVisible() );
|
||||||
|
|
||||||
|
splitter->setSplitPartition( StyleSheetLength( "0%" ) );
|
||||||
|
splitter->updateLayout();
|
||||||
|
EXPECT_FALSE( separator->isVisible() );
|
||||||
|
EXPECT_EQ( splitter->getFirstWidget()->getPixelsSize().getWidth(), 0.f );
|
||||||
|
EXPECT_EQ( splitter->getLastWidget()->getPixelsSize().getWidth(), 800.f );
|
||||||
|
|
||||||
|
splitter->setAlwaysShowSplitter( true );
|
||||||
|
splitter->updateLayout();
|
||||||
|
EXPECT_TRUE( separator->isVisible() );
|
||||||
|
}
|
||||||
|
|
||||||
|
UTEST( UISplitter, ControlsMatchParentChildWidth ) {
|
||||||
|
UIApplication app(
|
||||||
|
WindowSettings( 800, 600, "eepp - unit tests" ),
|
||||||
|
UIApplication::Settings( Sys::getProcessPath() + ".." + FileSystem::getOSSlash(), 1 ) );
|
||||||
|
|
||||||
|
auto* splitter = UISplitter::New();
|
||||||
|
splitter->setParent( app.getUI() );
|
||||||
|
splitter->setPixelsSize( 800, 600 );
|
||||||
|
auto* first = UIWidget::New();
|
||||||
|
first->setParent( splitter );
|
||||||
|
auto* last = UIWidget::New();
|
||||||
|
last->setLayoutSizePolicy( SizePolicy::MatchParent, SizePolicy::MatchParent );
|
||||||
|
last->setParent( splitter );
|
||||||
|
splitter->setSplitPartition( StyleSheetLength( "75%" ) );
|
||||||
|
splitter->updateLayout();
|
||||||
|
|
||||||
|
EXPECT_TRUE( first->getPixelsSize().getWidth() > 590.f &&
|
||||||
|
first->getPixelsSize().getWidth() < 600.f );
|
||||||
|
EXPECT_TRUE( last->getPixelsSize().getWidth() > 190.f &&
|
||||||
|
last->getPixelsSize().getWidth() < 200.f );
|
||||||
|
}
|
||||||
|
|
||||||
|
UTEST( UIAbstractTableView, PercentageColumnWidthsScaleAndPreserveAdjacentTotal ) {
|
||||||
|
UIApplication app(
|
||||||
|
WindowSettings( 800, 600, "eepp - unit tests" ),
|
||||||
|
UIApplication::Settings( Sys::getProcessPath() + ".." + FileSystem::getOSSlash(), 1 ) );
|
||||||
|
auto* table = PercentageTestTable::New();
|
||||||
|
table->setParent( app.getUI() );
|
||||||
|
table->setPixelsSize( 400, 200 );
|
||||||
|
table->setModel( std::make_shared<ThreeColumnModel>() );
|
||||||
|
table->setColumnWidth( 0, 100 );
|
||||||
|
table->setColumnWidth( 1, 200 );
|
||||||
|
table->setColumnWidth( 2, 100 );
|
||||||
|
table->setColumnWidthMode( UIAbstractTableView::ColumnWidthMode::Percentage );
|
||||||
|
|
||||||
|
EXPECT_TRUE( std::abs( table->getColumnWidthPercentage( 0 ) - 25.f ) < 0.01f );
|
||||||
|
EXPECT_TRUE( std::abs( table->getColumnWidthPercentage( 1 ) - 50.f ) < 0.01f );
|
||||||
|
EXPECT_TRUE( std::abs( table->getColumnWidthPercentage( 2 ) - 25.f ) < 0.01f );
|
||||||
|
|
||||||
|
table->setPixelsSize( 800, 200 );
|
||||||
|
Float contentWidth = table->getContentSpaceWidth();
|
||||||
|
EXPECT_TRUE( std::abs( table->getColumnWidth( 0 ) - contentWidth * 0.25f ) < 1.f );
|
||||||
|
EXPECT_TRUE( std::abs( table->getColumnWidth( 1 ) - contentWidth * 0.5f ) < 1.f );
|
||||||
|
EXPECT_TRUE( std::abs( table->getColumnWidth( 2 ) - contentWidth * 0.25f ) < 1.f );
|
||||||
|
|
||||||
|
table->userResizeColumn( 0, contentWidth * 0.4f );
|
||||||
|
EXPECT_TRUE( std::abs( table->getColumnWidthPercentage( 0 ) - 40.f ) < 0.01f );
|
||||||
|
EXPECT_TRUE( std::abs( table->getColumnWidthPercentage( 1 ) - 35.f ) < 0.01f );
|
||||||
|
EXPECT_TRUE( std::abs( table->getColumnWidthPercentage( 2 ) - 25.f ) < 0.01f );
|
||||||
|
|
||||||
|
table->setColumnWidthPercentage( 0, 70.f );
|
||||||
|
EXPECT_TRUE( std::abs( table->getColumnWidthPercentage( 0 ) - 70.f ) < 0.01f );
|
||||||
|
EXPECT_TRUE( std::abs( table->getColumnWidthPercentage( 1 ) - 5.f ) < 0.01f );
|
||||||
|
EXPECT_TRUE( std::abs( table->getColumnWidthPercentage( 2 ) - 25.f ) < 0.01f );
|
||||||
|
EXPECT_TRUE( table->getColumnWidth( 0 ) >= 0 && table->getColumnWidth( 1 ) >= 0 &&
|
||||||
|
table->getColumnWidth( 2 ) >= 0 );
|
||||||
|
}
|
||||||
|
|
||||||
|
UTEST( UIAbstractTableView, PercentageColumnWidthsPreserveMinimumWidths ) {
|
||||||
|
UIApplication app(
|
||||||
|
WindowSettings( 800, 600, "eepp - unit tests" ),
|
||||||
|
UIApplication::Settings( Sys::getProcessPath() + ".." + FileSystem::getOSSlash(), 1 ) );
|
||||||
|
auto* table = PercentageTestTable::New();
|
||||||
|
table->setParent( app.getUI() );
|
||||||
|
table->setPixelsSize( 150, 200 );
|
||||||
|
table->setModel( std::make_shared<ThreeColumnModel>() );
|
||||||
|
table->setColumnsWidthPercentage( { 25.f, 50.f, 25.f } );
|
||||||
|
table->setColumnMinimumWidth( 0, 80.f );
|
||||||
|
table->setColumnMinimumWidth( 1, 80.f );
|
||||||
|
table->setColumnMinimumWidth( 2, 80.f );
|
||||||
|
app.getUI()->update( Milliseconds( 16 ) );
|
||||||
|
|
||||||
|
EXPECT_TRUE( table->getColumnWidth( 0 ) >= 80.f );
|
||||||
|
EXPECT_TRUE( table->getColumnWidth( 1 ) >= 80.f );
|
||||||
|
EXPECT_TRUE( table->getColumnWidth( 2 ) >= 80.f );
|
||||||
|
EXPECT_TRUE( table->getColumnWidth( 0 ) + table->getColumnWidth( 1 ) +
|
||||||
|
table->getColumnWidth( 2 ) >
|
||||||
|
table->getContentSpaceWidth() );
|
||||||
|
EXPECT_TRUE( table->getHorizontalScrollBar()->isVisible() );
|
||||||
|
}
|
||||||
|
|
||||||
|
UTEST( UIAbstractTableView, PercentageColumnWidthsAcceptPartialAndSurplusValues ) {
|
||||||
|
UIApplication app(
|
||||||
|
WindowSettings( 800, 600, "eepp - unit tests" ),
|
||||||
|
UIApplication::Settings( Sys::getProcessPath() + ".." + FileSystem::getOSSlash(), 1 ) );
|
||||||
|
auto* table = PercentageTestTable::New();
|
||||||
|
table->setParent( app.getUI() );
|
||||||
|
table->setPixelsSize( 400, 200 );
|
||||||
|
table->setModel( std::make_shared<ThreeColumnModel>() );
|
||||||
|
|
||||||
|
table->setColumnsWidthPercentage( { 20.f, 30.f } );
|
||||||
|
EXPECT_TRUE( std::abs( table->getColumnWidthPercentage( 0 ) - 20.f ) < 0.01f );
|
||||||
|
EXPECT_TRUE( std::abs( table->getColumnWidthPercentage( 1 ) - 30.f ) < 0.01f );
|
||||||
|
EXPECT_TRUE( std::abs( table->getColumnWidthPercentage( 2 ) - 50.f ) < 0.01f );
|
||||||
|
|
||||||
|
table->setColumnsWidthPercentage( { 10.f, 20.f, 70.f, 500.f } );
|
||||||
|
EXPECT_TRUE( std::abs( table->getColumnWidthPercentage( 0 ) - 10.f ) < 0.01f );
|
||||||
|
EXPECT_TRUE( std::abs( table->getColumnWidthPercentage( 1 ) - 20.f ) < 0.01f );
|
||||||
|
EXPECT_TRUE( std::abs( table->getColumnWidthPercentage( 2 ) - 70.f ) < 0.01f );
|
||||||
|
|
||||||
|
EXPECT_TRUE( table->unserializeColumnWidths( nlohmann::json{ 25.f, 25.f } ) );
|
||||||
|
EXPECT_TRUE( std::abs( table->getColumnWidthPercentage( 0 ) - 25.f ) < 0.01f );
|
||||||
|
EXPECT_TRUE( std::abs( table->getColumnWidthPercentage( 1 ) - 25.f ) < 0.01f );
|
||||||
|
EXPECT_TRUE( std::abs( table->getColumnWidthPercentage( 2 ) - 50.f ) < 0.01f );
|
||||||
|
}
|
||||||
|
|
||||||
|
UTEST( UIAbstractTableView, PercentageColumnResizeToContentPreservesAdjacentTotal ) {
|
||||||
|
UIApplication app(
|
||||||
|
WindowSettings( 800, 600, "eepp - unit tests" ),
|
||||||
|
UIApplication::Settings( Sys::getProcessPath() + ".." + FileSystem::getOSSlash(), 1 ) );
|
||||||
|
auto* table = PercentageTestTable::New();
|
||||||
|
table->setParent( app.getUI() );
|
||||||
|
table->setPixelsSize( 400, 200 );
|
||||||
|
table->setModel( std::make_shared<ThreeColumnModel>() );
|
||||||
|
table->setColumnsWidthPercentage( { 25.f, 50.f, 25.f } );
|
||||||
|
const Float contentWidth = table->getContentSpaceWidth();
|
||||||
|
|
||||||
|
table->resizeColumnToContent( 0, contentWidth * 0.4f );
|
||||||
|
|
||||||
|
EXPECT_TRUE( std::abs( table->getColumnWidthPercentage( 0 ) - 40.f ) < 0.01f );
|
||||||
|
EXPECT_TRUE( std::abs( table->getColumnWidthPercentage( 1 ) - 35.f ) < 0.01f );
|
||||||
|
EXPECT_TRUE( std::abs( table->getColumnWidthPercentage( 2 ) - 25.f ) < 0.01f );
|
||||||
|
|
||||||
|
table->setColumnsWidthPercentage( { 25.f, 50.f, 25.f } );
|
||||||
|
table->setColumnMinimumWidth( 1, contentWidth * 0.3f );
|
||||||
|
table->resizeColumnToContent( 0, contentWidth * 0.6f );
|
||||||
|
EXPECT_TRUE( std::abs( table->getColumnWidthPercentage( 0 ) - 45.f ) < 0.01f );
|
||||||
|
EXPECT_TRUE( std::abs( table->getColumnWidthPercentage( 1 ) - 30.f ) < 0.01f );
|
||||||
|
EXPECT_TRUE( std::abs( table->getColumnWidthPercentage( 2 ) - 25.f ) < 0.01f );
|
||||||
|
}
|
||||||
|
|
||||||
|
UTEST( UIAbstractTableView, ColumnWidthModeAndMenuCanBeConfiguredFromXML ) {
|
||||||
|
UIApplication app(
|
||||||
|
WindowSettings( 800, 600, "eepp - unit tests" ),
|
||||||
|
UIApplication::Settings( Sys::getProcessPath() + ".." + FileSystem::getOSSlash(), 1 ) );
|
||||||
|
auto* defaultTable =
|
||||||
|
app.getUI()->loadLayoutFromString( R"xml(<TableView />)xml" )->asType<UITableView>();
|
||||||
|
EXPECT_FALSE( defaultTable->isColumnWidthModeMenuEnabled() );
|
||||||
|
auto* table = app.getUI()
|
||||||
|
->loadLayoutFromString( R"xml(<TableView column-width-mode="percentage"
|
||||||
|
column-width-mode-menu="true" />)xml" )
|
||||||
|
->asType<UITableView>();
|
||||||
|
EXPECT_TRUE( table->getColumnWidthMode() == UIAbstractTableView::ColumnWidthMode::Percentage );
|
||||||
|
EXPECT_TRUE( table->isColumnWidthModeMenuEnabled() );
|
||||||
|
auto* cssRoot = app.getUI()->loadLayoutFromString( R"xml(
|
||||||
|
<vbox>
|
||||||
|
<style>#css_table { column-width-mode-menu: true; }</style>
|
||||||
|
<TableView id="css_table" />
|
||||||
|
</vbox>)xml" );
|
||||||
|
auto* cssTable = cssRoot->querySelector( "#css_table" )->asType<UITableView>();
|
||||||
|
EXPECT_TRUE( cssTable->isColumnWidthModeMenuEnabled() );
|
||||||
|
}
|
||||||
|
|
||||||
|
UTEST( UIAbstractTableView, PercentageRoundingDoesNotCreatePhantomHorizontalScroll ) {
|
||||||
|
Float previousDensity = PixelDensity::getPixelDensity();
|
||||||
|
PixelDensity::setPixelDensity( 1.5f );
|
||||||
|
UIApplication app(
|
||||||
|
WindowSettings( 828, 300, "eepp - unit tests" ),
|
||||||
|
UIApplication::Settings( Sys::getProcessPath() + ".." + FileSystem::getOSSlash(), 1 ) );
|
||||||
|
auto* table = PercentageTestTable::New();
|
||||||
|
table->setParent( app.getUI() );
|
||||||
|
table->setPixelsSize( 828, 300 );
|
||||||
|
table->setModel( std::make_shared<ThreeColumnModel>() );
|
||||||
|
table->setColumnsWidthPercentage( { 100.f / 3.f, 100.f / 3.f, 100.f / 3.f } );
|
||||||
|
app.getUI()->update( Milliseconds( 16 ) );
|
||||||
|
|
||||||
|
EXPECT_EQ( table->getColumnWidth( 0 ) + table->getColumnWidth( 1 ) + table->getColumnWidth( 2 ),
|
||||||
|
table->getContentSpaceWidth() );
|
||||||
|
EXPECT_FALSE( table->getHorizontalScrollBar()->isVisible() );
|
||||||
|
|
||||||
|
table->setColumnsWidthPercentage( { 25.f, 50.f, 25.f } );
|
||||||
|
table->setColumnMinimumWidth( 2, 208.f );
|
||||||
|
app.getUI()->update( Milliseconds( 16 ) );
|
||||||
|
EXPECT_EQ( table->getColumnWidth( 0 ) + table->getColumnWidth( 1 ) + table->getColumnWidth( 2 ),
|
||||||
|
table->getContentSpaceWidth() );
|
||||||
|
EXPECT_TRUE( table->getColumnWidth( 2 ) >= 208.f );
|
||||||
|
EXPECT_FALSE( table->getHorizontalScrollBar()->isVisible() );
|
||||||
|
PixelDensity::setPixelDensity( previousDensity );
|
||||||
|
}
|
||||||
|
|
||||||
|
UTEST( UIAbstractTableView, PercentageColumnsRecomputeWhenVerticalScrollbarAppears ) {
|
||||||
|
UIApplication app(
|
||||||
|
WindowSettings( 800, 600, "eepp - unit tests" ),
|
||||||
|
UIApplication::Settings( Sys::getProcessPath() + ".." + FileSystem::getOSSlash(), 1 ) );
|
||||||
|
auto* table = PercentageTestTable::New();
|
||||||
|
table->setParent( app.getUI() );
|
||||||
|
table->setPixelsSize( 400, 100 );
|
||||||
|
table->setModel( std::make_shared<ThreeColumnModel>( 100 ) );
|
||||||
|
table->setColumnsWidthPercentage( { 25.f, 50.f, 25.f } );
|
||||||
|
app.getUI()->update( Milliseconds( 16 ) );
|
||||||
|
|
||||||
|
EXPECT_TRUE( table->getVerticalScrollBar()->isVisible() );
|
||||||
|
EXPECT_EQ( table->getColumnWidth( 0 ) + table->getColumnWidth( 1 ) + table->getColumnWidth( 2 ),
|
||||||
|
table->getContentSpaceWidth() );
|
||||||
|
EXPECT_FALSE( table->getHorizontalScrollBar()->isVisible() );
|
||||||
|
}
|
||||||
|
|
||||||
|
UTEST( UIAbstractTableView, AutoPixelColumnsRecomputeWhenVerticalScrollbarAppears ) {
|
||||||
|
UIApplication app(
|
||||||
|
WindowSettings( 800, 600, "eepp - unit tests" ),
|
||||||
|
UIApplication::Settings( Sys::getProcessPath() + ".." + FileSystem::getOSSlash(), 1 ) );
|
||||||
|
auto* table = PercentageTestTable::New();
|
||||||
|
table->setParent( app.getUI() );
|
||||||
|
table->setPixelsSize( 400, 100 );
|
||||||
|
table->setModel( std::make_shared<ThreeColumnModel>( 100 ) );
|
||||||
|
table->setAutoColumnsWidth( true );
|
||||||
|
app.getUI()->update( Milliseconds( 16 ) );
|
||||||
|
|
||||||
|
EXPECT_TRUE( table->getColumnWidthMode() == UIAbstractTableView::ColumnWidthMode::Pixels );
|
||||||
|
EXPECT_TRUE( table->getVerticalScrollBar()->isVisible() );
|
||||||
|
EXPECT_FALSE( table->getHorizontalScrollBar()->isVisible() );
|
||||||
|
}
|
||||||
|
|
||||||
|
UTEST( UIAbstractTableView, ColumnWidthsSerializationRoundTripsBothModes ) {
|
||||||
|
UIApplication app(
|
||||||
|
WindowSettings( 800, 600, "eepp - unit tests" ),
|
||||||
|
UIApplication::Settings( Sys::getProcessPath() + ".." + FileSystem::getOSSlash(), 1 ) );
|
||||||
|
auto* table = PercentageTestTable::New();
|
||||||
|
table->setParent( app.getUI() );
|
||||||
|
table->setPixelsSize( 400, 200 );
|
||||||
|
table->setModel( std::make_shared<ThreeColumnModel>() );
|
||||||
|
table->setColumnsWidthPercentage( { 20.f, 30.f, 50.f } );
|
||||||
|
auto percentageWidths = table->serializeColumnWidths();
|
||||||
|
table->setColumnWidthMode( UIAbstractTableView::ColumnWidthMode::Pixels );
|
||||||
|
EXPECT_TRUE( table->unserializeColumnWidths( percentageWidths ) );
|
||||||
|
EXPECT_TRUE( table->getColumnWidthMode() == UIAbstractTableView::ColumnWidthMode::Percentage );
|
||||||
|
EXPECT_TRUE( std::abs( table->getColumnWidthPercentage( 0 ) - 20.f ) < 0.01f );
|
||||||
|
|
||||||
|
table->setColumnWidthMode( UIAbstractTableView::ColumnWidthMode::Pixels );
|
||||||
|
table->setColumnWidth( 0, PixelDensity::dpToPx( 80.f ) );
|
||||||
|
table->setColumnWidth( 1, PixelDensity::dpToPx( 120.f ) );
|
||||||
|
table->setColumnWidth( 2, PixelDensity::dpToPx( 160.f ) );
|
||||||
|
auto pixelWidths = table->serializeColumnWidths();
|
||||||
|
table->setColumnsWidthPercentage( { 25.f, 50.f, 25.f } );
|
||||||
|
EXPECT_TRUE( table->unserializeColumnWidths( pixelWidths ) );
|
||||||
|
EXPECT_TRUE( table->getColumnWidthMode() == UIAbstractTableView::ColumnWidthMode::Pixels );
|
||||||
|
EXPECT_EQ( table->getColumnWidth( 0 ), PixelDensity::dpToPx( 80.f ) );
|
||||||
|
EXPECT_EQ( table->getColumnWidth( 1 ), PixelDensity::dpToPx( 120.f ) );
|
||||||
|
EXPECT_EQ( table->getColumnWidth( 2 ), PixelDensity::dpToPx( 160.f ) );
|
||||||
|
|
||||||
|
EXPECT_TRUE( table->unserializeColumnWidths( nlohmann::json{ 25.f, 50.f, 25.f } ) );
|
||||||
|
EXPECT_TRUE( table->getColumnWidthMode() == UIAbstractTableView::ColumnWidthMode::Percentage );
|
||||||
|
|
||||||
|
auto* deferredTable = PercentageTestTable::New();
|
||||||
|
deferredTable->setParent( app.getUI() );
|
||||||
|
deferredTable->setPixelsSize( 400, 200 );
|
||||||
|
EXPECT_TRUE( deferredTable->unserializeColumnWidths( percentageWidths ) );
|
||||||
|
EXPECT_TRUE( deferredTable->serializeColumnWidths() == percentageWidths );
|
||||||
|
deferredTable->setModel( std::make_shared<ThreeColumnModel>() );
|
||||||
|
EXPECT_TRUE( deferredTable->getColumnWidthMode() ==
|
||||||
|
UIAbstractTableView::ColumnWidthMode::Percentage );
|
||||||
|
EXPECT_TRUE( std::abs( deferredTable->getColumnWidthPercentage( 0 ) - 20.f ) < 0.01f );
|
||||||
|
}
|
||||||
|
|
||||||
|
UTEST( UITabWidget, TransferTabPreservesDestinationTabsAndOwnedWidgets ) {
|
||||||
|
UIApplication app(
|
||||||
|
WindowSettings( 800, 600, "eepp - unit tests" ),
|
||||||
|
UIApplication::Settings( Sys::getProcessPath() + ".." + FileSystem::getOSSlash(), 1 ) );
|
||||||
|
|
||||||
|
auto* source = TransferTestTabWidget::New();
|
||||||
|
source->setParent( app.getUI() );
|
||||||
|
auto* destination = TransferTestTabWidget::New();
|
||||||
|
destination->setParent( app.getUI() );
|
||||||
|
destination->setPixelsSize( 240, 180 );
|
||||||
|
auto* sourceWidget = UIWidget::New();
|
||||||
|
auto* destinationWidgetA = UIWidget::New();
|
||||||
|
auto* destinationWidgetB = UIWidget::New();
|
||||||
|
auto* movedTab = source->add( "Moved", sourceWidget );
|
||||||
|
destination->add( "Existing A", destinationWidgetA );
|
||||||
|
destination->add( "Existing B", destinationWidgetB );
|
||||||
|
|
||||||
|
source->detachTab( movedTab );
|
||||||
|
destination->add( movedTab );
|
||||||
|
destination->setTabSelected( movedTab );
|
||||||
|
|
||||||
|
EXPECT_EQ( source->getTabCount(), Uint32{ 0 } );
|
||||||
|
EXPECT_EQ( destination->getTabCount(), Uint32{ 3 } );
|
||||||
|
EXPECT_TRUE( destination->getTab( 0 )->getOwnedWidget() == destinationWidgetA );
|
||||||
|
EXPECT_TRUE( destination->getTab( 1 )->getOwnedWidget() == destinationWidgetB );
|
||||||
|
EXPECT_TRUE( destination->getTab( 2 )->getOwnedWidget() == sourceWidget );
|
||||||
|
EXPECT_TRUE( destination->isParentOf( sourceWidget ) );
|
||||||
|
EXPECT_TRUE( destination->isParentOf( destinationWidgetA ) );
|
||||||
|
EXPECT_TRUE( destination->isParentOf( destinationWidgetB ) );
|
||||||
|
EXPECT_TRUE( sourceWidget->getPixelsSize() == destination->getContainerPixelsSize() );
|
||||||
|
EXPECT_TRUE( sourceWidget->isVisible() );
|
||||||
|
EXPECT_FALSE( destinationWidgetA->isVisible() );
|
||||||
|
EXPECT_FALSE( destinationWidgetB->isVisible() );
|
||||||
|
}
|
||||||
|
|
||||||
|
UTEST( UITabWidgetSplitter, ClosingSplitNotifiesBeforeTabWidgetDestruction ) {
|
||||||
|
UIApplication app(
|
||||||
|
WindowSettings( 800, 600, "eepp - unit tests" ),
|
||||||
|
UIApplication::Settings( Sys::getProcessPath() + ".." + FileSystem::getOSSlash(), 1 ) );
|
||||||
|
TestClient client;
|
||||||
|
auto* splitter = UITabWidgetSplitter::New( &client, app.getUI() );
|
||||||
|
auto* container = UILayout::New();
|
||||||
|
container->setParent( app.getUI() );
|
||||||
|
auto* firstTabWidget = splitter->createTabWidget( container );
|
||||||
|
auto* firstWidget = UIWidget::New();
|
||||||
|
splitter->createWidgetInTabWidget( firstTabWidget, firstWidget, "First" );
|
||||||
|
auto* secondTabWidget = splitter->splitTabWidget( SplitDirection::Right, firstTabWidget );
|
||||||
|
auto* secondWidget = UIWidget::New();
|
||||||
|
splitter->createWidgetInTabWidget( secondTabWidget, secondWidget, "Second" );
|
||||||
|
UITabWidget* closedTabWidget = nullptr;
|
||||||
|
splitter->setOnTabWidgetCloseCb(
|
||||||
|
[&closedTabWidget]( UITabWidget* tabWidget ) { closedTabWidget = tabWidget; } );
|
||||||
|
|
||||||
|
splitter->closeTab( secondWidget, UITabWidget::FocusTabBehavior::Default );
|
||||||
|
app.getUI()->update( Milliseconds( 16 ) );
|
||||||
|
|
||||||
|
EXPECT_EQ( closedTabWidget, secondTabWidget );
|
||||||
|
EXPECT_EQ( splitter->getTabWidgets().size(), 1UL );
|
||||||
|
eeDelete( splitter );
|
||||||
|
}
|
||||||
|
|
||||||
UTEST( UITabWidgetSplitter, Serialization ) {
|
UTEST( UITabWidgetSplitter, Serialization ) {
|
||||||
UIApplication app(
|
UIApplication app(
|
||||||
WindowSettings( 800, 600, "eepp - unit tests" ),
|
WindowSettings( 800, 600, "eepp - unit tests" ),
|
||||||
@@ -34,15 +445,14 @@ UTEST( UITabWidgetSplitter, Serialization ) {
|
|||||||
auto* tabWidget = splitter->createTabWidget( (Node*)app.getUI() );
|
auto* tabWidget = splitter->createTabWidget( (Node*)app.getUI() );
|
||||||
|
|
||||||
bool saved = false;
|
bool saved = false;
|
||||||
splitter->registerWidgetType(
|
splitter->registerWidgetType( "testwidget",
|
||||||
"testwidget",
|
{ [&]( UIWidget* ) {
|
||||||
{ [&]( UIWidget* ) {
|
saved = true;
|
||||||
saved = true;
|
return nlohmann::json{ { "custom_key", 42 } };
|
||||||
return nlohmann::json{ { "custom_key", 42 } };
|
},
|
||||||
},
|
[]( const nlohmann::json& ) -> WidgetLoadResult {
|
||||||
[]( const nlohmann::json& ) -> WidgetLoadResult {
|
return { nullptr, nullptr, "" };
|
||||||
return { nullptr, nullptr, "" };
|
} } );
|
||||||
} } );
|
|
||||||
|
|
||||||
auto* w = UIWidget::New();
|
auto* w = UIWidget::New();
|
||||||
w->addClass( "testwidget" );
|
w->addClass( "testwidget" );
|
||||||
@@ -93,9 +503,8 @@ UTEST( UITabWidgetSplitter, Serialization ) {
|
|||||||
splitter->createWidgetInTabWidget( tabWidget, ghost, "Ghost" );
|
splitter->createWidgetInTabWidget( tabWidget, ghost, "Ghost" );
|
||||||
|
|
||||||
splitter->registerWidgetType(
|
splitter->registerWidgetType(
|
||||||
"known",
|
"known", { []( UIWidget* ) { return nlohmann::json{ { "val", 1 } }; },
|
||||||
{ []( UIWidget* ) { return nlohmann::json{ { "val", 1 } }; },
|
[]( const nlohmann::json& ) -> WidgetLoadResult { return {}; } } );
|
||||||
[]( const nlohmann::json& ) -> WidgetLoadResult { return {}; } } );
|
|
||||||
|
|
||||||
auto* reg = UIWidget::New();
|
auto* reg = UIWidget::New();
|
||||||
reg->addClass( "known" );
|
reg->addClass( "known" );
|
||||||
@@ -117,13 +526,12 @@ UTEST( UITabWidgetSplitter, Serialization ) {
|
|||||||
auto* tabWidget = splitter->createTabWidget( (Node*)app.getUI() );
|
auto* tabWidget = splitter->createTabWidget( (Node*)app.getUI() );
|
||||||
|
|
||||||
splitter->registerWidgetType(
|
splitter->registerWidgetType(
|
||||||
"panewidget",
|
"panewidget", { []( UIWidget* ) { return nlohmann::json{ { "pane", true } }; },
|
||||||
{ []( UIWidget* ) { return nlohmann::json{ { "pane", true } }; },
|
[]( const nlohmann::json& j ) -> WidgetLoadResult {
|
||||||
[]( const nlohmann::json& j ) -> WidgetLoadResult {
|
auto* w = UIWidget::New();
|
||||||
auto* w = UIWidget::New();
|
w->addClass( "panewidget" );
|
||||||
w->addClass( "panewidget" );
|
return { w, nullptr, j.value( "title", "" ) };
|
||||||
return { w, nullptr, j.value( "title", "" ) };
|
} } );
|
||||||
} } );
|
|
||||||
|
|
||||||
auto* w1 = UIWidget::New();
|
auto* w1 = UIWidget::New();
|
||||||
w1->addClass( "panewidget" );
|
w1->addClass( "panewidget" );
|
||||||
@@ -163,13 +571,12 @@ UTEST( UITabWidgetSplitter, Serialization ) {
|
|||||||
auto* tab1 = splitter1->createTabWidget( (Node*)app.getUI() );
|
auto* tab1 = splitter1->createTabWidget( (Node*)app.getUI() );
|
||||||
|
|
||||||
splitter1->registerWidgetType(
|
splitter1->registerWidgetType(
|
||||||
"mywidget",
|
"mywidget", { []( UIWidget* ) { return nlohmann::json{ { "data", "hello" } }; },
|
||||||
{ []( UIWidget* ) { return nlohmann::json{ { "data", "hello" } }; },
|
[]( const nlohmann::json& j ) -> WidgetLoadResult {
|
||||||
[]( const nlohmann::json& j ) -> WidgetLoadResult {
|
auto* w = UIWidget::New();
|
||||||
auto* w = UIWidget::New();
|
w->addClass( "mywidget" );
|
||||||
w->addClass( "mywidget" );
|
return { w, nullptr, j.value( "title", "" ) };
|
||||||
return { w, nullptr, j.value( "title", "" ) };
|
} } );
|
||||||
} } );
|
|
||||||
|
|
||||||
auto* wa = UIWidget::New();
|
auto* wa = UIWidget::New();
|
||||||
wa->addClass( "mywidget" );
|
wa->addClass( "mywidget" );
|
||||||
@@ -186,14 +593,13 @@ UTEST( UITabWidgetSplitter, Serialization ) {
|
|||||||
auto* splitter2 = UITabWidgetSplitter::New( &client2, app.getUI() );
|
auto* splitter2 = UITabWidgetSplitter::New( &client2, app.getUI() );
|
||||||
auto* tab2 = splitter2->createTabWidget( (Node*)app.getUI() );
|
auto* tab2 = splitter2->createTabWidget( (Node*)app.getUI() );
|
||||||
|
|
||||||
splitter2->registerWidgetType(
|
splitter2->registerWidgetType( "mywidget",
|
||||||
"mywidget",
|
{ []( UIWidget* ) { return nlohmann::json::object(); },
|
||||||
{ []( UIWidget* ) { return nlohmann::json::object(); },
|
[]( const nlohmann::json& j ) -> WidgetLoadResult {
|
||||||
[]( const nlohmann::json& j ) -> WidgetLoadResult {
|
auto* w = UIWidget::New();
|
||||||
auto* w = UIWidget::New();
|
w->addClass( "mywidget" );
|
||||||
w->addClass( "mywidget" );
|
return { w, nullptr, j.value( "title", "" ) };
|
||||||
return { w, nullptr, j.value( "title", "" ) };
|
} } );
|
||||||
} } );
|
|
||||||
|
|
||||||
splitter2->fromJSON( saved );
|
splitter2->fromJSON( saved );
|
||||||
|
|
||||||
@@ -256,14 +662,13 @@ UTEST( UITabWidgetSplitter, Serialization ) {
|
|||||||
auto* splitter1 = UITabWidgetSplitter::New( &client1, app.getUI() );
|
auto* splitter1 = UITabWidgetSplitter::New( &client1, app.getUI() );
|
||||||
auto* tab1 = splitter1->createTabWidget( (Node*)app.getUI() );
|
auto* tab1 = splitter1->createTabWidget( (Node*)app.getUI() );
|
||||||
|
|
||||||
splitter1->registerWidgetType(
|
splitter1->registerWidgetType( "partwidget",
|
||||||
"partwidget",
|
{ []( UIWidget* ) { return nlohmann::json::object(); },
|
||||||
{ []( UIWidget* ) { return nlohmann::json::object(); },
|
[]( const nlohmann::json& j ) -> WidgetLoadResult {
|
||||||
[]( const nlohmann::json& j ) -> WidgetLoadResult {
|
auto* w = UIWidget::New();
|
||||||
auto* w = UIWidget::New();
|
w->addClass( "partwidget" );
|
||||||
w->addClass( "partwidget" );
|
return { w, nullptr, j.value( "title", "" ) };
|
||||||
return { w, nullptr, j.value( "title", "" ) };
|
} } );
|
||||||
} } );
|
|
||||||
|
|
||||||
auto* w1 = UIWidget::New();
|
auto* w1 = UIWidget::New();
|
||||||
w1->addClass( "partwidget" );
|
w1->addClass( "partwidget" );
|
||||||
@@ -287,14 +692,13 @@ UTEST( UITabWidgetSplitter, Serialization ) {
|
|||||||
auto* splitter2 = UITabWidgetSplitter::New( &client2, app.getUI() );
|
auto* splitter2 = UITabWidgetSplitter::New( &client2, app.getUI() );
|
||||||
splitter2->createTabWidget( (Node*)app.getUI() );
|
splitter2->createTabWidget( (Node*)app.getUI() );
|
||||||
|
|
||||||
splitter2->registerWidgetType(
|
splitter2->registerWidgetType( "partwidget",
|
||||||
"partwidget",
|
{ []( UIWidget* ) { return nlohmann::json::object(); },
|
||||||
{ []( UIWidget* ) { return nlohmann::json::object(); },
|
[]( const nlohmann::json& j ) -> WidgetLoadResult {
|
||||||
[]( const nlohmann::json& j ) -> WidgetLoadResult {
|
auto* w = UIWidget::New();
|
||||||
auto* w = UIWidget::New();
|
w->addClass( "partwidget" );
|
||||||
w->addClass( "partwidget" );
|
return { w, nullptr, j.value( "title", "" ) };
|
||||||
return { w, nullptr, j.value( "title", "" ) };
|
} } );
|
||||||
} } );
|
|
||||||
|
|
||||||
splitter2->fromJSON( saved );
|
splitter2->fromJSON( saved );
|
||||||
|
|
||||||
|
|||||||
@@ -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.winIcon = ini.getValue( "window", "winicon", resPath + "icon/ecode.png" );
|
||||||
windowState.panelPartition = iniState.getValue( "window", "panel_partition", "15%" );
|
windowState.panelPartition = iniState.getValue( "window", "panel_partition", "15%" );
|
||||||
windowState.statusBarPartition = iniState.getValue( "window", "status_bar_partition", "85%" );
|
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.displayIndex = iniState.getValueI( "window", "display_index", 0 );
|
||||||
windowState.position.x = iniState.getValueI( "window", "x", -1 );
|
windowState.position.x = iniState.getValueI( "window", "x", -1 );
|
||||||
windowState.position.y = iniState.getValueI( "window", "y", -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.setValueF( "window", "pixeldensity", windowState.pixelDensity );
|
||||||
iniState.setValue( "window", "panel_partition", panelPartition );
|
iniState.setValue( "window", "panel_partition", panelPartition );
|
||||||
iniState.setValue( "window", "status_bar_partition", statusBarPartition );
|
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", "display_index", windowState.displayIndex );
|
||||||
iniState.setValueI( "window", "x", windowState.position.x );
|
iniState.setValueI( "window", "x", windowState.position.x );
|
||||||
iniState.setValueI( "window", "y", windowState.position.y );
|
iniState.setValueI( "window", "y", windowState.position.y );
|
||||||
|
|||||||
@@ -90,6 +90,7 @@ struct WindowStateConfig {
|
|||||||
bool maximized{ false };
|
bool maximized{ false };
|
||||||
std::string panelPartition;
|
std::string panelPartition;
|
||||||
std::string statusBarPartition;
|
std::string statusBarPartition;
|
||||||
|
std::string rightPanelPartition{ "75%" };
|
||||||
int displayIndex{ 0 };
|
int displayIndex{ 0 };
|
||||||
Vector2i position{ -1, -1 };
|
Vector2i position{ -1, -1 };
|
||||||
Uint32 lastRunVersion{ 0 };
|
Uint32 lastRunVersion{ 0 };
|
||||||
|
|||||||
@@ -615,6 +615,7 @@ TabWidget::container > ImageViewer > TextView {
|
|||||||
</RelativeLayout>
|
</RelativeLayout>
|
||||||
<Tab id="treeview_tab" text='@string("project", "Project")' owns="project_view_cont" text-as-fallback="true" icon="icon(folder-open, 12dp)" />
|
<Tab id="treeview_tab" text='@string("project", "Project")' owns="project_view_cont" text-as-fallback="true" icon="icon(folder-open, 12dp)" />
|
||||||
</TabWidget>
|
</TabWidget>
|
||||||
|
<Splitter id="right_panel_splitter" splitter-partition="100%" splitter-always-show="false" splitter-hide-on-edge="true">
|
||||||
<vbox>
|
<vbox>
|
||||||
<Splitter id="main_splitter" lw="mp" lh="0" lw8="1" orientation="vertical">
|
<Splitter id="main_splitter" lw="mp" lh="0" lw8="1" orientation="vertical">
|
||||||
<RelativeLayout id="main_splitter_cont">
|
<RelativeLayout id="main_splitter_cont">
|
||||||
@@ -706,6 +707,8 @@ TabWidget::container > ImageViewer > TextView {
|
|||||||
<Widget class="status_sep" lw="0" lw8="1" lh="1dp" />
|
<Widget class="status_sep" lw="0" lw8="1" lh="1dp" />
|
||||||
</statusbar>
|
</statusbar>
|
||||||
</vbox>
|
</vbox>
|
||||||
|
<RelativeLayout id="right_panel_container"></RelativeLayout>
|
||||||
|
</Splitter>
|
||||||
</Splitter>
|
</Splitter>
|
||||||
<Image id="settings" lw="16dp" lh="16dp" lg="top|right" gravity="center" />
|
<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"
|
<TextView id="menu_hint" class="app_hint" lw="wc" lh="wc" lg="bottom|right" visible="false"
|
||||||
|
|||||||
@@ -6,10 +6,12 @@
|
|||||||
#include "fontpickercontroller.hpp"
|
#include "fontpickercontroller.hpp"
|
||||||
#include "keybindingshelper.hpp"
|
#include "keybindingshelper.hpp"
|
||||||
#include "pathhelper.hpp"
|
#include "pathhelper.hpp"
|
||||||
|
#include "plugins/debugger/statusdebuggercontroller.hpp"
|
||||||
#include "settingsactions.hpp"
|
#include "settingsactions.hpp"
|
||||||
#include "settingsmenu.hpp"
|
#include "settingsmenu.hpp"
|
||||||
#include "uibuildsettings.hpp"
|
#include "uibuildsettings.hpp"
|
||||||
#include "uidownloadwindow.hpp"
|
#include "uidownloadwindow.hpp"
|
||||||
|
#include "uirightpanel.hpp"
|
||||||
#include "uitreeviewfs.hpp"
|
#include "uitreeviewfs.hpp"
|
||||||
#include "uiwelcomescreen.hpp"
|
#include "uiwelcomescreen.hpp"
|
||||||
#include "version.hpp"
|
#include "version.hpp"
|
||||||
@@ -860,6 +862,8 @@ bool App::loadConfig( const LogLevel& logLevel, const Sizeu& displaySize, bool s
|
|||||||
void App::saveConfig() {
|
void App::saveConfig() {
|
||||||
if ( !mCurrentProject.empty() )
|
if ( !mCurrentProject.empty() )
|
||||||
saveSidePanelTabsOrder();
|
saveSidePanelTabsOrder();
|
||||||
|
if ( mRightPanel )
|
||||||
|
mRightPanel->saveState();
|
||||||
|
|
||||||
mConfig.save(
|
mConfig.save(
|
||||||
mRecentFiles, mRecentFolders,
|
mRecentFiles, mRecentFolders,
|
||||||
@@ -3810,6 +3814,17 @@ UISplitter* App::getMainSplitter() const {
|
|||||||
return mMainSplitter;
|
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 {
|
StatusTerminalController* App::getStatusTerminalController() const {
|
||||||
return mStatusTerminalController.get();
|
return mStatusTerminalController.get();
|
||||||
}
|
}
|
||||||
@@ -4979,6 +4994,8 @@ void App::init( InitParameters& params ) {
|
|||||||
mSplitter->setOpenDocumentsInMainSplit( mConfig.editor.openDocumentsInMainSplit );
|
mSplitter->setOpenDocumentsInMainSplit( mConfig.editor.openDocumentsInMainSplit );
|
||||||
mSplitter->setRestoreEditorSelectionOnFocus( mConfig.editor.restoreEditorSelectionOnFocus );
|
mSplitter->setRestoreEditorSelectionOnFocus( mConfig.editor.restoreEditorSelectionOnFocus );
|
||||||
mSplitter->setOnTabWidgetCreateCb( [this]( UITabWidget* tabWidget ) {
|
mSplitter->setOnTabWidgetCreateCb( [this]( UITabWidget* tabWidget ) {
|
||||||
|
tabWidget->setAcceptsDropOfWidgetFn(
|
||||||
|
[]( const UIWidget* widget ) { return !widget->hasClass( "debugger-tab" ); } );
|
||||||
tabWidget->getTabBar()->onDoubleClick(
|
tabWidget->getTabBar()->onDoubleClick(
|
||||||
[this]( const MouseEvent* ) { mSplitter->createEditorInNewTab(); } );
|
[this]( const MouseEvent* ) { mSplitter->createEditorInNewTab(); } );
|
||||||
} );
|
} );
|
||||||
@@ -5032,6 +5049,10 @@ void App::init( InitParameters& params ) {
|
|||||||
mMainSplitter = mUISceneNode->find<UISplitter>( "main_splitter" );
|
mMainSplitter = mUISceneNode->find<UISplitter>( "main_splitter" );
|
||||||
mMainSplitter->setSplitPartition(
|
mMainSplitter->setSplitPartition(
|
||||||
StyleSheetLength( mConfig.windowState.statusBarPartition ) );
|
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" );
|
mStatusBar = mUISceneNode->find<UIStatusBar>( "status_bar" );
|
||||||
mPluginManager->setMainSplitter( mMainSplitter );
|
mPluginManager->setMainSplitter( mMainSplitter );
|
||||||
|
|
||||||
|
|||||||
@@ -35,6 +35,8 @@ class DateTimeController;
|
|||||||
class FontPickerController;
|
class FontPickerController;
|
||||||
class SettingsMenu;
|
class SettingsMenu;
|
||||||
class UITreeViewFS;
|
class UITreeViewFS;
|
||||||
|
class StatusDebuggerController;
|
||||||
|
class UIRightPanel;
|
||||||
|
|
||||||
class App : public UICodeEditorSplitter::Client, public PluginContextProvider {
|
class App : public UICodeEditorSplitter::Client, public PluginContextProvider {
|
||||||
public:
|
public:
|
||||||
@@ -599,6 +601,10 @@ class App : public UICodeEditorSplitter::Client, public PluginContextProvider {
|
|||||||
|
|
||||||
UISplitter* getMainSplitter() const;
|
UISplitter* getMainSplitter() const;
|
||||||
|
|
||||||
|
UIRightPanel* getRightPanel() const;
|
||||||
|
|
||||||
|
StatusDebuggerController* getStatusDebuggerController() const;
|
||||||
|
|
||||||
StatusTerminalController* getStatusTerminalController() const;
|
StatusTerminalController* getStatusTerminalController() const;
|
||||||
|
|
||||||
void hideStatusTerminal();
|
void hideStatusTerminal();
|
||||||
@@ -777,6 +783,7 @@ class App : public UICodeEditorSplitter::Client, public PluginContextProvider {
|
|||||||
UIThemePtr mTheme;
|
UIThemePtr mTheme;
|
||||||
UIStatusBar* mStatusBar{ nullptr };
|
UIStatusBar* mStatusBar{ nullptr };
|
||||||
UISplitter* mMainSplitter{ nullptr };
|
UISplitter* mMainSplitter{ nullptr };
|
||||||
|
std::unique_ptr<UIRightPanel> mRightPanel;
|
||||||
UIMessageBox* mCloseMsgBox{ nullptr };
|
UIMessageBox* mCloseMsgBox{ nullptr };
|
||||||
UIMenuBar* mMenuBar{ nullptr };
|
UIMenuBar* mMenuBar{ nullptr };
|
||||||
std::unique_ptr<SettingsActions> mSettingsActions;
|
std::unique_ptr<SettingsActions> mSettingsActions;
|
||||||
|
|||||||
@@ -140,8 +140,10 @@ void DebuggerClientListener::initUI() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if ( !mStackModel ) {
|
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();
|
UITableView* uiThreads = sdc->getUIThreads();
|
||||||
uiThreads->setModel( mThreadsModel );
|
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*/,
|
void DebuggerPlugin::onSaveProject( const std::string& /*projectFolder*/,
|
||||||
const std::string& projectStatePath,
|
const std::string& projectStatePath,
|
||||||
bool rewriteStateOnlyIfNeeded ) {
|
bool rewriteStateOnlyIfNeeded ) {
|
||||||
@@ -579,11 +584,11 @@ void DebuggerPlugin::loadDAPConfig( const std::string& path, bool updateConfigFi
|
|||||||
mKeyBindings["debugger-step-over"] = "f10";
|
mKeyBindings["debugger-step-over"] = "f10";
|
||||||
mKeyBindings["debugger-step-into"] = "f11";
|
mKeyBindings["debugger-step-into"] = "f11";
|
||||||
mKeyBindings["debugger-step-out"] = "shift+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";
|
mKeyBindings["toggle-status-app-debugger"] = "mod+6";
|
||||||
#else
|
#else
|
||||||
mKeyBindings["toggle-status-app-debugger"] = "alt+6";
|
mKeyBindings["toggle-status-app-debugger"] = "alt+6";
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
if ( j.contains( "keybindings" ) ) {
|
if ( j.contains( "keybindings" ) ) {
|
||||||
@@ -768,8 +773,9 @@ void DebuggerPlugin::buildSidePanelTab() {
|
|||||||
if ( mProjectPath.empty() )
|
if ( mProjectPath.empty() )
|
||||||
return;
|
return;
|
||||||
UIIcon* icon = findIcon( "debug" );
|
UIIcon* icon = findIcon( "debug" );
|
||||||
mTab = mSidePanel->add( i18n( "debugger", "Debugger" ), mTabContents,
|
mTab =
|
||||||
icon ? icon->createDrawable( PixelDensity::dpToPx( 12 ) ) : nullptr );
|
mSidePanel->add( i18n( "debugger", "Debugger" ), mTabContents,
|
||||||
|
icon ? icon->createDrawable( PixelDensity::dpToPx( 12 ) ) : nullptr );
|
||||||
mTab->setId( "debugger_tab" );
|
mTab->setId( "debugger_tab" );
|
||||||
mTab->setTextAsFallback( true );
|
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, FileSystem::getOSSlash() );
|
||||||
String::replaceAll( val, KEY_PATH_SEPARATOR_ABBR, FileSystem::getOSSlash() );
|
String::replaceAll( val, KEY_PATH_SEPARATOR_ABBR, FileSystem::getOSSlash() );
|
||||||
String::replaceAll( val, KEY_UUID, UUID().toString() );
|
String::replaceAll( val, KEY_UUID, UUID().toString() );
|
||||||
String::replaceAll( val, KEY_TIMESTAMP,
|
String::replaceAll( val, KEY_TIMESTAMP, std::to_string( Sys::getUnixTimestamp() ) );
|
||||||
std::to_string( Sys::getUnixTimestamp() ) );
|
|
||||||
|
|
||||||
auto* editor = getPluginContext()->getSplitter()->getCurEditor();
|
auto* editor = getPluginContext()->getSplitter()->getCurEditor();
|
||||||
if ( getPluginContext()->getSplitter()->getCurEditor() ) {
|
if ( getPluginContext()->getSplitter()->getCurEditor() ) {
|
||||||
|
|||||||
@@ -67,6 +67,8 @@ class DebuggerPlugin : public PluginBase {
|
|||||||
|
|
||||||
std::string getDescription() override { return Definition().description; }
|
std::string getDescription() override { return Definition().description; }
|
||||||
|
|
||||||
|
void onSaveState( IniFile* state ) override;
|
||||||
|
|
||||||
void onSaveProject( const std::string& projectFolder, const std::string& projectStatePath,
|
void onSaveProject( const std::string& projectFolder, const std::string& projectStatePath,
|
||||||
bool rewriteStateOnlyIfNeeded ) override;
|
bool rewriteStateOnlyIfNeeded ) override;
|
||||||
|
|
||||||
|
|||||||
@@ -1,10 +1,15 @@
|
|||||||
#include "stackmodel.hpp"
|
#include "stackmodel.hpp"
|
||||||
|
#include <eepp/system/filesystem.hpp>
|
||||||
#include <eepp/ui/uiscenenode.hpp>
|
#include <eepp/ui/uiscenenode.hpp>
|
||||||
|
|
||||||
namespace ecode {
|
namespace ecode {
|
||||||
|
|
||||||
StackModel::StackModel( StackTraceInfo&& stack, UISceneNode* sceneNode ) :
|
StackModel::StackModel( StackTraceInfo&& stack, UISceneNode* sceneNode, std::string projectPath ) :
|
||||||
mStack( std::move( stack ) ), mSceneNode( sceneNode ) {}
|
mStack( std::move( stack ) ),
|
||||||
|
mProjectPath( std::move( projectPath ) ),
|
||||||
|
mSceneNode( sceneNode ) {
|
||||||
|
FileSystem::dirAddSlashAtEnd( mProjectPath );
|
||||||
|
}
|
||||||
|
|
||||||
size_t StackModel::rowCount( const ModelIndex& ) const {
|
size_t StackModel::rowCount( const ModelIndex& ) const {
|
||||||
Lock l( mResourceLock );
|
Lock l( mResourceLock );
|
||||||
@@ -45,7 +50,8 @@ Variant StackModel::data( const ModelIndex& modelIndex, ModelRole role ) const {
|
|||||||
: Variant();
|
: Variant();
|
||||||
case Columns::SourcePath:
|
case Columns::SourcePath:
|
||||||
return mStack.stackFrames[modelIndex.row()].source
|
return mStack.stackFrames[modelIndex.row()].source
|
||||||
? Variant( mStack.stackFrames[modelIndex.row()].source->path )
|
? Variant( displaySourcePath(
|
||||||
|
mStack.stackFrames[modelIndex.row()].source->path ) )
|
||||||
: Variant();
|
: Variant();
|
||||||
case Columns::Line:
|
case Columns::Line:
|
||||||
return Variant( String::toString( mStack.stackFrames[modelIndex.row()].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
|
} // namespace ecode
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ class StackModel : public Model {
|
|||||||
public:
|
public:
|
||||||
enum Columns { ID, Name, SourceName, SourcePath, Line, Column };
|
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;
|
virtual size_t rowCount( const ModelIndex& ) const;
|
||||||
|
|
||||||
@@ -37,10 +37,15 @@ class StackModel : public Model {
|
|||||||
|
|
||||||
void setCurrentScopeId( int scope );
|
void setCurrentScopeId( int scope );
|
||||||
|
|
||||||
|
void setProjectPath( const std::string& projectPath );
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
StackTraceInfo mStack;
|
StackTraceInfo mStack;
|
||||||
|
std::string mProjectPath;
|
||||||
UISceneNode* mSceneNode{ nullptr };
|
UISceneNode* mSceneNode{ nullptr };
|
||||||
int mCurrentScopeId{ 0 };
|
int mCurrentScopeId{ 0 };
|
||||||
|
|
||||||
|
const char* displaySourcePath( const std::string& sourcePath ) const;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace ecode
|
} // namespace ecode
|
||||||
|
|||||||
@@ -1,11 +1,45 @@
|
|||||||
#include "statusdebuggercontroller.hpp"
|
#include "statusdebuggercontroller.hpp"
|
||||||
|
#include "../../appconfig.hpp"
|
||||||
|
#include "../../uirightpanel.hpp"
|
||||||
#include "../../widgetcommandexecuter.hpp"
|
#include "../../widgetcommandexecuter.hpp"
|
||||||
#include "../plugincontextprovider.hpp"
|
#include "../plugincontextprovider.hpp"
|
||||||
#include "eepp/ui/uiwidgetcreator.hpp"
|
#include "eepp/ui/uiwidgetcreator.hpp"
|
||||||
#include <eepp/ui/uicheckbox.hpp>
|
#include <eepp/ui/uicheckbox.hpp>
|
||||||
|
#include <nlohmann/json.hpp>
|
||||||
|
|
||||||
namespace ecode {
|
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 {
|
class UIBreakpointsTableCell : public UITableCell {
|
||||||
public:
|
public:
|
||||||
static UIBreakpointsTableCell* New( const std::string& tag, const BreakpointsModel* model,
|
static UIBreakpointsTableCell* New( const std::string& tag, const BreakpointsModel* model,
|
||||||
@@ -109,6 +143,37 @@ StatusDebuggerController::StatusDebuggerController( UISplitter* mainSplitter,
|
|||||||
PluginContextProvider* pluginContext ) :
|
PluginContextProvider* pluginContext ) :
|
||||||
StatusBarElement( mainSplitter, uiSceneNode, 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() {
|
UIWidget* StatusDebuggerController::getWidget() {
|
||||||
return mContainer;
|
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() {
|
void StatusDebuggerController::createContainer() {
|
||||||
if ( mContainer )
|
if ( mContainer )
|
||||||
return;
|
return;
|
||||||
@@ -206,21 +472,16 @@ void StatusDebuggerController::createContainer() {
|
|||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
<hboxce id="app_debugger" lw="mp" lh="mp" visible="false">
|
<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%">
|
<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_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" />
|
<TableView id="debugger_stack" layout_width="mp" layout_height="mp" column-width-mode="percentage" column-width-mode-menu="true" />
|
||||||
</Splitter>
|
</Splitter>
|
||||||
<TreeView id="debugger_variables" layout_width="mp" layout_height="mp" />
|
<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" />
|
<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" />
|
<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" />
|
<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" />
|
</vbox>
|
||||||
<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 id="app_debugger_buttons" class="vertical_bar" lw="16dp" lh="mp">
|
<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 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)" />
|
<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 );
|
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_and_stack", mUIThreadsSplitter );
|
||||||
mContainer->bind( "debugger_threads", mUIThreads );
|
mContainer->bind( "debugger_threads", mUIThreads );
|
||||||
mContainer->bind( "debugger_stack", mUIStack );
|
mContainer->bind( "debugger_stack", mUIStack );
|
||||||
@@ -256,6 +517,8 @@ void StatusDebuggerController::createContainer() {
|
|||||||
mContainer->bind( "debugger_variables", mUIVariables );
|
mContainer->bind( "debugger_variables", mUIVariables );
|
||||||
mContainer->bind( "debugger_expressions", mUIExpressions );
|
mContainer->bind( "debugger_expressions", mUIExpressions );
|
||||||
mContainer->bind( "debugger_console", mUIConsole );
|
mContainer->bind( "debugger_console", mUIConsole );
|
||||||
|
mEventConnections += mContainer->connect( Event::OnVisibleChange,
|
||||||
|
[this]( const Event* ) { updateRightPanel(); } );
|
||||||
mContainer->bind( "app_debugger_start", mUIButStart );
|
mContainer->bind( "app_debugger_start", mUIButStart );
|
||||||
mContainer->bind( "app_debugger_stop", mUIButStop );
|
mContainer->bind( "app_debugger_stop", mUIButStop );
|
||||||
mContainer->bind( "app_debugger_pause", mUIButPause );
|
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_into", mUIButStepInto );
|
||||||
mContainer->bind( "app_debugger_step_out", mUIButStepOut );
|
mContainer->bind( "app_debugger_step_out", mUIButStepOut );
|
||||||
|
|
||||||
|
createTabWidgets();
|
||||||
|
|
||||||
mContainer->setCommand( "next-tab", [this] {
|
mContainer->setCommand( "next-tab", [this] {
|
||||||
if ( mUITabWidget )
|
if ( mTabWidgetSplitter && mTabWidgetSplitter->getCurWidget() ) {
|
||||||
mUITabWidget->focusNextTab();
|
auto tabWidget =
|
||||||
|
mTabWidgetSplitter->tabWidgetFromWidget( mTabWidgetSplitter->getCurWidget() );
|
||||||
|
if ( tabWidget )
|
||||||
|
tabWidget->focusNextTab();
|
||||||
|
}
|
||||||
} );
|
} );
|
||||||
|
|
||||||
mContainer->setCommand( "previous-tab", [this] {
|
mContainer->setCommand( "previous-tab", [this] {
|
||||||
if ( mUITabWidget )
|
if ( mTabWidgetSplitter && mTabWidgetSplitter->getCurWidget() ) {
|
||||||
mUITabWidget->focusPreviousTab();
|
auto tabWidget =
|
||||||
|
mTabWidgetSplitter->tabWidgetFromWidget( mTabWidgetSplitter->getCurWidget() );
|
||||||
|
if ( tabWidget )
|
||||||
|
tabWidget->focusPreviousTab();
|
||||||
|
}
|
||||||
} );
|
} );
|
||||||
|
|
||||||
for ( int i = 1; i <= 5; i++ ) {
|
for ( int i = 1; i <= 5; i++ ) {
|
||||||
mContainer->setCommand( String::format( "switch-to-tab-%d", i ), [this, i] {
|
mContainer->setCommand( String::format( "switch-to-tab-%d", i ), [this, i] {
|
||||||
if ( mUITabWidget )
|
if ( mTabWidgetSplitter )
|
||||||
mUITabWidget->setTabSelected(
|
mTabWidgetSplitter->switchToTab( i - 1 );
|
||||||
eeclamp<Int32>( i - 1, 0, mUITabWidget->getTabCount() - 1 ) );
|
|
||||||
} );
|
} );
|
||||||
}
|
}
|
||||||
|
|
||||||
mContainer->on( Event::KeyDown, [this]( const Event* event ) {
|
mEventConnections += mContainer->connect( Event::KeyDown, [this]( const Event* event ) {
|
||||||
auto ke = event->asKeyEvent();
|
auto ke = event->asKeyEvent();
|
||||||
if ( ke->getSanitizedMod() == 0 && ke->getKeyCode() == EE::Window::KEY_ESCAPE &&
|
if ( ke->getSanitizedMod() == 0 && ke->getKeyCode() == EE::Window::KEY_ESCAPE &&
|
||||||
mSplitter->getCurEditor() ) {
|
mSplitter->getCurEditor() ) {
|
||||||
@@ -313,19 +585,12 @@ void StatusDebuggerController::createContainer() {
|
|||||||
|
|
||||||
mUIThreads->setAutoExpandOnSingleColumn( true );
|
mUIThreads->setAutoExpandOnSingleColumn( true );
|
||||||
|
|
||||||
mUIStack->setAutoColumnsWidth( true );
|
|
||||||
mUIStack->setFitAllColumnsToWidget( true );
|
|
||||||
mUIStack->setMainColumn( 1 );
|
mUIStack->setMainColumn( 1 );
|
||||||
|
|
||||||
mUIVariables->setAutoColumnsWidth( true );
|
mUIVariables->setMainColumn( 1 );
|
||||||
mUIVariables->setFitAllColumnsToWidget( true );
|
|
||||||
|
|
||||||
mUIBreakpoints->setAutoColumnsWidth( true );
|
|
||||||
mUIBreakpoints->setFitAllColumnsToWidget( true );
|
|
||||||
mUIBreakpoints->setMainColumn( 1 );
|
mUIBreakpoints->setMainColumn( 1 );
|
||||||
|
|
||||||
mUIExpressions->setAutoColumnsWidth( true );
|
|
||||||
mUIExpressions->setFitAllColumnsToWidget( true );
|
|
||||||
mUIExpressions->setMainColumn( 1 );
|
mUIExpressions->setMainColumn( 1 );
|
||||||
|
|
||||||
mUIConsole->setLocked( true );
|
mUIConsole->setLocked( true );
|
||||||
@@ -333,7 +598,7 @@ void StatusDebuggerController::createContainer() {
|
|||||||
mUIConsole->setShowLineNumber( false );
|
mUIConsole->setShowLineNumber( false );
|
||||||
mUIConsole->getDocument().reset();
|
mUIConsole->getDocument().reset();
|
||||||
mUIConsole->setScrollY( mUIConsole->getMaxScroll().y );
|
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;
|
mScrollLocked = mUIConsole->getMaxScroll().y == mUIConsole->getScroll().y;
|
||||||
} );
|
} );
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,8 +3,11 @@
|
|||||||
|
|
||||||
#include "../../uistatusbar.hpp"
|
#include "../../uistatusbar.hpp"
|
||||||
#include "models/breakpointsmodel.hpp"
|
#include "models/breakpointsmodel.hpp"
|
||||||
|
#include <eepp/core/containers.hpp>
|
||||||
|
#include <eepp/scene/eventconnection.hpp>
|
||||||
#include <eepp/system/luapattern.hpp>
|
#include <eepp/system/luapattern.hpp>
|
||||||
#include <eepp/ui/tools/uicodeeditorsplitter.hpp>
|
#include <eepp/ui/tools/uicodeeditorsplitter.hpp>
|
||||||
|
#include <eepp/ui/tools/uitabwidgetsplitter.hpp>
|
||||||
#include <eepp/ui/uicodeeditor.hpp>
|
#include <eepp/ui/uicodeeditor.hpp>
|
||||||
#include <eepp/ui/uirelativelayout.hpp>
|
#include <eepp/ui/uirelativelayout.hpp>
|
||||||
#include <eepp/ui/uiscenenode.hpp>
|
#include <eepp/ui/uiscenenode.hpp>
|
||||||
@@ -34,7 +37,7 @@ class UIBreakpointsTableView : public UITableView {
|
|||||||
UIWidget* createCell( UIWidget* rowWidget, const ModelIndex& index );
|
UIWidget* createCell( UIWidget* rowWidget, const ModelIndex& index );
|
||||||
};
|
};
|
||||||
|
|
||||||
class StatusDebuggerController : public StatusBarElement {
|
class StatusDebuggerController : public StatusBarElement, public UITabWidgetSplitter::Client {
|
||||||
public:
|
public:
|
||||||
enum class State { NotStarted, Running, Paused };
|
enum class State { NotStarted, Running, Paused };
|
||||||
|
|
||||||
@@ -43,7 +46,11 @@ class StatusDebuggerController : public StatusBarElement {
|
|||||||
StatusDebuggerController( UISplitter* mainSplitter, UISceneNode* uiSceneNode,
|
StatusDebuggerController( UISplitter* mainSplitter, UISceneNode* uiSceneNode,
|
||||||
PluginContextProvider* pluginContext );
|
PluginContextProvider* pluginContext );
|
||||||
|
|
||||||
virtual ~StatusDebuggerController() {};
|
virtual ~StatusDebuggerController();
|
||||||
|
|
||||||
|
virtual void show();
|
||||||
|
|
||||||
|
virtual void hide();
|
||||||
|
|
||||||
UIWidget* getWidget();
|
UIWidget* getWidget();
|
||||||
|
|
||||||
@@ -71,8 +78,14 @@ class StatusDebuggerController : public StatusBarElement {
|
|||||||
|
|
||||||
void setDebuggingState( State state );
|
void setDebuggingState( State state );
|
||||||
|
|
||||||
|
const std::string& saveLayout();
|
||||||
|
|
||||||
std::function<void( StatusDebuggerController*, UIWidget* )> onWidgetCreated{ nullptr };
|
std::function<void( StatusDebuggerController*, UIWidget* )> onWidgetCreated{ nullptr };
|
||||||
|
|
||||||
|
void onTabCreated( UITab* tab, UIWidget* widget );
|
||||||
|
|
||||||
|
void onWidgetFocusChange( UIWidget* widget );
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
UIHLinearLayoutCommandExecuter* mContainer{ nullptr };
|
UIHLinearLayoutCommandExecuter* mContainer{ nullptr };
|
||||||
UITableView* mUIThreads{ nullptr };
|
UITableView* mUIThreads{ nullptr };
|
||||||
@@ -90,9 +103,32 @@ class StatusDebuggerController : public StatusBarElement {
|
|||||||
UIPushButton* mUIButStepOver{ nullptr };
|
UIPushButton* mUIButStepOver{ nullptr };
|
||||||
UIPushButton* mUIButStepOut{ nullptr };
|
UIPushButton* mUIButStepOut{ nullptr };
|
||||||
UITabWidget* mUITabWidget{ 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 mScrollLocked{ true };
|
||||||
|
bool mRestoringLayout{ false };
|
||||||
|
bool mRightPanelDropPreview{ false };
|
||||||
|
std::string mSerializedLayout;
|
||||||
|
|
||||||
void createContainer();
|
void createContainer();
|
||||||
|
|
||||||
|
void createTabWidgets();
|
||||||
|
|
||||||
|
void restoreTabLayout();
|
||||||
|
|
||||||
|
void saveTabLayout();
|
||||||
|
|
||||||
|
bool rightPanelHasTabs() const;
|
||||||
|
|
||||||
|
void beginRightPanelDropPreview();
|
||||||
|
|
||||||
|
void endRightPanelDropPreview();
|
||||||
|
|
||||||
|
void updateRightPanel();
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace ecode
|
} // namespace ecode
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ class Font;
|
|||||||
namespace UI {
|
namespace UI {
|
||||||
|
|
||||||
class UISplitter;
|
class UISplitter;
|
||||||
|
class UILayout;
|
||||||
class UITabWidget;
|
class UITabWidget;
|
||||||
class UISceneNode;
|
class UISceneNode;
|
||||||
class UICodeEditor;
|
class UICodeEditor;
|
||||||
@@ -58,6 +59,8 @@ class ProjectDirectoryTree;
|
|||||||
struct TerminalConfig;
|
struct TerminalConfig;
|
||||||
class UIMainLayout;
|
class UIMainLayout;
|
||||||
class UITreeViewFS;
|
class UITreeViewFS;
|
||||||
|
class StatusDebuggerController;
|
||||||
|
class UIRightPanel;
|
||||||
|
|
||||||
class PluginContextProvider {
|
class PluginContextProvider {
|
||||||
public:
|
public:
|
||||||
@@ -65,6 +68,10 @@ class PluginContextProvider {
|
|||||||
|
|
||||||
virtual UISplitter* getMainSplitter() const = 0;
|
virtual UISplitter* getMainSplitter() const = 0;
|
||||||
|
|
||||||
|
virtual UIRightPanel* getRightPanel() const = 0;
|
||||||
|
|
||||||
|
virtual StatusDebuggerController* getStatusDebuggerController() const = 0;
|
||||||
|
|
||||||
virtual UITreeViewFS* getProjectTreeView() const = 0;
|
virtual UITreeViewFS* getProjectTreeView() const = 0;
|
||||||
|
|
||||||
virtual void hideGlobalSearchBar() = 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