From 65e9da5102c2f85dda955eb8ff676b72810963ae Mon Sep 17 00:00:00 2001 From: Marc Espin Date: Tue, 27 Aug 2024 18:32:07 +0200 Subject: [PATCH 01/12] feat: Ignore unnecessary dioxus vdom mutations (#821) --- crates/core/src/dom/mutations_writer.rs | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/crates/core/src/dom/mutations_writer.rs b/crates/core/src/dom/mutations_writer.rs index 4a35dadec..0fb55b6ca 100644 --- a/crates/core/src/dom/mutations_writer.rs +++ b/crates/core/src/dom/mutations_writer.rs @@ -115,21 +115,26 @@ impl<'a> WriteMutations for MutationsWriter<'a> { fn replace_node_with(&mut self, id: dioxus_core::ElementId, m: usize) { if m > 0 { self.remove(id); + self.native_writer.replace_node_with(id, m); } - - self.native_writer.replace_node_with(id, m); } fn replace_placeholder_with_nodes(&mut self, path: &'static [u8], m: usize) { - self.native_writer.replace_placeholder_with_nodes(path, m); + if m > 0 { + self.native_writer.replace_placeholder_with_nodes(path, m); + } } fn insert_nodes_after(&mut self, id: dioxus_core::ElementId, m: usize) { - self.native_writer.insert_nodes_after(id, m); + if m > 0 { + self.native_writer.insert_nodes_after(id, m); + } } fn insert_nodes_before(&mut self, id: dioxus_core::ElementId, m: usize) { - self.native_writer.insert_nodes_before(id, m); + if m > 0 { + self.native_writer.insert_nodes_before(id, m); + } } fn set_attribute( From 2bdd1a10dd0802842e959447d9136913b81160b3 Mon Sep 17 00:00:00 2001 From: Marc Espin Date: Tue, 27 Aug 2024 18:32:23 +0200 Subject: [PATCH 02/12] chore: Rust 1.80.1 (#818) --- rust-toolchain.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rust-toolchain.toml b/rust-toolchain.toml index e1b7e9508..94cbe48dd 100644 --- a/rust-toolchain.toml +++ b/rust-toolchain.toml @@ -1,3 +1,3 @@ [toolchain] -channel = "1.79.0" +channel = "1.80.1" profile = "default" \ No newline at end of file From a85ac707e4bb6d60d52814972affaa829135e9a4 Mon Sep 17 00:00:00 2001 From: marc2332 Date: Fri, 30 Aug 2024 18:26:23 +0200 Subject: [PATCH 03/12] chore: Rename `Contribute` button to `Source Code` --- website/src/pages/index.astro | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/src/pages/index.astro b/website/src/pages/index.astro index e6cf2152a..ee38c08a3 100644 --- a/website/src/pages/index.astro +++ b/website/src/pages/index.astro @@ -48,7 +48,7 @@ fn app() -> Element {

Build native & cross-platform GUI applications using 🦀 Rust.
Powered by 🧬 Dioxus and 🎨 Skia.


Get Started - Contribute + Source Code Sponsor From cfd30359346ed787bd6944024e5c48f50be19d69 Mon Sep 17 00:00:00 2001 From: marc2332 Date: Fri, 30 Aug 2024 18:31:50 +0200 Subject: [PATCH 04/12] chore: Button example --- examples/button.rs | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 examples/button.rs diff --git a/examples/button.rs b/examples/button.rs new file mode 100644 index 000000000..00861d214 --- /dev/null +++ b/examples/button.rs @@ -0,0 +1,26 @@ +#![cfg_attr( + all(not(debug_assertions), target_os = "windows"), + windows_subsystem = "windows" +)] + +use freya::prelude::*; + +fn main() { + launch_with_props(app, "Button", (400.0, 350.0)); +} + +fn app() -> Element { + rsx!( + Button { + onclick: move |_| println!("Button Clicked!"), + label { "Button A" } + } + Button { + onpress: move |_| println!("Button Pressed!"), + label { "Button B" } + } + Button { + label { "Button C" } + } + ) +} From a30caa6abb1933dadf6b342396fcbf11652b425e Mon Sep 17 00:00:00 2001 From: Marc Espin Date: Fri, 30 Aug 2024 18:37:39 +0200 Subject: [PATCH 05/12] fix: Force the dropdown items vertically (#827) --- crates/components/src/dropdown.rs | 85 ++++++++++++++++--------------- 1 file changed, 44 insertions(+), 41 deletions(-) diff --git a/crates/components/src/dropdown.rs b/crates/components/src/dropdown.rs index ecbc58d17..6218958b6 100644 --- a/crates/components/src/dropdown.rs +++ b/crates/components/src/dropdown.rs @@ -259,48 +259,51 @@ where rsx!( rect { - onmouseenter, - onmouseleave, - onclick, - onkeydown, - margin: "4", - focus_id, - background: "{button_background}", - color: "{font_theme.color}", - corner_radius: "8", - padding: "8 16", - border: "1 solid {border_fill}", - shadow: "0 4 5 0 rgb(0, 0, 0, 0.1)", - direction: "horizontal", - main_align: "center", - cross_align: "center", - label { - "{selected}" - } - ArrowIcon { - rotate: "0", - fill: "{arrow_fill}", - theme: theme_with!(IconTheme { - margin : "0 0 0 8".into(), - }) - } - } - if *opened.read() { + direction: "vertical", rect { - height: "0", + onmouseenter, + onmouseleave, + onclick, + onkeydown, + margin: "4", + focus_id, + background: "{button_background}", + color: "{font_theme.color}", + corner_radius: "8", + padding: "8 16", + border: "1 solid {border_fill}", + shadow: "0 4 5 0 rgb(0, 0, 0, 0.1)", + direction: "horizontal", + main_align: "center", + cross_align: "center", + label { + "{selected}" + } + ArrowIcon { + rotate: "0", + fill: "{arrow_fill}", + theme: theme_with!(IconTheme { + margin : "0 0 0 8".into(), + }) + } + } + if *opened.read() { rect { - onglobalclick, - onkeydown, - layer: "-99", - margin: "4", - border: "1 solid {border_fill}", - overflow: "clip", - corner_radius: "8", - background: "{dropdown_background}", - shadow: "0 4 5 0 rgb(0, 0, 0, 0.3)", - padding: "6", - content: "fit", - {props.children} + height: "0", + rect { + onglobalclick, + onkeydown, + layer: "-99", + margin: "4", + border: "1 solid {border_fill}", + overflow: "clip", + corner_radius: "8", + background: "{dropdown_background}", + shadow: "0 4 5 0 rgb(0, 0, 0, 0.3)", + padding: "6", + content: "fit", + {props.children} + } } } } @@ -344,7 +347,7 @@ mod test { let mut utils = launch_test(dropdown_app); let root = utils.root(); - let label = root.get(0).get(0); + let label = root.get(0).get(0).get(0); utils.wait_for_update().await; // Currently closed From 84a9b4ee2489e0a62d434bf0aeb2c781b5d6f06b Mon Sep 17 00:00:00 2001 From: marc2332 Date: Fri, 30 Aug 2024 18:47:12 +0200 Subject: [PATCH 06/12] feat: Dropdown layout improvements and new width theme option --- crates/components/src/dropdown.rs | 30 +++++++++++++---------- crates/hooks/src/theming/dark.rs | 1 + crates/hooks/src/theming/light.rs | 1 + crates/hooks/src/theming/mod.rs | 1 + examples/dropdown.rs | 40 +++++++++++++++++++++++-------- 5 files changed, 51 insertions(+), 22 deletions(-) diff --git a/crates/components/src/dropdown.rs b/crates/components/src/dropdown.rs index 6218958b6..6bd2aa8af 100644 --- a/crates/components/src/dropdown.rs +++ b/crates/components/src/dropdown.rs @@ -242,6 +242,7 @@ where }; let DropdownTheme { + width, font_theme, dropdown_background, background_button, @@ -261,6 +262,7 @@ where rect { direction: "vertical", rect { + width: "{width}", onmouseenter, onmouseleave, onclick, @@ -290,19 +292,23 @@ where if *opened.read() { rect { height: "0", + width: "0", rect { - onglobalclick, - onkeydown, - layer: "-99", - margin: "4", - border: "1 solid {border_fill}", - overflow: "clip", - corner_radius: "8", - background: "{dropdown_background}", - shadow: "0 4 5 0 rgb(0, 0, 0, 0.3)", - padding: "6", - content: "fit", - {props.children} + width: "100v", + rect { + onglobalclick, + onkeydown, + layer: "-99", + margin: "4", + border: "1 solid {border_fill}", + overflow: "clip", + corner_radius: "8", + background: "{dropdown_background}", + shadow: "0 4 5 0 rgb(0, 0, 0, 0.3)", + padding: "6", + content: "fit", + {props.children} + } } } } diff --git a/crates/hooks/src/theming/dark.rs b/crates/hooks/src/theming/dark.rs index 0187d6acf..97b9288fa 100644 --- a/crates/hooks/src/theming/dark.rs +++ b/crates/hooks/src/theming/dark.rs @@ -72,6 +72,7 @@ pub const DARK_THEME: Theme = Theme { border_fill: cow_borrowed!("rgb(80, 80, 80)"), }, dropdown: DropdownTheme { + width: LIGHT_THEME.dropdown.width, dropdown_background: cow_borrowed!("rgb(25, 25, 25)"), background_button: cow_borrowed!("rgb(35, 35, 35)"), hover_background: cow_borrowed!("rgb(45, 45, 45)"), diff --git a/crates/hooks/src/theming/light.rs b/crates/hooks/src/theming/light.rs index 40e40984c..f19bc32c1 100644 --- a/crates/hooks/src/theming/light.rs +++ b/crates/hooks/src/theming/light.rs @@ -72,6 +72,7 @@ pub const LIGHT_THEME: Theme = Theme { border_fill: cow_borrowed!("rgb(210, 210, 210)"), }, dropdown: DropdownTheme { + width: cow_borrowed!("auto"), dropdown_background: cow_borrowed!("white"), background_button: cow_borrowed!("rgb(245, 245, 245)"), hover_background: cow_borrowed!("rgb(235, 235, 235)"), diff --git a/crates/hooks/src/theming/mod.rs b/crates/hooks/src/theming/mod.rs index d8a224a7b..6fe924c38 100644 --- a/crates/hooks/src/theming/mod.rs +++ b/crates/hooks/src/theming/mod.rs @@ -224,6 +224,7 @@ define_theme! { %[component] pub Dropdown { %[cows] + width: str, dropdown_background: str, background_button: str, hover_background: str, diff --git a/examples/dropdown.rs b/examples/dropdown.rs index 724549706..2ee37d271 100644 --- a/examples/dropdown.rs +++ b/examples/dropdown.rs @@ -20,16 +20,36 @@ fn app() -> Element { let mut selected_dropdown = use_signal(|| "First Option".to_string()); rsx!( - Dropdown { - value: selected_dropdown.read().clone(), - for ch in values { - DropdownItem { - value: ch.clone(), - onclick: { - to_owned![ch]; - move |_| selected_dropdown.set(ch.clone()) - }, - label { "{ch}" } + rect { + direction: "horizontal", + Dropdown { + theme: theme_with!(DropdownTheme { + width: "200".into(), + arrow_fill: "rgb(0, 119, 182)".into() + }), + value: selected_dropdown.read().clone(), + for ch in values.iter() { + DropdownItem { + value: ch.clone(), + onclick: { + to_owned![ch]; + move |_| selected_dropdown.set(ch.clone()) + }, + label { "Custom {ch}" } + } + } + } + Dropdown { + value: selected_dropdown.read().clone(), + for ch in values { + DropdownItem { + value: ch.clone(), + onclick: { + to_owned![ch]; + move |_| selected_dropdown.set(ch.clone()) + }, + label { "{ch}" } + } } } } From 34062a472ba6b59639fc893b51d6a0541f3cf4ea Mon Sep 17 00:00:00 2001 From: marc2332 Date: Fri, 30 Aug 2024 18:53:46 +0200 Subject: [PATCH 07/12] chore: Updated mouse example --- examples/mouse.rs | 56 ++++++++++++++--------------------------------- 1 file changed, 17 insertions(+), 39 deletions(-) diff --git a/examples/mouse.rs b/examples/mouse.rs index e091b8dcb..18f3e1db1 100644 --- a/examples/mouse.rs +++ b/examples/mouse.rs @@ -10,55 +10,33 @@ fn main() { } fn app() -> Element { - rsx!( - rect { - color: "white", - height: "100%", - width: "100%", - Area { - - } - Area { - - } - } - ) -} - -#[allow(non_snake_case)] -fn Area() -> Element { - let mut cursor_pos_over = use_signal(|| (0f64, 0f64)); - let mut cursor_pos_click = use_signal(|| (0f64, 0f64)); + let mut cursor_pos_over = use_signal(|| CursorPoint::default()); + let mut cursor_pos_click = use_signal(|| CursorPoint::default()); - let cursor_moved = move |e: MouseEvent| { - let pos = e.get_screen_coordinates(); - cursor_pos_over.with_mut(|cursor_pos| { - cursor_pos.0 = pos.x; - cursor_pos.1 = pos.y; - }) + let onmouseover = move |e: MouseEvent| { + let cursor_pos = e.get_screen_coordinates(); + cursor_pos_over.set(cursor_pos); }; - let cursor_clicked = move |e: MouseEvent| { - let pos = e.get_screen_coordinates(); - cursor_pos_click.with_mut(|cursor_pos| { - cursor_pos.0 = pos.x; - cursor_pos.1 = pos.y; - }) + let onclick = move |e: MouseEvent| { + let cursor_pos = e.get_screen_coordinates(); + cursor_pos_click.set(cursor_pos); }; rsx!( rect { - height: "50%", - width: "100%", - background: "blue", - padding: "5", - onmouseover: cursor_moved, - onclick: cursor_clicked, + height: "fill", + width: "fill", + background: "rgb(0, 119, 182)", + color: "white", + padding: "15", + onmouseover, + onclick, label { - "Mouse is at [x: {cursor_pos_over.read().0}, y: {cursor_pos_over.read().1}] ", + "Mouse is at [x: {cursor_pos_over.read().x}, y: {cursor_pos_over.read().y}] ", }, label { - "Mouse clicked at [x: {cursor_pos_click.read().0}, y: {cursor_pos_click.read().1}]" + "Mouse clicked at [x: {cursor_pos_click.read().x}, y: {cursor_pos_click.read().y}]" } } ) From d87a9320cc775a1627b8a98dbd52d33ad93b0ffd Mon Sep 17 00:00:00 2001 From: Yevhenii Chekanskyi Date: Fri, 30 Aug 2024 20:20:04 +0300 Subject: [PATCH 08/12] Add opengl_rtt example. (#813) * Use CanvasRunnerContext instead of arguments in CanvasRunner * Implement opengl_rtt example. * opengl_rtt caches image. * automatically resize texture. * final polishing of example. * fix test --doc --- Cargo.toml | 1 + crates/components/src/graph.rs | 188 +++++---- crates/core/src/elements/rect.rs | 9 +- crates/hooks/src/use_canvas.rs | 4 +- crates/renderer/src/window_state.rs | 2 +- crates/state/src/custom_attributes.rs | 9 +- examples/opengl_rtt.rs | 580 ++++++++++++++++++++++++++ examples/render_canvas.rs | 15 +- examples/shader.rs | 14 +- examples/shader_editor.rs | 24 +- 10 files changed, 720 insertions(+), 126 deletions(-) create mode 100644 examples/opengl_rtt.rs diff --git a/Cargo.toml b/Cargo.toml index ba59fa10f..729b40ac2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -87,6 +87,7 @@ dioxus-router = { workspace = true } itertools = "0.13.0" home = "0.5.9" dioxus-query = "0.5.1" +gl = { workspace = true } [profile.release] lto = true diff --git a/crates/components/src/graph.rs b/crates/components/src/graph.rs index e73f4c2ac..7d3e55a95 100644 --- a/crates/components/src/graph.rs +++ b/crates/components/src/graph.rs @@ -10,9 +10,9 @@ use freya_hooks::{ }; use freya_node_state::{ CanvasRunner, + CanvasRunnerContext, Parse, }; -use torin::prelude::Area; /// Data line for the [`Graph`] component. #[derive(Debug, PartialEq, Clone)] @@ -52,111 +52,109 @@ pub fn Graph(props: GraphProps) -> Element { })); let canvas = use_canvas_with_deps(&props, |props| { - Box::new( - move |canvas: &Canvas, font_collection: &mut FontCollection, region: Area, _: f32| { - canvas.translate((region.min_x(), region.min_y())); - - let mut paragraph_style = ParagraphStyle::default(); - paragraph_style.set_text_align(TextAlign::Center); - - let mut text_style = TextStyle::new(); - text_style.set_color(Color::BLACK); - paragraph_style.set_text_style(&text_style); - - let x_labels = &props.labels; - let x_height: f32 = 50.0; - - let start_x = region.min_x(); - let start_y = region.height() - x_height; - let height = region.height() - x_height; - - let space_x = region.width() / x_labels.len() as f32; - - // Calculate the smallest and biggest items - let (smallest_y, biggest_y) = { - let mut smallest_y = 0; - let mut biggest_y = 0; - for line in props.data.iter() { - let max = line.points.iter().max().unwrap(); - let min = line.points.iter().min().unwrap(); - - if let Some(max) = *max { - if max > biggest_y { - biggest_y = max; - } + Box::new(move |ctx: &mut CanvasRunnerContext| { + ctx.canvas.translate((ctx.area.min_x(), ctx.area.min_y())); + + let mut paragraph_style = ParagraphStyle::default(); + paragraph_style.set_text_align(TextAlign::Center); + + let mut text_style = TextStyle::new(); + text_style.set_color(Color::BLACK); + paragraph_style.set_text_style(&text_style); + + let x_labels = &props.labels; + let x_height: f32 = 50.0; + + let start_x = ctx.area.min_x(); + let start_y = ctx.area.height() - x_height; + let height = ctx.area.height() - x_height; + + let space_x = ctx.area.width() / x_labels.len() as f32; + + // Calculate the smallest and biggest items + let (smallest_y, biggest_y) = { + let mut smallest_y = 0; + let mut biggest_y = 0; + for line in props.data.iter() { + let max = line.points.iter().max().unwrap(); + let min = line.points.iter().min().unwrap(); + + if let Some(max) = *max { + if max > biggest_y { + biggest_y = max; } - if let Some(min) = *min { - if min < smallest_y { - smallest_y = min; - } + } + if let Some(min) = *min { + if min < smallest_y { + smallest_y = min; } } + } - (smallest_y, biggest_y) - }; - - // Difference between the smalles and biggest Y Axis item - let y_axis_len = biggest_y - smallest_y; - // Space between items in the Y axis - let space_y = height / y_axis_len as f32; - - // Draw the lines - for line in &props.data { - let mut paint = Paint::default(); - - paint.set_anti_alias(true); - paint.set_style(PaintStyle::Fill); - paint.set_color(Color::parse(&line.color).unwrap()); - paint.set_stroke_width(3.0); - - let mut previous_x = None; - let mut previous_y = None; - - for (i, y_point) in line.points.iter().enumerate() { - let line_x = (space_x * i as f32) + start_x + (space_x / 2.0); - // Save the position where the last point drawed - let new_previous_x = previous_x.unwrap_or(line_x); - - if let Some(y_point) = y_point { - let line_y = start_y - (space_y * ((y_point - smallest_y) as f32)); - let new_previous_y = previous_y.unwrap_or(line_y); - - // Draw the line and circle - canvas.draw_circle((line_x, line_y), 5.0, &paint); - canvas.draw_line( - (new_previous_x, new_previous_y), - (line_x, line_y), - &paint, - ); - - previous_y = Some(line_y); - previous_x = Some(line_x); - } else { - previous_y = None; - previous_x = None; - } + (smallest_y, biggest_y) + }; + + // Difference between the smalles and biggest Y Axis item + let y_axis_len = biggest_y - smallest_y; + // Space between items in the Y axis + let space_y = height / y_axis_len as f32; + + // Draw the lines + for line in &props.data { + let mut paint = Paint::default(); + + paint.set_anti_alias(true); + paint.set_style(PaintStyle::Fill); + paint.set_color(Color::parse(&line.color).unwrap()); + paint.set_stroke_width(3.0); + + let mut previous_x = None; + let mut previous_y = None; + + for (i, y_point) in line.points.iter().enumerate() { + let line_x = (space_x * i as f32) + start_x + (space_x / 2.0); + // Save the position where the last point drawed + let new_previous_x = previous_x.unwrap_or(line_x); + + if let Some(y_point) = y_point { + let line_y = start_y - (space_y * ((y_point - smallest_y) as f32)); + let new_previous_y = previous_y.unwrap_or(line_y); + + // Draw the line and circle + ctx.canvas.draw_circle((line_x, line_y), 5.0, &paint); + ctx.canvas.draw_line( + (new_previous_x, new_previous_y), + (line_x, line_y), + &paint, + ); + + previous_y = Some(line_y); + previous_x = Some(line_x); + } else { + previous_y = None; + previous_x = None; } } + } - // Space between labels - let space_x = region.width() / x_labels.len() as f32; + // Space between labels + let space_x = ctx.area.width() / x_labels.len() as f32; - // Draw the labels - for (i, point) in x_labels.iter().enumerate() { - let x = (space_x * i as f32) + start_x; + // Draw the labels + for (i, point) in x_labels.iter().enumerate() { + let x = (space_x * i as f32) + start_x; - let mut paragrap_builder = - ParagraphBuilder::new(¶graph_style, font_collection.clone()); - paragrap_builder.add_text(point); - let mut text = paragrap_builder.build(); + let mut paragrap_builder = + ParagraphBuilder::new(¶graph_style, ctx.font_collection.clone()); + paragrap_builder.add_text(point); + let mut text = paragrap_builder.build(); - text.layout(space_x); - text.paint(canvas, (x, start_y + x_height - 30.0)); - } + text.layout(space_x); + text.paint(ctx.canvas, (x, start_y + x_height - 30.0)); + } - canvas.restore(); - }, - ) as Box + ctx.canvas.restore(); + }) as Box }); rsx!( diff --git a/crates/core/src/elements/rect.rs b/crates/core/src/elements/rect.rs index 64761b18c..bcd831d40 100644 --- a/crates/core/src/elements/rect.rs +++ b/crates/core/src/elements/rect.rs @@ -3,6 +3,7 @@ use freya_native_core::real_dom::NodeImmutable; use freya_node_state::{ BorderAlignment, BorderStyle, + CanvasRunnerContext, Fill, ReferencesState, ShadowPosition, @@ -262,7 +263,13 @@ impl ElementUtils for RectElement { let references = node_ref.get::().unwrap(); if let Some(canvas_ref) = &references.canvas_ref { - (canvas_ref.runner)(canvas, font_collection, area, scale_factor); + let mut ctx = CanvasRunnerContext { + canvas, + font_collection, + area, + scale_factor, + }; + (canvas_ref.runner)(&mut ctx); } } } diff --git a/crates/hooks/src/use_canvas.rs b/crates/hooks/src/use_canvas.rs index e9e70e4da..9bb03d495 100644 --- a/crates/hooks/src/use_canvas.rs +++ b/crates/hooks/src/use_canvas.rs @@ -50,7 +50,7 @@ impl UseCanvas { /// /// let canvas = use_canvas(move || { /// let curr = value(); -/// Box::new(move |canvas, font_collection, area, scale_factor| { +/// Box::new(move |ctx| { /// // Draw using the canvas ! /// // use `curr` /// }) @@ -75,7 +75,7 @@ pub fn use_canvas(renderer_cb: impl Fn() -> Box + 'static) -> UseC /// let value = use_signal(|| 0); /// /// let canvas = use_canvas_with_deps(&value(), |curr| { -/// Box::new(move |canvas, font_collection, area, scale_factor| { +/// Box::new(move |ctx| { /// // Draw using the canvas ! /// // use `curr` /// }) diff --git a/crates/renderer/src/window_state.rs b/crates/renderer/src/window_state.rs index fc53b2faf..95e73765e 100644 --- a/crates/renderer/src/window_state.rs +++ b/crates/renderer/src/window_state.rs @@ -62,6 +62,7 @@ pub struct NotCreatedState<'a, State: Clone + 'static> { } pub struct CreatedState { + pub(crate) app: Application, pub(crate) gr_context: DirectContext, pub(crate) surface: Surface, pub(crate) gl_surface: GlutinSurface, @@ -71,7 +72,6 @@ pub struct CreatedState { pub(crate) fb_info: FramebufferInfo, pub(crate) num_samples: usize, pub(crate) stencil_size: usize, - pub(crate) app: Application, pub(crate) is_window_focused: bool, } diff --git a/crates/state/src/custom_attributes.rs b/crates/state/src/custom_attributes.rs index bf557cc36..398b06c30 100644 --- a/crates/state/src/custom_attributes.rs +++ b/crates/state/src/custom_attributes.rs @@ -58,7 +58,14 @@ impl Display for NodeReference { } } -pub type CanvasRunner = dyn Fn(&Canvas, &mut FontCollection, Area, f32) + Sync + Send + 'static; +pub struct CanvasRunnerContext<'a> { + pub canvas: &'a Canvas, + pub font_collection: &'a mut FontCollection, + pub area: Area, + pub scale_factor: f32, +} + +pub type CanvasRunner = dyn Fn(&mut CanvasRunnerContext) + Sync + Send + 'static; /// Canvas Reference #[derive(Clone)] diff --git a/examples/opengl_rtt.rs b/examples/opengl_rtt.rs new file mode 100644 index 000000000..1e04f4601 --- /dev/null +++ b/examples/opengl_rtt.rs @@ -0,0 +1,580 @@ +#![cfg_attr( + all(not(debug_assertions), target_os = "windows"), + windows_subsystem = "windows" +)] + +use std::{ + ffi::CString, + ptr, + sync::{ + Arc, + Mutex, + }, +}; + +use freya::prelude::*; +use freya_testing::prelude::CanvasRunnerContext; +use gl::types::*; +use skia_safe::Image; +fn main() { + launch(app); +} + +fn compile_shader(src: &str, ty: GLenum) -> GLuint { + let shader; + unsafe { + shader = gl::CreateShader(ty); + let c_str = std::ffi::CString::new(src.as_bytes()).unwrap(); + gl::ShaderSource(shader, 1, &c_str.as_ptr(), ptr::null()); + gl::CompileShader(shader); + + // Check for compilation errors + let mut success = gl::FALSE as GLint; + gl::GetShaderiv(shader, gl::COMPILE_STATUS, &mut success); + if success != gl::TRUE as GLint { + panic!("Failed to compile shader"); + } + } + shader +} + +fn link_program(vs: GLuint, fs: GLuint) -> GLuint { + let program; + unsafe { + program = gl::CreateProgram(); + gl::AttachShader(program, vs); + gl::AttachShader(program, fs); + gl::LinkProgram(program); + + // Check for linking errors + let mut success = gl::FALSE as GLint; + gl::GetProgramiv(program, gl::LINK_STATUS, &mut success); + if success != gl::TRUE as GLint { + panic!("Failed to link program"); + } + } + program +} + +const VERTEX_SHADER_SOURCE: &str = r#" +#version 330 core + +layout (location = 0) in vec3 aPos; + + +void main() { + gl_Position = vec4(aPos, 1.0); +} +"#; + +const FRAGMENT_SHADER_SOURCE: &str = r#" +#version 330 core +out vec4 FragColor; + +// Define a uniform variable to accept the color from the application +uniform vec4 u_color; + +void main() { + // Set the fragment color to the color provided by the uniform + FragColor = u_color; +} +"#; + +/// Stores saved opengl state to safely mess with native rendering and restore it back. +struct GLStateGuard { + old_framebuffer: i32, + old_texture: i32, + old_vao: i32, + old_buffer: i32, + old_unpack_alignment: i32, + old_unpack_row_length: i32, + old_unpack_skip_pixels: i32, + old_unpack_skip_rows: i32, + old_viewport: [i32; 4], + old_scissor_box: [i32; 4], + old_program: i32, + old_blend: bool, + old_blend_src_rgb: i32, + old_blend_dst_rgb: i32, + old_blend_src_alpha: i32, + old_blend_dst_alpha: i32, + old_depth_test: bool, + old_stencil_test: bool, + old_stencil_func: i32, + old_stencil_ref: i32, + old_stencil_value_mask: i32, + old_stencil_fail: i32, + old_stencil_pass_depth_fail: i32, + old_stencil_pass_depth_pass: i32, + old_stencil_writemask: i32, + old_cull_face: bool, + old_cull_face_mode: i32, + old_polygon_mode: [i32; 2], +} + +impl GLStateGuard { + /// Capture current opengl context and save it for further restoring. + /// It definitely captures more than required for this example, so you can adjust it to your + /// needs. + fn new() -> GLStateGuard { + unsafe { + // Save framebuffer binding + let mut old_framebuffer = 0; + gl::GetIntegerv(gl::FRAMEBUFFER_BINDING, &mut old_framebuffer); + + // Save texture binding + let mut old_texture = 0; + gl::GetIntegerv(gl::TEXTURE_BINDING_2D, &mut old_texture); + + // Save vertex array binding + let mut old_vao = 0; + gl::GetIntegerv(gl::VERTEX_ARRAY_BINDING, &mut old_vao); + + // Save array buffer binding + let mut old_buffer = 0; + gl::GetIntegerv(gl::ARRAY_BUFFER_BINDING, &mut old_buffer); + + // Save pixel store parameters + let mut old_unpack_alignment = 0; + gl::GetIntegerv(gl::UNPACK_ALIGNMENT, &mut old_unpack_alignment); + let mut old_unpack_row_length = 0; + gl::GetIntegerv(gl::UNPACK_ROW_LENGTH, &mut old_unpack_row_length); + let mut old_unpack_skip_pixels = 0; + gl::GetIntegerv(gl::UNPACK_SKIP_PIXELS, &mut old_unpack_skip_pixels); + let mut old_unpack_skip_rows = 0; + gl::GetIntegerv(gl::UNPACK_SKIP_ROWS, &mut old_unpack_skip_rows); + + // Save viewport and scissor box + let mut old_viewport = [0; 4]; + gl::GetIntegerv(gl::VIEWPORT, old_viewport.as_mut_ptr()); + let mut old_scissor_box = [0; 4]; + gl::GetIntegerv(gl::SCISSOR_BOX, old_scissor_box.as_mut_ptr()); + + // Save current program + let mut old_program = 0; + gl::GetIntegerv(gl::CURRENT_PROGRAM, &mut old_program); + + // Save blend state + let old_blend = gl::IsEnabled(gl::BLEND) == gl::TRUE; + let mut old_blend_src_rgb = 0; + gl::GetIntegerv(gl::BLEND_SRC_RGB, &mut old_blend_src_rgb); + let mut old_blend_dst_rgb = 0; + gl::GetIntegerv(gl::BLEND_DST_RGB, &mut old_blend_dst_rgb); + let mut old_blend_src_alpha = 0; + gl::GetIntegerv(gl::BLEND_SRC_ALPHA, &mut old_blend_src_alpha); + let mut old_blend_dst_alpha = 0; + gl::GetIntegerv(gl::BLEND_DST_ALPHA, &mut old_blend_dst_alpha); + + // Save depth test state + let old_depth_test = gl::IsEnabled(gl::DEPTH_TEST) == gl::TRUE; + + // Save stencil test state + let old_stencil_test = gl::IsEnabled(gl::STENCIL_TEST) == gl::TRUE; + let mut old_stencil_func = 0; + gl::GetIntegerv(gl::STENCIL_FUNC, &mut old_stencil_func); + let mut old_stencil_ref = 0; + gl::GetIntegerv(gl::STENCIL_REF, &mut old_stencil_ref); + let mut old_stencil_value_mask = 0; + gl::GetIntegerv(gl::STENCIL_VALUE_MASK, &mut old_stencil_value_mask); + let mut old_stencil_fail = 0; + gl::GetIntegerv(gl::STENCIL_FAIL, &mut old_stencil_fail); + let mut old_stencil_pass_depth_fail = 0; + gl::GetIntegerv( + gl::STENCIL_PASS_DEPTH_FAIL, + &mut old_stencil_pass_depth_fail, + ); + let mut old_stencil_pass_depth_pass = 0; + gl::GetIntegerv( + gl::STENCIL_PASS_DEPTH_PASS, + &mut old_stencil_pass_depth_pass, + ); + let mut old_stencil_writemask = 0; + gl::GetIntegerv(gl::STENCIL_WRITEMASK, &mut old_stencil_writemask); + + // Save cull face state + let old_cull_face = gl::IsEnabled(gl::CULL_FACE) == gl::TRUE; + let mut old_cull_face_mode = 0; + gl::GetIntegerv(gl::CULL_FACE_MODE, &mut old_cull_face_mode); + + // Save polygon mode + let mut old_polygon_mode = [0; 2]; + gl::GetIntegerv(gl::POLYGON_MODE, old_polygon_mode.as_mut_ptr()); + + GLStateGuard { + old_framebuffer, + old_texture, + old_vao, + old_buffer, + old_unpack_alignment, + old_unpack_row_length, + old_unpack_skip_pixels, + old_unpack_skip_rows, + old_viewport, + old_scissor_box, + old_program, + old_blend, + old_blend_src_rgb, + old_blend_dst_rgb, + old_blend_src_alpha, + old_blend_dst_alpha, + old_depth_test, + old_stencil_test, + old_stencil_func, + old_stencil_ref, + old_stencil_value_mask, + old_stencil_fail, + old_stencil_pass_depth_fail, + old_stencil_pass_depth_pass, + old_stencil_writemask, + old_cull_face, + old_cull_face_mode, + old_polygon_mode, + } + } + } +} +impl Drop for GLStateGuard { + fn drop(&mut self) { + unsafe { + gl::BindFramebuffer(gl::FRAMEBUFFER, self.old_framebuffer as u32); + + // Restore texture binding + gl::BindTexture(gl::TEXTURE_2D, self.old_texture as u32); + + // Restore vertex array binding + gl::BindVertexArray(self.old_vao as u32); + + // Restore array buffer binding + gl::BindBuffer(gl::ARRAY_BUFFER, self.old_buffer as u32); + + // Restore pixel store parameters + gl::PixelStorei(gl::UNPACK_ALIGNMENT, self.old_unpack_alignment); + gl::PixelStorei(gl::UNPACK_ROW_LENGTH, self.old_unpack_row_length); + gl::PixelStorei(gl::UNPACK_SKIP_PIXELS, self.old_unpack_skip_pixels); + gl::PixelStorei(gl::UNPACK_SKIP_ROWS, self.old_unpack_skip_rows); + + // Restore viewport and scissor box + gl::Viewport( + self.old_viewport[0], + self.old_viewport[1], + self.old_viewport[2], + self.old_viewport[3], + ); + gl::Scissor( + self.old_scissor_box[0], + self.old_scissor_box[1], + self.old_scissor_box[2], + self.old_scissor_box[3], + ); + + // Restore current program + gl::UseProgram(self.old_program as u32); + + // Restore blend state + if self.old_blend { + gl::Enable(gl::BLEND); + } else { + gl::Disable(gl::BLEND); + } + gl::BlendFuncSeparate( + self.old_blend_src_rgb as u32, + self.old_blend_dst_rgb as u32, + self.old_blend_src_alpha as u32, + self.old_blend_dst_alpha as u32, + ); + + // Restore depth test state + if self.old_depth_test { + gl::Enable(gl::DEPTH_TEST); + } else { + gl::Disable(gl::DEPTH_TEST); + } + + // Restore stencil test state + if self.old_stencil_test { + gl::Enable(gl::STENCIL_TEST); + } else { + gl::Disable(gl::STENCIL_TEST); + } + gl::StencilFunc( + self.old_stencil_func as u32, + self.old_stencil_ref, + self.old_stencil_value_mask as u32, + ); + gl::StencilOp( + self.old_stencil_fail as u32, + self.old_stencil_pass_depth_fail as u32, + self.old_stencil_pass_depth_pass as u32, + ); + gl::StencilMask(self.old_stencil_writemask as u32); + + // Restore cull face state + if self.old_cull_face { + gl::Enable(gl::CULL_FACE); + } else { + gl::Disable(gl::CULL_FACE); + } + gl::CullFace(self.old_cull_face_mode as u32); + + // Restore polygon mode + gl::PolygonMode(gl::FRONT_AND_BACK, self.old_polygon_mode[0] as u32); + } + } +} + +struct TriangleRenderer { + fbo: GLuint, + program: GLuint, + texture: GLuint, + texture_image: Option, + vao: GLuint, + vbo: GLuint, + width: i32, + height: i32, + color_location: GLint, +} + +impl Drop for TriangleRenderer { + fn drop(&mut self) { + self.texture_image = None; + unsafe { + gl::DeleteProgram(self.program); + gl::DeleteFramebuffers(1, &self.fbo); + gl::DeleteTextures(1, &self.texture); + gl::DeleteVertexArrays(1, &self.vao); + gl::DeleteBuffers(1, &self.vbo); + } + } +} + +impl TriangleRenderer { + fn new() -> TriangleRenderer { + TriangleRenderer { + fbo: 0, + program: 0, + texture: 0, + texture_image: None, + vao: 0, + vbo: 0, + width: 0, + height: 0, + color_location: 0, + } + } + fn allocate_texture(&mut self, ctx: &mut CanvasRunnerContext) { + let current_width = ctx.area.width().round() as i32; + let current_height = ctx.area.height().round() as i32; + let mut create_image = false; + unsafe { + if self.texture == 0 { + gl::GenTextures(1, &mut self.texture); + gl::BindTexture(gl::TEXTURE_2D, self.texture); + gl::TexImage2D( + gl::TEXTURE_2D, + 0, + gl::RGB as GLint, + current_width, + current_height, + 0, + gl::RGB, + gl::UNSIGNED_BYTE, + ptr::null(), + ); + gl::TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_MIN_FILTER, gl::LINEAR as GLint); + gl::TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_MAG_FILTER, gl::LINEAR as GLint); + self.width = current_width; + self.height = current_height; + create_image = true; + } else { + // resize texture before rendering if required + if self.width != current_width || self.height != current_height { + gl::BindTexture(gl::TEXTURE_2D, self.texture); + gl::TexImage2D( + gl::TEXTURE_2D, + 0, + gl::RGB as GLint, + current_width, + current_height, + 0, + gl::RGB, + gl::UNSIGNED_BYTE, + ptr::null(), + ); + self.width = current_width; + self.height = current_height; + create_image = true; + } + } + + if create_image { + let backend_texture = skia_safe::gpu::backend_textures::make_gl( + (self.width, self.height), + skia_safe::gpu::Mipmapped::No, + skia_safe::gpu::gl::TextureInfo { + target: gl::TEXTURE_2D, + format: gl::RGBA8, + protected: skia_safe::gpu::Protected::No, + id: self.texture, + }, + "rtt", + ); + let mut direct_context = ctx.canvas.direct_context().unwrap(); + + self.texture_image = Image::from_texture( + &mut direct_context, + &backend_texture, + skia_safe::gpu::SurfaceOrigin::TopLeft, + skia_safe::ColorType::RGBA8888, + skia_safe::AlphaType::Premul, + None, + ); + } + } + } + fn render(&mut self, color: (f64, f64, f64), ctx: &mut CanvasRunnerContext) { + unsafe { + if self.fbo == 0 { + // create framebuffer and texture + let mut framebuffer: GLuint = 0; + gl::GenFramebuffers(1, &mut framebuffer); + gl::BindFramebuffer(gl::FRAMEBUFFER, framebuffer); + self.allocate_texture(ctx); + gl::FramebufferTexture2D( + gl::FRAMEBUFFER, + gl::COLOR_ATTACHMENT0, + gl::TEXTURE_2D, + self.texture, + 0, + ); + + if gl::CheckFramebufferStatus(gl::FRAMEBUFFER) != gl::FRAMEBUFFER_COMPLETE { + panic!("Framebuffer is not complete!"); + } + + gl::BindFramebuffer(gl::FRAMEBUFFER, 0); + + self.fbo = framebuffer; + + // create shader program + let vertex_shader = compile_shader(VERTEX_SHADER_SOURCE, gl::VERTEX_SHADER); + let fragment_shader = compile_shader(FRAGMENT_SHADER_SOURCE, gl::FRAGMENT_SHADER); + let program = link_program(vertex_shader, fragment_shader); + + gl::DeleteShader(vertex_shader); + gl::DeleteShader(fragment_shader); + let color_loc_name = CString::new("u_color").unwrap(); + self.color_location = gl::GetUniformLocation(program, color_loc_name.as_ptr()); + self.program = program; + + // create buffers + let vertices: [f32; 9] = [ + -0.5, -0.5, 0.0, // Bottom-left + 0.5, -0.5, 0.0, // Bottom-right + 0.0, 0.5, 0.0, // Top + ]; + + let mut vao: GLuint = 0; + let mut vbo: GLuint = 0; + + gl::GenVertexArrays(1, &mut vao); + gl::GenBuffers(1, &mut vbo); + + gl::BindVertexArray(vao); + + gl::BindBuffer(gl::ARRAY_BUFFER, vbo); + gl::BufferData( + gl::ARRAY_BUFFER, + (vertices.len() * std::mem::size_of::()) as GLsizeiptr, + vertices.as_ptr() as *const _, + gl::STATIC_DRAW, + ); + + gl::VertexAttribPointer( + 0, + 3, + gl::FLOAT, + gl::FALSE, + 3 * std::mem::size_of::() as GLsizei, + ptr::null(), + ); + gl::EnableVertexAttribArray(0); + + gl::BindBuffer(gl::ARRAY_BUFFER, 0); + gl::BindVertexArray(0); + + self.vao = vao; + self.vbo = vbo; + } + + self.allocate_texture(ctx); + + gl::BindFramebuffer(gl::FRAMEBUFFER, self.fbo); + gl::Viewport(0, 0, self.width, self.height); + gl::ClearColor(0.0, 0.0, 0.0, 1.0); + gl::Clear(gl::COLOR_BUFFER_BIT); + + gl::UseProgram(self.program); + gl::Uniform4f( + self.color_location, + (color.0 / 100.0) as GLfloat, + (color.1 / 100.0) as GLfloat, + (color.2 / 100.0) as GLfloat, + 1.0 as GLfloat, + ); + gl::BindVertexArray(self.vao); + gl::DrawArrays(gl::TRIANGLES, 0, 3); + } + } +} + +fn app() -> Element { + let mut r = use_signal(|| 100.0); + let mut g: Signal = use_signal(|| 0.0); + let mut b = use_signal(|| 0.0); + + let triangle_renderer = use_hook(|| Arc::new(Mutex::new(TriangleRenderer::new()))); + + let canvas = use_canvas(move || { + let color = (*r.read(), *g.read(), *b.read()); + let triangle_renderer = triangle_renderer.clone(); + + Box::new(move |ctx| { + ctx.canvas.translate((ctx.area.min_x(), ctx.area.min_y())); + let mut renderer_guard = triangle_renderer.lock().unwrap(); + { + let gl_state = GLStateGuard::new(); + renderer_guard.render(color, ctx); + drop(gl_state); // ensure we restore original opengl context state after rendering + } + ctx.canvas.draw_image( + renderer_guard.texture_image.clone().unwrap(), + (ctx.area.min_x(), ctx.area.min_y()), + None, + ); + ctx.canvas.restore(); + }) + }); + + rsx!( + rect { + canvas_reference: canvas.attribute(), + width: "100%", + height: "100%", + Slider { + width: "300", + value: *r.read(), + onmoved: move |value: f64| { r.set(value) } + } + Slider { + width: "300", + value: *g.read(), + onmoved: move |value: f64| { g.set(value) } + } + Slider { + width: "300", + value: *b.read(), + onmoved: move |value: f64| { b.set(value) } + } + } + ) +} diff --git a/examples/render_canvas.rs b/examples/render_canvas.rs index 18e1da6a4..0b96d4767 100644 --- a/examples/render_canvas.rs +++ b/examples/render_canvas.rs @@ -28,14 +28,15 @@ fn app() -> Element { let canvas = use_canvas(move || { let state = *state.read(); - Box::new(move |canvas, font_collection, region, _| { - canvas.translate((region.min_x(), region.min_y())); + Box::new(move |ctx| { + ctx.canvas.translate((ctx.area.min_x(), ctx.area.min_y())); let mut text_paint = Paint::default(); text_paint.set_anti_alias(true); text_paint.set_color(Color::WHITE); - let typefaces = - font_collection.find_typefaces(&["Times New Roman"], FontStyle::default()); + let typefaces = ctx + .font_collection + .find_typefaces(&["Times New Roman"], FontStyle::default()); let font = Font::new( typefaces .first() @@ -43,14 +44,14 @@ fn app() -> Element { 50.0, ); - canvas.draw_str( + ctx.canvas.draw_str( format!("value is {}", state), - ((region.max_x() / 2.0 - 120.0), region.max_y() / 2.0), + ((ctx.area.max_x() / 2.0 - 120.0), ctx.area.max_y() / 2.0), &font, &text_paint, ); - canvas.restore(); + ctx.canvas.restore(); }) }); diff --git a/examples/shader.rs b/examples/shader.rs index 41779d8dc..4dec0b69b 100644 --- a/examples/shader.rs +++ b/examples/shader.rs @@ -57,11 +57,11 @@ fn app() -> Element { let shader_wrapper = Arc::new(ShaderWrapper(shader)); let instant = Instant::now(); - Box::new(move |canvas, _, region, _| { + Box::new(move |ctx| { let mut builder = UniformsBuilder::default(); builder.set( "u_resolution", - UniformValue::FloatVec(vec![region.width(), region.height()]), + UniformValue::FloatVec(vec![ctx.area.width(), ctx.area.height()]), ); builder.set( "u_time", @@ -77,12 +77,12 @@ fn app() -> Element { paint.set_color(Color::WHITE); paint.set_shader(shader); - canvas.draw_rect( + ctx.canvas.draw_rect( Rect::new( - region.min_x(), - region.min_y(), - region.max_x(), - region.max_y(), + ctx.area.min_x(), + ctx.area.min_y(), + ctx.area.max_x(), + ctx.area.max_y(), ), &paint, ); diff --git a/examples/shader_editor.rs b/examples/shader_editor.rs index 702d4d559..34a627e1e 100644 --- a/examples/shader_editor.rs +++ b/examples/shader_editor.rs @@ -191,8 +191,8 @@ fn ShaderView(editable: UseEditable) -> Element { let shared_runtime_effect = Arc::new(RuntimeEffectWrapper(runtime_effect)); let instant = Instant::now(); - Box::new(move |canvas, font_collection, region, _| { - canvas.save(); + Box::new(move |ctx| { + ctx.canvas.save(); let runtime_effect = &shared_runtime_effect.0; @@ -200,7 +200,7 @@ fn ShaderView(editable: UseEditable) -> Element { let mut builder = UniformsBuilder::default(); builder.set( "u_resolution", - UniformValue::FloatVec(vec![region.width(), region.height()]), + UniformValue::FloatVec(vec![ctx.area.width(), ctx.area.height()]), ); builder.set( "u_time", @@ -216,12 +216,12 @@ fn ShaderView(editable: UseEditable) -> Element { paint.set_color(Color::WHITE); paint.set_shader(shader); - canvas.draw_rect( + ctx.canvas.draw_rect( Rect::new( - region.min_x(), - region.min_y(), - region.max_x(), - region.max_y(), + ctx.area.min_x(), + ctx.area.min_y(), + ctx.area.max_x(), + ctx.area.max_y(), ), &paint, ); @@ -230,15 +230,15 @@ fn ShaderView(editable: UseEditable) -> Element { text_paint.set_anti_alias(true); text_paint.set_color(Color::WHITE); let mut paragraph_builder = - ParagraphBuilder::new(&ParagraphStyle::default(), font_collection.clone()); + ParagraphBuilder::new(&ParagraphStyle::default(), ctx.font_collection.clone()); paragraph_builder.add_text(err); let mut paragraph = paragraph_builder.build(); - paragraph.layout(region.width()); + paragraph.layout(ctx.area.width()); - paragraph.paint(canvas, (region.min_x(), region.min_y())); + paragraph.paint(ctx.canvas, (ctx.area.min_x(), ctx.area.min_y())); } - canvas.restore(); + ctx.canvas.restore(); }) }); From 182e1e091fc4f3ebc405503c902de8e85cdc901c Mon Sep 17 00:00:00 2001 From: Marc Espin Date: Fri, 30 Aug 2024 19:20:46 +0200 Subject: [PATCH 09/12] feat: Refactor some parts of Torin (#807) * fix(torin): Consider `padding` when accumulating inner sizes, used in e.g scroll views * experiments * refactor: Start refactoring Torin * chore: Clean up and fixes * feat: MeasureContext * chore: Clean up * chore: add clipping back to Button * chore: Update counter.rs * chore: Update counter.rs * fix: inner percentage --- crates/torin/src/dom_adapter.rs | 2 +- crates/torin/src/geometry.rs | 194 ++----- crates/torin/src/lib.rs | 1 - crates/torin/src/measure.rs | 964 ++++++++++++++++++++----------- crates/torin/src/measure_mode.rs | 226 -------- crates/torin/src/torin.rs | 17 +- 6 files changed, 666 insertions(+), 738 deletions(-) delete mode 100644 crates/torin/src/measure_mode.rs diff --git a/crates/torin/src/dom_adapter.rs b/crates/torin/src/dom_adapter.rs index fb95167c3..bd76a3993 100644 --- a/crates/torin/src/dom_adapter.rs +++ b/crates/torin/src/dom_adapter.rs @@ -46,7 +46,7 @@ impl PartialEq for LayoutNode { impl LayoutNode { // The area without any margin pub fn visible_area(&self) -> Area { - self.area.after_gaps(&self.margin) + self.area.without_gaps(&self.margin) } } diff --git a/crates/torin/src/geometry.rs b/crates/torin/src/geometry.rs index 5767f9f95..32a5be37c 100644 --- a/crates/torin/src/geometry.rs +++ b/crates/torin/src/geometry.rs @@ -1,11 +1,8 @@ -use crate::{ - node::Node, - prelude::{ - Alignment, - DirectionMode, - Gaps, - Size, - }, +use crate::prelude::{ + DirectionMode, + Gaps, + Node, + Size, }; #[derive(PartialEq)] @@ -18,157 +15,34 @@ pub type CursorPoint = euclid::Point2D; pub type Length = euclid::Length; pub trait AreaModel { - // The area without any outer gap (e.g margin) - fn after_gaps(&self, margin: &Gaps) -> Area; + /// The area without any outer gap (e.g margin) + fn without_gaps(self, gap: &Gaps) -> Area; - // Adjust the available area with the node offsets (mainly used by scrollviews) + /// Adjust the available area with the node offsets (mainly used by scrollviews) fn move_with_offsets(&mut self, offset_x: &Length, offset_y: &Length); - // Align the content of this node. - fn align_content( - &mut self, - available_area: &Area, - contents_area: &Size2D, - alignment: &Alignment, - direction: &DirectionMode, - alignment_direction: AlignmentDirection, - ); - - // Align the position of this node. - #[allow(clippy::too_many_arguments)] - fn align_position( - &mut self, - initial_available_area: &Area, - inner_sizes: &Size2D, - alignment: &Alignment, - direction: &DirectionMode, - alignment_direction: AlignmentDirection, - siblings_len: usize, - child_position: usize, - ); - + /// Adjust the size given the Node data fn adjust_size(&mut self, node: &Node); } impl AreaModel for Area { - /// Get the area inside after including the gaps (margins or paddings) - fn after_gaps(&self, margin: &Gaps) -> Area { + fn without_gaps(self, gaps: &Gaps) -> Area { let origin = self.origin; let size = self.size; Area::new( - Point2D::new(origin.x + margin.left(), origin.y + margin.top()), + Point2D::new(origin.x + gaps.left(), origin.y + gaps.top()), Size2D::new( - size.width - margin.horizontal(), - size.height - margin.vertical(), + size.width - gaps.horizontal(), + size.height - gaps.vertical(), ), ) } - /// Get the area inside after including the gaps (margins or paddings) fn move_with_offsets(&mut self, offset_x: &Length, offset_y: &Length) { self.origin.x += offset_x.get(); self.origin.y += offset_y.get(); } - fn align_content( - &mut self, - available_area: &Area, - contents_size: &Size2D, - alignment: &Alignment, - direction: &DirectionMode, - alignment_direction: AlignmentDirection, - ) { - let axis = get_align_axis(direction, alignment_direction); - - match axis { - AlignAxis::Height => match alignment { - Alignment::Center => { - let new_origin_y = - (available_area.height() / 2.0) - (contents_size.height / 2.0); - - self.origin.y = available_area.min_y() + new_origin_y; - } - Alignment::End => { - self.origin.y = available_area.max_y() - contents_size.height; - } - _ => {} - }, - AlignAxis::Width => match alignment { - Alignment::Center => { - let new_origin_x = (available_area.width() / 2.0) - (contents_size.width / 2.0); - - self.origin.x = available_area.min_x() + new_origin_x; - } - Alignment::End => { - self.origin.x = available_area.max_x() - contents_size.width; - } - _ => {} - }, - } - } - - fn align_position( - &mut self, - initial_available_area: &Area, - inner_sizes: &Size2D, - alignment: &Alignment, - direction: &DirectionMode, - alignment_direction: AlignmentDirection, - siblings_len: usize, - child_position: usize, - ) { - let axis = get_align_axis(direction, alignment_direction); - - match axis { - AlignAxis::Height => match alignment { - Alignment::SpaceBetween if child_position > 0 => { - let all_gaps_sizes = initial_available_area.height() - inner_sizes.height; - let gap_size = all_gaps_sizes / (siblings_len - 1) as f32; - self.origin.y += gap_size; - } - Alignment::SpaceEvenly => { - let all_gaps_sizes = initial_available_area.height() - inner_sizes.height; - let gap_size = all_gaps_sizes / (siblings_len + 1) as f32; - self.origin.y += gap_size; - } - Alignment::SpaceAround => { - let all_gaps_sizes = initial_available_area.height() - inner_sizes.height; - let one_gap_size = all_gaps_sizes / siblings_len as f32; - let gap_size = if child_position == 0 || child_position == siblings_len { - one_gap_size / 2. - } else { - one_gap_size - }; - self.origin.y += gap_size; - } - _ => {} - }, - AlignAxis::Width => match alignment { - Alignment::SpaceBetween if child_position > 0 => { - let all_gaps_sizes = initial_available_area.width() - inner_sizes.width; - let gap_size = all_gaps_sizes / (siblings_len - 1) as f32; - self.origin.x += gap_size; - } - Alignment::SpaceEvenly => { - let all_gaps_sizes = initial_available_area.width() - inner_sizes.width; - let gap_size = all_gaps_sizes / (siblings_len + 1) as f32; - self.origin.x += gap_size; - } - Alignment::SpaceAround => { - let all_gaps_sizes = initial_available_area.width() - inner_sizes.width; - let one_gap_size = all_gaps_sizes / siblings_len as f32; - let gap_size = if child_position == 0 || child_position == siblings_len { - one_gap_size / 2. - } else { - one_gap_size - }; - self.origin.x += gap_size; - } - _ => {} - }, - } - } - fn adjust_size(&mut self, node: &Node) { if let Size::InnerPercentage(p) = node.width { self.size.width *= p.get() / 100.; @@ -179,22 +53,6 @@ impl AreaModel for Area { } } -pub fn get_align_axis( - direction: &DirectionMode, - alignment_direction: AlignmentDirection, -) -> AlignAxis { - match direction { - DirectionMode::Vertical => match alignment_direction { - AlignmentDirection::Main => AlignAxis::Height, - AlignmentDirection::Cross => AlignAxis::Width, - }, - DirectionMode::Horizontal => match alignment_direction { - AlignmentDirection::Main => AlignAxis::Width, - AlignmentDirection::Cross => AlignAxis::Height, - }, - } -} - pub enum AlignmentDirection { Main, Cross, @@ -205,3 +63,29 @@ pub enum AlignAxis { Height, Width, } + +impl AlignAxis { + pub fn new(direction: &DirectionMode, alignment_direction: AlignmentDirection) -> Self { + match direction { + DirectionMode::Vertical => match alignment_direction { + AlignmentDirection::Main => AlignAxis::Height, + AlignmentDirection::Cross => AlignAxis::Width, + }, + DirectionMode::Horizontal => match alignment_direction { + AlignmentDirection::Main => AlignAxis::Width, + AlignmentDirection::Cross => AlignAxis::Height, + }, + } + } +} + +pub trait SizeModel { + /// Get the size with the given gap, e.g padding. + fn with_gaps(self, gap: &Gaps) -> Size2D; +} + +impl SizeModel for Size2D { + fn with_gaps(self, gap: &Gaps) -> Size2D { + Size2D::new(self.width + gap.horizontal(), self.height + gap.vertical()) + } +} diff --git a/crates/torin/src/lib.rs b/crates/torin/src/lib.rs index 0911b91c7..0b9f3f996 100644 --- a/crates/torin/src/lib.rs +++ b/crates/torin/src/lib.rs @@ -2,7 +2,6 @@ pub mod custom_measurer; pub mod dom_adapter; pub mod geometry; mod measure; -mod measure_mode; pub mod node; pub mod scaled; pub mod torin; diff --git a/crates/torin/src/measure.rs b/crates/torin/src/measure.rs index b667b8273..c9d6b803f 100644 --- a/crates/torin/src/measure.rs +++ b/crates/torin/src/measure.rs @@ -12,11 +12,13 @@ use crate::{ Area, Size2D, }, - measure_mode::MeasureMode, node::Node, prelude::{ + AlignAxis, + Alignment, AlignmentDirection, AreaModel, + DirectionMode, LayoutMetadata, Torin, }, @@ -30,420 +32,686 @@ pub enum Phase { Final, } -/// Measure a Node layout -#[allow(clippy::too_many_arguments)] -#[inline(always)] -pub fn measure_node( - node_id: Key, - node: &Node, - layout: &mut Torin, - // Area occupied by it's parent - parent_area: &Area, - // Area that is available to use by the children of the parent - available_parent_area: &Area, - measurer: &mut Option>, - // Whether to cache the measurements of this Node's children - must_cache_inner_nodes: bool, - // Adapter for the provided DOM - dom_adapter: &mut impl DOMAdapter, - - layout_metadata: &LayoutMetadata, - - invalidated_tree: bool, - - phase: Phase, -) -> (bool, LayoutNode) { - let must_revalidate = invalidated_tree - || layout.dirty.contains(&node_id) - || !layout.results.contains_key(&node_id); - if must_revalidate { - // Create the initial Node area size - let mut area_size = Size2D::new(node.padding.horizontal(), node.padding.vertical()); - - // Compute the width and height given the size, the minimum size, the maximum size and margins - area_size.width = node.width.min_max( - area_size.width, - parent_area.size.width, - available_parent_area.size.width, - node.margin.left(), - node.margin.horizontal(), - &node.minimum_width, - &node.maximum_width, - layout_metadata.root_area.width(), - phase, - ); - area_size.height = node.height.min_max( - area_size.height, - parent_area.size.height, - available_parent_area.size.height, - node.margin.top(), - node.margin.vertical(), - &node.minimum_height, - &node.maximum_height, - layout_metadata.root_area.height(), - phase, - ); - - // If available, run a custom layout measure function - // This is useful when you use third-party libraries (e.g. rust-skia, cosmic-text) to measure text layouts - // When a Node is measured by a custom measurer function the inner children will be skipped - let (measure_inner_children, node_data) = if let Some(measurer) = measurer { - let most_fitting_width = *node - .width - .most_fitting_size(&area_size.width, &available_parent_area.size.width); - let most_fitting_height = *node - .height - .most_fitting_size(&area_size.height, &available_parent_area.size.height); - - let most_fitting_area_size = Size2D::new(most_fitting_width, most_fitting_height); - let res = measurer.measure(node_id, node, &most_fitting_area_size); - - // Compute the width and height again using the new custom area sizes - if let Some((custom_size, node_data)) = res { +pub struct MeasureContext<'a, Key, L, D> +where + Key: NodeKey, + L: LayoutMeasurer, + D: DOMAdapter, +{ + pub layout: &'a mut Torin, + pub measurer: &'a mut Option, + pub dom_adapter: &'a mut D, + pub layout_metadata: LayoutMetadata, +} + +impl MeasureContext<'_, Key, L, D> +where + Key: NodeKey, + L: LayoutMeasurer, + D: DOMAdapter, +{ + /// Measure a Node. + #[allow(clippy::too_many_arguments)] + #[inline(always)] + pub fn measure_node( + &mut self, + // ID for this Node + node_id: Key, + // Data of this Node + node: &Node, + // Area occupied by it's parent + parent_area: &Area, + // Area that is available to use by the children of the parent + available_parent_area: &Area, + // Whether to cache the measurements of this Node's children + must_cache_children: bool, + // Parent Node is dirty. + parent_is_dirty: bool, + // Current phase of measurement + phase: Phase, + ) -> (bool, LayoutNode) { + // 1. If parent is dirty + // 2. If this Node has been marked as dirty + // 3. If there is no know cached data about this Node. + let must_revalidate = parent_is_dirty + || self.layout.dirty.contains(&node_id) + || !self.layout.results.contains_key(&node_id); + if must_revalidate { + // Create the initial Node area size + let mut area_size = Size2D::new(node.padding.horizontal(), node.padding.vertical()); + + // Compute the width and height given the size, the minimum size, the maximum size and margins + area_size.width = node.width.min_max( + area_size.width, + parent_area.size.width, + available_parent_area.size.width, + node.margin.left(), + node.margin.horizontal(), + &node.minimum_width, + &node.maximum_width, + self.layout_metadata.root_area.width(), + phase, + ); + area_size.height = node.height.min_max( + area_size.height, + parent_area.size.height, + available_parent_area.size.height, + node.margin.top(), + node.margin.vertical(), + &node.minimum_height, + &node.maximum_height, + self.layout_metadata.root_area.height(), + phase, + ); + + // If available, run a custom layout measure function + // This is useful when you use third-party libraries (e.g. rust-skia, cosmic-text) to measure text layouts + // When a Node is measured by a custom measurer function the inner children will be skipped + let (measure_inner_children, node_data) = if let Some(measurer) = self.measurer { + let most_fitting_width = *node + .width + .most_fitting_size(&area_size.width, &available_parent_area.size.width); + let most_fitting_height = *node + .height + .most_fitting_size(&area_size.height, &available_parent_area.size.height); + + let most_fitting_area_size = Size2D::new(most_fitting_width, most_fitting_height); + let res = measurer.measure(node_id, node, &most_fitting_area_size); + + // Compute the width and height again using the new custom area sizes + if let Some((custom_size, node_data)) = res { + if node.width.inner_sized() { + area_size.width = node.width.min_max( + custom_size.width, + parent_area.size.width, + available_parent_area.size.width, + node.margin.left(), + node.margin.horizontal(), + &node.minimum_width, + &node.maximum_width, + self.layout_metadata.root_area.width(), + phase, + ); + } + if node.height.inner_sized() { + area_size.height = node.height.min_max( + custom_size.height, + parent_area.size.height, + available_parent_area.size.height, + node.margin.top(), + node.margin.vertical(), + &node.minimum_height, + &node.maximum_height, + self.layout_metadata.root_area.height(), + phase, + ); + } + + // Do not measure inner children + (false, Some(node_data)) + } else { + (true, None) + } + } else { + (true, None) + }; + + // There is no need to measure inner children in the initial phase if this Node size + // isn't decided by his children + let phase_measure_inner_children = if phase == Phase::Initial { + node.width.inner_sized() || node.height.inner_sized() + } else { + true + }; + + // Compute the inner size of the Node, which is basically the size inside the margins and paddings + let inner_size = { + let mut inner_size = area_size; + + // When having an unsized bound we set it to whatever is still available in the parent's area if node.width.inner_sized() { - area_size.width = node.width.min_max( - custom_size.width, + inner_size.width = node.width.min_max( + available_parent_area.width(), parent_area.size.width, - available_parent_area.size.width, + available_parent_area.width(), node.margin.left(), node.margin.horizontal(), &node.minimum_width, &node.maximum_width, - layout_metadata.root_area.width(), + self.layout_metadata.root_area.width(), phase, ); } if node.height.inner_sized() { - area_size.height = node.height.min_max( - custom_size.height, + inner_size.height = node.height.min_max( + available_parent_area.height(), parent_area.size.height, - available_parent_area.size.height, + available_parent_area.height(), node.margin.top(), node.margin.vertical(), &node.minimum_height, &node.maximum_height, - layout_metadata.root_area.height(), + self.layout_metadata.root_area.height(), phase, ); } + inner_size + }; - // Do not measure inner children - (false, Some(node_data)) - } else { - (true, None) - } - } else { - (true, None) - }; - - // There is no need to measure inner children in the initial phase if this Node size - // isn't decided by his children - let phase_measure_inner_children = if phase == Phase::Initial { - node.width.inner_sized() || node.height.inner_sized() - } else { - true - }; - - // Compute the inner size of the Node, which is basically the size inside the margins and paddings - let inner_size = { - let mut inner_size = area_size; - - // When having an unsized bound we set it to whatever is still available in the parent's area - if node.width.inner_sized() { - inner_size.width = node.width.min_max( - available_parent_area.width(), - parent_area.size.width, - available_parent_area.width(), - node.margin.left(), - node.margin.horizontal(), - &node.minimum_width, - &node.maximum_width, - layout_metadata.root_area.width(), - phase, + // Create the areas + let area_origin = + node.position + .get_origin(available_parent_area, parent_area, &area_size); + let mut area = Rect::new(area_origin, area_size); + let mut inner_area = Rect::new(area_origin, inner_size) + .without_gaps(&node.padding) + .without_gaps(&node.margin); + + let mut inner_sizes = Size2D::default(); + + if measure_inner_children && phase_measure_inner_children { + // Create an area containing the available space inside the inner area + let mut available_area = inner_area; + + available_area.move_with_offsets(&node.offset_x, &node.offset_y); + + // Measure the layout of this Node's children + self.measure_children( + &node_id, + node, + &mut available_area, + &mut inner_sizes, + must_cache_children, + &mut area, + &mut inner_area, + true, ); } - if node.height.inner_sized() { - inner_size.height = node.height.min_max( - available_parent_area.height(), - parent_area.size.height, - available_parent_area.height(), - node.margin.top(), - node.margin.vertical(), - &node.minimum_height, - &node.maximum_height, - layout_metadata.root_area.height(), - phase, - ); - } - inner_size - }; - - // Create the areas - let area_origin = node - .position - .get_origin(available_parent_area, parent_area, &area_size); - let mut area = Rect::new(area_origin, area_size); - let mut inner_area = Rect::new(area_origin, inner_size) - .after_gaps(&node.padding) - .after_gaps(&node.margin); - let mut inner_sizes = Size2D::default(); + ( + must_cache_children, + LayoutNode { + area, + margin: node.margin, + inner_area, + inner_sizes, + data: node_data, + }, + ) + } else { + let layout_node = self.layout.get(node_id).unwrap().clone(); - if measure_inner_children && phase_measure_inner_children { - // Create an area containing the available space inside the inner area - let mut available_area = inner_area; + let mut inner_sizes = layout_node.inner_sizes; + let mut available_area = layout_node.inner_area; + let mut area = layout_node.area; + let mut inner_area = layout_node.inner_area; available_area.move_with_offsets(&node.offset_x, &node.offset_y); - let mut measurement_mode = MeasureMode::ParentIsNotCached { - area: &mut area, - inner_area: &mut inner_area, + let measure_inner_children = if let Some(measurer) = self.measurer { + measurer.should_measure_inner_children(node_id) + } else { + true }; - // Measure the layout of this Node's children - measure_inner_nodes( - &node_id, - node, - layout, - &mut available_area, - &mut inner_sizes, - measurer, - must_cache_inner_nodes, - &mut measurement_mode, - dom_adapter, - layout_metadata, - true, - ); + if measure_inner_children { + self.measure_children( + &node_id, + node, + &mut available_area, + &mut inner_sizes, + must_cache_children, + &mut area, + &mut inner_area, + false, + ); + } + + (false, layout_node) } + } - ( - must_cache_inner_nodes, - LayoutNode { - area, - margin: node.margin, - inner_area, - inner_sizes, - data: node_data, - }, - ) - } else { - let layout_node = layout.get(node_id).unwrap().clone(); + /// Measure the children layouts of a Node + #[allow(clippy::too_many_arguments)] + #[inline(always)] + pub fn measure_children( + &mut self, + parent_node_id: &Key, + parent_node: &Node, + // Area available inside the Node + available_area: &mut Area, + // Accumulated sizes in both axis in the Node + inner_sizes: &mut Size2D, + // Whether to cache the measurements of this Node's children + must_cache_children: bool, + // Parent area. + area: &mut Area, + // Inner area of the parent. + inner_area: &mut Area, + // Parent Node is dirty. + parent_is_dirty: bool, + ) { + let children = self.dom_adapter.children_of(parent_node_id); + + let mut initial_phase_sizes = FxHashMap::default(); + let mut initial_phase_inner_sizes = *inner_sizes; + + // Initial phase: Measure the size and position of the children if the parent has a + // non-start cross alignment, non-start main aligment of a fit-content. + if parent_node.cross_alignment.is_not_start() + || parent_node.main_alignment.is_not_start() + || parent_node.content.is_fit() + { + let mut initial_phase_area = *area; + let mut initial_phase_inner_area = *inner_area; + let mut initial_phase_available_area = *available_area; + + // Measure the children + for child_id in &children { + let Some(child_data) = self.dom_adapter.get_node(child_id) else { + continue; + }; + + // No need to consider this Node for a two-phasing + // measurements as it will float on its own. + if child_data.position.is_absolute() { + continue; + } - let mut inner_sizes = layout_node.inner_sizes; - let mut available_area = layout_node.inner_area; + let inner_area = initial_phase_inner_area; - available_area.move_with_offsets(&node.offset_x, &node.offset_y); + let (_, child_areas) = self.measure_node( + *child_id, + &child_data, + &inner_area, + &initial_phase_available_area, + false, + parent_is_dirty, + Phase::Initial, + ); - let mut measurement_mode = MeasureMode::ParentIsCached { - inner_area: &layout_node.inner_area, - }; + // Stack this child into the parent + Self::stack_child( + &mut initial_phase_available_area, + parent_node, + &mut initial_phase_area, + &mut initial_phase_inner_area, + &mut initial_phase_inner_sizes, + &child_areas.area, + &child_data, + ); - let measure_inner_children = if let Some(measurer) = measurer { - measurer.should_measure_inner_children(node_id) - } else { - true - }; + if parent_node.cross_alignment.is_not_start() + || parent_node.main_alignment.is_spaced() + { + initial_phase_sizes.insert(*child_id, child_areas.area.size); + } + } - if measure_inner_children { - measure_inner_nodes( - &node_id, - node, - layout, - &mut available_area, - &mut inner_sizes, - measurer, - must_cache_inner_nodes, - &mut measurement_mode, - dom_adapter, - layout_metadata, - false, - ); + if parent_node.main_alignment.is_not_start() { + // Adjust the available and inner areas of the Main axis + Self::shrink_area_to_fit_when_unbounded( + available_area, + &initial_phase_area, + &mut initial_phase_inner_area, + parent_node, + AlignmentDirection::Main, + ); + + // Align the Main axis + Self::align_content( + available_area, + &initial_phase_inner_area, + &initial_phase_inner_sizes, + &parent_node.main_alignment, + &parent_node.direction, + AlignmentDirection::Main, + ); + } + + if parent_node.cross_alignment.is_not_start() || parent_node.content.is_fit() { + // Adjust the available and inner areas of the Cross axis + Self::shrink_area_to_fit_when_unbounded( + available_area, + &initial_phase_area, + &mut initial_phase_inner_area, + parent_node, + AlignmentDirection::Cross, + ); + } } - (false, layout_node) - } -} + let initial_available_area = *available_area; -/// Measure the children layouts of a Node -#[allow(clippy::too_many_arguments)] -#[inline(always)] -pub fn measure_inner_nodes( - parent_node_id: &Key, - parent_node: &Node, - layout: &mut Torin, - // Area available inside the Node - available_area: &mut Area, - // Accumulated sizes in both axis in the Node - inner_sizes: &mut Size2D, - measurer: &mut Option>, - // Whether to cache the measurements of this Node's children - must_cache_inner_nodes: bool, - mode: &mut MeasureMode, - // Adapter for the provided DOM - dom_adapter: &mut impl DOMAdapter, - - layout_metadata: &LayoutMetadata, - - invalidated_tree: bool, -) { - let children = dom_adapter.children_of(parent_node_id); - - let mut initial_phase_sizes = FxHashMap::default(); - let mut initial_phase_inner_sizes = *inner_sizes; - - // Initial phase: Measure the size and position of the children if the parent has a - // non-start cross alignment, non-start main aligment of a fit-content. - if parent_node.cross_alignment.is_not_start() - || parent_node.main_alignment.is_not_start() - || parent_node.content.is_fit() - { - let mut initial_phase_mode = mode.to_owned(); - let mut initial_phase_mode = initial_phase_mode.to_mut(); - let mut initial_phase_available_area = *available_area; - - // 1. Measure the children - for child_id in &children { - let Some(child_data) = dom_adapter.get_node(child_id) else { + // Final phase: measure the children with all the axis and sizes adjusted + for (child_n, child_id) in children.into_iter().enumerate() { + let Some(child_data) = self.dom_adapter.get_node(&child_id) else { continue; }; - if child_data.position.is_absolute() { - continue; + let mut adapted_available_area = *available_area; + + if parent_node.main_alignment.is_spaced() { + // Align the Main axis if necessary + Self::align_position( + AlignmentDirection::Main, + &mut adapted_available_area, + &initial_available_area, + &initial_phase_inner_sizes, + &parent_node.main_alignment, + &parent_node.direction, + initial_phase_sizes.len(), + child_n, + ); } - let inner_area = *initial_phase_mode.inner_area(); + if parent_node.cross_alignment.is_not_start() { + let initial_phase_size = initial_phase_sizes.get(&child_id); + + if let Some(initial_phase_size) = initial_phase_size { + // Align the Cross axis if necessary + Self::align_content( + &mut adapted_available_area, + available_area, + initial_phase_size, + &parent_node.cross_alignment, + &parent_node.direction, + AlignmentDirection::Cross, + ); + } + } - let (_, child_areas) = measure_node( - *child_id, + // Final measurement + let (child_revalidated, mut child_areas) = self.measure_node( + child_id, &child_data, - layout, - &inner_area, - &initial_phase_available_area, - measurer, - false, - dom_adapter, - layout_metadata, - invalidated_tree, - Phase::Initial, + inner_area, + &adapted_available_area, + must_cache_children, + parent_is_dirty, + Phase::Final, ); - initial_phase_mode.stack_into_node( + // Adjust the size of the area if needed + child_areas.area.adjust_size(&child_data); + + // Stack this child into the parent + Self::stack_child( + available_area, parent_node, - &mut initial_phase_available_area, + area, + inner_area, + inner_sizes, &child_areas.area, - &mut initial_phase_inner_sizes, &child_data, ); - if parent_node.cross_alignment.is_not_start() || parent_node.main_alignment.is_spaced() - { - initial_phase_sizes.insert(*child_id, child_areas.area.size); + // Cache the child layout if it was mutated and children must be cached + if child_revalidated && must_cache_children { + // In case of any layout listener, notify it with the new areas. + if child_data.has_layout_references { + if let Some(measurer) = self.measurer { + measurer.notify_layout_references(child_id, &child_areas); + } + } + + // Finally cache this node areas into Torin + self.layout.cache_node(child_id, child_areas); } } + } - if parent_node.main_alignment.is_not_start() { - // 2. Adjust the available and inner areas of the Main axis - initial_phase_mode.fit_bounds_when_unspecified( - parent_node, - AlignmentDirection::Main, - available_area, - ); - - // 3. Align the Main axis - available_area.align_content( - initial_phase_mode.inner_area(), - &initial_phase_inner_sizes, - &parent_node.main_alignment, - &parent_node.direction, - AlignmentDirection::Main, - ); + /// Align the content of this node. + fn align_content( + available_area: &mut Area, + inner_area: &Area, + contents_size: &Size2D, + alignment: &Alignment, + direction: &DirectionMode, + alignment_direction: AlignmentDirection, + ) { + let axis = AlignAxis::new(direction, alignment_direction); + + match axis { + AlignAxis::Height => match alignment { + Alignment::Center => { + let new_origin_y = (inner_area.height() / 2.0) - (contents_size.height / 2.0); + + available_area.origin.y = inner_area.min_y() + new_origin_y; + } + Alignment::End => { + available_area.origin.y = inner_area.max_y() - contents_size.height; + } + _ => {} + }, + AlignAxis::Width => match alignment { + Alignment::Center => { + let new_origin_x = (inner_area.width() / 2.0) - (contents_size.width / 2.0); + available_area.origin.x = inner_area.min_x() + new_origin_x; + } + Alignment::End => { + available_area.origin.x = inner_area.max_x() - contents_size.width; + } + _ => {} + }, } + } - if parent_node.cross_alignment.is_not_start() || parent_node.content.is_fit() { - // 4. Adjust the available and inner areas of the Cross axis - initial_phase_mode.fit_bounds_when_unspecified( - parent_node, - AlignmentDirection::Cross, - available_area, - ); + /// Align the position of this node. + #[allow(clippy::too_many_arguments)] + fn align_position( + alignment_direction: AlignmentDirection, + available_area: &mut Area, + initial_available_area: &Area, + inner_sizes: &Size2D, + alignment: &Alignment, + direction: &DirectionMode, + siblings_len: usize, + child_position: usize, + ) { + let axis = AlignAxis::new(direction, alignment_direction); + + match axis { + AlignAxis::Height => match alignment { + Alignment::SpaceBetween if child_position > 0 => { + let all_gaps_sizes = initial_available_area.height() - inner_sizes.height; + let gap_size = all_gaps_sizes / (siblings_len - 1) as f32; + available_area.origin.y += gap_size; + } + Alignment::SpaceEvenly => { + let all_gaps_sizes = initial_available_area.height() - inner_sizes.height; + let gap_size = all_gaps_sizes / (siblings_len + 1) as f32; + available_area.origin.y += gap_size; + } + Alignment::SpaceAround => { + let all_gaps_sizes = initial_available_area.height() - inner_sizes.height; + let one_gap_size = all_gaps_sizes / siblings_len as f32; + let gap_size = if child_position == 0 || child_position == siblings_len { + one_gap_size / 2. + } else { + one_gap_size + }; + available_area.origin.y += gap_size; + } + _ => {} + }, + AlignAxis::Width => match alignment { + Alignment::SpaceBetween if child_position > 0 => { + let all_gaps_sizes = initial_available_area.width() - inner_sizes.width; + let gap_size = all_gaps_sizes / (siblings_len - 1) as f32; + available_area.origin.x += gap_size; + } + Alignment::SpaceEvenly => { + let all_gaps_sizes = initial_available_area.width() - inner_sizes.width; + let gap_size = all_gaps_sizes / (siblings_len + 1) as f32; + available_area.origin.x += gap_size; + } + Alignment::SpaceAround => { + let all_gaps_sizes = initial_available_area.width() - inner_sizes.width; + let one_gap_size = all_gaps_sizes / siblings_len as f32; + let gap_size = if child_position == 0 || child_position == siblings_len { + one_gap_size / 2. + } else { + one_gap_size + }; + available_area.origin.x += gap_size; + } + _ => {} + }, } } - let initial_available_area = *available_area; - - // Final phase: measure the children with all the axis and sizes adjusted - for (child_n, child_id) in children.into_iter().enumerate() { - let Some(child_data) = dom_adapter.get_node(&child_id) else { - continue; - }; - - let mut adapted_available_area = *available_area; - - if parent_node.main_alignment.is_spaced() { - // Align the Main axis if necessary - adapted_available_area.align_position( - &initial_available_area, - &initial_phase_inner_sizes, - &parent_node.main_alignment, - &parent_node.direction, - AlignmentDirection::Main, - initial_phase_sizes.len(), - child_n, - ); + /// Stack a child Node into its parent + fn stack_child( + available_area: &mut Area, + parent_node: &Node, + parent_area: &mut Area, + inner_area: &mut Area, + inner_sizes: &mut Size2D, + child_area: &Area, + child_node: &Node, + ) { + // No need to stack a node that is positioned absolutely + if child_node.position.is_absolute() { + return; } - if parent_node.cross_alignment.is_not_start() { - let initial_phase_size = initial_phase_sizes.get(&child_id); + match parent_node.direction { + DirectionMode::Horizontal => { + // Move the available area + available_area.origin.x = child_area.max_x(); + available_area.size.width -= child_area.size.width; + + inner_sizes.height = child_area.height().max(inner_sizes.height); + inner_sizes.width += child_area.width(); + + // Keep the biggest height + if parent_node.height.inner_sized() { + parent_area.size.height = parent_area.size.height.max( + child_area.size.height + + parent_node.padding.vertical() + + parent_node.margin.vertical(), + ); + // Keep the inner area in sync + inner_area.size.height = parent_area.size.height + - parent_node.padding.vertical() + - parent_node.margin.vertical(); + } - if let Some(initial_phase_size) = initial_phase_size { - // Align the Cross axis if necessary - adapted_available_area.align_content( - available_area, - initial_phase_size, - &parent_node.cross_alignment, - &parent_node.direction, - AlignmentDirection::Cross, - ); + // Accumulate width + if parent_node.width.inner_sized() { + parent_area.size.width += child_area.size.width; + } } - } + DirectionMode::Vertical => { + // Move the available area + available_area.origin.y = child_area.max_y(); + available_area.size.height -= child_area.size.height; + + inner_sizes.width = child_area.width().max(inner_sizes.width); + inner_sizes.height += child_area.height(); + + // Keep the biggest width + if parent_node.width.inner_sized() { + parent_area.size.width = parent_area.size.width.max( + child_area.size.width + + parent_node.padding.horizontal() + + parent_node.margin.horizontal(), + ); + // Keep the inner area in sync + inner_area.size.width = parent_area.size.width + - parent_node.padding.horizontal() + - parent_node.margin.horizontal(); + } - let inner_area = *mode.inner_area(); - - // Final measurement - let (child_revalidated, mut child_areas) = measure_node( - child_id, - &child_data, - layout, - &inner_area, - &adapted_available_area, - measurer, - must_cache_inner_nodes, - dom_adapter, - layout_metadata, - invalidated_tree, - Phase::Final, - ); - - // Adjust the size of the area if needed - child_areas.area.adjust_size(&child_data); - - // Stack the child into its parent - mode.stack_into_node( - parent_node, - available_area, - &child_areas.area, - inner_sizes, - &child_data, - ); - - // Cache the child layout if it was mutated and inner nodes must be cache - if child_revalidated && must_cache_inner_nodes { - if let Some(measurer) = measurer { - if child_data.has_layout_references { - measurer.notify_layout_references(child_id, &child_areas); + // Accumulate height + if parent_node.height.inner_sized() { + parent_area.size.height += child_area.size.height; } } - layout.cache_node(child_id, child_areas); } } + + /// Shrink the available area and inner area of a parent node when for example height is set to "auto", + /// direction is vertical and main_alignment is set to "center" or "end" or the content is set to "fit". + /// The intended usage is to call this after the first measurement and before the second, + /// this way the second measurement will align the content relatively to the parent element instead + /// of overflowing due to being aligned relatively to the upper parent element + fn shrink_area_to_fit_when_unbounded( + available_area: &mut Area, + parent_area: &Area, + inner_area: &mut Area, + parent_node: &Node, + alignment_direction: AlignmentDirection, + ) { + struct NodeData<'a> { + pub inner_origin: &'a mut f32, + pub inner_size: &'a mut f32, + pub area_origin: f32, + pub area_size: f32, + pub one_side_padding: f32, + pub two_sides_padding: f32, + pub one_side_margin: f32, + pub two_sides_margin: f32, + pub available_size: &'a mut f32, + } + + let axis = AlignAxis::new(&parent_node.direction, alignment_direction); + let (is_vertical_not_start, is_horizontal_not_start) = match parent_node.direction { + DirectionMode::Vertical => ( + parent_node.main_alignment.is_not_start(), + parent_node.cross_alignment.is_not_start() || parent_node.content.is_fit(), + ), + DirectionMode::Horizontal => ( + parent_node.cross_alignment.is_not_start() || parent_node.content.is_fit(), + parent_node.main_alignment.is_not_start(), + ), + }; + let NodeData { + inner_origin, + inner_size, + area_origin, + area_size, + one_side_padding, + two_sides_padding, + one_side_margin, + two_sides_margin, + available_size, + } = match axis { + AlignAxis::Height if parent_node.height.inner_sized() && is_vertical_not_start => { + NodeData { + inner_origin: &mut inner_area.origin.y, + inner_size: &mut inner_area.size.height, + area_origin: parent_area.origin.y, + area_size: parent_area.size.height, + one_side_padding: parent_node.padding.top(), + two_sides_padding: parent_node.padding.vertical(), + one_side_margin: parent_node.margin.top(), + two_sides_margin: parent_node.margin.vertical(), + available_size: &mut available_area.size.height, + } + } + AlignAxis::Width if parent_node.width.inner_sized() && is_horizontal_not_start => { + NodeData { + inner_origin: &mut inner_area.origin.x, + inner_size: &mut inner_area.size.width, + area_origin: parent_area.origin.x, + area_size: parent_area.size.width, + one_side_padding: parent_node.padding.left(), + two_sides_padding: parent_node.padding.horizontal(), + one_side_margin: parent_node.margin.left(), + two_sides_margin: parent_node.margin.horizontal(), + available_size: &mut available_area.size.width, + } + } + _ => return, + }; + + // Set the origin of the inner area to the origin of the area plus the padding and margin for the given axis + *inner_origin = area_origin + one_side_padding + one_side_margin; + // Set the size of the inner area to the size of the area minus the padding and margin for the given axis + *inner_size = area_size - two_sides_padding - two_sides_margin; + // Set the same available size as the inner area for the given axis + *available_size = *inner_size; + } } diff --git a/crates/torin/src/measure_mode.rs b/crates/torin/src/measure_mode.rs deleted file mode 100644 index b2d7eef57..000000000 --- a/crates/torin/src/measure_mode.rs +++ /dev/null @@ -1,226 +0,0 @@ -use crate::prelude::{ - get_align_axis, - AlignAxis, - AlignmentDirection, - Area, - DirectionMode, - Node, - Size2D, -}; - -/// Measurement data for the inner Nodes of a Node -#[derive(Debug)] -pub enum MeasureMode<'a> { - ParentIsCached { - inner_area: &'a Area, - }, - ParentIsNotCached { - area: &'a mut Area, - inner_area: &'a mut Area, - }, -} - -impl<'a> MeasureMode<'a> { - /// Get a reference to the inner area - pub fn inner_area(&'a self) -> &'a Area { - match self { - Self::ParentIsCached { inner_area } => inner_area, - Self::ParentIsNotCached { inner_area, .. } => inner_area, - } - } - - /// Create an owned version of [MeasureMode] - pub fn to_owned(&self) -> OwnedMeasureMode { - match self { - MeasureMode::ParentIsCached { inner_area } => OwnedMeasureMode::ParentIsCached { - inner_area: *inner_area.to_owned(), - }, - MeasureMode::ParentIsNotCached { area, inner_area } => { - OwnedMeasureMode::ParentIsNotCached { - area: **area, - inner_area: **inner_area, - } - } - } - } - - /// This will fit the available area and inner area of a parent node when for example height is set to "auto", - /// direction is vertical and main_alignment is set to "center" or "end" or the content is set to "fit". - /// The intended usage is to call this after the first measurement and before the second, - /// this way the second measurement will align the content relatively to the parent element instead - /// of overflowing due to being aligned relatively to the upper parent element - pub fn fit_bounds_when_unspecified( - &mut self, - parent_node: &Node, - alignment_direction: AlignmentDirection, - available_area: &mut Area, - ) { - struct NodeData<'a> { - pub inner_origin: &'a mut f32, - pub inner_size: &'a mut f32, - pub area_origin: &'a mut f32, - pub area_size: &'a mut f32, - pub one_side_padding: f32, - pub two_sides_padding: f32, - pub one_side_margin: f32, - pub two_sides_margin: f32, - pub available_size: &'a mut f32, - } - - let axis = get_align_axis(&parent_node.direction, alignment_direction); - let (is_vertical_not_start, is_horizontal_not_start) = match parent_node.direction { - DirectionMode::Vertical => ( - parent_node.main_alignment.is_not_start(), - parent_node.cross_alignment.is_not_start() || parent_node.content.is_fit(), - ), - DirectionMode::Horizontal => ( - parent_node.cross_alignment.is_not_start() || parent_node.content.is_fit(), - parent_node.main_alignment.is_not_start(), - ), - }; - let params = if let MeasureMode::ParentIsNotCached { area, inner_area } = self { - match axis { - AlignAxis::Height if parent_node.height.inner_sized() && is_vertical_not_start => { - Some(NodeData { - inner_origin: &mut inner_area.origin.y, - inner_size: &mut inner_area.size.height, - area_origin: &mut area.origin.y, - area_size: &mut area.size.height, - one_side_padding: parent_node.padding.top(), - two_sides_padding: parent_node.padding.vertical(), - one_side_margin: parent_node.margin.top(), - two_sides_margin: parent_node.margin.vertical(), - available_size: &mut available_area.size.height, - }) - } - AlignAxis::Width if parent_node.width.inner_sized() && is_horizontal_not_start => { - Some(NodeData { - inner_origin: &mut inner_area.origin.x, - inner_size: &mut inner_area.size.width, - area_origin: &mut area.origin.x, - area_size: &mut area.size.width, - one_side_padding: parent_node.padding.left(), - two_sides_padding: parent_node.padding.horizontal(), - one_side_margin: parent_node.margin.left(), - two_sides_margin: parent_node.margin.horizontal(), - available_size: &mut available_area.size.width, - }) - } - _ => None, - } - } else { - None - }; - - if let Some(NodeData { - inner_origin, - inner_size, - area_origin, - area_size, - one_side_padding, - two_sides_padding, - one_side_margin, - two_sides_margin, - available_size, - }) = params - { - // Set the origin of the inner area to the origin of the area plus the padding and margin for the given axis - *inner_origin = *area_origin + one_side_padding + one_side_margin; - // Set the size of the inner area to the size of the area minus the padding and margin for the given axis - *inner_size = *area_size - two_sides_padding - two_sides_margin; - // Set the same available size as the inner area for the given axis - *available_size = *inner_size; - } - } - - /// Stack a Node into another Node - pub fn stack_into_node( - &mut self, - parent_node: &Node, - available_area: &mut Area, - content_area: &Area, - inner_sizes: &mut Size2D, - node_data: &Node, - ) { - if node_data.position.is_absolute() { - return; - } - - match parent_node.direction { - DirectionMode::Horizontal => { - // Move the available area - available_area.origin.x = content_area.max_x(); - available_area.size.width -= content_area.size.width; - - if let MeasureMode::ParentIsNotCached { area, inner_area } = self { - inner_sizes.height = content_area.height().max(inner_sizes.height); - inner_sizes.width += content_area.width(); - - // Keep the biggest height - if parent_node.height.inner_sized() { - area.size.height = area.size.height.max( - content_area.size.height - + parent_node.padding.vertical() - + parent_node.margin.vertical(), - ); - // Keep the inner area in sync - inner_area.size.height = area.size.height - - parent_node.padding.vertical() - - parent_node.margin.vertical(); - } - - // Accumulate width - if parent_node.width.inner_sized() { - area.size.width += content_area.size.width; - } - } - } - DirectionMode::Vertical => { - // Move the available area - available_area.origin.y = content_area.max_y(); - available_area.size.height -= content_area.size.height; - - if let MeasureMode::ParentIsNotCached { area, inner_area } = self { - inner_sizes.width = content_area.width().max(inner_sizes.width); - inner_sizes.height += content_area.height(); - - // Keep the biggest width - if parent_node.width.inner_sized() { - area.size.width = area.size.width.max( - content_area.size.width - + parent_node.padding.horizontal() - + parent_node.margin.horizontal(), - ); - // Keep the inner area in sync - inner_area.size.width = area.size.width - - parent_node.padding.horizontal() - - parent_node.margin.horizontal(); - } - - // Accumulate height - if parent_node.height.inner_sized() { - area.size.height += content_area.size.height; - } - } - } - } - } -} - -/// Just an owned version of [MeasureMode] -#[derive(Debug)] -pub enum OwnedMeasureMode { - ParentIsCached { inner_area: Area }, - ParentIsNotCached { area: Area, inner_area: Area }, -} - -impl OwnedMeasureMode { - pub fn to_mut(&mut self) -> MeasureMode<'_> { - match self { - Self::ParentIsCached { inner_area } => MeasureMode::ParentIsCached { inner_area }, - Self::ParentIsNotCached { area, inner_area } => { - MeasureMode::ParentIsNotCached { area, inner_area } - } - } - } -} diff --git a/crates/torin/src/torin.rs b/crates/torin/src/torin.rs index accb34be3..5d3ad4204 100644 --- a/crates/torin/src/torin.rs +++ b/crates/torin/src/torin.rs @@ -22,7 +22,7 @@ use crate::{ Size2D, }, measure::{ - measure_node, + MeasureContext, Phase, }, prelude::{ @@ -265,7 +265,7 @@ impl Torin { root_height ); - let metadata = LayoutMetadata { root_area }; + let layout_metadata = LayoutMetadata { root_area }; let mut available_area = layout_node.inner_area; if let Some(root_parent_id) = root_parent_id { @@ -273,16 +273,19 @@ impl Torin { available_area.move_with_offsets(&root_parent.offset_x, &root_parent.offset_y); } - let (root_revalidated, mut root_layout_node) = measure_node( + let mut measure_context = MeasureContext { + layout: self, + layout_metadata, + dom_adapter, + measurer, + }; + + let (root_revalidated, mut root_layout_node) = measure_context.measure_node( root_id, &root, - self, &layout_node.inner_area, &available_area, - measurer, true, - dom_adapter, - &metadata, false, Phase::Final, ); From 60fb267b463e00ac8cc07e9b58ac078d752ea96c Mon Sep 17 00:00:00 2001 From: marc2332 Date: Fri, 30 Aug 2024 19:37:08 +0200 Subject: [PATCH 10/12] chore: update popup example --- examples/popup.rs | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/examples/popup.rs b/examples/popup.rs index d3c0f4feb..43bf7b436 100644 --- a/examples/popup.rs +++ b/examples/popup.rs @@ -11,10 +11,14 @@ fn main() { fn app() -> Element { use_init_theme(|| DARK_THEME); + let mut value = use_signal(|| "Default text".to_string()); let mut show_popup = use_signal(|| false); rsx!( Body { + label { + "Value is: {value}" + } if *show_popup.read() { Popup { oncloserequest: move |_| { @@ -27,7 +31,21 @@ fn app() -> Element { } PopupContent { label { - "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua." + "Change the input value:" + } + Input { + value, + onchange: move |text| { + value.set(text); + } + } + Button { + onclick: move |_| { + show_popup.set(false) + }, + label { + "Submit" + } } } } From f8a0844e72e1c9ec9e3706b8643cc6b4f9e1795b Mon Sep 17 00:00:00 2001 From: Marc Espin Date: Fri, 30 Aug 2024 19:42:40 +0200 Subject: [PATCH 11/12] feat: `PluginHandle` (#793) * feat: `PluginHandle` * gamepad_focus * fix ci * install `libudev-sys` in CI * actually install `libudev-dev` in CI * chore: Clean up * chore: Doc comment * chore: fmt --- .github/workflows/rust.yml | 2 +- Cargo.toml | 1 + crates/common/Cargo.toml | 1 - crates/common/src/lib.rs | 2 - crates/components/src/native_container.rs | 2 +- crates/core/Cargo.toml | 1 + crates/core/src/dom/doms.rs | 6 +- crates/{common => core}/src/event_messages.rs | 4 + crates/core/src/lib.rs | 2 + crates/core/src/plugins.rs | 44 ++++- crates/core/src/skia/paragraph.rs | 2 +- crates/freya/src/lib.rs | 5 + .../freya/src/plugins/performance_overlay.rs | 11 +- crates/hooks/src/use_editable.rs | 4 +- crates/hooks/src/use_focus.rs | 2 +- crates/hooks/src/use_platform.rs | 2 +- crates/renderer/src/accessibility.rs | 2 +- crates/renderer/src/app.rs | 77 +++++--- crates/renderer/src/renderer.rs | 7 +- crates/renderer/src/window_state.rs | 6 +- crates/renderer/src/winit_waker.rs | 2 +- crates/testing/src/launch.rs | 6 +- crates/testing/src/test_handler.rs | 4 +- examples/gamepad_focus.rs | 115 ++++++++++++ examples/gamepad_trace.rs | 174 ++++++++++++++++++ examples/plugin.rs | 11 +- examples/render_canvas.rs | 2 +- 27 files changed, 434 insertions(+), 63 deletions(-) rename crates/{common => core}/src/event_messages.rs (91%) create mode 100644 examples/gamepad_focus.rs create mode 100644 examples/gamepad_trace.rs diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index d894b588e..6705e1a46 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -33,7 +33,7 @@ jobs: - name: Install linux dependencies if: runner.os == 'Linux' run: | - sudo apt update && sudo apt install build-essential libssl-dev pkg-config libglib2.0-dev libgtk-3-dev + sudo apt update && sudo apt install build-essential libssl-dev pkg-config libglib2.0-dev libgtk-3-dev libudev-dev - name: Check examples run: cargo check --examples - name: Lint diff --git a/Cargo.toml b/Cargo.toml index 729b40ac2..3201fec4d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -87,6 +87,7 @@ dioxus-router = { workspace = true } itertools = "0.13.0" home = "0.5.9" dioxus-query = "0.5.1" +gilrs = "0.10.8" gl = { workspace = true } [profile.release] diff --git a/crates/common/Cargo.toml b/crates/common/Cargo.toml index dcdea4549..24493b798 100644 --- a/crates/common/Cargo.toml +++ b/crates/common/Cargo.toml @@ -23,7 +23,6 @@ torin = { workspace = true } dioxus-core = { workspace = true } accesskit = { workspace = true } -accesskit_winit = { workspace = true } winit = { workspace = true } freya-engine = { workspace = true } freya-native-core = { workspace = true } diff --git a/crates/common/src/lib.rs b/crates/common/src/lib.rs index 19513673d..1e1fc030f 100644 --- a/crates/common/src/lib.rs +++ b/crates/common/src/lib.rs @@ -1,9 +1,7 @@ -mod event_messages; mod layers; mod layout; mod paragraphs; -pub use event_messages::*; pub use layers::*; pub use layout::*; pub use paragraphs::*; diff --git a/crates/components/src/native_container.rs b/crates/components/src/native_container.rs index d1a441c99..94034a2ac 100644 --- a/crates/components/src/native_container.rs +++ b/crates/components/src/native_container.rs @@ -1,5 +1,5 @@ use dioxus::prelude::*; -use freya_common::EventMessage; +use freya_core::prelude::EventMessage; use freya_elements::{ elements as dioxus_elements, events::KeyboardEvent, diff --git a/crates/core/Cargo.toml b/crates/core/Cargo.toml index 61be210bf..e25259a6f 100644 --- a/crates/core/Cargo.toml +++ b/crates/core/Cargo.toml @@ -31,6 +31,7 @@ dioxus-core = { workspace = true } tokio = { workspace = true } winit = { workspace = true } accesskit = { workspace = true } +accesskit_winit = { workspace = true } rustc-hash = { workspace = true } tracing = { workspace = true } diff --git a/crates/core/src/dom/doms.rs b/crates/core/src/dom/doms.rs index cbf64c31e..b64663a1a 100644 --- a/crates/core/src/dom/doms.rs +++ b/crates/core/src/dom/doms.rs @@ -8,7 +8,6 @@ use dioxus_core::VirtualDom; use freya_common::{ Layers, ParagraphElements, - TextGroupMeasurement, }; use freya_native_core::{ prelude::{ @@ -38,7 +37,10 @@ use torin::prelude::*; use tracing::info; use super::mutations_writer::MutationsWriter; -use crate::prelude::measure_paragraph; +use crate::prelude::{ + measure_paragraph, + TextGroupMeasurement, +}; pub type DioxusDOM = RealDom; pub type DioxusNode<'a> = NodeRef<'a, CustomAttributeValues>; diff --git a/crates/common/src/event_messages.rs b/crates/core/src/event_messages.rs similarity index 91% rename from crates/common/src/event_messages.rs rename to crates/core/src/event_messages.rs index e9aad5a8d..16ce98cea 100644 --- a/crates/common/src/event_messages.rs +++ b/crates/core/src/event_messages.rs @@ -6,6 +6,8 @@ use winit::window::{ Window, }; +use crate::prelude::PlatformEvent; + pub struct TextGroupMeasurement { pub text_id: Uuid, pub cursor_id: usize, @@ -39,6 +41,8 @@ pub enum EventMessage { ExitApp, /// Callback to access the Window. WithWindow(Box), + /// Raw platform event, this are low level events. + PlatformEvent(PlatformEvent), } impl From for EventMessage { diff --git a/crates/core/src/lib.rs b/crates/core/src/lib.rs index 490c79c81..c28513d1b 100644 --- a/crates/core/src/lib.rs +++ b/crates/core/src/lib.rs @@ -1,6 +1,7 @@ pub mod accessibility; pub mod dom; pub mod elements; +pub mod event_messages; pub mod events; pub mod layout; pub mod node; @@ -16,6 +17,7 @@ pub mod prelude { accessibility::*, dom::*, elements::*, + event_messages::*, events::*, layout::*, node::*, diff --git a/crates/core/src/plugins.rs b/crates/core/src/plugins.rs index 9455a7809..b5c36898d 100644 --- a/crates/core/src/plugins.rs +++ b/crates/core/src/plugins.rs @@ -4,9 +4,43 @@ use freya_engine::prelude::{ }; use freya_native_core::NodeId; use torin::torin::Torin; -use winit::window::Window; +use winit::{ + event_loop::EventLoopProxy, + window::Window, +}; + +use crate::{ + dom::FreyaDOM, + prelude::{ + EventMessage, + PlatformEvent, + }, +}; -use crate::dom::FreyaDOM; +#[derive(Clone)] +pub struct PluginHandle { + pub proxy: EventLoopProxy, +} + +impl PluginHandle { + pub fn new(proxy: &EventLoopProxy) -> Self { + Self { + proxy: proxy.clone(), + } + } + + /// Emit a [PlatformEvent]. Useful to simulate certain events. + pub fn send_platform_event(&self, event: PlatformEvent) { + self.proxy + .send_event(EventMessage::PlatformEvent(event)) + .ok(); + } + + /// Emit a [EventMessage]. + pub fn send_event_loop_event(&self, event: EventMessage) { + self.proxy.send_event(event).ok(); + } +} /// Manages all loaded plugins. #[derive(Default)] @@ -19,9 +53,9 @@ impl PluginsManager { self.plugins.push(Box::new(plugin)) } - pub fn send(&mut self, event: PluginEvent) { + pub fn send(&mut self, event: PluginEvent, handle: PluginHandle) { for plugin in &mut self.plugins { - plugin.on_event(&event) + plugin.on_event(&event, handle.clone()) } } } @@ -59,5 +93,5 @@ pub enum PluginEvent<'a> { /// Skeleton for Freya plugins. pub trait FreyaPlugin { /// React on events emitted by Freya. - fn on_event(&mut self, event: &PluginEvent); + fn on_event(&mut self, event: &PluginEvent, handle: PluginHandle); } diff --git a/crates/core/src/skia/paragraph.rs b/crates/core/src/skia/paragraph.rs index ded717dd0..84af671e1 100644 --- a/crates/core/src/skia/paragraph.rs +++ b/crates/core/src/skia/paragraph.rs @@ -3,7 +3,6 @@ use std::ops::Mul; use freya_common::{ CachedParagraph, CursorLayoutResponse, - TextGroupMeasurement, }; use freya_native_core::prelude::NodeImmutable; use freya_node_state::CursorState; @@ -15,6 +14,7 @@ use torin::prelude::{ use crate::prelude::{ align_main_align_paragraph, DioxusNode, + TextGroupMeasurement, }; /// Merasure the cursor positio and text selection and notify the subscribed component of the element. diff --git a/crates/freya/src/lib.rs b/crates/freya/src/lib.rs index cf2aab0e5..641caff6c 100644 --- a/crates/freya/src/lib.rs +++ b/crates/freya/src/lib.rs @@ -77,6 +77,11 @@ pub mod common { pub use freya_common::*; } +/// Core APIs. +pub mod core { + pub use freya_core::*; +} + /// Elements, attributes and events definitions. pub use freya_elements::elements; /// Events data. diff --git a/crates/freya/src/plugins/performance_overlay.rs b/crates/freya/src/plugins/performance_overlay.rs index c50257d97..11d257808 100644 --- a/crates/freya/src/plugins/performance_overlay.rs +++ b/crates/freya/src/plugins/performance_overlay.rs @@ -3,9 +3,12 @@ use std::time::{ Instant, }; -use freya_core::plugins::{ - FreyaPlugin, - PluginEvent, +use freya_core::{ + plugins::{ + FreyaPlugin, + PluginEvent, + }, + prelude::PluginHandle, }; use freya_engine::prelude::{ Color, @@ -35,7 +38,7 @@ pub struct PerformanceOverlayPlugin { } impl FreyaPlugin for PerformanceOverlayPlugin { - fn on_event(&mut self, event: &PluginEvent) { + fn on_event(&mut self, event: &PluginEvent, _handle: PluginHandle) { match event { PluginEvent::StartedLayout(_) => self.started_layout = Some(Instant::now()), PluginEvent::FinishedLayout(_) => { diff --git a/crates/hooks/src/use_editable.rs b/crates/hooks/src/use_editable.rs index 41a9d7d92..8ecfc4ad0 100644 --- a/crates/hooks/src/use_editable.rs +++ b/crates/hooks/src/use_editable.rs @@ -11,8 +11,8 @@ use dioxus_signals::{ Signal, Writable, }; -use freya_common::{ - CursorLayoutResponse, +use freya_common::CursorLayoutResponse; +use freya_core::prelude::{ EventMessage, TextGroupMeasurement, }; diff --git a/crates/hooks/src/use_focus.rs b/crates/hooks/src/use_focus.rs index 715784cfa..53c44b4f6 100644 --- a/crates/hooks/src/use_focus.rs +++ b/crates/hooks/src/use_focus.rs @@ -12,10 +12,10 @@ use dioxus_signals::{ Signal, Writable, }; -use freya_common::EventMessage; use freya_core::{ accessibility::ACCESSIBILITY_ROOT_ID, platform_state::NavigationMode, + prelude::EventMessage, types::AccessibilityId, }; use freya_elements::events::{ diff --git a/crates/hooks/src/use_platform.rs b/crates/hooks/src/use_platform.rs index a1d57f272..1b4c29a3f 100644 --- a/crates/hooks/src/use_platform.rs +++ b/crates/hooks/src/use_platform.rs @@ -9,7 +9,7 @@ use dioxus_signals::{ Readable, Signal, }; -use freya_common::EventMessage; +use freya_core::prelude::EventMessage; use tokio::sync::{ broadcast, mpsc::UnboundedSender, diff --git a/crates/renderer/src/accessibility.rs b/crates/renderer/src/accessibility.rs index 9bef78c53..9df07ca4d 100644 --- a/crates/renderer/src/accessibility.rs +++ b/crates/renderer/src/accessibility.rs @@ -1,9 +1,9 @@ use accesskit_winit::Adapter; -use freya_common::EventMessage; use freya_core::{ prelude::{ AccessibilityFocusDirection, AccessibilityManager, + EventMessage, SharedAccessibilityManager, ACCESSIBILITY_ROOT_ID, }, diff --git a/crates/renderer/src/app.rs b/crates/renderer/src/app.rs index 007034eb6..1bc09f1fb 100644 --- a/crates/renderer/src/app.rs +++ b/crates/renderer/src/app.rs @@ -4,10 +4,6 @@ use dioxus_core::{ Template, VirtualDom, }; -use freya_common::{ - EventMessage, - TextGroupMeasurement, -}; use freya_core::prelude::*; use freya_engine::prelude::*; use freya_native_core::{ @@ -76,7 +72,7 @@ impl Application { devtools: Option, window: &Window, fonts_config: EmbeddedFonts, - mut plugins: PluginsManager, + plugins: PluginsManager, default_fonts: Vec, ) -> Self { let accessibility = AccessKitManager::new(window, proxy.clone()); @@ -104,9 +100,7 @@ impl Application { scale_factor: window.scale_factor(), }); - plugins.send(PluginEvent::WindowCreated(window)); - - Self { + let mut app = Self { sdom, vdom, events: EventsQueue::new(), @@ -126,7 +120,14 @@ impl Application { measure_layout_on_next_render: false, default_fonts, queued_focus_node: None, - } + }; + + app.plugins.send( + PluginEvent::WindowCreated(window), + PluginHandle::new(&app.proxy), + ); + + app } /// Provide the launch state and few other utilities like the EventLoopProxy @@ -144,24 +145,36 @@ impl Application { /// Make the first build of the VirtualDOM and sync it with the RealDOM. pub fn init_doms(&mut self, scale_factor: f32, app_state: Option) { - self.plugins.send(PluginEvent::StartedUpdatingDOM); + self.plugins.send( + PluginEvent::StartedUpdatingDOM, + PluginHandle::new(&self.proxy), + ); self.provide_vdom_contexts(app_state); self.sdom.get_mut().init_dom(&mut self.vdom, scale_factor); - self.plugins.send(PluginEvent::FinishedUpdatingDOM); + self.plugins.send( + PluginEvent::FinishedUpdatingDOM, + PluginHandle::new(&self.proxy), + ); } /// Update the RealDOM, layout and others with the latest changes from the VirtualDOM pub fn render_mutations(&mut self, scale_factor: f32) -> (bool, bool) { - self.plugins.send(PluginEvent::StartedUpdatingDOM); + self.plugins.send( + PluginEvent::StartedUpdatingDOM, + PluginHandle::new(&self.proxy), + ); let (repaint, relayout) = self .sdom .get_mut() .render_mutations(&mut self.vdom, scale_factor); - self.plugins.send(PluginEvent::FinishedUpdatingDOM); + self.plugins.send( + PluginEvent::FinishedUpdatingDOM, + PluginHandle::new(&self.proxy), + ); if repaint { if let Some(devtools) = &self.devtools { @@ -264,11 +277,14 @@ impl Application { /// Render the App into the Window Canvas pub fn render(&mut self, hovered_node: &HoveredNode, canvas: &Canvas, window: &Window) { - self.plugins.send(PluginEvent::BeforeRender { - canvas, - font_collection: &self.font_collection, - freya_dom: &self.sdom.get(), - }); + self.plugins.send( + PluginEvent::BeforeRender { + canvas, + font_collection: &self.font_collection, + freya_dom: &self.sdom.get(), + }, + PluginHandle::new(&self.proxy), + ); self.start_render( hovered_node, @@ -280,11 +296,14 @@ impl Application { self.accessibility .render_accessibility(window.title().as_str()); - self.plugins.send(PluginEvent::AfterRender { - canvas, - font_collection: &self.font_collection, - freya_dom: &self.sdom.get(), - }); + self.plugins.send( + PluginEvent::AfterRender { + canvas, + font_collection: &self.font_collection, + freya_dom: &self.sdom.get(), + }, + PluginHandle::new(&self.proxy), + ); } /// Resize the Window @@ -336,8 +355,10 @@ impl Application { { let fdom = self.sdom.get(); - self.plugins - .send(PluginEvent::StartedLayout(&fdom.layout())); + self.plugins.send( + PluginEvent::StartedLayout(&fdom.layout()), + PluginHandle::new(&self.proxy), + ); process_layout( &fdom, @@ -350,8 +371,10 @@ impl Application { &self.default_fonts, ); - self.plugins - .send(PluginEvent::FinishedLayout(&fdom.layout())); + self.plugins.send( + PluginEvent::FinishedLayout(&fdom.layout()), + PluginHandle::new(&self.proxy), + ); } if let Some(devtools) = &self.devtools { diff --git a/crates/renderer/src/renderer.rs b/crates/renderer/src/renderer.rs index 97462bdba..1c412b04b 100644 --- a/crates/renderer/src/renderer.rs +++ b/crates/renderer/src/renderer.rs @@ -4,7 +4,6 @@ use std::{ }; use dioxus_core::VirtualDom; -use freya_common::EventMessage; use freya_core::{ accessibility::AccessibilityFocusDirection, dom::SafeDOM, @@ -12,7 +11,10 @@ use freya_core::{ EventName, PlatformEvent, }, - prelude::NavigationMode, + prelude::{ + EventMessage, + NavigationMode, + }, }; use freya_elements::events::{ map_winit_key, @@ -218,6 +220,7 @@ impl<'a, State: Clone> ApplicationHandler for DesktopRenderer<'a, app.queue_focus_node(node_id); } EventMessage::ExitApp => event_loop.exit(), + EventMessage::PlatformEvent(platform_event) => self.send_event(platform_event), ev => { if let EventMessage::UpdateTemplate(template) = ev { app.vdom_replace_template(template); diff --git a/crates/renderer/src/window_state.rs b/crates/renderer/src/window_state.rs index 95e73765e..d6a65c7fb 100644 --- a/crates/renderer/src/window_state.rs +++ b/crates/renderer/src/window_state.rs @@ -5,8 +5,10 @@ use std::{ }; use dioxus_core::VirtualDom; -use freya_common::EventMessage; -use freya_core::dom::SafeDOM; +use freya_core::{ + dom::SafeDOM, + prelude::EventMessage, +}; use freya_engine::prelude::*; use gl::{ types::*, diff --git a/crates/renderer/src/winit_waker.rs b/crates/renderer/src/winit_waker.rs index 1c3a205d6..a87917673 100644 --- a/crates/renderer/src/winit_waker.rs +++ b/crates/renderer/src/winit_waker.rs @@ -1,6 +1,6 @@ use std::sync::Arc; -use freya_common::EventMessage; +use freya_core::prelude::EventMessage; use futures_task::{ waker, ArcWake, diff --git a/crates/testing/src/launch.rs b/crates/testing/src/launch.rs index 62d9bccd1..bf67dbf8b 100644 --- a/crates/testing/src/launch.rs +++ b/crates/testing/src/launch.rs @@ -4,9 +4,11 @@ use dioxus_core::{ VirtualDom, }; use dioxus_core_macro::rsx; -use freya_common::EventMessage; use freya_components::NativeContainer; -use freya_core::prelude::*; +use freya_core::prelude::{ + EventMessage, + *, +}; use freya_engine::prelude::*; use tokio::sync::{ broadcast, diff --git a/crates/testing/src/test_handler.rs b/crates/testing/src/test_handler.rs index 4cbb8d66a..1b9e1757d 100644 --- a/crates/testing/src/test_handler.rs +++ b/crates/testing/src/test_handler.rs @@ -7,11 +7,11 @@ use std::{ }; use dioxus_core::VirtualDom; -use freya_common::{ +use freya_core::prelude::{ EventMessage, TextGroupMeasurement, + *, }; -use freya_core::prelude::*; use freya_engine::prelude::{ raster_n32_premul, Color, diff --git a/examples/gamepad_focus.rs b/examples/gamepad_focus.rs new file mode 100644 index 000000000..eb436e691 --- /dev/null +++ b/examples/gamepad_focus.rs @@ -0,0 +1,115 @@ +#![cfg_attr( + all(not(debug_assertions), target_os = "windows"), + windows_subsystem = "windows" +)] + +use std::thread; + +use freya::prelude::*; +use freya_core::prelude::{ + EventMessage, + EventName, + FreyaPlugin, + PlatformEvent, + PluginEvent, + PluginHandle, +}; +use gilrs::{ + EventType, + Gilrs, +}; + +fn main() { + launch_cfg( + app, + LaunchConfig::<()>::new().with_plugin(GamePadPlugin::default()), + ) +} + +#[derive(Default)] +pub struct GamePadPlugin; + +impl GamePadPlugin { + pub fn listen_gamepad(handle: PluginHandle) { + thread::spawn(move || { + println!("Listening for gamepads"); + + let mut gilrs_instance = Gilrs::new().unwrap(); + + 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(), + }); + } + _ => {} + } + } + _ => {} + } + } + } + }); + } +} + +impl FreyaPlugin for GamePadPlugin { + fn on_event(&mut self, event: &PluginEvent, handle: PluginHandle) { + match event { + PluginEvent::WindowCreated(_) => { + Self::listen_gamepad(handle); + } + _ => {} + } + } +} + +fn app() -> Element { + let mut count = use_signal(|| 0); + let mut enabled = use_signal(|| true); + + rsx!( + rect { + height: "fill", + width: "fill", + main_align: "center", + cross_align: "center", + Button { + onpress: move |_| count += 1, + label { + "Increase -> {count}" + } + } + Switch { + enabled: *enabled.read(), + ontoggled: move |_| { + enabled.toggle(); + } + } + Button { + onpress: move |_| count -= 1, + label { + "Decrease -> {count}" + } + } + } + ) +} diff --git a/examples/gamepad_trace.rs b/examples/gamepad_trace.rs new file mode 100644 index 000000000..512fb93ef --- /dev/null +++ b/examples/gamepad_trace.rs @@ -0,0 +1,174 @@ +#![cfg_attr( + all(not(debug_assertions), target_os = "windows"), + windows_subsystem = "windows" +)] + +use std::{ + thread::{ + self, + sleep, + }, + time::Duration, +}; + +use freya::prelude::*; +use freya_core::prelude::{ + EventName, + FreyaPlugin, + PlatformEvent, + PluginEvent, + PluginHandle, +}; +use gilrs::{ + Axis, + EventType, + Gilrs, +}; + +fn main() { + launch_cfg( + app, + LaunchConfig::<()>::new().with_plugin(GamePadPlugin::default()), + ) +} + +#[derive(Default)] +pub struct GamePadPlugin; + +impl GamePadPlugin { + pub fn listen_gamepad(handle: PluginHandle) { + thread::spawn(move || { + println!("Listening for gamepads"); + + let mut gilrs = Gilrs::new().unwrap(); + + loop { + let (mut x, mut y) = (200.0f64, 200.0f64); + let (mut diff_x, mut diff_y) = (0., 0.); + + let mut event = gilrs.next_event(); + while let Some(ev) = event { + loop { + sleep(Duration::from_millis(16)); + match ev.event { + EventType::AxisChanged(Axis::LeftStickX, diff, _) => { + diff_x = diff as f64; + } + EventType::AxisChanged(Axis::LeftStickY, diff, _) => { + diff_y = diff as f64; + } + _ => {} + } + + if diff_x != 0.0 { + x += diff_x as f64 * 10.; + handle.send_platform_event(PlatformEvent::Mouse { + name: EventName::MouseOver, + cursor: (x, y).into(), + button: None, + }); + } + + if diff_x != 0.0 { + y -= diff_y as f64 * 10.; + handle.send_platform_event(PlatformEvent::Mouse { + name: EventName::MouseOver, + cursor: (x, y).into(), + button: None, + }); + } + + let new_event = gilrs.next_event(); + + if let Some(new_ev) = new_event { + if new_ev != ev { + event = new_event; + break; + } + } + } + } + } + }); + } +} + +impl FreyaPlugin for GamePadPlugin { + fn on_event(&mut self, event: &PluginEvent, handle: PluginHandle) { + match event { + PluginEvent::WindowCreated(_) => { + Self::listen_gamepad(handle); + } + _ => {} + } + } +} + +const MOVEMENT_MARGIN: f64 = 75.0; +const BOX_COUNT: usize = 80; + +#[allow(non_snake_case)] +fn Box() -> Element { + rsx!( + rect { + background: "rgb(65, 53, 67)", + width: "250", + height: "250", + main_align: "center", + cross_align: "center", + corner_radius: "100", + rect { + background: "rgb(143, 67, 238)", + width: "180", + height: "180", + main_align: "center", + cross_align: "center", + corner_radius: "100", + rect { + background: "rgb(240, 235, 141)", + width: "100", + height: "100", + corner_radius: "100", + } + } + } + ) +} + +fn app() -> Element { + let mut positions = use_signal::>(Vec::new); + + let onmouseover = move |e: MouseEvent| { + let coordinates = e.get_screen_coordinates(); + positions.with_mut(|positions| { + if let Some(pos) = positions.first() { + if (pos.x + MOVEMENT_MARGIN < coordinates.x + && pos.x - MOVEMENT_MARGIN > coordinates.x) + && (pos.y + MOVEMENT_MARGIN < coordinates.y + && pos.y - MOVEMENT_MARGIN > coordinates.y) + { + return; + } + } + positions.insert(0, (coordinates.x - 125.0, coordinates.y - 125.0).into()); + positions.truncate(BOX_COUNT); + }) + }; + + rsx!( + rect { + onmouseover, + width: "100%", + height: "100%", + {positions.read().iter().map(|pos| rsx!( + rect { + width: "0", + height: "0", + offset_x: "{pos.x}", + offset_y: "{pos.y}", + Box {} + } + ))} + } + ) +} diff --git a/examples/plugin.rs b/examples/plugin.rs index c2849d380..3be516e87 100644 --- a/examples/plugin.rs +++ b/examples/plugin.rs @@ -4,15 +4,18 @@ )] use freya::prelude::*; -use freya_core::plugins::{ - FreyaPlugin, - PluginEvent, +use freya_core::{ + plugins::{ + FreyaPlugin, + PluginEvent, + }, + prelude::PluginHandle, }; struct DummyPlugin; impl FreyaPlugin for DummyPlugin { - fn on_event(&mut self, event: &PluginEvent) { + fn on_event(&mut self, event: &PluginEvent, handle: PluginHandle) { if let PluginEvent::AfterRender { .. } = event { println!("The app just got rendered to the canvas."); } diff --git a/examples/render_canvas.rs b/examples/render_canvas.rs index 0b96d4767..fd470b6a7 100644 --- a/examples/render_canvas.rs +++ b/examples/render_canvas.rs @@ -4,7 +4,7 @@ )] use freya::{ - common::EventMessage, + core::prelude::EventMessage, prelude::*, }; use skia_safe::{ From 0fb666191c3caf4090f5ab6d8f9f7686795bcde1 Mon Sep 17 00:00:00 2001 From: marc2332 Date: Sat, 31 Aug 2024 11:17:40 +0200 Subject: [PATCH 12/12] fix: Better input click handling --- crates/components/src/input.rs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/crates/components/src/input.rs b/crates/components/src/input.rs index 3679e4345..c53a33aac 100644 --- a/crates/components/src/input.rs +++ b/crates/components/src/input.rs @@ -106,7 +106,9 @@ pub fn Input( ); let theme = use_applied_theme!(&theme, input); let mut focus = use_focus(); - let display_placeholder = value.is_empty() && placeholder.is_some() && !focus.is_focused(); + + let is_focused = focus.is_focused(); + let display_placeholder = value.is_empty() && placeholder.is_some() && !is_focused; if &value != editable.editor().read().rope() { editable.editor_mut().write().set(&value); @@ -119,14 +121,14 @@ pub fn Input( }); let onkeydown = move |e: Event| { - if focus.is_focused() && e.data.key != Key::Enter { + if is_focused && e.data.key != Key::Enter { editable.process_event(&EditableEvent::KeyDown(e.data)); onchange.call(editable.editor().peek().to_string()); } }; let onkeyup = move |e: Event| { - if focus.is_focused() { + if is_focused { editable.process_event(&EditableEvent::KeyUp(e.data)); } }; @@ -155,6 +157,7 @@ pub fn Input( let onglobalclick = move |_| match *status.read() { InputStatus::Idle if focus.is_focused() => { focus.unfocus(); + editable.process_event(&EditableEvent::Click); } InputStatus::Hovering => { editable.process_event(&EditableEvent::Click);