1use crate::derives::*;
8use crate::parser::{Parse, ParserContext};
9use crate::properties::{NonCustomPropertyId, PropertyId, ShorthandId};
10use crate::typed_om::ToTyped;
11use crate::values::generics::animation as generics;
12use crate::values::generics::position::{IsTreeScoped, TreeScoped};
13use crate::values::specified::{LengthPercentage, NonNegativeNumber, Time};
14use crate::values::{AtomIdent, CustomIdent, DashedIdent, KeyframesName};
15use crate::Atom;
16use cssparser::{match_ignore_ascii_case, Parser};
17use std::fmt::{self, Write};
18use style_traits::{
19 CssWriter, KeywordsCollectFn, ParseError, SpecifiedValueInfo, StyleParseErrorKind, ToCss,
20};
21
22#[derive(
25 Clone, Debug, Eq, Hash, MallocSizeOf, PartialEq, ToComputedValue, ToResolvedValue, ToShmem,
26)]
27#[repr(u8)]
28pub enum TransitionProperty {
29 NonCustom(NonCustomPropertyId),
31 Custom(Atom),
33 Unsupported(CustomIdent),
36}
37
38impl ToCss for TransitionProperty {
39 fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
40 where
41 W: Write,
42 {
43 match *self {
44 TransitionProperty::NonCustom(ref id) => id.to_css(dest),
45 TransitionProperty::Custom(ref name) => {
46 dest.write_str("--")?;
47 crate::values::serialize_atom_name(name, dest)
48 },
49 TransitionProperty::Unsupported(ref i) => i.to_css(dest),
50 }
51 }
52}
53
54impl ToTyped for TransitionProperty {}
55
56impl Parse for TransitionProperty {
57 fn parse(context: &ParserContext, input: &mut Parser) -> Result<Self, ParseError> {
58 let ident = input.expect_ident()?;
59
60 let id = match PropertyId::parse_ignoring_rule_type(ident, context) {
61 Ok(id) => id,
62 Err(..) => {
63 return Ok(TransitionProperty::Unsupported(CustomIdent::from_ident(
65 ident,
66 &["none"],
67 )?));
68 },
69 };
70
71 Ok(match id {
72 PropertyId::NonCustom(id) => TransitionProperty::NonCustom(id.unaliased()),
73 PropertyId::Custom(name) => TransitionProperty::Custom(name),
74 })
75 }
76}
77
78impl SpecifiedValueInfo for TransitionProperty {
79 fn collect_completion_keywords(f: KeywordsCollectFn) {
80 f(&["all"]);
84 }
85}
86
87impl TransitionProperty {
88 #[inline]
90 pub fn none() -> Self {
91 TransitionProperty::Unsupported(CustomIdent(atom!("none")))
92 }
93
94 #[inline]
96 pub fn is_none(&self) -> bool {
97 matches!(*self, TransitionProperty::Unsupported(ref ident) if ident.0 == atom!("none"))
98 }
99
100 #[inline]
102 pub fn all() -> Self {
103 TransitionProperty::NonCustom(NonCustomPropertyId::from_shorthand(ShorthandId::All))
104 }
105
106 #[inline]
108 pub fn is_all(&self) -> bool {
109 self == &TransitionProperty::NonCustom(NonCustomPropertyId::from_shorthand(
110 ShorthandId::All,
111 ))
112 }
113}
114
115#[derive(
119 Clone,
120 Copy,
121 Debug,
122 MallocSizeOf,
123 Parse,
124 PartialEq,
125 SpecifiedValueInfo,
126 ToComputedValue,
127 ToCss,
128 ToResolvedValue,
129 ToShmem,
130 ToTyped,
131)]
132#[repr(u8)]
133pub enum TransitionBehavior {
134 Normal,
136 AllowDiscrete,
138}
139
140impl TransitionBehavior {
141 #[inline]
143 pub fn normal() -> Self {
144 Self::Normal
145 }
146
147 #[inline]
149 pub fn is_normal(&self) -> bool {
150 matches!(*self, Self::Normal)
151 }
152}
153
154pub type AnimationDuration = generics::GenericAnimationDuration<Time>;
156
157impl Parse for AnimationDuration {
158 fn parse(context: &ParserContext, input: &mut Parser) -> Result<Self, ParseError> {
159 if crate::pref!("layout.css.scroll-driven-animations.enabled")
160 && input.try_parse(|i| i.expect_ident_matching("auto")).is_ok()
161 {
162 return Ok(Self::auto());
163 }
164
165 Time::parse_non_negative(context, input).map(AnimationDuration::Time)
166 }
167}
168
169#[derive(
171 Clone, Debug, MallocSizeOf, PartialEq, Parse, SpecifiedValueInfo, ToCss, ToShmem, ToTyped,
172)]
173pub enum AnimationIterationCount {
174 Number(NonNegativeNumber),
176 Infinite,
178}
179
180impl AnimationIterationCount {
181 #[inline]
183 pub fn one() -> Self {
184 Self::Number(NonNegativeNumber::new(1.0))
185 }
186
187 #[inline]
189 pub fn is_one(&self) -> bool {
190 *self == Self::one()
191 }
192}
193
194#[derive(
196 Clone,
197 Debug,
198 Eq,
199 Hash,
200 MallocSizeOf,
201 PartialEq,
202 SpecifiedValueInfo,
203 ToComputedValue,
204 ToCss,
205 ToResolvedValue,
206 ToShmem,
207 ToTyped,
208)]
209#[value_info(other_values = "none")]
210#[repr(C)]
211pub struct AnimationName(pub KeyframesName);
212
213impl AnimationName {
214 pub fn as_atom(&self) -> Option<&Atom> {
216 if self.is_none() {
217 return None;
218 }
219 Some(self.0.as_atom())
220 }
221
222 pub fn none() -> Self {
224 AnimationName(KeyframesName::none())
225 }
226
227 pub fn is_none(&self) -> bool {
229 self.0.is_none()
230 }
231}
232
233impl Parse for AnimationName {
234 fn parse(context: &ParserContext, input: &mut Parser) -> Result<Self, ParseError> {
235 if let Ok(name) = input.try_parse(|input| KeyframesName::parse(context, input)) {
236 return Ok(AnimationName(name));
237 }
238
239 input.expect_ident_matching("none")?;
240 Ok(AnimationName(KeyframesName::none()))
241 }
242}
243
244#[derive(
246 Copy,
247 Clone,
248 Debug,
249 MallocSizeOf,
250 Parse,
251 PartialEq,
252 SpecifiedValueInfo,
253 ToComputedValue,
254 ToCss,
255 ToResolvedValue,
256 ToShmem,
257 ToTyped,
258)]
259#[repr(u8)]
260#[allow(missing_docs)]
261pub enum AnimationDirection {
262 Normal,
263 Reverse,
264 Alternate,
265 AlternateReverse,
266}
267
268impl AnimationDirection {
269 #[inline]
271 pub fn match_keywords(name: &AnimationName) -> bool {
272 if let Some(name) = name.as_atom() {
273 #[cfg(feature = "gecko")]
274 return name.with_str(|n| Self::from_ident(n).is_ok());
275 #[cfg(feature = "servo")]
276 return Self::from_ident(name).is_ok();
277 }
278 false
279 }
280}
281
282#[derive(
284 Copy,
285 Clone,
286 Debug,
287 MallocSizeOf,
288 Parse,
289 PartialEq,
290 SpecifiedValueInfo,
291 ToComputedValue,
292 ToCss,
293 ToResolvedValue,
294 ToShmem,
295 ToTyped,
296)]
297#[repr(u8)]
298#[allow(missing_docs)]
299pub enum AnimationPlayState {
300 Running,
301 Paused,
302}
303
304impl AnimationPlayState {
305 #[inline]
307 pub fn match_keywords(name: &AnimationName) -> bool {
308 if let Some(name) = name.as_atom() {
309 #[cfg(feature = "gecko")]
310 return name.with_str(|n| Self::from_ident(n).is_ok());
311 #[cfg(feature = "servo")]
312 return Self::from_ident(name).is_ok();
313 }
314 false
315 }
316}
317
318#[derive(
320 Copy,
321 Clone,
322 Debug,
323 MallocSizeOf,
324 Parse,
325 PartialEq,
326 SpecifiedValueInfo,
327 ToComputedValue,
328 ToCss,
329 ToResolvedValue,
330 ToShmem,
331 ToTyped,
332)]
333#[repr(u8)]
334#[allow(missing_docs)]
335pub enum AnimationFillMode {
336 None,
337 Forwards,
338 Backwards,
339 Both,
340}
341
342impl AnimationFillMode {
343 #[inline]
346 pub fn match_keywords(name: &AnimationName) -> bool {
347 if let Some(name) = name.as_atom() {
348 #[cfg(feature = "gecko")]
349 return name.with_str(|n| Self::from_ident(n).is_ok());
350 #[cfg(feature = "servo")]
351 return Self::from_ident(name).is_ok();
352 }
353 false
354 }
355}
356
357#[derive(
359 Copy,
360 Clone,
361 Debug,
362 MallocSizeOf,
363 Parse,
364 PartialEq,
365 SpecifiedValueInfo,
366 ToComputedValue,
367 ToCss,
368 ToResolvedValue,
369 ToShmem,
370 ToTyped,
371)]
372#[repr(u8)]
373#[allow(missing_docs)]
374pub enum AnimationComposition {
375 Replace,
376 Add,
377 Accumulate,
378}
379
380#[derive(
384 Copy,
385 Clone,
386 Debug,
387 Eq,
388 Hash,
389 MallocSizeOf,
390 Parse,
391 PartialEq,
392 SpecifiedValueInfo,
393 ToComputedValue,
394 ToCss,
395 ToResolvedValue,
396 ToShmem,
397)]
398#[repr(u8)]
399pub enum Scroller {
400 Nearest,
402 Root,
404 #[css(keyword = "self")]
406 SelfElement,
407}
408
409impl Scroller {
410 #[inline]
412 fn is_default(&self) -> bool {
413 matches!(*self, Self::Nearest)
414 }
415}
416
417impl Default for Scroller {
418 fn default() -> Self {
419 Self::Nearest
420 }
421}
422
423#[derive(
429 Copy,
430 Clone,
431 Debug,
432 Eq,
433 Hash,
434 MallocSizeOf,
435 Parse,
436 PartialEq,
437 SpecifiedValueInfo,
438 ToComputedValue,
439 ToCss,
440 ToResolvedValue,
441 ToShmem,
442 ToTyped,
443)]
444#[repr(u8)]
445pub enum ScrollAxis {
446 Block = 0,
448 Inline = 1,
450 X = 2,
452 Y = 3,
454}
455
456impl ScrollAxis {
457 #[inline]
459 pub fn is_default(&self) -> bool {
460 matches!(*self, Self::Block)
461 }
462}
463
464impl Default for ScrollAxis {
465 fn default() -> Self {
466 Self::Block
467 }
468}
469
470#[derive(
473 Copy,
474 Clone,
475 Debug,
476 MallocSizeOf,
477 PartialEq,
478 SpecifiedValueInfo,
479 ToComputedValue,
480 ToCss,
481 ToResolvedValue,
482 ToShmem,
483)]
484#[css(function = "scroll")]
485#[repr(C)]
486pub struct ScrollFunction {
487 #[css(skip_if = "Scroller::is_default")]
489 pub scroller: Scroller,
490 #[css(skip_if = "ScrollAxis::is_default")]
492 pub axis: ScrollAxis,
493}
494
495impl ScrollFunction {
496 fn parse_arguments(input: &mut Parser) -> Result<Self, ParseError> {
498 let mut scroller = None;
501 let mut axis = None;
502 loop {
503 if scroller.is_none() {
504 scroller = input.try_parse(Scroller::parse).ok();
505 }
506
507 if axis.is_none() {
508 axis = input.try_parse(ScrollAxis::parse).ok();
509 if axis.is_some() {
510 continue;
511 }
512 }
513 break;
514 }
515
516 Ok(Self {
517 scroller: scroller.unwrap_or_default(),
518 axis: axis.unwrap_or_default(),
519 })
520 }
521}
522
523impl generics::ViewFunction<LengthPercentage> {
524 fn parse_arguments(context: &ParserContext, input: &mut Parser) -> Result<Self, ParseError> {
526 let mut axis = None;
529 let mut inset = None;
530 loop {
531 if axis.is_none() {
532 axis = input.try_parse(ScrollAxis::parse).ok();
533 }
534
535 if inset.is_none() {
536 inset = input
537 .try_parse(|i| ViewTimelineInset::parse(context, i))
538 .ok();
539 if inset.is_some() {
540 continue;
541 }
542 }
543 break;
544 }
545
546 Ok(Self {
547 inset: inset.unwrap_or_default(),
548 axis: axis.unwrap_or_default(),
549 })
550 }
551}
552
553pub type TimelineName = TreeScoped<TimelineIdent>;
558
559impl TimelineName {
560 pub fn none() -> Self {
562 Self::with_default_level(TimelineIdent::none())
563 }
564}
565
566#[derive(
568 Clone,
569 Debug,
570 Eq,
571 Hash,
572 MallocSizeOf,
573 PartialEq,
574 SpecifiedValueInfo,
575 ToComputedValue,
576 ToResolvedValue,
577 ToShmem,
578)]
579#[repr(C)]
580pub struct TimelineIdent(DashedIdent);
581
582impl TimelineIdent {
583 pub fn none() -> Self {
585 Self(DashedIdent::empty())
586 }
587
588 pub fn is_none(&self) -> bool {
590 self.0.is_empty()
591 }
592}
593
594impl IsTreeScoped for TimelineIdent {
595 fn is_tree_scoped(&self) -> bool {
596 !self.is_none()
597 }
598}
599
600impl Parse for TimelineIdent {
601 fn parse(context: &ParserContext, input: &mut Parser) -> Result<Self, ParseError> {
602 if input.try_parse(|i| i.expect_ident_matching("none")).is_ok() {
603 return Ok(Self::none());
604 }
605
606 DashedIdent::parse(context, input).map(TimelineIdent)
607 }
608}
609
610impl ToCss for TimelineIdent {
611 fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
612 where
613 W: Write,
614 {
615 if self.is_none() {
616 return dest.write_str("none");
617 }
618
619 self.0.to_css(dest)
620 }
621}
622
623impl ToTyped for TimelineName {}
624
625pub type AnimationTimeline = generics::GenericAnimationTimeline<LengthPercentage>;
627
628impl Parse for AnimationTimeline {
629 fn parse(context: &ParserContext, input: &mut Parser) -> Result<Self, ParseError> {
630 use crate::values::generics::animation::ViewFunction;
631
632 if input.try_parse(|i| i.expect_ident_matching("auto")).is_ok() {
636 return Ok(Self::Auto);
637 }
638
639 if let Ok(name) = input.try_parse(|i| TimelineName::parse(context, i)) {
641 return Ok(AnimationTimeline::Timeline(name));
642 }
643
644 let function = input.expect_function()?.clone();
646 input.parse_nested_block(move |i| {
647 match_ignore_ascii_case! { &function,
648 "scroll" => ScrollFunction::parse_arguments(i).map(Self::Scroll),
649 "view" => ViewFunction::parse_arguments(context, i).map(Self::View),
650 _ => {
651 Err(ParseError::custom(
652 StyleParseErrorKind::UnexpectedFunction
653 ))
654 },
655 }
656 })
657 }
658}
659
660pub type ViewTimelineInset = generics::GenericViewTimelineInset<LengthPercentage>;
662
663impl Parse for ViewTimelineInset {
664 fn parse(context: &ParserContext, input: &mut Parser) -> Result<Self, ParseError> {
665 use crate::values::specified::LengthPercentageOrAuto;
666
667 let start = LengthPercentageOrAuto::parse(context, input)?;
668 let end = match input.try_parse(|input| LengthPercentageOrAuto::parse(context, input)) {
669 Ok(end) => end,
670 Err(_) => start.clone(),
671 };
672
673 Ok(Self { start, end })
674 }
675}
676
677#[derive(
683 Clone,
684 Debug,
685 Eq,
686 Hash,
687 PartialEq,
688 MallocSizeOf,
689 SpecifiedValueInfo,
690 ToCss,
691 ToComputedValue,
692 ToResolvedValue,
693 ToShmem,
694 ToTyped,
695)]
696#[repr(transparent)]
697#[typed(todo_derive_fields)]
698#[value_info(other_values = "none, match-element")]
699pub struct ViewTransitionNameKeyword(AtomIdent);
700
701impl ViewTransitionNameKeyword {
702 pub fn none() -> Self {
704 Self(AtomIdent::new(atom!("none")))
705 }
706}
707
708impl IsTreeScoped for ViewTransitionNameKeyword {
709 fn is_tree_scoped(&self) -> bool {
710 self.0 .0 != atom!("none")
711 }
712}
713
714impl Parse for ViewTransitionNameKeyword {
715 fn parse(_: &ParserContext, input: &mut Parser) -> Result<Self, ParseError> {
716 let ident = input.expect_ident()?;
717 if ident.eq_ignore_ascii_case("none") {
718 return Ok(Self::none());
719 }
720
721 if ident.eq_ignore_ascii_case("match-element") {
722 return Ok(Self(AtomIdent::new(atom!("match-element"))));
723 }
724
725 CustomIdent::from_ident(ident, &["auto"]).map(|i| Self(AtomIdent::new(i.0)))
728 }
729}
730
731pub type ViewTransitionName = TreeScoped<ViewTransitionNameKeyword>;
733
734impl ViewTransitionName {
735 pub fn none() -> Self {
737 Self::with_default_level(ViewTransitionNameKeyword::none())
738 }
739}
740
741#[derive(
747 Clone,
748 Debug,
749 Default,
750 Eq,
751 Hash,
752 PartialEq,
753 MallocSizeOf,
754 SpecifiedValueInfo,
755 ToComputedValue,
756 ToCss,
757 ToResolvedValue,
758 ToShmem,
759 ToTyped,
760)]
761#[repr(C)]
762#[value_info(other_values = "none")]
763pub struct ViewTransitionClassList(
764 #[css(iterable, if_empty = "none")]
765 #[ignore_malloc_size_of = "Arc"]
766 crate::ArcSlice<CustomIdent>,
767);
768
769impl IsTreeScoped for ViewTransitionClassList {
770 fn is_tree_scoped(&self) -> bool {
771 !self.is_none()
772 }
773}
774
775impl ViewTransitionClassList {
776 pub fn none() -> Self {
778 Self(Default::default())
779 }
780
781 pub fn is_none(&self) -> bool {
783 self.0.is_empty()
784 }
785
786 pub fn iter(&self) -> impl Iterator<Item = &CustomIdent> {
788 self.0.iter()
789 }
790}
791
792impl Parse for ViewTransitionClassList {
793 fn parse(_: &ParserContext, input: &mut Parser) -> Result<Self, ParseError> {
794 use style_traits::{Separator, Space};
795
796 if input.try_parse(|i| i.expect_ident_matching("none")).is_ok() {
797 return Ok(Self::none());
798 }
799
800 Ok(Self(crate::ArcSlice::from_iter(
801 Space::parse(input, |i| CustomIdent::parse(i, &["none"]))?.into_iter(),
802 )))
803 }
804}
805
806pub type ViewTransitionClass = TreeScoped<ViewTransitionClassList>;
808
809impl ViewTransitionClass {
810 pub fn none() -> Self {
812 Self::with_default_level(ViewTransitionClassList::none())
813 }
814}
815
816#[derive(
823 Copy,
824 Clone,
825 Debug,
826 Eq,
827 MallocSizeOf,
828 Parse,
829 PartialEq,
830 SpecifiedValueInfo,
831 ToComputedValue,
832 ToCss,
833 ToResolvedValue,
834 ToShmem,
835 ToTyped,
836)]
837#[repr(u8)]
838pub enum TimelineRangeName {
839 #[css(skip)]
841 Normal,
842 #[css(skip)]
844 None,
845 Cover,
847 Contain,
850 Entry,
853 Exit,
856 EntryCrossing,
858 ExitCrossing,
860 Scroll,
863}
864
865impl TimelineRangeName {
866 #[inline]
868 pub fn is_normal(&self) -> bool {
869 matches!(*self, Self::Normal)
870 }
871
872 #[inline]
874 pub fn is_none(&self) -> bool {
875 matches!(*self, Self::None)
876 }
877}
878
879pub type AnimationRangeValue = generics::GenericAnimationRangeValue<LengthPercentage>;
881
882fn parse_animation_range(
883 context: &ParserContext,
884 input: &mut Parser,
885 default: LengthPercentage,
886) -> Result<AnimationRangeValue, ParseError> {
887 if input
888 .try_parse(|i| i.expect_ident_matching("normal"))
889 .is_ok()
890 {
891 return Ok(AnimationRangeValue::normal(default));
892 }
893
894 if let Ok(lp) = input.try_parse(|i| LengthPercentage::parse(context, i)) {
895 return Ok(AnimationRangeValue::length_percentage(lp));
896 }
897
898 let name = TimelineRangeName::parse(input)?;
899 let lp = input
900 .try_parse(|i| LengthPercentage::parse(context, i))
901 .unwrap_or(default);
902 Ok(AnimationRangeValue::new(name, lp))
903}
904
905pub type AnimationRangeStart = generics::GenericAnimationRangeStart<LengthPercentage>;
907
908impl Parse for AnimationRangeStart {
909 fn parse(context: &ParserContext, input: &mut Parser) -> Result<Self, ParseError> {
910 parse_animation_range(context, input, LengthPercentage::zero_percent()).map(Self)
911 }
912}
913
914pub type AnimationRangeEnd = generics::GenericAnimationRangeEnd<LengthPercentage>;
916
917impl Parse for AnimationRangeEnd {
918 fn parse(context: &ParserContext, input: &mut Parser) -> Result<Self, ParseError> {
919 parse_animation_range(context, input, LengthPercentage::hundred_percent()).map(Self)
920 }
921}