style/values/specified/
motion.rs

1/* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
4
5//! Specified types for CSS values that are related to motion path.
6
7use crate::derives::*;
8use crate::parser::{Parse, ParserContext};
9use crate::values::computed::motion::OffsetRotate as ComputedOffsetRotate;
10use crate::values::computed::{Context, ToComputedValue};
11use crate::values::generics::motion as generics;
12use crate::values::specified::basic_shape::BasicShape;
13use crate::values::specified::position::{HorizontalPosition, VerticalPosition};
14use crate::values::specified::url::SpecifiedUrl;
15use crate::values::specified::{Angle, Position};
16use crate::Zero;
17use cssparser::Parser;
18use style_traits::{ParseError, StyleParseErrorKind};
19
20/// The specified value of ray() function.
21pub type RayFunction = generics::GenericRayFunction<Angle, Position>;
22
23/// The specified value of <offset-path>.
24pub type OffsetPathFunction =
25    generics::GenericOffsetPathFunction<BasicShape, RayFunction, SpecifiedUrl>;
26
27/// The specified value of `offset-path`.
28pub type OffsetPath = generics::GenericOffsetPath<OffsetPathFunction>;
29
30/// The specified value of `offset-position`.
31pub type OffsetPosition = generics::GenericOffsetPosition<HorizontalPosition, VerticalPosition>;
32
33/// The <coord-box> value, which defines the box that the <offset-path> sizes into.
34/// https://drafts.fxtf.org/motion-1/#valdef-offset-path-coord-box
35///
36/// <coord-box> = content-box | padding-box | border-box | fill-box | stroke-box | view-box
37/// https://drafts.csswg.org/css-box-4/#typedef-coord-box
38#[allow(missing_docs)]
39#[derive(
40    Animate,
41    Clone,
42    ComputeSquaredDistance,
43    Copy,
44    Debug,
45    Deserialize,
46    MallocSizeOf,
47    Parse,
48    PartialEq,
49    Serialize,
50    SpecifiedValueInfo,
51    ToAnimatedValue,
52    ToComputedValue,
53    ToCss,
54    ToResolvedValue,
55    ToShmem,
56)]
57#[repr(u8)]
58pub enum CoordBox {
59    ContentBox,
60    PaddingBox,
61    BorderBox,
62    FillBox,
63    StrokeBox,
64    ViewBox,
65}
66
67impl CoordBox {
68    /// Returns true if it is default value, border-box.
69    #[inline]
70    pub fn is_default(&self) -> bool {
71        matches!(*self, Self::BorderBox)
72    }
73}
74
75impl Parse for RayFunction {
76    fn parse<'i, 't>(
77        context: &ParserContext,
78        input: &mut Parser<'i, 't>,
79    ) -> Result<Self, ParseError<'i>> {
80        input.expect_function_matching("ray")?;
81        input.parse_nested_block(|i| Self::parse_function_arguments(context, i))
82    }
83}
84
85impl RayFunction {
86    /// Parse the inner arguments of a `ray` function.
87    fn parse_function_arguments<'i, 't>(
88        context: &ParserContext,
89        input: &mut Parser<'i, 't>,
90    ) -> Result<Self, ParseError<'i>> {
91        use crate::values::specified::PositionOrAuto;
92
93        let mut angle = None;
94        let mut size = None;
95        let mut contain = false;
96        let mut position = None;
97        loop {
98            if angle.is_none() {
99                angle = input.try_parse(|i| Angle::parse(context, i)).ok();
100            }
101
102            if size.is_none() {
103                size = input.try_parse(generics::RaySize::parse).ok();
104                if size.is_some() {
105                    continue;
106                }
107            }
108
109            if !contain {
110                contain = input
111                    .try_parse(|i| i.expect_ident_matching("contain"))
112                    .is_ok();
113                if contain {
114                    continue;
115                }
116            }
117
118            if position.is_none() {
119                if input.try_parse(|i| i.expect_ident_matching("at")).is_ok() {
120                    let pos = Position::parse(context, input)?;
121                    position = Some(PositionOrAuto::Position(pos));
122                }
123
124                if position.is_some() {
125                    continue;
126                }
127            }
128            break;
129        }
130
131        if angle.is_none() {
132            return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError));
133        }
134
135        Ok(RayFunction {
136            angle: angle.unwrap(),
137            // If no <ray-size> is specified it defaults to closest-side.
138            size: size.unwrap_or(generics::RaySize::ClosestSide),
139            contain,
140            position: position.unwrap_or(PositionOrAuto::auto()),
141        })
142    }
143}
144
145impl Parse for OffsetPathFunction {
146    fn parse<'i, 't>(
147        context: &ParserContext,
148        input: &mut Parser<'i, 't>,
149    ) -> Result<Self, ParseError<'i>> {
150        use crate::values::specified::basic_shape::{AllowedBasicShapes, ShapeType};
151
152        // <offset-path> = <ray()> | <url> | <basic-shape>
153        // https://drafts.fxtf.org/motion-1/#typedef-offset-path
154        if let Ok(ray) = input.try_parse(|i| RayFunction::parse(context, i)) {
155            return Ok(OffsetPathFunction::Ray(ray));
156        }
157
158        if static_prefs::pref!("layout.css.motion-path-url.enabled") {
159            if let Ok(url) = input.try_parse(|i| SpecifiedUrl::parse(context, i)) {
160                return Ok(OffsetPathFunction::Url(url));
161            }
162        }
163
164        BasicShape::parse(context, input, AllowedBasicShapes::ALL, ShapeType::Outline)
165            .map(OffsetPathFunction::Shape)
166    }
167}
168
169impl Parse for OffsetPath {
170    fn parse<'i, 't>(
171        context: &ParserContext,
172        input: &mut Parser<'i, 't>,
173    ) -> Result<Self, ParseError<'i>> {
174        // Parse none.
175        if input.try_parse(|i| i.expect_ident_matching("none")).is_ok() {
176            return Ok(OffsetPath::none());
177        }
178
179        let mut path = None;
180        let mut coord_box = None;
181        loop {
182            if path.is_none() {
183                path = input
184                    .try_parse(|i| OffsetPathFunction::parse(context, i))
185                    .ok();
186            }
187
188            if coord_box.is_none() {
189                coord_box = input.try_parse(CoordBox::parse).ok();
190                if coord_box.is_some() {
191                    continue;
192                }
193            }
194            break;
195        }
196
197        if let Some(p) = path {
198            return Ok(OffsetPath::OffsetPath {
199                path: Box::new(p),
200                coord_box: coord_box.unwrap_or(CoordBox::BorderBox),
201            });
202        }
203
204        match coord_box {
205            Some(c) => Ok(OffsetPath::CoordBox(c)),
206            None => Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)),
207        }
208    }
209}
210
211/// The direction of offset-rotate.
212#[derive(
213    Clone, Copy, Debug, MallocSizeOf, Parse, PartialEq, SpecifiedValueInfo, ToCss, ToShmem,
214)]
215#[repr(u8)]
216pub enum OffsetRotateDirection {
217    /// Unspecified direction keyword.
218    #[css(skip)]
219    None,
220    /// 0deg offset (face forward).
221    Auto,
222    /// 180deg offset (face backward).
223    Reverse,
224}
225
226impl OffsetRotateDirection {
227    /// Returns true if it is none (i.e. the keyword is not specified).
228    #[inline]
229    fn is_none(&self) -> bool {
230        *self == OffsetRotateDirection::None
231    }
232}
233
234#[inline]
235fn direction_specified_and_angle_is_zero(direction: &OffsetRotateDirection, angle: &Angle) -> bool {
236    !direction.is_none() && angle.is_zero()
237}
238
239/// The specified offset-rotate.
240/// The syntax is: "[ auto | reverse ] || <angle>"
241///
242/// https://drafts.fxtf.org/motion-1/#offset-rotate-property
243#[derive(
244    Clone, Copy, Debug, MallocSizeOf, PartialEq, SpecifiedValueInfo, ToCss, ToShmem, ToTyped,
245)]
246pub struct OffsetRotate {
247    /// [auto | reverse].
248    #[css(skip_if = "OffsetRotateDirection::is_none")]
249    direction: OffsetRotateDirection,
250    /// <angle>.
251    /// If direction is None, this is a fixed angle which indicates a
252    /// constant clockwise rotation transformation applied to it by this
253    /// specified rotation angle. Otherwise, the angle will be added to
254    /// the angle of the direction in layout.
255    #[css(contextual_skip_if = "direction_specified_and_angle_is_zero")]
256    angle: Angle,
257}
258
259impl OffsetRotate {
260    /// Returns the initial value, auto.
261    #[inline]
262    pub fn auto() -> Self {
263        OffsetRotate {
264            direction: OffsetRotateDirection::Auto,
265            angle: Angle::zero(),
266        }
267    }
268
269    /// Returns true if self is auto 0deg.
270    #[inline]
271    pub fn is_auto(&self) -> bool {
272        self.direction == OffsetRotateDirection::Auto && self.angle.is_zero()
273    }
274}
275
276impl Parse for OffsetRotate {
277    fn parse<'i, 't>(
278        context: &ParserContext,
279        input: &mut Parser<'i, 't>,
280    ) -> Result<Self, ParseError<'i>> {
281        let location = input.current_source_location();
282        let mut direction = input.try_parse(OffsetRotateDirection::parse);
283        let angle = input.try_parse(|i| Angle::parse(context, i));
284        if direction.is_err() {
285            // The direction and angle could be any order, so give it a change to parse
286            // direction again.
287            direction = input.try_parse(OffsetRotateDirection::parse);
288        }
289
290        if direction.is_err() && angle.is_err() {
291            return Err(location.new_custom_error(StyleParseErrorKind::UnspecifiedError));
292        }
293
294        Ok(OffsetRotate {
295            direction: direction.unwrap_or(OffsetRotateDirection::None),
296            angle: angle.unwrap_or(Zero::zero()),
297        })
298    }
299}
300
301impl ToComputedValue for OffsetRotate {
302    type ComputedValue = ComputedOffsetRotate;
303
304    #[inline]
305    fn to_computed_value(&self, context: &Context) -> Self::ComputedValue {
306        use crate::values::computed::Angle as ComputedAngle;
307
308        ComputedOffsetRotate {
309            auto: !self.direction.is_none(),
310            angle: if self.direction == OffsetRotateDirection::Reverse {
311                // The computed value should always convert "reverse" into "auto".
312                // e.g. "reverse calc(20deg + 10deg)" => "auto 210deg"
313                self.angle.to_computed_value(context) + ComputedAngle::from_degrees(180.0)
314            } else {
315                self.angle.to_computed_value(context)
316            },
317        }
318    }
319
320    #[inline]
321    fn from_computed_value(computed: &Self::ComputedValue) -> Self {
322        OffsetRotate {
323            direction: if computed.auto {
324                OffsetRotateDirection::Auto
325            } else {
326                OffsetRotateDirection::None
327            },
328            angle: ToComputedValue::from_computed_value(&computed.angle),
329        }
330    }
331}