Merge branch 'develop' into feature/grid-layout

This commit is contained in:
Martín Lucas Golini
2026-06-02 02:05:03 -03:00
7 changed files with 342 additions and 312 deletions

View File

@@ -1,57 +0,0 @@
#ifndef EE_GRAPHICS_LINEARGRADIENTDRAWABLE_HPP
#define EE_GRAPHICS_LINEARGRADIENTDRAWABLE_HPP
#include <eepp/graphics/drawable.hpp>
#include <eepp/system/color.hpp>
#include <vector>
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<ColorStop>& getColorStops() const;
void setColorStops( std::vector<ColorStop> stops );
Float getAngle() const;
void setAngle( Float angleDegrees );
void setSize( const Sizef& size );
bool isRepeating() const;
protected:
std::vector<ColorStop> mColorStops;
Float mAngle{ 180.f }; /* CSS angle: 0=to top, 90=to right, 180=to bottom (default) */
Sizef mSize;
};
}} // namespace EE::Graphics
#endif

View File

@@ -0,0 +1,72 @@
#ifndef EE_UI_LINEARGRADIENTDRAWABLE_HPP
#define EE_UI_LINEARGRADIENTDRAWABLE_HPP
#include <eepp/graphics/drawable.hpp>
#include <eepp/system/color.hpp>
#include <eepp/ui/css/stylesheetlength.hpp>
#include <vector>
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<ColorStop>& getColorStops() const;
void setColorStops( std::vector<ColorStop> stops );
Float getAngle() const;
void setAngle( Float angleDegrees );
void setSize( const Sizef& size );
bool isRepeating() const;
protected:
std::vector<ColorStop> mColorStops;
Float mAngle{ 180.f };
Sizef mSize;
};
}} // namespace EE::UI
#endif

View File

@@ -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 <eepp/graphics/drawable.hpp>
#include <eepp/system/color.hpp>
#include <eepp/ui/css/stylesheetlength.hpp>
#include <eepp/ui/lineargradientdrawable.hpp>
#include <vector>
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

View File

@@ -3,18 +3,19 @@
#include <eepp/graphics/drawable.hpp>
#include <eepp/graphics/drawablesearcher.hpp>
#include <eepp/graphics/fontmanager.hpp>
#include <eepp/graphics/lineargradientdrawable.hpp>
#include <eepp/graphics/radialgradientdrawable.hpp>
#include <eepp/graphics/rectangledrawable.hpp>
#include <eepp/graphics/triangledrawable.hpp>
#include <eepp/scene/scenemanager.hpp>
#include <eepp/system/log.hpp>
#include <eepp/system/luapattern.hpp>
#include <eepp/ui/css/drawableimageparser.hpp>
#include <eepp/ui/lineargradientdrawable.hpp>
#include <eepp/ui/radialgradientdrawable.hpp>
#include <eepp/ui/uiiconthememanager.hpp>
#include <eepp/ui/uinode.hpp>
#include <eepp/ui/uiscenenode.hpp>
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<LinearGradientDrawable::ColorStop> 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<Float> 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<RadialGradientDrawable::ColorStop> 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<Float> 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<std::string> 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<std::string> 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<std::string> 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() ) );
};

View File

@@ -1,20 +1,21 @@
#include <algorithm>
#include <cmath>
#include <eepp/graphics/globalbatchrenderer.hpp>
#include <eepp/graphics/lineargradientdrawable.hpp>
#include <eepp/math/math.hpp>
namespace EE { namespace Graphics {
#include <eepp/graphics/globalbatchrenderer.hpp>
#include <eepp/math/math.hpp>
#include <eepp/ui/lineargradientdrawable.hpp>
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<ColorStop> 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<PerpSeg> 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<ColorStop> 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

View File

@@ -2,20 +2,20 @@
#include <cmath>
#include <eepp/graphics/globalbatchrenderer.hpp>
#include <eepp/graphics/radialgradientdrawable.hpp>
#include <eepp/math/math.hpp>
#include <eepp/ui/radialgradientdrawable.hpp>
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<ColorStop> 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<ColorStop> 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

View File

@@ -2,13 +2,13 @@
#include <eepp/graphics/fontfamily.hpp>
#include <eepp/graphics/fonttruetype.hpp>
#include <eepp/graphics/lineargradientdrawable.hpp>
#include <eepp/graphics/radialgradientdrawable.hpp>
#include <eepp/scene/scenemanager.hpp>
#include <eepp/system/filesystem.hpp>
#include <eepp/system/sys.hpp>
#include <eepp/ui/css/drawableimageparser.hpp>
#include <eepp/ui/css/stylesheetspecification.hpp>
#include <eepp/ui/lineargradientdrawable.hpp>
#include <eepp/ui/radialgradientdrawable.hpp>
#include <eepp/ui/uiscenenode.hpp>
#include <eepp/ui/uithememanager.hpp>
#include <eepp/ui/uiwidget.hpp>
@@ -64,8 +64,8 @@ UTEST( DrawableImageParser, TwoStopsWithPosition ) {
auto* grad = static_cast<LinearGradientDrawable*>( 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<LinearGradientDrawable*>( 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<RadialGradientDrawable*>( 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<LinearGradientDrawable*>( 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();