diff --git a/Cargo.lock b/Cargo.lock index 7a29f0dda..245b05ed8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1529,6 +1529,7 @@ dependencies = [ "gosub_rendering", "gosub_shared", "gosub_styling", + "gosub_taffy", "gosub_testing", "gosub_useragent", "gosub_v8", @@ -1618,7 +1619,6 @@ dependencies = [ "gosub_shared", "gosub_styling", "image", - "taffy", "url", "wasm-bindgen-futures", "web-sys", @@ -1634,7 +1634,6 @@ dependencies = [ "gosub_styling", "regex", "rstar", - "taffy", ] [[package]] @@ -1683,6 +1682,18 @@ dependencies = [ "usvg", ] +[[package]] +name = "gosub_taffy" +version = "0.1.0" +dependencies = [ + "anyhow", + "gosub_render_backend", + "gosub_shared", + "gosub_styling", + "regex", + "taffy", +] + [[package]] name = "gosub_testing" version = "0.1.0" diff --git a/Cargo.toml b/Cargo.toml index c4ec4999b..f80668293 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -35,6 +35,7 @@ gosub_renderer = { path = "./crates/gosub_renderer", features = [] } gosub_render_backend = { path = "./crates/gosub_render_backend", features = [] } gosub_vello = { path = "./crates/gosub_vello", features = [] } gosub_useragent = { path = "./crates/gosub_useragent", features = [] } +gosub_taffy = { path = "./crates/gosub_taffy", features = [] } serde = { version = "1.0", features = ["derive"] } serde_derive = "1.0" derive_more = "0.99" diff --git a/crates/gosub_html5/src/node.rs b/crates/gosub_html5/src/node.rs index 0a4f08bff..660fd5ba9 100644 --- a/crates/gosub_html5/src/node.rs +++ b/crates/gosub_html5/src/node.rs @@ -63,6 +63,20 @@ impl From for NodeId { } } +impl From for NodeId { + /// Converts a u64 into a NodeId + fn from(value: u64) -> Self { + Self(value as usize) + } +} + +impl From for u64 { + /// Converts a NodeId into a u64 + fn from(value: NodeId) -> Self { + value.0 as u64 + } +} + impl Default for &NodeId { /// Returns the default NodeId, which is 0 fn default() -> Self { diff --git a/crates/gosub_html5/src/node/arena.rs b/crates/gosub_html5/src/node/arena.rs index 6791e88b2..d063b9a0d 100644 --- a/crates/gosub_html5/src/node/arena.rs +++ b/crates/gosub_html5/src/node/arena.rs @@ -88,7 +88,7 @@ mod tests { let id = document.arena.register_node(node); assert_eq!(document.arena.nodes.len(), 1); - assert_eq!(document.arena.next_id, 1.into()); + assert_eq!(document.arena.next_id, 1usize.into()); assert_eq!(id, NodeId::default()); } diff --git a/crates/gosub_html5/src/parser.rs b/crates/gosub_html5/src/parser.rs index 2d2b30e3b..774239bfc 100644 --- a/crates/gosub_html5/src/parser.rs +++ b/crates/gosub_html5/src/parser.rs @@ -4561,7 +4561,7 @@ mod test { let binding = document.get(); // document -> html -> head -> body -> div - let div = binding.get_node_by_id(4.into()).unwrap(); + let div = binding.get_node_by_id(4usize.into()).unwrap(); let NodeData::Element(element) = &div.data else { panic!() @@ -4593,7 +4593,7 @@ mod test { let binding = document.get(); // document -> html -> head -> body -> div - let div = binding.get_node_by_id(4.into()).unwrap(); + let div = binding.get_node_by_id(4usize.into()).unwrap(); let NodeData::Element(element) = &div.data else { panic!() @@ -4643,7 +4643,7 @@ mod test { // we are expecting the div (ID: 4) and p would be ignored let doc_read = document.get(); let div = doc_read.get_node_by_named_id("myid").unwrap(); - assert_eq!(div.id, NodeId::from(4)); + assert_eq!(div.id, NodeId::from(4usize)); assert_eq!(div.name, "div"); } } diff --git a/crates/gosub_html5/src/parser/document.rs b/crates/gosub_html5/src/parser/document.rs index fc47bf5d4..7938144ab 100755 --- a/crates/gosub_html5/src/parser/document.rs +++ b/crates/gosub_html5/src/parser/document.rs @@ -1058,7 +1058,9 @@ mod tests { let node3 = Node::new_element(&document, "div3", HashMap::new(), HTML_NAMESPACE); let node3_1 = Node::new_element(&document, "div3_1", HashMap::new(), HTML_NAMESPACE); - let parent_id = document.get_mut().add_node(parent, NodeId::from(0), None); + let parent_id = document + .get_mut() + .add_node(parent, NodeId::from(0usize), None); let node1_id = document.get_mut().add_node(node1, parent_id, None); let node2_id = document.get_mut().add_node(node2, parent_id, None); let node3_id = document.get_mut().add_node(node3, parent_id, None); @@ -1142,25 +1144,29 @@ mod tests { let node1 = Node::new_element(&document, "div", HashMap::new(), HTML_NAMESPACE); let node2 = Node::new_element(&document, "div", HashMap::new(), HTML_NAMESPACE); - document.get_mut().add_node(node1, NodeId::from(0), None); - document.get_mut().add_node(node2, NodeId::from(0), None); + document + .get_mut() + .add_node(node1, NodeId::from(0usize), None); + document + .get_mut() + .add_node(node2, NodeId::from(0usize), None); let doc_ptr = document.get(); - let get_node1 = doc_ptr.get_node_by_id(NodeId::from(1)).unwrap(); - let get_node2 = doc_ptr.get_node_by_id(NodeId::from(2)).unwrap(); + let get_node1 = doc_ptr.get_node_by_id(NodeId::from(1usize)).unwrap(); + let get_node2 = doc_ptr.get_node_by_id(NodeId::from(2usize)).unwrap(); let NodeData::Element(element1) = &get_node1.data else { panic!() }; - assert_eq!(element1.node_id, NodeId::from(1)); + assert_eq!(element1.node_id, NodeId::from(1usize)); let NodeData::Element(element2) = &get_node2.data else { panic!() }; - assert_eq!(element2.node_id, NodeId::from(2)); + assert_eq!(element2.node_id, NodeId::from(2usize)); } #[test] @@ -1182,10 +1188,10 @@ mod tests { // NOTE: only elements return the ID let div_id = task_queue.create_element("div", NodeId::root(), None, HTML_NAMESPACE); - assert_eq!(div_id, NodeId::from(1)); + assert_eq!(div_id, NodeId::from(1usize)); let p_id = task_queue.create_element("p", div_id, None, HTML_NAMESPACE); - assert_eq!(p_id, NodeId::from(2)); + assert_eq!(p_id, NodeId::from(2usize)); task_queue.create_comment("comment inside p", p_id); task_queue.create_text("hey", p_id); @@ -1282,13 +1288,13 @@ mod tests { // NOTE: inserting attribute in task queue always succeeds // since it doesn't touch DOM until flush - let _ = task_queue.insert_attribute("id", "myid", NodeId::from(1)); - let _ = task_queue.insert_attribute("id", "myid", NodeId::from(1)); - let _ = task_queue.insert_attribute("id", "otherid", NodeId::from(2)); - let _ = task_queue.insert_attribute("id", "dummyid", NodeId::from(42)); - let _ = task_queue.insert_attribute("id", "my id", NodeId::from(1)); - let _ = task_queue.insert_attribute("id", "123", NodeId::from(1)); - let _ = task_queue.insert_attribute("id", "", NodeId::from(1)); + let _ = task_queue.insert_attribute("id", "myid", NodeId::from(1usize)); + let _ = task_queue.insert_attribute("id", "myid", NodeId::from(1usize)); + let _ = task_queue.insert_attribute("id", "otherid", NodeId::from(2usize)); + let _ = task_queue.insert_attribute("id", "dummyid", NodeId::from(42usize)); + let _ = task_queue.insert_attribute("id", "my id", NodeId::from(1usize)); + let _ = task_queue.insert_attribute("id", "123", NodeId::from(1usize)); + let _ = task_queue.insert_attribute("id", "", NodeId::from(1usize)); let errors = task_queue.flush(); for error in &errors { println!("{}", error); @@ -1335,10 +1341,10 @@ mod tests { // NOTE: only elements return the ID let div_id = document.create_element("div", NodeId::root(), None, HTML_NAMESPACE); - assert_eq!(div_id, NodeId::from(1)); + assert_eq!(div_id, NodeId::from(1usize)); let p_id = document.create_element("p", div_id, None, HTML_NAMESPACE); - assert_eq!(p_id, NodeId::from(2)); + assert_eq!(p_id, NodeId::from(2usize)); document.create_comment("comment inside p", p_id); document.create_text("hey", p_id); diff --git a/crates/gosub_render_backend/src/geo.rs b/crates/gosub_render_backend/src/geo.rs new file mode 100644 index 000000000..fe9761fab --- /dev/null +++ b/crates/gosub_render_backend/src/geo.rs @@ -0,0 +1,7 @@ +use gosub_shared::types::Size as SizeT; + +pub type FP = f32; +pub type Point = gosub_shared::types::Point; +pub type Size = SizeT; +pub type SizeU32 = SizeT; +pub type Rect = gosub_shared::types::Rect; diff --git a/crates/gosub_render_backend/src/layout.rs b/crates/gosub_render_backend/src/layout.rs new file mode 100644 index 000000000..c7609373d --- /dev/null +++ b/crates/gosub_render_backend/src/layout.rs @@ -0,0 +1,123 @@ +use gosub_shared::types::Result; + +use crate::geo::{Point, Rect, Size, SizeU32}; + +pub trait LayoutTree: Sized { + type NodeId: Copy + Clone + From + Into; + type Node: Node; + + fn children(&self, id: Self::NodeId) -> Option>; + fn contains(&self, id: &Self::NodeId) -> bool; + fn child_count(&self, id: Self::NodeId) -> usize; + 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); + fn set_layout(&mut self, id: Self::NodeId, layout: L::Layout); + + fn style_dirty(&self, id: Self::NodeId) -> bool; + + fn clean_style(&mut self, id: Self::NodeId); + + fn get_node(&mut self, id: Self::NodeId) -> Option<&mut Self::Node>; +} + +pub trait Layouter: Sized + Clone { + type Cache: Default; + type Layout: Layout; + fn layout>( + &self, + tree: &mut LT, + root: LT::NodeId, + space: SizeU32, + ) -> Result<()>; +} + +pub trait Layout: Default { + /// Returns the relative upper left pos of the content box + fn rel_pos(&self) -> Point; + + /// Returns the z-index of the element + fn z_index(&self) -> u32; + + /// Size of the scroll box (content box without overflow), including scrollbars (if any) + fn size(&self) -> Size; + + /// Size of the content box (content without scrollbars, but with overflow) + fn content(&self) -> Size; + fn content_box(&self) -> Rect { + let pos = self.rel_pos(); + let size = self.size(); + Rect::from_components(pos, size) + } + + /// Additional space taken up by the scrollbar + fn scrollbar(&self) -> Size; + fn scrollbar_box(&self) -> Rect { + let pos = self.rel_pos(); + let content = self.content(); + let size = self.scrollbar(); + Rect::new( + pos.x, + pos.y, + content.width + size.width, + content.height + size.height, + ) + } + + fn border(&self) -> Rect; + fn border_box(&self) -> Rect { + let pos = self.rel_pos(); + let size = self.size(); + let border = self.border(); + + Rect::new( + pos.x - border.x1, + pos.y - border.y1, + size.width + border.x2, + size.height + border.y2, + ) + } + + fn padding(&self) -> Rect; + fn padding_box(&self) -> Rect { + let pos = self.rel_pos(); + let border = self.border(); + Rect::from_components(pos, border.size()) + } + + fn margin(&self) -> Rect; + fn margin_box(&self) -> Rect { + let border = self.border_box(); + let margin = self.margin(); + + Rect::new( + border.x1 - margin.x1, + border.y1 - margin.y1, + border.x2 + margin.x2, + border.y2 + margin.y2, + ) + } +} + +/// TODO: This struct should be removed and done somehow differently... +pub trait Node { + type Property: CssProperty; + + fn get_property(&mut self, name: &str) -> Option<&mut Self::Property>; //TODO: this needs to be more generic... + fn text_size(&mut self) -> Option; +} + +pub trait CssProperty { + fn compute_value(&mut self); + fn unit_to_px(&self) -> f32; + + fn as_string(&self) -> Option<&str>; + fn as_percentage(&self) -> Option; + fn as_unit(&self) -> Option<(f32, &str)>; + fn as_color(&self) -> Option<(f32, f32, f32, f32)>; + fn as_number(&self) -> Option; + fn is_none(&self) -> bool; +} diff --git a/crates/gosub_render_backend/src/lib.rs b/crates/gosub_render_backend/src/lib.rs index 24c12f1de..c7058707b 100644 --- a/crates/gosub_render_backend/src/lib.rs +++ b/crates/gosub_render_backend/src/lib.rs @@ -1,19 +1,16 @@ use std::fmt::Debug; use std::ops::{Div, Mul, MulAssign}; +use crate::svg::SvgRenderer; +pub use geo::*; +use gosub_shared::types::Result; use raw_window_handle::{HasDisplayHandle, HasWindowHandle}; use smallvec::SmallVec; -use gosub_shared::types::Result; -use gosub_shared::types::Size as SizeT; - -use crate::svg::SvgRenderer; - +pub mod geo; +pub mod layout; pub mod svg; -pub type Size = SizeT; -pub type SizeU32 = SizeT; - pub trait WindowHandle: HasDisplayHandle + HasWindowHandle + Send + Sync + Clone {} impl WindowHandle for T where T: HasDisplayHandle + HasWindowHandle + Send + Sync + Clone {} @@ -86,9 +83,6 @@ pub trait Scene { fn new(data: &mut B::WindowData<'_>) -> Self; } -pub type FP = f32; -pub type Point = gosub_shared::types::Point; - pub struct RenderRect { pub rect: B::Rect, pub transform: Option, diff --git a/crates/gosub_render_utils/Cargo.toml b/crates/gosub_render_utils/Cargo.toml index 6f952e858..f735b4678 100644 --- a/crates/gosub_render_utils/Cargo.toml +++ b/crates/gosub_render_utils/Cargo.toml @@ -9,7 +9,6 @@ license = "MIT" gosub_html5 = { path = "../gosub_html5" } gosub_styling = { path = "../gosub_styling" } gosub_render_backend = { path = "../gosub_render_backend" } -taffy = "0.5.2" anyhow = "1.0.86" regex = "1.10.6" rstar = "0.12.0" diff --git a/crates/gosub_render_utils/src/layout.rs b/crates/gosub_render_utils/src/layout.rs deleted file mode 100644 index d2a2a4c3c..000000000 --- a/crates/gosub_render_utils/src/layout.rs +++ /dev/null @@ -1,51 +0,0 @@ -use taffy::prelude::*; - -use gosub_html5::node::NodeId as GosubID; -use gosub_render_backend::RenderBackend; -use gosub_styling::render_tree::RenderTree; - -use crate::style::get_style_from_node; - -pub fn generate_taffy_tree( - rt: &mut RenderTree, -) -> anyhow::Result<(TaffyTree, NodeId)> { - let mut tree: TaffyTree = TaffyTree::with_capacity(rt.nodes.len()); - - 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, - node_id: GosubID, -) -> anyhow::Result { - 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.to_string()), - } - } - - 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 = tree - .new_with_children(style, &children) - .map_err(|e| anyhow::anyhow!(e.to_string()))?; - - tree.set_node_context(node, Some(node_id))?; - - Ok(node) -} diff --git a/crates/gosub_render_utils/src/lib.rs b/crates/gosub_render_utils/src/lib.rs index b70f15a87..c075d3275 100644 --- a/crates/gosub_render_utils/src/lib.rs +++ b/crates/gosub_render_utils/src/lib.rs @@ -3,7 +3,6 @@ //! This crate supplies functionality to render CSSOM and DOM trees into a viewable display. //! -pub mod layout; pub mod position; pub mod render_tree; -pub mod style; +pub mod text; diff --git a/crates/gosub_render_utils/src/position.rs b/crates/gosub_render_utils/src/position.rs index 36026c34f..4971c8f73 100644 --- a/crates/gosub_render_utils/src/position.rs +++ b/crates/gosub_render_utils/src/position.rs @@ -1,7 +1,11 @@ use std::cmp::Ordering; use rstar::{RTree, RTreeObject, AABB}; -use taffy::{NodeId, PrintTree, TaffyTree}; + +use gosub_html5::node::NodeId; +use gosub_render_backend::layout::{Layout, LayoutTree, Layouter}; +use gosub_render_backend::RenderBackend; +use gosub_styling::render_tree::RenderTree; #[derive(Debug)] pub struct Element { @@ -23,47 +27,53 @@ impl RTreeObject for Element { } } +#[derive(Default)] pub struct PositionTree { tree: RTree, } impl PositionTree { - pub fn from_taffy(taffy: &TaffyTree, root: NodeId) -> Self { + pub fn from_tree(from_tree: &RenderTree) -> Self { let mut tree = RTree::new(); //TODO: we somehow need to get the border radius and a potential stacking context of the element here - Self::add_node_to_tree(taffy, root, 0, &mut tree, (0.0, 0.0)); + Self::add_node_to_tree(from_tree, from_tree.root, 0, &mut tree, (0.0, 0.0)); Self { tree } } - fn add_node_to_tree( - taffy: &TaffyTree, + fn add_node_to_tree( + from_tree: &RenderTree, id: NodeId, z_index: i32, tree: &mut RTree, mut pos: (f32, f32), ) { - let layout = taffy.get_final_layout(id); + let Some(layout) = from_tree.get_layout(id) else { + return; + }; + + let p = layout.rel_pos(); - pos.0 += layout.location.x; - pos.1 += layout.location.y; + pos.0 += p.x; + pos.1 += p.y; + let size = layout.size(); let element = Element { id, x: pos.0, y: pos.1, - width: layout.size.width, - height: layout.size.height, + width: size.width, + height: size.height, radius: None, //TODO: border radius z_index, }; tree.insert(element); - for child in taffy.children(id).unwrap_or_default() { - Self::add_node_to_tree(taffy, child, z_index + 1, tree, pos); + for child in from_tree.children(id).unwrap_or_default() { + Self::add_node_to_tree(from_tree, child, z_index + 1, tree, pos); } } diff --git a/crates/gosub_render_utils/src/style/parse.rs b/crates/gosub_render_utils/src/style/parse.rs deleted file mode 100644 index 614e4b2d4..000000000 --- a/crates/gosub_render_utils/src/style/parse.rs +++ /dev/null @@ -1,206 +0,0 @@ -use taffy::prelude::*; -use taffy::{ - AlignContent, AlignItems, Dimension, GridPlacement, LengthPercentage, LengthPercentageAuto, - TrackSizingFunction, -}; - -use gosub_render_backend::{PreRenderText, RenderBackend}; -use gosub_styling::css_values::CssValue; -use gosub_styling::render_tree::{RenderTreeNode, TextData}; - -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 Some(property) = node.get_property(name) else { - return Dimension::Auto; - }; - - property.compute_value(); - - match &property.actual { - CssValue::String(value) => match value.as_str() { - "auto" => Dimension::Auto, - s if s.ends_with('%') => { - let value = s.trim_end_matches('%').parse::().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()), - _ => Dimension::Auto, - } -} - -pub(crate) fn parse_text_dim(text: &mut TextData, name: &str) -> Dimension { - let size = text.prerender.prerender(); - - if name == "width" || name == "max-width" || name == "min-width" { - Dimension::Length(size.width) - } else if name == "height" || name == "max-height" || name == "min-height" { - Dimension::Length(size.height) - } else { - Dimension::Auto - } -} - -pub(crate) fn parse_align_i( - node: &mut RenderTreeNode, - name: &str, -) -> Option { - 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 { - 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 { - 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 { - 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::() { - GridPlacement::from_span(value) - } else { - GridPlacement::Auto - } - } else if let Ok(value) = value.parse::() { - GridPlacement::from_line_index(value) - } else { - GridPlacement::Auto - } - } - CssValue::Number(value) => GridPlacement::from_line_index((*value) as i16), - _ => GridPlacement::Auto, - } -} diff --git a/crates/gosub_render_utils/src/text.rs b/crates/gosub_render_utils/src/text.rs new file mode 100644 index 000000000..39fd36fb2 --- /dev/null +++ b/crates/gosub_render_utils/src/text.rs @@ -0,0 +1 @@ +// fn measure_text() diff --git a/crates/gosub_renderer/Cargo.toml b/crates/gosub_renderer/Cargo.toml index 28fdb829b..c567ac8b7 100644 --- a/crates/gosub_renderer/Cargo.toml +++ b/crates/gosub_renderer/Cargo.toml @@ -14,7 +14,6 @@ gosub_shared = { path = "../gosub_shared" } gosub_styling = { path = "../gosub_styling" } gosub_net = { path = "../gosub_net" } gosub_render_backend = { path = "../gosub_render_backend" } -taffy = "0.5.2" anyhow = "1.0.86" image = "0.25.2" url = "2.5.2" diff --git a/crates/gosub_renderer/src/draw.rs b/crates/gosub_renderer/src/draw.rs index 0b867ca5d..285841b56 100644 --- a/crates/gosub_renderer/src/draw.rs +++ b/crates/gosub_renderer/src/draw.rs @@ -1,16 +1,14 @@ use anyhow::anyhow; -use taffy::{ - AvailableSpace, Layout, NodeId, PrintTree, Size as TSize, TaffyTree, TraversePartialTree, -}; use url::Url; -use gosub_html5::node::NodeId as GosubId; +use gosub_html5::node::NodeId; +use gosub_render_backend::geo::{Size, SizeU32, FP}; +use gosub_render_backend::layout::{Layout, LayoutTree, Layouter}; use gosub_render_backend::svg::SvgRenderer; use gosub_render_backend::{ - Border, BorderSide, BorderStyle, Brush, Color, ImageBuffer, PreRenderText, Rect, RenderBackend, - RenderBorder, RenderRect, RenderText, Scene as TScene, SizeU32, Text, Transform, FP, + Border, BorderSide, BorderStyle, Brush, Color, ImageBuffer, Rect, RenderBackend, RenderBorder, + RenderRect, RenderText, Scene as TScene, Text, Transform, }; -use gosub_rendering::layout::generate_taffy_tree; use gosub_rendering::position::PositionTree; use gosub_shared::types::Result; use gosub_styling::css_colors::RgbColor; @@ -18,18 +16,21 @@ use gosub_styling::css_values::CssValue; use gosub_styling::render_tree::{RenderNodeData, RenderTree, RenderTreeNode}; use crate::draw::img::request_img; -use crate::render_tree::{load_html_rendertree, NodeID, TreeDrawer}; +use crate::render_tree::{load_html_rendertree, TreeDrawer}; mod img; -pub trait SceneDrawer { +pub trait SceneDrawer> { fn draw(&mut self, backend: &mut B, data: &mut B::WindowData<'_>, size: SizeU32); fn mouse_move(&mut self, backend: &mut B, data: &mut B::WindowData<'_>, x: FP, y: FP) -> bool; fn scroll(&mut self, point: Point); - fn from_url(url: Url, debug: bool) -> Result + fn from_url(url: Url, layouter: L, debug: bool) -> Result where Self: Sized; + + fn clear_buffers(&mut self); + fn toggle_debug(&mut self); } const DEBUG_CONTENT_COLOR: (u8, u8, u8) = (0, 192, 255); //rgb(0, 192, 255) @@ -39,7 +40,7 @@ const DEBUG_BORDER_COLOR: (u8, u8, u8) = (255, 72, 72); //rgb(255, 72, 72) type Point = gosub_shared::types::Point; -impl SceneDrawer for TreeDrawer { +impl SceneDrawer> for TreeDrawer { fn draw(&mut self, backend: &mut B, data: &mut B::WindowData<'_>, size: SizeU32) { if !self.dirty && self.size == Some(size) { return; @@ -52,7 +53,7 @@ impl SceneDrawer for TreeDrawer { // Apply new maximums to the scene transform if let Some(scene_transform) = self.scene_transform.as_mut() { - let root_size = self.taffy.get_final_layout(self.root).content_size; + let root_size = self.tree.get_root().layout.content(); let max_x = root_size.width - size.width as f32; let max_y = root_size.height - size.height as f32; @@ -118,18 +119,18 @@ impl SceneDrawer for TreeDrawer { if self.debug { let mut scene = B::Scene::new(data); - let layout = self.taffy.get_final_layout(e); - - let content_size = layout.size; - let padding = layout.padding; - let border_size = layout.border; + let Some(layout) = self.tree.get_layout(e) else { + return false; + }; + let size = layout.size(); + let padding = layout.padding(); + let border_size = layout.border(); let Some((x, y)) = self.position.position(e) else { return false; }; - let content_rect = - Rect::new(x, y, content_size.width as FP, content_size.height as FP); + let content_rect = Rect::new(x, y, size.width as FP, size.height as FP); let padding_brush = B::Brush::color(B::Color::tuple3(DEBUG_PADDING_COLOR).alpha(127)); @@ -142,25 +143,25 @@ impl SceneDrawer for TreeDrawer { let mut border = B::Border::empty(); border.left(BorderSide::new( - padding.left as FP, + padding.x2 as FP, BorderStyle::Solid, padding_brush.clone(), )); border.right(BorderSide::new( - padding.right as FP, + padding.x1 as FP, BorderStyle::Solid, padding_brush.clone(), )); border.top(BorderSide::new( - padding.top as FP, + padding.y1 as FP, BorderStyle::Solid, padding_brush.clone(), )); border.bottom(BorderSide::new( - padding.bottom as FP, + padding.y2 as FP, BorderStyle::Solid, padding_brush, )); @@ -181,25 +182,25 @@ impl SceneDrawer for TreeDrawer { let mut border_border = B::Border::empty(); border_border.left(BorderSide::new( - border_size.left as FP, + border_size.x2 as FP, BorderStyle::Solid, border_brush.clone(), )); border_border.right(BorderSide::new( - border_size.right as FP, + border_size.x1 as FP, BorderStyle::Solid, border_brush.clone(), )); border_border.top(BorderSide::new( - border_size.top as FP, + border_size.y1 as FP, BorderStyle::Solid, border_brush.clone(), )); border_border.bottom(BorderSide::new( - border_size.bottom as FP, + border_size.y2 as FP, BorderStyle::Solid, border_brush, )); @@ -207,10 +208,10 @@ impl SceneDrawer for TreeDrawer { let border_border = RenderBorder::new(border_border); let border_rect = Rect::new( - x as FP - border_size.left as FP - padding.left as FP, - y as FP - border_size.top as FP - padding.top as FP, - (content_size.width + padding.left + padding.right) as FP, - (content_size.height + padding.top + padding.bottom) as FP, + x as FP - border_size.x2 as FP - padding.x2 as FP, + y as FP - border_size.y1 as FP - padding.y1 as FP, + (size.width + padding.x2 + padding.x1) as FP, + (size.height + padding.y1 + padding.y2) as FP, ); let render_rect = RenderRect { @@ -243,7 +244,7 @@ impl SceneDrawer for TreeDrawer { let x = transform.tx() + point.x; let y = transform.ty() + point.y; - let root_size = self.taffy.get_final_layout(self.root).content_size; + let root_size = self.tree.get_root().layout.content(); let size = self.size.unwrap_or(SizeU32::ZERO); let max_x = root_size.width - size.width as f32; @@ -259,52 +260,61 @@ impl SceneDrawer for TreeDrawer { self.dirty = true; } - fn from_url(url: Url, debug: bool) -> Result { - let mut rt = load_html_rendertree(url.clone())?; + fn from_url(url: Url, layouter: L, debug: bool) -> Result { + let rt = load_html_rendertree(url.clone())?; + + Ok(Self::new(rt, layouter, url, debug)) + } - let (taffy_tree, root) = generate_taffy_tree(&mut rt)?; + fn clear_buffers(&mut self) { + self.tree_scene = None; + self.debugger_scene = None; + self.last_hover = None; + self.dirty = true; + } - Ok(Self::new(rt, taffy_tree, root, url, debug)) + fn toggle_debug(&mut self) { + self.debug = !self.debug; + self.dirty = true; + self.last_hover = None; + self.debugger_scene = None; } } -struct Drawer<'s, 't, B: RenderBackend> { +struct Drawer<'s, 't, B: RenderBackend, L: Layouter> { scene: &'s mut B::Scene, - drawer: &'t mut TreeDrawer, + drawer: &'t mut TreeDrawer, svg: B::SVGRenderer, } -impl Drawer<'_, '_, B> { +impl Drawer<'_, '_, B, L> { pub(crate) fn render(&mut self, size: SizeU32) { - let space = TSize { - width: AvailableSpace::Definite(size.width as f32), - height: AvailableSpace::Definite(size.height as f32), - }; - - if let Err(e) = self.drawer.taffy.compute_layout(self.drawer.root, space) { + let root = self.drawer.tree.root; + if let Err(e) = self + .drawer + .layouter + .layout(&mut self.drawer.tree, root, size) + { eprintln!("Failed to compute layout: {:?}", e); return; } // print_tree(&self.taffy, self.root, &self.style); - self.drawer.position = PositionTree::from_taffy(&self.drawer.taffy, self.drawer.root); + self.drawer.position = PositionTree::from_tree(&self.drawer.tree); - self.render_node_with_children(self.drawer.root, Point::ZERO); + self.render_node_with_children(self.drawer.tree.root, Point::ZERO); } - fn render_node_with_children(&mut self, id: NodeID, mut pos: Point) { + fn render_node_with_children(&mut self, id: NodeId, mut pos: Point) { let err = self.render_node(id, &mut pos); if let Err(e) = err { - eprintln!("Error rendering node: {:?}", e); + eprintln!("Error rendering node: {}", e); } - let children = match self.drawer.taffy.children(id) { - Ok(children) => children, - Err(e) => { - eprintln!("Error rendering node children: {e}"); - return; - } + let Some(children) = self.drawer.tree.children(id) else { + eprintln!("Error rendering node children"); + return; }; for child in children { @@ -312,32 +322,18 @@ impl Drawer<'_, '_, B> { } } - fn render_node(&mut self, id: NodeID, pos: &mut Point) -> anyhow::Result<()> { - let gosub_id = *self + fn render_node(&mut self, id: NodeId, pos: &mut Point) -> anyhow::Result<()> { + let node = self .drawer - .taffy - .get_node_context(id) - .ok_or(anyhow!("Failed to get style id"))?; + .tree + .get_node_mut(id) + .ok_or(anyhow!("Node {id} not found"))?; - let layout = self.drawer.taffy.get_final_layout(id); + let p = node.layout.rel_pos(); + pos.x += p.x as FP; + pos.y += p.y as FP; - let node = self - .drawer - .style - .get_node_mut(gosub_id) - .ok_or(anyhow!("Node not found"))?; - - pos.x += layout.location.x as FP; - pos.y += layout.location.y as FP; - - let border_radius = render_bg( - node, - self.scene, - layout, - pos, - &self.drawer.url, - &mut self.svg, - ); + let border_radius = render_bg(node, self.scene, pos, &self.drawer.url, &mut self.svg); if let RenderNodeData::Element(element) = &node.data { if element.name() == "img" { @@ -356,23 +352,26 @@ impl Drawer<'_, '_, B> { .map(|prop| prop.as_str()) .unwrap_or("contain"); - println!("Rendering image at: {:?}", pos); - println!("with size: {:?}", layout.size); - - render_image::(img, self.scene, *pos, layout.size, border_radius, fit)?; + render_image::( + img, + self.scene, + *pos, + node.layout.size(), + border_radius, + fit, + )?; } } - render_text(node, self.scene, pos, layout); + render_text(node, self.scene, pos); Ok(()) } } -fn render_text( - node: &mut RenderTreeNode, +fn render_text( + node: &mut RenderTreeNode, scene: &mut B::Scene, pos: &Point, - layout: &Layout, ) { if let RenderNodeData::Text(text) = &mut node.data { let color = node @@ -392,11 +391,13 @@ fn render_text( let text = Text::new(&mut text.prerender); + let size = node.layout.size(); + let rect = Rect::new( pos.x as FP, pos.y as FP, - layout.size.width as FP, - layout.size.height as FP, + size.width as FP, + size.height as FP, ); let render_text = RenderText { @@ -411,10 +412,9 @@ fn render_text( } } -fn render_bg( - node: &mut RenderTreeNode, +fn render_bg( + node: &mut RenderTreeNode, scene: &mut B::Scene, - layout: &Layout, pos: &Point, root_url: &Url, svg: &mut B::SVGRenderer, @@ -479,11 +479,13 @@ fn render_bg( let border = get_border(node).map(|border| RenderBorder::new(border)); if let Some(bg_color) = bg_color { + let size = node.layout.size(); + let rect = Rect::new( pos.x as FP, pos.y as FP, - layout.content_size.width as FP, - layout.content_size.height as FP, + size.width as FP, + size.height as FP, ); let rect = RenderRect { @@ -497,11 +499,13 @@ fn render_bg( scene.draw_rect(&rect); } else if let Some(border) = border { + let size = node.layout.size(); + let rect = Rect::new( pos.x as FP, pos.y as FP, - layout.size.width as FP, - layout.size.height as FP, + size.width as FP, + size.height as FP, ); let rect = RenderRect { @@ -539,8 +543,8 @@ fn render_bg( } }; - let _ = - render_image::(img, scene, *pos, layout.size, border_radius, "fill").map_err(|e| { + let _ = render_image::(img, scene, *pos, node.layout.size(), border_radius, "fill") + .map_err(|e| { eprintln!("Error rendering image: {:?}", e); }); } @@ -570,7 +574,7 @@ fn render_image( img: ImageBuffer, scene: &mut B::Scene, pos: Point, - size: TSize, + size: Size, radii: (FP, FP, FP, FP), fit: &str, ) -> anyhow::Result<()> { @@ -639,8 +643,9 @@ fn render_image( Ok(()) } +/* //just for debugging -pub fn print_tree( +pub fn print_tree( tree: &TaffyTree, root: NodeId, gosub_tree: &RenderTree, @@ -649,7 +654,7 @@ pub fn print_tree( print_node(tree, root, false, String::new(), gosub_tree); /// Recursive function that prints each node in the tree - fn print_node( + fn print_node( tree: &TaffyTree, node_id: NodeId, has_sibling: bool, @@ -708,8 +713,9 @@ pub fn print_tree( } } } +*/ -fn get_border(node: &mut RenderTreeNode) -> Option { +fn get_border(node: &mut RenderTreeNode) -> Option { let left = get_border_side(node, Side::Left); let right = get_border_side(node, Side::Right); let top = get_border_side(node, Side::Top); @@ -740,8 +746,8 @@ fn get_border(node: &mut RenderTreeNode) -> Option( - node: &mut RenderTreeNode, +fn get_border_side( + node: &mut RenderTreeNode, side: Side, ) -> Option { let width = node diff --git a/crates/gosub_renderer/src/render_tree.rs b/crates/gosub_renderer/src/render_tree.rs index 4c9e58b26..6e4465af3 100644 --- a/crates/gosub_renderer/src/render_tree.rs +++ b/crates/gosub_renderer/src/render_tree.rs @@ -1,26 +1,23 @@ use std::fs; use anyhow::bail; -use taffy::{Layout, TaffyTree}; -use taffy::{NodeId as TaffyID, NodeId}; use url::Url; -use gosub_html5::node::NodeId as GosubID; +use gosub_html5::node::NodeId; use gosub_html5::parser::document::{Document, DocumentBuilder}; use gosub_html5::parser::Html5Parser; use gosub_net::http::ureq; -use gosub_render_backend::{RenderBackend, SizeU32}; +use gosub_render_backend::geo::SizeU32; +use gosub_render_backend::layout::Layouter; +use gosub_render_backend::RenderBackend; use gosub_rendering::position::PositionTree; use gosub_shared::byte_stream::{ByteStream, Confidence, Encoding}; use gosub_styling::css_values::CssProperties; -use gosub_styling::render_tree::{generate_render_tree, RenderNodeData, RenderTree as StyleTree}; +use gosub_styling::render_tree::{generate_render_tree, RenderNodeData, RenderTree}; -pub type NodeID = TaffyID; - -pub struct TreeDrawer { - pub(crate) style: StyleTree, - pub(crate) root: NodeID, - pub(crate) taffy: TaffyTree, +pub struct TreeDrawer { + pub(crate) tree: RenderTree, + pub(crate) layouter: L, pub(crate) size: Option, pub(crate) url: Url, pub(crate) position: PositionTree, @@ -32,22 +29,14 @@ pub struct TreeDrawer { pub(crate) scene_transform: Option, } -impl TreeDrawer { - pub fn new( - style: StyleTree, - taffy: TaffyTree, - root: TaffyID, - url: Url, - debug: bool, - ) -> Self { - let position = PositionTree::from_taffy(&taffy, root); +impl TreeDrawer { + pub fn new(tree: RenderTree, layouter: L, url: Url, debug: bool) -> Self { Self { - style, - root, - taffy, + tree, + layouter, size: None, url, - position, + position: PositionTree::default(), last_hover: None, debug, debugger_scene: None, @@ -59,18 +48,18 @@ impl TreeDrawer { } pub struct RenderTreeNode { - pub parent: Option, - pub children: Vec, - pub layout: Layout, + pub parent: Option, + pub children: Vec, + pub layout: i32, //TODO pub name: String, pub properties: CssProperties, pub namespace: Option, pub data: RenderNodeData, } -pub(crate) fn load_html_rendertree( +pub(crate) fn load_html_rendertree( url: Url, -) -> gosub_shared::types::Result> { +) -> gosub_shared::types::Result> { let html = if url.scheme() == "http" || url.scheme() == "https" { // Fetch the html from the url let response = ureq::get(url.as_ref()).call()?; diff --git a/crates/gosub_shared/src/types.rs b/crates/gosub_shared/src/types.rs index 59ec134af..c6fb88afa 100644 --- a/crates/gosub_shared/src/types.rs +++ b/crates/gosub_shared/src/types.rs @@ -1,5 +1,7 @@ //! Error results that can be returned from the engine + use crate::byte_stream::Location; +use std::ops::Add; use thiserror::Error; /// Parser error that defines an error (message) on the given position @@ -83,6 +85,20 @@ impl Point { } } +impl Add for Point +where + T: Add + Copy, +{ + type Output = Self; + + fn add(self, rhs: Self) -> Self::Output { + Self { + x: self.x + rhs.x, + y: self.y + rhs.y, + } + } +} + impl Point { pub const ZERO: Self = Self { x: 0, y: 0 }; @@ -259,3 +275,99 @@ impl Size { self.height as f32 } } + +/// Represents a Rectangle or a Rectangle edge +pub struct Rect { + /// top or top-left or origin x + pub y1: T, + /// right or top-right or origin y + pub x1: T, + /// bottom or bottom-right or width + pub y2: T, + /// left or bottom-left or height + pub x2: T, +} + +impl Rect { + pub fn new(x1: T, y1: T, x2: T, y2: T) -> Self { + Self { x1, y1, x2, y2 } + } + + pub fn from_components(origin: Point, size: Size) -> Self { + Self { + x1: origin.x, + y1: origin.y, + x2: size.width, + y2: size.height, + } + } + + /// Gets the origin, if x1 and y1 represent the origin of the rect + pub fn origin(&self) -> Point { + Point::new(self.x1, self.y1) + } + + /// Gets the size, if x2 and y2 represent the size of the rect + pub fn size(&self) -> Size { + Size::new(self.x2, self.y2) + } +} + +impl Rect { + pub const ZERO: Self = Self { + x1: 0, + y1: 0, + x2: 0, + y2: 0, + }; + + pub fn f64(&self) -> Rect { + Rect::new( + self.x1 as f64, + self.y1 as f64, + self.x2 as f64, + self.y2 as f64, + ) + } + + pub fn f32(&self) -> Rect { + Rect::new( + self.x1 as f32, + self.y1 as f32, + self.x2 as f32, + self.y2 as f32, + ) + } + + pub fn x1_f32(&self) -> f32 { + self.x1 as f32 + } + + pub fn y1_f32(&self) -> f32 { + self.y1 as f32 + } + + pub fn x2_f32(&self) -> f32 { + self.x2 as f32 + } + + pub fn y2_f32(&self) -> f32 { + self.y2 as f32 + } + + pub fn x1_f64(&self) -> f64 { + self.x1 as f64 + } + + pub fn y1_f64(&self) -> f64 { + self.y1 as f64 + } + + pub fn x2_f64(&self) -> f64 { + self.x2 as f64 + } + + pub fn y2_f64(&self) -> f64 { + self.y2 as f64 + } +} diff --git a/crates/gosub_styling/src/render_tree.rs b/crates/gosub_styling/src/render_tree.rs index d0e7ae929..272940feb 100644 --- a/crates/gosub_styling/src/render_tree.rs +++ b/crates/gosub_styling/src/render_tree.rs @@ -4,7 +4,9 @@ use std::fmt::Debug; use gosub_html5::node::data::element::ElementData; use gosub_html5::node::{NodeData, NodeId}; use gosub_html5::parser::document::{DocumentHandle, TreeIterator}; -use gosub_render_backend::{PreRenderText, RenderBackend, FP}; +use gosub_render_backend::geo::{Size, FP}; +use gosub_render_backend::layout::{LayoutTree, Layouter, Node}; +use gosub_render_backend::{PreRenderText, RenderBackend}; use gosub_shared::types::Result; use gosub_typeface::DEFAULT_FS; @@ -13,14 +15,75 @@ use crate::css_values::{ }; /// Map of all declared values for all nodes in the document -#[derive(Default, Debug)] -pub struct RenderTree { - pub nodes: HashMap>, +#[derive(Debug)] +pub struct RenderTree { + pub nodes: HashMap>, pub root: NodeId, pub dirty: bool, } -impl RenderTree { +#[allow(unused)] +impl LayoutTree for RenderTree { + type NodeId = NodeId; + + type Node = RenderTreeNode; + + fn children(&self, id: Self::NodeId) -> Option> { + self.get_children(id).cloned() + } + + fn contains(&self, id: &Self::NodeId) -> bool { + self.nodes.contains_key(id) + } + + fn child_count(&self, id: Self::NodeId) -> usize { + self.child_count(id) + } + + fn get_cache(&self, id: Self::NodeId) -> Option<&L::Cache> { + self.get_node(id).map(|node| &node.cache) + } + + fn get_layout(&self, id: Self::NodeId) -> Option<&L::Layout> { + self.get_node(id).map(|node| &node.layout) + } + + fn get_cache_mut(&mut self, id: Self::NodeId) -> Option<&mut L::Cache> { + self.get_node_mut(id).map(|node| &mut node.cache) + } + + fn get_layout_mut(&mut self, id: Self::NodeId) -> Option<&mut L::Layout> { + self.get_node_mut(id).map(|node| &mut node.layout) + } + + fn set_cache(&mut self, id: Self::NodeId, cache: L::Cache) { + if let Some(node) = self.get_node_mut(id) { + node.cache = cache; + } + } + + fn set_layout(&mut self, id: Self::NodeId, layout: L::Layout) { + if let Some(node) = self.get_node_mut(id) { + node.layout = layout; + } + } + + fn style_dirty(&self, id: Self::NodeId) -> bool { + self.get_node(id).map(|node| node.css_dirty).unwrap_or(true) + } + + fn clean_style(&mut self, id: Self::NodeId) { + if let Some(node) = self.get_node_mut(id) { + node.css_dirty = false; + } + } + + fn get_node(&mut self, id: Self::NodeId) -> Option<&mut Self::Node> { + self.get_node_mut(id) + } +} + +impl RenderTree { // Generates a new render tree with a root node pub fn with_capacity(capacity: usize) -> Self { let mut tree = Self { @@ -34,11 +97,14 @@ impl RenderTree { RenderTreeNode { id: NodeId::root(), properties: CssProperties::new(), + css_dirty: true, children: Vec::new(), parent: None, name: String::from("root"), namespace: None, data: RenderNodeData::Document, + cache: L::Cache::default(), + layout: L::Layout::default(), }, ); @@ -46,17 +112,17 @@ impl RenderTree { } /// Returns the root node of the render tree - pub fn get_root(&self) -> &RenderTreeNode { + pub fn get_root(&self) -> &RenderTreeNode { self.nodes.get(&self.root).expect("root node") } /// Returns the node with the given id - pub fn get_node(&self, id: NodeId) -> Option<&RenderTreeNode> { + pub fn get_node(&self, id: NodeId) -> Option<&RenderTreeNode> { self.nodes.get(&id) } /// Returns a mutable reference to the node with the given id - pub fn get_node_mut(&mut self, id: NodeId) -> Option<&mut RenderTreeNode> { + pub fn get_node_mut(&mut self, id: NodeId) -> Option<&mut RenderTreeNode> { self.nodes.get_mut(&id) } @@ -65,15 +131,33 @@ impl RenderTree { self.nodes.get(&id).map(|node| &node.children) } + /// Returns the children of the given node + pub fn child_count(&self, id: NodeId) -> usize { + self.nodes + .get(&id) + .map(|node| node.children.len()) + .unwrap_or(0) + } + /// Inserts a new node into the render tree, note that you are responsible for the node id /// and the children of the node - pub fn insert_node(&mut self, id: NodeId, node: RenderTreeNode) { + pub fn insert_node(&mut self, id: NodeId, node: RenderTreeNode) { self.nodes.insert(id, node); } /// Deletes the node with the given id from the render tree - pub fn delete_node(&mut self, id: &NodeId) -> Option<(NodeId, RenderTreeNode)> { - if self.nodes.contains_key(id) { + pub fn delete_node(&mut self, id: &NodeId) -> Option<(NodeId, RenderTreeNode)> { + println!("Deleting node: {id:?}"); + + if let Some(n) = self.nodes.get(id) { + let parent = n.parent; + + if let Some(parent) = parent { + if let Some(parent_node) = self.nodes.get_mut(&parent) { + parent_node.children.retain(|child| child != id); + } + } + self.nodes.remove_entry(id) } else { None @@ -226,6 +310,9 @@ impl RenderTree { name: node.name.clone(), // We might be able to move node into render_tree_node namespace: node.namespace.clone(), data, + css_dirty: true, + cache: L::Cache::default(), + layout: L::Layout::default(), }; self.nodes.insert(current_node_id, render_tree_node); @@ -242,6 +329,7 @@ impl RenderTree { for (id, node) in &self.nodes { if let RenderNodeData::Element(element) = &node.data { if removable_elements.contains(&element.name.as_str()) { + println!("removing: {:?}({id})", element.name); delete_list.append(&mut self.get_child_node_ids(*id)); delete_list.push(*id); continue; @@ -359,18 +447,35 @@ impl RenderNodeData { } } -#[derive(Debug)] -pub struct RenderTreeNode { +pub struct RenderTreeNode { pub id: NodeId, pub properties: CssProperties, + pub css_dirty: bool, pub children: Vec, pub parent: Option, pub name: String, pub namespace: Option, pub data: RenderNodeData, + pub cache: L::Cache, + pub layout: L::Layout, +} + +impl Debug for RenderTreeNode { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("RenderTreeNode") + .field("id", &self.id) + .field("properties", &self.properties) + .field("css_dirty", &self.css_dirty) + .field("children", &self.children) + .field("parent", &self.parent) + .field("name", &self.name) + .field("namespace", &self.namespace) + .field("data", &self.data) + .finish() + } } -impl RenderTreeNode { +impl RenderTreeNode { /// Returns true if the node is an element node pub fn is_element(&self) -> bool { matches!(self.data, RenderNodeData::Element(_)) @@ -395,8 +500,80 @@ impl RenderTreeNode { } } +impl Node for RenderTreeNode { + type Property = CssProperty; + + fn get_property(&mut self, name: &str) -> Option<&mut Self::Property> { + self.properties.properties.get_mut(name) + } + + fn text_size(&mut self) -> Option { + if let RenderNodeData::Text(text) = &mut self.data { + Some(text.prerender.prerender()) + } else { + None + } + } +} + +impl gosub_render_backend::layout::CssProperty for CssProperty { + fn compute_value(&mut self) { + self.compute_value(); + } + + fn unit_to_px(&self) -> f32 { + self.actual.unit_to_px() + } + + fn as_string(&self) -> Option<&str> { + if let CssValue::String(str) = &self.actual { + Some(str) + } else { + None + } + } + + fn as_percentage(&self) -> Option { + if let CssValue::Percentage(percent) = &self.actual { + Some(*percent) + } else { + None + } + } + + fn as_unit(&self) -> Option<(f32, &str)> { + if let CssValue::Unit(value, unit) = &self.actual { + Some((*value, unit)) + } else { + None + } + } + + fn as_color(&self) -> Option<(f32, f32, f32, f32)> { + if let CssValue::Color(color) = &self.actual { + Some((color.r, color.g, color.b, color.a)) + } else { + None + } + } + + fn as_number(&self) -> Option { + if let CssValue::Number(num) = &self.actual { + Some(*num) + } else { + None + } + } + + fn is_none(&self) -> bool { + matches!(self.actual, CssValue::None) + } +} + /// Generates a render tree for the given document based on its loaded stylesheets -pub fn generate_render_tree(document: DocumentHandle) -> Result> { +pub fn generate_render_tree( + document: DocumentHandle, +) -> Result> { let render_tree = RenderTree::from_document(document); Ok(render_tree) diff --git a/crates/gosub_svg/src/resvg.rs b/crates/gosub_svg/src/resvg.rs index 094a5eae4..28a2ee434 100644 --- a/crates/gosub_svg/src/resvg.rs +++ b/crates/gosub_svg/src/resvg.rs @@ -3,8 +3,9 @@ use tiny_skia::Pixmap; use gosub_html5::node::NodeId; use gosub_html5::parser::document::DocumentHandle; +use gosub_render_backend::geo::FP; use gosub_render_backend::svg::SvgRenderer; -use gosub_render_backend::{Image, ImageBuffer, RenderBackend, FP}; +use gosub_render_backend::{Image, ImageBuffer, RenderBackend}; use gosub_shared::types::Result; use crate::SVGDocument; diff --git a/crates/gosub_taffy/Cargo.toml b/crates/gosub_taffy/Cargo.toml new file mode 100644 index 000000000..9c4810b6f --- /dev/null +++ b/crates/gosub_taffy/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "gosub_taffy" +version = "0.1.0" +edition = "2021" + +[dependencies] +gosub_shared = { path = "../gosub_shared" } +gosub_render_backend = { path = "../gosub_render_backend" } +gosub_styling = { path = "../gosub_styling" } +taffy = "0.5.2" +anyhow = "1.0.86" +regex = "1.10.5" diff --git a/crates/gosub_taffy/src/conversions.rs b/crates/gosub_taffy/src/conversions.rs new file mode 100644 index 000000000..8b1378917 --- /dev/null +++ b/crates/gosub_taffy/src/conversions.rs @@ -0,0 +1 @@ + diff --git a/crates/gosub_taffy/src/lib.rs b/crates/gosub_taffy/src/lib.rs new file mode 100644 index 000000000..bcddd78a2 --- /dev/null +++ b/crates/gosub_taffy/src/lib.rs @@ -0,0 +1,256 @@ +use std::vec::IntoIter; + +use taffy::{ + compute_block_layout, compute_cached_layout, compute_flexbox_layout, compute_grid_layout, + compute_hidden_layout, compute_leaf_layout, compute_root_layout, AvailableSpace, + Cache as TaffyCache, Display as TaffyDisplay, Layout as TaffyLayout, LayoutInput, LayoutOutput, + LayoutPartialTree, NodeId as TaffyId, Style, TraversePartialTree, +}; + +use gosub_render_backend::geo::{Point, Rect, Size, SizeU32}; +use gosub_render_backend::layout::{Layout as TLayout, LayoutTree, Layouter}; +use gosub_shared::types::Result; + +use crate::style::get_style_from_node; + +mod conversions; +pub mod style; +#[repr(transparent)] +#[derive(Default, Debug)] +pub struct Layout(TaffyLayout); + +impl TLayout for Layout { + fn rel_pos(&self) -> Point { + let pos = self.0.location; + + Point::new(pos.x, pos.y) + } + + fn z_index(&self) -> u32 { + self.0.order + } + + fn size(&self) -> Size { + let size = self.0.size; + Size::new(size.width, size.height) + } + + fn content(&self) -> Size { + let content = self.0.content_size; + Size::new(content.width, content.height) + } + + fn scrollbar(&self) -> Size { + let scroll = self.0.scrollbar_size; + Size::new(scroll.width, scroll.height) + } + + fn border(&self) -> Rect { + let border = self.0.border; + Rect::new(border.top, border.right, border.bottom, border.left) + } + + fn padding(&self) -> Rect { + let padding = self.0.padding; + Rect::new(padding.top, padding.right, padding.bottom, padding.left) + } + + fn margin(&self) -> Rect { + Rect::new(0.0, 0.0, 0.0, 0.0) // Taffy doesn't have margin in its layout + } +} + +#[derive(Clone, Copy)] +pub struct TaffyLayouter; + +#[derive(Debug, Default)] +#[allow(unused)] +enum Display { + Inline, + Text, + #[default] + Taffy, +} + +#[derive(Default)] +#[allow(unused)] +pub struct Cache { + taffy: TaffyCache, + style: Style, + display: Display, +} + +impl Layouter for TaffyLayouter { + type Cache = Cache; + type Layout = Layout; + + fn layout>( + &self, + tree: &mut LT, + root: LT::NodeId, + space: SizeU32, + ) -> Result<()> { + println!("space: {:?}", space); + + let size = taffy::Size { + width: AvailableSpace::Definite(space.width as f32), + height: AvailableSpace::Definite(space.height as f32), + }; + + let mut tree = LayoutDocument(tree); + Self::precompute_style(&mut tree, root); + compute_root_layout(&mut tree, TaffyId::from(root.into()), size); + + Ok(()) + } +} + +impl TaffyLayouter { + fn precompute_style>(tree: &mut LayoutDocument, root: LT::NodeId) { + tree.update_style(root); + + let Some(children) = tree.0.children(root) else { + return; + }; + + for child in children { + Self::precompute_style(tree, LT::NodeId::from(child.into())); + } + } +} + +#[repr(transparent)] +pub struct LayoutDocument<'a, LT: LayoutTree>(&'a mut LT); + +impl> TraversePartialTree for LayoutDocument<'_, LT> { + type ChildIter<'a> = IntoIter + where + Self: 'a; + + fn child_ids(&self, parent: TaffyId) -> Self::ChildIter<'_> { + let parent = LT::NodeId::from(parent.into()); + + if let Some(children) = self.0.children(parent) { + children + .iter() + .filter(|id| self.0.contains(id)) //FIXME: This is a hack, we should not have to filter out non-existing nodes + .map(|id| TaffyId::from(Into::into(*id))) + .collect::>() + .into_iter() + } else { + Vec::new().into_iter() + } + } + + fn child_count(&self, parent: TaffyId) -> usize { + let parent = LT::NodeId::from(parent.into()); + + self.0.child_count(parent) + } + + fn get_child_id(&self, parent: TaffyId, index: usize) -> TaffyId { + let parent = LT::NodeId::from(parent.into()); + + if let Some(node) = self.0.children(parent) { + TaffyId::from( + node.into_iter() + .filter(|id| self.0.contains(id)) //FIXME: This is a hack, we should not have to filter out non-existing nodes + .nth(index) + .map(Into::into) + .unwrap_or_default(), + ) + } else { + TaffyId::from(0u64) //TODO: this maybe shouldn't be 0 + } + } +} + +impl> LayoutDocument<'_, LT> { + fn update_style(&mut self, node_id: LT::NodeId) { + let Some(node) = self.0.get_node(node_id) else { + return; + }; + + let style = get_style_from_node(node); + + if let Some(cache) = self.0.get_cache_mut(node_id) { + cache.style = style; + } + } + + fn get_taffy_style(&mut self, node_id: LT::NodeId) -> &Style { + let dirty_style = self.0.style_dirty(node_id); + + if dirty_style { + self.update_style(node_id); + } + + let cache = self + .0 + .get_cache(node_id) + .expect("Cache not found, why again does taffy don't use optionals?"); + + &cache.style + } + + fn get_taffy_style_no_update(&self, node_id: LT::NodeId) -> &Style { + if let Some(cache) = self.0.get_cache(node_id) { + return &cache.style; + } + panic!( + "Cache not found, why again does taffy don't use optionals? (node: {})", + node_id.into() + ); + } +} + +impl> LayoutPartialTree for LayoutDocument<'_, LT> { + fn get_style(&self, node_id: TaffyId) -> &Style { + self.get_taffy_style_no_update(LT::NodeId::from(node_id.into())) + } + + fn set_unrounded_layout(&mut self, node_id: TaffyId, layout: &TaffyLayout) { + let layout = Layout(*layout); + + let node_id = LT::NodeId::from(node_id.into()); + + self.0.set_layout(node_id, layout); + } + + fn get_cache_mut(&mut self, node_id: TaffyId) -> &mut TaffyCache { + let node_id = LT::NodeId::from(node_id.into()); + &mut self + .0 + .get_cache_mut(node_id) + .expect("Cache not found, why again does taffy don't use optionals?") + .taffy + } + + fn compute_child_layout(&mut self, node_id: TaffyId, inputs: LayoutInput) -> LayoutOutput { + compute_cached_layout(self, node_id, inputs, |tree, node_id_taffy, inputs| { + let node_id = LT::NodeId::from(node_id_taffy.into()); + // if let Some(cache) = tree.0.get_cache(node_id) { + // match cache.display { + // Display::Inline => { + // todo!() + // } + // Display::Text => { + // todo!() + // } + // Display::Taffy => {} + // } + // } + + let has_children = tree.0.child_count(node_id) > 0; + let style = tree.get_taffy_style(node_id); + + match (style.display, has_children) { + (TaffyDisplay::None, _) => compute_hidden_layout(tree, node_id_taffy), + (TaffyDisplay::Block, true) => compute_block_layout(tree, node_id_taffy, inputs), + (TaffyDisplay::Flex, true) => compute_flexbox_layout(tree, node_id_taffy, inputs), + (TaffyDisplay::Grid, true) => compute_grid_layout(tree, node_id_taffy, inputs), + (_, false) => compute_leaf_layout(inputs, style, |_, _| taffy::Size::ZERO), + } + }) + } +} diff --git a/crates/gosub_render_utils/src/style.rs b/crates/gosub_taffy/src/style.rs similarity index 92% rename from crates/gosub_render_utils/src/style.rs rename to crates/gosub_taffy/src/style.rs index 665160eaf..bcf2125c4 100644 --- a/crates/gosub_render_utils/src/style.rs +++ b/crates/gosub_taffy/src/style.rs @@ -1,14 +1,14 @@ use taffy::Style; -use gosub_render_backend::RenderBackend; -use gosub_styling::render_tree::RenderTreeNode; +use gosub_render_backend::layout::Node; mod parse; mod parse_properties; const SCROLLBAR_WIDTH: f32 = 16.0; -pub fn get_style_from_node(node: &mut RenderTreeNode) -> Style { +pub fn get_style_from_node(node: &mut impl Node) -> Style { + //TODO: theoretically we should limit this to the taffy layouter, since it doesn't make any sense otherweise let display = parse_properties::parse_display(node); let overflow = parse_properties::parse_overflow(node); let position = parse_properties::parse_position(node); diff --git a/crates/gosub_taffy/src/style/parse.rs b/crates/gosub_taffy/src/style/parse.rs new file mode 100644 index 000000000..52e2260bc --- /dev/null +++ b/crates/gosub_taffy/src/style/parse.rs @@ -0,0 +1,178 @@ +use taffy::style_helpers::{TaffyGridLine, TaffyGridSpan}; +use taffy::{ + AlignContent, AlignItems, Dimension, GridPlacement, LengthPercentage, LengthPercentageAuto, + NonRepeatedTrackSizingFunction, TrackSizingFunction, +}; + +use gosub_render_backend::geo::Size; +use gosub_render_backend::layout::{CssProperty, Node}; + +pub fn parse_len(node: &mut impl Node, name: &str) -> LengthPercentage { + let Some(property) = node.get_property(name) else { + return LengthPercentage::Length(0.0); + }; + + property.compute_value(); + + if let Some(percent) = property.as_percentage() { + return LengthPercentage::Percent(percent); + } + + LengthPercentage::Length(property.unit_to_px()) +} + +pub fn parse_len_auto(node: &mut impl Node, name: &str) -> LengthPercentageAuto { + let Some(property) = node.get_property(name) else { + return LengthPercentageAuto::Auto; + }; + + property.compute_value(); + + if let Some(str) = property.as_string() { + if str == "auto" { + return LengthPercentageAuto::Auto; + } + } + + if let Some(percent) = property.as_percentage() { + return LengthPercentageAuto::Percent(percent); + } + + LengthPercentageAuto::Length(property.unit_to_px()) +} + +pub fn parse_dimension(node: &mut impl Node, name: &str) -> Dimension { + let Some(property) = node.get_property(name) else { + return Dimension::Auto; + }; + + property.compute_value(); + + if let Some(str) = property.as_string() { + if str == "auto" { + return Dimension::Auto; + } + } + + if let Some(percent) = property.as_percentage() { + return Dimension::Percent(percent); + } + + Dimension::Length(property.unit_to_px()) +} + +pub fn parse_text_dim(size: Size, name: &str) -> Dimension { + if name == "width" || name == "max-width" || name == "min-width" { + Dimension::Length(size.width) + } else if name == "height" || name == "max-height" || name == "min-height" { + Dimension::Length(size.height) + } else { + Dimension::Auto + } +} + +pub fn parse_align_i(node: &mut impl Node, name: &str) -> Option { + let display = node.get_property(name)?; + display.compute_value(); + + let value = display.as_string()?; + + match value { + "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 fn parse_align_c(node: &mut impl Node, name: &str) -> Option { + let display = node.get_property(name)?; + + display.compute_value(); + + let value = display.as_string()?; + + match value { + "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 fn parse_tracking_sizing_function( + node: &mut impl Node, + name: &str, +) -> Vec { + let Some(display) = node.get_property(name) else { + return Vec::new(); + }; + + display.compute_value(); + + let Some(_value) = display.as_string() else { + return Vec::new(); + }; + + Vec::new() //TODO: Implement this +} + +#[allow(dead_code)] +pub fn parse_non_repeated_tracking_sizing_function( + _node: &mut impl Node, + _name: &str, +) -> NonRepeatedTrackSizingFunction { + todo!("implement parse_non_repeated_tracking_sizing_function") +} + +pub fn parse_grid_auto(node: &mut impl Node, name: &str) -> Vec { + let Some(display) = node.get_property(name) else { + return Vec::new(); + }; + + display.compute_value(); + + let Some(_value) = display.as_string() else { + return Vec::new(); + }; + + Vec::new() //TODO: Implement this +} + +pub fn parse_grid_placement(node: &mut impl Node, name: &str) -> GridPlacement { + let Some(display) = node.get_property(name) else { + return GridPlacement::Auto; + }; + + display.compute_value(); + + if let Some(value) = &display.as_string() { + return if value.starts_with("span") { + let value = value.trim_start_matches("span").trim(); + + if let Ok(value) = value.parse::() { + GridPlacement::from_span(value) + } else { + GridPlacement::Auto + } + } else if let Ok(value) = value.parse::() { + GridPlacement::from_line_index(value) + } else { + GridPlacement::Auto + }; + } + + if let Some(value) = display.as_number() { + return GridPlacement::from_line_index((value) as i16); + } + GridPlacement::Auto +} diff --git a/crates/gosub_render_utils/src/style/parse_properties.rs b/crates/gosub_taffy/src/style/parse_properties.rs similarity index 53% rename from crates/gosub_render_utils/src/style/parse_properties.rs rename to crates/gosub_taffy/src/style/parse_properties.rs index 2a6710205..e9ab0134c 100644 --- a/crates/gosub_render_utils/src/style/parse_properties.rs +++ b/crates/gosub_taffy/src/style/parse_properties.rs @@ -2,27 +2,25 @@ use regex::Regex; use taffy::prelude::*; use taffy::{Overflow, Point}; -use gosub_render_backend::RenderBackend; -use gosub_styling::css_values::CssValue; -use gosub_styling::render_tree::{RenderNodeData, RenderTreeNode}; +use gosub_render_backend::layout::{CssProperty, Node}; use crate::style::parse::{ parse_align_c, parse_align_i, parse_dimension, parse_grid_auto, parse_grid_placement, parse_len, parse_len_auto, parse_text_dim, parse_tracking_sizing_function, }; -pub(crate) fn parse_display(node: &mut RenderTreeNode) -> Display { +pub fn parse_display(node: &mut impl Node) -> Display { let Some(display) = node.get_property("display") else { return Display::Block; }; display.compute_value(); - let CssValue::String(ref value) = display.actual else { + let Some(value) = display.as_string() else { return Display::Block; }; - match value.as_str() { + match value { "none" => Display::None, "block" => Display::Block, "flex" => Display::Flex, @@ -31,7 +29,7 @@ pub(crate) fn parse_display(node: &mut RenderTreeNode) -> D } } -pub(crate) fn parse_overflow(node: &mut RenderTreeNode) -> Point { +pub fn parse_overflow(node: &mut impl Node) -> Point { fn parse(str: &str) -> Overflow { match str { "visible" => Overflow::Visible, @@ -49,7 +47,7 @@ pub(crate) fn parse_overflow(node: &mut RenderTreeNode) -> if let Some(display) = node.get_property("overflow-x") { display.compute_value(); - if let CssValue::String(ref value) = display.actual { + if let Some(value) = display.as_string() { let x = parse(value); overflow.x = x; }; @@ -58,7 +56,7 @@ pub(crate) fn parse_overflow(node: &mut RenderTreeNode) -> if let Some(display) = node.get_property("overflow-y") { display.compute_value(); - if let CssValue::String(ref value) = display.actual { + if let Some(value) = display.as_string() { let y = parse(value); overflow.y = y; }; @@ -67,27 +65,25 @@ pub(crate) fn parse_overflow(node: &mut RenderTreeNode) -> overflow } -pub(crate) fn parse_position(node: &mut RenderTreeNode) -> Position { +pub fn parse_position(node: &mut impl Node) -> Position { let Some(position) = node.get_property("position") else { return Position::Relative; }; position.compute_value(); - let CssValue::String(ref value) = position.actual else { + let Some(value) = position.as_string() else { return Position::Relative; }; - match value.as_str() { + match value { "relative" => Position::Relative, "absolute" => Position::Absolute, _ => Position::Relative, } } -pub(crate) fn parse_inset( - node: &mut RenderTreeNode, -) -> Rect { +pub fn parse_inset(node: &mut impl Node) -> Rect { Rect { top: parse_len_auto(node, "inset-top"), right: parse_len_auto(node, "inset-right"), @@ -96,8 +92,8 @@ pub(crate) fn parse_inset( } } -pub(crate) fn parse_size(node: &mut RenderTreeNode) -> Size { - if let RenderNodeData::Text(t) = &mut node.data { +pub fn parse_size(node: &mut impl Node) -> Size { + if let Some(t) = node.text_size() { return Size { width: parse_text_dim(t, "width"), height: parse_text_dim(t, "height"), @@ -110,8 +106,8 @@ pub(crate) fn parse_size(node: &mut RenderTreeNode) -> Size } } -pub(crate) fn parse_min_size(node: &mut RenderTreeNode) -> Size { - if let RenderNodeData::Text(t) = &mut node.data { +pub fn parse_min_size(node: &mut impl Node) -> Size { + if let Some(t) = node.text_size() { return Size { width: parse_text_dim(t, "min-width"), height: parse_text_dim(t, "min-height"), @@ -124,8 +120,8 @@ pub(crate) fn parse_min_size(node: &mut RenderTreeNode) -> } } -pub(crate) fn parse_max_size(node: &mut RenderTreeNode) -> Size { - if let RenderNodeData::Text(t) = &mut node.data { +pub fn parse_max_size(node: &mut impl Node) -> Size { + if let Some(t) = node.text_size() { return Size { width: parse_text_dim(t, "max-width"), height: parse_text_dim(t, "max-height"), @@ -138,45 +134,44 @@ pub(crate) fn parse_max_size(node: &mut RenderTreeNode) -> } } -pub(crate) fn parse_aspect_ratio(node: &mut RenderTreeNode) -> Option { +pub fn parse_aspect_ratio(node: &mut impl Node) -> Option { let aspect_ratio = node.get_property("aspect-ratio")?; aspect_ratio.compute_value(); - match &aspect_ratio.actual { - CssValue::Number(value) => Some(*value), - CssValue::String(value) => { - if value == "auto" { - None - } else { - //expecting: number / number - let Ok(regex) = Regex::new(r"(\d+\.?\d*)\s*/\s*(\d+\.?\d*)") else { - return None; - }; - let captures = regex.captures(value)?; - - if captures.len() != 3 { - return None; - } - - let Ok(numerator) = captures[1].parse::() else { - return None; - }; - let Ok(denominator) = captures[2].parse::() else { - return None; - }; - - Some(numerator / denominator) + if let Some(value) = aspect_ratio.as_number() { + return Some(value); + } + + if let Some(value) = aspect_ratio.as_string() { + return if value == "auto" { + None + } else { + //expecting: number / number + let Ok(regex) = Regex::new(r"(\d+\.?\d*)\s*/\s*(\d+\.?\d*)") else { + return None; + }; + let captures = regex.captures(value)?; + + if captures.len() != 3 { + return None; } - } - _ => None, + let Ok(numerator) = captures[1].parse::() else { + return None; + }; + let Ok(denominator) = captures[2].parse::() else { + return None; + }; + + Some(numerator / denominator) + }; } + + None } -pub(crate) fn parse_margin( - node: &mut RenderTreeNode, -) -> Rect { +pub fn parse_margin(node: &mut impl Node) -> Rect { Rect { top: parse_len_auto(node, "margin-top"), right: parse_len_auto(node, "margin-right"), @@ -185,9 +180,7 @@ pub(crate) fn parse_margin( } } -pub(crate) fn parse_padding( - node: &mut RenderTreeNode, -) -> Rect { +pub fn parse_padding(node: &mut impl Node) -> Rect { Rect { top: parse_len(node, "padding-top"), right: parse_len(node, "padding-right"), @@ -196,9 +189,7 @@ pub(crate) fn parse_padding( } } -pub(crate) fn parse_border( - node: &mut RenderTreeNode, -) -> Rect { +pub fn parse_border(node: &mut impl Node) -> Rect { Rect { top: parse_len(node, "border-top-width"), right: parse_len(node, "border-right-width"), @@ -207,18 +198,14 @@ pub(crate) fn parse_border( } } -pub(crate) fn parse_align_items( - node: &mut RenderTreeNode, -) -> Option { +pub fn parse_align_items(node: &mut impl Node) -> Option { let display = node.get_property("align-items")?; display.compute_value(); - let CssValue::String(ref value) = display.actual else { - return None; - }; + let value = display.as_string()?; - match value.as_str() { + match value { "start" => Some(AlignItems::Start), "end" => Some(AlignItems::End), "flex-start" => Some(AlignItems::FlexStart), @@ -230,167 +217,148 @@ pub(crate) fn parse_align_items( } } -pub(crate) fn parse_align_self( - node: &mut RenderTreeNode, -) -> Option { +pub fn parse_align_self(node: &mut impl Node) -> Option { parse_align_i(node, "align-self") } -pub(crate) fn parse_justify_items( - node: &mut RenderTreeNode, -) -> Option { +pub fn parse_justify_items(node: &mut impl Node) -> Option { parse_align_i(node, "justify-items") } -pub(crate) fn parse_justify_self( - node: &mut RenderTreeNode, -) -> Option { +pub fn parse_justify_self(node: &mut impl Node) -> Option { parse_align_i(node, "justify-self") } -pub(crate) fn parse_align_content( - node: &mut RenderTreeNode, -) -> Option { +pub fn parse_align_content(node: &mut impl Node) -> Option { parse_align_c(node, "align-content") } -pub(crate) fn parse_justify_content( - node: &mut RenderTreeNode, -) -> Option { +pub fn parse_justify_content(node: &mut impl Node) -> Option { parse_align_c(node, "justify-content") } -pub(crate) fn parse_gap(node: &mut RenderTreeNode) -> Size { +pub fn parse_gap(node: &mut impl Node) -> Size { Size { width: parse_len(node, "column-gap"), height: parse_len(node, "row-gap"), } } -pub(crate) fn parse_flex_direction( - node: &mut RenderTreeNode, -) -> FlexDirection { +pub fn parse_flex_direction(node: &mut impl Node) -> FlexDirection { let Some(property) = node.get_property("flex-direction") else { return FlexDirection::Row; }; property.compute_value(); - match &property.actual { - CssValue::String(value) => match value.as_str() { + if let Some(value) = property.as_string() { + match value { "row" => FlexDirection::Row, "row-reverse" => FlexDirection::RowReverse, "column" => FlexDirection::Column, "column-reverse" => FlexDirection::ColumnReverse, _ => FlexDirection::Row, - }, - _ => FlexDirection::Row, + } + } else { + FlexDirection::Row } } -pub(crate) fn parse_flex_wrap(node: &mut RenderTreeNode) -> FlexWrap { +pub fn parse_flex_wrap(node: &mut impl Node) -> FlexWrap { let Some(property) = node.get_property("flex-wrap") else { return FlexWrap::NoWrap; }; property.compute_value(); - match &property.actual { - CssValue::String(value) => match value.as_str() { + if let Some(value) = property.as_string() { + match value { "nowrap" => FlexWrap::NoWrap, "wrap" => FlexWrap::Wrap, "wrap-reverse" => FlexWrap::WrapReverse, _ => FlexWrap::NoWrap, - }, - _ => FlexWrap::NoWrap, + } + } else { + FlexWrap::NoWrap } } -pub(crate) fn parse_flex_basis(node: &mut RenderTreeNode) -> Dimension { +pub fn parse_flex_basis(node: &mut impl Node) -> Dimension { parse_dimension(node, "flex-basis") } -pub(crate) fn parse_flex_grow(node: &mut RenderTreeNode) -> f32 { +pub fn parse_flex_grow(node: &mut impl Node) -> f32 { let Some(property) = node.get_property("flex-grow") else { return 0.0; }; property.compute_value(); - match &property.actual { - CssValue::Number(value) => *value, - _ => 0.0, + if let Some(value) = property.as_number() { + value + } else { + 0.0 } } -pub(crate) fn parse_flex_shrink(node: &mut RenderTreeNode) -> f32 { +pub fn parse_flex_shrink(node: &mut impl Node) -> f32 { let Some(property) = node.get_property("flex-shrink") else { return 1.0; }; property.compute_value(); - match &property.actual { - CssValue::Number(value) => *value, - _ => 1.0, + if let Some(value) = property.as_number() { + value + } else { + 1.0 } } -pub(crate) fn parse_grid_template_rows( - node: &mut RenderTreeNode, -) -> Vec { +pub fn parse_grid_template_rows(node: &mut impl Node) -> Vec { parse_tracking_sizing_function(node, "grid-template-rows") } -pub(crate) fn parse_grid_template_columns( - node: &mut RenderTreeNode, -) -> Vec { +pub fn parse_grid_template_columns(node: &mut impl Node) -> Vec { parse_tracking_sizing_function(node, "grid-template-columns") } -pub(crate) fn parse_grid_auto_rows( - node: &mut RenderTreeNode, -) -> Vec { +pub fn parse_grid_auto_rows(node: &mut impl Node) -> Vec { parse_grid_auto(node, "grid-auto-rows") } -pub(crate) fn parse_grid_auto_columns( - node: &mut RenderTreeNode, -) -> Vec { +pub fn parse_grid_auto_columns(node: &mut impl Node) -> Vec { parse_grid_auto(node, "grid-auto-columns") } -pub(crate) fn parse_grid_auto_flow(node: &mut RenderTreeNode) -> GridAutoFlow { +pub fn parse_grid_auto_flow(node: &mut impl Node) -> GridAutoFlow { let Some(property) = node.get_property("grid-auto-flow") else { return GridAutoFlow::Row; }; property.compute_value(); - match &property.actual { - CssValue::String(value) => match value.as_str() { + if let Some(value) = property.as_string() { + match value { "row" => GridAutoFlow::Row, "column" => GridAutoFlow::Column, "row dense" => GridAutoFlow::RowDense, "column dense" => GridAutoFlow::ColumnDense, _ => GridAutoFlow::Row, - }, - _ => GridAutoFlow::Row, + } + } else { + GridAutoFlow::Row } } -pub(crate) fn parse_grid_row( - node: &mut RenderTreeNode, -) -> Line { +pub fn parse_grid_row(node: &mut impl Node) -> Line { Line { start: parse_grid_placement(node, "grid-row-start"), end: parse_grid_placement(node, "grid-row-end"), } } -pub(crate) fn parse_grid_column( - node: &mut RenderTreeNode, -) -> Line { +pub fn parse_grid_column(node: &mut impl Node) -> Line { Line { start: parse_grid_placement(node, "grid-column-start"), end: parse_grid_placement(node, "grid-column-end"), diff --git a/crates/gosub_useragent/src/application.rs b/crates/gosub_useragent/src/application.rs index e9ecbb14b..31af2a3f1 100644 --- a/crates/gosub_useragent/src/application.rs +++ b/crates/gosub_useragent/src/application.rs @@ -1,28 +1,36 @@ -use anyhow::anyhow; use std::collections::HashMap; +use anyhow::anyhow; use url::Url; use winit::application::ApplicationHandler; use winit::event::WindowEvent; use winit::event_loop::{ActiveEventLoop, EventLoop, EventLoopProxy}; use winit::window::WindowId; +use gosub_render_backend::layout::{LayoutTree, Layouter}; use gosub_render_backend::RenderBackend; use gosub_renderer::draw::SceneDrawer; use gosub_shared::types::Result; use crate::window::Window; -pub struct Application<'a, D: SceneDrawer, B: RenderBackend> { +pub struct Application< + 'a, + D: SceneDrawer, + B: RenderBackend, + L: Layouter, + LT: LayoutTree, +> { open_windows: Vec>, // Vec of Windows, each with a Vec of URLs, representing tabs - windows: HashMap>, + windows: HashMap>, backend: B, + layouter: L, proxy: Option>, debug: bool, } -impl<'a, D: SceneDrawer, B: RenderBackend> ApplicationHandler - for Application<'a, D, B> +impl<'a, D: SceneDrawer, B: RenderBackend, L: Layouter, LT: LayoutTree> + ApplicationHandler for Application<'a, D, B, L, LT> { fn resumed(&mut self, _event_loop: &ActiveEventLoop) { for window in self.windows.values_mut() { @@ -35,7 +43,13 @@ impl<'a, D: SceneDrawer, B: RenderBackend> ApplicationHandler fn user_event(&mut self, event_loop: &ActiveEventLoop, event: CustomEvent) { match event { CustomEvent::OpenWindow(url) => { - let mut window = match Window::new(event_loop, &mut self.backend, url, self.debug) { + let mut window = match Window::new( + event_loop, + &mut self.backend, + self.layouter.clone(), + url, + self.debug, + ) { Ok(window) => window, Err(e) => { eprintln!("Error opening window: {e:?}"); @@ -57,6 +71,7 @@ impl<'a, D: SceneDrawer, B: RenderBackend> ApplicationHandler let mut window = match Window::new( event_loop, &mut self.backend, + self.layouter.clone(), urls[0].clone(), self.debug, ) { @@ -98,11 +113,14 @@ impl<'a, D: SceneDrawer, B: RenderBackend> ApplicationHandler } } -impl<'a, D: SceneDrawer, B: RenderBackend> Application<'a, D, B> { - pub fn new(backend: B, debug: bool) -> Self { +impl<'a, D: SceneDrawer, B: RenderBackend, L: Layouter, LT: LayoutTree> + Application<'a, D, B, L, LT> +{ + pub fn new(backend: B, layouter: L, debug: bool) -> Self { Self { windows: HashMap::new(), backend, + layouter, proxy: None, open_windows: Vec::new(), debug, @@ -117,7 +135,7 @@ impl<'a, D: SceneDrawer, B: RenderBackend> Application<'a, D, B> { self.open_windows.append(&mut windows); } - pub fn add_window(&mut self, window: Window<'a, D, B>) { + pub fn add_window(&mut self, window: Window<'a, D, B, L, LT>) { self.windows.insert(window.window.id(), window); } diff --git a/crates/gosub_useragent/src/event_loop.rs b/crates/gosub_useragent/src/event_loop.rs index 5cc42fa4c..9dae4a3d4 100644 --- a/crates/gosub_useragent/src/event_loop.rs +++ b/crates/gosub_useragent/src/event_loop.rs @@ -1,13 +1,16 @@ -use winit::event::{MouseScrollDelta, WindowEvent}; -use winit::event_loop::ActiveEventLoop; - +use gosub_render_backend::layout::{LayoutTree, Layouter}; use gosub_render_backend::{Point, RenderBackend, SizeU32, FP}; use gosub_renderer::draw::SceneDrawer; use gosub_shared::types::Result; +use winit::event::{ElementState, MouseScrollDelta, WindowEvent}; +use winit::event_loop::ActiveEventLoop; +use winit::keyboard::{KeyCode, PhysicalKey}; use crate::window::{Window, WindowState}; -impl, B: RenderBackend> Window<'_, D, B> { +impl<'a, D: SceneDrawer, B: RenderBackend, L: Layouter, LT: LayoutTree> + Window<'a, D, B, L, LT> +{ pub fn event( &mut self, el: &ActiveEventLoop, @@ -81,6 +84,30 @@ impl, B: RenderBackend> Window<'_, D, B> { self.window.request_redraw(); } + WindowEvent::KeyboardInput { event, .. } => { + if event.repeat || event.state == ElementState::Released { + return Ok(()); + } + + let Some(tab) = self.tabs.get_current_tab() else { + return Ok(()); + }; + + if let PhysicalKey::Code(code) = event.physical_key { + match code { + KeyCode::KeyD => { + tab.data.toggle_debug(); + self.window.request_redraw(); + } + KeyCode::KeyC => { + tab.data.clear_buffers(); + self.window.request_redraw(); + } + _ => {} + } + } + } + _ => {} } diff --git a/crates/gosub_useragent/src/tabs.rs b/crates/gosub_useragent/src/tabs.rs index 619d4ed24..cbea4855f 100644 --- a/crates/gosub_useragent/src/tabs.rs +++ b/crates/gosub_useragent/src/tabs.rs @@ -1,18 +1,19 @@ use slotmap::{DefaultKey, SlotMap}; use url::Url; +use gosub_render_backend::layout::{LayoutTree, Layouter}; use gosub_render_backend::RenderBackend; use gosub_renderer::draw::SceneDrawer; use gosub_shared::types::Result; -pub struct Tabs, B: RenderBackend> { - pub tabs: SlotMap>, +pub struct Tabs, B: RenderBackend, L: Layouter, LT: LayoutTree> { + pub tabs: SlotMap>, pub active: TabID, - _marker: std::marker::PhantomData, + _marker: std::marker::PhantomData<(B, L, LT)>, } -impl, B: RenderBackend> Tabs { - pub fn new(initial: Tab) -> Self { +impl, L: Layouter, LT: LayoutTree, B: RenderBackend> Tabs { + pub fn new(initial: Tab) -> Self { let mut tabs = SlotMap::new(); let active = TabID(tabs.insert(initial)); @@ -23,7 +24,7 @@ impl, B: RenderBackend> Tabs { } } - pub fn add_tab(&mut self, tab: Tab) -> TabID { + pub fn add_tab(&mut self, tab: Tab) -> TabID { TabID(self.tabs.insert(tab)) } @@ -35,25 +36,25 @@ impl, B: RenderBackend> Tabs { self.active = id; } - pub fn get_current_tab(&mut self) -> Option<&mut Tab> { + pub fn get_current_tab(&mut self) -> Option<&mut Tab> { self.tabs.get_mut(self.active.0) } - pub(crate) fn from_url(url: Url, debug: bool) -> Result { - let tab = Tab::from_url(url, debug)?; + pub(crate) fn from_url(url: Url, layouter: L, debug: bool) -> Result { + let tab = Tab::from_url(url, layouter, debug)?; Ok(Self::new(tab)) } } -pub struct Tab, B: RenderBackend> { +pub struct Tab, B: RenderBackend, L: Layouter, LT: LayoutTree> { pub title: String, pub url: Url, pub data: D, - _marker: std::marker::PhantomData, + _marker: std::marker::PhantomData<(B, L, LT)>, } -impl, B: RenderBackend> Tab { +impl, B: RenderBackend, L: Layouter, LT: LayoutTree> Tab { pub fn new(title: String, url: Url, data: D) -> Self { Self { title, @@ -63,8 +64,8 @@ impl, B: RenderBackend> Tab { } } - pub fn from_url(url: Url, debug: bool) -> Result { - let data = D::from_url(url.clone(), debug)?; + pub fn from_url(url: Url, layouter: L, debug: bool) -> Result { + let data = D::from_url(url.clone(), layouter, debug)?; Ok(Self { title: url.as_str().to_string(), diff --git a/crates/gosub_useragent/src/window.rs b/crates/gosub_useragent/src/window.rs index 59cfecc84..746704d6b 100644 --- a/crates/gosub_useragent/src/window.rs +++ b/crates/gosub_useragent/src/window.rs @@ -2,14 +2,16 @@ use std::sync::Arc; use anyhow::anyhow; use log::warn; +use url::Url; use winit::dpi::LogicalSize; use winit::event_loop::ActiveEventLoop; use winit::window::{Window as WinitWindow, WindowId}; -use gosub_render_backend::{RenderBackend, SizeU32}; +use gosub_render_backend::geo::SizeU32; +use gosub_render_backend::layout::{LayoutTree, Layouter}; +use gosub_render_backend::RenderBackend; use gosub_renderer::draw::SceneDrawer; use gosub_shared::types::Result; -use url::Url; use crate::tabs::Tabs; @@ -19,17 +21,20 @@ pub enum WindowState<'a, B: RenderBackend> { Suspended, } -pub struct Window<'a, D: SceneDrawer, B: RenderBackend> { +pub struct Window<'a, D: SceneDrawer, B: RenderBackend, L: Layouter, LT: LayoutTree> { pub(crate) state: WindowState<'a, B>, pub(crate) window: Arc, pub(crate) renderer_data: B::WindowData<'a>, - pub(crate) tabs: Tabs, + pub(crate) tabs: Tabs, } -impl<'a, D: SceneDrawer, B: RenderBackend> Window<'a, D, B> { +impl<'a, D: SceneDrawer, B: RenderBackend, L: Layouter, LT: LayoutTree> + Window<'a, D, B, L, LT> +{ pub fn new( event_loop: &ActiveEventLoop, backend: &mut B, + layouter: L, default_url: Url, debug: bool, ) -> Result { @@ -41,7 +46,7 @@ impl<'a, D: SceneDrawer, B: RenderBackend> Window<'a, D, B> { state: WindowState::Suspended, window, renderer_data, - tabs: Tabs::from_url(default_url, debug)?, + tabs: Tabs::from_url(default_url, layouter, debug)?, }) } diff --git a/crates/gosub_vello/src/border.rs b/crates/gosub_vello/src/border.rs index 9ab9e6e62..b858317bd 100644 --- a/crates/gosub_vello/src/border.rs +++ b/crates/gosub_vello/src/border.rs @@ -2,13 +2,13 @@ use smallvec::SmallVec; use vello::kurbo::{Arc, BezPath, Cap, Join, RoundedRectRadii, Stroke}; use vello::Scene; +use crate::{Brush, Rect, Transform, VelloBackend}; +use gosub_render_backend::geo::FP; use gosub_render_backend::{ Border as TBorder, BorderRadius as TBorderRadius, BorderSide as TBorderSide, BorderStyle, - Radius, RenderBorder, FP, + Radius, RenderBorder, }; -use crate::{Brush, Rect, Transform, VelloBackend}; - pub struct Border { pub(crate) left: Option, pub(crate) right: Option, diff --git a/crates/gosub_vello/src/gradient.rs b/crates/gosub_vello/src/gradient.rs index 93cb807bc..82c059f70 100644 --- a/crates/gosub_vello/src/gradient.rs +++ b/crates/gosub_vello/src/gradient.rs @@ -2,9 +2,9 @@ use vello::peniko::{ ColorStop as VelloColorStop, ColorStops as VelloColorStops, Gradient as VelloGradient, }; -use gosub_render_backend::{ColorStop, ColorStops, Gradient as TGradient, Point, FP}; - use crate::{Convert, VelloBackend}; +use gosub_render_backend::geo::{Point, FP}; +use gosub_render_backend::{ColorStop, ColorStops, Gradient as TGradient}; pub struct Gradient(pub(crate) VelloGradient); diff --git a/crates/gosub_vello/src/image.rs b/crates/gosub_vello/src/image.rs index e0899fabe..977c286b6 100644 --- a/crates/gosub_vello/src/image.rs +++ b/crates/gosub_vello/src/image.rs @@ -3,7 +3,8 @@ use std::sync::Arc; use image::{DynamicImage, GenericImageView}; use vello::peniko::{Blob, Format, Image as VelloImage}; -use gosub_render_backend::{Image as TImage, FP}; +use gosub_render_backend::geo::FP; +use gosub_render_backend::Image as TImage; pub struct Image(pub(crate) VelloImage); diff --git a/crates/gosub_vello/src/lib.rs b/crates/gosub_vello/src/lib.rs index ff7f05255..fed09519b 100644 --- a/crates/gosub_vello/src/lib.rs +++ b/crates/gosub_vello/src/lib.rs @@ -9,9 +9,8 @@ use crate::render::{Renderer, RendererOptions}; pub use border::*; pub use brush::*; pub use color::*; -use gosub_render_backend::{ - Point, RenderBackend, RenderRect, RenderText, Scene as TScene, SizeU32, WindowHandle, -}; +use gosub_render_backend::geo::{Point, SizeU32}; +use gosub_render_backend::{RenderBackend, RenderRect, RenderText, Scene as TScene, WindowHandle}; use gosub_shared::types::Result; pub use gradient::*; pub use image::*; diff --git a/crates/gosub_vello/src/rect.rs b/crates/gosub_vello/src/rect.rs index d1c8f79fa..52dfc292a 100644 --- a/crates/gosub_vello/src/rect.rs +++ b/crates/gosub_vello/src/rect.rs @@ -1,4 +1,5 @@ -use gosub_render_backend::{Point, Rect as TRect, Size, FP}; +use gosub_render_backend::geo::{Point, Size, FP}; +use gosub_render_backend::Rect as TRect; use vello::kurbo::Rect as VelloRect; pub struct Rect(pub(crate) VelloRect); diff --git a/crates/gosub_vello/src/text.rs b/crates/gosub_vello/src/text.rs index 8c095a331..49c722ce2 100644 --- a/crates/gosub_vello/src/text.rs +++ b/crates/gosub_vello/src/text.rs @@ -4,7 +4,8 @@ use vello::peniko::{Blob, Fill, Font, StyleRef}; use vello::skrifa::{instance::Size as FSize, FontRef, MetadataProvider}; use vello::Scene; -use gosub_render_backend::{PreRenderText as TPreRenderText, RenderText, Size, Text as TText, FP}; +use gosub_render_backend::geo::{Size, FP}; +use gosub_render_backend::{PreRenderText as TPreRenderText, RenderText, Text as TText}; use gosub_typeface::{BACKUP_FONT, DEFAULT_LH, FONT_RENDERER_CACHE}; use crate::VelloBackend; diff --git a/crates/gosub_vello/src/transform.rs b/crates/gosub_vello/src/transform.rs index 27772c592..3d9738bd6 100644 --- a/crates/gosub_vello/src/transform.rs +++ b/crates/gosub_vello/src/transform.rs @@ -2,7 +2,8 @@ use std::ops::{Mul, MulAssign}; use vello::kurbo::Affine; -use gosub_render_backend::{Point, Transform as TTransform, FP}; +use gosub_render_backend::geo::{Point, FP}; +use gosub_render_backend::Transform as TTransform; #[derive(Debug, Clone, Copy, PartialEq)] pub struct Transform(pub(crate) Affine); diff --git a/src/bin/renderer.rs b/src/bin/renderer.rs index aaaee0ff7..09306079a 100644 --- a/src/bin/renderer.rs +++ b/src/bin/renderer.rs @@ -3,9 +3,16 @@ use url::Url; use gosub_renderer::render_tree::TreeDrawer; use gosub_shared::types::Result; +use gosub_styling::render_tree::RenderTree; +use gosub_taffy::TaffyLayouter; use gosub_useragent::application::Application; use gosub_vello::VelloBackend; +type Backend = VelloBackend; +type Layouter = TaffyLayouter; +type Drawer = TreeDrawer; +type Tree = RenderTree; + fn main() -> Result<()> { let matches = clap::Command::new("Gosub Renderer") .arg( @@ -25,10 +32,12 @@ fn main() -> Result<()> { let url: String = matches.get_one::("url").expect("url").to_string(); let debug = matches.get_one::("debug").copied().unwrap_or(false); - // let mut rt = load_html_rendertree(&url)?; + // let drawer: TreeDrawer = TreeDrawer::new(todo!(), TaffyLayouter, "".to_string().into(), debug); - let mut application: Application, VelloBackend> = - Application::new(VelloBackend::new(), debug); + // let mut rt = load_html_rendertree(&url)?; + // + let mut application: Application = + Application::new(VelloBackend::new(), TaffyLayouter, debug); application.initial_tab(Url::parse(&url)?); diff --git a/src/bin/resources/gosub.html b/src/bin/resources/gosub.html index 907ffa536..069146eb5 100644 --- a/src/bin/resources/gosub.html +++ b/src/bin/resources/gosub.html @@ -138,7 +138,8 @@

Gosub

The gateway to optimized searching and browsing

- + +
Join us on the journey to a new web browser