motion.rs (10407B)
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 7 use crate::derives::*; 8 use crate::parser::{Parse, ParserContext}; 9 use crate::values::computed::motion::OffsetRotate as ComputedOffsetRotate; 10 use crate::values::computed::{Context, ToComputedValue}; 11 use crate::values::generics::motion as generics; 12 use crate::values::specified::basic_shape::BasicShape; 13 use crate::values::specified::position::{HorizontalPosition, VerticalPosition}; 14 use crate::values::specified::url::SpecifiedUrl; 15 use crate::values::specified::{Angle, Position}; 16 use crate::Zero; 17 use cssparser::Parser; 18 use style_traits::{ParseError, StyleParseErrorKind}; 19 20 /// The specified value of ray() function. 21 pub type RayFunction = generics::GenericRayFunction<Angle, Position>; 22 23 /// The specified value of <offset-path>. 24 pub type OffsetPathFunction = 25 generics::GenericOffsetPathFunction<BasicShape, RayFunction, SpecifiedUrl>; 26 27 /// The specified value of `offset-path`. 28 pub type OffsetPath = generics::GenericOffsetPath<OffsetPathFunction>; 29 30 /// The specified value of `offset-position`. 31 pub 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)] 58 pub enum CoordBox { 59 ContentBox, 60 PaddingBox, 61 BorderBox, 62 FillBox, 63 StrokeBox, 64 ViewBox, 65 } 66 67 impl 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 75 impl 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 85 impl 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 145 impl 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 169 impl 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)] 216 pub 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 226 impl 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] 235 fn 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 )] 246 pub 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 259 impl 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 276 impl 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 301 impl 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 }