diff --git a/crates/gosub_render_backend/src/layout.rs b/crates/gosub_render_backend/src/layout.rs index d59ee9ad9..e290ccc5b 100644 --- a/crates/gosub_render_backend/src/layout.rs +++ b/crates/gosub_render_backend/src/layout.rs @@ -1,6 +1,7 @@ +use std::fmt::Debug; + use gosub_shared::types::Result; use gosub_typeface::font::{Font, Glyph}; -use std::fmt::Debug; use crate::geo::{Point, Rect, Size, SizeU32}; @@ -52,6 +53,17 @@ pub trait Layout: Default { /// Size of the scroll box (content box without overflow), including scrollbars (if any) fn size(&self) -> Size; + fn size_or(&self) -> Option; + + fn set_size_and_content(&mut self, size: SizeU32) { + self.set_size(size); + self.set_content(size); + } + + fn set_size(&mut self, size: SizeU32); + + fn set_content(&mut self, size: SizeU32); + /// Size of the content box (content without scrollbars, but with overflow) fn content(&self) -> Size; fn content_box(&self) -> Rect { diff --git a/crates/gosub_renderer/src/draw.rs b/crates/gosub_renderer/src/draw.rs index 5e673ef43..d8af8242d 100644 --- a/crates/gosub_renderer/src/draw.rs +++ b/crates/gosub_renderer/src/draw.rs @@ -1,11 +1,9 @@ use std::sync::mpsc::Sender; use anyhow::anyhow; +use log::warn; use url::Url; -use crate::debug::scale::px_scale; -use crate::draw::img::request_img; -use crate::render_tree::{load_html_rendertree, TreeDrawer}; use gosub_css3::colors::RgbColor; use gosub_css3::stylesheet::CssValue; use gosub_html5::node::NodeId; @@ -20,12 +18,15 @@ use gosub_render_backend::{ use gosub_rendering::position::PositionTree; use gosub_shared::types::Result; use gosub_styling::render_tree::{RenderNodeData, RenderTree, RenderTreeNode}; -use log::warn; + +use crate::debug::scale::px_scale; +use crate::draw::img::request_img; +use crate::render_tree::{load_html_rendertree, TreeDrawer}; mod img; pub trait SceneDrawer> { - fn draw(&mut self, backend: &mut B, data: &mut B::WindowData<'_>, size: SizeU32); + fn draw(&mut self, backend: &mut B, data: &mut B::WindowData<'_>, size: SizeU32) -> bool; fn mouse_move(&mut self, backend: &mut B, x: FP, y: FP) -> bool; fn scroll(&mut self, point: Point); @@ -40,6 +41,8 @@ pub trait SceneDrawer> { fn unselect_element(&mut self); fn send_nodes(&mut self, sender: Sender); + + fn set_needs_redraw(&mut self); } const DEBUG_CONTENT_COLOR: (u8, u8, u8) = (0, 192, 255); //rgb(0, 192, 255) @@ -54,9 +57,9 @@ where <::Text as Text>::Font: From<<::TextLayout as TextLayout>::Font>, { - fn draw(&mut self, backend: &mut B, data: &mut B::WindowData<'_>, size: SizeU32) { + fn draw(&mut self, backend: &mut B, data: &mut B::WindowData<'_>, size: SizeU32) -> bool { if !self.dirty && self.size == Some(size) { - return; + return false; } if self.tree_scene.is_none() || self.size != Some(size) { @@ -134,6 +137,14 @@ where backend.apply_scene(data, &scale, None); } + + if self.dirty { + self.dirty = false; + + return true; + } + + false } fn mouse_move(&mut self, _backend: &mut B, x: FP, y: FP) -> bool { @@ -219,6 +230,10 @@ where fn send_nodes(&mut self, sender: Sender) { let _ = sender.send(self.tree.desc()); } + + fn set_needs_redraw(&mut self) { + self.dirty = true; + } } struct Drawer<'s, 't, B: RenderBackend, L: Layouter> { @@ -267,6 +282,8 @@ where } fn render_node(&mut self, id: NodeId, pos: &mut Point) -> anyhow::Result<()> { + let mut needs_redraw = false; + let node = self .drawer .tree @@ -277,9 +294,11 @@ where pos.x += p.x as FP; pos.y += p.y as FP; - let border_radius = + let (border_radius, redraw) = render_bg::(node, self.scene, pos, &mut self.svg, &self.drawer.fetcher); + needs_redraw |= redraw; + if let RenderNodeData::Element(element) = &node.data { if element.name() == "img" { let src = element @@ -289,9 +308,14 @@ where let url = src.as_str(); - let size = node.layout.size(); + let size = node.layout.size_or().map(|x| x.u32()); + + let img = request_img(&self.drawer.fetcher, &mut self.svg, url, size)?; - let img = request_img(&self.drawer.fetcher, &mut self.svg, url, size.u32())?; + if size.is_none() { + node.layout.set_size_and_content(img.size()); + needs_redraw |= true; + } let fit = element .attributes @@ -299,11 +323,18 @@ where .map(|prop| prop.as_str()) .unwrap_or("contain"); + let size = size.unwrap_or(img.size()).f32(); + render_image::(img, self.scene, *pos, size, border_radius, fit)?; } } render_text::(node, self.scene, pos); + + if needs_redraw { + self.drawer.set_needs_redraw() + } + Ok(()) } } @@ -374,7 +405,7 @@ fn render_bg( pos: &Point, svg: &mut B::SVGRenderer, fetcher: &Fetcher, -) -> (FP, FP, FP, FP) { +) -> ((FP, FP, FP, FP), bool) { let bg_color = node .properties .get("background-color") @@ -485,22 +516,32 @@ fn render_bg( } }); + let mut redraw = false; + if let Some(url) = background_image { - let img = match request_img(fetcher, svg, url, node.layout.size().u32()) { + let size = node.layout.size_or().map(|x| x.u32()); + + let img = match request_img(fetcher, svg, url, size) { Ok(img) => img, Err(e) => { eprintln!("Error loading image: {:?}", e); - return border_radius; + return (border_radius, false); } }; + if size.is_none() { + node.layout.set_size_and_content(img.size()); + + redraw = true; + } + let _ = render_image::(img, scene, *pos, node.layout.size(), border_radius, "fill") .map_err(|e| { eprintln!("Error rendering image: {:?}", e); }); } - border_radius + (border_radius, redraw) } enum Side { diff --git a/crates/gosub_renderer/src/draw/img.rs b/crates/gosub_renderer/src/draw/img.rs index d2e0026a0..ff0bc96c4 100644 --- a/crates/gosub_renderer/src/draw/img.rs +++ b/crates/gosub_renderer/src/draw/img.rs @@ -1,15 +1,17 @@ +use std::io::Cursor; + use anyhow::anyhow; + use gosub_net::http::fetcher::Fetcher; use gosub_render_backend::svg::SvgRenderer; use gosub_render_backend::{Image as _, ImageBuffer, RenderBackend, SizeU32}; use gosub_shared::types::Result; -use std::io::Cursor; pub fn request_img( fetcher: &Fetcher, svg_renderer: &mut B::SVGRenderer, url: &str, - size: SizeU32, + size: Option, ) -> Result> { let res = fetcher.get(url)?; @@ -26,7 +28,11 @@ pub fn request_img( let svg = >::parse_external(svg)?; - svg_renderer.render_with_size(&svg, size)? + if let Some(size) = size { + svg_renderer.render_with_size(&svg, size)? + } else { + svg_renderer.render(&svg)? + } } else { let format = image::guess_format(&img)?; let img = image::load(Cursor::new(img), format)?; //In that way we don't need to copy the image data diff --git a/crates/gosub_taffy/src/compute/inline.rs b/crates/gosub_taffy/src/compute/inline.rs index 4de4171e0..2400a9125 100644 --- a/crates/gosub_taffy/src/compute/inline.rs +++ b/crates/gosub_taffy/src/compute/inline.rs @@ -1,18 +1,20 @@ use std::sync::{LazyLock, Mutex}; + +use log::warn; +use parley::layout::{Alignment, PositionedLayoutItem}; +use parley::style::{FontSettings, FontStack, FontStyle, FontVariation, FontWeight, StyleProperty}; +use parley::{FontContext, InlineBox, LayoutContext}; use taffy::{ AvailableSpace, CollapsibleMarginSet, Layout, LayoutInput, LayoutOutput, LayoutPartialTree, - NodeId, Point, Rect, RunMode, Size, SizingMode, + NodeId, Point, Rect, RunMode, Size, }; -use crate::text::{Font, TextLayout}; -use crate::{Display, LayoutDocument, TaffyLayouter}; use gosub_render_backend::geo; use gosub_render_backend::layout::{CssProperty, HasTextLayout, LayoutTree, Node}; use gosub_typeface::font::Glyph; -use log::warn; -use parley::layout::{Alignment, PositionedLayoutItem}; -use parley::style::{FontSettings, FontStack, FontStyle, FontVariation, FontWeight, StyleProperty}; -use parley::{FontContext, InlineBox, LayoutContext}; + +use crate::text::{Font, TextLayout}; +use crate::{Display, LayoutDocument, TaffyLayouter}; static FONT_CX: LazyLock> = LazyLock::new(|| Mutex::new(FontContext::default())); @@ -23,7 +25,7 @@ pub fn compute_inline_layout>( ) -> LayoutOutput { layout_input.known_dimensions = Size::NONE; layout_input.run_mode = RunMode::PerformLayout; //TODO: We should respect the run mode - layout_input.sizing_mode = SizingMode::ContentSize; + // layout_input.sizing_mode = SizingMode::ContentSize; let Some(children) = tree.0.children(nod_id) else { return LayoutOutput::HIDDEN; @@ -93,7 +95,7 @@ pub fn compute_inline_layout>( id: node_id, }); } else { - if u64::from(node_id) == 477u64 { + if u64::from(node_id) == 22u64 { println!("inline_box : {:?}", node_id); } @@ -105,9 +107,9 @@ pub fn compute_inline_layout>( if cache.display == Display::InlineBlock { //TODO: handle margins here - out.size - } else { out.content_size + } else { + out.size } } else { out.content_size @@ -126,7 +128,7 @@ pub fn compute_inline_layout>( return LayoutOutput::HIDDEN; } - if !inline_boxes.is_empty() && str_buf.is_empty() { + if str_buf.is_empty() { str_buf.push(0 as char); } @@ -272,144 +274,147 @@ pub fn compute_inline_layout>( height: layout.height().ceil(), }; + let mut current_node_idx = 0; + let mut current_node_id = LT::NodeId::from(0); + let mut current_to = 0; + if let Some(first) = text_node_data.first() { - let mut current_node_idx = 0; - let mut current_node_id = LT::NodeId::from(first.id.into()); - let mut current_to = first.to; + current_node_id = LT::NodeId::from(first.id.into()); + current_to = first.to; + } - let mut current_glyph_idx = 0; + let mut current_glyph_idx = 0; - 'lines: for line in layout.lines() { - let metrics = line.metrics(); + 'lines: for line in layout.lines() { + let metrics = line.metrics(); - let height = metrics.line_height; + let height = metrics.line_height; - for item in line.items() { - match item { - PositionedLayoutItem::GlyphRun(run) => { - let mut offset = 0.0; + for item in line.items() { + match item { + PositionedLayoutItem::GlyphRun(run) => { + let mut offset = 0.0; - let grun = run.run(); - let fs = grun.font_size(); + let grun = run.run(); + let fs = grun.font_size(); - let glyphs = run - .glyphs() - .map(|g| { - let gl = Glyph { - id: g.id, - x: g.x + offset, - y: g.y, - }; + let glyphs = run + .glyphs() + .map(|g| { + let gl = Glyph { + id: g.id, + x: g.x + offset, + y: g.y, + }; - offset += g.advance; + offset += g.advance; - gl - }) - .collect::>(); + gl + }) + .collect::>(); - let run_y = run.baseline(); + let run_y = run.baseline(); - if current_node_id.into() == 161 || current_node_id.into() == 163 { - println!("current_node_id: {:?}", current_node_id.into()); - println!("first glyph: {:?}", glyphs.get(2)); - } + if current_node_id.into() == 161 || current_node_id.into() == 163 { + println!("current_node_id: {:?}", current_node_id.into()); + println!("first glyph: {:?}", glyphs.get(2)); + } - current_glyph_idx += glyphs.len(); + current_glyph_idx += glyphs.len(); - if current_glyph_idx > current_to { - current_node_idx += 1; + if current_glyph_idx > current_to { + current_node_idx += 1; - if let Some(next) = text_node_data.get(current_node_idx) { - current_to = next.to; - current_node_id = LT::NodeId::from(next.id.into()); - } else { - break 'lines; - } + if let Some(next) = text_node_data.get(current_node_idx) { + current_to = next.to; + current_node_id = LT::NodeId::from(next.id.into()); + } else { + break 'lines; } + } - let size = geo::Size { - width: run.advance(), - height, - }; - - let coords = grun.normalized_coords().to_owned(); + let size = geo::Size { + width: run.advance(), + height, + }; + + let coords = grun.normalized_coords().to_owned(); + + let text_layout = TextLayout { + size, + font_size: fs, + font: Font(grun.font().clone()), + glyphs, + coords, + }; + + let Some(node) = tree.0.get_node(current_node_id) else { + continue; + }; + + node.set_text_layout(text_layout); + + let size = Size { + width: size.width, + height: size.height, + }; + + if current_node_id.into() == 160 { + println!("current_node_id: {:?}", current_node_id.into()); + println!("size: {:?}", size); + + println!( + "location: {:?}", + Point { + x: run.offset(), + y: run_y, + } + ); + } - let text_layout = TextLayout { + tree.set_unrounded_layout( + NodeId::new(current_node_id.into()), + &Layout { size, - font_size: fs, - font: Font(grun.font().clone()), - glyphs, - coords, - }; - - let Some(node) = tree.0.get_node(current_node_id) else { - continue; - }; - - node.set_text_layout(text_layout); - - let size = Size { - width: size.width, - height: size.height, - }; - - if current_node_id.into() == 160 { - println!("current_node_id: {:?}", current_node_id.into()); - println!("size: {:?}", size); - - println!( - "location: {:?}", - Point { - x: run.offset(), - y: run_y, - } - ); - } - - tree.set_unrounded_layout( - NodeId::new(current_node_id.into()), - &Layout { - size, - content_size: size, - scrollbar_size: Size::ZERO, - border: Rect::ZERO, - location: Point { - x: run.offset(), - y: run_y, - }, - order: 0, - padding: Rect::ZERO, + content_size: size, + scrollbar_size: Size::ZERO, + border: Rect::ZERO, + location: Point { + x: run.offset(), + y: run_y, }, - ); + order: 0, + padding: Rect::ZERO, + }, + ); + } + PositionedLayoutItem::InlineBox(inline_box) => { + let id = NodeId::from(inline_box.id); + + if inline_box.id == 162 { + println!("inline_box: {:?}", inline_box); } - PositionedLayoutItem::InlineBox(inline_box) => { - let id = NodeId::from(inline_box.id); - if inline_box.id == 162 { - println!("inline_box: {:?}", inline_box); - } + let size = Size { + width: inline_box.width, + height: inline_box.height, + }; - let size = Size { - width: inline_box.width, - height: inline_box.height, - }; - - tree.set_unrounded_layout( - id, - &Layout { - size, - content_size: size, - scrollbar_size: Size::ZERO, - border: Rect::ZERO, - location: Point { - x: inline_box.x, - y: 0.0, - }, - order: 0, - padding: Rect::ZERO, + tree.set_unrounded_layout( + id, + &Layout { + size, + content_size: size, + scrollbar_size: Size::ZERO, + border: Rect::ZERO, + location: Point { + x: inline_box.x, + y: 0.0, }, - ); - } + order: 0, + padding: Rect::ZERO, + }, + ); } } } diff --git a/crates/gosub_taffy/src/lib.rs b/crates/gosub_taffy/src/lib.rs index 453c5663c..4b8ae4d8e 100644 --- a/crates/gosub_taffy/src/lib.rs +++ b/crates/gosub_taffy/src/lib.rs @@ -2,18 +2,19 @@ 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, + compute_hidden_layout, compute_root_layout, AvailableSpace, Cache as TaffyCache, + Display as TaffyDisplay, Layout as TaffyLayout, LayoutInput, LayoutOutput, LayoutPartialTree, + NodeId as TaffyId, Style, TraversePartialTree, }; -use crate::compute::inline::compute_inline_layout; -use crate::style::get_style_from_node; -use crate::text::TextLayout; use gosub_render_backend::geo::{Point, Rect, Size, SizeU32}; use gosub_render_backend::layout::{Layout as TLayout, LayoutTree, Layouter, Node}; use gosub_shared::types::Result; +use crate::compute::inline::compute_inline_layout; +use crate::style::get_style_from_node; +use crate::text::TextLayout; + mod compute; mod conversions; pub mod style; @@ -39,6 +40,29 @@ impl TLayout for Layout { Size::new(size.width, size.height) } + fn size_or(&self) -> Option { + let size = self.0.size; + if size.width == 0.0 && size.height == 0.0 { + None //Temporary hack to indicate that the size is not set + } else { + Some(Size::new(size.width, size.height)) + } + } + + fn set_size(&mut self, size: SizeU32) { + self.0.size = taffy::Size { + width: size.width as f32, + height: size.height as f32, + }; + } + + fn set_content(&mut self, size: SizeU32) { + self.0.content_size = taffy::Size { + width: size.width as f32, + height: size.height as f32, + }; + } + fn content(&self) -> Size { let content = self.0.content_size; Size::new(content.width, content.height) @@ -245,15 +269,14 @@ impl> LayoutPartialTree for LayoutDocument<'_, LT> } } - let has_children = tree.0.child_count(node_id) > 0; //TODO: this isn't optimal, since we are now requesting the same node twice (up in get_cache and here) + // let has_children = tree.0.child_count(node_id) > 0; //TODO: this isn't optimal, since we are now requesting the same node twice (up in get_cache and here) 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), + match style.display { + TaffyDisplay::None => compute_hidden_layout(tree, node_id_taffy), + TaffyDisplay::Block => compute_block_layout(tree, node_id_taffy, inputs), + TaffyDisplay::Flex => compute_flexbox_layout(tree, node_id_taffy, inputs), + TaffyDisplay::Grid => compute_grid_layout(tree, node_id_taffy, inputs), } }) } diff --git a/crates/gosub_useragent/src/event_loop.rs b/crates/gosub_useragent/src/event_loop.rs index a2ee91be2..7a53b3899 100644 --- a/crates/gosub_useragent/src/event_loop.rs +++ b/crates/gosub_useragent/src/event_loop.rs @@ -1,10 +1,11 @@ +use winit::event::{ElementState, MouseScrollDelta, WindowEvent}; +use winit::event_loop::ActiveEventLoop; +use winit::keyboard::{KeyCode, PhysicalKey}; + 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}; @@ -47,9 +48,13 @@ impl<'a, D: SceneDrawer, B: RenderBackend, L: Layouter, LT: LayoutTree return Ok(()); }; - tab.data.draw(backend, &mut self.renderer_data, size); + let redraw = tab.data.draw(backend, &mut self.renderer_data, size); backend.render(&mut self.renderer_data, active_window_data)?; + + if redraw { + self.request_redraw(); + } } WindowEvent::CursorMoved { position, .. } => { diff --git a/resources/test_img.png b/resources/test_img.png new file mode 100644 index 000000000..492b9ac02 Binary files /dev/null and b/resources/test_img.png differ