> 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. > * > * @implSpec Implementations of this method that return an {@link > ObservableList} must emit all > * change notifications on the JavaFX application thread. > * > * @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 21, 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 can be modified on any thread, but it is not > thread-safe and must > * not be concurrently modified with {@link #styleThemeProperty() > styleTheme}. > */ > 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 can be modified on any thread, but it is not > thread-safe and must not be > * concurrently modified with {@link #userAgentStylesheetProperty() > userAgentStylesheet}. > */ > 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. Preferences > `javafx.application.Platform.Preferences` 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 `ObservableMap` 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 Preferences} extends {@link ObservableMap} 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 Preferences extends ObservableMap<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); > } > > An instance of `Preferences` can be retrieved via `Platform.getPreferences()`. > > Here's a list of the preferences available for Windows, as reported by the > [SystemParametersInfo](https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-systemparametersinfow), > > [GetSysColor](https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-getsyscolor) > and > [Windows.UI.ViewManagement.UISettings.GetColorValue](https://learn.microsoft.com/en-us/uwp/api/windows.ui.viewmanagement.uisettings.getcolorvalue) > APIs. Deprecated colors are not included. > | Windows preferences | Type | > |--------------------------------------|---------| > | Windows.SPI.HighContrast | Boolean | > | Windows.SPI.HighContrastColorScheme | String | > | Windows.SysColor.COLOR_3DFACE | Color | > | Windows.SysColor.COLOR_BTNTEXT | Color | > | Windows.SysColor.COLOR_GRAYTEXT | Color | > | Windows.SysColor.COLOR_HIGHLIGHT | Color | > | Windows.SysColor.COLOR_HIGHLIGHTTEXT | Color | > | Windows.SysColor.COLOR_HOTLIGHT | Color | > | Windows.SysColor.COLOR_WINDOW | Color | > | Windows.SysColor.COLOR_WINDOWTEXT | Color | > | Windows.UIColor.Background | Color | > | Windows.UIColor.Foreground | Color | > | Windows.UIColor.AccentDark3 | Color | > | Windows.UIColor.AccentDark2 | Color | > | Windows.UIColor.AccentDark1 | Color | > | Windows.UIColor.Accent | Color | > | Windows.UIColor.AccentLight1 | Color | > | Windows.UIColor.AccentLight2 | Color | > | Windows.UIColor.AccentLight3 | Color | > > Here is a list of macOS preferences as reported by `NSColor`'s [UI Element > Colors](https://developer.apple.com/documentation/appkit/nscolor/ui_element_colors) > and [Adaptable System > Colors](https://developer.apple.com/documentation/appkit/nscolor/standard_colors). > Deprecated colors are not included. > | macOS preferences | > Type | > |----------------------------------------------------------|---------| > | macOS.NSColor.labelColor | Color | > | macOS.NSColor.secondaryLabelColor | Color | > | macOS.NSColor.tertiaryLabelColor | Color | > | macOS.NSColor.quaternaryLabelColor | Color | > | macOS.NSColor.textColor | Color | > | macOS.NSColor.placeholderTextColor | Color | > | macOS.NSColor.selectedTextColor | Color | > | macOS.NSColor.textBackgroundColor | Color | > | macOS.NSColor.selectedTextBackgroundColor | Color | > | macOS.NSColor.keyboardFocusIndicatorColor | Color | > | macOS.NSColor.unemphasizedSelectedTextColor | Color | > | macOS.NSColor.unemphasizedSelectedTextBackgroundColor | Color | > | macOS.NSColor.linkColor | Color | > | macOS.NSColor.separatorColor | Color | > | macOS.NSColor.selectedContentBackgroundColor | Color | > | macOS.NSColor.unemphasizedSelectedContentBackgroundColor | Color | > | macOS.NSColor.selectedMenuItemTextColor | Color | > | macOS.NSColor.gridColor | Color | > | macOS.NSColor.headerTextColor | Color | > | macOS.NSColor.alternatingContentBackgroundColors | Color[] | > | macOS.NSColor.controlAccentColor | Color | > | macOS.NSColor.controlColor | Color | > | macOS.NSColor.controlBackgroundColor | Color | > | macOS.NSColor.controlTextColor | Color | > | macOS.NSColor.disabledControlTextColor | Color | > | macOS.NSColor.selectedControlColor | Color | > | macOS.NSColor.selectedControlTextColor | Color | > | macOS.NSColor.alternateSelectedControlTextColor | Color | > | macOS.NSColor.currentControlTint | String | > | macOS.NSColor.windowBackgroundColor | Color | > | macOS.NSColor.windowFrameTextColor | Color | > | macOS.NSColor.underPageBackgroundColor | Color | > | macOS.NSColor.findHighlightColor | Color | > | macOS.NSColor.highlightColor | Color | > | macOS.NSColor.shadowColor | Color | > | macOS.NSColor.systemBlueColor | Color | > | macOS.NSColor.systemBrownColor | Color | > | macOS.NSColor.systemGrayColor | Color | > | macOS.NSColor.systemGreenColor | Color | > | macOS.NSColor.systemIndigoColor | Color | > | macOS.NSColor.systemOrangeColor | Color | > | macOS.NSColor.systemPinkColor | Color | > | macOS.NSColor.systemPurpleColor | Color | > | macOS.NSColor.systemRedColor | Color | > | macOS.NSColor.systemTealColor | Color | > | macOS.NSColor.systemYellowColor | Color | > > On Linux, GTK's theme name and [public CSS > colors](https://gitlab.gnome.org/GNOME/gtk/-/blob/gtk-3-22/gtk/theme/Adwaita/_colors-public.scss) > are reported: > | Linux preferences | Type | > |----------------------------------------------------|---------| > | GTK.theme_name | String | > | GTK.theme_fg_color | Color | > | GTK.theme_bg_color | Color | > | GTK.theme_base_color | Color | > | GTK.theme_selected_bg_color | Color | > | GTK.theme_selected_fg_color | Color | > | GTK.insensitive_bg_color | Color | > | GTK.insensitive_fg_color | Color | > | GTK.insensitive_base_color | Color | > | GTK.theme_unfocused_fg_color | Color | > | GTK.theme_unfocused_bg_color | Color | > | GTK.theme_unfocused_base_color | Color | > | GTK.theme_unfocused_selected_bg_color | Color | > | GTK.theme_unfocused_selected_fg_color | Color | > | GTK.borders | Color | > | GTK.unfocused_borders | Color | > | GTK.warning_color | Color | > | GTK.error_color | Color | > | GTK.success_color | Color | > > ### Built-in themes > The two built-in themes `CaspianTheme` and `ModenaTheme` are exposed as > public API in the `javafx.scene.control.theme` package. Both classes extend > `ThemeBase`, which is a simple `StyleTheme` implementation that allows > developers to easily extend the built-in themes. > > ### 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 incrementally with one additional commit since the last revision: Rename Application.styleTheme to userAgentStyleTheme ------------- Changes: - all: https://git.openjdk.org/jfx/pull/511/files - new: https://git.openjdk.org/jfx/pull/511/files/757f7ff5..12882080 Webrevs: - full: https://webrevs.openjdk.org/?repo=jfx&pr=511&range=18 - incr: https://webrevs.openjdk.org/?repo=jfx&pr=511&range=17-18 Stats: 240 lines in 7 files changed: 111 ins; 33 del; 96 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