1 //! Final data structures that represent the high-level UI layout 2 use crate::geometry::{AbsoluteAxis, Line, Point, Rect, Size}; 3 use crate::style::AvailableSpace; 4 use crate::style_helpers::TaffyMaxContent; 5 use crate::util::sys::{f32_max, f32_min}; 6 7 /// Whether we are performing a full layout, or we merely need to size the node 8 #[derive(Copy, Clone, Debug, PartialEq, Eq)] 9 #[cfg_attr(feature = "serde", derive(Serialize))] 10 pub enum RunMode { 11 /// A full layout for this node and all children should be computed 12 PerformLayout, 13 /// The layout algorithm should be executed such that an accurate container size for the node can be determined. 14 /// Layout steps that aren't necessary for determining the container size of the current node can be skipped. 15 ComputeSize, 16 /// This node should have a null layout set as it has been hidden (i.e. using `Display::None`) 17 PerformHiddenLayout, 18 } 19 20 /// Whether styles should be taken into account when computing size 21 #[derive(Copy, Clone, Debug, PartialEq, Eq)] 22 #[cfg_attr(feature = "serde", derive(Serialize))] 23 pub enum SizingMode { 24 /// Only content contributions should be taken into account 25 ContentSize, 26 /// Inherent size styles should be taken into account in addition to content contributions 27 InherentSize, 28 } 29 30 /// A set of margins that are available for collapsing with for block layout's margin collapsing 31 #[derive(Copy, Clone, Debug, PartialEq)] 32 #[cfg_attr(feature = "serde", derive(Serialize))] 33 pub struct CollapsibleMarginSet { 34 /// The largest positive margin 35 positive: f32, 36 /// The smallest negative margin (with largest absolute value) 37 negative: f32, 38 } 39 40 impl CollapsibleMarginSet { 41 /// A default margin set with no collapsible margins 42 pub const ZERO: Self = Self { positive: 0.0, negative: 0.0 }; 43 44 /// Create a set from a single margin from_margin(margin: f32) -> Self45 pub fn from_margin(margin: f32) -> Self { 46 if margin >= 0.0 { 47 Self { positive: margin, negative: 0.0 } 48 } else { 49 Self { positive: 0.0, negative: margin } 50 } 51 } 52 53 /// Collapse a single margin with this set collapse_with_margin(mut self, margin: f32) -> Self54 pub fn collapse_with_margin(mut self, margin: f32) -> Self { 55 if margin >= 0.0 { 56 self.positive = f32_max(self.positive, margin); 57 } else { 58 self.negative = f32_min(self.negative, margin); 59 } 60 self 61 } 62 63 /// Collapse another margin set with this set collapse_with_set(mut self, other: CollapsibleMarginSet) -> Self64 pub fn collapse_with_set(mut self, other: CollapsibleMarginSet) -> Self { 65 self.positive = f32_max(self.positive, other.positive); 66 self.negative = f32_min(self.negative, other.negative); 67 self 68 } 69 70 /// Resolve the resultant margin from this set once all collapsible margins 71 /// have been collapsed into it resolve(&self) -> f3272 pub fn resolve(&self) -> f32 { 73 self.positive + self.negative 74 } 75 } 76 77 /// An axis that layout algorithms can be requested to compute a size for 78 #[derive(Debug, Copy, Clone, PartialEq, Eq)] 79 #[cfg_attr(feature = "serde", derive(Serialize))] 80 pub enum RequestedAxis { 81 /// The horizontal axis 82 Horizontal, 83 /// The vertical axis 84 Vertical, 85 /// Both axes 86 Both, 87 } 88 89 impl From<AbsoluteAxis> for RequestedAxis { from(value: AbsoluteAxis) -> Self90 fn from(value: AbsoluteAxis) -> Self { 91 match value { 92 AbsoluteAxis::Horizontal => RequestedAxis::Horizontal, 93 AbsoluteAxis::Vertical => RequestedAxis::Vertical, 94 } 95 } 96 } 97 impl TryFrom<RequestedAxis> for AbsoluteAxis { 98 type Error = (); try_from(value: RequestedAxis) -> Result<Self, Self::Error>99 fn try_from(value: RequestedAxis) -> Result<Self, Self::Error> { 100 match value { 101 RequestedAxis::Horizontal => Ok(AbsoluteAxis::Horizontal), 102 RequestedAxis::Vertical => Ok(AbsoluteAxis::Vertical), 103 RequestedAxis::Both => Err(()), 104 } 105 } 106 } 107 108 /// A struct containing the inputs constraints/hints for laying out a node, which are passed in by the parent 109 #[derive(Debug, Copy, Clone, PartialEq)] 110 #[cfg_attr(feature = "serde", derive(Serialize))] 111 pub struct LayoutInput { 112 /// Whether we only need to know the Node's size, or whe 113 pub run_mode: RunMode, 114 /// Whether a Node's style sizes should be taken into account or ignored 115 pub sizing_mode: SizingMode, 116 /// Which axis we need the size of 117 pub axis: RequestedAxis, 118 119 /// Known dimensions represent dimensions (width/height) which should be taken as fixed when performing layout. 120 /// For example, if known_dimensions.width is set to Some(WIDTH) then this means something like: 121 /// 122 /// "What would the height of this node be, assuming the width is WIDTH" 123 /// 124 /// Layout functions will be called with both known_dimensions set for final layout. Where the meaning is: 125 /// 126 /// "The exact size of this node is WIDTHxHEIGHT. Please lay out your children" 127 /// 128 pub known_dimensions: Size<Option<f32>>, 129 /// Parent size dimensions are intended to be used for percentage resolution. 130 pub parent_size: Size<Option<f32>>, 131 /// Available space represents an amount of space to layout into, and is used as a soft constraint 132 /// for the purpose of wrapping. 133 pub available_space: Size<AvailableSpace>, 134 /// Specific to CSS Block layout. Used for correctly computing margin collapsing. You probably want to set this to `Line::FALSE`. 135 pub vertical_margins_are_collapsible: Line<bool>, 136 } 137 138 impl LayoutInput { 139 /// A LayoutInput that can be used to request hidden layout 140 pub const HIDDEN: LayoutInput = LayoutInput { 141 // The important property for hidden layout 142 run_mode: RunMode::PerformHiddenLayout, 143 // The rest will be ignored 144 known_dimensions: Size::NONE, 145 parent_size: Size::NONE, 146 available_space: Size::MAX_CONTENT, 147 sizing_mode: SizingMode::InherentSize, 148 axis: RequestedAxis::Both, 149 vertical_margins_are_collapsible: Line::FALSE, 150 }; 151 } 152 153 /// A struct containing the result of laying a single node, which is returned up to the parent node 154 /// 155 /// A baseline is the line on which text sits. Your node likely has a baseline if it is a text node, or contains 156 /// children that may be text nodes. See <https://www.w3.org/TR/css-writing-modes-3/#intro-baselines> for details. 157 /// If your node does not have a baseline (or you are unsure how to compute it), then simply return `Point::NONE` 158 /// for the first_baselines field 159 #[derive(Debug, Copy, Clone, PartialEq)] 160 #[cfg_attr(feature = "serde", derive(Serialize))] 161 pub struct LayoutOutput { 162 /// The size of the node 163 pub size: Size<f32>, 164 #[cfg(feature = "content_size")] 165 /// The size of the content within the node 166 pub content_size: Size<f32>, 167 /// The first baseline of the node in each dimension, if any 168 pub first_baselines: Point<Option<f32>>, 169 /// Top margin that can be collapsed with. This is used for CSS block layout and can be set to 170 /// `CollapsibleMarginSet::ZERO` for other layout modes that don't support margin collapsing 171 pub top_margin: CollapsibleMarginSet, 172 /// Bottom margin that can be collapsed with. This is used for CSS block layout and can be set to 173 /// `CollapsibleMarginSet::ZERO` for other layout modes that don't support margin collapsing 174 pub bottom_margin: CollapsibleMarginSet, 175 /// Whether margins can be collapsed through this node. This is used for CSS block layout and can 176 /// be set to `false` for other layout modes that don't support margin collapsing 177 pub margins_can_collapse_through: bool, 178 } 179 180 impl LayoutOutput { 181 /// An all-zero `LayoutOutput` for hidden nodes 182 pub const HIDDEN: Self = Self { 183 size: Size::ZERO, 184 #[cfg(feature = "content_size")] 185 content_size: Size::ZERO, 186 first_baselines: Point::NONE, 187 top_margin: CollapsibleMarginSet::ZERO, 188 bottom_margin: CollapsibleMarginSet::ZERO, 189 margins_can_collapse_through: false, 190 }; 191 192 /// A blank layout output 193 pub const DEFAULT: Self = Self::HIDDEN; 194 195 /// Constructor to create a `LayoutOutput` from just the size and baselines from_sizes_and_baselines( size: Size<f32>, #[cfg_attr(not(feature = "content_size"), allow(unused_variables))] content_size: Size<f32>, first_baselines: Point<Option<f32>>, ) -> Self196 pub fn from_sizes_and_baselines( 197 size: Size<f32>, 198 #[cfg_attr(not(feature = "content_size"), allow(unused_variables))] content_size: Size<f32>, 199 first_baselines: Point<Option<f32>>, 200 ) -> Self { 201 Self { 202 size, 203 #[cfg(feature = "content_size")] 204 content_size, 205 first_baselines, 206 top_margin: CollapsibleMarginSet::ZERO, 207 bottom_margin: CollapsibleMarginSet::ZERO, 208 margins_can_collapse_through: false, 209 } 210 } 211 212 /// Construct a SizeBaselinesAndMargins from just the container and content sizes from_sizes(size: Size<f32>, content_size: Size<f32>) -> Self213 pub fn from_sizes(size: Size<f32>, content_size: Size<f32>) -> Self { 214 Self::from_sizes_and_baselines(size, content_size, Point::NONE) 215 } 216 217 /// Construct a SizeBaselinesAndMargins from just the container's size. from_outer_size(size: Size<f32>) -> Self218 pub fn from_outer_size(size: Size<f32>) -> Self { 219 Self::from_sizes(size, Size::zero()) 220 } 221 } 222 223 /// The final result of a layout algorithm for a single node. 224 #[derive(Debug, Copy, Clone, PartialEq)] 225 #[cfg_attr(feature = "serde", derive(Serialize))] 226 pub struct Layout { 227 /// The relative ordering of the node 228 /// 229 /// Nodes with a higher order should be rendered on top of those with a lower order. 230 /// This is effectively a topological sort of each tree. 231 pub order: u32, 232 /// The top-left corner of the node 233 pub location: Point<f32>, 234 /// The width and height of the node 235 pub size: Size<f32>, 236 #[cfg(feature = "content_size")] 237 /// The width and height of the content inside the node. This may be larger than the size of the node in the case of 238 /// overflowing content and is useful for computing a "scroll width/height" for scrollable nodes 239 pub content_size: Size<f32>, 240 /// The size of the scrollbars in each dimension. If there is no scrollbar then the size will be zero. 241 pub scrollbar_size: Size<f32>, 242 /// The size of the borders of the node 243 pub border: Rect<f32>, 244 /// The size of the padding of the node 245 pub padding: Rect<f32>, 246 /// The size of the margin of the node 247 pub margin: Rect<f32>, 248 } 249 250 impl Default for Layout { default() -> Self251 fn default() -> Self { 252 Self::new() 253 } 254 } 255 256 impl Layout { 257 /// Creates a new zero-[`Layout`]. 258 /// 259 /// The Zero-layout has size and location set to ZERO. 260 /// The `order` value of this layout is set to the minimum value of 0. 261 /// This means it should be rendered below all other [`Layout`]s. 262 #[must_use] new() -> Self263 pub const fn new() -> Self { 264 Self { 265 order: 0, 266 location: Point::ZERO, 267 size: Size::zero(), 268 #[cfg(feature = "content_size")] 269 content_size: Size::zero(), 270 scrollbar_size: Size::zero(), 271 border: Rect::zero(), 272 padding: Rect::zero(), 273 margin: Rect::zero(), 274 } 275 } 276 277 /// Creates a new zero-[`Layout`] with the supplied `order` value. 278 /// 279 /// Nodes with a higher order should be rendered on top of those with a lower order. 280 /// The Zero-layout has size and location set to ZERO. 281 #[must_use] with_order(order: u32) -> Self282 pub const fn with_order(order: u32) -> Self { 283 Self { 284 order, 285 size: Size::zero(), 286 location: Point::ZERO, 287 #[cfg(feature = "content_size")] 288 content_size: Size::zero(), 289 scrollbar_size: Size::zero(), 290 border: Rect::zero(), 291 padding: Rect::zero(), 292 margin: Rect::zero(), 293 } 294 } 295 296 /// Get the width of the node's content box 297 #[inline] content_box_width(&self) -> f32298 pub fn content_box_width(&self) -> f32 { 299 self.size.width - self.padding.left - self.padding.right - self.border.left - self.border.right 300 } 301 302 /// Get the height of the node's content box 303 #[inline] content_box_height(&self) -> f32304 pub fn content_box_height(&self) -> f32 { 305 self.size.height - self.padding.top - self.padding.bottom - self.border.top - self.border.bottom 306 } 307 308 /// Get the size of the node's content box 309 #[inline] content_box_size(&self) -> Size<f32>310 pub fn content_box_size(&self) -> Size<f32> { 311 Size { width: self.content_box_width(), height: self.content_box_height() } 312 } 313 314 /// Get x offset of the node's content box relative to it's parent's border box content_box_x(&self) -> f32315 pub fn content_box_x(&self) -> f32 { 316 self.location.x + self.border.left + self.padding.left 317 } 318 319 /// Get x offset of the node's content box relative to it's parent's border box content_box_y(&self) -> f32320 pub fn content_box_y(&self) -> f32 { 321 self.location.y + self.border.top + self.padding.top 322 } 323 } 324 325 #[cfg(feature = "content_size")] 326 impl Layout { 327 /// Return the scroll width of the node. 328 /// The scroll width is the difference between the width and the content width, floored at zero scroll_width(&self) -> f32329 pub fn scroll_width(&self) -> f32 { 330 f32_max( 331 0.0, 332 self.content_size.width + f32_min(self.scrollbar_size.width, self.size.width) - self.size.width 333 + self.border.right, 334 ) 335 } 336 337 /// Return the scroll height of the node. 338 /// The scroll height is the difference between the height and the content height, floored at zero scroll_height(&self) -> f32339 pub fn scroll_height(&self) -> f32 { 340 f32_max( 341 0.0, 342 self.content_size.height + f32_min(self.scrollbar_size.height, self.size.height) - self.size.height 343 + self.border.bottom, 344 ) 345 } 346 } 347 348 /// The additional information from layout algorithm 349 #[cfg(feature = "detailed_layout_info")] 350 #[derive(Debug, Clone, PartialEq)] 351 pub enum DetailedLayoutInfo { 352 /// Enum variant for [`DetailedGridInfo`](crate::compute::grid::DetailedGridInfo) 353 #[cfg(feature = "grid")] 354 Grid(Box<crate::compute::grid::DetailedGridInfo>), 355 /// For node that hasn't had any detailed information yet 356 None, 357 } 358