diff --git a/include/eepp/graphics/lineargradientdrawable.hpp b/include/eepp/graphics/lineargradientdrawable.hpp deleted file mode 100644 index 0902b9a96..000000000 --- a/include/eepp/graphics/lineargradientdrawable.hpp +++ /dev/null @@ -1,57 +0,0 @@ -#ifndef EE_GRAPHICS_LINEARGRADIENTDRAWABLE_HPP -#define EE_GRAPHICS_LINEARGRADIENTDRAWABLE_HPP - -#include -#include -#include - -namespace EE { namespace Graphics { - -class EE_API LinearGradientDrawable : public Drawable { - public: - struct ColorStop { - Float position{ 0 }; /* 0.0 to 1.0 */ - Color color; - - ColorStop() : color( Color::White ) {} - ColorStop( Float pos, const Color& col ) : position( pos ), color( col ) {} - }; - - static LinearGradientDrawable* New(); - static LinearGradientDrawable* NewRepeating(); - - LinearGradientDrawable( Drawable::Type drawableType = LINEARGRADIENT ); - - virtual Sizef getSize(); - - virtual Sizef getPixelsSize(); - - virtual void draw(); - - virtual void draw( const Vector2f& position ); - - virtual void draw( const Vector2f& position, const Sizef& size ); - - virtual bool isStateful() { return false; } - - const std::vector& getColorStops() const; - - void setColorStops( std::vector stops ); - - Float getAngle() const; - - void setAngle( Float angleDegrees ); - - void setSize( const Sizef& size ); - - bool isRepeating() const; - - protected: - std::vector mColorStops; - Float mAngle{ 180.f }; /* CSS angle: 0=to top, 90=to right, 180=to bottom (default) */ - Sizef mSize; -}; - -}} // namespace EE::Graphics - -#endif diff --git a/include/eepp/ui/lineargradientdrawable.hpp b/include/eepp/ui/lineargradientdrawable.hpp new file mode 100644 index 000000000..5082ee5f2 --- /dev/null +++ b/include/eepp/ui/lineargradientdrawable.hpp @@ -0,0 +1,72 @@ +#ifndef EE_UI_LINEARGRADIENTDRAWABLE_HPP +#define EE_UI_LINEARGRADIENTDRAWABLE_HPP + +#include +#include +#include +#include + +namespace EE { namespace UI { + +class EE_API LinearGradientDrawable : public Graphics::Drawable { + public: + using Length = CSS::StyleSheetLength; + using GradientUnit = CSS::StyleSheetLength::Unit; + + struct ColorStop { + Color color; + Float value{ 0 }; + GradientUnit unit{ CSS::StyleSheetLength::Percentage }; + + ColorStop() : color( Color::White ) {} + ColorStop( Float val, const Color& col, GradientUnit u = CSS::StyleSheetLength::Percentage ) : + value( val ), color( col ), unit( u ) {} + + Float getNormalized( Float gradientLineLength ) const { + if ( unit == CSS::StyleSheetLength::Percentage ) + return eemax( 0.f, value ) / 100.f; + if ( gradientLineLength > 0.0001f ) + return eemax( 0.f, value ) / gradientLineLength; + return 0.f; + } + }; + + static LinearGradientDrawable* New(); + static LinearGradientDrawable* NewRepeating(); + + LinearGradientDrawable( + Graphics::Drawable::Type drawableType = Graphics::Drawable::LINEARGRADIENT ); + + virtual Sizef getSize(); + + virtual Sizef getPixelsSize(); + + virtual void draw(); + + virtual void draw( const Vector2f& position ); + + virtual void draw( const Vector2f& position, const Sizef& size ); + + virtual bool isStateful() { return false; } + + const std::vector& getColorStops() const; + + void setColorStops( std::vector stops ); + + Float getAngle() const; + + void setAngle( Float angleDegrees ); + + void setSize( const Sizef& size ); + + bool isRepeating() const; + + protected: + std::vector mColorStops; + Float mAngle{ 180.f }; + Sizef mSize; +}; + +}} // namespace EE::UI + +#endif diff --git a/include/eepp/graphics/radialgradientdrawable.hpp b/include/eepp/ui/radialgradientdrawable.hpp similarity index 55% rename from include/eepp/graphics/radialgradientdrawable.hpp rename to include/eepp/ui/radialgradientdrawable.hpp index dcadf4af9..7c4d7fd14 100644 --- a/include/eepp/graphics/radialgradientdrawable.hpp +++ b/include/eepp/ui/radialgradientdrawable.hpp @@ -1,20 +1,35 @@ -#ifndef EE_GRAPHICS_RADIALGRADIENTDRAWABLE_HPP -#define EE_GRAPHICS_RADIALGRADIENTDRAWABLE_HPP +#ifndef EE_UI_RADIALGRADIENTDRAWABLE_HPP +#define EE_UI_RADIALGRADIENTDRAWABLE_HPP #include #include +#include +#include #include -namespace EE { namespace Graphics { +namespace EE { namespace UI { -class EE_API RadialGradientDrawable : public Drawable { +class EE_API RadialGradientDrawable : public Graphics::Drawable { public: + using Length = CSS::StyleSheetLength; + using GradientUnit = CSS::StyleSheetLength::Unit; + struct ColorStop { - Float position{ 0 }; Color color; + Float value{ 0 }; + GradientUnit unit{ CSS::StyleSheetLength::Percentage }; ColorStop() : color( Color::White ) {} - ColorStop( Float pos, const Color& col ) : position( pos ), color( col ) {} + ColorStop( Float val, const Color& col, GradientUnit u = CSS::StyleSheetLength::Percentage ) : + value( val ), color( col ), unit( u ) {} + + Float getNormalized( Float gradientRayLength ) const { + if ( unit == CSS::StyleSheetLength::Percentage ) + return eemax( 0.f, value ) / 100.f; + if ( gradientRayLength > 0.0001f ) + return eemax( 0.f, value ) / gradientRayLength; + return 0.f; + } }; enum ShapeType { CIRCLE, ELLIPSE }; @@ -23,7 +38,8 @@ class EE_API RadialGradientDrawable : public Drawable { static RadialGradientDrawable* New(); static RadialGradientDrawable* NewRepeating(); - RadialGradientDrawable( Drawable::Type drawableType = RADIALGRADIENT ); + RadialGradientDrawable( + Graphics::Drawable::Type drawableType = Graphics::Drawable::RADIALGRADIENT ); virtual Sizef getSize(); @@ -65,6 +81,6 @@ class EE_API RadialGradientDrawable : public Drawable { Sizef mSize; }; -}} // namespace EE::Graphics +}} // namespace EE::UI #endif diff --git a/src/eepp/ui/css/drawableimageparser.cpp b/src/eepp/ui/css/drawableimageparser.cpp index 0b6c8730a..f17c1e440 100644 --- a/src/eepp/ui/css/drawableimageparser.cpp +++ b/src/eepp/ui/css/drawableimageparser.cpp @@ -3,18 +3,19 @@ #include #include #include -#include -#include #include #include #include #include #include #include +#include +#include #include #include #include +using namespace EE::Graphics; using namespace EE::Scene; namespace EE { namespace UI { namespace CSS { @@ -23,30 +24,34 @@ struct ColorStopResult { bool valid{ false }; bool isHint{ false }; Color color; - Float position{ -1.f }; /* -1 = not specified */ - Float secondPosition{ -1.f }; /* -1 = single-position stop */ + Float value{ -1.f }; + StyleSheetLength::Unit unit{ StyleSheetLength::Percentage }; + Float secondValue{ -1.f }; + StyleSheetLength::Unit secondUnit{ StyleSheetLength::Percentage }; }; -static Float parsePositionValue( const std::string& token ) { +static StyleSheetLength parseStyleSheetLength( const std::string& token ) { if ( token.empty() ) - return -1.f; - Float val; + return StyleSheetLength( -1.f, StyleSheetLength::Percentage ); if ( token.back() == '%' ) { std::string num = token.substr( 0, token.size() - 1 ); + Float val; if ( String::fromString( val, num ) ) - return eemax( 0.f, eemin( 100.f, val ) ) / 100.f; - return -1.f; + return StyleSheetLength( eemax( 0.f, eemin( 100.f, val ) ), + StyleSheetLength::Percentage ); + return StyleSheetLength( -1.f, StyleSheetLength::Percentage ); } - if ( token.size() > 2 && ( token.substr( token.size() - 2 ) == "px" || - token.substr( token.size() - 2 ) == "dp" ) ) { - std::string num = token.substr( 0, token.size() - 2 ); - if ( String::fromString( val, num ) ) - return eemax( 0.f, eemin( 100.f, val ) ) / 100.f; - return -1.f; + // Handle bare numbers as percentages (CSS default for gradient stops) + { + Float val; + if ( String::fromString( val, token ) ) + return StyleSheetLength( eemax( 0.f, eemin( 100.f, val ) ), + StyleSheetLength::Percentage ); } - if ( String::fromString( val, token ) ) - return eemax( 0.f, eemin( 100.f, val ) ) / 100.f; - return -1.f; + StyleSheetLength len = StyleSheetLength::fromString( token ); + if ( len.getValue() >= 0.f ) + return len; + return StyleSheetLength( -1.f, StyleSheetLength::Percentage ); } static ColorStopResult parseColorStop( const std::string& stopStr ) { @@ -62,7 +67,8 @@ static ColorStopResult parseColorStop( const std::string& stopStr ) { std::string num = str.substr( 0, str.size() - 1 ); Float val; if ( String::fromString( val, num ) ) { - result.position = eemax( 0.f, eemin( 100.f, val ) ) / 100.f; + result.value = eemax( 0.f, eemin( 100.f, val ) ); + result.unit = StyleSheetLength::Percentage; result.isHint = true; result.valid = true; return result; @@ -111,11 +117,13 @@ static ColorStopResult parseColorStop( const std::string& stopStr ) { if ( Color::isColorString( altColor ) ) { std::string firstPos = colorPart.substr( innerSpace + 1 ); String::trimInPlace( firstPos ); - Float p1 = parsePositionValue( firstPos ); - Float p2 = parsePositionValue( posPart ); + StyleSheetLength p1 = parseStyleSheetLength( firstPos ); + StyleSheetLength p2 = parseStyleSheetLength( posPart ); result.color = Color::fromString( altColor ); - result.position = p1; - result.secondPosition = p2 >= 0.f ? p2 : -1.f; + result.value = p1.getValue(); + result.unit = p1.getUnit(); + result.secondValue = p2.getValue(); + result.secondUnit = p2.getUnit(); result.valid = true; return result; } @@ -123,20 +131,20 @@ static ColorStopResult parseColorStop( const std::string& stopStr ) { } // Try to parse the position part (single position) - Float posVal = parsePositionValue( posPart ); + StyleSheetLength posLen = parseStyleSheetLength( posPart ); if ( Color::isColorString( colorPart ) ) { result.color = Color::fromString( colorPart ); - result.position = posVal; + result.value = posLen.getValue(); + result.unit = posLen.getUnit(); result.valid = true; - } else if ( posVal < 0.f && Color::isColorString( str ) ) { + } else if ( posLen.getValue() < 0.f && Color::isColorString( str ) ) { result.color = Color::fromString( str ); result.valid = true; } return result; } - DrawableImageParser::DrawableImageParser() { registerBaseParsers(); } @@ -267,12 +275,13 @@ void DrawableImageParser::registerBaseParsers() { continue; gradientStops.push_back( stop ); - if ( stop.secondPosition >= 0.f ) { + if ( stop.secondValue >= 0.f ) { ColorStopResult stop2; stop2.valid = true; stop2.color = stop.color; - stop2.position = stop.secondPosition; - stop2.secondPosition = -1.f; + stop2.value = stop.secondValue; + stop2.unit = stop.secondUnit; + stop2.secondValue = -1.f; gradientStops.push_back( stop2 ); } } @@ -282,22 +291,34 @@ void DrawableImageParser::registerBaseParsers() { // list, set its position to be equal to the largest specified // position of any color-stop before it. This ensures hard // transitions instead of accidental gradients. + // We also convert the unit to match the dominating unit of the + // gradient, so that mixed px/% stops produce consistent results. { Float maxPos = -1.f; + StyleSheetLength::Unit maxUnit = StyleSheetLength::Percentage; + bool hasMax = false; for ( auto& s : gradientStops ) { if ( s.isHint ) continue; - if ( s.position >= 0.f ) { - if ( s.position < maxPos ) - s.position = maxPos; - else - maxPos = s.position; + if ( s.value >= 0.f ) { + if ( hasMax && s.value < maxPos ) { + s.value = maxPos; + s.unit = maxUnit; + } else { + maxPos = s.value; + maxUnit = s.unit; + hasMax = true; + } } - if ( s.secondPosition >= 0.f ) { - if ( s.secondPosition < maxPos ) - s.secondPosition = maxPos; - else - maxPos = s.secondPosition; + if ( s.secondValue >= 0.f ) { + if ( hasMax && s.secondValue < maxPos ) { + s.secondValue = maxPos; + s.secondUnit = maxUnit; + } else { + maxPos = s.secondValue; + maxUnit = s.secondUnit; + hasMax = true; + } } } } @@ -317,20 +338,22 @@ void DrawableImageParser::registerBaseParsers() { // irrelevant (the hint has no effect); drop them. std::sort( gradientStops.begin(), gradientStops.end(), []( const ColorStopResult& a, const ColorStopResult& b ) { - return a.position < b.position; + return a.value < b.value; } ); // Set position 0 for the first unpositioned color stop, and position 1 // for the last, so interior auto-distribution has proper boundaries. for ( size_t i = 0; i < gradientStops.size(); i++ ) { - if ( !gradientStops[i].isHint && gradientStops[i].position < 0.f ) { - gradientStops[i].position = 0.f; + if ( !gradientStops[i].isHint && gradientStops[i].value < 0.f ) { + gradientStops[i].value = 0.f; + gradientStops[i].unit = StyleSheetLength::Percentage; break; } } for ( size_t i = gradientStops.size(); i > 0; i-- ) { - if ( !gradientStops[i - 1].isHint && gradientStops[i - 1].position < 0.f ) { - gradientStops[i - 1].position = 1.f; + if ( !gradientStops[i - 1].isHint && gradientStops[i - 1].value < 0.f ) { + gradientStops[i - 1].value = 100.f; + gradientStops[i - 1].unit = StyleSheetLength::Percentage; break; } } @@ -338,27 +361,27 @@ void DrawableImageParser::registerBaseParsers() { // Fill missing positions on color stops. Hints always have explicit // positions so they are left untouched. for ( size_t i = 0; i < gradientStops.size(); i++ ) { - if ( gradientStops[i].isHint || gradientStops[i].position >= 0.f ) + if ( gradientStops[i].isHint || gradientStops[i].value >= 0.f ) continue; size_t prevKnown = i; size_t nextKnown = i; - while ( prevKnown > 0 && ( gradientStops[prevKnown - 1].position < 0.f ) ) + while ( prevKnown > 0 && ( gradientStops[prevKnown - 1].value < 0.f ) ) prevKnown--; while ( nextKnown < gradientStops.size() && - ( gradientStops[nextKnown].isHint || gradientStops[nextKnown].position < 0.f ) ) + ( gradientStops[nextKnown].isHint || gradientStops[nextKnown].value < 0.f ) ) nextKnown++; Float startPos = ( prevKnown > 0 && !gradientStops[prevKnown - 1].isHint ) - ? gradientStops[prevKnown - 1].position + ? gradientStops[prevKnown - 1].value : 0.f; Float endPos = - ( nextKnown < gradientStops.size() ) ? gradientStops[nextKnown].position : 1.f; + ( nextKnown < gradientStops.size() ) ? gradientStops[nextKnown].value : 100.f; size_t range = nextKnown - prevKnown + 1; for ( size_t j = prevKnown; j < nextKnown; j++ ) { if ( gradientStops[j].isHint ) continue; - gradientStops[j].position = + gradientStops[j].value = startPos + ( endPos - startPos ) * ( (Float)( j - prevKnown + 1 ) / (Float)range ); } @@ -366,24 +389,6 @@ void DrawableImageParser::registerBaseParsers() { i = nextKnown; } - // Non-repeating: force first/last color stops to exactly 0/1 so - // that the gradient fills the entire box (CSS §4.3). Repeating - // mode keeps raw positions for tiling. - if ( !repeating ) { - for ( size_t i = 0; i < gradientStops.size(); i++ ) { - if ( !gradientStops[i].isHint ) { - gradientStops[i].position = 0.f; - break; - } - } - for ( size_t i = gradientStops.size(); i > 0; i-- ) { - if ( !gradientStops[i - 1].isHint ) { - gradientStops[i - 1].position = 1.f; - break; - } - } - } - // Expand hinted segments into finely sampled color stops std::vector stops; const int HINT_SAMPLES = 16; @@ -393,8 +398,8 @@ void DrawableImageParser::registerBaseParsers() { continue; // Add the current color stop - stops.push_back( LinearGradientDrawable::ColorStop( gradientStops[i].position, - gradientStops[i].color ) ); + stops.push_back( LinearGradientDrawable::ColorStop( + gradientStops[i].value, gradientStops[i].color, gradientStops[i].unit ) ); // Find the next color stop and any hints between them size_t nextColor = i + 1; @@ -407,14 +412,14 @@ void DrawableImageParser::registerBaseParsers() { std::vector hints; for ( size_t h = i + 1; h < nextColor; h++ ) { if ( gradientStops[h].isHint ) - hints.push_back( gradientStops[h].position ); + hints.push_back( gradientStops[h].value ); } if ( hints.empty() ) continue; - Float p0 = gradientStops[i].position; - Float p1 = gradientStops[nextColor].position; + Float p0 = gradientStops[i].value; + Float p1 = gradientStops[nextColor].value; Color c0 = gradientStops[i].color; Color c1 = gradientStops[nextColor].color; @@ -500,12 +505,13 @@ void DrawableImageParser::registerBaseParsers() { continue; gradientStops.push_back( stop ); - if ( stop.secondPosition >= 0.f ) { + if ( stop.secondValue >= 0.f ) { ColorStopResult stop2; stop2.valid = true; stop2.color = stop.color; - stop2.position = stop.secondPosition; - stop2.secondPosition = -1.f; + stop2.value = stop.secondValue; + stop2.unit = stop.secondUnit; + stop2.secondValue = -1.f; gradientStops.push_back( stop2 ); } } @@ -516,20 +522,30 @@ void DrawableImageParser::registerBaseParsers() { // position of any color-stop before it. { Float maxPos = -1.f; + StyleSheetLength::Unit maxUnit = StyleSheetLength::Percentage; + bool hasMax = false; for ( auto& s : gradientStops ) { if ( s.isHint ) continue; - if ( s.position >= 0.f ) { - if ( s.position < maxPos ) - s.position = maxPos; - else - maxPos = s.position; + if ( s.value >= 0.f ) { + if ( hasMax && s.value < maxPos ) { + s.value = maxPos; + s.unit = maxUnit; + } else { + maxPos = s.value; + maxUnit = s.unit; + hasMax = true; + } } - if ( s.secondPosition >= 0.f ) { - if ( s.secondPosition < maxPos ) - s.secondPosition = maxPos; - else - maxPos = s.secondPosition; + if ( s.secondValue >= 0.f ) { + if ( hasMax && s.secondValue < maxPos ) { + s.secondValue = maxPos; + s.secondUnit = maxUnit; + } else { + maxPos = s.secondValue; + maxUnit = s.secondUnit; + hasMax = true; + } } } } @@ -546,19 +562,19 @@ void DrawableImageParser::registerBaseParsers() { std::sort( gradientStops.begin(), gradientStops.end(), []( const ColorStopResult& a, const ColorStopResult& b ) { - return a.position < b.position; + return a.value < b.value; } ); // Set boundary defaults for unpositioned stops for ( size_t i = 0; i < gradientStops.size(); i++ ) { - if ( !gradientStops[i].isHint && gradientStops[i].position < 0.f ) { - gradientStops[i].position = 0.f; + if ( !gradientStops[i].isHint && gradientStops[i].value < 0.f ) { + gradientStops[i].value = 0.f; break; } } for ( size_t i = gradientStops.size(); i > 0; i-- ) { - if ( !gradientStops[i - 1].isHint && gradientStops[i - 1].position < 0.f ) { - gradientStops[i - 1].position = 1.f; + if ( !gradientStops[i - 1].isHint && gradientStops[i - 1].value < 0.f ) { + gradientStops[i - 1].value = 1.f; break; } } @@ -566,27 +582,27 @@ void DrawableImageParser::registerBaseParsers() { // Fill missing positions on color stops. Hints always have explicit // positions so they are left untouched. for ( size_t i = 0; i < gradientStops.size(); i++ ) { - if ( gradientStops[i].isHint || gradientStops[i].position >= 0.f ) + if ( gradientStops[i].isHint || gradientStops[i].value >= 0.f ) continue; size_t prevKnown = i; size_t nextKnown = i; - while ( prevKnown > 0 && ( gradientStops[prevKnown - 1].position < 0.f ) ) + while ( prevKnown > 0 && ( gradientStops[prevKnown - 1].value < 0.f ) ) prevKnown--; while ( nextKnown < gradientStops.size() && - ( gradientStops[nextKnown].isHint || gradientStops[nextKnown].position < 0.f ) ) + ( gradientStops[nextKnown].isHint || gradientStops[nextKnown].value < 0.f ) ) nextKnown++; Float startPos = ( prevKnown > 0 && !gradientStops[prevKnown - 1].isHint ) - ? gradientStops[prevKnown - 1].position + ? gradientStops[prevKnown - 1].value : 0.f; Float endPos = - ( nextKnown < gradientStops.size() ) ? gradientStops[nextKnown].position : 1.f; + ( nextKnown < gradientStops.size() ) ? gradientStops[nextKnown].value : 100.f; size_t range = nextKnown - prevKnown + 1; for ( size_t j = prevKnown; j < nextKnown; j++ ) { if ( gradientStops[j].isHint ) continue; - gradientStops[j].position = + gradientStops[j].value = startPos + ( endPos - startPos ) * ( (Float)( j - prevKnown + 1 ) / (Float)range ); } @@ -594,22 +610,6 @@ void DrawableImageParser::registerBaseParsers() { i = nextKnown; } - // Non-repeating: force first/last color stops to exactly 0/1 - if ( !repeating ) { - for ( size_t i = 0; i < gradientStops.size(); i++ ) { - if ( !gradientStops[i].isHint ) { - gradientStops[i].position = 0.f; - break; - } - } - for ( size_t i = gradientStops.size(); i > 0; i-- ) { - if ( !gradientStops[i - 1].isHint ) { - gradientStops[i - 1].position = 1.f; - break; - } - } - } - // Expand hinted segments into finely sampled color stops std::vector stops; const int HINT_SAMPLES = 16; @@ -619,8 +619,8 @@ void DrawableImageParser::registerBaseParsers() { continue; // Add the current color stop - stops.push_back( RadialGradientDrawable::ColorStop( gradientStops[i].position, - gradientStops[i].color ) ); + stops.push_back( RadialGradientDrawable::ColorStop( + gradientStops[i].value, gradientStops[i].color, gradientStops[i].unit ) ); // Find the next color stop and any hints between them size_t nextColor = i + 1; @@ -633,14 +633,14 @@ void DrawableImageParser::registerBaseParsers() { std::vector hints; for ( size_t h = i + 1; h < nextColor; h++ ) { if ( gradientStops[h].isHint ) - hints.push_back( gradientStops[h].position ); + hints.push_back( gradientStops[h].value ); } if ( hints.empty() ) continue; - Float p0 = gradientStops[i].position; - Float p1 = gradientStops[nextColor].position; + Float p0 = gradientStops[i].value; + Float p1 = gradientStops[nextColor].value; Color c0 = gradientStops[i].color; Color c1 = gradientStops[nextColor].color; @@ -705,7 +705,7 @@ void DrawableImageParser::registerBaseParsers() { const auto& params( functionType.getParameters() ); - CSS::StyleSheetLength length( params[0] ); + StyleSheetLength length( params[0] ); drawable->setRadius( node->convertLength( length, size.getWidth() / 2.f ) ); if ( params.size() >= 2 ) { @@ -749,7 +749,7 @@ void DrawableImageParser::registerBaseParsers() { std::vector parts( String::split( param, ' ' ) ); if ( parts.size() >= 2 ) { - CSS::StyleSheetLength length( parts[1] ); + StyleSheetLength length( parts[1] ); drawable->setLineWidth( node->convertLength( length, size.getWidth() ) ); } } else if ( param.find( "º" ) != std::string::npos ) { @@ -822,8 +822,8 @@ void DrawableImageParser::registerBaseParsers() { std::vector coords( String::split( vertex[v], ' ' ) ); if ( coords.size() == 2 ) { - CSS::StyleSheetLength posX( coords[0] ); - CSS::StyleSheetLength posY( coords[1] ); + StyleSheetLength posX( coords[0] ); + StyleSheetLength posY( coords[1] ); vertices.push_back( Vector2f( node->convertLength( posX, size.getWidth() ), node->convertLength( posY, size.getHeight() ) ) ); @@ -891,8 +891,8 @@ void DrawableImageParser::registerBaseParsers() { std::vector coords( String::split( vertex[v], ' ' ) ); if ( coords.size() == 2 ) { - CSS::StyleSheetLength posX( coords[0] ); - CSS::StyleSheetLength posY( coords[1] ); + StyleSheetLength posX( coords[0] ); + StyleSheetLength posY( coords[1] ); vertices.push_back( Vector2f( node->convertLength( posX, size.getWidth() ), node->convertLength( posY, size.getHeight() ) ) ); @@ -946,7 +946,7 @@ void DrawableImageParser::registerBaseParsers() { const auto& params = functionType.getParameters(); if ( params.size() < 2 ) return nullptr; - CSS::StyleSheetLength length( params[1] ); + StyleSheetLength length( params[1] ); return uiScene->findIconDrawable( params[0], node->convertLength( length, size.getWidth() ) ); }; diff --git a/src/eepp/graphics/lineargradientdrawable.cpp b/src/eepp/ui/lineargradientdrawable.cpp similarity index 74% rename from src/eepp/graphics/lineargradientdrawable.cpp rename to src/eepp/ui/lineargradientdrawable.cpp index 6c9009b94..734cb1211 100644 --- a/src/eepp/graphics/lineargradientdrawable.cpp +++ b/src/eepp/ui/lineargradientdrawable.cpp @@ -1,20 +1,21 @@ #include #include -#include -#include -#include -namespace EE { namespace Graphics { +#include +#include +#include + +namespace EE { namespace UI { LinearGradientDrawable* LinearGradientDrawable::New() { return eeNew( LinearGradientDrawable, () ); } LinearGradientDrawable* LinearGradientDrawable::NewRepeating() { - return eeNew( LinearGradientDrawable, ( REPEATINGLINEARGRADIENT ) ); + return eeNew( LinearGradientDrawable, ( Graphics::Drawable::REPEATINGLINEARGRADIENT ) ); } -LinearGradientDrawable::LinearGradientDrawable( Drawable::Type drawableType ) : +LinearGradientDrawable::LinearGradientDrawable( Graphics::Drawable::Type drawableType ) : Drawable( drawableType ) {} Sizef LinearGradientDrawable::getSize() { @@ -40,16 +41,13 @@ void LinearGradientDrawable::draw( const Vector2f& position, const Sizef& size ) Float w = size.getWidth(); Float h = size.getHeight(); - // CSS angle: 0° = up, 90° = right, 180° = down (default) Float aRad = mAngle * EE_PI / 180.f; Float dx = std::sin( aRad ); Float dy = -std::cos( aRad ); - // Gradient line passes through box center Float cx = w * 0.5f; Float cy = h * 0.5f; - // Find gradient line intersections with box boundaries Float tVals[4]; int validCount = 0; @@ -91,7 +89,17 @@ void LinearGradientDrawable::draw( const Vector2f& position, const Sizef& size ) Float txLen = tMax - tMin; - // Perpendicular direction (rotated 90° clockwise in screen coords) + // Normalize all stops to [0,1] using the gradient-line pixel length + std::vector stops; + stops.reserve( mColorStops.size() ); + for ( const auto& s : mColorStops ) + stops.push_back( + ColorStop( s.getNormalized( txLen ), s.color, CSS::StyleSheetLength::Percentage ) ); + + std::sort( stops.begin(), stops.end(), + []( const ColorStop& a, const ColorStop& b ) { return a.value < b.value; } ); + + Float px = -dy; Float py = dx; @@ -101,19 +109,14 @@ void LinearGradientDrawable::draw( const Vector2f& position, const Sizef& size ) sBR->quadsBegin(); if ( isRepeating() ) { - Float firstPos = mColorStops.front().position; - Float lastPos = mColorStops.back().position; + Float firstPos = stops.front().value; + Float lastPos = stops.back().value; Float patternLen = lastPos - firstPos; if ( patternLen < 0.0001f ) { sBR->draw(); return; } - // Visible position range mapped to [0, 1] along the gradient-line - // segment visible in the box. - // Pattern start in position space: firstPos - // Repeat interval: patternLen - // Visible range: [0, 1] int rStart = (int)std::floor( ( -firstPos ) / patternLen ); int rEnd = (int)std::ceil( ( 1.f - lastPos ) / patternLen ); const int MAX_REPEATS = 128; @@ -123,9 +126,9 @@ void LinearGradientDrawable::draw( const Vector2f& position, const Sizef& size ) for ( int r = rStart; r <= rEnd; r++ ) { Float repeatOff = (Float)r * patternLen; - for ( size_t i = 0; i + 1 < mColorStops.size(); i++ ) { - Float p0 = mColorStops[i].position + repeatOff; - Float p1 = mColorStops[i + 1].position + repeatOff; + for ( size_t i = 0; i + 1 < stops.size(); i++ ) { + Float p0 = stops[i].value + repeatOff; + Float p1 = stops[i + 1].value + repeatOff; if ( p1 <= 0.f || p0 >= 1.f ) continue; @@ -135,20 +138,21 @@ void LinearGradientDrawable::draw( const Vector2f& position, const Sizef& size ) if ( clip1 - clip0 < 0.0001f ) continue; - // Colors at clipped band boundaries (interpolate within the stop pair) Float bw = p1 - p0; Float frac0 = ( clip0 - p0 ) / bw; Float frac1 = ( clip1 - p0 ) / bw; - const Color& sc0 = mColorStops[i].color; - const Color& sc1 = mColorStops[i + 1].color; - Color cc0( (Uint8)( (Float)sc0.r + frac0 * (Float)( sc1.r - sc0.r ) ), - (Uint8)( (Float)sc0.g + frac0 * (Float)( sc1.g - sc0.g ) ), - (Uint8)( (Float)sc0.b + frac0 * (Float)( sc1.b - sc0.b ) ), - (Uint8)( (Float)sc0.a + frac0 * (Float)( sc1.a - sc0.a ) ) ); - Color cc1( (Uint8)( (Float)sc0.r + frac1 * (Float)( sc1.r - sc0.r ) ), - (Uint8)( (Float)sc0.g + frac1 * (Float)( sc1.g - sc0.g ) ), - (Uint8)( (Float)sc0.b + frac1 * (Float)( sc1.b - sc0.b ) ), - (Uint8)( (Float)sc0.a + frac1 * (Float)( sc1.a - sc0.a ) ) ); + const Color& sc0 = stops[i].color; + const Color& sc1 = stops[i + 1].color; + Color cc0( + (Uint8)( (Float)sc0.r + frac0 * (Float)( sc1.r - sc0.r ) ), + (Uint8)( (Float)sc0.g + frac0 * (Float)( sc1.g - sc0.g ) ), + (Uint8)( (Float)sc0.b + frac0 * (Float)( sc1.b - sc0.b ) ), + (Uint8)( (Float)sc0.a + frac0 * (Float)( sc1.a - sc0.a ) ) ); + Color cc1( + (Uint8)( (Float)sc0.r + frac1 * (Float)( sc1.r - sc0.r ) ), + (Uint8)( (Float)sc0.g + frac1 * (Float)( sc1.g - sc0.g ) ), + (Uint8)( (Float)sc0.b + frac1 * (Float)( sc1.b - sc0.b ) ), + (Uint8)( (Float)sc0.a + frac1 * (Float)( sc1.a - sc0.a ) ) ); Float tc0 = tMin + clip0 * txLen; Float tc1 = tMin + clip1 * txLen; @@ -157,7 +161,6 @@ void LinearGradientDrawable::draw( const Vector2f& position, const Sizef& size ) Float gx1 = cx + tc1 * dx; Float gy1 = cy + tc1 * dy; - // Perpendicular intersection for point 0 Float ptVals0[4]; int ptCount0 = 0; auto addPT0 = [&]( Float pt, Float qx, Float qy ) { @@ -190,7 +193,6 @@ void LinearGradientDrawable::draw( const Vector2f& position, const Sizef& size ) Vector2f a0( gx0 + ptMin0 * px + position.x, gy0 + ptMin0 * py + position.y ); Vector2f b0( gx0 + ptMax0 * px + position.x, gy0 + ptMax0 * py + position.y ); - // Perpendicular intersection for point 1 Float ptVals1[4]; int ptCount1 = 0; auto addPT1 = [&]( Float pt, Float qx, Float qy ) { @@ -230,24 +232,19 @@ void LinearGradientDrawable::draw( const Vector2f& position, const Sizef& size ) } } } else { - // Pre-compute position on gradient line: center + t * dir - // Pre-compute perpendicular intersections for all stops struct PerpSeg { Vector2f a; Vector2f b; - Float gradPos; }; std::vector segments; - segments.reserve( mColorStops.size() ); + segments.reserve( stops.size() ); - for ( size_t i = 0; i < mColorStops.size(); i++ ) { + for ( size_t i = 0; i < stops.size(); i++ ) { PerpSeg seg; - Float t = tMin + mColorStops[i].position * txLen; - seg.gradPos = t; + Float t = tMin + stops[i].value * txLen; Float gx = cx + t * dx; Float gy = cy + t * dy; - // Find perpendicular line intersections with the box Float ptVals[4]; int ptCount = 0; @@ -289,10 +286,9 @@ void LinearGradientDrawable::draw( const Vector2f& position, const Sizef& size ) segments.push_back( seg ); } - // Render quads between consecutive stops - for ( size_t i = 0; i < mColorStops.size() - 1; i++ ) { - const Color& c0 = mColorStops[i].color; - const Color& c1 = mColorStops[i + 1].color; + for ( size_t i = 0; i < stops.size() - 1; i++ ) { + const Color& c0 = stops[i].color; + const Color& c1 = stops[i + 1].color; Color fc0 = ( mColor.a == 255 ) ? c0 : Color( c0 ).blendAlpha( mColor.a ); Color fc1 = ( mColor.a == 255 ) ? c1 : Color( c1 ).blendAlpha( mColor.a ); @@ -316,19 +312,6 @@ LinearGradientDrawable::getColorStops() const { void LinearGradientDrawable::setColorStops( std::vector stops ) { mColorStops = std::move( stops ); - if ( mColorStops.size() >= 2 ) { - std::sort( - mColorStops.begin(), mColorStops.end(), - []( const ColorStop& a, const ColorStop& b ) { return a.position < b.position; } ); - if ( !isRepeating() ) { - mColorStops.front().position = 0.f; - mColorStops.back().position = 1.f; - } - } -} - -bool LinearGradientDrawable::isRepeating() const { - return mDrawableType == REPEATINGLINEARGRADIENT; } Float LinearGradientDrawable::getAngle() const { @@ -343,4 +326,8 @@ void LinearGradientDrawable::setSize( const Sizef& size ) { mSize = size; } -}} // namespace EE::Graphics +bool LinearGradientDrawable::isRepeating() const { + return mDrawableType == Graphics::Drawable::REPEATINGLINEARGRADIENT; +} + +}} // namespace EE::UI diff --git a/src/eepp/graphics/radialgradientdrawable.cpp b/src/eepp/ui/radialgradientdrawable.cpp similarity index 72% rename from src/eepp/graphics/radialgradientdrawable.cpp rename to src/eepp/ui/radialgradientdrawable.cpp index 9d3e04f61..b506e35d0 100644 --- a/src/eepp/graphics/radialgradientdrawable.cpp +++ b/src/eepp/ui/radialgradientdrawable.cpp @@ -2,20 +2,20 @@ #include #include -#include #include +#include -namespace EE { namespace Graphics { +namespace EE { namespace UI { RadialGradientDrawable* RadialGradientDrawable::New() { return eeNew( RadialGradientDrawable, () ); } RadialGradientDrawable* RadialGradientDrawable::NewRepeating() { - return eeNew( RadialGradientDrawable, ( REPEATINGRADIALGRADIENT ) ); + return eeNew( RadialGradientDrawable, ( Graphics::Drawable::REPEATINGRADIALGRADIENT ) ); } -RadialGradientDrawable::RadialGradientDrawable( Drawable::Type drawableType ) : +RadialGradientDrawable::RadialGradientDrawable( Graphics::Drawable::Type drawableType ) : Drawable( drawableType ) {} Sizef RadialGradientDrawable::getSize() { @@ -65,6 +65,17 @@ void RadialGradientDrawable::draw( const Vector2f& position, const Sizef& size ) Float cy = mCenter.y * h; Float maxR = computeRadius( cx, cy, w, h, mExtent ); + // Normalize all stops to [0,1] using the gradient ray pixel length + std::vector stops; + stops.reserve( mColorStops.size() ); + for ( const auto& s : mColorStops ) + stops.push_back( + ColorStop( s.getNormalized( maxR ), s.color, CSS::StyleSheetLength::Percentage ) ); + + std::sort( stops.begin(), stops.end(), + []( const ColorStop& a, const ColorStop& b ) { return a.value < b.value; } ); + + const int SEGMENTS = 48; Float angleStep = 2.f * EE_PI / (Float)SEGMENTS; @@ -85,8 +96,8 @@ void RadialGradientDrawable::draw( const Vector2f& position, const Sizef& size ) } if ( isRepeating() ) { - Float firstPos = mColorStops.front().position; - Float lastPos = mColorStops.back().position; + Float firstPos = stops.front().value; + Float lastPos = stops.back().value; Float patternLen = lastPos - firstPos; if ( patternLen < 0.0001f ) { sBR->draw(); @@ -102,9 +113,9 @@ void RadialGradientDrawable::draw( const Vector2f& position, const Sizef& size ) for ( int r = rStart; r <= rEnd; r++ ) { Float repeatOff = (Float)r * patternLen; - for ( size_t i = 0; i + 1 < mColorStops.size(); i++ ) { - Float p0 = mColorStops[i].position + repeatOff; - Float p1 = mColorStops[i + 1].position + repeatOff; + for ( size_t i = 0; i + 1 < stops.size(); i++ ) { + Float p0 = stops[i].value + repeatOff; + Float p1 = stops[i + 1].value + repeatOff; if ( p1 <= 0.f || p0 >= 1.f ) continue; @@ -120,16 +131,18 @@ void RadialGradientDrawable::draw( const Vector2f& position, const Sizef& size ) Float bw = p1 - p0; Float frac0 = ( clip0 - p0 ) / bw; Float frac1 = ( clip1 - p0 ) / bw; - const Color& sc0 = mColorStops[i].color; - const Color& sc1 = mColorStops[i + 1].color; - Color cc0( (Uint8)( (Float)sc0.r + frac0 * (Float)( sc1.r - sc0.r ) ), - (Uint8)( (Float)sc0.g + frac0 * (Float)( sc1.g - sc0.g ) ), - (Uint8)( (Float)sc0.b + frac0 * (Float)( sc1.b - sc0.b ) ), - (Uint8)( (Float)sc0.a + frac0 * (Float)( sc1.a - sc0.a ) ) ); - Color cc1( (Uint8)( (Float)sc0.r + frac1 * (Float)( sc1.r - sc0.r ) ), - (Uint8)( (Float)sc0.g + frac1 * (Float)( sc1.g - sc0.g ) ), - (Uint8)( (Float)sc0.b + frac1 * (Float)( sc1.b - sc0.b ) ), - (Uint8)( (Float)sc0.a + frac1 * (Float)( sc1.a - sc0.a ) ) ); + const Color& sc0 = stops[i].color; + const Color& sc1 = stops[i + 1].color; + Color cc0( + (Uint8)( (Float)sc0.r + frac0 * (Float)( sc1.r - sc0.r ) ), + (Uint8)( (Float)sc0.g + frac0 * (Float)( sc1.g - sc0.g ) ), + (Uint8)( (Float)sc0.b + frac0 * (Float)( sc1.b - sc0.b ) ), + (Uint8)( (Float)sc0.a + frac0 * (Float)( sc1.a - sc0.a ) ) ); + Color cc1( + (Uint8)( (Float)sc0.r + frac1 * (Float)( sc1.r - sc0.r ) ), + (Uint8)( (Float)sc0.g + frac1 * (Float)( sc1.g - sc0.g ) ), + (Uint8)( (Float)sc0.b + frac1 * (Float)( sc1.b - sc0.b ) ), + (Uint8)( (Float)sc0.a + frac1 * (Float)( sc1.a - sc0.a ) ) ); Color fc0 = ( mColor.a == 255 ) ? cc0 : Color( cc0 ).blendAlpha( mColor.a ); Color fc1 = ( mColor.a == 255 ) ? cc1 : Color( cc1 ).blendAlpha( mColor.a ); @@ -147,12 +160,12 @@ void RadialGradientDrawable::draw( const Vector2f& position, const Sizef& size ) } } } else { - for ( size_t i = 0; i + 1 < mColorStops.size(); i++ ) { - Float r0 = mColorStops[i].position * maxR; - Float r1 = mColorStops[i + 1].position * maxR; + for ( size_t i = 0; i + 1 < stops.size(); i++ ) { + Float r0 = stops[i].value * maxR; + Float r1 = stops[i + 1].value * maxR; - const Color& c0 = mColorStops[i].color; - const Color& c1 = mColorStops[i + 1].color; + const Color& c0 = stops[i].color; + const Color& c1 = stops[i + 1].color; Color fc0 = ( mColor.a == 255 ) ? c0 : Color( c0 ).blendAlpha( mColor.a ); Color fc1 = ( mColor.a == 255 ) ? c1 : Color( c1 ).blendAlpha( mColor.a ); @@ -162,8 +175,9 @@ void RadialGradientDrawable::draw( const Vector2f& position, const Sizef& size ) Float cj = cosVals[j], sj = sinVals[j]; Float cj1 = cosVals[j + 1], sj1 = sinVals[j + 1]; - sBR->batchQuadFree( cx + r0 * cj + posX, cy + r0 * sj + posY, cx + r1 * cj + posX, - cy + r1 * sj + posY, cx + r1 * cj1 + posX, cy + r1 * sj1 + posY, + sBR->batchQuadFree( cx + r0 * cj + posX, cy + r0 * sj + posY, + cx + r1 * cj + posX, cy + r1 * sj + posY, + cx + r1 * cj1 + posX, cy + r1 * sj1 + posY, cx + r0 * cj1 + posX, cy + r0 * sj1 + posY ); } } @@ -179,15 +193,6 @@ RadialGradientDrawable::getColorStops() const { void RadialGradientDrawable::setColorStops( std::vector stops ) { mColorStops = std::move( stops ); - if ( mColorStops.size() >= 2 ) { - std::sort( - mColorStops.begin(), mColorStops.end(), - []( const ColorStop& a, const ColorStop& b ) { return a.position < b.position; } ); - if ( !isRepeating() ) { - mColorStops.front().position = 0.f; - mColorStops.back().position = 1.f; - } - } } RadialGradientDrawable::ShapeType RadialGradientDrawable::getShape() const { @@ -219,7 +224,7 @@ void RadialGradientDrawable::setSize( const Sizef& size ) { } bool RadialGradientDrawable::isRepeating() const { - return mDrawableType == REPEATINGRADIALGRADIENT; + return mDrawableType == Graphics::Drawable::REPEATINGRADIALGRADIENT; } -}} // namespace EE::Graphics +}} // namespace EE::UI diff --git a/src/tests/unit_tests/drawableimageparser_tests.cpp b/src/tests/unit_tests/drawableimageparser_tests.cpp index 0ded111b7..ebc0a69d6 100644 --- a/src/tests/unit_tests/drawableimageparser_tests.cpp +++ b/src/tests/unit_tests/drawableimageparser_tests.cpp @@ -2,13 +2,13 @@ #include #include -#include -#include #include #include #include #include #include +#include +#include #include #include #include @@ -64,8 +64,8 @@ UTEST( DrawableImageParser, TwoStopsWithPosition ) { auto* grad = static_cast( drawable ); const auto& stops = grad->getColorStops(); ASSERT_EQ( stops.size(), (size_t)2 ); - EXPECT_EQ( stops[0].position, 0.f ); - EXPECT_EQ( stops[1].position, 1.f ); + EXPECT_EQ( stops[0].value, 0.f ); + EXPECT_EQ( stops[1].value, 100.f ); Color expect05 = Color::fromString( "#f5eedd" ); Color expectEbe = Color::fromString( "#ebe0c2" ); EXPECT_TRUE( stops[0].color == expect05 ); @@ -161,7 +161,7 @@ UTEST( DrawableImageParser, ThreeStops ) { auto* grad = static_cast( drawable ); const auto& stops = grad->getColorStops(); ASSERT_EQ( stops.size(), (size_t)3 ); - EXPECT_EQ( stops[0].position, 0.f ); + EXPECT_EQ( stops[0].value, 0.f ); EXPECT_TRUE( stops[0].color == Color::fromString( "red" ) ); EXPECT_TRUE( stops[1].color == Color::fromString( "green" ) ); EXPECT_TRUE( stops[2].color == Color::fromString( "blue" ) ); @@ -191,16 +191,16 @@ UTEST( DrawableImageParser, ColorHint ) { * (stop 0 + 15 internal samples + stop 1 = 17, but setColorStops may * re-clamp, so we just check the boundaries and the hint position). */ ASSERT_GE( stops.size(), (size_t)17 ); - EXPECT_EQ( stops[0].position, 0.f ); + EXPECT_EQ( stops[0].value, 0.f ); EXPECT_TRUE( stops[0].color == Color::fromString( "red" ) ); - EXPECT_EQ( stops[stops.size() - 1].position, 1.f ); + EXPECT_EQ( stops[stops.size() - 1].value, 100.f ); EXPECT_TRUE( stops[stops.size() - 1].color == Color::fromString( "blue" ) ); /* At the hint position 25%, t = 0.25^0.5 = 0.5, so the color should * be ~50% red, 50% blue (rgb ~127, 0, 127). */ bool foundMidpoint = false; for ( const auto& s : stops ) { - if ( std::abs( s.position - 0.25f ) < 0.02f ) { + if ( std::abs( s.value - 25.f ) < 2.f ) { EXPECT_GT( s.color.r, (Uint8)100 ); EXPECT_GT( s.color.b, (Uint8)100 ); foundMidpoint = true; @@ -231,9 +231,9 @@ UTEST( DrawableImageParser, StopsWithoutPositions ) { const auto& stops = grad->getColorStops(); ASSERT_EQ( stops.size(), (size_t)3 ); /* Should be evenly distributed: 0%, 50%, 100% */ - EXPECT_EQ( stops[0].position, 0.f ); - EXPECT_NEAR( stops[1].position, 0.5f, 0.01f ); - EXPECT_EQ( stops[2].position, 1.f ); + EXPECT_EQ( stops[0].value, 0.f ); + EXPECT_NEAR( stops[1].value, 50.f, 0.01f ); + EXPECT_EQ( stops[2].value, 100.f ); eeSAFE_DELETE( drawable ); Engine::destroySingleton(); @@ -281,8 +281,8 @@ UTEST( DrawableImageParser, RepeatingTwoStops ) { EXPECT_TRUE( grad->isRepeating() ); const auto& stops = grad->getColorStops(); ASSERT_EQ( stops.size(), (size_t)2 ); - EXPECT_EQ( stops[0].position, 0.f ); - EXPECT_EQ( stops[1].position, 1.f ); + EXPECT_EQ( stops[0].value, 0.f ); + EXPECT_EQ( stops[1].value, 100.f ); eeSAFE_DELETE( drawable ); Engine::destroySingleton(); @@ -330,8 +330,8 @@ UTEST( DrawableImageParser, RepeatingWithPositions ) { EXPECT_TRUE( grad->isRepeating() ); const auto& stops = grad->getColorStops(); ASSERT_EQ( stops.size(), (size_t)2 ); - EXPECT_EQ( stops[0].position, 0.1f ); - EXPECT_EQ( stops[1].position, 0.4f ); + EXPECT_EQ( stops[0].value, 10.f ); + EXPECT_EQ( stops[1].value, 40.f ); eeSAFE_DELETE( drawable ); Engine::destroySingleton(); @@ -356,8 +356,8 @@ UTEST( DrawableImageParser, RadialTwoStops ) { EXPECT_FALSE( grad->isRepeating() ); const auto& stops = grad->getColorStops(); ASSERT_EQ( stops.size(), (size_t)2 ); - EXPECT_EQ( stops[0].position, 0.f ); - EXPECT_EQ( stops[1].position, 1.f ); + EXPECT_EQ( stops[0].value, 0.f ); + EXPECT_EQ( stops[1].value, 1.f ); eeSAFE_DELETE( drawable ); Engine::destroySingleton(); @@ -402,8 +402,8 @@ UTEST( DrawableImageParser, RadialWithPositions ) { auto* grad = static_cast( drawable ); const auto& stops = grad->getColorStops(); ASSERT_EQ( stops.size(), (size_t)2 ); - EXPECT_EQ( stops[0].position, 0.f ); - EXPECT_EQ( stops[1].position, 1.f ); + EXPECT_EQ( stops[0].value, 10.f ); + EXPECT_EQ( stops[1].value, 80.f ); eeSAFE_DELETE( drawable ); Engine::destroySingleton(); @@ -428,8 +428,8 @@ UTEST( DrawableImageParser, RepeatingRadial ) { EXPECT_TRUE( grad->isRepeating() ); const auto& stops = grad->getColorStops(); ASSERT_EQ( stops.size(), (size_t)2 ); - EXPECT_EQ( stops[0].position, 0.1f ); - EXPECT_EQ( stops[1].position, 0.4f ); + EXPECT_EQ( stops[0].value, 10.f ); + EXPECT_EQ( stops[1].value, 40.f ); eeSAFE_DELETE( drawable ); Engine::destroySingleton(); @@ -452,11 +452,18 @@ UTEST( DrawableImageParser, TwoLengthStopSyntax ) { auto* grad = static_cast( drawable ); EXPECT_TRUE( grad->isRepeating() ); const auto& stops = grad->getColorStops(); + /* red 0 10px → two stops: raw pos 0 and raw pos 10. blue 10px 20px → + * two stops: raw pos 10 and raw pos 20. All px positions are stored + * as raw pixel values (unit != NORMALIZED=true) and normalized at draw time. */ ASSERT_EQ( stops.size(), (size_t)4 ); - EXPECT_EQ( stops[0].position, 0.f ); - EXPECT_EQ( stops[1].position, 0.1f ); - EXPECT_EQ( stops[2].position, 0.1f ); - EXPECT_EQ( stops[3].position, 0.2f ); + EXPECT_EQ( stops[0].value, 0.f ); + EXPECT_EQ( stops[0].unit, EE::UI::CSS::StyleSheetLength::Percentage ); + EXPECT_EQ( stops[1].value, 10.f ); + EXPECT_EQ( stops[1].unit, EE::UI::CSS::StyleSheetLength::Px ); + EXPECT_EQ( stops[2].value, 10.f ); + EXPECT_EQ( stops[2].unit, EE::UI::CSS::StyleSheetLength::Px ); + EXPECT_EQ( stops[3].value, 20.f ); + EXPECT_EQ( stops[3].unit, EE::UI::CSS::StyleSheetLength::Px ); eeSAFE_DELETE( drawable ); Engine::destroySingleton();