> 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"); > } > });
Michael Strauß has updated the pull request with a new target base due to a merge or a rebase. The pull request now contains 15 commits: - Application.userAgentStylesheet/styleTheme can be modified on any thread - Merge branch 'master' into feature/theming - macOS platform preferences refactoring - GTK platform preferences implementation - Improved log formatting in PlatformPreferencesTest - Refactored win/ThemeSupport - Merge branch 'master' into feature/theming - Added manual PlatformPreferencesTest - Javadoc table formatting - Added macOS platform preferences implementation - ... and 5 more: https://git.openjdk.org/jfx/compare/48f6f5ba...e4f6d39f ------------- Changes: https://git.openjdk.org/jfx/pull/511/files Webrev: https://webrevs.openjdk.org/?repo=jfx&pr=511&range=11 Stats: 3889 lines in 51 files changed: 3504 ins; 240 del; 145 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