Skip to content

Commit

Permalink
refactor: menu & tray (rust-windowing#77)
Browse files Browse the repository at this point in the history
  • Loading branch information
lemarier authored Jun 3, 2021
1 parent 6ca0062 commit 7546dbd
Show file tree
Hide file tree
Showing 36 changed files with 2,768 additions and 1,050 deletions.
11 changes: 11 additions & 0 deletions .changes/menu-refactor.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
---
"tao": minor
---

**Breaking change**: New menu/tray API.

System tray now expose `set_icon()` to update the tray icon after initialization. The `system_tray::SystemTrayBuilder` has been moved to the root of the package as a module and available on Windows, Linux and macOS, only when the `tray` feature is enabled. Windows expose a `remove()` function available with `SystemTrayExtWindows`.

Menu builder has been rebuilt from scratch, exposing 2 different types, `ContextMenu` and `MenuBar`.

Please refer to the docs and examples for more details.
2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,9 @@ targets = [

[features]
default = [ "tray" ]
# Menu is unstable.
menu = [ "sourceview" ]
# Tray is unstable.
tray = [ "sourceview", "libappindicator" ]
dox = [ "gtk/dox", "sourceview/dox" ]

Expand Down
109 changes: 43 additions & 66 deletions examples/custom_menu.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,69 +2,52 @@
// SPDX-License-Identifier: Apache-2.0

use simple_logger::SimpleLogger;
#[cfg(target_os = "macos")]
use tao::platform::macos::{CustomMenuItemExtMacOS, NativeImage};
use tao::{
event::{Event, WindowEvent},
event_loop::{ControlFlow, EventLoop},
menu::{Menu, MenuItem, MenuType},
menu::{MenuBar as Menu, MenuItem, MenuItemAttributes, MenuType},
window::WindowBuilder,
};

fn main() {
SimpleLogger::new().init().unwrap();
let event_loop = EventLoop::new();

let custom_change_menu = MenuItem::new("Change menu").with_accelerators("F1");
let custom_change_menu_id = custom_change_menu.id();
// allocate our tray as it'll contain children
let mut menu_bar_menu = Menu::new();

// create our first menu
let mut my_app_menu = Menu::new();

// create a submenu
let mut my_sub_menu = Menu::new();

let mut test_menu_item =
my_sub_menu.add_item(MenuItemAttributes::new("Disable menu").with_accelerators("<Primary>d"));
// add Copy to `My App` menu
my_app_menu.add_native_item(MenuItem::Copy);

// add our submenu under Copy
my_app_menu.add_submenu("Sub menu", true, my_sub_menu);

let mut test_menu = Menu::new();
test_menu.add_item(
MenuItemAttributes::new("Selected and disabled")
.with_selected(true)
.with_enabled(false),
);
test_menu.add_native_item(MenuItem::Separator);
test_menu.add_item(MenuItemAttributes::new("Test"));

// add all our childs to menu_bar_menu (order is how they'll appear)
menu_bar_menu.add_submenu("My app", true, my_app_menu);
menu_bar_menu.add_submenu("Other menu", true, test_menu);

let window = WindowBuilder::new()
.with_title("A fantastic window!")
.with_menu(vec![
Menu::new(
// on macOS first menu is always app name
"my custom app",
vec![
MenuItem::About("Todos".to_string()),
MenuItem::Services,
MenuItem::Separator,
MenuItem::Hide,
MenuItem::HideOthers,
MenuItem::ShowAll,
MenuItem::Separator,
MenuItem::Quit,
],
),
Menu::new(
"File",
vec![
custom_change_menu,
MenuItem::Separator,
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::new("Custom help")
// `Primary` is a platform-agnostic accelerator modifier.
// On Windows and Linux, `Primary` maps to the `Ctrl` key,
// and on macOS it maps to the `command` key.
.with_accelerators("<Primary><Shift>h")],
),
])
.with_menu(menu_bar_menu)
.build(&event_loop)
.unwrap();

Expand All @@ -81,22 +64,16 @@ fn main() {
}
Event::MenuEvent {
menu_id,
origin: MenuType::Menubar,
} => {
if menu_id == custom_change_menu_id {
println!("Clicked on custom change menu");
window.set_menu(Some(vec![Menu::new(
"File",
vec![
MenuItem::new("Add Todo").with_accelerators("<Primary>T"),
MenuItem::Separator,
MenuItem::CloseWindow,
],
)]))
}

println!("Clicked on {:?}", menu_id);
window.set_title("New window title!");
origin: MenuType::MenuBar,
} if menu_id == test_menu_item.clone().id() => {
println!("Clicked on custom change menu");
// this allow us to get access to the menu and make changes
// without re-rendering the whole menu
test_menu_item.set_enabled(false);
test_menu_item.set_title("Menu disabled");
test_menu_item.set_selected(true);
#[cfg(target_os = "macos")]
test_menu_item.set_native_image(NativeImage::StatusUnavailable);
}
_ => (),
}
Expand Down
Binary file added examples/icon_blue.ico
Binary file not shown.
Binary file added examples/icon_dark.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
154 changes: 122 additions & 32 deletions examples/system_tray.rs
Original file line number Diff line number Diff line change
@@ -1,36 +1,53 @@
// Copyright 2019-2021 Tauri Programme within The Commons Conservancy
// SPDX-License-Identifier: Apache-2.0

#[cfg(any(
target_os = "macos",
target_os = "windows",
target_os = "linux",
target_os = "dragonfly",
target_os = "freebsd",
target_os = "netbsd",
target_os = "openbsd"
))]
// System tray is supported and availabled only if `tray` feature is enabled.
// Platform: Windows, Linux and macOS.
#[cfg(any(target_os = "windows", target_os = "linux", target_os = "macos"))]
#[cfg(feature = "tray")]
fn main() {
use simple_logger::SimpleLogger;
use std::collections::HashMap;
#[cfg(target_os = "linux")]
use std::path::Path;
#[cfg(target_os = "macos")]
use tao::platform::macos::{CustomMenuItemExtMacOS, NativeImage};
#[cfg(target_os = "windows")]
use tao::platform::windows::SystemTrayExtWindows;
use tao::{
event::{Event, WindowEvent},
event_loop::{ControlFlow, EventLoop},
menu::{MenuItem, MenuType},
platform::system_tray::SystemTrayBuilder,
window::Window,
menu::{ContextMenu as Menu, MenuItemAttributes, MenuType},
system_tray::SystemTrayBuilder,
window::{Window, WindowId},
};

SimpleLogger::new().init().unwrap();
let event_loop = EventLoop::new();
let mut windows = HashMap::new();
let mut windows: HashMap<WindowId, Window> = HashMap::new();

let mut tray_menu = Menu::new();

let open_new_window = MenuItem::new("Open new window");
let open_new_window_id = open_new_window.id();
let mut submenu = Menu::new();

// open new window menu item
let open_new_window_element = submenu.add_item(MenuItemAttributes::new("Open new window"));

// set default icon
#[cfg(target_os = "macos")]
open_new_window_element
.clone()
.set_native_image(NativeImage::StatusAvailable);

let focus_all_window = MenuItem::new("Focus window");
let focus_all_window_id = focus_all_window.id();
// focus all window menu item
let mut focus_all_window =
tray_menu.add_item(MenuItemAttributes::new("Focus window").with_enabled(false));

// inject submenu into tray_menu
tray_menu.add_submenu("Sub menu", true, submenu);

// add quit button
let quit_element = tray_menu.add_item(MenuItemAttributes::new("Quit"));

// Windows require Vec<u8> ICO file
#[cfg(target_os = "windows")]
Expand All @@ -42,36 +59,101 @@ fn main() {
#[cfg(target_os = "linux")]
let icon = Path::new(env!("CARGO_MANIFEST_DIR")).join("examples/icon.png");

// Only supported on macOS, linux and windows
#[cfg(any(target_os = "linux", target_os = "macos", target_os = "windows"))]
let _systemtray = SystemTrayBuilder::new(icon, vec![open_new_window, focus_all_window])
// Windows require Vec<u8> ICO file
#[cfg(target_os = "windows")]
let new_icon = include_bytes!("icon_blue.ico").to_vec();
// macOS require Vec<u8> PNG file
#[cfg(target_os = "macos")]
let new_icon = include_bytes!("icon_dark.png").to_vec();
// Linux require Pathbuf to PNG file
#[cfg(target_os = "linux")]
let new_icon = Path::new(env!("CARGO_MANIFEST_DIR")).join("examples/icon_dark.png");

// Menu is shown with left click on macOS and right click on Windows.
let mut system_tray = SystemTrayBuilder::new(icon.clone(), Some(tray_menu))
.build(&event_loop)
.unwrap();

event_loop.run(move |event, event_loop, control_flow| {
*control_flow = ControlFlow::Wait;

let mut create_window_or_focus = || {
// if we already have one window, let's focus instead
if !windows.is_empty() {
for window in windows.values() {
window.set_focus();
}
return;
}

// create new window
let mut open_new_window_element = open_new_window_element.clone();
let mut focus_all_window = focus_all_window.clone();

let window = Window::new(&event_loop).unwrap();
windows.insert(window.id(), window);
// disable button
open_new_window_element.set_enabled(false);
// change title (text)
open_new_window_element.set_title("Window already open");
// set checked
open_new_window_element.set_selected(true);
// enable focus window
focus_all_window.set_enabled(true);
// update tray icon
system_tray.set_icon(new_icon.clone());
// add macOS Native red dot
#[cfg(target_os = "macos")]
open_new_window_element.set_native_image(NativeImage::StatusUnavailable);
};

match event {
Event::WindowEvent { event, window_id } => {
if event == WindowEvent::CloseRequested {
println!("Window {:?} has received the signal to close", window_id);

let mut open_new_window_element = open_new_window_element.clone();
// Remove window from our hashmap
windows.remove(&window_id);
// Modify our button's state
open_new_window_element.set_enabled(true);
focus_all_window.set_enabled(false);
// Reset text
open_new_window_element.set_title("Open new window");
// Set selected
open_new_window_element.set_selected(false);
// Change tray icon
system_tray.set_icon(icon.clone());
// macOS have native image available that we can use in our menu-items
#[cfg(target_os = "macos")]
open_new_window_element.set_native_image(NativeImage::StatusAvailable);
}
}
// on Windows, habitually, we show the window with left click
#[cfg(target_os = "windows")]
Event::TrayEvent {
event: tao::event::TrayEvent::LeftClick,
..
} => create_window_or_focus(),
// left click on menu item
Event::MenuEvent {
menu_id,
origin: MenuType::SystemTray,
// specify only context menu's
origin: MenuType::ContextMenu,
} => {
if menu_id == open_new_window_id {
let window = Window::new(&event_loop).unwrap();
windows.insert(window.id(), window);
// Click on Open new window or focus item
if menu_id == open_new_window_element.clone().id()
|| menu_id == focus_all_window.clone().id()
{
create_window_or_focus();
}
if menu_id == focus_all_window_id {
for window in windows.values() {
window.set_focus();
}
// click on `quit` item
if menu_id == quit_element.clone().id() {
// on windows, we make sure to remove the icon from the tray
// it require the `SystemTrayExtWindows`
#[cfg(target_os = "windows")]
system_tray.remove();

// tell our app to close at the end of the loop.
*control_flow = ControlFlow::Exit;
}
println!("Clicked on {:?}", menu_id);
}
Expand All @@ -80,7 +162,15 @@ fn main() {
});
}

#[cfg(any(target_os = "ios", target_os = "android",))]
// System tray isn't supported on other's platforms.
#[cfg(not(any(target_os = "windows", target_os = "linux", target_os = "macos")))]
fn main() {
println!("This platform doesn't support system_tray.");
}

// Tray feature flag disabled but can be available.
#[cfg(any(target_os = "windows", target_os = "linux", target_os = "macos"))]
#[cfg(not(feature = "tray"))]
fn main() {
println!("This platform doesn't support run_return.");
println!("This platform doesn't have the `tray` feature enabled.");
}
Loading

0 comments on commit 7546dbd

Please sign in to comment.