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