mirror of https://github.com/tauri-apps/tauri
feat(core): window menus (#1745)
This commit is contained in:
parent
10715e63b1
commit
41d5d6aff2
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
"tauri": patch
|
||||
---
|
||||
|
||||
Implemented window menus APIs.
|
|
@ -24,7 +24,7 @@ thiserror = "1.0.24"
|
|||
once_cell = "1.7.2"
|
||||
tauri-macros = { version = "1.0.0-beta-rc.1", path = "../tauri-macros" }
|
||||
tauri-utils = { version = "1.0.0-beta-rc.1", path = "../tauri-utils" }
|
||||
wry = { git = "https://github.com/tauri-apps/wry", rev = "0570dcab90087af5b1d29218d9d25186a7ade357" }
|
||||
wry = { git = "https://github.com/tauri-apps/wry", rev = "6bc97aff525644b83a3a00537316c46d7afb985b" }
|
||||
rand = "0.8"
|
||||
reqwest = { version = "0.11", features = [ "json", "multipart" ] }
|
||||
tempfile = "3"
|
||||
|
|
|
@ -59,10 +59,12 @@ pub use {
|
|||
Invoke, InvokeError, InvokeHandler, InvokeMessage, InvokeResolver, InvokeResponse, OnPageLoad,
|
||||
PageLoadPayload, SetupHook,
|
||||
},
|
||||
self::runtime::app::{App, Builder},
|
||||
self::runtime::app::{App, Builder, WindowMenuEvent},
|
||||
self::runtime::flavors::wry::Wry,
|
||||
self::runtime::monitor::Monitor,
|
||||
self::runtime::webview::{WebviewAttributes, WindowBuilder},
|
||||
self::runtime::webview::{
|
||||
CustomMenuItem, Menu, MenuItem, MenuItemId, WebviewAttributes, WindowBuilder,
|
||||
},
|
||||
self::runtime::window::{
|
||||
export::{
|
||||
dpi::{LogicalPosition, LogicalSize, PhysicalPosition, PhysicalSize, Pixel, Position, Size},
|
||||
|
|
|
@ -10,7 +10,7 @@ use crate::{
|
|||
flavors::wry::Wry,
|
||||
manager::{Args, WindowManager},
|
||||
tag::Tag,
|
||||
webview::{CustomProtocol, WebviewAttributes, WindowBuilder},
|
||||
webview::{CustomProtocol, Menu, MenuItemId, WebviewAttributes, WindowBuilder},
|
||||
window::PendingWindow,
|
||||
Dispatch, Runtime,
|
||||
},
|
||||
|
@ -23,6 +23,26 @@ use std::{collections::HashMap, sync::Arc};
|
|||
#[cfg(feature = "updater")]
|
||||
use crate::updater;
|
||||
|
||||
pub(crate) type GlobalMenuEventListener<P> = Box<dyn Fn(WindowMenuEvent<P>) + Send + Sync>;
|
||||
|
||||
/// A menu event that was triggered on a window.
|
||||
pub struct WindowMenuEvent<P: Params> {
|
||||
pub(crate) menu_item_id: MenuItemId,
|
||||
pub(crate) window: Window<P>,
|
||||
}
|
||||
|
||||
impl<P: Params> WindowMenuEvent<P> {
|
||||
/// The menu item id.
|
||||
pub fn menu_item_id(&self) -> MenuItemId {
|
||||
self.menu_item_id
|
||||
}
|
||||
|
||||
/// The window that the menu belongs to.
|
||||
pub fn window(&self) -> &Window<P> {
|
||||
&self.window
|
||||
}
|
||||
}
|
||||
|
||||
/// A handle to the currently running application.
|
||||
///
|
||||
/// This type implements [`Manager`] which allows for manipulation of global application items.
|
||||
|
@ -154,6 +174,12 @@ where
|
|||
|
||||
/// App state.
|
||||
state: StateManager,
|
||||
|
||||
/// The menu set to all windows.
|
||||
menu: Vec<Menu>,
|
||||
|
||||
/// Menu event handlers that listens to all windows.
|
||||
menu_event_listeners: Vec<GlobalMenuEventListener<Args<E, L, A, R>>>,
|
||||
}
|
||||
|
||||
impl<E, L, A, R> Builder<E, L, A, R>
|
||||
|
@ -173,6 +199,8 @@ where
|
|||
plugins: PluginStore::default(),
|
||||
uri_scheme_protocols: Default::default(),
|
||||
state: StateManager::new(),
|
||||
menu: Vec::new(),
|
||||
menu_event_listeners: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -286,6 +314,21 @@ where
|
|||
self
|
||||
}
|
||||
|
||||
/// Sets the menu to use on all windows.
|
||||
pub fn menu(mut self, menu: Vec<Menu>) -> Self {
|
||||
self.menu = menu;
|
||||
self
|
||||
}
|
||||
|
||||
/// Registers a menu event handler for all windows.
|
||||
pub fn on_menu_event<F: Fn(WindowMenuEvent<Args<E, L, A, R>>) + Send + Sync + 'static>(
|
||||
mut self,
|
||||
handler: F,
|
||||
) -> Self {
|
||||
self.menu_event_listeners.push(Box::new(handler));
|
||||
self
|
||||
}
|
||||
|
||||
/// Registers a URI scheme protocol available to all webviews.
|
||||
/// Leverages [setURLSchemeHandler](https://developer.apple.com/documentation/webkit/wkwebviewconfiguration/2875766-seturlschemehandler) on macOS,
|
||||
/// [AddWebResourceRequestedFilter](https://docs.microsoft.com/en-us/dotnet/api/microsoft.web.webview2.core.corewebview2.addwebresourcerequestedfilter?view=webview2-dotnet-1.0.774.44) on Windows
|
||||
|
@ -321,6 +364,8 @@ where
|
|||
self.on_page_load,
|
||||
self.uri_scheme_protocols,
|
||||
self.state,
|
||||
self.menu,
|
||||
self.menu_event_listeners,
|
||||
);
|
||||
|
||||
// set up all the windows defined in the config
|
||||
|
|
|
@ -8,12 +8,12 @@ use crate::{
|
|||
api::config::WindowConfig,
|
||||
runtime::{
|
||||
webview::{
|
||||
FileDropEvent, FileDropHandler, RpcRequest, WebviewRpcHandler, WindowBuilder,
|
||||
WindowBuilderBase,
|
||||
CustomMenuItem, FileDropEvent, FileDropHandler, Menu, MenuItem, MenuItemId, RpcRequest,
|
||||
WebviewRpcHandler, WindowBuilder, WindowBuilderBase,
|
||||
},
|
||||
window::{
|
||||
dpi::{LogicalPosition, LogicalSize, PhysicalPosition, PhysicalSize, Position, Size},
|
||||
DetachedWindow, PendingWindow, WindowEvent,
|
||||
DetachedWindow, MenuEvent, PendingWindow, WindowEvent,
|
||||
},
|
||||
Dispatch, Monitor, Params, Runtime,
|
||||
},
|
||||
|
@ -31,6 +31,10 @@ use wry::{
|
|||
},
|
||||
event::{Event, WindowEvent as WryWindowEvent},
|
||||
event_loop::{ControlFlow, EventLoop, EventLoopProxy, EventLoopWindowTarget},
|
||||
menu::{
|
||||
CustomMenu as WryCustomMenu, Menu as WryMenu, MenuId as WryMenuId, MenuItem as WryMenuItem,
|
||||
MenuType,
|
||||
},
|
||||
monitor::MonitorHandle,
|
||||
window::{Fullscreen, Icon as WindowIcon, Window, WindowBuilder as WryWindowBuilder, WindowId},
|
||||
},
|
||||
|
@ -54,6 +58,8 @@ type CreateWebviewHandler =
|
|||
type MainThreadTask = Box<dyn FnOnce() + Send>;
|
||||
type WindowEventHandler = Box<dyn Fn(&WindowEvent) + Send>;
|
||||
type WindowEventListeners = Arc<Mutex<HashMap<Uuid, WindowEventHandler>>>;
|
||||
type MenuEventHandler = Box<dyn Fn(&MenuEvent) + Send>;
|
||||
type MenuEventListeners = Arc<Mutex<HashMap<Uuid, MenuEventHandler>>>;
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Debug)]
|
||||
|
@ -195,6 +201,50 @@ impl From<Position> for WryPosition {
|
|||
}
|
||||
}
|
||||
|
||||
impl From<CustomMenuItem> for WryCustomMenu {
|
||||
fn from(item: CustomMenuItem) -> Self {
|
||||
Self {
|
||||
id: WryMenuId(item.id.0),
|
||||
name: item.name,
|
||||
keyboard_accelerators: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<MenuItem> for WryMenuItem {
|
||||
fn from(item: MenuItem) -> Self {
|
||||
match item {
|
||||
MenuItem::Custom(custom) => Self::Custom(custom.into()),
|
||||
MenuItem::About(v) => Self::About(v),
|
||||
MenuItem::Hide => Self::Hide,
|
||||
MenuItem::Services => Self::Services,
|
||||
MenuItem::HideOthers => Self::HideOthers,
|
||||
MenuItem::ShowAll => Self::ShowAll,
|
||||
MenuItem::CloseWindow => Self::CloseWindow,
|
||||
MenuItem::Quit => Self::Quit,
|
||||
MenuItem::Copy => Self::Copy,
|
||||
MenuItem::Cut => Self::Cut,
|
||||
MenuItem::Undo => Self::Undo,
|
||||
MenuItem::Redo => Self::Redo,
|
||||
MenuItem::SelectAll => Self::SelectAll,
|
||||
MenuItem::Paste => Self::Paste,
|
||||
MenuItem::EnterFullScreen => Self::EnterFullScreen,
|
||||
MenuItem::Minimize => Self::Minimize,
|
||||
MenuItem::Zoom => Self::Zoom,
|
||||
MenuItem::Separator => Self::Separator,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Menu> for WryMenu {
|
||||
fn from(menu: Menu) -> Self {
|
||||
Self {
|
||||
title: menu.title,
|
||||
items: menu.items.into_iter().map(Into::into).collect(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl WindowBuilderBase for WryWindowBuilder {}
|
||||
impl WindowBuilder for WryWindowBuilder {
|
||||
fn new() -> Self {
|
||||
|
@ -226,6 +276,10 @@ impl WindowBuilder for WryWindowBuilder {
|
|||
window
|
||||
}
|
||||
|
||||
fn menu(self, menu: Vec<Menu>) -> Self {
|
||||
self.with_menu(menu.into_iter().map(Into::into).collect::<Vec<WryMenu>>())
|
||||
}
|
||||
|
||||
fn position(self, x: f64, y: f64) -> Self {
|
||||
self.with_position(WryLogicalPosition::new(x, y))
|
||||
}
|
||||
|
@ -285,6 +339,10 @@ impl WindowBuilder for WryWindowBuilder {
|
|||
fn has_icon(&self) -> bool {
|
||||
self.window.window_icon.is_some()
|
||||
}
|
||||
|
||||
fn has_menu(&self) -> bool {
|
||||
self.window.window_menu.is_some()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<WryRpcRequest> for RpcRequest {
|
||||
|
@ -353,19 +411,26 @@ enum Message {
|
|||
CreateWebview(Arc<Mutex<Option<CreateWebviewHandler>>>, Sender<WindowId>),
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
struct DispatcherContext {
|
||||
proxy: EventLoopProxy<Message>,
|
||||
task_tx: Sender<MainThreadTask>,
|
||||
window_event_listeners: WindowEventListeners,
|
||||
menu_event_listeners: MenuEventListeners,
|
||||
}
|
||||
|
||||
/// The Tauri [`Dispatch`] for [`Wry`].
|
||||
#[derive(Clone)]
|
||||
pub struct WryDispatcher {
|
||||
window_id: WindowId,
|
||||
proxy: EventLoopProxy<Message>,
|
||||
task_tx: Sender<MainThreadTask>,
|
||||
window_event_listeners: WindowEventListeners,
|
||||
context: DispatcherContext,
|
||||
}
|
||||
|
||||
macro_rules! dispatcher_getter {
|
||||
($self: ident, $message: expr) => {{
|
||||
let (tx, rx) = channel();
|
||||
$self
|
||||
.context
|
||||
.proxy
|
||||
.send_event(Message::Window($self.window_id, $message(tx)))
|
||||
.map_err(|_| crate::Error::FailedToSendMessage)?;
|
||||
|
@ -398,6 +463,7 @@ impl Dispatch for WryDispatcher {
|
|||
|
||||
fn run_on_main_thread<F: FnOnce() + Send + 'static>(&self, f: F) -> crate::Result<()> {
|
||||
self
|
||||
.context
|
||||
.task_tx
|
||||
.send(Box::new(f))
|
||||
.map_err(|_| crate::Error::FailedToSendMessage)
|
||||
|
@ -406,6 +472,7 @@ impl Dispatch for WryDispatcher {
|
|||
fn on_window_event<F: Fn(&WindowEvent) + Send + 'static>(&self, f: F) -> Uuid {
|
||||
let id = Uuid::new_v4();
|
||||
self
|
||||
.context
|
||||
.window_event_listeners
|
||||
.lock()
|
||||
.unwrap()
|
||||
|
@ -413,6 +480,17 @@ impl Dispatch for WryDispatcher {
|
|||
id
|
||||
}
|
||||
|
||||
fn on_menu_event<F: Fn(&MenuEvent) + Send + 'static>(&self, f: F) -> Uuid {
|
||||
let id = Uuid::new_v4();
|
||||
self
|
||||
.context
|
||||
.menu_event_listeners
|
||||
.lock()
|
||||
.unwrap()
|
||||
.insert(id, Box::new(f));
|
||||
id
|
||||
}
|
||||
|
||||
// Getters
|
||||
|
||||
fn scale_factor(&self) -> crate::Result<f64> {
|
||||
|
@ -464,6 +542,7 @@ impl Dispatch for WryDispatcher {
|
|||
|
||||
fn print(&self) -> crate::Result<()> {
|
||||
self
|
||||
.context
|
||||
.proxy
|
||||
.send_event(Message::Webview(self.window_id, WebviewMessage::Print))
|
||||
.map_err(|_| crate::Error::FailedToSendMessage)
|
||||
|
@ -475,14 +554,13 @@ impl Dispatch for WryDispatcher {
|
|||
) -> crate::Result<DetachedWindow<M>> {
|
||||
let (tx, rx) = channel();
|
||||
let label = pending.label.clone();
|
||||
let proxy = self.proxy.clone();
|
||||
let task_tx = self.task_tx.clone();
|
||||
let window_event_listeners = self.window_event_listeners.clone();
|
||||
let context = self.context.clone();
|
||||
self
|
||||
.context
|
||||
.proxy
|
||||
.send_event(Message::CreateWebview(
|
||||
Arc::new(Mutex::new(Some(Box::new(move |event_loop| {
|
||||
create_webview(event_loop, proxy, task_tx, window_event_listeners, pending)
|
||||
create_webview(event_loop, context, pending)
|
||||
})))),
|
||||
tx,
|
||||
))
|
||||
|
@ -490,15 +568,14 @@ impl Dispatch for WryDispatcher {
|
|||
let window_id = rx.recv().unwrap();
|
||||
let dispatcher = WryDispatcher {
|
||||
window_id,
|
||||
proxy: self.proxy.clone(),
|
||||
task_tx: self.task_tx.clone(),
|
||||
window_event_listeners: self.window_event_listeners.clone(),
|
||||
context: self.context.clone(),
|
||||
};
|
||||
Ok(DetachedWindow { label, dispatcher })
|
||||
}
|
||||
|
||||
fn set_resizable(&self, resizable: bool) -> crate::Result<()> {
|
||||
self
|
||||
.context
|
||||
.proxy
|
||||
.send_event(Message::Window(
|
||||
self.window_id,
|
||||
|
@ -509,6 +586,7 @@ impl Dispatch for WryDispatcher {
|
|||
|
||||
fn set_title<S: Into<String>>(&self, title: S) -> crate::Result<()> {
|
||||
self
|
||||
.context
|
||||
.proxy
|
||||
.send_event(Message::Window(
|
||||
self.window_id,
|
||||
|
@ -519,6 +597,7 @@ impl Dispatch for WryDispatcher {
|
|||
|
||||
fn maximize(&self) -> crate::Result<()> {
|
||||
self
|
||||
.context
|
||||
.proxy
|
||||
.send_event(Message::Window(self.window_id, WindowMessage::Maximize))
|
||||
.map_err(|_| crate::Error::FailedToSendMessage)
|
||||
|
@ -526,6 +605,7 @@ impl Dispatch for WryDispatcher {
|
|||
|
||||
fn unmaximize(&self) -> crate::Result<()> {
|
||||
self
|
||||
.context
|
||||
.proxy
|
||||
.send_event(Message::Window(self.window_id, WindowMessage::Unmaximize))
|
||||
.map_err(|_| crate::Error::FailedToSendMessage)
|
||||
|
@ -533,6 +613,7 @@ impl Dispatch for WryDispatcher {
|
|||
|
||||
fn minimize(&self) -> crate::Result<()> {
|
||||
self
|
||||
.context
|
||||
.proxy
|
||||
.send_event(Message::Window(self.window_id, WindowMessage::Minimize))
|
||||
.map_err(|_| crate::Error::FailedToSendMessage)
|
||||
|
@ -540,6 +621,7 @@ impl Dispatch for WryDispatcher {
|
|||
|
||||
fn unminimize(&self) -> crate::Result<()> {
|
||||
self
|
||||
.context
|
||||
.proxy
|
||||
.send_event(Message::Window(self.window_id, WindowMessage::Unminimize))
|
||||
.map_err(|_| crate::Error::FailedToSendMessage)
|
||||
|
@ -547,6 +629,7 @@ impl Dispatch for WryDispatcher {
|
|||
|
||||
fn show(&self) -> crate::Result<()> {
|
||||
self
|
||||
.context
|
||||
.proxy
|
||||
.send_event(Message::Window(self.window_id, WindowMessage::Show))
|
||||
.map_err(|_| crate::Error::FailedToSendMessage)
|
||||
|
@ -554,6 +637,7 @@ impl Dispatch for WryDispatcher {
|
|||
|
||||
fn hide(&self) -> crate::Result<()> {
|
||||
self
|
||||
.context
|
||||
.proxy
|
||||
.send_event(Message::Window(self.window_id, WindowMessage::Hide))
|
||||
.map_err(|_| crate::Error::FailedToSendMessage)
|
||||
|
@ -561,6 +645,7 @@ impl Dispatch for WryDispatcher {
|
|||
|
||||
fn close(&self) -> crate::Result<()> {
|
||||
self
|
||||
.context
|
||||
.proxy
|
||||
.send_event(Message::Window(self.window_id, WindowMessage::Close))
|
||||
.map_err(|_| crate::Error::FailedToSendMessage)
|
||||
|
@ -568,6 +653,7 @@ impl Dispatch for WryDispatcher {
|
|||
|
||||
fn set_decorations(&self, decorations: bool) -> crate::Result<()> {
|
||||
self
|
||||
.context
|
||||
.proxy
|
||||
.send_event(Message::Window(
|
||||
self.window_id,
|
||||
|
@ -578,6 +664,7 @@ impl Dispatch for WryDispatcher {
|
|||
|
||||
fn set_always_on_top(&self, always_on_top: bool) -> crate::Result<()> {
|
||||
self
|
||||
.context
|
||||
.proxy
|
||||
.send_event(Message::Window(
|
||||
self.window_id,
|
||||
|
@ -588,6 +675,7 @@ impl Dispatch for WryDispatcher {
|
|||
|
||||
fn set_size(&self, size: Size) -> crate::Result<()> {
|
||||
self
|
||||
.context
|
||||
.proxy
|
||||
.send_event(Message::Window(
|
||||
self.window_id,
|
||||
|
@ -598,6 +686,7 @@ impl Dispatch for WryDispatcher {
|
|||
|
||||
fn set_min_size(&self, size: Option<Size>) -> crate::Result<()> {
|
||||
self
|
||||
.context
|
||||
.proxy
|
||||
.send_event(Message::Window(
|
||||
self.window_id,
|
||||
|
@ -608,6 +697,7 @@ impl Dispatch for WryDispatcher {
|
|||
|
||||
fn set_max_size(&self, size: Option<Size>) -> crate::Result<()> {
|
||||
self
|
||||
.context
|
||||
.proxy
|
||||
.send_event(Message::Window(
|
||||
self.window_id,
|
||||
|
@ -618,6 +708,7 @@ impl Dispatch for WryDispatcher {
|
|||
|
||||
fn set_position(&self, position: Position) -> crate::Result<()> {
|
||||
self
|
||||
.context
|
||||
.proxy
|
||||
.send_event(Message::Window(
|
||||
self.window_id,
|
||||
|
@ -628,6 +719,7 @@ impl Dispatch for WryDispatcher {
|
|||
|
||||
fn set_fullscreen(&self, fullscreen: bool) -> crate::Result<()> {
|
||||
self
|
||||
.context
|
||||
.proxy
|
||||
.send_event(Message::Window(
|
||||
self.window_id,
|
||||
|
@ -638,6 +730,7 @@ impl Dispatch for WryDispatcher {
|
|||
|
||||
fn set_icon(&self, icon: Icon) -> crate::Result<()> {
|
||||
self
|
||||
.context
|
||||
.proxy
|
||||
.send_event(Message::Window(
|
||||
self.window_id,
|
||||
|
@ -648,6 +741,7 @@ impl Dispatch for WryDispatcher {
|
|||
|
||||
fn start_dragging(&self) -> crate::Result<()> {
|
||||
self
|
||||
.context
|
||||
.proxy
|
||||
.send_event(Message::Window(self.window_id, WindowMessage::DragWindow))
|
||||
.map_err(|_| crate::Error::FailedToSendMessage)
|
||||
|
@ -655,6 +749,7 @@ impl Dispatch for WryDispatcher {
|
|||
|
||||
fn eval_script<S: Into<String>>(&self, script: S) -> crate::Result<()> {
|
||||
self
|
||||
.context
|
||||
.proxy
|
||||
.send_event(Message::Webview(
|
||||
self.window_id,
|
||||
|
@ -670,6 +765,7 @@ pub struct Wry {
|
|||
webviews: HashMap<WindowId, WebView>,
|
||||
task_tx: Sender<MainThreadTask>,
|
||||
window_event_listeners: WindowEventListeners,
|
||||
menu_event_listeners: MenuEventListeners,
|
||||
task_rx: Receiver<MainThreadTask>,
|
||||
}
|
||||
|
||||
|
@ -685,6 +781,7 @@ impl Runtime for Wry {
|
|||
task_tx,
|
||||
task_rx,
|
||||
window_event_listeners: Default::default(),
|
||||
menu_event_listeners: Default::default(),
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -696,17 +793,23 @@ impl Runtime for Wry {
|
|||
let proxy = self.event_loop.create_proxy();
|
||||
let webview = create_webview(
|
||||
&self.event_loop,
|
||||
proxy.clone(),
|
||||
self.task_tx.clone(),
|
||||
self.window_event_listeners.clone(),
|
||||
DispatcherContext {
|
||||
proxy: proxy.clone(),
|
||||
task_tx: self.task_tx.clone(),
|
||||
window_event_listeners: self.window_event_listeners.clone(),
|
||||
menu_event_listeners: self.menu_event_listeners.clone(),
|
||||
},
|
||||
pending,
|
||||
)?;
|
||||
|
||||
let dispatcher = WryDispatcher {
|
||||
window_id: webview.window().id(),
|
||||
proxy,
|
||||
task_tx: self.task_tx.clone(),
|
||||
window_event_listeners: self.window_event_listeners.clone(),
|
||||
context: DispatcherContext {
|
||||
proxy,
|
||||
task_tx: self.task_tx.clone(),
|
||||
window_event_listeners: self.window_event_listeners.clone(),
|
||||
menu_event_listeners: self.menu_event_listeners.clone(),
|
||||
},
|
||||
};
|
||||
|
||||
self.webviews.insert(webview.window().id(), webview);
|
||||
|
@ -718,6 +821,7 @@ impl Runtime for Wry {
|
|||
let mut webviews = self.webviews;
|
||||
let task_rx = self.task_rx;
|
||||
let window_event_listeners = self.window_event_listeners.clone();
|
||||
let menu_event_listeners = self.menu_event_listeners.clone();
|
||||
self.event_loop.run(move |event, event_loop, control_flow| {
|
||||
*control_flow = ControlFlow::Wait;
|
||||
|
||||
|
@ -732,6 +836,17 @@ impl Runtime for Wry {
|
|||
}
|
||||
|
||||
match event {
|
||||
Event::MenuEvent {
|
||||
menu_id,
|
||||
origin: MenuType::Menubar,
|
||||
} => {
|
||||
let event = MenuEvent {
|
||||
menu_item_id: MenuItemId(menu_id.0),
|
||||
};
|
||||
for handler in menu_event_listeners.lock().unwrap().values() {
|
||||
handler(&event);
|
||||
}
|
||||
}
|
||||
Event::WindowEvent { event, window_id } => {
|
||||
if let Some(event) = WindowEventWrapper::from(&event).0 {
|
||||
for handler in window_event_listeners.lock().unwrap().values() {
|
||||
|
@ -859,9 +974,7 @@ impl Runtime for Wry {
|
|||
|
||||
fn create_webview<M: Params<Runtime = Wry>>(
|
||||
event_loop: &EventLoopWindowTarget<Message>,
|
||||
proxy: EventLoopProxy<Message>,
|
||||
task_tx: Sender<MainThreadTask>,
|
||||
window_event_listeners: WindowEventListeners,
|
||||
context: DispatcherContext,
|
||||
pending: PendingWindow<M>,
|
||||
) -> crate::Result<WebView> {
|
||||
let PendingWindow {
|
||||
|
@ -880,22 +993,12 @@ fn create_webview<M: Params<Runtime = Wry>>(
|
|||
.with_url(&url)
|
||||
.unwrap(); // safe to unwrap because we validate the URL beforehand
|
||||
if let Some(handler) = rpc_handler {
|
||||
webview_builder = webview_builder.with_rpc_handler(create_rpc_handler(
|
||||
proxy.clone(),
|
||||
task_tx.clone(),
|
||||
window_event_listeners.clone(),
|
||||
label.clone(),
|
||||
handler,
|
||||
));
|
||||
webview_builder =
|
||||
webview_builder.with_rpc_handler(create_rpc_handler(context.clone(), label.clone(), handler));
|
||||
}
|
||||
if let Some(handler) = file_drop_handler {
|
||||
webview_builder = webview_builder.with_file_drop_handler(create_file_drop_handler(
|
||||
proxy,
|
||||
task_tx,
|
||||
window_event_listeners,
|
||||
label,
|
||||
handler,
|
||||
));
|
||||
webview_builder =
|
||||
webview_builder.with_file_drop_handler(create_file_drop_handler(context, label, handler));
|
||||
}
|
||||
for (scheme, protocol) in webview_attributes.uri_scheme_protocols {
|
||||
webview_builder = webview_builder.with_custom_protocol(scheme, move |_window, url| {
|
||||
|
@ -916,9 +1019,7 @@ fn create_webview<M: Params<Runtime = Wry>>(
|
|||
|
||||
/// Create a wry rpc handler from a tauri rpc handler.
|
||||
fn create_rpc_handler<M: Params<Runtime = Wry>>(
|
||||
proxy: EventLoopProxy<Message>,
|
||||
task_tx: Sender<MainThreadTask>,
|
||||
window_event_listeners: WindowEventListeners,
|
||||
context: DispatcherContext,
|
||||
label: M::Label,
|
||||
handler: WebviewRpcHandler<M>,
|
||||
) -> Box<dyn Fn(&Window, WryRpcRequest) -> Option<RpcResponse> + 'static> {
|
||||
|
@ -927,9 +1028,7 @@ fn create_rpc_handler<M: Params<Runtime = Wry>>(
|
|||
DetachedWindow {
|
||||
dispatcher: WryDispatcher {
|
||||
window_id: window.id(),
|
||||
proxy: proxy.clone(),
|
||||
task_tx: task_tx.clone(),
|
||||
window_event_listeners: window_event_listeners.clone(),
|
||||
context: context.clone(),
|
||||
},
|
||||
label: label.clone(),
|
||||
},
|
||||
|
@ -941,9 +1040,7 @@ fn create_rpc_handler<M: Params<Runtime = Wry>>(
|
|||
|
||||
/// Create a wry file drop handler from a tauri file drop handler.
|
||||
fn create_file_drop_handler<M: Params<Runtime = Wry>>(
|
||||
proxy: EventLoopProxy<Message>,
|
||||
task_tx: Sender<MainThreadTask>,
|
||||
window_event_listeners: WindowEventListeners,
|
||||
context: DispatcherContext,
|
||||
label: M::Label,
|
||||
handler: FileDropHandler<M>,
|
||||
) -> Box<dyn Fn(&Window, WryFileDropEvent) -> bool + 'static> {
|
||||
|
@ -953,9 +1050,7 @@ fn create_file_drop_handler<M: Params<Runtime = Wry>>(
|
|||
DetachedWindow {
|
||||
dispatcher: WryDispatcher {
|
||||
window_id: window.id(),
|
||||
proxy: proxy.clone(),
|
||||
task_tx: task_tx.clone(),
|
||||
window_event_listeners: window_event_listeners.clone(),
|
||||
context: context.clone(),
|
||||
},
|
||||
label: label.clone(),
|
||||
},
|
||||
|
|
|
@ -13,12 +13,13 @@ use crate::{
|
|||
hooks::{InvokeHandler, OnPageLoad, PageLoadPayload},
|
||||
plugin::PluginStore,
|
||||
runtime::{
|
||||
app::{GlobalMenuEventListener, WindowMenuEvent},
|
||||
tag::{tags_to_javascript_array, Tag, TagRef, ToJsString},
|
||||
webview::{
|
||||
CustomProtocol, FileDropEvent, FileDropHandler, InvokePayload, WebviewRpcHandler,
|
||||
CustomProtocol, FileDropEvent, FileDropHandler, InvokePayload, Menu, WebviewRpcHandler,
|
||||
WindowBuilder,
|
||||
},
|
||||
window::{dpi::PhysicalSize, DetachedWindow, PendingWindow, WindowEvent},
|
||||
window::{dpi::PhysicalSize, DetachedWindow, MenuEvent, PendingWindow, WindowEvent},
|
||||
Icon, Runtime,
|
||||
},
|
||||
sealed::ParamsBase,
|
||||
|
@ -43,6 +44,7 @@ const WINDOW_DESTROYED_EVENT: &str = "tauri://destroyed";
|
|||
const WINDOW_FOCUS_EVENT: &str = "tauri://focus";
|
||||
const WINDOW_BLUR_EVENT: &str = "tauri://blur";
|
||||
const WINDOW_SCALE_FACTOR_CHANGED_EVENT: &str = "tauri://scale-change";
|
||||
const MENU_EVENT: &str = "tauri://menu";
|
||||
|
||||
/// Parse a string representing an internal tauri event into [`Params::Event`]
|
||||
///
|
||||
|
@ -79,6 +81,10 @@ pub struct InnerWindowManager<P: Params> {
|
|||
package_info: PackageInfo,
|
||||
/// The webview protocols protocols available to all windows.
|
||||
uri_scheme_protocols: HashMap<String, Arc<CustomProtocol>>,
|
||||
/// The menu set to all windows.
|
||||
menu: Vec<Menu>,
|
||||
/// Menu event listeners to all windows.
|
||||
menu_event_listeners: Arc<Vec<GlobalMenuEventListener<P>>>,
|
||||
}
|
||||
|
||||
/// A [Zero Sized Type] marker representing a full [`Params`].
|
||||
|
@ -125,6 +131,7 @@ impl<P: Params> Clone for WindowManager<P> {
|
|||
}
|
||||
|
||||
impl<P: Params> WindowManager<P> {
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub(crate) fn with_handlers(
|
||||
context: Context<P::Assets>,
|
||||
plugins: PluginStore<P>,
|
||||
|
@ -132,6 +139,8 @@ impl<P: Params> WindowManager<P> {
|
|||
on_page_load: Box<OnPageLoad<P>>,
|
||||
uri_scheme_protocols: HashMap<String, Arc<CustomProtocol>>,
|
||||
state: StateManager,
|
||||
menu: Vec<Menu>,
|
||||
menu_event_listeners: Vec<GlobalMenuEventListener<P>>,
|
||||
) -> Self {
|
||||
Self {
|
||||
inner: Arc::new(InnerWindowManager {
|
||||
|
@ -147,6 +156,8 @@ impl<P: Params> WindowManager<P> {
|
|||
salts: Mutex::default(),
|
||||
package_info: context.package_info,
|
||||
uri_scheme_protocols,
|
||||
menu,
|
||||
menu_event_listeners: Arc::new(menu_event_listeners),
|
||||
}),
|
||||
_marker: Args::default(),
|
||||
}
|
||||
|
@ -209,6 +220,10 @@ impl<P: Params> WindowManager<P> {
|
|||
}
|
||||
}
|
||||
|
||||
if !pending.window_attributes.has_menu() {
|
||||
pending.window_attributes = pending.window_attributes.menu(self.inner.menu.clone());
|
||||
}
|
||||
|
||||
for (uri_scheme, protocol) in &self.inner.uri_scheme_protocols {
|
||||
if !webview_attributes.has_uri_scheme_protocol(uri_scheme) {
|
||||
let protocol = protocol.clone();
|
||||
|
@ -414,6 +429,8 @@ mod test {
|
|||
Box::new(|_, _| ()),
|
||||
Default::default(),
|
||||
StateManager::new(),
|
||||
Vec::new(),
|
||||
Default::default(),
|
||||
);
|
||||
|
||||
#[cfg(custom_protocol)]
|
||||
|
@ -498,6 +515,17 @@ impl<P: Params> WindowManager<P> {
|
|||
window.on_window_event(move |event| {
|
||||
let _ = on_window_event(&window_, event);
|
||||
});
|
||||
let window_ = window.clone();
|
||||
let menu_event_listeners = self.inner.menu_event_listeners.clone();
|
||||
window.on_menu_event(move |event| {
|
||||
let _ = on_menu_event(&window_, event);
|
||||
for handler in menu_event_listeners.iter() {
|
||||
handler(WindowMenuEvent {
|
||||
window: window_.clone(),
|
||||
menu_item_id: event.menu_item_id,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// insert the window into our manager
|
||||
{
|
||||
|
@ -686,3 +714,12 @@ struct ScaleFactorChanged {
|
|||
scale_factor: f64,
|
||||
size: PhysicalSize<u32>,
|
||||
}
|
||||
|
||||
fn on_menu_event<P: Params>(window: &Window<P>, event: &MenuEvent) -> crate::Result<()> {
|
||||
window.emit(
|
||||
&MENU_EVENT
|
||||
.parse()
|
||||
.unwrap_or_else(|_| panic!("unhandled event")),
|
||||
Some(event),
|
||||
)
|
||||
}
|
||||
|
|
|
@ -22,7 +22,7 @@ pub mod window;
|
|||
use monitor::Monitor;
|
||||
use window::{
|
||||
dpi::{PhysicalPosition, PhysicalSize, Position, Size},
|
||||
WindowEvent,
|
||||
MenuEvent, WindowEvent,
|
||||
};
|
||||
|
||||
/// The webview runtime interface.
|
||||
|
@ -57,6 +57,9 @@ pub trait Dispatch: Clone + Send + Sized + 'static {
|
|||
/// Registers a window event handler.
|
||||
fn on_window_event<F: Fn(&WindowEvent) + Send + 'static>(&self, f: F) -> Uuid;
|
||||
|
||||
/// Registers a window event handler.
|
||||
fn on_menu_event<F: Fn(&MenuEvent) + Send + 'static>(&self, f: F) -> Uuid;
|
||||
|
||||
// GETTERS
|
||||
|
||||
/// Returns the scale factor that can be used to map logical pixels to physical pixels, and vice versa.
|
||||
|
|
|
@ -9,9 +9,13 @@ use crate::{
|
|||
api::config::{WindowConfig, WindowUrl},
|
||||
runtime::window::DetachedWindow,
|
||||
};
|
||||
use serde::Deserialize;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::Value as JsonValue;
|
||||
use std::{collections::HashMap, path::PathBuf};
|
||||
use std::{
|
||||
collections::{hash_map::DefaultHasher, HashMap},
|
||||
hash::{Hash, Hasher},
|
||||
path::PathBuf,
|
||||
};
|
||||
|
||||
type UriSchemeProtocol = dyn Fn(&str) -> crate::Result<Vec<u8>> + Send + Sync + 'static;
|
||||
|
||||
|
@ -92,6 +96,9 @@ pub trait WindowBuilder: WindowBuilderBase {
|
|||
/// Initializes a new webview builder from a [`WindowConfig`]
|
||||
fn with_config(config: WindowConfig) -> Self;
|
||||
|
||||
/// Sets the menu for the window.
|
||||
fn menu(self, menu: Vec<Menu>) -> Self;
|
||||
|
||||
/// The initial position of the window's.
|
||||
fn position(self, x: f64, y: f64) -> Self;
|
||||
|
||||
|
@ -134,6 +141,9 @@ pub trait WindowBuilder: WindowBuilderBase {
|
|||
|
||||
/// Whether the icon was set or not.
|
||||
fn has_icon(&self) -> bool;
|
||||
|
||||
/// Whether the menu was set or not.
|
||||
fn has_menu(&self) -> bool;
|
||||
}
|
||||
|
||||
/// Rpc request.
|
||||
|
@ -179,3 +189,210 @@ pub(crate) struct InvokePayload {
|
|||
#[serde(flatten)]
|
||||
pub(crate) inner: JsonValue,
|
||||
}
|
||||
|
||||
/// A window or system tray menu.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Menu {
|
||||
pub(crate) title: String,
|
||||
pub(crate) items: Vec<MenuItem>,
|
||||
}
|
||||
|
||||
impl Menu {
|
||||
/// Creates a new menu with the given title and items.
|
||||
pub fn new<T: Into<String>>(title: T, items: Vec<MenuItem>) -> Self {
|
||||
Self {
|
||||
title: title.into(),
|
||||
items,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Identifier of a custom menu item.
|
||||
///
|
||||
/// Whenever you receive an event arising from a particular menu, this event contains a `MenuId` which
|
||||
/// identifies its origin.
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, Serialize)]
|
||||
pub struct MenuItemId(pub(crate) u32);
|
||||
|
||||
impl MenuItemId {
|
||||
fn new<T: Into<String>>(menu_title: T) -> Self {
|
||||
Self(hash_string_to_u32(menu_title.into()))
|
||||
}
|
||||
}
|
||||
|
||||
fn hash_string_to_u32(title: String) -> u32 {
|
||||
let mut s = DefaultHasher::new();
|
||||
title.hash(&mut s);
|
||||
s.finish() as u32
|
||||
}
|
||||
|
||||
/// A custom menu item.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct CustomMenuItem {
|
||||
pub(crate) id: MenuItemId,
|
||||
pub(crate) name: String,
|
||||
}
|
||||
|
||||
impl CustomMenuItem {
|
||||
/// Create new custom menu item.
|
||||
pub fn new<T: Into<String>>(title: T) -> Self {
|
||||
let title = title.into();
|
||||
Self {
|
||||
id: MenuItemId::new(&title),
|
||||
name: title,
|
||||
}
|
||||
}
|
||||
|
||||
/// Return unique menu ID. Works only with `MenuItem::Custom`.
|
||||
pub fn id(self) -> MenuItemId {
|
||||
self.id
|
||||
}
|
||||
}
|
||||
|
||||
/// A menu item, bound to a pre-defined action or `Custom` emit an event. Note that status bar only
|
||||
/// supports `Custom` menu item variants. And on the menu bar, some platforms might not support some
|
||||
/// of the variants. Unsupported variant will be no-op on such platform.
|
||||
#[derive(Debug, Clone)]
|
||||
#[non_exhaustive]
|
||||
pub enum MenuItem {
|
||||
/// A custom menu item emits an event inside the EventLoop.
|
||||
Custom(CustomMenuItem),
|
||||
|
||||
/// Shows a standard "About" item
|
||||
///
|
||||
/// ## Platform-specific
|
||||
///
|
||||
/// - **Windows / Android / iOS:** Unsupported
|
||||
///
|
||||
About(String),
|
||||
|
||||
/// A standard "hide the app" menu item.
|
||||
///
|
||||
/// ## Platform-specific
|
||||
///
|
||||
/// - **Windows / Android / iOS:** Unsupported
|
||||
///
|
||||
Hide,
|
||||
|
||||
/// A standard "Services" menu item.
|
||||
///
|
||||
/// ## Platform-specific
|
||||
///
|
||||
/// - **Windows / Linux / Android / iOS:** Unsupported
|
||||
///
|
||||
Services,
|
||||
|
||||
/// A "hide all other windows" menu item.
|
||||
///
|
||||
/// ## Platform-specific
|
||||
///
|
||||
/// - **Windows / Linux / Android / iOS:** Unsupported
|
||||
///
|
||||
HideOthers,
|
||||
|
||||
/// A menu item to show all the windows for this app.
|
||||
///
|
||||
/// ## Platform-specific
|
||||
///
|
||||
/// - **Windows / Linux / Android / iOS:** Unsupported
|
||||
///
|
||||
ShowAll,
|
||||
|
||||
/// Close the current window.
|
||||
///
|
||||
/// ## Platform-specific
|
||||
///
|
||||
/// - **Windows / Android / iOS:** Unsupported
|
||||
///
|
||||
CloseWindow,
|
||||
|
||||
/// A "quit this app" menu icon.
|
||||
///
|
||||
/// ## Platform-specific
|
||||
///
|
||||
/// - **Windows / Android / iOS:** Unsupported
|
||||
///
|
||||
Quit,
|
||||
|
||||
/// A menu item for enabling copying (often text) from responders.
|
||||
///
|
||||
/// ## Platform-specific
|
||||
///
|
||||
/// - **Windows / Android / iOS:** Unsupported
|
||||
///
|
||||
Copy,
|
||||
|
||||
/// A menu item for enabling cutting (often text) from responders.
|
||||
///
|
||||
/// ## Platform-specific
|
||||
///
|
||||
/// - **Windows / Android / iOS:** Unsupported
|
||||
///
|
||||
Cut,
|
||||
|
||||
/// An "undo" menu item; particularly useful for supporting the cut/copy/paste/undo lifecycle
|
||||
/// of events.
|
||||
///
|
||||
/// ## Platform-specific
|
||||
///
|
||||
/// - **Windows / Linux / Android / iOS:** Unsupported
|
||||
///
|
||||
Undo,
|
||||
|
||||
/// An "redo" menu item; particularly useful for supporting the cut/copy/paste/undo lifecycle
|
||||
/// of events.
|
||||
///
|
||||
/// ## Platform-specific
|
||||
///
|
||||
/// - **Windows / Linux / Android / iOS:** Unsupported
|
||||
///
|
||||
Redo,
|
||||
|
||||
/// A menu item for selecting all (often text) from responders.
|
||||
///
|
||||
/// ## Platform-specific
|
||||
///
|
||||
/// - **Windows / Android / iOS:** Unsupported
|
||||
///
|
||||
SelectAll,
|
||||
|
||||
/// A menu item for pasting (often text) into responders.
|
||||
///
|
||||
/// ## Platform-specific
|
||||
///
|
||||
/// - **Windows / Android / iOS:** Unsupported
|
||||
///
|
||||
Paste,
|
||||
|
||||
/// A standard "enter full screen" item.
|
||||
///
|
||||
/// ## Platform-specific
|
||||
///
|
||||
/// - **Windows / Linux / Android / iOS:** Unsupported
|
||||
///
|
||||
EnterFullScreen,
|
||||
|
||||
/// An item for minimizing the window with the standard system controls.
|
||||
///
|
||||
/// ## Platform-specific
|
||||
///
|
||||
/// - **Windows / Android / iOS:** Unsupported
|
||||
///
|
||||
Minimize,
|
||||
|
||||
/// An item for instructing the app to zoom
|
||||
///
|
||||
/// ## Platform-specific
|
||||
///
|
||||
/// - **Windows / Linux / Android / iOS:** Unsupported
|
||||
///
|
||||
Zoom,
|
||||
|
||||
/// Represents a Separator
|
||||
///
|
||||
/// ## Platform-specific
|
||||
///
|
||||
/// - **Windows / Android / iOS:** Unsupported
|
||||
///
|
||||
Separator,
|
||||
}
|
||||
|
|
|
@ -10,7 +10,7 @@ use crate::{
|
|||
hooks::{InvokeMessage, InvokeResolver, PageLoadPayload},
|
||||
runtime::{
|
||||
tag::ToJsString,
|
||||
webview::{FileDropHandler, InvokePayload, WebviewAttributes, WebviewRpcHandler},
|
||||
webview::{FileDropHandler, InvokePayload, MenuItemId, WebviewAttributes, WebviewRpcHandler},
|
||||
Dispatch, Monitor, Runtime,
|
||||
},
|
||||
sealed::{ManagerBase, RuntimeOrDispatch},
|
||||
|
@ -55,6 +55,20 @@ pub enum WindowEvent {
|
|||
},
|
||||
}
|
||||
|
||||
/// A menu event.
|
||||
#[derive(Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct MenuEvent {
|
||||
pub(crate) menu_item_id: MenuItemId,
|
||||
}
|
||||
|
||||
impl MenuEvent {
|
||||
/// Returns the id of the menu item that triggered the event.
|
||||
pub fn item_id(&self) -> MenuItemId {
|
||||
self.menu_item_id
|
||||
}
|
||||
}
|
||||
|
||||
/// A webview window that has yet to be built.
|
||||
pub struct PendingWindow<M: Params> {
|
||||
/// The label that the window will be named.
|
||||
|
@ -378,6 +392,11 @@ pub(crate) mod export {
|
|||
self.window.dispatcher.on_window_event(f);
|
||||
}
|
||||
|
||||
/// Registers a menu event listener.
|
||||
pub fn on_menu_event<F: Fn(&MenuEvent) + Send + 'static>(&self, f: F) {
|
||||
self.window.dispatcher.on_menu_event(f);
|
||||
}
|
||||
|
||||
// Getters
|
||||
|
||||
/// Returns the scale factor that can be used to map logical pixels to physical pixels, and vice versa.
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
)]
|
||||
|
||||
mod cmd;
|
||||
mod menu;
|
||||
|
||||
use serde::Serialize;
|
||||
|
||||
|
@ -31,6 +32,10 @@ fn main() {
|
|||
.expect("failed to emit");
|
||||
});
|
||||
})
|
||||
.menu(menu::get_menu())
|
||||
.on_menu_event(|event| {
|
||||
println!("{:?}", event.menu_item_id());
|
||||
})
|
||||
.invoke_handler(tauri::generate_handler![
|
||||
cmd::log_operation,
|
||||
cmd::perform_request
|
||||
|
|
|
@ -0,0 +1,72 @@
|
|||
// Copyright 2019-2021 Tauri Programme within The Commons Conservancy
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
use tauri::{CustomMenuItem, Menu, MenuItem};
|
||||
|
||||
pub fn get_menu() -> Vec<Menu> {
|
||||
let custom_print_menu = MenuItem::Custom(CustomMenuItem::new("Print"));
|
||||
let other_test_menu = MenuItem::Custom(CustomMenuItem::new("Custom"));
|
||||
let quit_menu = MenuItem::Custom(CustomMenuItem::new("Quit"));
|
||||
|
||||
// macOS require to have at least Copy, Paste, Select all etc..
|
||||
// to works fine. You should always add them.
|
||||
#[cfg(any(target_os = "linux", target_os = "macos"))]
|
||||
let menu = vec![
|
||||
Menu::new(
|
||||
// on macOS first menu is always app name
|
||||
"Tauri API",
|
||||
vec![
|
||||
// All's non-custom menu, do NOT return event's
|
||||
// they are handled by the system automatically
|
||||
MenuItem::About("Tauri".to_string()),
|
||||
MenuItem::Services,
|
||||
MenuItem::Separator,
|
||||
MenuItem::Hide,
|
||||
MenuItem::HideOthers,
|
||||
MenuItem::ShowAll,
|
||||
MenuItem::Separator,
|
||||
quit_menu,
|
||||
],
|
||||
),
|
||||
Menu::new(
|
||||
"File",
|
||||
vec![
|
||||
custom_print_menu,
|
||||
MenuItem::Separator,
|
||||
other_test_menu,
|
||||
MenuItem::CloseWindow,
|
||||
],
|
||||
),
|
||||
Menu::new(
|
||||
"Edit",
|
||||
vec![
|
||||
MenuItem::Undo,
|
||||
MenuItem::Redo,
|
||||
MenuItem::Separator,
|
||||
MenuItem::Cut,
|
||||
MenuItem::Copy,
|
||||
MenuItem::Paste,
|
||||
MenuItem::Separator,
|
||||
MenuItem::SelectAll,
|
||||
],
|
||||
),
|
||||
Menu::new("View", vec![MenuItem::EnterFullScreen]),
|
||||
Menu::new("Window", vec![MenuItem::Minimize, MenuItem::Zoom]),
|
||||
Menu::new(
|
||||
"Help",
|
||||
vec![MenuItem::Custom(CustomMenuItem::new("Custom help"))],
|
||||
),
|
||||
];
|
||||
|
||||
// Attention, Windows only support custom menu for now.
|
||||
// If we add any `MenuItem::*` they'll not render
|
||||
// We need to use custom menu with `Menu::new()` and catch
|
||||
// the events in the EventLoop.
|
||||
#[cfg(target_os = "windows")]
|
||||
let menu = vec![
|
||||
Menu::new("File", vec![other_test_menu]),
|
||||
Menu::new("Other menu", vec![quit_menu]),
|
||||
];
|
||||
menu
|
||||
}
|
Loading…
Reference in New Issue