blob: 445c2ed71fab128763e976ae0206fb1757298056 [file] [log] [blame]
//! Final data structures that represent the high-level UI layout
use crate::geometry::{AbsoluteAxis, Line, Point, Rect, Size};
use crate::style::AvailableSpace;
use crate::style_helpers::TaffyMaxContent;
use crate::util::sys::{f32_max, f32_min};
/// Whether we are performing a full layout, or we merely need to size the node
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(Serialize))]
pub enum RunMode {
/// A full layout for this node and all children should be computed
PerformLayout,
/// The layout algorithm should be executed such that an accurate container size for the node can be determined.
/// Layout steps that aren't necessary for determining the container size of the current node can be skipped.
ComputeSize,
/// This node should have a null layout set as it has been hidden (i.e. using `Display::None`)
PerformHiddenLayout,
}
/// Whether styles should be taken into account when computing size
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(Serialize))]
pub enum SizingMode {
/// Only content contributions should be taken into account
ContentSize,
/// Inherent size styles should be taken into account in addition to content contributions
InherentSize,
}
/// A set of margins that are available for collapsing with for block layout's margin collapsing
#[derive(Copy, Clone, Debug, PartialEq)]
#[cfg_attr(feature = "serde", derive(Serialize))]
pub struct CollapsibleMarginSet {
/// The largest positive margin
positive: f32,
/// The smallest negative margin (with largest absolute value)
negative: f32,
}
impl CollapsibleMarginSet {
/// A default margin set with no collapsible margins
pub const ZERO: Self = Self { positive: 0.0, negative: 0.0 };
/// Create a set from a single margin
pub fn from_margin(margin: f32) -> Self {
if margin >= 0.0 {
Self { positive: margin, negative: 0.0 }
} else {
Self { positive: 0.0, negative: margin }
}
}
/// Collapse a single margin with this set
pub fn collapse_with_margin(mut self, margin: f32) -> Self {
if margin >= 0.0 {
self.positive = f32_max(self.positive, margin);
} else {
self.negative = f32_min(self.negative, margin);
}
self
}
/// Collapse another margin set with this set
pub fn collapse_with_set(mut self, other: CollapsibleMarginSet) -> Self {
self.positive = f32_max(self.positive, other.positive);
self.negative = f32_min(self.negative, other.negative);
self
}
/// Resolve the resultant margin from this set once all collapsible margins
/// have been collapsed into it
pub fn resolve(&self) -> f32 {
self.positive + self.negative
}
}
/// An axis that layout algorithms can be requested to compute a size for
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(Serialize))]
pub enum RequestedAxis {
/// The horizontal axis
Horizontal,
/// The vertical axis
Vertical,
/// Both axes
Both,
}
impl From<AbsoluteAxis> for RequestedAxis {
fn from(value: AbsoluteAxis) -> Self {
match value {
AbsoluteAxis::Horizontal => RequestedAxis::Horizontal,
AbsoluteAxis::Vertical => RequestedAxis::Vertical,
}
}
}
impl TryFrom<RequestedAxis> for AbsoluteAxis {
type Error = ();
fn try_from(value: RequestedAxis) -> Result<Self, Self::Error> {
match value {
RequestedAxis::Horizontal => Ok(AbsoluteAxis::Horizontal),
RequestedAxis::Vertical => Ok(AbsoluteAxis::Vertical),
RequestedAxis::Both => Err(()),
}
}
}
/// A struct containing the inputs constraints/hints for laying out a node, which are passed in by the parent
#[derive(Debug, Copy, Clone, PartialEq)]
#[cfg_attr(feature = "serde", derive(Serialize))]
pub struct LayoutInput {
/// Whether we only need to know the Node's size, or whe
pub run_mode: RunMode,
/// Whether a Node's style sizes should be taken into account or ignored
pub sizing_mode: SizingMode,
/// Which axis we need the size of
pub axis: RequestedAxis,
/// Known dimensions represent dimensions (width/height) which should be taken as fixed when performing layout.
/// For example, if known_dimensions.width is set to Some(WIDTH) then this means something like:
///
/// "What would the height of this node be, assuming the width is WIDTH"
///
/// Layout functions will be called with both known_dimensions set for final layout. Where the meaning is:
///
/// "The exact size of this node is WIDTHxHEIGHT. Please lay out your children"
///
pub known_dimensions: Size<Option<f32>>,
/// Parent size dimensions are intended to be used for percentage resolution.
pub parent_size: Size<Option<f32>>,
/// Available space represents an amount of space to layout into, and is used as a soft constraint
/// for the purpose of wrapping.
pub available_space: Size<AvailableSpace>,
/// Specific to CSS Block layout. Used for correctly computing margin collapsing. You probably want to set this to `Line::FALSE`.
pub vertical_margins_are_collapsible: Line<bool>,
}
impl LayoutInput {
/// A LayoutInput that can be used to request hidden layout
pub const HIDDEN: LayoutInput = LayoutInput {
// The important property for hidden layout
run_mode: RunMode::PerformHiddenLayout,
// The rest will be ignored
known_dimensions: Size::NONE,
parent_size: Size::NONE,
available_space: Size::MAX_CONTENT,
sizing_mode: SizingMode::InherentSize,
axis: RequestedAxis::Both,
vertical_margins_are_collapsible: Line::FALSE,
};
}
/// A struct containing the result of laying a single node, which is returned up to the parent node
///
/// A baseline is the line on which text sits. Your node likely has a baseline if it is a text node, or contains
/// children that may be text nodes. See <https://www.w3.org/TR/css-writing-modes-3/#intro-baselines> for details.
/// If your node does not have a baseline (or you are unsure how to compute it), then simply return `Point::NONE`
/// for the first_baselines field
#[derive(Debug, Copy, Clone, PartialEq)]
#[cfg_attr(feature = "serde", derive(Serialize))]
pub struct LayoutOutput {
/// The size of the node
pub size: Size<f32>,
#[cfg(feature = "content_size")]
/// The size of the content within the node
pub content_size: Size<f32>,
/// The first baseline of the node in each dimension, if any
pub first_baselines: Point<Option<f32>>,
/// Top margin that can be collapsed with. This is used for CSS block layout and can be set to
/// `CollapsibleMarginSet::ZERO` for other layout modes that don't support margin collapsing
pub top_margin: CollapsibleMarginSet,
/// Bottom margin that can be collapsed with. This is used for CSS block layout and can be set to
/// `CollapsibleMarginSet::ZERO` for other layout modes that don't support margin collapsing
pub bottom_margin: CollapsibleMarginSet,
/// Whether margins can be collapsed through this node. This is used for CSS block layout and can
/// be set to `false` for other layout modes that don't support margin collapsing
pub margins_can_collapse_through: bool,
}
impl LayoutOutput {
/// An all-zero `LayoutOutput` for hidden nodes
pub const HIDDEN: Self = Self {
size: Size::ZERO,
#[cfg(feature = "content_size")]
content_size: Size::ZERO,
first_baselines: Point::NONE,
top_margin: CollapsibleMarginSet::ZERO,
bottom_margin: CollapsibleMarginSet::ZERO,
margins_can_collapse_through: false,
};
/// A blank layout output
pub const DEFAULT: Self = Self::HIDDEN;
/// Constructor to create a `LayoutOutput` from just the size and baselines
pub fn 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>>,
) -> Self {
Self {
size,
#[cfg(feature = "content_size")]
content_size,
first_baselines,
top_margin: CollapsibleMarginSet::ZERO,
bottom_margin: CollapsibleMarginSet::ZERO,
margins_can_collapse_through: false,
}
}
/// Construct a SizeBaselinesAndMargins from just the container and content sizes
pub fn from_sizes(size: Size<f32>, content_size: Size<f32>) -> Self {
Self::from_sizes_and_baselines(size, content_size, Point::NONE)
}
/// Construct a SizeBaselinesAndMargins from just the container's size.
pub fn from_outer_size(size: Size<f32>) -> Self {
Self::from_sizes(size, Size::zero())
}
}
/// The final result of a layout algorithm for a single node.
#[derive(Debug, Copy, Clone, PartialEq)]
#[cfg_attr(feature = "serde", derive(Serialize))]
pub struct Layout {
/// The relative ordering of the node
///
/// Nodes with a higher order should be rendered on top of those with a lower order.
/// This is effectively a topological sort of each tree.
pub order: u32,
/// The top-left corner of the node
pub location: Point<f32>,
/// The width and height of the node
pub size: Size<f32>,
#[cfg(feature = "content_size")]
/// The width and height of the content inside the node. This may be larger than the size of the node in the case of
/// overflowing content and is useful for computing a "scroll width/height" for scrollable nodes
pub content_size: Size<f32>,
/// The size of the scrollbars in each dimension. If there is no scrollbar then the size will be zero.
pub scrollbar_size: Size<f32>,
/// The size of the borders of the node
pub border: Rect<f32>,
/// The size of the padding of the node
pub padding: Rect<f32>,
/// The size of the margin of the node
pub margin: Rect<f32>,
}
impl Default for Layout {
fn default() -> Self {
Self::new()
}
}
impl Layout {
/// Creates a new zero-[`Layout`].
///
/// The Zero-layout has size and location set to ZERO.
/// The `order` value of this layout is set to the minimum value of 0.
/// This means it should be rendered below all other [`Layout`]s.
#[must_use]
pub const fn new() -> Self {
Self {
order: 0,
location: Point::ZERO,
size: Size::zero(),
#[cfg(feature = "content_size")]
content_size: Size::zero(),
scrollbar_size: Size::zero(),
border: Rect::zero(),
padding: Rect::zero(),
margin: Rect::zero(),
}
}
/// Creates a new zero-[`Layout`] with the supplied `order` value.
///
/// Nodes with a higher order should be rendered on top of those with a lower order.
/// The Zero-layout has size and location set to ZERO.
#[must_use]
pub const fn with_order(order: u32) -> Self {
Self {
order,
size: Size::zero(),
location: Point::ZERO,
#[cfg(feature = "content_size")]
content_size: Size::zero(),
scrollbar_size: Size::zero(),
border: Rect::zero(),
padding: Rect::zero(),
margin: Rect::zero(),
}
}
/// Get the width of the node's content box
#[inline]
pub fn content_box_width(&self) -> f32 {
self.size.width - self.padding.left - self.padding.right - self.border.left - self.border.right
}
/// Get the height of the node's content box
#[inline]
pub fn content_box_height(&self) -> f32 {
self.size.height - self.padding.top - self.padding.bottom - self.border.top - self.border.bottom
}
/// Get the size of the node's content box
#[inline]
pub fn content_box_size(&self) -> Size<f32> {
Size { width: self.content_box_width(), height: self.content_box_height() }
}
/// Get x offset of the node's content box relative to it's parent's border box
pub fn content_box_x(&self) -> f32 {
self.location.x + self.border.left + self.padding.left
}
/// Get x offset of the node's content box relative to it's parent's border box
pub fn content_box_y(&self) -> f32 {
self.location.y + self.border.top + self.padding.top
}
}
#[cfg(feature = "content_size")]
impl Layout {
/// Return the scroll width of the node.
/// The scroll width is the difference between the width and the content width, floored at zero
pub fn scroll_width(&self) -> f32 {
f32_max(
0.0,
self.content_size.width + f32_min(self.scrollbar_size.width, self.size.width) - self.size.width
+ self.border.right,
)
}
/// Return the scroll height of the node.
/// The scroll height is the difference between the height and the content height, floored at zero
pub fn scroll_height(&self) -> f32 {
f32_max(
0.0,
self.content_size.height + f32_min(self.scrollbar_size.height, self.size.height) - self.size.height
+ self.border.bottom,
)
}
}
/// The additional information from layout algorithm
#[cfg(feature = "detailed_layout_info")]
#[derive(Debug, Clone, PartialEq)]
pub enum DetailedLayoutInfo {
/// Enum variant for [`DetailedGridInfo`](crate::compute::grid::DetailedGridInfo)
#[cfg(feature = "grid")]
Grid(Box<crate::compute::grid::DetailedGridInfo>),
/// For node that hasn't had any detailed information yet
None,
}