Load CSS default HTML element styles from a CSS file with no specificity. This simplyfies how to setup the HTML defaults. Also set new defaults matching closer HTML spec.

This commit is contained in:
Martín Lucas Golini
2026-05-24 13:15:31 -03:00
parent bb7682dd8f
commit aa9efcf13b
28 changed files with 407 additions and 188 deletions

View File

@@ -220,7 +220,7 @@
"working_dir": "${project_root}/bin"
},
{
"args": "-filter=\"UIRichText.InvalidWidthLengthComputation3\"",
"args": "",
"command": "${project_root}/bin/unit_tests/eepp-unit_tests-debug",
"name": "eepp-unit_tests-debug",
"working_dir": "${project_root}/bin/unit_tests/"

View File

@@ -52,91 +52,13 @@
droppable-hovering-color: #FFFFFF20;
}
MarkdownView > *,
body {
color: var(--font);
}
b,
strong {
font-style: bold;
}
small {
font-size: smaller;
}
u,
ins {
text-decoration: underline;
}
s,
del {
text-decoration: strikethrough;
}
i,
em,
cite {
font-style: italic;
}
CodeEditor,
code,
kbd {
font-family: monospace;
}
h1 {
font-size: 2em;
font-weight: bold;
}
h2 {
font-size: 1.5em;
font-weight: bold;
}
h3 {
font-size: 1.17em;
font-weight: bold;
}
h4 {
font-size: 1em;
font-weight: bold;
}
h5 {
font-size: 0.83em;
font-weight: bold;
}
h6 {
font-size: 0.67em;
font-weight: bold;
}
a {
cursor: arrow;
text-decoration: none;
}
a:hover {
text-decoration: underline;
cursor: hand;
}
sub,
sup {
font-size: smaller;
}
blockquote {
padding-left: 8dp;
MarkdownView {
background-color: var(--list-back);
border-left: 2dp solid var(--tab-line);
padding: 4dp;
}
MarkdownView body {
color: var(--font);
}
MarkdownView p,
@@ -171,11 +93,6 @@ MarkdownView a:hover {
color: var(--font-highlight);
}
MarkdownView {
background-color: var(--list-back);
padding: 4dp;
}
MarkdownView li > p { margin: 0; }
MarkdownView blockquote > *:first-child { margin-top: 0dp; }
MarkdownView blockquote > *:last-child { margin-bottom: 0dp; }

Binary file not shown.

Before

Width:  |  Height:  |  Size: 27 KiB

After

Width:  |  Height:  |  Size: 27 KiB

View File

@@ -0,0 +1,146 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>HTML Element Defaults</title>
<style>
body { font-family: sans-serif; padding: 20px; }
h2 { border-bottom: 1px solid #ccc; margin-top: 32px; }
.group { margin-bottom: 24px; }
.row { margin: 4px 0; }
.label { display: inline-block; min-width: 130px; color: #555; font-size: 12px; }
table { border-collapse: collapse; }
td, th { border: 1px solid #ccc; padding: 4px 8px; }
</style>
</head>
<body>
<h1>HTML Element Default Styles</h1>
<p>This page exercises all elements whose defaults were moved to widget-creation code.</p>
<h2>Headings</h2>
<div class="group">
<h1>Heading 1 &mdash; The quick brown fox</h1>
<h2>Heading 2 &mdash; The quick brown fox</h2>
<h3>Heading 3 &mdash; The quick brown fox</h3>
<h4>Heading 4 &mdash; The quick brown fox</h4>
<h5>Heading 5 &mdash; The quick brown fox</h5>
<h6>Heading 6 &mdash; The quick brown fox</h6>
</div>
<h2>Inline Text Semantics</h2>
<div class="group">
<div class="row"><span class="label">b / strong</span> <b>bold text</b> <strong>strong text</strong></div>
<div class="row"><span class="label">i / em / cite</span> <i>italic text</i> <em>emphasized</em> <cite>citation</cite></div>
<div class="row"><span class="label">u / ins</span> <u>underlined</u> <ins>inserted</ins></div>
<div class="row"><span class="label">s / del</span> <s>strikethrough</s> <del>deleted</del></div>
<div class="row"><span class="label">small</span> <small>small text</small></div>
<div class="row"><span class="label">code / kbd</span> <code>function()</code> <kbd>Ctrl+C</kbd></div>
<div class="row"><span class="label">sub / sup</span> H<sub>2</sub>O and E=mc<sup>2</sup></div>
<div class="row"><span class="label">a (link)</span> <a href="#">clickable link</a></div>
<div class="row"><span class="label">a (visited)</span> <a href="">visited link</a></div>
<div class="row"><span class="label">a (hover)</span> <a href="#" style="text-decoration:underline;cursor:hand;">hovered link</a></div>
<div class="row"><span class="label">mark</span> <mark>highlighted text</mark></div>
</div>
<h2>Block Elements</h2>
<div class="group">
<p>A paragraph of text. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.</p>
<p>Another paragraph with a <br>line break in the middle.</p>
<hr>
<pre>preformatted text
with spaces
and newlines</pre>
<blockquote>
<p>This is a blockquote. It should have left border, left padding, and background.</p>
<p>Multiple paragraphs in a blockquote.</p>
</blockquote>
<div>A div container with no special styling</div>
</div>
<h2>Lists</h2>
<div class="group">
<ul>
<li>Unordered list item 1</li>
<li>Unordered list item 2
<ul>
<li>Nested item A</li>
<li>Nested item B</li>
</ul>
</li>
<li>Unordered list item 3</li>
</ul>
<ol>
<li>Ordered list item 1</li>
<li>Ordered list item 2
<ol>
<li>Nested numbered item A</li>
<li>Nested numbered item B</li>
</ol>
</li>
<li>Ordered list item 3</li>
</ol>
</div>
<h2>Form Controls &mdash; Text Inputs</h2>
<div class="group">
<div class="row"><span class="label">text</span> <input type="text" value="default text input" style="width:200px"></div>
<div class="row"><span class="label">text (empty)</span> <input type="text" placeholder="placeholder text" style="width:200px"></div>
<div class="row"><span class="label">password</span> <input type="password" value="secret" style="width:200px"></div>
<div class="row"><span class="label">number</span> <input type="number" value="42" style="width:100px"></div>
<div class="row"><span class="label">textarea</span> <textarea rows="4" cols="40">Textarea content</textarea></div>
</div>
<h2>Form Controls &mdash; Buttons</h2>
<div class="group">
<div class="row"><span class="label">submit</span> <input type="submit" value="Submit"></div>
<div class="row"><span class="label">button</span> <input type="button" value="Button"></div>
<div class="row"><span class="label">reset</span> <input type="reset" value="Reset"></div>
</div>
<h2>Form Controls &mdash; Checkboxes</h2>
<div class="group">
<div class="row"><span class="label">checkbox</span> <input type="checkbox" checked> Option A <input type="checkbox"> Option B</div>
<div class="row"><span class="label">radio</span> <input type="radio" name="r" checked> Choice 1 <input type="radio" name="r"> Choice 2</div>
</div>
<h2>Tables</h2>
<div class="group">
<table>
<thead>
<tr><th>Header A</th><th>Header B</th><th>Header C</th></tr>
</thead>
<tbody>
<tr><td>Cell 1</td><td>Cell 2</td><td>Cell 3</td></tr>
<tr><td>Cell 4</td><td>Cell 5</td><td>Cell 6</td></tr>
</tbody>
<tfoot>
<tr><td>Footer 1</td><td>Footer 2</td><td>Footer 3</td></tr>
</tfoot>
</table>
</div>
<h2>Details / Summary</h2>
<div class="group">
<details>
<summary>Click to expand details</summary>
<p>Hidden content revealed when summary is clicked.</p>
</details>
<details open>
<summary>Already open details</summary>
<p>This content is visible by default.</p>
</details>
</div>
<h2>Misc</h2>
<div class="group">
<div class="row"><span class="label">image</span> <img src="triangle.svg" width="32" height="32" alt="triangle"></div>
<div class="row"><span class="label">fieldset</span>
<fieldset>
<legend>Legend</legend>
Fieldset content
</fieldset>
</div>
<div class="row"><span class="label">hidden input</span> <input type="hidden" value="should be invisible"> (should not be visible)</div>
</div>
</body>
</html>

View File

@@ -1,5 +1,6 @@
<!DOCTYPE html>
<html lang=en>
<head>
<title>Lobsters</title>
<style>
:root {
@@ -156,3 +157,6 @@
</li>
</ol>
</div>
</body>
</html>

View File

@@ -60,6 +60,8 @@ class EE_API StyleSheet {
void removeAllWithoutMarker( const Uint32& marker );
void setSelectorSpecificity( const Uint32& specificity );
bool markerExists( const Uint32& marker ) const;
StyleSheet getAllWithMarker( const Uint32& marker ) const;

View File

@@ -19,6 +19,8 @@ class EE_API StyleSheetSelector {
const Uint32& getSpecificity() const;
void setSpecificity( const Uint32& specificity );
bool select( UIWidget* element, const bool& applyPseudo = true ) const;
bool isCacheable() const;

View File

@@ -67,6 +67,8 @@ class EE_API StyleSheetStyle {
void setMarker( const Uint32& marker );
void setSelectorSpecificity( const Uint32& specificity );
bool updatePropertyValue( const std::string& name, const std::string& value );
protected:

View File

@@ -742,6 +742,8 @@ class EE_API UISceneNode : public SceneNode {
Uint32 getCurrentMarker() const { return mCurrentMarker; }
void loadHTMLBaseCSS();
protected:
friend class EE::UI::UIWindow;
friend class EE::UI::UIWidget;

View File

@@ -1375,6 +1375,10 @@ class EE_API UIWidget : public UINode {
/* @return The size of the widget when size policy is match_parent */
Sizef getSizeFromLayoutPolicy();
void setStyleSheetProperties( const CSS::StyleSheetProperties& properties );
void setStyleSheetProperty( const CSS::StyleSheetProperty& property );
protected:
friend class UIManager;
friend class UISceneNode;

View File

@@ -3,6 +3,7 @@
#include <eepp/core.hpp>
#include <eepp/ui/uiwidget.hpp>
#include <eepp/ui/css/stylesheet.hpp>
namespace EE { namespace UI {
@@ -33,6 +34,8 @@ class EE_API UIWidgetCreator {
static std::vector<std::string> getWidgetNames();
static void loadHTMLBaseDefaults( CSS::StyleSheet& styleSheet, Uint32 marker );
protected:
static RegisteredWidgetCallbackMap registeredWidget;

View File

@@ -95,6 +95,12 @@ void StyleSheet::removeAllWithoutMarker( const Uint32& marker ) {
invalidateCache();
}
void StyleSheet::setSelectorSpecificity( const Uint32& specificity ) {
for ( auto& node : mNodes )
node->setSelectorSpecificity( specificity );
invalidateCache();
}
StyleSheet StyleSheet::getAllWithMarker( const Uint32& marker ) const {
StyleSheet style;
std::vector<std::shared_ptr<StyleSheetStyle>> hits;

View File

@@ -20,6 +20,10 @@ const Uint32& StyleSheetSelector::getSpecificity() const {
return mSpecificity;
}
void StyleSheetSelector::setSpecificity( const Uint32& specificity ) {
mSpecificity = specificity;
}
void removeExtraSpaces( std::string& string ) {
// TODO: Optimize this
String::trimInPlace( string );

View File

@@ -340,7 +340,7 @@ void StyleSheetSpecification::registerDefaultProperties() {
registerProperty( "white-space-collapse", "collapse", true ).setType( PropertyType::String );
registerProperty( "hint", "" ).setType( PropertyType::String );
registerProperty( "hint", "" ).setType( PropertyType::String ).addAlias( "placeholder" );
registerProperty( "hint-color", "" ).setType( PropertyType::Color );
registerProperty( "hint-shadow-color", "" ).setType( PropertyType::Color );
registerProperty( "hint-shadow-offset", "" ).setType( PropertyType::Vector2 );

View File

@@ -23,6 +23,14 @@ StyleSheetStyle::StyleSheetStyle( const std::string& selector,
}
}
void StyleSheetStyle::setSelectorSpecificity( const Uint32& specificity ) {
const_cast<StyleSheetSelector&>( mSelector ).setSpecificity( specificity );
for ( auto& it : mProperties )
it.second.setSpecificity( specificity );
for ( auto& it : mVariables )
it.second.setSpecificity( specificity );
}
std::string StyleSheetStyle::build( bool emitMediaQueryStart, bool emitMediaQueryEnd ) {
std::string css;

View File

@@ -524,10 +524,18 @@ void TableLayouter::updateLayout() {
}
row->setPixelsSize( containerWidth - paddingH, rowHeight );
if ( r == 0 && mCells[start]->getParent()->isType( UI_TYPE_HTML_TABLE_HEAD ) ) {
if ( r == 0 && mCells[start]->getParent() &&
( mCells[start]->getParent()->isType( UI_TYPE_HTML_TABLE_HEAD ) ||
( mCells[start]->getParent()->isType( UI_TYPE_HTML_TABLE_ROW ) &&
mCells[start]->getParent()->getParent() &&
mCells[start]->getParent()->getParent()->isType( UI_TYPE_HTML_TABLE_HEAD ) ) ) ) {
headHeight = rowHeight;
} else if ( r == rowCount - 1 && columnCount &&
mCells[start]->getParent()->isType( UI_TYPE_HTML_TABLE_FOOTER ) ) {
( mCells[start]->getParent()->isType( UI_TYPE_HTML_TABLE_FOOTER ) ||
( mCells[start]->getParent()->isType( UI_TYPE_HTML_TABLE_ROW ) &&
mCells[start]->getParent()->getParent() &&
mCells[start]->getParent()->getParent()->isType(
UI_TYPE_HTML_TABLE_FOOTER ) ) ) ) {
footerHeight = rowHeight;
} else {
bodyHeight += rowHeight;

View File

@@ -3060,6 +3060,7 @@ bool UICodeEditor::applyProperty( const StyleSheetProperty& attribute ) {
setLineWrapType( LineWrap::toLineWrapType( attribute.asString() ) );
break;
case PropertyId::Text:
case PropertyId::Value:
mDoc->textInput( attribute.asString() );
break;
case PropertyId::BackgroundColor: {

View File

@@ -202,12 +202,9 @@ UIHTMLSummary* UIHTMLSummary::New() {
UIHTMLSummary::UIHTMLSummary() : UIRichText( "summary" ) {
mDisplay = CSSDisplay::ListItem;
setFlags( UI_CREATING_NODE );
applyProperty( StyleSheetProperty( "cursor", "pointer" ) );
applyProperty( StyleSheetProperty( "padding-left", "20dp" ) );
applyProperty( StyleSheetProperty( "list-style-type", "disclosure-closed" ) );
getUIStyle()->setStyleSheetProperty( StyleSheetProperty( "cursor", "pointer" ) );
getUIStyle()->setStyleSheetProperty(
StyleSheetProperty( "list-style-type", "disclosure-closed" ) );
setStyleSheetProperty( StyleSheetProperty( "cursor", "pointer" ) );
setStyleSheetProperty( StyleSheetProperty( "padding-left", "20dp" ) );
setStyleSheetProperty( StyleSheetProperty( "list-style-type", "disclosure-closed" ) );
unsetFlags( UI_CREATING_NODE );
setFlags( UI_TAB_FOCUSABLE );
}
@@ -278,7 +275,8 @@ bool UIHTMLSummary::applyProperty( const StyleSheetProperty& attribute ) {
setListStyleType( CSSListStyleTypeHelper::fromString( attribute.value() ) );
return true;
case PropertyId::PaddingLeft:
if ( !isCreatingNode() && !mApplyingDefaultListStylePadding )
if ( !isCreatingNode() && !mApplyingDefaultListStylePadding &&
attribute.getSpecificity() != 0 )
mUsingDefaultListStylePadding = false;
break;
default:
@@ -348,7 +346,7 @@ void UIHTMLSummary::syncDefaultListStylePadding() {
return;
mApplyingDefaultListStylePadding = true;
UIRichText::applyProperty( StyleSheetProperty(
setStyleSheetProperty( StyleSheetProperty(
"padding-left", mListStyleType == CSSListStyleType::None ? "0dp" : "20dp" ) );
mApplyingDefaultListStylePadding = false;
}

View File

@@ -119,12 +119,15 @@ void UIHTMLInput::createChildWidget() {
mChildWidget = nullptr;
}
if ( mInputType == "button" || mInputType == "submit" ) {
if ( mInputType == "button" || mInputType == "submit" || mInputType == "reset" ) {
mChildWidget = UIPushButton::New();
} else if ( mInputType == "checkbox" ) {
mChildWidget = UICheckBox::New();
} else if ( mInputType == "hidden" ) {
// We don't need it
setVisible( false );
setEnabled( false );
mVisible = false;
mDisplay = CSSDisplay::None;
} else if ( mInputType == "number" ) {
mChildWidget = UISpinBox::New();
} else if ( mInputType == "password" ) {
@@ -148,6 +151,7 @@ void UIHTMLInput::createChildWidget() {
if ( mChildWidget )
setPixelsSize( mChildWidget->getPixelsSize() );
} );
for ( const auto& propIt : mProperties ) {
mChildWidget->applyProperty( propIt.second );
}

View File

@@ -23,42 +23,6 @@
namespace EE { namespace UI {
static void applyDefaultBlockMargins( UIWidget* widget, const std::string& tag ) {
static const UnorderedMap<std::string, std::pair<Float, Float>> defaultsTopBottom = {
{ "h1", { 0.67f, 0.67f } }, { "h2", { 0.83f, 0.83f } },
{ "h3", { 1.00f, 1.00f } }, { "h4", { 1.33f, 1.33f } },
{ "h5", { 1.67f, 1.67f } }, { "h6", { 2.33f, 2.33f } },
{ "p", { 1.00f, 1.00f } }, { "pre", { 1.00f, 1.00f } },
{ "blockquote", { 1.00f, 1.00f } }, { "hr", { 0.50f, 0.50f } },
{ "ul", { 1.00f, 1.00f } }, { "ol", { 1.00f, 1.00f } },
{ "dl", { 1.00f, 1.00f } }, { "body", { 0.67f, 0.67f } },
};
static const UnorderedMap<std::string, std::pair<Float, Float>> defaultsLeftRight = {
{ "body", { 0.67f, 0.67f } },
};
widget->setFlags( UI_CREATING_NODE );
auto ittb = defaultsTopBottom.find( tag );
if ( ittb != defaultsTopBottom.end() ) {
widget->applyProperty(
StyleSheetProperty( "margin-top", String::format( "%gem", ittb->second.first ) ) );
widget->applyProperty(
StyleSheetProperty( "margin-bottom", String::format( "%gem", ittb->second.second ) ) );
}
auto itlr = defaultsLeftRight.find( tag );
if ( itlr != defaultsLeftRight.end() ) {
widget->applyProperty(
StyleSheetProperty( "margin-left", String::format( "%gem", itlr->second.first ) ) );
widget->applyProperty(
StyleSheetProperty( "margin-right", String::format( "%gem", itlr->second.second ) ) );
}
widget->unsetFlags( UI_CREATING_NODE );
}
UIRichText::WhiteSpaceCollapse UIRichText::toWhiteSpaceCollapse( std::string val ) {
String::toLowerInPlace( val );
if ( "preserve" == val )
@@ -94,6 +58,7 @@ UIHTMLHtml* UIHTMLHtml::New( const std::string& tag ) {
UIHTMLHtml::UIHTMLHtml( const std::string& tag ) : UIRichText( tag ) {
enableReportSizeChangeToChildren();
getUISceneNode()->loadHTMLBaseCSS();
}
Uint32 UIHTMLHtml::getType() const {
@@ -139,6 +104,7 @@ UIHTMLBody* UIHTMLBody::New( const std::string& tag ) {
UIHTMLBody::UIHTMLBody( const std::string& tag ) : UIRichText( tag ) {
enableReportSizeChangeToChildren();
getUISceneNode()->loadHTMLBaseCSS();
}
Uint32 UIHTMLBody::getType() const {
@@ -241,7 +207,6 @@ UIRichText* UIRichText::NewHtml() {
UIRichText* UIRichText::NewBody() {
auto* body = UIHTMLBody::New( "body" );
body->setClipType( ClipType::None );
applyDefaultBlockMargins( body, body->getElementTag() );
return body;
}
@@ -251,11 +216,7 @@ UIRichText* UIRichText::NewBr() {
UIRichText* UIRichText::NewHr() {
auto* w = UILineBreak::New( "hr" );
w->setFlags( UI_CREATING_NODE );
applyDefaultBlockMargins( w, "hr" );
w->setBackgroundColor( Color::Gray );
w->mMinHeightEq = "1dp";
w->unsetFlags( UI_CREATING_NODE );
return w;
};
@@ -264,9 +225,7 @@ UIRichText* UIRichText::New() {
}
UIRichText* UIRichText::NewWithTag( const std::string& tag ) {
auto* w = eeNew( UIRichText, ( tag ) );
applyDefaultBlockMargins( w, tag );
return w;
return eeNew( UIRichText, ( tag ) );
}
UIRichText::UIRichText( const std::string& tag ) : UIHTMLWidget( tag ) {
@@ -289,6 +248,8 @@ UIRichText::UIRichText( const std::string& tag ) : UIHTMLWidget( tag ) {
sceneNode->getUIThemeManager()->getDefaultFontSize();
}
mRichText.getFontStyleConfig().FontColor = Color::Black;
setLayoutSizePolicy( SizePolicy::MatchParent, SizePolicy::WrapContent );
}

View File

@@ -1624,4 +1624,9 @@ void UISceneNode::loadFontStyleVariants( Font* font, const std::string& family )
ft->setBoldItalicFont( boldItalicFont );
}
void UISceneNode::loadHTMLBaseCSS() {
// Load HTML base defaults (idempotent - marker check prevents duplicates)
UIWidgetCreator::loadHTMLBaseDefaults( mStyleSheet, String::hash( "html_defaults" ) );
}
}} // namespace EE::UI

View File

@@ -96,6 +96,7 @@ bool UITextEdit::applyProperty( const StyleSheetProperty& attribute ) {
switch ( attribute.getPropertyDefinition()->getPropertyId() ) {
case PropertyId::Text:
case PropertyId::Value:
setText( attribute.value() );
break;
case PropertyId::Wordwrap:

View File

@@ -617,6 +617,7 @@ bool UITextInput::applyProperty( const StyleSheetProperty& attribute ) {
switch ( attribute.getPropertyDefinition()->getPropertyId() ) {
case PropertyId::Text:
case PropertyId::Value:
setText( getTranslatorString( attribute.value() ) );
break;
case PropertyId::AllowEditing:

View File

@@ -223,7 +223,7 @@ Font* UITextSpan::getFont() const {
}
UITextSpan* UITextSpan::setFont( Font* font ) {
if ( mRichText.getFontStyleConfig().Font != font ) {
if ( mRichText.getFontStyleConfig().Font != font || !( mStyleState & StyleStateFont ) ) {
mRichText.getFontStyleConfig().Font = font;
mStyleState |= StyleStateFont;
mRichText.invalidate();
@@ -238,7 +238,8 @@ Uint32 UITextSpan::getFontSize() const {
}
UITextSpan* UITextSpan::setFontSize( const Uint32& characterSize ) {
if ( mRichText.getFontStyleConfig().CharacterSize != characterSize ) {
if ( mRichText.getFontStyleConfig().CharacterSize != characterSize ||
!( mStyleState & StyleStateFontSize ) ) {
if ( characterSize == 0 )
return this;
mRichText.getFontStyleConfig().CharacterSize = characterSize;
@@ -295,7 +296,8 @@ const Float& UITextSpan::getOutlineThickness() const {
}
UITextSpan* UITextSpan::setOutlineThickness( const Float& outlineThickness ) {
if ( mRichText.getFontStyleConfig().OutlineThickness != outlineThickness ) {
if ( mRichText.getFontStyleConfig().OutlineThickness != outlineThickness ||
!( mStyleState & StyleStateOutlineThickness ) ) {
mRichText.getFontStyleConfig().OutlineThickness = outlineThickness;
mStyleState |= StyleStateOutlineThickness;
mRichText.invalidate();
@@ -311,7 +313,8 @@ const Color& UITextSpan::getOutlineColor() const {
}
UITextSpan* UITextSpan::setOutlineColor( const Color& outlineColor ) {
if ( mRichText.getFontStyleConfig().OutlineColor != outlineColor ) {
if ( mRichText.getFontStyleConfig().OutlineColor != outlineColor ||
!( mStyleState & StyleStateOutlineColor ) ) {
mRichText.getFontStyleConfig().OutlineColor = outlineColor;
mStyleState |= StyleStateOutlineColor;
mRichText.invalidate();
@@ -326,7 +329,8 @@ const Color& UITextSpan::getFontColor() const {
}
UITextSpan* UITextSpan::setFontColor( const Color& color ) {
if ( mRichText.getFontStyleConfig().FontColor != color ) {
if ( mRichText.getFontStyleConfig().FontColor != color ||
!( mStyleState & StyleStateFontColor ) ) {
mRichText.getFontStyleConfig().FontColor = color;
mStyleState |= StyleStateFontColor;
mRichText.invalidate();
@@ -664,6 +668,9 @@ bool UIAnchorSpan::applyProperty( const StyleSheetProperty& attribute ) {
case PropertyId::Href:
setHref( attribute.asString() );
break;
case PropertyId::Color:
setFontColor( attribute.asColor() );
break;
default:
UITextSpan::applyProperty( attribute );
break;

View File

@@ -21,6 +21,8 @@ UIWebView::UIWebView() : UIScrollView( "webview" ) {
mDocContainer->setFlags( UI_OWNS_CHILDREN_POSITION );
mDocContainer->setLayoutSizePolicy( SizePolicy::MatchParent, SizePolicy::WrapContent );
mDocContainer->setParent( this );
mDocContainer->setBackgroundColor( Color::White );
mStyleSheetDefaultMarker = String::hash( "html_defaults" );
}
UIWebView::~UIWebView() {}

View File

@@ -2816,4 +2816,25 @@ Float UIWidget::getPropertyHeight() const {
}
return 0.f;
}
void UIWidget::setStyleSheetProperties( const CSS::StyleSheetProperties& properties ) {
mStyle->setStyleSheetProperties( properties );
for ( const auto& [_, property] : properties )
applyProperty( property );
}
void UIWidget::setStyleSheetProperty( const CSS::StyleSheetProperty& property ) {
mStyle->setStyleSheetProperty( property );
if ( StyleSheetSpecification::instance()->isShorthand( property.getName() ) ) {
auto properties = StyleSheetSpecification::instance()
->getShorthand( property.getName() )
->parse( property.getValue() );
for ( const auto& prop : properties ) {
applyProperty( prop );
}
} else
applyProperty( property );
}
}} // namespace EE::UI

View File

@@ -1,3 +1,4 @@
#include <eepp/ui/css/stylesheetparser.hpp>
#include <eepp/ui/tools/uidiffview.hpp>
#include <eepp/ui/tools/uiimageviewer.hpp>
#include <eepp/ui/tools/uitextureviewer.hpp>
@@ -66,6 +67,124 @@ UIWidgetCreator::WidgetCallbackMap UIWidgetCreator::widgetCallback =
UIWidgetCreator::RegisteredWidgetCallbackMap UIWidgetCreator::registeredWidget =
UIWidgetCreator::RegisteredWidgetCallbackMap();
static const std::string_view getHTMLBaseDefaultsCSS() {
return R"css(
body { color: black; margin: 0.67em; }
h1 { font-size: 2em; font-weight: bold; margin: 0.67em 0; }
h2 { font-size: 1.5em; font-weight: bold; margin: 0.83em 0; }
h3 { font-size: 1.17em; font-weight: bold; margin: 1em 0; }
h4 { font-size: 1em; font-weight: bold; margin: 1.33em 0; }
h5 { font-size: 0.83em; font-weight: bold; margin: 1.67em 0; }
h6 { font-size: 0.67em; font-weight: bold; margin: 2.33em 0; }
p { margin: 1em 0; }
pre { margin: 1em 0; }
blockquote { margin: 1em 0; }
hr { margin: 0.5em 0; background-color: gray; height: 1dp; }
ul, ol, dl { margin: 1em 0; }
b, strong { font-weight: bold; }
i, em, cite { font-style: italic; }
small { font-size: smaller; }
u, ins { text-decoration: underline; }
s, del { text-decoration: line-through; }
code, kbd { font-family: monospace; }
sub, sup { font-size: smaller; }
mark { background-color: yellow; }
a, a:link { color: #0000EE; text-decoration: none; cursor: arrow; }
a:hover { text-decoration: underline; cursor: hand; }
a:visited { color: #551A8B; }
ul { padding-left: 40dp; list-style-type: disc; }
ol { padding-left: 40dp; list-style-type: decimal; }
dd { margin-left: 40dp; }
summary { cursor: pointer; padding-left: 20dp; list-style-type: disclosure-closed; }
textarea { border-width: 1dp; border-color: #767676; background-color: white; color: black; padding: 2dp; selection-back-color: lightgray; }
input[type="text"],
input[type="password"],
input[type="number"] {
border-width: 1dp;
border-color: #767676;
background-color: white;
color: black;
padding-top: 1dp;
padding-bottom: 1dp;
padding-left: 2dp;
padding-right: 2dp;
hint-color: #767676;
}
input[type="submit"],
input[type="button"],
input[type="reset"] {
border-width: 1dp;
border-color: #767676;
background-color: #f0f0f0;
color: black;
padding-top: 2dp;
padding-bottom: 3dp;
padding-left: 6dp;
padding-right: 6dp;
text-decoration: none;
}
input[type="submit"]:hover,
input[type="button"]:hover,
input[type="reset"]:hover {
background-color: #e5e5e5;
}
input[type="checkbox"],
input[type="radio"] {
text-decoration: none;
}
CheckBox::active,
CheckBox::inactive {
width: 12dp;
height: 12dp;
border-width: 1dp;
}
CheckBox::inactive {
border-color: #767676;
}
CheckBox::active {
border-color: black;
foreground-image: rectangle(solid, black);
foreground-size: 8dpru 8dpru;
foreground-position: center;
}
RadioButton::active,
RadioButton::inactive {
width: 12dp;
height: 12dp;
border-width: 1dp;
border-radius: 100%;
}
RadioButton::inactive {
border-color: #767676;
}
RadioButton::active {
border-color: black;
foreground-image: circle(solid, black);
foreground-size: 8dp 8dp;
foreground-position: 6dp 6dp;
}
)css";
}
void UIWidgetCreator::createBaseWidgetList() {
if ( !sBaseListCreated ) {
registeredWidget["widget"] = UIWidget::New;
@@ -171,35 +290,11 @@ void UIWidgetCreator::createBaseWidgetList() {
registeredWidget["h6"] = UIRichText::NewH6;
registeredWidget["br"] = UIRichText::NewBr;
registeredWidget["hr"] = UIRichText::NewHr;
registeredWidget["ul"] = [] {
auto* w = UIRichText::NewWithTag( "ul" );
w->setFlags( UI_CREATING_NODE );
w->applyProperty( StyleSheetProperty( "padding-left", "40dp" ) );
w->applyProperty( StyleSheetProperty( "list-style-type", "disc" ) );
w->getUIStyle()->setStyleSheetProperty(
StyleSheetProperty( "list-style-type", "disc" ) );
w->unsetFlags( UI_CREATING_NODE );
return w;
};
registeredWidget["ol"] = [] {
auto* w = UIRichText::NewWithTag( "ol" );
w->setFlags( UI_CREATING_NODE );
w->applyProperty( StyleSheetProperty( "padding-left", "40dp" ) );
w->applyProperty( StyleSheetProperty( "list-style-type", "decimal" ) );
w->getUIStyle()->setStyleSheetProperty(
StyleSheetProperty( "list-style-type", "decimal" ) );
w->unsetFlags( UI_CREATING_NODE );
return w;
};
registeredWidget["ul"] = [] { return UIRichText::NewWithTag( "ul" ); };
registeredWidget["ol"] = [] { return UIRichText::NewWithTag( "ol" ); };
registeredWidget["dl"] = [] { return UIRichText::NewWithTag( "dl" ); };
registeredWidget["dt"] = [] { return UIRichText::NewWithTag( "dt" ); };
registeredWidget["dd"] = [] {
auto* w = UIRichText::NewWithTag( "dd" );
w->setFlags( UI_CREATING_NODE );
w->applyProperty( StyleSheetProperty( "margin-left", "40dp" ) );
w->unsetFlags( UI_CREATING_NODE );
return w;
};
registeredWidget["dd"] = [] { return UIRichText::NewWithTag( "dd" ); };
registeredWidget["li"] = UIHTMLListItem::New;
registeredWidget["details"] = UIHTMLDetails::New;
registeredWidget["summary"] = UIHTMLSummary::New;
@@ -250,10 +345,8 @@ void UIWidgetCreator::createBaseWidgetList() {
registeredWidget["textarea"] = UIHTMLTextArea::New;
registeredWidget["button"] = [] {
auto but = UIPushButton::NewWithTag( "button" );
but->setFlags( UI_CREATING_NODE );
but->setFlags( UI_HTML_ELEMENT );
but->setLayoutSizePolicy( SizePolicy::WrapContent, SizePolicy::WrapContent );
but->unsetFlags( UI_CREATING_NODE );
return but;
};
registeredWidget["webview"] = UIWebView::New;
@@ -322,4 +415,16 @@ std::vector<std::string> UIWidgetCreator::getWidgetNames() {
return names;
}
void UIWidgetCreator::loadHTMLBaseDefaults( CSS::StyleSheet& styleSheet, Uint32 marker ) {
if ( styleSheet.markerExists( marker ) )
return;
CSS::StyleSheetParser parser;
if ( parser.loadFromString( getHTMLBaseDefaultsCSS() ) ) {
CSS::StyleSheet baseDefaults = parser.getStyleSheet();
baseDefaults.setSelectorSpecificity( 0 );
baseDefaults.setMarker( marker );
styleSheet.combineStyleSheet( baseDefaults );
}
}
}} // namespace EE::UI

View File

@@ -1,4 +1,5 @@
#include "compareimages.hpp"
#include "eepp/ui/uiwindow.hpp"
#include "utest.hpp"
#include <eepp/graphics/fontfamily.hpp>
@@ -782,13 +783,17 @@ UTEST( UIHTMLDetails, summaryListStyleNoneClearsDefaultPadding ) {
init_ui_test();
auto* sceneNode = SceneManager::instance()->getUISceneNode();
sceneNode->loadLayoutFromString( HTMLFormatter::HTMLtoXML( R"html(
<details>
<summary id="default">Default marker</summary>
<summary id="none" style="list-style-type: none;">No marker</summary>
<summary id="explicit" style="list-style-type: none; padding-left: 7px;">
Explicit padding
</summary>
</details>
<html>
<body>
<details>
<summary id="default">Default marker</summary>
<summary id="none" style="list-style-type: none;">No marker</summary>
<summary id="explicit" style="list-style-type: none; padding-left: 7px;">
Explicit padding
</summary>
</details>
</body>
</html>
)html" ) );
sceneNode->updateDirtyLayouts();