mirror of
https://github.com/SpartanJ/eepp.git
synced 2026-05-28 17:16:29 +03:00
Simulate content-box for HTML compat layer.
This commit is contained in:
286
.agent/plans/border_box_model_plan.md
Normal file
286
.agent/plans/border_box_model_plan.md
Normal 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 |
@@ -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
|
||||
|
||||
@@ -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.
|
||||
*
|
||||
|
||||
@@ -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 );
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 );
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user