Implement changing font family at runtime

This commit is contained in:
mkpaz 2022-08-25 16:03:55 +04:00
parent cd16046340
commit c2e03d2193
10 changed files with 63 additions and 56 deletions

@ -55,10 +55,7 @@ public class Launcher extends Application {
tm.setTheme(tm.getAvailableThemes().get(0));
if (IS_DEV_MODE) { startCssFX(scene); }
scene.getStylesheets().addAll(
Resources.resolve("assets/fonts/index.css"),
Resources.resolve("assets/styles/index.css")
);
scene.getStylesheets().addAll(Resources.resolve("assets/styles/index.css"));
stage.setScene(scene);
stage.setTitle(System.getProperty("app.name"));

@ -5,13 +5,16 @@ import atlantafx.sampler.page.AbstractPage;
import atlantafx.sampler.page.SampleBlock;
import atlantafx.sampler.theme.ThemeManager;
import javafx.application.Platform;
import javafx.collections.FXCollections;
import javafx.geometry.Pos;
import javafx.scene.Node;
import javafx.scene.control.ComboBox;
import javafx.scene.control.Hyperlink;
import javafx.scene.control.Label;
import javafx.scene.control.Spinner;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.HBox;
import javafx.scene.text.Font;
import javafx.scene.text.Text;
import javafx.scene.text.TextFlow;
@ -23,6 +26,8 @@ import static atlantafx.base.theme.Styles.*;
public class TypographyPage extends AbstractPage {
private static final double CONTROL_WIDTH = 200;
public static final String NAME = "Typography";
@Override
@ -36,18 +41,20 @@ public class TypographyPage extends AbstractPage {
}
private void createView() {
Spinner<Integer> fontSizeSpinner = fontSizeSpinner();
fontSizeSpinner.getStyleClass().add(Spinner.STYLE_CLASS_SPLIT_ARROWS_HORIZONTAL);
fontSizeSpinner.setPrefWidth(200);
var controlsGrid = new GridPane();
controlsGrid.setVgap(10);
controlsGrid.setHgap(20);
var fontSizeBox = new HBox(20, new Label("Font size"), fontSizeSpinner);
fontSizeBox.setAlignment(Pos.CENTER_LEFT);
controlsGrid.add(new Label("Font family"), 0, 0);
controlsGrid.add(fontFamilyChooser(), 1, 0);
controlsGrid.add(new Label("Font size"), 0, 1);
controlsGrid.add(fontSizeSpinner(), 1, 1);
var fontSizeSample = fontSizeSample();
fontSizeSampleContent = (GridPane) fontSizeSample.getContent();
userContent.getChildren().setAll(
fontSizeBox,
controlsGrid,
fontSizeSample.getRoot(),
fontWeightSample().getRoot(),
fontStyleSample().getRoot(),
@ -57,19 +64,42 @@ public class TypographyPage extends AbstractPage {
);
}
private ComboBox<String> fontFamilyChooser() {
final var tm = ThemeManager.getInstance();
ComboBox<String> comboBox = new ComboBox<>();
comboBox.getItems().add(tm.getFontFamily());
comboBox.getItems().addAll(FXCollections.observableArrayList(Font.getFamilies()));
comboBox.setPrefWidth(CONTROL_WIDTH);
comboBox.getSelectionModel().select(tm.getFontFamily());
comboBox.valueProperty().addListener((obs, old, val) -> {
if (val != null) {
tm.setFontFamily(val);
tm.reloadCustomCSS();
updateFontInfo(Duration.ofMillis(1000));
}
});
return comboBox;
}
private Spinner<Integer> fontSizeSpinner() {
var spinner = new Spinner<Integer>(10, 24, 14);
final var tm = ThemeManager.getInstance();
var spinner = new Spinner<Integer>(10, 24, tm.getFontSize());
spinner.getStyleClass().add(Spinner.STYLE_CLASS_SPLIT_ARROWS_HORIZONTAL);
spinner.setPrefWidth(CONTROL_WIDTH);
// Instead of this we should obtain font size from a rendered node.
// But since it's not trivial (thanks to JavaFX doesn't expose relevant API)
// we just keep current font size inside ThemeManager singleton.
// It works fine if ThemeManager default font size value matches
// default theme font size value.
spinner.getValueFactory().setValue(ThemeManager.getInstance().getFontSize());
spinner.getValueFactory().setValue(tm.getFontSize());
spinner.valueProperty().addListener((obs, old, val) -> {
if (val != null && getScene() != null) {
var tm = ThemeManager.getInstance();
if (val != null) {
tm.setFontSize(val);
tm.reloadCustomCSS();
updateFontInfo(Duration.ofMillis(1000));

@ -19,6 +19,7 @@ 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");
private static final String DEFAULT_FONT_FAMILY_NAME = "Application Default";
// KEY | VALUE
// -fx-property | value;
@ -26,9 +27,10 @@ public final class ThemeManager {
// .foo | -fx-property: value;
private final Map<String, String> customCSSRules = new LinkedHashMap<>();
private Theme currentTheme = null;
private int currentFontSize = 14;
private Scene scene;
private Theme currentTheme = null;
private String fontFamily = DEFAULT_FONT_FAMILY_NAME;
private int fontSize = 14;
public Scene getScene() {
return scene;
@ -66,10 +68,7 @@ public final class ThemeManager {
public List<Theme> getAvailableThemes() {
var themes = new ArrayList<Theme>();
var appStylesheets = new URI[] {
URI.create(Resources.resolve("assets/fonts/index.css")),
URI.create(Resources.resolve("assets/styles/index.css"))
};
var appStylesheets = new URI[] { URI.create(Resources.resolve("assets/styles/index.css")) };
if (Launcher.IS_DEV_MODE) {
themes.add(new ExternalTheme("Primer Light", DUMMY_STYLESHEET, merge(
@ -95,14 +94,23 @@ public final class ThemeManager {
return themes;
}
public String getFontFamily() {
return fontFamily;
}
public void setFontFamily(String fontFamily) {
setCustomDeclaration("-fx-font-family", "\"" + fontFamily + "\"");
this.fontFamily = fontFamily;
}
public int getFontSize() {
return currentFontSize;
return fontSize;
}
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;
this.fontSize = fontSize;
}
private void setCustomDeclaration(String property, String value) {
@ -139,8 +147,6 @@ public final class ThemeManager {
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.toString().getBytes(UTF_8))

@ -1,30 +0,0 @@
/*
* Note that each font-family have to be unique. That's because unlike web browsers
* OpenJFX CSS parser doesn't support `font-weight` and `font-style` attributes
* in the `@font-face` at-rule. It just silently ignores them. So, the font variant
* can only be expressed via font name. OpenJFX always uses font transformation,
* even when corresponding font variant provided explicitly.
*
* See, CSS Reference guide:
* https://openjfx.io/javadoc/17/javafx.graphics/javafx/scene/doc-files/cssref.html
* > Although the parser will parse the syntax, all @fontface descriptors
* > are ignored except for the src descriptor. The src descriptor is expected
* > to be a <url>. The format hint is ignored.
*/
@font-face {
font-family: "Inter UI Regular";
src: url('Inter/InterUI-Regular.otf');
}
@font-face {
font-family: "Inter UI Medium";
src: url('Inter/InterUI-Medium.otf');
}
@font-face {
font-family: "Inter UI Bold";
src: url('Inter/InterUI-Bold.otf');
}
@font-face {
font-family: "Inter UI Italic";
src: url('Inter/InterUI-Italic.otf');
}

@ -1,7 +1,12 @@
/** SPDX-License-Identifier: MIT */
@font-face {
font-family: "Application Default";
src: url('../fonts/Inter-Regular.otf');
}
.root {
-fx-font-family: "Inter";
-fx-font-family: "Application Default";
}
.root:showcase-mode #sidebar,
.root:showcase-mode .page > .header {

@ -72,7 +72,6 @@
// default props inherited by all nodes
-fx-background-color: -color-bg-default;
-fx-font-family: "Inter";
-fx-font-size: cfg.$font-default;
// these are needed for Popup