Browse Source

feat(macOS): Implement tray icon template (#2322)

david 4 years ago
parent
commit
426a6b4996

+ 9 - 0
.changes/tauri-macos-tray-icon-template.md

@@ -0,0 +1,9 @@
+---
+"tauri": patch
+"tauri-runtime": patch
+"tauri-runtime-wry": patch
+---
+
+- Support [macOS tray icon template](https://developer.apple.com/documentation/appkit/nsimage/1520017-template?language=objc) to adjust automatically based on taskbar color.
+
+- Images you mark as template images should consist of only black and clear colors. You can use the alpha channel in the image to adjust the opacity of black content, however.

+ 22 - 0
core/tauri-runtime-wry/src/lib.rs

@@ -25,6 +25,8 @@ use tauri_runtime::{SystemTray, SystemTrayEvent};
 use winapi::shared::windef::HWND;
 #[cfg(target_os = "macos")]
 use wry::application::platform::macos::WindowExtMacOS;
+#[cfg(all(feature = "system-tray", target_os = "macos"))]
+use wry::application::platform::macos::{SystemTrayBuilderExtMacOS, SystemTrayExtMacOS};
 #[cfg(target_os = "linux")]
 use wry::application::platform::unix::{WindowBuilderExtUnix, WindowExtUnix};
 #[cfg(windows)]
@@ -725,6 +727,8 @@ enum WebviewEvent {
 pub(crate) enum TrayMessage {
   UpdateItem(u16, menu::MenuUpdate),
   UpdateIcon(Icon),
+  #[cfg(target_os = "macos")]
+  UpdateIconAsTemplate(bool),
 }
 
 #[derive(Clone)]
@@ -1448,6 +1452,18 @@ impl Runtime for Wry {
 
     let mut items = HashMap::new();
 
+    #[cfg(target_os = "macos")]
+    let tray = SystemTrayBuilder::new(
+      icon,
+      system_tray
+        .menu
+        .map(|menu| to_wry_context_menu(&mut items, menu)),
+    )
+    .with_icon_as_template(system_tray.icon_as_template)
+    .build(&self.event_loop)
+    .map_err(|e| Error::SystemTray(Box::new(e)))?;
+
+    #[cfg(not(target_os = "macos"))]
     let tray = SystemTrayBuilder::new(
       icon,
       system_tray
@@ -1940,6 +1956,12 @@ fn handle_event_loop(
             tray.lock().unwrap().set_icon(icon.into_tray_icon());
           }
         }
+        #[cfg(target_os = "macos")]
+        TrayMessage::UpdateIconAsTemplate(is_template) => {
+          if let Some(tray) = &*tray_context.tray.lock().unwrap() {
+            tray.lock().unwrap().set_icon_as_template(is_template);
+          }
+        }
       },
       Message::GlobalShortcut(message) => match message {
         GlobalShortcutMessage::IsRegistered(accelerator, tx) => tx

+ 9 - 0
core/tauri-runtime-wry/src/menu.rs

@@ -75,6 +75,15 @@ impl TrayHandle for SystemTrayHandle {
       .send_event(Message::Tray(TrayMessage::UpdateItem(id, update)))
       .map_err(|_| Error::FailedToSendMessage)
   }
+  #[cfg(target_os = "macos")]
+  fn set_icon_as_template(&self, is_template: bool) -> tauri_runtime::Result<()> {
+    self
+      .proxy
+      .send_event(Message::Tray(TrayMessage::UpdateIconAsTemplate(
+        is_template,
+      )))
+      .map_err(|_| Error::FailedToSendMessage)
+  }
 }
 
 #[cfg(target_os = "macos")]

+ 11 - 0
core/tauri-runtime/src/lib.rs

@@ -34,6 +34,8 @@ use window::{
 pub struct SystemTray {
   pub icon: Option<Icon>,
   pub menu: Option<menu::SystemTrayMenu>,
+  #[cfg(target_os = "macos")]
+  pub icon_as_template: bool,
 }
 
 #[cfg(feature = "system-tray")]
@@ -42,6 +44,8 @@ impl Default for SystemTray {
     Self {
       icon: None,
       menu: None,
+      #[cfg(target_os = "macos")]
+      icon_as_template: false,
     }
   }
 }
@@ -63,6 +67,13 @@ impl SystemTray {
     self
   }
 
+  /// Sets the tray icon as template.
+  #[cfg(target_os = "macos")]
+  pub fn with_icon_as_template(mut self, is_template: bool) -> Self {
+    self.icon_as_template = is_template;
+    self
+  }
+
   /// Sets the menu to show when the system tray is right clicked.
   pub fn with_menu(mut self, menu: menu::SystemTrayMenu) -> Self {
     self.menu.replace(menu);

+ 2 - 0
core/tauri-runtime/src/menu.rs

@@ -148,6 +148,8 @@ pub enum MenuUpdate {
 pub trait TrayHandle {
   fn set_icon(&self, icon: crate::Icon) -> crate::Result<()>;
   fn update_item(&self, id: u16, update: MenuUpdate) -> crate::Result<()>;
+  #[cfg(target_os = "macos")]
+  fn set_icon_as_template(&self, is_template: bool) -> crate::Result<()>;
 }
 
 /// A window menu.

+ 6 - 1
core/tauri-utils/src/config.rs

@@ -209,6 +209,9 @@ pub struct SystemTrayConfig {
   /// Path to the icon to use on the system tray.
   /// Automatically set to be an `.png` on macOS and Linux, and `.ico` on Windows.
   pub icon_path: PathBuf,
+  /// A Boolean value that determines whether the image represents a [template](https://developer.apple.com/documentation/appkit/nsimage/1520017-template?language=objc) image on macOS.
+  #[serde(default)]
+  pub icon_as_template: bool,
 }
 
 /// A CLI argument definition
@@ -391,6 +394,7 @@ pub struct TauriConfig {
   #[serde(default)]
   pub security: SecurityConfig,
   /// System tray configuration.
+  #[serde(default)]
   pub system_tray: Option<SystemTrayConfig>,
 }
 
@@ -866,8 +870,9 @@ mod build {
 
   impl ToTokens for SystemTrayConfig {
     fn to_tokens(&self, tokens: &mut TokenStream) {
+      let icon_as_template = self.icon_as_template;
       let icon_path = path_buf_lit(&self.icon_path);
-      literal_struct!(tokens, SystemTrayConfig, icon_path);
+      literal_struct!(tokens, SystemTrayConfig, icon_path, icon_as_template);
     }
   }
 

+ 29 - 0
core/tauri/src/app.rs

@@ -759,6 +759,15 @@ impl<R: Runtime> Builder<R> {
       icon
     };
 
+    #[cfg(all(feature = "system-tray", target_os = "macos"))]
+    let system_tray_icon_as_template = context
+      .config
+      .tauri
+      .system_tray
+      .as_ref()
+      .map(|t| t.icon_as_template)
+      .unwrap_or_default();
+
     let manager = WindowManager::with_handlers(
       context,
       self.plugins,
@@ -844,6 +853,8 @@ impl<R: Runtime> Builder<R> {
       if let Some(menu) = system_tray.menu {
         tray = tray.with_menu(menu);
       }
+
+      #[cfg(not(target_os = "macos"))]
       let tray_handler = app
         .runtime
         .as_ref()
@@ -857,6 +868,24 @@ impl<R: Runtime> Builder<R> {
           ),
         )
         .expect("failed to run tray");
+
+      #[cfg(target_os = "macos")]
+      let tray_handler = app
+        .runtime
+        .as_ref()
+        .unwrap()
+        .system_tray(
+          tray
+            .with_icon(
+              system_tray
+                .icon
+                .or(system_tray_icon)
+                .expect("tray icon not found; please configure it on tauri.conf.json"),
+            )
+            .with_icon_as_template(system_tray_icon_as_template),
+        )
+        .expect("failed to run tray");
+
       let tray_handle = tray::SystemTrayHandle {
         ids: Arc::new(ids.clone()),
         inner: tray_handler,

+ 9 - 0
core/tauri/src/app/tray.rs

@@ -126,6 +126,15 @@ impl<R: Runtime> SystemTrayHandle<R> {
   pub fn set_icon(&self, icon: Icon) -> crate::Result<()> {
     self.inner.set_icon(icon).map_err(Into::into)
   }
+
+  /// Support [macOS tray icon template](https://developer.apple.com/documentation/appkit/nsimage/1520017-template?language=objc) to adjust automatically based on taskbar color.
+  #[cfg(target_os = "macos")]
+  pub fn set_icon_as_template(&self, is_template: bool) -> crate::Result<()> {
+    self
+      .inner
+      .set_icon_as_template(is_template)
+      .map_err(Into::into)
+  }
 }
 
 impl<R: Runtime> SystemTrayMenuItemHandle<R> {

+ 5 - 1
docs/usage/guides/visual/system-tray.md

@@ -12,7 +12,8 @@ Configure the `systemTray` object on `tauri.conf.json`:
 {
   "tauri": {
     "systemTray": {
-      "iconPath": "icons/icon.png"
+      "iconPath": "icons/icon.png",
+      "iconAsTemplate": true,
     }
   }
 }
@@ -20,6 +21,9 @@ Configure the `systemTray` object on `tauri.conf.json`:
 
 The `iconPath` is pointed to a PNG file on macOS and Linux, and a `.ico` file must exist for Windows support.
 
+The `iconAsTemplate` is a boolean value that determines whether the image represents a [template](https://developer.apple.com/documentation/appkit/nsimage/1520017-template?language=objc) image on macOS.
+
+
 ### Creating a system tray
 
 To create a native system tray, import the `SystemTray` type:

BIN
examples/.icons/tray_icon.png


BIN
examples/.icons/tray_icon_with_transparency.ico


BIN
examples/.icons/tray_icon_with_transparency.png


+ 56 - 1
examples/api/src-tauri/src/main.rs

@@ -14,6 +14,9 @@
 mod cmd;
 mod menu;
 
+#[cfg(target_os = "linux")]
+use std::path::PathBuf;
+
 use serde::Serialize;
 use tauri::{
   CustomMenuItem, Event, Manager, SystemTray, SystemTrayEvent, SystemTrayMenu, WindowBuilder,
@@ -53,7 +56,9 @@ fn main() {
       SystemTray::new().with_menu(
         SystemTrayMenu::new()
           .add_item(CustomMenuItem::new("toggle", "Toggle"))
-          .add_item(CustomMenuItem::new("new", "New window")),
+          .add_item(CustomMenuItem::new("new", "New window"))
+          .add_item(CustomMenuItem::new("icon_1", "Tray Icon 1"))
+          .add_item(CustomMenuItem::new("icon_2", "Tray Icon 2")),
       ),
     )
     .on_system_tray_event(|app, event| match event {
@@ -89,6 +94,56 @@ fn main() {
               },
             )
             .unwrap(),
+          #[cfg(target_os = "macos")]
+          "icon_1" => {
+            app.tray_handle().set_icon_as_template(true).unwrap();
+
+            app
+              .tray_handle()
+              .set_icon(tauri::Icon::Raw(
+                include_bytes!("../../../.icons/tray_icon_with_transparency.png").to_vec(),
+              ))
+              .unwrap();
+          }
+          #[cfg(target_os = "macos")]
+          "icon_2" => {
+            app.tray_handle().set_icon_as_template(true).unwrap();
+
+            app
+              .tray_handle()
+              .set_icon(tauri::Icon::Raw(
+                include_bytes!("../../../.icons/tray_icon.png").to_vec(),
+              ))
+              .unwrap();
+          }
+          #[cfg(target_os = "linux")]
+          "icon_1" => app
+            .tray_handle()
+            .set_icon(tauri::Icon::File(PathBuf::from(
+              "../../../.icons/tray_icon_with_transparency.png",
+            )))
+            .unwrap(),
+          #[cfg(target_os = "linux")]
+          "icon_2" => app
+            .tray_handle()
+            .set_icon(tauri::Icon::File(PathBuf::from(
+              "../../../.icons/tray_icon.png",
+            )))
+            .unwrap(),
+          #[cfg(target_os = "windows")]
+          "icon_1" => app
+            .tray_handle()
+            .set_icon(tauri::Icon::Raw(
+              include_bytes!("../../../.icons/tray_icon_with_transparency.ico").to_vec(),
+            ))
+            .unwrap(),
+          #[cfg(target_os = "windows")]
+          "icon_2" => app
+            .tray_handle()
+            .set_icon(tauri::Icon::Raw(
+              include_bytes!("../../../.icons/icon.ico").to_vec(),
+            ))
+            .unwrap(),
           _ => {}
         }
       }

+ 2 - 1
examples/api/src-tauri/tauri.conf.json

@@ -78,7 +78,8 @@
       "csp": "default-src blob: data: filesystem: ws: wss: http: https: tauri: asset: 'unsafe-eval' 'unsafe-inline' 'self' img-src: 'self'"
     },
     "systemTray": {
-      "iconPath": "../../.icons/icon.png"
+      "iconPath": "../../.icons/tray_icon_with_transparency.png",
+      "iconAsTemplate": true
     }
   }
 }

+ 3 - 0
tooling/cli.rs/config_definition.rs

@@ -623,6 +623,9 @@ pub struct SystemTrayConfig {
   ///
   /// It is forced to be a `.png` file on Linux and macOS, and a `.ico` file on Windows.
   pub icon_path: PathBuf,
+  /// A Boolean value that determines whether the image represents a [template](https://developer.apple.com/documentation/appkit/nsimage/1520017-template?language=objc) image on macOS.
+  #[serde(default)]
+  pub icon_as_template: bool,
 }
 
 // We enable the unnecessary_wraps because we need

+ 5 - 0
tooling/cli.rs/schema.json

@@ -902,6 +902,11 @@
         "iconPath"
       ],
       "properties": {
+        "iconAsTemplate": {
+          "description": "A Boolean value that determines whether the image represents a [template](https://developer.apple.com/documentation/appkit/nsimage/1520017-template?language=objc) image on macOS.",
+          "default": false,
+          "type": "boolean"
+        },
         "iconPath": {
           "description": "Path to the icon to use on the system tray.\n\nIt is forced to be a `.png` file on Linux and macOS, and a `.ico` file on Windows.",
           "type": "string"