Skip to content

Commit

Permalink
Merge pull request #543 from Sharktheone/layout/taffy-inline
Browse files Browse the repository at this point in the history
Support Inline elements
  • Loading branch information
Sharktheone authored Aug 8, 2024
2 parents 09a9ccf + 65f6b6b commit 90a039e
Show file tree
Hide file tree
Showing 10 changed files with 319 additions and 66 deletions.
4 changes: 4 additions & 0 deletions crates/gosub_html5/src/parser/document.rs
Original file line number Diff line number Diff line change
Expand Up @@ -287,6 +287,10 @@ impl Document {
DocumentHandle(Rc::new(RefCell::new(Self::new(location))))
}

pub fn peek_next_id(&self) -> NodeId {
self.arena.peek_next_id()
}

/// Fast clone of a lightweight reference-counted handle for the document. This is a shallow
/// clone, and different handles will see the same underlying document.
pub fn clone(handle: &DocumentHandle) -> DocumentHandle {
Expand Down
9 changes: 8 additions & 1 deletion crates/gosub_render_backend/src/layout.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,9 @@ pub trait LayoutTree<L: Layouter>: Sized {
fn children(&self, id: Self::NodeId) -> Option<Vec<Self::NodeId>>;
fn contains(&self, id: &Self::NodeId) -> bool;
fn child_count(&self, id: Self::NodeId) -> usize;
fn parent_id(&self, id: Self::NodeId) -> Option<Self::NodeId>;
fn get_cache(&self, id: Self::NodeId) -> Option<&L::Cache>;
fn get_layout(&self, id: Self::NodeId) -> Option<&L::Layout>;

fn get_cache_mut(&mut self, id: Self::NodeId) -> Option<&mut L::Cache>;
fn get_layout_mut(&mut self, id: Self::NodeId) -> Option<&mut L::Layout>;
fn set_cache(&mut self, id: Self::NodeId, cache: L::Cache);
Expand All @@ -27,6 +27,9 @@ pub trait LayoutTree<L: Layouter>: Sized {
pub trait Layouter: Sized + Clone {
type Cache: Default;
type Layout: Layout;

const COLLAPSE_INLINE: bool;

fn layout<LT: LayoutTree<Self>>(
&self,
tree: &mut LT,
Expand Down Expand Up @@ -108,6 +111,10 @@ pub trait Node {

fn get_property(&mut self, name: &str) -> Option<&mut Self::Property>; //TODO: this needs to be more generic...
fn text_size(&mut self) -> Option<Size>;

/// This can only return true if the `Layout::COLLAPSE_INLINE` is set true for the layouter
///
fn is_anon_inline_parent(&self) -> bool;
}

pub trait CssProperty {
Expand Down
2 changes: 1 addition & 1 deletion crates/gosub_renderer/src/draw.rs
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ impl<B: RenderBackend, L: Layouter> SceneDrawer<B, L, RenderTree<B, L>> for Tree
rect: bg,
transform: None,
radius: None,
brush: Brush::color(Color::MAGENTA),
brush: Brush::color(Color::BLACK),
brush_transform: None,
border: None,
};
Expand Down
127 changes: 126 additions & 1 deletion crates/gosub_styling/src/render_tree.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ pub struct RenderTree<B: RenderBackend, L: Layouter> {
pub nodes: HashMap<NodeId, RenderTreeNode<B, L>>,
pub root: NodeId,
pub dirty: bool,
next_id: NodeId,
}

#[allow(unused)]
Expand All @@ -40,6 +41,10 @@ impl<B: RenderBackend, L: Layouter> LayoutTree<L> for RenderTree<B, L> {
self.child_count(id)
}

fn parent_id(&self, id: Self::NodeId) -> Option<Self::NodeId> {
self.get_node(id).and_then(|node| node.parent)
}

fn get_cache(&self, id: Self::NodeId) -> Option<&L::Cache> {
self.get_node(id).map(|node| &node.cache)
}
Expand Down Expand Up @@ -90,6 +95,7 @@ impl<B: RenderBackend, L: Layouter> RenderTree<B, L> {
nodes: HashMap::with_capacity(capacity),
root: NodeId::root(),
dirty: false,
next_id: NodeId::from(1u64),
};

tree.insert_node(
Expand Down Expand Up @@ -221,7 +227,6 @@ impl<B: RenderBackend, L: Layouter> RenderTree<B, L> {
let mut render_tree = RenderTree::with_capacity(document.get().count_nodes());

render_tree.generate_from(document);
render_tree.remove_unrenderable_nodes();

render_tree
}
Expand Down Expand Up @@ -317,6 +322,16 @@ impl<B: RenderBackend, L: Layouter> RenderTree<B, L> {

self.nodes.insert(current_node_id, render_tree_node);
}

self.next_id = document.get().peek_next_id();

self.remove_unrenderable_nodes();

if L::COLLAPSE_INLINE {
self.collapse_inline(self.root);
}

self.print_tree();
}

/// Removes all unrenderable nodes from the render tree
Expand Down Expand Up @@ -365,13 +380,108 @@ impl<B: RenderBackend, L: Layouter> RenderTree<B, L> {
self.delete_node(&id);
}
}

/// Collapse all inline elements / wrap inline elements with anonymous boxes
fn collapse_inline(&mut self, node_id: NodeId) {
let Some(node) = self.nodes.get(&node_id) else {
eprintln!("Node not found: {node_id}");
return;
};

let mut inline_wrapper = None;

for child_id in node.children.clone() {
let Some(child) = self.nodes.get_mut(&child_id) else {
eprintln!("Child not found: {child_id}");
continue;
};

if child.is_inline() {
if let Some(wrapper_id) = inline_wrapper {
let old_parent = child.parent;
child.parent = Some(wrapper_id);

let Some(wrapper) = self.nodes.get_mut(&wrapper_id) else {
eprintln!("Wrapper not found: {wrapper_id}");
continue;
};

wrapper.children.push(child_id);

if let Some(old_parent) = old_parent {
let Some(old_parent) = self.nodes.get_mut(&old_parent) else {
eprintln!("Old parent not found: {old_parent}");
continue;
};

old_parent.children.retain(|id| *id != child_id);
}
} else {
let wrapper_node = RenderTreeNode {
id: self.next_id,
properties: CssProperties::new(),
css_dirty: true,
children: vec![child_id],
parent: Some(node_id),
name: "#anonymous".to_string(),
namespace: None,
data: RenderNodeData::AnonymousInline,
cache: L::Cache::default(),
layout: L::Layout::default(),
};
let id = wrapper_node.id;

self.next_id = self.next_id.next();

let old_parent = child.parent;
child.parent = Some(id);
inline_wrapper = Some(id);

self.nodes.insert(id, wrapper_node);

if let Some(old_parent) = old_parent {
let Some(old_parent) = self.nodes.get_mut(&old_parent) else {
eprintln!("Old parent not found: {old_parent}");
continue;
};

if let Some(pos) = old_parent.children.iter().position(|id| *id == child_id)
{
old_parent.children[pos] = id;
}
}
}
} else {
inline_wrapper = None;
}

self.collapse_inline(child_id)
}
}

pub fn print_tree(&self) {
self.print_tree_from(self.root, 0);
}

fn print_tree_from(&self, node_id: NodeId, depth: usize) {
let Some(node) = self.nodes.get(&node_id) else {
return;
};
let indent = " ".repeat(depth);
println!("{indent}{node_id}: {}", node.name);

for child_id in &node.children {
self.print_tree_from(*child_id, depth + 1);
}
}
}

#[derive(Debug)]
pub enum RenderNodeData<B: RenderBackend> {
Document,
Element(Box<ElementData>),
Text(Box<TextData<B>>),
AnonymousInline,
}

pub struct TextData<B: RenderBackend> {
Expand Down Expand Up @@ -498,6 +608,17 @@ impl<B: RenderBackend, L: Layouter> RenderTreeNode<B, L> {
_ => None,
}
}

pub fn is_inline(&mut self) -> bool {
if matches!(self.data, RenderNodeData::Text(_)) {
return true;
}

return self
.properties
.get("display")
.map_or(false, |prop| prop.compute_value().to_string() == "inline");
}
}

impl<B: RenderBackend, L: Layouter> Node for RenderTreeNode<B, L> {
Expand All @@ -514,6 +635,10 @@ impl<B: RenderBackend, L: Layouter> Node for RenderTreeNode<B, L> {
None
}
}

fn is_anon_inline_parent(&self) -> bool {
matches!(self.data, RenderNodeData::AnonymousInline)
}
}

impl gosub_render_backend::layout::CssProperty for CssProperty {
Expand Down
1 change: 1 addition & 0 deletions crates/gosub_taffy/src/compute.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
pub mod inline;
94 changes: 94 additions & 0 deletions crates/gosub_taffy/src/compute/inline.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
use taffy::{
AvailableSpace, CollapsibleMarginSet, Layout, LayoutInput, LayoutOutput, LayoutPartialTree,
NodeId, Point, ResolveOrZero, RunMode, Size,
};

use gosub_render_backend::layout::LayoutTree;

use crate::{LayoutDocument, TaffyLayouter};

pub fn compute_inline_layout<LT: LayoutTree<TaffyLayouter>>(
tree: &mut LayoutDocument<LT>,
nod_id: LT::NodeId,
mut layout_input: LayoutInput,
) -> LayoutOutput {
layout_input.known_dimensions = Size::NONE;
layout_input.run_mode = RunMode::PerformLayout; //TODO: We should respect the run mode

let Some(children) = tree.0.children(nod_id) else {
return LayoutOutput::HIDDEN;
};

let mut outputs = Vec::with_capacity(children.len());

let mut height = 0.0f32;
for child in &children {
let node_id = NodeId::from((*child).into());

let out = tree.compute_child_layout(node_id, layout_input);

let style = tree.get_style(node_id);

let margin = style.margin.resolve_or_zero(layout_input.parent_size);

let child_height = out.size.height + margin.top + margin.bottom;

height = height.max(child_height);

outputs.push(out);
}

let mut width = 0.0f32;
for (child, out) in children.into_iter().zip(outputs.into_iter()) {
let node_id = NodeId::from(child.into());

let style = tree.get_style(node_id);

let location = Point {
x: width,
y: height - out.size.height,
};

let border = style.border.resolve_or_zero(layout_input.parent_size);
let padding = style.padding.resolve_or_zero(layout_input.parent_size);

width += out.size.width + border.left + border.right + padding.left + padding.right;

tree.set_unrounded_layout(
node_id,
&Layout {
size: out.size,
content_size: out.content_size,
order: 0,
location,
border,
padding,
scrollbar_size: Size::ZERO, //TODO
},
);
}

let content_size = Size { width, height };

let mut size = content_size;

if let AvailableSpace::Definite(width) = layout_input.available_space.width {
size.width = size.width.min(width);
}

if let AvailableSpace::Definite(height) = layout_input.available_space.height {
size.height = size.height.min(height);
}

dbg!(size);
dbg!(content_size);

LayoutOutput {
size,
content_size,
first_baselines: Point::NONE,
top_margin: CollapsibleMarginSet::ZERO,
bottom_margin: CollapsibleMarginSet::ZERO,
margins_can_collapse_through: false,
}
}
Loading

0 comments on commit 90a039e

Please sign in to comment.