From 419d906d1357603c20109f07365b2ce201b6e154 Mon Sep 17 00:00:00 2001 From: marc2332 Date: Fri, 6 Sep 2024 21:22:45 +0200 Subject: [PATCH 01/14] feat: Force SVG root element to have the specified size --- Cargo.toml | 2 +- crates/core/src/elements/svg.rs | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index bff3347e1..ba4077b22 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -51,7 +51,7 @@ dioxus-hot-reload = { version = "0.5", features = ["file_watcher"], default-feat dioxus-router = { version = "0.5", default-features = false } dioxus-sdk = { version = "0.5", features = ["clipboard"]} -skia-safe = { version = "0.75.0", features = ["gl", "textlayout", "svg"] } +skia-safe = { path = "C:/Users/mespi/rust-skia/skia-safe", features = ["gl", "textlayout", "svg"] } gl = "0.14.0" glutin = "0.32.0" diff --git a/crates/core/src/elements/svg.rs b/crates/core/src/elements/svg.rs index e0685d816..53a07afd8 100644 --- a/crates/core/src/elements/svg.rs +++ b/crates/core/src/elements/svg.rs @@ -30,6 +30,9 @@ impl ElementUtils for SvgElement { canvas.save(); canvas.translate((x, y)); svg_dom.set_container_size((area.width() as i32, area.height() as i32)); + let mut root = svg_dom.root(); + root.set_width(svg::DomSVGLength::new(100.0, svg::SvgUnit::Percentage)); + root.set_height(svg::DomSVGLength::new(100.0, svg::SvgUnit::Percentage)); svg_dom.render(canvas); canvas.restore(); } From b291c7f8802d53ee7bd076f95b5a6ae572b7af00 Mon Sep 17 00:00:00 2001 From: marc2332 Date: Fri, 4 Oct 2024 17:18:42 +0200 Subject: [PATCH 02/14] use new api --- Cargo.toml | 2 +- crates/core/src/elements/svg.rs | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 3dc247110..036173990 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -53,7 +53,7 @@ dioxus-hot-reload = { version = "0.5", features = ["file_watcher"], default-feat dioxus-router = { version = "0.5", default-features = false } dioxus-sdk = { version = "0.5", features = ["clipboard"]} -skia-safe = { path = "C:/Users/mespi/rust-skia/skia-safe", features = ["gl", "textlayout", "svg"] } +skia-safe = { path = "C:/Users/mespi/rust-skia-1030/skia-safe", features = ["gl", "textlayout", "svg"] } gl = "0.14.0" glutin = "0.32.0" diff --git a/crates/core/src/elements/svg.rs b/crates/core/src/elements/svg.rs index 53a07afd8..b87f881db 100644 --- a/crates/core/src/elements/svg.rs +++ b/crates/core/src/elements/svg.rs @@ -30,9 +30,9 @@ impl ElementUtils for SvgElement { canvas.save(); canvas.translate((x, y)); svg_dom.set_container_size((area.width() as i32, area.height() as i32)); - let mut root = svg_dom.root(); - root.set_width(svg::DomSVGLength::new(100.0, svg::SvgUnit::Percentage)); - root.set_height(svg::DomSVGLength::new(100.0, svg::SvgUnit::Percentage)); + let root = svg_dom.root(); + root.set_width(svg::Length::new(100.0, svg::LengthUnit::Percentage)); + root.set_height(svg::Length::new(100.0, svg::LengthUnit::Percentage)); svg_dom.render(canvas); canvas.restore(); } From d2604ab63b7916ba9feec9fbb6645cfb9df1f2c2 Mon Sep 17 00:00:00 2001 From: marc2332 Date: Sun, 17 Nov 2024 14:39:22 +0100 Subject: [PATCH 03/14] chore: Update skia-safe --- Cargo.toml | 2 +- crates/core/src/elements/svg.rs | 5 +++-- crates/engine/src/mocked.rs | 8 ++++++++ crates/engine/src/skia.rs | 1 + examples/ferris.svg | 2 +- rust-toolchain.toml | 2 +- 6 files changed, 15 insertions(+), 5 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index c16739293..0deac8d48 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -53,7 +53,7 @@ dioxus-hot-reload = { version = "0.5", features = ["file_watcher"], default-feat dioxus-router = { version = "0.5", default-features = false } dioxus-clipboard = "0.1" -skia-safe = { path = "C:/Users/mespi/rust-skia-1030/skia-safe", features = ["gl", "textlayout", "svg"] } +skia-safe = { version = "0.80.0", features = ["gl", "textlayout", "svg"] } gl = "0.14.0" glutin = "0.32.0" diff --git a/crates/core/src/elements/svg.rs b/crates/core/src/elements/svg.rs index b87f881db..ae2419172 100644 --- a/crates/core/src/elements/svg.rs +++ b/crates/core/src/elements/svg.rs @@ -25,12 +25,13 @@ impl ElementUtils for SvgElement { let x = area.min_x(); let y = area.min_y(); if let Some(svg_data) = &node_style.svg_data { - let svg_dom = svg::Dom::from_bytes(svg_data.as_slice(), font_manager); + let resource_provider = LocalResourceProvider::new(font_manager); + let svg_dom = svg::Dom::from_bytes(svg_data.as_slice(), resource_provider); if let Ok(mut svg_dom) = svg_dom { canvas.save(); canvas.translate((x, y)); svg_dom.set_container_size((area.width() as i32, area.height() as i32)); - let root = svg_dom.root(); + let mut root = svg_dom.root(); root.set_width(svg::Length::new(100.0, svg::LengthUnit::Percentage)); root.set_height(svg::Length::new(100.0, svg::LengthUnit::Percentage)); svg_dom.render(canvas); diff --git a/crates/engine/src/mocked.rs b/crates/engine/src/mocked.rs index 3ba169335..ec18e7287 100644 --- a/crates/engine/src/mocked.rs +++ b/crates/engine/src/mocked.rs @@ -1751,3 +1751,11 @@ pub enum EncodedImageFormat { AVIF = 12, JPEGXL = 13, } + +struct LocalResourceProvider; + +impl LocalResourceProvider { + pub fn new(font_mgr: &FontMgr) -> Self { + unimplemented!("This is mocked") + } +} diff --git a/crates/engine/src/skia.rs b/crates/engine/src/skia.rs index c9c73a9b5..e8eb0ad52 100644 --- a/crates/engine/src/skia.rs +++ b/crates/engine/src/skia.rs @@ -24,6 +24,7 @@ pub use skia_safe::{ set_resource_cache_total_bytes_limit, }, path::ArcSize, + resources::LocalResourceProvider, rrect::Corner, runtime_effect::Uniform, surfaces::raster_n32_premul, diff --git a/examples/ferris.svg b/examples/ferris.svg index 0d8a78b37..4f4900c10 100644 --- a/examples/ferris.svg +++ b/examples/ferris.svg @@ -1,6 +1,6 @@ - + diff --git a/rust-toolchain.toml b/rust-toolchain.toml index 94cbe48dd..8fc77ba56 100644 --- a/rust-toolchain.toml +++ b/rust-toolchain.toml @@ -1,3 +1,3 @@ [toolchain] -channel = "1.80.1" +channel = "1.82.0" profile = "default" \ No newline at end of file From dde62046d02f3a077c3506383b876ce85940655d Mon Sep 17 00:00:00 2001 From: marc2332 Date: Sun, 17 Nov 2024 14:45:45 +0100 Subject: [PATCH 04/14] chore: Fix cargo clippy --- crates/native-core/src/passes.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/crates/native-core/src/passes.rs b/crates/native-core/src/passes.rs index 11b4ca049..2ceba2255 100644 --- a/crates/native-core/src/passes.rs +++ b/crates/native-core/src/passes.rs @@ -61,9 +61,8 @@ impl DirtyNodes { } pub fn pop(&mut self) -> Option { - self.nodes_dirty.iter().next().copied().map(|id| { + self.nodes_dirty.iter().next().copied().inspect(|id| { self.nodes_dirty.remove(&id); - id }) } } From add61023d835726a2d97ff8d4e33c076a0ed84ed Mon Sep 17 00:00:00 2001 From: marc2332 Date: Sun, 17 Nov 2024 14:48:53 +0100 Subject: [PATCH 05/14] chore: Fix cargo clippy --- crates/native-core/src/passes.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/native-core/src/passes.rs b/crates/native-core/src/passes.rs index 2ceba2255..a8f84bd2a 100644 --- a/crates/native-core/src/passes.rs +++ b/crates/native-core/src/passes.rs @@ -62,7 +62,7 @@ impl DirtyNodes { pub fn pop(&mut self) -> Option { self.nodes_dirty.iter().next().copied().inspect(|id| { - self.nodes_dirty.remove(&id); + self.nodes_dirty.remove(id); }) } } From 426413a8c6c5fbe5c16cae186dade25a87eb922a Mon Sep 17 00:00:00 2001 From: marc2332 Date: Sun, 17 Nov 2024 15:50:15 +0100 Subject: [PATCH 06/14] fix: Update mocked svg APIs --- crates/engine/src/mocked.rs | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/crates/engine/src/mocked.rs b/crates/engine/src/mocked.rs index ec18e7287..a90a7ed72 100644 --- a/crates/engine/src/mocked.rs +++ b/crates/engine/src/mocked.rs @@ -1472,6 +1472,18 @@ pub mod svg { Size, }; + pub enum LengthUnit { + Percentage, + } + + pub struct Length; + + impl Length { + pub fn new(value: f32, unit: LengthUnit) -> Self { + unimplemented!("This is mocked") + } + } + pub struct Dom; impl Dom { @@ -1752,7 +1764,7 @@ pub enum EncodedImageFormat { JPEGXL = 13, } -struct LocalResourceProvider; +pub struct LocalResourceProvider; impl LocalResourceProvider { pub fn new(font_mgr: &FontMgr) -> Self { From aae4a644b4686a86ea3de7d65fd2c4db5ccadc0e Mon Sep 17 00:00:00 2001 From: marc2332 Date: Sun, 17 Nov 2024 15:55:05 +0100 Subject: [PATCH 07/14] fix: Update mocked svg APIs --- crates/engine/src/mocked.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/engine/src/mocked.rs b/crates/engine/src/mocked.rs index a90a7ed72..82cefb3d9 100644 --- a/crates/engine/src/mocked.rs +++ b/crates/engine/src/mocked.rs @@ -1487,7 +1487,7 @@ pub mod svg { pub struct Dom; impl Dom { - pub fn from_bytes(_bytes: &[u8], font_mgr: &FontMgr) -> Result { + pub fn from_bytes(_bytes: &[u8], provider: LocalResourceProvider) -> Result { unimplemented!("This is mocked") } From de38a5362e0f1f9d539e8d27f404dee0e61f90fe Mon Sep 17 00:00:00 2001 From: marc2332 Date: Sun, 17 Nov 2024 23:43:59 +0100 Subject: [PATCH 08/14] feat: Custom Paragraph Cache --- crates/common/src/layout.rs | 8 +- crates/core/Cargo.toml | 1 + crates/core/src/dom/doms.rs | 19 +++- crates/core/src/elements/image.rs | 6 +- crates/core/src/elements/label.rs | 12 ++- crates/core/src/elements/paragraph.rs | 12 ++- crates/core/src/elements/rect.rs | 2 + crates/core/src/elements/svg.rs | 6 +- crates/core/src/elements/utils.rs | 12 ++- crates/core/src/layout.rs | 10 +- crates/core/src/render/compositor.rs | 6 +- crates/core/src/render/pipeline.rs | 3 + crates/core/src/render/skia_measurer.rs | 21 ++-- crates/core/src/render/utils/label.rs | 62 +++++++++++- crates/core/src/render/utils/paragraph.rs | 115 +++++++++++++++++++++- crates/core/src/skia/paragraph.rs | 84 ---------------- crates/renderer/Cargo.toml | 1 + crates/renderer/src/app.rs | 1 + crates/renderer/src/renderer.rs | 2 + crates/state/src/values/font.rs | 2 +- crates/state/tests/parse_focusable.rs | 1 - crates/testing/Cargo.toml | 1 + crates/testing/src/test_handler.rs | 1 + crates/torin/tests/alignment.rs | 2 +- crates/torin/tests/size.rs | 24 ++--- examples/animation.rs | 5 +- examples/documents_editor.rs | 2 +- examples/gamepad_focus.rs | 57 +++++------ examples/gamepad_trace.rs | 16 +-- examples/mouse.rs | 4 +- examples/virtual_scroll_view.rs | 9 +- examples/website.rs | 2 +- 32 files changed, 325 insertions(+), 184 deletions(-) delete mode 100644 crates/core/src/skia/paragraph.rs diff --git a/crates/common/src/layout.rs b/crates/common/src/layout.rs index 449d5cfa8..84b6e3471 100644 --- a/crates/common/src/layout.rs +++ b/crates/common/src/layout.rs @@ -1,4 +1,7 @@ -use std::ops::Div; +use std::{ + ops::Div, + rc::Rc, +}; use freya_engine::prelude::Paragraph; use torin::geometry::{ @@ -27,7 +30,8 @@ pub enum CursorLayoutResponse { TextSelection { from: usize, to: usize, id: usize }, } -pub struct CachedParagraph(pub Paragraph, pub f32); +#[derive(Clone)] +pub struct CachedParagraph(pub Rc); /// # Safety /// Skia `Paragraph` are neither Sync or Send, but in order to store them in the Associated diff --git a/crates/core/Cargo.toml b/crates/core/Cargo.toml index 0fc033c83..f74a578e6 100644 --- a/crates/core/Cargo.toml +++ b/crates/core/Cargo.toml @@ -39,6 +39,7 @@ tracing = { workspace = true } uuid = { workspace = true } itertools = "0.13.0" smallvec = { workspace = true } +indexmap = "2.6.0" [dev-dependencies] dioxus = { workspace = true } diff --git a/crates/core/src/dom/doms.rs b/crates/core/src/dom/doms.rs index d93c5cf17..56a93981a 100644 --- a/crates/core/src/dom/doms.rs +++ b/crates/core/src/dom/doms.rs @@ -39,11 +39,14 @@ use freya_node_state::{ use torin::prelude::*; use super::mutations_writer::MutationsWriter; -use crate::prelude::{ - CompositorCache, - CompositorDirtyArea, - ParagraphElement, - TextGroupMeasurement, +use crate::{ + prelude::{ + CompositorCache, + CompositorDirtyArea, + ParagraphElement, + TextGroupMeasurement, + }, + render::ParagraphCache, }; pub type DioxusDOM = RealDom; @@ -130,6 +133,7 @@ pub struct FreyaDOM { compositor_cache: Arc>, accessibility_dirty_nodes: Arc>, accessibility_generator: Arc, + paragraph_cache: Arc>, } impl Default for FreyaDOM { @@ -157,6 +161,7 @@ impl Default for FreyaDOM { compositor_cache: Arc::default(), accessibility_dirty_nodes: Arc::default(), accessibility_generator: Arc::default(), + paragraph_cache: Arc::default(), } } } @@ -194,6 +199,10 @@ impl FreyaDOM { &self.accessibility_generator } + pub fn paragraph_cache(&self) -> MutexGuard { + self.paragraph_cache.lock().unwrap() + } + /// Create the initial DOM from the given Mutations pub fn init_dom(&mut self, vdom: &mut VirtualDom, scale_factor: f32) { // Build the RealDOM diff --git a/crates/core/src/elements/image.rs b/crates/core/src/elements/image.rs index bbc49c111..749aed76d 100644 --- a/crates/core/src/elements/image.rs +++ b/crates/core/src/elements/image.rs @@ -6,7 +6,10 @@ use freya_node_state::{ }; use super::utils::ElementUtils; -use crate::dom::DioxusNode; +use crate::{ + dom::DioxusNode, + render::ParagraphCache, +}; pub struct ImageElement; @@ -20,6 +23,7 @@ impl ElementUtils for ImageElement { _font_manager: &FontMgr, _default_fonts: &[String], _scale_factor: f32, + _paragraph_cache: &mut ParagraphCache, ) { let area = layout_node.visible_area(); let node_style = node_ref.get::().unwrap(); diff --git a/crates/core/src/elements/label.rs b/crates/core/src/elements/label.rs index 186cf107d..1da8f7b1e 100644 --- a/crates/core/src/elements/label.rs +++ b/crates/core/src/elements/label.rs @@ -13,7 +13,10 @@ use torin::prelude::{ use super::utils::ElementUtils; use crate::{ prelude::DioxusNode, - render::align_main_align_paragraph, + render::{ + align_main_align_paragraph, + ParagraphCache, + }, }; pub struct LabelElement; @@ -28,6 +31,7 @@ impl ElementUtils for LabelElement { _font_manager: &FontMgr, _default_fonts: &[String], _scale_factor: f32, + _paragraph_cache: &mut ParagraphCache, ) { let paragraph = &layout_node .data @@ -57,15 +61,15 @@ impl ElementUtils for LabelElement { node_ref: &DioxusNode, scale_factor: f32, ) -> Area { - let paragraph_font_height = &layout_node + let paragraph = &layout_node .data .as_ref() .unwrap() .get::() .unwrap() - .1; + .0; let mut area = layout_node.visible_area(); - area.size.height = area.size.height.max(*paragraph_font_height); + area.size.height = area.size.height.max(paragraph.height()); let font_style = node_ref.get::().unwrap(); diff --git a/crates/core/src/elements/paragraph.rs b/crates/core/src/elements/paragraph.rs index 05effff7e..c188bd3fc 100644 --- a/crates/core/src/elements/paragraph.rs +++ b/crates/core/src/elements/paragraph.rs @@ -37,6 +37,7 @@ use crate::{ create_paragraph, draw_cursor, draw_cursor_highlights, + ParagraphCache, }, }; @@ -121,6 +122,7 @@ impl ElementUtils for ParagraphElement { _font_manager: &FontMgr, default_fonts: &[String], scale_factor: f32, + paragraph_cache: &mut ParagraphCache, ) { let area = layout_node.visible_area(); let node_cursor_state = &*node_ref.get::().unwrap(); @@ -146,7 +148,9 @@ impl ElementUtils for ParagraphElement { true, default_fonts, scale_factor, - ); + paragraph_cache, + ) + .0; paint(¶graph); } else { let paragraph = &layout_node @@ -183,15 +187,15 @@ impl ElementUtils for ParagraphElement { node_ref: &DioxusNode, scale_factor: f32, ) -> Area { - let paragraph_font_height = &layout_node + let paragraph = &layout_node .data .as_ref() .unwrap() .get::() .unwrap() - .1; + .0; let mut area = layout_node.visible_area(); - area.size.height = area.size.height.max(*paragraph_font_height); + area.size.height = area.size.height.max(paragraph.height()); // Iterate over all the text spans inside this paragraph and if any of them // has a shadow at all, apply this shadow to the general paragraph. diff --git a/crates/core/src/elements/rect.rs b/crates/core/src/elements/rect.rs index b033ee415..10a49eed1 100644 --- a/crates/core/src/elements/rect.rs +++ b/crates/core/src/elements/rect.rs @@ -26,6 +26,7 @@ use crate::{ render_border, render_shadow, BorderShape, + ParagraphCache, }, }; @@ -89,6 +90,7 @@ impl ElementUtils for RectElement { _font_manager: &FontMgr, _default_fonts: &[String], scale_factor: f32, + _paragraph_cache: &mut ParagraphCache, ) { let node_style = &*node_ref.get::().unwrap(); diff --git a/crates/core/src/elements/svg.rs b/crates/core/src/elements/svg.rs index ae2419172..cd606a244 100644 --- a/crates/core/src/elements/svg.rs +++ b/crates/core/src/elements/svg.rs @@ -4,7 +4,10 @@ use freya_node_state::StyleState; use torin::prelude::LayoutNode; use super::utils::ElementUtils; -use crate::dom::DioxusNode; +use crate::{ + dom::DioxusNode, + render::ParagraphCache, +}; pub struct SvgElement; @@ -18,6 +21,7 @@ impl ElementUtils for SvgElement { font_manager: &FontMgr, _default_fonts: &[String], _scale_factor: f32, + _paragraph_cache: &mut ParagraphCache, ) { let area = layout_node.visible_area(); let node_style = &*node_ref.get::().unwrap(); diff --git a/crates/core/src/elements/utils.rs b/crates/core/src/elements/utils.rs index 4575e653b..164f18ae6 100644 --- a/crates/core/src/elements/utils.rs +++ b/crates/core/src/elements/utils.rs @@ -23,7 +23,10 @@ use torin::{ }; use super::*; -use crate::dom::DioxusNode; +use crate::{ + dom::DioxusNode, + render::ParagraphCache, +}; pub trait ElementUtils { fn is_point_inside_area( @@ -55,6 +58,7 @@ pub trait ElementUtils { font_manager: &FontMgr, default_fonts: &[String], scale_factor: f32, + paragraph_cache: &mut ParagraphCache, ); fn element_drawing_area( @@ -198,6 +202,7 @@ impl ElementUtils for ElementWithUtils { font_manager: &FontMgr, default_fonts: &[String], scale_factor: f32, + paragraph_cache: &mut ParagraphCache, ) { match self { Self::Rect(el) => el.render( @@ -208,6 +213,7 @@ impl ElementUtils for ElementWithUtils { font_manager, default_fonts, scale_factor, + paragraph_cache, ), Self::Svg(el) => el.render( layout_node, @@ -217,6 +223,7 @@ impl ElementUtils for ElementWithUtils { font_manager, default_fonts, scale_factor, + paragraph_cache, ), Self::Paragraph(el) => el.render( layout_node, @@ -226,6 +233,7 @@ impl ElementUtils for ElementWithUtils { font_manager, default_fonts, scale_factor, + paragraph_cache, ), Self::Image(el) => el.render( layout_node, @@ -235,6 +243,7 @@ impl ElementUtils for ElementWithUtils { font_manager, default_fonts, scale_factor, + paragraph_cache, ), Self::Label(el) => el.render( layout_node, @@ -244,6 +253,7 @@ impl ElementUtils for ElementWithUtils { font_manager, default_fonts, scale_factor, + paragraph_cache, ), } } diff --git a/crates/core/src/layout.rs b/crates/core/src/layout.rs index b81ff25b9..345c7928e 100644 --- a/crates/core/src/layout.rs +++ b/crates/core/src/layout.rs @@ -22,8 +22,8 @@ pub fn process_layout( ) { { let rdom = fdom.rdom(); + let mut paragraph_cache = fdom.paragraph_cache(); let mut dom_adapter = DioxusDOMAdapter::new(rdom, scale_factor); - let skia_measurer = SkiaMeasurer::new(rdom, font_collection, default_fonts, scale_factor); let mut layout = fdom.layout(); @@ -54,7 +54,15 @@ pub fn process_layout( buffer.extend(node.child_ids()); } } + let root_id = fdom.rdom().root_id(); + let skia_measurer = SkiaMeasurer::new( + rdom, + font_collection, + default_fonts, + scale_factor, + &mut paragraph_cache, + ); // Measure the layout layout.measure(root_id, area, &mut Some(skia_measurer), &mut dom_adapter); diff --git a/crates/core/src/render/compositor.rs b/crates/core/src/render/compositor.rs index 63c5e6800..734cc725f 100644 --- a/crates/core/src/render/compositor.rs +++ b/crates/core/src/render/compositor.rs @@ -273,10 +273,10 @@ mod test { // Process what nodes need to be rendered let rendering_layers = compositor.run( - &mut *compositor_dirty_nodes, - &mut *compositor_dirty_area, + &mut compositor_dirty_nodes, + &mut compositor_dirty_area, &mut compositor_cache, - &*layers, + &layers, &mut dirty_layers, &layout, rdom, diff --git a/crates/core/src/render/pipeline.rs b/crates/core/src/render/pipeline.rs index a477aaabb..6bc235e7a 100644 --- a/crates/core/src/render/pipeline.rs +++ b/crates/core/src/render/pipeline.rs @@ -37,6 +37,7 @@ use super::{ wireframe_renderer, CompositorCache, CompositorDirtyArea, + ParagraphCache, }; use crate::{ dom::{ @@ -68,6 +69,7 @@ pub struct RenderPipeline<'a> { pub scale_factor: f32, pub selected_node: Option, pub default_fonts: &'a [String], + pub paragraph_cache: &'a mut ParagraphCache, } impl RenderPipeline<'_> { @@ -257,6 +259,7 @@ impl RenderPipeline<'_> { self.font_manager, self.default_fonts, self.scale_factor, + self.paragraph_cache, ); if render_wireframe { diff --git a/crates/core/src/render/skia_measurer.rs b/crates/core/src/render/skia_measurer.rs index b3a03a207..9b1b6096f 100644 --- a/crates/core/src/render/skia_measurer.rs +++ b/crates/core/src/render/skia_measurer.rs @@ -29,12 +29,15 @@ use super::{ }; use crate::dom::*; +pub type ParagraphCache = indexmap::IndexMap; + /// Provides Text measurements using Skia APIs like SkParagraph pub struct SkiaMeasurer<'a> { pub font_collection: &'a FontCollection, pub rdom: &'a DioxusDOM, pub default_fonts: &'a [String], pub scale_factor: f32, + pub paragraph_cache: &'a mut ParagraphCache, } impl<'a> SkiaMeasurer<'a> { @@ -43,12 +46,14 @@ impl<'a> SkiaMeasurer<'a> { font_collection: &'a FontCollection, default_fonts: &'a [String], scale_factor: f32, + paragraph_cache: &'a mut ParagraphCache, ) -> Self { Self { font_collection, rdom, default_fonts, scale_factor, + paragraph_cache, } } } @@ -63,6 +68,8 @@ impl<'a> LayoutMeasurer for SkiaMeasurer<'a> { let node = self.rdom.get(node_id).unwrap(); let node_type = node.node_type(); + // println!("Measured in {}ms", a.elapsed().as_millis()); + match &*node_type { NodeType::Element(ElementNode { tag, .. }) if tag == &TagName::Label => { let label = create_label( @@ -71,11 +78,12 @@ impl<'a> LayoutMeasurer for SkiaMeasurer<'a> { self.font_collection, self.default_fonts, self.scale_factor, + self.paragraph_cache, ); - let height = label.height(); - let res = Size2D::new(label.longest_line(), height); + let height = label.0.height(); + let res = Size2D::new(label.0.longest_line(), height); let mut map = SendAnyMap::new(); - map.insert(CachedParagraph(label, height)); + map.insert(label); Some((res, Arc::new(map))) } NodeType::Element(ElementNode { tag, .. }) if tag == &TagName::Paragraph => { @@ -86,11 +94,12 @@ impl<'a> LayoutMeasurer for SkiaMeasurer<'a> { false, self.default_fonts, self.scale_factor, + self.paragraph_cache, ); - let height = paragraph.height(); - let res = Size2D::new(paragraph.longest_line(), height); + let height = paragraph.0.height(); + let res = Size2D::new(paragraph.0.longest_line(), height); let mut map = SendAnyMap::new(); - map.insert(CachedParagraph(paragraph, height)); + map.insert(paragraph); Some((res, Arc::new(map))) } _ => None, diff --git a/crates/core/src/render/utils/label.rs b/crates/core/src/render/utils/label.rs index 1ed471caa..585977c33 100644 --- a/crates/core/src/render/utils/label.rs +++ b/crates/core/src/render/utils/label.rs @@ -1,12 +1,20 @@ +use std::rc::Rc; + +use freya_common::CachedParagraph; use freya_engine::prelude::*; use freya_native_core::{ prelude::NodeType, real_dom::NodeImmutable, }; use freya_node_state::FontStyleState; +use rustc_hash::FxBuildHasher; use torin::prelude::Size2D; -use crate::dom::*; +use super::ParagraphCacheKey; +use crate::{ + dom::*, + render::ParagraphCache, +}; pub fn create_label( node: &DioxusNode, @@ -14,9 +22,50 @@ pub fn create_label( font_collection: &FontCollection, default_font_family: &[String], scale_factor: f32, -) -> Paragraph { + paragraph_cache: &mut ParagraphCache, +) -> CachedParagraph { let font_style = &*node.get::().unwrap(); + let mut paragraph_cache_key: (u32, ParagraphCacheKey) = ( + area_size.width.to_bits(), + ParagraphCacheKey { + color: ( + font_style.color.r(), + font_style.color.g(), + font_style.color.b(), + ), + font_family: default_font_family, + font_size: font_style.font_size.to_bits(), + font_slant: font_style.font_slant, + font_weight: *font_style.font_weight, + font_width: *font_style.font_width, + line_height: font_style.line_height.map(|n| n.to_bits()), + word_spacing: font_style.word_spacing.to_bits(), + letter_spacing: font_style.letter_spacing.to_bits(), + text_align: font_style.text_align, + max_lines: font_style.max_lines, + text_overflow: font_style.text_overflow.clone(), + text_height: font_style.text_height, + text: Some("".to_string()), + }, + ); + + for child in node.children() { + if let NodeType::Text(text) = &*child.node_type() { + paragraph_cache_key.1.text.as_mut().unwrap().push_str(text); + } + } + + use std::hash::BuildHasher; + + let hasher = FxBuildHasher; + let paragraph_cache_key_hash = hasher.hash_one(paragraph_cache_key); + + let paragraph = paragraph_cache.get(¶graph_cache_key_hash).cloned(); + if let Some(paragraph) = paragraph { + return paragraph; + } + let mut paragraph_style = ParagraphStyle::default(); paragraph_style.set_text_align(font_style.text_align); paragraph_style.set_max_lines(font_style.max_lines); @@ -48,5 +97,14 @@ pub fn create_label( }, ); + let paragraph = CachedParagraph(Rc::new(paragraph)); + + paragraph_cache.insert(paragraph_cache_key_hash, paragraph.clone()); + + if paragraph_cache.len() > 128 { + let first = *paragraph_cache.first().unwrap().0; + paragraph_cache.shift_remove(&first); + } + paragraph } diff --git a/crates/core/src/render/utils/paragraph.rs b/crates/core/src/render/utils/paragraph.rs index c4b84dabf..83bb820d3 100644 --- a/crates/core/src/render/utils/paragraph.rs +++ b/crates/core/src/render/utils/paragraph.rs @@ -1,3 +1,10 @@ +use std::{ + hash::Hash, + rc::Rc, + vec, +}; + +use freya_common::CachedParagraph; use freya_engine::prelude::*; use freya_native_core::{ node::ElementNode, @@ -10,14 +17,36 @@ use freya_node_state::{ FontStyleState, HighlightMode, LayoutState, + TextOverflow, }; +use rustc_hash::FxBuildHasher; use torin::prelude::{ Alignment, Area, Size2D, }; -use crate::dom::DioxusNode; +use crate::{ + dom::DioxusNode, + render::ParagraphCache, +}; +#[derive(Hash)] +pub struct ParagraphCacheKey<'a> { + pub color: (u8, u8, u8), + pub font_family: &'a [String], + pub font_size: u32, + pub font_slant: Slant, + pub font_weight: i32, + pub font_width: i32, + pub line_height: Option, + pub word_spacing: u32, + pub letter_spacing: u32, + pub text_align: TextAlign, + pub max_lines: Option, + pub text_overflow: TextOverflow, + pub text_height: TextHeightBehavior, + pub text: Option, +} /// Compose a new SkParagraph pub fn create_paragraph( @@ -27,9 +56,82 @@ pub fn create_paragraph( is_rendering: bool, default_font_family: &[String], scale_factor: f32, -) -> Paragraph { + paragraph_cache: &mut ParagraphCache, +) -> CachedParagraph { let font_style = &*node.get::().unwrap(); + let mut paragraph_cache_key: (u32, ParagraphCacheKey, Vec) = ( + area_size.width.to_bits(), + ParagraphCacheKey { + color: ( + font_style.color.r(), + font_style.color.g(), + font_style.color.b(), + ), + font_family: default_font_family, + font_size: font_style.font_size.to_bits(), + font_slant: font_style.font_slant, + font_weight: *font_style.font_weight, + font_width: *font_style.font_width, + line_height: font_style.line_height.map(|n| n.to_bits()), + word_spacing: font_style.word_spacing.to_bits(), + letter_spacing: font_style.letter_spacing.to_bits(), + text_align: font_style.text_align, + max_lines: font_style.max_lines, + text_overflow: font_style.text_overflow.clone(), + text_height: font_style.text_height, + text: None, + }, + vec![], + ); + + for text_span in node.children() { + if let NodeType::Element(ElementNode { + tag: TagName::Text, .. + }) = &*text_span.node_type() + { + let text_nodes = text_span.children(); + let text_node = *text_nodes.first().unwrap(); + let text_node_type = &*text_node.node_type(); + let text_font_style = text_span.get::().unwrap(); + let mut key = ParagraphCacheKey { + color: ( + text_font_style.color.r(), + font_style.color.g(), + font_style.color.b(), + ), + font_family: default_font_family, + font_size: text_font_style.font_size.to_bits(), + font_slant: text_font_style.font_slant, + font_weight: *text_font_style.font_weight, + font_width: *text_font_style.font_width, + line_height: text_font_style.line_height.map(|n| n.to_bits()), + word_spacing: text_font_style.word_spacing.to_bits(), + letter_spacing: text_font_style.letter_spacing.to_bits(), + text_align: text_font_style.text_align, + max_lines: text_font_style.max_lines, + text_overflow: text_font_style.text_overflow.clone(), + text_height: text_font_style.text_height, + text: None, + }; + + if let NodeType::Text(text) = text_node_type { + key.text = Some(text.clone()); + } + + paragraph_cache_key.2.push(key); + } + } + + use std::hash::BuildHasher; + let hasher = FxBuildHasher; + let paragraph_cache_key_hash = hasher.hash_one(paragraph_cache_key); + + let paragraph = paragraph_cache.get(¶graph_cache_key_hash).cloned(); + if let Some(paragraph) = paragraph { + return paragraph; + } + let mut paragraph_style = ParagraphStyle::default(); paragraph_style.set_text_align(font_style.text_align); paragraph_style.set_max_lines(font_style.max_lines); @@ -82,6 +184,15 @@ pub fn create_paragraph( }, ); + let paragraph = CachedParagraph(Rc::new(paragraph)); + + paragraph_cache.insert(paragraph_cache_key_hash, paragraph.clone()); + + if paragraph_cache.len() > 128 { + let first = *paragraph_cache.first().unwrap().0; + paragraph_cache.shift_remove(&first); + } + paragraph } diff --git a/crates/core/src/skia/paragraph.rs b/crates/core/src/skia/paragraph.rs deleted file mode 100644 index 84af671e1..000000000 --- a/crates/core/src/skia/paragraph.rs +++ /dev/null @@ -1,84 +0,0 @@ -use std::ops::Mul; - -use freya_common::{ - CachedParagraph, - CursorLayoutResponse, -}; -use freya_native_core::prelude::NodeImmutable; -use freya_node_state::CursorState; -use torin::prelude::{ - CursorPoint, - LayoutNode, -}; - -use crate::prelude::{ - align_main_align_paragraph, - DioxusNode, - TextGroupMeasurement, -}; - -/// Merasure the cursor positio and text selection and notify the subscribed component of the element. -pub fn measure_paragraph( - node: &DioxusNode, - layout_node: &LayoutNode, - text_measurement: &TextGroupMeasurement, - scale_factor: f64, -) { - let paragraph = &layout_node - .data - .as_ref() - .unwrap() - .get::() - .unwrap() - .0; - - let cursor_state = node.get::().unwrap(); - - if cursor_state.cursor_id != Some(text_measurement.cursor_id) { - return; - } - - let y = align_main_align_paragraph(node, &layout_node.area, paragraph); - - if let Some(cursor_reference) = &cursor_state.cursor_ref { - if let Some(cursor_position) = text_measurement.cursor_position { - let position = CursorPoint::new(cursor_position.x, cursor_position.y - y as f64); - - // Calculate the new cursor position - let char_position = paragraph - .get_glyph_position_at_coordinate(position.mul(scale_factor).to_i32().to_tuple()); - - // Notify the cursor reference listener - cursor_reference - .cursor_sender - .send(CursorLayoutResponse::CursorPosition { - position: char_position.position as usize, - id: text_measurement.cursor_id, - }) - .ok(); - } - - if let Some((origin, dist)) = text_measurement.cursor_selection { - let origin_position = CursorPoint::new(origin.x, origin.y - y as f64); - let dist_position = CursorPoint::new(dist.x, dist.y - y as f64); - - // Calculate the start of the highlighting - let origin_char = paragraph.get_glyph_position_at_coordinate( - origin_position.mul(scale_factor).to_i32().to_tuple(), - ); - // Calculate the end of the highlighting - let dist_char = paragraph.get_glyph_position_at_coordinate( - dist_position.mul(scale_factor).to_i32().to_tuple(), - ); - - cursor_reference - .cursor_sender - .send(CursorLayoutResponse::TextSelection { - from: origin_char.position as usize, - to: dist_char.position as usize, - id: text_measurement.cursor_id, - }) - .ok(); - } - } -} diff --git a/crates/renderer/Cargo.toml b/crates/renderer/Cargo.toml index f4c11b6ec..27562d06b 100644 --- a/crates/renderer/Cargo.toml +++ b/crates/renderer/Cargo.toml @@ -43,6 +43,7 @@ accesskit_winit = { workspace = true } tracing = { workspace = true } futures-task ={ workspace = true } futures-util = { workspace = true } +rustc-hash= { workspace = true } itertools = "0.13.0" uuid = { workspace = true } diff --git a/crates/renderer/src/app.rs b/crates/renderer/src/app.rs index dce9a35af..0d27c538b 100644 --- a/crates/renderer/src/app.rs +++ b/crates/renderer/src/app.rs @@ -439,6 +439,7 @@ impl Application { font_collection: &mut self.font_collection, font_manager: &self.font_mgr, default_fonts: &self.default_fonts, + paragraph_cache: &mut fdom.paragraph_cache(), }; render_pipeline.run(); } diff --git a/crates/renderer/src/renderer.rs b/crates/renderer/src/renderer.rs index d5796e5b7..74c978d2a 100644 --- a/crates/renderer/src/renderer.rs +++ b/crates/renderer/src/renderer.rs @@ -262,6 +262,8 @@ impl<'a, State: Clone> ApplicationHandler for DesktopRenderer<'a, } = self.state.created_state(); app.accessibility .process_accessibility_event(&event, window); + // let mut p_cache = app.font_collection.paragraph_cache_mut(); + // p_cache.print_statistics(); match event { WindowEvent::ThemeChanged(theme) => { app.platform_sender.send_modify(|state| { diff --git a/crates/state/src/values/font.rs b/crates/state/src/values/font.rs index 673e08732..64a93271e 100644 --- a/crates/state/src/values/font.rs +++ b/crates/state/src/values/font.rs @@ -82,7 +82,7 @@ impl Parse for Weight { } } -#[derive(Default, Clone, Debug, PartialEq)] +#[derive(Default, Clone, Debug, PartialEq, Hash)] pub enum TextOverflow { #[default] Clip, diff --git a/crates/state/tests/parse_focusable.rs b/crates/state/tests/parse_focusable.rs index 62ae1d9c9..27832e9a7 100644 --- a/crates/state/tests/parse_focusable.rs +++ b/crates/state/tests/parse_focusable.rs @@ -1,4 +1,3 @@ -use freya_engine::prelude::*; use freya_node_state::{ Focusable, Parse, diff --git a/crates/testing/Cargo.toml b/crates/testing/Cargo.toml index e5628e21e..4d547aa4c 100644 --- a/crates/testing/Cargo.toml +++ b/crates/testing/Cargo.toml @@ -34,6 +34,7 @@ dioxus = { workspace = true } tokio = { workspace = true } winit = { workspace = true } +rustc-hash = { workspace = true } [dev-dependencies] dioxus = { workspace = true } diff --git a/crates/testing/src/test_handler.rs b/crates/testing/src/test_handler.rs index db2e6e339..f5a1b9223 100644 --- a/crates/testing/src/test_handler.rs +++ b/crates/testing/src/test_handler.rs @@ -345,6 +345,7 @@ impl TestingHandler { font_collection: &mut self.font_collection, font_manager: &self.font_mgr, default_fonts: &["Fira Sans".to_string()], + paragraph_cache: &mut fdom.paragraph_cache(), }; render_pipeline.run(); diff --git a/crates/torin/tests/alignment.rs b/crates/torin/tests/alignment.rs index 40889d4c1..536a807cc 100644 --- a/crates/torin/tests/alignment.rs +++ b/crates/torin/tests/alignment.rs @@ -627,7 +627,7 @@ pub fn alignment_with_absolute_child() { Node::from_size_and_position( Size::Pixels(Length::new(100.)), Size::Pixels(Length::new(100.)), - Position::Absolute(Box::new(AbsolutePosition::default())), + Position::Absolute(Box::default()), ), ); mocked_dom.add( diff --git a/crates/torin/tests/size.rs b/crates/torin/tests/size.rs index f7e89a96a..1e4750d58 100644 --- a/crates/torin/tests/size.rs +++ b/crates/torin/tests/size.rs @@ -784,7 +784,7 @@ pub fn test_calc() { assert_eq!( run_calculations( - &vec![DynamicCalculation::Pixels(10.0)], + &[DynamicCalculation::Pixels(10.0)], PARENT_VALUE, PARENT_VALUE ), @@ -793,7 +793,7 @@ pub fn test_calc() { assert_eq!( run_calculations( - &vec![DynamicCalculation::Percentage(87.5)], + &[DynamicCalculation::Percentage(87.5)], PARENT_VALUE, PARENT_VALUE ), @@ -802,12 +802,12 @@ pub fn test_calc() { assert_eq!( run_calculations( - &vec![ + &[ DynamicCalculation::Pixels(10.0), DynamicCalculation::Add, DynamicCalculation::Pixels(20.0), DynamicCalculation::Mul, - DynamicCalculation::Percentage(50.0), + DynamicCalculation::Percentage(50.0) ], PARENT_VALUE, PARENT_VALUE @@ -817,7 +817,7 @@ pub fn test_calc() { assert_eq!( run_calculations( - &vec![ + &[ DynamicCalculation::Pixels(10.0), DynamicCalculation::Add, DynamicCalculation::Percentage(10.0), @@ -828,7 +828,7 @@ pub fn test_calc() { DynamicCalculation::Add, DynamicCalculation::Pixels(75.0), DynamicCalculation::Mul, - DynamicCalculation::Pixels(2.0), + DynamicCalculation::Pixels(2.0) ], PARENT_VALUE, PARENT_VALUE @@ -838,9 +838,9 @@ pub fn test_calc() { assert_eq!( run_calculations( - &vec![ + &[ DynamicCalculation::Pixels(10.0), - DynamicCalculation::Pixels(20.0), + DynamicCalculation::Pixels(20.0) ], PARENT_VALUE, PARENT_VALUE @@ -850,7 +850,7 @@ pub fn test_calc() { assert_eq!( run_calculations( - &vec![DynamicCalculation::Pixels(10.0), DynamicCalculation::Add], + &[DynamicCalculation::Pixels(10.0), DynamicCalculation::Add], PARENT_VALUE, PARENT_VALUE ), @@ -859,7 +859,7 @@ pub fn test_calc() { assert_eq!( run_calculations( - &vec![DynamicCalculation::Add, DynamicCalculation::Pixels(10.0)], + &[DynamicCalculation::Add, DynamicCalculation::Pixels(10.0)], PARENT_VALUE, PARENT_VALUE ), @@ -868,7 +868,7 @@ pub fn test_calc() { assert_eq!( run_calculations( - &vec![ + &[ DynamicCalculation::Pixels(10.0), DynamicCalculation::Add, DynamicCalculation::Add, @@ -882,7 +882,7 @@ pub fn test_calc() { assert_eq!( run_calculations( - &vec![ + &[ DynamicCalculation::Percentage(50.0), DynamicCalculation::Sub, DynamicCalculation::RootPercentage(20.0) diff --git a/examples/animation.rs b/examples/animation.rs index 7f413b23b..fe806bcf0 100644 --- a/examples/animation.rs +++ b/examples/animation.rs @@ -60,7 +60,10 @@ fn app() -> Element { rotate: "{rotate.read().as_f32()}deg", height: "50%", background: "{color.read().as_string()}", - corner_radius: "{radius.read().as_f32()}" + corner_radius: "{radius.read().as_f32()}", + label { + "{color.read().as_string()}" + } } } ) diff --git a/examples/documents_editor.rs b/examples/documents_editor.rs index 3929f5cb3..eb0be866f 100644 --- a/examples/documents_editor.rs +++ b/examples/documents_editor.rs @@ -266,7 +266,7 @@ fn DocumentView(path: ReadOnlySignal) -> Element { } } -static LOREM_IPSUM: &'static str = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."; +static LOREM_IPSUM: &str = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."; #[component] fn DocumentEdit(path: String) -> Element { diff --git a/examples/gamepad_focus.rs b/examples/gamepad_focus.rs index eb436e691..405b95972 100644 --- a/examples/gamepad_focus.rs +++ b/examples/gamepad_focus.rs @@ -20,10 +20,7 @@ use gilrs::{ }; fn main() { - launch_cfg( - app, - LaunchConfig::<()>::new().with_plugin(GamePadPlugin::default()), - ) + launch_cfg(app, LaunchConfig::<()>::new().with_plugin(GamePadPlugin)) } #[derive(Default)] @@ -38,32 +35,29 @@ impl GamePadPlugin { loop { while let Some(ev) = gilrs_instance.next_event() { - match ev.event { - EventType::ButtonReleased(_, code) => { - // NOTE: You might need to tweak these codes - match code.into_u32() { - 4 => { - handle.send_event_loop_event( - EventMessage::FocusPrevAccessibilityNode, - ); - } - 6 => { - handle.send_event_loop_event( - EventMessage::FocusNextAccessibilityNode, - ); - } - 13 => { - handle.send_platform_event(PlatformEvent::Keyboard { - name: EventName::KeyDown, - key: Key::Enter, - code: Code::Enter, - modifiers: Modifiers::default(), - }); - } - _ => {} + if let EventType::ButtonReleased(_, code) = ev.event { + // NOTE: You might need to tweak these codes + match code.into_u32() { + 4 => { + handle.send_event_loop_event( + EventMessage::FocusPrevAccessibilityNode, + ); + } + 6 => { + handle.send_event_loop_event( + EventMessage::FocusNextAccessibilityNode, + ); } + 13 => { + handle.send_platform_event(PlatformEvent::Keyboard { + name: EventName::KeyDown, + key: Key::Enter, + code: Code::Enter, + modifiers: Modifiers::default(), + }); + } + _ => {} } - _ => {} } } } @@ -73,11 +67,8 @@ impl GamePadPlugin { impl FreyaPlugin for GamePadPlugin { fn on_event(&mut self, event: &PluginEvent, handle: PluginHandle) { - match event { - PluginEvent::WindowCreated(_) => { - Self::listen_gamepad(handle); - } - _ => {} + if let PluginEvent::WindowCreated(_) = event { + Self::listen_gamepad(handle); } } } diff --git a/examples/gamepad_trace.rs b/examples/gamepad_trace.rs index 038a8349e..3dd20ce70 100644 --- a/examples/gamepad_trace.rs +++ b/examples/gamepad_trace.rs @@ -26,10 +26,7 @@ use gilrs::{ }; fn main() { - launch_cfg( - app, - LaunchConfig::<()>::new().with_plugin(GamePadPlugin::default()), - ) + launch_cfg(app, LaunchConfig::<()>::new().with_plugin(GamePadPlugin)) } #[derive(Default)] @@ -61,7 +58,7 @@ impl GamePadPlugin { } if diff_x != 0.0 { - x += diff_x as f64 * 10.; + x += diff_x * 10.; handle.send_platform_event(PlatformEvent::Mouse { name: EventName::MouseMove, cursor: (x, y).into(), @@ -70,7 +67,7 @@ impl GamePadPlugin { } if diff_x != 0.0 { - y -= diff_y as f64 * 10.; + y -= diff_y * 10.; handle.send_platform_event(PlatformEvent::Mouse { name: EventName::MouseMove, cursor: (x, y).into(), @@ -95,11 +92,8 @@ impl GamePadPlugin { impl FreyaPlugin for GamePadPlugin { fn on_event(&mut self, event: &PluginEvent, handle: PluginHandle) { - match event { - PluginEvent::WindowCreated(_) => { - Self::listen_gamepad(handle); - } - _ => {} + if let PluginEvent::WindowCreated(_) = event { + Self::listen_gamepad(handle); } } } diff --git a/examples/mouse.rs b/examples/mouse.rs index 3d07ae9f5..8018a5450 100644 --- a/examples/mouse.rs +++ b/examples/mouse.rs @@ -10,8 +10,8 @@ fn main() { } fn app() -> Element { - let mut cursor_pos_over = use_signal(|| CursorPoint::default()); - let mut cursor_pos_click = use_signal(|| CursorPoint::default()); + let mut cursor_pos_over = use_signal(CursorPoint::default); + let mut cursor_pos_click = use_signal(CursorPoint::default); let onmousemove = move |e: MouseEvent| { let cursor_pos = e.get_screen_coordinates(); diff --git a/examples/virtual_scroll_view.rs b/examples/virtual_scroll_view.rs index 0313c234f..572d89d81 100644 --- a/examples/virtual_scroll_view.rs +++ b/examples/virtual_scroll_view.rs @@ -10,11 +10,11 @@ fn main() { } fn app() -> Element { - let values = use_signal(|| ["Hello, World!"].repeat(300)); + let values = use_signal(|| ["Hello, World!"].repeat(128)); rsx!(VirtualScrollView { length: values.read().len(), - item_size: 25.0, + item_size: 5.0, direction: "vertical", builder: move |index, _: &Option<()>| { let value = values.read()[index]; @@ -27,9 +27,10 @@ fn app() -> Element { rect { key: "{index}", background: "{background}", - width: "100%", + width: "200", label { - height: "25", + height: "5", + width: "200", "{index} {value}" } } diff --git a/examples/website.rs b/examples/website.rs index 6796ddcc1..cbb56cb29 100644 --- a/examples/website.rs +++ b/examples/website.rs @@ -353,7 +353,7 @@ fn Code() -> Element { .highlight(&rust_config, CODE.as_bytes(), None, |_| None) .unwrap(); - let rope = Rope::from_str(&CODE); + let rope = Rope::from_str(CODE); let mut syntax_blocks = SyntaxBlocks::default(); From e617db5a733297fc4e52f75936b53a690d807c82 Mon Sep 17 00:00:00 2001 From: marc2332 Date: Mon, 18 Nov 2024 23:35:45 +0100 Subject: [PATCH 09/14] chore: Clean api --- crates/core/src/dom/doms.rs | 26 ++++--- crates/core/src/render/mod.rs | 2 + crates/core/src/render/paragraph_cache.rs | 91 +++++++++++++++++++++++ crates/core/src/render/skia_measurer.rs | 8 +- crates/core/src/render/utils/label.rs | 57 +++++--------- crates/core/src/render/utils/paragraph.rs | 85 ++++----------------- crates/freya/src/launch.rs | 2 +- crates/renderer/src/app.rs | 1 + crates/renderer/src/config.rs | 9 +++ crates/testing/src/launch.rs | 2 +- 10 files changed, 159 insertions(+), 124 deletions(-) create mode 100644 crates/core/src/render/paragraph_cache.rs diff --git a/crates/core/src/dom/doms.rs b/crates/core/src/dom/doms.rs index 56a93981a..9b9b086d1 100644 --- a/crates/core/src/dom/doms.rs +++ b/crates/core/src/dom/doms.rs @@ -1,7 +1,13 @@ -use std::sync::{ - Arc, - Mutex, - MutexGuard, +use std::{ + cell::{ + RefCell, + RefMut, + }, + sync::{ + Arc, + Mutex, + MutexGuard, + }, }; use dioxus_core::VirtualDom; @@ -133,11 +139,11 @@ pub struct FreyaDOM { compositor_cache: Arc>, accessibility_dirty_nodes: Arc>, accessibility_generator: Arc, - paragraph_cache: Arc>, + paragraph_cache: RefCell, } -impl Default for FreyaDOM { - fn default() -> Self { +impl FreyaDOM { + pub fn new(paragraph_cache_max_size: usize) -> Self { let mut rdom = RealDom::::new([ CursorState::to_type_erased(), FontStyleState::to_type_erased(), @@ -161,7 +167,7 @@ impl Default for FreyaDOM { compositor_cache: Arc::default(), accessibility_dirty_nodes: Arc::default(), accessibility_generator: Arc::default(), - paragraph_cache: Arc::default(), + paragraph_cache: RefCell::new(ParagraphCache::new(paragraph_cache_max_size)), } } } @@ -199,8 +205,8 @@ impl FreyaDOM { &self.accessibility_generator } - pub fn paragraph_cache(&self) -> MutexGuard { - self.paragraph_cache.lock().unwrap() + pub fn paragraph_cache(&self) -> RefMut { + self.paragraph_cache.borrow_mut() } /// Create the initial DOM from the given Mutations diff --git a/crates/core/src/render/mod.rs b/crates/core/src/render/mod.rs index 35d6d787c..2fccf4d2a 100644 --- a/crates/core/src/render/mod.rs +++ b/crates/core/src/render/mod.rs @@ -1,10 +1,12 @@ pub mod compositor; +pub mod paragraph_cache; pub mod pipeline; pub mod skia_measurer; pub mod utils; mod wireframe_renderer; pub use compositor::*; +pub use paragraph_cache::*; pub use pipeline::*; pub use skia_measurer::*; pub use utils::*; diff --git a/crates/core/src/render/paragraph_cache.rs b/crates/core/src/render/paragraph_cache.rs new file mode 100644 index 000000000..cf9f8219f --- /dev/null +++ b/crates/core/src/render/paragraph_cache.rs @@ -0,0 +1,91 @@ +use std::borrow::Cow; + +use freya_common::CachedParagraph; +use freya_engine::prelude::*; +use freya_node_state::{ + FontStyleState, + TextOverflow, +}; +use rustc_hash::FxBuildHasher; + +pub struct ParagraphCache { + cache: indexmap::IndexMap, + max_size: usize, +} + +impl ParagraphCache { + pub const MAX_SIZE: usize = 128; + + pub fn new(max_size: usize) -> Self { + Self { + cache: indexmap::IndexMap::with_hasher(FxBuildHasher), + max_size, + } + } + + pub fn insert(&mut self, key: u64, paragraph: CachedParagraph) { + self.cache.insert(key, paragraph); + + if self.cache.len() > self.max_size { + let first = *self.cache.first().unwrap().0; + self.cache.shift_remove(&first); + + #[cfg(debug_assertions)] + tracing::info!( + "Reached max size of paragraph cache ({}), removing first element", + self.max_size + ); + } + } + + pub fn get(&self, key: &u64) -> Option<&CachedParagraph> { + self.cache.get(key) + } +} + +#[derive(Hash)] +pub struct ParagraphCacheKey<'a> { + pub color: (u8, u8, u8), + pub font_family: &'a [String], + pub font_size: u32, + pub font_slant: Slant, + pub font_weight: i32, + pub font_width: i32, + pub line_height: Option, + pub word_spacing: u32, + pub letter_spacing: u32, + pub text_align: TextAlign, + pub max_lines: Option, + pub text_overflow: TextOverflow, + pub text_height: TextHeightBehavior, + pub text: Option>, +} + +impl<'a> ParagraphCacheKey<'a> { + pub fn new( + font_style: &FontStyleState, + font_family: &'a [String], + text: Option>, + ) -> Self { + Self { + color: ( + font_style.color.r(), + font_style.color.g(), + font_style.color.b(), + ), + font_family, + font_size: font_style.font_size.to_bits(), + font_slant: font_style.font_slant, + font_weight: *font_style.font_weight, + font_width: *font_style.font_width, + line_height: font_style.line_height.map(|n| n.to_bits()), + word_spacing: font_style.word_spacing.to_bits(), + letter_spacing: font_style.letter_spacing.to_bits(), + text_align: font_style.text_align, + max_lines: font_style.max_lines, + text_overflow: font_style.text_overflow.clone(), + text_height: font_style.text_height, + text, + } + } +} diff --git a/crates/core/src/render/skia_measurer.rs b/crates/core/src/render/skia_measurer.rs index 9b1b6096f..8a50437fd 100644 --- a/crates/core/src/render/skia_measurer.rs +++ b/crates/core/src/render/skia_measurer.rs @@ -1,9 +1,6 @@ use std::sync::Arc; -use freya_common::{ - CachedParagraph, - NodeReferenceLayout, -}; +use freya_common::NodeReferenceLayout; use freya_engine::prelude::*; use freya_native_core::{ prelude::{ @@ -26,11 +23,10 @@ use torin::prelude::{ use super::{ create_label, create_paragraph, + ParagraphCache, }; use crate::dom::*; -pub type ParagraphCache = indexmap::IndexMap; - /// Provides Text measurements using Skia APIs like SkParagraph pub struct SkiaMeasurer<'a> { pub font_collection: &'a FontCollection, diff --git a/crates/core/src/render/utils/label.rs b/crates/core/src/render/utils/label.rs index 585977c33..310c790d9 100644 --- a/crates/core/src/render/utils/label.rs +++ b/crates/core/src/render/utils/label.rs @@ -1,4 +1,7 @@ -use std::rc::Rc; +use std::{ + borrow::Cow, + rc::Rc, +}; use freya_common::CachedParagraph; use freya_engine::prelude::*; @@ -10,10 +13,12 @@ use freya_node_state::FontStyleState; use rustc_hash::FxBuildHasher; use torin::prelude::Size2D; -use super::ParagraphCacheKey; use crate::{ dom::*, - render::ParagraphCache, + render::{ + ParagraphCache, + ParagraphCacheKey, + }, }; pub fn create_label( @@ -26,36 +31,23 @@ pub fn create_label( ) -> CachedParagraph { let font_style = &*node.get::().unwrap(); - let mut paragraph_cache_key: (u32, ParagraphCacheKey) = ( - area_size.width.to_bits(), - ParagraphCacheKey { - color: ( - font_style.color.r(), - font_style.color.g(), - font_style.color.b(), - ), - font_family: default_font_family, - font_size: font_style.font_size.to_bits(), - font_slant: font_style.font_slant, - font_weight: *font_style.font_weight, - font_width: *font_style.font_width, - line_height: font_style.line_height.map(|n| n.to_bits()), - word_spacing: font_style.word_spacing.to_bits(), - letter_spacing: font_style.letter_spacing.to_bits(), - text_align: font_style.text_align, - max_lines: font_style.max_lines, - text_overflow: font_style.text_overflow.clone(), - text_height: font_style.text_height, - text: Some("".to_string()), - }, - ); + let mut label_text = String::new(); for child in node.children() { if let NodeType::Text(text) = &*child.node_type() { - paragraph_cache_key.1.text.as_mut().unwrap().push_str(text); + label_text.push_str(text); } } + let paragraph_cache_key: (u32, ParagraphCacheKey) = ( + area_size.width.to_bits(), + ParagraphCacheKey::new( + font_style, + default_font_family, + Some(Cow::Borrowed(&label_text)), + ), + ); + use std::hash::BuildHasher; let hasher = FxBuildHasher; @@ -82,11 +74,7 @@ pub fn create_label( let mut paragraph_builder = ParagraphBuilder::new(¶graph_style, font_collection); - for child in node.children() { - if let NodeType::Text(text) = &*child.node_type() { - paragraph_builder.add_text(text); - } - } + paragraph_builder.add_text(label_text); let mut paragraph = paragraph_builder.build(); paragraph.layout( @@ -101,10 +89,5 @@ pub fn create_label( paragraph_cache.insert(paragraph_cache_key_hash, paragraph.clone()); - if paragraph_cache.len() > 128 { - let first = *paragraph_cache.first().unwrap().0; - paragraph_cache.shift_remove(&first); - } - paragraph } diff --git a/crates/core/src/render/utils/paragraph.rs b/crates/core/src/render/utils/paragraph.rs index 83bb820d3..1bf12aa3f 100644 --- a/crates/core/src/render/utils/paragraph.rs +++ b/crates/core/src/render/utils/paragraph.rs @@ -1,5 +1,5 @@ use std::{ - hash::Hash, + borrow::Cow, rc::Rc, vec, }; @@ -17,7 +17,6 @@ use freya_node_state::{ FontStyleState, HighlightMode, LayoutState, - TextOverflow, }; use rustc_hash::FxBuildHasher; use torin::prelude::{ @@ -28,25 +27,11 @@ use torin::prelude::{ use crate::{ dom::DioxusNode, - render::ParagraphCache, + render::{ + ParagraphCache, + ParagraphCacheKey, + }, }; -#[derive(Hash)] -pub struct ParagraphCacheKey<'a> { - pub color: (u8, u8, u8), - pub font_family: &'a [String], - pub font_size: u32, - pub font_slant: Slant, - pub font_weight: i32, - pub font_width: i32, - pub line_height: Option, - pub word_spacing: u32, - pub letter_spacing: u32, - pub text_align: TextAlign, - pub max_lines: Option, - pub text_overflow: TextOverflow, - pub text_height: TextHeightBehavior, - pub text: Option, -} /// Compose a new SkParagraph pub fn create_paragraph( @@ -62,26 +47,7 @@ pub fn create_paragraph( let mut paragraph_cache_key: (u32, ParagraphCacheKey, Vec) = ( area_size.width.to_bits(), - ParagraphCacheKey { - color: ( - font_style.color.r(), - font_style.color.g(), - font_style.color.b(), - ), - font_family: default_font_family, - font_size: font_style.font_size.to_bits(), - font_slant: font_style.font_slant, - font_weight: *font_style.font_weight, - font_width: *font_style.font_width, - line_height: font_style.line_height.map(|n| n.to_bits()), - word_spacing: font_style.word_spacing.to_bits(), - letter_spacing: font_style.letter_spacing.to_bits(), - text_align: font_style.text_align, - max_lines: font_style.max_lines, - text_overflow: font_style.text_overflow.clone(), - text_height: font_style.text_height, - text: None, - }, + ParagraphCacheKey::new(font_style, default_font_family, None), vec![], ); @@ -94,32 +60,18 @@ pub fn create_paragraph( let text_node = *text_nodes.first().unwrap(); let text_node_type = &*text_node.node_type(); let text_font_style = text_span.get::().unwrap(); - let mut key = ParagraphCacheKey { - color: ( - text_font_style.color.r(), - font_style.color.g(), - font_style.color.b(), - ), - font_family: default_font_family, - font_size: text_font_style.font_size.to_bits(), - font_slant: text_font_style.font_slant, - font_weight: *text_font_style.font_weight, - font_width: *text_font_style.font_width, - line_height: text_font_style.line_height.map(|n| n.to_bits()), - word_spacing: text_font_style.word_spacing.to_bits(), - letter_spacing: text_font_style.letter_spacing.to_bits(), - text_align: text_font_style.text_align, - max_lines: text_font_style.max_lines, - text_overflow: text_font_style.text_overflow.clone(), - text_height: text_font_style.text_height, - text: None, - }; - if let NodeType::Text(text) = text_node_type { - key.text = Some(text.clone()); - } + let span_text = text_node_type.text().map(str::to_owned); - paragraph_cache_key.2.push(key); + if let Some(span_text) = span_text { + let key = ParagraphCacheKey::new( + &text_font_style, + default_font_family, + Some(Cow::Owned(span_text)), + ); + + paragraph_cache_key.2.push(key); + } } } @@ -188,11 +140,6 @@ pub fn create_paragraph( paragraph_cache.insert(paragraph_cache_key_hash, paragraph.clone()); - if paragraph_cache.len() > 128 { - let first = *paragraph_cache.first().unwrap().0; - paragraph_cache.shift_remove(&first); - } - paragraph } diff --git a/crates/freya/src/launch.rs b/crates/freya/src/launch.rs index 50d2b980d..42b4a5541 100644 --- a/crates/freya/src/launch.rs +++ b/crates/freya/src/launch.rs @@ -186,7 +186,7 @@ pub fn launch_cfg(app: AppComponent, config: LaunchConfig SafeDOM, }; - let fdom = FreyaDOM::default(); + let fdom = FreyaDOM::new(config.max_paragraph_cache_size); let sdom = SafeDOM::new(fdom); #[cfg(feature = "tracing-subscriber")] diff --git a/crates/renderer/src/app.rs b/crates/renderer/src/app.rs index 0d27c538b..7657151ad 100644 --- a/crates/renderer/src/app.rs +++ b/crates/renderer/src/app.rs @@ -84,6 +84,7 @@ impl Application { let font_mgr: FontMgr = provider.into(); font_collection.set_default_font_manager(def_mgr, None); font_collection.set_dynamic_font_manager(font_mgr.clone()); + font_collection.paragraph_cache_mut().turn_on(false); let (event_emitter, event_receiver) = mpsc::unbounded_channel(); let (platform_sender, platform_receiver) = watch::channel(NativePlatformState { diff --git a/crates/renderer/src/config.rs b/crates/renderer/src/config.rs index 226cf6e4d..05e9d11e4 100644 --- a/crates/renderer/src/config.rs +++ b/crates/renderer/src/config.rs @@ -9,6 +9,7 @@ use freya_core::{ PluginsManager, }, prelude::EventMessage, + render::ParagraphCache, style::default_fonts, }; use freya_engine::prelude::Color; @@ -84,6 +85,7 @@ pub struct LaunchConfig<'a, T: Clone = ()> { pub embedded_fonts: EmbeddedFonts<'a>, pub plugins: PluginsManager, pub default_fonts: Vec, + pub max_paragraph_cache_size: usize, } impl<'a, T: Clone> Default for LaunchConfig<'a, T> { @@ -94,6 +96,7 @@ impl<'a, T: Clone> Default for LaunchConfig<'a, T> { embedded_fonts: Default::default(), plugins: Default::default(), default_fonts: default_fonts(), + max_paragraph_cache_size: ParagraphCache::MAX_SIZE, } } } @@ -235,4 +238,10 @@ impl<'a, T: Clone> LaunchConfig<'a, T> { self.window_config.event_loop_builder_hook = Some(Box::new(event_loop_builder_hook)); self } + + /// Set a custom max paragraph cache size. + pub fn with_max_paragraph_cache_size(mut self, max_paragraph_cache_size: usize) -> Self { + self.max_paragraph_cache_size = max_paragraph_cache_size; + self + } } diff --git a/crates/testing/src/launch.rs b/crates/testing/src/launch.rs index 4d934666b..1df0d7852 100644 --- a/crates/testing/src/launch.rs +++ b/crates/testing/src/launch.rs @@ -42,7 +42,7 @@ pub fn launch_test_with_config( config: TestingConfig, ) -> TestingHandler { let vdom = with_accessibility(root); - let fdom = FreyaDOM::default(); + let fdom = FreyaDOM::new(ParagraphCache::MAX_SIZE); let sdom = SafeDOM::new(fdom); let (event_emitter, event_receiver) = unbounded_channel(); From e945b7baa4c0adb5887c9d3c5314a549b26dfc4f Mon Sep 17 00:00:00 2001 From: marc2332 Date: Tue, 19 Nov 2024 14:39:42 +0100 Subject: [PATCH 10/14] clean up --- crates/common/src/layout.rs | 3 +- crates/core/src/elements/label.rs | 6 +- crates/core/src/elements/paragraph.rs | 14 +-- crates/core/src/render/paragraph_cache.rs | 10 ++- crates/core/src/render/skia_measurer.rs | 8 +- crates/core/src/render/utils/label.rs | 63 +++++++------ crates/core/src/render/utils/paragraph.rs | 103 +++++++++++----------- crates/renderer/src/app.rs | 2 +- 8 files changed, 106 insertions(+), 103 deletions(-) diff --git a/crates/common/src/layout.rs b/crates/common/src/layout.rs index 84b6e3471..3ee0608ca 100644 --- a/crates/common/src/layout.rs +++ b/crates/common/src/layout.rs @@ -1,4 +1,5 @@ use std::{ + cell::RefCell, ops::Div, rc::Rc, }; @@ -31,7 +32,7 @@ pub enum CursorLayoutResponse { } #[derive(Clone)] -pub struct CachedParagraph(pub Rc); +pub struct CachedParagraph(pub Rc>); /// # Safety /// Skia `Paragraph` are neither Sync or Send, but in order to store them in the Associated diff --git a/crates/core/src/elements/label.rs b/crates/core/src/elements/label.rs index 1da8f7b1e..e8942b44d 100644 --- a/crates/core/src/elements/label.rs +++ b/crates/core/src/elements/label.rs @@ -43,9 +43,9 @@ impl ElementUtils for LabelElement { let area = layout_node.visible_area(); let x = area.min_x(); - let y = area.min_y() + align_main_align_paragraph(node_ref, &area, paragraph); + let y = area.min_y() + align_main_align_paragraph(node_ref, &area, ¶graph.borrow()); - paragraph.paint(canvas, (x, y)); + paragraph.borrow().paint(canvas, (x, y)); } #[inline] @@ -69,7 +69,7 @@ impl ElementUtils for LabelElement { .unwrap() .0; let mut area = layout_node.visible_area(); - area.size.height = area.size.height.max(paragraph.height()); + area.size.height = area.size.height.max(paragraph.borrow().height()); let font_style = node_ref.get::().unwrap(); diff --git a/crates/core/src/elements/paragraph.rs b/crates/core/src/elements/paragraph.rs index c188bd3fc..a83f43fe7 100644 --- a/crates/core/src/elements/paragraph.rs +++ b/crates/core/src/elements/paragraph.rs @@ -65,14 +65,14 @@ impl ParagraphElement { return; } - let y = align_main_align_paragraph(node, &layout_node.area, paragraph); + let y = align_main_align_paragraph(node, &layout_node.area, ¶graph.borrow()); if let Some(cursor_reference) = &cursor_state.cursor_ref { if let Some(cursor_position) = text_measurement.cursor_position { let position = CursorPoint::new(cursor_position.x, cursor_position.y - y as f64); // Calculate the new cursor position - let char_position = paragraph.get_glyph_position_at_coordinate( + let char_position = paragraph.borrow().get_glyph_position_at_coordinate( position.mul(scale_factor).to_i32().to_tuple(), ); @@ -91,11 +91,11 @@ impl ParagraphElement { let dist_position = CursorPoint::new(dist.x, dist.y - y as f64); // Calculate the start of the highlighting - let origin_char = paragraph.get_glyph_position_at_coordinate( + let origin_char = paragraph.borrow().get_glyph_position_at_coordinate( origin_position.mul(scale_factor).to_i32().to_tuple(), ); // Calculate the end of the highlighting - let dist_char = paragraph.get_glyph_position_at_coordinate( + let dist_char = paragraph.borrow().get_glyph_position_at_coordinate( dist_position.mul(scale_factor).to_i32().to_tuple(), ); @@ -151,7 +151,7 @@ impl ElementUtils for ParagraphElement { paragraph_cache, ) .0; - paint(¶graph); + paint(¶graph.borrow()); } else { let paragraph = &layout_node .data @@ -160,7 +160,7 @@ impl ElementUtils for ParagraphElement { .get::() .unwrap() .0; - paint(paragraph); + paint(¶graph.borrow()); }; } @@ -195,7 +195,7 @@ impl ElementUtils for ParagraphElement { .unwrap() .0; let mut area = layout_node.visible_area(); - area.size.height = area.size.height.max(paragraph.height()); + area.size.height = area.size.height.max(paragraph.borrow().height()); // Iterate over all the text spans inside this paragraph and if any of them // has a shadow at all, apply this shadow to the general paragraph. diff --git a/crates/core/src/render/paragraph_cache.rs b/crates/core/src/render/paragraph_cache.rs index cf9f8219f..1540ca5e1 100644 --- a/crates/core/src/render/paragraph_cache.rs +++ b/crates/core/src/render/paragraph_cache.rs @@ -58,14 +58,20 @@ pub struct ParagraphCacheKey<'a> { pub max_lines: Option, pub text_overflow: TextOverflow, pub text_height: TextHeightBehavior, - pub text: Option>, + pub text: Option, +} + +#[derive(Hash)] +pub enum ParagraphCacheText { + Hashes(Vec), + Hash(u64), } impl<'a> ParagraphCacheKey<'a> { pub fn new( font_style: &FontStyleState, font_family: &'a [String], - text: Option>, + text: Option, ) -> Self { Self { color: ( diff --git a/crates/core/src/render/skia_measurer.rs b/crates/core/src/render/skia_measurer.rs index 8a50437fd..ce670f18d 100644 --- a/crates/core/src/render/skia_measurer.rs +++ b/crates/core/src/render/skia_measurer.rs @@ -76,8 +76,8 @@ impl<'a> LayoutMeasurer for SkiaMeasurer<'a> { self.scale_factor, self.paragraph_cache, ); - let height = label.0.height(); - let res = Size2D::new(label.0.longest_line(), height); + let height = label.0.borrow().height(); + let res = Size2D::new(label.0.borrow().longest_line(), height); let mut map = SendAnyMap::new(); map.insert(label); Some((res, Arc::new(map))) @@ -92,8 +92,8 @@ impl<'a> LayoutMeasurer for SkiaMeasurer<'a> { self.scale_factor, self.paragraph_cache, ); - let height = paragraph.0.height(); - let res = Size2D::new(paragraph.0.longest_line(), height); + let height = paragraph.0.borrow().height(); + let res = Size2D::new(paragraph.0.borrow().longest_line(), height); let mut map = SendAnyMap::new(); map.insert(paragraph); Some((res, Arc::new(map))) diff --git a/crates/core/src/render/utils/label.rs b/crates/core/src/render/utils/label.rs index 310c790d9..e677a60bd 100644 --- a/crates/core/src/render/utils/label.rs +++ b/crates/core/src/render/utils/label.rs @@ -1,6 +1,7 @@ use std::{ - borrow::Cow, + cell::RefCell, rc::Rc, + time::Instant, }; use freya_common::CachedParagraph; @@ -18,6 +19,7 @@ use crate::{ render::{ ParagraphCache, ParagraphCacheKey, + ParagraphCacheText, }, }; @@ -31,21 +33,19 @@ pub fn create_label( ) -> CachedParagraph { let font_style = &*node.get::().unwrap(); - let mut label_text = String::new(); + let mut label_text = Vec::new(); for child in node.children() { if let NodeType::Text(text) = &*child.node_type() { - label_text.push_str(text); + let span_text_hash = FxBuildHasher.hash_one(text); + label_text.push(span_text_hash); } } - let paragraph_cache_key: (u32, ParagraphCacheKey) = ( - area_size.width.to_bits(), - ParagraphCacheKey::new( - font_style, - default_font_family, - Some(Cow::Borrowed(&label_text)), - ), + let paragraph_cache_key = ParagraphCacheKey::new( + font_style, + default_font_family, + Some(ParagraphCacheText::Hashes(label_text)), ); use std::hash::BuildHasher; @@ -54,30 +54,33 @@ pub fn create_label( let paragraph_cache_key_hash = hasher.hash_one(paragraph_cache_key); let paragraph = paragraph_cache.get(¶graph_cache_key_hash).cloned(); - if let Some(paragraph) = paragraph { - return paragraph; - } - let mut paragraph_style = ParagraphStyle::default(); - paragraph_style.set_text_align(font_style.text_align); - paragraph_style.set_max_lines(font_style.max_lines); - paragraph_style.set_replace_tab_characters(true); - paragraph_style.set_text_height_behavior(font_style.text_height); + let paragraph = paragraph.unwrap_or_else(|| { + let mut paragraph_style = ParagraphStyle::default(); + paragraph_style.set_text_align(font_style.text_align); + paragraph_style.set_max_lines(font_style.max_lines); + paragraph_style.set_replace_tab_characters(true); + paragraph_style.set_text_height_behavior(font_style.text_height); - if let Some(ellipsis) = font_style.text_overflow.get_ellipsis() { - paragraph_style.set_ellipsis(ellipsis); - } + if let Some(ellipsis) = font_style.text_overflow.get_ellipsis() { + paragraph_style.set_ellipsis(ellipsis); + } - let text_style = - font_style.text_style(default_font_family, scale_factor, font_style.text_height); - paragraph_style.set_text_style(&text_style); + let text_style = + font_style.text_style(default_font_family, scale_factor, font_style.text_height); + paragraph_style.set_text_style(&text_style); - let mut paragraph_builder = ParagraphBuilder::new(¶graph_style, font_collection); + let mut paragraph_builder = ParagraphBuilder::new(¶graph_style, font_collection); - paragraph_builder.add_text(label_text); + for child in node.children() { + if let NodeType::Text(text) = &*child.node_type() { + paragraph_builder.add_text(text); + } + } - let mut paragraph = paragraph_builder.build(); - paragraph.layout( + CachedParagraph(Rc::new(RefCell::new(paragraph_builder.build()))) + }); + paragraph.0.borrow_mut().layout( if font_style.max_lines == Some(1) && font_style.text_align == TextAlign::default() { f32::MAX } else { @@ -85,9 +88,5 @@ pub fn create_label( }, ); - let paragraph = CachedParagraph(Rc::new(paragraph)); - - paragraph_cache.insert(paragraph_cache_key_hash, paragraph.clone()); - paragraph } diff --git a/crates/core/src/render/utils/paragraph.rs b/crates/core/src/render/utils/paragraph.rs index 1bf12aa3f..5334f4421 100644 --- a/crates/core/src/render/utils/paragraph.rs +++ b/crates/core/src/render/utils/paragraph.rs @@ -1,6 +1,7 @@ use std::{ - borrow::Cow, + cell::RefCell, rc::Rc, + time::Instant, vec, }; @@ -30,6 +31,7 @@ use crate::{ render::{ ParagraphCache, ParagraphCacheKey, + ParagraphCacheText, }, }; @@ -45,8 +47,7 @@ pub fn create_paragraph( ) -> CachedParagraph { let font_style = &*node.get::().unwrap(); - let mut paragraph_cache_key: (u32, ParagraphCacheKey, Vec) = ( - area_size.width.to_bits(), + let mut paragraph_cache_key: (ParagraphCacheKey, Vec) = ( ParagraphCacheKey::new(font_style, default_font_family, None), vec![], ); @@ -61,16 +62,17 @@ pub fn create_paragraph( let text_node_type = &*text_node.node_type(); let text_font_style = text_span.get::().unwrap(); - let span_text = text_node_type.text().map(str::to_owned); + let span_text = text_node_type.text(); if let Some(span_text) = span_text { + let span_text_hash = FxBuildHasher.hash_one(span_text); let key = ParagraphCacheKey::new( &text_font_style, default_font_family, - Some(Cow::Owned(span_text)), + Some(ParagraphCacheText::Hash(span_text_hash)), ); - paragraph_cache_key.2.push(key); + paragraph_cache_key.1.push(key); } } } @@ -80,55 +82,54 @@ pub fn create_paragraph( let paragraph_cache_key_hash = hasher.hash_one(paragraph_cache_key); let paragraph = paragraph_cache.get(¶graph_cache_key_hash).cloned(); - if let Some(paragraph) = paragraph { - return paragraph; - } - - let mut paragraph_style = ParagraphStyle::default(); - paragraph_style.set_text_align(font_style.text_align); - paragraph_style.set_max_lines(font_style.max_lines); - paragraph_style.set_replace_tab_characters(true); - paragraph_style.set_text_height_behavior(font_style.text_height); - - if let Some(ellipsis) = font_style.text_overflow.get_ellipsis() { - paragraph_style.set_ellipsis(ellipsis); - } - - let mut paragraph_builder = ParagraphBuilder::new(¶graph_style, font_collection); - - let text_style = - font_style.text_style(default_font_family, scale_factor, font_style.text_height); - paragraph_builder.push_style(&text_style); + let paragraph = paragraph.unwrap_or_else(|| { + let mut paragraph_style = ParagraphStyle::default(); + paragraph_style.set_text_align(font_style.text_align); + paragraph_style.set_max_lines(font_style.max_lines); + paragraph_style.set_replace_tab_characters(true); + paragraph_style.set_text_height_behavior(font_style.text_height); + + if let Some(ellipsis) = font_style.text_overflow.get_ellipsis() { + paragraph_style.set_ellipsis(ellipsis); + } - for text_span in node.children() { - if let NodeType::Element(ElementNode { - tag: TagName::Text, .. - }) = &*text_span.node_type() - { - let text_nodes = text_span.children(); - let text_node = *text_nodes.first().unwrap(); - let text_node_type = &*text_node.node_type(); - let text_font_style = text_span.get::().unwrap(); - let text_style = text_font_style.text_style( - default_font_family, - scale_factor, - font_style.text_height, - ); - paragraph_builder.push_style(&text_style); + let mut paragraph_builder = ParagraphBuilder::new(¶graph_style, font_collection); + + let text_style = + font_style.text_style(default_font_family, scale_factor, font_style.text_height); + paragraph_builder.push_style(&text_style); + + for text_span in node.children() { + if let NodeType::Element(ElementNode { + tag: TagName::Text, .. + }) = &*text_span.node_type() + { + let text_nodes = text_span.children(); + let text_node = *text_nodes.first().unwrap(); + let text_node_type = &*text_node.node_type(); + let text_font_style = text_span.get::().unwrap(); + let text_style = text_font_style.text_style( + default_font_family, + scale_factor, + font_style.text_height, + ); + paragraph_builder.push_style(&text_style); - if let NodeType::Text(text) = text_node_type { - paragraph_builder.add_text(text); + if let NodeType::Text(text) = text_node_type { + paragraph_builder.add_text(text); + } } } - } - if is_rendering { - // This is very tricky, but it works! It allows freya to render the cursor at the end of a line. - paragraph_builder.add_text(" "); - } + if is_rendering { + // This is very tricky, but it works! It allows freya to render the cursor at the end of a line. + paragraph_builder.add_text(" "); + } - let mut paragraph = paragraph_builder.build(); - paragraph.layout( + CachedParagraph(Rc::new(RefCell::new(paragraph_builder.build()))) + }); + + paragraph.0.borrow_mut().layout( if font_style.max_lines == Some(1) && font_style.text_align == TextAlign::default() { f32::MAX } else { @@ -136,10 +137,6 @@ pub fn create_paragraph( }, ); - let paragraph = CachedParagraph(Rc::new(paragraph)); - - paragraph_cache.insert(paragraph_cache_key_hash, paragraph.clone()); - paragraph } diff --git a/crates/renderer/src/app.rs b/crates/renderer/src/app.rs index 7657151ad..4fce02cf6 100644 --- a/crates/renderer/src/app.rs +++ b/crates/renderer/src/app.rs @@ -84,7 +84,7 @@ impl Application { let font_mgr: FontMgr = provider.into(); font_collection.set_default_font_manager(def_mgr, None); font_collection.set_dynamic_font_manager(font_mgr.clone()); - font_collection.paragraph_cache_mut().turn_on(false); + font_collection.paragraph_cache_mut().turn_on(true); let (event_emitter, event_receiver) = mpsc::unbounded_channel(); let (platform_sender, platform_receiver) = watch::channel(NativePlatformState { From f9f6bf386da9cbdfd585a3cabecd244f3e52019a Mon Sep 17 00:00:00 2001 From: marc2332 Date: Tue, 19 Nov 2024 15:07:18 +0100 Subject: [PATCH 11/14] clean up.. --- crates/core/src/render/paragraph_cache.rs | 1 - crates/core/src/render/utils/label.rs | 3 ++- crates/core/src/render/utils/paragraph.rs | 4 +++- crates/renderer/src/app.rs | 2 +- 4 files changed, 6 insertions(+), 4 deletions(-) diff --git a/crates/core/src/render/paragraph_cache.rs b/crates/core/src/render/paragraph_cache.rs index 1540ca5e1..275b64b9a 100644 --- a/crates/core/src/render/paragraph_cache.rs +++ b/crates/core/src/render/paragraph_cache.rs @@ -1,4 +1,3 @@ -use std::borrow::Cow; use freya_common::CachedParagraph; use freya_engine::prelude::*; diff --git a/crates/core/src/render/utils/label.rs b/crates/core/src/render/utils/label.rs index e677a60bd..b6d1562e1 100644 --- a/crates/core/src/render/utils/label.rs +++ b/crates/core/src/render/utils/label.rs @@ -1,7 +1,6 @@ use std::{ cell::RefCell, rc::Rc, - time::Instant, }; use freya_common::CachedParagraph; @@ -88,5 +87,7 @@ pub fn create_label( }, ); + paragraph_cache.insert(paragraph_cache_key_hash, paragraph.clone()); + paragraph } diff --git a/crates/core/src/render/utils/paragraph.rs b/crates/core/src/render/utils/paragraph.rs index 5334f4421..c1955506f 100644 --- a/crates/core/src/render/utils/paragraph.rs +++ b/crates/core/src/render/utils/paragraph.rs @@ -1,7 +1,6 @@ use std::{ cell::RefCell, rc::Rc, - time::Instant, vec, }; @@ -82,6 +81,7 @@ pub fn create_paragraph( let paragraph_cache_key_hash = hasher.hash_one(paragraph_cache_key); let paragraph = paragraph_cache.get(¶graph_cache_key_hash).cloned(); + let paragraph = paragraph.unwrap_or_else(|| { let mut paragraph_style = ParagraphStyle::default(); paragraph_style.set_text_align(font_style.text_align); @@ -137,6 +137,8 @@ pub fn create_paragraph( }, ); + paragraph_cache.insert(paragraph_cache_key_hash, paragraph.clone()); + paragraph } diff --git a/crates/renderer/src/app.rs b/crates/renderer/src/app.rs index 4fce02cf6..7657151ad 100644 --- a/crates/renderer/src/app.rs +++ b/crates/renderer/src/app.rs @@ -84,7 +84,7 @@ impl Application { let font_mgr: FontMgr = provider.into(); font_collection.set_default_font_manager(def_mgr, None); font_collection.set_dynamic_font_manager(font_mgr.clone()); - font_collection.paragraph_cache_mut().turn_on(true); + font_collection.paragraph_cache_mut().turn_on(false); let (event_emitter, event_receiver) = mpsc::unbounded_channel(); let (platform_sender, platform_receiver) = watch::channel(NativePlatformState { From 85adf2ca0f41abc42208d7e84982d73b1ef0724f Mon Sep 17 00:00:00 2001 From: marc2332 Date: Wed, 20 Nov 2024 18:51:13 +0100 Subject: [PATCH 12/14] fmt --- crates/core/src/render/paragraph_cache.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/crates/core/src/render/paragraph_cache.rs b/crates/core/src/render/paragraph_cache.rs index 275b64b9a..b9f749308 100644 --- a/crates/core/src/render/paragraph_cache.rs +++ b/crates/core/src/render/paragraph_cache.rs @@ -1,4 +1,3 @@ - use freya_common::CachedParagraph; use freya_engine::prelude::*; use freya_node_state::{ From b98b04b285ec63fb5b9957f1221a92ecd0ff093f Mon Sep 17 00:00:00 2001 From: marc2332 Date: Wed, 27 Nov 2024 19:54:02 +0100 Subject: [PATCH 13/14] chore: Fix mocked api --- crates/engine/src/mocked.rs | 46 ++++++++++++++++++++++++++++++++++++- crates/engine/src/skia.rs | 38 ------------------------------ 2 files changed, 45 insertions(+), 39 deletions(-) diff --git a/crates/engine/src/mocked.rs b/crates/engine/src/mocked.rs index 27f15a925..ba771a4b3 100644 --- a/crates/engine/src/mocked.rs +++ b/crates/engine/src/mocked.rs @@ -304,6 +304,13 @@ impl From for Weight { } } +impl Deref for Weight { + type Target = i32; + fn deref(&self) -> &Self::Target { + unimplemented!("This is mocked") + } +} + #[allow(non_upper_case_globals)] impl Weight { pub const INVISIBLE: Self = Self(0); @@ -319,7 +326,7 @@ impl Weight { pub const EXTRA_BLACK: Self = Self(1000); } -#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Debug)] +#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Debug, Hash)] pub enum Slant { Upright = 0, Italic = 1, @@ -330,6 +337,13 @@ pub enum Slant { #[repr(transparent)] pub struct Width(i32); +impl Deref for Width { + type Target = i32; + fn deref(&self) -> &Self::Target { + unimplemented!("This is mocked") + } +} + #[allow(non_upper_case_globals)] impl Width { pub const ULTRA_CONDENSED: Self = Self(1); @@ -697,6 +711,12 @@ pub struct FontFeature; pub struct TypefaceFontProvider; +impl Default for TypefaceFontProvider { + fn default() -> Self { + Self::new() + } +} + impl TypefaceFontProvider { pub fn new() -> Self { unimplemented!("This is mocked") @@ -720,6 +740,12 @@ impl From for FontMgr { #[derive(Clone)] pub struct FontCollection; +impl Default for FontCollection { + fn default() -> Self { + Self::new() + } +} + impl FontCollection { pub fn new() -> Self { unimplemented!("This is mocked") @@ -736,6 +762,18 @@ impl FontCollection { pub fn set_dynamic_font_manager(&mut self, _font_manager: impl Into>) { unimplemented!("This is mocked") } + + pub fn paragraph_cache_mut(&mut self) -> &mut ParagraphCache { + unimplemented!("This is mocked") + } +} + +pub struct ParagraphCache; + +impl ParagraphCache { + pub fn turn_on(&self, state: bool) -> f32 { + unimplemented!("This is mocked") + } } pub struct Paragraph; @@ -1291,6 +1329,12 @@ impl FilterMode { pub struct Path; +impl Default for Path { + fn default() -> Self { + Self::new() + } +} + impl Path { pub fn new() -> Self { unimplemented!("This is mocked") diff --git a/crates/engine/src/skia.rs b/crates/engine/src/skia.rs index e8eb0ad52..e98679aa6 100644 --- a/crates/engine/src/skia.rs +++ b/crates/engine/src/skia.rs @@ -5,47 +5,24 @@ pub use skia_safe::{ Width, }, gpu::{ - backend_render_targets, - direct_contexts, gl::{ Format, FramebufferInfo, - Interface, }, - surfaces::wrap_backend_render_target, - BackendRenderTarget, - DirectContext, - RecordingContext, SurfaceOrigin, }, - gradient_shader::GradientShaderColors, - graphics::{ - set_resource_cache_single_allocation_byte_limit, - set_resource_cache_total_bytes_limit, - }, path::ArcSize, - resources::LocalResourceProvider, rrect::Corner, runtime_effect::Uniform, surfaces::raster_n32_premul, - svg, textlayout::{ - paragraph::GlyphClusterInfo, Decoration, FontCollection, - FontFeature, - LineMetrics, - Paragraph, - ParagraphBuilder, ParagraphStyle, - PlaceholderStyle, - PositionWithAffinity, RectHeightStyle, RectWidthStyle, - StrutStyle, TextAlign, TextBaseline, - TextBox, TextDecoration, TextDecorationStyle, TextDirection, @@ -54,41 +31,26 @@ pub use skia_safe::{ TextRange, TextShadow, TextStyle, - TypefaceFontProvider, }, Bitmap, BlurStyle, - Canvas, ClipOp, Color, - ColorSpace, ColorType, - Data, - EncodedImageFormat, FilterMode, FontArguments, FontMgr, - FontStyle, IPoint, IRect, - Image, ImageInfo, - MaskFilter, Matrix, Paint, - PaintStyle, - Path, PathDirection, PathFillType, Point, RRect, Rect, - RuntimeEffect, SamplingOptions, - Shader, - Surface, - TileMode, - Typeface, HSV, M44, RGB, From cbe0020426635b9696fa2d83b3c7ad464ec2a3a0 Mon Sep 17 00:00:00 2001 From: marc2332 Date: Sat, 14 Dec 2024 10:40:03 +0100 Subject: [PATCH 14/14] fix: Update skia reexports --- crates/engine/src/skia.rs | 39 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 38 insertions(+), 1 deletion(-) diff --git a/crates/engine/src/skia.rs b/crates/engine/src/skia.rs index dfe4d9ba9..1d484ace9 100644 --- a/crates/engine/src/skia.rs +++ b/crates/engine/src/skia.rs @@ -6,54 +6,91 @@ pub use skia_safe::{ Width, }, gpu::{ + backend_render_targets, + direct_contexts, gl::{ Format, FramebufferInfo, + Interface, }, + surfaces::wrap_backend_render_target, + BackendRenderTarget, + DirectContext, + RecordingContext, SurfaceOrigin, }, + gradient_shader::GradientShaderColors, + graphics::{ + set_resource_cache_single_allocation_byte_limit, + set_resource_cache_total_bytes_limit, + }, path::ArcSize, + resources::LocalResourceProvider, rrect::Corner, runtime_effect::Uniform, surfaces::raster_n32_premul, + svg, textlayout::{ + paragraph::GlyphClusterInfo, Decoration, FontCollection, + FontFeature, + LineMetrics, + Paragraph, + ParagraphBuilder, ParagraphStyle, + PlaceholderStyle, + PositionWithAffinity, RectHeightStyle, RectWidthStyle, + StrutStyle, TextAlign, TextBaseline, + TextBox, TextDecoration, TextDecorationStyle, TextDirection, TextHeightBehavior, - TextIndex, TextRange, TextShadow, TextStyle, + TypefaceFontProvider, }, wrapper::PointerWrapper, Bitmap, BlendMode, BlurStyle, + Canvas, ClipOp, Color, + ColorSpace, ColorType, + Data, + EncodedImageFormat, FilterMode, FontArguments, FontMgr, + FontStyle, IPoint, IRect, + Image, ImageInfo, + MaskFilter, Matrix, Paint, + PaintStyle, + Path, PathDirection, PathFillType, Point, RRect, Rect, + RuntimeEffect, SamplingOptions, + Shader, + Surface, + TileMode, + Typeface, HSV, M44, RGB,