1use super::{Property, PropertyId};
4use crate::compat;
5use crate::context::PropertyHandlerContext;
6use crate::declaration::{DeclarationBlock, DeclarationList};
7use crate::error::{ParserError, PrinterError};
8use crate::macros::define_list_shorthand;
9use crate::prefixes::Feature;
10use crate::printer::Printer;
11use crate::properties::masking::get_webkit_mask_property;
12use crate::traits::{Parse, PropertyHandler, Shorthand, ToCss, Zero};
13use crate::values::{easing::EasingFunction, time::Time};
14use crate::vendor_prefix::VendorPrefix;
15#[cfg(feature = "visitor")]
16use crate::visitor::Visit;
17use cssparser::*;
18use itertools::izip;
19use smallvec::SmallVec;
20
21define_list_shorthand! {
22 pub struct Transition<'i>(VendorPrefix) {
24 #[cfg_attr(feature = "serde", serde(borrow))]
26 property: TransitionProperty(PropertyId<'i>, VendorPrefix),
27 duration: TransitionDuration(Time, VendorPrefix),
29 delay: TransitionDelay(Time, VendorPrefix),
31 timing_function: TransitionTimingFunction(EasingFunction, VendorPrefix),
33 }
34}
35
36impl<'i> Parse<'i> for Transition<'i> {
37 fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {
38 let mut property = None;
39 let mut duration = None;
40 let mut delay = None;
41 let mut timing_function = None;
42
43 loop {
44 if duration.is_none() {
45 if let Ok(value) = input.try_parse(Time::parse) {
46 duration = Some(value);
47 continue;
48 }
49 }
50
51 if timing_function.is_none() {
52 if let Ok(value) = input.try_parse(EasingFunction::parse) {
53 timing_function = Some(value);
54 continue;
55 }
56 }
57
58 if delay.is_none() {
59 if let Ok(value) = input.try_parse(Time::parse) {
60 delay = Some(value);
61 continue;
62 }
63 }
64
65 if property.is_none() {
66 if let Ok(value) = input.try_parse(PropertyId::parse) {
67 property = Some(value);
68 continue;
69 }
70 }
71
72 break;
73 }
74
75 Ok(Transition {
76 property: property.unwrap_or(PropertyId::All),
77 duration: duration.unwrap_or(Time::Seconds(0.0)),
78 delay: delay.unwrap_or(Time::Seconds(0.0)),
79 timing_function: timing_function.unwrap_or(EasingFunction::Ease),
80 })
81 }
82}
83
84impl<'i> ToCss for Transition<'i> {
85 fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>
86 where
87 W: std::fmt::Write,
88 {
89 self.property.to_css(dest)?;
90 if !self.duration.is_zero() || !self.delay.is_zero() {
91 dest.write_char(' ')?;
92 self.duration.to_css(dest)?;
93 }
94
95 if !self.timing_function.is_ease() {
96 dest.write_char(' ')?;
97 self.timing_function.to_css(dest)?;
98 }
99
100 if !self.delay.is_zero() {
101 dest.write_char(' ')?;
102 self.delay.to_css(dest)?;
103 }
104
105 Ok(())
106 }
107}
108
109#[derive(Default)]
110pub(crate) struct TransitionHandler<'i> {
111 properties: Option<(SmallVec<[PropertyId<'i>; 1]>, VendorPrefix)>,
112 durations: Option<(SmallVec<[Time; 1]>, VendorPrefix)>,
113 delays: Option<(SmallVec<[Time; 1]>, VendorPrefix)>,
114 timing_functions: Option<(SmallVec<[EasingFunction; 1]>, VendorPrefix)>,
115 has_any: bool,
116}
117
118impl<'i> PropertyHandler<'i> for TransitionHandler<'i> {
119 fn handle_property(
120 &mut self,
121 property: &Property<'i>,
122 dest: &mut DeclarationList<'i>,
123 context: &mut PropertyHandlerContext<'i, '_>,
124 ) -> bool {
125 use Property::*;
126
127 macro_rules! maybe_flush {
128 ($prop: ident, $val: expr, $vp: ident) => {{
129 if let Some((val, prefixes)) = &self.$prop {
132 if val != $val && !prefixes.contains(*$vp) {
133 self.flush(dest, context);
134 }
135 }
136 }};
137 }
138
139 macro_rules! property {
140 ($feature: ident, $prop: ident, $val: expr, $vp: ident) => {{
141 maybe_flush!($prop, $val, $vp);
142
143 if let Some((val, prefixes)) = &mut self.$prop {
145 *val = $val.clone();
146 *prefixes |= *$vp;
147 *prefixes = context.targets.prefixes(*prefixes, Feature::$feature);
148 } else {
149 let prefixes = context.targets.prefixes(*$vp, Feature::$feature);
150 self.$prop = Some(($val.clone(), prefixes));
151 self.has_any = true;
152 }
153 }};
154 }
155
156 match property {
157 TransitionProperty(val, vp) => property!(TransitionProperty, properties, val, vp),
158 TransitionDuration(val, vp) => property!(TransitionDuration, durations, val, vp),
159 TransitionDelay(val, vp) => property!(TransitionDelay, delays, val, vp),
160 TransitionTimingFunction(val, vp) => property!(TransitionTimingFunction, timing_functions, val, vp),
161 Transition(val, vp) => {
162 let properties: SmallVec<[PropertyId; 1]> = val.iter().map(|b| b.property.clone()).collect();
163 maybe_flush!(properties, &properties, vp);
164
165 let durations: SmallVec<[Time; 1]> = val.iter().map(|b| b.duration.clone()).collect();
166 maybe_flush!(durations, &durations, vp);
167
168 let delays: SmallVec<[Time; 1]> = val.iter().map(|b| b.delay.clone()).collect();
169 maybe_flush!(delays, &delays, vp);
170
171 let timing_functions: SmallVec<[EasingFunction; 1]> =
172 val.iter().map(|b| b.timing_function.clone()).collect();
173 maybe_flush!(timing_functions, &timing_functions, vp);
174
175 property!(TransitionProperty, properties, &properties, vp);
176 property!(TransitionDuration, durations, &durations, vp);
177 property!(TransitionDelay, delays, &delays, vp);
178 property!(TransitionTimingFunction, timing_functions, &timing_functions, vp);
179 }
180 Unparsed(val) if is_transition_property(&val.property_id) => {
181 self.flush(dest, context);
182 dest.push(Property::Unparsed(
183 val.get_prefixed(context.targets, Feature::Transition),
184 ));
185 }
186 _ => return false,
187 }
188
189 true
190 }
191
192 fn finalize(&mut self, dest: &mut DeclarationList<'i>, context: &mut PropertyHandlerContext<'i, '_>) {
193 self.flush(dest, context);
194 }
195}
196
197impl<'i> TransitionHandler<'i> {
198 fn flush(&mut self, dest: &mut DeclarationList<'i>, context: &mut PropertyHandlerContext<'i, '_>) {
199 if !self.has_any {
200 return;
201 }
202
203 self.has_any = false;
204
205 let mut properties = std::mem::take(&mut self.properties);
206 let mut durations = std::mem::take(&mut self.durations);
207 let mut delays = std::mem::take(&mut self.delays);
208 let mut timing_functions = std::mem::take(&mut self.timing_functions);
209
210 let rtl_properties = if let Some((properties, _)) = &mut properties {
211 expand_properties(properties, context)
212 } else {
213 None
214 };
215
216 if let (
217 Some((properties, property_prefixes)),
218 Some((durations, duration_prefixes)),
219 Some((delays, delay_prefixes)),
220 Some((timing_functions, timing_prefixes)),
221 ) = (&mut properties, &mut durations, &mut delays, &mut timing_functions)
222 {
223 let intersection = *property_prefixes & *duration_prefixes & *delay_prefixes & *timing_prefixes;
227 if !intersection.is_empty() {
228 macro_rules! get_transitions {
229 ($properties: ident) => {{
230 let mut transitions = SmallVec::with_capacity($properties.len());
233 let mut durations_iter = durations.iter().cycle().cloned();
234 let mut delays_iter = delays.iter().cycle().cloned();
235 let mut timing_iter = timing_functions.iter().cycle().cloned();
236 for property_id in $properties {
237 let duration = durations_iter.next().unwrap_or(Time::Seconds(0.0));
238 let delay = delays_iter.next().unwrap_or(Time::Seconds(0.0));
239 let timing_function = timing_iter.next().unwrap_or(EasingFunction::Ease);
240 let transition = Transition {
241 property: property_id.clone(),
242 duration,
243 delay,
244 timing_function,
245 };
246
247 for p in property_id.prefix().or_none() {
249 let mut t = transition.clone();
250 t.property = property_id.with_prefix(p);
251 transitions.push(t);
252 }
253 }
254 transitions
255 }};
256 }
257
258 let transitions: SmallVec<[Transition; 1]> = get_transitions!(properties);
259
260 if let Some(rtl_properties) = &rtl_properties {
261 let rtl_transitions = get_transitions!(rtl_properties);
262 context.add_logical_rule(
263 Property::Transition(transitions, intersection),
264 Property::Transition(rtl_transitions, intersection),
265 );
266 } else {
267 dest.push(Property::Transition(transitions.clone(), intersection));
268 }
269
270 property_prefixes.remove(intersection);
271 duration_prefixes.remove(intersection);
272 delay_prefixes.remove(intersection);
273 timing_prefixes.remove(intersection);
274 }
275 }
276
277 if let Some((properties, prefix)) = properties {
278 if !prefix.is_empty() {
279 if let Some(rtl_properties) = rtl_properties {
280 context.add_logical_rule(
281 Property::TransitionProperty(properties, prefix),
282 Property::TransitionProperty(rtl_properties, prefix),
283 );
284 } else {
285 dest.push(Property::TransitionProperty(properties, prefix));
286 }
287 }
288 }
289
290 if let Some((durations, prefix)) = durations {
291 if !prefix.is_empty() {
292 dest.push(Property::TransitionDuration(durations, prefix));
293 }
294 }
295
296 if let Some((delays, prefix)) = delays {
297 if !prefix.is_empty() {
298 dest.push(Property::TransitionDelay(delays, prefix));
299 }
300 }
301
302 if let Some((timing_functions, prefix)) = timing_functions {
303 if !prefix.is_empty() {
304 dest.push(Property::TransitionTimingFunction(timing_functions, prefix));
305 }
306 }
307
308 self.reset();
309 }
310
311 fn reset(&mut self) {
312 self.properties = None;
313 self.durations = None;
314 self.delays = None;
315 self.timing_functions = None;
316 }
317}
318
319#[inline]
320fn is_transition_property(property_id: &PropertyId) -> bool {
321 match property_id {
322 PropertyId::TransitionProperty(_)
323 | PropertyId::TransitionDuration(_)
324 | PropertyId::TransitionDelay(_)
325 | PropertyId::TransitionTimingFunction(_)
326 | PropertyId::Transition(_) => true,
327 _ => false,
328 }
329}
330
331fn expand_properties<'i>(
332 properties: &mut SmallVec<[PropertyId<'i>; 1]>,
333 context: &mut PropertyHandlerContext,
334) -> Option<SmallVec<[PropertyId<'i>; 1]>> {
335 let mut rtl_properties: Option<SmallVec<[PropertyId; 1]>> = None;
336 let mut i = 0;
337
338 macro_rules! replace {
339 ($properties: ident, $props: ident) => {
340 $properties[i] = $props[0].clone();
341 if $props.len() > 1 {
342 $properties.insert_many(i + 1, $props[1..].into_iter().cloned());
343 }
344 };
345 }
346
347 while i < properties.len() {
349 match get_logical_properties(&properties[i]) {
350 LogicalPropertyId::Block(feature, props) if context.should_compile_logical(feature) => {
351 replace!(properties, props);
352 if let Some(rtl_properties) = &mut rtl_properties {
353 replace!(rtl_properties, props);
354 }
355 i += props.len();
356 }
357 LogicalPropertyId::Inline(feature, ltr, rtl) if context.should_compile_logical(feature) => {
358 if rtl_properties.is_none() {
360 rtl_properties = Some(properties.clone());
361 }
362
363 replace!(properties, ltr);
364 if let Some(rtl_properties) = &mut rtl_properties {
365 replace!(rtl_properties, rtl);
366 }
367
368 i += ltr.len();
369 }
370 _ => {
371 properties[i].set_prefixes_for_targets(context.targets);
373
374 if let Some(property_id) = get_webkit_mask_property(&properties[i]) {
376 if context
377 .targets
378 .prefixes(VendorPrefix::None, Feature::MaskBorder)
379 .contains(VendorPrefix::WebKit)
380 {
381 properties.insert(i, property_id);
382 i += 1;
383 }
384 }
385
386 if let Some(rtl_properties) = &mut rtl_properties {
387 rtl_properties[i].set_prefixes_for_targets(context.targets);
388
389 if let Some(property_id) = get_webkit_mask_property(&rtl_properties[i]) {
390 if context
391 .targets
392 .prefixes(VendorPrefix::None, Feature::MaskBorder)
393 .contains(VendorPrefix::WebKit)
394 {
395 rtl_properties.insert(i, property_id);
396 }
397 }
398 }
399 i += 1;
400 }
401 }
402 }
403
404 rtl_properties
405}
406
407enum LogicalPropertyId {
408 None,
409 Block(compat::Feature, &'static [PropertyId<'static>]),
410 Inline(
411 compat::Feature,
412 &'static [PropertyId<'static>],
413 &'static [PropertyId<'static>],
414 ),
415}
416
417#[inline]
418fn get_logical_properties(property_id: &PropertyId) -> LogicalPropertyId {
419 use compat::Feature::*;
420 use LogicalPropertyId::*;
421 use PropertyId::*;
422 match property_id {
423 BlockSize => Block(LogicalSize, &[Height]),
424 InlineSize => Inline(LogicalSize, &[Width], &[Height]),
425 MinBlockSize => Block(LogicalSize, &[MinHeight]),
426 MaxBlockSize => Block(LogicalSize, &[MaxHeight]),
427 MinInlineSize => Inline(LogicalSize, &[MinWidth], &[MinHeight]),
428 MaxInlineSize => Inline(LogicalSize, &[MaxWidth], &[MaxHeight]),
429
430 InsetBlockStart => Block(LogicalInset, &[Top]),
431 InsetBlockEnd => Block(LogicalInset, &[Bottom]),
432 InsetInlineStart => Inline(LogicalInset, &[Left], &[Right]),
433 InsetInlineEnd => Inline(LogicalInset, &[Right], &[Left]),
434 InsetBlock => Block(LogicalInset, &[Top, Bottom]),
435 InsetInline => Block(LogicalInset, &[Left, Right]),
436 Inset => Block(LogicalInset, &[Top, Bottom, Left, Right]),
437
438 MarginBlockStart => Block(LogicalMargin, &[MarginTop]),
439 MarginBlockEnd => Block(LogicalMargin, &[MarginBottom]),
440 MarginInlineStart => Inline(LogicalMargin, &[MarginLeft], &[MarginRight]),
441 MarginInlineEnd => Inline(LogicalMargin, &[MarginRight], &[MarginLeft]),
442 MarginBlock => Block(LogicalMargin, &[MarginTop, MarginBottom]),
443 MarginInline => Block(LogicalMargin, &[MarginLeft, MarginRight]),
444
445 PaddingBlockStart => Block(LogicalPadding, &[PaddingTop]),
446 PaddingBlockEnd => Block(LogicalPadding, &[PaddingBottom]),
447 PaddingInlineStart => Inline(LogicalPadding, &[PaddingLeft], &[PaddingRight]),
448 PaddingInlineEnd => Inline(LogicalPadding, &[PaddingRight], &[PaddingLeft]),
449 PaddingBlock => Block(LogicalPadding, &[PaddingTop, PaddingBottom]),
450 PaddingInline => Block(LogicalPadding, &[PaddingLeft, PaddingRight]),
451
452 BorderBlockStart => Block(LogicalBorders, &[BorderTop]),
453 BorderBlockStartWidth => Block(LogicalBorders, &[BorderTopWidth]),
454 BorderBlockStartColor => Block(LogicalBorders, &[BorderTopColor]),
455 BorderBlockStartStyle => Block(LogicalBorders, &[BorderTopStyle]),
456
457 BorderBlockEnd => Block(LogicalBorders, &[BorderBottom]),
458 BorderBlockEndWidth => Block(LogicalBorders, &[BorderBottomWidth]),
459 BorderBlockEndColor => Block(LogicalBorders, &[BorderBottomColor]),
460 BorderBlockEndStyle => Block(LogicalBorders, &[BorderBottomStyle]),
461
462 BorderInlineStart => Inline(LogicalBorders, &[BorderLeft], &[BorderRight]),
463 BorderInlineStartWidth => Inline(LogicalBorders, &[BorderLeftWidth], &[BorderRightWidth]),
464 BorderInlineStartColor => Inline(LogicalBorders, &[BorderLeftColor], &[BorderRightColor]),
465 BorderInlineStartStyle => Inline(LogicalBorders, &[BorderLeftStyle], &[BorderRightStyle]),
466
467 BorderInlineEnd => Inline(LogicalBorders, &[BorderRight], &[BorderLeft]),
468 BorderInlineEndWidth => Inline(LogicalBorders, &[BorderRightWidth], &[BorderLeftWidth]),
469 BorderInlineEndColor => Inline(LogicalBorders, &[BorderRightColor], &[BorderLeftColor]),
470 BorderInlineEndStyle => Inline(LogicalBorders, &[BorderRightStyle], &[BorderLeftStyle]),
471
472 BorderBlock => Block(LogicalBorders, &[BorderTop, BorderBottom]),
473 BorderBlockColor => Block(LogicalBorders, &[BorderTopColor, BorderBottomColor]),
474 BorderBlockWidth => Block(LogicalBorders, &[BorderTopWidth, BorderBottomWidth]),
475 BorderBlockStyle => Block(LogicalBorders, &[BorderTopStyle, BorderBottomStyle]),
476
477 BorderInline => Block(LogicalBorders, &[BorderLeft, BorderRight]),
478 BorderInlineColor => Block(LogicalBorders, &[BorderLeftColor, BorderRightColor]),
479 BorderInlineWidth => Block(LogicalBorders, &[BorderLeftWidth, BorderRightWidth]),
480 BorderInlineStyle => Block(LogicalBorders, &[BorderLeftStyle, BorderRightStyle]),
481
482 BorderStartStartRadius => Inline(
485 LogicalBorders,
486 &[BorderTopLeftRadius(VendorPrefix::None)],
487 &[BorderTopRightRadius(VendorPrefix::None)],
488 ),
489 BorderStartEndRadius => Inline(
490 LogicalBorders,
491 &[BorderTopRightRadius(VendorPrefix::None)],
492 &[BorderTopLeftRadius(VendorPrefix::None)],
493 ),
494 BorderEndStartRadius => Inline(
495 LogicalBorders,
496 &[BorderBottomLeftRadius(VendorPrefix::None)],
497 &[BorderBottomRightRadius(VendorPrefix::None)],
498 ),
499 BorderEndEndRadius => Inline(
500 LogicalBorders,
501 &[BorderBottomRightRadius(VendorPrefix::None)],
502 &[BorderBottomLeftRadius(VendorPrefix::None)],
503 ),
504
505 _ => None,
506 }
507}