diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index bdf29eab6..1d6168c4f 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -52,3 +52,6 @@ jobs: if: runner.os == 'Linux' with: token: ${{ secrets.CODECOV_TOKEN }} + - name: Make sure the mocked Skia bindings are on sync + if: runner.os == 'Linux' + run: cargo build --package freya --features mocked-engine-development --no-default-features diff --git a/.vscode/settings.json b/.vscode/settings.json index 27cc6116c..d01413707 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,6 +1,7 @@ { "rust-analyzer.cargo.features": [ "devtools", - "log" + "log", + "use_camera" ] } \ No newline at end of file diff --git a/Cargo.toml b/Cargo.toml index 5e5abe267..d76472d74 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -37,21 +37,21 @@ dioxus-core = { version = "0.4" } dioxus-hot-reload = { version = "0.4", features = ["file_watcher"] } dioxus-router = { version = "0.4", default-features = false } -skia-safe = { version = "0.66.3", features = ["gl", "textlayout", "svg"] } +skia-safe = { version = "0.67.0", features = ["gl", "textlayout", "svg"] } gl = "0.14.0" -glutin = "0.30.6" +glutin = "0.30.10" glutin-winit = "0.3.0" raw-window-handle = "0.5.1" -winit = "0.28.2" -tokio = { version = "1.23.0", features = ["sync", "rt-multi-thread", "time", "macros"] } +winit = "0.28.7" +tokio = { version = "1.33.0", features = ["sync", "rt-multi-thread", "time", "macros"] } accesskit = { version = "0.11.0", features = ["serde"]} accesskit_winit = "0.14.1" -zbus = "3.13.1" +zbus = "3.14.1" -euclid = "0.22.7" -uuid = { version = "1.2.2", features = ["v4"]} -futures = "0.3.25" +euclid = "0.22.9" +uuid = { version = "1.4.1", features = ["v4"]} +futures = "0.3.28" anymap = "0.12.1" tracing = "0.1" tracing-subscriber = "0.3.17" @@ -64,9 +64,9 @@ tokio = { workspace = true } dioxus = { workspace = true } freya = { workspace = true } freya-node-state = { workspace = true } -reqwest = { version = "0.11.13", features = ["json"] } -serde = "1.0.152" -tracing-subscriber = "0.2.25" +reqwest = { version = "0.11.22", features = ["json"] } +serde = "1.0.189" +tracing-subscriber = "0.3.17" dioxus-std = { version = "0.4", features = ["utils", "i18n"] } rand = "0.8.5" dioxus-router = { workspace = true } diff --git a/crates/components/Cargo.toml b/crates/components/Cargo.toml index b38aa9f5a..839fda116 100644 --- a/crates/components/Cargo.toml +++ b/crates/components/Cargo.toml @@ -34,7 +34,7 @@ tokio = { workspace = true } tracing = { workspace = true } open = "5" -reqwest = { version = "0.11.13", features = ["json"] } +reqwest = { version = "0.11.22", features = ["json"] } [dev-dependencies] freya = { path = "../freya" } diff --git a/crates/components/src/input.rs b/crates/components/src/input.rs index 29352ee6d..ec78aef21 100644 --- a/crates/components/src/input.rs +++ b/crates/components/src/input.rs @@ -84,6 +84,12 @@ pub fn Input<'a>(cx: Scope<'a, InputProps<'a>>) -> Element { let theme = use_get_theme(cx); let focus_manager = use_focus(cx); + if &cx.props.value != editable.editor().current().rope() { + editable.editor().with_mut(|editor| { + editor.set(&cx.props.value); + }); + } + let text = match cx.props.hidden { InputMode::Hidden(ch) => ch.to_string().repeat(cx.props.value.len()), InputMode::Shown => cx.props.value.clone(), @@ -94,15 +100,6 @@ pub fn Input<'a>(cx: Scope<'a, InputProps<'a>>) -> Element { let height = &cx.props.height; let max_lines = &cx.props.max_lines; - use_memo(cx, &(cx.props.value.to_string(),), { - to_owned![editable]; - move |(text,)| { - editable.editor().with_mut(|editor| { - editor.set(&text); - }); - } - }); - let onkeydown = { to_owned![editable, focus_manager]; move |e: Event| { diff --git a/crates/elements/Cargo.toml b/crates/elements/Cargo.toml index da9a636d4..ba0f6e41d 100644 --- a/crates/elements/Cargo.toml +++ b/crates/elements/Cargo.toml @@ -25,4 +25,4 @@ winit = { workspace = true } tokio = { workspace = true } accesskit = { workspace = true } -keyboard-types = "0.6.2" +keyboard-types = "0.7.0" diff --git a/crates/engine/src/mocked.rs b/crates/engine/src/mocked.rs index 6eb0e1690..b8e3eca08 100644 --- a/crates/engine/src/mocked.rs +++ b/crates/engine/src/mocked.rs @@ -2,6 +2,7 @@ #![allow(non_upper_case_globals)] #![allow(clippy::upper_case_acronyms)] #![allow(non_camel_case_types)] +#![allow(unused_variables)] use std::ops::*; @@ -167,7 +168,7 @@ impl Matrix { unimplemented!("This is mocked") } - pub fn set_rotate(&self, _degrees: f32, _pivot: impl Into>) -> &mut Self { + pub fn set_rotate(&mut self, _degrees: f32, _pivot: impl Into>) -> &mut Self { unimplemented!("This is mocked") } } @@ -420,7 +421,7 @@ impl TextStyle { unimplemented!("This is mocked") } - pub fn decoration_mut(&mut self) -> &mut Decoration { + pub fn set_decoration(&mut self, decoration: &Decoration) { unimplemented!("This is mocked") } @@ -708,7 +709,7 @@ impl Paragraph { unimplemented!("This is mocked") } - pub fn paint(&self, _canvas: &mut Canvas, _p: impl Into) { + pub fn paint(&self, _canvas: &Canvas, _p: impl Into) { unimplemented!("This is mocked") } @@ -947,43 +948,43 @@ impl Canvas { unimplemented!("This is mocked") } - pub fn concat(&mut self, _matrix: &Matrix) { + pub fn concat(&self, _matrix: &Matrix) { unimplemented!("This is mocked") } - pub fn clip_rect(&mut self, _rect: Rect, _clip: ClipOp, _: bool) { + pub fn clip_rect(&self, _rect: Rect, _clip: ClipOp, _: bool) { unimplemented!("This is mocked") } pub fn draw_image_nine( - &mut self, + &self, _image: Image, _center: IRect, _dst: Rect, _filter_mode: FilterMode, _paint: Option<&Paint>, - ) -> &mut Self { + ) -> &Self { unimplemented!("This is mocked") } - pub fn draw_rect(&mut self, _rect: Rect, _paint: &Paint) -> &mut Self { + pub fn draw_rect(&self, _rect: Rect, _paint: &Paint) -> &Self { unimplemented!("This is mocked") } - pub fn draw_path(&mut self, _path: &Path, _paint: &Paint) -> &mut Self { + pub fn draw_path(&self, _path: &Path, _paint: &Paint) -> &Self { unimplemented!("This is mocked") } pub fn clip_path( - &mut self, + &self, _path: &Path, _op: impl Into>, _do_anti_alias: impl Into>, - ) -> &mut Self { + ) -> &Self { unimplemented!("This is mocked") } - pub fn translate(&mut self, _d: impl Into) -> &mut Self { + pub fn translate(&self, _d: impl Into) -> &Self { unimplemented!("This is mocked") } @@ -995,21 +996,11 @@ impl Canvas { unimplemented!("This is mocked") } - pub fn draw_line( - &mut self, - _p1: impl Into, - _p2: impl Into, - _paint: &Paint, - ) -> &mut Self { + pub fn draw_line(&self, _p1: impl Into, _p2: impl Into, _paint: &Paint) -> &Self { unimplemented!("This is mocked") } - pub fn draw_circle( - &mut self, - _center: impl Into, - _radius: f32, - _paint: &Paint, - ) -> &mut Self { + pub fn draw_circle(&self, _center: impl Into, _radius: f32, _paint: &Paint) -> &Self { unimplemented!("This is mocked") } } @@ -1132,7 +1123,7 @@ impl Image { pub struct Data; impl Data { - pub fn new_bytes(_bytes: &[u8]) -> Self { + pub unsafe fn new_bytes(_bytes: &[u8]) -> Self { unimplemented!("This is mocked") } } @@ -1328,7 +1319,7 @@ pub mod svg { unimplemented!("This is mocked") } - pub fn render(&self, _canvas: &mut Canvas) { + pub fn render(&self, _canvas: &Canvas) { unimplemented!("This is mocked") } } @@ -1353,7 +1344,7 @@ impl From<(i32, i32)> for Size { pub struct Surface; impl Surface { - pub fn canvas(&self) -> Canvas { + pub fn canvas(&mut self) -> Canvas { unimplemented!("This is mocked") } @@ -1436,12 +1427,14 @@ impl DirectContext { use std::ffi::c_void; #[repr(u8)] +#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)] pub enum Protected { No, Yes, } -#[derive(Clone, Copy)] +#[derive(Copy, Clone, PartialEq, Eq, Debug)] +#[repr(C)] pub struct FramebufferInfo { pub fboid: i32, pub format: Format, @@ -1450,11 +1443,7 @@ pub struct FramebufferInfo { impl Default for FramebufferInfo { fn default() -> Self { - Self { - fboid: 0, - format: 0, - protected: Protected::No, - } + unimplemented!("This is mocked") } } @@ -1466,16 +1455,7 @@ pub fn wrap_backend_render_target( color_space: impl Into>, surface_props: Option<&SurfaceProps>, ) -> Option { - Surface::from_ptr(unsafe { - sb::C_SkSurfaces_WrapBackendRenderTarget( - context.native_mut(), - backend_render_target.native(), - origin, - color_type.into_native(), - color_space.into().into_ptr_or_null(), - surface_props.native_ptr_or_null(), - ) - }) + unimplemented!("This is mocked") } pub struct Interface; @@ -1534,3 +1514,15 @@ impl BackendRenderTarget { unimplemented!("This is mocked") } } + +pub mod backend_render_targets { + use crate::prelude::*; + pub fn make_gl( + (width, height): (i32, i32), + sample_count: impl Into>, + stencil_bits: usize, + info: FramebufferInfo, + ) -> BackendRenderTarget { + unimplemented!("This is mocked") + } +} diff --git a/crates/engine/src/skia.rs b/crates/engine/src/skia.rs index e73b8784e..38cb695be 100644 --- a/crates/engine/src/skia.rs +++ b/crates/engine/src/skia.rs @@ -1,6 +1,7 @@ pub use skia_safe::{ font_style::{Slant, Weight, Width}, gpu::{ + backend_render_targets, gl::{Format, FramebufferInfo, Interface}, surfaces::wrap_backend_render_target, BackendRenderTarget, DirectContext, RecordingContext, SurfaceOrigin, diff --git a/crates/freya/Cargo.toml b/crates/freya/Cargo.toml index 11187017e..1b3008aa7 100644 --- a/crates/freya/Cargo.toml +++ b/crates/freya/Cargo.toml @@ -19,6 +19,7 @@ no-default-features = true log = ["dep:tracing", "dep:tracing-subscriber"] devtools = ["dep:freya-devtools", "freya-dom/shared"] use_camera = ["freya-hooks/use_camera"] +mocked-engine-development = ["freya-engine/mocked-engine"] # This is just for the CI default = ["freya-engine/skia-engine"] [dependencies] diff --git a/crates/hooks/Cargo.toml b/crates/hooks/Cargo.toml index eedf87bdd..762167197 100644 --- a/crates/hooks/Cargo.toml +++ b/crates/hooks/Cargo.toml @@ -38,10 +38,10 @@ accesskit = { workspace = true } euclid = { workspace = true } uuid = { workspace = true } -tween = "2.0.0" +tween = "2.0.1" ropey = "1.6.0" nokhwa = { version = "0.10.4", features = ["input-native"], optional = true } -bytes = "1.3.0" +bytes = "1.5.0" [dev-dependencies] dioxus = { workspace = true } diff --git a/crates/hooks/src/rope_editor.rs b/crates/hooks/src/rope_editor.rs index e3f3fb93b..8eec3bc39 100644 --- a/crates/hooks/src/rope_editor.rs +++ b/crates/hooks/src/rope_editor.rs @@ -32,6 +32,10 @@ impl RopeEditor { mode, } } + + pub fn rope(&self) -> &Rope { + &self.rope + } } impl TextEditor for RopeEditor { @@ -157,6 +161,9 @@ impl TextEditor for RopeEditor { fn set(&mut self, text: &str) { self.rope.remove(0..); self.rope.insert(0, text); + if self.cursor_pos() > text.len() { + self.set_cursor_pos(text.len()); + } } fn unhighlight(&mut self) { diff --git a/crates/hooks/src/text_editor.rs b/crates/hooks/src/text_editor.rs index 9bfb80ff7..03daf6334 100644 --- a/crates/hooks/src/text_editor.rs +++ b/crates/hooks/src/text_editor.rs @@ -4,7 +4,7 @@ use freya_elements::events::keyboard::{Code, Key, Modifiers}; pub use ropey::Rope; /// Holds the position of a cursor in a text -#[derive(Clone, Default)] +#[derive(Clone, Default, PartialEq, Debug)] pub struct TextCursor { col: usize, row: usize, diff --git a/crates/hooks/src/use_accessibility.rs b/crates/hooks/src/use_accessibility.rs index 5467aa6cb..1717b2c72 100644 --- a/crates/hooks/src/use_accessibility.rs +++ b/crates/hooks/src/use_accessibility.rs @@ -66,7 +66,7 @@ mod test { let mut utils = launch_test_with_config( use_focus_app, - TestingConfig::default().with_size((100.0, 100.0).into()), + *TestingConfig::default().with_size((100.0, 100.0).into()), ); // Initial state diff --git a/crates/hooks/src/use_animation.rs b/crates/hooks/src/use_animation.rs index 1caad2148..b6ab36135 100644 --- a/crates/hooks/src/use_animation.rs +++ b/crates/hooks/src/use_animation.rs @@ -1,12 +1,9 @@ use dioxus_core::ScopeState; use dioxus_hooks::{use_state, UseState}; -use std::time::Duration; -use tokio::time::interval; +use tokio::time::Instant; use uuid::Uuid; -use crate::Animation; - -const ANIMATION_MS: i32 = 16; // Assume 60 FPS for now +use crate::{use_platform, Animation, UsePlatform}; /// Manage the lifecyle of an [Animation]. #[derive(Clone)] @@ -15,14 +12,16 @@ pub struct AnimationManager<'a> { current_animation_id: &'a UseState>, value: &'a UseState, cx: &'a ScopeState, + platform: UsePlatform, } impl<'a> AnimationManager<'a> { /// Start the given [Animation]. pub fn start(&self, mut anim: Animation) { let new_id = Uuid::new_v4(); - let mut index = 0; + let platform = self.platform.clone(); + let mut ticker = platform.new_ticker(); let value = self.value.clone(); let current_animation_id = self.current_animation_id.clone(); @@ -31,8 +30,16 @@ impl<'a> AnimationManager<'a> { // Spawn the animation that will run at 1ms speed self.cx.spawn(async move { - let mut ticker = interval(Duration::from_millis(ANIMATION_MS as u64)); + platform.request_animation_frame(); + + let mut index = 0; + let mut prev_frame = Instant::now(); + loop { + // Wait for the event loop to tick + ticker.tick().await; + platform.request_animation_frame(); + // Stop running the animation if it was removed if *current_animation_id.current() == Some(new_id) { // Remove the current animation if it has finished @@ -41,12 +48,10 @@ impl<'a> AnimationManager<'a> { break; } - // Advance one tick + index += prev_frame.elapsed().as_millis() as i32; value.set(anim.move_value(index)); - index += ANIMATION_MS; - // Wait 1m - ticker.tick().await; + prev_frame = Instant::now(); } else { break; } @@ -102,12 +107,14 @@ pub fn use_animation(cx: &ScopeState, init_value: impl FnOnce() -> f64) -> Anima let current_animation_id = use_state(cx, || None); let init_value = *cx.use_hook(init_value); let value = use_state(cx, || init_value); + let platform = use_platform(cx); AnimationManager { current_animation_id, value, cx, init_value, + platform, } } @@ -139,20 +146,24 @@ mod test { let mut utils = launch_test(use_animation_app); + // Disable event loop ticker + utils.config().enable_ticker(false); + // Initial state utils.wait_for_update().await; assert_eq!(utils.root().get(0).layout().unwrap().width(), 0.0); // State somewhere in the middle - utils.wait_for_update().await; + sleep(Duration::from_millis(32)).await; utils.wait_for_update().await; let width = utils.root().get(0).layout().unwrap().width(); assert!(width > 0.0); assert!(width < 100.0); - sleep(Duration::from_millis(50)).await; + // Enable event loop ticker + utils.config().enable_ticker(true); // State in the end utils.wait_for_update().await; @@ -189,26 +200,34 @@ mod test { let mut utils = launch_test(use_animation_app); + // Disable event loop ticker + utils.config().enable_ticker(false); + // Initial state utils.wait_for_update().await; assert_eq!(utils.root().get(0).layout().unwrap().width(), 10.0); // State somewhere in the middle - utils.wait_for_update().await; + sleep(Duration::from_millis(32)).await; utils.wait_for_update().await; let width = utils.root().get(0).layout().unwrap().width(); assert!(width > 10.0); + // Trigger the click event to restart the animation utils.push_event(FreyaEvent::Mouse { name: "click".to_string(), cursor: (5.0, 5.0).into(), button: Some(MouseButton::Left), }); + // Enable event loop ticker + utils.config().enable_ticker(true); + // State has been restarted utils.wait_for_update().await; + utils.wait_for_update().await; let width = utils.root().get(0).layout().unwrap().width(); assert_eq!(width, 10.0); diff --git a/crates/hooks/src/use_animation_transition.rs b/crates/hooks/src/use_animation_transition.rs index 6487e37f0..1164dd0c9 100644 --- a/crates/hooks/src/use_animation_transition.rs +++ b/crates/hooks/src/use_animation_transition.rs @@ -2,13 +2,10 @@ use dioxus_core::ScopeState; use dioxus_hooks::{use_memo, use_state, UseFutureDep, UseState}; use freya_engine::prelude::Color; use freya_node_state::Parse; -use std::time::Duration; -use tokio::time::interval; +use tokio::time::Instant; use uuid::Uuid; -use crate::{Animation, TransitionAnimation}; - -const ANIMATION_MS: i32 = 16; // Assume 60 FPS for now +use crate::{use_platform, Animation, TransitionAnimation, UsePlatform}; /// Configure a `Transition` animation. #[derive(Clone, Debug, Copy, PartialEq)] @@ -145,6 +142,8 @@ pub struct TransitionsManager<'a> { current_animation_id: &'a UseState>, /// The scope. cx: &'a ScopeState, + /// Platform APIs + platform: UsePlatform, } impl<'a> TransitionsManager<'a> { @@ -165,6 +164,8 @@ impl<'a> TransitionsManager<'a> { fn run_with_animation(&self, mut animation: Animation) { let animation_id = Uuid::new_v4(); + let platform = self.platform.clone(); + let mut ticker = platform.new_ticker(); let transitions = self.transitions.clone(); let transitions_storage = self.transitions_storage.clone(); let current_animation_id = self.current_animation_id.clone(); @@ -174,9 +175,16 @@ impl<'a> TransitionsManager<'a> { // Spawn the animation that will run at 1ms speed self.cx.spawn(async move { - let mut ticker = interval(Duration::from_millis(ANIMATION_MS as u64)); + platform.request_animation_frame(); + let mut index = 0; + let mut prev_frame = Instant::now(); + loop { + // Wait for the event loop to tick + ticker.tick().await; + platform.request_animation_frame(); + // Stop running the animation if it's no longer selected if *current_animation_id.current() == Some(animation_id) { // Remove the current animation if it has finished @@ -185,7 +193,7 @@ impl<'a> TransitionsManager<'a> { break; } - // Advance one tick + index += prev_frame.elapsed().as_millis() as i32; let value = animation.move_value(index); transitions_storage.with_mut(|storage| { for (i, storage) in storage.iter_mut().enumerate() { @@ -195,10 +203,7 @@ impl<'a> TransitionsManager<'a> { } }); - index += ANIMATION_MS; - - // Wait 1ms - ticker.tick().await; + prev_frame = Instant::now(); } else { break; } @@ -278,6 +283,7 @@ where let current_animation_id = use_state(cx, || None); let transitions = use_memo(cx, dependencies.clone(), &mut init); let transitions_storage = use_state(cx, || animations_map(transitions)); + let platform = use_platform(cx); use_memo(cx, dependencies, { let storage_setter = transitions_storage.setter(); @@ -292,6 +298,7 @@ where transitions_storage, cx, transition_animation: transition, + platform, } } @@ -332,20 +339,24 @@ mod test { let mut utils = launch_test(use_animation_transition_app); + // Disable event loop ticker + utils.config().enable_ticker(false); + // Initial state utils.wait_for_update().await; assert_eq!(utils.root().get(0).layout().unwrap().width(), 0.0); // State somewhere in the middle - utils.wait_for_update().await; + sleep(Duration::from_millis(32)).await; utils.wait_for_update().await; let width = utils.root().get(0).layout().unwrap().width(); assert!(width > 0.0); assert!(width < 100.0); - sleep(Duration::from_millis(50)).await; + // Enable event loop ticker + utils.config().enable_ticker(true); // State in the end utils.wait_for_update().await; diff --git a/crates/hooks/src/use_camera.rs b/crates/hooks/src/use_camera.rs index c33c9b1ce..f1d58e954 100644 --- a/crates/hooks/src/use_camera.rs +++ b/crates/hooks/src/use_camera.rs @@ -1,33 +1,20 @@ -use std::{ - sync::{Arc, Mutex}, - time::Duration, -}; +use std::sync::{Arc, Mutex}; use crate::use_platform; use dioxus_core::{AttributeValue, ScopeState}; use dioxus_hooks::{to_owned, use_effect, use_state, UseState}; -use freya_common::EventMessage; use freya_node_state::{CustomAttributeValues, ImageReference}; -use nokhwa::{pixel_format::RgbFormat, utils::RequestedFormat, Camera, NokhwaError}; -use tokio::time::sleep; - pub use nokhwa::utils::{CameraIndex, RequestedFormatType, Resolution}; +use nokhwa::{pixel_format::RgbFormat, utils::RequestedFormat, Camera, NokhwaError}; /// Configuration for a camera pub struct CameraSettings { - frame_rate: u32, camera_index: CameraIndex, resolution: Option, camera_format: RequestedFormatType, } impl CameraSettings { - /// Specify a frame rate - pub fn with_frame_rate(mut self, frame_rate: u32) -> Self { - self.frame_rate = frame_rate; - self - } - /// Specify a camera index pub fn with_camera_index(mut self, camera_index: CameraIndex) -> Self { self.camera_index = camera_index; @@ -50,7 +37,6 @@ impl CameraSettings { impl Default for CameraSettings { fn default() -> Self { Self { - frame_rate: 30, camera_index: CameraIndex::Index(0), resolution: None, camera_format: RequestedFormatType::AbsoluteHighestFrameRate, @@ -89,11 +75,11 @@ pub fn use_camera( .unwrap_or_else(handle_error); } - let frame_rate = camera_settings.frame_rate; - let fps = 1000 / frame_rate; + let mut ticker = platform.new_ticker(); loop { - sleep(Duration::from_millis(fps as u64)).await; + // Wait for the event loop to tick + ticker.tick().await; // Capture the next frame let frame = camera.frame(); @@ -104,9 +90,10 @@ pub fn use_camera( image_reference.lock().unwrap().replace(bts); // Request the renderer to rerender - platform.send(EventMessage::RequestRerender).unwrap(); + platform.request_animation_frame(); } else if let Err(err) = frame { handle_error(err); + break; } } } else if let Err(err) = camera { diff --git a/crates/hooks/src/use_focus.rs b/crates/hooks/src/use_focus.rs index 0a9d83dd0..4aeacc730 100644 --- a/crates/hooks/src/use_focus.rs +++ b/crates/hooks/src/use_focus.rs @@ -93,7 +93,7 @@ mod test { let mut utils = launch_test_with_config( use_focus_app, - TestingConfig::default().with_size((100.0, 100.0).into()), + *TestingConfig::default().with_size((100.0, 100.0).into()), ); // Initial state diff --git a/crates/hooks/src/use_node.rs b/crates/hooks/src/use_node.rs index 58249d17f..39a264df9 100644 --- a/crates/hooks/src/use_node.rs +++ b/crates/hooks/src/use_node.rs @@ -77,14 +77,14 @@ mod test { let mut utils = launch_test_with_config( use_node_app, - TestingConfig::default().with_size((500.0, 800.0).into()), + *TestingConfig::default().with_size((500.0, 800.0).into()), ); utils.wait_for_update().await; let root = utils.root().get(0); assert_eq!(root.get(0).text().unwrap().parse::(), Ok(500.0 * 0.5)); - utils.set_config(TestingConfig::default().with_size((300.0, 800.0).into())); + utils.config().with_size((300.0, 800.0).into()); utils.wait_for_update().await; let root = utils.root().get(0); diff --git a/crates/hooks/src/use_platform.rs b/crates/hooks/src/use_platform.rs index 40f78ec70..fc77e5c03 100644 --- a/crates/hooks/src/use_platform.rs +++ b/crates/hooks/src/use_platform.rs @@ -1,10 +1,13 @@ +use std::sync::Arc; + use dioxus_core::ScopeState; use freya_common::EventMessage; -use tokio::sync::mpsc::UnboundedSender; +use tokio::sync::{broadcast, mpsc::UnboundedSender}; use winit::event_loop::EventLoopProxy; #[derive(Clone)] pub struct UsePlatform { + ticker: Arc>, event_loop_proxy: Option>, platform_emitter: Option>, } @@ -28,11 +31,34 @@ impl UsePlatform { } Ok(()) } + + pub fn request_animation_frame(&self) { + self.send(EventMessage::RequestRerender).ok(); + } + + pub fn new_ticker(&self) -> Ticker { + Ticker { + inner: self.ticker.resubscribe(), + } + } } pub fn use_platform(cx: &ScopeState) -> UsePlatform { UsePlatform { event_loop_proxy: cx.consume_context::>(), platform_emitter: cx.consume_context::>(), + ticker: cx + .consume_context::>>() + .expect("This is not expected, and likely a bug. Please, report it."), + } +} + +pub struct Ticker { + inner: broadcast::Receiver<()>, +} + +impl Ticker { + pub async fn tick(&mut self) { + self.inner.recv().await.ok(); } } diff --git a/crates/layout/Cargo.toml b/crates/layout/Cargo.toml index dcde1a117..f5355221f 100644 --- a/crates/layout/Cargo.toml +++ b/crates/layout/Cargo.toml @@ -33,4 +33,4 @@ tokio = { workspace = true } accesskit = { workspace = true } rustc-hash= { workspace = true } -uuid = { version = "1.2.2", features = ["v4"]} \ No newline at end of file +uuid = { version = "1.4.1", features = ["v4"]} \ No newline at end of file diff --git a/crates/renderer/src/app.rs b/crates/renderer/src/app.rs index 885b1b33c..312319d09 100644 --- a/crates/renderer/src/app.rs +++ b/crates/renderer/src/app.rs @@ -11,6 +11,7 @@ use futures::{ pin_mut, task::{self, ArcWake}, }; +use tokio::sync::broadcast; use tokio::{ select, sync::{mpsc, watch, Notify}, @@ -65,6 +66,8 @@ pub struct App { accessibility: NativeAccessibility, font_collection: FontCollection, + + ticker_sender: broadcast::Sender<()>, } impl App { @@ -114,6 +117,7 @@ impl App { focus_sender, focus_receiver, font_collection, + ticker_sender: broadcast::channel(5).0, } } @@ -126,6 +130,9 @@ impl App { self.vdom .base_scope() .provide_context(self.focus_receiver.clone()); + self.vdom + .base_scope() + .provide_context(Arc::new(self.ticker_sender.subscribe())); } /// Make the first build of the VirtualDOM. @@ -321,4 +328,8 @@ impl App { self.accessibility .focus_next_node(direction, &self.focus_sender) } + + pub fn tick(&self) { + self.ticker_sender.send(()).unwrap(); + } } diff --git a/crates/renderer/src/elements/image.rs b/crates/renderer/src/elements/image.rs index 18c520a30..7a8e9f14d 100644 --- a/crates/renderer/src/elements/image.rs +++ b/crates/renderer/src/elements/image.rs @@ -5,11 +5,11 @@ use freya_node_state::{References, Style}; use torin::geometry::Area; /// Render an `image` element -pub fn render_image(area: &Area, node_ref: &DioxusNode, canvas: &mut Canvas) { +pub fn render_image(area: &Area, node_ref: &DioxusNode, canvas: &Canvas) { let node_style = node_ref.get::