diff --git a/packages/blitz-dom/src/layout/inline.rs b/packages/blitz-dom/src/layout/inline.rs new file mode 100644 index 00000000..a9f9c77b --- /dev/null +++ b/packages/blitz-dom/src/layout/inline.rs @@ -0,0 +1,215 @@ +use taffy::{ + compute_leaf_layout, AvailableSpace, LayoutPartialTree as _, MaybeMath as _, MaybeResolve as _, + NodeId, Position, ResolveOrZero as _, Size, +}; + +use crate::Document; + +impl Document { + pub(crate) fn compute_inline_layout( + &mut self, + node_id: usize, + inputs: taffy::tree::LayoutInput, + ) -> taffy::LayoutOutput { + let scale = self.viewport.scale(); + + // Take inline layout to satisfy borrow checker + let mut inline_layout = self.nodes[node_id] + .raw_dom_data + .downcast_element_mut() + .unwrap() + .take_inline_layout() + .unwrap(); + + // TODO: eliminate clone + let style = self.nodes[node_id].style.clone(); + + let output = compute_leaf_layout(inputs, &style, |_known_dimensions, available_space| { + // Short circuit if inline context contains no text or inline boxes + if inline_layout.text.is_empty() && inline_layout.layout.inline_boxes().is_empty() { + return Size::ZERO; + } + + // Compute size of inline boxes + let child_inputs = taffy::tree::LayoutInput { + known_dimensions: Size::NONE, + available_space, + parent_size: available_space.into_options(), + ..inputs + }; + for ibox in inline_layout.layout.inline_boxes_mut() { + let style = &self.nodes[ibox.id as usize].style; + let margin = style.margin.resolve_or_zero(inputs.parent_size); + + if style.position == Position::Absolute { + ibox.width = 0.0; + ibox.height = 0.0; + } else { + let output = self.compute_child_layout(NodeId::from(ibox.id), child_inputs); + ibox.width = (margin.left + margin.right + output.size.width) * scale; + ibox.height = (margin.top + margin.bottom + output.size.height) * scale; + } + } + + // Perform inline layout + let max_advance = match available_space.width { + AvailableSpace::Definite(px) => Some(px * scale), + AvailableSpace::MinContent => Some(0.0), + AvailableSpace::MaxContent => None, + }; + + let alignment = self.nodes[node_id] + .primary_styles() + .map(|s| { + use parley::layout::Alignment; + use style::values::specified::TextAlignKeyword; + + match s.clone_text_align() { + TextAlignKeyword::Start => Alignment::Start, + TextAlignKeyword::Left => Alignment::Start, + TextAlignKeyword::Right => Alignment::End, + TextAlignKeyword::Center => Alignment::Middle, + TextAlignKeyword::Justify => Alignment::Justified, + TextAlignKeyword::End => Alignment::End, + TextAlignKeyword::MozCenter => Alignment::Middle, + TextAlignKeyword::MozLeft => Alignment::Start, + TextAlignKeyword::MozRight => Alignment::End, + } + }) + .unwrap_or(parley::layout::Alignment::Start); + + inline_layout.layout.break_all_lines(max_advance); + + let padding = style.padding.resolve_or_zero(inputs.parent_size); + let border = style.border.resolve_or_zero(inputs.parent_size); + + let container_pb = padding + border; + let pbw = container_pb.horizontal_components().sum() * scale; + + // Align layout + let alignment_width = inputs + .known_dimensions + .width + .map(|w| (w * scale) - pbw) + .unwrap_or_else(|| { + let computed_width = inline_layout.layout.width(); + let style_width = style + .size + .width + .maybe_resolve(inputs.parent_size.width) + .map(|w| w * scale); + let min_width = style + .min_size + .width + .maybe_resolve(inputs.parent_size.width) + .map(|w| w * scale); + let max_width = style + .max_size + .width + .maybe_resolve(inputs.parent_size.width) + .map(|w| w * scale); + + (style_width) + .unwrap_or(computed_width + pbw) + .max(computed_width) + .maybe_clamp(min_width, max_width) + - pbw + }); + + inline_layout.layout.align(Some(alignment_width), alignment); + + // Store sizes and positions of inline boxes + for line in inline_layout.layout.lines() { + for item in line.items() { + if let parley::layout::PositionedLayoutItem::InlineBox(ibox) = item { + let node = &mut self.nodes[ibox.id as usize]; + let padding = node.style.padding.resolve_or_zero(child_inputs.parent_size); + let border = node.style.border.resolve_or_zero(child_inputs.parent_size); + let margin = node.style.margin.resolve_or_zero(child_inputs.parent_size); + + // Resolve inset + let left = node + .style + .inset + .left + .maybe_resolve(child_inputs.parent_size.width); + let right = node + .style + .inset + .right + .maybe_resolve(child_inputs.parent_size.width); + let top = node + .style + .inset + .top + .maybe_resolve(child_inputs.parent_size.height); + let bottom = node + .style + .inset + .bottom + .maybe_resolve(child_inputs.parent_size.height); + + if node.style.position == Position::Absolute { + let output = + self.compute_child_layout(NodeId::from(ibox.id), child_inputs); + + let layout = &mut self.nodes[ibox.id as usize].unrounded_layout; + layout.size = output.size; + + // TODO: Implement absolute positioning + layout.location.x = left + .or_else(|| { + child_inputs + .parent_size + .width + .zip(right) + .map(|(w, r)| w - r) + }) + .unwrap_or(0.0); + layout.location.y = top + .or_else(|| { + child_inputs + .parent_size + .height + .zip(bottom) + .map(|(w, r)| w - r) + }) + .unwrap_or(0.0); + + layout.padding = padding; //.map(|p| p / scale); + layout.border = border; //.map(|p| p / scale); + } else { + let layout = &mut node.unrounded_layout; + layout.size.width = (ibox.width / scale) - margin.left - margin.right; + layout.size.height = (ibox.height / scale) - margin.top - margin.bottom; + layout.location.x = (ibox.x / scale) + margin.left + container_pb.left; + layout.location.y = (ibox.y / scale) + margin.top + container_pb.top; + layout.padding = padding; //.map(|p| p / scale); + layout.border = border; //.map(|p| p / scale); + } + } + } + } + + // println!("INLINE LAYOUT FOR {:?}. max_advance: {:?}", node_id, max_advance); + // dbg!(&inline_layout.text); + // println!("Computed: w: {} h: {}", inline_layout.layout.width(), inline_layout.layout.height()); + // println!("known_dimensions: w: {:?} h: {:?}", inputs.known_dimensions.width, inputs.known_dimensions.height); + // println!("\n"); + + inputs.known_dimensions.unwrap_or(taffy::Size { + width: inline_layout.layout.width() / scale, + height: inline_layout.layout.height() / scale, + }) + }); + + // Put layout back + self.nodes[node_id] + .raw_dom_data + .downcast_element_mut() + .unwrap() + .inline_layout_data = Some(inline_layout); + + output + } +} diff --git a/packages/blitz-dom/src/layout/mod.rs b/packages/blitz-dom/src/layout/mod.rs index ea81b2c6..c2edaf0c 100644 --- a/packages/blitz-dom/src/layout/mod.rs +++ b/packages/blitz-dom/src/layout/mod.rs @@ -15,11 +15,12 @@ use std::cell::Ref; use std::sync::Arc; use taffy::{ compute_block_layout, compute_cached_layout, compute_flexbox_layout, compute_grid_layout, - compute_leaf_layout, prelude::*, Cache, FlexDirection, LayoutPartialTree, MaybeMath as _, - MaybeResolve, NodeId, ResolveOrZero, RoundTree, Size, Style, TraversePartialTree, TraverseTree, + compute_leaf_layout, prelude::*, Cache, FlexDirection, LayoutPartialTree, NodeId, + ResolveOrZero, RoundTree, Style, TraversePartialTree, TraverseTree, }; pub(crate) mod construct; +pub(crate) mod inline; pub(crate) mod table; use self::table::TableTreeWrapper; @@ -261,7 +262,7 @@ impl LayoutPartialTree for Document { } if node.is_inline_root { - return tree.compute_inline_layout(node_id, inputs); + return tree.compute_inline_layout(usize::from(node_id), inputs); } // The default CSS file will set @@ -340,215 +341,6 @@ impl taffy::LayoutGridContainer for Document { } } -impl Document { - fn compute_inline_layout( - &mut self, - node_id: NodeId, - inputs: taffy::tree::LayoutInput, - ) -> taffy::LayoutOutput { - let scale = self.viewport.scale(); - - // Take inline layout to satisfy borrow checker - let mut inline_layout = self.nodes[usize::from(node_id)] - .raw_dom_data - .downcast_element_mut() - .unwrap() - .take_inline_layout() - .unwrap(); - - // TODO: eliminate clone - let style = self.nodes[usize::from(node_id)].style.clone(); - - let output = compute_leaf_layout(inputs, &style, |_known_dimensions, available_space| { - // Short circuit if inline context contains no text or inline boxes - if inline_layout.text.is_empty() && inline_layout.layout.inline_boxes().is_empty() { - return Size::ZERO; - } - - // Compute size of inline boxes - let child_inputs = taffy::tree::LayoutInput { - known_dimensions: Size::NONE, - available_space, - parent_size: available_space.into_options(), - ..inputs - }; - for ibox in inline_layout.layout.inline_boxes_mut() { - let style = &self.nodes[ibox.id as usize].style; - let margin = style.margin.resolve_or_zero(inputs.parent_size); - - if style.position == Position::Absolute { - ibox.width = 0.0; - ibox.height = 0.0; - } else { - let output = self.compute_child_layout(NodeId::from(ibox.id), child_inputs); - ibox.width = (margin.left + margin.right + output.size.width) * scale; - ibox.height = (margin.top + margin.bottom + output.size.height) * scale; - } - } - - // Perform inline layout - let max_advance = match available_space.width { - AvailableSpace::Definite(px) => Some(px * scale), - AvailableSpace::MinContent => Some(0.0), - AvailableSpace::MaxContent => None, - }; - - let alignment = self.nodes[usize::from(node_id)] - .primary_styles() - .map(|s| { - use parley::layout::Alignment; - use style::values::specified::TextAlignKeyword; - - match s.clone_text_align() { - TextAlignKeyword::Start => Alignment::Start, - TextAlignKeyword::Left => Alignment::Start, - TextAlignKeyword::Right => Alignment::End, - TextAlignKeyword::Center => Alignment::Middle, - TextAlignKeyword::Justify => Alignment::Justified, - TextAlignKeyword::End => Alignment::End, - TextAlignKeyword::MozCenter => Alignment::Middle, - TextAlignKeyword::MozLeft => Alignment::Start, - TextAlignKeyword::MozRight => Alignment::End, - } - }) - .unwrap_or(parley::layout::Alignment::Start); - - inline_layout.layout.break_all_lines(max_advance); - - let padding = style.padding.resolve_or_zero(inputs.parent_size); - let border = style.border.resolve_or_zero(inputs.parent_size); - - let container_pb = padding + border; - let pbw = container_pb.horizontal_components().sum() * scale; - - // Align layout - let alignment_width = inputs - .known_dimensions - .width - .map(|w| (w * scale) - pbw) - .unwrap_or_else(|| { - let computed_width = inline_layout.layout.width(); - let style_width = style - .size - .width - .maybe_resolve(inputs.parent_size.width) - .map(|w| w * scale); - let min_width = style - .min_size - .width - .maybe_resolve(inputs.parent_size.width) - .map(|w| w * scale); - let max_width = style - .max_size - .width - .maybe_resolve(inputs.parent_size.width) - .map(|w| w * scale); - - (style_width) - .unwrap_or(computed_width + pbw) - .max(computed_width) - .maybe_clamp(min_width, max_width) - - pbw - }); - - inline_layout.layout.align(Some(alignment_width), alignment); - - // Store sizes and positions of inline boxes - for line in inline_layout.layout.lines() { - for item in line.items() { - if let parley::layout::PositionedLayoutItem::InlineBox(ibox) = item { - let node = &mut self.nodes[ibox.id as usize]; - let padding = node.style.padding.resolve_or_zero(child_inputs.parent_size); - let border = node.style.border.resolve_or_zero(child_inputs.parent_size); - let margin = node.style.margin.resolve_or_zero(child_inputs.parent_size); - - // Resolve inset - let left = node - .style - .inset - .left - .maybe_resolve(child_inputs.parent_size.width); - let right = node - .style - .inset - .right - .maybe_resolve(child_inputs.parent_size.width); - let top = node - .style - .inset - .top - .maybe_resolve(child_inputs.parent_size.height); - let bottom = node - .style - .inset - .bottom - .maybe_resolve(child_inputs.parent_size.height); - - if node.style.position == Position::Absolute { - let output = - self.compute_child_layout(NodeId::from(ibox.id), child_inputs); - - let layout = &mut self.nodes[ibox.id as usize].unrounded_layout; - layout.size = output.size; - - // TODO: Implement absolute positioning - layout.location.x = left - .or_else(|| { - child_inputs - .parent_size - .width - .zip(right) - .map(|(w, r)| w - r) - }) - .unwrap_or(0.0); - layout.location.y = top - .or_else(|| { - child_inputs - .parent_size - .height - .zip(bottom) - .map(|(w, r)| w - r) - }) - .unwrap_or(0.0); - - layout.padding = padding; //.map(|p| p / scale); - layout.border = border; //.map(|p| p / scale); - } else { - let layout = &mut node.unrounded_layout; - layout.size.width = (ibox.width / scale) - margin.left - margin.right; - layout.size.height = (ibox.height / scale) - margin.top - margin.bottom; - layout.location.x = (ibox.x / scale) + margin.left + container_pb.left; - layout.location.y = (ibox.y / scale) + margin.top + container_pb.top; - layout.padding = padding; //.map(|p| p / scale); - layout.border = border; //.map(|p| p / scale); - } - } - } - } - - // println!("INLINE LAYOUT FOR {:?}. max_advance: {:?}", node_id, max_advance); - // dbg!(&inline_layout.text); - // println!("Computed: w: {} h: {}", inline_layout.layout.width(), inline_layout.layout.height()); - // println!("known_dimensions: w: {:?} h: {:?}", inputs.known_dimensions.width, inputs.known_dimensions.height); - // println!("\n"); - - inputs.known_dimensions.unwrap_or(taffy::Size { - width: inline_layout.layout.width() / scale, - height: inline_layout.layout.height() / scale, - }) - }); - - // Put layout back - self.nodes[usize::from(node_id)] - .raw_dom_data - .downcast_element_mut() - .unwrap() - .inline_layout_data = Some(inline_layout); - - output - } -} - impl RoundTree for Document { fn get_unrounded_layout(&self, node_id: NodeId) -> &Layout { &self.node_from_id(node_id).unrounded_layout