diff --git a/.agent/plans/border_box_model_plan.md b/.agent/plans/border_box_model_plan.md new file mode 100644 index 000000000..6fcee76ef --- /dev/null +++ b/.agent/plans/border_box_model_plan.md @@ -0,0 +1,286 @@ +# Border Box Model Content Offset Plan + +## Problem Statement + +In browser engines, content (text, child widgets) is positioned at `border-width + padding` from the element's edge. In eepp's GUI system, borders are `BorderType::Inside` by default — a pure visual decoration drawn OVER the padding/content area. Content is only offset by padding, ignoring border width entirely. This causes HTML widgets to render text misaligned compared to real browsers. + +**User requirement:** UIHTMLWidget elements must behave like browsers — the border should consume space and content should be offset by border + padding. + +--- + +## Key Concepts + +| Concept | eepp Current | Browser/CSS | Desired | +|---------|-------------|-------------|---------| +| Border space | 0 (Inside type) | border-width | border-width | +| Content offset | padding only | border + padding | border + padding | +| `box-sizing` | not implemented | `content-box` (default) | add `content-box`/`border-box` | + +**BorderType behavior:** +- `Inside` (default): border drawn inside element; `getBorderBoxDiff()` returns zero rect; no space consumed +- `Outside`: border extends outward from element; adds space outside +- `Outline`: border centered on element edge; half inside, half outside + +For HTML compatibility, we treat borders as **space-consuming** regardless of BorderType — they push content inward by their width. This matches the CSS `content-box` model where `width` specifies the content area. + +--- + +## Scope: What Changes + +### 1. Add helper method to UINode/UIWidget + +**File:** `include/eepp/ui/uiwidget.hpp` (declaration), `src/eepp/ui/uiwidget.cpp` (implementation) + +Add `getPixelsContentOffset()` that returns a `Rectf` containing `padding + border` for all 4 sides. This becomes the single source of truth for content positioning in HTML widgets. + +```cpp +// Returns the content area origin offset = padding + border +Rectf getPixelsContentOffset() const; +``` + +Implementation: +```cpp +Rectf UIWidget::getPixelsContentOffset() const { + Rectf offset = getPixelsPadding(); + if (hasBorder()) { + const auto& b = getBorder()->getBorders(); + offset.Left += b.left.width; + offset.Right += b.right.width; + offset.Top += b.top.width; + offset.Bottom += b.bottom.width; + } + return offset; +} +``` + +**Complexity: LOW** — one new method, ~15 lines. + +--- + +### 2. BlockLayouter — Content Area Calculations + +**File:** `src/eepp/ui/blocklayouter.cpp` + +All locations that use `mContainer->getPixelsPadding()` for child positioning must switch to `mContainer->getPixelsContentOffset()`. + +**Affected lines (~8 sites):** + +| Line(s) | Current | Change | +|---------|---------|--------| +| 34-43 `computeIntrinsicWidths` | `getPixelsPadding().Left + .Right` | add border widths to intrinsic size | +| 74-77 `updateLayout` totW | `getPixelsPadding().Left + .Right` | `getPixelsContentOffset().Left + .Right` | +| 88-92 `updateLayout` totH | `getPixelsPadding().Top + .Bottom` | `getPixelsContentOffset().Top + .Bottom` | +| 161-167 `positionRichTextChildren` hitbox | `getPixelsPadding().Left/Top` | `getPixelsContentOffset().Left/Top` | +| 214-216 BR element width | `getPixelsPadding().Left + .Right` | `getPixelsContentOffset().Left + .Right` | +| 227-228 custom widget position | `getPixelsPadding().Left/Top` | `getPixelsContentOffset().Left/Top` | + +**Complexity: MEDIUM** — mechanical replacement, ~8 call sites. + +--- + +### 3. UIRichText — Text Rendering & Intrinsic Widths + +**File:** `src/eepp/ui/uirichtext.cpp` + +| Line(s) | Current | Change | +|---------|---------|--------| +| 180-186 `draw()` | `mScreenPos + mPaddingPx.Left/Top` | add border width to offset | +| 589-590 `rebuildRichText` maxWidth | `- getPixelsPadding().Left - .Right` | `- getPixelsContentOffset().Left - .Right` | +| 638-641 block child width | `- getPixelsPadding().Left - .Right` | `- getPixelsContentOffset().Left - .Right` | +| 665-668 child size computation | same pattern | same | +| 725-728 `getMinIntrinsicWidth` | `+ mPaddingPx.Left + .Right` | `+ getPixelsContentOffset().Left + .Right` | +| 753-756 `getMaxIntrinsicWidth` | same | same | + +**Complexity: MEDIUM** — ~6 call sites, same mechanical pattern. + +--- + +### 4. UIHTMLWidget — Out-of-Flow Children + +**File:** `src/eepp/ui/uihtmlwidget.cpp` + +| Line | Current | Change | +|------|---------|--------| +| 202 `positionOutOfFlowChildren` | `getPixelsPadding().Left, .Top` | `getPixelsContentOffset().Left, .Top` | + +Container block origin for absolutely positioned children must include border. + +**Complexity: LOW** — single line change. + +--- + +### 5. TableLayouter — Table Layout + +**File:** `src/eepp/ui/tablelayouter.cpp` + +| Line(s) | Current | Change | +|---------|---------|--------| +| 274-277 `computeIntrinsicWidths` | `getPixelsPadding().Left + .Right` | `getPixelsContentOffset().Left + .Right` | +| 309 available width | `getPixelsPadding().Left + .Right` | `getPixelsContentOffset().Left + .Right` | +| 513, 516 row positioning | `getPixelsPadding().Left` | `getPixelsContentOffset().Left` | +| 527-529 wrap-content height | `getPixelsPadding().Top + .Bottom` | `getPixelsContentOffset().Top + .Bottom` | + +**Complexity: LOW** — ~4 call sites. + +--- + +### 6. UIWidget::getMatchParentWidth/Height + +**File:** `src/eepp/ui/uiwidget.cpp` (lines ~2577-2617) + +These methods calculate how much space a `match_parent` child can use. Currently subtract parent padding; must also subtract parent border. + +```cpp +// Before: +Float width = getParent()->getPixelsSize().getWidth() - marginLeft - marginRight - + padding.Left - padding.Right; +// After: +Rectf parentOffset = getParent()->asType()->getPixelsContentOffset(); +Float width = getParent()->getPixelsSize().getWidth() - marginLeft - marginRight - + parentOffset.Left - parentOffset.Right; +``` + +**Complexity: LOW** — 2 methods, ~4 subtraction lines each. + +--- + +### 7. UIWidget::calculateAutoMargin + +**File:** `src/eepp/ui/uiwidget.cpp` (lines ~590-659) + +`margin: auto` calculation uses parent padding to determine available space. Must include parent border. + +```cpp +// Before: +Float availableWidth = parentSize.getWidth() - parentPadding.Left - parentPadding.Right - + getPixelsSize().getWidth(); +// After: +Rectf parentContentOffset = ...getPixelsContentOffset(); +Float availableWidth = parentSize.getWidth() - parentContentOffset.Left - + parentContentOffset.Right - getPixelsSize().getWidth(); +``` + +**Complexity: LOW** — ~4 call sites. + +--- + +### 8. (Optional) CSS `box-sizing` Property + +**Scope:** Can be deferred. Adding it now would make the fix more complete but doubles the complexity. + +If implemented: +- Add `BoxSizing` to `PropertyId` enum (`propertydefinition.hpp`) +- Register property: `registerProperty("box-sizing", "content-box")` +- Under `content-box`: width/height set on content area; border+padding added outside (current plan) +- Under `border-box`: width/height include border+padding; content = width - padding - border (would need reverse calculation) + +**Complexity: HIGH** — new property, two calculation modes, affects all width/height resolution. Recommended as follow-up. + +--- + +## Non-Scope / NOT Changing + +- **Non-HTML widgets** (UIPushButton, UITextInput, etc.) — they continue using `getPixelsPadding()` directly and border remains decorative. +- **UINode::nodeDraw()` clip regions** — the clipping pipeline already uses `getBorderBoxDiff()` for BorderBox clip; no change needed. +- **UIBorderDrawable rendering** — border geometry generation is unchanged. +- **BorderType behavior** — Inside/Outside/Outline remain as-is; we only USE the border width value for content offset, regardless of type. +- **Background rendering** — backgrounds already render within the padded area; we're only moving content inward. + +--- + +## Test Impact & Validation Protocol + +### Expected Test Failures + +This change alters the content area origin for all HTML widgets — text, child widgets, intrinsic widths, and match-parent sizing all shift. This means: + +**Guaranteed to fail:** +- `UIBorder.renderingVariations` — text inside bordered boxes will shift inward by the border width, changing pixel positions. **This failure IS the expected correct behavior** (the test proves the fix works). +- `UIRichText.anchorMargins` — content offset changes affect the rendered layout. +- `UIRichText.spanPadding` — spans with padding inside bordered containers shift. +- `UIHTMLTable.complexLayout` (1,2,3) — any elements with borders will have their text/content shifted. + +**Expected to pass unchanged:** +- Non-HTML widget tests (UILayout, FontRendering, etc.) — these widgets don't use the HTML border model. +- Tests where no element has a border — no content offset change occurs. + +**Unknown (may or may not differ):** +- Margin-dependent tests (e.g., `UILayout.marginAuto`) — if parent has a border, `getMatchParentWidth/Height` result changes. +- Layout tests with nested containers — cascading size changes from border inclusion could alter layouts. + +### What "Re-generate and Verify" Means + +The `compareImages` helper in the unit tests works as follows: + +1. **Golden image check:** On each test run, the rendered frame is captured via `win->getFrontBufferImage()` and pixel-compared against a stored `.webp` image at `bin/unit_tests/assets//.webp`. + +2. **Auto-generation on first run:** If the golden image file does not exist, the captured frame is saved AS the new golden image and the test passes. This is how `eepp-ui-border-rendering.webp` was created. + +3. **Re-generation for updated rendering:** To update a golden image after an intentional rendering change: + ```bash + # Delete the old golden image, re-run the test to auto-create a new one + rm bin/unit_tests/assets/html/eepp-ui-table-complex.webp + ASAN_OPTIONS=detect_leaks=0 xvfb-run -a -s "-screen 0 1280x1024x24" \ + bin/unit_tests/eepp-unit_tests-debug --filter="UIHTMLTable.complexLayout" + ``` + +4. **Human validation is REQUIRED after re-generation.** The test will pass automatically once the golden image is regenerated, but this proves nothing — it only proves the rendering is consistent with itself. A human must visually inspect the new rendering (against the old golden image, or against a reference browser rendering) to confirm the change is correct and not a regression. The agent can assist by: + - Describing expected visual differences (e.g., "all text should be shifted right by 4px in bordered elements") + - Comparing pixel dimensions between old and new golden images + - Rendering the same HTML in a reference browser for side-by-side comparison (if the agent has image analysis capabilities) + +### Agent Protocol for Failing Tests + +When tests fail due to expected rendering changes, the agent MUST: + +1. **Report** which tests failed and whether the failure is expected (border-related shift) or unexpected (regression). +2. **Do NOT auto-regenerate** golden images without first describing the expected visual differences to the user. +3. **Request human validation** by explaining what changed and asking the user to confirm the new rendering looks correct. Example: *"The UIBorder.renderingVariations test failed because text inside bordered boxes shifted right by border-left-width and down by border-top-width. I'll regenerate the golden image now — please visually verify the result matches expectations."* +4. **Regenerate golden images only after approval** — delete the old `.webp`, re-run the test, and confirm it passes. +5. **Verify with a reference browser** if the agent has image analysis capabilities — render the same HTML in a browser and compare. + +--- + +## Risk Assessment + +| Risk | Severity | Mitigation | +|------|----------|------------| +| Breaking non-HTML widgets | HIGH | Helper method on UIWidget, but only HTML layouters (BlockLayouter, UIRichText, TableLayouter) call it. Non-HTML widgets keep using `getPixelsPadding()` directly. | +| Intrinsic width changes breaking layout | MEDIUM | Run existing HTML layout image tests after each change. Verify pixel-identical rendering with re-generated golden images. | +| Match-parent calculations | MEDIUM | `getMatchParentWidth/Height` is called by ALL widgets, not just HTML. Must gate the border addition on whether parent has a border. | +| Circle dependency on border resolution | LOW | `updateBorders()` is lazy — widths are empty strings until first draw. We must call `mOwner->lengthFromValue(...)` to resolve before reading. In the `getPixelsContentOffset()` method, `getBorder()->getBorders()` accesses already-resolved values — `updateBorders()` is called in `UIBorderDrawable::update()` before draw. | + +--- + +## Implementation Order + +1. **Add `getPixelsContentOffset()` method** to UIWidget (declaration + implementation) +2. **Update BlockLayouter** — switch all `getPixelsPadding()` to `getPixelsContentOffset()` +3. **Update UIRichText** — text rendering offset and intrinsic widths +4. **Update UIHTMLWidget** — out-of-flow children offset +5. **Update TableLayouter** — table cell padding offset +6. **Update `getMatchParentWidth/Height`** — gate on parent having border, subtract parent border +7. **Update `calculateAutoMargin`** — gate on parent having border, subtract parent border +8. **Run all tests** — identify which fail and classify as expected vs unexpected +9. **Request human validation** — for all tests with expected failures, describe the visual change and ask for confirmation +10. **Regenerate golden images after approval** — delete old `.webp` files, re-run tests to capture new baseline +11. **Verify non-HTML widgets unaffected** — ensure non-HTML tests still pass with existing golden images + +--- + +## Verification + +After implementation, the agent must: + +1. **Run the full test suite** and compile a failure report categorizing each as: + - **Expected failure (border shift):** tests where content moved due to border offset — these visually differ from old golden images + - **Unexpected failure:** tests where the change caused a regression — these must be investigated and fixed + - **Passing unchanged:** tests that continue to match their existing golden images + +2. **For each expected failure**, describe to the user exactly what changed (e.g., "text in `UIRichText.anchorMargins` shifted right by the container's border-left-width of 4px"). See [Test Impact & Validation Protocol](#test-impact--validation-protocol). + +3. **Await human approval** before regenerating any golden images. + +4. **After approval**, regenerate golden images for the affected tests and confirm they pass. + +5. **Verify** the `UIBorder.renderingVariations` test now produces the correct browser-like rendering (text inside bordered boxes is properly offset by border + padding). diff --git a/bin/unit_tests/assets/html/eepp-ui-anchor-margins.webp b/bin/unit_tests/assets/html/eepp-ui-anchor-margins.webp index e8e35dabb..ac9873391 100644 Binary files a/bin/unit_tests/assets/html/eepp-ui-anchor-margins.webp and b/bin/unit_tests/assets/html/eepp-ui-anchor-margins.webp differ diff --git a/bin/unit_tests/assets/html/eepp-ui-border-rendering.webp b/bin/unit_tests/assets/html/eepp-ui-border-rendering.webp index d63bb804c..68a5cd5c8 100644 Binary files a/bin/unit_tests/assets/html/eepp-ui-border-rendering.webp and b/bin/unit_tests/assets/html/eepp-ui-border-rendering.webp differ diff --git a/include/eepp/ui/uiborderdrawable.hpp b/include/eepp/ui/uiborderdrawable.hpp index f6dd03d05..9f2b50577 100644 --- a/include/eepp/ui/uiborderdrawable.hpp +++ b/include/eepp/ui/uiborderdrawable.hpp @@ -88,13 +88,13 @@ class EE_API UIBorderDrawable : public Drawable { protected: const UINode* mOwner; VertexBuffer* mVertexBuffer; - Borders mBorders; + mutable Borders mBorders; BorderStr mBorderStr; BorderType mBorderType; Sizef mSize; bool mNeedsUpdate; bool mColorNeedsUpdate; - bool mHasBorder; + mutable bool mHasBorder; bool mSmooth{ false }; virtual void onAlphaChange(); @@ -105,7 +105,7 @@ class EE_API UIBorderDrawable : public Drawable { void update(); - void updateBorders(); + void updateBorders() const; }; }} // namespace EE::UI diff --git a/include/eepp/ui/uiwidget.hpp b/include/eepp/ui/uiwidget.hpp index f5bb94529..21c554d50 100644 --- a/include/eepp/ui/uiwidget.hpp +++ b/include/eepp/ui/uiwidget.hpp @@ -611,6 +611,15 @@ class EE_API UIWidget : public UINode { */ const Rectf& getPixelsPadding() const; + /** + * @brief Gets the content offset area (padding + border). + * + * Returns a Rectf containing padding + border for all 4 sides. + * + * @return The content offset as a Rectf. + */ + Rectf getPixelsContentOffset() const; + /** * @brief Sets the padding for all sides. * diff --git a/src/eepp/ui/blocklayouter.cpp b/src/eepp/ui/blocklayouter.cpp index 1ea04c78c..463c00e01 100644 --- a/src/eepp/ui/blocklayouter.cpp +++ b/src/eepp/ui/blocklayouter.cpp @@ -34,11 +34,13 @@ void BlockLayouter::computeIntrinsicWidths() { if ( mIntrinsicWidthsDirty ) { RichText tmpRt( *rt ); UIRichText::rebuildRichText( widget, tmpRt, UIRichText::IntrinsicMode::Min ); - mMinIntrinsicWidth = tmpRt.getMinIntrinsicWidth() + mContainer->getPixelsPadding().Left + - mContainer->getPixelsPadding().Right; + mMinIntrinsicWidth = tmpRt.getMinIntrinsicWidth() + + mContainer->getPixelsContentOffset().Left + + mContainer->getPixelsContentOffset().Right; UIRichText::rebuildRichText( widget, tmpRt, UIRichText::IntrinsicMode::Max ); - mMaxIntrinsicWidth = tmpRt.getMaxIntrinsicWidth() + mContainer->getPixelsPadding().Left + - mContainer->getPixelsPadding().Right; + mMaxIntrinsicWidth = tmpRt.getMaxIntrinsicWidth() + + mContainer->getPixelsContentOffset().Left + + mContainer->getPixelsContentOffset().Right; mIntrinsicWidthsDirty = false; } } @@ -73,8 +75,8 @@ void BlockLayouter::updateLayout() { Float totW = mContainer->getPixelsSize().getWidth(); if ( mContainer->getLayoutWidthPolicy() == SizePolicy::WrapContent ) { - totW = rt->getSize().getWidth() + mContainer->getPixelsPadding().Left + - mContainer->getPixelsPadding().Right; + totW = rt->getSize().getWidth() + mContainer->getPixelsContentOffset().Left + + mContainer->getPixelsContentOffset().Right; if ( !mContainer->getMaxWidthEq().empty() && totW > mContainer->getMaxSizePx().getWidth() ) mContainer->setClipType( ClipType::ContentBox ); } @@ -85,8 +87,8 @@ void BlockLayouter::updateLayout() { Float totH = mContainer->getPixelsSize().getHeight(); if ( mContainer->getLayoutHeightPolicy() == SizePolicy::WrapContent ) { - totH = rt->getSize().getHeight() + mContainer->getPixelsPadding().Top + - mContainer->getPixelsPadding().Bottom; + totH = rt->getSize().getHeight() + mContainer->getPixelsContentOffset().Top + + mContainer->getPixelsContentOffset().Bottom; if ( !mContainer->getMaxHeightEq().empty() && totH > mContainer->getMaxSizePx().getHeight() ) mContainer->setClipType( ClipType::ContentBox ); @@ -158,12 +160,12 @@ void BlockLayouter::positionRichTextChildren( Graphics::RichText* rt ) { 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 + + Rectf hb( mContainer->getPixelsContentOffset().Left + rspan.position.x, + mContainer->getPixelsContentOffset().Top + line.y + rspan.position.y, - mContainer->getPixelsPadding().Left + rspan.position.x + + mContainer->getPixelsContentOffset().Left + rspan.position.x + rspan.size.getWidth(), - mContainer->getPixelsPadding().Top + line.y + + mContainer->getPixelsContentOffset().Top + line.y + rspan.position.y + rspan.size.getHeight() ); hitBoxes.push_back( hb ); @@ -212,8 +214,8 @@ void BlockLayouter::positionRichTextChildren( Graphics::RichText* rt ) { } widget->setPixelsPosition( pos ); widget->setPixelsSize( { eemax( 0.f, mContainer->getPixelsSize().getWidth() - - mContainer->getPixelsPadding().Left - - mContainer->getPixelsPadding().Right ), + mContainer->getPixelsContentOffset().Left - + mContainer->getPixelsContentOffset().Right ), 0 } ); } else { curCharIdx += 1; @@ -223,9 +225,10 @@ void BlockLayouter::positionRichTextChildren( Graphics::RichText* rt ) { 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 ); + Vector2f targetPos( mContainer->getPixelsContentOffset().Left + span->position.x + + margin.Left, + mContainer->getPixelsContentOffset().Top + lineY + + span->position.y + margin.Top ); widget->setPixelsPosition( targetPos - offset ); diff --git a/src/eepp/ui/tablelayouter.cpp b/src/eepp/ui/tablelayouter.cpp index 763896c33..f5322bf42 100644 --- a/src/eepp/ui/tablelayouter.cpp +++ b/src/eepp/ui/tablelayouter.cpp @@ -89,7 +89,7 @@ void TableLayouter::computeIntrinsicWidths() { if ( mRows.empty() ) { mMinIntrinsicWidth = mMaxIntrinsicWidth = - mContainer->getPixelsPadding().Left + mContainer->getPixelsPadding().Right; + mContainer->getPixelsContentOffset().Left + mContainer->getPixelsContentOffset().Right; mIntrinsicWidthsDirty = false; return; } @@ -114,7 +114,7 @@ void TableLayouter::computeIntrinsicWidths() { if ( maxCols == 0 ) { mMinIntrinsicWidth = mMaxIntrinsicWidth = - mContainer->getPixelsPadding().Left + mContainer->getPixelsPadding().Right; + mContainer->getPixelsContentOffset().Left + mContainer->getPixelsContentOffset().Right; mIntrinsicWidthsDirty = false; return; } @@ -271,10 +271,10 @@ void TableLayouter::computeIntrinsicWidths() { 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; + mMinIntrinsicWidth = totalMin + mContainer->getPixelsContentOffset().Left + + mContainer->getPixelsContentOffset().Right + ( maxCols + 1 ) * mCellspacing; + mMaxIntrinsicWidth = totalMax + mContainer->getPixelsContentOffset().Left + + mContainer->getPixelsContentOffset().Right + ( maxCols + 1 ) * mCellspacing; mIntrinsicWidthsDirty = false; } @@ -306,7 +306,7 @@ void TableLayouter::updateLayout() { size_t maxCols = mColMinWidths.size(); mColWidths.assign( maxCols, 0.f ); - Float paddingH = mContainer->getPixelsPadding().Left + mContainer->getPixelsPadding().Right; + Float paddingH = mContainer->getPixelsContentOffset().Left + mContainer->getPixelsContentOffset().Right; Float containerWidth = mContainer->getPixelsSize().getWidth(); Float availableWidth = sanitizeFloat( std::max( 0.f, containerWidth - paddingH - ( maxCols + 1 ) * mCellspacing ) ); @@ -510,23 +510,23 @@ void TableLayouter::updateLayout() { mFooter->setPixelsSize( { mContainer->getPixelsSize().x, footerHeight } ); } - Float currentY = mContainer->getPixelsPadding().Top + mCellspacing - headHeight; + Float currentY = mContainer->getPixelsContentOffset().Top + mCellspacing - headHeight; for ( size_t r = 0; r < rowCount; ++r ) { UIHTMLTableRow* row = mRows[r]; - row->setPixelsPosition( mContainer->getPixelsPadding().Left, currentY ); + row->setPixelsPosition( mContainer->getPixelsContentOffset().Left, currentY ); currentY += row->getPixelsSize().getHeight() + mCellspacing; } if ( mHead && !mRows.empty() ) - mRows[0]->setPixelsPosition( mContainer->getPixelsPadding().Left, 0 ); + mRows[0]->setPixelsPosition( mContainer->getPixelsContentOffset().Left, 0 ); if ( mFooter && !mRows.empty() ) - mRows[rowCount - 1]->setPixelsPosition( mContainer->getPixelsPadding().Left, 0 ); + mRows[rowCount - 1]->setPixelsPosition( mContainer->getPixelsContentOffset().Left, 0 ); if ( mContainer->getLayoutHeightPolicy() == SizePolicy::WrapContent ) { mContainer->asType()->setInternalPixelsHeight( - mContainer->getPixelsPadding().Top + headHeight + bodyHeight + footerHeight + - ( rowCount + 1 ) * mCellspacing + mContainer->getPixelsPadding().Bottom ); + mContainer->getPixelsContentOffset().Top + headHeight + bodyHeight + footerHeight + + ( rowCount + 1 ) * mCellspacing + mContainer->getPixelsContentOffset().Bottom ); } mPacking = false; diff --git a/src/eepp/ui/uiborderdrawable.cpp b/src/eepp/ui/uiborderdrawable.cpp index 1774bf97d..bf6e26621 100644 --- a/src/eepp/ui/uiborderdrawable.cpp +++ b/src/eepp/ui/uiborderdrawable.cpp @@ -225,6 +225,8 @@ void UIBorderDrawable::setBottomRightRadius( const std::string& radius ) { } const Borders& UIBorderDrawable::getBorders() const { + if ( mNeedsUpdate ) + updateBorders(); return mBorders; } @@ -249,6 +251,9 @@ void UIBorderDrawable::onPositionChange() { } Rectf UIBorderDrawable::getBorderBoxDiff() const { + if ( mNeedsUpdate ) + updateBorders(); + Rectf bd; switch ( mBorderType ) { case BorderType::Outside: { @@ -342,7 +347,7 @@ void UIBorderDrawable::update() { mNeedsUpdate = false; } -void UIBorderDrawable::updateBorders() { +void UIBorderDrawable::updateBorders() const { if ( !mBorderStr.width.left.empty() ) { mBorders.left.width = mOwner->lengthFromValue( mBorderStr.width.left, CSS::PropertyRelativeTarget::LocalBlockRadiusWidth ); diff --git a/src/eepp/ui/uihtmlwidget.cpp b/src/eepp/ui/uihtmlwidget.cpp index 220d477c3..6e96404df 100644 --- a/src/eepp/ui/uihtmlwidget.cpp +++ b/src/eepp/ui/uihtmlwidget.cpp @@ -199,7 +199,7 @@ void UIHTMLWidget::positionOutOfFlowChildren() { Float top = PixelDensity::dpToPx( offsets.Top ); Float left = PixelDensity::dpToPx( offsets.Left ); - Vector2f cbPos( cb->getPixelsPadding().Left, cb->getPixelsPadding().Top ); + Vector2f cbPos( cb->getPixelsContentOffset().Left, cb->getPixelsContentOffset().Top ); cbPos.x += left; cbPos.y += top; diff --git a/src/eepp/ui/uirichtext.cpp b/src/eepp/ui/uirichtext.cpp index 711fac9e2..a7f471589 100644 --- a/src/eepp/ui/uirichtext.cpp +++ b/src/eepp/ui/uirichtext.cpp @@ -176,14 +176,16 @@ void UIRichText::draw() { UIWidget::draw(); if ( mRichText.getSize().getWidth() > 0.f ) { + Rectf contentOffset = getPixelsContentOffset(); if ( isClipped() ) { - clipSmartEnable( mScreenPos.x + mPaddingPx.Left, mScreenPos.y + mPaddingPx.Top, - mSize.getWidth() - mPaddingPx.Left - mPaddingPx.Right, - mSize.getHeight() - mPaddingPx.Top - mPaddingPx.Bottom ); + clipSmartEnable( mScreenPos.x + contentOffset.Left, + mScreenPos.y + contentOffset.Top, + mSize.getWidth() - contentOffset.Left - contentOffset.Right, + mSize.getHeight() - contentOffset.Top - contentOffset.Bottom ); } - mRichText.draw( std::trunc( mScreenPos.x ) + (int)mPaddingPx.Left, - std::trunc( mScreenPos.y ) + (int)mPaddingPx.Top, Vector2f::One, 0.f, + mRichText.draw( std::trunc( mScreenPos.x ) + (int)contentOffset.Left, + std::trunc( mScreenPos.y ) + (int)contentOffset.Top, Vector2f::One, 0.f, getBlendMode() ); if ( isClipped() ) @@ -586,15 +588,16 @@ void UIRichText::onAlphaChange() { void UIRichText::rebuildRichText( UILayout* container, RichText& richText, IntrinsicMode mode ) { richText.clear(); - Float maxWidth = container->getPixelsSize().getWidth() - container->getPixelsPadding().Left - - container->getPixelsPadding().Right; + Float maxWidth = container->getPixelsSize().getWidth() - + container->getPixelsContentOffset().Left - + container->getPixelsContentOffset().Right; if ( maxWidth < 0 ) maxWidth = 0; Float mw = 0.f; if ( !container->getMaxWidthEq().empty() ) { - mw = container->getMaxSizePx().getWidth() - container->getPixelsPadding().Left - - container->getPixelsPadding().Right; + mw = container->getMaxSizePx().getWidth() - container->getPixelsContentOffset().Left - + container->getPixelsContentOffset().Right; if ( mw < 0 ) mw = 0.f; } @@ -636,8 +639,8 @@ void UIRichText::rebuildRichText( UILayout* container, RichText& richText, Intri if ( isBlock ) { if ( container->getPixelsSize().getWidth() != 0 ) { Float maxSize = eemax( 0.f, container->getPixelsSize().getWidth() - - container->getPixelsPadding().Left - - container->getPixelsPadding().Right - + container->getPixelsContentOffset().Left - + container->getPixelsContentOffset().Right - margin.Left - margin.Right ); widget->setPixelsSize( eemax( 0.f, maxSize ), widget->getPixelsSize().getHeight() ); @@ -663,8 +666,8 @@ void UIRichText::rebuildRichText( UILayout* container, RichText& richText, Intri if ( isBlock && mode == IntrinsicMode::None && container->getPixelsSize().getWidth() != 0 ) { w = eemax( 0.f, container->getPixelsSize().getWidth() - - container->getPixelsPadding().Left - - container->getPixelsPadding().Right - margin.Left - + container->getPixelsContentOffset().Left - + container->getPixelsContentOffset().Right - margin.Left - margin.Right ); } @@ -722,10 +725,12 @@ Float UIRichText::getMinIntrinsicWidth() const { RichText richText( mRichText ); UIRichText::rebuildRichText( const_cast( this ), richText, IntrinsicMode::Min ); - mMinIntrinsicWidth = richText.getMinIntrinsicWidth() + mPaddingPx.Left + mPaddingPx.Right; + mMinIntrinsicWidth = richText.getMinIntrinsicWidth() + getPixelsContentOffset().Left + + getPixelsContentOffset().Right; UIRichText::rebuildRichText( const_cast( this ), richText, IntrinsicMode::Max ); - mMaxIntrinsicWidth = richText.getMaxIntrinsicWidth() + mPaddingPx.Left + mPaddingPx.Right; + mMaxIntrinsicWidth = richText.getMaxIntrinsicWidth() + getPixelsContentOffset().Left + + getPixelsContentOffset().Right; mIntrinsicWidthsDirty = false; } @@ -749,11 +754,11 @@ Float UIRichText::getMaxIntrinsicWidth() const { if ( mIntrinsicWidthsDirty ) { RichText richText( mRichText ); const_cast( this )->rebuildRichText( richText, IntrinsicMode::Min ); - mMinIntrinsicWidth = - richText.getMinIntrinsicWidth() + mPaddingPx.Left + mPaddingPx.Right; + mMinIntrinsicWidth = richText.getMinIntrinsicWidth() + getPixelsContentOffset().Left + + getPixelsContentOffset().Right; const_cast( this )->rebuildRichText( richText, IntrinsicMode::Max ); - mMaxIntrinsicWidth = - richText.getMaxIntrinsicWidth() + mPaddingPx.Left + mPaddingPx.Right; + mMaxIntrinsicWidth = richText.getMaxIntrinsicWidth() + getPixelsContentOffset().Left + + getPixelsContentOffset().Right; mIntrinsicWidthsDirty = false; } maxW = mMaxIntrinsicWidth; diff --git a/src/eepp/ui/uiwidget.cpp b/src/eepp/ui/uiwidget.cpp index a8719bdc6..41960f721 100644 --- a/src/eepp/ui/uiwidget.cpp +++ b/src/eepp/ui/uiwidget.cpp @@ -593,12 +593,12 @@ void UIWidget::calculateAutoMargin() { UIWidget* parent = getParent()->asType(); Sizef parentSize = parent->getPixelsSize(); - Rectf parentPadding = parent->getPixelsPadding(); + Rectf parentContentOffset = parent->getPixelsContentOffset(); bool changed = false; if ( ( mMarginAuto & MarginAutoLeft ) && ( mMarginAuto & MarginAutoRight ) ) { - Float availableWidth = parentSize.getWidth() - parentPadding.Left - parentPadding.Right - - getPixelsSize().getWidth(); + Float availableWidth = parentSize.getWidth() - parentContentOffset.Left - + parentContentOffset.Right - getPixelsSize().getWidth(); Float newMarginLeft = availableWidth > 0 ? availableWidth / 2.f : 0.f; Float newMarginRight = availableWidth > 0 ? availableWidth / 2.f : 0.f; if ( mLayoutMarginPx.Left != newMarginLeft || mLayoutMarginPx.Right != newMarginRight ) { @@ -607,16 +607,18 @@ void UIWidget::calculateAutoMargin() { changed = true; } } else if ( mMarginAuto & MarginAutoLeft ) { - Float availableWidth = parentSize.getWidth() - parentPadding.Left - parentPadding.Right - - getPixelsSize().getWidth() - mLayoutMarginPx.Right; + Float availableWidth = parentSize.getWidth() - parentContentOffset.Left - + parentContentOffset.Right - getPixelsSize().getWidth() - + mLayoutMarginPx.Right; Float newMarginLeft = std::max( 0.f, availableWidth ); if ( mLayoutMarginPx.Left != newMarginLeft ) { mLayoutMarginPx.Left = newMarginLeft; changed = true; } } else if ( mMarginAuto & MarginAutoRight ) { - Float availableWidth = parentSize.getWidth() - parentPadding.Left - parentPadding.Right - - getPixelsSize().getWidth() - mLayoutMarginPx.Left; + Float availableWidth = parentSize.getWidth() - parentContentOffset.Left - + parentContentOffset.Right - getPixelsSize().getWidth() - + mLayoutMarginPx.Left; Float newMarginRight = std::max( 0.f, availableWidth ); if ( mLayoutMarginPx.Right != newMarginRight ) { mLayoutMarginPx.Right = newMarginRight; @@ -625,8 +627,8 @@ void UIWidget::calculateAutoMargin() { } if ( ( mMarginAuto & MarginAutoTop ) && ( mMarginAuto & MarginAutoBottom ) ) { - Float availableHeight = parentSize.getHeight() - parentPadding.Top - parentPadding.Bottom - - getPixelsSize().getHeight(); + Float availableHeight = parentSize.getHeight() - parentContentOffset.Top - + parentContentOffset.Bottom - getPixelsSize().getHeight(); Float newMarginTop = availableHeight > 0 ? availableHeight / 2.f : 0.f; Float newMarginBottom = availableHeight > 0 ? availableHeight / 2.f : 0.f; if ( mLayoutMarginPx.Top != newMarginTop || mLayoutMarginPx.Bottom != newMarginBottom ) { @@ -635,16 +637,18 @@ void UIWidget::calculateAutoMargin() { changed = true; } } else if ( mMarginAuto & MarginAutoTop ) { - Float availableHeight = parentSize.getHeight() - parentPadding.Top - parentPadding.Bottom - - getPixelsSize().getHeight() - mLayoutMarginPx.Bottom; + Float availableHeight = parentSize.getHeight() - parentContentOffset.Top - + parentContentOffset.Bottom - getPixelsSize().getHeight() - + mLayoutMarginPx.Bottom; Float newMarginTop = std::max( 0.f, availableHeight ); if ( mLayoutMarginPx.Top != newMarginTop ) { mLayoutMarginPx.Top = newMarginTop; changed = true; } } else if ( mMarginAuto & MarginAutoBottom ) { - Float availableHeight = parentSize.getHeight() - parentPadding.Top - parentPadding.Bottom - - getPixelsSize().getHeight() - mLayoutMarginPx.Top; + Float availableHeight = parentSize.getHeight() - parentContentOffset.Top - + parentContentOffset.Bottom - getPixelsSize().getHeight() - + mLayoutMarginPx.Top; Float newMarginBottom = std::max( 0.f, availableHeight ); if ( mLayoutMarginPx.Bottom != newMarginBottom ) { mLayoutMarginPx.Bottom = newMarginBottom; @@ -837,6 +841,18 @@ const Rectf& UIWidget::getPixelsPadding() const { return mPaddingPx; } +Rectf UIWidget::getPixelsContentOffset() const { + Rectf offset = getPixelsPadding(); + if ( hasBorder() ) { + const auto& b = getBorder()->getBorders(); + offset.Left += b.left.width; + offset.Right += b.right.width; + offset.Top += b.top.width; + offset.Bottom += b.bottom.width; + } + return offset; +} + UIWidget* UIWidget::setPadding( const Rectf& padding ) { if ( padding != mPadding ) { mPadding = padding; @@ -2578,7 +2594,7 @@ Float UIWidget::getMatchParentWidth() const { Rectf padding = Rectf::Zero; if ( getParent()->isWidget() ) - padding = static_cast( getParent() )->getPixelsPadding(); + padding = static_cast( getParent() )->getPixelsContentOffset(); Float marginLeft = ( mMarginAuto & MarginAutoLeft ) ? 0.f : mLayoutMarginPx.Left; Float marginRight = ( mMarginAuto & MarginAutoRight ) ? 0.f : mLayoutMarginPx.Right; @@ -2599,7 +2615,7 @@ Float UIWidget::getMatchParentHeight() const { Rectf padding = Rectf::Zero; if ( getParent()->isWidget() ) - padding = static_cast( getParent() )->getPixelsPadding(); + padding = static_cast( getParent() )->getPixelsContentOffset(); Float marginTop = ( mMarginAuto & MarginAutoTop ) ? 0.f : mLayoutMarginPx.Top; Float marginBottom = ( mMarginAuto & MarginAutoBottom ) ? 0.f : mLayoutMarginPx.Bottom;