position.rs (9856B)
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 //! CSS handling for the computed value of 6 //! [`position`][position] values. 7 //! 8 //! [position]: https://drafts.csswg.org/css-backgrounds-3/#position 9 10 use crate::values::computed::{ 11 Context, Integer, LengthPercentage, NonNegativeNumber, Percentage, ToComputedValue, 12 }; 13 use crate::values::generics; 14 use crate::values::generics::position::{ 15 AnchorSideKeyword, AspectRatio as GenericAspectRatio, GenericAnchorFunction, GenericAnchorSide, 16 GenericInset, Position as GenericPosition, PositionComponent as GenericPositionComponent, 17 PositionOrAuto as GenericPositionOrAuto, ZIndex as GenericZIndex, 18 }; 19 pub use crate::values::specified::position::{ 20 AnchorName, AnchorScope, DashedIdentAndOrTryTactic, GridAutoFlow, GridTemplateAreas, 21 MasonryAutoFlow, PositionAnchor, PositionArea, PositionAreaAxis, PositionAreaKeyword, 22 PositionAreaType, PositionTryFallbacks, PositionTryFallbacksTryTactic, 23 PositionTryFallbacksTryTacticKeyword, PositionTryOrder, PositionVisibility, 24 }; 25 use crate::Zero; 26 use std::fmt::{self, Write}; 27 use style_traits::{CssWriter, ToCss}; 28 29 /// The computed value of a CSS `<position>` 30 pub type Position = GenericPosition<HorizontalPosition, VerticalPosition>; 31 32 /// The computed value of an `auto | <position>` 33 pub type PositionOrAuto = GenericPositionOrAuto<Position>; 34 35 /// The computed value of a CSS horizontal position. 36 pub type HorizontalPosition = LengthPercentage; 37 38 /// The computed value of a CSS vertical position. 39 pub type VerticalPosition = LengthPercentage; 40 41 /// The computed value of anchor side. 42 pub type AnchorSide = GenericAnchorSide<Percentage>; 43 44 impl AnchorSide { 45 /// Break down given anchor side into its equivalent keyword and percentage. 46 pub fn keyword_and_percentage(&self) -> (AnchorSideKeyword, Percentage) { 47 match self { 48 Self::Percentage(p) => (AnchorSideKeyword::Start, *p), 49 Self::Keyword(k) => { 50 if matches!(k, AnchorSideKeyword::Center) { 51 (AnchorSideKeyword::Start, Percentage(0.5)) 52 } else { 53 (*k, Percentage::zero()) 54 } 55 }, 56 } 57 } 58 } 59 60 /// The computed value of an `anchor()` function. 61 pub type AnchorFunction = GenericAnchorFunction<Percentage, Inset>; 62 63 #[cfg(feature = "gecko")] 64 use crate::{ 65 gecko_bindings::structs::AnchorPosOffsetResolutionParams, 66 logical_geometry::PhysicalSide, 67 values::{computed::Length, DashedIdent}, 68 }; 69 70 impl AnchorFunction { 71 /// Resolve the anchor function with the given resolver. Returns `Err()` if no anchor is found. 72 #[cfg(feature = "gecko")] 73 pub fn resolve( 74 anchor_name: &DashedIdent, 75 anchor_side: &AnchorSide, 76 prop_side: PhysicalSide, 77 params: &AnchorPosOffsetResolutionParams, 78 ) -> Result<Length, ()> { 79 use crate::gecko_bindings::structs::Gecko_GetAnchorPosOffset; 80 81 let (keyword, percentage) = anchor_side.keyword_and_percentage(); 82 let mut offset = Length::zero(); 83 let valid = unsafe { 84 Gecko_GetAnchorPosOffset( 85 params, 86 anchor_name.0.as_ptr(), 87 prop_side as u8, 88 keyword as u8, 89 percentage.0, 90 &mut offset, 91 ) 92 }; 93 94 if !valid { 95 return Err(()); 96 } 97 98 Ok(offset) 99 } 100 } 101 102 /// Perform the adjustment of a given value for a given try tactic, as per: 103 /// https://drafts.csswg.org/css-anchor-position-1/#swap-due-to-a-try-tactic 104 pub(crate) trait TryTacticAdjustment { 105 /// Performs the adjustments necessary given an old side we're relative to, and a new side 106 /// we're relative to. 107 fn try_tactic_adjustment(&mut self, old_side: PhysicalSide, new_side: PhysicalSide); 108 } 109 110 impl<T: TryTacticAdjustment> TryTacticAdjustment for Box<T> { 111 fn try_tactic_adjustment(&mut self, old_side: PhysicalSide, new_side: PhysicalSide) { 112 (**self).try_tactic_adjustment(old_side, new_side); 113 } 114 } 115 116 impl<T: TryTacticAdjustment> TryTacticAdjustment for generics::NonNegative<T> { 117 fn try_tactic_adjustment(&mut self, old_side: PhysicalSide, new_side: PhysicalSide) { 118 self.0.try_tactic_adjustment(old_side, new_side); 119 } 120 } 121 122 impl<Percentage: TryTacticAdjustment> TryTacticAdjustment for GenericAnchorSide<Percentage> { 123 fn try_tactic_adjustment(&mut self, old_side: PhysicalSide, new_side: PhysicalSide) { 124 match self { 125 Self::Percentage(p) => p.try_tactic_adjustment(old_side, new_side), 126 Self::Keyword(side) => side.try_tactic_adjustment(old_side, new_side), 127 } 128 } 129 } 130 131 impl<Percentage: TryTacticAdjustment, Fallback: TryTacticAdjustment> TryTacticAdjustment 132 for GenericAnchorFunction<Percentage, Fallback> 133 { 134 fn try_tactic_adjustment(&mut self, old_side: PhysicalSide, new_side: PhysicalSide) { 135 self.side.try_tactic_adjustment(old_side, new_side); 136 if let Some(fallback) = self.fallback.as_mut() { 137 fallback.try_tactic_adjustment(old_side, new_side); 138 } 139 } 140 } 141 142 /// A computed type for `inset` properties. 143 pub type Inset = GenericInset<Percentage, LengthPercentage>; 144 impl TryTacticAdjustment for Inset { 145 // https://drafts.csswg.org/css-anchor-position-1/#swap-due-to-a-try-tactic: 146 // 147 // For inset properties, change the specified side in anchor() functions to maintain the 148 // same relative relationship to the new direction that they had to the old. 149 // 150 // If a <percentage> is used, and directions are opposing, change it to 100% minus the 151 // original percentage. 152 fn try_tactic_adjustment(&mut self, old_side: PhysicalSide, new_side: PhysicalSide) { 153 match self { 154 Self::Auto => {}, 155 Self::AnchorContainingCalcFunction(lp) | Self::LengthPercentage(lp) => { 156 lp.try_tactic_adjustment(old_side, new_side) 157 }, 158 Self::AnchorFunction(anchor) => anchor.try_tactic_adjustment(old_side, new_side), 159 Self::AnchorSizeFunction(anchor) => anchor.try_tactic_adjustment(old_side, new_side), 160 } 161 } 162 } 163 164 impl Position { 165 /// `50% 50%` 166 #[inline] 167 pub fn center() -> Self { 168 Self::new( 169 LengthPercentage::new_percent(Percentage(0.5)), 170 LengthPercentage::new_percent(Percentage(0.5)), 171 ) 172 } 173 174 /// `0% 0%` 175 #[inline] 176 pub fn zero() -> Self { 177 Self::new(LengthPercentage::zero(), LengthPercentage::zero()) 178 } 179 } 180 181 impl ToCss for Position { 182 fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result 183 where 184 W: Write, 185 { 186 self.horizontal.to_css(dest)?; 187 dest.write_char(' ')?; 188 self.vertical.to_css(dest) 189 } 190 } 191 192 impl GenericPositionComponent for LengthPercentage { 193 fn is_center(&self) -> bool { 194 match self.to_percentage() { 195 Some(Percentage(per)) => per == 0.5, 196 _ => false, 197 } 198 } 199 } 200 201 #[inline] 202 fn block_or_inline_to_inferred(keyword: PositionAreaKeyword) -> PositionAreaKeyword { 203 if matches!( 204 keyword.axis(), 205 PositionAreaAxis::Block | PositionAreaAxis::Inline 206 ) { 207 keyword.with_axis(PositionAreaAxis::Inferred) 208 } else { 209 keyword 210 } 211 } 212 213 #[inline] 214 fn inferred_to_block(keyword: PositionAreaKeyword) -> PositionAreaKeyword { 215 keyword.with_inferred_axis(PositionAreaAxis::Block) 216 } 217 218 #[inline] 219 fn inferred_to_inline(keyword: PositionAreaKeyword) -> PositionAreaKeyword { 220 keyword.with_inferred_axis(PositionAreaAxis::Inline) 221 } 222 223 // This exists because the spec currently says that further simplifications 224 // should be made to the computed value. That's confusing though, and probably 225 // all these simplifications should be wrapped up into the simplifications that 226 // we make to the specified value. I.e. all this should happen in 227 // PositionArea::parse_internal(). 228 // See also https://github.com/w3c/csswg-drafts/issues/12759 229 impl ToComputedValue for PositionArea { 230 type ComputedValue = Self; 231 232 fn to_computed_value(&self, _context: &Context) -> Self { 233 let mut computed = self.clone(); 234 let pair_type = self.get_type(); 235 if pair_type == PositionAreaType::Logical || pair_type == PositionAreaType::SelfLogical { 236 if computed.second != PositionAreaKeyword::None { 237 computed.first = block_or_inline_to_inferred(computed.first); 238 computed.second = block_or_inline_to_inferred(computed.second); 239 } 240 } else if pair_type == PositionAreaType::Inferred 241 || pair_type == PositionAreaType::SelfInferred 242 { 243 if computed.second == PositionAreaKeyword::SpanAll { 244 // We remove the superfluous span-all, converting the inferred 245 // keyword to a logical keyword to avoid ambiguity, per spec. 246 computed.first = inferred_to_block(computed.first); 247 computed.second = PositionAreaKeyword::None; 248 } else if computed.first == PositionAreaKeyword::SpanAll { 249 computed.first = computed.second; 250 computed.first = inferred_to_inline(computed.first); 251 computed.second = PositionAreaKeyword::None; 252 } 253 } 254 255 if computed.first == computed.second { 256 computed.second = PositionAreaKeyword::None; 257 } 258 computed 259 } 260 261 fn from_computed_value(computed: &Self) -> Self { 262 computed.clone() 263 } 264 } 265 266 /// A computed value for the `z-index` property. 267 pub type ZIndex = GenericZIndex<Integer>; 268 269 /// A computed value for the `aspect-ratio` property. 270 pub type AspectRatio = GenericAspectRatio<NonNegativeNumber>;