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