Skip to main content

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(context: &ParserContext, input: &mut Parser) -> Result<Self, ParseError> {
77        input.expect_function_matching("ray")?;
78        input.parse_nested_block(|i| Self::parse_function_arguments(context, i))
79    }
80}
81
82impl RayFunction {
83    /// Parse the inner arguments of a `ray` function.
84    fn parse_function_arguments(
85        context: &ParserContext,
86        input: &mut Parser,
87    ) -> Result<Self, ParseError> {
88        use crate::values::specified::PositionOrAuto;
89
90        let mut angle = None;
91        let mut size = None;
92        let mut contain = false;
93        let mut position = None;
94        loop {
95            if angle.is_none() {
96                angle = input.try_parse(|i| Angle::parse(context, i)).ok();
97            }
98
99            if size.is_none() {
100                size = input.try_parse(generics::RaySize::parse).ok();
101                if size.is_some() {
102                    continue;
103                }
104            }
105
106            if !contain {
107                contain = input
108                    .try_parse(|i| i.expect_ident_matching("contain"))
109                    .is_ok();
110                if contain {
111                    continue;
112                }
113            }
114
115            if position.is_none() {
116                if input.try_parse(|i| i.expect_ident_matching("at")).is_ok() {
117                    let pos = Position::parse(context, input)?;
118                    position = Some(PositionOrAuto::Position(pos));
119                }
120
121                if position.is_some() {
122                    continue;
123                }
124            }
125            break;
126        }
127
128        if angle.is_none() {
129            return Err(ParseError::custom(StyleParseErrorKind::UnspecifiedError));
130        }
131
132        Ok(RayFunction {
133            angle: angle.unwrap(),
134            // If no <ray-size> is specified it defaults to closest-side.
135            size: size.unwrap_or(generics::RaySize::ClosestSide),
136            contain,
137            position: position.unwrap_or(PositionOrAuto::auto()),
138        })
139    }
140}
141
142impl Parse for OffsetPathFunction {
143    fn parse(context: &ParserContext, input: &mut Parser) -> Result<Self, ParseError> {
144        use crate::values::specified::basic_shape::{AllowedBasicShapes, ShapeType};
145
146        // <offset-path> = <ray()> | <url> | <basic-shape>
147        // https://drafts.fxtf.org/motion-1/#typedef-offset-path
148        if let Ok(ray) = input.try_parse(|i| RayFunction::parse(context, i)) {
149            return Ok(OffsetPathFunction::Ray(ray));
150        }
151
152        if crate::pref!("layout.css.motion-path-url.enabled") {
153            if let Ok(url) = input.try_parse(|i| SpecifiedUrl::parse(context, i)) {
154                return Ok(OffsetPathFunction::Url(url));
155            }
156        }
157
158        BasicShape::parse(context, input, AllowedBasicShapes::ALL, ShapeType::Outline)
159            .map(OffsetPathFunction::Shape)
160    }
161}
162
163impl Parse for OffsetPath {
164    fn parse(context: &ParserContext, input: &mut Parser) -> Result<Self, ParseError> {
165        // Parse none.
166        if input.try_parse(|i| i.expect_ident_matching("none")).is_ok() {
167            return Ok(OffsetPath::none());
168        }
169
170        let mut path = None;
171        let mut coord_box = None;
172        loop {
173            if path.is_none() {
174                path = input
175                    .try_parse(|i| OffsetPathFunction::parse(context, i))
176                    .ok();
177            }
178
179            if coord_box.is_none() {
180                coord_box = input.try_parse(CoordBox::parse).ok();
181                if coord_box.is_some() {
182                    continue;
183                }
184            }
185            break;
186        }
187
188        if let Some(p) = path {
189            return Ok(OffsetPath::OffsetPath {
190                path: Box::new(p),
191                coord_box: coord_box.unwrap_or(CoordBox::BorderBox),
192            });
193        }
194
195        match coord_box {
196            Some(c) => Ok(OffsetPath::CoordBox(c)),
197            None => Err(ParseError::custom(StyleParseErrorKind::UnspecifiedError)),
198        }
199    }
200}
201
202/// The direction of offset-rotate.
203#[derive(
204    Clone, Copy, Debug, MallocSizeOf, Parse, PartialEq, SpecifiedValueInfo, ToCss, ToShmem, ToTyped,
205)]
206#[repr(u8)]
207pub enum OffsetRotateDirection {
208    /// Unspecified direction keyword.
209    #[css(skip)]
210    None,
211    /// 0deg offset (face forward).
212    Auto,
213    /// 180deg offset (face backward).
214    Reverse,
215}
216
217impl OffsetRotateDirection {
218    /// Returns true if it is none (i.e. the keyword is not specified).
219    #[inline]
220    fn is_none(&self) -> bool {
221        *self == OffsetRotateDirection::None
222    }
223}
224
225#[inline]
226fn direction_specified_and_angle_is_zero(direction: &OffsetRotateDirection, angle: &Angle) -> bool {
227    !direction.is_none() && angle.is_zero()
228}
229
230/// The specified offset-rotate.
231/// The syntax is: "[ auto | reverse ] || <angle>"
232///
233/// https://drafts.fxtf.org/motion-1/#offset-rotate-property
234#[derive(Clone, Debug, MallocSizeOf, PartialEq, SpecifiedValueInfo, ToCss, ToShmem, ToTyped)]
235pub struct OffsetRotate {
236    /// [auto | reverse].
237    #[css(skip_if = "OffsetRotateDirection::is_none")]
238    direction: OffsetRotateDirection,
239    /// <angle>.
240    /// If direction is None, this is a fixed angle which indicates a
241    /// constant clockwise rotation transformation applied to it by this
242    /// specified rotation angle. Otherwise, the angle will be added to
243    /// the angle of the direction in layout.
244    #[css(contextual_skip_if = "direction_specified_and_angle_is_zero")]
245    angle: Angle,
246}
247
248impl OffsetRotate {
249    /// Returns the initial value, auto.
250    #[inline]
251    pub fn auto() -> Self {
252        OffsetRotate {
253            direction: OffsetRotateDirection::Auto,
254            angle: Angle::zero(),
255        }
256    }
257
258    /// Returns true if self is auto 0deg.
259    #[inline]
260    pub fn is_auto(&self) -> bool {
261        self.direction == OffsetRotateDirection::Auto && self.angle.is_zero()
262    }
263}
264
265impl Parse for OffsetRotate {
266    fn parse(context: &ParserContext, input: &mut Parser) -> Result<Self, ParseError> {
267        let mut direction = input.try_parse(OffsetRotateDirection::parse);
268        let angle = input.try_parse(|i| Angle::parse(context, i));
269        if direction.is_err() {
270            // The direction and angle could be any order, so give it a change to parse
271            // direction again.
272            direction = input.try_parse(OffsetRotateDirection::parse);
273        }
274
275        if direction.is_err() && angle.is_err() {
276            return Err(ParseError::custom(StyleParseErrorKind::UnspecifiedError));
277        }
278
279        Ok(OffsetRotate {
280            direction: direction.unwrap_or(OffsetRotateDirection::None),
281            angle: angle.unwrap_or(Zero::zero()),
282        })
283    }
284}
285
286impl ToComputedValue for OffsetRotate {
287    type ComputedValue = ComputedOffsetRotate;
288
289    #[inline]
290    fn to_computed_value(&self, context: &Context) -> Self::ComputedValue {
291        use crate::values::computed::Angle as ComputedAngle;
292
293        ComputedOffsetRotate {
294            auto: !self.direction.is_none(),
295            angle: if self.direction == OffsetRotateDirection::Reverse {
296                // The computed value should always convert "reverse" into "auto".
297                // e.g. "reverse calc(20deg + 10deg)" => "auto 210deg"
298                self.angle.to_computed_value(context) + ComputedAngle::from_degrees(180.0)
299            } else {
300                self.angle.to_computed_value(context)
301            },
302        }
303    }
304
305    #[inline]
306    fn from_computed_value(computed: &Self::ComputedValue) -> Self {
307        OffsetRotate {
308            direction: if computed.auto {
309                OffsetRotateDirection::Auto
310            } else {
311                OffsetRotateDirection::None
312            },
313            angle: ToComputedValue::from_computed_value(&computed.angle),
314        }
315    }
316}