|
4 | 4 | // System tray is supported and availabled only if `tray` feature is enabled.
|
5 | 5 | // Platform: Windows, Linux and macOS.
|
6 | 6 | #[cfg(any(target_os = "windows", target_os = "linux", target_os = "macos"))]
|
7 |
| -#[cfg(feature = "tray")] |
| 7 | +#[cfg(any(feature = "tray", all(target_os = "linux", feature = "ayatana")))] |
8 | 8 | fn main() {
|
9 |
| - use std::collections::HashMap; |
10 |
| - #[cfg(target_os = "linux")] |
11 |
| - use std::path::Path; |
12 | 9 | #[cfg(target_os = "macos")]
|
13 | 10 | use tao::platform::macos::{CustomMenuItemExtMacOS, NativeImage, SystemTrayBuilderExtMacOS};
|
14 | 11 | use tao::{
|
15 |
| - event::{Event, WindowEvent}, |
| 12 | + event::Event, |
16 | 13 | event_loop::{ControlFlow, EventLoop},
|
17 | 14 | menu::{ContextMenu as Menu, MenuItemAttributes, MenuType},
|
18 | 15 | system_tray::SystemTrayBuilder,
|
19 |
| - window::{Window, WindowId}, |
20 | 16 | };
|
21 | 17 |
|
22 | 18 | env_logger::init();
|
23 | 19 | let event_loop = EventLoop::new();
|
24 |
| - let mut windows: HashMap<WindowId, Window> = HashMap::new(); |
25 | 20 |
|
26 | 21 | let mut tray_menu = Menu::new();
|
| 22 | + let quit = tray_menu.add_item(MenuItemAttributes::new("Quit")); |
27 | 23 |
|
28 |
| - let mut submenu = Menu::new(); |
| 24 | + // You'll have to choose an icon size at your own discretion. On Linux, the icon should be |
| 25 | + // provided in whatever size it was naturally drawn; that is, don’t scale the image before passing |
| 26 | + // it to Tao. But on Windows, you will have to account for screen scaling. Here we use 32px, |
| 27 | + // since it seems to work well enough in most cases. Be careful about going too high, or |
| 28 | + // you'll be bitten by the low-quality downscaling built into the WM. |
| 29 | + let path = concat!(env!("CARGO_MANIFEST_DIR"), "/examples/icon.png"); |
29 | 30 |
|
30 |
| - // open new window menu item |
31 |
| - let open_new_window_element = submenu.add_item(MenuItemAttributes::new("Open new window")); |
| 31 | + let icon = load_icon(std::path::Path::new(path)); |
32 | 32 |
|
33 |
| - // set default icon |
34 |
| - #[cfg(target_os = "macos")] |
35 |
| - open_new_window_element |
36 |
| - .clone() |
37 |
| - .set_native_image(NativeImage::StatusAvailable); |
38 |
| - |
39 |
| - // focus all window menu item |
40 |
| - let mut focus_all_window = |
41 |
| - tray_menu.add_item(MenuItemAttributes::new("Focus window").with_enabled(false)); |
42 |
| - |
43 |
| - let change_menu = tray_menu.add_item(MenuItemAttributes::new("Change menu")); |
44 |
| - |
45 |
| - // inject submenu into tray_menu |
46 |
| - tray_menu.add_submenu("Sub menu", true, submenu); |
47 |
| - |
48 |
| - // add quit button |
49 |
| - let quit_element = tray_menu.add_item(MenuItemAttributes::new("Quit")); |
50 |
| - |
51 |
| - // Windows require Vec<u8> ICO file |
52 |
| - #[cfg(target_os = "windows")] |
53 |
| - let icon = include_bytes!("icon.ico").to_vec(); |
54 |
| - // macOS require Vec<u8> PNG file |
55 |
| - #[cfg(target_os = "macos")] |
56 |
| - let icon = include_bytes!("icon.png").to_vec(); |
57 |
| - // Linux require Pathbuf to PNG file |
58 |
| - #[cfg(target_os = "linux")] |
59 |
| - let icon = Path::new(env!("CARGO_MANIFEST_DIR")).join("examples/icon.png"); |
60 |
| - |
61 |
| - // Windows require Vec<u8> ICO file |
62 |
| - #[cfg(target_os = "windows")] |
63 |
| - let new_icon = include_bytes!("icon_blue.ico").to_vec(); |
64 |
| - // macOS require Vec<u8> PNG file |
65 |
| - #[cfg(target_os = "macos")] |
66 |
| - let new_icon = include_bytes!("icon_dark.png").to_vec(); |
67 |
| - // Linux require Pathbuf to PNG file |
68 |
| - #[cfg(target_os = "linux")] |
69 |
| - let new_icon = Path::new(env!("CARGO_MANIFEST_DIR")).join("examples/icon_dark.png"); |
70 |
| - |
71 |
| - // Menu is shown with left click on macOS and right click on Windows. |
72 |
| - #[cfg(target_os = "macos")] |
73 |
| - let mut system_tray = SystemTrayBuilder::new(icon.clone(), Some(tray_menu)) |
74 |
| - .with_icon_as_template(true) |
75 |
| - .build(&event_loop) |
76 |
| - .unwrap(); |
77 |
| - |
78 |
| - #[cfg(not(target_os = "macos"))] |
79 |
| - let mut system_tray = SystemTrayBuilder::new(icon.clone(), Some(tray_menu)) |
| 33 | + let system_tray = SystemTrayBuilder::new(icon, Some(tray_menu)) |
80 | 34 | .build(&event_loop)
|
81 | 35 | .unwrap();
|
82 | 36 |
|
83 |
| - event_loop.run(move |event, event_loop, control_flow| { |
| 37 | + event_loop.run(move |event, _event_loop, control_flow| { |
84 | 38 | *control_flow = ControlFlow::Wait;
|
85 | 39 |
|
86 |
| - let mut create_window_or_focus = || { |
87 |
| - // if we already have one window, let's focus instead |
88 |
| - if !windows.is_empty() { |
89 |
| - for window in windows.values() { |
90 |
| - window.set_focus(); |
91 |
| - } |
92 |
| - return; |
93 |
| - } |
94 |
| - |
95 |
| - // create new window |
96 |
| - let mut open_new_window_element = open_new_window_element.clone(); |
97 |
| - let mut focus_all_window = focus_all_window.clone(); |
98 |
| - |
99 |
| - let window = Window::new(event_loop).unwrap(); |
100 |
| - windows.insert(window.id(), window); |
101 |
| - // disable button |
102 |
| - open_new_window_element.set_enabled(false); |
103 |
| - // change title (text) |
104 |
| - open_new_window_element.set_title("Window already open"); |
105 |
| - println!( |
106 |
| - "Changed the menu item title: {}", |
107 |
| - open_new_window_element.title() |
108 |
| - ); |
109 |
| - // set checked |
110 |
| - open_new_window_element.set_selected(true); |
111 |
| - // enable focus window |
112 |
| - focus_all_window.set_enabled(true); |
113 |
| - // update tray icon |
114 |
| - system_tray.set_icon(new_icon.clone()); |
115 |
| - // add macOS Native red dot |
116 |
| - #[cfg(target_os = "macos")] |
117 |
| - open_new_window_element.set_native_image(NativeImage::StatusUnavailable); |
118 |
| - }; |
119 |
| - |
120 | 40 | match event {
|
121 |
| - Event::WindowEvent { |
122 |
| - event, window_id, .. |
123 |
| - } => { |
124 |
| - if event == WindowEvent::CloseRequested { |
125 |
| - let mut open_new_window_element = open_new_window_element.clone(); |
126 |
| - // Remove window from our hashmap |
127 |
| - windows.remove(&window_id); |
128 |
| - // Modify our button's state |
129 |
| - open_new_window_element.set_enabled(true); |
130 |
| - focus_all_window.set_enabled(false); |
131 |
| - // Reset text |
132 |
| - open_new_window_element.set_title("Open new window"); |
133 |
| - println!( |
134 |
| - "Reset the menu item title: {}", |
135 |
| - open_new_window_element.title() |
136 |
| - ); |
137 |
| - // Set selected |
138 |
| - open_new_window_element.set_selected(false); |
139 |
| - // Change tray icon |
140 |
| - system_tray.set_icon(icon.clone()); |
141 |
| - // macOS have native image available that we can use in our menu-items |
142 |
| - #[cfg(target_os = "macos")] |
143 |
| - open_new_window_element.set_native_image(NativeImage::StatusAvailable); |
144 |
| - } |
145 |
| - } |
146 |
| - // on Windows, habitually, we show the window with left click |
147 |
| - #[cfg(target_os = "windows")] |
148 |
| - Event::TrayEvent { |
149 |
| - event: tao::event::TrayEvent::LeftClick, |
150 |
| - .. |
151 |
| - } => create_window_or_focus(), |
152 |
| - // left click on menu item |
153 | 41 | Event::MenuEvent {
|
154 | 42 | menu_id,
|
155 | 43 | // specify only context menu's
|
156 | 44 | origin: MenuType::ContextMenu,
|
157 | 45 | ..
|
158 | 46 | } => {
|
159 |
| - // Click on Open new window or focus item |
160 |
| - if menu_id == open_new_window_element.clone().id() |
161 |
| - || menu_id == focus_all_window.clone().id() |
162 |
| - { |
163 |
| - create_window_or_focus(); |
164 |
| - } |
165 |
| - // click on `quit` item |
166 |
| - if menu_id == quit_element.clone().id() { |
167 |
| - // tell our app to close at the end of the loop. |
| 47 | + if menu_id == quit.clone().id() { |
| 48 | + // drop the system tray before exiting to remove the icon from system tray on Windows |
| 49 | + drop(&system_tray); |
168 | 50 | *control_flow = ControlFlow::Exit;
|
169 | 51 | }
|
170 |
| - |
171 |
| - if menu_id == change_menu.clone().id() { |
172 |
| - let mut tray_menu = Menu::new(); |
173 |
| - tray_menu.add_item(MenuItemAttributes::new("Quit")); |
174 |
| - system_tray.set_menu(&tray_menu); |
175 |
| - } |
176 |
| - |
177 |
| - println!("Clicked on {:?}", menu_id); |
178 | 52 | }
|
179 | 53 | _ => (),
|
180 | 54 | }
|
181 | 55 | });
|
182 | 56 | }
|
183 | 57 |
|
| 58 | +#[cfg(any(target_os = "windows", target_os = "linux", target_os = "macos"))] |
| 59 | +#[cfg(any(feature = "tray", all(target_os = "linux", feature = "ayatana")))] |
| 60 | +fn load_icon(path: &std::path::Path) -> tao::system_tray::Icon { |
| 61 | + let (icon_rgba, icon_width, icon_height) = { |
| 62 | + let image = image::open(path) |
| 63 | + .expect("Failed to open icon path") |
| 64 | + .into_rgba8(); |
| 65 | + let (width, height) = image.dimensions(); |
| 66 | + let rgba = image.into_raw(); |
| 67 | + (rgba, width, height) |
| 68 | + }; |
| 69 | + tao::system_tray::Icon::from_rgba(icon_rgba, icon_width, icon_height) |
| 70 | + .expect("Failed to open icon") |
| 71 | +} |
| 72 | + |
184 | 73 | // System tray isn't supported on other's platforms.
|
185 | 74 | #[cfg(not(any(target_os = "windows", target_os = "linux", target_os = "macos")))]
|
186 | 75 | fn main() {
|
|
0 commit comments