mirror of
https://github.com/SpartanJ/eepp.git
synced 2026-10-02 03:00:23 +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
|
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 {
|
struct EE_API ColorSchemePreferences {
|
||||||
static ColorSchemePreference fromString( std::string_view str );
|
static ColorSchemePreference fromString( std::string_view str );
|
||||||
|
|
||||||
@@ -31,4 +42,18 @@ struct EE_API ColorSchemePreferences {
|
|||||||
static std::string toString( ColorSchemeExtPreference pref );
|
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
|
} // namespace EE::UI
|
||||||
|
|||||||
@@ -65,6 +65,8 @@ enum MediaFeature {
|
|||||||
media_feature_max_pixel_density,
|
media_feature_max_pixel_density,
|
||||||
|
|
||||||
media_feature_prefers_color_scheme,
|
media_feature_prefers_color_scheme,
|
||||||
|
|
||||||
|
media_feature_prefers_contrast,
|
||||||
};
|
};
|
||||||
|
|
||||||
enum MediaType {
|
enum MediaType {
|
||||||
@@ -102,9 +104,10 @@ struct MediaFeatures {
|
|||||||
// device does not use a color lookup table, the value is zero.
|
// 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
|
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.
|
// not a monochrome device, the output device value will be 0.
|
||||||
int resolution; // The resolution of the output device (in DPI)
|
int resolution; // The resolution of the output device (in DPI)
|
||||||
float pixelDensity; // Screen pixel density
|
float pixelDensity; // Screen pixel density
|
||||||
std::string prefersColorScheme{ "dark" }; // Color Scheme Default Preference
|
std::string prefersColorScheme{ "dark" }; // Color Scheme Default Preference
|
||||||
|
std::string prefersContrast{ "no-preference" }; // Contrast Preference
|
||||||
};
|
};
|
||||||
|
|
||||||
struct EE_API MediaQueryExpression {
|
struct EE_API MediaQueryExpression {
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
#define EE_UI_CSS_STYLESHEETPARSER
|
#define EE_UI_CSS_STYLESHEETPARSER
|
||||||
|
|
||||||
#include <eepp/core/string.hpp>
|
#include <eepp/core/string.hpp>
|
||||||
|
#include <eepp/network/uri.hpp>
|
||||||
#include <eepp/system/iostream.hpp>
|
#include <eepp/system/iostream.hpp>
|
||||||
#include <eepp/ui/css/mediaquery.hpp>
|
#include <eepp/ui/css/mediaquery.hpp>
|
||||||
#include <eepp/ui/css/stylesheet.hpp>
|
#include <eepp/ui/css/stylesheet.hpp>
|
||||||
@@ -38,6 +39,10 @@ class EE_API StyleSheetParser {
|
|||||||
|
|
||||||
const bool& isLoaded() const;
|
const bool& isLoaded() const;
|
||||||
|
|
||||||
|
void setBaseURI( const Network::URI& uri );
|
||||||
|
|
||||||
|
void setBaseURI( const std::string& uri );
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
enum ReadState { ReadingSelector, ReadingProperty, ReadingComment };
|
enum ReadState { ReadingSelector, ReadingProperty, ReadingComment };
|
||||||
|
|
||||||
@@ -46,6 +51,7 @@ class EE_API StyleSheetParser {
|
|||||||
std::vector<std::string> mComments;
|
std::vector<std::string> mComments;
|
||||||
MediaQueryList::ptr mMediaQueryList;
|
MediaQueryList::ptr mMediaQueryList;
|
||||||
bool mLoaded;
|
bool mLoaded;
|
||||||
|
Network::URI mBaseURI;
|
||||||
|
|
||||||
bool parse( std::string& css, std::vector<std::string>& importedList );
|
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 );
|
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.
|
* @brief Gets the maximum invalidation depth.
|
||||||
*
|
*
|
||||||
@@ -771,6 +789,7 @@ class EE_API UISceneNode : public SceneNode {
|
|||||||
UnorderedSet<UILayout*> mDirtyLayouts;
|
UnorderedSet<UILayout*> mDirtyLayouts;
|
||||||
std::vector<std::pair<Float, std::string>> mTimes;
|
std::vector<std::pair<Float, std::string>> mTimes;
|
||||||
ColorSchemePreference mColorSchemePreference{ ColorSchemePreference::Dark };
|
ColorSchemePreference mColorSchemePreference{ ColorSchemePreference::Dark };
|
||||||
|
ContrastPreference mContrastPreference{ ContrastPreference::NoPreference };
|
||||||
Uint32 mMaxInvalidationDepth{ 2 };
|
Uint32 mMaxInvalidationDepth{ 2 };
|
||||||
Node* mCurParent{ nullptr };
|
Node* mCurParent{ nullptr };
|
||||||
Uint32 mCurOnSizeChangeListener{ 0 };
|
Uint32 mCurOnSizeChangeListener{ 0 };
|
||||||
|
|||||||
@@ -112,9 +112,8 @@ static bool parseHslHue( Float& out, std::string_view tok ) {
|
|||||||
tok.remove_suffix( 1 );
|
tok.remove_suffix( 1 );
|
||||||
if ( tok.empty() )
|
if ( tok.empty() )
|
||||||
return false;
|
return false;
|
||||||
if ( tok.size() == 4 &&
|
if ( tok.size() == 4 && ( std::strncmp( tok.data(), "none", 4 ) == 0 ||
|
||||||
( std::strncmp( tok.data(), "none", 4 ) == 0 ||
|
std::strncmp( tok.data(), "None", 4 ) == 0 ) ) {
|
||||||
std::strncmp( tok.data(), "None", 4 ) == 0 ) ) {
|
|
||||||
out = 0;
|
out = 0;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -140,9 +139,8 @@ static bool parseHslPercentOrNumber( Float& out, std::string_view tok ) {
|
|||||||
tok.remove_suffix( 1 );
|
tok.remove_suffix( 1 );
|
||||||
if ( tok.empty() )
|
if ( tok.empty() )
|
||||||
return false;
|
return false;
|
||||||
if ( tok.size() == 4 &&
|
if ( tok.size() == 4 && ( std::strncmp( tok.data(), "none", 4 ) == 0 ||
|
||||||
( std::strncmp( tok.data(), "none", 4 ) == 0 ||
|
std::strncmp( tok.data(), "None", 4 ) == 0 ) ) {
|
||||||
std::strncmp( tok.data(), "None", 4 ) == 0 ) ) {
|
|
||||||
out = 0;
|
out = 0;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -757,7 +755,22 @@ Color Color::fromString( std::string str ) {
|
|||||||
if ( functionString.getParameters().size() >= 3 &&
|
if ( functionString.getParameters().size() >= 3 &&
|
||||||
( functionString.getName() == "rgb" || functionString.getName() == "rgba" ) ) {
|
( functionString.getName() == "rgb" || functionString.getName() == "rgba" ) ) {
|
||||||
Color color( Color::Transparent );
|
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;
|
Float val = 0;
|
||||||
if ( !parseCssColorComponent( val, params[0].data(),
|
if ( !parseCssColorComponent( val, params[0].data(),
|
||||||
|
|||||||
@@ -56,4 +56,56 @@ std::string ColorSchemePreferences::toString( ColorSchemeExtPreference pref ) {
|
|||||||
return "system";
|
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
|
} // namespace EE::UI
|
||||||
|
|||||||
@@ -10,13 +10,14 @@ namespace EE { namespace UI { namespace CSS {
|
|||||||
|
|
||||||
#define MediaOrientationStrings "portrait;landscape"
|
#define MediaOrientationStrings "portrait;landscape"
|
||||||
|
|
||||||
#define MediaFeatureStrings \
|
#define MediaFeatureStrings \
|
||||||
"none;width;min-width;max-width;height;min-height;max-height;device-width;min-device-" \
|
"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;" \
|
"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-" \
|
"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-" \
|
"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-" \
|
"color-index;monochrome;min-monochrome;max-monochrome;resolution;min-resolution;max-" \
|
||||||
"resolution;pixel-density;min-pixel-density;max-pixel-density;prefers-color-scheme"
|
"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"
|
#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 )
|
if ( features.prefersColorScheme == valStr )
|
||||||
return true;
|
return true;
|
||||||
break;
|
break;
|
||||||
|
case media_feature_prefers_contrast:
|
||||||
|
if ( features.prefersContrast == valStr )
|
||||||
|
return true;
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,6 +23,14 @@ namespace EE { namespace UI { namespace CSS {
|
|||||||
|
|
||||||
StyleSheetParser::StyleSheetParser() : mLoaded( false ) {}
|
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 ) {
|
bool StyleSheetParser::loadFromStream( IOStream& stream ) {
|
||||||
Clock elapsed;
|
Clock elapsed;
|
||||||
std::vector<std::string> importedList;
|
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 );
|
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() ) {
|
if ( std::find( importedList.begin(), importedList.end(), path ) == importedList.end() ) {
|
||||||
std::string newCss( importCSS( path, importedList ) );
|
std::string newCss( importCSS( path, importedList ) );
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
#include <cmath>
|
#include <cmath>
|
||||||
|
#include <eepp/core/debug.hpp>
|
||||||
#include <eepp/graphics/fontmanager.hpp>
|
#include <eepp/graphics/fontmanager.hpp>
|
||||||
#include <eepp/graphics/primitives.hpp>
|
#include <eepp/graphics/primitives.hpp>
|
||||||
#include <eepp/graphics/text.hpp>
|
#include <eepp/graphics/text.hpp>
|
||||||
@@ -425,7 +426,10 @@ bool UIRichText::applyProperty( const StyleSheetProperty& attribute ) {
|
|||||||
Color color = attribute.asColor();
|
Color color = attribute.asColor();
|
||||||
if ( color == Color::Transparent &&
|
if ( color == Color::Transparent &&
|
||||||
attribute.getValue().find( "var(" ) != std::string::npos ) {
|
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;
|
break;
|
||||||
}
|
}
|
||||||
setFontColor( attribute.asColor() );
|
setFontColor( attribute.asColor() );
|
||||||
@@ -1006,6 +1010,7 @@ void UIRichText::loadFromXmlNode( const pugi::xml_node& node ) {
|
|||||||
}
|
}
|
||||||
} else if ( String::iequals( child.name(), "style" ) ) {
|
} else if ( String::iequals( child.name(), "style" ) ) {
|
||||||
CSS::StyleSheetParser parser;
|
CSS::StyleSheetParser parser;
|
||||||
|
parser.setBaseURI( getUISceneNode()->getURI() );
|
||||||
std::string styleContent;
|
std::string styleContent;
|
||||||
for ( pugi::xml_node styleChild = child.first_child(); styleChild;
|
for ( pugi::xml_node styleChild = child.first_child(); styleChild;
|
||||||
styleChild = styleChild.next_sibling() ) {
|
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" ) ) {
|
if ( String::iequals( widget.name(), "style" ) ) {
|
||||||
CSS::StyleSheetParser parser;
|
CSS::StyleSheetParser parser;
|
||||||
|
parser.setBaseURI( mURI );
|
||||||
std::string styleContent;
|
std::string styleContent;
|
||||||
for ( pugi::xml_node child = widget.first_child(); child;
|
for ( pugi::xml_node child = widget.first_child(); child;
|
||||||
child = child.next_sibling() ) {
|
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,
|
void UISceneNode::combineStyleSheet( const std::string& inlineStyleSheet, bool forceReloadStyle,
|
||||||
const Uint32& marker, URI baseURI ) {
|
const Uint32& marker, URI baseURI ) {
|
||||||
CSS::StyleSheetParser parser;
|
CSS::StyleSheetParser parser;
|
||||||
|
parser.setBaseURI( baseURI );
|
||||||
|
|
||||||
if ( parser.loadFromString( inlineStyleSheet ) ) {
|
if ( parser.loadFromString( inlineStyleSheet ) ) {
|
||||||
parser.getStyleSheet().setMarker( marker );
|
parser.getStyleSheet().setMarker( marker );
|
||||||
@@ -989,6 +991,7 @@ CSS::MediaFeatures UISceneNode::getMediaFeatures() const {
|
|||||||
media.pixelDensity = PixelDensity::getPixelDensity();
|
media.pixelDensity = PixelDensity::getPixelDensity();
|
||||||
media.prefersColorScheme =
|
media.prefersColorScheme =
|
||||||
mColorSchemePreference == ColorSchemePreference::Dark ? "dark" : "light";
|
mColorSchemePreference == ColorSchemePreference::Dark ? "dark" : "light";
|
||||||
|
media.prefersContrast = ContrastPreferences::toString( mContrastPreference );
|
||||||
return media;
|
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 {
|
const Uint32& UISceneNode::getMaxInvalidationDepth() const {
|
||||||
return mMaxInvalidationDepth;
|
return mMaxInvalidationDepth;
|
||||||
}
|
}
|
||||||
|
|||||||
+25
-13
@@ -284,24 +284,36 @@ void UIStyle::applyLightDarkValue( std::string& value ) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void UIStyle::setVariableFromValue( StyleSheetProperty* property, const std::string& value ) {
|
void UIStyle::setVariableFromValue( StyleSheetProperty* property, const std::string& value ) {
|
||||||
if ( !property->getVarCache().empty() ) {
|
if ( property->getVarCache().empty() && !property->isLightDarkValue() )
|
||||||
std::string newValue( value );
|
return;
|
||||||
for ( auto& var : property->getVarCache() ) {
|
|
||||||
for ( auto& val : var.variableList ) {
|
std::string newValue( value );
|
||||||
StyleSheetVariable variable( getVariable( val ) );
|
static constexpr int maxDepth = 16;
|
||||||
if ( !variable.isEmpty() ) {
|
|
||||||
String::replaceAll( newValue, var.definition, variable.getValue() );
|
for ( int depth = 0; depth < maxDepth; depth++ ) {
|
||||||
break;
|
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 );
|
applyLightDarkValue( newValue );
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
if ( !changed )
|
||||||
|
break;
|
||||||
property->setValue( newValue );
|
property->setValue( newValue );
|
||||||
} else if ( property->isLightDarkValue() ) {
|
if ( property->getVarCache().empty() && !property->isLightDarkValue() )
|
||||||
std::string newValue( value );
|
break;
|
||||||
applyLightDarkValue( newValue );
|
|
||||||
property->setValue( newValue );
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
#include <eepp/core/debug.hpp>
|
||||||
#include <eepp/graphics/fontmanager.hpp>
|
#include <eepp/graphics/fontmanager.hpp>
|
||||||
#include <eepp/graphics/text.hpp>
|
#include <eepp/graphics/text.hpp>
|
||||||
#include <eepp/scene/scenemanager.hpp>
|
#include <eepp/scene/scenemanager.hpp>
|
||||||
@@ -111,7 +112,10 @@ bool UITextSpan::applyProperty( const StyleSheetProperty& attribute ) {
|
|||||||
Color color = attribute.asColor();
|
Color color = attribute.asColor();
|
||||||
if ( color == Color::Transparent &&
|
if ( color == Color::Transparent &&
|
||||||
attribute.getValue().find( "var(" ) != std::string::npos ) {
|
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;
|
break;
|
||||||
}
|
}
|
||||||
setFontColor( color );
|
setFontColor( color );
|
||||||
@@ -730,9 +734,6 @@ bool UIAnchorSpan::applyProperty( const StyleSheetProperty& attribute ) {
|
|||||||
case PropertyId::Href:
|
case PropertyId::Href:
|
||||||
setHref( attribute.asString() );
|
setHref( attribute.asString() );
|
||||||
break;
|
break;
|
||||||
case PropertyId::Color:
|
|
||||||
setFontColor( attribute.asColor() );
|
|
||||||
break;
|
|
||||||
default:
|
default:
|
||||||
UITextSpan::applyProperty( attribute );
|
UITextSpan::applyProperty( attribute );
|
||||||
break;
|
break;
|
||||||
|
|||||||
@@ -534,3 +534,307 @@ UTEST( CSSVariables, VariableReferencesCircular ) {
|
|||||||
UIRichText* div = root->querySelector( "#testdiv" )->asType<UIRichText>();
|
UIRichText* div = root->querySelector( "#testdiv" )->asType<UIRichText>();
|
||||||
EXPECT_TRUE( div != nullptr );
|
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