mirror of
https://github.com/SpartanJ/eepp.git
synced 2026-10-01 18:50:16 +03:00
Fix importing CSS within CSS.
Fix in colors with vars processing. Added prefers-contrast support at least for parsing.
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 );
|
||||
|
||||
|
||||
@@ -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 };
|
||||
|
||||
@@ -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(),
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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 ) );
|
||||
|
||||
|
||||
@@ -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() ) {
|
||||
|
||||
@@ -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
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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 );
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user