Make themes a first-class concept in JavaFX

Michael Strauß michaelstrau2 at gmail.com
Fri May 21 04:36:57 UTC 2021


Currently, the two themes shipped with JavaFX (Caspian and Modena) are
implemented as a set of stylesheets and some internal logic, mostly in
`PlatformImpl`.

Much of this logic deals with optional features or platform-specific
theme modifications. Another piece of logic deals with accessibility
themes: on Windows platforms, both Caspian and Modena have
high-contrast stylesheets that are added or removed in response to OS
theme changes.

Apart from the fact that high-contrast detection only works on English
locales (and there's no simple fix, see JDK-8185447), a major downside
of this implementation is that it special-cases the built-in themes
and doesn't offer enough extension points to make it easy for
developers to create new rich themes.

I think this can be very much improved by making themes a first-class
citizen in JavaFX, and removing the special handling of the two
built-in themes. Here's my draft PR of this feature:
https://github.com/openjdk/jfx/pull/511

This PR adds a new interface in the javafx.application package:

public interface Theme {
    ObservableList<String> getStylesheets();
    void platformThemeChanged(Map<String, String> properties);
}

An implementation of this interface populates the list returned by
`Theme.getStylesheets()` with its collection of stylesheets.
Optionally, it can listen to the platform theme properties reported by
`Theme.platformThemeChanged(...)` and modify its list of stylesheets
accordingly.

Platform properties are platform-specific key-value pairs, and theme
implementations are responsible for interpreting the values. For
example, on the Windows platform, a theme implementation might listen
to the "Windows.SPI_HighContrastOn" property in order to detect
whether or not a high-contrast stylesheet should be used. The `Theme`
class documents all currently supported platform properties.

A theme is activated by setting it as the user-agent stylesheet of the
application, where "theme:" is a custom URI scheme:
    Application.setUserAgentStylesheet("theme:com.example.CustomTheme");

The existing constants `Application.STYLESHEET_CASPIAN` and
`Application.STYLESHEET_MODENA` are simply redefined to their new
`Theme` implementations
("theme:com.sun.javafx.application.theme.Caspian" and
"theme:com.sun.javafx.application.theme.Modena").

Because theme-specific code is now located in its corresponding
`Theme` class, I've been able to clean up `PlatformImpl` so that it
doesn't special-case the built-in themes anymore.

Going forward: If we add support for loading stylesheets from
data-URIs (similar to https://github.com/openjdk/jfx/pull/508), we can
then fix the high-contrast themes by reading the system colors
reported back by Windows, and creating an in-memory stylesheet for
these colors within the `Modena` implementation.

This feature will also allow developers to create themes that
dynamically adjust to OS-level accent colors or dark mode settings.
For example, a `Theme` implementation can detect dark mode on Windows
10 by listening to the
"Windows.UI.ViewManagement.UISettings.ColorValue_Background" and
"Windows.UI.ViewManagement.UISettings.ColorValue_Foreground"
properties.

I'm looking forward to any opinions on this.


More information about the openjfx-dev mailing list