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)]
60#[typed_value(derive_fields)]
61pub struct TreeScoped<T> {
62    /// The scoped value.
63    pub value: T,
64    /// The cascade level in the shadow tree hierarchy.
65    #[css(skip)]
66    pub scope: CascadeLevel,
67}
68
69impl<T: IsTreeScoped + PartialEq> PartialEq for TreeScoped<T> {
70    fn eq(&self, other: &Self) -> bool {
71        let tree_scoped = self.value.is_tree_scoped();
72        if tree_scoped != other.value.is_tree_scoped() {
73            // Trivially different.
74            return false;
75        }
76        let scopes_equal = self.scope == other.scope;
77        if !scopes_equal && tree_scoped {
78            // Scope difference matters if the name is actually tree-scoped.
79            return false;
80        }
81        // Ok, do the actual value comparison.
82        self.value == other.value
83    }
84}
85
86impl<T> TreeScoped<T> {
87    /// Creates a new `TreeScoped` value.
88    pub fn new(value: T, scope: CascadeLevel) -> Self {
89        Self { value, scope }
90    }
91
92    /// Creates a new `TreeScoped` value with the default cascade level
93    /// (same tree author normal).
94    pub fn with_default_level(value: T) -> Self {
95        Self {
96            value,
97            scope: CascadeLevel::same_tree_author_normal(),
98        }
99    }
100}
101
102impl<T> Parse for TreeScoped<T>
103where
104    T: Parse,
105{
106    fn parse<'i, 't>(
107        context: &ParserContext,
108        input: &mut Parser<'i, 't>,
109    ) -> Result<Self, ParseError<'i>> {
110        Ok(TreeScoped {
111            value: T::parse(context, input)?,
112            scope: CascadeLevel::same_tree_author_normal(),
113        })
114    }
115}
116
117impl<T> ToComputedValue for TreeScoped<T>
118where
119    T: ToComputedValue + IsTreeScoped,
120    T::ComputedValue: IsTreeScoped,
121{
122    type ComputedValue = TreeScoped<T::ComputedValue>;
123    fn to_computed_value(&self, context: &Context) -> Self::ComputedValue {
124        TreeScoped {
125            value: self.value.to_computed_value(context),
126            scope: if context.current_scope().is_tree() {
127                context.current_scope()
128            } else {
129                self.scope.clone()
130            },
131        }
132    }
133
134    fn from_computed_value(computed: &Self::ComputedValue) -> Self {
135        Self {
136            value: ToComputedValue::from_computed_value(&computed.value),
137            scope: computed.scope.clone(),
138        }
139    }
140}
141
142/// A generic type for representing a CSS [position](https://drafts.csswg.org/css-values/#position).
143#[derive(
144    Animate,
145    Clone,
146    ComputeSquaredDistance,
147    Copy,
148    Debug,
149    Deserialize,
150    MallocSizeOf,
151    PartialEq,
152    Serialize,
153    SpecifiedValueInfo,
154    ToAnimatedValue,
155    ToAnimatedZero,
156    ToComputedValue,
157    ToResolvedValue,
158    ToShmem,
159    ToTyped,
160)]
161#[repr(C)]
162pub struct GenericPosition<H, V> {
163    /// The horizontal component of position.
164    pub horizontal: H,
165    /// The vertical component of position.
166    pub vertical: V,
167}
168
169impl<H, V> PositionComponent for Position<H, V>
170where
171    H: PositionComponent,
172    V: PositionComponent,
173{
174    #[inline]
175    fn is_center(&self) -> bool {
176        self.horizontal.is_center() && self.vertical.is_center()
177    }
178}
179
180pub use self::GenericPosition as Position;
181
182impl<H, V> Position<H, V> {
183    /// Returns a new position.
184    pub fn new(horizontal: H, vertical: V) -> Self {
185        Self {
186            horizontal,
187            vertical,
188        }
189    }
190}
191
192/// Implements a method that checks if the position is centered.
193pub trait PositionComponent {
194    /// Returns if the position component is 50% or center.
195    /// For pixel lengths, it always returns false.
196    fn is_center(&self) -> bool;
197}
198
199/// A generic type for representing an `Auto | <position>`.
200/// This is used by <offset-anchor> for now.
201/// https://drafts.fxtf.org/motion-1/#offset-anchor-property
202#[derive(
203    Animate,
204    Clone,
205    ComputeSquaredDistance,
206    Copy,
207    Debug,
208    Deserialize,
209    MallocSizeOf,
210    Parse,
211    PartialEq,
212    Serialize,
213    SpecifiedValueInfo,
214    ToAnimatedZero,
215    ToAnimatedValue,
216    ToComputedValue,
217    ToCss,
218    ToResolvedValue,
219    ToShmem,
220    ToTyped,
221)]
222#[repr(C, u8)]
223pub enum GenericPositionOrAuto<Pos> {
224    /// The <position> value.
225    Position(Pos),
226    /// The keyword `auto`.
227    Auto,
228}
229
230pub use self::GenericPositionOrAuto as PositionOrAuto;
231
232impl<Pos> PositionOrAuto<Pos> {
233    /// Return `auto`.
234    #[inline]
235    pub fn auto() -> Self {
236        PositionOrAuto::Auto
237    }
238
239    /// Return true if it is 'auto'.
240    #[inline]
241    pub fn is_auto(&self) -> bool {
242        matches!(self, PositionOrAuto::Auto)
243    }
244}
245
246/// A generic value for the `z-index` property.
247#[derive(
248    Animate,
249    Clone,
250    ComputeSquaredDistance,
251    Copy,
252    Debug,
253    MallocSizeOf,
254    PartialEq,
255    Parse,
256    SpecifiedValueInfo,
257    ToAnimatedValue,
258    ToAnimatedZero,
259    ToComputedValue,
260    ToCss,
261    ToResolvedValue,
262    ToShmem,
263    ToTyped,
264)]
265#[repr(C, u8)]
266pub enum GenericZIndex<I> {
267    /// An integer value.
268    Integer(I),
269    /// The keyword `auto`.
270    Auto,
271}
272
273pub use self::GenericZIndex as ZIndex;
274
275impl<Integer> ZIndex<Integer> {
276    /// Returns `auto`
277    #[inline]
278    pub fn auto() -> Self {
279        ZIndex::Auto
280    }
281
282    /// Returns whether `self` is `auto`.
283    #[inline]
284    pub fn is_auto(self) -> bool {
285        matches!(self, ZIndex::Auto)
286    }
287
288    /// Returns the integer value if it is an integer, or `auto`.
289    #[inline]
290    pub fn integer_or(self, auto: Integer) -> Integer {
291        match self {
292            ZIndex::Integer(n) => n,
293            ZIndex::Auto => auto,
294        }
295    }
296}
297
298/// Ratio or None.
299#[derive(
300    Animate,
301    Clone,
302    ComputeSquaredDistance,
303    Copy,
304    Debug,
305    MallocSizeOf,
306    PartialEq,
307    SpecifiedValueInfo,
308    ToAnimatedValue,
309    ToComputedValue,
310    ToCss,
311    ToResolvedValue,
312    ToShmem,
313)]
314#[repr(C, u8)]
315pub enum PreferredRatio<N> {
316    /// Without specified ratio
317    #[css(skip)]
318    None,
319    /// With specified ratio
320    Ratio(
321        #[animation(field_bound)]
322        #[css(field_bound)]
323        #[distance(field_bound)]
324        Ratio<N>,
325    ),
326}
327
328/// A generic value for the `aspect-ratio` property, the value is `auto || <ratio>`.
329#[derive(
330    Animate,
331    Clone,
332    ComputeSquaredDistance,
333    Copy,
334    Debug,
335    MallocSizeOf,
336    PartialEq,
337    SpecifiedValueInfo,
338    ToAnimatedValue,
339    ToComputedValue,
340    ToCss,
341    ToResolvedValue,
342    ToShmem,
343    ToTyped,
344)]
345#[repr(C)]
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)]
402#[typed_value(derive_fields)]
403pub enum GenericInset<P, LP> {
404    /// A `<length-percentage>` value.
405    LengthPercentage(LP),
406    /// An `auto` value.
407    Auto,
408    /// Inset defined by the anchor element.
409    ///
410    /// <https://drafts.csswg.org/css-anchor-position-1/#anchor-pos>
411    AnchorFunction(Box<GenericAnchorFunction<P, Self>>),
412    /// Inset defined by the size of the anchor element.
413    ///
414    /// <https://drafts.csswg.org/css-anchor-position-1/#anchor-pos>
415    AnchorSizeFunction(Box<GenericAnchorSizeFunction<Self>>),
416    /// A `<length-percentage>` value, guaranteed to contain `calc()`,
417    /// which then is guaranteed to contain `anchor()` or `anchor-size()`.
418    AnchorContainingCalcFunction(LP),
419}
420
421impl<P, LP> SpecifiedValueInfo for GenericInset<P, LP>
422where
423    LP: SpecifiedValueInfo,
424{
425    fn collect_completion_keywords(f: style_traits::KeywordsCollectFn) {
426        LP::collect_completion_keywords(f);
427        f(&["auto"]);
428        if static_prefs::pref!("layout.css.anchor-positioning.enabled") {
429            f(&["anchor", "anchor-size"]);
430        }
431    }
432}
433
434impl<P, LP> GenericInset<P, LP> {
435    /// `auto` value.
436    #[inline]
437    pub fn auto() -> Self {
438        Self::Auto
439    }
440
441    /// Return true if it is 'auto'.
442    #[inline]
443    #[cfg(feature = "servo")]
444    pub fn is_auto(&self) -> bool {
445        matches!(self, Self::Auto)
446    }
447}
448
449pub use self::GenericInset as Inset;
450
451/// Anchor function used by inset properties. This resolves
452/// to length at computed time.
453///
454/// https://drafts.csswg.org/css-anchor-position-1/#funcdef-anchor
455#[derive(
456    Animate,
457    Clone,
458    ComputeSquaredDistance,
459    Debug,
460    MallocSizeOf,
461    PartialEq,
462    SpecifiedValueInfo,
463    ToShmem,
464    ToAnimatedValue,
465    ToAnimatedZero,
466    ToComputedValue,
467    ToResolvedValue,
468    Serialize,
469    Deserialize,
470    ToTyped,
471)]
472#[repr(C)]
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}