diff --git a/Cargo.lock b/Cargo.lock index 2fd195b..da95466 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -313,6 +313,29 @@ dependencies = [ "libc", ] +[[package]] +name = "cosmic-text" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c578f2b9abb4d5f3fbb12aba4008084d435dc6a8425c195cfe0b3594bfea0c25" +dependencies = [ + "bitflags 2.4.2", + "fontdb", + "libm", + "log", + "rangemap", + "rustc-hash", + "rustybuzz 0.12.1", + "self_cell", + "swash", + "sys-locale", + "ttf-parser", + "unicode-bidi", + "unicode-linebreak", + "unicode-script", + "unicode-segmentation", +] + [[package]] name = "crc32fast" version = "1.4.0" @@ -444,9 +467,9 @@ dependencies = [ [[package]] name = "femtovg" -version = "0.7.1" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a3a2d0ff0df09856a5c1c89cc83863a1f0f994c55452186621bb57a01f270b3" +checksum = "18ab822e58e8bc2b89840dc5dde49afe39302e129c60d39c8520200c085404a7" dependencies = [ "bitflags 2.4.2", "fnv", @@ -454,9 +477,10 @@ dependencies = [ "glow", "image", "imgref", + "log", "lru", "rgb", - "rustybuzz", + "rustybuzz 0.11.0", "unicode-bidi", "unicode-segmentation", "wasm-bindgen", @@ -488,6 +512,35 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" +[[package]] +name = "font-types" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bd7f3ea17572640b606b35df42cfb6ecdf003704b062580e59918692190b73d" + +[[package]] +name = "fontconfig-parser" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a595cb550439a117696039dfc69830492058211b771a2a165379f2a1a53d84d" +dependencies = [ + "roxmltree", +] + +[[package]] +name = "fontdb" +version = "0.16.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0299020c3ef3f60f526a4f64ab4a3d4ce116b1acbf24cdd22da0068e5d81dc3" +dependencies = [ + "fontconfig-parser", + "log", + "memmap2", + "slotmap", + "tinyvec", + "ttf-parser", +] + [[package]] name = "foreign-types" version = "0.3.2" @@ -660,9 +713,9 @@ dependencies = [ [[package]] name = "gif" -version = "0.12.0" +version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "80792593675e051cf94a4b111980da2ba60d4a83e43e0048c5693baab3977045" +checksum = "3fb2d69b19215e18bb912fa30f7ce15846e301408695e44e0ef719f1da9e19f2" dependencies = [ "color_quant", "weezl", @@ -687,9 +740,9 @@ dependencies = [ [[package]] name = "glow" -version = "0.12.3" +version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca0fe580e4b60a8ab24a868bc08e2f03cbcb20d3d676601fa909386713333728" +checksum = "bd348e04c43b32574f2de31c8bb397d96c9fcfa1371bd4ca6d8bdc464ab121b1" dependencies = [ "js-sys", "slotmap", @@ -764,9 +817,9 @@ dependencies = [ [[package]] name = "grid" -version = "0.10.0" +version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eec1c01eb1de97451ee0d60de7d81cf1e72aabefb021616027f3d1c3ec1c723c" +checksum = "d196ffc1627db18a531359249b2bf8416178d84b729f3cebeb278f285fb9b58c" [[package]] name = "h2" @@ -903,9 +956,9 @@ dependencies = [ [[package]] name = "image" -version = "0.24.8" +version = "0.24.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "034bbe799d1909622a74d1193aa50147769440040ff36cb2baa947609b0a4e23" +checksum = "5690139d2f55868e080017335e4b94cb7414274c74f1669c84fb5feba2c9f69d" dependencies = [ "bytemuck", "byteorder", @@ -1030,6 +1083,12 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "libm" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ec2a862134d2a7d32d7983ddcdd1c4923530833c9f2ea1a44fc5fa473989058" + [[package]] name = "libredox" version = "0.0.2" @@ -1065,9 +1124,9 @@ checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" [[package]] name = "lru" -version = "0.10.1" +version = "0.12.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "718e8fae447df0c7e1ba7f5189829e63fd536945c8988d61444c19039f16b670" +checksum = "db2c024b41519440580066ba82aab04092b333e09066a5eb86c7c4890df31f22" [[package]] name = "mangades" @@ -1089,10 +1148,13 @@ dependencies = [ name = "mangui" version = "0.1.0" dependencies = [ + "cosmic-text", "femtovg", "glutin", "glutin-winit", + "lazy_static", "raw-window-handle 0.5.2", + "swash", "taffy", "weak-table", "winit", @@ -1325,7 +1387,7 @@ version = "0.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d4586edfe4c648c71797a74c84bacb32b52b212eff5dfe2bb9f2c599844023e7" dependencies = [ - "ttf-parser 0.20.0", + "ttf-parser", ] [[package]] @@ -1447,6 +1509,12 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "rangemap" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "795915a3930a5d6bafd9053d37602fea3e61be2e5d4d788983a8ba9654c1c6f2" + [[package]] name = "raw-window-handle" version = "0.5.2" @@ -1479,6 +1547,15 @@ dependencies = [ "crossbeam-utils", ] +[[package]] +name = "read-fonts" +version = "0.15.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c044ab88c43e2eae05b34a17fc13598736679fdb03d71b49fcfe114443ec8a86" +dependencies = [ + "font-types", +] + [[package]] name = "redox_syscall" version = "0.3.5" @@ -1546,6 +1623,12 @@ dependencies = [ "bytemuck", ] +[[package]] +name = "roxmltree" +version = "0.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3cd14fd5e3b777a7422cca79358c57a8f6e3a703d9ac187448d0daf220c2407f" + [[package]] name = "rusalka" version = "0.1.0" @@ -1566,6 +1649,12 @@ version = "0.1.23" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" +[[package]] +name = "rustc-hash" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" + [[package]] name = "rustix" version = "0.38.31" @@ -1590,17 +1679,34 @@ dependencies = [ [[package]] name = "rustybuzz" -version = "0.7.0" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "162bdf42e261bee271b3957691018634488084ef577dddeb6420a9684cab2a6a" +checksum = "2ee8fe2a8461a0854a37101fe7a1b13998d0cfa987e43248e81d2a5f4570f6fa" dependencies = [ "bitflags 1.3.2", "bytemuck", "smallvec", - "ttf-parser 0.18.1", + "ttf-parser", "unicode-bidi-mirroring", "unicode-ccc", - "unicode-general-category", + "unicode-properties", + "unicode-script", +] + +[[package]] +name = "rustybuzz" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0ae5692c5beaad6a9e22830deeed7874eae8a4e3ba4076fb48e12c56856222c" +dependencies = [ + "bitflags 2.4.2", + "bytemuck", + "libm", + "smallvec", + "ttf-parser", + "unicode-bidi-mirroring", + "unicode-ccc", + "unicode-properties", "unicode-script", ] @@ -1676,6 +1782,12 @@ dependencies = [ "libc", ] +[[package]] +name = "self_cell" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "58bf37232d3bb9a2c4e641ca2a11d83b5062066f88df7fed36c28772046d65ba" + [[package]] name = "serde" version = "1.0.197" @@ -1817,6 +1929,17 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6637bab7722d379c8b41ba849228d680cc12d0a45ba1fa2b48f2a30577a06731" +[[package]] +name = "swash" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d06ff4664af8923625604261c645f5c4cc610cc83c84bec74b50d76237089de7" +dependencies = [ + "read-fonts", + "yazi", + "zeno", +] + [[package]] name = "syn" version = "2.0.50" @@ -1834,6 +1957,15 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160" +[[package]] +name = "sys-locale" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e801cf239ecd6ccd71f03d270d67dd53d13e90aab208bf4b8fe4ad957ea949b0" +dependencies = [ + "libc", +] + [[package]] name = "system-configuration" version = "0.5.1" @@ -1857,13 +1989,14 @@ dependencies = [ [[package]] name = "taffy" -version = "0.3.18" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c2287b6d7f721ada4cddf61ade5e760b2c6207df041cac9bfaa192897362fd3" +checksum = "fddbee94e20bc4612dcb789953324236eebd4fc6a08c650ccbf7f615e59a0d6a" dependencies = [ "arrayvec", "grid", "num-traits", + "serde", "slotmap", ] @@ -2052,12 +2185,6 @@ version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" -[[package]] -name = "ttf-parser" -version = "0.18.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0609f771ad9c6155384897e1df4d948e692667cc0588548b68eb44d052b27633" - [[package]] name = "ttf-parser" version = "0.20.0" @@ -2082,18 +2209,18 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cc2520efa644f8268dce4dcd3050eaa7fc044fca03961e9998ac7e2e92b77cf1" -[[package]] -name = "unicode-general-category" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2281c8c1d221438e373249e065ca4989c4c36952c211ff21a0ee91c44a3869e7" - [[package]] name = "unicode-ident" version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" +[[package]] +name = "unicode-linebreak" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b09c83c3c29d37506a3e260c08c03743a6bb66a9cd432c6934ab501a190571f" + [[package]] name = "unicode-normalization" version = "0.1.23" @@ -2103,6 +2230,12 @@ dependencies = [ "tinyvec", ] +[[package]] +name = "unicode-properties" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4259d9d4425d9f0661581b804cb85fe66a4c631cadd8f490d1c13a35d5d9291" + [[package]] name = "unicode-script" version = "0.5.6" @@ -2730,6 +2863,18 @@ version = "0.8.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fcb9cbac069e033553e8bb871be2fbdffcab578eb25bd0f7c508cedc6dcd75a" +[[package]] +name = "yazi" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c94451ac9513335b5e23d7a8a2b61a7102398b8cca5160829d313e84c9d98be1" + +[[package]] +name = "zeno" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd15f8e0dbb966fd9245e7498c7e9e5055d9e5c8b676b95bd67091cd11a1e697" + [[package]] name = "zerocopy" version = "0.7.32" diff --git a/mangades/src/component_demo.rs b/mangades/src/component_demo.rs index da8ad00..8361923 100644 --- a/mangades/src/component_demo.rs +++ b/mangades/src/component_demo.rs @@ -33,8 +33,8 @@ impl Component for DemoComponent { style: Style { layout: mangui::nodes::TaffyStyle { min_size: Size { - width: mangui::taffy::style::Dimension::Points( if **test.lock().unwrap() { 50. } else { 100. }), - height: mangui::taffy::style::Dimension::Points(100.) + width: mangui::taffy::style::Dimension::Length( if **test.lock().unwrap() { 50. } else { 100. }), + height: mangui::taffy::style::Dimension::Length(100.) }, ..Default::default() }, diff --git a/mangades/src/component_demo_syntax.rs b/mangades/src/component_demo_syntax.rs index fa2e9ab..7d2ba05 100644 --- a/mangades/src/component_demo_syntax.rs +++ b/mangades/src/component_demo_syntax.rs @@ -1,6 +1,8 @@ use rusalka_macro::make_component; use std::default::Default; use mangui::{femtovg::{ImageFlags, Color, Paint}, cosmic_text::Metrics, nodes::{layout::Layout, Style}, nodes::text::Text, nodes::image::Image, taffy::prelude::Size}; +use mangui::nodes::TaffyStyle; +use mangui::taffy::Display::Block; use rusalka::nodes::primitives::{Rectangle, RectangleAttributes, PartialRectangleAttributes}; @@ -28,34 +30,32 @@ make_component!( radius: if $test_ { attrs.radius } else { 0. }, ..Default::default() } - @image { - style: Style { - layout: mangui::nodes::TaffyStyle { - min_size: Size { - width: mangui::taffy::style::Dimension::Points(width), - height: mangui::taffy::style::Dimension::Points(height) + @layout { + @image { + style: Style { + layout: TaffyStyle { + min_size: Size { + width: mangui::taffy::style::Dimension::Length(width), + height: mangui::taffy::style::Dimension::Length(height) + }, + ..Default::default() }, ..Default::default() }, + image: mangui::nodes::image::ImageLoad::LoadFile(imgpath, imgflags), + radius: 5., ..Default::default() - }, - image: mangui::nodes::image::ImageLoad::LoadFile(imgpath, imgflags), - width, - height, - radius: 5., - events: Default::default(), - parent: None - } - @text { - text: String::from("Hello, World!"), - metrics: Metrics::new(20., 25.), - paint: Paint::color(Color::rgb(0, 255, 0)), + } + @text { + text: String::from("Hello, World!"), + metrics: Metrics::new(20., 25.), + paint: Paint::color(Color::rgb(0, 255, 0)), + ..Default::default() + } + style: Style { - layout: mangui::nodes::TaffyStyle { - min_size: Size { - width: mangui::taffy::style::Dimension::Points(200.), - height: mangui::taffy::style::Dimension::Points(40.) - }, + layout: TaffyStyle { + display: Block, ..Default::default() }, ..Default::default() diff --git a/mangades/src/main.rs b/mangades/src/main.rs index 2aaaa63..a138b7f 100644 --- a/mangades/src/main.rs +++ b/mangades/src/main.rs @@ -4,6 +4,7 @@ use mangui::{self, nodes::{layout::Layout, self, Style, TaffyStyle}, taffy::{sel mod component_demo; mod component_demo_syntax; +mod anilist; use rusalka::component::Component; @@ -15,11 +16,10 @@ fn main() { root.style.layout.flex_direction = taffy::style::FlexDirection::Row; let right_node = Arc::new(RwLock::new(nodes::primitives::Rectangle { style: Style { - overflow: nodes::Overflow::Visible, layout: TaffyStyle { min_size: Size { - width: Dimension::Points(50.), - height: Dimension::Points(100.) + width: Dimension::Length(50.), + height: Dimension::Length(100.) }, ..Default::default() }, diff --git a/rusalka-macro/src/lib.rs b/rusalka-macro/src/lib.rs index c8cf986..b5407b9 100644 --- a/rusalka-macro/src/lib.rs +++ b/rusalka-macro/src/lib.rs @@ -791,6 +791,9 @@ pub fn make_component(item: TokenStream) -> TokenStream { let num_offset = index % 32; *keys.get_mut(array_offset).unwrap() |= 1 << num_offset; } + if keys.iter().all(|x| *x == 0) { + continue 'block; + } update_stream.extend(TokenStream::from(quote!(if))); let mut i = 0; diff --git a/rusalka/src/nodes/primitives.rs b/rusalka/src/nodes/primitives.rs index 90cf17b..b3c44af 100644 --- a/rusalka/src/nodes/primitives.rs +++ b/rusalka/src/nodes/primitives.rs @@ -4,7 +4,7 @@ use std::sync::{Arc, RwLock}; use mangui::{SharedNode, nodes::{primitives, Style}, taffy::prelude::Size, femtovg::{Paint, Color}}; -use crate::{component::Component, SharedComponent, WeakSharedComponent}; +use crate::{component::Component, WeakSharedComponent}; use super::{insert, detach}; @@ -43,8 +43,8 @@ impl Component for Rectangle { style: Style { layout: mangui::nodes::TaffyStyle { min_size: Size { - width: mangui::taffy::style::Dimension::Points(50.), - height: mangui::taffy::style::Dimension::Points(100.) + width: mangui::taffy::style::Dimension::Length(50.), + height: mangui::taffy::style::Dimension::Length(100.) }, ..Default::default() }, diff --git a/ui/Cargo.toml b/ui/Cargo.toml index c45f19e..f4313a8 100644 --- a/ui/Cargo.toml +++ b/ui/Cargo.toml @@ -6,10 +6,13 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -femtovg = "0.7.1" -glutin = "0.31.0" +femtovg = "0.8.2" +glutin = "0.31.3" raw-window-handle = "0.5.0" -winit = { version = "0.29.2" } +winit = { version = "0.29.10" } glutin-winit = "0.4.2" -taffy = "0.3.16" -weak-table = "0.3.2" \ No newline at end of file +taffy = "0.4.0" +weak-table = "0.3.2" +cosmic-text = "0.11.2" +swash = "0.1.12" +lazy_static = "1.4.0" \ No newline at end of file diff --git a/ui/src/events/mod.rs b/ui/src/events/mod.rs index b3c55fa..e43af5d 100644 --- a/ui/src/events/mod.rs +++ b/ui/src/events/mod.rs @@ -185,8 +185,8 @@ impl Into<(f32, f32)> for Location { impl Into> for Location { fn into(self) -> Size { Size { - width: Dimension::Points(self.x as f32), - height: Dimension::Points(self.y as f32) + width: Dimension::Length(self.x as f32), + height: Dimension::Length(self.y as f32) } } } diff --git a/ui/src/lib.rs b/ui/src/lib.rs index 03f7547..c3e20f9 100644 --- a/ui/src/lib.rs +++ b/ui/src/lib.rs @@ -26,10 +26,9 @@ use glutin::{ surface::{SurfaceAttributesBuilder, WindowSurface}, }; use taffy::geometry::Size; -use taffy::style_helpers::TaffyMaxContent; -use taffy::Taffy; +use taffy::{style::AvailableSpace, TaffyTree}; use weak_table::PtrWeakKeyHashMap; -use crate::nodes::{layout_recursively, Node, render_recursively, RenderContext}; +use crate::nodes::{update_taffynode_children, MeasureContext, Node, render_recursively, RenderContext, prepare_render_recursively}; pub mod nodes; pub mod events; @@ -41,7 +40,7 @@ pub type CurrentRenderer = OpenGl; pub type SharedNode = Arc>; type WeakNode = Weak>; type NodePtr = Option>; -type NodeLayoutMap = PtrWeakKeyHashMap>, taffy::node::Node>; +type NodeLayoutMap = PtrWeakKeyHashMap>, taffy::tree::NodeId>; lazy_static::lazy_static! { pub static ref FONT_SYSTEM: Mutex = Mutex::new(FontSystem::new()); @@ -77,7 +76,7 @@ pub fn run_event_loop(entry: MainEntry) -> () { let canvas = Canvas::new(renderer).expect("Cannot create canvas"); - let mut taffy = Taffy::new(); + let mut taffy = TaffyTree::new(); let mut taffy_map = NodeLayoutMap::new(); { let clonned = entry.root.clone(); @@ -89,13 +88,15 @@ pub fn run_event_loop(entry: MainEntry) -> () { taffy_map.insert(entry.root.clone(), taffy_root_node); } + let size = window.inner_size(); let mut context = RenderContext { canvas, node_layout: taffy_map, taffy, mouse: None, keyboard_focus: None, - scale_factor: window.scale_factor() as f32 + scale_factor: window.scale_factor() as f32, + window_size: Size { width: size.width as f32, height: size.height as f32 } }; let root = entry.root.clone(); @@ -250,7 +251,7 @@ pub fn run_event_loop(entry: MainEntry) -> () { }, WindowEvent::RedrawRequested => { if should_recompute { - layout_recursively(&root, &mut context); + update_taffynode_children(&root, &mut context); let src_nodes = context.node_layout.values().map(|v| v.to_owned()).collect::>(); context.node_layout.remove_expired(); let dst_nodes = context.node_layout.values().map(|v| v.to_owned()).collect::>(); @@ -260,12 +261,33 @@ pub fn run_event_loop(entry: MainEntry) -> () { dbg!("Removed node", src_node); } } + prepare_render_recursively(&root, &mut context); for (node, taffy_node) in context.node_layout.iter() { let node = node.read().unwrap(); let node_style = node.style(); context.taffy.set_style(*taffy_node, node_style.layout.to_owned()).unwrap(); } - context.taffy.compute_layout(*context.node_layout.get(&root).unwrap(), Size::MAX_CONTENT).unwrap(); + let size = window.inner_size(); + let size = Size { width: AvailableSpace::Definite(size.width as f32), height: AvailableSpace::Definite(size.height as f32) }; + let RenderContext { taffy, node_layout, canvas, scale_factor, .. } = &mut context; + let mut measure_context = MeasureContext { canvas, scale_factor: *scale_factor }; + taffy.compute_layout_with_measure( + *node_layout.get(&root).unwrap(), + size, + |known_dimensions, available_space, _node_id, node_context| { + match node_context { + Some(node) => { + match node.upgrade() { + Some(node) => { + node.write().unwrap().measure(&mut measure_context, known_dimensions, available_space) + }, + None => Size::ZERO + } + }, + None => Size::ZERO + } + }, + ).unwrap(); should_recompute = false; // Additional optimizations could be done here // - When setting styles, check that the styles aren't the same (taffy doesn't do that and instead always mark it as dirty) @@ -275,7 +297,7 @@ pub fn run_event_loop(entry: MainEntry) -> () { // could perhaps be a significant boost regarding memory usage (and performance) during large layout changes // dbg!("recomputed"); } - // dbg!(&root); + // Clear the render queue while let Ok(_) = entry.render.try_recv() {} render(&buffer_context, &surface, &window, &mut context, &root); } diff --git a/ui/src/nodes/image.rs b/ui/src/nodes/image.rs index 6cbb734..221c742 100644 --- a/ui/src/nodes/image.rs +++ b/ui/src/nodes/image.rs @@ -1,9 +1,11 @@ use std::{fmt::Debug, mem, path::PathBuf}; +use std::sync::Mutex; use femtovg::{Color, ErrorKind, ImageFlags, ImageId, Paint, Path}; +use taffy::{AvailableSpace, Size}; use crate::{events::handler::EventHandlerDatabase, SharedNode, WeakNode}; -use super::{Node, NodeChildren, Style}; +use super::{MeasureContext, Node, NodeChildren, RenderContext, Style}; -#[derive(Debug)] +#[derive(Debug, Default)] /// Status of the image - when rendering, image node attempts to load the image and sets this status accordingly. /// Changes this if you want to change the image. If the previous status was loaded, free the image. /// In case the loading fails, image load status changes to Error and the node doesn't render. @@ -11,27 +13,48 @@ pub enum ImageLoad { LoadFile(PathBuf, ImageFlags), // LoadArray(&[u8]), LoadVec(Vec, ImageFlags), - Loaded(ImageId), - Error(ErrorKind) + Loaded(ImageHandle), + Error(ErrorKind), + #[default] + Empty } #[derive(Debug)] +pub struct ImageHandle { + image: ImageId +} + +impl ImageHandle { + fn new(image: ImageId) -> ImageHandle { + ImageHandle { + image + } + } +} + +impl Drop for ImageHandle { + fn drop(&mut self) { + IMAGES_TO_UNLOAD.lock().unwrap().push(self.image); + } +} + +#[derive(Debug, Default)] /// Image node. /// Sadly doesn't implement `Default` because of the `ImageLoad` enum. pub struct Image { pub style: Style, /// The image to be rendered. pub image: ImageLoad, - /// Image width - note that you also have to set the style accordingly for it to render correctly, this is more about scaling the image - pub width: f32, - /// Image height - note that you also have to set the style accordingly for it to render correctly, this is more about scaling the image - pub height: f32, /// Border radius pub radius: f32, pub events: EventHandlerDatabase, pub parent: Option } +lazy_static::lazy_static! { + pub static ref IMAGES_TO_UNLOAD: Mutex> = Mutex::new(Vec::new()); +} + impl Node for Image { fn style(&self) -> &Style { &self.style @@ -41,14 +64,14 @@ impl Node for Image { None } - fn render_pre_children(&mut self, context: &mut super::RenderContext, layout: taffy::prelude::Layout) { + fn prepare_render(&mut self, context: &mut RenderContext) { match &self.image { ImageLoad::LoadFile(_, _) => { let image = mem::replace(&mut self.image, ImageLoad::Error(ErrorKind::UnknownError)); if let ImageLoad::LoadFile(path, flags) = image { match context.canvas.load_image_file(path, flags) { Ok(image) => { - self.image = ImageLoad::Loaded(image); + self.image = ImageLoad::Loaded(ImageHandle::new(image)); }, Err(e) => { self.image = ImageLoad::Error(e); @@ -59,7 +82,7 @@ impl Node for Image { ImageLoad::LoadVec(data, flags) => { match context.canvas.load_image_mem(data, *flags) { Ok(image) => { - self.image = ImageLoad::Loaded(image); + self.image = ImageLoad::Loaded(ImageHandle::new(image)); }, Err(e) => { self.image = ImageLoad::Error(e); @@ -68,6 +91,9 @@ impl Node for Image { }, _ => {} } + } + + fn render_pre_children(&mut self, context: &mut super::RenderContext, layout: taffy::prelude::Layout) { let mut path = Path::new(); path.rounded_rect( 0., @@ -80,13 +106,37 @@ impl Node for Image { ImageLoad::Loaded(image) => { context.canvas.fill_path( &path, - &Paint::image(*image, 0., 0., self.width, self.height, 0., 1.) + &Paint::image(image.image, 0., 0., layout.size.width, layout.size.height, 0., 1.) ); }, ImageLoad::Error(_) => { context.canvas.fill_path(&path, &Paint::color(Color::rgb(255, 0, 0))) }, - _ => unreachable!("We just loaded the image before, so it's either loaded or errored out.") + _ => { + // this shouldn't happen as the image should be loaded earlier during the render pass, + // but someone can still change the image in another thread + } + } + } + + fn measure(&mut self, context: &mut MeasureContext, known_dimensions: Size>, _available_space: Size) -> Size { + match &self.image { + ImageLoad::Loaded(image) => { + match context.canvas.image_size(image.image) { + Ok((img_width, img_height)) => { + let img_width = img_width as f32; + let img_height = img_height as f32; + match (known_dimensions.width, known_dimensions.height) { + (Some(width), Some(height)) => Size { width, height }, + (Some(width), None) => Size { width, height: (width / img_width) * img_height }, + (None, Some(height)) => Size { width: (height / img_height) * img_width, height }, + (None, None) => Size { width: img_width, height: img_height }, + } + }, + _ => Size::ZERO + } + }, + _ => Size::ZERO } } diff --git a/ui/src/nodes/layout.rs b/ui/src/nodes/layout.rs index a144540..50f9e69 100644 --- a/ui/src/nodes/layout.rs +++ b/ui/src/nodes/layout.rs @@ -39,8 +39,8 @@ impl Node for Layout { Some(&self.children) } fn resize(&mut self, width: f32, height: f32) { - self.style.layout.size.width = Dimension::Points(width); - self.style.layout.size.height = Dimension::Points(height); + self.style.layout.size.width = Dimension::Length(width); + self.style.layout.size.height = Dimension::Length(height); } fn add_child_at(&mut self, child: crate::SharedNode, index: usize) -> Result<(), super::ChildAddError> { diff --git a/ui/src/nodes/mod.rs b/ui/src/nodes/mod.rs index 30101f3..8fc3afc 100644 --- a/ui/src/nodes/mod.rs +++ b/ui/src/nodes/mod.rs @@ -7,22 +7,27 @@ pub mod text_render_cache; use std::fmt::Debug; use std::sync::Arc; use femtovg::{Canvas, Color}; -use taffy::layout::Layout; -use taffy::Taffy; use crate::events::Location; use crate::events::handler::InnerEventHandlerDataset; use crate::{NodeLayoutMap, NodePtr, CurrentRenderer, SharedNode, WeakNode}; pub use taffy::style::Style as TaffyStyle; +use taffy::{Layout, Overflow, Size, TaffyTree}; pub type CanvasRenderer = Canvas; pub struct RenderContext { pub canvas: CanvasRenderer, pub node_layout: NodeLayoutMap, - pub taffy: Taffy, + pub taffy: TaffyTree, pub mouse: NodePtr, pub keyboard_focus: NodePtr, + pub scale_factor: f32, + pub window_size: Size +} + +pub struct MeasureContext<'a> { + pub canvas: &'a mut CanvasRenderer, pub scale_factor: f32 } @@ -39,18 +44,6 @@ impl RenderContext { } } -#[derive(Copy, Clone, Default, Debug)] -#[non_exhaustive] -pub enum Overflow { - #[default] - /// Content is not clipped and may be rendered outside the element's box - Visible, - /// Clips the content at the border of the element - Hidden, - // tbd :) - // Scroll, - // Auto -} #[derive(Copy, Clone, Default, Debug)] pub enum Cursor { #[default] @@ -59,7 +52,6 @@ pub enum Cursor { #[derive(Clone, Default, Debug)] pub struct Style { pub layout: TaffyStyle, - pub overflow: Overflow, pub cursor: Cursor } @@ -95,9 +87,26 @@ pub enum ChildAddError { /// # Events /// /// If you need to handle events, implement [`Node::event_handlers`]. +/// +/// # Function call order +/// +/// Read-only functions are called in any order (style, children, event_handlers, measure). +/// +/// `resize` is called independently of the rendering process, but not concurrently with it. The render process is single-threaded. +/// +/// During rendering, the order is following: +/// +/// - parents are changed ([`Node::set_parent`]) to new state according to read-only functions +/// - [`Node::prepare_render`] is called on each node +/// - [`Node::style`] is read +/// - [`Node::measure`] is called on some nodes (depends on taffy); can be called multiple times +/// - nodes are rendered, i.e. on each node, starting from the root node, the following is called: +/// - [`Node::render_pre_children`] is called +/// - children are rendered +/// - [`Node::render_post_children`] is called pub trait Node: Debug { /// Return style. - /// + ///insert /// If you're using [`Style`] in your struct directly, your implementation can be as simple as: /// ```rust /// fn style(&self) -> &Style { &self.style } @@ -219,6 +228,22 @@ pub trait Node: Debug { None } + /// Called on each redraw. Use this to prepare for rendering. Called before any layouting or rendering happens. + /// Order between nodes is not guaranteed. + fn prepare_render(&mut self, _context: &mut RenderContext) {} + + /// Called before rendering the node to measure it's size. + /// The calling of this method is managed by taffy, and as such: + /// - It may be called multiple times (with same or different arguments) during the same render pass + /// - It may not be called at all + /// - order between nodes is not guaranteed + /// + /// If you need to change self during layouting, use [`Node::prepare_render`] to do so. + /// You're getting &mut self here to support things like cosmic text that require changing text data to measure it. + fn measure(&mut self, _context: &mut MeasureContext, _known_dimensions: Size>, _available_space: Size) -> Size { + Size::ZERO + } + /// Returns true if the node has the given child /// Returns false if there are no children (or if the node does not support children) fn has_child(&self, child: &SharedNode) -> Option { @@ -295,12 +320,15 @@ pub(crate) fn get_element_at(node: &SharedNode, context: &RenderContext, locatio } } -pub(crate) fn layout_recursively(node: &SharedNode, context: &mut RenderContext) -> taffy::node::Node { +pub(crate) fn update_taffynode_children(node: &SharedNode, context: &mut RenderContext) -> taffy::tree::NodeId { let taffy_node = context.node_layout.get(node); let taffy_node = match taffy_node { Some(taffy_node) => taffy_node, None => { - let taffy_node = context.taffy.new_leaf(node.read().unwrap().style().layout.to_owned()).unwrap(); + let taffy_node = context.taffy.new_leaf_with_context( + node.read().unwrap().style().layout.to_owned(), + Arc::downgrade(node) + ).unwrap(); context.node_layout.insert(node.clone(), taffy_node); context.node_layout.get(node).unwrap() } @@ -313,7 +341,7 @@ pub(crate) fn layout_recursively(node: &SharedNode, context: &mut RenderContext) Some(children) => { let mut t_children = Vec::with_capacity(children.len()); for child in children { - t_children.push(layout_recursively(child, context).to_owned()); + t_children.push(update_taffynode_children(child, context).to_owned()); child.write().unwrap().set_parent(Some(Arc::downgrade(node))); } context.taffy.set_children(taffy_node, t_children.as_slice()).unwrap(); @@ -331,16 +359,15 @@ pub(crate) fn render_recursively(node: &SharedNode, context: &mut RenderContext) let sself = node.clone(); context.canvas.save(); context.canvas.translate(layout.location.x, layout.location.y); - match styles.overflow { - Overflow::Visible => {}, - Overflow::Hidden => { - context.canvas.scissor( - 0., - 0., - layout.size.width, - layout.size.height, - ); - } + let clip_width = matches!(styles.layout.overflow.x, Overflow::Hidden | Overflow::Clip); + let clip_height = matches!(styles.layout.overflow.y, Overflow::Hidden | Overflow::Clip); + if clip_width || clip_height { + context.canvas.scissor( + 0., + 0., + if clip_width { layout.size.width } else { f32::INFINITY }, + if clip_height { layout.size.height } else { f32::INFINITY }, + ); } drop(read_node); sself.write().unwrap().render_pre_children(context, layout); @@ -351,4 +378,14 @@ pub(crate) fn render_recursively(node: &SharedNode, context: &mut RenderContext) } sself.write().unwrap().render_post_children(context, layout); context.canvas.restore(); +} + +pub(crate) fn prepare_render_recursively(node: &SharedNode, context: &mut RenderContext) { + let mut write_node = node.write().unwrap(); + write_node.prepare_render(context); + if let Some(children) = write_node.children() { + for child in children { + prepare_render_recursively(child, context); + } + } } \ No newline at end of file diff --git a/ui/src/nodes/primitives.rs b/ui/src/nodes/primitives.rs index 3886477..9e8310f 100644 --- a/ui/src/nodes/primitives.rs +++ b/ui/src/nodes/primitives.rs @@ -1,5 +1,5 @@ use femtovg::{Color, Paint, Path}; -use taffy::layout::Layout; +use taffy::Layout; use crate::{nodes::{Node, NodeChildren, RenderContext, Style}, events::handler::EventHandlerDatabase, WeakNode, SharedNode}; #[derive(Default, Debug)] diff --git a/ui/src/nodes/text.rs b/ui/src/nodes/text.rs index 9654fa9..83688d8 100644 --- a/ui/src/nodes/text.rs +++ b/ui/src/nodes/text.rs @@ -1,8 +1,9 @@ use std::fmt::Debug; use crate::{events::handler::EventHandlerDatabase, SharedNode, WeakNode, FONT_SYSTEM}; -use super::{text_render_cache::RENDER_CACHE, Node, NodeChildren, Style}; +use super::{text_render_cache::RENDER_CACHE, Node, NodeChildren, Style, MeasureContext, RenderContext}; use cosmic_text::{Attrs, Buffer, Metrics, Shaping}; -use femtovg::Paint; +use femtovg::{Color, Paint, Path}; +use taffy::{AvailableSpace, Size}; #[derive(Debug, Default)] pub struct Text { @@ -24,22 +25,66 @@ impl Node for Text { None } - fn render_pre_children(&mut self, context: &mut super::RenderContext, layout: taffy::prelude::Layout) { + fn prepare_render(&mut self, _context: &mut RenderContext) { if let None = self.buffer { self.buffer = Some(Buffer::new(&mut FONT_SYSTEM.lock().unwrap(), self.metrics)); } let buf = self.buffer.as_mut().unwrap(); let mut font = FONT_SYSTEM.lock().unwrap(); buf.set_text(&mut font, &self.text, Attrs::new(), Shaping::Advanced); + } + + fn render_pre_children(&mut self, context: &mut super::RenderContext, layout: taffy::prelude::Layout) { + // this can crash, but it should crash earlier during measure -> see the comment there. + let buf = self.buffer.as_mut().unwrap(); + let mut font = FONT_SYSTEM.lock().unwrap(); buf.set_size(&mut font, layout.size.width, layout.size.height); buf.set_metrics(&mut font, self.metrics.scale(context.scale_factor)); + // fill_to_cmds requires FONT_SYSTEM lock. drop(font); + let mut path = Path::new(); + path.rounded_rect( + 0., + 0., + layout.size.width, + layout.size.height, + 0. + ); + context.canvas.fill_path( + &path, + &Paint::color(Color::rgb(255, 0, 0)) + ); let cmds = RENDER_CACHE.lock().unwrap() .fill_to_cmds(&mut context.canvas, buf, (0.0, 0.0), context.scale_factor) .unwrap(); context.canvas.draw_glyph_commands(cmds, &self.paint, 1.0); } + fn measure(&mut self, _context: &mut MeasureContext, known_dimensions: Size>, available_space: Size) -> Size { + let width_constraint = known_dimensions.width.unwrap_or(match available_space.width { + AvailableSpace::MinContent => 0.0, + AvailableSpace::MaxContent => f32::INFINITY, + AvailableSpace::Definite(width) => width, + }); + // yes, this can crash if someone removes `buffer` during render from another thread. + // though they're asking for it, so let them crash. + let buf = self.buffer.as_mut().unwrap(); + buf.set_size(&mut FONT_SYSTEM.lock().unwrap(), width_constraint, f32::INFINITY); + + // Compute layout + buf.shape_until_scroll(&mut FONT_SYSTEM.lock().unwrap(), false); + + // Determine measured size of text + let (width, total_lines) = buf + .layout_runs() + .fold((0.0, 0usize), |(width, total_lines), run| (run.line_w.max(width), total_lines + 1)); + let height = total_lines as f32 * buf.metrics().line_height; + // fixes flickering of text on devices with non-integer scale factors due to loss of precision + let width = width + 0.5; + + Size { width, height } + } + fn event_handlers(&self) -> Option { Some(self.events.handlers.clone()) } diff --git a/ui/src/nodes/text_render_cache.rs b/ui/src/nodes/text_render_cache.rs index 6cb30dd..58a515a 100644 --- a/ui/src/nodes/text_render_cache.rs +++ b/ui/src/nodes/text_render_cache.rs @@ -49,6 +49,8 @@ lazy_static::lazy_static! { } impl RenderCache { + /// Generates draw commands from cosmic text buffer. + /// Note that this requires a lock on FONT_SYSTEM. pub(crate) fn fill_to_cmds( &mut self, canvas: &mut CanvasRenderer,