Skip to main content

css_ast/functions/
gradient_functions.rs

1use css_parse::Box;
2
3use super::prelude::*;
4use crate::{AngleOrZero, AnglePercentage, AtPosition, CalcableValue, ColorInterpolationMethod, LengthPercentage};
5
6/// <https://drafts.csswg.org/css-images-4/#typedef-gradient>
7/// ```text,ignore
8/// <gradient> = <linear-gradient()> | <repeating-linear-gradient()> | <radial-gradient()> | <repeating-radial-gradient()>
9///            | <conic-gradient()> | <repeating-conic-gradient()>
10/// ```
11#[node]
12#[derive(Parse, Peek, ToCursors, ToSpan, SemanticEq, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
13#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
14#[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable), visit)]
15#[derive(csskit_derives::NodeWithMetadata)]
16pub enum Gradient<'a> {
17	#[atom(CssAtomSet::LinearGradient)]
18	LinearGradientFunction(Box<'a, LinearGradientFunction<'a>>),
19	#[atom(CssAtomSet::RepeatingLinearGradient)]
20	RepeatingLinearGradientFunction(Box<'a, RepeatingLinearGradientFunction<'a>>),
21	#[atom(CssAtomSet::RadialGradient)]
22	RadialGradientFunction(Box<'a, RadialGradientFunction<'a>>),
23	#[atom(CssAtomSet::RepeatingRadialGradient)]
24	RepeatingRadialGradientFunction(Box<'a, RepeatingRadialGradientFunction<'a>>),
25	#[atom(CssAtomSet::ConicGradient)]
26	ConicGradientFunction(Box<'a, ConicGradientFunction<'a>>),
27	#[atom(CssAtomSet::RepeatingConicGradient)]
28	RepeatingConicGradientFunction(Box<'a, RepeatingConicGradientFunction<'a>>),
29}
30
31/// <https://drafts.csswg.org/css-images-4/#funcdef-linear-gradient>
32/// ```text,ignore
33/// <linear-gradient()> = linear-gradient( [ <linear-gradient-syntax> ] )
34/// <linear-gradient-syntax> = [ [ <angle> | <zero> | to <side-or-corner> ] || <color-interpolation-method> ]? , <color-stop-list>
35/// <side-or-corner> = [left | right] || [top | bottom]
36/// ```
37#[node]
38#[derive(Parse, Peek, ToCursors, ToSpan, SemanticEq, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
39#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
40#[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable), visit)]
41#[derive(csskit_derives::NodeWithMetadata)]
42#[metadata(value_kinds = Image)]
43pub struct LinearGradientFunction<'a> {
44	#[atom(CssAtomSet::LinearGradient)]
45	pub name: T![Function],
46	pub params: LinearGradientFunctionParams<'a>,
47	#[semantic_eq(skip)]
48	pub close: T![')'],
49}
50
51#[node]
52#[derive(Parse, Peek, ToCursors, ToSpan, SemanticEq, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
53#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
54#[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable))]
55#[derive(csskit_derives::NodeWithMetadata)]
56pub struct LinearGradientFunctionParams<'a> {
57	pub options: Option<LinearGradientOptions<'a>>,
58	#[semantic_eq(skip)]
59	pub comma: Option<T![,]>,
60	pub stops: CommaSeparated<'a, ColorStopOrHint<'a>>,
61}
62
63/// <https://drafts.csswg.org/css-images-4/#funcdef-repeating-linear-gradient>
64/// ```text,ignore
65/// <repeating-linear-gradient()> = repeating-linear-gradient( [ <linear-gradient-syntax> ] )
66/// <linear-gradient-syntax> = [ [ <angle> | <zero> | to <side-or-corner> ] || <color-interpolation-method> ]? , <color-stop-list>
67/// <side-or-corner> = [left | right] || [top | bottom]
68/// ```
69#[node]
70#[derive(Parse, Peek, ToCursors, ToSpan, SemanticEq, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
71#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
72#[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable), visit)]
73#[derive(csskit_derives::NodeWithMetadata)]
74#[metadata(value_kinds = Image)]
75pub struct RepeatingLinearGradientFunction<'a> {
76	#[atom(CssAtomSet::RepeatingLinearGradient)]
77	pub name: T![Function],
78	pub params: RepeatingLinearGradientFunctionParams<'a>,
79	#[semantic_eq(skip)]
80	pub close: T![')'],
81}
82
83#[node]
84#[derive(Parse, Peek, ToCursors, ToSpan, SemanticEq, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
85#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
86#[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable))]
87#[derive(csskit_derives::NodeWithMetadata)]
88pub struct RepeatingLinearGradientFunctionParams<'a> {
89	pub options: Option<LinearGradientOptions<'a>>,
90	#[semantic_eq(skip)]
91	pub comma: Option<T![,]>,
92	pub stops: CommaSeparated<'a, ColorStopOrHint<'a>>,
93}
94
95/// The direction and interpolation method of a linear gradient, in either order.
96///
97/// ```text,ignore
98/// [ <angle> | <zero> | to <side-or-corner> ] || <color-interpolation-method>
99/// ```
100#[node]
101#[derive(Parse, Peek, ToCursors, ToSpan, SemanticEq, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
102#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
103#[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable))]
104#[derive(csskit_derives::NodeWithMetadata)]
105#[parse(one_must_occur)]
106pub struct LinearGradientOptions<'a> {
107	pub direction: Option<LinearDirection<'a>>,
108	pub interpolation: Option<ColorInterpolationMethod>,
109}
110
111/// <https://drafts.csswg.org/css-images-4/#funcdef-radial-gradient>
112/// ```text,ignore
113/// <radial-gradient()> = radial-gradient( [ <radial-gradient-syntax> ] )
114/// <radial-gradient-syntax> = [ [ [ <radial-shape> || <radial-size> ]? [ at <position> ]? ] || <color-interpolation-method> ]? , <color-stop-list>
115/// ```
116#[node]
117#[derive(Parse, Peek, ToCursors, ToSpan, SemanticEq, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
118#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
119#[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable), visit)]
120#[derive(csskit_derives::NodeWithMetadata)]
121#[metadata(value_kinds = Image)]
122pub struct RadialGradientFunction<'a> {
123	#[atom(CssAtomSet::RadialGradient)]
124	pub name: T![Function],
125	pub params: RadialGradientFunctionParams<'a>,
126	#[semantic_eq(skip)]
127	pub close: T![')'],
128}
129
130#[node]
131#[derive(Parse, Peek, ToCursors, ToSpan, SemanticEq, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
132#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
133#[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable))]
134#[derive(csskit_derives::NodeWithMetadata)]
135pub struct RadialGradientFunctionParams<'a> {
136	pub options: Option<RadialGradientOptions<'a>>,
137	#[semantic_eq(skip)]
138	pub comma: Option<T![,]>,
139	pub stops: CommaSeparated<'a, ColorStopOrHint<'a>>,
140}
141
142/// <https://drafts.csswg.org/css-images-4/#funcdef-repeating-radial-gradient>
143/// ```text,ignore
144/// <repeating-radial-gradient()> = repeating-radial-gradient( [ <radial-gradient-syntax> ] )
145/// <radial-gradient-syntax> = [ [ [ <radial-shape> || <radial-size> ]? [ at <position> ]? ] || <color-interpolation-method> ]? , <color-stop-list>
146/// ```
147#[node]
148#[derive(Parse, Peek, ToCursors, ToSpan, SemanticEq, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
149#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
150#[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable), visit)]
151#[derive(csskit_derives::NodeWithMetadata)]
152#[metadata(value_kinds = Image)]
153pub struct RepeatingRadialGradientFunction<'a> {
154	#[atom(CssAtomSet::RepeatingRadialGradient)]
155	pub name: T![Function],
156	pub params: RepeatingRadialGradientFunctionParams<'a>,
157	#[semantic_eq(skip)]
158	pub close: T![')'],
159}
160
161#[node]
162#[derive(Parse, Peek, ToCursors, ToSpan, SemanticEq, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
163#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
164#[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable))]
165#[derive(csskit_derives::NodeWithMetadata)]
166pub struct RepeatingRadialGradientFunctionParams<'a> {
167	pub options: Option<RadialGradientOptions<'a>>,
168	#[semantic_eq(skip)]
169	pub comma: Option<T![,]>,
170	pub stops: CommaSeparated<'a, ColorStopOrHint<'a>>,
171}
172
173/// The geometry and interpolation method of a radial gradient, in either order.
174///
175/// ```text,ignore
176/// [ [ <radial-shape> || <radial-size> ]? [ at <position> ]? ] || <color-interpolation-method>
177/// ```
178#[node]
179#[derive(Parse, Peek, ToCursors, ToSpan, SemanticEq, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
180#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
181#[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable))]
182#[derive(csskit_derives::NodeWithMetadata)]
183#[parse(one_must_occur)]
184pub struct RadialGradientOptions<'a> {
185	pub geometry: Option<RadialGeometry<'a>>,
186	pub interpolation: Option<ColorInterpolationMethod>,
187}
188
189/// ```text,ignore
190/// [ <radial-shape> || <radial-size> ]? [ at <position> ]?
191/// ```
192#[node]
193#[derive(Parse, Peek, ToCursors, ToSpan, SemanticEq, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
194#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
195#[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable))]
196#[derive(csskit_derives::NodeWithMetadata)]
197pub struct RadialGeometry<'a> {
198	pub ending_shape: Option<RadialEndingShape<'a>>,
199	pub at: Option<AtPosition<'a>>,
200}
201
202/// ```text,ignore
203/// <radial-shape> || <radial-size>
204/// ```
205#[node]
206#[derive(Parse, Peek, ToCursors, ToSpan, SemanticEq, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
207#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
208#[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable))]
209#[derive(csskit_derives::NodeWithMetadata)]
210#[parse(one_must_occur)]
211pub struct RadialEndingShape<'a> {
212	pub shape: Option<RadialShape>,
213	pub size: Option<RadialSize<'a>>,
214}
215
216/// <https://drafts.csswg.org/css-images-4/#funcdef-conic-gradient>
217/// ```text,ignore
218/// <conic-gradient()> = conic-gradient( [ <conic-gradient-syntax> ] )
219/// <conic-gradient-syntax> = [ [ [ from [ <angle> | <zero> ] ]? [ at <position> ]? ] || <color-interpolation-method> ]? , <angular-color-stop-list>
220/// ```
221#[node]
222#[derive(Parse, Peek, ToCursors, ToSpan, SemanticEq, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
223#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
224#[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable), visit)]
225#[derive(csskit_derives::NodeWithMetadata)]
226#[metadata(value_kinds = Image)]
227pub struct ConicGradientFunction<'a> {
228	#[atom(CssAtomSet::ConicGradient)]
229	pub name: T![Function],
230	pub params: ConicGradientFunctionParams<'a>,
231	#[semantic_eq(skip)]
232	pub close: T![')'],
233}
234
235#[node]
236#[derive(Parse, Peek, ToCursors, ToSpan, SemanticEq, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
237#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
238#[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable))]
239#[derive(csskit_derives::NodeWithMetadata)]
240pub struct ConicGradientFunctionParams<'a> {
241	pub options: Option<ConicGradientOptions<'a>>,
242	#[semantic_eq(skip)]
243	pub comma: Option<T![,]>,
244	pub stops: CommaSeparated<'a, AngularColorStopOrHint<'a>>,
245}
246
247/// <https://drafts.csswg.org/css-images-4/#funcdef-repeating-conic-gradient>
248/// ```text,ignore
249/// <repeating-conic-gradient()> = repeating-conic-gradient( [ <conic-gradient-syntax> ] )
250/// <conic-gradient-syntax> = [ [ [ from [ <angle> | <zero> ] ]? [ at <position> ]? ] || <color-interpolation-method> ]? , <angular-color-stop-list>
251/// ```
252#[node]
253#[derive(Parse, Peek, ToCursors, ToSpan, SemanticEq, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
254#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
255#[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable), visit)]
256#[derive(csskit_derives::NodeWithMetadata)]
257#[metadata(value_kinds = Image)]
258pub struct RepeatingConicGradientFunction<'a> {
259	#[atom(CssAtomSet::RepeatingConicGradient)]
260	pub name: T![Function],
261	pub params: RepeatingConicGradientFunctionParams<'a>,
262	#[semantic_eq(skip)]
263	pub close: T![')'],
264}
265
266#[node]
267#[derive(Parse, Peek, ToCursors, ToSpan, SemanticEq, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
268#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
269#[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable))]
270#[derive(csskit_derives::NodeWithMetadata)]
271pub struct RepeatingConicGradientFunctionParams<'a> {
272	pub options: Option<ConicGradientOptions<'a>>,
273	#[semantic_eq(skip)]
274	pub comma: Option<T![,]>,
275	pub stops: CommaSeparated<'a, AngularColorStopOrHint<'a>>,
276}
277
278/// The geometry and interpolation method of a conic gradient, in either order.
279///
280/// ```text,ignore
281/// [ [ from [ <angle> | <zero> ] ]? [ at <position> ]? ] || <color-interpolation-method>
282/// ```
283#[node]
284#[derive(Parse, Peek, ToCursors, ToSpan, SemanticEq, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
285#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
286#[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable))]
287#[derive(csskit_derives::NodeWithMetadata)]
288#[parse(one_must_occur)]
289pub struct ConicGradientOptions<'a> {
290	pub geometry: Option<ConicGeometry<'a>>,
291	pub interpolation: Option<ColorInterpolationMethod>,
292}
293
294/// ```text,ignore
295/// [ from [ <angle> | <zero> ] ]? [ at <position> ]?
296/// ```
297#[node]
298#[derive(Parse, Peek, ToCursors, ToSpan, SemanticEq, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
299#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
300#[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable))]
301#[derive(csskit_derives::NodeWithMetadata)]
302pub struct ConicGeometry<'a> {
303	pub from: Option<ConicFrom<'a>>,
304	pub at: Option<AtPosition<'a>>,
305}
306
307/// ```text,ignore
308/// from [ <angle> | <zero> ]
309/// ```
310#[node]
311#[derive(Parse, Peek, ToCursors, ToSpan, SemanticEq, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
312#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
313#[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable))]
314#[derive(csskit_derives::NodeWithMetadata)]
315pub struct ConicFrom<'a> {
316	#[atom(CssAtomSet::From)]
317	pub keyword: T![Ident],
318	pub angle: CalcableValue<'a, AngleOrZero>,
319}
320
321#[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable))]
322#[node]
323#[derive(Parse, Peek, ToCursors, ToSpan, SemanticEq, Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
324#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
325#[cfg_attr(feature = "visitable", visit(skip))]
326#[derive(csskit_derives::NodeWithMetadata)]
327pub enum NamedDirection {
328	#[atom(CssAtomSet::Bottom)]
329	Bottom(T![Ident]),
330	#[atom(CssAtomSet::Top)]
331	Top(T![Ident]),
332	#[atom(CssAtomSet::Left)]
333	Left(T![Ident]),
334	#[atom(CssAtomSet::Right)]
335	Right(T![Ident]),
336}
337
338#[node]
339#[derive(Parse, Peek, ToSpan, ToCursors, SemanticEq, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
340#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
341#[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable))]
342#[derive(csskit_derives::NodeWithMetadata)]
343pub enum LinearDirection<'a> {
344	Angle(CalcableValue<'a, Angle>),
345	Named(#[atom(CssAtomSet::To)] T![Ident], NamedDirection, Option<NamedDirection>),
346}
347
348/// <https://drafts.csswg.org/css-images-4/#typedef-radial-size>
349/// <https://drafts.csswg.org/css-images-4/#radial-size>
350///
351/// ```text,ignore
352/// <radial-size> = <radial-extent>{1,2} | <length-percentage [0,∞]>{1,2}
353/// <radial-extent> = closest-corner | closest-side | farthest-corner | farthest-side
354/// ```
355#[node]
356#[derive(Peek, ToSpan, ToCursors, SemanticEq, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
357#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
358#[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable))]
359#[derive(csskit_derives::NodeWithMetadata)]
360pub enum RadialSize<'a> {
361	Extent(RadialExtent, Option<RadialExtent>),
362	Circular(CalcableValue<'a, LengthPercentage>),
363	Elliptical(CalcableValue<'a, LengthPercentage>, CalcableValue<'a, LengthPercentage>),
364}
365
366/// <https://drafts.csswg.org/css-images-3/#typedef-radial-extent>
367///
368/// ```text,ignore
369/// <radial-extent> = closest-corner | closest-side | farthest-corner | farthest-side
370/// ```
371#[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable))]
372#[node]
373#[derive(Parse, Peek, ToCursors, ToSpan, SemanticEq, Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
374#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
375#[cfg_attr(feature = "visitable", visit(skip))]
376#[derive(csskit_derives::NodeWithMetadata)]
377pub enum RadialExtent {
378	#[atom(CssAtomSet::ClosestCorner)]
379	ClosestCorner(T![Ident]),
380	#[atom(CssAtomSet::ClosestSide)]
381	ClosestSide(T![Ident]),
382	#[atom(CssAtomSet::FarthestCorner)]
383	FarthestCorner(T![Ident]),
384	#[atom(CssAtomSet::FarthestSide)]
385	FarthestSide(T![Ident]),
386}
387
388impl<'a> Parse<'a> for RadialSize<'a> {
389	fn parse<I>(p: &mut Parser<'a, I>) -> ParserResult<Self>
390	where
391		I: Iterator<Item = Cursor> + Clone,
392	{
393		if let Some(extent) = p.parse_if_peek::<RadialExtent>()? {
394			let second = p.parse_if_peek::<RadialExtent>()?;
395			return Ok(RadialSize::Extent(extent, second));
396		}
397		let first = p.parse::<CalcableValue<LengthPercentage>>()?;
398		if let Some(second) = p.parse_if_peek::<CalcableValue<LengthPercentage>>()? {
399			return Ok(Self::Elliptical(first, second));
400		}
401		Ok(Self::Circular(first))
402	}
403}
404
405/// <https://drafts.csswg.org/css-images-3/#typedef-radial-shape>
406///
407/// ```text,ignore
408/// <radial-shape> = circle | ellipse
409/// ```
410#[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable))]
411#[node]
412#[derive(Parse, Peek, ToCursors, ToSpan, SemanticEq, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
413#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
414#[cfg_attr(feature = "visitable", visit(skip))]
415#[derive(csskit_derives::NodeWithMetadata)]
416pub enum RadialShape {
417	#[atom(CssAtomSet::Circle)]
418	Circle(T![Ident]),
419	#[atom(CssAtomSet::Ellipse)]
420	Ellipse(T![Ident]),
421}
422
423/// <https://drafts.csswg.org/css-images-4/#typedef-color-stop-list>
424///
425/// ```text,ignore
426/// <linear-color-stop> = <color> <length-percentage>{1,2}?
427/// <linear-color-hint> = <length-percentage>
428/// ```
429#[node]
430#[derive(Parse, Peek, ToSpan, ToCursors, SemanticEq, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
431#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
432#[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable))]
433#[derive(csskit_derives::NodeWithMetadata)]
434pub enum ColorStopOrHint<'a> {
435	Hint(CalcableValue<'a, LengthPercentage>),
436	Stop(Color<'a>, Option<CalcableValue<'a, LengthPercentage>>, Option<CalcableValue<'a, LengthPercentage>>),
437}
438
439/// <https://drafts.csswg.org/css-images-4/#typedef-angular-color-stop-list>
440///
441/// ```text,ignore
442/// <angular-color-stop> = <color> <color-stop-angle>?
443/// <color-stop-angle> = [ <angle-percentage> | <zero> ]{1,2}
444/// <angular-color-hint> = <angle-percentage> | <zero>
445/// ```
446#[node]
447#[derive(Parse, Peek, ToSpan, ToCursors, SemanticEq, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
448#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
449#[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable))]
450#[derive(csskit_derives::NodeWithMetadata)]
451pub enum AngularColorStopOrHint<'a> {
452	Hint(CalcableValue<'a, AnglePercentage>),
453	Stop(Color<'a>, Option<CalcableValue<'a, AnglePercentage>>, Option<CalcableValue<'a, AnglePercentage>>),
454}
455
456#[cfg(test)]
457mod tests {
458	use super::*;
459	use crate::CssAtomSet;
460	use css_parse::{assert_parse, assert_parse_error};
461
462	#[test]
463	fn test_writes() {
464		assert_parse!(CssAtomSet::ATOMS, Gradient, "linear-gradient(to bottom,yellow,blue)");
465		assert_parse!(CssAtomSet::ATOMS, Gradient, "linear-gradient(yellow,blue)");
466		assert_parse!(CssAtomSet::ATOMS, Gradient, "linear-gradient(to bottom,#fff,#fff 85%,#e6e6e6)");
467		assert_parse!(CssAtomSet::ATOMS, Gradient, "linear-gradient(45deg,#808080 25%,transparent 25%)");
468		assert_parse!(CssAtomSet::ATOMS, Gradient, "linear-gradient(to right,transparent,red 20%,red 80%,transparent)");
469		assert_parse!(CssAtomSet::ATOMS, Gradient, "linear-gradient(red 0% 40%,tan 0 70%,blue 0 100%)");
470		assert_parse!(
471			CssAtomSet::ATOMS,
472			Gradient,
473			"radial-gradient(closest-corner circle,rgba(1,65,255,0.4),rgba(1,65,255,0))"
474		);
475	}
476
477	#[test]
478	fn test_substitution() {
479		// calc()/var() permitted in angle, stop-position, and radial-size slots.
480		assert_parse!(CssAtomSet::ATOMS, Gradient, "linear-gradient(calc(45deg),red,blue)");
481		assert_parse!(CssAtomSet::ATOMS, Gradient, "linear-gradient(red calc(20% + 5px),blue)");
482		assert_parse!(CssAtomSet::ATOMS, Gradient, "radial-gradient(calc(50px) circle,red,blue)");
483	}
484
485	#[test]
486	fn test_interpolation() {
487		assert_parse!(CssAtomSet::ATOMS, Gradient, "linear-gradient(in oklab,red,blue)");
488		assert_parse!(CssAtomSet::ATOMS, Gradient, "linear-gradient(45deg in oklch longer hue,red,blue)");
489		assert_parse!(CssAtomSet::ATOMS, Gradient, "linear-gradient(in srgb to right,red,blue)");
490		assert_parse!(CssAtomSet::ATOMS, Gradient, "radial-gradient(in oklab circle,red,blue)");
491		assert_parse!(CssAtomSet::ATOMS, Gradient, "radial-gradient(ellipse 10px 20px at 10% 20% in srgb,red,blue)");
492		assert_parse!(CssAtomSet::ATOMS, Gradient, "conic-gradient(in hsl shorter hue,red,blue)");
493	}
494
495	#[test]
496	fn test_radial_geometry() {
497		assert_parse!(CssAtomSet::ATOMS, Gradient, "radial-gradient(circle closest-side,red,blue)");
498		assert_parse!(CssAtomSet::ATOMS, Gradient, "radial-gradient(at center,red,blue)");
499		assert_parse!(CssAtomSet::ATOMS, Gradient, "radial-gradient(farthest-side at left top,red,blue)");
500		assert_parse!(CssAtomSet::ATOMS, Gradient, "repeating-radial-gradient(circle 10px at 0 0,red,blue 10%)");
501	}
502
503	#[test]
504	fn test_double_position_stops() {
505		assert_parse!(CssAtomSet::ATOMS, Gradient, "linear-gradient(red 10% 20%,blue)");
506		assert_parse!(CssAtomSet::ATOMS, Gradient, "repeating-linear-gradient(red 0 10px,blue 10px 20px)");
507	}
508
509	#[test]
510	fn test_conic() {
511		assert_parse!(CssAtomSet::ATOMS, Gradient, "conic-gradient(red,blue)");
512		assert_parse!(CssAtomSet::ATOMS, Gradient, "conic-gradient(from 45deg,red,blue)");
513		assert_parse!(CssAtomSet::ATOMS, Gradient, "conic-gradient(from 0,red,10%,blue)");
514		assert_parse!(CssAtomSet::ATOMS, Gradient, "conic-gradient(at 10% 20%,red 0deg,blue 50% 75%,green)");
515		assert_parse!(CssAtomSet::ATOMS, Gradient, "conic-gradient(from 0.25turn at center in oklab,red,blue)");
516		assert_parse!(CssAtomSet::ATOMS, Gradient, "repeating-conic-gradient(red 0 10%,blue 10% 20%)");
517	}
518
519	#[test]
520	fn test_errors() {
521		assert_parse_error!(CssAtomSet::ATOMS, Gradient, "radial-gradient(at center circle,red,blue)");
522		assert_parse_error!(CssAtomSet::ATOMS, Gradient, "conic-gradient(at center from 45deg,red,blue)");
523		assert_parse_error!(CssAtomSet::ATOMS, Gradient, "linear-gradient(in oklab in srgb,red,blue)");
524		assert_parse_error!(CssAtomSet::ATOMS, Gradient, "conic-gradient(red 10px,blue)");
525	}
526}