From 5f1f21893e267b23d5c4dbce6c03f62a555ce9a8 Mon Sep 17 00:00:00 2001 From: Nico Burns Date: Mon, 13 Oct 2025 16:31:41 +0100 Subject: [PATCH 1/5] Add static and fixed position styles Signed-off-by: Nico Burns --- src/style/mod.rs | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/src/style/mod.rs b/src/style/mod.rs index e44d06b74..00b78c4d2 100644 --- a/src/style/mod.rs +++ b/src/style/mod.rs @@ -254,6 +254,8 @@ impl Default for BoxGenerationMode { #[derive(Copy, Clone, PartialEq, Eq, Debug)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub enum Position { + /// Offets are not applied + Static, /// The offset is computed relative to the final position given by the layout algorithm. /// Offsets do not affect the position of any other items; they are effectively a correction factor applied at the end. Relative, @@ -263,6 +265,8 @@ pub enum Position { /// /// WARNING: to opt-out of layouting entirely, you must use [`Display::None`] instead on your [`Style`] object. Absolute, + /// The offset is computed relative to the viewport or transform boundary + Fixed, } impl Default for Position { @@ -271,6 +275,26 @@ impl Default for Position { } } +impl Position { + /// Whether the element has a non-static position + #[inline(always)] + pub fn is_positioned(self) -> bool { + !matches!(self, Self::Static) + } + + /// Whether the element is positioned out-of-flow (absolute or fixed position) + #[inline(always)] + pub fn is_out_of_flow(self) -> bool { + matches!(self, Self::Absolute | Self::Fixed) + } + + /// Whether the element is positioned in-flow (NOT absolute or fixed position) + #[inline(always)] + pub fn is_in_flow(self) -> bool { + !self.is_out_of_flow() + } +} + /// Specifies whether size styles for this node are assigned to the node's "content box" or "border box" /// /// - The "content box" is the node's inner size excluding padding, border and margin From f8741b3fb1f63c4c52f9bcf7cdca6db439894af6 Mon Sep 17 00:00:00 2001 From: Nico Burns Date: Mon, 13 Oct 2025 16:56:18 +0100 Subject: [PATCH 2/5] Use is_in_flow and is_out_of_flow helpers in compute functions Signed-off-by: Nico Burns --- src/compute/block.rs | 17 ++++++++--------- src/compute/flexbox.rs | 6 ++---- src/compute/grid/alignment.rs | 10 +++++----- src/compute/grid/mod.rs | 6 +++--- src/compute/leaf.rs | 4 ++-- 5 files changed, 20 insertions(+), 23 deletions(-) diff --git a/src/compute/block.rs b/src/compute/block.rs index 64792de93..be19cb637 100644 --- a/src/compute/block.rs +++ b/src/compute/block.rs @@ -174,13 +174,13 @@ fn compute_inner(tree: &mut impl LayoutBlockContainer, node_id: NodeId, inputs: start: vertical_margins_are_collapsible.start && !style.overflow().x.is_scroll_container() && !style.overflow().y.is_scroll_container() - && style.position() == Position::Relative + && style.position().is_in_flow() && padding.top == 0.0 && border.top == 0.0, end: vertical_margins_are_collapsible.end && !style.overflow().x.is_scroll_container() && !style.overflow().y.is_scroll_container() - && style.position() == Position::Relative + && style.position().is_in_flow() && padding.bottom == 0.0 && border.bottom == 0.0 && size.height.is_none(), @@ -188,7 +188,7 @@ fn compute_inner(tree: &mut impl LayoutBlockContainer, node_id: NodeId, inputs: let has_styles_preventing_being_collapsed_through = !style.is_block() || style.overflow().x.is_scroll_container() || style.overflow().y.is_scroll_container() - || style.position() == Position::Absolute + || style.position().is_out_of_flow() || padding.top > 0.0 || padding.bottom > 0.0 || border.top > 0.0 @@ -267,7 +267,7 @@ fn compute_inner(tree: &mut impl LayoutBlockContainer, node_id: NodeId, inputs: // 7. Determine whether this node can be collapsed through let all_in_flow_children_can_be_collapsed_through = - items.iter().all(|item| item.position == Position::Absolute || item.can_be_collapsed_through); + items.iter().all(|item| item.position.is_out_of_flow() || item.can_be_collapsed_through); let can_be_collapsed_through = !has_styles_preventing_being_collapsed_through && all_in_flow_children_can_be_collapsed_through; @@ -361,7 +361,7 @@ fn determine_content_based_container_width( let available_space = Size { width: available_width, height: AvailableSpace::MinContent }; let mut max_child_width = 0.0; - for item in items.iter().filter(|item| item.position != Position::Absolute) { + for item in items.iter().filter(|item| item.position.is_in_flow()) { let known_dimensions = item.size.maybe_clamp(item.min_size, item.max_size); let width = known_dimensions.width.unwrap_or_else(|| { @@ -413,7 +413,7 @@ fn perform_final_layout_on_in_flow_children( let mut active_collapsible_margin_set = CollapsibleMarginSet::ZERO; let mut is_collapsing_with_first_margin_set = true; for item in items.iter_mut() { - if item.position == Position::Absolute { + if item.position.is_out_of_flow() { item.static_position = Point { x: resolved_content_box_inset.left, y: y_offset_for_absolute } } else { let item_margin = item @@ -589,12 +589,11 @@ fn perform_absolute_layout_on_absolute_children( #[cfg_attr(not(feature = "content_size"), allow(unused_mut))] let mut absolute_content_size = Size::ZERO; - for item in items.iter().filter(|item| item.position == Position::Absolute) { + for item in items.iter().filter(|item| item.position.is_out_of_flow()) { let child_style = tree.get_block_child_style(item.node_id); // Skip items that are display:none or are not position:absolute - if child_style.box_generation_mode() == BoxGenerationMode::None || child_style.position() != Position::Absolute - { + if child_style.box_generation_mode() == BoxGenerationMode::None { continue; } diff --git a/src/compute/flexbox.rs b/src/compute/flexbox.rs index 2cf25ae4b..7c9670836 100644 --- a/src/compute/flexbox.rs +++ b/src/compute/flexbox.rs @@ -3,7 +3,6 @@ use crate::compute::common::alignment::compute_alignment_offset; use crate::geometry::{Line, Point, Rect, Size}; use crate::style::{ AlignContent, AlignItems, AlignSelf, AvailableSpace, FlexWrap, JustifyContent, LengthPercentageAuto, Overflow, - Position, }; use crate::style::{CoreStyle, FlexDirection, FlexboxContainerStyle, FlexboxItemStyle}; use crate::style_helpers::{TaffyMaxContent, TaffyMinContent}; @@ -502,7 +501,7 @@ fn generate_anonymous_flex_items( tree.child_ids(node) .enumerate() .map(|(index, child)| (index, child, tree.get_flexbox_child_style(child))) - .filter(|(_, _, style)| style.position() != Position::Absolute) + .filter(|(_, _, style)| style.position().is_in_flow()) .filter(|(_, _, style)| style.box_generation_mode() != BoxGenerationMode::None) .map(|(index, child, child_style)| { let aspect_ratio = child_style.aspect_ratio(); @@ -2075,8 +2074,7 @@ fn perform_absolute_layout_on_absolute_children( let child_style = tree.get_flexbox_child_style(child); // Skip items that are display:none or are not position:absolute - if child_style.box_generation_mode() == BoxGenerationMode::None || child_style.position() != Position::Absolute - { + if child_style.box_generation_mode() == BoxGenerationMode::None || child_style.position().is_in_flow() { continue; } diff --git a/src/compute/grid/alignment.rs b/src/compute/grid/alignment.rs index 9ec256adb..a1b2f9b22 100644 --- a/src/compute/grid/alignment.rs +++ b/src/compute/grid/alignment.rs @@ -146,7 +146,7 @@ pub(super) fn align_and_position_item( let width = inherent_size.width.or_else(|| { // Apply width derived from both the left and right properties of an absolutely // positioned element being set - if position == Position::Absolute { + if position.is_out_of_flow() { if let (Some(left), Some(right)) = (inset_horizontal.start, inset_horizontal.end) { return Some(f32_max(grid_area_minus_item_margins_size.width - left - right, 0.0)); } @@ -159,7 +159,7 @@ pub(super) fn align_and_position_item( if margin.left.is_some() && margin.right.is_some() && alignment_styles.horizontal == AlignSelf::Stretch - && position != Position::Absolute + && position.is_in_flow() { return Some(grid_area_minus_item_margins_size.width); } @@ -171,7 +171,7 @@ pub(super) fn align_and_position_item( let Size { width, height } = Size { width, height: inherent_size.height }.maybe_apply_aspect_ratio(aspect_ratio); let height = height.or_else(|| { - if position == Position::Absolute { + if position.is_out_of_flow() { if let (Some(top), Some(bottom)) = (inset_vertical.start, inset_vertical.end) { return Some(f32_max(grid_area_minus_item_margins_size.height - top - bottom, 0.0)); } @@ -184,7 +184,7 @@ pub(super) fn align_and_position_item( if margin.top.is_some() && margin.bottom.is_some() && alignment_styles.vertical == AlignSelf::Stretch - && position != Position::Absolute + && position.is_in_flow() { return Some(grid_area_minus_item_margins_size.height); } @@ -309,7 +309,7 @@ pub(super) fn align_item_within_area( AlignSelf::Stretch => resolved_margin.start, }; - let offset_within_area = if position == Position::Absolute { + let offset_within_area = if position.is_out_of_flow() { if let Some(start) = inset.start { start + non_auto_margin.start } else if let Some(end) = inset.end { diff --git a/src/compute/grid/mod.rs b/src/compute/grid/mod.rs index a9a3ab089..a5f7a2865 100644 --- a/src/compute/grid/mod.rs +++ b/src/compute/grid/mod.rs @@ -2,7 +2,7 @@ //! use crate::geometry::{AbsoluteAxis, AbstractAxis, InBothAbsAxis}; use crate::geometry::{Line, Point, Rect, Size}; -use crate::style::{AlignItems, AlignSelf, AvailableSpace, Overflow, Position}; +use crate::style::{AlignItems, AlignSelf, AvailableSpace, Overflow}; use crate::tree::{Layout, LayoutInput, LayoutOutput, LayoutPartialTreeExt, NodeId, RunMode, SizingMode}; use crate::util::debug::debug_log; use crate::util::sys::{f32_max, GridTrackVec, Vec}; @@ -202,7 +202,7 @@ pub fn compute_grid_layout( .enumerate() .map(|(index, child_node)| (index, child_node, tree.get_grid_child_style(child_node))) .filter(|(_, _, style)| { - style.box_generation_mode() != BoxGenerationMode::None && style.position() != Position::Absolute + style.box_generation_mode() != BoxGenerationMode::None && style.position().is_in_flow() }) }; place_grid_items( @@ -548,7 +548,7 @@ pub fn compute_grid_layout( } // Position absolutely positioned child - if child_style.position() == Position::Absolute { + if child_style.position().is_out_of_flow() { // Convert grid-col-{start/end} into Option's of indexes into the columns vector // The Option is None if the style property is Auto and an unresolvable Span let maybe_col_indexes = name_resolver diff --git a/src/compute/leaf.rs b/src/compute/leaf.rs index 4f1d979cd..33b93d40c 100644 --- a/src/compute/leaf.rs +++ b/src/compute/leaf.rs @@ -1,7 +1,7 @@ //! Computes size using styles and measure functions use crate::geometry::{Point, Size}; -use crate::style::{AvailableSpace, Overflow, Position}; +use crate::style::{AvailableSpace, Overflow}; use crate::tree::{CollapsibleMarginSet, RunMode}; use crate::tree::{LayoutInput, LayoutOutput, SizingMode}; use crate::util::debug::debug_log; @@ -76,7 +76,7 @@ where let has_styles_preventing_being_collapsed_through = !style.is_block() || style.overflow().x.is_scroll_container() || style.overflow().y.is_scroll_container() - || style.position() == Position::Absolute + || style.position().is_out_of_flow() || padding.top > 0.0 || padding.bottom > 0.0 || border.top > 0.0 From d5e4f12f6d0feb823a7d1aa829c22471c1f5018d Mon Sep 17 00:00:00 2001 From: Nico Burns Date: Mon, 13 Oct 2025 17:03:56 +0100 Subject: [PATCH 3/5] Don't apply inset to Position::Static nodes Signed-off-by: Nico Burns --- src/compute/block.rs | 10 ++++++++-- src/compute/flexbox.rs | 25 +++++++++++++++---------- 2 files changed, 23 insertions(+), 12 deletions(-) diff --git a/src/compute/block.rs b/src/compute/block.rs index be19cb637..fe159dd57 100644 --- a/src/compute/block.rs +++ b/src/compute/block.rs @@ -490,11 +490,17 @@ fn perform_final_layout_on_in_flow_children( x: resolved_content_box_inset.left, y: committed_y_offset + active_collapsible_margin_set.resolve(), }; + let mut location = Point { - x: resolved_content_box_inset.left + inset_offset.x + resolved_margin.left, - y: committed_y_offset + inset_offset.y + y_margin_offset, + x: resolved_content_box_inset.left + resolved_margin.left, + y: committed_y_offset + y_margin_offset, }; + if item.position == Position::Relative { + location.x += inset_offset.x; + location.y += inset_offset.y; + } + // Apply alignment let item_outer_width = item_layout.size.width + resolved_margin.horizontal_axis_sum(); if item_outer_width < container_inner_width { diff --git a/src/compute/flexbox.rs b/src/compute/flexbox.rs index 7c9670836..7ad8e48eb 100644 --- a/src/compute/flexbox.rs +++ b/src/compute/flexbox.rs @@ -12,7 +12,7 @@ use crate::util::debug::debug_log; use crate::util::sys::{f32_max, new_vec_with_capacity, Vec}; use crate::util::MaybeMath; use crate::util::{MaybeResolve, ResolveOrZero}; -use crate::{BoxGenerationMode, BoxSizing}; +use crate::{BoxGenerationMode, BoxSizing, Position}; use super::common::alignment::apply_alignment_fallback; #[cfg(feature = "content_size")] @@ -35,6 +35,9 @@ struct FlexItem { /// The cross-alignment of this item align_self: AlignSelf, + /// The position style of the item + position: Position, + /// The overflow style of the item overflow: Point, /// The width of the scrollbars (if it has any) @@ -517,6 +520,7 @@ fn generate_anonymous_flex_items( FlexItem { node: child, order: index as u32, + position: child_style.position(), size: child_style .size() .maybe_resolve(constants.node_inner_size, |val, basis| tree.calc(val, basis)) @@ -1902,16 +1906,17 @@ fn calculate_flex_item( .. } = layout_output; - let offset_main = *total_offset_main - + item.offset_main - + item.margin.main_start(direction) - + (item.inset.main_start(direction).or(item.inset.main_end(direction).map(|pos| -pos)).unwrap_or(0.0)); + let mut offset_main = *total_offset_main + item.offset_main + item.margin.main_start(direction); + + let mut offset_cross = + total_offset_cross + item.offset_cross + line_offset_cross + item.margin.cross_start(direction); - let offset_cross = total_offset_cross - + item.offset_cross - + line_offset_cross - + item.margin.cross_start(direction) - + (item.inset.cross_start(direction).or(item.inset.cross_end(direction).map(|pos| -pos)).unwrap_or(0.0)); + if item.position == Position::Relative { + offset_main += + item.inset.main_start(direction).or(item.inset.main_end(direction).map(|pos| -pos)).unwrap_or(0.0); + offset_cross += + item.inset.cross_start(direction).or(item.inset.cross_end(direction).map(|pos| -pos)).unwrap_or(0.0); + } if direction.is_row() { let baseline_offset_cross = total_offset_cross + item.offset_cross + item.margin.cross_start(direction); From 3acea02a5e3d99f63e53f62aac222f8f0b3bf0dc Mon Sep 17 00:00:00 2001 From: Nico Burns Date: Thu, 16 Oct 2025 14:02:34 +0100 Subject: [PATCH 4/5] Remove position:fixed This allows us to land position:static without completing the implementation of fixed position (which we will land later). --- src/style/mod.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/style/mod.rs b/src/style/mod.rs index 00b78c4d2..c9c0581d8 100644 --- a/src/style/mod.rs +++ b/src/style/mod.rs @@ -265,8 +265,6 @@ pub enum Position { /// /// WARNING: to opt-out of layouting entirely, you must use [`Display::None`] instead on your [`Style`] object. Absolute, - /// The offset is computed relative to the viewport or transform boundary - Fixed, } impl Default for Position { @@ -285,7 +283,7 @@ impl Position { /// Whether the element is positioned out-of-flow (absolute or fixed position) #[inline(always)] pub fn is_out_of_flow(self) -> bool { - matches!(self, Self::Absolute | Self::Fixed) + matches!(self, Self::Absolute) } /// Whether the element is positioned in-flow (NOT absolute or fixed position) From 072e6f45048e03196b18a50055cfc77bb0f49b97 Mon Sep 17 00:00:00 2001 From: Nico Burns Date: Mon, 20 Oct 2025 13:51:27 +0100 Subject: [PATCH 5/5] Fix yoga benchmarks Signed-off-by: Nico Burns --- benches/src/yoga_helpers.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/benches/src/yoga_helpers.rs b/benches/src/yoga_helpers.rs index f91280dee..83b82d5fa 100644 --- a/benches/src/yoga_helpers.rs +++ b/benches/src/yoga_helpers.rs @@ -202,6 +202,7 @@ fn apply_taffy_style(node: &mut yg::Node, style: &tf::Style) { // position node.set_position_type(match style.position { + tf::Position::Static => yg::PositionType::Static, tf::Position::Relative => yg::PositionType::Relative, tf::Position::Absolute => yg::PositionType::Absolute, });