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)]
371pub enum GenericInset<P, LP> {
372    /// A `<length-percentage>` value.
373    LengthPercentage(LP),
374    /// An `auto` value.
375    Auto,
376    /// Inset defined by the anchor element.
377    ///
378    /// <https://drafts.csswg.org/css-anchor-position-1/#anchor-pos>
379    AnchorFunction(Box<GenericAnchorFunction<P, Self>>),
380    /// Inset defined by the size of the anchor element.
381    ///
382    /// <https://drafts.csswg.org/css-anchor-position-1/#anchor-pos>
383    AnchorSizeFunction(Box<GenericAnchorSizeFunction<Self>>),
384    /// A `<length-percentage>` value, guaranteed to contain `calc()`,
385    /// which then is guaranteed to contain `anchor()` or `anchor-size()`.
386    AnchorContainingCalcFunction(LP),
387}
388
389impl<P, LP> SpecifiedValueInfo for GenericInset<P, LP>
390where
391    LP: SpecifiedValueInfo,
392{
393    fn collect_completion_keywords(f: style_traits::KeywordsCollectFn) {
394        LP::collect_completion_keywords(f);
395        f(&["auto"]);
396        if static_prefs::pref!("layout.css.anchor-positioning.enabled") {
397            f(&["anchor", "anchor-size"]);
398        }
399    }
400}
401
402impl<P, LP> GenericInset<P, LP> {
403    /// `auto` value.
404    #[inline]
405    pub fn auto() -> Self {
406        Self::Auto
407    }
408
409    /// Return true if it is 'auto'.
410    #[inline]
411    #[cfg(feature = "servo")]
412    pub fn is_auto(&self) -> bool {
413        matches!(self, Self::Auto)
414    }
415}
416
417pub use self::GenericInset as Inset;
418
419/// Anchor function used by inset properties. This resolves
420/// to length at computed time.
421///
422/// https://drafts.csswg.org/css-anchor-position-1/#funcdef-anchor
423#[derive(
424    Animate,
425    Clone,
426    ComputeSquaredDistance,
427    Debug,
428    MallocSizeOf,
429    PartialEq,
430    SpecifiedValueInfo,
431    ToShmem,
432    ToAnimatedValue,
433    ToAnimatedZero,
434    ToComputedValue,
435    ToResolvedValue,
436    Serialize,
437    Deserialize,
438)]
439#[repr(C)]
440pub struct GenericAnchorFunction<Percentage, Fallback> {
441    /// Anchor name of the element to anchor to.
442    /// If omitted, selects the implicit anchor element.
443    /// The shadow cascade order of the tree-scoped anchor name
444    /// associates the name with the host of the originating stylesheet.
445    #[animation(constant)]
446    pub target_element: TreeScoped<DashedIdent>,
447    /// Where relative to the target anchor element to position
448    /// the anchored element to.
449    pub side: GenericAnchorSide<Percentage>,
450    /// Value to use in case the anchor function is invalid.
451    pub fallback: Optional<Fallback>,
452}
453
454impl<Percentage, Fallback> ToCss for GenericAnchorFunction<Percentage, Fallback>
455where
456    Percentage: ToCss,
457    Fallback: ToCss,
458{
459    fn to_css<W>(&self, dest: &mut CssWriter<W>) -> std::fmt::Result
460    where
461        W: Write,
462    {
463        dest.write_str("anchor(")?;
464        if !self.target_element.value.is_empty() {
465            self.target_element.to_css(dest)?;
466            dest.write_str(" ")?;
467        }
468        self.side.to_css(dest)?;
469        if let Some(f) = self.fallback.as_ref() {
470            // This comma isn't really `derive()`-able, unfortunately.
471            dest.write_str(", ")?;
472            f.to_css(dest)?;
473        }
474        dest.write_str(")")
475    }
476}
477
478impl<Percentage, Fallback> GenericAnchorFunction<Percentage, Fallback> {
479    /// Is the anchor valid for given property?
480    pub fn valid_for(&self, side: PhysicalSide, position_property: PositionProperty) -> bool {
481        position_property.is_absolutely_positioned() && self.side.valid_for(side)
482    }
483}
484
485/// Keyword values for the anchor positioning function.
486#[derive(
487    Animate,
488    Clone,
489    ComputeSquaredDistance,
490    Copy,
491    Debug,
492    MallocSizeOf,
493    PartialEq,
494    SpecifiedValueInfo,
495    ToCss,
496    ToShmem,
497    Parse,
498    ToAnimatedValue,
499    ToAnimatedZero,
500    ToComputedValue,
501    ToResolvedValue,
502    Serialize,
503    Deserialize,
504)]
505#[repr(u8)]
506pub enum AnchorSideKeyword {
507    /// Inside relative (i.e. Same side) to the inset property it's used in.
508    Inside,
509    /// Same as above, but outside (i.e. Opposite side).
510    Outside,
511    /// Top of the anchor element.
512    Top,
513    /// Left of the anchor element.
514    Left,
515    /// Right of the anchor element.
516    Right,
517    /// Bottom of the anchor element.
518    Bottom,
519    /// Refers to the start side of the anchor element for the same axis of the inset
520    /// property it's used in, resolved against the positioned element's containing
521    /// block's writing mode.
522    Start,
523    /// Same as above, but for the end side.
524    End,
525    /// Same as `start`, resolved against the positioned element's writing mode.
526    SelfStart,
527    /// Same as above, but for the end side.
528    SelfEnd,
529    /// Halfway between `start` and `end` sides.
530    Center,
531}
532
533impl AnchorSideKeyword {
534    fn from_physical_side(side: PhysicalSide) -> Self {
535        match side {
536            PhysicalSide::Top => Self::Top,
537            PhysicalSide::Right => Self::Right,
538            PhysicalSide::Bottom => Self::Bottom,
539            PhysicalSide::Left => Self::Left,
540        }
541    }
542
543    fn physical_side(self) -> Option<PhysicalSide> {
544        Some(match self {
545            Self::Top => PhysicalSide::Top,
546            Self::Right => PhysicalSide::Right,
547            Self::Bottom => PhysicalSide::Bottom,
548            Self::Left => PhysicalSide::Left,
549            _ => return None,
550        })
551    }
552}
553
554impl TryTacticAdjustment for AnchorSideKeyword {
555    fn try_tactic_adjustment(&mut self, old_side: PhysicalSide, new_side: PhysicalSide) {
556        if !old_side.parallel_to(new_side) {
557            let Some(s) = self.physical_side() else {
558                return;
559            };
560            *self = Self::from_physical_side(if s == new_side {
561                old_side
562            } else if s == old_side {
563                new_side
564            } else if s == new_side.opposite_side() {
565                old_side.opposite_side()
566            } else {
567                debug_assert_eq!(s, old_side.opposite_side());
568                new_side.opposite_side()
569            });
570            return;
571        }
572
573        *self = match self {
574            Self::Center | Self::Inside | Self::Outside => *self,
575            Self::SelfStart => Self::SelfEnd,
576            Self::SelfEnd => Self::SelfStart,
577            Self::Start => Self::End,
578            Self::End => Self::Start,
579            Self::Top => Self::Bottom,
580            Self::Bottom => Self::Top,
581            Self::Left => Self::Right,
582            Self::Right => Self::Left,
583        }
584    }
585}
586
587impl AnchorSideKeyword {
588    fn valid_for(&self, side: PhysicalSide) -> bool {
589        match self {
590            Self::Left | Self::Right => matches!(side, PhysicalSide::Left | PhysicalSide::Right),
591            Self::Top | Self::Bottom => matches!(side, PhysicalSide::Top | PhysicalSide::Bottom),
592            Self::Inside
593            | Self::Outside
594            | Self::Start
595            | Self::End
596            | Self::SelfStart
597            | Self::SelfEnd
598            | Self::Center => true,
599        }
600    }
601}
602
603/// Anchor side for the anchor positioning function.
604#[derive(
605    Animate,
606    Clone,
607    ComputeSquaredDistance,
608    Copy,
609    Debug,
610    MallocSizeOf,
611    PartialEq,
612    Parse,
613    SpecifiedValueInfo,
614    ToCss,
615    ToShmem,
616    ToAnimatedValue,
617    ToAnimatedZero,
618    ToComputedValue,
619    ToResolvedValue,
620    Serialize,
621    Deserialize,
622)]
623#[repr(C)]
624pub enum GenericAnchorSide<P> {
625    /// A keyword value for the anchor side.
626    Keyword(AnchorSideKeyword),
627    /// Percentage value between the `start` and `end` sides.
628    Percentage(P),
629}
630
631impl<P> GenericAnchorSide<P> {
632    /// Is this anchor side valid for a given side?
633    pub fn valid_for(&self, side: PhysicalSide) -> bool {
634        match self {
635            Self::Keyword(k) => k.valid_for(side),
636            Self::Percentage(_) => true,
637        }
638    }
639}