IsDarkMode

So, now that we’ve determined all this, let’s update the documentation to clarify!

“For the most accurate results, IsDarkMode is not cached. If you are using it within a tight drawing space (such as Listbox rows) you may optionally cache it yourself for faster drawing.” or something like that.

Since Tim’s suggestion was so well-written, I’ve added it to the Notes for the IsDarkMode doc page.

My question is, why do you need to know if darkMode is enabled?

custom canvas based controls and drawing them properly in dark mode
drawing custom listboxes in dark mode (where calling into IsDarkMode a lot might be slow as heck apparently)

If you use the NSColor constants; you don’t need to worry about the theme. For images; if you use [NSImage template] the OS will draw them in the correct color also (albeit they don’t work correctly in a listbox without some extra math).
I’ve seen a couple of 3rd party classes floating around in this forum. I also wrote a tutorial in xDev last year on how to get the colors.

Another advantage of using the NSColor constants; is as Apple tweak their dark mode or introduce a new theme, you’ll get theme compatibility for FREE.

Why not just observe NSApplication.effectiveAppearance? Whenever it changes, set the isDarkMode property appropriately, update NSAppearance.currentAppearance (so that NSColor stuff works as expected) and then raise the appearanceChanged event for updating any custom drawn assets or whatever you need/want to do.

That’s the process I’ve settled on (after moderate hair-pulling) and it’s working flawlessly so far. No need for isDarkMode to be a function or computed property at all.

As things currently are, it looks like the appearanceChanged event fires on a timer AFTER paint has already been called with isDarkMode=true? So I’m not sure how AppearanceChanged is a particularly useful feature in it’s current state… It seems like it should fire as soon as the property changes and before any paint events fire…

I would also suggest adding a ThemeChanged event to Canvas either observing NSView.effectiveAppearance or the viewDidChangeEffectiveAppearance notification.

Jim, this just helps illustrate what I am trying to say. You’ve added a solution to help you know what the current theme is, in order for your application to function correctly it relies upon Apple not changing something somewhere and not breaking your solution.

By abstracting away the need to know what the current theme is and designing a solution where you simply don’t need to care; you’ll may find some of the frustration goes away. I call this playing dumb, as I don’t need to know, nor do I care anymore. In theory, my apps should look consistent if Apple changes their drakTheme or adds a completely different theme (obviously this doesn’t take into account UI shapes and such).

Retina was a huge lesson for me in this regard; where my solutions got more and more complicated in-order to handle edge cases I discovered. Once I understood how Objective-C applications approached Retina and I adopted a similar model, the need for these ‘solutions’ disappeared as my apps handled Retina far more elegantly (most of the functions are now part of the Xojo framework).

In all fairness; I am using the appearanceChanged event to flush a cache, a cache I create to reduce the conversion between NSColor constants and Xojo colors. ideally it would quite helpful if there was the ability to use NSColor constants in Xojo (and NSImages) without the need for any conversion. I would imagine that would require some redesign and rewriting of the Xojo framework.

I would love to see true theming (like macOS 8) making a come back.

That would be nice but the Color type cant handle it
NSColor uses doubles - Xojo’s color uses Uint8 (a single 32 bit value for a color)
So the range of color expressible in NSColor is outside the range Color can express
And NSColor includes other things that Color has not means to like patterns

But you know this already :stuck_out_tongue:

Even with the loss of precision, the colors you can produce from NSColor are very accurate. My Beacon app only needs to query IsDarkMode so it can make an HTMLViewer request with a “dark” parameter. Doing everything with system colors is SOOOO much simpler.

Warning - slightly off topic old man developer rant follows:

And THEREIN lies the TRUE rub - we, as developers, can’t depend on Apple to maintain a stable API for as long as it takes one of their kid engineers to wipe his runny nose and scratch his bottom side. And then, the documentation for such a change does not occur until someone is bitten by the change, can’t find the documentation OF the change, and reports it.

I made the mistake of depending on an Apple implementation of a FreeBSD API for SCSI device access back in 2001, only to have it ripped out from under me with no warning and no documentation of the change. Their answer to an engineering incident (remember when we had those?) was that they didn’t believe that anyone was using the API because of the implementation of scsilib - a higher level wrapper around that prior existing FreeBSD API - that was included that came from the prior Mac OS. We finally got the SCSITaskLib API in 10.2 (2004-ish) that finally replaced that missing functionality.

In deed; what I was thinking and forgive me for not making it clear is a solution similar to how NSColor works. Where the color class is just a base class; it’s up to the various subclasses how to deal with the values. For instance, we have to convert NSColor constants to Xojo colors; only for Xojo to convert them back to NSColors when using them (I understand that this isn’t 100% accurate as they have to be converted to a CGColor for the graphics class on a Mac).

Amen to that brother.

colors in xojo are just UInt32, so no way to reference a NSColor. Maybe someday it could be changed to be an object, so we could have colors referencing NSColor for Mac.

I haven’t added anything. I’m just using what Apple has provided and suggested for this situation. (rather than Xojo’s solution)

I’m just trying to convey these concepts to Xojo in hopes that they’ll rethink the AppearanceChanged event and isDarkMode. I wouldn’t use it in its current state as it seems fragile/unpredictable. currentAppearance is not set for the AppearanceChanged event, so your NSColor stuff will give wrong results, and isDarkMode is just inefficient as it stands.

Unless you do any themed drawing outside of the paint event (or caching of color values). NSColor relies on NSAppearance.currentAppearance being set. AppKit only sets it during drawRect:, updateLayer, layout and updateConstraints. That means that you (and Xojo) should set it to either NSView.effectiveAppearance or NSApplication.effectiveAppearance if you intend on doing anything that might pull themed colors (NSColor).

I just checked and currentAppearance always returns the appearance an app was launched under during app.AppearanceChanged, regardless of the actual appearance (Dark/not Dark). That is just going to lead to a bunch of confusion and issues for other users when they think they can update their nice NSColor values in response to that event.

That’s the thing. Don’t check the property (well, maybe once at open), let the OS tell you about it. Add an observer to sharedApplication.effectiveAppearance. 100% reliable. Just be sure to set currentAppearance to effectiveAppearance before you raise the AppearanceChanged event and everything works as expected.

Here, I’ll keep it simple so that those who don’t know anything about AppKit can still understand what I mean.

Both you and I agree that at the time of the “Paint” event, the NSColor constants are correct.
This is the only time you should be doing custom GUI drawing, so using them in the Paint event is a sure fire way to get the correct colors. If you need to cache colors, the Paint event is the most predicable event to ensure they’re correct.

In the appearanceChanged event; you’re pretty sure that the theme has changed, so you flush that cache.

And for good measure, you can flush it again on app.activate. That way if the order of the events changes and the app isn’t displaying correctly, simply bringing it forwards will fix that.

So to recap, the easiest route that I have found (there may of course be other/better ways): “Paint” is where you do your GUI drawing and any color caching. “appearanceChanged” & “app.activate” is where you flush said cache.

I forgot to mention that there’s a CSS media query for switching the theme, which auto-updates on change.

[quote=437165:@Sam Rowlands]I forgot to mention that there’s a CSS media query for switching the theme, which auto-updates on change.
https://css-tricks.com/dark-modes-with-css/ [/quote]
Beacon’s website already uses it. But I needed it supported before the Safari update that turned it on.

@Sam Rowlands [quote] I forgot to mention that there’s a CSS media query for switching the theme, which auto-updates on change.[/quote]
I too use it for my website (@media (prefers-color-scheme: dark) {etc…).
Although at present it works only with Safari. Other browsers ignore it.

Lol… I was thinking when using an HTMLViewer in an app to display HTML content.

In all honesty; I’d not given much thought to web broweser in a dark themed macOS. I think FireFox has it’s own dark theme.

@Sam Rowlands [quote]I think FireFox has it’s own dark theme[/quote]
Indeed Firefox applies its own dark theme, but at present only to its own interface, not to the content of a page relying on CSS query media.

Firefox dark mode media query is due in Firefox 67 which was allegedly planned for May.
My eyes are eagerly awaiting this update…