beeware / toga

A Python native, OS native GUI toolkit.
https://toga.readthedocs.io/en/latest/
BSD 3-Clause "New" or "Revised" License
4.35k stars 671 forks source link

Allow status icons on macOS to adapt to light / dark mode #2861

Open samschott opened 1 month ago

samschott commented 1 month ago

What is the problem or limitation you are having?

Menu bar icons on macOS should adapt with dark / light mode. This fits in with menu bar icons provided by the OS and most macOS apps. It also allows developers to use a black or white menu bar icon which would otherwise be almost invisible in dark or light mode, respectively.

Describe the solution you'd like

On macOS, icons automatically adapt to their surroundings when setting template = true on the NSImage. See https://developer.apple.com/documentation/appkit/nsimage/1520017-template.

We should make template: bool a general argument to toga.Icon for all platforms that support it. This would allow icons to be used more flexibly as templates and fit in more naturally in many environments besides the menu bar.

Describe alternatives you've considered

We always set this flag in the toga-cocoa StatusIcon implementation. While macOS does allow colored menu bar icons, those are very uncommon.

Additional context

No response

freakboy3742 commented 1 month ago

I don't know if template=True is the right "spelling" for this; but I agree that there's something worth capturing here - the idea of a "simple bitmap" icon, that can be masked both dark and light.

One of the bigger questions I have is how this would be interpreted on GTK and Windows. Those platforms don't have the template=True argument... so we'd need to work out what a "template" icon means on those platforms. I guess we could do a "convert to 1 bit mask" and then convert to a white/black based on that mask...

samschott commented 1 month ago

The naming was just copied over from Apple platforms, there might indeed be better ways to express that concept.

Regarding platform support, it's definitely going to be difficult to find an abstraction that works for all of them, especially due to restrictions on file format and content.

macOS seems to like its "template" icons in black and clear colors with an alpha channel to adjust opacity. All typical file formats appear supported.

Gnome and Gtk have the concept of symbolic icons, see https://wiki.gnome.org/Design/OS/SymbolicIcons and https://docs.gtk.org/gtk3/method.IconInfo.load_symbolic.html, but there appear to be quite tight restrictions about the input format (only SVG) and content that is allowed.

I'm not at all familiar with Windows, but symbolic icons that are monochrome and adapt according to system style appear to be the default here. From https://learn.microsoft.com/en-us/windows/apps/design/style/icons, only ImageIcon retains color information from the loaded file

samschott commented 1 month ago

What is toga's and your approach to APIs that might result in different results depending on the platform? For example, a symbolic argument that attempts to convert or declare the icon to be symbolic, but logs a warning if not supported by the platform or the provided file format?

freakboy3742 commented 1 month ago

The general approach to platform inconsistencies is to find the "higher order" idea that is being represented, and build an API for that; and if that idea doesn't make any sense on the platform, either log a warning, or not implement the API at all for that platform.

In this case, it feels like there's 2 "higher order" features -

Both of these seem like the sort of thing that either exists natively, or can be reasonably implemented for a platform. We should be able to come up with a standard set of icons that are commonly available (Home, Back, Upload, Download etc); macOS may not have default icons, but we could source some standard icons that can be used as a fallback.

Similarly, mask icons are definitely a quirk of macOS operation, but any image can be read/converted into a mask and then turned into a black/white-on transparent icon based on whether the app is in light or dark mode.

So - I wouldn't object to a "mask=True" argument on toga.Icon being interpreted as "template" on macOS, with a conversion mechanism on other platforms.

I also wouldn't object to toga.Icon accepting some representation of a symbolic icon - possibly a new type that captures the platform specific name for an icon for platforms that have them, and the name of an image that will be used as a fallback. We could then define an enum of those types for "standard" Toga icons.

samschott commented 1 month ago

Agreed on distinguishing the concepts of "mask" and "symbolic" icons, and your suggested fallbacks when not natively supported by a platform.

mask icons are definitely a quirk of macOS operation

I believe the concept also exists on Gtk and Windows, that's what I was trying to say in https://github.com/beeware/toga/issues/2861#issuecomment-2362245024, though the nomenclature varies for the platforms.

What you call mask, Gnome seems to call symbolic (which is a different concept there from default icons for "home" etc). But it would indeed require either a correctly crafted SVG input, or some preceding conversion steps by toga.

Similarly, Windows always tries to adapt icon colors to the theme, for all of its icon classes except for ImageIcon. E.g., the docs for a BitmapIcon specify:

The file that you use should be a solid image on a transparent background. [...]

All color info is stripped from the bitmap when the BitmapIcon is rendered. The remaining non-transparent colors are combined to produce an image that's entirely the foreground color as set by the Foreground property (this typically comes from styles or templates, such as the default template resolving to a theme resource). You can override this behavior by setting the ShowAsMonochrome property.

freakboy3742 commented 1 month ago

What you call mask, Gnome seems to call symbolic (which is a different concept there from default icons for "home" etc). But it would indeed require either a correctly crafted SVG input, or some preceding conversion steps by toga.

My apologies - I didn't follow the link you provided, assuming I knew which part of the GTK API you were referring to. I assumed you were talking about named icons in icon themes; I see now that symbolic icons are a whole other thing.

I'm not sure how we'd reconcile a hard SVG icon requirement, though. The best thought I've got off the top of my head would be make the list of accepted icon input formats tied to this new flag we're talking about - so a "mask" icon on GTK would require SVG format. This is in addition to baseline addition of support for SVG icons, which look like they will need a different loading mechanism to bitmap-based image sources.

Similarly, Windows always tries to adapt icon colors to the theme, for all of its icon classes except for ImageIcon. E.g., the docs for a BitmapIcon specify:

The file that you use should be a solid image on a transparent background. [...]

I'd be interested to see how this interacts with Toga's existing icon support - we're doing a straightforward "icon from file"; I presume that means we're implicitly creating Bitmap icons, as I haven't seen any color adaptation. However, the docs you've referenced here are also WinUI3 docs, not Winforms, so maybe the treatment is different due to that,