Skip to content

Commit

Permalink
refactor: system tray icons (#328)
Browse files Browse the repository at this point in the history
  • Loading branch information
amrbashir committed Jun 6, 2022
1 parent 8af4d8f commit 0a98eb3
Show file tree
Hide file tree
Showing 18 changed files with 201 additions and 477 deletions.
5 changes: 5 additions & 0 deletions .changes/icons.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"tao": "minor"
---

**Breaking change** `SystemTrayBuilder::new` and `SystemTray::set_icon` now takes `system_tray::Icon` on all platforms.
18 changes: 11 additions & 7 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -62,23 +62,25 @@ core-foundation = "0.9"
core-graphics = "0.22"
dispatch = "0.2"
scopeguard = "1.1"
png = "0.17"

[target."cfg(target_os = \"macos\")".dependencies.tao-core-video-sys]
version = "0.2"
default_features = false
features = [ "display_link" ]
[target."cfg(target_os = \"macos\")".dependencies.tao-core-video-sys]
version = "0.2"
default_features = false
features = [ "display_link" ]

[target."cfg(target_os = \"macos\")".build-dependencies]
cc = "1"

[target."cfg(target_os = \"windows\")".dependencies]
parking_lot = "0.11"
unicode-segmentation = "1.8.0"
image = { version = "0.24", default-features = false }
windows-implement = "0.37.0"

[target."cfg(target_os = \"windows\")".dependencies.windows]
version = "0.37.0"
features = [
[target."cfg(target_os = \"windows\")".dependencies.windows]
version = "0.37.0"
features = [
"alloc",
"implement",
"Win32_Devices_HumanInterfaceDevice",
Expand Down Expand Up @@ -120,3 +122,5 @@ gdkx11-sys = "0.15"
gdk-pixbuf = { version = "0.15", features = [ "v2_36_8" ] }
libappindicator = { version = "0.7", optional = true }
x11-dl = "2.18"
uuid = { version = "0.8", features = [ "v4" ] }
png = "0.17"
Binary file removed examples/icon.ico
Binary file not shown.
Binary file modified examples/icon.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file removed examples/icon_blue.ico
Binary file not shown.
Binary file removed examples/icon_dark.png
Binary file not shown.
171 changes: 30 additions & 141 deletions examples/system_tray.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,183 +4,72 @@
// 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")]
#[cfg(any(feature = "tray", all(target_os = "linux", feature = "ayatana")))]
fn main() {
use std::collections::HashMap;
#[cfg(target_os = "linux")]
use std::path::Path;
#[cfg(target_os = "macos")]
use tao::platform::macos::{CustomMenuItemExtMacOS, NativeImage, SystemTrayBuilderExtMacOS};
use tao::{
event::{Event, WindowEvent},
event::Event,
event_loop::{ControlFlow, EventLoop},
menu::{ContextMenu as Menu, MenuItemAttributes, MenuType},
system_tray::SystemTrayBuilder,
window::{Window, WindowId},
};

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

let mut tray_menu = Menu::new();
let quit = tray_menu.add_item(MenuItemAttributes::new("Quit"));

let mut submenu = Menu::new();
// You'll have to choose an icon size at your own discretion. On Linux, the icon should be
// provided in whatever size it was naturally drawn; that is, don’t scale the image before passing
// it to Tao. But on Windows, you will have to account for screen scaling. Here we use 32px,
// since it seems to work well enough in most cases. Be careful about going too high, or
// you'll be bitten by the low-quality downscaling built into the WM.
let path = concat!(env!("CARGO_MANIFEST_DIR"), "/examples/icon.png");

// open new window menu item
let open_new_window_element = submenu.add_item(MenuItemAttributes::new("Open new window"));
let icon = load_icon(std::path::Path::new(path));

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

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

let change_menu = tray_menu.add_item(MenuItemAttributes::new("Change menu"));

// 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")]
let icon = include_bytes!("icon.ico").to_vec();
// macOS require Vec<u8> PNG file
#[cfg(target_os = "macos")]
let icon = include_bytes!("icon.png").to_vec();
// Linux require Pathbuf to PNG file
#[cfg(target_os = "linux")]
let icon = Path::new(env!("CARGO_MANIFEST_DIR")).join("examples/icon.png");

// 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.
#[cfg(target_os = "macos")]
let mut system_tray = SystemTrayBuilder::new(icon.clone(), Some(tray_menu))
.with_icon_as_template(true)
.build(&event_loop)
.unwrap();

#[cfg(not(target_os = "macos"))]
let mut system_tray = SystemTrayBuilder::new(icon.clone(), Some(tray_menu))
let system_tray = SystemTrayBuilder::new(icon, Some(tray_menu))
.build(&event_loop)
.unwrap();

event_loop.run(move |event, event_loop, control_flow| {
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");
println!(
"Changed the menu item title: {}",
open_new_window_element.title()
);
// 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 {
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");
println!(
"Reset the menu item title: {}",
open_new_window_element.title()
);
// 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,
// specify only context menu's
origin: MenuType::ContextMenu,
..
} => {
// 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();
}
// click on `quit` item
if menu_id == quit_element.clone().id() {
// tell our app to close at the end of the loop.
if menu_id == quit.clone().id() {
// drop the system tray before exiting to remove the icon from system tray on Windows
drop(&system_tray);
*control_flow = ControlFlow::Exit;
}

if menu_id == change_menu.clone().id() {
let mut tray_menu = Menu::new();
tray_menu.add_item(MenuItemAttributes::new("Quit"));
system_tray.set_menu(&tray_menu);
}

println!("Clicked on {:?}", menu_id);
}
_ => (),
}
});
}

#[cfg(any(target_os = "windows", target_os = "linux", target_os = "macos"))]
#[cfg(any(feature = "tray", all(target_os = "linux", feature = "ayatana")))]
fn load_icon(path: &std::path::Path) -> tao::system_tray::Icon {
let (icon_rgba, icon_width, icon_height) = {
let image = image::open(path)
.expect("Failed to open icon path")
.into_rgba8();
let (width, height) = image.dimensions();
let rgba = image.into_raw();
(rgba, width, height)
};
tao::system_tray::Icon::from_rgba(icon_rgba, icon_width, icon_height)
.expect("Failed to open icon")
}

// System tray isn't supported on other's platforms.
#[cfg(not(any(target_os = "windows", target_os = "linux", target_os = "macos")))]
fn main() {
Expand Down
Loading

0 comments on commit 0a98eb3

Please sign in to comment.