1
0
Эх сурвалжийг харах

Add a menu bar option to the desktop config (#2107)

* add an option to set a custom menu in the desktop config

* Fix rename issue

---------

Co-authored-by: Jonathan Kelley <jkelleyrtp@gmail.com>
Evan Almloff 1 жил өмнө
parent
commit
d442dac168

+ 1 - 1
Cargo.toml

@@ -63,7 +63,7 @@ dioxus-html-internal-macro = { path = "packages/html-internal-macro", version =
 dioxus-hooks = { path = "packages/hooks", version = "0.5.0-alpha.2" }
 dioxus-web = { path = "packages/web", version = "0.5.0-alpha.2" }
 dioxus-ssr = { path = "packages/ssr", version = "0.5.0-alpha.2", default-features = false }
-dioxus-desktop = { path = "packages/desktop", version = "0.5.0-alpha.2" }
+dioxus-desktop = { path = "packages/desktop", version = "0.5.0-alpha.2", default-features = false }
 dioxus-mobile = { path = "packages/mobile", version = "0.5.0-alpha.2" }
 dioxus-interpreter-js = { path = "packages/interpreter", version = "0.5.0-alpha.2" }
 dioxus-liveview = { path = "packages/liveview", version = "0.5.0-alpha.2" }

+ 35 - 0
examples/custom_menu.rs

@@ -0,0 +1,35 @@
+//! This example shows how to use a custom menu bar with Dioxus desktop.
+//! This example is not supported on the mobile or web renderers.
+
+use dioxus::desktop::muda::*;
+use dioxus::prelude::*;
+
+fn main() {
+    // Create a menu bar that only contains the edit menu
+    let menu = Menu::new();
+    let edit_menu = Submenu::new("Edit", true);
+
+    edit_menu
+        .append_items(&[
+            &PredefinedMenuItem::undo(None),
+            &PredefinedMenuItem::redo(None),
+            &PredefinedMenuItem::separator(),
+            &PredefinedMenuItem::cut(None),
+            &PredefinedMenuItem::copy(None),
+            &PredefinedMenuItem::paste(None),
+            &PredefinedMenuItem::select_all(None),
+        ])
+        .unwrap();
+
+    menu.append(&edit_menu).unwrap();
+
+    // Create a desktop config that overrides the default menu with the custom menu
+    let config = dioxus::desktop::Config::new().with_menu(menu);
+
+    // Launch the app with the custom menu
+    LaunchBuilder::new().with_cfg(config).launch(app)
+}
+
+fn app() -> Element {
+    rsx! {"Hello World!"}
+}

+ 2 - 2
examples/window_focus.rs

@@ -8,12 +8,12 @@
 use dioxus::desktop::tao::event::Event as WryEvent;
 use dioxus::desktop::tao::event::WindowEvent;
 use dioxus::desktop::use_wry_event_handler;
-use dioxus::desktop::{Config, WindowCloseBehaviour};
+use dioxus::desktop::{Config, WindowCloseBehavior};
 use dioxus::prelude::*;
 
 fn main() {
     LaunchBuilder::desktop()
-        .with_cfg(Config::new().with_close_behaviour(WindowCloseBehaviour::CloseWindow))
+        .with_cfg(Config::new().with_close_behaviour(WindowCloseBehavior::CloseWindow))
         .launch(app)
 }
 

+ 5 - 7
packages/desktop/src/app.rs

@@ -1,5 +1,5 @@
 use crate::{
-    config::{Config, WindowCloseBehaviour},
+    config::{Config, WindowCloseBehavior},
     element::DesktopElement,
     event_handlers::WindowEventHandlers,
     file_upload::{DesktopFileDragEvent, DesktopFileUploadForm, FileDialogRequest},
@@ -33,7 +33,7 @@ pub(crate) struct App {
     // Stuff we need mutable access to
     pub(crate) control_flow: ControlFlow,
     pub(crate) is_visible_before_start: bool,
-    pub(crate) window_behavior: WindowCloseBehaviour,
+    pub(crate) window_behavior: WindowCloseBehavior,
     pub(crate) webviews: HashMap<WindowId, WebviewInstance>,
 
     /// This single blob of state is shared between all the windows so they have access to the runtime state
@@ -43,8 +43,6 @@ pub(crate) struct App {
 }
 
 /// A bundle of state shared between all the windows, providing a way for us to communicate with running webview.
-///
-/// Todo: everything in this struct is wrapped in Rc<>, but we really only need the one top-level refcell
 pub(crate) struct SharedContext {
     pub(crate) event_handlers: WindowEventHandlers,
     pub(crate) pending_webviews: RefCell<Vec<WebviewInstance>>,
@@ -58,7 +56,7 @@ impl App {
         let event_loop = EventLoopBuilder::<UserWindowEvent>::with_user_event().build();
 
         let app = Self {
-            window_behavior: cfg.last_window_close_behaviour,
+            window_behavior: cfg.last_window_close_behavior,
             is_visible_before_start: true,
             webviews: HashMap::new(),
             control_flow: ControlFlow::Wait,
@@ -128,7 +126,7 @@ impl App {
     }
 
     pub fn handle_close_requested(&mut self, id: WindowId) {
-        use WindowCloseBehaviour::*;
+        use WindowCloseBehavior::*;
 
         match self.window_behavior {
             LastWindowExitsApp => {
@@ -156,7 +154,7 @@ impl App {
 
         if matches!(
             self.window_behavior,
-            WindowCloseBehaviour::LastWindowExitsApp
+            WindowCloseBehavior::LastWindowExitsApp
         ) && self.webviews.is_empty()
         {
             self.control_flow = ControlFlow::Exit

+ 22 - 16
packages/desktop/src/config.rs

@@ -3,9 +3,11 @@ use std::path::PathBuf;
 use tao::window::{Icon, WindowBuilder};
 use wry::http::{Request as HttpRequest, Response as HttpResponse};
 
+use crate::menubar::{default_menu_bar, DioxusMenu};
+
 /// The behaviour of the application when the last window is closed.
 #[derive(Copy, Clone, Eq, PartialEq)]
-pub enum WindowCloseBehaviour {
+pub enum WindowCloseBehavior {
     /// Default behaviour, closing the last window exits the app
     LastWindowExitsApp,
     /// Closing the last window will not actually close it, just hide it
@@ -17,6 +19,7 @@ pub enum WindowCloseBehaviour {
 /// The configuration for the desktop application.
 pub struct Config {
     pub(crate) window: WindowBuilder,
+    pub(crate) menu: Option<DioxusMenu>,
     pub(crate) protocols: Vec<WryProtocol>,
     pub(crate) pre_rendered: Option<String>,
     pub(crate) disable_context_menu: bool,
@@ -26,8 +29,7 @@ pub struct Config {
     pub(crate) custom_index: Option<String>,
     pub(crate) root_name: String,
     pub(crate) background_color: Option<(u8, u8, u8, u8)>,
-    pub(crate) last_window_close_behaviour: WindowCloseBehaviour,
-    pub(crate) enable_default_menu_bar: bool,
+    pub(crate) last_window_close_behavior: WindowCloseBehavior,
 }
 
 pub(crate) type WryProtocol = (
@@ -48,6 +50,7 @@ impl Config {
 
         Self {
             window,
+            menu: Some(default_menu_bar()),
             protocols: Vec::new(),
             pre_rendered: None,
             disable_context_menu: !cfg!(debug_assertions),
@@ -57,19 +60,10 @@ impl Config {
             custom_index: None,
             root_name: "main".to_string(),
             background_color: None,
-            last_window_close_behaviour: WindowCloseBehaviour::LastWindowExitsApp,
-            enable_default_menu_bar: true,
+            last_window_close_behavior: WindowCloseBehavior::LastWindowExitsApp,
         }
     }
 
-    /// Set whether the default menu bar should be enabled.
-    ///
-    /// > Note: `enable` is `true` by default. To disable the default menu bar pass `false`.
-    pub fn with_default_menu_bar(mut self, enable: bool) -> Self {
-        self.enable_default_menu_bar = enable;
-        self
-    }
-
     /// set the directory from which assets will be searched in release mode
     pub fn with_resource_directory(mut self, path: impl Into<PathBuf>) -> Self {
         self.resource_dir = Some(path.into());
@@ -105,8 +99,8 @@ impl Config {
     }
 
     /// Sets the behaviour of the application when the last window is closed.
-    pub fn with_close_behaviour(mut self, behaviour: WindowCloseBehaviour) -> Self {
-        self.last_window_close_behaviour = behaviour;
+    pub fn with_close_behaviour(mut self, behaviour: WindowCloseBehavior) -> Self {
+        self.last_window_close_behavior = behaviour;
         self
     }
 
@@ -146,7 +140,7 @@ impl Config {
 
     /// Set the name of the element that Dioxus will use as the root.
     ///
-    /// This is akint to calling React.render() on the element with the specified name.
+    /// This is akin to calling React.render() on the element with the specified name.
     pub fn with_root_name(mut self, name: impl Into<String>) -> Self {
         self.root_name = name.into();
         self
@@ -159,6 +153,18 @@ impl Config {
         self.background_color = Some(color);
         self
     }
+
+    /// Sets the menu the window will use. This will override the default menu bar.
+    ///
+    /// > Note: A default menu bar will be enabled unless the menu is overridden or set to `None`.
+    #[allow(unused)]
+    pub fn with_menu(mut self, menu: impl Into<Option<DioxusMenu>>) -> Self {
+        #[cfg(not(any(target_os = "ios", target_os = "android")))]
+        {
+            self.menu = menu.into();
+        }
+        self
+    }
 }
 
 impl Default for Config {

+ 4 - 1
packages/desktop/src/lib.rs

@@ -35,10 +35,13 @@ pub use tao::dpi::{LogicalPosition, LogicalSize};
 pub use tao::event::WindowEvent;
 pub use tao::window::WindowBuilder;
 pub use wry;
+// Reexport muda only if we are on desktop platforms that support menus
+#[cfg(not(any(target_os = "ios", target_os = "android")))]
+pub use muda;
 
 // Public exports
 pub use assets::AssetRequest;
-pub use config::{Config, WindowCloseBehaviour};
+pub use config::{Config, WindowCloseBehavior};
 pub use desktop_context::{window, DesktopContext, DesktopService};
 pub use event_handlers::WryEventHandler;
 pub use hooks::{use_asset_handler, use_global_shortcut, use_window, use_wry_event_handler};

+ 65 - 56
packages/desktop/src/menubar.rs

@@ -1,31 +1,39 @@
-use std::any::Any;
-
 use tao::window::Window;
 
+#[cfg(not(any(target_os = "ios", target_os = "android")))]
+pub type DioxusMenu = muda::Menu;
+#[cfg(any(target_os = "ios", target_os = "android"))]
+pub type DioxusMenu = ();
+
+/// Initializes the menu bar for the window.
 #[allow(unused)]
-pub fn build_menu(window: &Window, default_menu_bar: bool) -> Option<Box<dyn Any>> {
+pub fn init_menu_bar(menu: &DioxusMenu, window: &Window) {
     #[cfg(not(any(target_os = "ios", target_os = "android")))]
     {
-        return Some(Box::new(impl_::build_menu_bar(default_menu_bar, window)) as Box<dyn Any>);
+        desktop_platforms::init_menu_bar(menu, window);
     }
+}
 
-    None
+/// Creates a standard menu bar depending on the users platform. It may be used as a starting point
+/// to further customize the menu bar and pass it to a [`WindowBuilder`](tao::window::WindowBuilder).
+/// > Note: The default menu bar enables macOS shortcuts like cut/copy/paste.
+/// > The menu bar differs per platform because of constraints introduced
+/// > by [`MenuItem`](tao::menu::MenuItem).
+#[allow(unused)]
+pub fn default_menu_bar() -> DioxusMenu {
+    #[cfg(not(any(target_os = "ios", target_os = "android")))]
+    {
+        desktop_platforms::default_menu_bar()
+    }
 }
 
 #[cfg(not(any(target_os = "ios", target_os = "android")))]
-mod impl_ {
+mod desktop_platforms {
     use super::*;
     use muda::{Menu, MenuItem, PredefinedMenuItem, Submenu};
 
-    /// Builds a standard menu bar depending on the users platform. It may be used as a starting point
-    /// to further customize the menu bar and pass it to a [`WindowBuilder`](tao::window::WindowBuilder).
-    /// > Note: The default menu bar enables macOS shortcuts like cut/copy/paste.
-    /// > The menu bar differs per platform because of constraints introduced
-    /// > by [`MenuItem`](tao::menu::MenuItem).
     #[allow(unused)]
-    pub fn build_menu_bar(default: bool, window: &Window) -> Menu {
-        let menu = Menu::new();
-
+    pub fn init_menu_bar(menu: &Menu, window: &Window) {
         #[cfg(target_os = "windows")]
         {
             use tao::platform::windows::WindowExtWindows;
@@ -44,53 +52,54 @@ mod impl_ {
             use tao::platform::macos::WindowExtMacOS;
             menu.init_for_nsapp();
         }
+    }
 
-        if default {
-            // since it is uncommon on windows to have an "application menu"
-            // we add a "window" menu to be more consistent across platforms with the standard menu
-            let window_menu = Submenu::new("Window", true);
-            window_menu
-                .append_items(&[
-                    &PredefinedMenuItem::fullscreen(None),
-                    &PredefinedMenuItem::separator(),
-                    &PredefinedMenuItem::hide(None),
-                    &PredefinedMenuItem::hide_others(None),
-                    &PredefinedMenuItem::show_all(None),
-                    &PredefinedMenuItem::maximize(None),
-                    &PredefinedMenuItem::minimize(None),
-                    &PredefinedMenuItem::close_window(None),
-                    &PredefinedMenuItem::separator(),
-                    &PredefinedMenuItem::quit(None),
-                ])
-                .unwrap();
+    pub fn default_menu_bar() -> Menu {
+        let menu = Menu::new();
+        // since it is uncommon on windows to have an "application menu"
+        // we add a "window" menu to be more consistent across platforms with the standard menu
+        let window_menu = Submenu::new("Window", true);
+        window_menu
+            .append_items(&[
+                &PredefinedMenuItem::fullscreen(None),
+                &PredefinedMenuItem::separator(),
+                &PredefinedMenuItem::hide(None),
+                &PredefinedMenuItem::hide_others(None),
+                &PredefinedMenuItem::show_all(None),
+                &PredefinedMenuItem::maximize(None),
+                &PredefinedMenuItem::minimize(None),
+                &PredefinedMenuItem::close_window(None),
+                &PredefinedMenuItem::separator(),
+                &PredefinedMenuItem::quit(None),
+            ])
+            .unwrap();
 
-            let edit_menu = Submenu::new("Edit", true);
-            edit_menu
-                .append_items(&[
-                    &PredefinedMenuItem::undo(None),
-                    &PredefinedMenuItem::redo(None),
-                    &PredefinedMenuItem::separator(),
-                    &PredefinedMenuItem::cut(None),
-                    &PredefinedMenuItem::copy(None),
-                    &PredefinedMenuItem::paste(None),
-                    &PredefinedMenuItem::separator(),
-                    &PredefinedMenuItem::select_all(None),
-                ])
-                .unwrap();
+        let edit_menu = Submenu::new("Edit", true);
+        edit_menu
+            .append_items(&[
+                &PredefinedMenuItem::undo(None),
+                &PredefinedMenuItem::redo(None),
+                &PredefinedMenuItem::separator(),
+                &PredefinedMenuItem::cut(None),
+                &PredefinedMenuItem::copy(None),
+                &PredefinedMenuItem::paste(None),
+                &PredefinedMenuItem::separator(),
+                &PredefinedMenuItem::select_all(None),
+            ])
+            .unwrap();
 
-            let help_menu = Submenu::new("Help", true);
-            help_menu
-                .append_items(&[&MenuItem::new("Toggle Developer Tools", true, None)])
-                .unwrap();
+        let help_menu = Submenu::new("Help", true);
+        help_menu
+            .append_items(&[&MenuItem::new("Toggle Developer Tools", true, None)])
+            .unwrap();
 
-            menu.append_items(&[&window_menu, &edit_menu, &help_menu])
-                .unwrap();
+        menu.append_items(&[&window_menu, &edit_menu, &help_menu])
+            .unwrap();
 
-            #[cfg(target_os = "macos")]
-            {
-                window_menu.set_as_windows_menu_for_nsapp();
-                help_menu.set_as_help_menu_for_nsapp();
-            }
+        #[cfg(target_os = "macos")]
+        {
+            window_menu.set_as_windows_menu_for_nsapp();
+            help_menu.set_as_help_menu_for_nsapp();
         }
 
         menu

+ 9 - 6
packages/desktop/src/webview.rs

@@ -1,3 +1,4 @@
+use crate::menubar::DioxusMenu;
 use crate::{
     app::SharedContext, assets::AssetHandlerRegistry, edits::EditQueue, eval::DesktopEvalProvider,
     file_upload::NativeFileHover, ipc::UserWindowEvent, protocol, waker::tao_waker, Config,
@@ -6,7 +7,7 @@ use crate::{
 use dioxus_core::{ScopeId, VirtualDom};
 use dioxus_html::prelude::EvalProvider;
 use futures_util::{pin_mut, FutureExt};
-use std::{any::Any, rc::Rc, task::Waker};
+use std::{rc::Rc, task::Waker};
 use wry::{RequestAsyncResponder, WebContext, WebViewBuilder};
 
 pub(crate) struct WebviewInstance {
@@ -19,11 +20,11 @@ pub(crate) struct WebviewInstance {
     _web_context: WebContext,
 
     // Same with the menu.
-    // Currently it's a box<dyn any> because 1) we don't touch it and 2) we support a number of platforms
+    // Currently it's a DioxusMenu because 1) we don't touch it and 2) we support a number of platforms
     // like ios where muda does not give us a menu type. It sucks but alas.
     //
     // This would be a good thing for someone looking to contribute to fix.
-    _menu: Option<Box<dyn Any>>,
+    _menu: Option<DioxusMenu>,
 }
 
 impl WebviewInstance {
@@ -165,9 +166,11 @@ impl WebviewInstance {
 
         let webview = webview.build().unwrap();
 
-        // TODO: allow users to specify their own menubars, again :/
         let menu = if cfg!(not(any(target_os = "android", target_os = "ios"))) {
-            crate::menubar::build_menu(&window, cfg.enable_default_menu_bar)
+            if let Some(menu) = &cfg.menu {
+                crate::menubar::init_menu_bar(menu, &window);
+            }
+            cfg.menu
         } else {
             None
         };
@@ -221,7 +224,7 @@ impl WebviewInstance {
         }
     }
 
-    #[allow(unused)]
+    #[cfg(all(feature = "hot-reload", debug_assertions))]
     pub fn kick_stylsheets(&self) {
         // run eval in the webview to kick the stylesheets by appending a query string
         // we should do something less clunky than this

+ 3 - 3
packages/dioxus/Cargo.toml

@@ -20,14 +20,14 @@ dioxus-signals = { workspace = true, optional = true }
 dioxus-router = { workspace = true, optional = true }
 dioxus-web = { workspace = true, optional = true }
 dioxus-mobile = { workspace = true, optional = true }
-dioxus-desktop = { workspace = true, optional = true }
+dioxus-desktop = { workspace = true, default-features = true, optional = true }
 dioxus-fullstack = { workspace = true, optional = true }
 dioxus-liveview = { workspace = true, optional = true }
 dioxus-ssr ={ workspace = true, optional = true }
 
 serde = { version = "1.0.136", optional = true }
 
-[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
+[target.'cfg(not(any(target_arch = "wasm32", target_os = "ios", target_os = "android")))'.dependencies]
 dioxus-hot-reload = { workspace = true, optional = true }
 
 [features]
@@ -44,7 +44,7 @@ router = ["dioxus-router"]
 # Platforms
 fullstack = ["dioxus-fullstack", "dioxus-config-macro/fullstack", "serde", "dioxus-router?/fullstack"]
 desktop = ["dioxus-desktop", "dioxus-fullstack?/desktop", "dioxus-config-macro/desktop"]
-mobile = ["dioxus-mobile", "dioxus-desktop", "dioxus-fullstack?/mobile", "dioxus-config-macro/mobile"]
+mobile = ["dioxus-mobile", "dioxus-fullstack?/mobile", "dioxus-config-macro/mobile"]
 web = ["dioxus-web", "dioxus-fullstack?/web", "dioxus-config-macro/web", "dioxus-router?/web"]
 ssr = ["dioxus-ssr", "dioxus-router?/ssr", "dioxus-config-macro/ssr"]
 liveview = ["dioxus-liveview", "dioxus-config-macro/liveview", "dioxus-router?/liveview"]

+ 1 - 1
packages/dioxus/src/launch.rs

@@ -151,7 +151,7 @@ mod current_platform {
     pub use dioxus_desktop::launch::*;
 
     #[cfg(all(feature = "mobile", not(feature = "fullstack")))]
-    pub use dioxus_desktop::launch::*;
+    pub use dioxus_mobile::launch::*;
 
     #[cfg(feature = "fullstack")]
     pub use dioxus_fullstack::launch::*;

+ 5 - 2
packages/dioxus/src/lib.rs

@@ -63,7 +63,10 @@ pub mod prelude {
     #[cfg_attr(docsrs, doc(cfg(feature = "html")))]
     pub use dioxus_elements::{prelude::*, GlobalAttributes, SvgAttributes};
 
-    #[cfg(all(not(target_arch = "wasm32"), feature = "hot-reload"))]
+    #[cfg(all(
+        not(any(target_arch = "wasm32", target_os = "ios", target_os = "android")),
+        feature = "hot-reload"
+    ))]
     #[cfg_attr(docsrs, doc(cfg(feature = "hot-reload")))]
     pub use dioxus_hot_reload::{self, hot_reload_init};
 
@@ -100,7 +103,7 @@ pub use dioxus_desktop as desktop;
 
 #[cfg(feature = "mobile")]
 #[cfg_attr(docsrs, doc(cfg(feature = "mobile")))]
-pub use dioxus_desktop as mobile;
+pub use dioxus_mobile as mobile;
 
 #[cfg(feature = "liveview")]
 #[cfg_attr(docsrs, doc(cfg(feature = "liveview")))]