From cf9f2a20bfab908872fc393c368df824569de70d Mon Sep 17 00:00:00 2001 From: marc2332 Date: Tue, 1 Oct 2024 14:36:30 +0200 Subject: [PATCH 1/9] feat: Event Loop Builder hook --- crates/renderer/src/config.rs | 31 +++++++++++++++++++++++------ crates/renderer/src/renderer.rs | 9 +++++++-- crates/renderer/src/window_state.rs | 2 +- 3 files changed, 33 insertions(+), 9 deletions(-) diff --git a/crates/renderer/src/config.rs b/crates/renderer/src/config.rs index 4466ac4a4..594ba23a8 100644 --- a/crates/renderer/src/config.rs +++ b/crates/renderer/src/config.rs @@ -8,18 +8,24 @@ use freya_core::{ FreyaPlugin, PluginsManager, }, + prelude::EventMessage, style::default_fonts, }; use freya_engine::prelude::Color; use freya_node_state::Parse; use image::ImageReader; -use winit::window::{ - Icon, - Window, - WindowAttributes, +use winit::{ + event_loop::EventLoopBuilder, + window::{ + Icon, + Window, + WindowAttributes, + }, }; -pub type WindowBuilderHook = Box WindowAttributes>; +pub type EventLoopBuilderHook = + Box) -> EventLoopBuilder>; +pub type WindowBuilderHook = Box WindowAttributes>; pub type EmbeddedFonts<'a> = Vec<(&'a str, &'a [u8])>; /// Configuration for a Window. @@ -48,6 +54,8 @@ pub struct WindowConfig { pub on_exit: Option, /// Hook function called with the Window Attributes. pub window_attributes_hook: Option, + /// Hook function called with the Event Loop Builder. + pub event_loop_builder_hook: Option, } impl Default for WindowConfig { @@ -65,6 +73,7 @@ impl Default for WindowConfig { on_setup: None, on_exit: None, window_attributes_hook: None, + event_loop_builder_hook: None, } } } @@ -213,9 +222,19 @@ impl<'a, T: Clone> LaunchConfig<'a, T> { /// Register a Window Attributes hook. pub fn with_window_attributes( mut self, - window_attributes_hook: impl Fn(WindowAttributes) -> WindowAttributes + 'static, + window_attributes_hook: impl FnOnce(WindowAttributes) -> WindowAttributes + 'static, ) -> Self { self.window_config.window_attributes_hook = Some(Box::new(window_attributes_hook)); self } + + /// Register an Event Loop Builder hook. + pub fn with_event_loop_builder( + mut self, + event_loop_builder_hook: impl FnOnce(EventLoopBuilder) -> EventLoopBuilder + + 'static, + ) -> Self { + self.window_config.event_loop_builder_hook = Some(Box::new(event_loop_builder_hook)); + self + } } diff --git a/crates/renderer/src/renderer.rs b/crates/renderer/src/renderer.rs index 49fd15fa3..b1e64113b 100644 --- a/crates/renderer/src/renderer.rs +++ b/crates/renderer/src/renderer.rs @@ -71,11 +71,16 @@ impl<'a, State: Clone + 'static> DesktopRenderer<'a, State> { pub fn launch( vdom: VirtualDom, sdom: SafeDOM, - config: LaunchConfig, + mut config: LaunchConfig, devtools: Option, hovered_node: HoveredNode, ) { - let event_loop = EventLoop::::with_user_event() + let mut event_loop_builder = EventLoop::::with_user_event(); + let event_loop_builder_hook = config.window_config.event_loop_builder_hook.take(); + if let Some(event_loop_builder_hook) = event_loop_builder_hook { + event_loop_builder = event_loop_builder_hook(event_loop_builder); + } + let event_loop = event_loop_builder .build() .expect("Failed to create event loop."); let proxy = event_loop.create_proxy(); diff --git a/crates/renderer/src/window_state.rs b/crates/renderer/src/window_state.rs index 57a136264..2027682dd 100644 --- a/crates/renderer/src/window_state.rs +++ b/crates/renderer/src/window_state.rs @@ -94,7 +94,7 @@ impl<'a, State: Clone + 'a> WindowState<'a, State> { window_attributes.with_max_inner_size(LogicalSize::::from(max_size)); } - if let Some(with_window_attributes) = &config.window_config.window_attributes_hook { + if let Some(with_window_attributes) = config.window_config.window_attributes_hook.take() { window_attributes = (with_window_attributes)(window_attributes); } From ca1c359e3ac4d0200f4200564e74117ccc0b2c51 Mon Sep 17 00:00:00 2001 From: marc2332 Date: Tue, 1 Oct 2024 14:44:28 +0200 Subject: [PATCH 2/9] clean up --- Cargo.toml | 1 + crates/renderer/src/config.rs | 6 ++---- crates/renderer/src/renderer.rs | 2 +- 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 4bbdb6991..315f39f6a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -97,6 +97,7 @@ tree-sitter-rust = "0.23.0" rfd = "0.14.1" bytes = "1.5.0" dioxus-sdk = { workspace = true } +winit = { workspace = true } [profile.release] lto = true diff --git a/crates/renderer/src/config.rs b/crates/renderer/src/config.rs index 594ba23a8..226cf6e4d 100644 --- a/crates/renderer/src/config.rs +++ b/crates/renderer/src/config.rs @@ -23,8 +23,7 @@ use winit::{ }, }; -pub type EventLoopBuilderHook = - Box) -> EventLoopBuilder>; +pub type EventLoopBuilderHook = Box)>; pub type WindowBuilderHook = Box WindowAttributes>; pub type EmbeddedFonts<'a> = Vec<(&'a str, &'a [u8])>; @@ -231,8 +230,7 @@ impl<'a, T: Clone> LaunchConfig<'a, T> { /// Register an Event Loop Builder hook. pub fn with_event_loop_builder( mut self, - event_loop_builder_hook: impl FnOnce(EventLoopBuilder) -> EventLoopBuilder - + 'static, + event_loop_builder_hook: impl FnOnce(&mut EventLoopBuilder) + 'static, ) -> Self { self.window_config.event_loop_builder_hook = Some(Box::new(event_loop_builder_hook)); self diff --git a/crates/renderer/src/renderer.rs b/crates/renderer/src/renderer.rs index b1e64113b..d5796e5b7 100644 --- a/crates/renderer/src/renderer.rs +++ b/crates/renderer/src/renderer.rs @@ -78,7 +78,7 @@ impl<'a, State: Clone + 'static> DesktopRenderer<'a, State> { let mut event_loop_builder = EventLoop::::with_user_event(); let event_loop_builder_hook = config.window_config.event_loop_builder_hook.take(); if let Some(event_loop_builder_hook) = event_loop_builder_hook { - event_loop_builder = event_loop_builder_hook(event_loop_builder); + event_loop_builder_hook(&mut event_loop_builder); } let event_loop = event_loop_builder .build() From b1edc1e61820b4a71e46d4e814702f2afe6cf794 Mon Sep 17 00:00:00 2001 From: marc2332 Date: Tue, 1 Oct 2024 17:55:58 +0200 Subject: [PATCH 3/9] feat: muda integration --- Cargo.toml | 1 + examples/muda.rs | 42 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 43 insertions(+) create mode 100644 examples/muda.rs diff --git a/Cargo.toml b/Cargo.toml index 4bbdb6991..6b95e1ea6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -97,6 +97,7 @@ tree-sitter-rust = "0.23.0" rfd = "0.14.1" bytes = "1.5.0" dioxus-sdk = { workspace = true } +muda = "0.15.1" [profile.release] lto = true diff --git a/examples/muda.rs b/examples/muda.rs new file mode 100644 index 000000000..7211a87ca --- /dev/null +++ b/examples/muda.rs @@ -0,0 +1,42 @@ +#![cfg_attr( + all(not(debug_assertions), target_os = "windows"), + windows_subsystem = "windows" +)] + +use accelerator::Accelerator; +use freya::prelude::*; +use muda::*; + +fn main() { + launch_cfg(app, LaunchConfig::<()>::new().with_title("Muda")); +} + +fn app() -> Element { + let platform = use_platform(); + + use_hook(|| { + platform.with_window(|window| { + let menu_bar = Menu::new(); + let submenu = Submenu::with_items( + "Submenu Outer", + true, + &[ + &MenuItem::new("Menu item #3", true, None), + ], + ) + .unwrap(); + + menu_bar.append_items(&[&submenu]); + + use winit::raw_window_handle::*; + if let RawWindowHandle::Win32(handle) = window.window_handle().unwrap().as_raw() { + println!("Works"); + unsafe { menu_bar.init_for_hwnd(handle.hwnd.get()).unwrap() }; + } + }); + }); + + rsx!(label { + "hi" + }) +} From c879115b50a6dcf2933ab45fabd5383af15d266e Mon Sep 17 00:00:00 2001 From: marc2332 Date: Wed, 2 Oct 2024 20:59:26 +0200 Subject: [PATCH 4/9] fmt --- examples/muda.rs | 460 ++++++++++++++++++++++++----------------------- 1 file changed, 231 insertions(+), 229 deletions(-) diff --git a/examples/muda.rs b/examples/muda.rs index a1e3f67f2..30ba94e44 100644 --- a/examples/muda.rs +++ b/examples/muda.rs @@ -1,229 +1,231 @@ -#![cfg_attr( - all(not(debug_assertions), target_os = "windows"), - windows_subsystem = "windows" -)] - -use std::sync::Arc; - -use freya::prelude::*; -use tokio::sync::broadcast::Receiver; -use winit::platform::windows::EventLoopBuilderExtWindows; - -fn main() { - let menu_bar = muda::Menu::new(); - - launch_cfg( - app, - LaunchConfig::::new() - .with_title("Muda") - .with_state(menu_bar.clone()) - .with_event_loop_builder(move |event_loop_builder| { - let menu_bar = menu_bar.clone(); - event_loop_builder.with_msg_hook(move |msg| { - use windows_sys::Win32::UI::WindowsAndMessaging::{TranslateAcceleratorW, MSG}; - unsafe { - let msg = msg as *const MSG; - let translated = - TranslateAcceleratorW((*msg).hwnd, menu_bar.haccel() as _, msg); - translated == 1 - } - }); - }), - ); -} - -fn app() -> Element { - let mut count = use_signal(|| 0); - - rsx!( - WindowMenu { - MenuItem { - text: "+", - enabled: true, - onclick: move |_| count += 1 - } - MenuItem { - text: "{count}", - enabled: true, - onclick: move |_| {} - } - MenuItem { - text: "-", - enabled: true, - onclick: move |_| count -= 1 - } - SubMenu { - text: "Stuff", - enabled: true, - MenuItem { - text: "Reset to 0", - enabled: true, - onclick: move |_| count.set(0) - } - } - } - label { - "{count}" - } - ) -} - -#[component] -fn WindowMenu(children: Element) -> Element { - let platform = use_platform(); - let mut menus_notifier = use_context_provider(|| Signal::new(())); - - let rx = use_hook(|| { - let menu_bar = Arc::new(SharedMenu(consume_context::())); - struct SharedMenu(pub muda::Menu); - - unsafe impl Send for SharedMenu {}; - unsafe impl Sync for SharedMenu {}; - - platform.with_window(move |window| { - use winit::raw_window_handle::*; - if let RawWindowHandle::Win32(handle) = window.window_handle().unwrap().as_raw() { - unsafe { menu_bar.0.init_for_hwnd(handle.hwnd.get()).unwrap() }; - } - }); - - let (tx, rx) = tokio::sync::broadcast::channel::(5); - - { - let tx = tx.clone(); - muda::MenuEvent::set_event_handler(Some(move |event| { - tx.send(event); - })); - } - - MenuReceiver(rx) - }); - - let (tx, rx2) = use_hook(|| { - let (tx, rx) = tokio::sync::broadcast::channel::<()>(50); - - (tx, MenuReceiver(rx)) - }); - - use_memo(move || { - menus_notifier(); - - let menu_bar = consume_context::(); - - for item in menu_bar.items() { - if let Some(item) = item.as_menuitem() { - menu_bar.remove(item); - } else if let Some(submenu) = item.as_submenu() { - menu_bar.remove(submenu); - } - } - - tx.send(()); - }); - - provide_context(rx); - provide_context(rx2); - - children -} - - -struct MenuReceiver(pub Receiver); - -impl Clone for MenuReceiver { - fn clone(&self) -> Self { - Self(self.0.resubscribe()) - } -} - -#[component] -fn SubMenu( - text: ReadOnlySignal, - enabled: ReadOnlySignal, - children: Element, -) -> Element { - let mut submenu = use_context_provider(|| Signal::new(None)); - - use_memo(move || { - let mut menus_notifier = consume_context::>(); - - text.read(); - enabled.read(); - - menus_notifier.write(); - }); - - use_hook(move || { - let mut rx = consume_context::>(); - - spawn(async move { - while let Ok(ev) = rx.0.recv().await { - let menu_bar = consume_context::(); - let new_submenu = muda::Submenu::new(&*text.peek(), *enabled.peek()); - - menu_bar.append(&new_submenu).unwrap(); - submenu.set(Some(new_submenu)); - } - }); - }); - - children -} - -#[component] -fn MenuItem( - onclick: ReadOnlySignal>, - text: ReadOnlySignal, - enabled: ReadOnlySignal, -) -> Element { - use_memo(move || { - let mut menus_notifier = consume_context::>(); - - onclick.read(); - text.read(); - enabled.read(); - - menus_notifier.write(); - }); - - let mut menu = use_signal(|| None); - - use_hook(move || { - let mut rx = consume_context::>(); - - spawn(async move { - while let Ok(ev) = rx.0.recv().await { - let mut submenu_signal = try_consume_context::>>(); - - let new_menu_item = muda::MenuItem::new(&*text.peek(), *enabled.peek(), None); - - if let Some(submenu_signal) = submenu_signal { - let submenu = submenu_signal.peek(); - let submenu = submenu.as_ref().unwrap(); - submenu.append(&new_menu_item).unwrap(); - } else { - let menu_bar = consume_context::(); - menu_bar.append(&new_menu_item).unwrap(); - } - - menu.set(Some(new_menu_item)); - } - }); - }); - - use_hook(move || { - let mut rx = consume_context::>(); - - spawn(async move { - while let Ok(ev) = rx.0.recv().await { - if let Some(menu) = &*menu.read() { - if event.id == menu.id() { - onclick.peek().call(()); - } - } - } - }); - }); - - None -} +#![cfg_attr( + all(not(debug_assertions), target_os = "windows"), + windows_subsystem = "windows" +)] + +use std::sync::Arc; + +use freya::prelude::*; +use tokio::sync::broadcast::Receiver; +use winit::platform::windows::EventLoopBuilderExtWindows; + +fn main() { + let menu_bar = muda::Menu::new(); + + launch_cfg( + app, + LaunchConfig::::new() + .with_title("Muda") + .with_state(menu_bar.clone()) + .with_event_loop_builder(move |event_loop_builder| { + let menu_bar = menu_bar.clone(); + event_loop_builder.with_msg_hook(move |msg| { + use windows_sys::Win32::UI::WindowsAndMessaging::{ + TranslateAcceleratorW, + MSG, + }; + unsafe { + let msg = msg as *const MSG; + let translated = + TranslateAcceleratorW((*msg).hwnd, menu_bar.haccel() as _, msg); + translated == 1 + } + }); + }), + ); +} + +fn app() -> Element { + let mut count = use_signal(|| 0); + + rsx!( + WindowMenu { + MenuItem { + text: "+", + enabled: true, + onclick: move |_| count += 1 + } + MenuItem { + text: "{count}", + enabled: true, + onclick: move |_| {} + } + MenuItem { + text: "-", + enabled: true, + onclick: move |_| count -= 1 + } + SubMenu { + text: "Stuff", + enabled: true, + MenuItem { + text: "Reset to 0", + enabled: true, + onclick: move |_| count.set(0) + } + } + } + label { + "{count}" + } + ) +} + +#[component] +fn WindowMenu(children: Element) -> Element { + let platform = use_platform(); + let mut menus_notifier = use_context_provider(|| Signal::new(())); + + let rx = use_hook(|| { + let menu_bar = Arc::new(SharedMenu(consume_context::())); + struct SharedMenu(pub muda::Menu); + + unsafe impl Send for SharedMenu {}; + unsafe impl Sync for SharedMenu {}; + + platform.with_window(move |window| { + use winit::raw_window_handle::*; + if let RawWindowHandle::Win32(handle) = window.window_handle().unwrap().as_raw() { + unsafe { menu_bar.0.init_for_hwnd(handle.hwnd.get()).unwrap() }; + } + }); + + let (tx, rx) = tokio::sync::broadcast::channel::(5); + + { + let tx = tx.clone(); + muda::MenuEvent::set_event_handler(Some(move |event| { + tx.send(event); + })); + } + + MenuReceiver(rx) + }); + + let (tx, rx2) = use_hook(|| { + let (tx, rx) = tokio::sync::broadcast::channel::<()>(50); + + (tx, MenuReceiver(rx)) + }); + + use_memo(move || { + menus_notifier(); + + let menu_bar = consume_context::(); + + for item in menu_bar.items() { + if let Some(item) = item.as_menuitem() { + menu_bar.remove(item); + } else if let Some(submenu) = item.as_submenu() { + menu_bar.remove(submenu); + } + } + + tx.send(()); + }); + + provide_context(rx); + provide_context(rx2); + + children +} + +struct MenuReceiver(pub Receiver); + +impl Clone for MenuReceiver { + fn clone(&self) -> Self { + Self(self.0.resubscribe()) + } +} + +#[component] +fn SubMenu( + text: ReadOnlySignal, + enabled: ReadOnlySignal, + children: Element, +) -> Element { + let mut submenu = use_context_provider(|| Signal::new(None)); + + use_memo(move || { + let mut menus_notifier = consume_context::>(); + + text.read(); + enabled.read(); + + menus_notifier.write(); + }); + + use_hook(move || { + let mut rx = consume_context::>(); + + spawn(async move { + while let Ok(ev) = rx.0.recv().await { + let menu_bar = consume_context::(); + let new_submenu = muda::Submenu::new(&*text.peek(), *enabled.peek()); + + menu_bar.append(&new_submenu).unwrap(); + submenu.set(Some(new_submenu)); + } + }); + }); + + children +} + +#[component] +fn MenuItem( + onclick: ReadOnlySignal>, + text: ReadOnlySignal, + enabled: ReadOnlySignal, +) -> Element { + use_memo(move || { + let mut menus_notifier = consume_context::>(); + + onclick.read(); + text.read(); + enabled.read(); + + menus_notifier.write(); + }); + + let mut menu = use_signal(|| None); + + use_hook(move || { + let mut rx = consume_context::>(); + + spawn(async move { + while let Ok(ev) = rx.0.recv().await { + let mut submenu_signal = try_consume_context::>>(); + + let new_menu_item = muda::MenuItem::new(&*text.peek(), *enabled.peek(), None); + + if let Some(submenu_signal) = submenu_signal { + let submenu = submenu_signal.peek(); + let submenu = submenu.as_ref().unwrap(); + submenu.append(&new_menu_item).unwrap(); + } else { + let menu_bar = consume_context::(); + menu_bar.append(&new_menu_item).unwrap(); + } + + menu.set(Some(new_menu_item)); + } + }); + }); + + use_hook(move || { + let mut rx = consume_context::>(); + + spawn(async move { + while let Ok(ev) = rx.0.recv().await { + if let Some(menu) = &*menu.read() { + if event.id == menu.id() { + onclick.peek().call(()); + } + } + } + }); + }); + + None +} From ca8fa6bf03fbc105cfa9b2362d7fa659e765976b Mon Sep 17 00:00:00 2001 From: marc2332 Date: Sat, 12 Oct 2024 13:40:00 +0200 Subject: [PATCH 5/9] clean up --- examples/muda.rs | 140 +++++++++++++++++++++++------------------------ 1 file changed, 68 insertions(+), 72 deletions(-) diff --git a/examples/muda.rs b/examples/muda.rs index 30ba94e44..f1cacadf5 100644 --- a/examples/muda.rs +++ b/examples/muda.rs @@ -18,7 +18,7 @@ fn main() { .with_title("Muda") .with_state(menu_bar.clone()) .with_event_loop_builder(move |event_loop_builder| { - let menu_bar = menu_bar.clone(); + #[cfg(target_os = "windows")] event_loop_builder.with_msg_hook(move |msg| { use windows_sys::Win32::UI::WindowsAndMessaging::{ TranslateAcceleratorW, @@ -40,30 +40,39 @@ fn app() -> Element { rsx!( WindowMenu { - MenuItem { - text: "+", - enabled: true, - onclick: move |_| count += 1 - } - MenuItem { - text: "{count}", - enabled: true, - onclick: move |_| {} - } - MenuItem { - text: "-", - enabled: true, - onclick: move |_| count -= 1 - } - SubMenu { - text: "Stuff", - enabled: true, + menu: rsx!( MenuItem { - text: "Reset to 0", + text: "+", enabled: true, - onclick: move |_| count.set(0) + onclick: move |_| count += 1 } - } + MenuItem { + text: "{count}", + enabled: true, + onclick: move |_| {} + } + MenuItem { + text: "-", + enabled: true, + onclick: move |_| count -= 1 + } + if count() < 3 { + MenuItem { + text: "count is smaller than 3", + enabled: true, + onclick: move |_| {} + } + } + SubMenu { + text: "Stuff", + enabled: true, + MenuItem { + text: "Reset to 0", + enabled: true, + onclick: move |_| count.set(0) + } + } + ) } label { "{count}" @@ -72,67 +81,73 @@ fn app() -> Element { } #[component] -fn WindowMenu(children: Element) -> Element { +fn WindowMenu(menu: ReadOnlySignal) -> Element { let platform = use_platform(); - let mut menus_notifier = use_context_provider(|| Signal::new(())); - let rx = use_hook(|| { + let click_receiver = use_hook(|| { let menu_bar = Arc::new(SharedMenu(consume_context::())); struct SharedMenu(pub muda::Menu); - unsafe impl Send for SharedMenu {}; - unsafe impl Sync for SharedMenu {}; + unsafe impl Send for SharedMenu {} + unsafe impl Sync for SharedMenu {} platform.with_window(move |window| { use winit::raw_window_handle::*; + + #[cfg(target_os = "windows")] if let RawWindowHandle::Win32(handle) = window.window_handle().unwrap().as_raw() { unsafe { menu_bar.0.init_for_hwnd(handle.hwnd.get()).unwrap() }; } + + #[cfg(target_os = "linux")] + menu.init_for_gtk_window(&window, None); + + #[cfg(target_os = "macos")] + menu.init_for_nsapp(); }); let (tx, rx) = tokio::sync::broadcast::channel::(5); - { - let tx = tx.clone(); - muda::MenuEvent::set_event_handler(Some(move |event| { - tx.send(event); - })); - } + muda::MenuEvent::set_event_handler(Some(move |event| { + tx.send(event).ok(); + })); - MenuReceiver(rx) + MenuEventReceiver(rx) }); - let (tx, rx2) = use_hook(|| { + let (creation_sender, creation_receiver) = use_hook(|| { let (tx, rx) = tokio::sync::broadcast::channel::<()>(50); - (tx, MenuReceiver(rx)) + (tx, MenuEventReceiver(rx)) }); - use_memo(move || { - menus_notifier(); + use_effect(move || { + menu.read(); let menu_bar = consume_context::(); for item in menu_bar.items() { if let Some(item) = item.as_menuitem() { - menu_bar.remove(item); + menu_bar.remove(item).expect("Failed to remove menu."); } else if let Some(submenu) = item.as_submenu() { - menu_bar.remove(submenu); + menu_bar.remove(submenu).expect("Failed to remove submenu."); } } - tx.send(()); + creation_sender + .send(()) + .expect("Failed to notify menus of an update."); }); - provide_context(rx); - provide_context(rx2); + provide_context(click_receiver); + provide_context(creation_receiver); - children + menu() } -struct MenuReceiver(pub Receiver); +struct MenuEventReceiver(pub Receiver); -impl Clone for MenuReceiver { +impl Clone for MenuEventReceiver { fn clone(&self) -> Self { Self(self.0.resubscribe()) } @@ -146,20 +161,11 @@ fn SubMenu( ) -> Element { let mut submenu = use_context_provider(|| Signal::new(None)); - use_memo(move || { - let mut menus_notifier = consume_context::>(); - - text.read(); - enabled.read(); - - menus_notifier.write(); - }); - use_hook(move || { - let mut rx = consume_context::>(); + let mut creation_receiver = consume_context::>(); spawn(async move { - while let Ok(ev) = rx.0.recv().await { + while creation_receiver.0.recv().await.is_ok() { let menu_bar = consume_context::(); let new_submenu = muda::Submenu::new(&*text.peek(), *enabled.peek()); @@ -178,24 +184,14 @@ fn MenuItem( text: ReadOnlySignal, enabled: ReadOnlySignal, ) -> Element { - use_memo(move || { - let mut menus_notifier = consume_context::>(); - - onclick.read(); - text.read(); - enabled.read(); - - menus_notifier.write(); - }); - let mut menu = use_signal(|| None); use_hook(move || { - let mut rx = consume_context::>(); + let mut creation_receiver = consume_context::>(); spawn(async move { - while let Ok(ev) = rx.0.recv().await { - let mut submenu_signal = try_consume_context::>>(); + while creation_receiver.0.recv().await.is_ok() { + let submenu_signal = try_consume_context::>>(); let new_menu_item = muda::MenuItem::new(&*text.peek(), *enabled.peek(), None); @@ -214,10 +210,10 @@ fn MenuItem( }); use_hook(move || { - let mut rx = consume_context::>(); + let mut click_receiver = consume_context::>(); spawn(async move { - while let Ok(ev) = rx.0.recv().await { + while let Ok(event) = click_receiver.0.recv().await { if let Some(menu) = &*menu.read() { if event.id == menu.id() { onclick.peek().call(()); From 3f3b6484a741569a3e0ec3d389b4d68c4673e001 Mon Sep 17 00:00:00 2001 From: marc2332 Date: Sat, 12 Oct 2024 13:44:11 +0200 Subject: [PATCH 6/9] fix --- examples/muda.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/muda.rs b/examples/muda.rs index f1cacadf5..d470b9b22 100644 --- a/examples/muda.rs +++ b/examples/muda.rs @@ -100,10 +100,10 @@ fn WindowMenu(menu: ReadOnlySignal) -> Element { } #[cfg(target_os = "linux")] - menu.init_for_gtk_window(&window, None); + menu_bar.0.init_for_gtk_window(&window, None); #[cfg(target_os = "macos")] - menu.init_for_nsapp(); + menu_bar.0.init_for_nsapp(); }); let (tx, rx) = tokio::sync::broadcast::channel::(5); From 581beff9cafa4943ba94f082f5dac417a49ff408 Mon Sep 17 00:00:00 2001 From: marc2332 Date: Sat, 12 Oct 2024 13:50:02 +0200 Subject: [PATCH 7/9] clean up --- examples/muda.rs | 44 ++++++++++++++++++++++++-------------------- 1 file changed, 24 insertions(+), 20 deletions(-) diff --git a/examples/muda.rs b/examples/muda.rs index d470b9b22..17cda5c6d 100644 --- a/examples/muda.rs +++ b/examples/muda.rs @@ -7,7 +7,6 @@ use std::sync::Arc; use freya::prelude::*; use tokio::sync::broadcast::Receiver; -use winit::platform::windows::EventLoopBuilderExtWindows; fn main() { let menu_bar = muda::Menu::new(); @@ -19,18 +18,21 @@ fn main() { .with_state(menu_bar.clone()) .with_event_loop_builder(move |event_loop_builder| { #[cfg(target_os = "windows")] - event_loop_builder.with_msg_hook(move |msg| { - use windows_sys::Win32::UI::WindowsAndMessaging::{ - TranslateAcceleratorW, - MSG, - }; - unsafe { - let msg = msg as *const MSG; - let translated = - TranslateAcceleratorW((*msg).hwnd, menu_bar.haccel() as _, msg); - translated == 1 - } - }); + { + use winit::platform::windows::EventLoopBuilderExtWindows; + event_loop_builder.with_msg_hook(move |msg| { + use windows_sys::Win32::UI::WindowsAndMessaging::{ + TranslateAcceleratorW, + MSG, + }; + unsafe { + let msg = msg as *const MSG; + let translated = + TranslateAcceleratorW((*msg).hwnd, menu_bar.haccel() as _, msg); + translated == 1 + } + }); + } }), ); } @@ -91,21 +93,23 @@ fn WindowMenu(menu: ReadOnlySignal) -> Element { unsafe impl Send for SharedMenu {} unsafe impl Sync for SharedMenu {} + #[cfg(not(target_os = "macos"))] platform.with_window(move |window| { - use winit::raw_window_handle::*; - #[cfg(target_os = "windows")] - if let RawWindowHandle::Win32(handle) = window.window_handle().unwrap().as_raw() { - unsafe { menu_bar.0.init_for_hwnd(handle.hwnd.get()).unwrap() }; + { + use winit::raw_window_handle::*; + if let RawWindowHandle::Win32(handle) = window.window_handle().unwrap().as_raw() { + unsafe { menu_bar.0.init_for_hwnd(handle.hwnd.get()).unwrap() }; + } } #[cfg(target_os = "linux")] menu_bar.0.init_for_gtk_window(&window, None); - - #[cfg(target_os = "macos")] - menu_bar.0.init_for_nsapp(); }); + #[cfg(target_os = "macos")] + menu_bar.0.init_for_nsapp(); + let (tx, rx) = tokio::sync::broadcast::channel::(5); muda::MenuEvent::set_event_handler(Some(move |event| { From 07e777ecb83f85304a89d4bd396c738b14d32bd6 Mon Sep 17 00:00:00 2001 From: marc2332 Date: Sat, 12 Oct 2024 13:57:00 +0200 Subject: [PATCH 8/9] clean up --- examples/muda.rs | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/examples/muda.rs b/examples/muda.rs index 17cda5c6d..0151a9ba6 100644 --- a/examples/muda.rs +++ b/examples/muda.rs @@ -93,18 +93,12 @@ fn WindowMenu(menu: ReadOnlySignal) -> Element { unsafe impl Send for SharedMenu {} unsafe impl Sync for SharedMenu {} - #[cfg(not(target_os = "macos"))] + #[cfg(target_os = "windows")] platform.with_window(move |window| { - #[cfg(target_os = "windows")] - { - use winit::raw_window_handle::*; - if let RawWindowHandle::Win32(handle) = window.window_handle().unwrap().as_raw() { - unsafe { menu_bar.0.init_for_hwnd(handle.hwnd.get()).unwrap() }; - } + use winit::raw_window_handle::*; + if let RawWindowHandle::Win32(handle) = window.window_handle().unwrap().as_raw() { + unsafe { menu_bar.0.init_for_hwnd(handle.hwnd.get()).unwrap() }; } - - #[cfg(target_os = "linux")] - menu_bar.0.init_for_gtk_window(&window, None); }); #[cfg(target_os = "macos")] From 9ade5de4785556d63a6c0cfe18ca80f53f58152f Mon Sep 17 00:00:00 2001 From: marc2332 Date: Sun, 2 Feb 2025 13:31:20 +0100 Subject: [PATCH 9/9] attempt to improve --- .vscode/settings.json | 3 +- Cargo.toml | 1 + crates/components/Cargo.toml | 2 + crates/components/src/lib.rs | 4 + crates/components/src/window_menu.rs | 156 +++++++++++++++ crates/freya/Cargo.toml | 1 + examples/muda.rs | 288 ++++++++++++--------------- 7 files changed, 294 insertions(+), 161 deletions(-) create mode 100644 crates/components/src/window_menu.rs diff --git a/.vscode/settings.json b/.vscode/settings.json index 19addb2b6..76794c509 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -3,6 +3,7 @@ "devtools", "tracing-subscriber", "use_camera", - "fade-cached-incremental-areas" + "fade-cached-incremental-areas", + "muda" ] } diff --git a/Cargo.toml b/Cargo.toml index ca76ebab9..798e1f9d6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,6 +16,7 @@ performance-overlay = ["freya/performance-overlay"] fade-cached-incremental-areas = ["freya/fade-cached-incremental-areas"] disable-zoom-shortcuts = ["freya/disable-zoom-shortcuts"] docs = ["freya/docs"] +muda = ["freya/muda"] [patch.crates-io] dioxus = { git = "https://github.com/ealmloff/dioxus", rev = "ab5ab78c3a095c6e983c6d3c998164d1932c9079" } diff --git a/crates/components/Cargo.toml b/crates/components/Cargo.toml index 932829c9f..518350d2d 100644 --- a/crates/components/Cargo.toml +++ b/crates/components/Cargo.toml @@ -15,6 +15,7 @@ categories = ["gui", "asynchronous"] features = ["freya-engine/mocked-engine", "docs"] [features] +muda = ["dep:muda"] skia-engine = ["freya-engine/skia-engine"] docs = [] @@ -35,6 +36,7 @@ winit = { workspace = true } tokio = { workspace = true } tracing = { workspace = true } +muda = { version = "0.15.1", optional = true } open = "5" reqwest = "0.12.0" bytes = "1.5.0" diff --git a/crates/components/src/lib.rs b/crates/components/src/lib.rs index 3acc34174..7e3786520 100644 --- a/crates/components/src/lib.rs +++ b/crates/components/src/lib.rs @@ -79,6 +79,8 @@ mod tile; mod tooltip; mod tree; mod window_drag_area; +#[cfg(feature = "muda")] +mod window_menu; pub use accordion::*; pub use activable_route::*; @@ -119,3 +121,5 @@ pub use tile::*; pub use tooltip::*; pub use tree::*; pub use window_drag_area::*; +#[cfg(feature = "muda")] +pub use window_menu::*; diff --git a/crates/components/src/window_menu.rs b/crates/components/src/window_menu.rs new file mode 100644 index 000000000..5a597463f --- /dev/null +++ b/crates/components/src/window_menu.rs @@ -0,0 +1,156 @@ +use std::sync::Arc; + +use dioxus::prelude::*; +use freya_hooks::use_platform; +use tokio::sync::broadcast::Receiver; + +#[derive(Clone)] +struct RootMarkerInit; + +#[component] +pub fn WindowMenu(menu: ReadOnlySignal) -> Element { + let platform = use_platform(); + let click_receiver = use_hook(|| { + if try_consume_context::().is_none() { + let menu_bar = Arc::new(SharedMenu(consume_context::())); + struct SharedMenu(pub muda::Menu); + + unsafe impl Send for SharedMenu {} + unsafe impl Sync for SharedMenu {} + + #[cfg(target_os = "windows")] + platform.with_window(move |window| { + use winit::raw_window_handle::*; + if let RawWindowHandle::Win32(handle) = window.window_handle().unwrap().as_raw() { + unsafe { menu_bar.0.init_for_hwnd(handle.hwnd.get()).unwrap() }; + } + }); + + #[cfg(target_os = "macos")] + menu_bar.0.init_for_nsapp(); + } + + provide_root_context(RootMarkerInit); + + let (tx, rx) = tokio::sync::broadcast::channel::(5); + + muda::MenuEvent::set_event_handler(Some(move |event| { + tx.send(event).ok(); + })); + + MenuEventReceiver(rx) + }); + + let (creation_sender, creation_receiver) = use_hook(|| { + let (tx, rx) = tokio::sync::broadcast::channel::<()>(50); + + (tx, MenuEventReceiver(rx)) + }); + + use_effect(move || { + menu.read(); + + let menu_bar = consume_context::(); + + for item in menu_bar.items() { + if let Some(item) = item.as_menuitem() { + menu_bar.remove(item).expect("Failed to remove menu."); + } else if let Some(submenu) = item.as_submenu() { + menu_bar.remove(submenu).expect("Failed to remove submenu."); + } + } + + creation_sender + .send(()) + .expect("Failed to notify menus of an update."); + }); + + provide_context(click_receiver); + provide_context(creation_receiver); + + menu() +} + +struct MenuEventReceiver(pub Receiver); + +impl Clone for MenuEventReceiver { + fn clone(&self) -> Self { + Self(self.0.resubscribe()) + } +} + +#[component] +pub fn WindowSubMenu( + text: ReadOnlySignal, + enabled: ReadOnlySignal>, + children: Element, +) -> Element { + let mut submenu = use_context_provider(|| Signal::new(None)); + + use_hook(move || { + let mut creation_receiver = consume_context::>(); + + spawn(async move { + while creation_receiver.0.recv().await.is_ok() { + let menu_bar = consume_context::(); + let new_submenu = muda::Submenu::new(&*text.peek(), enabled.peek().clone().unwrap_or(true)); + + menu_bar.append(&new_submenu).unwrap(); + submenu.set(Some(new_submenu)); + } + }); + }); + + children +} + +#[component] +pub fn WindowMenuItem( + onclick: ReadOnlySignal>>, + text: ReadOnlySignal, + enabled: ReadOnlySignal>, +) -> Element { + let mut menu = use_signal(|| None); + + use_hook(move || { + let mut creation_receiver = consume_context::>(); + + spawn(async move { + while creation_receiver.0.recv().await.is_ok() { + let submenu_signal = try_consume_context::>>(); + + let new_menu_item = muda::MenuItem::new(&*text.peek(), enabled.peek().clone().unwrap_or(true), None); + + if let Some(submenu_signal) = submenu_signal { + let submenu = submenu_signal.peek(); + let submenu = submenu.as_ref().unwrap(); + submenu.append(&new_menu_item).unwrap(); + } else { + let menu_bar = consume_context::(); + menu_bar.append(&new_menu_item).unwrap(); + } + + menu.set(Some(new_menu_item)); + } + }); + }); + + use_hook(move || { + let mut click_receiver = consume_context::>(); + + spawn(async move { + while let Ok(event) = click_receiver.0.recv().await { + if let Some(menu) = &*menu.read() { + if event.id == menu.id() { + println!("{}", onclick.peek().is_some()); + if let Some(onclick) = &*onclick.peek() { + onclick.call(()); + } + } + } + } + }); + }); + + Ok(VNode::placeholder()) +} diff --git a/crates/freya/Cargo.toml b/crates/freya/Cargo.toml index 8d1de04f2..57354d2ef 100644 --- a/crates/freya/Cargo.toml +++ b/crates/freya/Cargo.toml @@ -27,6 +27,7 @@ performance-overlay = [] fade-cached-incremental-areas = ["freya-core/fade-cached-incremental-areas"] disable-zoom-shortcuts = ["freya-renderer/disable-zoom-shortcuts"] docs = ["dep:freya-testing", "dep:dioxus-i18n", "dep:dioxus-router"] +muda = ["freya-components/muda"] [dependencies] freya-devtools = { workspace = true, optional = true } diff --git a/examples/muda.rs b/examples/muda.rs index 0151a9ba6..e36f8d704 100644 --- a/examples/muda.rs +++ b/examples/muda.rs @@ -3,10 +3,9 @@ windows_subsystem = "windows" )] -use std::sync::Arc; +use std::fmt::Display; use freya::prelude::*; -use tokio::sync::broadcast::Receiver; fn main() { let menu_bar = muda::Menu::new(); @@ -37,189 +36,158 @@ fn main() { ); } -fn app() -> Element { + +#[derive(Clone, Copy, PartialEq)] +enum MenuExample { + Counter, + FileEditor, + Minimal +} + +impl Display for MenuExample { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Counter => f.write_str("Counter"), + Self::FileEditor => f.write_str("FileEditor"), + Self::Minimal => f.write_str("Minimal"), + } + } +} + +#[component] +fn CounterExample() -> Element { let mut count = use_signal(|| 0); + println!("{count:?}"); + rsx!( WindowMenu { menu: rsx!( - MenuItem { - text: "+", - enabled: true, - onclick: move |_| count += 1 + WindowMenuItem { + text: "+", + onclick: move |_| { + println!("increased"); + count += 1 } - MenuItem { - text: "{count}", - enabled: true, + } + WindowMenuItem { + text: "{count}", + onclick: move |_| {} + } + WindowMenuItem { + text: "-", + onclick: move |_| count -= 1 + } + if count() < 3 { + WindowMenuItem { + text: "count is smaller than 3", onclick: move |_| {} } - MenuItem { - text: "-", - enabled: true, - onclick: move |_| count -= 1 + } + WindowSubMenu { + text: "Stuff", + WindowMenuItem { + text: "Reset to 0", + onclick: move |_| count.set(0) } - if count() < 3 { - MenuItem { - text: "count is smaller than 3", - enabled: true, - onclick: move |_| {} + } + ) + } + ) +} + + +#[component] +fn FileEditor() -> Element { + rsx!( + + WindowMenu { + menu: rsx!( + WindowSubMenu { + text: "File", + WindowMenuItem { + text: "New File", + } + WindowMenuItem { + text: "Open", + } + WindowMenuItem { + text: "Save", } + WindowMenuItem { + text: "Save As", + } + WindowMenuItem { + text: "Close", + } + } + WindowSubMenu { + text: "Beta Features", + enabled: false } - SubMenu { - text: "Stuff", - enabled: true, - MenuItem { - text: "Reset to 0", - enabled: true, - onclick: move |_| count.set(0) + WindowSubMenu { + text: "About", + WindowMenuItem { + text: "Help", + } + WindowMenuItem { + text: "Contact", + } + WindowMenuItem { + text: "Version 0.3.0", } } ) } - label { - "{count}" - } ) } #[component] -fn WindowMenu(menu: ReadOnlySignal) -> Element { - let platform = use_platform(); - - let click_receiver = use_hook(|| { - let menu_bar = Arc::new(SharedMenu(consume_context::())); - struct SharedMenu(pub muda::Menu); - - unsafe impl Send for SharedMenu {} - unsafe impl Sync for SharedMenu {} - - #[cfg(target_os = "windows")] - platform.with_window(move |window| { - use winit::raw_window_handle::*; - if let RawWindowHandle::Win32(handle) = window.window_handle().unwrap().as_raw() { - unsafe { menu_bar.0.init_for_hwnd(handle.hwnd.get()).unwrap() }; - } - }); - - #[cfg(target_os = "macos")] - menu_bar.0.init_for_nsapp(); - - let (tx, rx) = tokio::sync::broadcast::channel::(5); - - muda::MenuEvent::set_event_handler(Some(move |event| { - tx.send(event).ok(); - })); - - MenuEventReceiver(rx) - }); - - let (creation_sender, creation_receiver) = use_hook(|| { - let (tx, rx) = tokio::sync::broadcast::channel::<()>(50); - - (tx, MenuEventReceiver(rx)) - }); - - use_effect(move || { - menu.read(); - - let menu_bar = consume_context::(); - - for item in menu_bar.items() { - if let Some(item) = item.as_menuitem() { - menu_bar.remove(item).expect("Failed to remove menu."); - } else if let Some(submenu) = item.as_submenu() { - menu_bar.remove(submenu).expect("Failed to remove submenu."); +fn Minimal() -> Element { + rsx!( + WindowMenu { + menu: rsx!( + WindowSubMenu { + text: "About", + WindowMenuItem { + text: "Help", + } + WindowMenuItem { + text: "Contact", + } + WindowMenuItem { + text: "Version 0.3.0", + } } - } - - creation_sender - .send(()) - .expect("Failed to notify menus of an update."); - }); - - provide_context(click_receiver); - provide_context(creation_receiver); - - menu() -} - -struct MenuEventReceiver(pub Receiver); - -impl Clone for MenuEventReceiver { - fn clone(&self) -> Self { - Self(self.0.resubscribe()) + ) } + ) } -#[component] -fn SubMenu( - text: ReadOnlySignal, - enabled: ReadOnlySignal, - children: Element, -) -> Element { - let mut submenu = use_context_provider(|| Signal::new(None)); - - use_hook(move || { - let mut creation_receiver = consume_context::>(); - - spawn(async move { - while creation_receiver.0.recv().await.is_ok() { - let menu_bar = consume_context::(); - let new_submenu = muda::Submenu::new(&*text.peek(), *enabled.peek()); - - menu_bar.append(&new_submenu).unwrap(); - submenu.set(Some(new_submenu)); - } - }); - }); - - children -} - -#[component] -fn MenuItem( - onclick: ReadOnlySignal>, - text: ReadOnlySignal, - enabled: ReadOnlySignal, -) -> Element { - let mut menu = use_signal(|| None); - - use_hook(move || { - let mut creation_receiver = consume_context::>(); - - spawn(async move { - while creation_receiver.0.recv().await.is_ok() { - let submenu_signal = try_consume_context::>>(); - - let new_menu_item = muda::MenuItem::new(&*text.peek(), *enabled.peek(), None); - - if let Some(submenu_signal) = submenu_signal { - let submenu = submenu_signal.peek(); - let submenu = submenu.as_ref().unwrap(); - submenu.append(&new_menu_item).unwrap(); - } else { - let menu_bar = consume_context::(); - menu_bar.append(&new_menu_item).unwrap(); - } - - menu.set(Some(new_menu_item)); - } - }); - }); - - use_hook(move || { - let mut click_receiver = consume_context::>(); +fn app() -> Element { + let mut example = use_signal(|| MenuExample::Counter); - spawn(async move { - while let Ok(event) = click_receiver.0.recv().await { - if let Some(menu) = &*menu.read() { - if event.id == menu.id() { - onclick.peek().call(()); + rsx!( + match example() { + MenuExample::Counter => rsx!( CounterExample {} ), + MenuExample::FileEditor => rsx!( FileEditor {} ), + MenuExample::Minimal => rsx!( Minimal {} ), + } + rect { + main_align: "center", + cross_align: "center", + width: "fill", + height: "fill", + Dropdown { + value: example(), + for ex in [MenuExample::Counter, MenuExample::FileEditor, MenuExample::Minimal] { + DropdownItem { + value: ex, + onpress: move |_| example.set(ex), + label { "{ex}" } } } } - }); - }); - - None + } + ) }