diff --git a/include/eepp/ui/colorschemepreferences.hpp b/include/eepp/ui/colorschemepreferences.hpp index 4a4c7b694..1d5699786 100644 --- a/include/eepp/ui/colorschemepreferences.hpp +++ b/include/eepp/ui/colorschemepreferences.hpp @@ -17,6 +17,17 @@ enum class ColorSchemeExtPreference : std::underlying_type_t { + NoPreference = + static_cast>( ContrastPreference::NoPreference ), + More = static_cast>( ContrastPreference::More ), + Less = static_cast>( ContrastPreference::Less ), + Custom = static_cast>( 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 diff --git a/include/eepp/ui/css/mediaquery.hpp b/include/eepp/ui/css/mediaquery.hpp index 6ae863375..1aa249430 100644 --- a/include/eepp/ui/css/mediaquery.hpp +++ b/include/eepp/ui/css/mediaquery.hpp @@ -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 { diff --git a/include/eepp/ui/css/stylesheetparser.hpp b/include/eepp/ui/css/stylesheetparser.hpp index ce4a17134..7754d242c 100644 --- a/include/eepp/ui/css/stylesheetparser.hpp +++ b/include/eepp/ui/css/stylesheetparser.hpp @@ -2,6 +2,7 @@ #define EE_UI_CSS_STYLESHEETPARSER #include +#include #include #include #include @@ -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 mComments; MediaQueryList::ptr mMediaQueryList; bool mLoaded; + Network::URI mBaseURI; bool parse( std::string& css, std::vector& importedList ); diff --git a/include/eepp/ui/uiscenenode.hpp b/include/eepp/ui/uiscenenode.hpp index 9f6d36fa9..0f130e965 100644 --- a/include/eepp/ui/uiscenenode.hpp +++ b/include/eepp/ui/uiscenenode.hpp @@ -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 mDirtyLayouts; std::vector> mTimes; ColorSchemePreference mColorSchemePreference{ ColorSchemePreference::Dark }; + ContrastPreference mContrastPreference{ ContrastPreference::NoPreference }; Uint32 mMaxInvalidationDepth{ 2 }; Node* mCurParent{ nullptr }; Uint32 mCurOnSizeChangeListener{ 0 }; diff --git a/src/eepp/system/color.cpp b/src/eepp/system/color.cpp index f29b7a120..aa23cd405 100644 --- a/src/eepp/system/color.cpp +++ b/src/eepp/system/color.cpp @@ -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(), diff --git a/src/eepp/ui/colorschemepreferences.cpp b/src/eepp/ui/colorschemepreferences.cpp index 1e11f6e11..9bfa0f03e 100644 --- a/src/eepp/ui/colorschemepreferences.cpp +++ b/src/eepp/ui/colorschemepreferences.cpp @@ -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( pref ); +} + +ContrastExtPreference ContrastPreferences::toExt( ContrastPreference pref ) { + return static_cast( 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( pref ) ); +} + } // namespace EE::UI diff --git a/src/eepp/ui/css/mediaquery.cpp b/src/eepp/ui/css/mediaquery.cpp index 7931e8d1f..f495b43b4 100644 --- a/src/eepp/ui/css/mediaquery.cpp +++ b/src/eepp/ui/css/mediaquery.cpp @@ -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; } diff --git a/src/eepp/ui/css/stylesheetparser.cpp b/src/eepp/ui/css/stylesheetparser.cpp index 46d102035..cb6ac3240 100644 --- a/src/eepp/ui/css/stylesheetparser.cpp +++ b/src/eepp/ui/css/stylesheetparser.cpp @@ -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 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 ) ); diff --git a/src/eepp/ui/uirichtext.cpp b/src/eepp/ui/uirichtext.cpp index c101943e0..527d08e51 100644 --- a/src/eepp/ui/uirichtext.cpp +++ b/src/eepp/ui/uirichtext.cpp @@ -1,4 +1,5 @@ #include +#include #include #include #include @@ -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() ) { diff --git a/src/eepp/ui/uiscenenode.cpp b/src/eepp/ui/uiscenenode.cpp index a913b5400..9032a4fe5 100644 --- a/src/eepp/ui/uiscenenode.cpp +++ b/src/eepp/ui/uiscenenode.cpp @@ -276,6 +276,7 @@ std::vector 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; } diff --git a/src/eepp/ui/uistyle.cpp b/src/eepp/ui/uistyle.cpp index 254d9a0d9..4218411b3 100644 --- a/src/eepp/ui/uistyle.cpp +++ b/src/eepp/ui/uistyle.cpp @@ -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; } } diff --git a/src/eepp/ui/uitextspan.cpp b/src/eepp/ui/uitextspan.cpp index 44b16b998..f27fd5b2b 100644 --- a/src/eepp/ui/uitextspan.cpp +++ b/src/eepp/ui/uitextspan.cpp @@ -1,3 +1,4 @@ +#include #include #include #include @@ -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; diff --git a/src/tests/unit_tests/uicss_inheritance_tests.cpp b/src/tests/unit_tests/uicss_inheritance_tests.cpp index 442a254de..1e2907944 100644 --- a/src/tests/unit_tests/uicss_inheritance_tests.cpp +++ b/src/tests/unit_tests/uicss_inheritance_tests.cpp @@ -534,3 +534,307 @@ UTEST( CSSVariables, VariableReferencesCircular ) { UIRichText* div = root->querySelector( "#testdiv" )->asType(); 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( + + + + + +
Test text
+ + + )html"; + + UIWidget* root = app.getUI()->loadLayoutFromString( xml ); + EXPECT_TRUE( root != nullptr ); + + UIRichText* div = root->querySelector( "#testdiv" )->asType(); + 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( + + + + + +
Test text
+ + + )html"; + + UIWidget* root = app.getUI()->loadLayoutFromString( xml ); + EXPECT_TRUE( root != nullptr ); + + UIRichText* div = root->querySelector( "#testdiv" )->asType(); + 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( + + + + + +
Test text
+ + + )html"; + + UIWidget* root = app.getUI()->loadLayoutFromString( xml ); + EXPECT_TRUE( root != nullptr ); + + UIRichText* div = root->querySelector( "#testdiv" )->asType(); + 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 () 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( + + + + + + Test link + + + )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()->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( + + + + + + Indirect test link + + + )html"; + + UIWidget* root = app.getUI()->loadLayoutFromString( xml ); + EXPECT_TRUE( root != nullptr ); + + auto* link = root->querySelector( "#testlink" ); + EXPECT_TRUE( link != nullptr ); + + Color result = link->asType()->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( + +
Test
+ + )html" ); + + auto* lightDiv = lightRoot->querySelector( "#testdiv" )->asType(); + 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( + +
Test
+ + )html" ); + + auto* darkDiv = darkRoot->querySelector( "#testdiv" )->asType(); + 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( + +
Test
+ + )html" ); + + auto* div = root->querySelector( "#testdiv" )->asType(); + 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( + +
Test
+ + )html" ); + + auto* div = root->querySelector( "#testdiv" )->asType(); + EXPECT_TRUE( div != nullptr ); + Color result = div->getFontColor(); + EXPECT_EQ( 255, result.r ); + EXPECT_EQ( 0, result.g ); + EXPECT_EQ( 0, result.b ); +}