Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

NSStatusItem change image for dark tint

With OSX 10.10 beta 3, Apple released their dark tint option. Unfortunately, it also means that pretty much all status bar icons (with the exception of Apple's and Path Finder's that I've seen), including mine, remain dark on a dark background. How can I provide an alternate image for when dark tint is applied?

I don't see an API change on NSStatusBar or NSStatusItem that shows me a change, I'm assuming it's a notification or something reactive to easily make the change as the user alters the tint.

Current code to draw the image is encased within an NSView:

- (void)drawRect:(NSRect)dirtyRect
{
    // set view background color
    if (self.isActive) {
        [[NSColor selectedMenuItemColor] setFill];
    } else {
        [[NSColor clearColor] setFill];
    }

    NSRectFill(dirtyRect);

    // set image
    NSImage *image = (self.isActive ? self.alternateImage : self.image);
    _imageView.image = image;
}
like image 369
Joel Fischer Avatar asked Jul 08 '14 04:07

Joel Fischer


2 Answers

TL;DR: You don't have to do anything special in Dark Theme. Give NSStatusItem (or NSStatusBarButton) a template image and it will style it correctly in any menubar context.


The reason why some apps' status items (such as PathFinder's) already work in Dark Theme is because they're not setting their own custom view on the StatusItem, but only setting a template image on the StatusItem.

Something like:

_statusItem = [[NSStatusBar systemStatusBar] statusItemWithLength:NSSquareStatusItemLength];
NSImage *image = [NSImage imageNamed:@"statusItemIcon"];
[image setTemplate:YES];
[_statusItem setImage:image];

This works exactly as you'd expect in Mavericks and earlier, as well as Yosemite and any future releases because it allows AppKit to do all of the styling of the image depending on the status item state.

Mavericks

In Mavericks (and earlier) there were only 2 unique styles of the items. Unpressed and Pressed. These two styles pretty much looked purely black and purely white, respectively. (Actually "purely black" isn't entirely correct -- there was a small effect that made them look slightly inset).

Because there were only two possible state, status bar apps could set their own view and easily get the same appearance by just drawing black or white depending on their highlighted state. (But again note that it wasn't purely black, so apps either had to build the effect in the image or be satisfied with a hardly-noticeable out of place icon).

Yosemite

In Yosemite there are at least 32 unique styling of items. Unpressed in Dark Theme is only one of those. There is no practical (or unpractical) way for an app to be able to do their own styling of items and have it look correct in all contexts.

Here are examples of six of those possible stylings:

Six possible status item stylings

Status items on an inactive menubar now have a specific styling, as opposed to a simple opacity change as in the past. Disabled appearance is one other possible variation; there are also other additional dimensions to this matrix of possibilities.

API

Arbitrary views set as NSStatusItem's view property have no way to capture all of these variations, hence it (and other related API) is deprecated in 10.10.

However, seed 3 introduces new API on NSStatusItem:

@property (readonly, strong) NSStatusBarButton *button NS_AVAILABLE_MAC(10_10);

This piece of API has a few purposes:

  1. An app can now get the screen position (or show a popover from) a status item without setting its own custom view.
  2. Removes the need for API like image, title, sendActionOn: on NSStatusItem.
  3. Provides a class for new API: i.e. looksDisabled. This allows apps to get the standard disabled/off styling (like Bluetooth/Time Machine when off) without requiring a custom image.

If there's something that can't be done with the current (non- custom view) API, please file an enhancement request for it. StatusItems should provide behavior or appearances in a way that it standard across all status items.


More discussion is at https://devforums.apple.com/thread/234839, although I've summarized most everything here.

like image 121
Taylor Avatar answered Oct 16 '22 11:10

Taylor


I end up did something like following to my custom drag and drop NSStatusItemView: (Using Swift)

var isDark = false

func isDarkMode() {
    isDark = NSAppearance.currentAppearance().name.hasPrefix("NSAppearanceNameVibrantDark")
}

override func drawRect(dirtyRect: NSRect) {
    super.drawRect(dirtyRect)
    isDarkMode()
    // Now use "isDark" to determine the drawing colour.
    if isDark {
        // ...
    } else {
        // ...
    }
}

When the user changed the Theme in System Preferences, the NSView will be called by the system for re-drawing, you can change the icon colour accordingly.

If you wish to adjust other custom UI outside this view, you can either use KVO to observer the isDark key of the view or do it on your own.

like image 6
Cai Avatar answered Oct 16 '22 09:10

Cai