diff --git a/Cargo.toml b/Cargo.toml index ce446ef03..ee50819cb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -38,6 +38,7 @@ default = [ "flexbox", "grid", "block_layout", + "float_layout", "calc", "content_size", "detailed_layout_info", @@ -48,6 +49,8 @@ default = [ ## Enables the Block layout algorithm. See [`compute_block_layout`](crate::compute_block_layout). block_layout = [] +## Enables the Float layout algorithm. This is a sub-feature of block layout. +float_layout = [] ## Enables the Flexbox layout algorithm. See [`compute_flexbox_layout`](crate::compute_flexbox_layout). flexbox = [] ## Enables the CSS Grid layout algorithm. See [`compute_grid_layout`](crate::compute_grid_layout). diff --git a/scripts/gentest/src/main.rs b/scripts/gentest/src/main.rs index 04ade7b7e..ee7b15a1c 100644 --- a/scripts/gentest/src/main.rs +++ b/scripts/gentest/src/main.rs @@ -420,6 +420,25 @@ fn generate_node(ident: &str, node: &Value) -> TokenStream { _ => quote!(), }; + let float = match style["cssFloat"] { + Value::String(ref value) => match value.as_ref() { + "left" => quote!(float: taffy::style::Float::Left,), + "right" => quote!(float: taffy::style::Float::Right,), + _ => quote!(), + }, + _ => quote!(), + }; + + let clear = match style["clear"] { + Value::String(ref value) => match value.as_ref() { + "left" => quote!(clear: taffy::style::Clear::Left,), + "right" => quote!(clear: taffy::style::Clear::Right,), + "both" => quote!(clear: taffy::style::Clear::Right,), + _ => quote!(), + }, + _ => quote!(), + }; + let flex_direction = match style["flexDirection"] { Value::String(ref value) => match value.as_ref() { "row-reverse" => quote!(flex_direction: taffy::style::FlexDirection::RowReverse,), @@ -646,6 +665,8 @@ fn generate_node(ident: &str, node: &Value) -> TokenStream { #box_sizing #direction #position + #float + #clear #text_align #flex_direction #flex_wrap diff --git a/scripts/gentest/test_helper.js b/scripts/gentest/test_helper.js index 09a53e897..6480f90d9 100644 --- a/scripts/gentest/test_helper.js +++ b/scripts/gentest/test_helper.js @@ -212,6 +212,9 @@ function describeElement(e) { writingMode: parseEnum(e.style.writingMode), + cssFloat: parseEnum(e.style.cssFloat), + clear: parseEnum(e.style.clear), + textAlign: parseEnum(e.style.textAlign), flexDirection: parseEnum(e.style.flexDirection), diff --git a/src/compute/block.rs b/src/compute/block.rs index 64792de93..9e5c9d929 100644 --- a/src/compute/block.rs +++ b/src/compute/block.rs @@ -9,7 +9,173 @@ use crate::util::sys::f32_max; use crate::util::sys::Vec; use crate::util::MaybeMath; use crate::util::{MaybeResolve, ResolveOrZero}; -use crate::{BlockContainerStyle, BlockItemStyle, BoxGenerationMode, BoxSizing, LayoutBlockContainer, TextAlign}; +use crate::{ + BlockContainerStyle, BlockItemStyle, BoxGenerationMode, BoxSizing, LayoutBlockContainer, RequestedAxis, TextAlign, +}; + +#[cfg(feature = "float_layout")] +use super::float::FloatIntrinsicWidthCalculator; +#[cfg(feature = "float_layout")] +use super::{ContentSlot, FloatContext}; +#[cfg(feature = "float_layout")] +use crate::{Clear, Float, FloatDirection}; + +/// Context for positioning Block and Float boxes within a Block Formatting Context +pub struct BlockFormattingContext { + /// The float positioning context that handles positioning floats within this Block Formatting Context + #[cfg(feature = "float_layout")] + float_context: FloatContext, +} + +impl Default for BlockFormattingContext { + fn default() -> Self { + Self { + #[cfg(feature = "float_layout")] + float_context: FloatContext::new(), + } + } +} + +impl BlockFormattingContext { + /// Create a new `BlockFormattingContext` with the specified width constraint + pub fn new() -> Self { + Default::default() + } + + /// Create an initial `BlockContext` for this `BlockFormattingContext` + pub fn root_block_context(&mut self) -> BlockContext<'_> { + BlockContext { + bfc: self, + y_offset: 0.0, + insets: [0.0, 0.0], + content_box_insets: [0.0, 0.0], + float_content_contribution: 0.0, + is_root: true, + } + } +} + +/// Context for each individual Block within a Block Formatting Context +/// +/// Contains a mutable reference to the BlockFormattingContext + block-specific data +pub struct BlockContext<'bfc> { + /// A mutable reference to the root BlockFormatttingContext that this BlockContext belongs to + bfc: &'bfc mut BlockFormattingContext, + /// The y-offset of the border-top of the block node, relative to the to the border-top of the + /// root node of the Block Formatting Context it belongs to. + y_offset: f32, + /// The x-inset of the border-box in from each side of the block node, relative to the root node of the Block Formatting Context it belongs to. + insets: [f32; 2], + /// The x-insets of the content box + content_box_insets: [f32; 2], + /// The height that floats take up in the element + float_content_contribution: f32, + /// Whether the node is the root of the Block Formatting Context is belongs to. + is_root: bool, +} + +impl BlockContext<'_> { + /// Create a sub-`BlockContext` for a child block node + pub fn sub_context(&mut self, additional_y_offset: f32, insets: [f32; 2]) -> BlockContext<'_> { + let insets = [self.insets[0] + insets[0], self.insets[1] + insets[1]]; + BlockContext { + bfc: self.bfc, + y_offset: self.y_offset + additional_y_offset, + insets, + content_box_insets: insets, + float_content_contribution: 0.0, + is_root: false, + } + } + + /// Returns whether this block is the root block of it's Block Formatting Context + pub fn is_bfc_root(&self) -> bool { + self.is_root + } +} + +#[cfg(feature = "float_layout")] +impl BlockContext<'_> { + /// Set the width of the overall Block Formatting Context. This is used to resolve positions + /// that are relative to the right of the context such as right-floated boxes. + /// + /// Sub-blocks within a Block Formatting Context should use the `Self::sub_context` method to create + /// a sub-`BlockContext` with `insets` instead. + pub fn set_width(&mut self, available_width: f32) { + self.bfc.float_context.set_width(available_width); + } + + /// Set the x-axis content-box insets of the `BlockContext`. These are the difference between the border-box + /// and the content-box of the box (padding + border + scrollbar_gutter). + pub fn apply_content_box_inset(&mut self, content_box_x_insets: [f32; 2]) { + self.content_box_insets[0] = self.insets[0] + content_box_x_insets[0]; + self.content_box_insets[1] = self.insets[1] + content_box_x_insets[1]; + } + + /// Whether the float context contains any floats + #[inline(always)] + pub fn has_floats(&self) -> bool { + self.bfc.float_context.has_floats() + } + + /// Whether the float context contains any floats that extend to or below min_y + #[inline(always)] + pub fn has_active_floats(&self, min_y: f32) -> bool { + self.bfc.float_context.has_active_floats(min_y + self.y_offset) + } + + /// Position a floated box with the context + pub fn place_floated_box( + &mut self, + floated_box: Size, + min_y: f32, + direction: FloatDirection, + clear: Clear, + ) -> Point { + let mut pos = self.bfc.float_context.place_floated_box( + floated_box, + min_y + self.y_offset, + self.content_box_insets, + direction, + clear, + ); + pos.y -= self.y_offset; + pos.x -= self.insets[0]; + + self.float_content_contribution = self.float_content_contribution.max(pos.y + floated_box.height); + + pos + } + + /// Search a space suitable for laying out non-floated content into + pub fn find_content_slot(&self, min_y: f32, clear: Clear, after: Option) -> ContentSlot { + let mut slot = + self.bfc.float_context.find_content_slot(min_y + self.y_offset, self.content_box_insets, clear, after); + slot.y -= self.y_offset; + slot.x -= self.insets[0]; + slot + } + + /// Update the height that descendent floats with the height that floats consume + /// within a particular child + fn add_child_floated_content_height_contribution(&mut self, child_contribution: f32) { + self.float_content_contribution = self.float_content_contribution.max(child_contribution); + } + + /// Returns the height that descendent floats consume + pub fn floated_content_height_contribution(&self) -> f32 { + self.float_content_contribution + } +} + +#[cfg(not(feature = "float_layout"))] +impl BlockContext<'_> { + #[inline(always)] + /// Returns the height that descendent floats consume (always 0.0 when the float feature is disabled) + fn float_content_contribution(&self) -> f32 { + 0.0 + } +} #[cfg(feature = "content_size")] use super::common::content_size::compute_content_size_contribution; @@ -26,6 +192,16 @@ struct BlockItem { /// Items that are tables don't have stretch sizing applied to them is_table: bool, + /// Whether the child is a non-independent block or inline node + is_in_same_bfc: bool, + + #[cfg(feature = "float_layout")] + /// The `float` style of the node + float: Float, + #[cfg(feature = "float_layout")] + /// The `clear` style of the node + clear: Clear, + /// The base size of this item size: Size>, /// The minimum allowable size of this item @@ -65,11 +241,14 @@ pub fn compute_block_layout( tree: &mut impl LayoutBlockContainer, node_id: NodeId, inputs: LayoutInput, + block_ctx: Option<&mut BlockContext<'_>>, ) -> LayoutOutput { let LayoutInput { known_dimensions, parent_size, run_mode, .. } = inputs; let style = tree.get_block_container_style(node_id); // Pull these out earlier to avoid borrowing issues + let overflow = style.overflow(); + let is_scroll_container = overflow.x.is_scroll_container() || overflow.y.is_scroll_container(); let aspect_ratio = style.aspect_ratio(); let padding = style.padding().resolve_or_zero(parent_size.width, |val, basis| tree.calc(val, basis)); let border = style.border().resolve_or_zero(parent_size.width, |val, basis| tree.calc(val, basis)); @@ -117,12 +296,35 @@ pub fn compute_block_layout( } } + // Unwrap the block formatting context if one was passed, or else create a new one debug_log!("BLOCK"); - compute_inner(tree, node_id, LayoutInput { known_dimensions: styled_based_known_dimensions, ..inputs }) + match block_ctx { + Some(inherited_bfc) if !is_scroll_container => compute_inner( + tree, + node_id, + LayoutInput { known_dimensions: styled_based_known_dimensions, ..inputs }, + inherited_bfc, + ), + _ => { + let mut root_bfc = BlockFormattingContext::new(); + let mut root_ctx = root_bfc.root_block_context(); + compute_inner( + tree, + node_id, + LayoutInput { known_dimensions: styled_based_known_dimensions, ..inputs }, + &mut root_ctx, + ) + } + } } /// Computes the layout of [`LayoutBlockContainer`] according to the block layout algorithm -fn compute_inner(tree: &mut impl LayoutBlockContainer, node_id: NodeId, inputs: LayoutInput) -> LayoutOutput { +fn compute_inner( + tree: &mut impl LayoutBlockContainer, + node_id: NodeId, + inputs: LayoutInput, + #[allow(unused_mut)] mut block_ctx: &mut BlockContext<'_>, +) -> LayoutOutput { let LayoutInput { known_dimensions, parent_size, available_space, run_mode, vertical_margins_are_collapsible, .. } = inputs; @@ -151,6 +353,10 @@ fn compute_inner(tree: &mut impl LayoutBlockContainer, node_id: NodeId, inputs: let content_box_inset = padding_border + scrollbar_gutter; let container_content_box_size = known_dimensions.maybe_sub(content_box_inset.sum_axes()); + // Apply content box inset + #[cfg(feature = "float_layout")] + block_ctx.apply_content_box_inset([content_box_inset.left, content_box_inset.right]); + let box_sizing_adjustment = if style.box_sizing() == BoxSizing::ContentBox { padding_border_size } else { Size::ZERO }; let size = style @@ -169,25 +375,26 @@ fn compute_inner(tree: &mut impl LayoutBlockContainer, node_id: NodeId, inputs: .maybe_apply_aspect_ratio(aspect_ratio) .maybe_add(box_sizing_adjustment); + let overflow = style.overflow(); + let is_scroll_container = overflow.x.is_scroll_container() || overflow.y.is_scroll_container(); + // Determine margin collapsing behaviour let own_margins_collapse_with_children = Line { start: vertical_margins_are_collapsible.start - && !style.overflow().x.is_scroll_container() - && !style.overflow().y.is_scroll_container() + && !is_scroll_container && style.position() == Position::Relative && 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() + && !is_scroll_container && style.position() == Position::Relative && padding.bottom == 0.0 && border.bottom == 0.0 && size.height.is_none(), }; let has_styles_preventing_being_collapsed_through = !style.is_block() - || style.overflow().x.is_scroll_container() - || style.overflow().y.is_scroll_container() + || block_ctx.is_bfc_root() + || is_scroll_container || style.position() == Position::Absolute || padding.top > 0.0 || padding.bottom > 0.0 @@ -220,7 +427,7 @@ fn compute_inner(tree: &mut impl LayoutBlockContainer, node_id: NodeId, inputs: let resolved_padding = raw_padding.resolve_or_zero(Some(container_outer_width), |val, basis| tree.calc(val, basis)); let resolved_border = raw_border.resolve_or_zero(Some(container_outer_width), |val, basis| tree.calc(val, basis)); let resolved_content_box_inset = resolved_padding + resolved_border + scrollbar_gutter; - let (inflow_content_size, intrinsic_outer_height, first_child_top_margin_set, last_child_bottom_margin_set) = + let (inflow_content_size, mut intrinsic_outer_height, first_child_top_margin_set, last_child_bottom_margin_set) = perform_final_layout_on_in_flow_children( tree, &mut items, @@ -229,7 +436,15 @@ fn compute_inner(tree: &mut impl LayoutBlockContainer, node_id: NodeId, inputs: resolved_content_box_inset, text_align, own_margins_collapse_with_children, + block_ctx, ); + + // Root BFCs contain floats + #[cfg(feature = "float_layout")] + if block_ctx.is_bfc_root() || is_scroll_container { + intrinsic_outer_height = intrinsic_outer_height.max(block_ctx.floated_content_height_contribution()); + } + let container_outer_height = known_dimensions .height .unwrap_or(intrinsic_outer_height.maybe_clamp(min_size.height, max_size.height)) @@ -314,10 +529,34 @@ fn generate_item_list( let pb_sum = (padding + border).sum_axes(); let box_sizing_adjustment = if child_style.box_sizing() == BoxSizing::ContentBox { pb_sum } else { Size::ZERO }; + + let position = child_style.position(); + let overflow = child_style.overflow(); + + #[cfg(feature = "float_layout")] + let float = child_style.float(); + #[cfg(feature = "float_layout")] + let is_not_floated = float == Float::None; + + #[cfg(not(feature = "float_layout"))] + let is_not_floated = true; + + let is_block = child_style.is_block(); + let is_table = child_style.is_table(); + 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; + BlockItem { node_id: child_node_id, order: order as u32, - is_table: child_style.is_table(), + is_table, + is_in_same_bfc, + #[cfg(feature = "float_layout")] + float, + #[cfg(feature = "float_layout")] + clear: child_style.clear(), size: child_style .size() .maybe_resolve(node_inner_size, |val, basis| tree.calc(val, basis)) @@ -333,9 +572,9 @@ fn generate_item_list( .maybe_resolve(node_inner_size, |val, basis| tree.calc(val, basis)) .maybe_apply_aspect_ratio(aspect_ratio) .maybe_add(box_sizing_adjustment), - overflow: child_style.overflow(), + overflow, scrollbar_width: child_style.scrollbar_width(), - position: child_style.position(), + position, inset: child_style.inset(), margin: child_style.margin(), padding, @@ -361,14 +600,16 @@ fn determine_content_based_container_width( let available_space = Size { width: available_width, height: AvailableSpace::MinContent }; 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) { let known_dimensions = item.size.maybe_clamp(item.min_size, item.max_size); + let item_x_margin_sum = item + .margin + .resolve_or_zero(available_space.width.into_option(), |val, basis| tree.calc(val, basis)) + .horizontal_axis_sum(); let width = known_dimensions.width.unwrap_or_else(|| { - let item_x_margin_sum = item - .margin - .resolve_or_zero(available_space.width.into_option(), |val, basis| tree.calc(val, basis)) - .horizontal_axis_sum(); let size_and_baselines = tree.perform_child_layout( item.node_id, known_dimensions, @@ -378,33 +619,54 @@ fn determine_content_based_container_width( Line::TRUE, ); - size_and_baselines.size.width + item_x_margin_sum + size_and_baselines.size.width }); - let width = f32_max(width, item.padding_border_sum.width); + + let width = f32_max(width, item.padding_border_sum.width) + item_x_margin_sum; + + #[cfg(feature = "float_layout")] + if let Some(direction) = item.float.float_direction() { + float_contribution.add_float(width, direction, item.clear); + continue; + } max_child_width = f32_max(max_child_width, width); } + #[cfg(feature = "float_layout")] + { + max_child_width = max_child_width.max(float_contribution.result()); + } + max_child_width } /// Compute each child's final size and position #[inline] +#[allow(clippy::too_many_arguments)] fn perform_final_layout_on_in_flow_children( - tree: &mut impl LayoutPartialTree, + tree: &mut impl LayoutBlockContainer, items: &mut [BlockItem], container_outer_width: f32, content_box_inset: Rect, resolved_content_box_inset: Rect, text_align: TextAlign, own_margins_collapse_with_children: Line, + block_ctx: &mut BlockContext<'_>, ) -> (Size, f32, CollapsibleMarginSet, CollapsibleMarginSet) { // Resolve container_inner_width for sizing child nodes using initial content_box_inset - let container_inner_width = container_outer_width - content_box_inset.horizontal_axis_sum(); + let container_inner_width = container_outer_width - resolved_content_box_inset.horizontal_axis_sum(); let parent_size = Size { width: Some(container_outer_width), height: None }; let available_space = Size { width: AvailableSpace::Definite(container_inner_width), height: AvailableSpace::MinContent }; + // TODO: handle nested blocks with different widths + #[cfg(feature = "float_layout")] + if block_ctx.is_bfc_root() { + block_ctx.set_width(container_outer_width); + block_ctx.apply_content_box_inset([resolved_content_box_inset.left, resolved_content_box_inset.right]); + } + #[cfg_attr(not(feature = "content_size"), allow(unused_mut))] let mut inflow_content_size = Size::ZERO; let mut committed_y_offset = resolved_content_box_inset.top; @@ -412,6 +674,14 @@ fn perform_final_layout_on_in_flow_children( let mut first_child_top_margin_set = CollapsibleMarginSet::ZERO; let mut active_collapsible_margin_set = CollapsibleMarginSet::ZERO; let mut is_collapsing_with_first_margin_set = true; + + #[cfg(feature = "float_layout")] + let mut has_active_floats = block_ctx.has_active_floats(committed_y_offset); + #[cfg(not(feature = "float_layout"))] + let has_active_floats = false; + #[cfg(feature = "float_layout")] + let mut y_offset_for_float = resolved_content_box_inset.top; + for item in items.iter_mut() { if item.position == Position::Absolute { item.static_position = Point { x: resolved_content_box_inset.left, y: y_offset_for_absolute } @@ -421,6 +691,112 @@ fn perform_final_layout_on_in_flow_children( .map(|margin| margin.resolve_to_option(container_outer_width, |val, basis| tree.calc(val, basis))); let item_non_auto_margin = item_margin.map(|m| m.unwrap_or(0.0)); let item_non_auto_x_margin_sum = item_non_auto_margin.horizontal_axis_sum(); + + let scrollbar_size = Size { + width: if item.overflow.y == Overflow::Scroll { item.scrollbar_width } else { 0.0 }, + height: if item.overflow.x == Overflow::Scroll { item.scrollbar_width } else { 0.0 }, + }; + + // Handle floated boxes + #[cfg(feature = "float_layout")] + if let Some(float_direction) = item.float.float_direction() { + has_active_floats = true; + + let item_layout = tree.perform_child_layout( + item.node_id, + Size::NONE, + parent_size, + // available_space, + Size::MAX_CONTENT, + SizingMode::InherentSize, + Line::TRUE, + ); + let margin_box = item_layout.size + item_non_auto_margin.sum_axes(); + + let mut location = + block_ctx.place_floated_box(margin_box, y_offset_for_float, float_direction, item.clear); + + // Ensure that content that appears after a float does not get positioned before/above the float + // + // FIXME: this isn't quite right, because a second float at the same location + // shouldn't cause content to push down to it's level + // committed_y_offset = committed_y_offset.max(location.y); + // y_offset_for_absolute = y_offset_for_absolute.max(location.y); + // y_offset_for_float = y_offset_for_float.max(location.y); + + // Convert the margin-box location returned by float placement into a border-box location + // for the output Layout + location.y += item_non_auto_margin.top; + location.x += item_non_auto_margin.left; + + // println!("BLOCK FLOATED BOX ({:?}) {:?}", item.node_id, float_direction); + // println!("w:{} h:{} x:{}, y:{}", margin_box.width, margin_box.height, location.x, location.y); + + tree.set_unrounded_layout( + item.node_id, + &Layout { + order: item.order, + size: item_layout.size, + #[cfg(feature = "content_size")] + content_size: item_layout.content_size, + scrollbar_size, + location, + padding: item.padding, + border: item.border, + margin: item_non_auto_margin, + }, + ); + + #[cfg(feature = "content_size")] + { + // TODO: Should content size of floated boxes count as "inflow_content_size" + // or should it be counted separately? + inflow_content_size = inflow_content_size.f32_max(compute_content_size_contribution( + location, + item_layout.size, + item_layout.content_size, + item.overflow, + )); + } + + continue; + } + + // Handle non-floated boxes + + let mut y_margin_offset: f32 = 0.0; + + let (stretch_width, float_avoiding_position) = if item.is_in_same_bfc { + let stretch_width = container_inner_width - item_non_auto_x_margin_sum; + let position = Point { x: 0.0, y: 0.0 }; + + (stretch_width, position) + } else { + 'block: { + // Set y_margin_offset (different bfc child) + if !is_collapsing_with_first_margin_set || !own_margins_collapse_with_children.start { + y_margin_offset = + active_collapsible_margin_set.collapse_with_margin(item_non_auto_margin.top).resolve(); + }; + let min_y = committed_y_offset + y_margin_offset; + + #[cfg(feature = "float_layout")] + if has_active_floats { + let slot = block_ctx.find_content_slot(min_y, item.clear, None); + has_active_floats = slot.segment_id.is_some(); + let stretch_width = slot.width - item_non_auto_x_margin_sum; + break 'block (stretch_width, Point { x: slot.x, y: slot.y }); + } + + if !has_active_floats { + let stretch_width = container_inner_width - item_non_auto_x_margin_sum; + break 'block (stretch_width, Point { x: 0.0, y: min_y }); + } + + unreachable!("One of the above cases will always be hit"); + } + }; + let known_dimensions = if item.is_table { Size::NONE } else { @@ -428,23 +804,49 @@ fn perform_final_layout_on_in_flow_children( .map_width(|width| { // TODO: Allow stretch-sizing to be conditional, as there are exceptions. // e.g. Table children of blocks do not stretch fit - Some( - width - .unwrap_or(container_inner_width - item_non_auto_x_margin_sum) - .maybe_clamp(item.min_size.width, item.max_size.width), - ) + Some(width.unwrap_or(stretch_width).maybe_clamp(item.min_size.width, item.max_size.width)) }) .maybe_clamp(item.min_size, item.max_size) }; - let item_layout = tree.perform_child_layout( - item.node_id, + // + + let inputs = LayoutInput { + run_mode: RunMode::PerformLayout, + sizing_mode: SizingMode::InherentSize, + axis: RequestedAxis::Both, known_dimensions, parent_size, - available_space.map_width(|w| w.maybe_sub(item_non_auto_x_margin_sum)), - SizingMode::InherentSize, - Line::TRUE, - ); + available_space: available_space.map_width(|w| w.maybe_sub(item_non_auto_x_margin_sum)), + vertical_margins_are_collapsible: if item.is_in_same_bfc { Line::TRUE } else { Line::FALSE }, + }; + + let item_layout = if item.is_in_same_bfc { + let width = known_dimensions + .width + .expect("Same-bfc child will always have defined width due to stretch sizing"); + + // TODO: account for auto margins + let inset_left = item_non_auto_margin.left + content_box_inset.left; + let inset_right = container_outer_width - width - inset_left; + let insets = [inset_left, inset_right]; + + // Compute child layout + let mut child_block_ctx = + block_ctx.sub_context(y_offset_for_absolute + item_non_auto_margin.top, insets); + let output = tree.compute_block_child_layout(item.node_id, inputs, Some(&mut child_block_ctx)); + + // Extract float contribution from child block context + #[cfg(feature = "float_layout")] + { + let child_contribution = child_block_ctx.floated_content_height_contribution(); + block_ctx.add_child_floated_content_height_contribution(y_offset_for_absolute + child_contribution); + } + + output + } else { + tree.compute_child_layout(item.node_id, inputs) + }; let final_size = item_layout.size; let top_margin_set = item_layout.top_margin.collapse_with_margin(item_margin.top.unwrap_or(0.0)); @@ -453,7 +855,7 @@ fn perform_final_layout_on_in_flow_children( // Expand auto margins to fill available space // Note: Vertical auto-margins for relatively positioned block items simply resolve to 0. // See: https://www.w3.org/TR/CSS21/visudet.html#abs-non-replaced-width - let free_x_space = f32_max(0.0, container_inner_width - final_size.width - item_non_auto_x_margin_sum); + let free_x_space = f32_max(0.0, stretch_width - final_size.width); let x_axis_auto_margin_size = { let auto_margin_count = item_margin.left.is_none() as u8 + item_margin.right.is_none() as u8; if auto_margin_count > 0 { @@ -478,26 +880,43 @@ fn perform_final_layout_on_in_flow_children( y: inset.top.or(inset.bottom.map(|x| -x)).unwrap_or(0.0), }; - let y_margin_offset = if is_collapsing_with_first_margin_set && own_margins_collapse_with_children.start { - 0.0 - } else { - active_collapsible_margin_set.collapse_with_margin(resolved_margin.top).resolve() + // Set y_margin_offset (same bfc child) + if item.is_in_same_bfc + && (!is_collapsing_with_first_margin_set || !own_margins_collapse_with_children.start) + { + y_margin_offset = active_collapsible_margin_set.collapse_with_margin(resolved_margin.top).resolve() }; item.computed_size = item_layout.size; item.can_be_collapsed_through = item_layout.margins_can_collapse_through; - item.static_position = Point { - x: resolved_content_box_inset.left, - y: committed_y_offset + active_collapsible_margin_set.resolve(), + item.static_position = if item.is_in_same_bfc { + Point { + x: resolved_content_box_inset.left, + y: committed_y_offset + active_collapsible_margin_set.resolve(), + } + } else { + // TODO: handle inset and margins + Point { x: float_avoiding_position.x + resolved_content_box_inset.left, y: float_avoiding_position.y } }; - 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, + let mut location = if item.is_in_same_bfc { + Point { + x: resolved_content_box_inset.left + inset_offset.x + resolved_margin.left, + y: committed_y_offset + inset_offset.y + y_margin_offset, + } + } else { + // TODO: handle inset and margins + Point { + x: float_avoiding_position.x + + resolved_content_box_inset.left + + inset_offset.x + + resolved_margin.left, + y: float_avoiding_position.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 { + if item_outer_width < stretch_width { match text_align { TextAlign::Auto => { // Do nothing @@ -505,16 +924,11 @@ fn perform_final_layout_on_in_flow_children( TextAlign::LegacyLeft => { // Do nothing. Left aligned by default. } - TextAlign::LegacyRight => location.x += container_inner_width - item_outer_width, - TextAlign::LegacyCenter => location.x += (container_inner_width - item_outer_width) / 2.0, + TextAlign::LegacyRight => location.x += stretch_width - item_outer_width, + TextAlign::LegacyCenter => location.x += (stretch_width - item_outer_width) / 2.0, } } - let scrollbar_size = Size { - width: if item.overflow.y == Overflow::Scroll { item.scrollbar_width } else { 0.0 }, - height: if item.overflow.x == Overflow::Scroll { item.scrollbar_width } else { 0.0 }, - }; - tree.set_unrounded_layout( item.node_id, &Layout { @@ -558,10 +972,18 @@ fn perform_final_layout_on_in_flow_children( .collapse_with_set(top_margin_set) .collapse_with_set(bottom_margin_set); y_offset_for_absolute = committed_y_offset + item_layout.size.height + y_margin_offset; + #[cfg(feature = "float_layout")] + { + y_offset_for_float = committed_y_offset + item_layout.size.height + y_margin_offset; + } } else { committed_y_offset += item_layout.size.height + y_margin_offset; active_collapsible_margin_set = bottom_margin_set; y_offset_for_absolute = committed_y_offset + active_collapsible_margin_set.resolve(); + #[cfg(feature = "float_layout")] + { + y_offset_for_float = committed_y_offset; + } } } } @@ -683,7 +1105,7 @@ fn perform_absolute_layout_on_absolute_children( left: if left.is_some() { margin.left.unwrap_or(0.0) } else { 0.0 }, right: if right.is_some() { margin.right.unwrap_or(0.0) } else { 0.0 }, top: if top.is_some() { margin.top.unwrap_or(0.0) } else { 0.0 }, - bottom: if bottom.is_some() { margin.left.unwrap_or(0.0) } else { 0.0 }, + bottom: if bottom.is_some() { margin.bottom.unwrap_or(0.0) } else { 0.0 }, }; // Expand auto margins to fill available space diff --git a/src/compute/float.rs b/src/compute/float.rs new file mode 100644 index 000000000..d4cbeaef3 --- /dev/null +++ b/src/compute/float.rs @@ -0,0 +1,550 @@ +//! Computes the position of floats in a block formatting context +//! +//! Here are the precise rules that govern the behavior of floats: +//! +//! 1. The left outer edge of a left-floating box may not be to the left of the left edge of its containing block. An analogous rule +//! holds for right-floating elements. +//! 2. If the current box is left-floating, and there are any left-floating boxes generated by elements earlier in the source document, +//! then for each such earlier box, either the left outer edge of the current box must be to the right of the right outer edge of the earlier box, +//! or its top must be lower than the bottom of the earlier box. Analogous rules hold for right-floating boxes. +//! 3. The right outer edge of a left-floating box may not be to the right of the left outer edge of any right-floating box that is next to it. +//! Analogous rules hold for right-floating elements. +//! 4. A floating box's outer top may not be higher than the top of its containing block. When the float occurs between two collapsing margins, +//! the float is positioned as if it had an otherwise empty anonymous block parent taking part in the flow. The position of such a parent is defined +//! by the rules in the section on margin collapsing. +//! 5. The outer top of a floating box may not be higher than the outer top of any block or floated box generated by an element earlier in the source document. +//! 6. The outer top of an element's floating box may not be higher than the top of any line-box containing a box generated by +//! an element earlier in the source document. +//! 7. A left-floating box that has another left-floating box to its left may not have its right outer edge to the right of its containing block's +//! right edge. (Loosely: a left float may not stick out at the right edge, unless it is already as far to the left as possible.) An analogous rule +//! holds for right-floating elements. +//! 8. A floating box must be placed as high as possible. +//! 9. A left-floating box must be put as far to the left as possible, a right-floating box as far to the right as possible. A higher position is +//! preferred over one that is further to the left/right. +//! +//! But in CSS 2.2, if, within the block formatting context, there is an in-flow negative vertical margin such that the float's position is above the position it would be at were all such negative margins set to zero, the position of the float is undefined. +//! +//! + +use core::ops::Range; + +use crate::{AvailableSpace, Clear, FloatDirection, Point, Size}; + +/// An empty "slot" that avoids floats that is suitable for non-floated content +/// to be laid out into +#[derive(Debug, Clone, Copy, Default)] +pub struct ContentSlot { + /// The id of the segment that the slot starts in + pub segment_id: Option, + /// The x position of the start of the slot + pub x: f32, + /// The y position of the start of the slot + pub y: f32, + /// The width of the slot + pub width: f32, + /// The height of the slot + pub height: f32, +} + +/// A floated box +#[derive(Debug, Clone, Default)] +pub struct PlacedFloatedBox { + /// A user defined ID for the box + // id: u64, + /// The width of the box + pub width: f32, + /// The height of the box + pub height: f32, + /// Horizontal distance from the edge of the container that the box is floated towards + /// (distance from the left for left floats, from the right for right floats) + pub x_inset: f32, + /// Vertical distance from top edge of the container + pub y: f32, +} + +/// A non-overlapping horizontal segment of the Block Formatting Context container +#[derive(Debug, Clone)] +struct Segment { + /// The vertical start and end points of the segment + y: Range, + /// Left inset in slot 0. Right inset in slot 1. + insets: [f32; 2], +} + +impl Segment { + /// Whether the segment can fit the passed floated box (in the horizontal axis) + fn fits_float_width(&self, floated_box: Size, direction: FloatDirection, bfc_width: f32) -> bool { + let slot = direction as usize; + self.insets[slot] == 0.0 || (bfc_width - floated_box.width - self.inset_sum()) >= 0.0 + } + + /// The total space taken up by both insets + #[inline(always)] + fn inset_sum(&self) -> f32 { + self.insets[0] + self.insets[1] + } +} + +/// Helper type for placing a single floated box +/// +/// Given a pinned starting y position, this type helps determine if there is any x position +/// such that there is sufficient horizontal space for the box accross it's entire height. +#[derive(Debug, Clone)] +struct FloatFitter { + /// The overall width of the Block Formatting Context + bfc_width: f32, + /// The total height of the set of segments currently being considered + slot_height: f64, + /// The union of the insets of the set of segments currently being considered + insets: [f32; 2], +} + +impl FloatFitter { + /// Create a new `FloatFitter` + fn new(bfc_width: f32, slot_height: f32, insets: [f32; 2]) -> Self { + Self { bfc_width, slot_height: slot_height as f64, insets } + } + + // Horizontal fitting + + /// Union the insets of another segment. This is a "max" of the insets on each side. + fn union_insets(&mut self, insets: [f32; 2]) { + self.insets[0] = self.insets[0].max(insets[0]); + self.insets[1] = self.insets[1].max(insets[1]); + } + + /// Given the currently accounted for insets, check whether there is an x position + /// such that the box fits horizontally + fn fits_horiontally(&self, width: f32) -> bool { + self.insets == [0.0, 0.0] || self.bfc_width - self.insets[0] - self.insets[1] - width >= 0.0 + } + + // Vertical fitting + + /// Add the height of another segment. + fn add_height(&mut self, height: f32) { + self.slot_height += height as f64; + } + + /// Given the currently accounted for height, check whether the box fits vertically + fn fits_vertically(&mut self, height: f32) -> bool { + self.slot_height >= height as f64 + } +} + +/// A context for placing floated boxes +#[derive(Debug, Clone)] +pub struct FloatContext { + /// The available space constraint that applies to the root Block Formatting Context + /// for which this `FloatContext` manages floats. + available_width: f32, + /// Whether the float context contains any floats + has_floats: bool, + /// A list of left-floated boxes within the context + left_floats: Vec, + /// A list of right-floated boxes within the context + right_floats: Vec, + /// A list of non-overlapping horizontal "segments" within the context. + /// Each segment has the same available width for it's entire height. + segments: Vec, + /// A closed-open range indicating which segment the last placed float + /// was placed(on each side). + last_placed_floats: [Range; 2], + // Left hwm in slot 0. Right hwm in slot 1. + // high_water_marks: [usize; 2], + // left_float_high_water_mark: usize, + // right_float_high_water_mark: usize, +} + +impl Default for FloatContext { + fn default() -> Self { + Self { + available_width: 0.0, + has_floats: false, + left_floats: Vec::new(), + right_floats: Vec::new(), + segments: Vec::new(), + last_placed_floats: [0..0, 0..0], // high_water_marks: [0, 0], + } + } +} + +impl FloatContext { + /// Create a new empty `FloatContext` + pub fn new() -> Self { + Default::default() + } + + /// Whether the float context contains any floats + #[inline(always)] + pub fn has_floats(&self) -> bool { + self.has_floats + } + + /// Whether the float context contains any floats that extend to or below min_y + #[inline(always)] + pub fn has_active_floats(&self, min_y: f32) -> bool { + self.has_floats && self.segments.last().map(|seg| seg.y.end).unwrap_or(0.0) > min_y + } + + /// Set the width of the `FloatContext` + pub fn set_width(&mut self, available_width: f32) { + self.available_width = available_width; + } + + /// Returns a slice of placed left floats + pub fn left_floats(&self) -> &[PlacedFloatedBox] { + &self.left_floats + } + + /// Returns a slice of placed right floats + pub fn right_floats(&self) -> &[PlacedFloatedBox] { + &self.right_floats + } + + /// Divide a segment into two segments so that a new float can be placed and have it's + /// vertical start and end at exact segment boundaries + fn subdivide_segment(&mut self, idx: usize, divide_at_y: f32) { + let old_segment = &mut self.segments[idx]; + let new_segment = Segment { insets: old_segment.insets, y: divide_at_y..old_segment.y.end }; + if !old_segment.y.contains(÷_at_y) || old_segment.y.start == divide_at_y { + dbg!(&old_segment); + dbg!(divide_at_y); + assert!(old_segment.y.contains(÷_at_y) && old_segment.y.start != divide_at_y); + } + old_segment.y.end = divide_at_y; + + self.segments.splice((idx + 1)..(idx + 1), core::iter::once(new_segment)); + } + + /// Update the last placed float start and end values + fn update_last_placed_float(&mut self, direction: FloatDirection, placement: Range) { + let slot = direction as usize; + self.last_placed_floats[slot].start = self.last_placed_floats[slot].start.max(placement.start); + self.last_placed_floats[slot].end = self.last_placed_floats[slot].end.max(placement.end); + } + + /// Position a floated box with the context, returning the (x, y) coordinates + pub fn place_floated_box( + &mut self, + floated_box: Size, + min_y: f32, + containing_block_insets: [f32; 2], + direction: FloatDirection, + clear: Clear, + ) -> Point { + self.has_floats = true; + + let placed_floated_box = + self.place_floated_box_inner(floated_box, min_y, containing_block_insets, direction, clear); + + let x_inset = placed_floated_box.x_inset; + let y = placed_floated_box.y; + match direction { + FloatDirection::Left => { + self.left_floats.push(placed_floated_box); + Point { x: x_inset, y } + } + FloatDirection::Right => { + self.right_floats.push(placed_floated_box); + Point { x: self.available_width - x_inset - floated_box.width, y } + } + } + } + + /// Inner implementation of float placement, split into a separate function so that it can early-return + fn place_floated_box_inner( + &mut self, + floated_box: Size, + min_y: f32, + containing_block_insets: [f32; 2], + direction: FloatDirection, + clear: Clear, + ) -> PlacedFloatedBox { + let slot = direction as usize; + + // Ensure that float: + // - Starts at or after the last placed float that was floated in the same direction as it + // - Respects "clear" + let hwm = match clear { + Clear::Left => { + let float_dir_start = self.last_placed_floats[slot].start; + let left_end = self.last_placed_floats[0].end; + float_dir_start.max(left_end + 1) + } + Clear::Right => { + let float_dir_start = self.last_placed_floats[slot].start; + let right_end = self.last_placed_floats[1].end; + float_dir_start.max(right_end + 1) + } + Clear::Both => { + let left_end = self.last_placed_floats[0].end; + let right_end = self.last_placed_floats[1].end; + left_end.max(right_end) + 1 + } + Clear::None => { + // float_dir_start + self.last_placed_floats[slot].start + } + }; + + // Ensure that float is placed in a segment at or below "min_y" + // (ensuring that it is placed at or below min_y within it's segment happens below) + let start_idx = self + .segments + .get(hwm..) + .and_then(|segments| segments.iter().position(|segment| segment.y.end > min_y).map(|idx| idx + hwm)); + + let mut start_idx = start_idx.unwrap_or(self.segments.len()); + let mut start_y = min_y; + let mut end_idx = start_idx; + // let mut end_y = min_y; + + // Loop over remaining segments, trying to place the float in a position + // that has space to accomodate it. + let (start, mut end, placed_inset) = 'outer: loop { + // Start segment does not exist: + // + // This means no existing segment can accomodate the float so we must create a new + // segment below all existing segments. A new segment will always have space for + // the float, so we can exit the loop at this point. + let Some(start_segment) = self.segments.get(start_idx) else { + break (None, None, containing_block_insets[slot]); + }; + + // Candidate start segment doesn't have (horizontal) space for the float: + // => retry with the next segment + if !start_segment.fits_float_width(floated_box, direction, self.available_width) { + start_idx += 1; + end_idx = end_idx.max(start_idx); + continue; + } + + start_y = start_y.max(start_segment.y.start); + let available_height = start_segment.y.end - start_y; + let mut fitter = FloatFitter::new(self.available_width, available_height, containing_block_insets); + fitter.union_insets(start_segment.insets); + + // Pinning the start segment, loop over segments starting with the start segment + // to find the end segment: + // - The selected segment range must have enough height to contain the float + // - All of the segments in the range must have enough horizontal width to contain the float + // TODO: must ensure that width is in the correct place. + loop { + // End segment does not exist: + // + // This means no existing segment can accomodate the float so we must create a new + // segment below all existing segments + let Some(end_segment) = self.segments.get(end_idx) else { + let inset = fitter.insets[slot]; + break 'outer (Some(start_idx), None, inset); + }; + + // Check horizontal fit + // + // If it does not fit horizontally then it will never fit in this position, so + // continue the outer loop to find and check a new position + fitter.union_insets(end_segment.insets); + if !fitter.fits_horiontally(floated_box.width) { + start_idx += 1; + end_idx = end_idx.max(start_idx); + continue 'outer; + } + + // Check vertical fit + // + // If it does not (yet) fit vertically then continue the inner loop to add another + // segment to the range of segments we are placing the float in + if end_idx != start_idx { + fitter.add_height(end_segment.y.end - end_segment.y.start); + } + if !fitter.fits_vertically(floated_box.height) { + end_idx += 1; + continue; + } + + let inset = fitter.insets[slot]; + break 'outer (Some(start_idx), Some(end_idx), inset); + } + }; + + // Short-circuit for zero-sized boxes + if floated_box.width == 0.0 || floated_box.height == 0.0 { + // TODO: need to update last_placed_float? + + return PlacedFloatedBox { + width: floated_box.width, + height: floated_box.height, + y: start_y, + x_inset: placed_inset, + }; + } + + // Handle case where floated box is placed after all existing segments + if start.is_none() { + let last_y_end = self.segments.last().map(|seg| seg.y.end).unwrap_or(0.0); + if start_y > last_y_end { + self.segments.push(Segment { y: last_y_end..start_y, insets: [0.0, 0.0] }); + } + + let start_y = last_y_end.max(start_y); + + let mut insets = containing_block_insets; + insets[slot] += floated_box.width; + self.segments.push(Segment { y: start_y..(start_y + floated_box.height), insets }); + + // Update last_placed_float + let start_idx = self.segments.len() - 1; + let end_idx = start_idx + 1; + self.update_last_placed_float(direction, start_idx..end_idx); + + return PlacedFloatedBox { + width: floated_box.width, + height: floated_box.height, + y: start_y, + x_inset: containing_block_insets[slot], + }; + } + + // Else unwrap the index of the segment that the start of the floating box is placed in + let mut start_idx = start.unwrap(); + + // If the floated box doesn't start at the exact same y-offset as the segment it starts in, then + // subdivide that segment into two segments at the y-offset that the floated box starts at, and increment + // `start_idx` so that the floating box is placed in the second of the two segments. + if start_y != self.segments[start_idx].y.start { + self.subdivide_segment(start_idx, start_y); + start_idx += 1; + if let Some(end_idx) = end.as_mut() { + *end_idx += 1; + } + } + + let end_idx = match end { + None => { + let last_y_end = self.segments.last().map(|seg| seg.y.end).unwrap_or(0.0); + if min_y > last_y_end { + self.segments.push(Segment { y: last_y_end..min_y, insets: [0.0, 0.0] }); + } + self.segments.len() - 1 + } + Some(end_idx) => { + let end_y = start_y + floated_box.height; + if end_y != self.segments[end_idx].y.end { + self.subdivide_segment(end_idx, end_y); + } + + end_idx + } + }; + + // Update inset for the range of segments that the float is placed in + let placed_inset_plus_width = placed_inset + floated_box.width; + for segment in &mut self.segments[start_idx..=end_idx] { + segment.insets[slot] = placed_inset_plus_width; + } + + // Update last_placed_float + self.update_last_placed_float(direction, start_idx..(end_idx + 1)); + + PlacedFloatedBox { width: floated_box.width, height: floated_box.height, y: start_y, x_inset: placed_inset } + } + + /// Search for a space suitable for laying out non-floated content into + pub fn find_content_slot( + &self, + min_y: f32, + containing_block_insets: [f32; 2], + clear: Clear, + after: Option, + ) -> ContentSlot { + if !self.has_active_floats(min_y) { + return ContentSlot { + segment_id: None, + x: containing_block_insets[0], + y: min_y, + width: self.available_width - containing_block_insets[0] - containing_block_insets[1], + height: f32::INFINITY, + }; + } + + // The min starting segment index + let at_least = after.map(|idx| idx + 1).unwrap_or(0); + + // Ensure that content respects "clear" and "after" + let hwm = match clear { + Clear::Left => { + let left_end = self.last_placed_floats[0].end; + at_least.max(left_end + 1) + } + Clear::Right => { + let right_end = self.last_placed_floats[1].end; + at_least.max(right_end + 1) + } + Clear::Both => { + let left_end = self.last_placed_floats[0].end; + let right_end = self.last_placed_floats[1].end; + at_least.max(left_end).max(right_end) + 1 + } + Clear::None => at_least, + }; + + let start_idx = self + .segments + .get(hwm..) + .and_then(|segments| segments.iter().position(|segment| segment.y.end > min_y).map(|idx| idx + hwm)); + let start_idx = start_idx.unwrap_or(self.segments.len()); + let segment = self.segments.get(start_idx); + match segment { + Some(segment) => { + let inset_left = segment.insets[0].max(containing_block_insets[0]); + let inset_right = segment.insets[1].max(containing_block_insets[1]); + ContentSlot { + segment_id: Some(start_idx), + x: inset_left, + y: segment.y.start.max(min_y), + width: self.available_width - inset_left - inset_right, + height: f32::INFINITY, + } + } + None => ContentSlot { + segment_id: None, + x: containing_block_insets[0], + y: min_y, + width: self.available_width - containing_block_insets[0] - containing_block_insets[1], + height: f32::INFINITY, + }, + } + } +} + +/// Context for computing the intrinsic width contribution of a set of floats +pub struct FloatIntrinsicWidthCalculator { + /// The available width of the container + available_width: AvailableSpace, + /// The running total intrinsic width contribution + contribution: f32, +} + +impl FloatIntrinsicWidthCalculator { + /// Create a new `FloatIntrinsicWidthCalculator` + pub fn new(available_width: AvailableSpace) -> Self { + Self { available_width, contribution: 0.0 } + } + + /// Add a float to the computation + pub fn add_float(&mut self, width: f32, _direction: FloatDirection, _clear: Clear) { + match self.available_width { + AvailableSpace::Definite(_) => { + // We will never hit this code path with definite available space + } + AvailableSpace::MinContent => self.contribution = self.contribution.max(width), + AvailableSpace::MaxContent => self.contribution += width, + }; + } + + /// Get the computed float contribution to intrinsic width + pub fn result(&self) -> f32 { + self.contribution + } +} diff --git a/src/compute/mod.rs b/src/compute/mod.rs index aeb5a5f3e..af697e7af 100644 --- a/src/compute/mod.rs +++ b/src/compute/mod.rs @@ -27,6 +27,9 @@ pub(crate) mod leaf; #[cfg(feature = "block_layout")] pub(crate) mod block; +#[cfg(feature = "float_layout")] +pub(crate) mod float; + #[cfg(feature = "flexbox")] pub(crate) mod flexbox; @@ -36,7 +39,7 @@ pub(crate) mod grid; pub use leaf::compute_leaf_layout; #[cfg(feature = "block_layout")] -pub use self::block::compute_block_layout; +pub use self::block::{compute_block_layout, BlockContext, BlockFormattingContext}; #[cfg(feature = "flexbox")] pub use self::flexbox::compute_flexbox_layout; @@ -44,6 +47,9 @@ pub use self::flexbox::compute_flexbox_layout; #[cfg(feature = "grid")] pub use self::grid::compute_grid_layout; +#[cfg(feature = "float_layout")] +pub use self::float::{ContentSlot, FloatContext, FloatIntrinsicWidthCalculator}; + use crate::geometry::{Line, Point, Size}; use crate::style::{AvailableSpace, CoreStyle, Overflow}; use crate::tree::{ @@ -162,10 +168,10 @@ pub fn compute_cached_layout( tree: &mut Tree, node: NodeId, inputs: LayoutInput, - mut compute_uncached: ComputeFunction, + compute_uncached: ComputeFunction, ) -> LayoutOutput where - ComputeFunction: FnMut(&mut Tree, NodeId, LayoutInput) -> LayoutOutput, + ComputeFunction: FnOnce(&mut Tree, NodeId, LayoutInput) -> LayoutOutput, { debug_push_node!(node); let LayoutInput { known_dimensions, available_space, run_mode, .. } = inputs; diff --git a/src/geometry.rs b/src/geometry.rs index 8cabefcf2..2a6bc17d6 100644 --- a/src/geometry.rs +++ b/src/geometry.rs @@ -190,7 +190,7 @@ where /// /// **NOTE:** this is *not* the width of the rectangle. #[inline(always)] - pub(crate) fn horizontal_axis_sum(&self) -> U { + pub fn horizontal_axis_sum(&self) -> U { self.left + self.right } @@ -200,16 +200,16 @@ where /// /// **NOTE:** this is *not* the height of the rectangle. #[inline(always)] - pub(crate) fn vertical_axis_sum(&self) -> U { + pub fn vertical_axis_sum(&self) -> U { self.top + self.bottom } - /// Both horizontal_axis_sum and vertical_axis_sum as a Size + /// Both horizontal_axis_sum and vertical_axis_sum as a `Size` /// /// **NOTE:** this is *not* the width/height of the rectangle. #[inline(always)] #[allow(dead_code)] // Fixes spurious clippy warning: this function is used! - pub(crate) fn sum_axes(&self) -> Size { + pub fn sum_axes(&self) -> Size { Size { width: self.horizontal_axis_sum(), height: self.vertical_axis_sum() } } diff --git a/src/lib.rs b/src/lib.rs index 9722e155b..97d368009 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -123,6 +123,7 @@ pub use crate::tree::TaffyTree; #[doc(inline)] pub use crate::util::print_tree; +pub use crate::compute::*; pub use crate::geometry::*; pub use crate::style::*; pub use crate::tree::*; diff --git a/src/style/block.rs b/src/style/block.rs index 187698e54..ae61931dd 100644 --- a/src/style/block.rs +++ b/src/style/block.rs @@ -17,6 +17,20 @@ pub trait BlockItemStyle: CoreStyle { fn is_table(&self) -> bool { false } + + /// Whether the item is a floated + #[cfg(feature = "float_layout")] + #[inline(always)] + fn float(&self) -> super::Float { + super::Float::None + } + + /// Whether the item is a floated + #[cfg(feature = "float_layout")] + #[inline(always)] + fn clear(&self) -> super::Clear { + super::Clear::None + } } /// Used by block layout to implement the legacy behaviour of `
` and `
` diff --git a/src/style/flex.rs b/src/style/flex.rs index 1bbcb8f14..adf73516a 100644 --- a/src/style/flex.rs +++ b/src/style/flex.rs @@ -73,10 +73,11 @@ use crate::geometry::AbsoluteAxis; /// Defaults to [`FlexWrap::NoWrap`] /// /// [Specification](https://www.w3.org/TR/css-flexbox-1/#flex-wrap-property) -#[derive(Copy, Clone, PartialEq, Eq, Debug)] +#[derive(Copy, Clone, PartialEq, Eq, Debug, Default)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub enum FlexWrap { /// Items will not wrap and stay on a single line + #[default] NoWrap, /// Items will wrap according to this item's [`FlexDirection`] Wrap, @@ -84,12 +85,6 @@ pub enum FlexWrap { WrapReverse, } -impl Default for FlexWrap { - fn default() -> Self { - Self::NoWrap - } -} - /// The direction of the flexbox layout main axis. /// /// There are always two perpendicular layout axes: main (or primary) and cross (or secondary). @@ -101,12 +96,13 @@ impl Default for FlexWrap { /// The default behavior is [`FlexDirection::Row`]. /// /// [Specification](https://www.w3.org/TR/css-flexbox-1/#flex-direction-property) -#[derive(Copy, Clone, PartialEq, Eq, Debug)] +#[derive(Copy, Clone, PartialEq, Eq, Debug, Default)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub enum FlexDirection { /// Defines +x as the main axis /// /// Items will be added from left to right in a row. + #[default] Row, /// Defines +y as the main axis /// @@ -122,12 +118,6 @@ pub enum FlexDirection { ColumnReverse, } -impl Default for FlexDirection { - fn default() -> Self { - Self::Row - } -} - impl FlexDirection { #[inline] /// Is the direction [`FlexDirection::Row`] or [`FlexDirection::RowReverse`]? diff --git a/src/style/float.rs b/src/style/float.rs new file mode 100644 index 000000000..049717a22 --- /dev/null +++ b/src/style/float.rs @@ -0,0 +1,65 @@ +//! Style types for float layout + +/// Floats a box to the left or right. +/// This property only applies to children of a block layout +/// +/// See +#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +pub enum Float { + /// The box is floated to the left + Left, + /// The box is floated to the right + Right, + /// The box is not floated + #[default] + None, +} + +/// Whether a box that is definitely floated is floated to the left +/// of to the right. +/// +/// This type is only used in the low-level parts of the +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[repr(u8)] +pub enum FloatDirection { + /// The box is floated to the left + Left = 0, + /// The box is floated to the right + Right = 1, +} + +impl Float { + /// Whether the box is floated + pub fn is_floated(self) -> bool { + matches!(self, Self::Left | Self::Right) + } + + /// Converts [`Float`] into `Option`ca + pub fn float_direction(&self) -> Option { + match self { + Float::Left => Some(FloatDirection::Left), + Float::Right => Some(FloatDirection::Right), + Float::None => None, + } + } +} + +/// Gives a box "clearance", which moves it below floated boxes which precede +/// it in the tree. +/// +/// See +#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +pub enum Clear { + /// The box clears left-floated boxes + Left, + /// The box clears right-floated boxes + Right, + /// The box clears boxes floated in either direction + Both, + /// The box does not clear floated boxes + #[default] + None, +} diff --git a/src/style/grid.rs b/src/style/grid.rs index 9f949d61d..91775587d 100644 --- a/src/style/grid.rs +++ b/src/style/grid.rs @@ -273,10 +273,11 @@ pub trait GridItemStyle: CoreStyle { /// Defaults to [`GridAutoFlow::Row`] /// /// [MDN](https://developer.mozilla.org/en-US/docs/Web/CSS/grid-auto-flow) -#[derive(Copy, Clone, PartialEq, Eq, Debug)] +#[derive(Copy, Clone, PartialEq, Eq, Debug, Default)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub enum GridAutoFlow { /// Items are placed by filling each row in turn, adding new rows as necessary + #[default] Row, /// Items are placed by filling each column in turn, adding new columns as necessary. Column, @@ -286,12 +287,6 @@ pub enum GridAutoFlow { ColumnDense, } -impl Default for GridAutoFlow { - fn default() -> Self { - Self::Row - } -} - impl GridAutoFlow { /// Whether grid auto placement uses the sparse placement algorithm or the dense placement algorithm /// See: @@ -315,10 +310,8 @@ impl GridAutoFlow { /// A grid line placement specification which is generic over the coordinate system that it uses to define /// grid line positions. /// -/// GenericGridPlacement is aliased as GridPlacement and is exposed to users of Taffy to define styles. -/// GenericGridPlacement is aliased as OriginZeroGridPlacement and is used internally for placement computations. -/// -/// See [`crate::compute::grid::type::coordinates`] for documentation on the different coordinate systems. +/// `GenericGridPlacement` is aliased as GridPlacement and is exposed to users of Taffy to define styles. +/// `GenericGridPlacement` is aliased as OriginZeroGridPlacement and is used internally for placement computations. #[derive(Copy, Clone, PartialEq, Eq, Debug)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub enum GenericGridPlacement { @@ -343,10 +336,11 @@ pub(crate) type NonNamedGridPlacement = GenericGridPlacement; /// Defaults to `GridPlacement::Auto` /// /// [Specification](https://www.w3.org/TR/css3-grid-layout/#typedef-grid-row-start-grid-line) -#[derive(Clone, PartialEq, Debug)] +#[derive(Clone, PartialEq, Debug, Default)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub enum GridPlacement { /// Place item according to the auto-placement algorithm, and the parent's grid_auto_flow property + #[default] Auto, /// Place item at specified line (column or row) index Line(GridLine), @@ -354,9 +348,9 @@ pub enum GridPlacement { NamedLine(S, i16), /// Item should span specified number of tracks (columns or rows) Span(u16), - /// Item should span until the nth line named . + /// Item should span until the nth line named ``. /// - /// If there are less than n lines named in the specified direction then + /// If there are less than n lines named `` in the specified direction then /// all implicit lines will be counted. NamedSpan(S, u16), } @@ -384,12 +378,6 @@ impl TaffyGridSpan for Line> { } } -impl Default for GridPlacement { - fn default() -> Self { - Self::Auto - } -} - impl GridPlacement { /// Apply a mapping function if the [`GridPlacement`] is a `Line`. Otherwise return `self` unmodified. pub fn into_origin_zero_placement_ignoring_named(&self, explicit_track_count: u16) -> OriginZeroGridPlacement { diff --git a/src/style/mod.rs b/src/style/mod.rs index e44d06b74..fe60e0411 100644 --- a/src/style/mod.rs +++ b/src/style/mod.rs @@ -8,6 +8,8 @@ mod dimension; mod block; #[cfg(feature = "flexbox")] mod flex; +#[cfg(feature = "float_layout")] +mod float; #[cfg(feature = "grid")] mod grid; @@ -21,6 +23,8 @@ use crate::sys::DefaultCheapStr; pub use self::block::{BlockContainerStyle, BlockItemStyle, TextAlign}; #[cfg(feature = "flexbox")] pub use self::flex::{FlexDirection, FlexWrap, FlexboxContainerStyle, FlexboxItemStyle}; +#[cfg(feature = "float_layout")] +pub use self::float::{Clear, Float, FloatDirection}; #[cfg(feature = "grid")] pub use self::grid::{ GenericGridPlacement, GenericGridTemplateComponent, GenericRepetition, GridAutoFlow, GridContainerStyle, @@ -251,11 +255,12 @@ impl Default for BoxGenerationMode { /// which can be unintuitive. /// /// [`Position::Relative`] is the default value, in contrast to the default behavior in CSS. -#[derive(Copy, Clone, PartialEq, Eq, Debug)] +#[derive(Copy, Clone, PartialEq, Eq, Debug, Default)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub enum Position { /// 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] Relative, /// The offset is computed relative to this item's closest positioned ancestor, if any. /// Otherwise, it is placed relative to the origin. @@ -265,12 +270,6 @@ pub enum Position { Absolute, } -impl Default for Position { - fn default() -> Self { - Self::Relative - } -} - /// 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 @@ -284,21 +283,16 @@ impl Default for Position { /// - `flex_basis` /// /// See -#[derive(Copy, Clone, PartialEq, Eq, Debug)] +#[derive(Copy, Clone, PartialEq, Eq, Debug, Default)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub enum BoxSizing { /// Size styles such size, min_size, max_size specify the box's "border box" (the size excluding margin but including padding/border) + #[default] BorderBox, /// Size styles such size, min_size, max_size specify the box's "content box" (the size excluding padding/border/margin) ContentBox, } -impl Default for BoxSizing { - fn default() -> Self { - Self::BorderBox - } -} - /// How children overflowing their container should affect layout /// /// In CSS the primary effect of this property is to control whether contents of a parent container that overflow that container should @@ -335,7 +329,7 @@ impl Overflow { /// Returns true for overflow modes that contain their contents (`Overflow::Hidden`, `Overflow::Scroll`, `Overflow::Auto`) /// or else false for overflow modes that allow their contains to spill (`Overflow::Visible`). #[inline(always)] - pub(crate) fn is_scroll_container(self) -> bool { + pub fn is_scroll_container(self) -> bool { match self { Self::Visible | Self::Clip => false, Self::Hidden | Self::Scroll => true, @@ -391,6 +385,13 @@ pub struct Style { /// How much space (in points) should be reserved for the scrollbars of `Overflow::Scroll` and `Overflow::Auto` nodes. pub scrollbar_width: f32, + #[cfg(feature = "float_layout")] + /// Should the box be floated + pub float: Float, + #[cfg(feature = "float_layout")] + /// Should the box clear floats + pub clear: Clear, + // Position properties /// What should the `position` value of this struct use as a base offset? pub position: Position, @@ -525,6 +526,10 @@ impl Style { box_sizing: BoxSizing::BorderBox, overflow: Point { x: Overflow::Visible, y: Overflow::Visible }, scrollbar_width: 0.0, + #[cfg(feature = "float_layout")] + float: Float::None, + #[cfg(feature = "float_layout")] + clear: Clear::None, position: Position::Relative, inset: Rect::auto(), margin: Rect::zero(), @@ -749,6 +754,18 @@ impl BlockItemStyle for Style { fn is_table(&self) -> bool { self.item_is_table } + + #[cfg(feature = "float_layout")] + #[inline(always)] + fn float(&self) -> Float { + self.float + } + + #[cfg(feature = "float_layout")] + #[inline(always)] + fn clear(&self) -> Clear { + self.clear + } } #[cfg(feature = "block_layout")] @@ -757,6 +774,18 @@ impl BlockItemStyle for &'_ T { fn is_table(&self) -> bool { (*self).is_table() } + + #[cfg(feature = "float_layout")] + #[inline(always)] + fn float(&self) -> Float { + (*self).float() + } + + #[cfg(feature = "float_layout")] + #[inline(always)] + fn clear(&self) -> Clear { + (*self).clear() + } } #[cfg(feature = "flexbox")] @@ -1093,6 +1122,10 @@ mod tests { item_is_table: false, item_is_replaced: false, box_sizing: Default::default(), + #[cfg(feature = "float_layout")] + float: Default::default(), + #[cfg(feature = "float_layout")] + clear: Default::default(), overflow: Default::default(), scrollbar_width: 0.0, position: Default::default(), diff --git a/src/tree/taffy_tree.rs b/src/tree/taffy_tree.rs index af343aebf..785e97fa5 100644 --- a/src/tree/taffy_tree.rs +++ b/src/tree/taffy_tree.rs @@ -5,6 +5,8 @@ use slotmap::SecondaryMap; use slotmap::SparseSecondaryMap as SecondaryMap; use slotmap::{DefaultKey, SlotMap}; +#[cfg(feature = "block_layout")] +use crate::block::BlockContext; use crate::geometry::Size; use crate::style::{AvailableSpace, Display, Style}; use crate::sys::DefaultCheapStr; @@ -19,6 +21,7 @@ use crate::compute::{ compute_cached_layout, compute_hidden_layout, compute_leaf_layout, compute_root_layout, round_layout, }; use crate::CacheTree; + #[cfg(feature = "block_layout")] use crate::{compute::compute_block_layout, LayoutBlockContainer}; #[cfg(feature = "flexbox")] @@ -283,6 +286,69 @@ where pub(crate) measure_function: MeasureFunction, } +impl TaffyView<'_, NodeContext, MeasureFunction> +where + MeasureFunction: + FnMut(Size>, Size, NodeId, Option<&mut NodeContext>, &Style) -> Size, +{ + #[inline(always)] + /// Unified implementation that both `LayoutPartialTree::compute_child_layout` + /// and `LayoutBlockContainer::compute_block_child_layout` delegate to. + fn compute_child_layout( + &mut self, + node_id: NodeId, + inputs: LayoutInput, + #[cfg(feature = "block_layout")] block_ctx: Option<&mut BlockContext<'_>>, + ) -> LayoutOutput { + // If RunMode is PerformHiddenLayout then this indicates that an ancestor node is `Display::None` + // and thus that we should lay out this node using hidden layout regardless of it's own display style. + if inputs.run_mode == RunMode::PerformHiddenLayout { + debug_log!("HIDDEN"); + return compute_hidden_layout(self, node_id); + } + + // We run the following wrapped in "compute_cached_layout", which will check the cache for an entry matching the node and inputs and: + // - Return that entry if exists + // - Else call the passed closure (below) to compute the result + // + // If there was no cache match and a new result needs to be computed then that result will be added to the cache + compute_cached_layout(self, node_id, inputs, |tree, node_id, inputs| { + let display_mode = tree.taffy.nodes[node_id.into()].style.display; + let has_children = tree.child_count(node_id) > 0; + + debug_log!(display_mode); + debug_log_node!( + inputs.known_dimensions, + inputs.parent_size, + inputs.available_space, + inputs.run_mode, + inputs.sizing_mode + ); + + // Dispatch to a layout algorithm based on the node's display style and whether the node has children or not. + match (display_mode, has_children) { + (Display::None, _) => compute_hidden_layout(tree, node_id), + #[cfg(feature = "block_layout")] + (Display::Block, true) => compute_block_layout(tree, node_id, inputs, block_ctx), + #[cfg(feature = "flexbox")] + (Display::Flex, true) => compute_flexbox_layout(tree, node_id, inputs), + #[cfg(feature = "grid")] + (Display::Grid, true) => compute_grid_layout(tree, node_id, inputs), + (_, false) => { + let node_key = node_id.into(); + let style = &tree.taffy.nodes[node_key].style; + let has_context = tree.taffy.nodes[node_key].has_context; + let node_context = has_context.then(|| tree.taffy.node_context_data.get_mut(node_key)).flatten(); + let measure_function = |known_dimensions, available_space| { + (tree.measure_function)(known_dimensions, available_space, node_id, node_context, style) + }; + compute_leaf_layout(inputs, style, |_, _| 0.0, measure_function) + } + } + }) + } +} + // TraversePartialTree impl for TaffyView impl TraversePartialTree for TaffyView<'_, NodeContext, MeasureFunction> where @@ -346,54 +412,13 @@ where } #[inline(always)] - fn compute_child_layout(&mut self, node: NodeId, inputs: LayoutInput) -> LayoutOutput { - // If RunMode is PerformHiddenLayout then this indicates that an ancestor node is `Display::None` - // and thus that we should lay out this node using hidden layout regardless of it's own display style. - if inputs.run_mode == RunMode::PerformHiddenLayout { - debug_log!("HIDDEN"); - return compute_hidden_layout(self, node); - } - - // We run the following wrapped in "compute_cached_layout", which will check the cache for an entry matching the node and inputs and: - // - Return that entry if exists - // - Else call the passed closure (below) to compute the result - // - // If there was no cache match and a new result needs to be computed then that result will be added to the cache - compute_cached_layout(self, node, inputs, |tree, node, inputs| { - let display_mode = tree.taffy.nodes[node.into()].style.display; - let has_children = tree.child_count(node) > 0; - - debug_log!(display_mode); - debug_log_node!( - inputs.known_dimensions, - inputs.parent_size, - inputs.available_space, - inputs.run_mode, - inputs.sizing_mode - ); - - // Dispatch to a layout algorithm based on the node's display style and whether the node has children or not. - match (display_mode, has_children) { - (Display::None, _) => compute_hidden_layout(tree, node), - #[cfg(feature = "block_layout")] - (Display::Block, true) => compute_block_layout(tree, node, inputs), - #[cfg(feature = "flexbox")] - (Display::Flex, true) => compute_flexbox_layout(tree, node, inputs), - #[cfg(feature = "grid")] - (Display::Grid, true) => compute_grid_layout(tree, node, inputs), - (_, false) => { - let node_key = node.into(); - let style = &tree.taffy.nodes[node_key].style; - let has_context = tree.taffy.nodes[node_key].has_context; - let node_context = has_context.then(|| tree.taffy.node_context_data.get_mut(node_key)).flatten(); - let measure_function = |known_dimensions, available_space| { - (tree.measure_function)(known_dimensions, available_space, node, node_context, style) - }; - // TODO: implement calc() in high-level API - compute_leaf_layout(inputs, style, |_, _| 0.0, measure_function) - } - } - }) + fn compute_child_layout(&mut self, node_id: NodeId, inputs: LayoutInput) -> LayoutOutput { + self.compute_child_layout( + node_id, + inputs, + #[cfg(feature = "block_layout")] + None, + ) } } @@ -452,6 +477,16 @@ where fn get_block_child_style(&self, child_node_id: NodeId) -> Self::BlockItemStyle<'_> { self.get_core_container_style(child_node_id) } + + #[inline(always)] + fn compute_block_child_layout( + &mut self, + node_id: NodeId, + inputs: LayoutInput, + block_ctx: Option<&mut BlockContext<'_>>, + ) -> LayoutOutput { + self.compute_child_layout(node_id, inputs, block_ctx) + } } #[cfg(feature = "flexbox")] diff --git a/src/tree/traits.rs b/src/tree/traits.rs index f4548cb4b..194110ef5 100644 --- a/src/tree/traits.rs +++ b/src/tree/traits.rs @@ -137,7 +137,7 @@ use crate::style::{FlexboxContainerStyle, FlexboxItemStyle}; use crate::style::{GridContainerStyle, GridItemStyle}; use crate::CheapCloneStr; #[cfg(feature = "block_layout")] -use crate::{BlockContainerStyle, BlockItemStyle}; +use crate::{BlockContainerStyle, BlockContext, BlockItemStyle}; #[cfg(all(feature = "grid", feature = "detailed_layout_info"))] use crate::compute::grid::DetailedGridInfo; @@ -313,6 +313,18 @@ pub trait LayoutBlockContainer: LayoutPartialTree { /// Get the child's styles fn get_block_child_style(&self, child_node_id: NodeId) -> Self::BlockItemStyle<'_>; + + /// Compute the specified node's size or full layout given the specified constraints + #[cfg(feature = "block_layout")] + fn compute_block_child_layout( + &mut self, + node_id: NodeId, + inputs: LayoutInput, + block_ctx: Option<&mut BlockContext<'_>>, + ) -> LayoutOutput { + let _ = block_ctx; + self.compute_child_layout(node_id, inputs) + } } // --- PRIVATE TRAITS diff --git a/test_fixtures/float/float_simple.html b/test_fixtures/float/float_simple.html new file mode 100644 index 000000000..4ced54bd0 --- /dev/null +++ b/test_fixtures/float/float_simple.html @@ -0,0 +1,21 @@ + + + + + + + Test description + + + + +
+
+
+
+
+
+
+ + + \ No newline at end of file diff --git a/test_fixtures/float/xfloat_max_content.html b/test_fixtures/float/xfloat_max_content.html new file mode 100644 index 000000000..4e51d112e --- /dev/null +++ b/test_fixtures/float/xfloat_max_content.html @@ -0,0 +1,94 @@ + + + + + + + Test description + + + + +
+ HH +
+ HH +
+ HH +
+ HH +
+ HH +
+ HH +
+ HH +
+ HH +
+
+
+
+
+
+
Flow Root
+
Flow Root
+
+
Flow Root
+
+
+
+
+
+ Inline content +
Same BFC
+
Same BFC
+
+
Same BFC
+
+
+
+
+
+
Flow root
+
Flow root
+
+
Same BFC
+
+
+
+
+
+
Same BFC Block
+
+
+
+
+
+
+
Flow Root +
+
+
+
+
+
+
+
+
+
Same BFC Block. reiogh reihg ierhg iorehg ioreh giorehg ioreh giorehgio +
+
+
+
+
+
+
+
Same BFC Block. reiogh reihg ierhg iorehg ioreh giorehg ioreh giorehgio
+
+
+
Same BFC Block. reiogh reihg ierhg iorehg ioreh giorehg ioreh giorehgio
+
+ + + \ No newline at end of file diff --git a/test_fixtures/float/xfloat_min_content.html b/test_fixtures/float/xfloat_min_content.html new file mode 100644 index 000000000..a9007c053 --- /dev/null +++ b/test_fixtures/float/xfloat_min_content.html @@ -0,0 +1,32 @@ + + + + + + + Test description + + + + +
+ HH +
+ HH +
+ HH +
+ HH +
+ HH +
+ HH +
+ HH +
+ HH +
+
+ + + \ No newline at end of file diff --git a/test_fixtures/float/xfloat_old.html b/test_fixtures/float/xfloat_old.html new file mode 100644 index 000000000..3d14f0fd1 --- /dev/null +++ b/test_fixtures/float/xfloat_old.html @@ -0,0 +1,25 @@ + + + + + + + Test description + + + + +
+ hellek hfio erhgio heriugher iogh eiro +
+
+
+ rgre guguyg +
+
+
+
+
+ + + \ No newline at end of file diff --git a/test_fixtures/float/xfloat_same_bfc_max_content.html b/test_fixtures/float/xfloat_same_bfc_max_content.html new file mode 100644 index 000000000..db7424986 --- /dev/null +++ b/test_fixtures/float/xfloat_same_bfc_max_content.html @@ -0,0 +1,66 @@ + + + + + + + Test description + + + + +
+
+
+
Flow Root
+
Flow Root
+
+
Flow Root
+
+ +
+ +
+
+
+ Inline content
+ Inline content
+
+ Inline content
+
+ +
+ +
+
+
+
Flow root
+
Flow root
+
+
Same BFC
+
+ +
+ +
+
+
+
Same BFC
+
Same BFC
+
+
Same BFC
+
+ +
+ +
+
+
+ Inline Content +
Same BFC
+
+
Same BFC
+
+ + + \ No newline at end of file diff --git a/tests/generated/float/float_simple.rs b/tests/generated/float/float_simple.rs new file mode 100644 index 000000000..3336baa3e --- /dev/null +++ b/tests/generated/float/float_simple.rs @@ -0,0 +1,233 @@ +#[test] +#[allow(non_snake_case)] +fn float_simple__border_box() { + #[allow(unused_imports)] + use taffy::{prelude::*, Layout}; + let mut taffy = crate::new_test_tree(); + let node0 = taffy + .new_leaf(taffy::style::Style { + float: taffy::style::Float::Right, + size: taffy::geometry::Size { + width: taffy::style::Dimension::from_length(50f32), + height: taffy::style::Dimension::from_length(50f32), + }, + ..Default::default() + }) + .unwrap(); + let node1 = taffy + .new_leaf(taffy::style::Style { + float: taffy::style::Float::Right, + size: taffy::geometry::Size { + width: taffy::style::Dimension::from_length(50f32), + height: taffy::style::Dimension::from_length(50f32), + }, + ..Default::default() + }) + .unwrap(); + let node2 = taffy + .new_leaf(taffy::style::Style { + float: taffy::style::Float::Right, + size: taffy::geometry::Size { + width: taffy::style::Dimension::from_length(50f32), + height: taffy::style::Dimension::from_length(50f32), + }, + ..Default::default() + }) + .unwrap(); + let node3 = taffy + .new_leaf(taffy::style::Style { + float: taffy::style::Float::Right, + size: taffy::geometry::Size { + width: taffy::style::Dimension::from_length(50f32), + height: taffy::style::Dimension::from_length(50f32), + }, + ..Default::default() + }) + .unwrap(); + let node4 = taffy + .new_leaf(taffy::style::Style { + float: taffy::style::Float::Right, + size: taffy::geometry::Size { + width: taffy::style::Dimension::from_length(50f32), + height: taffy::style::Dimension::from_length(50f32), + }, + ..Default::default() + }) + .unwrap(); + let node = taffy + .new_with_children( + taffy::style::Style { + display: taffy::style::Display::Block, + size: taffy::geometry::Size { width: auto(), height: taffy::style::Dimension::from_length(300f32) }, + border: taffy::geometry::Rect { + left: length(2f32), + right: length(2f32), + top: length(2f32), + bottom: length(2f32), + }, + ..Default::default() + }, + &[node0, node1, node2, node3, node4], + ) + .unwrap(); + taffy.compute_layout_with_measure(node, taffy::geometry::Size::MAX_CONTENT, crate::test_measure_function).unwrap(); + println!("\nComputed tree:"); + taffy.print_tree(node); + println!(); + let layout = taffy.layout(node).unwrap(); + let Layout { size, location, .. } = layout; + assert_eq!(size.width, 254f32, "width of node {:?}. Expected {}. Actual {}", node, 254f32, size.width); + assert_eq!(size.height, 300f32, "height of node {:?}. Expected {}. Actual {}", node, 300f32, size.height); + assert_eq!(location.x, 0f32, "x of node {:?}. Expected {}. Actual {}", node, 0f32, location.x); + assert_eq!(location.y, 0f32, "y of node {:?}. Expected {}. Actual {}", node, 0f32, location.y); + let layout = taffy.layout(node0).unwrap(); + let Layout { size, location, .. } = layout; + assert_eq!(size.width, 50f32, "width of node {:?}. Expected {}. Actual {}", node0, 50f32, size.width); + assert_eq!(size.height, 50f32, "height of node {:?}. Expected {}. Actual {}", node0, 50f32, size.height); + assert_eq!(location.x, 202f32, "x of node {:?}. Expected {}. Actual {}", node0, 202f32, location.x); + assert_eq!(location.y, 2f32, "y of node {:?}. Expected {}. Actual {}", node0, 2f32, location.y); + let layout = taffy.layout(node1).unwrap(); + let Layout { size, location, .. } = layout; + assert_eq!(size.width, 50f32, "width of node {:?}. Expected {}. Actual {}", node1, 50f32, size.width); + assert_eq!(size.height, 50f32, "height of node {:?}. Expected {}. Actual {}", node1, 50f32, size.height); + assert_eq!(location.x, 152f32, "x of node {:?}. Expected {}. Actual {}", node1, 152f32, location.x); + assert_eq!(location.y, 2f32, "y of node {:?}. Expected {}. Actual {}", node1, 2f32, location.y); + let layout = taffy.layout(node2).unwrap(); + let Layout { size, location, .. } = layout; + assert_eq!(size.width, 50f32, "width of node {:?}. Expected {}. Actual {}", node2, 50f32, size.width); + assert_eq!(size.height, 50f32, "height of node {:?}. Expected {}. Actual {}", node2, 50f32, size.height); + assert_eq!(location.x, 102f32, "x of node {:?}. Expected {}. Actual {}", node2, 102f32, location.x); + assert_eq!(location.y, 2f32, "y of node {:?}. Expected {}. Actual {}", node2, 2f32, location.y); + let layout = taffy.layout(node3).unwrap(); + let Layout { size, location, .. } = layout; + assert_eq!(size.width, 50f32, "width of node {:?}. Expected {}. Actual {}", node3, 50f32, size.width); + assert_eq!(size.height, 50f32, "height of node {:?}. Expected {}. Actual {}", node3, 50f32, size.height); + assert_eq!(location.x, 52f32, "x of node {:?}. Expected {}. Actual {}", node3, 52f32, location.x); + assert_eq!(location.y, 2f32, "y of node {:?}. Expected {}. Actual {}", node3, 2f32, location.y); + let layout = taffy.layout(node4).unwrap(); + let Layout { size, location, .. } = layout; + assert_eq!(size.width, 50f32, "width of node {:?}. Expected {}. Actual {}", node4, 50f32, size.width); + assert_eq!(size.height, 50f32, "height of node {:?}. Expected {}. Actual {}", node4, 50f32, size.height); + assert_eq!(location.x, 2f32, "x of node {:?}. Expected {}. Actual {}", node4, 2f32, location.x); + assert_eq!(location.y, 2f32, "y of node {:?}. Expected {}. Actual {}", node4, 2f32, location.y); +} + +#[test] +#[allow(non_snake_case)] +fn float_simple__content_box() { + #[allow(unused_imports)] + use taffy::{prelude::*, Layout}; + let mut taffy = crate::new_test_tree(); + let node0 = taffy + .new_leaf(taffy::style::Style { + box_sizing: taffy::style::BoxSizing::ContentBox, + float: taffy::style::Float::Right, + size: taffy::geometry::Size { + width: taffy::style::Dimension::from_length(50f32), + height: taffy::style::Dimension::from_length(50f32), + }, + ..Default::default() + }) + .unwrap(); + let node1 = taffy + .new_leaf(taffy::style::Style { + box_sizing: taffy::style::BoxSizing::ContentBox, + float: taffy::style::Float::Right, + size: taffy::geometry::Size { + width: taffy::style::Dimension::from_length(50f32), + height: taffy::style::Dimension::from_length(50f32), + }, + ..Default::default() + }) + .unwrap(); + let node2 = taffy + .new_leaf(taffy::style::Style { + box_sizing: taffy::style::BoxSizing::ContentBox, + float: taffy::style::Float::Right, + size: taffy::geometry::Size { + width: taffy::style::Dimension::from_length(50f32), + height: taffy::style::Dimension::from_length(50f32), + }, + ..Default::default() + }) + .unwrap(); + let node3 = taffy + .new_leaf(taffy::style::Style { + box_sizing: taffy::style::BoxSizing::ContentBox, + float: taffy::style::Float::Right, + size: taffy::geometry::Size { + width: taffy::style::Dimension::from_length(50f32), + height: taffy::style::Dimension::from_length(50f32), + }, + ..Default::default() + }) + .unwrap(); + let node4 = taffy + .new_leaf(taffy::style::Style { + box_sizing: taffy::style::BoxSizing::ContentBox, + float: taffy::style::Float::Right, + size: taffy::geometry::Size { + width: taffy::style::Dimension::from_length(50f32), + height: taffy::style::Dimension::from_length(50f32), + }, + ..Default::default() + }) + .unwrap(); + let node = taffy + .new_with_children( + taffy::style::Style { + display: taffy::style::Display::Block, + box_sizing: taffy::style::BoxSizing::ContentBox, + size: taffy::geometry::Size { width: auto(), height: taffy::style::Dimension::from_length(300f32) }, + border: taffy::geometry::Rect { + left: length(2f32), + right: length(2f32), + top: length(2f32), + bottom: length(2f32), + }, + ..Default::default() + }, + &[node0, node1, node2, node3, node4], + ) + .unwrap(); + taffy.compute_layout_with_measure(node, taffy::geometry::Size::MAX_CONTENT, crate::test_measure_function).unwrap(); + println!("\nComputed tree:"); + taffy.print_tree(node); + println!(); + let layout = taffy.layout(node).unwrap(); + let Layout { size, location, .. } = layout; + assert_eq!(size.width, 254f32, "width of node {:?}. Expected {}. Actual {}", node, 254f32, size.width); + assert_eq!(size.height, 304f32, "height of node {:?}. Expected {}. Actual {}", node, 304f32, size.height); + assert_eq!(location.x, 0f32, "x of node {:?}. Expected {}. Actual {}", node, 0f32, location.x); + assert_eq!(location.y, 0f32, "y of node {:?}. Expected {}. Actual {}", node, 0f32, location.y); + let layout = taffy.layout(node0).unwrap(); + let Layout { size, location, .. } = layout; + assert_eq!(size.width, 50f32, "width of node {:?}. Expected {}. Actual {}", node0, 50f32, size.width); + assert_eq!(size.height, 50f32, "height of node {:?}. Expected {}. Actual {}", node0, 50f32, size.height); + assert_eq!(location.x, 202f32, "x of node {:?}. Expected {}. Actual {}", node0, 202f32, location.x); + assert_eq!(location.y, 2f32, "y of node {:?}. Expected {}. Actual {}", node0, 2f32, location.y); + let layout = taffy.layout(node1).unwrap(); + let Layout { size, location, .. } = layout; + assert_eq!(size.width, 50f32, "width of node {:?}. Expected {}. Actual {}", node1, 50f32, size.width); + assert_eq!(size.height, 50f32, "height of node {:?}. Expected {}. Actual {}", node1, 50f32, size.height); + assert_eq!(location.x, 152f32, "x of node {:?}. Expected {}. Actual {}", node1, 152f32, location.x); + assert_eq!(location.y, 2f32, "y of node {:?}. Expected {}. Actual {}", node1, 2f32, location.y); + let layout = taffy.layout(node2).unwrap(); + let Layout { size, location, .. } = layout; + assert_eq!(size.width, 50f32, "width of node {:?}. Expected {}. Actual {}", node2, 50f32, size.width); + assert_eq!(size.height, 50f32, "height of node {:?}. Expected {}. Actual {}", node2, 50f32, size.height); + assert_eq!(location.x, 102f32, "x of node {:?}. Expected {}. Actual {}", node2, 102f32, location.x); + assert_eq!(location.y, 2f32, "y of node {:?}. Expected {}. Actual {}", node2, 2f32, location.y); + let layout = taffy.layout(node3).unwrap(); + let Layout { size, location, .. } = layout; + assert_eq!(size.width, 50f32, "width of node {:?}. Expected {}. Actual {}", node3, 50f32, size.width); + assert_eq!(size.height, 50f32, "height of node {:?}. Expected {}. Actual {}", node3, 50f32, size.height); + assert_eq!(location.x, 52f32, "x of node {:?}. Expected {}. Actual {}", node3, 52f32, location.x); + assert_eq!(location.y, 2f32, "y of node {:?}. Expected {}. Actual {}", node3, 2f32, location.y); + let layout = taffy.layout(node4).unwrap(); + let Layout { size, location, .. } = layout; + assert_eq!(size.width, 50f32, "width of node {:?}. Expected {}. Actual {}", node4, 50f32, size.width); + assert_eq!(size.height, 50f32, "height of node {:?}. Expected {}. Actual {}", node4, 50f32, size.height); + assert_eq!(location.x, 2f32, "x of node {:?}. Expected {}. Actual {}", node4, 2f32, location.x); + assert_eq!(location.y, 2f32, "y of node {:?}. Expected {}. Actual {}", node4, 2f32, location.y); +} diff --git a/tests/generated/float/mod.rs b/tests/generated/float/mod.rs new file mode 100644 index 000000000..9c41e5ea9 --- /dev/null +++ b/tests/generated/float/mod.rs @@ -0,0 +1 @@ +mod float_simple; diff --git a/tests/generated/mod.rs b/tests/generated/mod.rs index e02bffaf5..609852f02 100644 --- a/tests/generated/mod.rs +++ b/tests/generated/mod.rs @@ -2,6 +2,7 @@ mod block; mod blockflex; mod blockgrid; mod flex; +mod float; mod grid; mod gridflex; mod leaf;