Fix importing CSS within CSS.

Fix in colors with vars processing.
Added prefers-contrast support at least for parsing.
This commit is contained in:
Martín Lucas Golini
2026-06-02 18:37:41 -03:00
parent b97232cecc
commit 7795ec2fc3
13 changed files with 519 additions and 35 deletions
@@ -17,6 +17,17 @@ enum class ColorSchemeExtPreference : std::underlying_type_t<ColorSchemePreferen
System
};
enum class ContrastPreference { NoPreference, More, Less, Custom };
enum class ContrastExtPreference : std::underlying_type_t<ContrastPreference> {
NoPreference =
static_cast<std::underlying_type_t<ContrastPreference>>( ContrastPreference::NoPreference ),
More = static_cast<std::underlying_type_t<ContrastPreference>>( ContrastPreference::More ),
Less = static_cast<std::underlying_type_t<ContrastPreference>>( ContrastPreference::Less ),
Custom = static_cast<std::underlying_type_t<ContrastPreference>>( ContrastPreference::Custom ),
System
};
struct EE_API ColorSchemePreferences {
static ColorSchemePreference fromString( std::string_view str );
@@ -31,4 +42,18 @@ struct EE_API ColorSchemePreferences {
static std::string toString( ColorSchemeExtPreference pref );
};
struct EE_API ContrastPreferences {
static ContrastPreference fromString( std::string_view str );
static ContrastExtPreference fromStringExt( std::string_view str );
static ContrastPreference fromExt( ContrastExtPreference pref );
static ContrastExtPreference toExt( ContrastPreference pref );
static std::string toString( ContrastPreference pref );
static std::string toString( ContrastExtPreference pref );
};
} // namespace EE::UI
+6 -3
View File
@@ -65,6 +65,8 @@ enum MediaFeature {
media_feature_max_pixel_density,
media_feature_prefers_color_scheme,
media_feature_prefers_contrast,
};
enum MediaType {
@@ -102,9 +104,10 @@ struct MediaFeatures {
// device does not use a color lookup table, the value is zero.
int monochrome; // The number of bits per pixel in a monochrome frame buffer. If the device is
// not a monochrome device, the output device value will be 0.
int resolution; // The resolution of the output device (in DPI)
float pixelDensity; // Screen pixel density
std::string prefersColorScheme{ "dark" }; // Color Scheme Default Preference
int resolution; // The resolution of the output device (in DPI)
float pixelDensity; // Screen pixel density
std::string prefersColorScheme{ "dark" }; // Color Scheme Default Preference
std::string prefersContrast{ "no-preference" }; // Contrast Preference
};
struct EE_API MediaQueryExpression {
+6
View File
@@ -2,6 +2,7 @@
#define EE_UI_CSS_STYLESHEETPARSER
#include <eepp/core/string.hpp>
#include <eepp/network/uri.hpp>
#include <eepp/system/iostream.hpp>
#include <eepp/ui/css/mediaquery.hpp>
#include <eepp/ui/css/stylesheet.hpp>
@@ -38,6 +39,10 @@ class EE_API StyleSheetParser {
const bool& isLoaded() const;
void setBaseURI( const Network::URI& uri );
void setBaseURI( const std::string& uri );
protected:
enum ReadState { ReadingSelector, ReadingProperty, ReadingComment };
@@ -46,6 +51,7 @@ class EE_API StyleSheetParser {
std::vector<std::string> mComments;
MediaQueryList::ptr mMediaQueryList;
bool mLoaded;
Network::URI mBaseURI;
bool parse( std::string& css, std::vector<std::string>& importedList );
+19
View File
@@ -609,6 +609,24 @@ class EE_API UISceneNode : public SceneNode {
*/
void setColorSchemePreference( const ColorSchemePreference& colorSchemePreference );
/**
* @brief Gets the current contrast preference.
* @return The ContrastPreference.
*/
ContrastPreference getContrastPreference() const;
/**
* @brief Sets the contrast preference from extended preference.
* @param contrastPreference The extended ContrastExtPreference.
*/
void setContrastPreference( const ContrastExtPreference& contrastPreference );
/**
* @brief Sets the contrast preference directly.
* @param contrastPreference The ContrastPreference.
*/
void setContrastPreference( const ContrastPreference& contrastPreference );
/**
* @brief Gets the maximum invalidation depth.
*
@@ -771,6 +789,7 @@ class EE_API UISceneNode : public SceneNode {
UnorderedSet<UILayout*> mDirtyLayouts;
std::vector<std::pair<Float, std::string>> mTimes;
ColorSchemePreference mColorSchemePreference{ ColorSchemePreference::Dark };
ContrastPreference mContrastPreference{ ContrastPreference::NoPreference };
Uint32 mMaxInvalidationDepth{ 2 };
Node* mCurParent{ nullptr };
Uint32 mCurOnSizeChangeListener{ 0 };
+20 -7
View File
@@ -112,9 +112,8 @@ static bool parseHslHue( Float& out, std::string_view tok ) {
tok.remove_suffix( 1 );
if ( tok.empty() )
return false;
if ( tok.size() == 4 &&
( std::strncmp( tok.data(), "none", 4 ) == 0 ||
std::strncmp( tok.data(), "None", 4 ) == 0 ) ) {
if ( tok.size() == 4 && ( std::strncmp( tok.data(), "none", 4 ) == 0 ||
std::strncmp( tok.data(), "None", 4 ) == 0 ) ) {
out = 0;
return true;
}
@@ -140,9 +139,8 @@ static bool parseHslPercentOrNumber( Float& out, std::string_view tok ) {
tok.remove_suffix( 1 );
if ( tok.empty() )
return false;
if ( tok.size() == 4 &&
( std::strncmp( tok.data(), "none", 4 ) == 0 ||
std::strncmp( tok.data(), "None", 4 ) == 0 ) ) {
if ( tok.size() == 4 && ( std::strncmp( tok.data(), "none", 4 ) == 0 ||
std::strncmp( tok.data(), "None", 4 ) == 0 ) ) {
out = 0;
return true;
}
@@ -757,7 +755,22 @@ Color Color::fromString( std::string str ) {
if ( functionString.getParameters().size() >= 3 &&
( functionString.getName() == "rgb" || functionString.getName() == "rgba" ) ) {
Color color( Color::Transparent );
const auto& params = functionString.getParameters();
auto params = functionString.getParameters();
// Handle "rgb(r, g, b / alpha)" hybrid syntax produced by var()
// substitution where comma-separated channels are mixed with the
// modern-slash alpha separator.
if ( params.size() == 3 ) {
std::string::size_type slashPos = params[2].find( '/' );
if ( slashPos != std::string::npos ) {
std::string alphaStr = String::trim( params[2].substr( slashPos + 1 ) );
std::string blueStr = String::trim( params[2].substr( 0, slashPos ) );
if ( !blueStr.empty() ) {
params[2] = blueStr;
params.push_back( alphaStr );
}
}
}
Float val = 0;
if ( !parseCssColorComponent( val, params[0].data(),
+52
View File
@@ -56,4 +56,56 @@ std::string ColorSchemePreferences::toString( ColorSchemeExtPreference pref ) {
return "system";
}
ContrastPreference ContrastPreferences::fromString( std::string_view str ) {
if ( str == "more"sv )
return ContrastPreference::More;
if ( str == "less"sv )
return ContrastPreference::Less;
if ( str == "custom"sv )
return ContrastPreference::Custom;
return ContrastPreference::NoPreference;
}
ContrastExtPreference ContrastPreferences::fromStringExt( std::string_view str ) {
if ( str == "system"sv )
return ContrastExtPreference::System;
if ( str == "more"sv )
return ContrastExtPreference::More;
if ( str == "less"sv )
return ContrastExtPreference::Less;
if ( str == "custom"sv )
return ContrastExtPreference::Custom;
return ContrastExtPreference::NoPreference;
}
ContrastPreference ContrastPreferences::fromExt( ContrastExtPreference pref ) {
if ( pref == ContrastExtPreference::System )
return ContrastPreference::NoPreference;
return static_cast<ContrastPreference>( pref );
}
ContrastExtPreference ContrastPreferences::toExt( ContrastPreference pref ) {
return static_cast<ContrastExtPreference>( pref );
}
std::string ContrastPreferences::toString( ContrastPreference pref ) {
switch ( pref ) {
case ContrastPreference::More:
return "more";
case ContrastPreference::Less:
return "less";
case ContrastPreference::Custom:
return "custom";
default:
break;
}
return "no-preference";
}
std::string ContrastPreferences::toString( ContrastExtPreference pref ) {
if ( pref == ContrastExtPreference::System )
return "system";
return toString( static_cast<ContrastPreference>( pref ) );
}
} // namespace EE::UI
+12 -7
View File
@@ -10,13 +10,14 @@ namespace EE { namespace UI { namespace CSS {
#define MediaOrientationStrings "portrait;landscape"
#define MediaFeatureStrings \
"none;width;min-width;max-width;height;min-height;max-height;device-width;min-device-" \
"width;max-device-width;device-height;min-device-height;max-device-height;orientation;" \
"aspect-ratio;min-aspect-ratio;max-aspect-ratio;device-aspect-ratio;min-device-aspect-" \
"ratio;max-device-aspect-ratio;color;min-color;max-color;color-index;min-color-index;max-" \
"color-index;monochrome;min-monochrome;max-monochrome;resolution;min-resolution;max-" \
"resolution;pixel-density;min-pixel-density;max-pixel-density;prefers-color-scheme"
#define MediaFeatureStrings \
"none;width;min-width;max-width;height;min-height;max-height;device-width;min-device-" \
"width;max-device-width;device-height;min-device-height;max-device-height;orientation;" \
"aspect-ratio;min-aspect-ratio;max-aspect-ratio;device-aspect-ratio;min-device-aspect-" \
"ratio;max-device-aspect-ratio;color;min-color;max-color;color-index;min-color-index;max-" \
"color-index;monochrome;min-monochrome;max-monochrome;resolution;min-resolution;max-" \
"resolution;pixel-density;min-pixel-density;max-pixel-density;prefers-color-scheme;prefers-" \
"contrast"
#define MediaTypeStrings "none;all;screen;print;braille;embossed;handheld;projection;speech;tty;tv"
@@ -416,6 +417,10 @@ bool MediaQueryExpression::check( const MediaFeatures& features ) const {
if ( features.prefersColorScheme == valStr )
return true;
break;
case media_feature_prefers_contrast:
if ( features.prefersContrast == valStr )
return true;
break;
default:
return false;
}
+16
View File
@@ -23,6 +23,14 @@ namespace EE { namespace UI { namespace CSS {
StyleSheetParser::StyleSheetParser() : mLoaded( false ) {}
void StyleSheetParser::setBaseURI( const URI& uri ) {
mBaseURI = uri;
}
void StyleSheetParser::setBaseURI( const std::string& uri ) {
mBaseURI = URI( uri );
}
bool StyleSheetParser::loadFromStream( IOStream& stream ) {
Clock elapsed;
std::vector<std::string> importedList;
@@ -332,6 +340,14 @@ void StyleSheetParser::importParse( std::string& css, std::size_t& pos, std::str
path = function.getParameters().at( 0 );
}
// Resolve relative @import paths against the base URI (document URL)
if ( !mBaseURI.empty() && !String::startsWith( path, "http://" ) &&
!String::startsWith( path, "https://" ) ) {
URI resolved = mBaseURI;
resolved.resolve( URI( path ) );
path = resolved.toString();
}
if ( std::find( importedList.begin(), importedList.end(), path ) == importedList.end() ) {
std::string newCss( importCSS( path, importedList ) );
+6 -1
View File
@@ -1,4 +1,5 @@
#include <cmath>
#include <eepp/core/debug.hpp>
#include <eepp/graphics/fontmanager.hpp>
#include <eepp/graphics/primitives.hpp>
#include <eepp/graphics/text.hpp>
@@ -425,7 +426,10 @@ bool UIRichText::applyProperty( const StyleSheetProperty& attribute ) {
Color color = attribute.asColor();
if ( color == Color::Transparent &&
attribute.getValue().find( "var(" ) != std::string::npos ) {
// Do not set unresolved colors
#ifdef EE_DEBUG
eePRINTL( "UIRichText: unresolved var() in Color value for <%s>: '%s'",
getElementTag().c_str(), attribute.getValue().c_str() );
#endif
break;
}
setFontColor( attribute.asColor() );
@@ -1006,6 +1010,7 @@ void UIRichText::loadFromXmlNode( const pugi::xml_node& node ) {
}
} else if ( String::iequals( child.name(), "style" ) ) {
CSS::StyleSheetParser parser;
parser.setBaseURI( getUISceneNode()->getURI() );
std::string styleContent;
for ( pugi::xml_node styleChild = child.first_child(); styleChild;
styleChild = styleChild.next_sibling() ) {
+23
View File
@@ -276,6 +276,7 @@ std::vector<UIWidget*> UISceneNode::loadNode( pugi::xml_node node, Node* parent,
if ( String::iequals( widget.name(), "style" ) ) {
CSS::StyleSheetParser parser;
parser.setBaseURI( mURI );
std::string styleContent;
for ( pugi::xml_node child = widget.first_child(); child;
child = child.next_sibling() ) {
@@ -459,6 +460,7 @@ void UISceneNode::combineStyleSheet( const CSS::StyleSheet& styleSheet, bool for
void UISceneNode::combineStyleSheet( const std::string& inlineStyleSheet, bool forceReloadStyle,
const Uint32& marker, URI baseURI ) {
CSS::StyleSheetParser parser;
parser.setBaseURI( baseURI );
if ( parser.loadFromString( inlineStyleSheet ) ) {
parser.getStyleSheet().setMarker( marker );
@@ -989,6 +991,7 @@ CSS::MediaFeatures UISceneNode::getMediaFeatures() const {
media.pixelDensity = PixelDensity::getPixelDensity();
media.prefersColorScheme =
mColorSchemePreference == ColorSchemePreference::Dark ? "dark" : "light";
media.prefersContrast = ContrastPreferences::toString( mContrastPreference );
return media;
}
@@ -1421,6 +1424,26 @@ void UISceneNode::setColorSchemePreference( const ColorSchemePreference& colorSc
}
}
ContrastPreference UISceneNode::getContrastPreference() const {
return mContrastPreference;
}
void UISceneNode::setContrastPreference( const ContrastExtPreference& contrastPreference ) {
setContrastPreference( ContrastPreferences::fromExt( contrastPreference ) );
}
void UISceneNode::setContrastPreference( const ContrastPreference& contrastPreference ) {
if ( mContrastPreference != contrastPreference ) {
mContrastPreference = contrastPreference;
if ( !mStyleSheet.isMediaQueryListEmpty() ) {
if ( mStyleSheet.updateMediaLists( getMediaFeatures() ) ) {
mStyleSheet.invalidateCache();
mRoot->reloadStyle( true, true, true, true, true );
}
}
}
}
const Uint32& UISceneNode::getMaxInvalidationDepth() const {
return mMaxInvalidationDepth;
}
+25 -13
View File
@@ -284,24 +284,36 @@ void UIStyle::applyLightDarkValue( std::string& value ) {
}
void UIStyle::setVariableFromValue( StyleSheetProperty* property, const std::string& value ) {
if ( !property->getVarCache().empty() ) {
std::string newValue( value );
for ( auto& var : property->getVarCache() ) {
for ( auto& val : var.variableList ) {
StyleSheetVariable variable( getVariable( val ) );
if ( !variable.isEmpty() ) {
String::replaceAll( newValue, var.definition, variable.getValue() );
break;
if ( property->getVarCache().empty() && !property->isLightDarkValue() )
return;
std::string newValue( value );
static constexpr int maxDepth = 16;
for ( int depth = 0; depth < maxDepth; depth++ ) {
bool changed = false;
if ( !property->getVarCache().empty() ) {
auto varCacheCopy = property->getVarCache();
for ( auto& var : varCacheCopy ) {
for ( auto& val : var.variableList ) {
StyleSheetVariable variable( getVariable( val ) );
if ( !variable.isEmpty() ) {
String::replaceAll( newValue, var.definition, variable.getValue() );
changed = true;
break;
}
}
}
}
if ( property->isLightDarkValue() )
if ( property->isLightDarkValue() ) {
applyLightDarkValue( newValue );
changed = true;
}
if ( !changed )
break;
property->setValue( newValue );
} else if ( property->isLightDarkValue() ) {
std::string newValue( value );
applyLightDarkValue( newValue );
property->setValue( newValue );
if ( property->getVarCache().empty() && !property->isLightDarkValue() )
break;
}
}
+5 -4
View File
@@ -1,3 +1,4 @@
#include <eepp/core/debug.hpp>
#include <eepp/graphics/fontmanager.hpp>
#include <eepp/graphics/text.hpp>
#include <eepp/scene/scenemanager.hpp>
@@ -111,7 +112,10 @@ bool UITextSpan::applyProperty( const StyleSheetProperty& attribute ) {
Color color = attribute.asColor();
if ( color == Color::Transparent &&
attribute.getValue().find( "var(" ) != std::string::npos ) {
// Do not set unresolved colors
#ifdef EE_DEBUG
eePRINTL( "UITextSpan: unresolved var() in Color value for <%s>: '%s'",
getElementTag().c_str(), attribute.getValue().c_str() );
#endif
break;
}
setFontColor( color );
@@ -730,9 +734,6 @@ 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;
@@ -534,3 +534,307 @@ UTEST( CSSVariables, VariableReferencesCircular ) {
UIRichText* div = root->querySelector( "#testdiv" )->asType<UIRichText>();
EXPECT_TRUE( div != nullptr );
}
UTEST( CSSVariables, VarInRgbMixedSyntax ) {
UIApplication app(
WindowSettings( 800, 600, "eepp - Var In Rgb Test", WindowStyle::Default,
WindowBackend::Default, 32 ),
UIApplication::Settings( Sys::getProcessPath() + ".." + FileSystem::getOSSlash(), 1 ) );
// Test rgb(var(--ch) / var(--alpha)) where the variable holds
// comma-separated channel values. After substitution this becomes
// rgb(255, 0, 0 / 0.5) — mixed comma + slash syntax.
std::string xml = R"html(
<html>
<head>
<style>
#testdiv {
--ch: 255, 0, 0;
--alpha: 0.5;
color: rgb(var(--ch) / var(--alpha));
}
</style>
</head>
<body>
<div id="testdiv">Test text</div>
</body>
</html>
)html";
UIWidget* root = app.getUI()->loadLayoutFromString( xml );
EXPECT_TRUE( root != nullptr );
UIRichText* div = root->querySelector( "#testdiv" )->asType<UIRichText>();
EXPECT_TRUE( div != nullptr );
Color result = div->getFontColor();
EXPECT_EQ( 255, result.r );
EXPECT_EQ( 0, result.g );
EXPECT_EQ( 0, result.b );
EXPECT_EQ( 128, result.a );
}
UTEST( CSSVariables, VarInRgbMixedSyntaxBackground ) {
UIApplication app(
WindowSettings( 800, 600, "eepp - Var In Rgb Bg Test", WindowStyle::Default,
WindowBackend::Default, 32 ),
UIApplication::Settings( Sys::getProcessPath() + ".." + FileSystem::getOSSlash(), 1 ) );
std::string xml = R"html(
<html>
<head>
<style>
#testdiv {
--ch: 0, 128, 255;
--alpha: 0.75;
background-color: rgb(var(--ch) / var(--alpha));
}
</style>
</head>
<body>
<div id="testdiv">Test text</div>
</body>
</html>
)html";
UIWidget* root = app.getUI()->loadLayoutFromString( xml );
EXPECT_TRUE( root != nullptr );
UIRichText* div = root->querySelector( "#testdiv" )->asType<UIRichText>();
EXPECT_TRUE( div != nullptr );
Color result = div->getBackgroundColor();
EXPECT_EQ( 0, result.r );
EXPECT_EQ( 128, result.g );
EXPECT_EQ( 255, result.b );
EXPECT_EQ( 191, result.a );
}
UTEST( CSSVariables, VarInRgbModernSpaceSyntax ) {
UIApplication app(
WindowSettings( 800, 600, "eepp - Var In Rgb Space Test", WindowStyle::Default,
WindowBackend::Default, 32 ),
UIApplication::Settings( Sys::getProcessPath() + ".." + FileSystem::getOSSlash(), 1 ) );
// Space-separated channel values: after substitution the result is
// rgb(255 128 0 / 0.8) which maps to the modern syntax path.
std::string xml = R"html(
<html>
<head>
<style>
#testdiv {
--ch: 255 128 0;
--alpha: 0.8;
background-color: rgb(var(--ch) / var(--alpha));
}
</style>
</head>
<body>
<div id="testdiv">Test text</div>
</body>
</html>
)html";
UIWidget* root = app.getUI()->loadLayoutFromString( xml );
EXPECT_TRUE( root != nullptr );
UIRichText* div = root->querySelector( "#testdiv" )->asType<UIRichText>();
EXPECT_TRUE( div != nullptr );
Color result = div->getBackgroundColor();
EXPECT_EQ( 255, result.r );
EXPECT_EQ( 128, result.g );
EXPECT_EQ( 0, result.b );
EXPECT_EQ( 204, result.a );
}
UTEST( CSSVariables, VarInRgbAnchorColor ) {
UIApplication app(
WindowSettings( 800, 600, "eepp - Var In Rgb Anchor Test", WindowStyle::Default,
WindowBackend::Default, 32 ),
UIApplication::Settings( Sys::getProcessPath() + ".." + FileSystem::getOSSlash(), 1 ) );
// Test that anchor (<a>) elements resolve color with rgb(var()) syntax.
// This verifies that anchors (which start in Link state) go through the
// same var resolution pipeline as other elements.
std::string xml = R"html(
<html>
<head>
<style>
:root {
--ch: 255, 0, 128;
--alpha: 0.6;
}
a {
color: rgb(var(--ch) / var(--alpha));
}
</style>
</head>
<body>
<a id="testlink" href="/">Test link</a>
</body>
</html>
)html";
UIWidget* root = app.getUI()->loadLayoutFromString( xml );
EXPECT_TRUE( root != nullptr );
auto* link = root->querySelector( "#testlink" );
EXPECT_TRUE( link != nullptr );
// Anchor should be a UITextSpan (or UIAnchorSpan which extends it)
EXPECT_TRUE( link->isType( UI_TYPE_TEXTSPAN ) );
Color result = link->asType<UITextSpan>()->getFontColor();
EXPECT_EQ( 255, result.r );
EXPECT_EQ( 0, result.g );
EXPECT_EQ( 128, result.b );
EXPECT_EQ( 153, result.a ); // 0.6 * 255 = 153
}
UTEST( CSSVariables, VarInRgbViaIntermediateVar ) {
UIApplication app(
WindowSettings( 800, 600, "eepp - Var In Rgb Indirect Test", WindowStyle::Default,
WindowBackend::Default, 32 ),
UIApplication::Settings( Sys::getProcessPath() + ".." + FileSystem::getOSSlash(), 1 ) );
// Test variable that references another variable which contains
// rgb(var()) - requiring recursive var resolution.
std::string xml = R"html(
<html>
<head>
<style>
:root {
--r: 0;
--g: 128;
--b: 255;
--a: 0.75;
--link-color: rgb(var(--r) var(--g) var(--b) / var(--a));
}
a {
color: var(--link-color);
}
</style>
</head>
<body>
<a id="testlink" href="/">Indirect test link</a>
</body>
</html>
)html";
UIWidget* root = app.getUI()->loadLayoutFromString( xml );
EXPECT_TRUE( root != nullptr );
auto* link = root->querySelector( "#testlink" );
EXPECT_TRUE( link != nullptr );
Color result = link->asType<UITextSpan>()->getFontColor();
EXPECT_EQ( 0, result.r );
EXPECT_EQ( 128, result.g );
EXPECT_EQ( 255, result.b );
EXPECT_EQ( 191, result.a ); // 0.75 * 255 = 191
}
UTEST( CSSVariables, LightDarkBasic ) {
// Light scheme: picks first parameter of light-dark()
UIApplication lightApp(
WindowSettings( 800, 600, "eepp - LightDark Basic Light", WindowStyle::Default,
WindowBackend::Default, 32 ),
UIApplication::Settings( Sys::getProcessPath() + ".." + FileSystem::getOSSlash(), 1 ) );
lightApp.getUI()->setColorSchemePreference( ColorSchemePreference::Light );
UIWidget* lightRoot = lightApp.getUI()->loadLayoutFromString( R"html(
<html><head><style>
#testdiv { color: light-dark(#FF0000, #00FF00); }
</style></head><body>
<div id="testdiv">Test</div>
</body></html>
)html" );
auto* lightDiv = lightRoot->querySelector( "#testdiv" )->asType<UIRichText>();
EXPECT_TRUE( lightDiv != nullptr );
Color lightResult = lightDiv->getFontColor();
EXPECT_EQ( 255, lightResult.r );
EXPECT_EQ( 0, lightResult.g );
EXPECT_EQ( 0, lightResult.b );
// Dark scheme: picks second parameter
UIApplication darkApp(
WindowSettings( 800, 600, "eepp - LightDark Basic Dark", WindowStyle::Default,
WindowBackend::Default, 32 ),
UIApplication::Settings( Sys::getProcessPath() + ".." + FileSystem::getOSSlash(), 1 ) );
darkApp.getUI()->setColorSchemePreference( ColorSchemePreference::Dark );
UIWidget* darkRoot = darkApp.getUI()->loadLayoutFromString( R"html(
<html><head><style>
#testdiv { color: light-dark(#FF0000, #00FF00); }
</style></head><body>
<div id="testdiv">Test</div>
</body></html>
)html" );
auto* darkDiv = darkRoot->querySelector( "#testdiv" )->asType<UIRichText>();
EXPECT_TRUE( darkDiv != nullptr );
Color darkResult = darkDiv->getFontColor();
EXPECT_EQ( 0, darkResult.r );
EXPECT_EQ( 255, darkResult.g );
EXPECT_EQ( 0, darkResult.b );
}
UTEST( CSSVariables, LightDarkWithVars ) {
UIApplication app(
WindowSettings( 800, 600, "eepp - LightDark Var Test", WindowStyle::Default,
WindowBackend::Default, 32 ),
UIApplication::Settings( Sys::getProcessPath() + ".." + FileSystem::getOSSlash(), 1 ) );
app.getUI()->setColorSchemePreference( ColorSchemePreference::Light );
UIWidget* root = app.getUI()->loadLayoutFromString( R"html(
<html><head><style>
:root {
--light: #0000FF;
--dark: #FF8800;
}
#testdiv {
color: light-dark(var(--light), var(--dark));
}
</style></head><body>
<div id="testdiv">Test</div>
</body></html>
)html" );
auto* div = root->querySelector( "#testdiv" )->asType<UIRichText>();
EXPECT_TRUE( div != nullptr );
Color result = div->getFontColor();
EXPECT_EQ( 0, result.r );
EXPECT_EQ( 0, result.g );
EXPECT_EQ( 255, result.b );
}
UTEST( CSSVariables, LightDarkWithVarRgb ) {
UIApplication app(
WindowSettings( 800, 600, "eepp - LightDark Rgb Test", WindowStyle::Default,
WindowBackend::Default, 32 ),
UIApplication::Settings( Sys::getProcessPath() + ".." + FileSystem::getOSSlash(), 1 ) );
app.getUI()->setColorSchemePreference( ColorSchemePreference::Light );
UIWidget* root = app.getUI()->loadLayoutFromString( R"html(
<html><head><style>
:root {
--l-ch: 255, 0, 0;
--d-ch: 0, 128, 0;
}
#testdiv {
color: light-dark(rgb(var(--l-ch) / 1), rgb(var(--d-ch) / 1));
}
</style></head><body>
<div id="testdiv">Test</div>
</body></html>
)html" );
auto* div = root->querySelector( "#testdiv" )->asType<UIRichText>();
EXPECT_TRUE( div != nullptr );
Color result = div->getFontColor();
EXPECT_EQ( 255, result.r );
EXPECT_EQ( 0, result.g );
EXPECT_EQ( 0, result.b );
}