mirror of
https://github.com/SpartanJ/eepp.git
synced 2026-10-18 19:43:24 +03:00
Avoid redundant layout invalidation from inline fragment sizing
Track when the formatting owner assigns fragment bounds and suppress layout feedback from text-node and inline-span size changes. Preserve size events and invalidation from callback-driven text or style changes. Add regression coverage for content reuse and table sizing after size callbacks. Preserve class sizes and existing member offsets. Adjust browser toolbar padding and remove the URL bar's right margin.
This commit is contained in:
@@ -25,12 +25,16 @@ class EE_API UILayouter {
|
||||
|
||||
virtual bool isPacking() const { return mPacking; }
|
||||
|
||||
/** True while the inline formatting owner assigns its computed fragment boxes. */
|
||||
bool isPositioningInlineFragments() const { return mPositioningInlineFragments; }
|
||||
|
||||
protected:
|
||||
UIWidget* mContainer;
|
||||
bool mPacking{ false };
|
||||
// These flags occupy existing padding, preserving the size and member offsets of layouters.
|
||||
bool mInlineContentDirty{ true };
|
||||
bool mInlineContentReusable{ false };
|
||||
bool mPositioningInlineFragments{ false };
|
||||
size_t mResizedCount{ 0 };
|
||||
bool mIntrinsicWidthsDirty{ true };
|
||||
Float mMinIntrinsicWidth{ 0 };
|
||||
|
||||
@@ -34,6 +34,9 @@ class EE_API UIRichText : public UIHTMLWidget {
|
||||
static void rebuildRichText( UILayout* container, RichText& richText,
|
||||
IntrinsicMode mode = IntrinsicMode::None );
|
||||
|
||||
/** Whether the nearest inline formatting owner is assigning this child's fragment bounds. */
|
||||
static bool isAssigningInlineFragments( const UIWidget* child );
|
||||
|
||||
/** Resolves the wrapping constraint independently of constructing the inline content stream. */
|
||||
static Float getLayoutMaxWidth( UILayout* container, IntrinsicMode mode = IntrinsicMode::None );
|
||||
|
||||
|
||||
@@ -50,6 +50,8 @@ class EE_API UITextNode : public UIWidget {
|
||||
Uint32 mTextHintsOverrideMask{ 0 };
|
||||
|
||||
UITextNode();
|
||||
|
||||
virtual void onSizeChange();
|
||||
};
|
||||
|
||||
}} // namespace EE::UI
|
||||
|
||||
@@ -159,6 +159,8 @@ class EE_API UITextSpan : public UIRichText {
|
||||
|
||||
explicit UITextSpan( const std::string& tag = "span" );
|
||||
|
||||
virtual void onSizeChange();
|
||||
|
||||
virtual void onTextChanged();
|
||||
|
||||
virtual void onFontChanged();
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#include <eepp/core/containers.hpp>
|
||||
#include <eepp/graphics/richtext.hpp>
|
||||
#include <eepp/system/scopedop.hpp>
|
||||
#include <eepp/ui/blocklayouter.hpp>
|
||||
#include <eepp/ui/flexlayouter.hpp>
|
||||
#include <eepp/ui/uihtmltable.hpp>
|
||||
@@ -295,6 +296,8 @@ void BlockLayouter::positionRichTextChildren( Graphics::RichText* rt ) {
|
||||
}
|
||||
|
||||
void BlockLayouter::positionRichTextChildren( Graphics::RichText* rt, bool reuseFragments ) {
|
||||
BoolScopedOpOptional positioning( !mPositioningInlineFragments, mPositioningInlineFragments,
|
||||
true );
|
||||
const auto& lines = rt->getLines();
|
||||
const auto& fragments = rt->getInlineFragments();
|
||||
Node* child = mContainer->getFirstChild();
|
||||
|
||||
@@ -1728,6 +1728,19 @@ static Drawable* getInlineBorderDrawable( UIWidget* widget ) {
|
||||
: nullptr;
|
||||
}
|
||||
|
||||
bool UIRichText::isAssigningInlineFragments( const UIWidget* child ) {
|
||||
for ( Node* parent = child->getParent(); parent && parent->isType( UI_TYPE_HTML_WIDGET );
|
||||
parent = parent->getParent() ) {
|
||||
auto* owner = parent->asType<UIHTMLWidget>();
|
||||
if ( auto* layouter = owner->getLayouter();
|
||||
layouter && layouter->isPositioningInlineFragments() )
|
||||
return true;
|
||||
if ( !owner->isInline() )
|
||||
break;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
Float UIRichText::getLayoutMaxWidth( UILayout* container, IntrinsicMode mode ) {
|
||||
if ( mode != IntrinsicMode::None )
|
||||
return 0.f;
|
||||
|
||||
@@ -28,6 +28,12 @@ bool UITextNode::isType( const Uint32& type ) const {
|
||||
return UITextNode::getType() == type ? true : UIWidget::isType( type );
|
||||
}
|
||||
|
||||
void UITextNode::onSizeChange() {
|
||||
// Raw text-node bounds are also outputs of the nearest inline formatting owner. Changes
|
||||
// to the source text still emit TextFormatting invalidations through setText().
|
||||
UIWidget::onSizeChange( !UIRichText::isAssigningInlineFragments( this ) );
|
||||
}
|
||||
|
||||
void UITextNode::draw() {
|
||||
if ( mText.empty() )
|
||||
return;
|
||||
|
||||
@@ -72,6 +72,18 @@ bool UITextSpan::isInlineBlock() const {
|
||||
return mDisplay == CSSDisplay::InlineBlock && !isOutOfFlow();
|
||||
}
|
||||
|
||||
void UITextSpan::onSizeChange() {
|
||||
if ( isInline() && UIRichText::isAssigningInlineFragments( this ) ) {
|
||||
// CSS inline fragment bounds are results of the owning formatting pass, not new
|
||||
// intrinsic content inputs. Keep size events/drawing current without feeding those
|
||||
// bounds back into the stream. Callback-created text/style changes still emit their
|
||||
// own formatting invalidations while the owner is measuring.
|
||||
UIWidget::onSizeChange( false );
|
||||
} else {
|
||||
UIRichText::onSizeChange();
|
||||
}
|
||||
}
|
||||
|
||||
void UITextSpan::onDisplayChange() {
|
||||
bool nowInline = isInline();
|
||||
if ( nowInline && getFontBackgroundColor() == Color::Transparent ) {
|
||||
|
||||
@@ -176,7 +176,7 @@ EE_MAIN_FUNC int main( int argc, char** argv ) {
|
||||
}
|
||||
</style>
|
||||
<vbox layout_width="match_parent" layout_height="match_parent">
|
||||
<hbox layout_width="match_parent" layout_height="wrap_content" padding-bottom="1dp">
|
||||
<hbox layout_width="match_parent" layout_height="wrap_content" padding="1dp 4dp 1dp 4dp">
|
||||
<PushButton lw="26dp" id="backbtn" class="webview_ui" text="@string(back, Back)"
|
||||
icon="icon(arrow-left-s, 22dp)"
|
||||
text-as-fallback="true" />
|
||||
@@ -189,7 +189,7 @@ EE_MAIN_FUNC int main( int argc, char** argv ) {
|
||||
<PushButton lw="26dp" id="newtabbtn" class="webview_ui" text="New Tab"
|
||||
icon="icon(add, 18dp)" text-as-fallback="true" />
|
||||
<TextInput id="url_bar" layout_width="0" layout_weight="1"
|
||||
hint="@string(enter_address, Enter Address)" margin-right="1dp" />
|
||||
hint="@string(enter_address, Enter Address)" />
|
||||
</hbox>
|
||||
<vbox id="tabs_host" layout_width="match_parent" layout_height="0" layout_weight="1" />
|
||||
</vbox>
|
||||
|
||||
@@ -1993,6 +1993,81 @@ UTEST( UIHTMLTable, tableCellAnchorHoverRelayoutsRichText ) {
|
||||
destroyRichTextScene( sceneNode );
|
||||
}
|
||||
|
||||
UTEST( UIRichText, inlineFragmentResizeKeepsContentReusable ) {
|
||||
auto* sceneNode = createRichTextScene();
|
||||
ASSERT_TRUE( sceneNode != nullptr );
|
||||
auto* host = UIWidget::New();
|
||||
host->setParent( sceneNode->getRoot() );
|
||||
host->setLayoutSizePolicy( SizePolicy::Fixed, SizePolicy::Fixed );
|
||||
host->enableReportSizeChangeToChildren();
|
||||
host->setPixelsSize( 240, 200 );
|
||||
sceneNode->loadLayoutFromString( R"xml(
|
||||
<p id="text" layout_width="match_parent" layout_height="200px">
|
||||
<a id="link">Several inline words must wrap when the available width changes.</a>
|
||||
</p>
|
||||
)xml",
|
||||
host );
|
||||
sceneNode->flushDirtyStyleAndLayout();
|
||||
auto* text = sceneNode->find<UIRichText>( "text" );
|
||||
auto* link = sceneNode->find<UITextSpan>( "link" );
|
||||
ASSERT_TRUE( text && link );
|
||||
text->updateLayout();
|
||||
const auto widerLines = text->getRichText().getLines().size();
|
||||
host->setPixelsSize( 120, 200 );
|
||||
sceneNode->flushDirtyStyleAndLayout();
|
||||
EXPECT_NEAR( 120.f, text->getPixelsSize().getWidth(), 1.f );
|
||||
EXPECT_GT( text->getRichText().getLines().size(), widerLines );
|
||||
EXPECT_GT( link->getHitBoxes().size(), 1u );
|
||||
|
||||
// A new font changes both the content measurement and its assigned fragment bounds.
|
||||
// The following owner pass must consume that input without invalidating it again merely
|
||||
// because the newly measured bounds were committed. The container's size is already settled.
|
||||
link->setFontSize( 24 );
|
||||
text->updateLayout();
|
||||
UILayout::resetMetrics();
|
||||
text->updateLayout();
|
||||
auto metrics = UILayout::getMetrics();
|
||||
UILayout::setMetricsEnabled( false );
|
||||
EXPECT_EQ( 0u, metrics.richTextRebuilds );
|
||||
destroyRichTextScene( sceneNode );
|
||||
}
|
||||
|
||||
UTEST( UIHTMLTable, contentChangedByInlineFragmentSizeEventIsMeasured ) {
|
||||
auto* sceneNode = createRichTextScene();
|
||||
ASSERT_TRUE( sceneNode != nullptr );
|
||||
auto* host = UIWidget::New();
|
||||
host->setParent( sceneNode->getRoot() );
|
||||
host->setLayoutSizePolicy( SizePolicy::Fixed, SizePolicy::Fixed );
|
||||
host->enableReportSizeChangeToChildren();
|
||||
host->setPixelsSize( 240, 400 );
|
||||
sceneNode->loadLayoutFromString( R"xml(
|
||||
<table id="table" style="width: 100%; table-layout: fixed">
|
||||
<tr><td id="cell"><a id="link">Inline words that wrap in a narrower column.</a></td></tr>
|
||||
</table>
|
||||
)xml",
|
||||
host );
|
||||
sceneNode->flushDirtyStyleAndLayout();
|
||||
auto* cell = sceneNode->find<UIHTMLTableCell>( "cell" );
|
||||
auto* link = sceneNode->find<UITextSpan>( "link" );
|
||||
ASSERT_TRUE( cell && link );
|
||||
bool changedContent = false;
|
||||
link->on( Event::OnSizeChange, [link, &changedContent]( const Event* ) {
|
||||
if ( !changedContent ) {
|
||||
changedContent = true;
|
||||
link->setText( "Replacement content created by an inline size event must be measured "
|
||||
"and enclosed by its table row after the column becomes narrower." );
|
||||
}
|
||||
} );
|
||||
host->setPixelsSize( 120, 400 );
|
||||
sceneNode->flushDirtyStyleAndLayout();
|
||||
EXPECT_TRUE( changedContent );
|
||||
EXPECT_TRUE( richTextRenderedText( cell->getRichText() ).find( "Replacement" ) !=
|
||||
String::InvalidPos );
|
||||
EXPECT_GE( cell->getPixelsSize().getHeight(), cell->getRichTextPtr()->getSize().getHeight() );
|
||||
EXPECT_GT( link->getHitBoxes().size(), 1u );
|
||||
destroyRichTextScene( sceneNode );
|
||||
}
|
||||
|
||||
UTEST( UIRichText, unchangedInlineContentIsReused ) {
|
||||
auto* sceneNode = createRichTextScene();
|
||||
ASSERT_TRUE( sceneNode != nullptr );
|
||||
|
||||
Reference in New Issue
Block a user