Add support for --var: var(--other-var).

Reset global definition for body and html elements when new styles are loaded (required when these elements have only variable definitions).
This commit is contained in:
Martín Lucas Golini
2026-05-11 21:22:49 -03:00
parent 4fa495f0e1
commit 0f8d930e80
4 changed files with 219 additions and 35 deletions

View File

@@ -40,6 +40,8 @@ class EE_API ElementDefinition : NonCopyable {
bool mStructurallyVolatile;
void findVariables( const CSS::StyleSheetStyle* style );
void resolveVariables();
};
}}} // namespace EE::UI::CSS

View File

@@ -1,3 +1,4 @@
#include <eepp/system/functionstring.hpp>
#include <eepp/ui/css/elementdefinition.hpp>
namespace EE { namespace UI { namespace CSS {
@@ -70,6 +71,8 @@ void ElementDefinition::refresh() {
findVariables( styleSheetStyle );
}
resolveVariables();
for ( auto& property : mProperties )
mPropertyIds.insert( property.first );
}
@@ -78,11 +81,68 @@ void ElementDefinition::findVariables( const StyleSheetStyle* style ) {
for ( const auto& vars : style->getVariables() ) {
const StyleSheetVariable& variable = vars.second;
const auto& it = mVariables.find( variable.getNameHash() );
if ( it == mVariables.end() || variable.getSpecificity() >= it->second.getSpecificity() ) {
mVariables[variable.getNameHash()] = variable;
}
}
}
static void resolveVarValue( std::string& value, const StyleSheetVariables& variables,
UnorderedSet<String::HashType>& visited, int depth = 0 ) {
static constexpr int maxDepth = 32;
if ( depth > maxDepth )
return;
std::string::size_type tokenStart = 0;
while ( true ) {
tokenStart = value.find( "var(", tokenStart );
if ( tokenStart == std::string::npos )
return;
std::string::size_type tokenEnd = String::findCloseBracket( value, tokenStart, '(', ')' );
if ( tokenEnd == std::string::npos )
return;
std::string varDef( value, tokenStart, tokenEnd + 1 - tokenStart );
System::FunctionString functionType = System::FunctionString::parse( varDef );
if ( functionType.getParameters().empty() ) {
tokenStart = tokenEnd;
continue;
}
bool resolved = false;
for ( auto& param : functionType.getParameters() ) {
if ( String::startsWith( param, "--" ) ) {
auto it = variables.find( String::hash( param ) );
if ( it != variables.end() &&
visited.find( it->second.getNameHash() ) == visited.end() ) {
visited.insert( it->second.getNameHash() );
std::string deepValue( it->second.getValue() );
resolveVarValue( deepValue, variables, visited, depth + 1 );
String::replaceAll( value, varDef, deepValue );
resolved = true;
break;
}
}
}
if ( resolved )
tokenStart = 0;
else
tokenStart = tokenEnd;
}
}
void ElementDefinition::resolveVariables() {
static UnorderedSet<String::HashType> visited;
for ( auto& varPair : mVariables ) {
std::string value( varPair.second.getValue() );
visited.clear();
resolveVarValue( value, mVariables, visited );
varPair.second.setValue( value );
}
}
}}} // namespace EE::UI::CSS

View File

@@ -422,6 +422,14 @@ void UISceneNode::updateStyleSheet( bool forceReloadStyle ) {
if ( mRoot && mRoot->getUIStyle() )
mRoot->getUIStyle()->resetGlobalDefinition();
auto bodies = mRoot->findAllByType( UI_TYPE_HTML_BODY );
for ( auto body : bodies )
body->asType<UIWidget>()->getUIStyle()->resetGlobalDefinition();
auto htmls = mRoot->findAllByType( UI_TYPE_HTML_HTML );
for ( auto html : htmls )
html->asType<UIWidget>()->getUIStyle()->resetGlobalDefinition();
if ( mRoot && mediaChanged )
mRoot->reportStyleStateChangeRecursive( false, false );

View File

@@ -1,6 +1,7 @@
#include "utest.hpp"
#include <eepp/graphics/font.hpp>
#include <eepp/graphics/fontmanager.hpp>
#include <eepp/graphics/pixeldensity.hpp>
#include <eepp/scene/node.hpp>
#include <eepp/system/filesystem.hpp>
#include <eepp/ui/css/stylesheet.hpp>
@@ -354,8 +355,7 @@ UTEST( CSSUnits, ExChWithFont ) {
WindowBackend::Default, 32 ),
UIApplication::Settings( Sys::getProcessPath() + ".." + FileSystem::getOSSlash() ), 1.f );
Graphics::Font* font =
app.getUI()->getUIThemeManager()->getDefaultFont();
Graphics::Font* font = app.getUI()->getUIThemeManager()->getDefaultFont();
EXPECT_TRUE( font != nullptr );
Float elFontSize = 24.f;
@@ -374,49 +374,163 @@ UTEST( CSSUnits, ExChWithFont ) {
EXPECT_NE( fallback, resultCh );
}
UTEST( CSSUnits, Integration ) {
for ( Float scale : { 1.f, 1.5f, 2.f } ) {
UTEST_PRINT_STEP( String::format( "SCALE %.1f", scale ).c_str() );
UIApplication app( WindowSettings( 800, 600, "eepp - CSS Units Ex/Ch Integration Test",
WindowStyle::Default, WindowBackend::Default, 32 ),
UIApplication::Settings(
Sys::getProcessPath() + ".." + FileSystem::getOSSlash(), scale ) );
UTEST( CSSVariables, VariableReferencesSimple ) {
UIApplication app(
WindowSettings( 800, 600, "eepp - CSS Var Ref Test 1", WindowStyle::Default,
WindowBackend::Default, 32 ),
UIApplication::Settings( Sys::getProcessPath() + ".." + FileSystem::getOSSlash() ), 1 );
std::string xml = R"(
std::string xml = R"(
<html>
<head>
<style>
.text {
font-size: 20px;
}
#exbox {
width: 10ex;
height: 2ex;
}
#chbox {
width: 10ch;
height: 2ch;
body {
--primary: #FF0000;
--text-color: var(--primary);
color: var(--text-color);
}
</style>
</head>
<body>
<div id="exbox" class="text">x</div>
<div id="chbox" class="text">0</div>
<div id="testdiv">Test text</div>
</body>
</html>
)";
)";
UIWidget* root = app.getUI()->loadLayoutFromString( xml );
EXPECT_TRUE( root != nullptr );
UIWidget* root = app.getUI()->loadLayoutFromString( xml );
EXPECT_TRUE( root != nullptr );
UIRichText* exbox = root->querySelector( "#exbox" )->asType<UIRichText>();
EXPECT_TRUE( exbox != nullptr );
EXPECT_GT( exbox->getPixelsSize().getWidth(), 0.f );
EXPECT_GT( exbox->getPixelsSize().getHeight(), 0.f );
UIRichText* div = root->querySelector( "#testdiv" )->asType<UIRichText>();
EXPECT_TRUE( div != nullptr );
UIRichText* chbox = root->querySelector( "#chbox" )->asType<UIRichText>();
EXPECT_TRUE( chbox != nullptr );
EXPECT_GT( chbox->getPixelsSize().getWidth(), 0.f );
EXPECT_GT( chbox->getPixelsSize().getHeight(), 0.f );
}
EXPECT_TRUE( Color( "#FF0000" ) == div->getFontColor() );
}
UTEST( CSSVariables, VariableReferencesChain ) {
UIApplication app(
WindowSettings( 800, 600, "eepp - CSS Var Ref Test 2", WindowStyle::Default,
WindowBackend::Default, 32 ),
UIApplication::Settings( Sys::getProcessPath() + ".." + FileSystem::getOSSlash() ), 1 );
std::string xml = R"(
<html>
<head>
<style>
body {
--a: #00FF00;
--b: var(--a);
--c: var(--b);
color: var(--c);
}
</style>
</head>
<body>
<div id="testdiv">Test text</div>
</body>
</html>
)";
UIWidget* root = app.getUI()->loadLayoutFromString( xml );
EXPECT_TRUE( root != nullptr );
UIRichText* div = root->querySelector( "#testdiv" )->asType<UIRichText>();
EXPECT_TRUE( div != nullptr );
EXPECT_TRUE( Color( "#00FF00" ) == div->getFontColor() );
}
UTEST( CSSVariables, VariableReferencesWithPadding ) {
UIApplication app(
WindowSettings( 800, 600, "eepp - CSS Var Ref Test 3", WindowStyle::Default,
WindowBackend::Default, 32 ),
UIApplication::Settings( Sys::getProcessPath() + ".." + FileSystem::getOSSlash() ), 1 );
std::string xml = R"(
<html>
<head>
<style>
#testdiv {
--base: 20px;
--spacing: var(--base);
padding: var(--spacing);
}
</style>
</head>
<body>
<div id="testdiv">Test text</div>
</body>
</html>
)";
UIWidget* root = app.getUI()->loadLayoutFromString( xml );
EXPECT_TRUE( root != nullptr );
UIWidget* div = root->querySelector( "#testdiv" );
EXPECT_TRUE( div != nullptr );
Float padding = PixelDensity::dpToPx( div->getPadding().Left );
EXPECT_NEAR( 20.f, padding, 1.f );
}
UTEST( CSSVariables, VariableReferencesMultiple ) {
UIApplication app(
WindowSettings( 800, 600, "eepp - CSS Var Ref Test 4", WindowStyle::Default,
WindowBackend::Default, 32 ),
UIApplication::Settings( Sys::getProcessPath() + ".." + FileSystem::getOSSlash() ), 1 );
std::string xml = R"(
<html>
<head>
<style>
#testdiv {
--color1: #0000FF;
--color2: var(--color1);
--color3: var(--color2);
color: var(--color3);
}
</style>
</head>
<body>
<div id="testdiv">Test text</div>
</body>
</html>
)";
UIWidget* root = app.getUI()->loadLayoutFromString( xml );
EXPECT_TRUE( root != nullptr );
UIRichText* div = root->querySelector( "#testdiv" )->asType<UIRichText>();
EXPECT_TRUE( div != nullptr );
EXPECT_TRUE( Color( "#0000FF" ) == div->getFontColor() );
}
UTEST( CSSVariables, VariableReferencesCircular ) {
UIApplication app(
WindowSettings( 800, 600, "eepp - CSS Var Ref Test 5", WindowStyle::Default,
WindowBackend::Default, 32 ),
UIApplication::Settings( Sys::getProcessPath() + ".." + FileSystem::getOSSlash() ), 1 );
std::string xml = R"(
<html>
<head>
<style>
body {
--x: var(--y);
--y: var(--x);
color: var(--x, #FF0000);
}
</style>
</head>
<body>
<div id="testdiv">Test text</div>
</body>
</html>
)";
UIWidget* root = app.getUI()->loadLayoutFromString( xml );
EXPECT_TRUE( root != nullptr );
UIRichText* div = root->querySelector( "#testdiv" )->asType<UIRichText>();
EXPECT_TRUE( div != nullptr );
}