On Mon, 24 Apr 2023 22:08:20 GMT, Andy Goryachev <ango...@openjdk.org> wrote:

> I meant
> 
> ```
> ObjectProperty<?> Preferences.getProperty(String key);
> Set<String> listPropertyKeys();
> ```
> 
> it would also be nice to obtain a typed property right away. That might be 
> more difficult, since one needs to use a dedicated class for the key what 
> contains the value type, which might be clunky.

I discussed this briefly in a previous 
[comment](https://github.com/openjdk/jfx/pull/1014#issuecomment-1500535347). 
The problem with this idea is that you need to do two things: first, check 
whether a given preference is available on the current platform, and second, 
get the property for the key.

What happens if you try to retrieve a property for a key that's not available? 
Do you get an exception? Do you get a property implementation that holds a 
default value? What if you want to control the default value?

The `Optional` approach, as currently proposed, solves this problem with an API 
that Java users already know. For example, if you are targeting macOS and you 
are interested in the `macOS.NSColor.findHighlightColor` preference, which is 
only available starting with macOS 10.13, you could provide a suitable fallback 
like this:

var findHighlightColor = Platform.getPreferences()
    .getColor("macOS.NSColor.findHighlightColor")
    .orElse(Color.BLUE);

> Map.containsValue() would have to resolve values for all the keys. Is this 
> expected? If the code already resolves all values, I presume on the first 
> call to `getPreferences`, the Map is probably ok.
> 
> Does querying for all the values take long? Any possibility of getting 
> blocked waiting for something?

Yes, the `Map` contract holds, which is why all values will have to be resolved 
by the time you're calling `Map.containsValue()`. From an API perspective, it 
is unspecified how that works, i.e. whether values are lazily or eagerly 
resolved. From an implementation perspective, all values are queried on 
application startup, and after that the values are updated as updates come in.

For example, in the Windows toolkit, this works by intercepting several events 
in the main window loop, including `WM_SETTINGCHANGE`, `WM_THEMECHANGED`, or 
`WM_SYSCOLORCHANGE`. On macOS, the native toolkit subscribes to the 
`AppleColorPreferencesChangedNotification` and 
`AppleInterfaceThemeChangedNotification` events. After receiving one of these 
events, the updated values are sent back to the `PlatformImpl` class, where 
they are integrated into the `Preferences` map.

The event-based approach of the current implementation doesn't run any risk of 
blocking calls.

> > What I've been calling `Appearance` in this PR is _one_ of those knobs.
> 
> I think we are on the same page here.
> 
> My point here is that, from the API design standpoint, it is better to think 
> ahead and consider controlling multiple attributes instead of a singularly 
> invented Appearance enum. Why not have `Stage.setAttributes(Set)` instead of 
> `setAppearance()` then?
> 
> We can still have an Apperance enum, but this time it will be just one of 
> many possible attributes. it could be platform-independent if we agree that 
> it's well defined, or it could be platform-specific like 
> DWMWA_USE_IMMERSIVE_DARK_MODE.

There's a reason why `Appearance` is kind of singled out among all of the 
potential other knobs, and that is because it represents a concept that 
interacts very strongly with the upcoming Style Themes feature.

In total, I propose three PRs:
[JDK-8301302: Platform preferences 
API](https://bugs.openjdk.org/browse/JDK-8301302)
[JDK-8301303: Allow Stage to control the appearance of window 
decorations](https://bugs.openjdk.org/browse/JDK-8301303)
[JDK-8267546: Add CSS themes as a first-class 
concept](https://bugs.openjdk.org/browse/JDK-8267546)

`Appearance` is central to _all_ of those features, as it encodes the "light 
mode or dark mode" knob. This is the most significant knob that users will 
interact with, as it represents:
1. An OS-wide system setting
2. The general "light or dark" appearance of window borders
3. The colors used in application stylesheets, icons, images, etc.

For a style theme author, this bit of information is central, as the entire 
look and feel of the theme will be designed around it. Most likely there will 
be at least two hand-picked sets of colors for a style theme depending on 
whether light or dark mode is enabled.

I don't think we'll want the style theme author to bother figuring out whether 
some obscure platform-specific `DWMWA_USE_IMMERSIVE_DARK_MODE` flag is set, or 
whether the current macOS appearance is named `NSAppearanceNameDarkAqua`. So we 
need a platform-independent way of encoding this bit, and `Appearance` is 
pretty easy to use for style theme authors:

// Note that a real implementation would need to subscribe to Appearance 
changes,
// and update the stylesheets accordingly
public class MyTheme implements StyleTheme {
    public MyTheme() {
        if (Platform.getPreferences().getAppearance() == Appearance.LIGHT) {
            // compose light stylesheets
        } else {
            // compose dark stylesheets
        }
    }
}


Note that the water actually gets a bit muddy when you factor in accessibility 
themes. Is a high-contrast accessibility theme a separate appearance? What 
about an inverted color mode? My best answer to this is: no, it's not.

On macOS, inverted color mode works without the application knowing about it. 
On Windows, accessibility themes certainly don't work without the application 
knowing about it, so a theme that works with high-contrast accessibility needs 
to monitor several very platform-specific keys, and then integrate those 
special colors into the theme. That's not something that we can easily 
represent or condense down, or even represent in a platform-agnostic way.

I agree with you that we should carefully plan ahead and see how we might 
extend this in the future to control more knobs. The `Preferences` API is not 
the right place for this, since `Preferences` can only query settings from the 
OS, it cannot change them.

My current thinking on this is that there are three categories of knobs:
1. Knobs that significantly affect the window border *and* the window content
2. Knobs that significantly affect *only* the window border
3. Knobs that affect OS-specific details of the window border

The first category is represented by `Appearance`, that's why it gets to have 
its own enumeration.
The second category is represented by `StageStyle`, because we have broad 
support on _most_ operating systems.
The third category is currently unrepresented, and it includes knobs like 
`DWM_WINDOW_CORNER_PREFERENCE`, which is only available on Windows 11.

I can imagine that we could have something like a `Stage.hints` property, where 
developers can specify those cat3 knobs. The name "hint" strongly suggests that 
we're talking about optional things. Yes, an app developer might want to have 
rounded window corners. But when the app runs on Windows 10, there simply won't 
be rounded corners, and that's not a big deal.

-------------

PR Comment: https://git.openjdk.org/jfx/pull/1014#issuecomment-1520924295
PR Comment: https://git.openjdk.org/jfx/pull/1014#issuecomment-1520948693
PR Comment: https://git.openjdk.org/jfx/pull/1014#issuecomment-1520968329

Reply via email to