mirror of
https://github.com/SpartanJ/eepp.git
synced 2026-10-02 11:10:31 +03:00
Fix nested float propagation in rich text layout
Propagate descendant float exclusions through non-BFC HTML containers so floats participate in their nearest block formatting context. Finalize line geometry before translating propagated exclusions, track finalized lines incrementally, and search successive float bands when a fixed-width inline-block does not fit beside active floats. Recognize inline-block elements as block formatting contexts while preserving their fixed width. Reuse persistent float-exclusion storage across relayouts to avoid repeated allocations. Add SS64 regressions covering narrow viewports, HiDPI rendering, stacked floats, anchor sizing, SVG sizing, and footer overlap. Fixed `cursor: pointer`, `pointer` was not registered.
This commit is contained in:
@@ -169,6 +169,8 @@ class EE_API RichText : public Drawable {
|
|||||||
return mExternalFloatExclusions;
|
return mExternalFloatExclusions;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const std::vector<FloatExclusion>& getLocalFloatExclusions();
|
||||||
|
|
||||||
/** @return The minimum intrinsic width of the text block. */
|
/** @return The minimum intrinsic width of the text block. */
|
||||||
Float getMinIntrinsicWidth();
|
Float getMinIntrinsicWidth();
|
||||||
|
|
||||||
@@ -188,7 +190,8 @@ class EE_API RichText : public Drawable {
|
|||||||
void addCustomSize( const Sizef& size, InlineFloat floatType = InlineFloat::None,
|
void addCustomSize( const Sizef& size, InlineFloat floatType = InlineFloat::None,
|
||||||
InlineClear clearType = InlineClear::None, Float baseline = -1.f,
|
InlineClear clearType = InlineClear::None, Float baseline = -1.f,
|
||||||
const BaselineAlignValue& baselineAlign = {}, InlineSource source = {},
|
const BaselineAlignValue& baselineAlign = {}, InlineSource source = {},
|
||||||
bool isBlock = false, bool isBlockFormattingContext = false );
|
bool isBlock = false, bool isBlockFormattingContext = false,
|
||||||
|
std::shared_ptr<const std::vector<FloatExclusion>> propagatedFloats = {} );
|
||||||
|
|
||||||
/** @brief Adds a virtual line break that is not associated with a DOM text character. */
|
/** @brief Adds a virtual line break that is not associated with a DOM text character. */
|
||||||
void addLineBreak();
|
void addLineBreak();
|
||||||
@@ -233,6 +236,7 @@ class EE_API RichText : public Drawable {
|
|||||||
bool isLineBreak{ false };
|
bool isLineBreak{ false };
|
||||||
bool isBlock{ false };
|
bool isBlock{ false };
|
||||||
bool isBlockFormattingContext{ false };
|
bool isBlockFormattingContext{ false };
|
||||||
|
std::shared_ptr<const std::vector<FloatExclusion>> propagatedFloats;
|
||||||
InlinePath inlinePath;
|
InlinePath inlinePath;
|
||||||
Vector2f position; // Local position relative to RichText origin
|
Vector2f position; // Local position relative to RichText origin
|
||||||
Sizef size;
|
Sizef size;
|
||||||
@@ -349,6 +353,7 @@ class EE_API RichText : public Drawable {
|
|||||||
bool isLineBreak{ false };
|
bool isLineBreak{ false };
|
||||||
bool isBlock{ false };
|
bool isBlock{ false };
|
||||||
bool isBlockFormattingContext{ false };
|
bool isBlockFormattingContext{ false };
|
||||||
|
std::shared_ptr<const std::vector<FloatExclusion>> propagatedFloats;
|
||||||
BaselineAlignValue baselineAlign;
|
BaselineAlignValue baselineAlign;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -454,6 +459,7 @@ class EE_API RichText : public Drawable {
|
|||||||
RenderSpan::InlinePath mInlinePath; // Path into the inline tree for the stack-based builder
|
RenderSpan::InlinePath mInlinePath; // Path into the inline tree for the stack-based builder
|
||||||
std::vector<InlineFragment> mInlineFragments;
|
std::vector<InlineFragment> mInlineFragments;
|
||||||
std::vector<FloatExclusion> mExternalFloatExclusions;
|
std::vector<FloatExclusion> mExternalFloatExclusions;
|
||||||
|
std::vector<FloatExclusion> mLocalFloatExclusions;
|
||||||
std::vector<RenderParagraph> mLines;
|
std::vector<RenderParagraph> mLines;
|
||||||
FontStyleConfig mDefaultStyle;
|
FontStyleConfig mDefaultStyle;
|
||||||
TextSelectionRange mSelection{ 0, 0 };
|
TextSelectionRange mSelection{ 0, 0 };
|
||||||
|
|||||||
+107
-18
@@ -737,7 +737,7 @@ class RichTextInlineLayouter {
|
|||||||
for ( const auto& run : buildLayoutRuns( inlineItems ) ) {
|
for ( const auto& run : buildLayoutRuns( inlineItems ) ) {
|
||||||
const auto& payload = run.payload;
|
const auto& payload = run.payload;
|
||||||
if ( payload.type != RichText::RenderSpan::Type::Text ) {
|
if ( payload.type != RichText::RenderSpan::Type::Text ) {
|
||||||
if ( payload.floatType != RichText::InlineFloat::None ||
|
if ( payload.floatType != RichText::InlineFloat::None || payload.propagatedFloats ||
|
||||||
payload.clearType != RichText::InlineClear::None )
|
payload.clearType != RichText::InlineClear::None )
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -1053,6 +1053,7 @@ class RichTextInlineLayouter {
|
|||||||
Float textIndent, Uint32 align, Float forcedLineHeight,
|
Float textIndent, Uint32 align, Float forcedLineHeight,
|
||||||
const FontStyleConfig& defaultStyle,
|
const FontStyleConfig& defaultStyle,
|
||||||
const std::vector<RichText::FloatExclusion>& externalFloatExclusions,
|
const std::vector<RichText::FloatExclusion>& externalFloatExclusions,
|
||||||
|
std::vector<RichText::FloatExclusion>& localFloatExclusions,
|
||||||
bool lineWrap, RichText::WhiteSpaceWrapMode whiteSpaceWrapMode ) {
|
bool lineWrap, RichText::WhiteSpaceWrapMode whiteSpaceWrapMode ) {
|
||||||
LayoutResult result;
|
LayoutResult result;
|
||||||
result.lines.push_back( RichText::RenderParagraph() );
|
result.lines.push_back( RichText::RenderParagraph() );
|
||||||
@@ -1134,6 +1135,27 @@ class RichTextInlineLayouter {
|
|||||||
return advanced;
|
return advanced;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
size_t finalizedLineCount = 0;
|
||||||
|
Float finalizedLinesBottom = 0.f;
|
||||||
|
// Propagated float groups need final line metrics before translating their exclusions.
|
||||||
|
// Keep a monotonic cursor so each preceding line is finalized at most once here.
|
||||||
|
auto finalizeLinesThrough = [&]( size_t end ) {
|
||||||
|
for ( ; finalizedLineCount < end; ++finalizedLineCount ) {
|
||||||
|
auto& line = result.lines[finalizedLineCount];
|
||||||
|
alignLineSpans( line, 0.f, defaultStyle, forcedLineHeight, true, inlineItems );
|
||||||
|
finalizedLinesBottom =
|
||||||
|
eemax( finalizedLinesBottom, line.y + line.height );
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
auto advanceInFlowLine = [&]() {
|
||||||
|
finalizeLinesThrough( result.lines.size() );
|
||||||
|
curY = eemax( curY, finalizedLinesBottom );
|
||||||
|
result.lines.push_back( RichText::RenderParagraph() );
|
||||||
|
result.lines.back().y = curY;
|
||||||
|
curX = 0;
|
||||||
|
};
|
||||||
|
|
||||||
for ( const auto& run : runs ) {
|
for ( const auto& run : runs ) {
|
||||||
const auto& payload = run.payload;
|
const auto& payload = run.payload;
|
||||||
if ( payload.type == RichText::RenderSpan::Type::Text ) {
|
if ( payload.type == RichText::RenderSpan::Type::Text ) {
|
||||||
@@ -1189,8 +1211,7 @@ class RichTextInlineLayouter {
|
|||||||
inlineEndSpacing( payload, inlineItems ) );
|
inlineEndSpacing( payload, inlineItems ) );
|
||||||
if ( lineWrap && effW > 0 && effW < 1e9f && curX > effW ) {
|
if ( lineWrap && effW > 0 && effW < 1e9f && curX > effW ) {
|
||||||
maxWidth = std::max( maxWidth, curX );
|
maxWidth = std::max( maxWidth, curX );
|
||||||
result.lines.push_back( RichText::RenderParagraph() );
|
advanceInFlowLine();
|
||||||
curX = 0;
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1209,8 +1230,7 @@ class RichTextInlineLayouter {
|
|||||||
}
|
}
|
||||||
if ( !trailingNewlineAlreadyAdvanced ) {
|
if ( !trailingNewlineAlreadyAdvanced ) {
|
||||||
maxWidth = std::max( maxWidth, curX );
|
maxWidth = std::max( maxWidth, curX );
|
||||||
result.lines.push_back( RichText::RenderParagraph() );
|
advanceInFlowLine();
|
||||||
curX = 0;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1313,7 +1333,8 @@ class RichTextInlineLayouter {
|
|||||||
Float re = floatRightEdge( curY );
|
Float re = floatRightEdge( curY );
|
||||||
Float availableWidth = re - le;
|
Float availableWidth = re - le;
|
||||||
if ( availableWidth > 0 && availableWidth < 1e9f ) {
|
if ( availableWidth > 0 && availableWidth < 1e9f ) {
|
||||||
if ( metrics.size.getWidth() > availableWidth + 0.01f )
|
if ( metrics.isBlock &&
|
||||||
|
metrics.size.getWidth() > availableWidth + 0.01f )
|
||||||
metrics.size.setWidth( availableWidth );
|
metrics.size.setWidth( availableWidth );
|
||||||
curX = le;
|
curX = le;
|
||||||
effW = availableWidth;
|
effW = availableWidth;
|
||||||
@@ -1322,16 +1343,24 @@ class RichTextInlineLayouter {
|
|||||||
|
|
||||||
if ( lineWrap && !metrics.isBlock && effW > 0 && effW < 1e9f &&
|
if ( lineWrap && !metrics.isBlock && effW > 0 && effW < 1e9f &&
|
||||||
metrics.size.getWidth() > effW + 0.01f ) {
|
metrics.size.getWidth() > effW + 0.01f ) {
|
||||||
Float maxBottom = activeFloatBottom( curY );
|
Float placedY = curY;
|
||||||
if ( maxBottom > curY ) {
|
Float placedWidth = effW;
|
||||||
|
while ( metrics.size.getWidth() > placedWidth + 0.01f ) {
|
||||||
|
Float nextY = activeFloatBottom( placedY );
|
||||||
|
if ( nextY <= placedY )
|
||||||
|
break;
|
||||||
|
placedY = nextY;
|
||||||
|
placedWidth = effectiveMaxWidthAt( placedY );
|
||||||
|
}
|
||||||
|
if ( placedY > curY ) {
|
||||||
maxWidth = std::max( maxWidth, curX );
|
maxWidth = std::max( maxWidth, curX );
|
||||||
if ( !result.lines.back().spans.empty() )
|
if ( !result.lines.back().spans.empty() )
|
||||||
result.lines.push_back( RichText::RenderParagraph() );
|
result.lines.push_back( RichText::RenderParagraph() );
|
||||||
curX = 0;
|
curX = 0;
|
||||||
curY = maxBottom;
|
curY = placedY;
|
||||||
result.lines.back().y = curY;
|
result.lines.back().y = curY;
|
||||||
le = floatLeftEdge( curY );
|
le = floatLeftEdge( curY );
|
||||||
effW = effectiveMaxWidthAt( curY );
|
effW = placedWidth;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1339,20 +1368,60 @@ class RichTextInlineLayouter {
|
|||||||
( curX + metrics.size.getWidth() >= effW || curX >= effW ) && curX > 0 &&
|
( curX + metrics.size.getWidth() >= effW || curX >= effW ) && curX > 0 &&
|
||||||
hadLineContentBeforeSpacing ) {
|
hadLineContentBeforeSpacing ) {
|
||||||
maxWidth = std::max( maxWidth, curX );
|
maxWidth = std::max( maxWidth, curX );
|
||||||
result.lines.push_back( RichText::RenderParagraph() );
|
advanceInFlowLine();
|
||||||
curX = 0;
|
|
||||||
if ( hadLineContentBeforeSpacing )
|
if ( hadLineContentBeforeSpacing )
|
||||||
addInlineSpacingToCurrentLine( result, curX, startSpacing );
|
addInlineSpacingToCurrentLine( result, curX, startSpacing );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ( payload.propagatedFloats ) {
|
||||||
|
finalizeLinesThrough( result.lines.size() - 1 );
|
||||||
|
if ( finalizedLinesBottom > curY ) {
|
||||||
|
curY = finalizedLinesBottom;
|
||||||
|
result.lines.back().y = curY;
|
||||||
|
}
|
||||||
|
Float placedY = curY;
|
||||||
|
bool moved;
|
||||||
|
do {
|
||||||
|
moved = false;
|
||||||
|
for ( const auto& propagated : *payload.propagatedFloats ) {
|
||||||
|
Rectf rect = propagated.rect;
|
||||||
|
rect.move( { curX, placedY } );
|
||||||
|
auto avoidActiveFloat = [&]( const Rectf& active ) {
|
||||||
|
if ( rect.intersect( active ) && active.Bottom > placedY ) {
|
||||||
|
placedY += active.Bottom - rect.Top;
|
||||||
|
moved = true;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
for ( const auto& active : leftFloats )
|
||||||
|
avoidActiveFloat( active );
|
||||||
|
for ( const auto& active : rightFloats )
|
||||||
|
avoidActiveFloat( active );
|
||||||
|
}
|
||||||
|
} while ( moved );
|
||||||
|
if ( placedY > curY ) {
|
||||||
|
curY = placedY;
|
||||||
|
result.lines.back().y = curY;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
appendAtomicRenderSpan( result.lines.back(), payload, metrics, curX,
|
appendAtomicRenderSpan( result.lines.back(), payload, metrics, curX,
|
||||||
curCharIdx );
|
curCharIdx );
|
||||||
|
if ( payload.propagatedFloats ) {
|
||||||
|
const Vector2f atomicPosition = result.lines.back().spans.back().position;
|
||||||
|
for ( const auto& propagated : *payload.propagatedFloats ) {
|
||||||
|
Rectf rect = propagated.rect;
|
||||||
|
rect.move( { atomicPosition.x, curY } );
|
||||||
|
if ( propagated.type == RichText::InlineFloat::Left )
|
||||||
|
leftFloats.push_back( rect );
|
||||||
|
else if ( propagated.type == RichText::InlineFloat::Right )
|
||||||
|
rightFloats.push_back( rect );
|
||||||
|
}
|
||||||
|
}
|
||||||
addInlineSpacingToCurrentLine( result, curX, endSpacing );
|
addInlineSpacingToCurrentLine( result, curX, endSpacing );
|
||||||
|
|
||||||
if ( lineWrap && effW > 0 && effW < 1e9f && curX >= effW ) {
|
if ( lineWrap && effW > 0 && effW < 1e9f && curX >= effW ) {
|
||||||
maxWidth = std::max( maxWidth, curX );
|
maxWidth = std::max( maxWidth, curX );
|
||||||
result.lines.push_back( RichText::RenderParagraph() );
|
advanceInFlowLine();
|
||||||
curX = 0;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1386,6 +1455,16 @@ class RichTextInlineLayouter {
|
|||||||
floatBoundsBottom = std::max( floatBoundsBottom, rightFloats[i].Bottom );
|
floatBoundsBottom = std::max( floatBoundsBottom, rightFloats[i].Bottom );
|
||||||
floatBoundsRight = std::max( floatBoundsRight, rightFloats[i].Right );
|
floatBoundsRight = std::max( floatBoundsRight, rightFloats[i].Right );
|
||||||
}
|
}
|
||||||
|
// This is persistent RichText storage: clear without discarding capacity so repeated
|
||||||
|
// viewport-driven layouts do not allocate a fresh exclusion vector every time.
|
||||||
|
localFloatExclusions.clear();
|
||||||
|
localFloatExclusions.reserve( leftFloats.size() + rightFloats.size() -
|
||||||
|
externalLeftFloatCount - externalRightFloatCount );
|
||||||
|
for ( size_t i = externalLeftFloatCount; i < leftFloats.size(); ++i )
|
||||||
|
localFloatExclusions.push_back( { leftFloats[i], RichText::InlineFloat::Left } );
|
||||||
|
for ( size_t i = externalRightFloatCount; i < rightFloats.size(); ++i )
|
||||||
|
localFloatExclusions.push_back(
|
||||||
|
{ rightFloats[i], RichText::InlineFloat::Right } );
|
||||||
|
|
||||||
result.size =
|
result.size =
|
||||||
Sizef( std::max( maxWidth, floatBoundsRight ), std::max( accumY, floatBoundsBottom ) );
|
Sizef( std::max( maxWidth, floatBoundsRight ), std::max( accumY, floatBoundsBottom ) );
|
||||||
@@ -1638,6 +1717,7 @@ class RichTextInlineLayouter {
|
|||||||
run.payload.isLineBreak = box.isLineBreak;
|
run.payload.isLineBreak = box.isLineBreak;
|
||||||
run.payload.isBlock = box.isBlock;
|
run.payload.isBlock = box.isBlock;
|
||||||
run.payload.isBlockFormattingContext = box.isBlockFormattingContext;
|
run.payload.isBlockFormattingContext = box.isBlockFormattingContext;
|
||||||
|
run.payload.propagatedFloats = box.propagatedFloats;
|
||||||
run.payload.baselineAlign = box.baselineAlign;
|
run.payload.baselineAlign = box.baselineAlign;
|
||||||
run.payload.inlinePath = path;
|
run.payload.inlinePath = path;
|
||||||
run.payload._leafIndex = nextLeafIndex++;
|
run.payload._leafIndex = nextLeafIndex++;
|
||||||
@@ -2116,9 +2196,11 @@ void RichText::addDrawable( std::shared_ptr<Drawable> drawable ) {
|
|||||||
invalidateLayout();
|
invalidateLayout();
|
||||||
}
|
}
|
||||||
|
|
||||||
void RichText::addCustomSize( const Sizef& size, InlineFloat floatType, InlineClear clearType,
|
void RichText::addCustomSize(
|
||||||
Float baseline, const BaselineAlignValue& baselineAlign,
|
const Sizef& size, InlineFloat floatType, InlineClear clearType, Float baseline,
|
||||||
InlineSource source, bool isBlock, bool isBlockFormattingContext ) {
|
const BaselineAlignValue& baselineAlign, InlineSource source, bool isBlock,
|
||||||
|
bool isBlockFormattingContext,
|
||||||
|
std::shared_ptr<const std::vector<FloatExclusion>> propagatedFloats ) {
|
||||||
Float usedBaseline = baseline >= 0.f ? baseline : size.getHeight();
|
Float usedBaseline = baseline >= 0.f ? baseline : size.getHeight();
|
||||||
|
|
||||||
InlineItem item;
|
InlineItem item;
|
||||||
@@ -2130,6 +2212,7 @@ void RichText::addCustomSize( const Sizef& size, InlineFloat floatType, InlineCl
|
|||||||
box.clearType = clearType;
|
box.clearType = clearType;
|
||||||
box.isBlock = isBlock;
|
box.isBlock = isBlock;
|
||||||
box.isBlockFormattingContext = isBlockFormattingContext;
|
box.isBlockFormattingContext = isBlockFormattingContext;
|
||||||
|
box.propagatedFloats = std::move( propagatedFloats );
|
||||||
box.baselineAlign = baselineAlign;
|
box.baselineAlign = baselineAlign;
|
||||||
item.data = std::move( box );
|
item.data = std::move( box );
|
||||||
resolveInlinePath( mInlineItems, mInlinePath )->push_back( std::move( item ) );
|
resolveInlinePath( mInlineItems, mInlinePath )->push_back( std::move( item ) );
|
||||||
@@ -2359,6 +2442,7 @@ void RichText::updateLayout() {
|
|||||||
mAlign, mLineHeight, mDefaultStyle,
|
mAlign, mLineHeight, mDefaultStyle,
|
||||||
mLineWrap, mWhiteSpaceWrapMode );
|
mLineWrap, mWhiteSpaceWrapMode );
|
||||||
mLines = std::move( result.lines );
|
mLines = std::move( result.lines );
|
||||||
|
mLocalFloatExclusions.clear();
|
||||||
mSize = result.size;
|
mSize = result.size;
|
||||||
mTotalCharacterCount = result.totalCharacterCount;
|
mTotalCharacterCount = result.totalCharacterCount;
|
||||||
rebuildInlineFragments();
|
rebuildInlineFragments();
|
||||||
@@ -2368,7 +2452,7 @@ void RichText::updateLayout() {
|
|||||||
|
|
||||||
auto result = RichTextInlineLayouter::layoutWithFloats(
|
auto result = RichTextInlineLayouter::layoutWithFloats(
|
||||||
mInlineItems, mMaxWidth, mTextIndent, mAlign, mLineHeight, mDefaultStyle,
|
mInlineItems, mMaxWidth, mTextIndent, mAlign, mLineHeight, mDefaultStyle,
|
||||||
mExternalFloatExclusions, mLineWrap, mWhiteSpaceWrapMode );
|
mExternalFloatExclusions, mLocalFloatExclusions, mLineWrap, mWhiteSpaceWrapMode );
|
||||||
mLines = std::move( result.lines );
|
mLines = std::move( result.lines );
|
||||||
mSize = result.size;
|
mSize = result.size;
|
||||||
mTotalCharacterCount = result.totalCharacterCount;
|
mTotalCharacterCount = result.totalCharacterCount;
|
||||||
@@ -2376,6 +2460,11 @@ void RichText::updateLayout() {
|
|||||||
mNeedsLayoutUpdate = false;
|
mNeedsLayoutUpdate = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const std::vector<RichText::FloatExclusion>& RichText::getLocalFloatExclusions() {
|
||||||
|
updateLayout();
|
||||||
|
return mLocalFloatExclusions;
|
||||||
|
}
|
||||||
|
|
||||||
Sizef RichText::getSize() {
|
Sizef RichText::getSize() {
|
||||||
updateLayout();
|
updateLayout();
|
||||||
return mSize;
|
return mSize;
|
||||||
|
|||||||
@@ -2116,7 +2116,7 @@ void UIRichText::rebuildRichText( UILayout* container, RichText& richText, Intri
|
|||||||
bool isFloating = floatType != CSSFloat::None;
|
bool isFloating = floatType != CSSFloat::None;
|
||||||
bool isNormalFlowBlock = isBlock && !isFloating;
|
bool isNormalFlowBlock = isBlock && !isFloating;
|
||||||
bool isBlockFormattingContext =
|
bool isBlockFormattingContext =
|
||||||
isNormalFlowBlock && widget->isType( UI_TYPE_HTML_WIDGET ) &&
|
!isFloating && widget->isType( UI_TYPE_HTML_WIDGET ) &&
|
||||||
widget->asType<UIHTMLWidget>()->establishesBlockFormattingContext();
|
widget->asType<UIHTMLWidget>()->establishesBlockFormattingContext();
|
||||||
bool shrinkToFitFloat =
|
bool shrinkToFitFloat =
|
||||||
isFloating && widget->getLayoutWidthPolicy() == SizePolicy::MatchParent;
|
isFloating && widget->getLayoutWidthPolicy() == SizePolicy::MatchParent;
|
||||||
@@ -2145,11 +2145,30 @@ void UIRichText::rebuildRichText( UILayout* container, RichText& richText, Intri
|
|||||||
|
|
||||||
Sizef customSize( w + margin.Left + margin.Right,
|
Sizef customSize( w + margin.Left + margin.Right,
|
||||||
size.getHeight() + margin.Top + margin.Bottom );
|
size.getHeight() + margin.Top + margin.Bottom );
|
||||||
richText.addCustomSize(
|
std::shared_ptr<std::vector<RichText::FloatExclusion>> propagatedFloats;
|
||||||
customSize, toRichTextFloat( floatType ), toRichTextClear( clearType ),
|
if ( widget->isType( UI_TYPE_HTML_WIDGET ) ) {
|
||||||
getAtomicInlineBoxBaseline( widget, size, margin ),
|
auto* htmlWidget = widget->asType<UIHTMLWidget>();
|
||||||
toRichTextBaselineAlign( getWidgetBaselineAlign( widget ) ),
|
if ( !htmlWidget->establishesBlockFormattingContext() &&
|
||||||
toRichTextWidgetSource( widget ), isNormalFlowBlock, isBlockFormattingContext );
|
htmlWidget->getRichTextPtr() ) {
|
||||||
|
const auto& localFloats =
|
||||||
|
htmlWidget->getRichTextPtr()->getLocalFloatExclusions();
|
||||||
|
if ( !localFloats.empty() ) {
|
||||||
|
propagatedFloats =
|
||||||
|
std::make_shared<std::vector<RichText::FloatExclusion>>(
|
||||||
|
localFloats );
|
||||||
|
const Rectf contentOffset = widget->getPixelsContentOffset();
|
||||||
|
for ( auto& exclusion : *propagatedFloats )
|
||||||
|
exclusion.rect.move( { margin.Left + contentOffset.Left,
|
||||||
|
margin.Top + contentOffset.Top } );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
richText.addCustomSize( customSize, toRichTextFloat( floatType ),
|
||||||
|
toRichTextClear( clearType ),
|
||||||
|
getAtomicInlineBoxBaseline( widget, size, margin ),
|
||||||
|
toRichTextBaselineAlign( getWidgetBaselineAlign( widget ) ),
|
||||||
|
toRichTextWidgetSource( widget ), isNormalFlowBlock,
|
||||||
|
isBlockFormattingContext, std::move( propagatedFloats ) );
|
||||||
|
|
||||||
if ( widget->isType( UI_TYPE_TEXTSPAN ) &&
|
if ( widget->isType( UI_TYPE_TEXTSPAN ) &&
|
||||||
widget->asType<UITextSpan>()->isInlineBlock() &&
|
widget->asType<UITextSpan>()->isInlineBlock() &&
|
||||||
|
|||||||
@@ -63,7 +63,7 @@ Cursor::Type Cursor::fromName( std::string name ) {
|
|||||||
String::toLowerInPlace( name );
|
String::toLowerInPlace( name );
|
||||||
if ( "arrow" == name )
|
if ( "arrow" == name )
|
||||||
return Arrow;
|
return Arrow;
|
||||||
if ( "hand" == name )
|
if ( "hand" == name || "pointer" == name )
|
||||||
return Hand;
|
return Hand;
|
||||||
if ( "ibream" == name )
|
if ( "ibream" == name )
|
||||||
return IBeam;
|
return IBeam;
|
||||||
|
|||||||
@@ -618,14 +618,21 @@ UTEST( UIHTMLFloat, floatedListItemsShrinkToFitBlockAnchors ) {
|
|||||||
UTEST( UIHTMLFloat, ss64BlockAnchorsAndSvgAtPixelDensity2 ) {
|
UTEST( UIHTMLFloat, ss64BlockAnchorsAndSvgAtPixelDensity2 ) {
|
||||||
init_float_test();
|
init_float_test();
|
||||||
PixelDensity::setPixelDensity( 2.f );
|
PixelDensity::setPixelDensity( 2.f );
|
||||||
|
Engine::instance()->getCurrentWindow()->setSize( 475, 900 );
|
||||||
UISceneNode* sceneNode = SceneManager::instance()->getUISceneNode();
|
UISceneNode* sceneNode = SceneManager::instance()->getUISceneNode();
|
||||||
std::string html;
|
std::string html;
|
||||||
ASSERT_TRUE( FileSystem::fileGet( "assets/html/ss64.html", html ) );
|
ASSERT_TRUE( FileSystem::fileGet( "assets/html/ss64.html", html ) );
|
||||||
sceneNode->loadLayoutFromString( HTMLFormatter::HTMLtoXML( html ) );
|
sceneNode->loadLayoutFromString( HTMLFormatter::HTMLtoXML( html ) );
|
||||||
sceneNode->updateDirtyLayouts();
|
sceneNode->updateDirtyLayouts();
|
||||||
|
SceneManager::instance()->update();
|
||||||
|
|
||||||
auto buttons = sceneNode->getRoot()->querySelectorAll( ".tbtn" );
|
auto buttons = sceneNode->getRoot()->querySelectorAll( ".tbtn" );
|
||||||
auto anchors = sceneNode->getRoot()->querySelectorAll( ".tbtn a" );
|
auto anchors = sceneNode->getRoot()->querySelectorAll( ".tbtn a" );
|
||||||
|
auto worldRect = []( UIWidget* widget ) {
|
||||||
|
Vector2f position = widget->getPixelsPosition();
|
||||||
|
widget->nodeToWorldTranslation( position );
|
||||||
|
return Rectf( position, widget->getPixelsSize() );
|
||||||
|
};
|
||||||
ASSERT_EQ( buttons.size(), (size_t)8 );
|
ASSERT_EQ( buttons.size(), (size_t)8 );
|
||||||
ASSERT_EQ( anchors.size(), buttons.size() );
|
ASSERT_EQ( anchors.size(), buttons.size() );
|
||||||
for ( size_t i = 0; i < buttons.size(); ++i ) {
|
for ( size_t i = 0; i < buttons.size(); ++i ) {
|
||||||
@@ -634,6 +641,8 @@ UTEST( UIHTMLFloat, ss64BlockAnchorsAndSvgAtPixelDensity2 ) {
|
|||||||
EXPECT_NEAR( anchor->getPixelsSize().getHeight(), 80.f, 1.f );
|
EXPECT_NEAR( anchor->getPixelsSize().getHeight(), 80.f, 1.f );
|
||||||
EXPECT_NEAR( anchor->getPixelsSize().getHeight() + 4.f,
|
EXPECT_NEAR( anchor->getPixelsSize().getHeight() + 4.f,
|
||||||
buttons[i]->getPixelsSize().getHeight(), 1.f );
|
buttons[i]->getPixelsSize().getHeight(), 1.f );
|
||||||
|
for ( size_t j = 0; j < i; ++j )
|
||||||
|
EXPECT_FALSE( worldRect( buttons[i] ).intersect( worldRect( buttons[j] ) ) );
|
||||||
}
|
}
|
||||||
auto* svg = sceneNode->getRoot()->querySelector( "#sherlock svg" )->asType<UISvg>();
|
auto* svg = sceneNode->getRoot()->querySelector( "#sherlock svg" )->asType<UISvg>();
|
||||||
auto* input = sceneNode->getRoot()->querySelector( "#qu" );
|
auto* input = sceneNode->getRoot()->querySelector( "#qu" );
|
||||||
@@ -656,10 +665,42 @@ UTEST( UIHTMLFloat, ss64BlockAnchorsAndSvgAtPixelDensity2 ) {
|
|||||||
1.f );
|
1.f );
|
||||||
EXPECT_NEAR( svg->getDrawable()->getPixelsSize().getHeight(), svg->getPixelsSize().getHeight(),
|
EXPECT_NEAR( svg->getDrawable()->getPixelsSize().getHeight(), svg->getPixelsSize().getHeight(),
|
||||||
1.f );
|
1.f );
|
||||||
|
auto* footer = sceneNode->getRoot()->querySelector( ".footer" )->asType<UIRichText>();
|
||||||
|
ASSERT_TRUE( footer != nullptr );
|
||||||
|
for ( auto* button : buttons ) {
|
||||||
|
EXPECT_FALSE( worldRect( footer ).intersect( worldRect( button ) ) );
|
||||||
|
}
|
||||||
Engine::destroySingleton();
|
Engine::destroySingleton();
|
||||||
PixelDensity::setPixelDensity( 1.f );
|
PixelDensity::setPixelDensity( 1.f );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
UTEST( UIHTMLFloat, ss64NarrowViewportFloatsDoNotOverlapAtPixelDensity1 ) {
|
||||||
|
init_float_test();
|
||||||
|
auto* window = Engine::instance()->getCurrentWindow();
|
||||||
|
ASSERT_TRUE( window != nullptr );
|
||||||
|
window->setSize( 400, 600 );
|
||||||
|
UISceneNode* sceneNode = SceneManager::instance()->getUISceneNode();
|
||||||
|
std::string html;
|
||||||
|
ASSERT_TRUE( FileSystem::fileGet( "assets/html/ss64.html", html ) );
|
||||||
|
sceneNode->loadLayoutFromString( HTMLFormatter::HTMLtoXML( html ) );
|
||||||
|
sceneNode->updateDirtyLayouts();
|
||||||
|
SceneManager::instance()->update();
|
||||||
|
|
||||||
|
auto buttons = sceneNode->getRoot()->querySelectorAll( ".tbtn" );
|
||||||
|
ASSERT_EQ( buttons.size(), (size_t)8 );
|
||||||
|
auto worldRect = []( UIWidget* widget ) {
|
||||||
|
Vector2f position = widget->getPixelsPosition();
|
||||||
|
widget->nodeToWorldTranslation( position );
|
||||||
|
return Rectf( position, widget->getPixelsSize() );
|
||||||
|
};
|
||||||
|
for ( size_t i = 0; i < buttons.size(); ++i ) {
|
||||||
|
for ( size_t j = 0; j < i; ++j )
|
||||||
|
EXPECT_FALSE( worldRect( buttons[i] ).intersect( worldRect( buttons[j] ) ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
Engine::destroySingleton();
|
||||||
|
}
|
||||||
|
|
||||||
UTEST( UIHTMLFloat, autoHorizontalMarginsCenterBlockInsideFloat ) {
|
UTEST( UIHTMLFloat, autoHorizontalMarginsCenterBlockInsideFloat ) {
|
||||||
init_float_test();
|
init_float_test();
|
||||||
UISceneNode* sceneNode = SceneManager::instance()->getUISceneNode();
|
UISceneNode* sceneNode = SceneManager::instance()->getUISceneNode();
|
||||||
|
|||||||
@@ -10,6 +10,7 @@
|
|||||||
#include <eepp/ui/uiscenenode.hpp>
|
#include <eepp/ui/uiscenenode.hpp>
|
||||||
#include <eepp/ui/uithememanager.hpp>
|
#include <eepp/ui/uithememanager.hpp>
|
||||||
#include <eepp/ui/uiwidget.hpp>
|
#include <eepp/ui/uiwidget.hpp>
|
||||||
|
#include <eepp/window/cursor.hpp>
|
||||||
#include <eepp/window/engine.hpp>
|
#include <eepp/window/engine.hpp>
|
||||||
#include <eepp/window/input.hpp>
|
#include <eepp/window/input.hpp>
|
||||||
|
|
||||||
@@ -19,6 +20,11 @@ using namespace EE::Window;
|
|||||||
using namespace EE::Scene;
|
using namespace EE::Scene;
|
||||||
using namespace EE::UI;
|
using namespace EE::UI;
|
||||||
|
|
||||||
|
UTEST( UISceneNode, CssPointerCursorUsesHandCursor ) {
|
||||||
|
EXPECT_EQ( Cursor::fromName( "pointer" ), Cursor::Hand );
|
||||||
|
EXPECT_EQ( Cursor::fromName( "POINTER" ), Cursor::Hand );
|
||||||
|
}
|
||||||
|
|
||||||
static UISceneNode* init_test_scene_node() {
|
static UISceneNode* init_test_scene_node() {
|
||||||
FileSystem::changeWorkingDirectory( Sys::getProcessPath() );
|
FileSystem::changeWorkingDirectory( Sys::getProcessPath() );
|
||||||
FontTrueType* font = FontTrueType::New( "NotoSans-Regular" ).get();
|
FontTrueType* font = FontTrueType::New( "NotoSans-Regular" ).get();
|
||||||
|
|||||||
Reference in New Issue
Block a user