This PR adds style themes as a first-class concept to OpenJFX. A style theme is a collection of stylesheets and the logic that governs them. Style themes can respond to OS notifications and update their stylesheets dynamically. This PR also re-implements Caspian and Modena as style themes.
### New APIs in `javafx.graphics` The new theming-related APIs in `javafx.graphics` provide a basic framework to support application-wide style themes. Higher-level theming concepts (for example, "dark mode" detection or accent coloring) are not a part of this basic framework, because any API invented here might soon be out of date. Implementations can build on top of this framework to add useful higher-level features. #### 1. StyleTheme A style theme is an implementation of the `javafx.css.StyleTheme` interface: /** * {@code StyleTheme} is a collection of stylesheets that specify the appearance of UI controls and other * nodes in the application. Like a user-agent stylesheet, a {@code StyleTheme} is implicitly used by all * JavaFX nodes in the scene graph. * <p> * The list of stylesheets that comprise a {@code StyleTheme} can be modified while the application is running, * enabling applications to create dynamic themes that respond to changing user preferences. * <p> * In the CSS subsystem, stylesheets that comprise a {@code StyleTheme} are classified as * {@link StyleOrigin#USER_AGENT} stylesheets, but have a higher precedence in the CSS cascade * than a stylesheet referenced by {@link Application#userAgentStylesheetProperty()}. */ public interface StyleTheme { /** * Gets the list of stylesheet URLs that comprise this {@code StyleTheme}. * <p> * If the list of stylesheets that comprise this {@code StyleTheme} is changed at runtime, this * method must return an {@link ObservableList} to allow the CSS subsystem to subscribe to list * change notifications. * * @implNote Implementations of this method that return an {@link ObservableList} are encouraged * to minimize the number of subsequent list change notifications that are fired by the * list, as each change notification causes the CSS subsystem to re-apply the referenced * stylesheets. */ List<String> getStylesheets(); } A new `styleTheme` property is added to `javafx.application.Application`, and `userAgentStylesheet` is promoted to a JavaFX property (currently, this is just a getter/setter pair): public class Application { ... /** * Specifies the user-agent stylesheet of the application. * <p> * A user-agent stylesheet is a global stylesheet that can be specified in addition to a * {@link StyleTheme} and that is implicitly used by all JavaFX nodes in the scene graph. * It can be used to provide default styling for UI controls and other nodes. * A user-agent stylesheets has the lowest precedence in the CSS cascade. * <p> * Before JavaFX 20, built-in themes were selectable using the special user-agent stylesheet constants * {@link #STYLESHEET_CASPIAN} and {@link #STYLESHEET_MODENA}. For backwards compatibility, the meaning * of these special constants is retained: setting the user-agent stylesheet to either {@code STYLESHEET_CASPIAN} * or {@code STYLESHEET_MODENA} will also set the value of the {@link #styleThemeProperty() styleTheme} * property to a new instance of the corresponding theme class. * <p> * Note: this property must only be modified on the JavaFX application thread. */ public static StringProperty userAgentStylesheetProperty(); public static String getUserAgentStylesheet(); public static void setUserAgentStylesheet(String url); /** * Specifies the {@link StyleTheme} of the application. * <p> * {@code StyleTheme} is a collection of stylesheets that define the appearance of the application. * Like a user-agent stylesheet, a {@code StyleTheme} is implicitly used by all JavaFX nodes in the * scene graph. * <p> * Stylesheets that comprise a {@code StyleTheme} have a higher precedence in the CSS cascade than a * stylesheet referenced by the {@link #userAgentStylesheetProperty() userAgentStylesheet} property. * <p> * Note: this property must only be modified on the JavaFX application thread. */ public static ObjectProperty<StyleTheme> styleThemeProperty(); public static StyleTheme getStyleTheme(); public static void setStyleTheme(StyleTheme theme); ... } `styleTheme` and `userAgentStylesheet` are correlated to preserve backwards compatibility: setting `userAgentStylesheet` to the magic values "CASPIAN" or "MODENA" will implicitly set `styleTheme` to a new instance of the `CaspianTheme` or `ModenaTheme` class. Aside from these magic values, `userAgentStylesheet` can be set independently from `styleTheme`. In the CSS cascade, `userAgentStylesheet` has a lower precedence than `styleTheme`. #### 2. PlatformPreferences `javafx.application.PlatformPreferences` can be used to query UI-related information about the current platform to allow theme implementations to adapt to the operating system. The interface extends `Map` and adds several useful methods, as well as the option to register a listener for change notifications: /** * Contains UI preferences of the current platform. * <p> * {@code PlatformPreferences} implements {@link Map} to expose platform preferences as key-value pairs. * For convenience, {@link #getString}, {@link #getBoolean} and {@link #getColor} are provided as typed * alternatives to the untyped {@link #get} method. * <p> * The preferences that are reported by the platform may be dependent on the operating system version. * Applications should always test whether a preference is available, or use the {@link #getString(String, String)}, * {@link #getBoolean(String, boolean)} or {@link #getColor(String, Color)} overloads that accept a fallback * value if the preference is not available. */ public interface PlatformPreferences extends Map<String, Object> { String getString(String key); String getString(String key, String fallbackValue); Boolean getBoolean(String key); boolean getBoolean(String key, boolean fallbackValue); Color getColor(String key); Color getColor(String key, Color fallbackValue); void addListener(PlatformPreferencesListener listener); void removeListener(PlatformPreferencesListener listener); } An instance of `PlatformPreferences` can be retrieved via `Platform.getPreferences()`. ### Usage In its simplest form, a style theme is just a static collection of stylesheets: Application.setStyleTheme(() -> List.of("stylesheet1.css", "stylesheet2.css"); A dynamic theme can be created by returning an instance of `ObservableList`: public class MyCustomTheme implements StyleTheme { private final ObservableList<String> stylesheets = FXCollections.observableArrayList("colors-light.css", "controls.css"); @Override public List<String> getStylesheets() { return stylesheets; } public void setDarkMode(boolean enabled) { stylesheets.set(0, enabled ? "colors-dark.css" : "colors-light.css"); } } `CaspianTheme` and `ModenaTheme` can be extended by prepending or appending additional stylesheets: Application.setStyleTheme(new ModenaTheme() { { addFirst("stylesheet1.css"); addLast("stylesheet2.css"); } }); ------------- Commit messages: - Removed unrelated code from ThemeBase - StyleTheme implementation Changes: https://git.openjdk.org/jfx/pull/511/files Webrev: https://webrevs.openjdk.org/?repo=jfx&pr=511&range=00 Issue: https://bugs.openjdk.org/browse/JDK-8267546 Stats: 2971 lines in 40 files changed: 2596 ins; 227 del; 148 mod Patch: https://git.openjdk.org/jfx/pull/511.diff Fetch: git fetch https://git.openjdk.org/jfx pull/511/head:pull/511 PR: https://git.openjdk.org/jfx/pull/511