Simulate content-box for HTML compat layer.

This commit is contained in:
Martín Lucas Golini
2026-04-29 00:28:11 -03:00
parent f765eae28d
commit e1d9642dc6
11 changed files with 393 additions and 69 deletions

View File

@@ -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<UIWidget>()->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/<folder>/<imageName>.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).

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.3 KiB

After

Width:  |  Height:  |  Size: 8.0 KiB

View File

@@ -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

View File

@@ -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.
*

View File

@@ -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 );

View File

@@ -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<UINode>()->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;

View File

@@ -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 );

View File

@@ -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;

View File

@@ -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<UIRichText*>( this ), richText,
IntrinsicMode::Min );
mMinIntrinsicWidth = richText.getMinIntrinsicWidth() + mPaddingPx.Left + mPaddingPx.Right;
mMinIntrinsicWidth = richText.getMinIntrinsicWidth() + getPixelsContentOffset().Left +
getPixelsContentOffset().Right;
UIRichText::rebuildRichText( const_cast<UIRichText*>( 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<UIRichText*>( this )->rebuildRichText( richText, IntrinsicMode::Min );
mMinIntrinsicWidth =
richText.getMinIntrinsicWidth() + mPaddingPx.Left + mPaddingPx.Right;
mMinIntrinsicWidth = richText.getMinIntrinsicWidth() + getPixelsContentOffset().Left +
getPixelsContentOffset().Right;
const_cast<UIRichText*>( this )->rebuildRichText( richText, IntrinsicMode::Max );
mMaxIntrinsicWidth =
richText.getMaxIntrinsicWidth() + mPaddingPx.Left + mPaddingPx.Right;
mMaxIntrinsicWidth = richText.getMaxIntrinsicWidth() + getPixelsContentOffset().Left +
getPixelsContentOffset().Right;
mIntrinsicWidthsDirty = false;
}
maxW = mMaxIntrinsicWidth;

View File

@@ -593,12 +593,12 @@ void UIWidget::calculateAutoMargin() {
UIWidget* parent = getParent()->asType<UIWidget>();
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<UIWidget*>( getParent() )->getPixelsPadding();
padding = static_cast<UIWidget*>( 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<UIWidget*>( getParent() )->getPixelsPadding();
padding = static_cast<UIWidget*>( getParent() )->getPixelsContentOffset();
Float marginTop = ( mMarginAuto & MarginAutoTop ) ? 0.f : mLayoutMarginPx.Top;
Float marginBottom = ( mMarginAuto & MarginAutoBottom ) ? 0.f : mLayoutMarginPx.Bottom;