Skip to content

Commit f24ad34

Browse files
authored
Add support for CSS conic-gradient 'from <angle>' syntax (slint-ui#9830)
* Add support for CSS conic-gradient 'from <angle>' syntax Implement rotation support for conic gradients by adding the 'from <angle>' syntax, which rotates the entire gradient by the specified angle. - Add `from_angle` field to ConicGradient expression - Parse 'from <angle>' syntax in compiler (defaults to 0deg when omitted) - Normalize angles to 0-1 range (0.0 = 0°, 1.0 = 360°) - Add `ConicGradientBrush::rotated_stops()` method that: * Applies rotation by adding from_angle to each stop position * Adds boundary stops at 0.0 and 1.0 with interpolated colors * Handles stops outside [0, 1] range for boundary interpolation - Update all renderers (Skia, FemtoVG, Qt, Software) to use rotated_stops() The rotation is applied at render time by the rotated_stops() method, which ensures all renderers consistently handle the gradient rotation. * Add screenshot to rotated conic gradient docs example Wraps the rotated conic gradient example in CodeSnippetMD to automatically generate and display a visual screenshot of the gradient rotation effect. This makes it easier for users to understand how the 'from' parameter rotates the gradient. * Make ConicGradientBrush fields private The from_angle and stops fields don't need to be pub since: - Rust code in the same module can access them without pub - C++ FFI access works through cbindgen-generated struct (C++ struct members are public by default) * Optimize ConicGradientBrush::rotated_stops to avoid allocations - Changed return type from Vec to SharedVector - When from_angle is zero, returns a clone of internal SharedVector (only increments reference count instead of allocating new Vec) - Removed break from duplicate position separation loop to handle all duplicate pairs, not just the first one - Updated documentation to match actual implementation * Remove automatic sorting in ConicGradientBrush::new() to match CSS spec - CSS conic-gradient does not automatically sort color stops - Stops are processed in the order specified by the user - Changed boundary stop interpolation logic to use max_by/min_by instead of relying on sorted order - This allows CSS-style hard transitions when stops are out of order * Move conic gradient rotation processing to construction time Major changes: - ConicGradientBrush::new() now applies rotation and boundary stop processing immediately, instead of deferring to rotated_stops() - Removed rotated_stops() method - backends now use stops() directly - Changed to LinearGradientBrush pattern: store angle in first dummy stop - Added angle() method to retrieve the stored angle - Maintained #[repr(transparent)] by removing from_angle field - All backends updated: rotated_stops() -> stops() - Qt backend - Skia renderer - femtovg renderer - Software renderer C++ API changes: - Added FFI function slint_conic_gradient_new() for C++ to call Rust's new() - Updated make_conic_gradient() to call FFI function instead of manually constructing SharedVector - Ensures C++-created gradients get full rotation processing Benefits: - Eliminates per-frame rotation calculations - Reduces memory usage (no from_angle field) - Consistent with LinearGradientBrush design - C++ and Rust APIs now produce identical results * Change ConicGradientBrush::new() from_angle parameter to use degrees - Changed from_angle parameter from normalized form (0.0-1.0) to degrees - Matches LinearGradientBrush API convention (angle in degrees) - Updated internal conversion: from_angle / 360.0 for normalization - Stores angle as-is in degrees in the first dummy stop - FFI function slint_conic_gradient_new() passes degrees directly Example usage: ConicGradientBrush::new(90.0, stops) // 90 degrees LinearGradientBrush::new(90.0, stops) // 90 degrees (consistent) * Fix ConicGradient color transformation methods to preserve angle Changed brighter(), darker(), transparentize(), and with_alpha() methods to clone and modify the gradient in-place instead of calling new(). - Clones the existing gradient (preserves angle and rotation) - Modifies only color stops (skips first stop which contains angle) - Avoids re-running expensive rotation processing - Maintains the original angle information Before: ConicGradientBrush::new(0.0, ...) // Lost angle information After: Clone + modify colors in-place // Preserves angle * Use premultiplied alpha interpolation for conic gradient colors - Changed interpolate_color() to use premultiplied RGBA interpolation - Updated signature to match Color::mix convention (&Color, factor) - Added documentation explaining why we can't use Color::mix() here (Sass algorithm vs CSS gradient color interpolation) - Reference: https://www.w3.org/TR/css-images-4/#color-interpolation This ensures correct visual interpolation of semi-transparent colors in gradient boundaries, following CSS gradient specification. * Run rustfmt on conic gradient code * Fix ConicGradientBrush edge cases and add comprehensive tests - Handle stops that are all below 0.0 or all above 1.0 - Add default transparent gradient when no valid stops remain - Add 7 unit tests covering basic functionality and edge cases * Apply clippy suggestion: use retain() instead of filter().collect() * Fix radial-gradient parsing to allow empty gradients Allow @radial-gradient(circle) without color stops, fixing syntax test regression from commit 820ae2b. The previous logic required a comma after 'circle', but it should only error if there's something that is NOT a comma. * Fix conic-gradient syntax test error markers Update error markers to match actual compiler error positions. The 'from 2' case produces two errors: - One at the @conic-gradient expression level - One at the literal '2' position Auto-updated using SLINT_SYNTAX_TEST_UPDATE=1. * Refactor ConicGradientBrush epsilon adjustment and update tests - Move epsilon adjustment for first stop into rotation block (only needed when rotation is applied) - Update property_view test to reflect boundary stops added by ConicGradientBrush::new() * Update conic-gradient screenshot reference image Update the reference screenshot to match the current rendering output. The small pixel differences (1% different pixels, max color diff 3.46) are due to minor rounding differences in the conic gradient implementation. * Fix ConicGradientBrush C++ FFI to avoid C-linkage return type error Refactored ConicGradientBrush construction to match LinearGradientBrush pattern, fixing macOS Clang error about returning C++ types from extern "C" functions. Changes: - Rust: Split ConicGradientBrush::new into simple construction + separate normalize_stops() and apply_rotation() methods - Rust: Added FFI functions slint_conic_gradient_normalize_stops() and slint_conic_gradient_apply_rotation() that take pointers (no return value) - C++: Construct SharedVector directly in make_conic_gradient(), then call Rust functions via pointer (matching LinearGradientBrush pattern) - Optimized both methods to only copy when changes are needed This resolves the macOS Clang error: "'slint_conic_gradient_new' has C-linkage specified, but returns incomplete type 'ConicGradientBrush' which could be incompatible with C" The new approach maintains ABI compatibility while keeping complex gradient processing logic in Rust. * Fix C++ header generation to avoid GradientStop redefinition error Resolves the macOS CI compilation error where GradientStop and ConicGradientBrush were being defined in multiple headers (slint_color_internal.h, slint_image_internal.h, and slint_brush_internal.h). Changes: - cbindgen.rs: Add ConicGradientBrush and FFI functions to slint_brush_internal.h include list - cbindgen.rs: Add GradientStop, ConicGradientBrush, and FFI functions to exclude list for other headers - slint_color.h: Add forward declaration for ConicGradientBrush - slint_color.h: Add friend declaration for ConicGradientBrush to allow access to Color::inner Root cause: After adding extern "C" functions in graphics/brush.rs, cbindgen automatically detects and tries to include them in all headers that use graphics/brush.rs as a source. The exclude list + filter logic ensures these types only appear in slint_brush_internal.h. This fixes the C++ compilation errors: - "redefinition of 'GradientStop'" - "ConicGradientBrush does not name a type" - "Color::inner is private within this context" * Prepare ConicGradientBrush FFI for Rust 2024 edition Update FFI functions to use the new `#[unsafe(no_mangle)]` attribute syntax and safe function signatures in preparation for Rust 2024 edition. - Add `#![allow(unsafe_code)]` to graphics module for `#[unsafe(no_mangle)]` - Add `#[cfg(feature = "ffi")]` to conditionally compile FFI functions - Change from raw pointers to safe references (&mut) - Remove manual null checks and unsafe blocks
1 parent be0aea7 commit f24ad34

File tree

23 files changed

+614
-122
lines changed

23 files changed

+614
-122
lines changed

api/cpp/cbindgen.rs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -518,7 +518,8 @@ fn gen_corelib(
518518
"#include \"slint_sharedvector.h\"\n#include \"slint_point.h\"",
519519
),
520520
(
521-
vec!["Brush", "LinearGradient", "GradientStop", "RadialGradient"],
521+
vec!["Brush", "LinearGradient", "GradientStop", "RadialGradient", "ConicGradientBrush",
522+
"slint_conic_gradient_normalize_stops", "slint_conic_gradient_apply_rotation"],
522523
"slint_brush_internal.h",
523524
"",
524525
),
@@ -578,6 +579,10 @@ fn gen_corelib(
578579
"slint_windowrc_is_minimized",
579580
"slint_windowrc_is_maximized",
580581
"slint_windowrc_take_snapshot",
582+
"GradientStop",
583+
"ConicGradientBrush",
584+
"slint_conic_gradient_normalize_stops",
585+
"slint_conic_gradient_apply_rotation",
581586
]
582587
.into_iter()
583588
.chain(config.export.exclude.iter().map(|s| s.as_str()))

api/cpp/include/slint_brush.h

Lines changed: 28 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -105,18 +105,22 @@ class ConicGradientBrush
105105
public:
106106
/// Constructs an empty conic gradient with no color stops.
107107
ConicGradientBrush() = default;
108-
/// Constructs a new conic gradient. The color stops will be
109-
/// constructed from the stops array pointed to be \a firstStop, with the length \a stopCount.
110-
ConicGradientBrush(const GradientStop *firstStop, int stopCount)
111-
: inner(make_conic_gradient(firstStop, stopCount))
108+
/// Constructs a new conic gradient with the specified starting \a angle. The color stops will
109+
/// be constructed from the stops array pointed to be \a firstStop, with the length \a
110+
/// stopCount.
111+
ConicGradientBrush(float angle, const GradientStop *firstStop, int stopCount)
112+
: inner(make_conic_gradient(angle, firstStop, stopCount))
112113
{
113114
}
114115

116+
/// Returns the conic gradient's starting angle (rotation) in degrees.
117+
float angle() const { return inner[0].position; }
118+
115119
/// Returns the number of gradient stops.
116-
int stopCount() const { return int(inner.size()); }
120+
int stopCount() const { return int(inner.size()) - 1; }
117121

118122
/// Returns a pointer to the first gradient stop; undefined if the gradient has not stops.
119-
const GradientStop *stopsBegin() const { return inner.begin(); }
123+
const GradientStop *stopsBegin() const { return inner.begin() + 1; }
120124
/// Returns a pointer past the last gradient stop. The returned pointer cannot be dereferenced,
121125
/// it can only be used for comparison.
122126
const GradientStop *stopsEnd() const { return inner.end(); }
@@ -127,11 +131,22 @@ class ConicGradientBrush
127131
friend class slint::Brush;
128132

129133
static SharedVector<private_api::GradientStop>
130-
make_conic_gradient(const GradientStop *firstStop, int stopCount)
134+
make_conic_gradient(float angle, const GradientStop *firstStop, int stopCount)
131135
{
132136
SharedVector<private_api::GradientStop> gradient;
137+
// The gradient's first stop is a fake stop to store the angle (same pattern as
138+
// LinearGradient)
139+
gradient.push_back({ Color::from_argb_encoded(0).inner, angle });
133140
for (int i = 0; i < stopCount; ++i, ++firstStop)
134141
gradient.push_back(*firstStop);
142+
143+
// Normalize stops to [0, 1] range with proper boundary stops
144+
cbindgen_private::types::slint_conic_gradient_normalize_stops(&gradient);
145+
146+
// Apply rotation if angle is non-zero
147+
if (angle != 0.0f) {
148+
cbindgen_private::types::slint_conic_gradient_apply_rotation(&gradient, angle);
149+
}
135150
return gradient;
136151
}
137152
};
@@ -223,8 +238,8 @@ Color Brush::color() const
223238
}
224239
break;
225240
case Tag::ConicGradient:
226-
if (data.conic_gradient._0.size() > 0) {
227-
result.inner = data.conic_gradient._0[0].color;
241+
if (data.conic_gradient._0.size() > 1) {
242+
result.inner = data.conic_gradient._0[1].color;
228243
}
229244
break;
230245
}
@@ -252,7 +267,7 @@ inline Brush Brush::brighter(float factor) const
252267
}
253268
break;
254269
case Tag::ConicGradient:
255-
for (std::size_t i = 0; i < data.conic_gradient._0.size(); ++i) {
270+
for (std::size_t i = 1; i < data.conic_gradient._0.size(); ++i) {
256271
cbindgen_private::types::slint_color_brighter(&data.conic_gradient._0[i].color, factor,
257272
&result.data.conic_gradient._0[i].color);
258273
}
@@ -282,7 +297,7 @@ inline Brush Brush::darker(float factor) const
282297
}
283298
break;
284299
case Tag::ConicGradient:
285-
for (std::size_t i = 0; i < data.conic_gradient._0.size(); ++i) {
300+
for (std::size_t i = 1; i < data.conic_gradient._0.size(); ++i) {
286301
cbindgen_private::types::slint_color_darker(&data.conic_gradient._0[i].color, factor,
287302
&result.data.conic_gradient._0[i].color);
288303
}
@@ -314,7 +329,7 @@ inline Brush Brush::transparentize(float factor) const
314329
}
315330
break;
316331
case Tag::ConicGradient:
317-
for (std::size_t i = 0; i < data.conic_gradient._0.size(); ++i) {
332+
for (std::size_t i = 1; i < data.conic_gradient._0.size(); ++i) {
318333
cbindgen_private::types::slint_color_transparentize(
319334
&data.conic_gradient._0[i].color, factor,
320335
&result.data.conic_gradient._0[i].color);
@@ -347,7 +362,7 @@ inline Brush Brush::with_alpha(float alpha) const
347362
}
348363
break;
349364
case Tag::ConicGradient:
350-
for (std::size_t i = 0; i < data.conic_gradient._0.size(); ++i) {
365+
for (std::size_t i = 1; i < data.conic_gradient._0.size(); ++i) {
351366
cbindgen_private::types::slint_color_with_alpha(
352367
&data.conic_gradient._0[i].color, alpha,
353368
&result.data.conic_gradient._0[i].color);

api/cpp/include/slint_color.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ namespace slint {
99

1010
namespace private_api {
1111
class LinearGradientBrush;
12+
class ConicGradientBrush;
1213
}
1314

1415
class Color;
@@ -214,6 +215,7 @@ class Color
214215
private:
215216
cbindgen_private::types::Color inner;
216217
friend class private_api::LinearGradientBrush;
218+
friend class private_api::ConicGradientBrush;
217219
friend class Brush;
218220
};
219221

docs/astro/src/content/docs/reference/colors-and-brushes.mdx

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -167,12 +167,16 @@ export component Example inherits Window {
167167
Conic gradients are gradients where the color transitions rotate around a center point (like the angle on a color wheel).
168168
To describe a conic gradient, use the `@conic-gradient` macro with the following signature:
169169

170-
### @conic-gradient(color angle, color angle, ...)
170+
### @conic-gradient([from angle,] color angle, color angle, ...)
171171

172172
The conic gradient is described by a series of color stops, each consisting of a color and an angle.
173173
The angle specifies where the color is placed along the circular sweep (0deg to 360deg).
174174
Colors are interpolated between the stops along the circular path.
175175

176+
The optional `from` parameter specifies the starting angle of the gradient rotation.
177+
If omitted, the gradient starts at 0deg (pointing upward). For example, `from 90deg` rotates
178+
the entire gradient 90 degrees clockwise.
179+
176180
Example:
177181

178182
<CodeSnippetMD imagePath="/src/assets/generated/gradients-conic.png" scale="3" imageWidth="200" imageHeight="200" imageAlt='Conic Gradient Example'>
@@ -189,6 +193,22 @@ export component Example inherits Window {
189193

190194
This creates a color wheel effect with red at the top (0deg/360deg), green at 120 degrees, and blue at 240 degrees.
191195

196+
You can also rotate the gradient using the `from` parameter:
197+
198+
<CodeSnippetMD imagePath="/src/assets/generated/gradients-conic-rotated.png" scale="3" imageWidth="200" imageHeight="200" imageAlt='Rotated Conic Gradient Example'>
199+
```slint
200+
export component Example inherits Window {
201+
preferred-width: 100px;
202+
preferred-height: 100px;
203+
Rectangle {
204+
background: @conic-gradient(from 90deg, #f00 0deg, #0f0 120deg, #00f 240deg, #f00 360deg);
205+
}
206+
}
207+
```
208+
</CodeSnippetMD>
209+
210+
This rotates the same color wheel 90 degrees clockwise, so red starts at the right (90deg) instead of the top.
211+
192212
:::note[Known Limitation]
193213
Negative angles cannot be used directly in conic gradients (e.g., `#ff0000 -90deg`).
194214
Instead, use one of these workarounds:

internal/compiler/expression_tree.rs

Lines changed: 13 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -739,6 +739,8 @@ pub enum Expression {
739739
},
740740

741741
ConicGradient {
742+
/// The starting angle (rotation) of the gradient, corresponding to CSS `from <angle>`
743+
from_angle: Box<Expression>,
742744
/// First expression in the tuple is a color, second expression is the stop angle
743745
stops: Vec<(Expression, Expression)>,
744746
},
@@ -974,7 +976,8 @@ impl Expression {
974976
visitor(s);
975977
}
976978
}
977-
Expression::ConicGradient { stops } => {
979+
Expression::ConicGradient { from_angle, stops } => {
980+
visitor(from_angle);
978981
for (c, s) in stops {
979982
visitor(c);
980983
visitor(s);
@@ -1077,7 +1080,8 @@ impl Expression {
10771080
visitor(s);
10781081
}
10791082
}
1080-
Expression::ConicGradient { stops } => {
1083+
Expression::ConicGradient { from_angle, stops } => {
1084+
visitor(from_angle);
10811085
for (c, s) in stops {
10821086
visitor(c);
10831087
visitor(s);
@@ -1174,8 +1178,9 @@ impl Expression {
11741178
Expression::RadialGradient { stops } => {
11751179
stops.iter().all(|(c, s)| c.is_constant(ga) && s.is_constant(ga))
11761180
}
1177-
Expression::ConicGradient { stops } => {
1178-
stops.iter().all(|(c, s)| c.is_constant(ga) && s.is_constant(ga))
1181+
Expression::ConicGradient { from_angle, stops } => {
1182+
from_angle.is_constant(ga)
1183+
&& stops.iter().all(|(c, s)| c.is_constant(ga) && s.is_constant(ga))
11791184
}
11801185
Expression::EnumerationValue(_) => true,
11811186
Expression::ReturnStatement(expr) => {
@@ -1827,14 +1832,11 @@ pub fn pretty_print(f: &mut dyn std::fmt::Write, expression: &Expression) -> std
18271832
}
18281833
write!(f, ")")
18291834
}
1830-
Expression::ConicGradient { stops } => {
1831-
write!(f, "@conic-gradient(")?;
1832-
let mut first = true;
1835+
Expression::ConicGradient { from_angle, stops } => {
1836+
write!(f, "@conic-gradient(from ")?;
1837+
pretty_print(f, from_angle)?;
18331838
for (c, s) in stops {
1834-
if !first {
1835-
write!(f, ", ")?;
1836-
}
1837-
first = false;
1839+
write!(f, ", ")?;
18381840
pretty_print(f, c)?;
18391841
write!(f, " ")?;
18401842
pretty_print(f, s)?;

internal/compiler/generator/cpp.rs

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3508,7 +3508,7 @@ fn compile_expression(expr: &llr::Expression, ctx: &EvaluationContext) -> String
35083508
stops_it.join(", "), angle, stops.len()
35093509
)
35103510
}
3511-
Expression::RadialGradient{ stops} => {
3511+
Expression::RadialGradient{ stops } => {
35123512
let mut stops_it = stops.iter().map(|(color, stop)| {
35133513
let color = compile_expression(color, ctx);
35143514
let position = compile_expression(stop, ctx);
@@ -3519,15 +3519,16 @@ fn compile_expression(expr: &llr::Expression, ctx: &EvaluationContext) -> String
35193519
stops_it.join(", "), stops.len()
35203520
)
35213521
}
3522-
Expression::ConicGradient{ stops} => {
3522+
Expression::ConicGradient{ from_angle, stops } => {
3523+
let from_angle = compile_expression(from_angle, ctx);
35233524
let mut stops_it = stops.iter().map(|(color, stop)| {
35243525
let color = compile_expression(color, ctx);
35253526
let position = compile_expression(stop, ctx);
35263527
format!("slint::private_api::GradientStop{{ {color}, float({position}), }}")
35273528
});
35283529
format!(
3529-
"[&] {{ const slint::private_api::GradientStop stops[] = {{ {} }}; return slint::Brush(slint::private_api::ConicGradientBrush(stops, {})); }}()",
3530-
stops_it.join(", "), stops.len()
3530+
"[&] {{ const slint::private_api::GradientStop stops[] = {{ {} }}; return slint::Brush(slint::private_api::ConicGradientBrush(float({}), stops, {})); }}()",
3531+
stops_it.join(", "), from_angle, stops.len()
35313532
)
35323533
}
35333534
Expression::EnumerationValue(value) => {

internal/compiler/generator/rust.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2671,14 +2671,15 @@ fn compile_expression(expr: &Expression, ctx: &EvaluationContext) -> TokenStream
26712671
sp::RadialGradientBrush::new_circle([#(#stops),*])
26722672
))
26732673
}
2674-
Expression::ConicGradient { stops } => {
2674+
Expression::ConicGradient { from_angle, stops } => {
2675+
let from_angle = compile_expression(from_angle, ctx);
26752676
let stops = stops.iter().map(|(color, stop)| {
26762677
let color = compile_expression(color, ctx);
26772678
let position = compile_expression(stop, ctx);
26782679
quote!(sp::GradientStop{ color: #color, position: #position as _ })
26792680
});
26802681
quote!(slint::Brush::ConicGradient(
2681-
sp::ConicGradientBrush::new([#(#stops),*])
2682+
sp::ConicGradientBrush::new(#from_angle as _, [#(#stops),*])
26822683
))
26832684
}
26842685
Expression::EnumerationValue(value) => {

internal/compiler/llr/expression.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,8 @@ pub enum Expression {
159159
},
160160

161161
ConicGradient {
162+
/// The starting angle (rotation) of the gradient, corresponding to CSS `from <angle>`
163+
from_angle: Box<Expression>,
162164
/// First expression in the tuple is a color, second expression is the stop position (normalized angle 0-1)
163165
stops: Vec<(Expression, Expression)>,
164166
},
@@ -390,7 +392,8 @@ macro_rules! visit_impl {
390392
$visitor(b);
391393
}
392394
}
393-
Expression::ConicGradient { stops } => {
395+
Expression::ConicGradient { from_angle, stops } => {
396+
$visitor(from_angle);
394397
for (a, b) in stops {
395398
$visitor(a);
396399
$visitor(b);

internal/compiler/llr/lower_expression.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -228,7 +228,8 @@ pub fn lower_expression(
228228
.map(|(a, b)| (lower_expression(a, ctx), lower_expression(b, ctx)))
229229
.collect::<_>(),
230230
},
231-
tree_Expression::ConicGradient { stops } => llr_Expression::ConicGradient {
231+
tree_Expression::ConicGradient { from_angle, stops } => llr_Expression::ConicGradient {
232+
from_angle: Box::new(lower_expression(from_angle, ctx)),
232233
stops: stops
233234
.iter()
234235
.map(|(a, b)| (lower_expression(a, ctx), lower_expression(b, ctx)))

internal/compiler/llr/pretty_print.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -385,9 +385,10 @@ impl<'a, T> Display for DisplayExpression<'a, T> {
385385
"@radial-gradient(circle, {})",
386386
stops.iter().map(|(e1, e2)| format!("{} {}", e(e1), e(e2))).join(", ")
387387
),
388-
Expression::ConicGradient { stops } => write!(
388+
Expression::ConicGradient { from_angle, stops } => write!(
389389
f,
390-
"@conic-gradient({})",
390+
"@conic-gradient(from {}, {})",
391+
e(from_angle),
391392
stops.iter().map(|(e1, e2)| format!("{} {}", e(e1), e(e2))).join(", ")
392393
),
393394
Expression::EnumerationValue(x) => write!(f, "{x}"),

0 commit comments

Comments
 (0)