mirror of
https://github.com/SpartanJ/eepp.git
synced 2026-05-28 17:16:29 +03:00
Merge branch 'feature/css-display' into develop
This commit is contained in:
98
.agent/plans/layout_separation_plan.md
Normal file
98
.agent/plans/layout_separation_plan.md
Normal file
@@ -0,0 +1,98 @@
|
||||
# UI Layout Separation & CSS Display/Position Support Plan (Strict Implementation Guide)
|
||||
|
||||
This document outlines the architectural plan for decoupling layout logic from specific widgets (like `UIRichText` and `UIHTMLTable`), introducing a robust generic layouter system, and supporting standard CSS `display` and `position` properties.
|
||||
|
||||
**AGENT DIRECTIVE (CRITICAL):** You are Negen. Fulfill this plan iteratively. You MUST compile and run the unit tests (`bin/unit_tests/eepp-unit_tests-debug`) after EVERY step. Do NOT proceed to the next step if there is even a 1-pixel difference in visual layout tests. Take a git stash snapshot (`git stash push -m "Phase X.Y passed"`) upon passing a step.
|
||||
|
||||
---
|
||||
|
||||
## IMPLEMENTATION HAZARDS (READ BEFORE CODING)
|
||||
When migrating logic from Widgets to Layouters, you will face these traps:
|
||||
1. **Pixel vs DP APIs:** Widgets have `getSize()` and `getPixelsSize()`, `getPadding()` and `getPixelsPadding()`. **Layouters MUST exclusively use the `Pixels` variants** (`getPixelsSize()`, `getPixelsPadding()`, `getLayoutPixelsMargin()`). Using the non-pixel variants will cause massive visual regressions due to DPI scaling.
|
||||
2. **Infinite Recursion:** When a Layouter delegates to a widget's intrinsic width calculation, or vice versa, you must strictly manage the dirty flags (e.g., `mIntrinsicWidthsDirty = false`). Failure to clear these flags inside `computeIntrinsicWidths` will cause stack overflows.
|
||||
3. **Table Hierarchy:** In HTML/eepp tables, the hierarchy is `Table` -> `TableSection` (`thead`, `tbody`) -> `TableRow` (`tr`) -> `TableCell` (`td`). The original code often checks `row->getParent()->isType(...)`. If you change sections to `UIHTMLWidget`, be extremely careful not to accidentally assign them a `BlockLayouter` that overrides the `TableLayouter`'s positioning.
|
||||
|
||||
---
|
||||
|
||||
## Phase 1: Core Infrastructure
|
||||
|
||||
**Step 1.1: CSS Properties & Enums**
|
||||
- Add `Display`, `Position`, `Top`, `Right`, `Bottom`, `Left`, `ZIndex` to `PropertyId` enum (`propertydefinition.hpp`).
|
||||
- Create `CSSDisplay` and `CSSPosition` enums (`csslayouttypes.hpp`).
|
||||
- **Validation:** Compile and run all tests. Must pass. (Snapshot)
|
||||
|
||||
**Step 1.2: Layouter Interfaces & Manager**
|
||||
- Create `UILayouter` base interface (`uilayouter.hpp`). Must hold `UIWidget* mContainer` and `bool mValid`.
|
||||
- Create `UILayouterManager` (`uilayoutermanager.hpp`/`cpp`) to spawn layouters.
|
||||
- Create empty skeletons for `BlockLayouter`, `InlineLayouter`, `TableLayouter`, `NoneLayouter`.
|
||||
- **CRITICAL:** Run `premake4` to regenerate makefiles now that new files are added.
|
||||
- **Validation:** Compile and run all tests. Must pass. (Snapshot)
|
||||
|
||||
---
|
||||
|
||||
## Phase 2: UIHTMLWidget Base Class
|
||||
|
||||
**Step 2.1: Implement UIHTMLWidget**
|
||||
- Create `UIHTMLWidget` inheriting from `UILayout`.
|
||||
- Add CSS properties (`mDisplay`, `mPosition`, offsets, `mZIndex`).
|
||||
- Implement `getLayouter()` which lazily instantiates via `UILayouterManager`.
|
||||
- Override `onChildCountChange` and `onDisplayChange` to call `if (mLayouter) mLayouter->invalidate()`.
|
||||
- **Validation:** Compile and run all tests. Must pass. (Snapshot)
|
||||
|
||||
---
|
||||
|
||||
## Phase 3: UIRichText & BlockLayouter (High Risk)
|
||||
|
||||
**Step 3.1: Inheritance and Access Control**
|
||||
- Change `UIRichText` and `UITextSpan` to inherit from `UIHTMLWidget`.
|
||||
- Add `BlockLayouter` and `InlineLayouter` as `friend class` to `UIRichText`, `UILayout`, and `UIWidget`.
|
||||
- **CRITICAL:** In `UIHTMLWidget::getLayouter()`, temporarily return `nullptr`. We want `UIRichText` to still use its monolithic logic while we set up inheritance.
|
||||
- **Validation:** Compile and run `UIRichText.*` tests. Must pass exactly. (Snapshot)
|
||||
|
||||
**Step 3.2: Implement BlockLayouter**
|
||||
- Copy `UIRichText::updateLayout()`, `getMinIntrinsicWidth()`, and `getMaxIntrinsicWidth()` into `BlockLayouter`.
|
||||
- **INVARIANTS TO MAINTAIN:**
|
||||
- You MUST use `getPixelsPadding()` everywhere `mPaddingPx` was used.
|
||||
- You MUST use `getPixelsSize()` everywhere `mSize` was used.
|
||||
- The `mResizedCount` loop must be preserved: if `richText->mResizedCount > 0` after `setInternalPixelsWidth/Height`, `positionRichTextChildren` must run again.
|
||||
- Do NOT skip `MatchParent` children in the `positionRichTextChildren` while loop.
|
||||
- Enable `BlockLayouter` for `UIRichText` (but explicitly disable it for `UI_TYPE_HTML_TABLE_CELL` for now to isolate bugs).
|
||||
- **Validation:** Compile and run `UIRichText.*` tests. **Zero pixel difference allowed.** (Snapshot)
|
||||
|
||||
**Step 3.3: Implement InlineLayouter**
|
||||
- Replicate inline logic for `UITextSpan` into `InlineLayouter`.
|
||||
- **Validation:** Compile and run all tests. (Snapshot)
|
||||
|
||||
---
|
||||
|
||||
## Phase 4: UIHTMLTable Refactoring (High Risk)
|
||||
|
||||
**Step 4.1: Table Base Classes & Friends**
|
||||
- Change `UIHTMLTable`, `UIHTMLTableRow`, `UIHTMLTableHead`, `UIHTMLTableBody`, `UIHTMLTableFooter` to inherit from `UIHTMLWidget`.
|
||||
- Make `TableLayouter` a friend of `UIHTMLTable`.
|
||||
- **CRITICAL:** `UILayouterManager` MUST return `nullptr` for Table Sections and Table Rows. Only the `Table` itself gets `TableLayouter`.
|
||||
- **Validation:** Compile and run `UIHTMLTable.*` tests. (Snapshot)
|
||||
|
||||
**Step 4.2: Implement TableLayouter**
|
||||
- Move `UIHTMLTable::updateLayout()` and `computeIntrinsicWidths()` into `TableLayouter`.
|
||||
- **INVARIANTS TO MAINTAIN:**
|
||||
- `computeIntrinsicWidths` MUST clear `table->mIntrinsicWidthsDirty = false;` at all exit points to prevent recursion.
|
||||
- `currentY` for rows MUST be accumulated exactly as: `Float currentY = padding.Top + mCellspacing - headHeight;` then incremented by `rowHeight + mCellspacing`. Do not attempt to "fix" this math; it offsets based on section anchors.
|
||||
- When determining `headHeight`, use `row->getParent()->isType(UI_TYPE_HTML_TABLE_HEAD)`. Do not check the cell's parent.
|
||||
- Enable `TableLayouter` in `getLayouter()`.
|
||||
- **Validation:** Compile and run `UIHTMLTable.*` tests. **Zero pixel difference allowed.** (Snapshot)
|
||||
|
||||
**Step 4.3: Unify TableCell with BlockLayouter**
|
||||
- Now that TableLayouter is proven, allow `UILayouterManager` to return `BlockLayouter` for `CSSDisplay::TableCell`.
|
||||
- Ensure `BlockLayouter` does NOT override fixed widths if the container is a `TableCell` (the table layouter owns the cell width).
|
||||
- **Validation:** Run all tests. (Snapshot)
|
||||
|
||||
---
|
||||
|
||||
## Phase 5: CSS Position (Out-of-Flow)
|
||||
|
||||
**Step 5.1: Position Implementation**
|
||||
- Implement `getContainingBlock()` in `UIHTMLWidget`.
|
||||
- Update layouters to skip children with `position: absolute|fixed`.
|
||||
- Add `positionOutOfFlowChildren()` to `UIHTMLWidget::updateLayout()` after the layouter finishes.
|
||||
- **Validation:** Compile and run all tests. Existing UI should remain unaffected. (Snapshot)
|
||||
@@ -62,6 +62,10 @@ strong {
|
||||
font-style: bold;
|
||||
}
|
||||
|
||||
small {
|
||||
font-size: smaller;
|
||||
}
|
||||
|
||||
u,
|
||||
ins {
|
||||
text-decoration: underline;
|
||||
|
||||
28
include/eepp/ui/blocklayouter.hpp
Normal file
28
include/eepp/ui/blocklayouter.hpp
Normal file
@@ -0,0 +1,28 @@
|
||||
#ifndef EE_UI_BLOCKLAYOUTER_HPP
|
||||
#define EE_UI_BLOCKLAYOUTER_HPP
|
||||
|
||||
#include <eepp/ui/uilayouter.hpp>
|
||||
|
||||
namespace EE::Graphics {
|
||||
class RichText;
|
||||
}
|
||||
|
||||
using namespace EE::Graphics;
|
||||
|
||||
namespace EE { namespace UI {
|
||||
|
||||
class EE_API BlockLayouter : public UILayouter {
|
||||
public:
|
||||
BlockLayouter( UIWidget* container ) : UILayouter( container ) {}
|
||||
void updateLayout() override;
|
||||
void computeIntrinsicWidths() override;
|
||||
Float getMinIntrinsicWidth() override;
|
||||
Float getMaxIntrinsicWidth() override;
|
||||
|
||||
protected:
|
||||
void positionRichTextChildren( RichText* rt );
|
||||
};
|
||||
|
||||
}} // namespace EE::UI
|
||||
|
||||
#endif
|
||||
@@ -239,6 +239,14 @@ enum class PropertyId : Uint32 {
|
||||
Rows = String::hash( "rows" ),
|
||||
Cols = String::hash( "cols" ),
|
||||
InputMode = String::hash( "input-mode" ),
|
||||
Hidden = String::hash( "hidden" ),
|
||||
Display = String::hash( "display" ),
|
||||
Position = String::hash( "position" ),
|
||||
Top = String::hash( "top" ),
|
||||
Right = String::hash( "right" ),
|
||||
Bottom = String::hash( "bottom" ),
|
||||
Left = String::hash( "left" ),
|
||||
ZIndex = String::hash( "z-index" ),
|
||||
};
|
||||
|
||||
enum class PropertyType : Uint32 {
|
||||
|
||||
39
include/eepp/ui/csslayouttypes.hpp
Normal file
39
include/eepp/ui/csslayouttypes.hpp
Normal file
@@ -0,0 +1,39 @@
|
||||
#ifndef EE_UI_CSSLAYOUTTYPES_HPP
|
||||
#define EE_UI_CSSLAYOUTTYPES_HPP
|
||||
|
||||
#include <eepp/config.hpp>
|
||||
#include <string>
|
||||
|
||||
namespace EE { namespace UI {
|
||||
|
||||
enum class CSSDisplay {
|
||||
Inline,
|
||||
Block,
|
||||
InlineBlock,
|
||||
Flex,
|
||||
None,
|
||||
Table,
|
||||
TableRow,
|
||||
TableCell,
|
||||
TableHead,
|
||||
TableBody,
|
||||
TableFooter
|
||||
};
|
||||
|
||||
struct CSSDisplayHelper {
|
||||
static std::string toString( CSSDisplay display );
|
||||
|
||||
static CSSDisplay fromString( std::string_view val );
|
||||
};
|
||||
|
||||
enum class CSSPosition { Static, Relative, Absolute, Fixed, Sticky };
|
||||
|
||||
struct CSSPositionHelper {
|
||||
static std::string toString( CSSPosition position );
|
||||
|
||||
static CSSPosition fromString( std::string_view val );
|
||||
};
|
||||
|
||||
}} // namespace EE::UI
|
||||
|
||||
#endif
|
||||
17
include/eepp/ui/inlinelayouter.hpp
Normal file
17
include/eepp/ui/inlinelayouter.hpp
Normal file
@@ -0,0 +1,17 @@
|
||||
#ifndef EE_UI_INLINELAYOUTER_HPP
|
||||
#define EE_UI_INLINELAYOUTER_HPP
|
||||
|
||||
#include <eepp/ui/uilayouter.hpp>
|
||||
|
||||
namespace EE { namespace UI {
|
||||
|
||||
class EE_API InlineLayouter : public UILayouter {
|
||||
public:
|
||||
InlineLayouter( UIWidget* container ) : UILayouter( container ) {}
|
||||
void updateLayout() override {}
|
||||
void computeIntrinsicWidths() override {}
|
||||
};
|
||||
|
||||
}} // namespace EE::UI
|
||||
|
||||
#endif
|
||||
17
include/eepp/ui/nonelayouter.hpp
Normal file
17
include/eepp/ui/nonelayouter.hpp
Normal file
@@ -0,0 +1,17 @@
|
||||
#ifndef EE_UI_NONELAYOUTER_HPP
|
||||
#define EE_UI_NONELAYOUTER_HPP
|
||||
|
||||
#include <eepp/ui/uilayouter.hpp>
|
||||
|
||||
namespace EE { namespace UI {
|
||||
|
||||
class EE_API NoneLayouter : public UILayouter {
|
||||
public:
|
||||
NoneLayouter( UIWidget* container ) : UILayouter( container ) {}
|
||||
void updateLayout() override {}
|
||||
void computeIntrinsicWidths() override {}
|
||||
};
|
||||
|
||||
}} // namespace EE::UI
|
||||
|
||||
#endif
|
||||
51
include/eepp/ui/tablelayouter.hpp
Normal file
51
include/eepp/ui/tablelayouter.hpp
Normal file
@@ -0,0 +1,51 @@
|
||||
#ifndef EE_UI_TABLELAYOUTER_HPP
|
||||
#define EE_UI_TABLELAYOUTER_HPP
|
||||
|
||||
#include <eepp/ui/uilayouter.hpp>
|
||||
#include <eepp/core/small_vector.hpp>
|
||||
|
||||
namespace EE { namespace UI {
|
||||
|
||||
class UIHTMLTableRow;
|
||||
class UIHTMLTableCell;
|
||||
class UIHTMLTableHead;
|
||||
class UIHTMLTableBody;
|
||||
class UIHTMLTableFooter;
|
||||
|
||||
enum class TableLayout { Auto, Fixed };
|
||||
|
||||
class EE_API TableLayouter : public UILayouter {
|
||||
public:
|
||||
TableLayouter( UIWidget* container ) : UILayouter( container ) {}
|
||||
void updateLayout() override;
|
||||
void computeIntrinsicWidths() override;
|
||||
|
||||
void setTableLayout( TableLayout layout );
|
||||
TableLayout getTableLayout() const;
|
||||
void setCellpadding( Float padding );
|
||||
Float getCellpadding() const;
|
||||
void setCellspacing( Float spacing );
|
||||
Float getCellspacing() const;
|
||||
|
||||
Float getMinIntrinsicWidth() override;
|
||||
Float getMaxIntrinsicWidth() override;
|
||||
|
||||
protected:
|
||||
SmallVector<UIHTMLTableRow*> mRows;
|
||||
SmallVector<Float> mColWidths;
|
||||
SmallVector<UIHTMLTableCell*> mCells;
|
||||
SmallVector<Uint32> mRowCellOffsets;
|
||||
SmallVector<Float> mColMinWidths;
|
||||
SmallVector<Float> mColMaxWidths;
|
||||
SmallVector<Float> mColSpecifiedWidths;
|
||||
TableLayout mTableLayout{ TableLayout::Auto };
|
||||
UIHTMLTableHead* mHead{ nullptr };
|
||||
UIHTMLTableBody* mBody{ nullptr };
|
||||
UIHTMLTableFooter* mFooter{ nullptr };
|
||||
Float mCellpadding{ 0 };
|
||||
Float mCellspacing{ 0 };
|
||||
};
|
||||
|
||||
}} // namespace EE::UI
|
||||
|
||||
#endif
|
||||
@@ -113,6 +113,7 @@ enum UINodeType {
|
||||
UI_TYPE_TEXTSPAN,
|
||||
UI_TYPE_RICHTEXT,
|
||||
UI_TYPE_MARKDOWNVIEW,
|
||||
UI_TYPE_HTML_WIDGET,
|
||||
UI_TYPE_HTML_TABLE,
|
||||
UI_TYPE_HTML_TABLE_HEAD,
|
||||
UI_TYPE_HTML_TABLE_BODY,
|
||||
|
||||
@@ -2,29 +2,18 @@
|
||||
#define EE_UI_UIHTMLTABLE_HPP
|
||||
|
||||
#include <eepp/core/small_vector.hpp>
|
||||
#include <eepp/ui/uilayout.hpp>
|
||||
#include <eepp/ui/uihtmlwidget.hpp>
|
||||
#include <eepp/ui/uirichtext.hpp>
|
||||
|
||||
namespace EE { namespace UI {
|
||||
|
||||
class UIHTMLTableRow;
|
||||
class UIHTMLTableCell;
|
||||
class UIHTMLTableHead;
|
||||
class UIHTMLTableBody;
|
||||
class UIHTMLTableFooter;
|
||||
|
||||
enum class TableLayout { Auto, Fixed };
|
||||
|
||||
class EE_API UIHTMLTable : public UILayout {
|
||||
class EE_API UIHTMLTable : public UIHTMLWidget {
|
||||
public:
|
||||
friend class TableLayouter;
|
||||
static UIHTMLTable* New();
|
||||
|
||||
UIHTMLTable();
|
||||
|
||||
void setTableLayout( TableLayout layout );
|
||||
|
||||
TableLayout getTableLayout() const;
|
||||
|
||||
virtual Uint32 getType() const;
|
||||
|
||||
virtual bool isType( const Uint32& type ) const;
|
||||
@@ -41,25 +30,12 @@ class EE_API UIHTMLTable : public UILayout {
|
||||
virtual Uint32 onMessage( const NodeMessage* Msg );
|
||||
|
||||
void computeIntrinsicWidths() const;
|
||||
|
||||
SmallVector<UIHTMLTableRow*> mRows;
|
||||
SmallVector<Float> mColWidths;
|
||||
SmallVector<UIHTMLTableCell*> mCells;
|
||||
SmallVector<Uint32> mRowCellOffsets;
|
||||
mutable SmallVector<Float> mColMinWidths;
|
||||
mutable SmallVector<Float> mColMaxWidths;
|
||||
mutable SmallVector<Float> mColSpecifiedWidths;
|
||||
TableLayout mTableLayout{ TableLayout::Auto };
|
||||
mutable UIHTMLTableHead* mHead{ nullptr };
|
||||
mutable UIHTMLTableBody* mBody{ nullptr };
|
||||
mutable UIHTMLTableFooter* mFooter{ nullptr };
|
||||
Float mCellpadding{ 0 };
|
||||
Float mCellspacing{ 0 };
|
||||
};
|
||||
|
||||
class EE_API UIHTMLTableCell : public UIRichText {
|
||||
public:
|
||||
friend class UIHTMLTable;
|
||||
friend class UIHTMLTable;
|
||||
friend class TableLayouter;
|
||||
|
||||
static UIHTMLTableCell* New( const std::string& tag );
|
||||
|
||||
@@ -79,7 +55,7 @@ class EE_API UIHTMLTableCell : public UIRichText {
|
||||
Uint32 mColspan{ 1 };
|
||||
};
|
||||
|
||||
class EE_API UIHTMLTableRow : public UIWidget {
|
||||
class EE_API UIHTMLTableRow : public UIHTMLWidget {
|
||||
public:
|
||||
static UIHTMLTableRow* New();
|
||||
|
||||
@@ -90,7 +66,7 @@ class EE_API UIHTMLTableRow : public UIWidget {
|
||||
virtual bool isType( const Uint32& type ) const;
|
||||
};
|
||||
|
||||
class EE_API UIHTMLTableHead : public UIWidget {
|
||||
class EE_API UIHTMLTableHead : public UIHTMLWidget {
|
||||
public:
|
||||
static UIHTMLTableHead* New();
|
||||
|
||||
@@ -101,7 +77,7 @@ class EE_API UIHTMLTableHead : public UIWidget {
|
||||
virtual bool isType( const Uint32& type ) const;
|
||||
};
|
||||
|
||||
class EE_API UIHTMLTableFooter : public UIWidget {
|
||||
class EE_API UIHTMLTableFooter : public UIHTMLWidget {
|
||||
public:
|
||||
static UIHTMLTableFooter* New();
|
||||
|
||||
@@ -112,7 +88,7 @@ class EE_API UIHTMLTableFooter : public UIWidget {
|
||||
virtual bool isType( const Uint32& type ) const;
|
||||
};
|
||||
|
||||
class EE_API UIHTMLTableBody : public UIWidget {
|
||||
class EE_API UIHTMLTableBody : public UIHTMLWidget {
|
||||
public:
|
||||
static UIHTMLTableBody* New();
|
||||
|
||||
|
||||
63
include/eepp/ui/uihtmlwidget.hpp
Normal file
63
include/eepp/ui/uihtmlwidget.hpp
Normal file
@@ -0,0 +1,63 @@
|
||||
#ifndef EE_UI_UIHTMLWIDGET_HPP
|
||||
#define EE_UI_UIHTMLWIDGET_HPP
|
||||
|
||||
#include <eepp/ui/csslayouttypes.hpp>
|
||||
#include <eepp/ui/uilayout.hpp>
|
||||
|
||||
namespace EE { namespace Graphics {
|
||||
class RichText;
|
||||
}} // namespace EE::Graphics
|
||||
|
||||
namespace EE { namespace UI {
|
||||
|
||||
class UILayouter;
|
||||
|
||||
class EE_API UIHTMLWidget : public UILayout {
|
||||
public:
|
||||
static UIHTMLWidget* New();
|
||||
|
||||
UIHTMLWidget( const std::string& tag = "htmlwidget" );
|
||||
|
||||
virtual ~UIHTMLWidget();
|
||||
|
||||
virtual Uint32 getType() const;
|
||||
|
||||
virtual bool isType( const Uint32& type ) const;
|
||||
|
||||
UILayouter* getLayouter();
|
||||
|
||||
virtual bool isPacking() const;
|
||||
|
||||
virtual void onDisplayChange();
|
||||
|
||||
CSSDisplay getDisplay() const { return mDisplay; }
|
||||
void setDisplay( CSSDisplay display );
|
||||
|
||||
CSSPosition getCSSPosition() const { return mPosition; }
|
||||
void setCSSPosition( CSSPosition position );
|
||||
|
||||
const Rectf& getOffsets() const { return mOffsets; }
|
||||
void setOffsets( const Rectf& offsets );
|
||||
|
||||
int getZIndex() const { return mZIndex; }
|
||||
void setZIndex( int zIndex );
|
||||
|
||||
virtual std::string getPropertyString( const PropertyDefinition* propertyDef,
|
||||
const Uint32& state = 0 ) const;
|
||||
virtual bool applyProperty( const StyleSheetProperty& attribute );
|
||||
|
||||
virtual RichText* getRichTextPtr() { return nullptr; }
|
||||
|
||||
virtual void invalidateIntrinsicSize();
|
||||
|
||||
protected:
|
||||
CSSDisplay mDisplay{ CSSDisplay::Block };
|
||||
CSSPosition mPosition{ CSSPosition::Static };
|
||||
Rectf mOffsets{ 0, 0, 0, 0 };
|
||||
int mZIndex{ 0 };
|
||||
UILayouter* mLayouter{ nullptr };
|
||||
};
|
||||
|
||||
}} // namespace EE::UI
|
||||
|
||||
#endif
|
||||
@@ -19,12 +19,14 @@ class EE_API UILayout : public UIWidget {
|
||||
|
||||
void setGravityOwner( bool gravityOwner );
|
||||
|
||||
bool isPacking() const { return mPacking; }
|
||||
virtual bool isPacking() const { return mPacking; }
|
||||
|
||||
bool isLayoutDirty() const { return mDirtyLayout; }
|
||||
|
||||
void onAutoSizeChild( UIWidget* child );
|
||||
protected:
|
||||
friend class UISceneNode;
|
||||
friend class UILayouter;
|
||||
|
||||
UnorderedSet<UILayout*> mLayouts;
|
||||
bool mDirtyLayout{ false };
|
||||
@@ -52,8 +54,6 @@ class EE_API UILayout : public UIWidget {
|
||||
void setLayoutDirty();
|
||||
|
||||
bool setMatchParentIfNeededVerticalGrowth();
|
||||
|
||||
void onAutoSizeChild( UIWidget* child );
|
||||
};
|
||||
|
||||
}} // namespace EE::UI
|
||||
|
||||
37
include/eepp/ui/uilayouter.hpp
Normal file
37
include/eepp/ui/uilayouter.hpp
Normal file
@@ -0,0 +1,37 @@
|
||||
#ifndef EE_UI_UILAYOUTER_HPP
|
||||
#define EE_UI_UILAYOUTER_HPP
|
||||
|
||||
#include <cstddef>
|
||||
#include <eepp/config.hpp>
|
||||
|
||||
namespace EE { namespace UI {
|
||||
|
||||
class UIWidget;
|
||||
|
||||
class EE_API UILayouter {
|
||||
public:
|
||||
UILayouter( UIWidget* container ) : mContainer( container ) {}
|
||||
virtual ~UILayouter() {}
|
||||
|
||||
virtual void updateLayout() = 0;
|
||||
virtual void computeIntrinsicWidths() {}
|
||||
virtual Float getMinIntrinsicWidth() { return 0; }
|
||||
virtual Float getMaxIntrinsicWidth() { return 0; }
|
||||
|
||||
virtual void invalidateIntrinsicWidths() { mIntrinsicWidthsDirty = true; }
|
||||
virtual bool isPacking() const { return mPacking; }
|
||||
|
||||
protected:
|
||||
UIWidget* mContainer;
|
||||
bool mPacking{ false };
|
||||
size_t mResizedCount{ 0 };
|
||||
bool mIntrinsicWidthsDirty{ true };
|
||||
Float mMinIntrinsicWidth{ 0 };
|
||||
Float mMaxIntrinsicWidth{ 0 };
|
||||
|
||||
void setMatchParentIfNeededVerticalGrowth();
|
||||
};
|
||||
|
||||
}} // namespace EE::UI
|
||||
|
||||
#endif
|
||||
19
include/eepp/ui/uilayoutermanager.hpp
Normal file
19
include/eepp/ui/uilayoutermanager.hpp
Normal file
@@ -0,0 +1,19 @@
|
||||
#ifndef EE_UI_UILAYOUTERMANAGER_HPP
|
||||
#define EE_UI_UILAYOUTERMANAGER_HPP
|
||||
|
||||
#include <eepp/config.hpp>
|
||||
#include <eepp/ui/csslayouttypes.hpp>
|
||||
|
||||
namespace EE { namespace UI {
|
||||
|
||||
class UILayouter;
|
||||
class UIWidget;
|
||||
|
||||
class EE_API UILayouterManager {
|
||||
public:
|
||||
static UILayouter* create( CSSDisplay display, UIWidget* container );
|
||||
};
|
||||
|
||||
}} // namespace EE::UI
|
||||
|
||||
#endif
|
||||
@@ -32,6 +32,9 @@ class UIWidget;
|
||||
|
||||
class EE_API UINode : public Node {
|
||||
public:
|
||||
friend class BlockLayouter;
|
||||
friend class InlineLayouter;
|
||||
friend class TableLayouter;
|
||||
/**
|
||||
* @brief Creates a new UINode instance.
|
||||
*
|
||||
|
||||
@@ -89,6 +89,8 @@ class EE_API UIPushButton : public UIWidget {
|
||||
|
||||
UIPushButton* setExpandTextView( bool expand );
|
||||
|
||||
virtual void loadFromXmlNode( const pugi::xml_node& node );
|
||||
|
||||
protected:
|
||||
UIImage* mIcon;
|
||||
UITextView* mTextBox;
|
||||
|
||||
@@ -2,12 +2,18 @@
|
||||
#define EE_UI_UIRICHTEXT_HPP
|
||||
|
||||
#include <eepp/graphics/richtext.hpp>
|
||||
#include <eepp/ui/uihtmlwidget.hpp>
|
||||
#include <eepp/ui/uilayout.hpp>
|
||||
|
||||
namespace EE { namespace UI {
|
||||
|
||||
class EE_API UIRichText : public UILayout {
|
||||
class EE_API UIRichText : public UIHTMLWidget {
|
||||
public:
|
||||
enum class IntrinsicMode { None, Min, Max };
|
||||
|
||||
static void rebuildRichText( UILayout* container, RichText& richText,
|
||||
IntrinsicMode mode = IntrinsicMode::None );
|
||||
|
||||
static UIRichText* New();
|
||||
|
||||
static UIRichText* NewWithTag( const std::string& tag );
|
||||
@@ -127,12 +133,13 @@ class EE_API UIRichText : public UILayout {
|
||||
|
||||
virtual void updateLayout();
|
||||
|
||||
virtual RichText* getRichTextPtr() { return &mRichText; }
|
||||
|
||||
protected:
|
||||
RichText mRichText;
|
||||
Int64 mSelCurInit{ 0 };
|
||||
Int64 mSelCurEnd{ 0 };
|
||||
bool mSelecting{ false };
|
||||
size_t mResizedCount{ 0 };
|
||||
|
||||
explicit UIRichText( const std::string& tag = "richtext" );
|
||||
|
||||
@@ -155,9 +162,7 @@ class EE_API UIRichText : public UILayout {
|
||||
Int64 selCurInit() const { return mSelCurInit; }
|
||||
Int64 selCurEnd() const { return mSelCurEnd; }
|
||||
|
||||
enum class IntrinsicMode { None, Min, Max };
|
||||
void rebuildRichText( RichText& richText, IntrinsicMode mode = IntrinsicMode::None );
|
||||
void positionChildren();
|
||||
void updateDefaultSpansStyle();
|
||||
};
|
||||
|
||||
@@ -184,6 +189,18 @@ class EE_API UIHTMLBody : public UIRichText {
|
||||
UIHTMLBody( const std::string& tag = "body" );
|
||||
};
|
||||
|
||||
class EE_API UILineBreak : public UIRichText {
|
||||
public:
|
||||
static UILineBreak* New( const std::string& tag );
|
||||
|
||||
virtual Uint32 getType() const;
|
||||
|
||||
bool isType( const Uint32& type ) const;
|
||||
|
||||
protected:
|
||||
UILineBreak( const std::string& tag = "br" );
|
||||
};
|
||||
|
||||
}} // namespace EE::UI
|
||||
|
||||
#endif
|
||||
|
||||
@@ -2,13 +2,14 @@
|
||||
#define EE_UI_UITEXTSPAN_HPP
|
||||
|
||||
#include <eepp/ui/uifontstyleconfig.hpp>
|
||||
#include <eepp/ui/uihtmlwidget.hpp>
|
||||
#include <eepp/ui/uiwidget.hpp>
|
||||
|
||||
namespace EE { namespace UI {
|
||||
|
||||
using SpanHitBoxes = SmallVector<Rectf, 4>;
|
||||
|
||||
class EE_API UITextSpan : public UIWidget {
|
||||
class EE_API UITextSpan : public UIHTMLWidget {
|
||||
public:
|
||||
static UITextSpan* New();
|
||||
|
||||
@@ -32,6 +33,8 @@ class EE_API UITextSpan : public UIWidget {
|
||||
|
||||
static UITextSpan* NewCode() { return NewWithTag( "code" ); }
|
||||
|
||||
static UITextSpan* NewSmall() { return NewWithTag( "small" ); }
|
||||
|
||||
virtual ~UITextSpan();
|
||||
|
||||
virtual Uint32 getType() const;
|
||||
|
||||
@@ -531,7 +531,7 @@ class EE_API UIWidget : public UINode {
|
||||
* Forces a recalculation of the intrinsic widths on the next call to
|
||||
* getMinIntrinsicWidth() or getMaxIntrinsicWidth().
|
||||
*/
|
||||
void invalidateIntrinsicSize();
|
||||
virtual void invalidateIntrinsicSize();
|
||||
|
||||
/**
|
||||
* @brief Loads widget configuration from an XML node.
|
||||
|
||||
88
src/eepp/graphics/csslayouttypes.cpp
Normal file
88
src/eepp/graphics/csslayouttypes.cpp
Normal file
@@ -0,0 +1,88 @@
|
||||
#include <eepp/ui/csslayouttypes.hpp>
|
||||
|
||||
namespace EE { namespace UI {
|
||||
|
||||
std::string CSSDisplayHelper::toString( CSSDisplay display ) {
|
||||
switch ( display ) {
|
||||
case CSSDisplay::Inline:
|
||||
return "inline";
|
||||
case CSSDisplay::InlineBlock:
|
||||
return "inline-block";
|
||||
case CSSDisplay::Flex:
|
||||
return "flex";
|
||||
case CSSDisplay::None:
|
||||
return "none";
|
||||
case CSSDisplay::Table:
|
||||
return "table";
|
||||
case CSSDisplay::TableRow:
|
||||
return "table-row";
|
||||
case CSSDisplay::TableCell:
|
||||
return "table-cell";
|
||||
case CSSDisplay::TableHead:
|
||||
return "table-header-group";
|
||||
case CSSDisplay::TableBody:
|
||||
return "table-row-group";
|
||||
case CSSDisplay::TableFooter:
|
||||
return "table-footer-group";
|
||||
case CSSDisplay::Block:
|
||||
default:
|
||||
return "block";
|
||||
}
|
||||
};
|
||||
|
||||
CSSDisplay CSSDisplayHelper::fromString( std::string_view val ) {
|
||||
CSSDisplay display = CSSDisplay::Block;
|
||||
if ( val == "inline" )
|
||||
display = CSSDisplay::Inline;
|
||||
else if ( val == "inline-block" )
|
||||
display = CSSDisplay::InlineBlock;
|
||||
else if ( val == "none" )
|
||||
display = CSSDisplay::None;
|
||||
else if ( val == "table" )
|
||||
display = CSSDisplay::Table;
|
||||
else if ( val == "table-row" )
|
||||
display = CSSDisplay::TableRow;
|
||||
else if ( val == "table-cell" )
|
||||
display = CSSDisplay::TableCell;
|
||||
else if ( val == "table-header-group" )
|
||||
display = CSSDisplay::TableHead;
|
||||
else if ( val == "table-row-group" )
|
||||
display = CSSDisplay::TableBody;
|
||||
else if ( val == "table-footer-group" )
|
||||
display = CSSDisplay::TableFooter;
|
||||
else if ( val == "flex" )
|
||||
display = CSSDisplay::Flex;
|
||||
return display;
|
||||
}
|
||||
|
||||
std::string CSSPositionHelper::toString( CSSPosition position ) {
|
||||
switch ( position ) {
|
||||
case CSSPosition::Relative:
|
||||
return "relative";
|
||||
case CSSPosition::Absolute:
|
||||
return "absolute";
|
||||
case CSSPosition::Fixed:
|
||||
return "fixed";
|
||||
case CSSPosition::Sticky:
|
||||
return "sticky";
|
||||
case CSSPosition::Static:
|
||||
default: {
|
||||
}
|
||||
}
|
||||
return "static";
|
||||
}
|
||||
|
||||
CSSPosition CSSPositionHelper::fromString( std::string_view val ) {
|
||||
CSSPosition position = CSSPosition::Static;
|
||||
if ( val == "relative" )
|
||||
position = CSSPosition::Relative;
|
||||
else if ( val == "absolute" )
|
||||
position = CSSPosition::Absolute;
|
||||
else if ( val == "fixed" )
|
||||
position = CSSPosition::Fixed;
|
||||
else if ( val == "sticky" )
|
||||
position = CSSPosition::Sticky;
|
||||
return position;
|
||||
}
|
||||
|
||||
}} // namespace EE::UI
|
||||
243
src/eepp/ui/blocklayouter.cpp
Normal file
243
src/eepp/ui/blocklayouter.cpp
Normal file
@@ -0,0 +1,243 @@
|
||||
#include <eepp/graphics/richtext.hpp>
|
||||
#include <eepp/ui/blocklayouter.hpp>
|
||||
#include <eepp/ui/uihtmlwidget.hpp>
|
||||
#include <eepp/ui/uirichtext.hpp>
|
||||
#include <eepp/ui/uistyle.hpp>
|
||||
#include <eepp/ui/uitextspan.hpp>
|
||||
|
||||
namespace EE { namespace UI {
|
||||
|
||||
Float BlockLayouter::getMinIntrinsicWidth() {
|
||||
computeIntrinsicWidths();
|
||||
return mMinIntrinsicWidth;
|
||||
}
|
||||
|
||||
Float BlockLayouter::getMaxIntrinsicWidth() {
|
||||
computeIntrinsicWidths();
|
||||
return mMaxIntrinsicWidth;
|
||||
}
|
||||
|
||||
void BlockLayouter::computeIntrinsicWidths() {
|
||||
if ( !mContainer->isType( UI_TYPE_HTML_WIDGET ) )
|
||||
return;
|
||||
|
||||
auto* widget = mContainer->asType<UIHTMLWidget>();
|
||||
auto* rt = widget->getRichTextPtr();
|
||||
if ( rt == nullptr )
|
||||
return;
|
||||
|
||||
if ( mContainer->getLayoutWidthPolicy() == SizePolicy::Fixed ) {
|
||||
// Do nothing here, UIWidget handles fixed width.
|
||||
return;
|
||||
}
|
||||
|
||||
if ( mIntrinsicWidthsDirty ) {
|
||||
RichText tmpRt( *rt );
|
||||
UIRichText::rebuildRichText( widget, tmpRt, UIRichText::IntrinsicMode::Min );
|
||||
mMinIntrinsicWidth = tmpRt.getMinIntrinsicWidth() + mContainer->getPixelsPadding().Left +
|
||||
mContainer->getPixelsPadding().Right;
|
||||
UIRichText::rebuildRichText( widget, tmpRt, UIRichText::IntrinsicMode::Max );
|
||||
mMaxIntrinsicWidth = tmpRt.getMaxIntrinsicWidth() + mContainer->getPixelsPadding().Left +
|
||||
mContainer->getPixelsPadding().Right;
|
||||
mIntrinsicWidthsDirty = false;
|
||||
}
|
||||
}
|
||||
|
||||
void BlockLayouter::updateLayout() {
|
||||
if ( !mContainer->isType( UI_TYPE_HTML_WIDGET ) )
|
||||
return;
|
||||
|
||||
auto* widget = mContainer->asType<UIHTMLWidget>();
|
||||
auto* rt = widget->getRichTextPtr();
|
||||
if ( rt == nullptr || mPacking )
|
||||
return;
|
||||
mResizedCount = 0;
|
||||
mPacking = true;
|
||||
|
||||
setMatchParentIfNeededVerticalGrowth();
|
||||
|
||||
const StyleSheetProperty* prop = nullptr;
|
||||
if ( mContainer->getLayoutWidthPolicy() == SizePolicy::Fixed && mContainer->getUIStyle() &&
|
||||
( prop = mContainer->getUIStyle()->getProperty( PropertyId::Width ) ) ) {
|
||||
mContainer->setInternalPixelsSize(
|
||||
{ mContainer->lengthFromValue( *prop ), mContainer->getPixelsSize().getHeight() } );
|
||||
}
|
||||
|
||||
UIRichText::rebuildRichText( widget, *rt );
|
||||
|
||||
rt->updateLayout();
|
||||
|
||||
positionRichTextChildren( rt );
|
||||
|
||||
Float totW = mContainer->getPixelsSize().getWidth();
|
||||
if ( mContainer->getLayoutWidthPolicy() == SizePolicy::WrapContent ) {
|
||||
totW = rt->getSize().getWidth() + mContainer->getPixelsPadding().Left +
|
||||
mContainer->getPixelsPadding().Right;
|
||||
if ( !mContainer->getMaxWidthEq().empty() && totW > mContainer->getMaxSizePx().getWidth() )
|
||||
mContainer->setClipType( ClipType::ContentBox );
|
||||
}
|
||||
|
||||
if ( totW != mContainer->getPixelsSize().getWidth() ||
|
||||
mContainer->getLayoutWidthPolicy() == SizePolicy::WrapContent )
|
||||
mContainer->setInternalPixelsWidth( totW );
|
||||
|
||||
Float totH = mContainer->getPixelsSize().getHeight();
|
||||
if ( mContainer->getLayoutHeightPolicy() == SizePolicy::WrapContent ) {
|
||||
totH = rt->getSize().getHeight() + mContainer->getPixelsPadding().Top +
|
||||
mContainer->getPixelsPadding().Bottom;
|
||||
if ( !mContainer->getMaxHeightEq().empty() &&
|
||||
totH > mContainer->getMaxSizePx().getHeight() )
|
||||
mContainer->setClipType( ClipType::ContentBox );
|
||||
}
|
||||
|
||||
if ( totH != mContainer->getPixelsSize().getHeight() ||
|
||||
mContainer->getLayoutHeightPolicy() == SizePolicy::WrapContent )
|
||||
mContainer->setInternalPixelsHeight( totH );
|
||||
|
||||
if ( mResizedCount > 0 )
|
||||
positionRichTextChildren( rt );
|
||||
|
||||
mPacking = false;
|
||||
mResizedCount = 0;
|
||||
}
|
||||
|
||||
void BlockLayouter::positionRichTextChildren( Graphics::RichText* rt ) {
|
||||
const auto& lines = rt->getLines();
|
||||
Node* child = mContainer->getFirstChild();
|
||||
|
||||
size_t currentLine = 0;
|
||||
size_t currentSpan = 0;
|
||||
|
||||
auto getNextCustomSpan = [&]() -> const RichText::RenderSpan* {
|
||||
while ( currentLine < lines.size() ) {
|
||||
const auto& line = lines[currentLine];
|
||||
while ( currentSpan < line.spans.size() ) {
|
||||
const auto& span = line.spans[currentSpan];
|
||||
currentSpan++;
|
||||
if ( std::holds_alternative<RichText::CustomBlock>( span.block ) )
|
||||
return &span;
|
||||
}
|
||||
currentSpan = 0;
|
||||
currentLine++;
|
||||
}
|
||||
return nullptr;
|
||||
};
|
||||
|
||||
Int64 curCharIdx = 0;
|
||||
|
||||
auto processWidget = [&]( UIWidget* widget, auto& processWidgetRef ) -> Rectf {
|
||||
constexpr Float maxF = std::numeric_limits<Float>::max();
|
||||
constexpr Float lowF = std::numeric_limits<Float>::lowest();
|
||||
Rectf bounds( maxF, maxF, lowF, lowF );
|
||||
|
||||
Vector2f offset;
|
||||
Node* p = widget->getParent();
|
||||
while ( p && p != mContainer ) {
|
||||
offset += p->isWidget() ? p->asType<UIWidget>()->getPixelsPosition() : p->getPosition();
|
||||
p = p->getParent();
|
||||
}
|
||||
|
||||
if ( widget->isType( UI_TYPE_TEXTSPAN ) ) {
|
||||
UITextSpan* textSpan = widget->asType<UITextSpan>();
|
||||
Int64 startChar = curCharIdx;
|
||||
Int64 endChar = curCharIdx;
|
||||
if ( !textSpan->getText().empty() ) {
|
||||
endChar += textSpan->getText().length();
|
||||
curCharIdx = endChar;
|
||||
}
|
||||
|
||||
auto& hitBoxes = textSpan->getHitBoxes();
|
||||
hitBoxes.clear();
|
||||
|
||||
if ( startChar < endChar ) {
|
||||
for ( const auto& line : lines ) {
|
||||
bool passedText = false;
|
||||
for ( const auto& rspan : line.spans ) {
|
||||
if ( rspan.startCharIndex >= startChar && rspan.endCharIndex <= endChar ) {
|
||||
Rectf hb( mContainer->getPixelsPadding().Left + rspan.position.x,
|
||||
mContainer->getPixelsPadding().Top + line.y +
|
||||
rspan.position.y,
|
||||
mContainer->getPixelsPadding().Left + rspan.position.x +
|
||||
rspan.size.getWidth(),
|
||||
mContainer->getPixelsPadding().Top + line.y +
|
||||
rspan.position.y + rspan.size.getHeight() );
|
||||
|
||||
hitBoxes.push_back( hb );
|
||||
bounds.expand( hb );
|
||||
} else if ( rspan.startCharIndex > endChar ) {
|
||||
passedText = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if ( passedText )
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Node* spanChild = widget->getFirstChild();
|
||||
while ( spanChild != NULL ) {
|
||||
if ( spanChild->isWidget() ) {
|
||||
bounds.expand(
|
||||
processWidgetRef( spanChild->asType<UIWidget>(), processWidgetRef ) );
|
||||
}
|
||||
spanChild = spanChild->getNextNode();
|
||||
}
|
||||
|
||||
if ( bounds.Left <= bounds.Right && bounds.Top <= bounds.Bottom ) {
|
||||
Vector2f boundsPos = bounds.getPosition();
|
||||
|
||||
widget->setPixelsPosition( boundsPos - offset );
|
||||
if ( bounds.getSize() != widget->getPixelsSize() ) {
|
||||
widget->setPixelsSize( bounds.getSize() );
|
||||
mResizedCount++;
|
||||
}
|
||||
|
||||
for ( auto& hb : hitBoxes )
|
||||
hb.move( -boundsPos );
|
||||
|
||||
} else {
|
||||
hitBoxes.clear();
|
||||
}
|
||||
|
||||
} else if ( widget->isType( UI_TYPE_BR ) ) {
|
||||
curCharIdx += 1;
|
||||
Vector2f pos;
|
||||
if ( widget->getPrevNode() && widget->getPrevNode()->isWidget() ) {
|
||||
pos = widget->getPrevNode()->asType<UIWidget>()->getPixelsPosition();
|
||||
pos.y += widget->getPrevNode()->getPixelsSize().getHeight();
|
||||
}
|
||||
widget->setPixelsPosition( pos );
|
||||
widget->setPixelsSize( { eemax( 0.f, mContainer->getPixelsSize().getWidth() -
|
||||
mContainer->getPixelsPadding().Left -
|
||||
mContainer->getPixelsPadding().Right ),
|
||||
0 } );
|
||||
} else {
|
||||
curCharIdx += 1;
|
||||
const auto* span = getNextCustomSpan();
|
||||
if ( span ) {
|
||||
size_t lineIdx = currentSpan > 0 ? currentLine : currentLine - 1;
|
||||
Float lineY = lines[lineIdx].y;
|
||||
Rectf margin = widget->getLayoutPixelsMargin();
|
||||
|
||||
Vector2f targetPos(
|
||||
mContainer->getPixelsPadding().Left + span->position.x + margin.Left,
|
||||
mContainer->getPixelsPadding().Top + lineY + span->position.y + margin.Top );
|
||||
|
||||
widget->setPixelsPosition( targetPos - offset );
|
||||
|
||||
bounds = Rectf( targetPos, span->size );
|
||||
}
|
||||
}
|
||||
return bounds;
|
||||
};
|
||||
|
||||
child = mContainer->getFirstChild();
|
||||
while ( NULL != child ) {
|
||||
if ( child->isWidget() ) {
|
||||
processWidget( child->asType<UIWidget>(), processWidget );
|
||||
}
|
||||
child = child->getNextNode();
|
||||
}
|
||||
}
|
||||
|
||||
}} // namespace EE::UI
|
||||
@@ -425,6 +425,15 @@ void StyleSheetSpecification::registerDefaultProperties() {
|
||||
registerProperty( "cols", "20" ).setType( PropertyType::NumberInt );
|
||||
registerProperty( "input-mode", "normal" ).setType( PropertyType::String );
|
||||
|
||||
registerProperty( "hidden", "" ).setType( PropertyType::Bool );
|
||||
registerProperty( "display", "inline" ).setType( PropertyType::String );
|
||||
registerProperty( "position", "static" ).setType( PropertyType::String );
|
||||
registerProperty( "top", "auto" ).setType( PropertyType::NumberLength );
|
||||
registerProperty( "right", "auto" ).setType( PropertyType::NumberLength );
|
||||
registerProperty( "bottom", "auto" ).setType( PropertyType::NumberLength );
|
||||
registerProperty( "left", "auto" ).setType( PropertyType::NumberLength );
|
||||
registerProperty( "z-index", "auto" ).setType( PropertyType::NumberInt );
|
||||
|
||||
registerProperty( "inner-widget-orientation", "widgeticontextbox" )
|
||||
.setType( PropertyType::String );
|
||||
|
||||
|
||||
533
src/eepp/ui/tablelayouter.cpp
Normal file
533
src/eepp/ui/tablelayouter.cpp
Normal file
@@ -0,0 +1,533 @@
|
||||
#include <eepp/ui/tablelayouter.hpp>
|
||||
#include <eepp/ui/uihtmltable.hpp>
|
||||
#include <eepp/ui/uistyle.hpp>
|
||||
|
||||
namespace EE { namespace UI {
|
||||
|
||||
static inline Float sanitizeFloat( Float val ) {
|
||||
return std::isfinite( val ) ? val : 0.f;
|
||||
}
|
||||
|
||||
void TableLayouter::setTableLayout( TableLayout layout ) {
|
||||
if ( layout != mTableLayout ) {
|
||||
mTableLayout = layout;
|
||||
}
|
||||
}
|
||||
|
||||
TableLayout TableLayouter::getTableLayout() const {
|
||||
return mTableLayout;
|
||||
}
|
||||
|
||||
void TableLayouter::setCellpadding( Float padding ) {
|
||||
mCellpadding = padding;
|
||||
}
|
||||
|
||||
Float TableLayouter::getCellpadding() const {
|
||||
return mCellpadding;
|
||||
}
|
||||
|
||||
void TableLayouter::setCellspacing( Float spacing ) {
|
||||
mCellspacing = spacing;
|
||||
}
|
||||
|
||||
Float TableLayouter::getCellspacing() const {
|
||||
return mCellspacing;
|
||||
}
|
||||
|
||||
Float TableLayouter::getMinIntrinsicWidth() {
|
||||
computeIntrinsicWidths();
|
||||
return mMinIntrinsicWidth;
|
||||
}
|
||||
|
||||
Float TableLayouter::getMaxIntrinsicWidth() {
|
||||
computeIntrinsicWidths();
|
||||
return mMaxIntrinsicWidth;
|
||||
}
|
||||
|
||||
void TableLayouter::computeIntrinsicWidths() {
|
||||
if ( !mIntrinsicWidthsDirty )
|
||||
return;
|
||||
|
||||
mRows.clear();
|
||||
mHead = nullptr;
|
||||
mBody = nullptr;
|
||||
mFooter = nullptr;
|
||||
mCells.clear();
|
||||
mRowCellOffsets.clear();
|
||||
|
||||
auto collectRows = [&]( auto&& self, Node* node ) -> void {
|
||||
for ( Node* child = node->getFirstChild(); child; child = child->getNextNode() ) {
|
||||
if ( child->getType() == UI_TYPE_HTML_TABLE_ROW ) {
|
||||
mRows.push_back( child->asType<UIHTMLTableRow>() );
|
||||
} else if ( child->getType() != UI_TYPE_HTML_TABLE ) {
|
||||
if ( child->getType() == UI_TYPE_HTML_TABLE_HEAD )
|
||||
mHead = child->asType<UIHTMLTableHead>();
|
||||
else if ( child->getType() == UI_TYPE_HTML_TABLE_BODY )
|
||||
mBody = child->asType<UIHTMLTableBody>();
|
||||
else if ( child->getType() == UI_TYPE_HTML_TABLE_FOOTER )
|
||||
mFooter = child->asType<UIHTMLTableFooter>();
|
||||
|
||||
self( self, child );
|
||||
}
|
||||
}
|
||||
};
|
||||
collectRows( collectRows, mContainer );
|
||||
|
||||
auto getRecursiveSpecifiedWidth = [&]( auto&& self, Node* node ) -> Float {
|
||||
if ( !node->isWidget() )
|
||||
return 0.f;
|
||||
UIWidget* widget = node->asType<UIWidget>();
|
||||
Float spec = 0.f;
|
||||
if ( widget->getLayoutWidthPolicy() == SizePolicy::Fixed )
|
||||
spec = sanitizeFloat( widget->getPropertyWidth() );
|
||||
for ( Node* child = node->getFirstChild(); child; child = child->getNextNode() )
|
||||
spec = std::max( spec, self( self, child ) );
|
||||
return spec;
|
||||
};
|
||||
|
||||
if ( mRows.empty() ) {
|
||||
mMinIntrinsicWidth = mMaxIntrinsicWidth =
|
||||
mContainer->getPixelsPadding().Left + mContainer->getPixelsPadding().Right;
|
||||
mIntrinsicWidthsDirty = false;
|
||||
return;
|
||||
}
|
||||
|
||||
mRowCellOffsets.push_back( 0 );
|
||||
size_t maxCols = 0;
|
||||
for ( auto* row : mRows ) {
|
||||
size_t colCount = 0;
|
||||
for ( Node* child = row->getFirstChild(); child; child = child->getNextNode() ) {
|
||||
if ( child->getType() == UI_TYPE_HTML_TABLE_CELL ) {
|
||||
auto* cell = child->asType<UIHTMLTableCell>();
|
||||
mCells.push_back( cell );
|
||||
colCount += cell->getColspan();
|
||||
if ( mCellpadding > 0 && cell->getPadding() == Rectf::Zero ) {
|
||||
cell->setPadding( { mCellpadding, mCellpadding, mCellpadding, mCellpadding } );
|
||||
}
|
||||
}
|
||||
}
|
||||
mRowCellOffsets.push_back( static_cast<Uint32>( mCells.size() ) );
|
||||
maxCols = std::max( maxCols, colCount );
|
||||
}
|
||||
|
||||
if ( maxCols == 0 ) {
|
||||
mMinIntrinsicWidth = mMaxIntrinsicWidth =
|
||||
mContainer->getPixelsPadding().Left + mContainer->getPixelsPadding().Right;
|
||||
mIntrinsicWidthsDirty = false;
|
||||
return;
|
||||
}
|
||||
|
||||
mColMinWidths.assign( maxCols, 0.f );
|
||||
mColMaxWidths.assign( maxCols, 0.f );
|
||||
mColSpecifiedWidths.assign( maxCols, 0.f ); // 0 = no explicit width
|
||||
|
||||
if ( mTableLayout == TableLayout::Fixed ) {
|
||||
if ( !mRows.empty() ) {
|
||||
Uint32 start = mRowCellOffsets[0];
|
||||
Uint32 end = mRowCellOffsets[1];
|
||||
Uint32 colIndex = 0;
|
||||
|
||||
// PASS 1: Single colspan first row
|
||||
for ( Uint32 i = 0; i < end - start; ++i ) {
|
||||
UIHTMLTableCell* cell = mCells[start + i];
|
||||
Float cellSpecified = sanitizeFloat(
|
||||
std::max( cell->getPropertyWidth(),
|
||||
getRecursiveSpecifiedWidth( getRecursiveSpecifiedWidth, cell ) ) );
|
||||
Uint32 colspan = cell->getColspan();
|
||||
|
||||
if ( colspan == 1 && colIndex < maxCols ) {
|
||||
if ( cellSpecified > 0.f ) {
|
||||
mColSpecifiedWidths[colIndex] =
|
||||
std::max( mColSpecifiedWidths[colIndex], cellSpecified );
|
||||
}
|
||||
}
|
||||
colIndex += colspan;
|
||||
}
|
||||
|
||||
// PASS 2: Multi-colspan cells first row
|
||||
colIndex = 0;
|
||||
for ( Uint32 i = 0; i < end - start; ++i ) {
|
||||
UIHTMLTableCell* cell = mCells[start + i];
|
||||
Float cellSpecified = sanitizeFloat(
|
||||
std::max( cell->getPropertyWidth(),
|
||||
getRecursiveSpecifiedWidth( getRecursiveSpecifiedWidth, cell ) ) );
|
||||
Uint32 colspan = cell->getColspan();
|
||||
|
||||
if ( colspan > 1 && cellSpecified > 0.f ) {
|
||||
Float curSpec = 0.f;
|
||||
for ( Uint32 j = 0; j < colspan && colIndex + j < maxCols; ++j )
|
||||
curSpec += mColSpecifiedWidths[colIndex + j];
|
||||
Float extraSpec = std::max( 0.f, cellSpecified - curSpec );
|
||||
if ( extraSpec > 0.f ) {
|
||||
Float add = extraSpec / colspan;
|
||||
for ( Uint32 j = 0; j < colspan && colIndex + j < maxCols; ++j )
|
||||
mColSpecifiedWidths[colIndex + j] =
|
||||
std::max( mColSpecifiedWidths[colIndex + j], add );
|
||||
}
|
||||
}
|
||||
colIndex += colspan;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// PASS 1: Collect intrinsic + explicit widths (single colspan first)
|
||||
for ( size_t r = 0; r < mRows.size(); ++r ) {
|
||||
Uint32 start = mRowCellOffsets[r];
|
||||
Uint32 end = mRowCellOffsets[r + 1];
|
||||
Uint32 colIndex = 0;
|
||||
|
||||
for ( Uint32 i = 0; i < end - start; ++i ) {
|
||||
UIHTMLTableCell* cell = mCells[start + i];
|
||||
auto widthPolicy = cell->getLayoutWidthPolicy();
|
||||
cell->mWidthPolicy = SizePolicy::WrapContent;
|
||||
Float cellMin = sanitizeFloat( cell->getMinIntrinsicWidth() );
|
||||
Float cellMax = sanitizeFloat( cell->getMaxIntrinsicWidth() );
|
||||
Float cellSpecified = sanitizeFloat(
|
||||
std::max( cell->getPropertyWidth(),
|
||||
getRecursiveSpecifiedWidth( getRecursiveSpecifiedWidth, cell ) ) );
|
||||
cell->mWidthPolicy = widthPolicy;
|
||||
|
||||
Uint32 colspan = cell->getColspan();
|
||||
|
||||
if ( colspan == 1 && colIndex < maxCols ) {
|
||||
mColMinWidths[colIndex] = std::max( mColMinWidths[colIndex], cellMin );
|
||||
mColMaxWidths[colIndex] = std::max( mColMaxWidths[colIndex], cellMax );
|
||||
if ( cellSpecified > 0.f ) {
|
||||
mColSpecifiedWidths[colIndex] =
|
||||
std::max( mColSpecifiedWidths[colIndex], cellSpecified );
|
||||
}
|
||||
}
|
||||
colIndex += colspan;
|
||||
}
|
||||
}
|
||||
|
||||
// PASS 2: Multi-colspan cells - distribute excess only
|
||||
for ( size_t r = 0; r < mRows.size(); ++r ) {
|
||||
Uint32 start = mRowCellOffsets[r];
|
||||
Uint32 end = mRowCellOffsets[r + 1];
|
||||
Uint32 colIndex = 0;
|
||||
|
||||
for ( Uint32 i = 0; i < end - start; ++i ) {
|
||||
UIHTMLTableCell* cell = mCells[start + i];
|
||||
auto widthPolicy = cell->getLayoutWidthPolicy();
|
||||
cell->mWidthPolicy = SizePolicy::WrapContent;
|
||||
Float cellMin = sanitizeFloat( cell->getMinIntrinsicWidth() );
|
||||
Float cellMax = sanitizeFloat( cell->getMaxIntrinsicWidth() );
|
||||
Float cellSpecified = sanitizeFloat(
|
||||
std::max( cell->getPropertyWidth(),
|
||||
getRecursiveSpecifiedWidth( getRecursiveSpecifiedWidth, cell ) ) );
|
||||
cell->mWidthPolicy = widthPolicy;
|
||||
|
||||
Uint32 colspan = cell->getColspan();
|
||||
|
||||
if ( colspan > 1 ) {
|
||||
// Min excess
|
||||
Float curMin = 0.f;
|
||||
for ( Uint32 j = 0; j < colspan && colIndex + j < maxCols; ++j )
|
||||
curMin += mColMinWidths[colIndex + j];
|
||||
Float extraMin = std::max( 0.f, cellMin - curMin );
|
||||
if ( extraMin > 0.f ) {
|
||||
Float add = extraMin / colspan;
|
||||
for ( Uint32 j = 0; j < colspan && colIndex + j < maxCols; ++j )
|
||||
mColMinWidths[colIndex + j] += add;
|
||||
}
|
||||
|
||||
// Max excess
|
||||
Float curMax = 0.f;
|
||||
for ( Uint32 j = 0; j < colspan && colIndex + j < maxCols; ++j )
|
||||
curMax += mColMaxWidths[colIndex + j];
|
||||
Float extraMax = std::max( 0.f, cellMax - curMax );
|
||||
if ( extraMax > 0.f ) {
|
||||
Float add = extraMax / colspan;
|
||||
for ( Uint32 j = 0; j < colspan && colIndex + j < maxCols; ++j )
|
||||
mColMaxWidths[colIndex + j] += add;
|
||||
}
|
||||
|
||||
// Specified width excess
|
||||
if ( cellSpecified > 0.f ) {
|
||||
Float curSpec = 0.f;
|
||||
for ( Uint32 j = 0; j < colspan && colIndex + j < maxCols; ++j )
|
||||
curSpec += mColSpecifiedWidths[colIndex + j];
|
||||
Float extraSpec = std::max( 0.f, cellSpecified - curSpec );
|
||||
if ( extraSpec > 0.f ) {
|
||||
Float add = extraSpec / colspan;
|
||||
for ( Uint32 j = 0; j < colspan && colIndex + j < maxCols; ++j )
|
||||
mColSpecifiedWidths[colIndex + j] =
|
||||
std::max( mColSpecifiedWidths[colIndex + j], add );
|
||||
}
|
||||
}
|
||||
}
|
||||
colIndex += colspan;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Float totalMin = 0.f, totalMax = 0.f;
|
||||
for ( size_t i = 0; i < maxCols; ++i ) {
|
||||
mColMinWidths[i] = sanitizeFloat( std::max( mColMinWidths[i], mColSpecifiedWidths[i] ) );
|
||||
mColMaxWidths[i] = sanitizeFloat( std::max( mColMaxWidths[i], mColSpecifiedWidths[i] ) );
|
||||
totalMin += mColMinWidths[i];
|
||||
totalMax += mColMaxWidths[i];
|
||||
}
|
||||
|
||||
mMinIntrinsicWidth = totalMin + mContainer->getPixelsPadding().Left +
|
||||
mContainer->getPixelsPadding().Right + ( maxCols + 1 ) * mCellspacing;
|
||||
mMaxIntrinsicWidth = totalMax + mContainer->getPixelsPadding().Left +
|
||||
mContainer->getPixelsPadding().Right + ( maxCols + 1 ) * mCellspacing;
|
||||
|
||||
mIntrinsicWidthsDirty = false;
|
||||
}
|
||||
|
||||
void TableLayouter::updateLayout() {
|
||||
if ( !mContainer->isVisible() )
|
||||
return;
|
||||
|
||||
if ( mPacking )
|
||||
return;
|
||||
mPacking = true;
|
||||
|
||||
setMatchParentIfNeededVerticalGrowth();
|
||||
|
||||
const StyleSheetProperty* prop = nullptr;
|
||||
UIWidget* widget = mContainer->asType<UIWidget>();
|
||||
if ( widget->getLayoutWidthPolicy() == SizePolicy::Fixed && widget->getUIStyle() &&
|
||||
( prop = widget->getUIStyle()->getProperty( PropertyId::Width ) ) ) {
|
||||
widget->asType<UINode>()->setInternalPixelsSize(
|
||||
{ widget->lengthFromValue( *prop ), widget->getPixelsSize().getHeight() } );
|
||||
}
|
||||
|
||||
computeIntrinsicWidths();
|
||||
|
||||
if ( mRows.empty() ) {
|
||||
mPacking = false;
|
||||
return;
|
||||
}
|
||||
|
||||
size_t maxCols = mColMinWidths.size();
|
||||
mColWidths.assign( maxCols, 0.f );
|
||||
Float paddingH = mContainer->getPixelsPadding().Left + mContainer->getPixelsPadding().Right;
|
||||
Float containerWidth = mContainer->getPixelsSize().getWidth();
|
||||
Float availableWidth = sanitizeFloat(
|
||||
std::max( 0.f, containerWidth - paddingH - ( maxCols + 1 ) * mCellspacing ) );
|
||||
|
||||
if ( availableWidth <= 0.f || maxCols == 0 ) {
|
||||
mPacking = false;
|
||||
return;
|
||||
}
|
||||
|
||||
Float totalMin = 0.f;
|
||||
Float totalMax = 0.f;
|
||||
for ( size_t i = 0; i < maxCols; ++i ) {
|
||||
totalMin += sanitizeFloat( mColMinWidths[i] );
|
||||
totalMax += sanitizeFloat( mColMaxWidths[i] );
|
||||
}
|
||||
|
||||
Float tableUsedWidth = availableWidth;
|
||||
|
||||
// Assign column widths
|
||||
if ( mTableLayout == TableLayout::Fixed ) {
|
||||
Float sumOfSpecifiedWidths = 0.f;
|
||||
size_t unspecifiedCount = 0;
|
||||
for ( size_t i = 0; i < maxCols; ++i ) {
|
||||
if ( mColSpecifiedWidths[i] > 0.f ) {
|
||||
sumOfSpecifiedWidths += mColSpecifiedWidths[i];
|
||||
mColWidths[i] = mColSpecifiedWidths[i];
|
||||
} else {
|
||||
unspecifiedCount++;
|
||||
}
|
||||
}
|
||||
|
||||
Float remainingSpace = std::max( 0.f, availableWidth - sumOfSpecifiedWidths );
|
||||
|
||||
if ( unspecifiedCount > 0 ) {
|
||||
Float share = remainingSpace / static_cast<Float>( unspecifiedCount );
|
||||
for ( size_t i = 0; i < maxCols; ++i ) {
|
||||
if ( mColSpecifiedWidths[i] <= 0.f ) {
|
||||
mColWidths[i] = share;
|
||||
}
|
||||
}
|
||||
} else if ( remainingSpace > 0.f && sumOfSpecifiedWidths > 0.f ) {
|
||||
for ( size_t i = 0; i < maxCols; ++i ) {
|
||||
Float scale = mColSpecifiedWidths[i] / sumOfSpecifiedWidths;
|
||||
mColWidths[i] += remainingSpace * scale;
|
||||
}
|
||||
}
|
||||
} else if ( tableUsedWidth <= totalMin + 0.001f ) {
|
||||
Float scale = totalMin > 0.001f ? ( tableUsedWidth / totalMin ) : 0.f;
|
||||
for ( size_t i = 0; i < maxCols; ++i )
|
||||
mColWidths[i] = mColMinWidths[i] * scale;
|
||||
} else if ( tableUsedWidth <= totalMax + 0.001f ) {
|
||||
Float extraSpace = tableUsedWidth - totalMin;
|
||||
Float totalFlex = 0.f;
|
||||
|
||||
for ( size_t i = 0; i < maxCols; ++i ) {
|
||||
Float flex = mColMaxWidths[i] - mColMinWidths[i];
|
||||
if ( mColSpecifiedWidths[i] > 0.f )
|
||||
flex = 0.f;
|
||||
totalFlex += flex;
|
||||
}
|
||||
|
||||
if ( totalFlex > 0.001f ) {
|
||||
for ( size_t i = 0; i < maxCols; ++i ) {
|
||||
Float flex = mColMaxWidths[i] - mColMinWidths[i];
|
||||
if ( mColSpecifiedWidths[i] > 0.f )
|
||||
flex = 0.f;
|
||||
Float added = extraSpace * ( flex / totalFlex );
|
||||
mColWidths[i] = mColMinWidths[i] + added;
|
||||
}
|
||||
} else {
|
||||
Float scale = totalMin > 0.001f ? ( tableUsedWidth / totalMin ) : 0.f;
|
||||
for ( size_t i = 0; i < maxCols; ++i )
|
||||
mColWidths[i] = mColMinWidths[i] * scale;
|
||||
}
|
||||
} else {
|
||||
Float leftOver = tableUsedWidth - totalMax;
|
||||
Float totalMaxUnspecified = 0.f;
|
||||
size_t unspecifiedCount = 0;
|
||||
|
||||
for ( size_t i = 0; i < maxCols; ++i ) {
|
||||
if ( mColSpecifiedWidths[i] <= 0.f ) {
|
||||
totalMaxUnspecified += mColMaxWidths[i];
|
||||
unspecifiedCount++;
|
||||
}
|
||||
}
|
||||
|
||||
if ( unspecifiedCount > 0 ) {
|
||||
if ( totalMaxUnspecified > 0.001f ) {
|
||||
for ( size_t i = 0; i < maxCols; ++i ) {
|
||||
if ( mColSpecifiedWidths[i] <= 0.f ) {
|
||||
Float scale = mColMaxWidths[i] / totalMaxUnspecified;
|
||||
mColWidths[i] = mColMaxWidths[i] + ( leftOver * scale );
|
||||
} else {
|
||||
mColWidths[i] = mColMaxWidths[i];
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Float share = leftOver / static_cast<Float>( unspecifiedCount );
|
||||
for ( size_t i = 0; i < maxCols; ++i ) {
|
||||
if ( mColSpecifiedWidths[i] <= 0.f ) {
|
||||
mColWidths[i] = mColMaxWidths[i] + share;
|
||||
} else {
|
||||
mColWidths[i] = mColMaxWidths[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Float scale = totalMax > 0.001f ? ( tableUsedWidth / totalMax ) : 0.f;
|
||||
for ( size_t i = 0; i < maxCols; ++i )
|
||||
mColWidths[i] = mColMaxWidths[i] * scale;
|
||||
}
|
||||
}
|
||||
|
||||
Float sum = 0.f;
|
||||
for ( float w : mColWidths )
|
||||
sum += w;
|
||||
if ( sum < 1.f && maxCols > 0 ) {
|
||||
Float w = tableUsedWidth / static_cast<Float>( maxCols );
|
||||
for ( size_t i = 0; i < maxCols; ++i )
|
||||
mColWidths[i] = w;
|
||||
}
|
||||
|
||||
for ( float& w : mColWidths )
|
||||
w = sanitizeFloat( w );
|
||||
|
||||
Float headHeight = 0;
|
||||
Float bodyHeight = 0;
|
||||
Float footerHeight = 0;
|
||||
|
||||
size_t rowCount = mRows.size();
|
||||
for ( size_t r = 0; r < rowCount; ++r ) {
|
||||
Float rowHeight = 0;
|
||||
Uint32 start = mRowCellOffsets[r];
|
||||
Uint32 end = mRowCellOffsets[r + 1];
|
||||
Uint32 columnCount = end - start;
|
||||
Uint32 colIndex = 0;
|
||||
for ( Uint32 c = 0; c < columnCount; ++c ) {
|
||||
UIHTMLTableCell* cell = mCells[start + c];
|
||||
cell->beginAttributesTransaction();
|
||||
cell->setLayoutWidthPolicy( SizePolicy::Fixed );
|
||||
cell->setLayoutHeightPolicy( SizePolicy::WrapContent );
|
||||
Uint32 cellColspan = cell->getColspan();
|
||||
Float cellWidth = 0;
|
||||
for ( Uint32 j = 0; j < cellColspan && ( colIndex + j ) < maxCols; ++j ) {
|
||||
cellWidth += mColWidths[colIndex + j];
|
||||
}
|
||||
if ( cellColspan > 1 )
|
||||
cellWidth += ( cellColspan - 1 ) * mCellspacing;
|
||||
cell->setPixelsSize( cellWidth, cell->getPixelsSize().getHeight() );
|
||||
cell->updateLayout();
|
||||
cell->setLayoutHeightPolicy( SizePolicy::Fixed );
|
||||
cell->endAttributesTransaction();
|
||||
rowHeight = std::max( rowHeight, cell->getPixelsSize().getHeight() );
|
||||
colIndex += cellColspan;
|
||||
}
|
||||
|
||||
Float currentX = mCellspacing;
|
||||
colIndex = 0;
|
||||
for ( Uint32 c = 0; c < columnCount; ++c ) {
|
||||
UIHTMLTableCell* cell = mCells[start + c];
|
||||
cell->beginAttributesTransaction();
|
||||
cell->setPixelsPosition( currentX, 0 );
|
||||
Uint32 cellColspan = cell->getColspan();
|
||||
Float cellWidth = 0;
|
||||
for ( Uint32 j = 0; j < cellColspan && ( colIndex + j ) < maxCols; ++j ) {
|
||||
cellWidth += mColWidths[colIndex + j];
|
||||
}
|
||||
if ( cellColspan > 1 )
|
||||
cellWidth += ( cellColspan - 1 ) * mCellspacing;
|
||||
cell->setPixelsSize( cellWidth, rowHeight );
|
||||
cell->endAttributesTransaction();
|
||||
currentX += cellWidth + mCellspacing;
|
||||
colIndex += cellColspan;
|
||||
}
|
||||
|
||||
UIHTMLTableRow* row = mRows[r];
|
||||
row->setPixelsSize( containerWidth - paddingH, rowHeight );
|
||||
|
||||
if ( r == 0 && mCells[start]->getParent()->isType( UI_TYPE_HTML_TABLE_HEAD ) ) {
|
||||
headHeight = rowHeight;
|
||||
} else if ( r == rowCount - 1 && columnCount &&
|
||||
mCells[start]->getParent()->isType( UI_TYPE_HTML_TABLE_FOOTER ) ) {
|
||||
footerHeight = rowHeight;
|
||||
} else {
|
||||
bodyHeight += rowHeight;
|
||||
}
|
||||
}
|
||||
|
||||
if ( mHead ) {
|
||||
mHead->setPixelsPosition( 0, 0 );
|
||||
mHead->setPixelsSize( { mContainer->getPixelsSize().x, headHeight } );
|
||||
}
|
||||
|
||||
if ( mBody ) {
|
||||
mBody->setPixelsPosition( 0, headHeight );
|
||||
mBody->setPixelsSize( { mContainer->getPixelsSize().x, bodyHeight } );
|
||||
}
|
||||
|
||||
if ( mFooter ) {
|
||||
mFooter->setPixelsPosition( 0, headHeight + bodyHeight );
|
||||
mFooter->setPixelsSize( { mContainer->getPixelsSize().x, footerHeight } );
|
||||
}
|
||||
|
||||
Float currentY = mContainer->getPixelsPadding().Top + mCellspacing - headHeight;
|
||||
for ( size_t r = 0; r < rowCount; ++r ) {
|
||||
UIHTMLTableRow* row = mRows[r];
|
||||
row->setPixelsPosition( mContainer->getPixelsPadding().Left, currentY );
|
||||
currentY += row->getPixelsSize().getHeight() + mCellspacing;
|
||||
}
|
||||
|
||||
if ( mHead && !mRows.empty() )
|
||||
mRows[0]->setPixelsPosition( mContainer->getPixelsPadding().Left, 0 );
|
||||
|
||||
if ( mFooter && !mRows.empty() )
|
||||
mRows[rowCount - 1]->setPixelsPosition( mContainer->getPixelsPadding().Left, 0 );
|
||||
|
||||
if ( mContainer->getLayoutHeightPolicy() == SizePolicy::WrapContent ) {
|
||||
mContainer->asType<UINode>()->setInternalPixelsHeight(
|
||||
mContainer->getPixelsPadding().Top + headHeight + bodyHeight + footerHeight +
|
||||
( rowCount + 1 ) * mCellspacing + mContainer->getPixelsPadding().Bottom );
|
||||
}
|
||||
|
||||
mPacking = false;
|
||||
}
|
||||
|
||||
}} // namespace EE::UI
|
||||
@@ -1,42 +1,27 @@
|
||||
#include "eepp/ui/uistyle.hpp"
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <eepp/ui/tablelayouter.hpp>
|
||||
#include <eepp/ui/uihtmltable.hpp>
|
||||
#include <eepp/ui/uilayouter.hpp>
|
||||
#include <eepp/ui/uistyle.hpp>
|
||||
|
||||
namespace EE { namespace UI {
|
||||
|
||||
static inline Float sanitizeFloat( Float val ) {
|
||||
return std::isfinite( val ) ? val : 0.f;
|
||||
}
|
||||
|
||||
UIHTMLTable* UIHTMLTable::New() {
|
||||
return eeNew( UIHTMLTable, () );
|
||||
}
|
||||
|
||||
UIHTMLTable::UIHTMLTable() : UILayout( "table" ) {
|
||||
UIHTMLTable::UIHTMLTable() : UIHTMLWidget( "table" ) {
|
||||
mDisplay = CSSDisplay::Table;
|
||||
mFlags |= UI_HTML_ELEMENT | UI_OWNS_CHILDREN_POSITION;
|
||||
mWidthPolicy = SizePolicy::MatchParent;
|
||||
mHeightPolicy = SizePolicy::WrapContent;
|
||||
}
|
||||
|
||||
void UIHTMLTable::setTableLayout( TableLayout layout ) {
|
||||
if ( layout != mTableLayout ) {
|
||||
mTableLayout = layout;
|
||||
invalidateIntrinsicSize();
|
||||
tryUpdateLayout();
|
||||
}
|
||||
}
|
||||
|
||||
TableLayout UIHTMLTable::getTableLayout() const {
|
||||
return mTableLayout;
|
||||
}
|
||||
|
||||
Uint32 UIHTMLTable::getType() const {
|
||||
return UI_TYPE_HTML_TABLE;
|
||||
}
|
||||
|
||||
bool UIHTMLTable::isType( const Uint32& type ) const {
|
||||
return UIHTMLTable::getType() == type || UILayout::isType( type );
|
||||
return UIHTMLTable::getType() == type || UIHTMLWidget::isType( type );
|
||||
}
|
||||
|
||||
bool UIHTMLTable::applyProperty( const StyleSheetProperty& attribute ) {
|
||||
@@ -45,22 +30,29 @@ bool UIHTMLTable::applyProperty( const StyleSheetProperty& attribute ) {
|
||||
|
||||
switch ( attribute.getPropertyDefinition()->getPropertyId() ) {
|
||||
case PropertyId::Cellspacing:
|
||||
mCellspacing = lengthFromValue( attribute );
|
||||
invalidateIntrinsicSize();
|
||||
tryUpdateLayout();
|
||||
if ( const_cast<UIHTMLTable*>( this )->getLayouter() ) {
|
||||
static_cast<TableLayouter*>( const_cast<UIHTMLTable*>( this )->getLayouter() )
|
||||
->setCellspacing( lengthFromValue( attribute ) );
|
||||
invalidateIntrinsicSize();
|
||||
tryUpdateLayout();
|
||||
}
|
||||
return true;
|
||||
case PropertyId::Cellpadding:
|
||||
mCellpadding = lengthFromValue( attribute );
|
||||
invalidateIntrinsicSize();
|
||||
tryUpdateLayout();
|
||||
if ( const_cast<UIHTMLTable*>( this )->getLayouter() ) {
|
||||
static_cast<TableLayouter*>( const_cast<UIHTMLTable*>( this )->getLayouter() )
|
||||
->setCellpadding( lengthFromValue( attribute ) );
|
||||
invalidateIntrinsicSize();
|
||||
tryUpdateLayout();
|
||||
}
|
||||
return true;
|
||||
case PropertyId::TableLayout: {
|
||||
std::string val = attribute.asString();
|
||||
String::toLowerInPlace( val );
|
||||
if ( val == "fixed" ) {
|
||||
setTableLayout( TableLayout::Fixed );
|
||||
} else if ( val == "auto" ) {
|
||||
setTableLayout( TableLayout::Auto );
|
||||
if ( const_cast<UIHTMLTable*>( this )->getLayouter() ) {
|
||||
static_cast<TableLayouter*>( const_cast<UIHTMLTable*>( this )->getLayouter() )
|
||||
->setTableLayout( val == "fixed" ? TableLayout::Fixed : TableLayout::Auto );
|
||||
invalidateIntrinsicSize();
|
||||
tryUpdateLayout();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@@ -68,521 +60,47 @@ bool UIHTMLTable::applyProperty( const StyleSheetProperty& attribute ) {
|
||||
break;
|
||||
}
|
||||
|
||||
return UILayout::applyProperty( attribute );
|
||||
return UIHTMLWidget::applyProperty( attribute );
|
||||
}
|
||||
|
||||
void UIHTMLTable::computeIntrinsicWidths() const {
|
||||
if ( !mIntrinsicWidthsDirty )
|
||||
return;
|
||||
|
||||
UIHTMLTable* me = const_cast<UIHTMLTable*>( this );
|
||||
|
||||
me->mRows.clear();
|
||||
me->mHead = nullptr;
|
||||
me->mBody = nullptr;
|
||||
me->mFooter = nullptr;
|
||||
me->mCells.clear();
|
||||
me->mRowCellOffsets.clear();
|
||||
|
||||
auto collectRows = [&]( auto&& self, Node* node ) -> void {
|
||||
for ( Node* child = node->getFirstChild(); child; child = child->getNextNode() ) {
|
||||
if ( child->getType() == UI_TYPE_HTML_TABLE_ROW ) {
|
||||
me->mRows.push_back( child->asType<UIHTMLTableRow>() );
|
||||
} else if ( child->getType() != UI_TYPE_HTML_TABLE ) {
|
||||
if ( child->getType() == UI_TYPE_HTML_TABLE_HEAD )
|
||||
me->mHead = child->asType<UIHTMLTableHead>();
|
||||
else if ( child->getType() == UI_TYPE_HTML_TABLE_BODY )
|
||||
me->mBody = child->asType<UIHTMLTableBody>();
|
||||
else if ( child->getType() == UI_TYPE_HTML_TABLE_FOOTER )
|
||||
me->mFooter = child->asType<UIHTMLTableFooter>();
|
||||
|
||||
self( self, child );
|
||||
}
|
||||
}
|
||||
};
|
||||
collectRows( collectRows, me );
|
||||
|
||||
auto getRecursiveSpecifiedWidth = [&]( auto&& self, Node* node ) -> Float {
|
||||
if ( !node->isWidget() )
|
||||
return 0.f;
|
||||
UIWidget* widget = node->asType<UIWidget>();
|
||||
Float spec = 0.f;
|
||||
if ( widget->getLayoutWidthPolicy() == SizePolicy::Fixed )
|
||||
spec = sanitizeFloat( widget->getPropertyWidth() );
|
||||
for ( Node* child = node->getFirstChild(); child; child = child->getNextNode() )
|
||||
spec = std::max( spec, self( self, child ) );
|
||||
return spec;
|
||||
};
|
||||
|
||||
if ( mRows.empty() ) {
|
||||
mMinIntrinsicWidth = mMaxIntrinsicWidth = mPaddingPx.Left + mPaddingPx.Right;
|
||||
mIntrinsicWidthsDirty = false;
|
||||
return;
|
||||
}
|
||||
|
||||
me->mRowCellOffsets.push_back( 0 );
|
||||
size_t maxCols = 0;
|
||||
for ( auto* row : mRows ) {
|
||||
size_t colCount = 0;
|
||||
for ( Node* child = row->getFirstChild(); child; child = child->getNextNode() ) {
|
||||
if ( child->getType() == UI_TYPE_HTML_TABLE_CELL ) {
|
||||
auto* cell = child->asType<UIHTMLTableCell>();
|
||||
me->mCells.push_back( cell );
|
||||
colCount += cell->getColspan();
|
||||
if ( mCellpadding > 0 && cell->getPadding() == Rectf::Zero ) {
|
||||
cell->setPadding( { mCellpadding, mCellpadding, mCellpadding, mCellpadding } );
|
||||
}
|
||||
}
|
||||
}
|
||||
me->mRowCellOffsets.push_back( static_cast<Uint32>( mCells.size() ) );
|
||||
maxCols = std::max( maxCols, colCount );
|
||||
}
|
||||
|
||||
if ( maxCols == 0 ) {
|
||||
mMinIntrinsicWidth = mMaxIntrinsicWidth = mPaddingPx.Left + mPaddingPx.Right;
|
||||
mIntrinsicWidthsDirty = false;
|
||||
return;
|
||||
}
|
||||
|
||||
me->mColMinWidths.assign( maxCols, 0.f );
|
||||
me->mColMaxWidths.assign( maxCols, 0.f );
|
||||
me->mColSpecifiedWidths.assign( maxCols, 0.f ); // 0 = no explicit width
|
||||
|
||||
if ( mTableLayout == TableLayout::Fixed ) {
|
||||
if ( !mRows.empty() ) {
|
||||
Uint32 start = mRowCellOffsets[0];
|
||||
Uint32 end = mRowCellOffsets[1];
|
||||
Uint32 colIndex = 0;
|
||||
|
||||
// PASS 1: Single colspan first row
|
||||
for ( Uint32 i = 0; i < end - start; ++i ) {
|
||||
UIHTMLTableCell* cell = mCells[start + i];
|
||||
Float cellSpecified = sanitizeFloat(
|
||||
std::max( cell->getPropertyWidth(),
|
||||
getRecursiveSpecifiedWidth( getRecursiveSpecifiedWidth, cell ) ) );
|
||||
Uint32 colspan = cell->getColspan();
|
||||
|
||||
if ( colspan == 1 && colIndex < maxCols ) {
|
||||
if ( cellSpecified > 0.f ) {
|
||||
mColSpecifiedWidths[colIndex] =
|
||||
std::max( mColSpecifiedWidths[colIndex], cellSpecified );
|
||||
}
|
||||
}
|
||||
colIndex += colspan;
|
||||
}
|
||||
|
||||
// PASS 2: Multi-colspan cells first row
|
||||
colIndex = 0;
|
||||
for ( Uint32 i = 0; i < end - start; ++i ) {
|
||||
UIHTMLTableCell* cell = mCells[start + i];
|
||||
Float cellSpecified = sanitizeFloat(
|
||||
std::max( cell->getPropertyWidth(),
|
||||
getRecursiveSpecifiedWidth( getRecursiveSpecifiedWidth, cell ) ) );
|
||||
Uint32 colspan = cell->getColspan();
|
||||
|
||||
if ( colspan > 1 && cellSpecified > 0.f ) {
|
||||
Float curSpec = 0.f;
|
||||
for ( Uint32 j = 0; j < colspan && colIndex + j < maxCols; ++j )
|
||||
curSpec += mColSpecifiedWidths[colIndex + j];
|
||||
Float extraSpec = std::max( 0.f, cellSpecified - curSpec );
|
||||
if ( extraSpec > 0.f ) {
|
||||
Float add = extraSpec / colspan;
|
||||
for ( Uint32 j = 0; j < colspan && colIndex + j < maxCols; ++j )
|
||||
mColSpecifiedWidths[colIndex + j] =
|
||||
std::max( mColSpecifiedWidths[colIndex + j], add );
|
||||
}
|
||||
}
|
||||
colIndex += colspan;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// PASS 1: Collect intrinsic + explicit widths (single colspan first)
|
||||
for ( size_t r = 0; r < mRows.size(); ++r ) {
|
||||
Uint32 start = mRowCellOffsets[r];
|
||||
Uint32 end = mRowCellOffsets[r + 1];
|
||||
Uint32 colIndex = 0;
|
||||
|
||||
for ( Uint32 i = 0; i < end - start; ++i ) {
|
||||
UIHTMLTableCell* cell = mCells[start + i];
|
||||
auto widthPolicy = cell->getLayoutWidthPolicy();
|
||||
cell->mWidthPolicy = SizePolicy::WrapContent;
|
||||
Float cellMin = sanitizeFloat( cell->getMinIntrinsicWidth() );
|
||||
Float cellMax = sanitizeFloat( cell->getMaxIntrinsicWidth() );
|
||||
Float cellSpecified = sanitizeFloat(
|
||||
std::max( cell->getPropertyWidth(),
|
||||
getRecursiveSpecifiedWidth( getRecursiveSpecifiedWidth, cell ) ) );
|
||||
cell->mWidthPolicy = widthPolicy;
|
||||
|
||||
Uint32 colspan = cell->getColspan();
|
||||
|
||||
if ( colspan == 1 && colIndex < maxCols ) {
|
||||
mColMinWidths[colIndex] = std::max( mColMinWidths[colIndex], cellMin );
|
||||
mColMaxWidths[colIndex] = std::max( mColMaxWidths[colIndex], cellMax );
|
||||
if ( cellSpecified > 0.f ) {
|
||||
mColSpecifiedWidths[colIndex] =
|
||||
std::max( mColSpecifiedWidths[colIndex], cellSpecified );
|
||||
}
|
||||
}
|
||||
colIndex += colspan;
|
||||
}
|
||||
}
|
||||
|
||||
// PASS 2: Multi-colspan cells - distribute excess only
|
||||
for ( size_t r = 0; r < mRows.size(); ++r ) {
|
||||
Uint32 start = mRowCellOffsets[r];
|
||||
Uint32 end = mRowCellOffsets[r + 1];
|
||||
Uint32 colIndex = 0;
|
||||
|
||||
for ( Uint32 i = 0; i < end - start; ++i ) {
|
||||
UIHTMLTableCell* cell = mCells[start + i];
|
||||
auto widthPolicy = cell->getLayoutWidthPolicy();
|
||||
cell->mWidthPolicy = SizePolicy::WrapContent;
|
||||
Float cellMin = sanitizeFloat( cell->getMinIntrinsicWidth() );
|
||||
Float cellMax = sanitizeFloat( cell->getMaxIntrinsicWidth() );
|
||||
Float cellSpecified = sanitizeFloat(
|
||||
std::max( cell->getPropertyWidth(),
|
||||
getRecursiveSpecifiedWidth( getRecursiveSpecifiedWidth, cell ) ) );
|
||||
cell->mWidthPolicy = widthPolicy;
|
||||
|
||||
Uint32 colspan = cell->getColspan();
|
||||
|
||||
if ( colspan > 1 ) {
|
||||
// Min excess
|
||||
Float curMin = 0.f;
|
||||
for ( Uint32 j = 0; j < colspan && colIndex + j < maxCols; ++j )
|
||||
curMin += mColMinWidths[colIndex + j];
|
||||
Float extraMin = std::max( 0.f, cellMin - curMin );
|
||||
if ( extraMin > 0.f ) {
|
||||
Float add = extraMin / colspan;
|
||||
for ( Uint32 j = 0; j < colspan && colIndex + j < maxCols; ++j )
|
||||
mColMinWidths[colIndex + j] += add;
|
||||
}
|
||||
|
||||
// Max excess
|
||||
Float curMax = 0.f;
|
||||
for ( Uint32 j = 0; j < colspan && colIndex + j < maxCols; ++j )
|
||||
curMax += mColMaxWidths[colIndex + j];
|
||||
Float extraMax = std::max( 0.f, cellMax - curMax );
|
||||
if ( extraMax > 0.f ) {
|
||||
Float add = extraMax / colspan;
|
||||
for ( Uint32 j = 0; j < colspan && colIndex + j < maxCols; ++j )
|
||||
mColMaxWidths[colIndex + j] += add;
|
||||
}
|
||||
|
||||
// Specified width excess (simple even distribution for now)
|
||||
if ( cellSpecified > 0.f ) {
|
||||
Float curSpec = 0.f;
|
||||
for ( Uint32 j = 0; j < colspan && colIndex + j < maxCols; ++j )
|
||||
curSpec += mColSpecifiedWidths[colIndex + j];
|
||||
Float extraSpec = std::max( 0.f, cellSpecified - curSpec );
|
||||
if ( extraSpec > 0.f ) {
|
||||
Float add = extraSpec / colspan;
|
||||
for ( Uint32 j = 0; j < colspan && colIndex + j < maxCols; ++j )
|
||||
mColSpecifiedWidths[colIndex + j] =
|
||||
std::max( mColSpecifiedWidths[colIndex + j], add );
|
||||
}
|
||||
}
|
||||
}
|
||||
colIndex += colspan;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Float totalMin = 0.f, totalMax = 0.f;
|
||||
for ( size_t i = 0; i < maxCols; ++i ) {
|
||||
mColMinWidths[i] = sanitizeFloat( std::max( mColMinWidths[i], mColSpecifiedWidths[i] ) );
|
||||
mColMaxWidths[i] = sanitizeFloat( std::max( mColMaxWidths[i], mColSpecifiedWidths[i] ) );
|
||||
totalMin += mColMinWidths[i];
|
||||
totalMax += mColMaxWidths[i];
|
||||
}
|
||||
|
||||
mMinIntrinsicWidth =
|
||||
totalMin + mPaddingPx.Left + mPaddingPx.Right + ( maxCols + 1 ) * mCellspacing;
|
||||
mMaxIntrinsicWidth =
|
||||
totalMax + mPaddingPx.Left + mPaddingPx.Right + ( maxCols + 1 ) * mCellspacing;
|
||||
|
||||
mIntrinsicWidthsDirty = false;
|
||||
UILayouter* layouter = const_cast<UIHTMLTable*>( this )->getLayouter();
|
||||
if ( layouter )
|
||||
layouter->computeIntrinsicWidths();
|
||||
}
|
||||
|
||||
Float UIHTMLTable::getMinIntrinsicWidth() const {
|
||||
computeIntrinsicWidths();
|
||||
return mMinIntrinsicWidth;
|
||||
UILayouter* layouter = const_cast<UIHTMLTable*>( this )->getLayouter();
|
||||
if ( layouter )
|
||||
return static_cast<TableLayouter*>( layouter )->getMinIntrinsicWidth();
|
||||
return 0;
|
||||
}
|
||||
|
||||
Float UIHTMLTable::getMaxIntrinsicWidth() const {
|
||||
computeIntrinsicWidths();
|
||||
return mMaxIntrinsicWidth;
|
||||
UILayouter* layouter = const_cast<UIHTMLTable*>( this )->getLayouter();
|
||||
if ( layouter )
|
||||
return static_cast<TableLayouter*>( layouter )->getMaxIntrinsicWidth();
|
||||
return 0;
|
||||
}
|
||||
|
||||
void UIHTMLTable::updateLayout() {
|
||||
if ( mPacking || !mVisible )
|
||||
return;
|
||||
mPacking = true;
|
||||
setMatchParentIfNeededVerticalGrowth();
|
||||
UILayouter* layouter = const_cast<UIHTMLTable*>( this )->getLayouter();
|
||||
if ( layouter )
|
||||
getLayouter()->updateLayout();
|
||||
else
|
||||
UIHTMLWidget::updateLayout();
|
||||
|
||||
const StyleSheetProperty* prop = nullptr;
|
||||
if ( getLayoutWidthPolicy() == SizePolicy::Fixed && mStyle &&
|
||||
( prop = mStyle->getProperty( PropertyId::Width ) ) ) {
|
||||
setInternalPixelsSize( { lengthFromValue( *prop ), mSize.getHeight() } );
|
||||
}
|
||||
|
||||
computeIntrinsicWidths();
|
||||
|
||||
if ( mRows.empty() ) {
|
||||
mPacking = false;
|
||||
return;
|
||||
}
|
||||
|
||||
size_t maxCols = mColMinWidths.size();
|
||||
mColWidths.assign( maxCols, 0.f );
|
||||
Float paddingH = mPaddingPx.Left + mPaddingPx.Right;
|
||||
Float containerWidth = getPixelsSize().getWidth();
|
||||
Float availableWidth = sanitizeFloat(
|
||||
std::max( 0.f, containerWidth - paddingH - ( maxCols + 1 ) * mCellspacing ) );
|
||||
|
||||
if ( availableWidth <= 0.f || maxCols == 0 ) {
|
||||
mPacking = false;
|
||||
return;
|
||||
}
|
||||
Float totalMin = 0.f;
|
||||
Float totalMax = 0.f; // Make sure this is uncommented
|
||||
for ( size_t i = 0; i < maxCols; ++i ) {
|
||||
totalMin += sanitizeFloat( mColMinWidths[i] );
|
||||
totalMax += sanitizeFloat( mColMaxWidths[i] ); // Accumulate max widths
|
||||
}
|
||||
|
||||
Float tableUsedWidth = availableWidth; // always try to fill the container
|
||||
|
||||
// Assign column widths
|
||||
if ( mTableLayout == TableLayout::Fixed ) {
|
||||
Float sumOfSpecifiedWidths = 0.f;
|
||||
size_t unspecifiedCount = 0;
|
||||
for ( size_t i = 0; i < maxCols; ++i ) {
|
||||
if ( mColSpecifiedWidths[i] > 0.f ) {
|
||||
sumOfSpecifiedWidths += mColSpecifiedWidths[i];
|
||||
mColWidths[i] = mColSpecifiedWidths[i];
|
||||
} else {
|
||||
unspecifiedCount++;
|
||||
}
|
||||
}
|
||||
|
||||
Float remainingSpace = std::max( 0.f, availableWidth - sumOfSpecifiedWidths );
|
||||
|
||||
if ( unspecifiedCount > 0 ) {
|
||||
Float share = remainingSpace / static_cast<Float>( unspecifiedCount );
|
||||
for ( size_t i = 0; i < maxCols; ++i ) {
|
||||
if ( mColSpecifiedWidths[i] <= 0.f ) {
|
||||
mColWidths[i] = share;
|
||||
}
|
||||
}
|
||||
} else if ( remainingSpace > 0.f && sumOfSpecifiedWidths > 0.f ) {
|
||||
for ( size_t i = 0; i < maxCols; ++i ) {
|
||||
Float scale = mColSpecifiedWidths[i] / sumOfSpecifiedWidths;
|
||||
mColWidths[i] += remainingSpace * scale;
|
||||
}
|
||||
}
|
||||
} else if ( tableUsedWidth <= totalMin + 0.001f ) {
|
||||
// 1. Too narrow → scale down proportionally to min widths
|
||||
Float scale = totalMin > 0.001f ? ( tableUsedWidth / totalMin ) : 0.f;
|
||||
for ( size_t i = 0; i < maxCols; ++i )
|
||||
mColWidths[i] = mColMinWidths[i] * scale;
|
||||
|
||||
} else if ( tableUsedWidth <= totalMax + 0.001f ) {
|
||||
// 2. Partial flex → space is between min and max. Distribute extra by flexibility (text
|
||||
// wrapping)
|
||||
Float extraSpace = tableUsedWidth - totalMin;
|
||||
Float totalFlex = 0.f;
|
||||
|
||||
for ( size_t i = 0; i < maxCols; ++i ) {
|
||||
Float flex = mColMaxWidths[i] - mColMinWidths[i];
|
||||
if ( mColSpecifiedWidths[i] > 0.f )
|
||||
flex = 0.f; // explicit widths stay rigid here
|
||||
totalFlex += flex;
|
||||
}
|
||||
|
||||
if ( totalFlex > 0.001f ) {
|
||||
for ( size_t i = 0; i < maxCols; ++i ) {
|
||||
Float flex = mColMaxWidths[i] - mColMinWidths[i];
|
||||
if ( mColSpecifiedWidths[i] > 0.f )
|
||||
flex = 0.f;
|
||||
Float added = extraSpace * ( flex / totalFlex );
|
||||
mColWidths[i] = mColMinWidths[i] + added;
|
||||
}
|
||||
} else {
|
||||
// Fallback if no flex exists
|
||||
Float scale = totalMin > 0.001f ? ( tableUsedWidth / totalMin ) : 0.f;
|
||||
for ( size_t i = 0; i < maxCols; ++i )
|
||||
mColWidths[i] = mColMinWidths[i] * scale;
|
||||
}
|
||||
|
||||
} else {
|
||||
// 3. Abundant space → table is wider than all max widths combined.
|
||||
// Give everyone their max width, then distribute the leftover space.
|
||||
Float leftOver = tableUsedWidth - totalMax;
|
||||
|
||||
Float totalMaxUnspecified = 0.f;
|
||||
size_t unspecifiedCount = 0;
|
||||
|
||||
for ( size_t i = 0; i < maxCols; ++i ) {
|
||||
if ( mColSpecifiedWidths[i] <= 0.f ) {
|
||||
totalMaxUnspecified += mColMaxWidths[i];
|
||||
unspecifiedCount++;
|
||||
}
|
||||
}
|
||||
|
||||
if ( unspecifiedCount > 0 ) {
|
||||
// Distribute leftover space proportionally to max-widths for a balanced look
|
||||
if ( totalMaxUnspecified > 0.001f ) {
|
||||
for ( size_t i = 0; i < maxCols; ++i ) {
|
||||
if ( mColSpecifiedWidths[i] <= 0.f ) {
|
||||
Float scale = mColMaxWidths[i] / totalMaxUnspecified;
|
||||
mColWidths[i] = mColMaxWidths[i] + ( leftOver * scale );
|
||||
} else {
|
||||
mColWidths[i] = mColMaxWidths[i]; // Rigid explicit column stays rigid
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Fallback to strict even split if max widths are 0
|
||||
Float share = leftOver / static_cast<Float>( unspecifiedCount );
|
||||
for ( size_t i = 0; i < maxCols; ++i ) {
|
||||
if ( mColSpecifiedWidths[i] <= 0.f ) {
|
||||
mColWidths[i] = mColMaxWidths[i] + share;
|
||||
} else {
|
||||
mColWidths[i] = mColMaxWidths[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Absolute fallback: All columns explicitly specified, but space remains. Scale up.
|
||||
Float scale = totalMax > 0.001f ? ( tableUsedWidth / totalMax ) : 0.f;
|
||||
for ( size_t i = 0; i < maxCols; ++i )
|
||||
mColWidths[i] = mColMaxWidths[i] * scale;
|
||||
}
|
||||
}
|
||||
|
||||
// Safety fallback (should never trigger now)
|
||||
Float sum = 0.f;
|
||||
for ( float w : mColWidths )
|
||||
sum += w;
|
||||
if ( sum < 1.f && maxCols > 0 ) {
|
||||
Float w = tableUsedWidth / static_cast<Float>( maxCols );
|
||||
for ( size_t i = 0; i < maxCols; ++i )
|
||||
mColWidths[i] = w;
|
||||
}
|
||||
|
||||
for ( float& w : mColWidths )
|
||||
w = sanitizeFloat( w );
|
||||
|
||||
Float headHeight = 0;
|
||||
Float bodyHeight = 0;
|
||||
Float footerHeight = 0;
|
||||
|
||||
// Apply layout and calculate heights
|
||||
size_t rowCount = mRows.size();
|
||||
for ( size_t r = 0; r < rowCount; ++r ) {
|
||||
Float rowHeight = 0;
|
||||
Uint32 start = mRowCellOffsets[r];
|
||||
Uint32 end = mRowCellOffsets[r + 1];
|
||||
Uint32 columnCount = end - start;
|
||||
Uint32 colIndex = 0;
|
||||
for ( Uint32 c = 0; c < columnCount; ++c ) {
|
||||
UIHTMLTableCell* cell = mCells[start + c];
|
||||
cell->beginAttributesTransaction();
|
||||
cell->setLayoutWidthPolicy( SizePolicy::Fixed );
|
||||
cell->setLayoutHeightPolicy( SizePolicy::WrapContent );
|
||||
Uint32 cellColspan = cell->getColspan();
|
||||
Float cellWidth = 0;
|
||||
for ( Uint32 j = 0; j < cellColspan && ( colIndex + j ) < maxCols; ++j ) {
|
||||
cellWidth += mColWidths[colIndex + j];
|
||||
}
|
||||
if ( cellColspan > 1 )
|
||||
cellWidth += ( cellColspan - 1 ) * mCellspacing;
|
||||
cell->setPixelsSize( cellWidth, cell->getPixelsSize().getHeight() );
|
||||
cell->updateLayout();
|
||||
cell->setLayoutHeightPolicy( SizePolicy::Fixed );
|
||||
cell->endAttributesTransaction();
|
||||
rowHeight = std::max( rowHeight, cell->getPixelsSize().getHeight() );
|
||||
colIndex += cellColspan;
|
||||
}
|
||||
|
||||
// Position cells inside the row and equalize height
|
||||
Float currentX = mCellspacing;
|
||||
colIndex = 0;
|
||||
for ( Uint32 c = 0; c < columnCount; ++c ) {
|
||||
UIHTMLTableCell* cell = mCells[start + c];
|
||||
cell->beginAttributesTransaction();
|
||||
cell->setPixelsPosition( currentX, 0 );
|
||||
Uint32 cellColspan = cell->getColspan();
|
||||
Float cellWidth = 0;
|
||||
for ( Uint32 j = 0; j < cellColspan && ( colIndex + j ) < maxCols; ++j ) {
|
||||
cellWidth += mColWidths[colIndex + j];
|
||||
}
|
||||
if ( cellColspan > 1 )
|
||||
cellWidth += ( cellColspan - 1 ) * mCellspacing;
|
||||
cell->setPixelsSize( cellWidth, rowHeight );
|
||||
cell->endAttributesTransaction();
|
||||
currentX += cellWidth + mCellspacing;
|
||||
colIndex += cellColspan;
|
||||
}
|
||||
|
||||
// Set row height and width
|
||||
UIHTMLTableRow* row = mRows[r];
|
||||
row->setPixelsSize( containerWidth - paddingH, rowHeight );
|
||||
|
||||
if ( r == 0 && mCells[start]->getParent()->isType( UI_TYPE_HTML_TABLE_HEAD ) ) {
|
||||
headHeight = rowHeight;
|
||||
} else if ( r == rowCount - 1 && columnCount &&
|
||||
mCells[start]->getParent()->isType( UI_TYPE_HTML_TABLE_FOOTER ) ) {
|
||||
footerHeight = rowHeight;
|
||||
} else {
|
||||
bodyHeight += rowHeight;
|
||||
}
|
||||
}
|
||||
|
||||
// Position rows vertically
|
||||
if ( mHead ) {
|
||||
mHead->setPixelsPosition( 0, 0 );
|
||||
mHead->setPixelsSize( { getPixelsSize().x, headHeight } );
|
||||
}
|
||||
|
||||
if ( mBody ) {
|
||||
mBody->setPixelsPosition( 0, headHeight );
|
||||
mBody->setPixelsSize( { getPixelsSize().x, bodyHeight } );
|
||||
}
|
||||
|
||||
if ( mFooter ) {
|
||||
mFooter->setPixelsPosition( 0, headHeight + bodyHeight );
|
||||
mFooter->setPixelsSize( { getPixelsSize().x, footerHeight } );
|
||||
}
|
||||
|
||||
Float currentY = mPaddingPx.Top + mCellspacing - headHeight;
|
||||
for ( size_t r = 0; r < rowCount; ++r ) {
|
||||
UIHTMLTableRow* row = mRows[r];
|
||||
row->setPixelsPosition( mPaddingPx.Left, currentY );
|
||||
currentY += row->getPixelsSize().getHeight() + mCellspacing;
|
||||
}
|
||||
|
||||
// Reset positions if they are inside specialized containers
|
||||
if ( mHead && !mRows.empty() )
|
||||
mRows[0]->setPixelsPosition( mPaddingPx.Left, 0 );
|
||||
|
||||
if ( mFooter && !mRows.empty() )
|
||||
mRows[rowCount - 1]->setPixelsPosition( mPaddingPx.Left, 0 );
|
||||
|
||||
if ( mHeightPolicy == SizePolicy::WrapContent ) {
|
||||
setInternalPixelsHeight( mPaddingPx.Top + headHeight + bodyHeight + footerHeight +
|
||||
( rowCount + 1 ) * mCellspacing + mPaddingPx.Bottom );
|
||||
}
|
||||
|
||||
mPacking = false;
|
||||
mDirtyLayout = false;
|
||||
}
|
||||
|
||||
Uint32 UIHTMLTable::onMessage( const NodeMessage* Msg ) {
|
||||
switch ( Msg->getMsg() ) {
|
||||
case NodeMessage::LayoutAttributeChange: {
|
||||
if ( Msg->getSender() != this && !mPacking ) {
|
||||
mIntrinsicWidthsDirty = true;
|
||||
if ( Msg->getSender() != this && !isPacking() ) {
|
||||
if ( getLayouter() )
|
||||
getLayouter()->invalidateIntrinsicWidths();
|
||||
notifyLayoutAttrChangeParent();
|
||||
}
|
||||
tryUpdateLayout();
|
||||
@@ -597,7 +115,8 @@ UIHTMLTableRow* UIHTMLTableRow::New() {
|
||||
return eeNew( UIHTMLTableRow, () );
|
||||
}
|
||||
|
||||
UIHTMLTableRow::UIHTMLTableRow() : UIWidget( "tr" ) {
|
||||
UIHTMLTableRow::UIHTMLTableRow() : UIHTMLWidget( "tr" ) {
|
||||
mDisplay = CSSDisplay::TableRow;
|
||||
mWidthPolicy = SizePolicy::MatchParent;
|
||||
mHeightPolicy = SizePolicy::WrapContent;
|
||||
}
|
||||
@@ -607,7 +126,7 @@ Uint32 UIHTMLTableRow::getType() const {
|
||||
}
|
||||
|
||||
bool UIHTMLTableRow::isType( const Uint32& type ) const {
|
||||
return UIHTMLTableRow::getType() == type || UIWidget::isType( type );
|
||||
return UIHTMLTableRow::getType() == type || UIHTMLWidget::isType( type );
|
||||
}
|
||||
|
||||
UIHTMLTableCell* UIHTMLTableCell::New( const std::string& tag ) {
|
||||
@@ -615,6 +134,7 @@ UIHTMLTableCell* UIHTMLTableCell::New( const std::string& tag ) {
|
||||
}
|
||||
|
||||
UIHTMLTableCell::UIHTMLTableCell( const std::string& tag ) : UIRichText( tag ) {
|
||||
mDisplay = CSSDisplay::TableCell;
|
||||
mWidthPolicy = SizePolicy::WrapContent;
|
||||
mHeightPolicy = SizePolicy::WrapContent;
|
||||
}
|
||||
@@ -657,7 +177,8 @@ UIHTMLTableHead* UIHTMLTableHead::New() {
|
||||
return eeNew( UIHTMLTableHead, () );
|
||||
}
|
||||
|
||||
UIHTMLTableHead::UIHTMLTableHead() : UIWidget( "thead" ) {
|
||||
UIHTMLTableHead::UIHTMLTableHead() : UIHTMLWidget( "thead" ) {
|
||||
mDisplay = CSSDisplay::TableHead;
|
||||
mWidthPolicy = SizePolicy::MatchParent;
|
||||
mHeightPolicy = SizePolicy::WrapContent;
|
||||
}
|
||||
@@ -667,14 +188,15 @@ Uint32 UIHTMLTableHead::getType() const {
|
||||
}
|
||||
|
||||
bool UIHTMLTableHead::isType( const Uint32& type ) const {
|
||||
return UIHTMLTableHead::getType() == type || UIWidget::isType( type );
|
||||
return UIHTMLTableHead::getType() == type || UIHTMLWidget::isType( type );
|
||||
}
|
||||
|
||||
UIHTMLTableBody* UIHTMLTableBody::New() {
|
||||
return eeNew( UIHTMLTableBody, () );
|
||||
}
|
||||
|
||||
UIHTMLTableBody::UIHTMLTableBody() : UIWidget( "tbody" ) {
|
||||
UIHTMLTableBody::UIHTMLTableBody() : UIHTMLWidget( "tbody" ) {
|
||||
mDisplay = CSSDisplay::TableBody;
|
||||
mWidthPolicy = SizePolicy::MatchParent;
|
||||
mHeightPolicy = SizePolicy::WrapContent;
|
||||
}
|
||||
@@ -684,14 +206,15 @@ Uint32 UIHTMLTableBody::getType() const {
|
||||
}
|
||||
|
||||
bool UIHTMLTableBody::isType( const Uint32& type ) const {
|
||||
return UIHTMLTableBody::getType() == type || UIWidget::isType( type );
|
||||
return UIHTMLTableBody::getType() == type || UIHTMLWidget::isType( type );
|
||||
}
|
||||
|
||||
UIHTMLTableFooter* UIHTMLTableFooter::New() {
|
||||
return eeNew( UIHTMLTableFooter, () );
|
||||
}
|
||||
|
||||
UIHTMLTableFooter::UIHTMLTableFooter() : UIWidget( "tfoot" ) {
|
||||
UIHTMLTableFooter::UIHTMLTableFooter() : UIHTMLWidget( "tfoot" ) {
|
||||
mDisplay = CSSDisplay::TableFooter;
|
||||
mWidthPolicy = SizePolicy::MatchParent;
|
||||
mHeightPolicy = SizePolicy::WrapContent;
|
||||
}
|
||||
@@ -701,7 +224,7 @@ Uint32 UIHTMLTableFooter::getType() const {
|
||||
}
|
||||
|
||||
bool UIHTMLTableFooter::isType( const Uint32& type ) const {
|
||||
return UIHTMLTableFooter::getType() == type || UIWidget::isType( type );
|
||||
return UIHTMLTableFooter::getType() == type || UIHTMLWidget::isType( type );
|
||||
}
|
||||
|
||||
}} // namespace EE::UI
|
||||
|
||||
158
src/eepp/ui/uihtmlwidget.cpp
Normal file
158
src/eepp/ui/uihtmlwidget.cpp
Normal file
@@ -0,0 +1,158 @@
|
||||
#include <eepp/ui/css/propertydefinition.hpp>
|
||||
#include <eepp/ui/uihtmlwidget.hpp>
|
||||
#include <eepp/ui/uilayouter.hpp>
|
||||
#include <eepp/ui/uilayoutermanager.hpp>
|
||||
|
||||
namespace EE { namespace UI {
|
||||
|
||||
UIHTMLWidget* UIHTMLWidget::New() {
|
||||
return eeNew( UIHTMLWidget, () );
|
||||
}
|
||||
|
||||
UIHTMLWidget::UIHTMLWidget( const std::string& tag ) : UILayout( tag ) {}
|
||||
|
||||
UIHTMLWidget::~UIHTMLWidget() {
|
||||
eeSAFE_DELETE( mLayouter );
|
||||
}
|
||||
|
||||
UILayouter* UIHTMLWidget::getLayouter() {
|
||||
if ( !mLayouter ) {
|
||||
mLayouter = UILayouterManager::create( mDisplay, this );
|
||||
}
|
||||
return mLayouter;
|
||||
}
|
||||
|
||||
Uint32 UIHTMLWidget::getType() const {
|
||||
return UI_TYPE_HTML_WIDGET;
|
||||
}
|
||||
|
||||
bool UIHTMLWidget::isType( const Uint32& type ) const {
|
||||
return UIHTMLWidget::getType() == type ? true : UILayout::isType( type );
|
||||
}
|
||||
|
||||
bool UIHTMLWidget::isPacking() const {
|
||||
UILayouter* layouter = const_cast<UIHTMLWidget*>( this )->getLayouter();
|
||||
if ( layouter )
|
||||
return layouter->isPacking();
|
||||
return UILayout::isPacking();
|
||||
}
|
||||
|
||||
void UIHTMLWidget::onDisplayChange() {
|
||||
eeSAFE_DELETE( mLayouter );
|
||||
getLayouter();
|
||||
notifyLayoutAttrChange();
|
||||
}
|
||||
|
||||
void UIHTMLWidget::setDisplay( CSSDisplay display ) {
|
||||
if ( mDisplay != display ) {
|
||||
mDisplay = display;
|
||||
onDisplayChange();
|
||||
}
|
||||
}
|
||||
|
||||
void UIHTMLWidget::setCSSPosition( CSSPosition position ) {
|
||||
if ( mPosition != position ) {
|
||||
mPosition = position;
|
||||
onPositionChange();
|
||||
}
|
||||
}
|
||||
|
||||
void UIHTMLWidget::setOffsets( const Rectf& offsets ) {
|
||||
if ( mOffsets != offsets ) {
|
||||
mOffsets = offsets;
|
||||
notifyLayoutAttrChange();
|
||||
}
|
||||
}
|
||||
|
||||
void UIHTMLWidget::setZIndex( int zIndex ) {
|
||||
mZIndex = zIndex;
|
||||
}
|
||||
|
||||
std::string UIHTMLWidget::getPropertyString( const PropertyDefinition* propertyDef,
|
||||
const Uint32& state ) const {
|
||||
if ( NULL == propertyDef )
|
||||
return "";
|
||||
|
||||
switch ( propertyDef->getPropertyId() ) {
|
||||
case PropertyId::Display:
|
||||
return CSSDisplayHelper::toString( mDisplay );
|
||||
case PropertyId::Position:
|
||||
return CSSPositionHelper::toString( mPosition );
|
||||
case PropertyId::Top:
|
||||
return String::fromFloat( mOffsets.Top, "dp" );
|
||||
case PropertyId::Right:
|
||||
return String::fromFloat( mOffsets.Right, "dp" );
|
||||
case PropertyId::Bottom:
|
||||
return String::fromFloat( mOffsets.Bottom, "dp" );
|
||||
case PropertyId::Left:
|
||||
return String::fromFloat( mOffsets.Left, "dp" );
|
||||
case PropertyId::ZIndex:
|
||||
return String::toString( mZIndex );
|
||||
default:
|
||||
return UILayout::getPropertyString( propertyDef );
|
||||
}
|
||||
}
|
||||
|
||||
bool UIHTMLWidget::applyProperty( const StyleSheetProperty& attribute ) {
|
||||
if ( !checkPropertyDefinition( attribute ) )
|
||||
return false;
|
||||
|
||||
switch ( attribute.getPropertyDefinition()->getPropertyId() ) {
|
||||
case PropertyId::Display: {
|
||||
setDisplay( CSSDisplayHelper::fromString( attribute.asString() ) );
|
||||
return true;
|
||||
}
|
||||
case PropertyId::Position: {
|
||||
setCSSPosition( CSSPositionHelper::fromString( attribute.asString() ) );
|
||||
return true;
|
||||
}
|
||||
case PropertyId::ZIndex: {
|
||||
setZIndex( attribute.asInt() );
|
||||
return true;
|
||||
}
|
||||
case PropertyId::Top: {
|
||||
if ( attribute.asString() == "auto" )
|
||||
mOffsets.Top = 0;
|
||||
else
|
||||
mOffsets.Top = lengthFromValueAsDp( attribute );
|
||||
notifyLayoutAttrChange();
|
||||
return true;
|
||||
}
|
||||
case PropertyId::Right: {
|
||||
if ( attribute.asString() == "auto" )
|
||||
mOffsets.Right = 0;
|
||||
else
|
||||
mOffsets.Right = lengthFromValueAsDp( attribute );
|
||||
notifyLayoutAttrChange();
|
||||
return true;
|
||||
}
|
||||
case PropertyId::Bottom: {
|
||||
if ( attribute.asString() == "auto" )
|
||||
mOffsets.Bottom = 0;
|
||||
else
|
||||
mOffsets.Bottom = lengthFromValueAsDp( attribute );
|
||||
notifyLayoutAttrChange();
|
||||
return true;
|
||||
}
|
||||
case PropertyId::Left: {
|
||||
if ( attribute.asString() == "auto" )
|
||||
mOffsets.Left = 0;
|
||||
else
|
||||
mOffsets.Left = lengthFromValueAsDp( attribute );
|
||||
notifyLayoutAttrChange();
|
||||
return true;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return UILayout::applyProperty( attribute );
|
||||
}
|
||||
|
||||
void UIHTMLWidget::invalidateIntrinsicSize() {
|
||||
if ( mLayouter )
|
||||
mLayouter->invalidateIntrinsicWidths();
|
||||
UIWidget::invalidateIntrinsicSize();
|
||||
}
|
||||
|
||||
}} // namespace EE::UI
|
||||
10
src/eepp/ui/uilayouter.cpp
Normal file
10
src/eepp/ui/uilayouter.cpp
Normal file
@@ -0,0 +1,10 @@
|
||||
#include <eepp/ui/uilayout.hpp>
|
||||
#include <eepp/ui/uilayouter.hpp>
|
||||
|
||||
namespace EE { namespace UI {
|
||||
|
||||
void UILayouter::setMatchParentIfNeededVerticalGrowth() {
|
||||
mContainer->asType<UILayout>()->setMatchParentIfNeededVerticalGrowth();
|
||||
}
|
||||
|
||||
}} // namespace EE::UI
|
||||
28
src/eepp/ui/uilayoutermanager.cpp
Normal file
28
src/eepp/ui/uilayoutermanager.cpp
Normal file
@@ -0,0 +1,28 @@
|
||||
#include <eepp/core/memorymanager.hpp>
|
||||
#include <eepp/ui/blocklayouter.hpp>
|
||||
#include <eepp/ui/inlinelayouter.hpp>
|
||||
#include <eepp/ui/nonelayouter.hpp>
|
||||
#include <eepp/ui/tablelayouter.hpp>
|
||||
#include <eepp/ui/uilayouter.hpp>
|
||||
#include <eepp/ui/uilayoutermanager.hpp>
|
||||
|
||||
namespace EE { namespace UI {
|
||||
|
||||
UILayouter* UILayouterManager::create( CSSDisplay display, UIWidget* container ) {
|
||||
switch ( display ) {
|
||||
case CSSDisplay::Block:
|
||||
case CSSDisplay::TableCell:
|
||||
return eeNew( BlockLayouter, ( container ) );
|
||||
case CSSDisplay::Inline:
|
||||
case CSSDisplay::InlineBlock:
|
||||
return eeNew( InlineLayouter, ( container ) );
|
||||
case CSSDisplay::Table:
|
||||
return eeNew( TableLayouter, ( container ) );
|
||||
case CSSDisplay::None:
|
||||
return eeNew( NoneLayouter, ( container ) );
|
||||
default:
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
}} // namespace EE::UI
|
||||
@@ -1670,6 +1670,11 @@ Float UINode::lengthFromValue( const StyleSheetProperty& property,
|
||||
res.setValue( 1.2f, StyleSheetLength::Unit::Em );
|
||||
}
|
||||
return convertLength( res, 0 );
|
||||
} else if ( property.getValue() == "inherit" ) {
|
||||
// TODO: FIX inherit value
|
||||
StyleSheetLength res;
|
||||
res.setValue( 1, StyleSheetLength::Unit::Em );
|
||||
return convertLength( res, 0 );
|
||||
}
|
||||
}
|
||||
return lengthFromValue( property.getValue(),
|
||||
|
||||
@@ -6,6 +6,9 @@
|
||||
#include <eepp/ui/uiscenenode.hpp>
|
||||
#include <eepp/ui/uithememanager.hpp>
|
||||
|
||||
#define PUGIXML_HEADER_ONLY
|
||||
#include <pugixml/pugixml.hpp>
|
||||
|
||||
namespace EE { namespace UI {
|
||||
|
||||
InnerWidgetOrientation UIPushButton::innerWidgetOrientationFromString( std::string iwo ) {
|
||||
@@ -739,4 +742,10 @@ bool UIPushButton::applyProperty( const StyleSheetProperty& attribute ) {
|
||||
return attributeSet;
|
||||
}
|
||||
|
||||
void UIPushButton::loadFromXmlNode( const pugi::xml_node& node ) {
|
||||
UIWidget::loadFromXmlNode( node );
|
||||
if ( !node.text().empty() )
|
||||
setText( node.text().as_string() );
|
||||
}
|
||||
|
||||
}} // namespace EE::UI
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
#include <eepp/ui/css/propertydefinition.hpp>
|
||||
#include <eepp/ui/tools/htmlformatter.hpp>
|
||||
#include <eepp/ui/uicodeeditor.hpp>
|
||||
#include <eepp/ui/uilayouter.hpp>
|
||||
#include <eepp/ui/uirichtext.hpp>
|
||||
#include <eepp/ui/uiscenenode.hpp>
|
||||
#include <eepp/ui/uistyle.hpp>
|
||||
@@ -15,24 +16,13 @@
|
||||
|
||||
namespace EE { namespace UI {
|
||||
|
||||
class UILineBreak : public UIRichText {
|
||||
public:
|
||||
static UILineBreak* New( const std::string& tag ) { return eeNew( UILineBreak, ( tag ) ); }
|
||||
|
||||
UILineBreak( const std::string& tag = "br" ) : UIRichText( tag ) {}
|
||||
|
||||
virtual Uint32 getType() const { return UI_TYPE_BR; }
|
||||
|
||||
bool isType( const Uint32& type ) const {
|
||||
return UILineBreak::getType() == type ? true : UINode::isType( type );
|
||||
}
|
||||
};
|
||||
|
||||
UIHTMLHtml* UIHTMLHtml::New( const std::string& tag ) {
|
||||
return eeNew( UIHTMLHtml, ( tag ) );
|
||||
}
|
||||
|
||||
UIHTMLHtml::UIHTMLHtml( const std::string& tag ) : UIRichText( tag ) {}
|
||||
UIHTMLHtml::UIHTMLHtml( const std::string& tag ) : UIRichText( tag ) {
|
||||
enableReportSizeChangeToChildren();
|
||||
}
|
||||
|
||||
Uint32 UIHTMLHtml::getType() const {
|
||||
return UI_TYPE_HTML_HTML;
|
||||
@@ -42,6 +32,20 @@ bool UIHTMLHtml::isType( const Uint32& type ) const {
|
||||
return UIHTMLHtml::getType() == type ? true : UIRichText::isType( type );
|
||||
}
|
||||
|
||||
UILineBreak* UILineBreak::New( const std::string& tag ) {
|
||||
return eeNew( UILineBreak, ( tag ) );
|
||||
}
|
||||
|
||||
UILineBreak::UILineBreak( const std::string& tag ) : UIRichText( tag ) {}
|
||||
|
||||
Uint32 UILineBreak::getType() const {
|
||||
return UI_TYPE_BR;
|
||||
}
|
||||
|
||||
bool UILineBreak::isType( const Uint32& type ) const {
|
||||
return UILineBreak::getType() == type ? true : UIHTMLWidget::isType( type );
|
||||
}
|
||||
|
||||
UIHTMLBody* UIHTMLBody::New( const std::string& tag ) {
|
||||
return eeNew( UIHTMLBody, ( tag ) );
|
||||
}
|
||||
@@ -110,7 +114,7 @@ UIRichText* UIRichText::NewWithTag( const std::string& tag ) {
|
||||
return eeNew( UIRichText, ( tag ) );
|
||||
}
|
||||
|
||||
UIRichText::UIRichText( const std::string& tag ) : UILayout( tag ) {
|
||||
UIRichText::UIRichText( const std::string& tag ) : UIHTMLWidget( tag ) {
|
||||
mFlags |= UI_HTML_ELEMENT | UI_LOADS_ITS_CHILDREN | UI_OWNS_CHILDREN_POSITION;
|
||||
|
||||
UITheme* theme = getUISceneNode()->getUIThemeManager()->getDefaultTheme();
|
||||
@@ -137,7 +141,7 @@ Uint32 UIRichText::getType() const {
|
||||
}
|
||||
|
||||
bool UIRichText::isType( const Uint32& type ) const {
|
||||
return UIRichText::getType() == type ? true : UILayout::isType( type );
|
||||
return UIRichText::getType() == type ? true : UIHTMLWidget::isType( type );
|
||||
}
|
||||
|
||||
const RichText& UIRichText::getRichText() {
|
||||
@@ -542,22 +546,23 @@ void UIRichText::onAlphaChange() {
|
||||
UILayout::onAlphaChange();
|
||||
}
|
||||
|
||||
void UIRichText::rebuildRichText( RichText& richText, IntrinsicMode mode ) {
|
||||
void UIRichText::rebuildRichText( UILayout* container, RichText& richText, IntrinsicMode mode ) {
|
||||
richText.clear();
|
||||
|
||||
Float maxWidth = mSize.getWidth() - mPaddingPx.Left - mPaddingPx.Right;
|
||||
Float maxWidth = container->getPixelsSize().getWidth() - container->getPixelsPadding().Left -
|
||||
container->getPixelsPadding().Right;
|
||||
if ( maxWidth < 0 )
|
||||
maxWidth = 0;
|
||||
|
||||
Float mw = 0.f;
|
||||
if ( !mMaxWidthEq.empty() ) {
|
||||
mw = getMaxSizePx().getWidth() - mPaddingPx.Left - mPaddingPx.Right;
|
||||
if ( !container->getMaxWidthEq().empty() ) {
|
||||
mw = container->getMaxSizePx().getWidth() - container->getPixelsPadding().Left -
|
||||
container->getPixelsPadding().Right;
|
||||
if ( mw < 0 )
|
||||
mw = 0.f;
|
||||
}
|
||||
|
||||
if ( mode == IntrinsicMode::None ) {
|
||||
if ( !mMaxWidthEq.empty() && ( maxWidth == 0 || mw < maxWidth ) ) {
|
||||
if ( !container->getMaxWidthEq().empty() && ( maxWidth == 0 || mw < maxWidth ) ) {
|
||||
richText.setMaxWidth( mw );
|
||||
} else {
|
||||
richText.setMaxWidth( maxWidth );
|
||||
@@ -588,18 +593,19 @@ void UIRichText::rebuildRichText( RichText& richText, IntrinsicMode mode ) {
|
||||
|
||||
if ( mode == IntrinsicMode::None ) {
|
||||
if ( isBlock ) {
|
||||
if ( mSize.getWidth() != 0 ) {
|
||||
Float maxSize =
|
||||
eemax( 0.f, mSize.getWidth() - mPaddingPx.Left - mPaddingPx.Right -
|
||||
margin.Left - margin.Right );
|
||||
if ( container->getPixelsSize().getWidth() != 0 ) {
|
||||
Float maxSize = eemax( 0.f, container->getPixelsSize().getWidth() -
|
||||
container->getPixelsPadding().Left -
|
||||
container->getPixelsPadding().Right -
|
||||
margin.Left - margin.Right );
|
||||
widget->setPixelsSize( eemax( 0.f, maxSize ),
|
||||
widget->getPixelsSize().getHeight() );
|
||||
} else {
|
||||
onAutoSizeChild( widget );
|
||||
container->onAutoSizeChild( widget );
|
||||
}
|
||||
} else if ( widget->getLayoutWidthPolicy() == SizePolicy::WrapContent ||
|
||||
widget->getLayoutHeightPolicy() == SizePolicy::WrapContent ) {
|
||||
onAutoSizeChild( widget );
|
||||
container->onAutoSizeChild( widget );
|
||||
}
|
||||
}
|
||||
|
||||
@@ -613,9 +619,12 @@ void UIRichText::rebuildRichText( RichText& richText, IntrinsicMode mode ) {
|
||||
}
|
||||
|
||||
Float w = size.getWidth();
|
||||
if ( isBlock && mode == IntrinsicMode::None && mSize.getWidth() != 0 ) {
|
||||
w = eemax( 0.f, mSize.getWidth() - mPaddingPx.Left - mPaddingPx.Right -
|
||||
margin.Left - margin.Right );
|
||||
if ( isBlock && mode == IntrinsicMode::None &&
|
||||
container->getPixelsSize().getWidth() != 0 ) {
|
||||
w = eemax( 0.f, container->getPixelsSize().getWidth() -
|
||||
container->getPixelsPadding().Left -
|
||||
container->getPixelsPadding().Right - margin.Left -
|
||||
margin.Right );
|
||||
}
|
||||
|
||||
richText.addCustomSize( Sizef( w + margin.Left + margin.Right,
|
||||
@@ -624,7 +633,7 @@ void UIRichText::rebuildRichText( RichText& richText, IntrinsicMode mode ) {
|
||||
}
|
||||
};
|
||||
|
||||
Node* child = mChild;
|
||||
Node* child = container->getFirstChild();
|
||||
while ( NULL != child ) {
|
||||
if ( child->isWidget() ) {
|
||||
processWidget( child->asType<UIWidget>(), processWidget );
|
||||
@@ -633,140 +642,8 @@ void UIRichText::rebuildRichText( RichText& richText, IntrinsicMode mode ) {
|
||||
}
|
||||
}
|
||||
|
||||
void UIRichText::positionChildren() {
|
||||
const auto& lines = mRichText.getLines();
|
||||
Node* child = mChild;
|
||||
|
||||
size_t currentLine = 0;
|
||||
size_t currentSpan = 0;
|
||||
|
||||
// Helper to find the next RenderSpan of type CustomSize
|
||||
auto getNextCustomSpan = [&]() -> const RichText::RenderSpan* {
|
||||
while ( currentLine < lines.size() ) {
|
||||
const auto& line = lines[currentLine];
|
||||
while ( currentSpan < line.spans.size() ) {
|
||||
const auto& span = line.spans[currentSpan];
|
||||
currentSpan++;
|
||||
if ( std::holds_alternative<RichText::CustomBlock>( span.block ) )
|
||||
return &span;
|
||||
}
|
||||
currentSpan = 0;
|
||||
currentLine++;
|
||||
}
|
||||
return nullptr;
|
||||
};
|
||||
|
||||
Int64 curCharIdx = 0;
|
||||
|
||||
auto processWidget = [&]( UIWidget* widget, auto& processWidgetRef ) -> Rectf {
|
||||
constexpr Float maxF = std::numeric_limits<Float>::max();
|
||||
constexpr Float lowF = std::numeric_limits<Float>::lowest();
|
||||
Rectf bounds( maxF, maxF, lowF, lowF );
|
||||
|
||||
Vector2f offset;
|
||||
Node* p = widget->getParent();
|
||||
while ( p && p != this ) {
|
||||
offset += p->isWidget() ? p->asType<UIWidget>()->getPixelsPosition() : p->getPosition();
|
||||
p = p->getParent();
|
||||
}
|
||||
|
||||
if ( widget->isType( UI_TYPE_TEXTSPAN ) ) {
|
||||
UITextSpan* textSpan = widget->asType<UITextSpan>();
|
||||
Int64 startChar = curCharIdx;
|
||||
Int64 endChar = curCharIdx;
|
||||
if ( !textSpan->getText().empty() ) {
|
||||
endChar += textSpan->getText().length();
|
||||
curCharIdx = endChar;
|
||||
}
|
||||
|
||||
auto& hitBoxes = textSpan->getHitBoxes();
|
||||
hitBoxes.clear();
|
||||
|
||||
if ( startChar < endChar ) {
|
||||
for ( const auto& line : lines ) {
|
||||
bool passedText = false;
|
||||
for ( const auto& rspan : line.spans ) {
|
||||
if ( rspan.startCharIndex >= startChar && rspan.endCharIndex <= endChar ) {
|
||||
Rectf hb( mPaddingPx.Left + rspan.position.x,
|
||||
mPaddingPx.Top + line.y + rspan.position.y,
|
||||
mPaddingPx.Left + rspan.position.x + rspan.size.getWidth(),
|
||||
mPaddingPx.Top + line.y + rspan.position.y +
|
||||
rspan.size.getHeight() );
|
||||
|
||||
hitBoxes.push_back( hb );
|
||||
bounds.expand( hb );
|
||||
} else if ( rspan.startCharIndex > endChar ) {
|
||||
passedText = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if ( passedText )
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Node* spanChild = widget->getFirstChild();
|
||||
while ( spanChild != NULL ) {
|
||||
if ( spanChild->isWidget() ) {
|
||||
bounds.expand(
|
||||
processWidgetRef( spanChild->asType<UIWidget>(), processWidgetRef ) );
|
||||
}
|
||||
spanChild = spanChild->getNextNode();
|
||||
}
|
||||
|
||||
// Ensure the parent span at least has enough size to cover its children
|
||||
if ( bounds.Left <= bounds.Right && bounds.Top <= bounds.Bottom ) {
|
||||
Vector2f boundsPos = bounds.getPosition();
|
||||
|
||||
widget->setPixelsPosition( boundsPos - offset );
|
||||
if ( bounds.getSize() != widget->getPixelsSize() ) {
|
||||
widget->setPixelsSize( bounds.getSize() );
|
||||
mResizedCount++;
|
||||
}
|
||||
|
||||
for ( auto& hb : hitBoxes )
|
||||
hb.move( -boundsPos );
|
||||
|
||||
} else {
|
||||
hitBoxes.clear();
|
||||
}
|
||||
|
||||
} else if ( widget->isType( UI_TYPE_BR ) ) {
|
||||
curCharIdx += 1;
|
||||
Vector2f pos;
|
||||
if ( widget->getPrevNode() && widget->getPrevNode()->isWidget() ) {
|
||||
pos = widget->getPrevNode()->asType<UIWidget>()->getPixelsPosition();
|
||||
pos.y += widget->getPrevNode()->getPixelsSize().getHeight();
|
||||
}
|
||||
widget->setPixelsPosition( pos );
|
||||
widget->setPixelsSize(
|
||||
{ eemax( 0.f, mSize.getWidth() - mPaddingPx.Left - mPaddingPx.Right ), 0 } );
|
||||
} else {
|
||||
curCharIdx += 1;
|
||||
const auto* span = getNextCustomSpan();
|
||||
if ( span ) {
|
||||
size_t lineIdx = currentSpan > 0 ? currentLine : currentLine - 1;
|
||||
Float lineY = lines[lineIdx].y;
|
||||
Rectf margin = widget->getLayoutPixelsMargin();
|
||||
|
||||
Vector2f targetPos( mPaddingPx.Left + span->position.x + margin.Left,
|
||||
mPaddingPx.Top + lineY + span->position.y + margin.Top );
|
||||
|
||||
widget->setPixelsPosition( targetPos - offset );
|
||||
|
||||
bounds = Rectf( targetPos, span->size );
|
||||
}
|
||||
}
|
||||
return bounds;
|
||||
};
|
||||
|
||||
child = mChild;
|
||||
while ( NULL != child ) {
|
||||
if ( child->isWidget() ) {
|
||||
processWidget( child->asType<UIWidget>(), processWidget );
|
||||
}
|
||||
child = child->getNextNode();
|
||||
}
|
||||
void UIRichText::rebuildRichText( RichText& richText, IntrinsicMode mode ) {
|
||||
rebuildRichText( this, richText, mode );
|
||||
}
|
||||
|
||||
void UIRichText::updateDefaultSpansStyle() {
|
||||
@@ -780,51 +657,13 @@ void UIRichText::updateDefaultSpansStyle() {
|
||||
}
|
||||
|
||||
void UIRichText::updateLayout() {
|
||||
if ( mPacking )
|
||||
return;
|
||||
mResizedCount = 0;
|
||||
mPacking = true;
|
||||
|
||||
setMatchParentIfNeededVerticalGrowth();
|
||||
|
||||
const StyleSheetProperty* prop = nullptr;
|
||||
if ( getLayoutWidthPolicy() == SizePolicy::Fixed && mStyle &&
|
||||
( prop = mStyle->getProperty( PropertyId::Width ) ) ) {
|
||||
setInternalPixelsSize( { lengthFromValue( *prop ), mSize.getHeight() } );
|
||||
if ( getLayouter() ) {
|
||||
getLayouter()->updateLayout();
|
||||
} else {
|
||||
UILayout::updateLayout();
|
||||
}
|
||||
|
||||
rebuildRichText( mRichText );
|
||||
|
||||
mRichText.updateLayout();
|
||||
|
||||
positionChildren();
|
||||
|
||||
Float totW = mSize.getWidth();
|
||||
if ( mWidthPolicy == SizePolicy::WrapContent ) {
|
||||
totW = mRichText.getSize().getWidth() + mPaddingPx.Left + mPaddingPx.Right;
|
||||
if ( !mMaxWidthEq.empty() && totW > getMaxSizePx().getWidth() )
|
||||
setClipType( ClipType::ContentBox );
|
||||
}
|
||||
|
||||
if ( totW != mSize.getWidth() || mWidthPolicy == SizePolicy::WrapContent )
|
||||
setInternalPixelsWidth( totW );
|
||||
|
||||
Float totH = mSize.getHeight();
|
||||
if ( mHeightPolicy == SizePolicy::WrapContent ) {
|
||||
totH = mRichText.getSize().getHeight() + mPaddingPx.Top + mPaddingPx.Bottom;
|
||||
if ( !mMaxHeightEq.empty() && totH > getMaxSizePx().getHeight() )
|
||||
setClipType( ClipType::ContentBox );
|
||||
}
|
||||
|
||||
if ( totH != mSize.getHeight() || mHeightPolicy == SizePolicy::WrapContent )
|
||||
setInternalPixelsHeight( totH );
|
||||
|
||||
if ( mResizedCount )
|
||||
positionChildren();
|
||||
|
||||
mPacking = false;
|
||||
mDirtyLayout = false;
|
||||
mResizedCount = 0;
|
||||
}
|
||||
|
||||
Float UIRichText::getMinIntrinsicWidth() const {
|
||||
@@ -832,11 +671,18 @@ Float UIRichText::getMinIntrinsicWidth() const {
|
||||
return getPropertyWidth();
|
||||
}
|
||||
|
||||
if ( mIntrinsicWidthsDirty ) {
|
||||
UILayouter* layouter = const_cast<UIRichText*>( this )->getLayouter();
|
||||
if ( mIntrinsicWidthsDirty && layouter ) {
|
||||
layouter->computeIntrinsicWidths();
|
||||
mMinIntrinsicWidth = layouter->getMinIntrinsicWidth();
|
||||
mMaxIntrinsicWidth = layouter->getMaxIntrinsicWidth();
|
||||
} else if ( mIntrinsicWidthsDirty ) {
|
||||
RichText richText( mRichText );
|
||||
const_cast<UIRichText*>( this )->rebuildRichText( richText, IntrinsicMode::Min );
|
||||
UIRichText::rebuildRichText( const_cast<UIRichText*>( this ), richText,
|
||||
IntrinsicMode::Min );
|
||||
mMinIntrinsicWidth = richText.getMinIntrinsicWidth() + mPaddingPx.Left + mPaddingPx.Right;
|
||||
const_cast<UIRichText*>( this )->rebuildRichText( richText, IntrinsicMode::Max );
|
||||
UIRichText::rebuildRichText( const_cast<UIRichText*>( this ), richText,
|
||||
IntrinsicMode::Max );
|
||||
mMaxIntrinsicWidth = richText.getMaxIntrinsicWidth() + mPaddingPx.Left + mPaddingPx.Right;
|
||||
mIntrinsicWidthsDirty = false;
|
||||
}
|
||||
@@ -854,16 +700,24 @@ Float UIRichText::getMaxIntrinsicWidth() const {
|
||||
return getPropertyWidth();
|
||||
}
|
||||
|
||||
if ( mIntrinsicWidthsDirty ) {
|
||||
RichText richText( mRichText );
|
||||
const_cast<UIRichText*>( this )->rebuildRichText( richText, IntrinsicMode::Min );
|
||||
mMinIntrinsicWidth = richText.getMinIntrinsicWidth() + mPaddingPx.Left + mPaddingPx.Right;
|
||||
const_cast<UIRichText*>( this )->rebuildRichText( richText, IntrinsicMode::Max );
|
||||
mMaxIntrinsicWidth = richText.getMaxIntrinsicWidth() + mPaddingPx.Left + mPaddingPx.Right;
|
||||
mIntrinsicWidthsDirty = false;
|
||||
Float maxW = 0;
|
||||
if ( const_cast<UIRichText*>( this )->getLayouter() ) {
|
||||
maxW = const_cast<UIRichText*>( this )->getLayouter()->getMaxIntrinsicWidth();
|
||||
} else {
|
||||
if ( mIntrinsicWidthsDirty ) {
|
||||
RichText richText( mRichText );
|
||||
const_cast<UIRichText*>( this )->rebuildRichText( richText, IntrinsicMode::Min );
|
||||
mMinIntrinsicWidth =
|
||||
richText.getMinIntrinsicWidth() + mPaddingPx.Left + mPaddingPx.Right;
|
||||
const_cast<UIRichText*>( this )->rebuildRichText( richText, IntrinsicMode::Max );
|
||||
mMaxIntrinsicWidth =
|
||||
richText.getMaxIntrinsicWidth() + mPaddingPx.Left + mPaddingPx.Right;
|
||||
mIntrinsicWidthsDirty = false;
|
||||
}
|
||||
maxW = mMaxIntrinsicWidth;
|
||||
}
|
||||
|
||||
Float maxWidth = mMaxIntrinsicWidth;
|
||||
Float maxWidth = maxW;
|
||||
if ( !mMinWidthEq.empty() )
|
||||
maxWidth = eemax( maxWidth, getMinSizePx().getWidth() );
|
||||
if ( !mMaxWidthEq.empty() )
|
||||
@@ -875,7 +729,7 @@ Uint32 UIRichText::onMessage( const NodeMessage* Msg ) {
|
||||
switch ( Msg->getMsg() ) {
|
||||
case NodeMessage::LayoutAttributeChange: {
|
||||
if ( Msg->getSender() != this && !mPacking ) {
|
||||
mIntrinsicWidthsDirty = true;
|
||||
invalidateIntrinsicSize();
|
||||
notifyLayoutAttrChangeParent();
|
||||
}
|
||||
tryUpdateLayout();
|
||||
|
||||
@@ -400,10 +400,21 @@ void UISceneNode::setStyleSheet( const std::string& inlineStyleSheet ) {
|
||||
void UISceneNode::combineStyleSheet( const CSS::StyleSheet& styleSheet, bool forceReloadStyle,
|
||||
URI baseURI ) {
|
||||
mStyleSheet.combineStyleSheet( styleSheet );
|
||||
|
||||
bool mediaChanged = false;
|
||||
if ( !mStyleSheet.isMediaQueryListEmpty() &&
|
||||
mStyleSheet.updateMediaLists( getMediaFeatures() ) ) {
|
||||
mediaChanged = true;
|
||||
}
|
||||
|
||||
processStyleSheetAtRules( styleSheet, baseURI );
|
||||
|
||||
if ( mRoot && mRoot->getUIStyle() )
|
||||
mRoot->getUIStyle()->resetGlobalDefinition();
|
||||
processStyleSheetAtRules( styleSheet, baseURI );
|
||||
onMediaChanged();
|
||||
|
||||
if ( mRoot && mediaChanged )
|
||||
mRoot->reportStyleStateChangeRecursive( false, false );
|
||||
|
||||
if ( forceReloadStyle )
|
||||
reloadStyle();
|
||||
}
|
||||
|
||||
@@ -20,7 +20,8 @@ UITextSpan* UITextSpan::NewWithTag( const std::string& tag ) {
|
||||
return eeNew( UITextSpan, ( tag ) );
|
||||
}
|
||||
|
||||
UITextSpan::UITextSpan( const std::string& tag ) : UIWidget( tag ) {
|
||||
UITextSpan::UITextSpan( const std::string& tag ) : UIHTMLWidget( tag ) {
|
||||
mDisplay = CSSDisplay::Inline;
|
||||
mFlags |= UI_HTML_ELEMENT | UI_VALIGN_CENTER | UI_HALIGN_LEFT | UI_LOADS_ITS_CHILDREN;
|
||||
|
||||
UITheme* theme = getUISceneNode()->getUIThemeManager()->getDefaultTheme();
|
||||
|
||||
@@ -742,15 +742,17 @@ bool UITextView::applyProperty( const StyleSheetProperty& attribute ) {
|
||||
setSelectionBackColor( attribute.asColor() );
|
||||
break;
|
||||
case PropertyId::FontFamily: {
|
||||
Font* font = FontManager::instance()->getByName( attribute.value() );
|
||||
if ( attribute.value() != "inherit" ) {
|
||||
Font* font = FontManager::instance()->getByName( attribute.value() );
|
||||
|
||||
if ( !mUsingCustomStyling && NULL != font && font->loaded() ) {
|
||||
setFont( font );
|
||||
if ( !mUsingCustomStyling && NULL != font && font->loaded() ) {
|
||||
setFont( font );
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case PropertyId::FontSize:
|
||||
if ( !mUsingCustomStyling )
|
||||
if ( !mUsingCustomStyling && attribute.value() != "inherit" )
|
||||
setFontSize( lengthFromValue( attribute ) );
|
||||
break;
|
||||
case PropertyId::TextDecoration:
|
||||
|
||||
@@ -2248,6 +2248,9 @@ bool UIWidget::applyProperty( const StyleSheetProperty& attribute ) {
|
||||
unsetFlags( UI_TAB_FOCUSABLE );
|
||||
}
|
||||
break;
|
||||
case PropertyId::Hidden:
|
||||
setVisible( false );
|
||||
break;
|
||||
default:
|
||||
attributeSet = false;
|
||||
break;
|
||||
|
||||
@@ -126,7 +126,6 @@ void UIWidgetCreator::createBaseWidgetList() {
|
||||
registeredWidget["hslider"] = UISlider::NewHorizontal;
|
||||
registeredWidget["vscrollbar"] = UIScrollBar::NewVertical;
|
||||
registeredWidget["hscrollbar"] = UIScrollBar::NewHorizontal;
|
||||
registeredWidget["button"] = UIPushButton::New;
|
||||
registeredWidget["rlay"] = UIRelativeLayout::New;
|
||||
registeredWidget["tooltip"] = UITooltip::New;
|
||||
registeredWidget["tv"] = UITextView::New;
|
||||
@@ -137,6 +136,7 @@ void UIWidgetCreator::createBaseWidgetList() {
|
||||
registeredWidget["em"] = UITextSpan::NewEmphasis;
|
||||
registeredWidget["b"] = UITextSpan::NewBold;
|
||||
registeredWidget["strong"] = UITextSpan::NewBold;
|
||||
registeredWidget["small"] = UITextSpan::NewSmall;
|
||||
registeredWidget["i"] = UITextSpan::NewItalics;
|
||||
registeredWidget["u"] = UITextSpan::NewUnderline;
|
||||
registeredWidget["ins"] = UITextSpan::NewUnderline;
|
||||
@@ -186,6 +186,12 @@ void UIWidgetCreator::createBaseWidgetList() {
|
||||
registeredWidget["td"] = [] { return UIHTMLTableCell::New( "td" ); };
|
||||
registeredWidget["input"] = HTMLInput::New;
|
||||
registeredWidget["textarea"] = HTMLTextArea::New;
|
||||
registeredWidget["button"] = [] {
|
||||
auto but = UIPushButton::NewWithTag( "button" );
|
||||
but->setFlags( UI_HTML_ELEMENT );
|
||||
but->setLayoutSizePolicy( SizePolicy::WrapContent, SizePolicy::WrapContent );
|
||||
return but;
|
||||
};
|
||||
|
||||
sBaseListCreated = true;
|
||||
}
|
||||
|
||||
@@ -156,14 +156,14 @@ static const auto LAYOUT = R"xml(
|
||||
</vbox>
|
||||
</vbox>
|
||||
<vbox class="right" lw="0" lh="wc" lw8="0.5" lg="center">
|
||||
<button id="create-new" text="@string(new_file, New File)" />
|
||||
<button id="create-new-terminal" text="@string(new_terminal, New Terminal)" />
|
||||
<button id="open-folder" text="@string(open_a_folder, Open a Folder)" />
|
||||
<button id="open-file" text="@string(open_a_file, Open a File)" />
|
||||
<button id="recent-folders" text="@string(recent_folders_ellipsis, Recent Folders...)" />
|
||||
<button id="recent-files" text="@string(recent_files_ellipsis, Recent Files...)" />
|
||||
<button id="plugin-manager-open" text="@string(plugin_manager, Plugins Manager)" />
|
||||
<button id="keybindings" text="@string(keybindings, Keybindings)" />
|
||||
<PushButton id="create-new" text="@string(new_file, New File)" />
|
||||
<PushButton id="create-new-terminal" text="@string(new_terminal, New Terminal)" />
|
||||
<PushButton id="open-folder" text="@string(open_a_folder, Open a Folder)" />
|
||||
<PushButton id="open-file" text="@string(open_a_file, Open a File)" />
|
||||
<PushButton id="recent-folders" text="@string(recent_folders_ellipsis, Recent Folders...)" />
|
||||
<PushButton id="recent-files" text="@string(recent_files_ellipsis, Recent Files...)" />
|
||||
<PushButton id="plugin-manager-open" text="@string(plugin_manager, Plugins Manager)" />
|
||||
<PushButton id="keybindings" text="@string(keybindings, Keybindings)" />
|
||||
<widget class="separator" lw="mp" lh="32dp" />
|
||||
<tv class="bold" text="@string(for_help_please_visit, For help, please visit:)" lg="center" />
|
||||
<vbox lw="wc" lh="wc" lg="center">
|
||||
|
||||
Reference in New Issue
Block a user