From 50a1f7943bdd6cd49cd331a07c3605a577325ae3 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 e32018624..67dc31c21 100644 --- a/src/style/mod.rs +++ b/src/style/mod.rs @@ -275,6 +275,8 @@ impl Default for BoxGenerationMode { #[derive(Copy, Clone, PartialEq, Eq, Debug, Default)] #[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. #[default] @@ -285,6 +287,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, } #[cfg(feature = "parse")] @@ -293,6 +297,26 @@ crate::util::parse::impl_parse_for_keyword_enum!(Position, "absolute" => Absolute, ); +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 67bfcf32d458ef73b05638d73e7d4c86691ee3e6 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 | 19 +++++++++---------- src/compute/flexbox.rs | 6 ++---- src/compute/grid/alignment.rs | 12 ++++++------ src/compute/grid/mod.rs | 6 +++--- src/compute/leaf.rs | 4 ++-- 5 files changed, 22 insertions(+), 25 deletions(-) diff --git a/src/compute/block.rs b/src/compute/block.rs index 660b485e9..0b0617f58 100644 --- a/src/compute/block.rs +++ b/src/compute/block.rs @@ -389,12 +389,12 @@ fn compute_inner( let own_margins_collapse_with_children = Line { start: vertical_margins_are_collapsible.start && !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 && !is_scroll_container - && style.position() == Position::Relative + && style.position().is_in_flow() && padding.bottom == 0.0 && border.bottom == 0.0 && size.height.is_none(), @@ -402,7 +402,7 @@ fn compute_inner( let has_styles_preventing_being_collapsed_through = !style.is_block() || block_ctx.is_bfc_root() || 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 @@ -500,7 +500,7 @@ fn compute_inner( // 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; @@ -564,7 +564,7 @@ fn generate_item_list( let is_scroll_container = overflow.x.is_scroll_container() || overflow.y.is_scroll_container(); let is_in_same_bfc: bool = - is_block && !is_table && position != Position::Absolute && is_not_floated && !is_scroll_container; + is_block && !is_table && position.is_in_flow() && is_not_floated && !is_scroll_container; BlockItem { node_id: child_node_id, @@ -620,7 +620,7 @@ fn determine_content_based_container_width( let mut max_child_width = 0.0; #[cfg(feature = "float_layout")] let mut float_contribution = FloatIntrinsicWidthCalculator::new(available_width); - 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 item_x_margin_sum = item @@ -705,7 +705,7 @@ fn perform_final_layout_on_in_flow_children( let mut y_offset_for_float = resolved_content_box_inset.top; for item in items.iter_mut() { - if item.position == Position::Absolute { + if item.position.is_out_of_flow() { let x = match direction { Direction::Ltr => resolved_content_box_inset.left, Direction::Rtl => container_outer_width - resolved_content_box_inset.right, @@ -1085,12 +1085,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 eb56619a9..fa941cd82 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}; @@ -509,7 +508,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(); @@ -2151,8 +2150,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 c4b946fe5..ac5301dbe 100644 --- a/src/compute/grid/alignment.rs +++ b/src/compute/grid/alignment.rs @@ -154,7 +154,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)); } @@ -167,7 +167,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); } @@ -179,7 +179,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)); } @@ -192,7 +192,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); } @@ -208,7 +208,7 @@ pub(super) fn align_and_position_item( // Layout node drop(style); - let size = if position == Position::Absolute && (width.is_none() || height.is_none()) { + let size = if position.is_out_of_flow() && (width.is_none() || height.is_none()) { tree.measure_child_size_both( node, Size { width, height }, @@ -335,7 +335,7 @@ pub(super) fn align_item_within_area( AlignSelf::Center => (grid_area_size - resolved_size + resolved_margin.start - resolved_margin.end) / 2.0, }; - let offset_within_area = if position == Position::Absolute { + let offset_within_area = if position.is_out_of_flow() { match (inset.start, inset.end) { (Some(start), Some(end)) => { if direction.is_rtl() { diff --git a/src/compute/grid/mod.rs b/src/compute/grid/mod.rs index 8aa439171..81c432d03 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, f32_min, GridTrackVec, Vec}; @@ -206,7 +206,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( @@ -579,7 +579,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 493c5b3ad376c2e29c75ced392cf8a09d545c6a5 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 | 15 +++++++++------ src/compute/flexbox.rs | 26 +++++++++++++++++++------- 2 files changed, 28 insertions(+), 13 deletions(-) diff --git a/src/compute/block.rs b/src/compute/block.rs index 0b0617f58..5074d2476 100644 --- a/src/compute/block.rs +++ b/src/compute/block.rs @@ -956,31 +956,34 @@ fn perform_final_layout_on_in_flow_children( let mut location = if item.is_in_same_bfc { Point { x: match direction { - Direction::Ltr => resolved_content_box_inset.left + inset_offset.x + resolved_margin.left, + Direction::Ltr => resolved_content_box_inset.left + resolved_margin.left, Direction::Rtl => { container_outer_width - resolved_content_box_inset.right - final_size.width - resolved_margin.right - + inset_offset.x } }, - y: committed_y_offset.max(clear_pos) + y_margin_offset + inset_offset.y, + y: committed_y_offset.max(clear_pos) + y_margin_offset, } } else { // TODO: handle inset and margins Point { x: match direction { - Direction::Ltr => float_avoiding_position.x + resolved_margin.left + inset_offset.x, + Direction::Ltr => float_avoiding_position.x + resolved_margin.left, Direction::Rtl => { float_avoiding_position.x + float_avoiding_width - final_size.width - resolved_margin.right - + inset_offset.x } }, - y: float_avoiding_position.y + inset_offset.y, + y: float_avoiding_position.y, } }; + 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 fa941cd82..8801471a1 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, Direction}; +use crate::{BoxGenerationMode, BoxSizing, Direction, 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) @@ -524,6 +527,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)) @@ -1926,15 +1930,23 @@ fn calculate_flex_item( let is_rtl_row = direction.is_row() && layout_direction.is_rtl(); let is_rtl_column = direction.is_column() && layout_direction.is_rtl(); - let main_relative_inset = if is_rtl_row { - item.inset.main_end(direction).or(item.inset.main_start(direction).map(|pos| -pos)).unwrap_or(0.0) + let main_relative_inset = if item.position == Position::Relative { + if is_rtl_row { + item.inset.main_end(direction).or(item.inset.main_start(direction).map(|pos| -pos)).unwrap_or(0.0) + } else { + item.inset.main_start(direction).or(item.inset.main_end(direction).map(|pos| -pos)).unwrap_or(0.0) + } } else { - item.inset.main_start(direction).or(item.inset.main_end(direction).map(|pos| -pos)).unwrap_or(0.0) + 0.0 }; - let cross_relative_inset = if is_rtl_column { - item.inset.cross_end(direction).map(|pos| -pos).or(item.inset.cross_start(direction)).unwrap_or(0.0) + let cross_relative_inset = if item.position == Position::Relative { + if is_rtl_column { + item.inset.cross_end(direction).map(|pos| -pos).or(item.inset.cross_start(direction)).unwrap_or(0.0) + } else { + item.inset.cross_start(direction).or(item.inset.cross_end(direction).map(|pos| -pos)).unwrap_or(0.0) + } } else { - item.inset.cross_start(direction).or(item.inset.cross_end(direction).map(|pos| -pos)).unwrap_or(0.0) + 0.0 }; let effective_line_offset_cross = if is_rtl_column { 0.0 } else { line_offset_cross }; From 5583c43a5423692a5f07c5df937d944f14871f76 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 67dc31c21..c8b9abfb8 100644 --- a/src/style/mod.rs +++ b/src/style/mod.rs @@ -287,8 +287,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, } #[cfg(feature = "parse")] @@ -307,7 +305,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 5a546e1171d04cc29f3cd0a19f37de81a2176424 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, });