Skip to main content

style/values/generics/
position.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//! Generic types for CSS handling of specified and computed values of
6//! [`position`](https://drafts.csswg.org/css-backgrounds-3/#position)
7
8use cssparser::Parser;
9use std::fmt::Write;
10
11use style_derive::Animate;
12use style_traits::CssWriter;
13use style_traits::ParseError;
14use style_traits::SpecifiedValueInfo;
15use style_traits::ToCss;
16
17use crate::derives::*;
18use crate::logical_geometry::PhysicalSide;
19use crate::parser::{Parse, ParserContext};
20use crate::rule_tree::CascadeLevel;
21use crate::values::animated::ToAnimatedZero;
22use crate::values::computed::position::TryTacticAdjustment;
23use crate::values::generics::box_::PositionProperty;
24use crate::values::generics::length::GenericAnchorSizeFunction;
25use crate::values::generics::ratio::Ratio;
26use crate::values::generics::Optional;
27use crate::values::DashedIdent;
28
29use crate::values::computed::Context;
30use crate::values::computed::ToComputedValue;
31
32/// A generic type for representing a value scoped to a specific cascade level
33/// in the shadow tree hierarchy.
34#[repr(C)]
35#[derive(
36    Clone,
37    Copy,
38    Debug,
39    MallocSizeOf,
40    PartialEq,
41    SpecifiedValueInfo,
42    ToAnimatedValue,
43    ToCss,
44    ToResolvedValue,
45    ToShmem,
46    ToTyped,
47    Serialize,
48    Deserialize,
49)]
50#[typed_value(derive_fields)]
51pub struct TreeScoped<T> {
52    /// The scoped value.
53    pub value: T,
54    /// The cascade level in the shadow tree hierarchy.
55    #[css(skip)]
56    pub scope: CascadeLevel,
57}
58
59impl<T> TreeScoped<T> {
60    /// Creates a new `TreeScoped` value.
61    pub fn new(value: T, scope: CascadeLevel) -> Self {
62        Self { value, scope }
63    }
64
65    /// Creates a new `TreeScoped` value with the default cascade level
66    /// (same tree author normal).
67    pub fn with_default_level(value: T) -> Self {
68        Self {
69            value,
70            scope: CascadeLevel::same_tree_author_normal(),
71        }
72    }
73}
74
75impl<T> Parse for TreeScoped<T>
76where
77    T: Parse,
78{
79    fn parse<'i, 't>(
80        context: &ParserContext,
81        input: &mut Parser<'i, 't>,
82    ) -> Result<Self, ParseError<'i>> {
83        Ok(TreeScoped {
84            value: T::parse(context, input)?,
85            scope: CascadeLevel::same_tree_author_normal(),
86        })
87    }
88}
89
90impl<T: ToComputedValue> ToComputedValue for TreeScoped<T> {
91    type ComputedValue = TreeScoped<T::ComputedValue>;
92    fn to_computed_value(&self, context: &Context) -> Self::ComputedValue {
93        TreeScoped {
94            value: self.value.to_computed_value(context),
95            scope: if context.current_scope().is_tree() {
96                context.current_scope()
97            } else {
98                self.scope.clone()
99            },
100        }
101    }
102
103    fn from_computed_value(computed: &Self::ComputedValue) -> Self {
104        Self {
105            value: ToComputedValue::from_computed_value(&computed.value),
106            scope: computed.scope.clone(),
107        }
108    }
109}
110
111/// A generic type for representing a CSS [position](https://drafts.csswg.org/css-values/#position).
112#[derive(
113    Animate,
114    Clone,
115    ComputeSquaredDistance,
116    Copy,
117    Debug,
118    Deserialize,
119    MallocSizeOf,
120    PartialEq,
121    Serialize,
122    SpecifiedValueInfo,
123    ToAnimatedValue,
124    ToAnimatedZero,
125    ToComputedValue,
126    ToResolvedValue,
127    ToShmem,
128    ToTyped,
129)]
130#[repr(C)]
131pub struct GenericPosition<H, V> {
132    /// The horizontal component of position.
133    pub horizontal: H,
134    /// The vertical component of position.
135    pub vertical: V,
136}
137
138impl<H, V> PositionComponent for Position<H, V>
139where
140    H: PositionComponent,
141    V: PositionComponent,
142{
143    #[inline]
144    fn is_center(&self) -> bool {
145        self.horizontal.is_center() && self.vertical.is_center()
146    }
147}
148
149pub use self::GenericPosition as Position;
150
151impl<H, V> Position<H, V> {
152    /// Returns a new position.
153    pub fn new(horizontal: H, vertical: V) -> Self {
154        Self {
155            horizontal,
156            vertical,
157        }
158    }
159}
160
161/// Implements a method that checks if the position is centered.
162pub trait PositionComponent {
163    /// Returns if the position component is 50% or center.
164    /// For pixel lengths, it always returns false.
165    fn is_center(&self) -> bool;
166}
167
168/// A generic type for representing an `Auto | <position>`.
169/// This is used by <offset-anchor> for now.
170/// https://drafts.fxtf.org/motion-1/#offset-anchor-property
171#[derive(
172    Animate,
173    Clone,
174    ComputeSquaredDistance,
175    Copy,
176    Debug,
177    Deserialize,
178    MallocSizeOf,
179    Parse,
180    PartialEq,
181    Serialize,
182    SpecifiedValueInfo,
183    ToAnimatedZero,
184    ToAnimatedValue,
185    ToComputedValue,
186    ToCss,
187    ToResolvedValue,
188    ToShmem,
189    ToTyped,
190)]
191#[repr(C, u8)]
192pub enum GenericPositionOrAuto<Pos> {
193    /// The <position> value.
194    Position(Pos),
195    /// The keyword `auto`.
196    Auto,
197}
198
199pub use self::GenericPositionOrAuto as PositionOrAuto;
200
201impl<Pos> PositionOrAuto<Pos> {
202    /// Return `auto`.
203    #[inline]
204    pub fn auto() -> Self {
205        PositionOrAuto::Auto
206    }
207
208    /// Return true if it is 'auto'.
209    #[inline]
210    pub fn is_auto(&self) -> bool {
211        matches!(self, PositionOrAuto::Auto)
212    }
213}
214
215/// A generic value for the `z-index` property.
216#[derive(
217    Animate,
218    Clone,
219    ComputeSquaredDistance,
220    Copy,
221    Debug,
222    MallocSizeOf,
223    PartialEq,
224    Parse,
225    SpecifiedValueInfo,
226    ToAnimatedValue,
227    ToAnimatedZero,
228    ToComputedValue,
229    ToCss,
230    ToResolvedValue,
231    ToShmem,
232    ToTyped,
233)]
234#[repr(C, u8)]
235pub enum GenericZIndex<I> {
236    /// An integer value.
237    Integer(I),
238    /// The keyword `auto`.
239    Auto,
240}
241
242pub use self::GenericZIndex as ZIndex;
243
244impl<Integer> ZIndex<Integer> {
245    /// Returns `auto`
246    #[inline]
247    pub fn auto() -> Self {
248        ZIndex::Auto
249    }
250
251    /// Returns whether `self` is `auto`.
252    #[inline]
253    pub fn is_auto(self) -> bool {
254        matches!(self, ZIndex::Auto)
255    }
256
257    /// Returns the integer value if it is an integer, or `auto`.
258    #[inline]
259    pub fn integer_or(self, auto: Integer) -> Integer {
260        match self {
261            ZIndex::Integer(n) => n,
262            ZIndex::Auto => auto,
263        }
264    }
265}
266
267/// Ratio or None.
268#[derive(
269    Animate,
270    Clone,
271    ComputeSquaredDistance,
272    Copy,
273    Debug,
274    MallocSizeOf,
275    PartialEq,
276    SpecifiedValueInfo,
277    ToAnimatedValue,
278    ToComputedValue,
279    ToCss,
280    ToResolvedValue,
281    ToShmem,
282)]
283#[repr(C, u8)]
284pub enum PreferredRatio<N> {
285    /// Without specified ratio
286    #[css(skip)]
287    None,
288    /// With specified ratio
289    Ratio(
290        #[animation(field_bound)]
291        #[css(field_bound)]
292        #[distance(field_bound)]
293        Ratio<N>,
294    ),
295}
296
297/// A generic value for the `aspect-ratio` property, the value is `auto || <ratio>`.
298#[derive(
299    Animate,
300    Clone,
301    ComputeSquaredDistance,
302    Copy,
303    Debug,
304    MallocSizeOf,
305    PartialEq,
306    SpecifiedValueInfo,
307    ToAnimatedValue,
308    ToComputedValue,
309    ToCss,
310    ToResolvedValue,
311    ToShmem,
312    ToTyped,
313)]
314#[repr(C)]
315pub struct GenericAspectRatio<N> {
316    /// Specifiy auto or not.
317    #[animation(constant)]
318    #[css(represents_keyword)]
319    pub auto: bool,
320    /// The preferred aspect-ratio value.
321    #[animation(field_bound)]
322    #[css(field_bound)]
323    #[distance(field_bound)]
324    pub ratio: PreferredRatio<N>,
325}
326
327pub use self::GenericAspectRatio as AspectRatio;
328
329impl<N> AspectRatio<N> {
330    /// Returns `auto`
331    #[inline]
332    pub fn auto() -> Self {
333        AspectRatio {
334            auto: true,
335            ratio: PreferredRatio::None,
336        }
337    }
338}
339
340impl<N> ToAnimatedZero for AspectRatio<N> {
341    #[inline]
342    fn to_animated_zero(&self) -> Result<Self, ()> {
343        Err(())
344    }
345}
346
347/// Specified type for `inset` properties, which allows
348/// the use of the `anchor()` function.
349/// Note(dshin): `LengthPercentageOrAuto` is not used here because
350/// having `LengthPercentageOrAuto` and `AnchorFunction` in the enum
351/// pays the price of the discriminator for `LengthPercentage | Auto`
352/// as well as `LengthPercentageOrAuto | AnchorFunction`. This increases
353/// the size of the style struct, which would not be great.
354/// On the other hand, we trade for code duplication, so... :(
355#[derive(
356    Animate,
357    Clone,
358    ComputeSquaredDistance,
359    Debug,
360    MallocSizeOf,
361    PartialEq,
362    ToCss,
363    ToShmem,
364    ToAnimatedValue,
365    ToAnimatedZero,
366    ToComputedValue,
367    ToResolvedValue,
368    ToTyped,
369)]
370#[repr(C)]
371#[typed_value(derive_fields)]
372pub enum GenericInset<P, LP> {
373    /// A `<length-percentage>` value.
374    LengthPercentage(LP),
375    /// An `auto` value.
376    Auto,
377    /// Inset defined by the anchor element.
378    ///
379    /// <https://drafts.csswg.org/css-anchor-position-1/#anchor-pos>
380    AnchorFunction(Box<GenericAnchorFunction<P, Self>>),
381    /// Inset defined by the size of the anchor element.
382    ///
383    /// <https://drafts.csswg.org/css-anchor-position-1/#anchor-pos>
384    AnchorSizeFunction(Box<GenericAnchorSizeFunction<Self>>),
385    /// A `<length-percentage>` value, guaranteed to contain `calc()`,
386    /// which then is guaranteed to contain `anchor()` or `anchor-size()`.
387    AnchorContainingCalcFunction(LP),
388}
389
390impl<P, LP> SpecifiedValueInfo for GenericInset<P, LP>
391where
392    LP: SpecifiedValueInfo,
393{
394    fn collect_completion_keywords(f: style_traits::KeywordsCollectFn) {
395        LP::collect_completion_keywords(f);
396        f(&["auto"]);
397        if static_prefs::pref!("layout.css.anchor-positioning.enabled") {
398            f(&["anchor", "anchor-size"]);
399        }
400    }
401}
402
403impl<P, LP> GenericInset<P, LP> {
404    /// `auto` value.
405    #[inline]
406    pub fn auto() -> Self {
407        Self::Auto
408    }
409
410    /// Return true if it is 'auto'.
411    #[inline]
412    #[cfg(feature = "servo")]
413    pub fn is_auto(&self) -> bool {
414        matches!(self, Self::Auto)
415    }
416}
417
418pub use self::GenericInset as Inset;
419
420/// Anchor function used by inset properties. This resolves
421/// to length at computed time.
422///
423/// https://drafts.csswg.org/css-anchor-position-1/#funcdef-anchor
424#[derive(
425    Animate,
426    Clone,
427    ComputeSquaredDistance,
428    Debug,
429    MallocSizeOf,
430    PartialEq,
431    SpecifiedValueInfo,
432    ToShmem,
433    ToAnimatedValue,
434    ToAnimatedZero,
435    ToComputedValue,
436    ToResolvedValue,
437    Serialize,
438    Deserialize,
439    ToTyped,
440)]
441#[repr(C)]
442pub struct GenericAnchorFunction<Percentage, Fallback> {
443    /// Anchor name of the element to anchor to.
444    /// If omitted, selects the implicit anchor element.
445    /// The shadow cascade order of the tree-scoped anchor name
446    /// associates the name with the host of the originating stylesheet.
447    #[animation(constant)]
448    pub target_element: TreeScoped<DashedIdent>,
449    /// Where relative to the target anchor element to position
450    /// the anchored element to.
451    pub side: GenericAnchorSide<Percentage>,
452    /// Value to use in case the anchor function is invalid.
453    pub fallback: Optional<Fallback>,
454}
455
456impl<Percentage, Fallback> ToCss for GenericAnchorFunction<Percentage, Fallback>
457where
458    Percentage: ToCss,
459    Fallback: ToCss,
460{
461    fn to_css<W>(&self, dest: &mut CssWriter<W>) -> std::fmt::Result
462    where
463        W: Write,
464    {
465        dest.write_str("anchor(")?;
466        if !self.target_element.value.is_empty() {
467            self.target_element.to_css(dest)?;
468            dest.write_str(" ")?;
469        }
470        self.side.to_css(dest)?;
471        if let Some(f) = self.fallback.as_ref() {
472            // This comma isn't really `derive()`-able, unfortunately.
473            dest.write_str(", ")?;
474            f.to_css(dest)?;
475        }
476        dest.write_str(")")
477    }
478}
479
480impl<Percentage, Fallback> GenericAnchorFunction<Percentage, Fallback> {
481    /// Is the anchor valid for given property?
482    pub fn valid_for(&self, side: PhysicalSide, position_property: PositionProperty) -> bool {
483        position_property.is_absolutely_positioned() && self.side.valid_for(side)
484    }
485}
486
487/// Keyword values for the anchor positioning function.
488#[derive(
489    Animate,
490    Clone,
491    ComputeSquaredDistance,
492    Copy,
493    Debug,
494    MallocSizeOf,
495    PartialEq,
496    SpecifiedValueInfo,
497    ToCss,
498    ToShmem,
499    Parse,
500    ToAnimatedValue,
501    ToAnimatedZero,
502    ToComputedValue,
503    ToResolvedValue,
504    Serialize,
505    Deserialize,
506)]
507#[repr(u8)]
508pub enum AnchorSideKeyword {
509    /// Inside relative (i.e. Same side) to the inset property it's used in.
510    Inside,
511    /// Same as above, but outside (i.e. Opposite side).
512    Outside,
513    /// Top of the anchor element.
514    Top,
515    /// Left of the anchor element.
516    Left,
517    /// Right of the anchor element.
518    Right,
519    /// Bottom of the anchor element.
520    Bottom,
521    /// Refers to the start side of the anchor element for the same axis of the inset
522    /// property it's used in, resolved against the positioned element's containing
523    /// block's writing mode.
524    Start,
525    /// Same as above, but for the end side.
526    End,
527    /// Same as `start`, resolved against the positioned element's writing mode.
528    SelfStart,
529    /// Same as above, but for the end side.
530    SelfEnd,
531    /// Halfway between `start` and `end` sides.
532    Center,
533}
534
535impl AnchorSideKeyword {
536    fn from_physical_side(side: PhysicalSide) -> Self {
537        match side {
538            PhysicalSide::Top => Self::Top,
539            PhysicalSide::Right => Self::Right,
540            PhysicalSide::Bottom => Self::Bottom,
541            PhysicalSide::Left => Self::Left,
542        }
543    }
544
545    fn physical_side(self) -> Option<PhysicalSide> {
546        Some(match self {
547            Self::Top => PhysicalSide::Top,
548            Self::Right => PhysicalSide::Right,
549            Self::Bottom => PhysicalSide::Bottom,
550            Self::Left => PhysicalSide::Left,
551            _ => return None,
552        })
553    }
554}
555
556impl TryTacticAdjustment for AnchorSideKeyword {
557    fn try_tactic_adjustment(&mut self, old_side: PhysicalSide, new_side: PhysicalSide) {
558        if !old_side.parallel_to(new_side) {
559            let Some(s) = self.physical_side() else {
560                return;
561            };
562            *self = Self::from_physical_side(if s == new_side {
563                old_side
564            } else if s == old_side {
565                new_side
566            } else if s == new_side.opposite_side() {
567                old_side.opposite_side()
568            } else {
569                debug_assert_eq!(s, old_side.opposite_side());
570                new_side.opposite_side()
571            });
572            return;
573        }
574
575        *self = match self {
576            Self::Center | Self::Inside | Self::Outside => *self,
577            Self::SelfStart => Self::SelfEnd,
578            Self::SelfEnd => Self::SelfStart,
579            Self::Start => Self::End,
580            Self::End => Self::Start,
581            Self::Top => Self::Bottom,
582            Self::Bottom => Self::Top,
583            Self::Left => Self::Right,
584            Self::Right => Self::Left,
585        }
586    }
587}
588
589impl AnchorSideKeyword {
590    fn valid_for(&self, side: PhysicalSide) -> bool {
591        match self {
592            Self::Left | Self::Right => matches!(side, PhysicalSide::Left | PhysicalSide::Right),
593            Self::Top | Self::Bottom => matches!(side, PhysicalSide::Top | PhysicalSide::Bottom),
594            Self::Inside
595            | Self::Outside
596            | Self::Start
597            | Self::End
598            | Self::SelfStart
599            | Self::SelfEnd
600            | Self::Center => true,
601        }
602    }
603}
604
605/// Anchor side for the anchor positioning function.
606#[derive(
607    Animate,
608    Clone,
609    ComputeSquaredDistance,
610    Copy,
611    Debug,
612    MallocSizeOf,
613    PartialEq,
614    Parse,
615    SpecifiedValueInfo,
616    ToCss,
617    ToShmem,
618    ToAnimatedValue,
619    ToAnimatedZero,
620    ToComputedValue,
621    ToResolvedValue,
622    Serialize,
623    Deserialize,
624)]
625#[repr(C)]
626pub enum GenericAnchorSide<P> {
627    /// A keyword value for the anchor side.
628    Keyword(AnchorSideKeyword),
629    /// Percentage value between the `start` and `end` sides.
630    Percentage(P),
631}
632
633impl<P> GenericAnchorSide<P> {
634    /// Is this anchor side valid for a given side?
635    pub fn valid_for(&self, side: PhysicalSide) -> bool {
636        match self {
637            Self::Keyword(k) => k.valid_for(side),
638            Self::Percentage(_) => true,
639        }
640    }
641}