css_ast/rules/container/
features.rs

1use crate::{StyleValue, Visit, VisitMut, Visitable as VisitableTrait, VisitableMut, types::Ratio, units::Length};
2use bumpalo::collections::Vec;
3use css_parse::{
4	ConditionKeyword, Cursor, Declaration, FeatureConditionList, Parse, Parser, Peek, RangedFeatureKeyword,
5	Result as ParserResult, discrete_feature, keyword_set, ranged_feature,
6};
7use csskit_derives::{ToCursors, ToSpan, Visitable};
8use csskit_proc_macro::visit;
9
10keyword_set!(pub enum WidthContainerFeatureKeyword { Width: "width" });
11impl RangedFeatureKeyword for WidthContainerFeatureKeyword {}
12
13ranged_feature!(
14	#[derive(Visitable)]
15	#[visit(self)]
16	pub enum WidthContainerFeature<WidthContainerFeatureKeyword, Length>
17);
18
19keyword_set!(pub enum HeightContainerFeatureKeyword { Height: "height" });
20impl RangedFeatureKeyword for HeightContainerFeatureKeyword {}
21
22ranged_feature!(
23	#[derive(Visitable)]
24	#[visit(self)]
25	pub enum HeightContainerFeature<HeightContainerFeatureKeyword, Length>
26);
27
28keyword_set!(pub enum InlineSizeContainerFeatureKeyword { InlineSize: "inline-size" });
29impl RangedFeatureKeyword for InlineSizeContainerFeatureKeyword {}
30
31ranged_feature!(
32	#[derive(Visitable)]
33	#[visit(self)]
34	pub enum InlineSizeContainerFeature<InlineSizeContainerFeatureKeyword, Length>
35);
36
37keyword_set!(pub enum BlockSizeContainerFeatureKeyword { BlockSize: "block-size" });
38impl RangedFeatureKeyword for BlockSizeContainerFeatureKeyword {}
39
40ranged_feature!(
41	#[derive(Visitable)]
42	#[visit(self)]
43	pub enum BlockSizeContainerFeature<BlockSizeContainerFeatureKeyword, Length>
44);
45
46keyword_set!(pub enum AspectRatioContainerFeatureKeyword { AspectRatio: "aspect-ratio" });
47impl RangedFeatureKeyword for AspectRatioContainerFeatureKeyword {}
48
49ranged_feature!(
50	#[derive(Visitable)]
51	#[visit(self)]
52	pub enum AspectRatioContainerFeature<AspectRatioContainerFeatureKeyword, Ratio>
53);
54
55keyword_set!(pub enum OrientationContainerFeatureKeyword { Portrait: "portrait", Landscape: "landscape" });
56
57discrete_feature!(
58	#[derive(Visitable)]
59	#[visit(self)]
60	pub enum OrientationContainerFeature<"orientation", OrientationContainerFeatureKeyword>
61);
62
63#[derive(ToCursors, ToSpan, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
64#[cfg_attr(feature = "serde", derive(serde::Serialize), serde(tag = "type", content = "value"))]
65#[visit]
66pub enum StyleQuery<'a> {
67	Is(Declaration<'a, StyleValue<'a>>),
68	Not(ConditionKeyword, Declaration<'a, StyleValue<'a>>),
69	And(Vec<'a, (Declaration<'a, StyleValue<'a>>, Option<ConditionKeyword>)>),
70	Or(Vec<'a, (Declaration<'a, StyleValue<'a>>, Option<ConditionKeyword>)>),
71}
72
73impl<'a> FeatureConditionList<'a> for StyleQuery<'a> {
74	type FeatureCondition = Declaration<'a, StyleValue<'a>>;
75	fn build_is(feature: Self::FeatureCondition) -> Self {
76		Self::Is(feature)
77	}
78	fn build_not(keyword: ConditionKeyword, feature: Self::FeatureCondition) -> Self {
79		Self::Not(keyword, feature)
80	}
81	fn build_and(feature: Vec<'a, (Self::FeatureCondition, Option<ConditionKeyword>)>) -> Self {
82		Self::And(feature)
83	}
84	fn build_or(feature: Vec<'a, (Self::FeatureCondition, Option<ConditionKeyword>)>) -> Self {
85		Self::Or(feature)
86	}
87}
88
89impl<'a> Parse<'a> for StyleQuery<'a> {
90	fn parse(p: &mut Parser<'a>) -> ParserResult<Self> {
91		Self::parse_condition(p)
92	}
93}
94
95impl<'a> VisitableTrait for StyleQuery<'a> {
96	fn accept<V: Visit>(&self, v: &mut V) {
97		v.visit_style_query(self);
98		match self {
99			Self::Is(feature) => feature.accept(v),
100			Self::Not(_, feature) => feature.accept(v),
101			Self::And(features) => {
102				for (feature, _) in features {
103					feature.accept(v);
104				}
105			}
106			Self::Or(features) => {
107				for (feature, _) in features {
108					feature.accept(v);
109				}
110			}
111		}
112	}
113}
114
115impl<'a> VisitableMut for StyleQuery<'a> {
116	fn accept_mut<V: VisitMut>(&mut self, v: &mut V) {
117		v.visit_style_query(self);
118		match self {
119			Self::Is(feature) => feature.accept_mut(v),
120			Self::Not(_, feature) => feature.accept_mut(v),
121			Self::And(features) => {
122				for (feature, _) in features {
123					feature.accept_mut(v);
124				}
125			}
126			Self::Or(features) => {
127				for (feature, _) in features {
128					feature.accept_mut(v);
129				}
130			}
131		}
132	}
133}
134
135#[derive(ToCursors, ToSpan, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
136#[cfg_attr(feature = "serde", derive(serde::Serialize), serde(tag = "type", content = "value"))]
137#[visit]
138pub enum ScrollStateQuery<'a> {
139	Is(ScrollStateFeature),
140	Not(ConditionKeyword, ScrollStateFeature),
141	And(Vec<'a, (ScrollStateFeature, Option<ConditionKeyword>)>),
142	Or(Vec<'a, (ScrollStateFeature, Option<ConditionKeyword>)>),
143}
144
145impl<'a> FeatureConditionList<'a> for ScrollStateQuery<'a> {
146	type FeatureCondition = ScrollStateFeature;
147	fn build_is(feature: ScrollStateFeature) -> Self {
148		Self::Is(feature)
149	}
150	fn build_not(keyword: ConditionKeyword, feature: ScrollStateFeature) -> Self {
151		Self::Not(keyword, feature)
152	}
153	fn build_and(feature: Vec<'a, (ScrollStateFeature, Option<ConditionKeyword>)>) -> Self {
154		Self::And(feature)
155	}
156	fn build_or(feature: Vec<'a, (ScrollStateFeature, Option<ConditionKeyword>)>) -> Self {
157		Self::Or(feature)
158	}
159}
160
161impl<'a> Parse<'a> for ScrollStateQuery<'a> {
162	fn parse(p: &mut Parser<'a>) -> ParserResult<Self> {
163		Self::parse_condition(p)
164	}
165}
166
167impl<'a> VisitableTrait for ScrollStateQuery<'a> {
168	fn accept<V: Visit>(&self, v: &mut V) {
169		match self {
170			Self::Is(feature) => feature.accept(v),
171			Self::Not(_, feature) => feature.accept(v),
172			Self::And(features) => {
173				for (feature, _) in features {
174					feature.accept(v);
175				}
176			}
177			Self::Or(features) => {
178				for (feature, _) in features {
179					feature.accept(v);
180				}
181			}
182		}
183	}
184}
185
186impl<'a> VisitableMut for ScrollStateQuery<'a> {
187	fn accept_mut<V: VisitMut>(&mut self, v: &mut V) {
188		match self {
189			Self::Is(feature) => feature.accept_mut(v),
190			Self::Not(_, feature) => feature.accept_mut(v),
191			Self::And(features) => {
192				for (feature, _) in features {
193					feature.accept_mut(v);
194				}
195			}
196			Self::Or(features) => {
197				for (feature, _) in features {
198					feature.accept_mut(v);
199				}
200			}
201		}
202	}
203}
204
205#[derive(ToCursors, ToSpan, Visitable, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
206#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
207#[visit]
208pub enum ScrollStateFeature {
209	Scrollable(ScrollableScrollStateFeature),
210	Snapped(SnappedScrollStateFeature),
211	Stuck(StuckScrollStateFeature),
212}
213
214keyword_set!(pub enum ScrollStateFeatureKeyword { Scrollable: "scrollable", Snapped: "snapped", Stuck: "stuck" });
215
216impl<'a> Peek<'a> for ScrollStateFeature {
217	fn peek(p: &Parser<'a>, c: Cursor) -> bool {
218		ScrollStateFeatureKeyword::peek(p, c)
219	}
220}
221
222impl<'a> Parse<'a> for ScrollStateFeature {
223	fn parse(p: &mut Parser<'a>) -> ParserResult<Self> {
224		let keyword = p.parse::<ScrollStateFeatureKeyword>()?;
225		match keyword {
226			ScrollStateFeatureKeyword::Scrollable(_) => p.parse::<ScrollableScrollStateFeature>().map(Self::Scrollable),
227			ScrollStateFeatureKeyword::Snapped(_) => p.parse::<SnappedScrollStateFeature>().map(Self::Snapped),
228			ScrollStateFeatureKeyword::Stuck(_) => p.parse::<StuckScrollStateFeature>().map(Self::Stuck),
229		}
230	}
231}
232
233discrete_feature!(
234	#[derive(Visitable)]
235	#[visit(self)]
236	pub enum ScrollableScrollStateFeature<"scrollable", ScrollableScrollStateFeatureKeyword>
237);
238
239keyword_set!(pub enum ScrollableScrollStateFeatureKeyword {
240	None: "none",
241	Top: "top",
242	Right: "right",
243	Bottom: "bottom",
244	Left: "left",
245	BlockStart: "block-start",
246	InlineStart: "inline-start",
247	BlockEnd: "block-end",
248	InlineEnd: "inline-end",
249	X: "x",
250	Y: "y",
251	Block: "block",
252	Inline: "inline",
253	Discrete: "discrete",
254});
255
256discrete_feature!(
257	#[derive(Visitable)]
258	#[visit(self)]
259	pub enum SnappedScrollStateFeature<"snapped", SnappedScrollStateFeatureKeyword>
260);
261
262keyword_set!(pub enum SnappedScrollStateFeatureKeyword {
263	None: "none",
264	X: "x",
265	Y: "y",
266	Block: "block",
267	Inline: "inline",
268	Both: "both",
269	Discrete: "discrete",
270});
271
272discrete_feature!(
273	#[derive(Visitable)]
274	#[visit(self)]
275	pub enum StuckScrollStateFeature<"stuck", StuckScrollStateFeatureKeyword>
276);
277
278keyword_set!(pub enum StuckScrollStateFeatureKeyword {
279	None: "none",
280	Top: "top",
281	Right: "right",
282	Bottom: "bottom",
283	Left: "left",
284	BlockStart: "block-start",
285	InlineStart: "inline-start",
286	BlockEnd: "block-end",
287	InlineEnd: "inline-end",
288	Discrete: "discrete",
289});
290
291#[cfg(test)]
292mod tests {
293	use super::*;
294	use css_parse::{assert_parse, assert_parse_error};
295
296	#[test]
297	fn size_test() {
298		assert_eq!(std::mem::size_of::<WidthContainerFeature>(), 124);
299		assert_eq!(std::mem::size_of::<HeightContainerFeature>(), 124);
300		assert_eq!(std::mem::size_of::<InlineSizeContainerFeature>(), 124);
301		assert_eq!(std::mem::size_of::<BlockSizeContainerFeature>(), 124);
302		assert_eq!(std::mem::size_of::<AspectRatioContainerFeature>(), 180);
303		assert_eq!(std::mem::size_of::<OrientationContainerFeature>(), 64);
304		assert_eq!(std::mem::size_of::<StyleQuery>(), 384);
305		assert_eq!(std::mem::size_of::<ScrollStateQuery>(), 88);
306		assert_eq!(std::mem::size_of::<ScrollStateFeature>(), 68);
307		assert_eq!(std::mem::size_of::<ScrollableScrollStateFeature>(), 64);
308		assert_eq!(std::mem::size_of::<SnappedScrollStateFeature>(), 64);
309		assert_eq!(std::mem::size_of::<StuckScrollStateFeature>(), 64);
310	}
311
312	#[test]
313	fn test_writes() {
314		assert_parse!(WidthContainerFeature, "(width:360px)");
315		assert_parse!(WidthContainerFeature, "(width>=1400px)");
316		assert_parse!(WidthContainerFeature, "(100px<=width)");
317		assert_parse!(WidthContainerFeature, "(100px<=width>1400px)");
318		assert_parse!(HeightContainerFeature, "(height:360px)");
319		assert_parse!(HeightContainerFeature, "(height>=1400px)");
320		assert_parse!(HeightContainerFeature, "(100px<=height)");
321		assert_parse!(HeightContainerFeature, "(100px<=height>1400px)");
322	}
323
324	#[test]
325	fn test_errors() {
326		assert_parse_error!(WidthContainerFeature, "(min-width > 10px)");
327		assert_parse_error!(WidthContainerFeature, "(width: 1%)");
328	}
329}