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}