Refactor theme manager to support arbitrary CSS modification at runtime

This commit is contained in:
mkpaz 2022-08-25 14:53:48 +04:00
parent 8c60ae7c07
commit cd16046340
4 changed files with 91 additions and 26 deletions

@ -51,7 +51,8 @@ public class Launcher extends Application {
var scene = new Scene(root, 1200, 768);
var tm = ThemeManager.getInstance();
tm.setTheme(scene, tm.getAvailableThemes().get(0));
tm.setScene(scene);
tm.setTheme(tm.getAvailableThemes().get(0));
if (IS_DEV_MODE) { startCssFX(scene); }
scene.getStylesheets().addAll(

@ -62,7 +62,9 @@ public class ThemePage extends AbstractPage {
selector.getItems().setAll(manager.getAvailableThemes());
selector.getSelectionModel().selectedItemProperty().addListener((obs, old, val) -> {
if (val != null && getScene() != null) {
ThemeManager.getInstance().setTheme(getScene(), val);
var tm = ThemeManager.getInstance();
tm.setTheme(val);
tm.reloadCustomCSS();
colorPalette.updateColorInfo(Duration.ofSeconds(1));
}
});

@ -69,7 +69,9 @@ public class TypographyPage extends AbstractPage {
spinner.valueProperty().addListener((obs, old, val) -> {
if (val != null && getScene() != null) {
ThemeManager.getInstance().setFontSize(getScene(), val);
var tm = ThemeManager.getInstance();
tm.setFontSize(val);
tm.reloadCustomCSS();
updateFontInfo(Duration.ofMillis(1000));
}
});
@ -122,10 +124,10 @@ public class TypographyPage extends AbstractPage {
private SampleBlock fontWeightSample() {
var box = new HBox(10,
text("Bold", TEXT_BOLD),
text("Bolder", TEXT_BOLDER),
text("Normal", TEXT_NORMAL),
text("Lighter", TEXT_LIGHTER)
text("Bold", TEXT_BOLD),
text("Bolder", TEXT_BOLDER),
text("Normal", TEXT_NORMAL),
text("Lighter", TEXT_LIGHTER)
);
box.setAlignment(Pos.BASELINE_LEFT);
@ -134,10 +136,10 @@ public class TypographyPage extends AbstractPage {
private SampleBlock fontStyleSample() {
var box = new HBox(10,
text("Italic", TEXT_ITALIC),
text("Oblique", TEXT_OBLIQUE),
text("Underlined", TEXT_UNDERLINED),
text("Strikethrough", TEXT_STRIKETHROUGH)
text("Italic", TEXT_ITALIC),
text("Oblique", TEXT_OBLIQUE),
text("Underlined", TEXT_UNDERLINED),
text("Strikethrough", TEXT_STRIKETHROUGH)
);
box.setAlignment(Pos.BASELINE_LEFT);
@ -146,10 +148,10 @@ public class TypographyPage extends AbstractPage {
private SampleBlock textColorSample() {
var box = new HBox(10,
text("Accent", TEXT, ACCENT),
text("Success", TEXT, SUCCESS),
text("Warning", TEXT, WARNING),
text("Danger", TEXT, DANGER)
text("Accent", TEXT, ACCENT),
text("Success", TEXT, SUCCESS),
text("Warning", TEXT, WARNING),
text("Danger", TEXT, DANGER)
);
box.setAlignment(Pos.BASELINE_LEFT);
@ -164,9 +166,9 @@ public class TypographyPage extends AbstractPage {
linkVisited.setMnemonicParsing(true);
var box = new HBox(10,
linkNormal,
linkVisited,
hyperlink("Disabled", false, true)
linkNormal,
linkVisited,
hyperlink("Disabled", false, true)
);
box.setAlignment(Pos.BASELINE_LEFT);

@ -7,6 +7,7 @@ import atlantafx.base.theme.Theme;
import atlantafx.sampler.Launcher;
import atlantafx.sampler.Resources;
import javafx.application.Application;
import javafx.css.PseudoClass;
import javafx.scene.Scene;
import java.net.URI;
@ -17,9 +18,25 @@ import static java.nio.charset.StandardCharsets.UTF_8;
public final class ThemeManager {
private static final String DUMMY_STYLESHEET = Resources.getResource("assets/styles/empty.css").toString();
private static final PseudoClass USER_CUSTOM = PseudoClass.getPseudoClass("user-custom");
// KEY | VALUE
// -fx-property | value;
private final Map<String, String> customCSSDeclarations = new LinkedHashMap<>();
// .foo | -fx-property: value;
private final Map<String, String> customCSSRules = new LinkedHashMap<>();
private Theme currentTheme = null;
private int currentFontSize = 14;
private Scene scene;
public Scene getScene() {
return scene;
}
public void setScene(Scene scene) {
this.scene = scene;
}
public Theme getTheme() {
return currentTheme;
@ -33,7 +50,8 @@ public final class ThemeManager {
* E.g. JavaFX ignores Ikonli -fx-icon-color and -fx-icon-size properties, but for an unknown
* reason they won't be ignored when exactly the same stylesheet is set via {@link Scene#getStylesheets()}.
*/
public void setTheme(Scene scene, Theme theme) {
public void setTheme(Theme theme) {
Objects.requireNonNull(scene);
Objects.requireNonNull(theme);
Application.setUserAgentStylesheet(Objects.requireNonNull(theme.getUserAgentStylesheet()));
@ -81,17 +99,59 @@ public final class ThemeManager {
return currentFontSize;
}
public void setFontSize(Scene scene, int fontSize) {
String css = String.format(".root { -fx-font-size: %dpx; } .ikonli-font-icon { -fx-icon-size: %dpx; }",
fontSize,
fontSize + 2
);
public void setFontSize(int fontSize) {
setCustomDeclaration("-fx-font-size", fontSize + "px");
setCustomRule(".ikonli-font-icon", String.format("-fx-icon-size: %dpx;", fontSize + 2));
currentFontSize = fontSize;
}
private void setCustomDeclaration(String property, String value) {
customCSSDeclarations.put(property, value);
}
private void setCustomRule(String selector, String rule) {
customCSSRules.put(selector, rule);
}
public void reloadCustomCSS() {
Objects.requireNonNull(scene);
StringBuilder css = new StringBuilder();
css.append(".root:");
css.append(USER_CUSTOM.getPseudoClassName());
css.append(" {\n");
customCSSDeclarations.forEach((k, v) -> {
css.append("\t");
css.append(k);
css.append(":\s");
css.append(v);
css.append(";\n");
});
css.append("}\n");
customCSSRules.forEach((k, v) -> {
css.append(".root:");
css.append(USER_CUSTOM.getPseudoClassName());
css.append(" ");
css.append(k);
css.append(" {");
css.append(v);
css.append("}\n");
});
System.out.println(css);
scene.getStylesheets().removeIf(uri -> uri.startsWith("data:text/css"));
scene.getStylesheets().add(
"data:text/css;base64," + Base64.getEncoder().encodeToString(css.getBytes(UTF_8))
"data:text/css;base64," + Base64.getEncoder().encodeToString(css.toString().getBytes(UTF_8))
);
scene.getRoot().pseudoClassStateChanged(USER_CUSTOM, true);
}
currentFontSize = fontSize;
public void resetCustomCSS() {
customCSSDeclarations.clear();
customCSSRules.clear();
scene.getRoot().pseudoClassStateChanged(USER_CUSTOM, false);
}
public HighlightJSTheme getMatchingHighlightJSTheme(Theme theme) {