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