improved text and image support, support measuring sizes for taffy

This commit is contained in:
Daniel Bulant 2024-02-24 17:48:08 +01:00
parent f65588ec19
commit 389b147bfb
15 changed files with 435 additions and 128 deletions

209
Cargo.lock generated
View file

@ -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"

View file

@ -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()
},

View file

@ -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()

View file

@ -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()
},

View file

@ -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;

View file

@ -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()
},

View file

@ -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"
taffy = "0.4.0"
weak-table = "0.3.2"
cosmic-text = "0.11.2"
swash = "0.1.12"
lazy_static = "1.4.0"

View file

@ -185,8 +185,8 @@ impl Into<(f32, f32)> for Location {
impl Into<Size<Dimension>> for Location {
fn into(self) -> Size<Dimension> {
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)
}
}
}

View file

@ -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<RwLock<dyn Node>>;
type WeakNode = Weak<RwLock<dyn Node>>;
type NodePtr = Option<Vec<WeakNode>>;
type NodeLayoutMap = PtrWeakKeyHashMap<Weak<RwLock<dyn Node>>, taffy::node::Node>;
type NodeLayoutMap = PtrWeakKeyHashMap<Weak<RwLock<dyn Node>>, taffy::tree::NodeId>;
lazy_static::lazy_static! {
pub static ref FONT_SYSTEM: Mutex<FontSystem> = 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::<Vec<_>>();
context.node_layout.remove_expired();
let dst_nodes = context.node_layout.values().map(|v| v.to_owned()).collect::<Vec<_>>();
@ -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);
}

View file

@ -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<u8>, 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<WeakNode>
}
lazy_static::lazy_static! {
pub static ref IMAGES_TO_UNLOAD: Mutex<Vec<ImageId>> = 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<Option<f32>>, _available_space: Size<AvailableSpace>) -> Size<f32> {
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
}
}

View file

@ -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> {

View file

@ -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<CurrentRenderer>;
pub struct RenderContext {
pub canvas: CanvasRenderer,
pub node_layout: NodeLayoutMap,
pub taffy: Taffy,
pub taffy: TaffyTree<WeakNode>,
pub mouse: NodePtr,
pub keyboard_focus: NodePtr,
pub scale_factor: f32,
pub window_size: Size<f32>
}
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<Option<f32>>, _available_space: Size<taffy::AvailableSpace>) -> Size<f32> {
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<usize> {
@ -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);
}
}
}

View file

@ -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)]

View file

@ -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<Option<f32>>, available_space: Size<AvailableSpace>) -> Size<f32> {
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<crate::events::handler::InnerEventHandlerDataset> {
Some(self.events.handlers.clone())
}

View file

@ -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,