Custom Tray Icons with Objc / Swift
It would be cool to support custom icon implementations. While currently possible on macOS using custom NSView within an NSStatusItem, it's yet to make it into any Tauri implementations.
In Swift you can do it roughly like below, which I based on this blog
let statusItem = NSStatusBar.system.statusItem(withLength: NSStatusItem.variableLength)
let customView = NSView(frame: NSRect(x: 0, y: 0, width: 30, height: 30))
let arcPath = NSBezierPath()
arcPath.appendArc(withCenter: NSPoint(x: 15, y: 15), radius: 10, startAngle: 90, endAngle: 90 - 270, clockwise: true)
NSColor.systemBlue.setStroke()
arcPath.lineWidth = 3
arcPath.stroke()
statusItem.button?.addSubview(customView)
A hacky(?) way to do it could be modify the set_icon_for_ns_status_item_button. The example below is not very flexible, but is just to prove the concept. For flexibility, it could accept an NSView, which the user can define.
use objc2::foundation::{NSPoint, NSRect, NSString};
use objc2::rc::Retained;
use objc2::{msg_send, sel};
use objc2_app_kit::{NSColor, NSStatusItem, NSView};
use objc2_foundation::MainThreadMarker;
pub fn set_icon_for_ns_status_item_button(
ns_status_item: &NSStatusItem,
progress: Option<f64>, // Use progress to set circle color dynamically
mtm: MainThreadMarker,
) -> crate::Result<()> {
let button = unsafe { ns_status_item.button(mtm).unwrap() };
// Remove any previous image or view from the button
unsafe { button.setImage(None) };
// Create a new custom view and configure it
let view = create_custom_view(progress);
// Set the frame of the button and add the custom view as a subview
let frame = NSRect::new(0.0, 0.0, 120.0, 22.0);
view.setFrame(frame);
button.setFrame(frame);
button.addSubview(&view);
Ok(())
}
// Function to create the custom view with dynamic color based on progress
fn create_custom_view(progress: Option<f64>) -> Retained<NSView> {
unsafe {
// Create a base NSView with specified frame dimensions
let frame = NSRect::new(0.0, 0.0, 120.0, 22.0);
let view = NSView::alloc(nil).initWithFrame(frame);
// Enable the layer for custom drawing
view.setWantsLayer(true);
// Add a label displaying "Custom View" to the view
let label: *mut Object = msg_send![class!(NSTextField), alloc];
let label: *mut Object = msg_send![label, initWithFrame: NSRect::new(10.0, 2.0, 80.0, 20.0)];
let text = NSString::from_str("Custom View");
let _: () = msg_send![label, setStringValue: text];
let _: () = msg_send![label, setBezeled: false];
let _: () = msg_send![label, setDrawsBackground: false];
let _: () = msg_send![label, setEditable: false];
let _: () = msg_send![label, setSelectable: false];
view.addSubview(label);
// Draw a partial circle to represent a progress indicator
let circle_path: *mut Object = msg_send![class!(NSBezierPath), bezierPath];
let _: () = msg_send![circle_path, appendBezierPathWithArcWithCenter: NSPoint::new(100.0, 11.0)
radius: 8.0
startAngle: 0.0
endAngle: 270.0
clockwise: true];
// Determine the color based on the progress value
let stroke_color: *mut Object = match progress {
Some(p) if p < 40.0 => msg_send![class!(NSColor), systemRedColor],
Some(p) if p < 65.0 => msg_send![class!(NSColor), systemYellowColor],
_ => msg_send![class!(NSColor), systemGreenColor],
};
// Set the color and line width, then draw the circle
let _: () = msg_send![stroke_color, setStroke];
let _: () = msg_send![circle_path, setLineWidth: 3.0];
let _: () = msg_send![circle_path, stroke];
view
}
}
We can add a macOS-specific trait that could expose a set_icon_from_ns_view, this way you can create the view using Objc as you like.
Please open a PR if you'd like. I can't really work on this since I don't have a macOS but I will help in any way I can.
I'm not sure if this is still relevant but adding this would be great for customizing tray-icons on macos. I'm personally interested in something like the keyboard light slider in the tray icon like this:
Should this be something achievable in macos?
https://github.com/tauri-apps/tray-icon/pull/272