mirror of
https://github.com/SpartanJ/eepp.git
synced 2026-05-28 17:16:29 +03:00
Add float and clear support.
Fixes in Base64 implementation. Fixes in remote image loading.
This commit is contained in:
@@ -143,4 +143,48 @@ CSSListStylePosition CSSListStylePositionHelper::fromString( std::string_view va
|
||||
return CSSListStylePosition::Outside;
|
||||
}
|
||||
|
||||
std::string CSSFloatHelper::toString( CSSFloat val ) {
|
||||
switch ( val ) {
|
||||
case CSSFloat::Left:
|
||||
return "left";
|
||||
case CSSFloat::Right:
|
||||
return "right";
|
||||
case CSSFloat::None:
|
||||
default:
|
||||
return "none";
|
||||
}
|
||||
}
|
||||
|
||||
CSSFloat CSSFloatHelper::fromString( std::string_view val ) {
|
||||
if ( val == "left" )
|
||||
return CSSFloat::Left;
|
||||
if ( val == "right" )
|
||||
return CSSFloat::Right;
|
||||
return CSSFloat::None;
|
||||
}
|
||||
|
||||
std::string CSSClearHelper::toString( CSSClear val ) {
|
||||
switch ( val ) {
|
||||
case CSSClear::Left:
|
||||
return "left";
|
||||
case CSSClear::Right:
|
||||
return "right";
|
||||
case CSSClear::Both:
|
||||
return "both";
|
||||
case CSSClear::None:
|
||||
default:
|
||||
return "none";
|
||||
}
|
||||
}
|
||||
|
||||
CSSClear CSSClearHelper::fromString( std::string_view val ) {
|
||||
if ( val == "left" )
|
||||
return CSSClear::Left;
|
||||
if ( val == "right" )
|
||||
return CSSClear::Right;
|
||||
if ( val == "both" )
|
||||
return CSSClear::Both;
|
||||
return CSSClear::None;
|
||||
}
|
||||
|
||||
}} // namespace EE::UI
|
||||
|
||||
@@ -78,16 +78,14 @@ static Drawable* parseDataURI( const std::string& name ) {
|
||||
format.svgScale( PixelDensity::getPixelDensity() );
|
||||
if ( decodingType == "base64" ) {
|
||||
int fileStart = formatAndEncSep + 1;
|
||||
int base64Size = name.size() - fileStart;
|
||||
int bufSize = Base64::decodeSafeOutLen( base64Size );
|
||||
if ( bufSize <= 0 )
|
||||
return nullptr;
|
||||
ScopedBuffer buffer( bufSize );
|
||||
int len = Base64::decode( base64Size, &name[fileStart], bufSize, buffer.get() );
|
||||
if ( len > 0 )
|
||||
std::string_view fileBase64 = std::string_view{ name }.substr( fileStart );
|
||||
std::string buffer;
|
||||
int len = Base64::decode( fileBase64, buffer );
|
||||
if ( len > 0 ) {
|
||||
tex = TextureFactory::instance()->loadFromMemory(
|
||||
buffer.get(), len, false, Texture::ClampMode::ClampToEdge, false, false,
|
||||
format );
|
||||
(const unsigned char*)buffer.c_str(), buffer.size(), false,
|
||||
Texture::ClampMode::ClampToEdge, false, false, format );
|
||||
}
|
||||
} else if ( decodingType == "urldecode" ) {
|
||||
int fileStart = formatAndEncSep + 1;
|
||||
std::string decoded( URI::decode( name.substr( fileStart ) ) );
|
||||
|
||||
@@ -306,8 +306,9 @@ void RichText::addDrawable( std::shared_ptr<Drawable> drawable ) {
|
||||
invalidateLayout();
|
||||
}
|
||||
|
||||
void RichText::addCustomSize( const Sizef& size, bool isBlock ) {
|
||||
mBlocks.push_back( CustomBlock{ size, isBlock } );
|
||||
void RichText::addCustomSize( const Sizef& size, bool isBlock, UI::CSSFloat floatType,
|
||||
UI::CSSClear clearType ) {
|
||||
mBlocks.push_back( CustomBlock{ size, isBlock, floatType, clearType } );
|
||||
invalidateLayout();
|
||||
}
|
||||
|
||||
@@ -447,6 +448,241 @@ void RichText::updateLayout() {
|
||||
if ( !mNeedsLayoutUpdate )
|
||||
return;
|
||||
|
||||
// Detect whether any block has float/clear — if not, use the original
|
||||
// non-float layout path which is simpler and faster.
|
||||
bool hasFloats = false;
|
||||
for ( auto& block : mBlocks ) {
|
||||
if ( auto pSize = std::get_if<CustomBlock>( &block ) ) {
|
||||
if ( pSize->floatType != UI::CSSFloat::None ||
|
||||
pSize->clearType != UI::CSSClear::None ) {
|
||||
hasFloats = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ─── Fast path: no floats or clears ─────────────────────────────
|
||||
if ( !hasFloats ) {
|
||||
mLines.clear();
|
||||
mLines.push_back( RenderParagraph() );
|
||||
|
||||
Float curX = 0;
|
||||
Float maxWidth = 0;
|
||||
Int64 curCharIdx = 0;
|
||||
|
||||
// Pass 1: flow blocks into lines, wrapping at mMaxWidth.
|
||||
for ( auto& block : mBlocks ) {
|
||||
if ( auto pText = std::get_if<SpanBlock>( &block ) ) {
|
||||
auto& span = pText->text;
|
||||
if ( !span )
|
||||
continue;
|
||||
|
||||
// Empty-string spans contribute only their margin/padding.
|
||||
if ( span->getString().empty() ) {
|
||||
Float l = pText->margin.Left + pText->padding.Left;
|
||||
Float r = pText->margin.Right + pText->padding.Right;
|
||||
if ( l <= 0 && r <= 0 )
|
||||
continue;
|
||||
curX += l + r;
|
||||
if ( !mLines.empty() )
|
||||
mLines.back().width += l + r;
|
||||
continue;
|
||||
}
|
||||
|
||||
auto& fontStyle = span->getFontStyleConfig();
|
||||
if ( !fontStyle.Font )
|
||||
continue;
|
||||
|
||||
Float extraLeft = pText->margin.Left + pText->padding.Left;
|
||||
curX += extraLeft;
|
||||
if ( !mLines.empty() )
|
||||
mLines.back().width += extraLeft;
|
||||
|
||||
Uint32 textHints = span->getTextHints();
|
||||
|
||||
// Compute where lines break within this text span.
|
||||
LineWrapInfoEx wrapInfo = LineWrap::computeLineBreaksEx(
|
||||
span->getString(), fontStyle, mMaxWidth > 0 ? mMaxWidth : 1e9f,
|
||||
mMaxWidth > 0 ? LineWrapMode::Word : LineWrapMode::NoWrap, false, 4, 0.f,
|
||||
textHints, false, curX );
|
||||
|
||||
if ( wrapInfo.wraps.empty() ||
|
||||
wrapInfo.wraps.back() != (Float)span->getString().size() )
|
||||
wrapInfo.wraps.push_back( span->getString().size() );
|
||||
|
||||
// Emit a RenderSpan for each segment, wrapping to new lines as needed.
|
||||
for ( size_t i = 0; i < wrapInfo.wraps.size() - 1; ++i ) {
|
||||
size_t startIdx = wrapInfo.wraps[i];
|
||||
size_t endIdx = wrapInfo.wraps[i + 1];
|
||||
bool isNewline =
|
||||
( endIdx - startIdx == 1 && span->getString()[startIdx] == '\n' );
|
||||
|
||||
if ( !isNewline ) {
|
||||
std::shared_ptr<Text> renderSpanText = std::make_shared<Text>();
|
||||
renderSpanText->setString(
|
||||
span->getString().substr( startIdx, endIdx - startIdx ) );
|
||||
renderSpanText->setStyleConfig( fontStyle );
|
||||
|
||||
Float ascent = fontStyle.Font->getAscent( fontStyle.CharacterSize );
|
||||
Float height = fontStyle.Font->getLineSpacing( fontStyle.CharacterSize );
|
||||
Float spanWidth = renderSpanText->getTextWidth();
|
||||
|
||||
RenderSpan renderSpan;
|
||||
renderSpan.block =
|
||||
SpanBlock{ renderSpanText, pText->margin, pText->padding };
|
||||
renderSpan.position = { curX, 0 };
|
||||
renderSpan.size = Sizef( spanWidth, height );
|
||||
renderSpan.startCharIndex = curCharIdx;
|
||||
renderSpan.endCharIndex = curCharIdx + ( endIdx - startIdx );
|
||||
curCharIdx = renderSpan.endCharIndex;
|
||||
|
||||
RenderParagraph& currentLine = mLines.back();
|
||||
currentLine.spans.push_back( renderSpan );
|
||||
|
||||
currentLine.maxAscent = std::max( currentLine.maxAscent, ascent );
|
||||
currentLine.height = std::max( currentLine.height, height );
|
||||
|
||||
curX += spanWidth;
|
||||
currentLine.width += spanWidth;
|
||||
}
|
||||
|
||||
// After the last segment, add trailing margin and check if the
|
||||
// margin itself forces a wrap.
|
||||
if ( i == wrapInfo.wraps.size() - 2 && !isNewline ) {
|
||||
Float extraRight = pText->margin.Right + pText->padding.Right;
|
||||
curX += extraRight;
|
||||
mLines.back().width += extraRight;
|
||||
if ( !isNewline && mMaxWidth > 0 && curX > mMaxWidth ) {
|
||||
maxWidth = std::max( maxWidth, curX );
|
||||
mLines.push_back( RenderParagraph() );
|
||||
curX = 0;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// Start a new line for hard breaks (newlines) or soft wraps.
|
||||
if ( i < wrapInfo.wraps.size() - 2 || isNewline ) {
|
||||
if ( isNewline ) {
|
||||
curCharIdx++;
|
||||
if ( i == wrapInfo.wraps.size() - 2 ) {
|
||||
Float extraRight = pText->margin.Right + pText->padding.Right;
|
||||
curX += extraRight;
|
||||
mLines.back().width += extraRight;
|
||||
}
|
||||
}
|
||||
maxWidth = std::max( maxWidth, curX );
|
||||
mLines.push_back( RenderParagraph() );
|
||||
curX = 0;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Drawable or CustomBlock (non-float).
|
||||
Sizef blockSize;
|
||||
bool isBlock = false;
|
||||
if ( auto pDrawable = std::get_if<std::shared_ptr<Drawable>>( &block ) ) {
|
||||
auto& drawable = *pDrawable;
|
||||
blockSize = drawable ? drawable->getPixelsSize() : Sizef();
|
||||
} else if ( auto pSize = std::get_if<CustomBlock>( &block ) ) {
|
||||
blockSize = pSize->size;
|
||||
isBlock = pSize->isBlock;
|
||||
}
|
||||
|
||||
// Block elements force a line break before themselves.
|
||||
if ( isBlock && curX > 0 ) {
|
||||
maxWidth = std::max( maxWidth, curX );
|
||||
mLines.push_back( RenderParagraph() );
|
||||
curX = 0;
|
||||
}
|
||||
|
||||
// Inline elements that don't fit wrap to the next line.
|
||||
if ( mMaxWidth > 0 && !isBlock &&
|
||||
( curX + blockSize.getWidth() >= mMaxWidth || curX >= mMaxWidth ) &&
|
||||
curX > 0 ) {
|
||||
maxWidth = std::max( maxWidth, curX );
|
||||
mLines.push_back( RenderParagraph() );
|
||||
curX = 0;
|
||||
}
|
||||
|
||||
RenderSpan renderSpan;
|
||||
renderSpan.block = block;
|
||||
renderSpan.position = { curX, 0 };
|
||||
renderSpan.size = blockSize;
|
||||
renderSpan.startCharIndex = curCharIdx;
|
||||
renderSpan.endCharIndex = curCharIdx + 1;
|
||||
curCharIdx = renderSpan.endCharIndex;
|
||||
|
||||
RenderParagraph& currentLine = mLines.back();
|
||||
currentLine.spans.push_back( renderSpan );
|
||||
|
||||
currentLine.maxAscent = std::max( currentLine.maxAscent, blockSize.getHeight() );
|
||||
currentLine.height = std::max( currentLine.height, blockSize.getHeight() );
|
||||
|
||||
curX += blockSize.getWidth();
|
||||
currentLine.width += blockSize.getWidth();
|
||||
|
||||
// Block elements also force a line break after themselves.
|
||||
if ( ( mMaxWidth > 0 && curX >= mMaxWidth ) || isBlock ) {
|
||||
maxWidth = std::max( maxWidth, curX );
|
||||
mLines.push_back( RenderParagraph() );
|
||||
curX = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
maxWidth = std::max( maxWidth, curX );
|
||||
|
||||
// Remove trailing empty line if present.
|
||||
if ( !mLines.empty() && mLines.back().spans.empty() && mLines.size() > 1 ) {
|
||||
mLines.pop_back();
|
||||
}
|
||||
|
||||
// Pass 2: assign Y positions to each line, apply text alignment,
|
||||
// and compute vertical offsets for spans within their line.
|
||||
Float curY = 0;
|
||||
for ( auto& line : mLines ) {
|
||||
line.y = curY;
|
||||
|
||||
// Compute horizontal alignment offset for this line.
|
||||
Float xOffset = 0;
|
||||
if ( mMaxWidth > 0 && mAlign != 0 ) {
|
||||
Uint32 hAlign = Font::getHorizontalAlign( mAlign );
|
||||
if ( hAlign == TEXT_ALIGN_CENTER ) {
|
||||
xOffset = ( mMaxWidth - line.width ) * 0.5f;
|
||||
} else if ( hAlign == TEXT_ALIGN_RIGHT ) {
|
||||
xOffset = mMaxWidth - line.width;
|
||||
}
|
||||
}
|
||||
|
||||
Float maxLineHeight = 0;
|
||||
for ( auto& span : line.spans ) {
|
||||
if ( auto pText = std::get_if<SpanBlock>( &span.block ) ) {
|
||||
auto& textBlock = pText->text;
|
||||
Float offsetY = line.maxAscent - textBlock->getCharacterSize();
|
||||
span.position.x += xOffset;
|
||||
span.position.y = offsetY;
|
||||
maxLineHeight = std::max( maxLineHeight, offsetY + span.size.getHeight() );
|
||||
} else {
|
||||
Float offsetY = line.maxAscent - span.size.getHeight();
|
||||
if ( offsetY < 0 )
|
||||
offsetY = 0;
|
||||
span.position.x += xOffset;
|
||||
span.position.y = offsetY;
|
||||
maxLineHeight = std::max( maxLineHeight, offsetY + span.size.getHeight() );
|
||||
}
|
||||
}
|
||||
|
||||
line.height = std::max( line.height, maxLineHeight );
|
||||
curY += line.height;
|
||||
}
|
||||
|
||||
mSize = Sizef( maxWidth, curY );
|
||||
mTotalCharacterCount = curCharIdx;
|
||||
mNeedsLayoutUpdate = false;
|
||||
return;
|
||||
}
|
||||
|
||||
// ─── Float-aware path ────────────────────────────────────────────
|
||||
|
||||
mLines.clear();
|
||||
mLines.push_back( RenderParagraph() );
|
||||
|
||||
@@ -454,8 +690,64 @@ void RichText::updateLayout() {
|
||||
Float maxWidth = 0;
|
||||
Int64 curCharIdx = 0;
|
||||
|
||||
// Active float rectangles: { left, top, right, bottom } in local coords.
|
||||
std::vector<Rectf> leftFloats;
|
||||
std::vector<Rectf> rightFloats;
|
||||
Float curY = 0;
|
||||
|
||||
// ── Helper lambdas ─────────────────────────────────────────────
|
||||
// Returns the rightmost x-coordinate occupied by left floats at the given y.
|
||||
auto floatLeftEdge = [&]( Float y ) -> Float {
|
||||
Float l = 0;
|
||||
for ( auto& f : leftFloats ) {
|
||||
if ( y >= f.Top && y < f.Bottom )
|
||||
l = std::max( l, f.Right );
|
||||
}
|
||||
return l;
|
||||
};
|
||||
|
||||
// Returns the leftmost x-coordinate occupied by right floats at the given y.
|
||||
auto floatRightEdge = [&]( Float y ) -> Float {
|
||||
Float r = mMaxWidth > 0 ? mMaxWidth : 1e9f;
|
||||
for ( auto& f : rightFloats ) {
|
||||
if ( y >= f.Top && y < f.Bottom )
|
||||
r = std::min( r, f.Left );
|
||||
}
|
||||
return r;
|
||||
};
|
||||
|
||||
// Available horizontal space at y, narrowed by active floats on both sides.
|
||||
auto effectiveMaxWidthAt = [&]( Float y ) -> Float {
|
||||
return floatRightEdge( y ) - floatLeftEdge( y );
|
||||
};
|
||||
|
||||
// Advances curY past the bottom of active floats specified by clearType.
|
||||
// Returns true if curY was moved.
|
||||
auto clearFloats = [&]( UI::CSSClear clearType ) -> bool {
|
||||
bool advanced = false;
|
||||
if ( clearType == UI::CSSClear::Left || clearType == UI::CSSClear::Both ) {
|
||||
for ( auto& f : leftFloats ) {
|
||||
if ( f.Bottom > curY ) {
|
||||
curY = f.Bottom;
|
||||
advanced = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
if ( clearType == UI::CSSClear::Right || clearType == UI::CSSClear::Both ) {
|
||||
for ( auto& f : rightFloats ) {
|
||||
if ( f.Bottom > curY ) {
|
||||
curY = f.Bottom;
|
||||
advanced = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return advanced;
|
||||
};
|
||||
|
||||
// ── Pass 1: flow blocks with float awareness ────────────────────
|
||||
for ( auto& block : mBlocks ) {
|
||||
if ( auto pText = std::get_if<SpanBlock>( &block ) ) {
|
||||
// ── Text span ─────────────────────────────────────────
|
||||
auto& span = pText->text;
|
||||
if ( !span )
|
||||
continue;
|
||||
@@ -480,14 +772,23 @@ void RichText::updateLayout() {
|
||||
if ( !mLines.empty() )
|
||||
mLines.back().width += extraLeft;
|
||||
|
||||
// Shift curX inside to the left edge — text starts
|
||||
// to the right of any left floats.
|
||||
Float le = floatLeftEdge( curY );
|
||||
if ( curX < le )
|
||||
curX = le;
|
||||
|
||||
// Narrow the available width by active floats at this Y.
|
||||
Uint32 textHints = span->getTextHints();
|
||||
Float effW = effectiveMaxWidthAt( curY );
|
||||
if ( mMaxWidth > 0 && mMaxWidth < effW )
|
||||
effW = mMaxWidth;
|
||||
|
||||
LineWrapInfoEx wrapInfo = LineWrap::computeLineBreaksEx(
|
||||
span->getString(), fontStyle, mMaxWidth > 0 ? mMaxWidth : 1e9f,
|
||||
mMaxWidth > 0 ? LineWrapMode::Word : LineWrapMode::NoWrap, false, 4, 0.f, textHints,
|
||||
false, curX );
|
||||
LineWrapInfoEx wrapInfo =
|
||||
LineWrap::computeLineBreaksEx( span->getString(), fontStyle, effW > 0 ? effW : 1e9f,
|
||||
effW > 0 ? LineWrapMode::Word : LineWrapMode::NoWrap,
|
||||
false, 4, 0.f, textHints, false, curX );
|
||||
|
||||
// Make sure we have the end of the string as a "wrap" point for the loop
|
||||
if ( wrapInfo.wraps.empty() ||
|
||||
wrapInfo.wraps.back() != (Float)span->getString().size() )
|
||||
wrapInfo.wraps.push_back( span->getString().size() );
|
||||
@@ -509,9 +810,8 @@ void RichText::updateLayout() {
|
||||
|
||||
RenderSpan renderSpan;
|
||||
renderSpan.block = SpanBlock{ renderSpanText, pText->margin, pText->padding };
|
||||
renderSpan.position = { curX, 0 }; // Y adjusted later
|
||||
renderSpan.size =
|
||||
Sizef( spanWidth, height ); // Configured BEFORE pushing to vector
|
||||
renderSpan.position = { curX, 0 };
|
||||
renderSpan.size = Sizef( spanWidth, height );
|
||||
renderSpan.startCharIndex = curCharIdx;
|
||||
renderSpan.endCharIndex = curCharIdx + ( endIdx - startIdx );
|
||||
curCharIdx = renderSpan.endCharIndex;
|
||||
@@ -526,22 +826,20 @@ void RichText::updateLayout() {
|
||||
currentLine.width += spanWidth;
|
||||
}
|
||||
|
||||
// Trailing margin may force a wrap.
|
||||
if ( i == wrapInfo.wraps.size() - 2 && !isNewline ) {
|
||||
Float extraRight = pText->margin.Right + pText->padding.Right;
|
||||
curX += extraRight;
|
||||
mLines.back().width += extraRight;
|
||||
if ( !isNewline && mMaxWidth > 0 && curX > mMaxWidth ) {
|
||||
// the margin forced a wrap
|
||||
if ( effW > 0 && effW < 1e9f && curX > effW ) {
|
||||
maxWidth = std::max( maxWidth, curX );
|
||||
mLines.push_back( RenderParagraph() );
|
||||
curX = 0;
|
||||
continue; // skip the next newline check
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// If it's a newline, or if it's not the very last segment (which means it wrapped),
|
||||
// start a new line. Exception: If the last segment was just a newline, we already
|
||||
// handled it.
|
||||
// Newline or soft-wrap → start a new line.
|
||||
if ( i < wrapInfo.wraps.size() - 2 || isNewline ) {
|
||||
if ( isNewline ) {
|
||||
curCharIdx++;
|
||||
@@ -556,52 +854,113 @@ void RichText::updateLayout() {
|
||||
curX = 0;
|
||||
}
|
||||
}
|
||||
} else { // Drawable or CustomSize
|
||||
} else {
|
||||
// ── Drawable or CustomBlock ────────────────────────────
|
||||
Sizef blockSize;
|
||||
bool isBlock = false;
|
||||
UI::CSSFloat floatType = UI::CSSFloat::None;
|
||||
UI::CSSClear clearType = UI::CSSClear::None;
|
||||
if ( auto pDrawable = std::get_if<std::shared_ptr<Drawable>>( &block ) ) {
|
||||
auto& drawable = *pDrawable;
|
||||
blockSize = drawable ? drawable->getPixelsSize() : Sizef();
|
||||
} else if ( auto pSize = std::get_if<CustomBlock>( &block ) ) {
|
||||
blockSize = pSize->size;
|
||||
isBlock = pSize->isBlock;
|
||||
floatType = pSize->floatType;
|
||||
clearType = pSize->clearType;
|
||||
}
|
||||
|
||||
if ( isBlock && curX > 0 ) {
|
||||
maxWidth = std::max( maxWidth, curX );
|
||||
mLines.push_back( RenderParagraph() );
|
||||
curX = 0;
|
||||
// ── Clear: advance curY past active floats ─────────────
|
||||
if ( clearType != UI::CSSClear::None ) {
|
||||
if ( clearFloats( clearType ) ) {
|
||||
maxWidth = std::max( maxWidth, curX );
|
||||
mLines.push_back( RenderParagraph() );
|
||||
curX = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// Wrap if needed
|
||||
if ( mMaxWidth > 0 && !isBlock &&
|
||||
( curX + blockSize.getWidth() >= mMaxWidth || curX >= mMaxWidth ) && curX > 0 ) {
|
||||
maxWidth = std::max( maxWidth, curX );
|
||||
mLines.push_back( RenderParagraph() );
|
||||
curX = 0;
|
||||
}
|
||||
// Left edge of open space at current Y (after any clears).
|
||||
Float le = floatLeftEdge( curY );
|
||||
|
||||
RenderSpan renderSpan;
|
||||
renderSpan.block = block;
|
||||
renderSpan.position = { curX, 0 };
|
||||
renderSpan.size = blockSize;
|
||||
renderSpan.startCharIndex = curCharIdx;
|
||||
renderSpan.endCharIndex = curCharIdx + 1;
|
||||
curCharIdx = renderSpan.endCharIndex;
|
||||
if ( floatType != UI::CSSFloat::None ) {
|
||||
// ── Float placement ────────────────────────────────
|
||||
// Position the float at the left/right edge of the
|
||||
// available space. Floats do NOT consume inline-flow
|
||||
// horizontal space (curX is not advanced) and are not
|
||||
// affected by text-align (see pass 2).
|
||||
Float posX;
|
||||
if ( floatType == UI::CSSFloat::Left ) {
|
||||
posX = le;
|
||||
} else {
|
||||
Float re = floatRightEdge( curY );
|
||||
posX = re - blockSize.getWidth();
|
||||
if ( posX < le )
|
||||
posX = le;
|
||||
}
|
||||
|
||||
RenderParagraph& currentLine = mLines.back();
|
||||
currentLine.spans.push_back( renderSpan );
|
||||
RenderSpan renderSpan;
|
||||
renderSpan.block = block;
|
||||
renderSpan.position = { posX, 0 };
|
||||
renderSpan.size = blockSize;
|
||||
renderSpan.startCharIndex = curCharIdx;
|
||||
renderSpan.endCharIndex = curCharIdx + 1;
|
||||
curCharIdx = renderSpan.endCharIndex;
|
||||
|
||||
currentLine.maxAscent = std::max( currentLine.maxAscent, blockSize.getHeight() );
|
||||
currentLine.height = std::max( currentLine.height, blockSize.getHeight() );
|
||||
mLines.back().spans.push_back( renderSpan );
|
||||
|
||||
curX += blockSize.getWidth();
|
||||
currentLine.width += blockSize.getWidth();
|
||||
// Record the float's bounding box so subsequent
|
||||
// content can wrap around it.
|
||||
Rectf fr( posX, curY, posX + blockSize.getWidth(),
|
||||
curY + blockSize.getHeight() );
|
||||
if ( floatType == UI::CSSFloat::Left )
|
||||
leftFloats.push_back( fr );
|
||||
else
|
||||
rightFloats.push_back( fr );
|
||||
} else {
|
||||
// ── Normal (non-float) block ────────────────────
|
||||
if ( curX < le )
|
||||
curX = le;
|
||||
|
||||
if ( ( mMaxWidth > 0 && curX >= mMaxWidth ) || isBlock ) {
|
||||
maxWidth = std::max( maxWidth, curX );
|
||||
mLines.push_back( RenderParagraph() );
|
||||
curX = 0;
|
||||
// Block elements force a line break before.
|
||||
if ( isBlock && curX > 0 ) {
|
||||
maxWidth = std::max( maxWidth, curX );
|
||||
mLines.push_back( RenderParagraph() );
|
||||
curX = 0;
|
||||
}
|
||||
|
||||
// Wrap if the block doesn't fit in the available width
|
||||
// (narrowed by active floats).
|
||||
Float effW = effectiveMaxWidthAt( curY );
|
||||
if ( effW > 0 && effW < 1e9f && !isBlock &&
|
||||
( curX + blockSize.getWidth() >= effW || curX >= effW ) && curX > 0 ) {
|
||||
maxWidth = std::max( maxWidth, curX );
|
||||
mLines.push_back( RenderParagraph() );
|
||||
curX = 0;
|
||||
}
|
||||
|
||||
RenderSpan renderSpan;
|
||||
renderSpan.block = block;
|
||||
renderSpan.position = { curX, 0 };
|
||||
renderSpan.size = blockSize;
|
||||
renderSpan.startCharIndex = curCharIdx;
|
||||
renderSpan.endCharIndex = curCharIdx + 1;
|
||||
curCharIdx = renderSpan.endCharIndex;
|
||||
|
||||
RenderParagraph& currentLine = mLines.back();
|
||||
currentLine.spans.push_back( renderSpan );
|
||||
|
||||
currentLine.maxAscent = std::max( currentLine.maxAscent, blockSize.getHeight() );
|
||||
currentLine.height = std::max( currentLine.height, blockSize.getHeight() );
|
||||
|
||||
curX += blockSize.getWidth();
|
||||
currentLine.width += blockSize.getWidth();
|
||||
|
||||
// Block elements or overflow force a line break after.
|
||||
if ( ( effW > 0 && effW < 1e9f && curX >= effW ) || isBlock ) {
|
||||
maxWidth = std::max( maxWidth, curX );
|
||||
mLines.push_back( RenderParagraph() );
|
||||
curX = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -612,9 +971,12 @@ void RichText::updateLayout() {
|
||||
mLines.pop_back();
|
||||
}
|
||||
|
||||
Float curY = 0;
|
||||
// ── Pass 2: assign Y positions and apply text alignment ───────
|
||||
// NOTE: float spans are excluded from the xOffset because
|
||||
// text-align only affects inline-flow content, not floated elements.
|
||||
Float accumY = 0;
|
||||
for ( auto& line : mLines ) {
|
||||
line.y = curY;
|
||||
line.y = accumY;
|
||||
|
||||
Float xOffset = 0;
|
||||
if ( mMaxWidth > 0 && mAlign != 0 ) {
|
||||
@@ -628,6 +990,11 @@ void RichText::updateLayout() {
|
||||
|
||||
Float maxLineHeight = 0;
|
||||
for ( auto& span : line.spans ) {
|
||||
bool isFloat = false;
|
||||
if ( auto pSize = std::get_if<CustomBlock>( &span.block ) ) {
|
||||
if ( pSize->floatType != UI::CSSFloat::None )
|
||||
isFloat = true;
|
||||
}
|
||||
if ( auto pText = std::get_if<SpanBlock>( &span.block ) ) {
|
||||
auto& textBlock = pText->text;
|
||||
Float offsetY = line.maxAscent - textBlock->getCharacterSize();
|
||||
@@ -638,17 +1005,19 @@ void RichText::updateLayout() {
|
||||
Float offsetY = line.maxAscent - span.size.getHeight();
|
||||
if ( offsetY < 0 )
|
||||
offsetY = 0;
|
||||
span.position.x += xOffset;
|
||||
// Float spans keep their edge-aligned x; only inline-flow spans shift.
|
||||
if ( !isFloat )
|
||||
span.position.x += xOffset;
|
||||
span.position.y = offsetY;
|
||||
maxLineHeight = std::max( maxLineHeight, offsetY + span.size.getHeight() );
|
||||
}
|
||||
}
|
||||
|
||||
line.height = std::max( line.height, maxLineHeight );
|
||||
curY += line.height;
|
||||
accumY += line.height;
|
||||
}
|
||||
|
||||
mSize = Sizef( maxWidth, curY );
|
||||
mSize = Sizef( maxWidth, accumY );
|
||||
mTotalCharacterCount = curCharIdx;
|
||||
mNeedsLayoutUpdate = false;
|
||||
}
|
||||
|
||||
@@ -2309,7 +2309,7 @@ Uint32 Text::getNumLines() {
|
||||
return mString.countChar( '\n' ) + 1;
|
||||
}
|
||||
|
||||
const std::vector<Float>& Text::getLinesWidth() {
|
||||
const SmallVector<Float, 4>& Text::getLinesWidth() {
|
||||
cacheWidth();
|
||||
|
||||
return mLinesWidth;
|
||||
|
||||
@@ -538,8 +538,8 @@ TextLayout::Cache TextLayout::layout( const String& string, Font* font, const Ui
|
||||
keepIndentation, initialXOffset );
|
||||
}
|
||||
|
||||
std::vector<Float> TextLayout::getLinesWidth() const {
|
||||
std::vector<Float> lw;
|
||||
SmallVector<Float, 4> TextLayout::getLinesWidth() const {
|
||||
SmallVector<Float, 4> lw;
|
||||
std::size_t total = 0;
|
||||
for ( const auto& sp : paragraphs )
|
||||
total += sp.wrapInfo.wrapsWidth.size();
|
||||
@@ -570,7 +570,7 @@ void TextLayout::wrapLayout( const String::View& string, TextLayout& result,
|
||||
Sizef maxSize{ 0, vspace + yShift };
|
||||
std::size_t startWrapsCount = sp.wrapInfo.wraps.size();
|
||||
|
||||
std::vector<Float> wrapsWidth = std::move( sp.wrapInfo.wrapsWidth );
|
||||
auto wrapsWidth = std::move( sp.wrapInfo.wrapsWidth );
|
||||
sp.wrapInfo.wrapsWidth.clear();
|
||||
|
||||
if ( keepIndentation && shapedGlyphCount ) {
|
||||
|
||||
@@ -7,7 +7,7 @@ namespace EE { namespace System {
|
||||
/* $Id: base64.c 156 2007-07-12 23:29:10Z orange $ */
|
||||
|
||||
/* decode a base64 string in one shot */
|
||||
int Base64::decode( size_t in_len, const char* in, size_t out_len, unsigned char* out ) {
|
||||
size_t Base64::decode( size_t in_len, const char* in, size_t out_len, unsigned char* out ) {
|
||||
static const Uint8 base64dec_tab[256] = {
|
||||
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
|
||||
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
|
||||
@@ -26,17 +26,18 @@ int Base64::decode( size_t in_len, const char* in, size_t out_len, unsigned char
|
||||
255, 255, 255, 255,
|
||||
};
|
||||
|
||||
unsigned ii, io;
|
||||
size_t ii, io;
|
||||
Uint32 v;
|
||||
unsigned rem;
|
||||
|
||||
for ( io = 0, ii = 0, v = 0, rem = 0; ii < in_len; ii++ ) {
|
||||
unsigned char ch;
|
||||
if ( isspace( in[ii] ) )
|
||||
unsigned char c = (unsigned char)in[ii];
|
||||
if ( isspace( c ) )
|
||||
continue;
|
||||
if ( in[ii] == '=' )
|
||||
if ( c == '=' )
|
||||
break; /* stop at = */
|
||||
ch = base64dec_tab[(unsigned)in[ii]];
|
||||
ch = base64dec_tab[c];
|
||||
if ( ch == 255 )
|
||||
break; /* stop at a parse error */
|
||||
v = ( v << 6 ) | ch;
|
||||
@@ -57,11 +58,11 @@ int Base64::decode( size_t in_len, const char* in, size_t out_len, unsigned char
|
||||
return io;
|
||||
}
|
||||
|
||||
int Base64::encode( size_t in_len, const unsigned char* in, size_t out_len, char* out ) {
|
||||
size_t Base64::encode( size_t in_len, const unsigned char* in, size_t out_len, char* out ) {
|
||||
static const Uint8 base64enc_tab[] =
|
||||
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
||||
|
||||
unsigned ii, io;
|
||||
size_t ii, io;
|
||||
Uint32 v;
|
||||
unsigned rem;
|
||||
|
||||
@@ -94,14 +95,14 @@ int Base64::encode( size_t in_len, const unsigned char* in, size_t out_len, char
|
||||
return io;
|
||||
}
|
||||
|
||||
bool Base64::encode( const std::string& in, std::string& out ) {
|
||||
bool Base64::encode( std::string_view in, std::string& out ) {
|
||||
size_t b64len = encodeSafeOutLen( in.size() );
|
||||
|
||||
if ( out.size() < b64len ) {
|
||||
out.resize( b64len );
|
||||
}
|
||||
|
||||
int len = encode( in.size(), (const unsigned char*)in.c_str(), out.size(), (char*)&out[0] );
|
||||
int len = encode( in.size(), (const unsigned char*)in.data(), out.size(), (char*)&out[0] );
|
||||
|
||||
if ( -1 != len && (size_t)len != out.size() ) {
|
||||
out.resize( len );
|
||||
@@ -110,20 +111,20 @@ bool Base64::encode( const std::string& in, std::string& out ) {
|
||||
return -1 != len;
|
||||
}
|
||||
|
||||
bool Base64::decode( const std::string& in, std::string& out ) {
|
||||
size_t Base64::decode( std::string_view in, std::string& out ) {
|
||||
size_t d64len = decodeSafeOutLen( in.size() );
|
||||
|
||||
if ( out.size() < d64len ) {
|
||||
out.resize( d64len );
|
||||
}
|
||||
|
||||
int len = decode( in.size(), in.c_str(), out.size(), (unsigned char*)&out[0] );
|
||||
int len = decode( in.size(), in.data(), out.size(), (unsigned char*)&out[0] );
|
||||
|
||||
if ( -1 != len && (size_t)len != out.size() ) {
|
||||
out.resize( len );
|
||||
}
|
||||
|
||||
return -1 != len;
|
||||
return len;
|
||||
}
|
||||
|
||||
}} // namespace EE::System
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
#include <eepp/graphics/triangledrawable.hpp>
|
||||
#include <eepp/scene/scenemanager.hpp>
|
||||
#include <eepp/system/log.hpp>
|
||||
#include <eepp/system/luapattern.hpp>
|
||||
#include <eepp/ui/css/drawableimageparser.hpp>
|
||||
#include <eepp/ui/uiiconthememanager.hpp>
|
||||
#include <eepp/ui/uinode.hpp>
|
||||
@@ -331,12 +332,14 @@ void DrawableImageParser::registerBaseParsers() {
|
||||
UINode* node ) -> Drawable* {
|
||||
if ( functionType.getParameters().size() < 1 )
|
||||
return NULL;
|
||||
|
||||
return DrawableSearcher::searchByName(
|
||||
node->getUISceneNode()
|
||||
->solveRelativePath( functionType.getParameters().at( 0 ) )
|
||||
.toString(),
|
||||
false, node->getUISceneNode()->getReferer() );
|
||||
const auto& param = functionType.getParameters().at( 0 );
|
||||
if ( functionType.getName() == "url" && !param.empty() && param[0] != '@' &&
|
||||
!String::startsWith( param, "data:image/" ) ) {
|
||||
return DrawableSearcher::searchByName(
|
||||
node->getUISceneNode()->solveRelativePath( param ).toString(), false,
|
||||
node->getUISceneNode()->getReferer() );
|
||||
}
|
||||
return DrawableSearcher::searchByName( param, false, node->getUISceneNode()->getReferer() );
|
||||
};
|
||||
|
||||
mFuncs["icon"] = []( const FunctionString& functionType, const Sizef& size, bool&,
|
||||
|
||||
@@ -433,6 +433,8 @@ void StyleSheetSpecification::registerDefaultProperties() {
|
||||
registerProperty( "hidden", "" ).setType( PropertyType::Bool );
|
||||
registerProperty( "display", "inline" ).setType( PropertyType::String );
|
||||
registerProperty( "position", "static" ).setType( PropertyType::String );
|
||||
registerProperty( "float", "none" ).setType( PropertyType::String );
|
||||
registerProperty( "clear", "none" ).setType( PropertyType::String );
|
||||
registerProperty( "list-style-type", "none", true ).setType( PropertyType::String );
|
||||
registerProperty( "list-style-position", "outside", true ).setType( PropertyType::String );
|
||||
registerProperty( "list-style-image", "none" ).setType( PropertyType::String );
|
||||
@@ -478,6 +480,7 @@ void StyleSheetSpecification::registerDefaultProperties() {
|
||||
registerProperty( "method", "GET" ).setType( PropertyType::String );
|
||||
registerProperty( "enctype", "application/x-www-form-urlencoded" )
|
||||
.setType( PropertyType::String );
|
||||
registerProperty( "target", "_self" ).setType( PropertyType::String );
|
||||
|
||||
// Shorthands
|
||||
registerShorthand( "margin", { "margin-top", "margin-right", "margin-bottom", "margin-left" },
|
||||
@@ -1006,7 +1009,10 @@ void StyleSheetSpecification::registerDefaultShorthandParsers() {
|
||||
std::string positionStr;
|
||||
|
||||
for ( auto& tok : tokens ) {
|
||||
if ( mDrawableImageParser.exists( tok ) ) {
|
||||
auto open = tok.find_first_of( '(' );
|
||||
|
||||
if ( open != std::string::npos &&
|
||||
mDrawableImageParser.exists( tok.substr( 0, open ) ) ) {
|
||||
int pos = getIndexEndingWith( propNames, "-image" );
|
||||
if ( pos != -1 )
|
||||
properties.emplace_back( StyleSheetProperty( propNames[pos], tok ) );
|
||||
|
||||
@@ -69,6 +69,20 @@ void UIHTMLWidget::setCSSPosition( CSSPosition position ) {
|
||||
}
|
||||
}
|
||||
|
||||
void UIHTMLWidget::setCSSFloat( CSSFloat cssFloat ) {
|
||||
if ( mFloat != cssFloat ) {
|
||||
mFloat = cssFloat;
|
||||
notifyLayoutAttrChange();
|
||||
}
|
||||
}
|
||||
|
||||
void UIHTMLWidget::setCSSClear( CSSClear cssClear ) {
|
||||
if ( mClear != cssClear ) {
|
||||
mClear = cssClear;
|
||||
notifyLayoutAttrChange();
|
||||
}
|
||||
}
|
||||
|
||||
void UIHTMLWidget::setOffsets( const Rectf& offsets ) {
|
||||
if ( mOffsets != offsets ) {
|
||||
mOffsets = offsets;
|
||||
@@ -86,7 +100,8 @@ void UIHTMLWidget::setZIndex( int zIndex ) {
|
||||
|
||||
std::vector<PropertyId> UIHTMLWidget::getPropertiesImplemented() const {
|
||||
auto props = UILayout::getPropertiesImplemented();
|
||||
auto local = { PropertyId::Display, PropertyId::Position, PropertyId::Top, PropertyId::Right,
|
||||
auto local = { PropertyId::Display, PropertyId::Position, PropertyId::Float,
|
||||
PropertyId::Clear, PropertyId::Top, PropertyId::Right,
|
||||
PropertyId::Bottom, PropertyId::Left, PropertyId::ZIndex };
|
||||
props.insert( props.end(), local.begin(), local.end() );
|
||||
return props;
|
||||
@@ -102,6 +117,10 @@ std::string UIHTMLWidget::getPropertyString( const PropertyDefinition* propertyD
|
||||
return CSSDisplayHelper::toString( mDisplay );
|
||||
case PropertyId::Position:
|
||||
return CSSPositionHelper::toString( mPosition );
|
||||
case PropertyId::Float:
|
||||
return CSSFloatHelper::toString( mFloat );
|
||||
case PropertyId::Clear:
|
||||
return CSSClearHelper::toString( mClear );
|
||||
case PropertyId::Top:
|
||||
return mTopEq;
|
||||
case PropertyId::Right:
|
||||
@@ -130,6 +149,14 @@ bool UIHTMLWidget::applyProperty( const StyleSheetProperty& attribute ) {
|
||||
setCSSPosition( CSSPositionHelper::fromString( attribute.asString() ) );
|
||||
return true;
|
||||
}
|
||||
case PropertyId::Float: {
|
||||
setCSSFloat( CSSFloatHelper::fromString( attribute.asString() ) );
|
||||
return true;
|
||||
}
|
||||
case PropertyId::Clear: {
|
||||
setCSSClear( CSSClearHelper::fromString( attribute.asString() ) );
|
||||
return true;
|
||||
}
|
||||
case PropertyId::ZIndex: {
|
||||
setZIndex( attribute.asInt() );
|
||||
return true;
|
||||
|
||||
@@ -737,9 +737,16 @@ void UIRichText::rebuildRichText( UILayout* container, RichText& richText, Intri
|
||||
margin.Right );
|
||||
}
|
||||
|
||||
CSSFloat floatType = CSSFloat::None;
|
||||
CSSClear clearType = CSSClear::None;
|
||||
if ( widget->isType( UI_TYPE_HTML_WIDGET ) ) {
|
||||
floatType = widget->asType<UIHTMLWidget>()->getCSSFloat();
|
||||
clearType = widget->asType<UIHTMLWidget>()->getCSSClear();
|
||||
}
|
||||
|
||||
richText.addCustomSize( Sizef( w + margin.Left + margin.Right,
|
||||
size.getHeight() + margin.Top + margin.Bottom ),
|
||||
isBlock );
|
||||
isBlock, floatType, clearType );
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -623,6 +623,10 @@ bool UIAnchorSpan::applyProperty( const StyleSheetProperty& attribute ) {
|
||||
return false;
|
||||
|
||||
switch ( attribute.getPropertyDefinition()->getPropertyId() ) {
|
||||
case PropertyId::Target:{
|
||||
mTarget = attribute.value();
|
||||
break;
|
||||
}
|
||||
case PropertyId::Href:
|
||||
setHref( attribute.asString() );
|
||||
break;
|
||||
@@ -661,6 +665,8 @@ std::string UIAnchorSpan::getPropertyString( const PropertyDefinition* propertyD
|
||||
return "";
|
||||
|
||||
switch ( propertyDef->getPropertyId() ) {
|
||||
case PropertyId::Target:
|
||||
return mTarget;
|
||||
case PropertyId::Href:
|
||||
return mHref;
|
||||
default:
|
||||
@@ -670,7 +676,7 @@ std::string UIAnchorSpan::getPropertyString( const PropertyDefinition* propertyD
|
||||
|
||||
std::vector<PropertyId> UIAnchorSpan::getPropertiesImplemented() const {
|
||||
auto props = UITextSpan::getPropertiesImplemented();
|
||||
auto local = { PropertyId::Href };
|
||||
auto local = { PropertyId::Href, PropertyId::Target };
|
||||
props.insert( props.end(), local.begin(), local.end() );
|
||||
return props;
|
||||
}
|
||||
|
||||
607
src/tests/unit_tests/uihtml_float_tests.cpp
Normal file
607
src/tests/unit_tests/uihtml_float_tests.cpp
Normal file
@@ -0,0 +1,607 @@
|
||||
#include "utest.h"
|
||||
#include <eepp/graphics/fontfamily.hpp>
|
||||
#include <eepp/graphics/fonttruetype.hpp>
|
||||
#include <eepp/scene/scenemanager.hpp>
|
||||
#include <eepp/system/filesystem.hpp>
|
||||
#include <eepp/ui/css/stylesheetparser.hpp>
|
||||
#include <eepp/ui/tools/htmlformatter.hpp>
|
||||
#include <eepp/ui/uihtmlwidget.hpp>
|
||||
#include <eepp/ui/uirichtext.hpp>
|
||||
#include <eepp/ui/uiscenenode.hpp>
|
||||
#include <eepp/ui/uithememanager.hpp>
|
||||
#include <eepp/ui/uitheme.hpp>
|
||||
#include <eepp/window/engine.hpp>
|
||||
#include <eepp/window/window.hpp>
|
||||
|
||||
using namespace EE;
|
||||
using namespace EE::UI;
|
||||
using namespace EE::Window;
|
||||
using namespace EE::Graphics;
|
||||
|
||||
static void init_float_test() {
|
||||
Engine::instance()->createWindow(
|
||||
WindowSettings( 800, 600, "Float Layout Test", WindowStyle::Default, WindowBackend::Default,
|
||||
32, {}, 1, false, true ) );
|
||||
FileSystem::changeWorkingDirectory( Sys::getProcessPath() );
|
||||
|
||||
FontTrueType* font = FontTrueType::New( "NotoSans-Regular" );
|
||||
font->loadFromFile( "../assets/fonts/NotoSans-Regular.ttf" );
|
||||
FontFamily::loadFromRegular( font );
|
||||
|
||||
UI::UISceneNode* sceneNode = UI::UISceneNode::New();
|
||||
SceneManager::instance()->add( sceneNode );
|
||||
UI::UIThemeManager* themeManager = sceneNode->getUIThemeManager();
|
||||
themeManager->setDefaultFont( font );
|
||||
}
|
||||
|
||||
UTEST( UIHTMLFloat, structure_FloatAndClearEnums ) {
|
||||
EXPECT_TRUE( CSSFloatHelper::toString( CSSFloat::None ) == "none" );
|
||||
EXPECT_TRUE( CSSFloatHelper::toString( CSSFloat::Left ) == "left" );
|
||||
EXPECT_TRUE( CSSFloatHelper::toString( CSSFloat::Right ) == "right" );
|
||||
|
||||
EXPECT_EQ( (int)CSSFloat::None, (int)CSSFloatHelper::fromString( "none" ) );
|
||||
EXPECT_EQ( (int)CSSFloat::Left, (int)CSSFloatHelper::fromString( "left" ) );
|
||||
EXPECT_EQ( (int)CSSFloat::Right, (int)CSSFloatHelper::fromString( "right" ) );
|
||||
EXPECT_EQ( (int)CSSFloat::None, (int)CSSFloatHelper::fromString( "invalid" ) );
|
||||
|
||||
EXPECT_TRUE( CSSClearHelper::toString( CSSClear::None ) == "none" );
|
||||
EXPECT_TRUE( CSSClearHelper::toString( CSSClear::Left ) == "left" );
|
||||
EXPECT_TRUE( CSSClearHelper::toString( CSSClear::Right ) == "right" );
|
||||
EXPECT_TRUE( CSSClearHelper::toString( CSSClear::Both ) == "both" );
|
||||
|
||||
EXPECT_EQ( (int)CSSClear::None, (int)CSSClearHelper::fromString( "none" ) );
|
||||
EXPECT_EQ( (int)CSSClear::Left, (int)CSSClearHelper::fromString( "left" ) );
|
||||
EXPECT_EQ( (int)CSSClear::Right, (int)CSSClearHelper::fromString( "right" ) );
|
||||
EXPECT_EQ( (int)CSSClear::Both, (int)CSSClearHelper::fromString( "both" ) );
|
||||
EXPECT_EQ( (int)CSSClear::None, (int)CSSClearHelper::fromString( "garbage" ) );
|
||||
}
|
||||
|
||||
UTEST( UIHTMLFloat, property_DefaultsAreNone ) {
|
||||
UIHTMLWidget* w = UIHTMLWidget::New();
|
||||
EXPECT_EQ( CSSFloat::None, w->getCSSFloat() );
|
||||
EXPECT_EQ( CSSClear::None, w->getCSSClear() );
|
||||
eeDelete( w );
|
||||
}
|
||||
|
||||
UTEST( UIHTMLFloat, property_SetFloatViaApplyProperty ) {
|
||||
UIHTMLWidget* w = UIHTMLWidget::New();
|
||||
w->applyProperty( StyleSheetProperty( "float", "left" ) );
|
||||
EXPECT_EQ( CSSFloat::Left, w->getCSSFloat() );
|
||||
w->applyProperty( StyleSheetProperty( "float", "right" ) );
|
||||
EXPECT_EQ( CSSFloat::Right, w->getCSSFloat() );
|
||||
w->applyProperty( StyleSheetProperty( "float", "none" ) );
|
||||
EXPECT_EQ( CSSFloat::None, w->getCSSFloat() );
|
||||
eeDelete( w );
|
||||
}
|
||||
|
||||
UTEST( UIHTMLFloat, property_SetClearViaApplyProperty ) {
|
||||
UIHTMLWidget* w = UIHTMLWidget::New();
|
||||
w->applyProperty( StyleSheetProperty( "clear", "left" ) );
|
||||
EXPECT_EQ( CSSClear::Left, w->getCSSClear() );
|
||||
w->applyProperty( StyleSheetProperty( "clear", "right" ) );
|
||||
EXPECT_EQ( CSSClear::Right, w->getCSSClear() );
|
||||
w->applyProperty( StyleSheetProperty( "clear", "both" ) );
|
||||
EXPECT_EQ( CSSClear::Both, w->getCSSClear() );
|
||||
w->applyProperty( StyleSheetProperty( "clear", "none" ) );
|
||||
EXPECT_EQ( CSSClear::None, w->getCSSClear() );
|
||||
eeDelete( w );
|
||||
}
|
||||
|
||||
UTEST( UIHTMLFloat, property_GetPropertyString ) {
|
||||
UIHTMLWidget* w = UIHTMLWidget::New();
|
||||
w->setCSSFloat( CSSFloat::Left );
|
||||
w->setCSSClear( CSSClear::Right );
|
||||
auto props = w->getPropertiesImplemented();
|
||||
bool hasFloat = false, hasClear = false;
|
||||
for ( auto& p : props ) {
|
||||
if ( p == PropertyId::Float )
|
||||
hasFloat = true;
|
||||
if ( p == PropertyId::Clear )
|
||||
hasClear = true;
|
||||
}
|
||||
EXPECT_TRUE( hasFloat );
|
||||
EXPECT_TRUE( hasClear );
|
||||
eeDelete( w );
|
||||
}
|
||||
|
||||
UTEST( UIHTMLFloat, richtext_NoFloatLayout_NoChange ) {
|
||||
init_float_test();
|
||||
UISceneNode* sceneNode = SceneManager::instance()->getUISceneNode();
|
||||
|
||||
UIRichText* container = UIRichText::New();
|
||||
container->setParent( sceneNode->getRoot() );
|
||||
container->setPixelsSize( 600, 400 );
|
||||
container->setPixelsPosition( 10, 10 );
|
||||
container->setLayoutSizePolicy( SizePolicy::Fixed, SizePolicy::WrapContent );
|
||||
|
||||
UIHTMLWidget* child1 = UIHTMLWidget::New();
|
||||
child1->setParent( container );
|
||||
child1->setPixelsSize( 100, 50 );
|
||||
child1->setLayoutSizePolicy( SizePolicy::Fixed, SizePolicy::Fixed );
|
||||
|
||||
UIHTMLWidget* child2 = UIHTMLWidget::New();
|
||||
child2->setParent( container );
|
||||
child2->setPixelsSize( 150, 30 );
|
||||
child2->setLayoutSizePolicy( SizePolicy::Fixed, SizePolicy::Fixed );
|
||||
|
||||
sceneNode->updateDirtyLayouts();
|
||||
|
||||
Vector2f pos1 = child1->convertToWorldSpace( { 0, 0 } );
|
||||
Vector2f pos2 = child2->convertToWorldSpace( { 0, 0 } );
|
||||
|
||||
EXPECT_GE( pos2.x, pos1.x + child1->getPixelsSize().getWidth() - 1.f );
|
||||
|
||||
Engine::destroySingleton();
|
||||
}
|
||||
|
||||
UTEST( UIHTMLFloat, floatLeft_TextWrapsRight ) {
|
||||
init_float_test();
|
||||
UISceneNode* sceneNode = SceneManager::instance()->getUISceneNode();
|
||||
|
||||
UIRichText* container = UIRichText::New();
|
||||
container->setParent( sceneNode->getRoot() );
|
||||
container->setPixelsSize( 600, 400 );
|
||||
container->setPixelsPosition( 10, 10 );
|
||||
container->setLayoutSizePolicy( SizePolicy::Fixed, SizePolicy::WrapContent );
|
||||
|
||||
UIHTMLWidget* floatChild = UIHTMLWidget::New();
|
||||
floatChild->setParent( container );
|
||||
floatChild->setPixelsSize( 100, 50 );
|
||||
floatChild->setCSSFloat( CSSFloat::Left );
|
||||
floatChild->setLayoutSizePolicy( SizePolicy::Fixed, SizePolicy::Fixed );
|
||||
|
||||
UIHTMLWidget* inlineChild = UIHTMLWidget::New();
|
||||
inlineChild->setParent( container );
|
||||
inlineChild->setPixelsSize( 80, 30 );
|
||||
inlineChild->setLayoutSizePolicy( SizePolicy::Fixed, SizePolicy::Fixed );
|
||||
|
||||
sceneNode->updateDirtyLayouts();
|
||||
|
||||
Vector2f fpos = floatChild->convertToWorldSpace( { 0, 0 } );
|
||||
Vector2f ipos = inlineChild->convertToWorldSpace( { 0, 0 } );
|
||||
|
||||
EXPECT_NEAR( fpos.y, ipos.y, 1.f );
|
||||
EXPECT_GE( ipos.x, fpos.x + floatChild->getPixelsSize().getWidth() - 1.f );
|
||||
|
||||
Engine::destroySingleton();
|
||||
}
|
||||
|
||||
UTEST( UIHTMLFloat, floatRight_TextFlowsLeft ) {
|
||||
init_float_test();
|
||||
UISceneNode* sceneNode = SceneManager::instance()->getUISceneNode();
|
||||
|
||||
UIRichText* container = UIRichText::New();
|
||||
container->setParent( sceneNode->getRoot() );
|
||||
container->setPixelsSize( 600, 400 );
|
||||
container->setPixelsPosition( 10, 10 );
|
||||
container->setLayoutSizePolicy( SizePolicy::Fixed, SizePolicy::WrapContent );
|
||||
|
||||
UIHTMLWidget* floatChild = UIHTMLWidget::New();
|
||||
floatChild->setParent( container );
|
||||
floatChild->setPixelsSize( 100, 50 );
|
||||
floatChild->setCSSFloat( CSSFloat::Right );
|
||||
floatChild->setLayoutSizePolicy( SizePolicy::Fixed, SizePolicy::Fixed );
|
||||
|
||||
UIHTMLWidget* inlineChild = UIHTMLWidget::New();
|
||||
inlineChild->setParent( container );
|
||||
inlineChild->setPixelsSize( 80, 30 );
|
||||
inlineChild->setLayoutSizePolicy( SizePolicy::Fixed, SizePolicy::Fixed );
|
||||
|
||||
sceneNode->updateDirtyLayouts();
|
||||
|
||||
Vector2f fpos = floatChild->convertToWorldSpace( { 0, 0 } );
|
||||
Vector2f ipos = inlineChild->convertToWorldSpace( { 0, 0 } );
|
||||
|
||||
EXPECT_NEAR( fpos.y, ipos.y, 1.f );
|
||||
Float fRightEdge = fpos.x + floatChild->getPixelsSize().getWidth();
|
||||
EXPECT_LT( ipos.x + inlineChild->getPixelsSize().getWidth(), fRightEdge + 1.f );
|
||||
|
||||
Engine::destroySingleton();
|
||||
}
|
||||
|
||||
UTEST( UIHTMLFloat, twoFloatsLeft_StackHorizontally ) {
|
||||
init_float_test();
|
||||
UISceneNode* sceneNode = SceneManager::instance()->getUISceneNode();
|
||||
|
||||
UIRichText* container = UIRichText::New();
|
||||
container->setParent( sceneNode->getRoot() );
|
||||
container->setPixelsSize( 600, 400 );
|
||||
container->setPixelsPosition( 10, 10 );
|
||||
container->setLayoutSizePolicy( SizePolicy::Fixed, SizePolicy::WrapContent );
|
||||
|
||||
UIHTMLWidget* float1 = UIHTMLWidget::New();
|
||||
float1->setParent( container );
|
||||
float1->setPixelsSize( 100, 50 );
|
||||
float1->setCSSFloat( CSSFloat::Left );
|
||||
float1->setLayoutSizePolicy( SizePolicy::Fixed, SizePolicy::Fixed );
|
||||
|
||||
UIHTMLWidget* float2 = UIHTMLWidget::New();
|
||||
float2->setParent( container );
|
||||
float2->setPixelsSize( 120, 40 );
|
||||
float2->setCSSFloat( CSSFloat::Left );
|
||||
float2->setLayoutSizePolicy( SizePolicy::Fixed, SizePolicy::Fixed );
|
||||
|
||||
sceneNode->updateDirtyLayouts();
|
||||
|
||||
Vector2f f1pos = float1->convertToWorldSpace( { 0, 0 } );
|
||||
Vector2f f2pos = float2->convertToWorldSpace( { 0, 0 } );
|
||||
|
||||
EXPECT_NEAR( f1pos.y, f2pos.y, 1.f );
|
||||
EXPECT_NEAR( f2pos.x, f1pos.x + 100.f, 1.f );
|
||||
|
||||
Engine::destroySingleton();
|
||||
}
|
||||
|
||||
UTEST( UIHTMLFloat, twoFloatsRight_StackHorizontally ) {
|
||||
init_float_test();
|
||||
UISceneNode* sceneNode = SceneManager::instance()->getUISceneNode();
|
||||
|
||||
UIRichText* container = UIRichText::New();
|
||||
container->setParent( sceneNode->getRoot() );
|
||||
container->setPixelsSize( 600, 400 );
|
||||
container->setPixelsPosition( 10, 10 );
|
||||
container->setLayoutSizePolicy( SizePolicy::Fixed, SizePolicy::WrapContent );
|
||||
|
||||
UIHTMLWidget* float1 = UIHTMLWidget::New();
|
||||
float1->setParent( container );
|
||||
float1->setPixelsSize( 100, 50 );
|
||||
float1->setCSSFloat( CSSFloat::Right );
|
||||
float1->setLayoutSizePolicy( SizePolicy::Fixed, SizePolicy::Fixed );
|
||||
|
||||
UIHTMLWidget* float2 = UIHTMLWidget::New();
|
||||
float2->setParent( container );
|
||||
float2->setPixelsSize( 80, 40 );
|
||||
float2->setCSSFloat( CSSFloat::Right );
|
||||
float2->setLayoutSizePolicy( SizePolicy::Fixed, SizePolicy::Fixed );
|
||||
|
||||
sceneNode->updateDirtyLayouts();
|
||||
|
||||
Vector2f f1pos = float1->convertToWorldSpace( { 0, 0 } );
|
||||
Vector2f f2pos = float2->convertToWorldSpace( { 0, 0 } );
|
||||
|
||||
EXPECT_NEAR( f1pos.y, f2pos.y, 1.f );
|
||||
EXPECT_GT( f1pos.x, f2pos.x );
|
||||
|
||||
Engine::destroySingleton();
|
||||
}
|
||||
|
||||
UTEST( UIHTMLFloat, clearBoth_JumpsBelowAllFloats ) {
|
||||
init_float_test();
|
||||
UISceneNode* sceneNode = SceneManager::instance()->getUISceneNode();
|
||||
|
||||
UIRichText* container = UIRichText::New();
|
||||
container->setParent( sceneNode->getRoot() );
|
||||
container->setPixelsSize( 600, 400 );
|
||||
container->setPixelsPosition( 10, 10 );
|
||||
container->setLayoutSizePolicy( SizePolicy::Fixed, SizePolicy::WrapContent );
|
||||
|
||||
UIHTMLWidget* floatLeft = UIHTMLWidget::New();
|
||||
floatLeft->setParent( container );
|
||||
floatLeft->setPixelsSize( 100, 80 );
|
||||
floatLeft->setCSSFloat( CSSFloat::Left );
|
||||
floatLeft->setLayoutSizePolicy( SizePolicy::Fixed, SizePolicy::Fixed );
|
||||
|
||||
UIHTMLWidget* floatRight = UIHTMLWidget::New();
|
||||
floatRight->setParent( container );
|
||||
floatRight->setPixelsSize( 90, 60 );
|
||||
floatRight->setCSSFloat( CSSFloat::Right );
|
||||
floatRight->setLayoutSizePolicy( SizePolicy::Fixed, SizePolicy::Fixed );
|
||||
|
||||
UIHTMLWidget* clearChild = UIHTMLWidget::New();
|
||||
clearChild->setParent( container );
|
||||
clearChild->setPixelsSize( 200, 30 );
|
||||
clearChild->setCSSClear( CSSClear::Both );
|
||||
clearChild->setLayoutSizePolicy( SizePolicy::Fixed, SizePolicy::Fixed );
|
||||
|
||||
sceneNode->updateDirtyLayouts();
|
||||
|
||||
Vector2f fLeftPos = floatLeft->convertToWorldSpace( { 0, 0 } );
|
||||
Vector2f fRightPos = floatRight->convertToWorldSpace( { 0, 0 } );
|
||||
Vector2f clearPos = clearChild->convertToWorldSpace( { 0, 0 } );
|
||||
|
||||
EXPECT_GE( clearPos.y, fLeftPos.y + floatLeft->getPixelsSize().getHeight() - 1.f );
|
||||
EXPECT_GE( clearPos.y, fRightPos.y + floatRight->getPixelsSize().getHeight() - 1.f );
|
||||
|
||||
Engine::destroySingleton();
|
||||
}
|
||||
|
||||
UTEST( UIHTMLFloat, clearLeft_OnlyJumpsPastLeftFloats ) {
|
||||
init_float_test();
|
||||
UISceneNode* sceneNode = SceneManager::instance()->getUISceneNode();
|
||||
|
||||
UIRichText* container = UIRichText::New();
|
||||
container->setParent( sceneNode->getRoot() );
|
||||
container->setPixelsSize( 600, 400 );
|
||||
container->setPixelsPosition( 10, 10 );
|
||||
container->setLayoutSizePolicy( SizePolicy::Fixed, SizePolicy::WrapContent );
|
||||
|
||||
UIHTMLWidget* floatLeft = UIHTMLWidget::New();
|
||||
floatLeft->setParent( container );
|
||||
floatLeft->setPixelsSize( 100, 120 );
|
||||
floatLeft->setCSSFloat( CSSFloat::Left );
|
||||
floatLeft->setLayoutSizePolicy( SizePolicy::Fixed, SizePolicy::Fixed );
|
||||
|
||||
UIHTMLWidget* inlineChild = UIHTMLWidget::New();
|
||||
inlineChild->setParent( container );
|
||||
inlineChild->setPixelsSize( 50, 20 );
|
||||
inlineChild->setLayoutSizePolicy( SizePolicy::Fixed, SizePolicy::Fixed );
|
||||
|
||||
UIHTMLWidget* clearLeftChild = UIHTMLWidget::New();
|
||||
clearLeftChild->setParent( container );
|
||||
clearLeftChild->setPixelsSize( 200, 30 );
|
||||
clearLeftChild->setCSSClear( CSSClear::Left );
|
||||
clearLeftChild->setLayoutSizePolicy( SizePolicy::Fixed, SizePolicy::Fixed );
|
||||
|
||||
sceneNode->updateDirtyLayouts();
|
||||
|
||||
Vector2f floatPos = floatLeft->convertToWorldSpace( { 0, 0 } );
|
||||
Vector2f clearPos = clearLeftChild->convertToWorldSpace( { 0, 0 } );
|
||||
|
||||
EXPECT_GE( clearPos.y, floatPos.y + floatLeft->getPixelsSize().getHeight() - 1.f );
|
||||
|
||||
Engine::destroySingleton();
|
||||
}
|
||||
|
||||
UTEST( UIHTMLFloat, clearRight_RespectsRightFloats ) {
|
||||
init_float_test();
|
||||
UISceneNode* sceneNode = SceneManager::instance()->getUISceneNode();
|
||||
|
||||
UIRichText* container = UIRichText::New();
|
||||
container->setParent( sceneNode->getRoot() );
|
||||
container->setPixelsSize( 600, 400 );
|
||||
container->setPixelsPosition( 10, 10 );
|
||||
container->setLayoutSizePolicy( SizePolicy::Fixed, SizePolicy::WrapContent );
|
||||
|
||||
UIHTMLWidget* floatRight = UIHTMLWidget::New();
|
||||
floatRight->setParent( container );
|
||||
floatRight->setPixelsSize( 100, 100 );
|
||||
floatRight->setCSSFloat( CSSFloat::Right );
|
||||
floatRight->setLayoutSizePolicy( SizePolicy::Fixed, SizePolicy::Fixed );
|
||||
|
||||
UIHTMLWidget* clearRightChild = UIHTMLWidget::New();
|
||||
clearRightChild->setParent( container );
|
||||
clearRightChild->setPixelsSize( 200, 30 );
|
||||
clearRightChild->setCSSClear( CSSClear::Right );
|
||||
clearRightChild->setLayoutSizePolicy( SizePolicy::Fixed, SizePolicy::Fixed );
|
||||
|
||||
sceneNode->updateDirtyLayouts();
|
||||
|
||||
Vector2f fpos = floatRight->convertToWorldSpace( { 0, 0 } );
|
||||
Vector2f clearPos = clearRightChild->convertToWorldSpace( { 0, 0 } );
|
||||
|
||||
EXPECT_GE( clearPos.y, fpos.y + floatRight->getPixelsSize().getHeight() - 1.f );
|
||||
|
||||
Engine::destroySingleton();
|
||||
}
|
||||
|
||||
UTEST( UIHTMLFloat, mixedLeftRight_ContentBetween ) {
|
||||
init_float_test();
|
||||
UISceneNode* sceneNode = SceneManager::instance()->getUISceneNode();
|
||||
|
||||
UIRichText* container = UIRichText::New();
|
||||
container->setParent( sceneNode->getRoot() );
|
||||
container->setPixelsSize( 600, 400 );
|
||||
container->setPixelsPosition( 10, 10 );
|
||||
container->setLayoutSizePolicy( SizePolicy::Fixed, SizePolicy::WrapContent );
|
||||
|
||||
UIHTMLWidget* floatLeft = UIHTMLWidget::New();
|
||||
floatLeft->setParent( container );
|
||||
floatLeft->setPixelsSize( 100, 50 );
|
||||
floatLeft->setCSSFloat( CSSFloat::Left );
|
||||
floatLeft->setLayoutSizePolicy( SizePolicy::Fixed, SizePolicy::Fixed );
|
||||
|
||||
UIHTMLWidget* floatRight = UIHTMLWidget::New();
|
||||
floatRight->setParent( container );
|
||||
floatRight->setPixelsSize( 80, 50 );
|
||||
floatRight->setCSSFloat( CSSFloat::Right );
|
||||
floatRight->setLayoutSizePolicy( SizePolicy::Fixed, SizePolicy::Fixed );
|
||||
|
||||
UIHTMLWidget* middleChild = UIHTMLWidget::New();
|
||||
middleChild->setParent( container );
|
||||
middleChild->setPixelsSize( 150, 30 );
|
||||
middleChild->setLayoutSizePolicy( SizePolicy::Fixed, SizePolicy::Fixed );
|
||||
|
||||
sceneNode->updateDirtyLayouts();
|
||||
|
||||
Vector2f fLeftPos = floatLeft->convertToWorldSpace( { 0, 0 } );
|
||||
Vector2f fRightPos = floatRight->convertToWorldSpace( { 0, 0 } );
|
||||
Vector2f midPos = middleChild->convertToWorldSpace( { 0, 0 } );
|
||||
|
||||
EXPECT_NEAR( fLeftPos.y, fRightPos.y, 1.f );
|
||||
EXPECT_NEAR( fLeftPos.y, midPos.y, 1.f );
|
||||
|
||||
EXPECT_GE( midPos.x, fLeftPos.x + floatLeft->getPixelsSize().getWidth() - 1.f );
|
||||
EXPECT_LE( midPos.x + middleChild->getPixelsSize().getWidth(), fRightPos.x + 1.f );
|
||||
|
||||
Engine::destroySingleton();
|
||||
}
|
||||
|
||||
UTEST( UIHTMLFloat, floatWrapsContentBelowWhenTooWide ) {
|
||||
init_float_test();
|
||||
UISceneNode* sceneNode = SceneManager::instance()->getUISceneNode();
|
||||
|
||||
UIRichText* container = UIRichText::New();
|
||||
container->setParent( sceneNode->getRoot() );
|
||||
container->setPixelsSize( 600, 400 );
|
||||
container->setPixelsPosition( 10, 10 );
|
||||
container->setLayoutSizePolicy( SizePolicy::Fixed, SizePolicy::WrapContent );
|
||||
|
||||
UIHTMLWidget* floatLeft = UIHTMLWidget::New();
|
||||
floatLeft->setParent( container );
|
||||
floatLeft->setPixelsSize( 350, 30 );
|
||||
floatLeft->setCSSFloat( CSSFloat::Left );
|
||||
floatLeft->setLayoutSizePolicy( SizePolicy::Fixed, SizePolicy::Fixed );
|
||||
|
||||
UIHTMLWidget* wideChild = UIHTMLWidget::New();
|
||||
wideChild->setParent( container );
|
||||
wideChild->setPixelsSize( 400, 25 );
|
||||
wideChild->setLayoutSizePolicy( SizePolicy::Fixed, SizePolicy::Fixed );
|
||||
|
||||
sceneNode->updateDirtyLayouts();
|
||||
|
||||
Vector2f widePos = wideChild->convertToWorldSpace( { 0, 0 } );
|
||||
Vector2f fpos = floatLeft->convertToWorldSpace( { 0, 0 } );
|
||||
|
||||
EXPECT_GT( widePos.y, fpos.y + 1.f );
|
||||
|
||||
Engine::destroySingleton();
|
||||
}
|
||||
|
||||
UTEST( UIHTMLFloat, floatLeft_InlineBlockBeside ) {
|
||||
init_float_test();
|
||||
UISceneNode* sceneNode = SceneManager::instance()->getUISceneNode();
|
||||
|
||||
UIRichText* container = UIRichText::New();
|
||||
container->setParent( sceneNode->getRoot() );
|
||||
container->setPixelsSize( 600, 400 );
|
||||
container->setPixelsPosition( 10, 10 );
|
||||
container->setLayoutSizePolicy( SizePolicy::Fixed, SizePolicy::WrapContent );
|
||||
|
||||
UIHTMLWidget* floatLeft = UIHTMLWidget::New();
|
||||
floatLeft->setParent( container );
|
||||
floatLeft->setPixelsSize( 100, 50 );
|
||||
floatLeft->setCSSFloat( CSSFloat::Left );
|
||||
floatLeft->setLayoutSizePolicy( SizePolicy::Fixed, SizePolicy::Fixed );
|
||||
|
||||
UIHTMLWidget* inlineBlock = UIHTMLWidget::New();
|
||||
inlineBlock->setParent( container );
|
||||
inlineBlock->setPixelsSize( 80, 30 );
|
||||
inlineBlock->setLayoutSizePolicy( SizePolicy::Fixed, SizePolicy::Fixed );
|
||||
|
||||
sceneNode->updateDirtyLayouts();
|
||||
|
||||
Vector2f fpos = floatLeft->convertToWorldSpace( { 0, 0 } );
|
||||
Vector2f ipos = inlineBlock->convertToWorldSpace( { 0, 0 } );
|
||||
|
||||
EXPECT_NEAR( fpos.y, ipos.y, 1.f );
|
||||
EXPECT_GE( ipos.x, fpos.x + floatLeft->getPixelsSize().getWidth() - 1.f );
|
||||
|
||||
Engine::destroySingleton();
|
||||
}
|
||||
|
||||
UTEST( UIHTMLFloat, floatLeft_LargeFloat_PushesContentDown ) {
|
||||
init_float_test();
|
||||
UISceneNode* sceneNode = SceneManager::instance()->getUISceneNode();
|
||||
|
||||
UIRichText* container = UIRichText::New();
|
||||
container->setParent( sceneNode->getRoot() );
|
||||
container->setPixelsSize( 600, 400 );
|
||||
container->setPixelsPosition( 10, 10 );
|
||||
container->setLayoutSizePolicy( SizePolicy::Fixed, SizePolicy::WrapContent );
|
||||
|
||||
UIHTMLWidget* floatLeft = UIHTMLWidget::New();
|
||||
floatLeft->setParent( container );
|
||||
floatLeft->setPixelsSize( 200, 120 );
|
||||
floatLeft->setCSSFloat( CSSFloat::Left );
|
||||
floatLeft->setLayoutSizePolicy( SizePolicy::Fixed, SizePolicy::Fixed );
|
||||
|
||||
UIHTMLWidget* afterFloat = UIHTMLWidget::New();
|
||||
afterFloat->setParent( container );
|
||||
afterFloat->setPixelsSize( 200, 30 );
|
||||
afterFloat->setCSSClear( CSSClear::Both );
|
||||
afterFloat->setLayoutSizePolicy( SizePolicy::Fixed, SizePolicy::Fixed );
|
||||
|
||||
sceneNode->updateDirtyLayouts();
|
||||
|
||||
Vector2f fpos = floatLeft->convertToWorldSpace( { 0, 0 } );
|
||||
Vector2f afterPos = afterFloat->convertToWorldSpace( { 0, 0 } );
|
||||
|
||||
EXPECT_GE( afterPos.y, fpos.y + floatLeft->getPixelsSize().getHeight() - 1.f );
|
||||
|
||||
Engine::destroySingleton();
|
||||
}
|
||||
|
||||
UTEST( UIHTMLFloat, floatLeftNonHTMLwidget_NoCrash ) {
|
||||
init_float_test();
|
||||
UISceneNode* sceneNode = SceneManager::instance()->getUISceneNode();
|
||||
|
||||
UIRichText* container = UIRichText::New();
|
||||
container->setParent( sceneNode->getRoot() );
|
||||
container->setPixelsSize( 600, 400 );
|
||||
container->setPixelsPosition( 10, 10 );
|
||||
container->setLayoutSizePolicy( SizePolicy::Fixed, SizePolicy::WrapContent );
|
||||
|
||||
UIWidget* plainWidget = UIWidget::New();
|
||||
plainWidget->setParent( container );
|
||||
plainWidget->setPixelsSize( 100, 50 );
|
||||
plainWidget->setLayoutSizePolicy( SizePolicy::Fixed, SizePolicy::Fixed );
|
||||
|
||||
UIWidget* plainWidget2 = UIWidget::New();
|
||||
plainWidget2->setParent( container );
|
||||
plainWidget2->setPixelsSize( 80, 30 );
|
||||
plainWidget2->setLayoutSizePolicy( SizePolicy::Fixed, SizePolicy::Fixed );
|
||||
|
||||
sceneNode->updateDirtyLayouts();
|
||||
|
||||
Vector2f pos1 = plainWidget->convertToWorldSpace( { 0, 0 } );
|
||||
Vector2f pos2 = plainWidget2->convertToWorldSpace( { 0, 0 } );
|
||||
|
||||
EXPECT_GE( pos2.x, pos1.x + plainWidget->getPixelsSize().getWidth() - 1.f );
|
||||
|
||||
Engine::destroySingleton();
|
||||
}
|
||||
|
||||
UTEST( UIHTMLFloat, floatNotAffectedByTextAlignCenter ) {
|
||||
Engine::instance()->createWindow(
|
||||
WindowSettings( 800, 600, "Float + TextAlign Test", WindowStyle::Default,
|
||||
WindowBackend::Default, 32, {}, 1, false, true ),
|
||||
ContextSettings( false, 0, 0, GLv_default, true, false ) );
|
||||
FileSystem::changeWorkingDirectory( Sys::getProcessPath() );
|
||||
|
||||
FontTrueType* font = FontTrueType::New( "NotoSans-Regular" );
|
||||
font->loadFromFile( "../assets/fonts/NotoSans-Regular.ttf" );
|
||||
FontFamily::loadFromRegular( font );
|
||||
|
||||
UI::UISceneNode* sceneNode = UI::UISceneNode::New();
|
||||
SceneManager::instance()->add( sceneNode );
|
||||
UI::UIThemeManager* themeManager = sceneNode->getUIThemeManager();
|
||||
themeManager->setDefaultFont( font );
|
||||
|
||||
sceneNode->setURI( "file://" + Sys::getProcessPath() + "assets/html/" );
|
||||
std::string html;
|
||||
FileSystem::fileGet( "assets/html/position_absolute_and_float.html", html );
|
||||
sceneNode->loadLayoutFromString( UI::Tools::HTMLFormatter::HTMLtoXML( html ) );
|
||||
|
||||
sceneNode->update( Milliseconds( 16 ) );
|
||||
sceneNode->updateDirtyLayouts();
|
||||
|
||||
UIWidget* mainWidget = sceneNode->getRoot()->find<UIWidget>( "main" );
|
||||
ASSERT_TRUE( mainWidget != nullptr );
|
||||
|
||||
// The "main" div has two children with class "box"
|
||||
// Each "box" has float:left, clear:both, text-align:center
|
||||
// Inside the first box: .titlebox (float:left) and .login_inbox (float:left)
|
||||
Node* child = mainWidget->getFirstChild();
|
||||
UIWidget* firstBox = nullptr;
|
||||
while ( child ) {
|
||||
if ( child->isWidget() ) {
|
||||
UIWidget* w = child->asType<UIWidget>();
|
||||
if ( w->isType( UI_TYPE_HTML_WIDGET ) &&
|
||||
w->asType<UIHTMLWidget>()->getCSSFloat() == CSSFloat::Left ) {
|
||||
firstBox = w;
|
||||
break;
|
||||
}
|
||||
}
|
||||
child = child->getNextNode();
|
||||
}
|
||||
ASSERT_TRUE( firstBox != nullptr );
|
||||
|
||||
// The box's children (float:left) should not be shifted by text-align:center
|
||||
Vector2f boxOrigin = firstBox->convertToWorldSpace( { 0, 0 } );
|
||||
|
||||
Node* boxChild = firstBox->getFirstChild();
|
||||
while ( boxChild ) {
|
||||
if ( boxChild->isWidget() ) {
|
||||
UIWidget* bc = boxChild->asType<UIWidget>();
|
||||
if ( bc->isType( UI_TYPE_HTML_WIDGET ) &&
|
||||
bc->asType<UIHTMLWidget>()->getCSSFloat() == CSSFloat::Left ) {
|
||||
Vector2f bcWorld = bc->convertToWorldSpace( { 0, 0 } );
|
||||
// Float children should be at the left edge of the box (not shifted to center)
|
||||
EXPECT_NEAR( bcWorld.x, boxOrigin.x, 1.f );
|
||||
}
|
||||
}
|
||||
boxChild = boxChild->getNextNode();
|
||||
}
|
||||
|
||||
Engine::destroySingleton();
|
||||
}
|
||||
@@ -1,18 +1,20 @@
|
||||
#pragma once
|
||||
#include "utest.h"
|
||||
#include <eepp/core/small_vector.hpp>
|
||||
|
||||
#include <ranges>
|
||||
#include <sstream>
|
||||
#include <vector>
|
||||
|
||||
template <typename T> std::string vectorToString( const std::vector<T>& vec ) {
|
||||
template <std::ranges::input_range Range> std::string vectorToString( const Range& vec ) {
|
||||
std::ostringstream oss;
|
||||
oss << "[";
|
||||
bool first = true;
|
||||
for ( const auto& element : vec ) {
|
||||
if ( !first )
|
||||
oss << ", ";
|
||||
oss << element;
|
||||
first = false;
|
||||
auto it = std::ranges::begin( vec );
|
||||
auto end = std::ranges::end( vec );
|
||||
if ( it != end ) {
|
||||
oss << *it;
|
||||
for ( ++it; it != end; ++it ) {
|
||||
oss << ", " << *it;
|
||||
}
|
||||
}
|
||||
oss << "]";
|
||||
return oss.str();
|
||||
|
||||
Reference in New Issue
Block a user