mirror of
https://github.com/SpartanJ/eepp.git
synced 2026-09-30 18:20:19 +03:00
Greatly improve linear-gradient support (still not complete). Added repeating-linear-gradient, radial-gradient and repeating-radial-gradient (WIP though).
This commit is contained in:
@@ -377,7 +377,7 @@
|
||||
"working_dir": "${project_root}/bin"
|
||||
},
|
||||
{
|
||||
"args": "-c system --hn-dark",
|
||||
"args": "https://www.0xsid.com/blog/wont-download-your-app",
|
||||
"command": "${project_root}/bin/eepp-ui-html-debug",
|
||||
"name": "eepp-ui-html-debug",
|
||||
"working_dir": "${project_root}/bin"
|
||||
|
||||
@@ -33,6 +33,9 @@ class EE_API Drawable {
|
||||
UIBACKGROUNDDRAWABLE,
|
||||
RICHTEXT,
|
||||
LINEARGRADIENT,
|
||||
REPEATINGLINEARGRADIENT,
|
||||
RADIALGRADIENT,
|
||||
REPEATINGRADIALGRADIENT,
|
||||
CUSTOM
|
||||
};
|
||||
|
||||
|
||||
@@ -18,8 +18,9 @@ class EE_API LinearGradientDrawable : public Drawable {
|
||||
};
|
||||
|
||||
static LinearGradientDrawable* New();
|
||||
static LinearGradientDrawable* NewRepeating();
|
||||
|
||||
LinearGradientDrawable();
|
||||
LinearGradientDrawable( Drawable::Type drawableType = LINEARGRADIENT );
|
||||
|
||||
virtual Sizef getSize();
|
||||
|
||||
@@ -43,6 +44,8 @@ class EE_API LinearGradientDrawable : public Drawable {
|
||||
|
||||
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) */
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
#ifndef EE_GRAPHICS_RADIALGRADIENTDRAWABLE_HPP
|
||||
#define EE_GRAPHICS_RADIALGRADIENTDRAWABLE_HPP
|
||||
|
||||
#include <eepp/graphics/drawable.hpp>
|
||||
#include <eepp/system/color.hpp>
|
||||
#include <vector>
|
||||
|
||||
namespace EE { namespace Graphics {
|
||||
|
||||
class EE_API RadialGradientDrawable : public Drawable {
|
||||
public:
|
||||
struct ColorStop {
|
||||
Float position{ 0 };
|
||||
Color color;
|
||||
|
||||
ColorStop() : color( Color::White ) {}
|
||||
ColorStop( Float pos, const Color& col ) : position( pos ), color( col ) {}
|
||||
};
|
||||
|
||||
enum ShapeType { CIRCLE, ELLIPSE };
|
||||
enum Extent { CLOSEST_SIDE, FARTHEST_SIDE, CLOSEST_CORNER, FARTHEST_CORNER };
|
||||
|
||||
static RadialGradientDrawable* New();
|
||||
static RadialGradientDrawable* NewRepeating();
|
||||
|
||||
RadialGradientDrawable( Drawable::Type drawableType = RADIALGRADIENT );
|
||||
|
||||
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 );
|
||||
|
||||
ShapeType getShape() const;
|
||||
|
||||
void setShape( ShapeType shape );
|
||||
|
||||
Extent getExtent() const;
|
||||
|
||||
void setExtent( Extent extent );
|
||||
|
||||
const Vector2f& getCenter() const;
|
||||
|
||||
void setCenter( const Vector2f& centerNormalized );
|
||||
|
||||
void setSize( const Sizef& size );
|
||||
|
||||
bool isRepeating() const;
|
||||
|
||||
protected:
|
||||
std::vector<ColorStop> mColorStops;
|
||||
ShapeType mShape{ CIRCLE };
|
||||
Extent mExtent{ FARTHEST_CORNER };
|
||||
Vector2f mCenter{ 0.5f, 0.5f };
|
||||
Sizef mSize;
|
||||
};
|
||||
|
||||
}} // namespace EE::Graphics
|
||||
|
||||
#endif
|
||||
@@ -10,7 +10,12 @@ LinearGradientDrawable* LinearGradientDrawable::New() {
|
||||
return eeNew( LinearGradientDrawable, () );
|
||||
}
|
||||
|
||||
LinearGradientDrawable::LinearGradientDrawable() : Drawable( LINEARGRADIENT ) {}
|
||||
LinearGradientDrawable* LinearGradientDrawable::NewRepeating() {
|
||||
return eeNew( LinearGradientDrawable, ( REPEATINGLINEARGRADIENT ) );
|
||||
}
|
||||
|
||||
LinearGradientDrawable::LinearGradientDrawable( Drawable::Type drawableType ) :
|
||||
Drawable( drawableType ) {}
|
||||
|
||||
Sizef LinearGradientDrawable::getSize() {
|
||||
return mSize;
|
||||
@@ -51,7 +56,6 @@ void LinearGradientDrawable::draw( const Vector2f& position, const Sizef& size )
|
||||
auto addT = [&]( Float t, Float px, Float py ) {
|
||||
if ( !std::isfinite( t ) )
|
||||
return;
|
||||
// Clamp check point to box
|
||||
if ( px >= -0.001f && px <= w + 0.001f && py >= -0.001f && py <= h + 0.001f ) {
|
||||
tVals[validCount++] = t;
|
||||
}
|
||||
@@ -85,6 +89,8 @@ void LinearGradientDrawable::draw( const Vector2f& position, const Sizef& size )
|
||||
if ( std::abs( tMax - tMin ) < 0.0001f )
|
||||
return;
|
||||
|
||||
Float txLen = tMax - tMin;
|
||||
|
||||
// Perpendicular direction (rotated 90° clockwise in screen coords)
|
||||
Float px = -dy;
|
||||
Float py = dx;
|
||||
@@ -94,81 +100,210 @@ void LinearGradientDrawable::draw( const Vector2f& position, const Sizef& size )
|
||||
sBR->setBlendMode( BlendMode::Alpha() );
|
||||
sBR->quadsBegin();
|
||||
|
||||
// Pre-compute position on gradient line: center + t * dir
|
||||
Float txLen = tMax - tMin;
|
||||
|
||||
// Pre-compute perpendicular intersections for all stops
|
||||
struct PerpSeg {
|
||||
Vector2f a;
|
||||
Vector2f b;
|
||||
Float gradPos; // t value along gradient line
|
||||
};
|
||||
std::vector<PerpSeg> segments;
|
||||
segments.reserve( mColorStops.size() );
|
||||
|
||||
for ( size_t i = 0; i < mColorStops.size(); i++ ) {
|
||||
PerpSeg seg;
|
||||
Float t = tMin + mColorStops[i].position * txLen;
|
||||
seg.gradPos = t;
|
||||
Float gx = cx + t * dx;
|
||||
Float gy = cy + t * dy;
|
||||
|
||||
// Find perpendicular line intersections with the box
|
||||
Float ptVals[4];
|
||||
int ptCount = 0;
|
||||
|
||||
auto addPT = [&]( Float pt, Float qx, Float qy ) {
|
||||
if ( !std::isfinite( pt ) )
|
||||
return;
|
||||
if ( qx >= -0.001f && qx <= w + 0.001f && qy >= -0.001f && qy <= h + 0.001f ) {
|
||||
ptVals[ptCount++] = pt;
|
||||
}
|
||||
};
|
||||
|
||||
if ( std::abs( px ) > 0.0001f ) {
|
||||
Float ptLeft = -gx / px;
|
||||
addPT( ptLeft, 0, gy + ptLeft * py );
|
||||
Float ptRight = ( w - gx ) / px;
|
||||
addPT( ptRight, w, gy + ptRight * py );
|
||||
}
|
||||
if ( std::abs( py ) > 0.0001f ) {
|
||||
Float ptTop = -gy / py;
|
||||
addPT( ptTop, gx + ptTop * px, 0 );
|
||||
Float ptBottom = ( h - gy ) / py;
|
||||
addPT( ptBottom, gx + ptBottom * px, h );
|
||||
}
|
||||
|
||||
if ( ptCount < 2 )
|
||||
if ( isRepeating() ) {
|
||||
Float firstPos = mColorStops.front().position;
|
||||
Float lastPos = mColorStops.back().position;
|
||||
Float patternLen = lastPos - firstPos;
|
||||
if ( patternLen < 0.0001f ) {
|
||||
sBR->draw();
|
||||
return;
|
||||
|
||||
Float ptMin = ptVals[0];
|
||||
Float ptMax = ptVals[0];
|
||||
for ( int j = 1; j < ptCount; j++ ) {
|
||||
if ( ptVals[j] < ptMin )
|
||||
ptMin = ptVals[j];
|
||||
if ( ptVals[j] > ptMax )
|
||||
ptMax = ptVals[j];
|
||||
}
|
||||
|
||||
seg.a = Vector2f( gx + ptMin * px + position.x, gy + ptMin * py + position.y );
|
||||
seg.b = Vector2f( gx + ptMax * px + position.x, gy + ptMax * py + position.y );
|
||||
segments.push_back( seg );
|
||||
}
|
||||
// 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;
|
||||
if ( rEnd - rStart > MAX_REPEATS )
|
||||
rEnd = rStart + MAX_REPEATS;
|
||||
|
||||
// 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 ( int r = rStart; r <= rEnd; r++ ) {
|
||||
Float repeatOff = (Float)r * patternLen;
|
||||
|
||||
Color fc0 = ( mColor.a == 255 ) ? c0 : Color( c0 ).blendAlpha( mColor.a );
|
||||
Color fc1 = ( mColor.a == 255 ) ? c1 : Color( c1 ).blendAlpha( mColor.a );
|
||||
for ( size_t i = 0; i + 1 < mColorStops.size(); i++ ) {
|
||||
Float p0 = mColorStops[i].position + repeatOff;
|
||||
Float p1 = mColorStops[i + 1].position + repeatOff;
|
||||
|
||||
sBR->quadsSetColorFree( fc0, fc1, fc1, fc0 );
|
||||
if ( p1 <= 0.f || p0 >= 1.f )
|
||||
continue;
|
||||
|
||||
const PerpSeg& s0 = segments[i];
|
||||
const PerpSeg& s1 = segments[i + 1];
|
||||
Float clip0 = eemax( 0.f, p0 );
|
||||
Float clip1 = eemin( 1.f, p1 );
|
||||
if ( clip1 - clip0 < 0.0001f )
|
||||
continue;
|
||||
|
||||
sBR->batchQuadFree( s0.a.x, s0.a.y, s1.a.x, s1.a.y, s1.b.x, s1.b.y, s0.b.x, s0.b.y );
|
||||
// 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 ) ) );
|
||||
|
||||
Float tc0 = tMin + clip0 * txLen;
|
||||
Float tc1 = tMin + clip1 * txLen;
|
||||
Float gx0 = cx + tc0 * dx;
|
||||
Float gy0 = cy + tc0 * dy;
|
||||
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 ) {
|
||||
if ( !std::isfinite( pt ) )
|
||||
return;
|
||||
if ( qx >= -0.001f && qx <= w + 0.001f && qy >= -0.001f && qy <= h + 0.001f )
|
||||
ptVals0[ptCount0++] = pt;
|
||||
};
|
||||
if ( std::abs( px ) > 0.0001f ) {
|
||||
Float ptLeft = -gx0 / px;
|
||||
addPT0( ptLeft, 0, gy0 + ptLeft * py );
|
||||
Float ptRight = ( w - gx0 ) / px;
|
||||
addPT0( ptRight, w, gy0 + ptRight * py );
|
||||
}
|
||||
if ( std::abs( py ) > 0.0001f ) {
|
||||
Float ptTop = -gy0 / py;
|
||||
addPT0( ptTop, gx0 + ptTop * px, 0 );
|
||||
Float ptBottom = ( h - gy0 ) / py;
|
||||
addPT0( ptBottom, gx0 + ptBottom * px, h );
|
||||
}
|
||||
if ( ptCount0 < 2 )
|
||||
continue;
|
||||
Float ptMin0 = ptVals0[0], ptMax0 = ptVals0[0];
|
||||
for ( int j = 1; j < ptCount0; j++ ) {
|
||||
if ( ptVals0[j] < ptMin0 )
|
||||
ptMin0 = ptVals0[j];
|
||||
if ( ptVals0[j] > ptMax0 )
|
||||
ptMax0 = ptVals0[j];
|
||||
}
|
||||
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 ) {
|
||||
if ( !std::isfinite( pt ) )
|
||||
return;
|
||||
if ( qx >= -0.001f && qx <= w + 0.001f && qy >= -0.001f && qy <= h + 0.001f )
|
||||
ptVals1[ptCount1++] = pt;
|
||||
};
|
||||
if ( std::abs( px ) > 0.0001f ) {
|
||||
Float ptLeft = -gx1 / px;
|
||||
addPT1( ptLeft, 0, gy1 + ptLeft * py );
|
||||
Float ptRight = ( w - gx1 ) / px;
|
||||
addPT1( ptRight, w, gy1 + ptRight * py );
|
||||
}
|
||||
if ( std::abs( py ) > 0.0001f ) {
|
||||
Float ptTop = -gy1 / py;
|
||||
addPT1( ptTop, gx1 + ptTop * px, 0 );
|
||||
Float ptBottom = ( h - gy1 ) / py;
|
||||
addPT1( ptBottom, gx1 + ptBottom * px, h );
|
||||
}
|
||||
if ( ptCount1 < 2 )
|
||||
continue;
|
||||
Float ptMin1 = ptVals1[0], ptMax1 = ptVals1[0];
|
||||
for ( int j = 1; j < ptCount1; j++ ) {
|
||||
if ( ptVals1[j] < ptMin1 )
|
||||
ptMin1 = ptVals1[j];
|
||||
if ( ptVals1[j] > ptMax1 )
|
||||
ptMax1 = ptVals1[j];
|
||||
}
|
||||
Vector2f a1( gx1 + ptMin1 * px + position.x, gy1 + ptMin1 * py + position.y );
|
||||
Vector2f b1( gx1 + ptMax1 * px + position.x, gy1 + ptMax1 * py + position.y );
|
||||
|
||||
Color fc0 = ( mColor.a == 255 ) ? cc0 : Color( cc0 ).blendAlpha( mColor.a );
|
||||
Color fc1 = ( mColor.a == 255 ) ? cc1 : Color( cc1 ).blendAlpha( mColor.a );
|
||||
sBR->quadsSetColorFree( fc0, fc1, fc1, fc0 );
|
||||
sBR->batchQuadFree( a0.x, a0.y, a1.x, a1.y, b1.x, b1.y, b0.x, b0.y );
|
||||
}
|
||||
}
|
||||
} 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() );
|
||||
|
||||
for ( size_t i = 0; i < mColorStops.size(); i++ ) {
|
||||
PerpSeg seg;
|
||||
Float t = tMin + mColorStops[i].position * txLen;
|
||||
seg.gradPos = t;
|
||||
Float gx = cx + t * dx;
|
||||
Float gy = cy + t * dy;
|
||||
|
||||
// Find perpendicular line intersections with the box
|
||||
Float ptVals[4];
|
||||
int ptCount = 0;
|
||||
|
||||
auto addPT = [&]( Float pt, Float qx, Float qy ) {
|
||||
if ( !std::isfinite( pt ) )
|
||||
return;
|
||||
if ( qx >= -0.001f && qx <= w + 0.001f && qy >= -0.001f && qy <= h + 0.001f ) {
|
||||
ptVals[ptCount++] = pt;
|
||||
}
|
||||
};
|
||||
|
||||
if ( std::abs( px ) > 0.0001f ) {
|
||||
Float ptLeft = -gx / px;
|
||||
addPT( ptLeft, 0, gy + ptLeft * py );
|
||||
Float ptRight = ( w - gx ) / px;
|
||||
addPT( ptRight, w, gy + ptRight * py );
|
||||
}
|
||||
if ( std::abs( py ) > 0.0001f ) {
|
||||
Float ptTop = -gy / py;
|
||||
addPT( ptTop, gx + ptTop * px, 0 );
|
||||
Float ptBottom = ( h - gy ) / py;
|
||||
addPT( ptBottom, gx + ptBottom * px, h );
|
||||
}
|
||||
|
||||
if ( ptCount < 2 )
|
||||
return;
|
||||
|
||||
Float ptMin = ptVals[0];
|
||||
Float ptMax = ptVals[0];
|
||||
for ( int j = 1; j < ptCount; j++ ) {
|
||||
if ( ptVals[j] < ptMin )
|
||||
ptMin = ptVals[j];
|
||||
if ( ptVals[j] > ptMax )
|
||||
ptMax = ptVals[j];
|
||||
}
|
||||
|
||||
seg.a = Vector2f( gx + ptMin * px + position.x, gy + ptMin * py + position.y );
|
||||
seg.b = Vector2f( gx + ptMax * px + position.x, gy + ptMax * py + position.y );
|
||||
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;
|
||||
|
||||
Color fc0 = ( mColor.a == 255 ) ? c0 : Color( c0 ).blendAlpha( mColor.a );
|
||||
Color fc1 = ( mColor.a == 255 ) ? c1 : Color( c1 ).blendAlpha( mColor.a );
|
||||
|
||||
sBR->quadsSetColorFree( fc0, fc1, fc1, fc0 );
|
||||
|
||||
const PerpSeg& s0 = segments[i];
|
||||
const PerpSeg& s1 = segments[i + 1];
|
||||
|
||||
sBR->batchQuadFree( s0.a.x, s0.a.y, s1.a.x, s1.a.y, s1.b.x, s1.b.y, s0.b.x, s0.b.y );
|
||||
}
|
||||
}
|
||||
|
||||
sBR->draw();
|
||||
@@ -185,11 +320,17 @@ void LinearGradientDrawable::setColorStops( std::vector<ColorStop> stops ) {
|
||||
std::sort(
|
||||
mColorStops.begin(), mColorStops.end(),
|
||||
[]( const ColorStop& a, const ColorStop& b ) { return a.position < b.position; } );
|
||||
mColorStops.front().position = 0.f;
|
||||
mColorStops.back().position = 1.f;
|
||||
if ( !isRepeating() ) {
|
||||
mColorStops.front().position = 0.f;
|
||||
mColorStops.back().position = 1.f;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool LinearGradientDrawable::isRepeating() const {
|
||||
return mDrawableType == REPEATINGLINEARGRADIENT;
|
||||
}
|
||||
|
||||
Float LinearGradientDrawable::getAngle() const {
|
||||
return mAngle;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,225 @@
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
|
||||
#include <eepp/graphics/globalbatchrenderer.hpp>
|
||||
#include <eepp/graphics/radialgradientdrawable.hpp>
|
||||
#include <eepp/math/math.hpp>
|
||||
|
||||
namespace EE { namespace Graphics {
|
||||
|
||||
RadialGradientDrawable* RadialGradientDrawable::New() {
|
||||
return eeNew( RadialGradientDrawable, () );
|
||||
}
|
||||
|
||||
RadialGradientDrawable* RadialGradientDrawable::NewRepeating() {
|
||||
return eeNew( RadialGradientDrawable, ( REPEATINGRADIALGRADIENT ) );
|
||||
}
|
||||
|
||||
RadialGradientDrawable::RadialGradientDrawable( Drawable::Type drawableType ) :
|
||||
Drawable( drawableType ) {}
|
||||
|
||||
Sizef RadialGradientDrawable::getSize() {
|
||||
return mSize;
|
||||
}
|
||||
|
||||
Sizef RadialGradientDrawable::getPixelsSize() {
|
||||
return mSize;
|
||||
}
|
||||
|
||||
void RadialGradientDrawable::draw() {
|
||||
draw( mPosition, mSize );
|
||||
}
|
||||
|
||||
void RadialGradientDrawable::draw( const Vector2f& position ) {
|
||||
draw( position, mSize );
|
||||
}
|
||||
|
||||
static Float computeRadius( Float cx, Float cy, Float w, Float h,
|
||||
RadialGradientDrawable::Extent extent ) {
|
||||
Float dTL = std::sqrt( cx * cx + cy * cy );
|
||||
Float dTR = std::sqrt( ( w - cx ) * ( w - cx ) + cy * cy );
|
||||
Float dBL = std::sqrt( cx * cx + ( h - cy ) * ( h - cy ) );
|
||||
Float dBR = std::sqrt( ( w - cx ) * ( w - cx ) + ( h - cy ) * ( h - cy ) );
|
||||
|
||||
switch ( extent ) {
|
||||
case RadialGradientDrawable::CLOSEST_SIDE:
|
||||
return eemin( eemin( cx, w - cx ), eemin( cy, h - cy ) );
|
||||
case RadialGradientDrawable::FARTHEST_SIDE:
|
||||
return eemax( eemax( cx, w - cx ), eemax( cy, h - cy ) );
|
||||
case RadialGradientDrawable::CLOSEST_CORNER:
|
||||
return eemin( eemin( dTL, dTR ), eemin( dBL, dBR ) );
|
||||
case RadialGradientDrawable::FARTHEST_CORNER:
|
||||
default:
|
||||
return eemax( eemax( dTL, dTR ), eemax( dBL, dBR ) );
|
||||
}
|
||||
}
|
||||
|
||||
void RadialGradientDrawable::draw( const Vector2f& position, const Sizef& size ) {
|
||||
if ( mColorStops.size() < 2 || size.getWidth() <= 0 || size.getHeight() <= 0 )
|
||||
return;
|
||||
|
||||
Float w = size.getWidth();
|
||||
Float h = size.getHeight();
|
||||
|
||||
Float cx = mCenter.x * w;
|
||||
Float cy = mCenter.y * h;
|
||||
Float maxR = computeRadius( cx, cy, w, h, mExtent );
|
||||
|
||||
const int SEGMENTS = 48;
|
||||
Float angleStep = 2.f * EE_PI / (Float)SEGMENTS;
|
||||
|
||||
BatchRenderer* sBR = GlobalBatchRenderer::instance();
|
||||
sBR->setTexture( NULL );
|
||||
sBR->setBlendMode( BlendMode::Alpha() );
|
||||
sBR->quadsBegin();
|
||||
|
||||
Float posX = position.x;
|
||||
Float posY = position.y;
|
||||
|
||||
Float cosVals[SEGMENTS + 1];
|
||||
Float sinVals[SEGMENTS + 1];
|
||||
for ( int j = 0; j <= SEGMENTS; j++ ) {
|
||||
Float a = (Float)j * angleStep;
|
||||
cosVals[j] = std::cos( a );
|
||||
sinVals[j] = std::sin( a );
|
||||
}
|
||||
|
||||
if ( isRepeating() ) {
|
||||
Float firstPos = mColorStops.front().position;
|
||||
Float lastPos = mColorStops.back().position;
|
||||
Float patternLen = lastPos - firstPos;
|
||||
if ( patternLen < 0.0001f ) {
|
||||
sBR->draw();
|
||||
return;
|
||||
}
|
||||
|
||||
int rStart = (int)std::floor( ( -firstPos ) / patternLen );
|
||||
int rEnd = (int)std::ceil( ( 1.f - lastPos ) / patternLen );
|
||||
const int MAX_REPEATS = 64;
|
||||
if ( rEnd - rStart > MAX_REPEATS )
|
||||
rEnd = rStart + MAX_REPEATS;
|
||||
|
||||
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;
|
||||
|
||||
if ( p1 <= 0.f || p0 >= 1.f )
|
||||
continue;
|
||||
|
||||
Float clip0 = eemax( 0.f, p0 );
|
||||
Float clip1 = eemin( 1.f, p1 );
|
||||
if ( clip1 - clip0 < 0.0001f )
|
||||
continue;
|
||||
|
||||
Float r0 = clip0 * maxR;
|
||||
Float r1 = clip1 * maxR;
|
||||
|
||||
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 ) ) );
|
||||
|
||||
Color fc0 = ( mColor.a == 255 ) ? cc0 : Color( cc0 ).blendAlpha( mColor.a );
|
||||
Color fc1 = ( mColor.a == 255 ) ? cc1 : Color( cc1 ).blendAlpha( mColor.a );
|
||||
sBR->quadsSetColorFree( fc0, fc1, fc1, fc0 );
|
||||
|
||||
for ( int j = 0; j < SEGMENTS; j++ ) {
|
||||
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,
|
||||
cx + r0 * cj1 + posX, cy + r0 * sj1 + posY );
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for ( size_t i = 0; i + 1 < mColorStops.size(); i++ ) {
|
||||
Float r0 = mColorStops[i].position * maxR;
|
||||
Float r1 = mColorStops[i + 1].position * maxR;
|
||||
|
||||
const Color& c0 = mColorStops[i].color;
|
||||
const Color& c1 = mColorStops[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 );
|
||||
|
||||
sBR->quadsSetColorFree( fc0, fc1, fc1, fc0 );
|
||||
|
||||
for ( int j = 0; j < SEGMENTS; j++ ) {
|
||||
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,
|
||||
cx + r0 * cj1 + posX, cy + r0 * sj1 + posY );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sBR->draw();
|
||||
}
|
||||
|
||||
const std::vector<RadialGradientDrawable::ColorStop>&
|
||||
RadialGradientDrawable::getColorStops() const {
|
||||
return mColorStops;
|
||||
}
|
||||
|
||||
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 {
|
||||
return mShape;
|
||||
}
|
||||
|
||||
void RadialGradientDrawable::setShape( ShapeType shape ) {
|
||||
mShape = shape;
|
||||
}
|
||||
|
||||
RadialGradientDrawable::Extent RadialGradientDrawable::getExtent() const {
|
||||
return mExtent;
|
||||
}
|
||||
|
||||
void RadialGradientDrawable::setExtent( Extent extent ) {
|
||||
mExtent = extent;
|
||||
}
|
||||
|
||||
const Vector2f& RadialGradientDrawable::getCenter() const {
|
||||
return mCenter;
|
||||
}
|
||||
|
||||
void RadialGradientDrawable::setCenter( const Vector2f& centerNormalized ) {
|
||||
mCenter = centerNormalized;
|
||||
}
|
||||
|
||||
void RadialGradientDrawable::setSize( const Sizef& size ) {
|
||||
mSize = size;
|
||||
}
|
||||
|
||||
bool RadialGradientDrawable::isRepeating() const {
|
||||
return mDrawableType == REPEATINGRADIALGRADIENT;
|
||||
}
|
||||
|
||||
}} // namespace EE::Graphics
|
||||
@@ -4,6 +4,7 @@
|
||||
#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>
|
||||
@@ -22,9 +23,32 @@ struct ColorStopResult {
|
||||
bool valid{ false };
|
||||
bool isHint{ false };
|
||||
Color color;
|
||||
Float position{ -1.f }; /* -1 = not specified */
|
||||
Float position{ -1.f }; /* -1 = not specified */
|
||||
Float secondPosition{ -1.f }; /* -1 = single-position stop */
|
||||
};
|
||||
|
||||
static Float parsePositionValue( const std::string& token ) {
|
||||
if ( token.empty() )
|
||||
return -1.f;
|
||||
Float val;
|
||||
if ( token.back() == '%' ) {
|
||||
std::string num = token.substr( 0, token.size() - 1 );
|
||||
if ( String::fromString( val, num ) )
|
||||
return eemax( 0.f, eemin( 100.f, val ) ) / 100.f;
|
||||
return -1.f;
|
||||
}
|
||||
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;
|
||||
}
|
||||
if ( String::fromString( val, token ) )
|
||||
return eemax( 0.f, eemin( 100.f, val ) ) / 100.f;
|
||||
return -1.f;
|
||||
}
|
||||
|
||||
static ColorStopResult parseColorStop( const std::string& stopStr ) {
|
||||
ColorStopResult result;
|
||||
|
||||
@@ -45,7 +69,7 @@ static ColorStopResult parseColorStop( const std::string& stopStr ) {
|
||||
}
|
||||
}
|
||||
|
||||
// Find the last whitespace that separates color from position
|
||||
// Find the last whitespace that separates color from position(s)
|
||||
size_t lastSpace = std::string::npos;
|
||||
for ( size_t i = str.size(); i > 0; i-- ) {
|
||||
if ( str[i - 1] == ' ' ) {
|
||||
@@ -69,23 +93,38 @@ static ColorStopResult parseColorStop( const std::string& stopStr ) {
|
||||
return result;
|
||||
}
|
||||
|
||||
// Try to parse the position part
|
||||
String::trimInPlace( posPart );
|
||||
Float posVal = -1.f;
|
||||
if ( !posPart.empty() ) {
|
||||
if ( posPart.back() == '%' ) {
|
||||
std::string num = posPart.substr( 0, posPart.size() - 1 );
|
||||
if ( String::fromString( posVal, num ) ) {
|
||||
posVal = eemax( 0.f, eemin( 100.f, posVal ) ) / 100.f;
|
||||
String::trimInPlace( colorPart );
|
||||
|
||||
// Try to split colorPart further for two-length stops (e.g. "red 0 10px")
|
||||
if ( !Color::isColorString( colorPart ) ) {
|
||||
size_t innerSpace = std::string::npos;
|
||||
for ( size_t i = colorPart.size(); i > 0; i-- ) {
|
||||
if ( colorPart[i - 1] == ' ' ) {
|
||||
innerSpace = i - 1;
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
if ( String::fromString( posVal, posPart ) ) {
|
||||
posVal = eemax( 0.f, eemin( 100.f, posVal ) ) / 100.f;
|
||||
}
|
||||
if ( innerSpace != std::string::npos ) {
|
||||
std::string altColor = colorPart.substr( 0, innerSpace );
|
||||
String::trimInPlace( altColor );
|
||||
if ( Color::isColorString( altColor ) ) {
|
||||
std::string firstPos = colorPart.substr( innerSpace + 1 );
|
||||
String::trimInPlace( firstPos );
|
||||
Float p1 = parsePositionValue( firstPos );
|
||||
Float p2 = parsePositionValue( posPart );
|
||||
result.color = Color::fromString( altColor );
|
||||
result.position = p1;
|
||||
result.secondPosition = p2 >= 0.f ? p2 : -1.f;
|
||||
result.valid = true;
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
String::trimInPlace( colorPart );
|
||||
// Try to parse the position part (single position)
|
||||
Float posVal = parsePositionValue( posPart );
|
||||
|
||||
if ( Color::isColorString( colorPart ) ) {
|
||||
result.color = Color::fromString( colorPart );
|
||||
result.position = posVal;
|
||||
@@ -141,8 +180,9 @@ void DrawableImageParser::addParser( const std::string& name,
|
||||
}
|
||||
|
||||
void DrawableImageParser::registerBaseParsers() {
|
||||
mFuncs["linear-gradient"] = []( const FunctionString& functionType, const Sizef& /*size*/,
|
||||
bool& ownIt, UINode* node ) -> Drawable* {
|
||||
// Shared parsing logic for linear-gradient and repeating-linear-gradient
|
||||
auto parseGradient = []( const FunctionString& functionType, bool& ownIt, UINode* node,
|
||||
bool repeating ) -> Drawable* {
|
||||
const auto& params( functionType.getParameters() );
|
||||
if ( params.size() < 2 )
|
||||
return NULL;
|
||||
@@ -175,7 +215,6 @@ void DrawableImageParser::registerBaseParsers() {
|
||||
angle = 315.f;
|
||||
}
|
||||
} else if ( !Color::isColorString( firstParam ) && !functionType.parameterWasString( 0 ) ) {
|
||||
// Try to parse angle value
|
||||
std::string angleStr = firstParam;
|
||||
String::toLowerInPlace( angleStr );
|
||||
|
||||
@@ -196,7 +235,7 @@ void DrawableImageParser::registerBaseParsers() {
|
||||
} else if ( String::endsWith( s, "grad" ) ) {
|
||||
std::string num = s.substr( 0, s.size() - 4 );
|
||||
if ( String::fromString( val, num ) ) {
|
||||
out = val * 0.9f; /* 1 grad = 0.9 deg */
|
||||
out = val * 0.9f;
|
||||
return true;
|
||||
}
|
||||
} else if ( String::endsWith( s, "turn" ) ) {
|
||||
@@ -206,7 +245,7 @@ void DrawableImageParser::registerBaseParsers() {
|
||||
return true;
|
||||
}
|
||||
} else if ( String::fromString( val, s ) ) {
|
||||
out = val; /* bare number treated as degrees */
|
||||
out = val;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
@@ -228,6 +267,39 @@ void DrawableImageParser::registerBaseParsers() {
|
||||
continue;
|
||||
|
||||
gradientStops.push_back( stop );
|
||||
if ( stop.secondPosition >= 0.f ) {
|
||||
ColorStopResult stop2;
|
||||
stop2.valid = true;
|
||||
stop2.color = stop.color;
|
||||
stop2.position = stop.secondPosition;
|
||||
stop2.secondPosition = -1.f;
|
||||
gradientStops.push_back( stop2 );
|
||||
}
|
||||
}
|
||||
|
||||
// CSS §4.3: If a color-stop has a position that is less than
|
||||
// the specified position of any color-stop before it in the
|
||||
// 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.
|
||||
{
|
||||
Float maxPos = -1.f;
|
||||
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.secondPosition >= 0.f ) {
|
||||
if ( s.secondPosition < maxPos )
|
||||
s.secondPosition = maxPos;
|
||||
else
|
||||
maxPos = s.secondPosition;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Need at least two color stops (hints don't count)
|
||||
@@ -248,6 +320,21 @@ void DrawableImageParser::registerBaseParsers() {
|
||||
return a.position < b.position;
|
||||
} );
|
||||
|
||||
// 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;
|
||||
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;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// 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++ ) {
|
||||
@@ -279,17 +366,21 @@ void DrawableImageParser::registerBaseParsers() {
|
||||
i = nextKnown;
|
||||
}
|
||||
|
||||
// Clamp first and last color stops. Skip hints at the boundaries.
|
||||
for ( size_t i = 0; i < gradientStops.size(); i++ ) {
|
||||
if ( !gradientStops[i].isHint ) {
|
||||
gradientStops[i].position = 0.f;
|
||||
break;
|
||||
// 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;
|
||||
for ( size_t i = gradientStops.size(); i > 0; i-- ) {
|
||||
if ( !gradientStops[i - 1].isHint ) {
|
||||
gradientStops[i - 1].position = 1.f;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -356,13 +447,254 @@ void DrawableImageParser::registerBaseParsers() {
|
||||
if ( stops.size() < 2 )
|
||||
return NULL;
|
||||
|
||||
LinearGradientDrawable* drawable = LinearGradientDrawable::New();
|
||||
LinearGradientDrawable* drawable =
|
||||
repeating ? LinearGradientDrawable::NewRepeating() : LinearGradientDrawable::New();
|
||||
drawable->setColorStops( std::move( stops ) );
|
||||
drawable->setAngle( angle );
|
||||
ownIt = true;
|
||||
return drawable;
|
||||
};
|
||||
|
||||
mFuncs["linear-gradient"] = [parseGradient]( const FunctionString& functionType,
|
||||
const Sizef& /*size*/, bool& ownIt,
|
||||
UINode* node ) -> Drawable* {
|
||||
return parseGradient( functionType, ownIt, node, false );
|
||||
};
|
||||
|
||||
mFuncs["repeating-linear-gradient"] = [parseGradient]( const FunctionString& functionType,
|
||||
const Sizef& /*size*/, bool& ownIt,
|
||||
UINode* node ) -> Drawable* {
|
||||
return parseGradient( functionType, ownIt, node, true );
|
||||
};
|
||||
|
||||
// Shared parsing logic for radial-gradient and repeating-radial-gradient
|
||||
auto parseRadialGradient = []( const FunctionString& functionType, bool& ownIt, UINode* node,
|
||||
bool repeating ) -> Drawable* {
|
||||
const auto& params( functionType.getParameters() );
|
||||
if ( params.size() < 2 )
|
||||
return NULL;
|
||||
|
||||
size_t paramIdx = 0;
|
||||
RadialGradientDrawable::ShapeType shape = RadialGradientDrawable::CIRCLE;
|
||||
RadialGradientDrawable::Extent extent = RadialGradientDrawable::FARTHEST_CORNER;
|
||||
Vector2f center( 0.5f, 0.5f );
|
||||
|
||||
if ( paramIdx < params.size() ) {
|
||||
std::string kw = String::toLower( String::trim( params[paramIdx] ) );
|
||||
if ( kw == "circle" ) {
|
||||
shape = RadialGradientDrawable::CIRCLE;
|
||||
paramIdx++;
|
||||
} else if ( kw == "ellipse" ) {
|
||||
shape = RadialGradientDrawable::ELLIPSE;
|
||||
paramIdx++;
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<ColorStopResult> gradientStops;
|
||||
for ( size_t i = paramIdx; i < params.size(); i++ ) {
|
||||
std::string stopParam = params[i];
|
||||
String::trimInPlace( stopParam );
|
||||
|
||||
ColorStopResult stop = parseColorStop( stopParam );
|
||||
if ( !stop.valid )
|
||||
continue;
|
||||
|
||||
gradientStops.push_back( stop );
|
||||
if ( stop.secondPosition >= 0.f ) {
|
||||
ColorStopResult stop2;
|
||||
stop2.valid = true;
|
||||
stop2.color = stop.color;
|
||||
stop2.position = stop.secondPosition;
|
||||
stop2.secondPosition = -1.f;
|
||||
gradientStops.push_back( stop2 );
|
||||
}
|
||||
}
|
||||
|
||||
// CSS §4.3: If a color-stop has a position that is less than
|
||||
// the specified position of any color-stop before it in the
|
||||
// list, set its position to be equal to the largest specified
|
||||
// position of any color-stop before it.
|
||||
{
|
||||
Float maxPos = -1.f;
|
||||
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.secondPosition >= 0.f ) {
|
||||
if ( s.secondPosition < maxPos )
|
||||
s.secondPosition = maxPos;
|
||||
else
|
||||
maxPos = s.secondPosition;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
int colorStopCount = 0;
|
||||
for ( const auto& s : gradientStops ) {
|
||||
if ( !s.isHint )
|
||||
colorStopCount++;
|
||||
}
|
||||
if ( colorStopCount < 2 )
|
||||
return NULL;
|
||||
}
|
||||
|
||||
std::sort( gradientStops.begin(), gradientStops.end(),
|
||||
[]( const ColorStopResult& a, const ColorStopResult& b ) {
|
||||
return a.position < b.position;
|
||||
} );
|
||||
|
||||
// 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;
|
||||
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;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// 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 )
|
||||
continue;
|
||||
size_t prevKnown = i;
|
||||
size_t nextKnown = i;
|
||||
while ( prevKnown > 0 && ( gradientStops[prevKnown - 1].position < 0.f ) )
|
||||
prevKnown--;
|
||||
while ( nextKnown < gradientStops.size() &&
|
||||
( gradientStops[nextKnown].isHint || gradientStops[nextKnown].position < 0.f ) )
|
||||
nextKnown++;
|
||||
|
||||
Float startPos = ( prevKnown > 0 && !gradientStops[prevKnown - 1].isHint )
|
||||
? gradientStops[prevKnown - 1].position
|
||||
: 0.f;
|
||||
Float endPos =
|
||||
( nextKnown < gradientStops.size() ) ? gradientStops[nextKnown].position : 1.f;
|
||||
size_t range = nextKnown - prevKnown + 1;
|
||||
|
||||
for ( size_t j = prevKnown; j < nextKnown; j++ ) {
|
||||
if ( gradientStops[j].isHint )
|
||||
continue;
|
||||
gradientStops[j].position =
|
||||
startPos +
|
||||
( endPos - startPos ) * ( (Float)( j - prevKnown + 1 ) / (Float)range );
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
for ( size_t i = 0; i < gradientStops.size(); i++ ) {
|
||||
if ( gradientStops[i].isHint )
|
||||
continue;
|
||||
|
||||
// Add the current color stop
|
||||
stops.push_back( RadialGradientDrawable::ColorStop( gradientStops[i].position,
|
||||
gradientStops[i].color ) );
|
||||
|
||||
// Find the next color stop and any hints between them
|
||||
size_t nextColor = i + 1;
|
||||
while ( nextColor < gradientStops.size() && gradientStops[nextColor].isHint )
|
||||
nextColor++;
|
||||
if ( nextColor >= gradientStops.size() )
|
||||
break;
|
||||
|
||||
// Collect hints between current and next color stop
|
||||
std::vector<Float> hints;
|
||||
for ( size_t h = i + 1; h < nextColor; h++ ) {
|
||||
if ( gradientStops[h].isHint )
|
||||
hints.push_back( gradientStops[h].position );
|
||||
}
|
||||
|
||||
if ( hints.empty() )
|
||||
continue;
|
||||
|
||||
Float p0 = gradientStops[i].position;
|
||||
Float p1 = gradientStops[nextColor].position;
|
||||
Color c0 = gradientStops[i].color;
|
||||
Color c1 = gradientStops[nextColor].color;
|
||||
|
||||
if ( std::abs( p1 - p0 ) < 0.0001f )
|
||||
continue;
|
||||
|
||||
// Apply hints sequentially. Each hint adjusts the transition
|
||||
// between the previously computed mid-points.
|
||||
// For a single hint at position h between p0 and p1:
|
||||
// normalized hint hn = (h - p0) / (p1 - p0)
|
||||
// power = log(0.5) / log(hn)
|
||||
// color(p) = lerp(c0, c1, ((p-p0)/(p1-p0))^power)
|
||||
// We sample HINT_SAMPLES points and add them as color stops.
|
||||
Float hintNorm = ( hints[0] - p0 ) / ( p1 - p0 );
|
||||
hintNorm = eemax( 0.001f, eemin( 0.999f, hintNorm ) );
|
||||
Float power = std::log( 0.5f ) / std::log( hintNorm );
|
||||
|
||||
for ( int s = 1; s < HINT_SAMPLES; s++ ) {
|
||||
Float x = (Float)s / (Float)HINT_SAMPLES;
|
||||
Float t = std::pow( x, power );
|
||||
Float pos = p0 + x * ( p1 - p0 );
|
||||
Uint8 r = (Uint8)( (Float)c0.r + t * (Float)( c1.r - c0.r ) );
|
||||
Uint8 g = (Uint8)( (Float)c0.g + t * (Float)( c1.g - c0.g ) );
|
||||
Uint8 b = (Uint8)( (Float)c0.b + t * (Float)( c1.b - c0.b ) );
|
||||
Uint8 a = (Uint8)( (Float)c0.a + t * (Float)( c1.a - c0.a ) );
|
||||
stops.push_back( RadialGradientDrawable::ColorStop( pos, Color( r, g, b, a ) ) );
|
||||
}
|
||||
}
|
||||
|
||||
if ( stops.size() < 2 )
|
||||
return NULL;
|
||||
|
||||
RadialGradientDrawable* drawable =
|
||||
repeating ? RadialGradientDrawable::NewRepeating() : RadialGradientDrawable::New();
|
||||
drawable->setColorStops( std::move( stops ) );
|
||||
drawable->setShape( shape );
|
||||
drawable->setExtent( extent );
|
||||
drawable->setCenter( center );
|
||||
ownIt = true;
|
||||
return drawable;
|
||||
};
|
||||
|
||||
mFuncs["radial-gradient"] = [parseRadialGradient]( const FunctionString& functionType,
|
||||
const Sizef& /*size*/, bool& ownIt,
|
||||
UINode* node ) -> Drawable* {
|
||||
return parseRadialGradient( functionType, ownIt, node, false );
|
||||
};
|
||||
|
||||
mFuncs["repeating-radial-gradient"] = [parseRadialGradient]( const FunctionString& functionType,
|
||||
const Sizef& /*size*/, bool& ownIt,
|
||||
UINode* node ) -> Drawable* {
|
||||
return parseRadialGradient( functionType, ownIt, node, true );
|
||||
};
|
||||
|
||||
mFuncs["circle"] = []( const FunctionString& functionType, const Sizef& size, bool& ownIt,
|
||||
UINode* node ) -> Drawable* {
|
||||
if ( functionType.getParameters().size() < 1 ) {
|
||||
|
||||
@@ -711,7 +711,10 @@ Sizef UINodeDrawable::LayerDrawable::calcDrawableSize( const std::string& drawab
|
||||
|
||||
if ( drawableSizeEq == "auto" ) {
|
||||
if ( mDrawable->getDrawableType() == Drawable::RECTANGLE ||
|
||||
mDrawable->getDrawableType() == LINEARGRADIENT ) {
|
||||
mDrawable->getDrawableType() == LINEARGRADIENT ||
|
||||
mDrawable->getDrawableType() == REPEATINGLINEARGRADIENT ||
|
||||
mDrawable->getDrawableType() == RADIALGRADIENT ||
|
||||
mDrawable->getDrawableType() == REPEATINGRADIALGRADIENT ) {
|
||||
size = mSize;
|
||||
} else {
|
||||
size = mDrawable->getPixelsSize();
|
||||
@@ -749,7 +752,10 @@ Sizef UINodeDrawable::LayerDrawable::calcDrawableSize( const std::string& drawab
|
||||
if ( sizePart.size() == 2 ) {
|
||||
if ( sizePart[0] == "auto" && sizePart[1] == "auto" ) {
|
||||
if ( mDrawable->getDrawableType() == Drawable::RECTANGLE ||
|
||||
mDrawable->getDrawableType() == Drawable::LINEARGRADIENT ) {
|
||||
mDrawable->getDrawableType() == Drawable::LINEARGRADIENT ||
|
||||
mDrawable->getDrawableType() == Drawable::REPEATINGLINEARGRADIENT ||
|
||||
mDrawable->getDrawableType() == Drawable::RADIALGRADIENT ||
|
||||
mDrawable->getDrawableType() == Drawable::REPEATINGRADIALGRADIENT ) {
|
||||
size = mSize;
|
||||
} else {
|
||||
size = mDrawable->getSize();
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
#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>
|
||||
@@ -260,3 +261,203 @@ UTEST( DrawableImageParser, EmptyAngleFallsThrough ) {
|
||||
eeSAFE_DELETE( drawable );
|
||||
Engine::destroySingleton();
|
||||
}
|
||||
|
||||
UTEST( DrawableImageParser, RepeatingTwoStops ) {
|
||||
Engine::instance()->createWindow( WindowSettings( 512, 512, "DrawableImageParser test",
|
||||
WindowStyle::Default, WindowBackend::Default,
|
||||
32, {}, 1, false, true ),
|
||||
ContextSettings( false, 0, 0, GLv_default, true, false ) );
|
||||
UISceneNode* sceneNode = initDrawableParserTest();
|
||||
UINode* node = createTestNode( sceneNode );
|
||||
|
||||
auto& parser = StyleSheetSpecification::instance()->getDrawableImageParser();
|
||||
bool ownIt = false;
|
||||
Drawable* drawable = parser.createDrawable( "repeating-linear-gradient(red, blue)",
|
||||
Sizef( 100, 100 ), ownIt, node );
|
||||
|
||||
ASSERT_TRUE( drawable != NULL );
|
||||
ASSERT_TRUE( drawable->getDrawableType() == Drawable::REPEATINGLINEARGRADIENT );
|
||||
auto* grad = static_cast<LinearGradientDrawable*>( drawable );
|
||||
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 );
|
||||
|
||||
eeSAFE_DELETE( drawable );
|
||||
Engine::destroySingleton();
|
||||
}
|
||||
|
||||
UTEST( DrawableImageParser, RepeatingWithAngle ) {
|
||||
Engine::instance()->createWindow( WindowSettings( 512, 512, "DrawableImageParser test",
|
||||
WindowStyle::Default, WindowBackend::Default,
|
||||
32, {}, 1, false, true ),
|
||||
ContextSettings( false, 0, 0, GLv_default, true, false ) );
|
||||
UISceneNode* sceneNode = initDrawableParserTest();
|
||||
UINode* node = createTestNode( sceneNode );
|
||||
|
||||
auto& parser = StyleSheetSpecification::instance()->getDrawableImageParser();
|
||||
bool ownIt = false;
|
||||
Drawable* drawable = parser.createDrawable(
|
||||
"repeating-linear-gradient(45deg, #f00, #0f0, #00f)", Sizef( 100, 100 ), ownIt, node );
|
||||
|
||||
ASSERT_TRUE( drawable != NULL );
|
||||
auto* grad = static_cast<LinearGradientDrawable*>( drawable );
|
||||
EXPECT_EQ( grad->getAngle(), 45.f );
|
||||
EXPECT_TRUE( grad->isRepeating() );
|
||||
const auto& stops = grad->getColorStops();
|
||||
ASSERT_EQ( stops.size(), (size_t)3 );
|
||||
|
||||
eeSAFE_DELETE( drawable );
|
||||
Engine::destroySingleton();
|
||||
}
|
||||
|
||||
UTEST( DrawableImageParser, RepeatingWithPositions ) {
|
||||
Engine::instance()->createWindow( WindowSettings( 512, 512, "DrawableImageParser test",
|
||||
WindowStyle::Default, WindowBackend::Default,
|
||||
32, {}, 1, false, true ),
|
||||
ContextSettings( false, 0, 0, GLv_default, true, false ) );
|
||||
UISceneNode* sceneNode = initDrawableParserTest();
|
||||
UINode* node = createTestNode( sceneNode );
|
||||
|
||||
auto& parser = StyleSheetSpecification::instance()->getDrawableImageParser();
|
||||
bool ownIt = false;
|
||||
Drawable* drawable = parser.createDrawable( "repeating-linear-gradient(red 10%, blue 40%)",
|
||||
Sizef( 100, 100 ), ownIt, node );
|
||||
|
||||
ASSERT_TRUE( drawable != NULL );
|
||||
auto* grad = static_cast<LinearGradientDrawable*>( drawable );
|
||||
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 );
|
||||
|
||||
eeSAFE_DELETE( drawable );
|
||||
Engine::destroySingleton();
|
||||
}
|
||||
|
||||
UTEST( DrawableImageParser, RadialTwoStops ) {
|
||||
Engine::instance()->createWindow( WindowSettings( 512, 512, "DrawableImageParser test",
|
||||
WindowStyle::Default, WindowBackend::Default,
|
||||
32, {}, 1, false, true ),
|
||||
ContextSettings( false, 0, 0, GLv_default, true, false ) );
|
||||
UISceneNode* sceneNode = initDrawableParserTest();
|
||||
UINode* node = createTestNode( sceneNode );
|
||||
|
||||
auto& parser = StyleSheetSpecification::instance()->getDrawableImageParser();
|
||||
bool ownIt = false;
|
||||
Drawable* drawable =
|
||||
parser.createDrawable( "radial-gradient(red, blue)", Sizef( 100, 100 ), ownIt, node );
|
||||
|
||||
ASSERT_TRUE( drawable != NULL );
|
||||
ASSERT_TRUE( drawable->getDrawableType() == Drawable::RADIALGRADIENT );
|
||||
auto* grad = static_cast<RadialGradientDrawable*>( drawable );
|
||||
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 );
|
||||
|
||||
eeSAFE_DELETE( drawable );
|
||||
Engine::destroySingleton();
|
||||
}
|
||||
|
||||
UTEST( DrawableImageParser, RadialCircleKeyword ) {
|
||||
Engine::instance()->createWindow( WindowSettings( 512, 512, "DrawableImageParser test",
|
||||
WindowStyle::Default, WindowBackend::Default,
|
||||
32, {}, 1, false, true ),
|
||||
ContextSettings( false, 0, 0, GLv_default, true, false ) );
|
||||
UISceneNode* sceneNode = initDrawableParserTest();
|
||||
UINode* node = createTestNode( sceneNode );
|
||||
|
||||
auto& parser = StyleSheetSpecification::instance()->getDrawableImageParser();
|
||||
bool ownIt = false;
|
||||
Drawable* drawable = parser.createDrawable( "radial-gradient(circle, #f00, #00f)",
|
||||
Sizef( 100, 100 ), ownIt, node );
|
||||
|
||||
ASSERT_TRUE( drawable != NULL );
|
||||
auto* grad = static_cast<RadialGradientDrawable*>( drawable );
|
||||
EXPECT_EQ( grad->getShape(), RadialGradientDrawable::CIRCLE );
|
||||
EXPECT_EQ( grad->getColorStops().size(), (size_t)2 );
|
||||
|
||||
eeSAFE_DELETE( drawable );
|
||||
Engine::destroySingleton();
|
||||
}
|
||||
|
||||
UTEST( DrawableImageParser, RadialWithPositions ) {
|
||||
Engine::instance()->createWindow( WindowSettings( 512, 512, "DrawableImageParser test",
|
||||
WindowStyle::Default, WindowBackend::Default,
|
||||
32, {}, 1, false, true ),
|
||||
ContextSettings( false, 0, 0, GLv_default, true, false ) );
|
||||
UISceneNode* sceneNode = initDrawableParserTest();
|
||||
UINode* node = createTestNode( sceneNode );
|
||||
|
||||
auto& parser = StyleSheetSpecification::instance()->getDrawableImageParser();
|
||||
bool ownIt = false;
|
||||
Drawable* drawable = parser.createDrawable( "radial-gradient(red 10%, blue 80%)",
|
||||
Sizef( 100, 100 ), ownIt, node );
|
||||
|
||||
ASSERT_TRUE( drawable != NULL );
|
||||
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 );
|
||||
|
||||
eeSAFE_DELETE( drawable );
|
||||
Engine::destroySingleton();
|
||||
}
|
||||
|
||||
UTEST( DrawableImageParser, RepeatingRadial ) {
|
||||
Engine::instance()->createWindow( WindowSettings( 512, 512, "DrawableImageParser test",
|
||||
WindowStyle::Default, WindowBackend::Default,
|
||||
32, {}, 1, false, true ),
|
||||
ContextSettings( false, 0, 0, GLv_default, true, false ) );
|
||||
UISceneNode* sceneNode = initDrawableParserTest();
|
||||
UINode* node = createTestNode( sceneNode );
|
||||
|
||||
auto& parser = StyleSheetSpecification::instance()->getDrawableImageParser();
|
||||
bool ownIt = false;
|
||||
Drawable* drawable = parser.createDrawable( "repeating-radial-gradient(red 10%, blue 40%)",
|
||||
Sizef( 100, 100 ), ownIt, node );
|
||||
|
||||
ASSERT_TRUE( drawable != NULL );
|
||||
ASSERT_TRUE( drawable->getDrawableType() == Drawable::REPEATINGRADIALGRADIENT );
|
||||
auto* grad = static_cast<RadialGradientDrawable*>( drawable );
|
||||
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 );
|
||||
|
||||
eeSAFE_DELETE( drawable );
|
||||
Engine::destroySingleton();
|
||||
}
|
||||
|
||||
UTEST( DrawableImageParser, TwoLengthStopSyntax ) {
|
||||
Engine::instance()->createWindow( WindowSettings( 512, 512, "DrawableImageParser test",
|
||||
WindowStyle::Default, WindowBackend::Default,
|
||||
32, {}, 1, false, true ),
|
||||
ContextSettings( false, 0, 0, GLv_default, true, false ) );
|
||||
UISceneNode* sceneNode = initDrawableParserTest();
|
||||
UINode* node = createTestNode( sceneNode );
|
||||
|
||||
auto& parser = StyleSheetSpecification::instance()->getDrawableImageParser();
|
||||
bool ownIt = false;
|
||||
Drawable* drawable = parser.createDrawable(
|
||||
"repeating-linear-gradient(red 0 10px, blue 10px 20px)", Sizef( 100, 100 ), ownIt, node );
|
||||
|
||||
ASSERT_TRUE( drawable != NULL );
|
||||
auto* grad = static_cast<LinearGradientDrawable*>( drawable );
|
||||
EXPECT_TRUE( grad->isRepeating() );
|
||||
const auto& stops = grad->getColorStops();
|
||||
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 );
|
||||
|
||||
eeSAFE_DELETE( drawable );
|
||||
Engine::destroySingleton();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user