Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Calculating layout with taffy #417

Merged
merged 1 commit into from
Mar 31, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
996 changes: 990 additions & 6 deletions Cargo.lock

Large diffs are not rendered by default.

4 changes: 4 additions & 0 deletions crates/gosub_rendering/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,7 @@ license = "MIT"

[dependencies]
gosub_html5 = { path = "../gosub_html5" }
gosub_styling = { path = "../gosub_styling" }
taffy = "0.4.1"
anyhow = "1.0.81"
regex = "1.10.4"
56 changes: 56 additions & 0 deletions crates/gosub_rendering/src/layout.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
use taffy::prelude::*;

use gosub_html5::node::NodeId as GosubID;
use gosub_styling::render_tree::{RenderNodeData, RenderTree};

use crate::style::get_style_from_node;

pub fn generate_taffy_tree(rt: &mut RenderTree) -> anyhow::Result<(TaffyTree<GosubID>, NodeId)> {
let mut tree: TaffyTree<GosubID> = TaffyTree::with_capacity(rt.nodes.len());

rt.get_root();

let root = add_children_to_tree(rt, &mut tree, rt.root)?;

Ok((tree, root))
}

fn add_children_to_tree(
rt: &mut RenderTree,
tree: &mut TaffyTree<GosubID>,
node_id: GosubID,
) -> anyhow::Result<NodeId> {
let Some(node_children) = rt.get_children(node_id) else {
return Err(anyhow::anyhow!("Node not found {:?}", node_id));
};

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

//clone, so we can drop the borrow of RT, we would be copying the NodeID anyway, so it's not a big deal (only a few bytes)
for child in node_children.clone() {
match add_children_to_tree(rt, tree, child) {
Ok(node) => children.push(node),
Err(e) => eprintln!("Error adding child to tree: {:?}", e),
}
}

let Some(node) = rt.get_node_mut(node_id) else {
return Err(anyhow::anyhow!("Node not found"));
};

let style = get_style_from_node(node);

let node = rt.get_node(node_id).unwrap();
if let RenderNodeData::Text(text) = &node.data {
println!("Text: {:?}", text.text);
println!("Style: {:?}", style.size);
}

let node = tree
.new_with_children(style, &children)
.map_err(|e| anyhow::anyhow!(e.to_string()))?;

tree.set_node_context(node, Some(node_id))?;

Ok(node)
}
2 changes: 2 additions & 0 deletions crates/gosub_rendering/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,6 @@
//! This crate supplies functionality to render CSSOM and DOM trees into a viewable display.
//!

pub mod layout;
pub mod render_tree;
pub mod style;
74 changes: 74 additions & 0 deletions crates/gosub_rendering/src/style.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
mod parse;
mod parse_properties;

use gosub_styling::render_tree::RenderTreeNode;
use taffy::Style;

const SCROLLBAR_WIDTH: f32 = 16.0;

pub fn get_style_from_node(node: &mut RenderTreeNode) -> Style {
let display = parse_properties::parse_display(node);
let overflow = parse_properties::parse_overflow(node);
let position = parse_properties::parse_position(node);
let inset = parse_properties::parse_inset(node);
let size = parse_properties::parse_size(node);
let min_size = parse_properties::parse_min_size(node);
let max_size = parse_properties::parse_max_size(node);
let aspect_ratio = parse_properties::parse_aspect_ratio(node);
let margin = parse_properties::parse_margin(node);
let padding = parse_properties::parse_padding(node);
let border = parse_properties::parse_border(node);
let align_items = parse_properties::parse_align_items(node);
let align_self = parse_properties::parse_align_self(node);
let justify_items = parse_properties::parse_justify_items(node);
let justify_self = parse_properties::parse_justify_self(node);
let align_content = parse_properties::parse_align_content(node);
let justify_content = parse_properties::parse_justify_content(node);
let gap = parse_properties::parse_gap(node);
let flex_direction = parse_properties::parse_flex_direction(node);
let flex_wrap = parse_properties::parse_flex_wrap(node);
let flex_basis = parse_properties::parse_flex_basis(node);
let flex_grow = parse_properties::parse_flex_grow(node);
let flex_shrink = parse_properties::parse_flex_shrink(node);
let grid_template_rows = parse_properties::parse_grid_template_rows(node);
let grid_template_columns = parse_properties::parse_grid_template_columns(node);
let grid_auto_rows = parse_properties::parse_grid_auto_rows(node);
let grid_auto_columns = parse_properties::parse_grid_auto_columns(node);
let grid_auto_flow = parse_properties::parse_grid_auto_flow(node);
let grid_row = parse_properties::parse_grid_row(node);
let grid_column = parse_properties::parse_grid_column(node);

Style {
display,
overflow,
scrollbar_width: SCROLLBAR_WIDTH,
position,
inset,
size,
min_size,
max_size,
aspect_ratio,
margin,
padding,
border,
align_items,
align_self,
justify_items,
justify_self,
align_content,
justify_content,
gap,
flex_direction,
flex_wrap,
flex_basis,
flex_grow,
flex_shrink,
grid_template_rows,
grid_template_columns,
grid_auto_rows,
grid_auto_columns,
grid_auto_flow,
grid_row,
grid_column,
}
}
188 changes: 188 additions & 0 deletions crates/gosub_rendering/src/style/parse.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,188 @@
use taffy::prelude::*;
use taffy::{
AlignContent, AlignItems, Dimension, GridPlacement, LengthPercentage, LengthPercentageAuto,
TrackSizingFunction,
};

use gosub_styling::css_values::CssValue;
use gosub_styling::render_tree::{RenderNodeData, RenderTreeNode};

pub(crate) fn parse_len(node: &mut RenderTreeNode, name: &str) -> LengthPercentage {
let Some(property) = node.get_property(name) else {
return LengthPercentage::Length(0.0);
};

property.compute_value();

match &property.actual {
CssValue::Percentage(value) => LengthPercentage::Percent(*value),
CssValue::Unit(..) => LengthPercentage::Length(property.actual.unit_to_px()),
CssValue::String(_) => LengthPercentage::Length(property.actual.unit_to_px()), //HACK
_ => LengthPercentage::Length(0.0),
}
}

pub(crate) fn parse_len_auto(node: &mut RenderTreeNode, name: &str) -> LengthPercentageAuto {
let Some(property) = node.get_property(name) else {
return LengthPercentageAuto::Auto;
};

property.compute_value();

match &property.actual {
CssValue::String(value) => match value.as_str() {
"auto" => LengthPercentageAuto::Auto,
_ => LengthPercentageAuto::Length(property.actual.unit_to_px()), //HACK
},
CssValue::Percentage(value) => LengthPercentageAuto::Percent(*value),
CssValue::Unit(..) => LengthPercentageAuto::Length(property.actual.unit_to_px()),
_ => LengthPercentageAuto::Auto,
}
}

pub(crate) fn parse_dimension(node: &mut RenderTreeNode, name: &str) -> Dimension {
let mut auto = Dimension::Auto;
if let RenderNodeData::Text(text) = &node.data {
if name == "width" {
auto = Dimension::Length(text.width);
} else if name == "height" {
auto = Dimension::Length(text.height);
}
}

let Some(property) = node.get_property(name) else {
return auto;
};

property.compute_value();

if name == "width" {
println!("Width: {:?}", property.actual);
}

match &property.actual {
CssValue::String(value) => match value.as_str() {
"auto" => auto,
s if s.ends_with('%') => {
let value = s.trim_end_matches('%').parse::<f32>().unwrap_or(0.0);
Dimension::Percent(value)
}
_ => Dimension::Length(property.actual.unit_to_px()), //HACK
},
CssValue::Percentage(value) => Dimension::Percent(*value),
CssValue::Unit(..) => Dimension::Length(property.actual.unit_to_px()),
_ => auto,
}
}

pub(crate) fn parse_align_i(node: &mut RenderTreeNode, name: &str) -> Option<AlignItems> {
let display = node.get_property(name)?;
display.compute_value();

let CssValue::String(ref value) = display.actual else {
return None;
};

match value.as_str() {
"start" => Some(AlignItems::Start),
"end" => Some(AlignItems::End),
"flex-start" => Some(AlignItems::FlexStart),
"flex-end" => Some(AlignItems::FlexEnd),
"center" => Some(AlignItems::Center),
"baseline" => Some(AlignItems::Baseline),
"stretch" => Some(AlignItems::Stretch),
_ => None,
}
}

pub(crate) fn parse_align_c(node: &mut RenderTreeNode, name: &str) -> Option<AlignContent> {
let display = node.get_property(name)?;

display.compute_value();

let CssValue::String(ref value) = display.actual else {
return None;
};

match value.as_str() {
"start" => Some(AlignContent::Start),
"end" => Some(AlignContent::End),
"flex-start" => Some(AlignContent::FlexStart),
"flex-end" => Some(AlignContent::FlexEnd),
"center" => Some(AlignContent::Center),
"stretch" => Some(AlignContent::Stretch),
"space-between" => Some(AlignContent::SpaceBetween),
"space-around" => Some(AlignContent::SpaceAround),
_ => None,
}
}

pub(crate) fn parse_tracking_sizing_function(
node: &mut RenderTreeNode,
name: &str,
) -> Vec<TrackSizingFunction> {
let Some(display) = node.get_property(name) else {
return Vec::new();
};

display.compute_value();

let CssValue::String(ref _value) = display.actual else {
return Vec::new();
};

Vec::new() //TODO: Implement this
}

#[allow(dead_code)]
pub(crate) fn parse_non_repeated_tracking_sizing_function(
_node: &mut RenderTreeNode,
_name: &str,
) -> NonRepeatedTrackSizingFunction {
todo!("implement parse_non_repeated_tracking_sizing_function")
}

pub(crate) fn parse_grid_auto(
node: &mut RenderTreeNode,
name: &str,
) -> Vec<NonRepeatedTrackSizingFunction> {
let Some(display) = node.get_property(name) else {
return Vec::new();
};

display.compute_value();

let CssValue::String(ref _value) = display.actual else {
return Vec::new();
};

Vec::new() //TODO: Implement this
}

pub(crate) fn parse_grid_placement(node: &mut RenderTreeNode, name: &str) -> GridPlacement {
let Some(display) = node.get_property(name) else {
return GridPlacement::Auto;
};

display.compute_value();

match &display.actual {
CssValue::String(value) => {
if value.starts_with("span") {
let value = value.trim_start_matches("span").trim();

if let Ok(value) = value.parse::<u16>() {
GridPlacement::from_span(value)
} else {
GridPlacement::Auto
}
} else if let Ok(value) = value.parse::<i16>() {
GridPlacement::from_line_index(value)
} else {
GridPlacement::Auto
}
}
CssValue::Number(value) => GridPlacement::from_line_index(*value as i16),
_ => GridPlacement::Auto,
}
}
Loading
Loading