Implement changing accent color
This commit is contained in:
parent
8a7204d93c
commit
21858a3ace
@ -0,0 +1,23 @@
|
||||
package atlantafx.sampler.event;
|
||||
|
||||
public class ThemeEvent extends Event {
|
||||
|
||||
private final EventType eventType;
|
||||
|
||||
public ThemeEvent(EventType eventType) {
|
||||
this.eventType = eventType;
|
||||
}
|
||||
|
||||
public EventType getEventType() {
|
||||
return eventType;
|
||||
}
|
||||
|
||||
public enum EventType {
|
||||
// theme can change both, base font size and colors
|
||||
THEME_CHANGE,
|
||||
// font size or family only change
|
||||
FONT_CHANGE,
|
||||
// colors only change
|
||||
COLOR_CHANGE
|
||||
}
|
||||
}
|
@ -3,6 +3,7 @@ package atlantafx.sampler.layout;
|
||||
|
||||
import atlantafx.sampler.page.Page;
|
||||
import atlantafx.sampler.page.components.OverviewPage;
|
||||
import atlantafx.sampler.theme.ThemeManager;
|
||||
import javafx.animation.FadeTransition;
|
||||
import javafx.application.Platform;
|
||||
import javafx.scene.layout.BorderPane;
|
||||
|
@ -149,6 +149,7 @@ public abstract class AbstractPage extends BorderPane implements Page {
|
||||
quickConfigPopover.setHeaderAlwaysVisible(false);
|
||||
quickConfigPopover.setDetachable(false);
|
||||
quickConfigPopover.setArrowLocation(TOP_CENTER);
|
||||
quickConfigPopover.setOnShowing(e -> content.update());
|
||||
}
|
||||
|
||||
quickConfigPopover.show(quickConfigBtn);
|
||||
@ -169,7 +170,7 @@ public abstract class AbstractPage extends BorderPane implements Page {
|
||||
|
||||
// set syntax highlight theme according to JavaFX theme
|
||||
ThemeManager tm = ThemeManager.getInstance();
|
||||
codeViewer.setContent(stream, tm.getMatchingHighlightJSTheme(tm.getTheme()));
|
||||
codeViewer.setContent(stream, tm.getMatchingSourceCodeHighlightTheme(tm.getTheme()));
|
||||
|
||||
graphic.setIconCode(ICON_SAMPLE);
|
||||
codeViewerWrapper.toFront();
|
||||
|
@ -2,12 +2,15 @@
|
||||
package atlantafx.sampler.page;
|
||||
|
||||
import atlantafx.base.controls.Spacer;
|
||||
import atlantafx.sampler.page.general.AccentColorSelector;
|
||||
import atlantafx.sampler.theme.ThemeManager;
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.beans.binding.BooleanBinding;
|
||||
import javafx.beans.property.IntegerProperty;
|
||||
import javafx.beans.property.SimpleIntegerProperty;
|
||||
import javafx.css.PseudoClass;
|
||||
import javafx.geometry.HorizontalDirection;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.control.Button;
|
||||
import javafx.scene.control.Label;
|
||||
@ -26,6 +29,8 @@ import java.util.Objects;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import static atlantafx.base.theme.Styles.*;
|
||||
import static atlantafx.sampler.theme.ThemeManager.DEFAULT_ZOOM;
|
||||
import static atlantafx.sampler.theme.ThemeManager.SUPPORTED_ZOOM;
|
||||
import static javafx.geometry.Pos.CENTER_LEFT;
|
||||
import static org.kordamp.ikonli.material2.Material2AL.ARROW_BACK;
|
||||
import static org.kordamp.ikonli.material2.Material2AL.ARROW_FORWARD;
|
||||
@ -43,14 +48,10 @@ public class QuickConfigMenu extends StackPane {
|
||||
private Runnable exitHandler;
|
||||
|
||||
private final Consumer<String> navHandler = s -> {
|
||||
Pane pane = null;
|
||||
Menu menu = null;
|
||||
switch (s) {
|
||||
case MainMenu.ID -> pane = getOrCreateMainMenu();
|
||||
case ThemeSelectionMenu.ID -> {
|
||||
ThemeSelectionMenu menu = getOrCreateThemeSelectionMenu();
|
||||
menu.update();
|
||||
pane = menu;
|
||||
}
|
||||
case MainMenu.ID -> menu = getOrCreateMainMenu();
|
||||
case ThemeSelectionMenu.ID -> menu = getOrCreateThemeSelectionMenu();
|
||||
default -> {
|
||||
if (exitHandler != null) {
|
||||
exitHandler.run();
|
||||
@ -58,7 +59,10 @@ public class QuickConfigMenu extends StackPane {
|
||||
}
|
||||
}
|
||||
}
|
||||
getChildren().setAll(Objects.requireNonNull(pane));
|
||||
|
||||
Objects.requireNonNull(menu);
|
||||
menu.update();
|
||||
getChildren().setAll(menu.getRoot());
|
||||
};
|
||||
|
||||
public void setExitHandler(Runnable exitHandler) {
|
||||
@ -98,16 +102,30 @@ public class QuickConfigMenu extends StackPane {
|
||||
return root;
|
||||
}
|
||||
|
||||
public void update() {
|
||||
getOrCreateMainMenu().update();
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
|
||||
private static class MainMenu extends VBox {
|
||||
private interface Menu {
|
||||
|
||||
void update();
|
||||
|
||||
Pane getRoot();
|
||||
}
|
||||
|
||||
private static class MainMenu extends VBox implements Menu {
|
||||
|
||||
private static final String ID = "MainMenu";
|
||||
private static final List<Integer> FONT_SCALE = List.of(
|
||||
50, 75, 80, 90, 100, 110, 125, 150, 175, 200
|
||||
);
|
||||
|
||||
private final IntegerProperty fontScale = new SimpleIntegerProperty(100);
|
||||
private final IntegerProperty zoom = new SimpleIntegerProperty(DEFAULT_ZOOM);
|
||||
private final BooleanBinding canZoomIn = Bindings.createBooleanBinding(
|
||||
() -> SUPPORTED_ZOOM.indexOf(zoom.get()) < SUPPORTED_ZOOM.size() - 1, zoom
|
||||
);
|
||||
private final BooleanBinding canZoomOut = Bindings.createBooleanBinding(
|
||||
() -> SUPPORTED_ZOOM.indexOf(zoom.get()) >= 1, zoom
|
||||
);
|
||||
|
||||
public MainMenu(Consumer<String> navHandler) {
|
||||
super();
|
||||
@ -117,54 +135,66 @@ public class QuickConfigMenu extends StackPane {
|
||||
var themeSelectionMenu = menu("Theme", HorizontalDirection.RIGHT);
|
||||
themeSelectionMenu.setOnMouseClicked(e -> navHandler.accept(ThemeSelectionMenu.ID));
|
||||
|
||||
var accentSelector = new AccentColorSelector();
|
||||
accentSelector.setAlignment(Pos.CENTER);
|
||||
|
||||
// ~
|
||||
|
||||
var zoomInBtn = new Button("", new FontIcon(Feather.ZOOM_IN));
|
||||
zoomInBtn.getStyleClass().addAll(BUTTON_CIRCLE, BUTTON_ICON, FLAT);
|
||||
zoomInBtn.setOnAction(e -> {
|
||||
int idx = FONT_SCALE.indexOf(fontScale.get());
|
||||
if (idx < FONT_SCALE.size() - 1) { fontScale.set(FONT_SCALE.get(idx + 1)); }
|
||||
if (canZoomIn.get()) {
|
||||
zoom.set(SUPPORTED_ZOOM.get(SUPPORTED_ZOOM.indexOf(zoom.get()) + 1));
|
||||
}
|
||||
});
|
||||
zoomInBtn.disableProperty().bind(Bindings.createBooleanBinding(
|
||||
() -> FONT_SCALE.indexOf(fontScale.get()) >= FONT_SCALE.size() - 1, fontScale)
|
||||
);
|
||||
zoomInBtn.disableProperty().bind(canZoomIn.not());
|
||||
|
||||
var zoomOutBtn = new Button("", new FontIcon(Feather.ZOOM_OUT));
|
||||
zoomOutBtn.getStyleClass().addAll(BUTTON_CIRCLE, BUTTON_ICON, FLAT);
|
||||
zoomOutBtn.setOnAction(e -> {
|
||||
int idx = FONT_SCALE.indexOf(fontScale.get());
|
||||
if (idx >= 1) { fontScale.set(FONT_SCALE.get(idx - 1)); }
|
||||
if (canZoomOut.get()) {
|
||||
zoom.set(SUPPORTED_ZOOM.get(SUPPORTED_ZOOM.indexOf(zoom.get()) - 1));
|
||||
}
|
||||
});
|
||||
zoomOutBtn.disableProperty().bind(Bindings.createBooleanBinding(
|
||||
() -> FONT_SCALE.indexOf(fontScale.get()) <= 0, fontScale)
|
||||
);
|
||||
zoomOutBtn.disableProperty().bind(canZoomOut.not());
|
||||
|
||||
// FIXME: Default zoom value is always 100% which isn't correct because it may have been changed earlier
|
||||
var zoomLabel = new Label();
|
||||
zoomLabel.textProperty().bind(Bindings.createStringBinding(() -> fontScale.get() + "%", fontScale));
|
||||
zoomLabel.textProperty().bind(Bindings.createStringBinding(() -> zoom.get() + "%", zoom));
|
||||
|
||||
var zoomBox = new HBox(zoomOutBtn, new Spacer(), zoomLabel, new Spacer(), zoomInBtn);
|
||||
zoomBox.setAlignment(CENTER_LEFT);
|
||||
zoomBox.getStyleClass().addAll("row");
|
||||
|
||||
final var tm = ThemeManager.getInstance();
|
||||
fontScale.addListener((obs, old, val) -> {
|
||||
if (val != null) {
|
||||
double fontSize = val.intValue() != 100 ?
|
||||
ThemeManager.DEFAULT_FONT_SIZE / 100.0 * val.intValue() :
|
||||
ThemeManager.DEFAULT_FONT_SIZE;
|
||||
tm.setFontSize((int) Math.ceil(fontSize));
|
||||
tm.reloadCustomCSS();
|
||||
zoom.addListener((obs, old, val) -> {
|
||||
if (val != null && tm.getZoom() != val.intValue()) {
|
||||
tm.setZoom(val.intValue());
|
||||
}
|
||||
});
|
||||
|
||||
// ~
|
||||
|
||||
getChildren().setAll(themeSelectionMenu, new Separator(), zoomBox);
|
||||
getChildren().setAll(
|
||||
themeSelectionMenu,
|
||||
new Separator(),
|
||||
accentSelector,
|
||||
new Separator(),
|
||||
zoomBox
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void update() {
|
||||
zoom.set(ThemeManager.getInstance().getZoom());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Pane getRoot() {
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
private static class ThemeSelectionMenu extends VBox {
|
||||
private static class ThemeSelectionMenu extends VBox implements Menu {
|
||||
|
||||
public static final String ID = "ThemeSelectionMenu";
|
||||
|
||||
@ -174,7 +204,7 @@ public class QuickConfigMenu extends StackPane {
|
||||
super();
|
||||
|
||||
Objects.requireNonNull(navHandler);
|
||||
final var tm = ThemeManager.getInstance();
|
||||
var tm = ThemeManager.getInstance();
|
||||
|
||||
var mainMenu = menu("Theme", HorizontalDirection.LEFT);
|
||||
mainMenu.setOnMouseClicked(e -> navHandler.accept(MainMenu.ID));
|
||||
@ -189,7 +219,6 @@ public class QuickConfigMenu extends StackPane {
|
||||
item.setUserData(theme.getName());
|
||||
item.setOnMouseClicked(e -> {
|
||||
tm.setTheme(theme);
|
||||
tm.reloadCustomCSS();
|
||||
navHandler.accept(MainMenu.ID);
|
||||
navHandler.accept(QuickConfigMenu.EXIT_ID);
|
||||
});
|
||||
@ -199,11 +228,17 @@ public class QuickConfigMenu extends StackPane {
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void update() {
|
||||
items.forEach(item -> item.pseudoClassStateChanged(
|
||||
SELECTED,
|
||||
Objects.equals(item.getUserData(), ThemeManager.getInstance().getTheme().getName())
|
||||
));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Pane getRoot() {
|
||||
return this;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,53 @@
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
package atlantafx.sampler.page.general;
|
||||
|
||||
import atlantafx.sampler.theme.AccentColor;
|
||||
import atlantafx.sampler.theme.ThemeManager;
|
||||
import atlantafx.sampler.util.JColorUtils;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.scene.control.Button;
|
||||
import javafx.scene.layout.HBox;
|
||||
import javafx.scene.layout.Region;
|
||||
import org.kordamp.ikonli.javafx.FontIcon;
|
||||
import org.kordamp.ikonli.material2.Material2AL;
|
||||
|
||||
import static atlantafx.base.theme.Styles.BUTTON_ICON;
|
||||
import static atlantafx.base.theme.Styles.FLAT;
|
||||
|
||||
public class AccentColorSelector extends HBox {
|
||||
|
||||
public AccentColorSelector() {
|
||||
super();
|
||||
createView();
|
||||
}
|
||||
|
||||
private void createView() {
|
||||
var resetBtn = new Button("", new FontIcon(Material2AL.CLEAR));
|
||||
resetBtn.getStyleClass().addAll(BUTTON_ICON, FLAT);
|
||||
resetBtn.setOnAction(e -> ThemeManager.getInstance().resetAccentColor());
|
||||
|
||||
setAlignment(Pos.CENTER_LEFT);
|
||||
getChildren().setAll(
|
||||
colorButton(AccentColor.PURPLE),
|
||||
colorButton(AccentColor.PINK),
|
||||
colorButton(AccentColor.CORAL),
|
||||
resetBtn
|
||||
);
|
||||
getStyleClass().add("accent-color-selector");
|
||||
}
|
||||
|
||||
private Button colorButton(AccentColor accentColor) {
|
||||
var icon = new Region();
|
||||
icon.getStyleClass().add("icon");
|
||||
|
||||
var colorMap = accentColor.getColorMap();
|
||||
|
||||
var btn = new Button("", icon);
|
||||
btn.getStyleClass().addAll(BUTTON_ICON, FLAT, "color-button");
|
||||
btn.setStyle("-color-primary:" + JColorUtils.toHexWithAlpha(colorMap.getPrimaryColor()) + ";");
|
||||
btn.setUserData(accentColor);
|
||||
btn.setOnAction(e -> ThemeManager.getInstance().setAccentColor((AccentColor) btn.getUserData()));
|
||||
|
||||
return btn;
|
||||
}
|
||||
}
|
@ -32,6 +32,7 @@ import org.kordamp.ikonli.feather.Feather;
|
||||
import org.kordamp.ikonli.javafx.FontIcon;
|
||||
import org.kordamp.ikonli.material2.Material2AL;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
||||
import static atlantafx.sampler.page.general.ColorPaletteBlock.validateColorName;
|
||||
@ -148,10 +149,10 @@ class ContrastChecker extends GridPane {
|
||||
contrastRatio.addListener((obs, old, val) -> {
|
||||
if (val == null) { return; }
|
||||
float ratio = val.floatValue();
|
||||
updateWsagLabel(aaNormalLabel, ContrastLevel.AA_NORMAL.satisfies(ratio));
|
||||
updateWsagLabel(aaLargeLabel, ContrastLevel.AA_LARGE.satisfies(ratio));
|
||||
updateWsagLabel(aaaNormalLabel, ContrastLevel.AAA_NORMAL.satisfies(ratio));
|
||||
updateWsagLabel(aaaLargeLabel, ContrastLevel.AAA_LARGE.satisfies(ratio));
|
||||
updateContrastLevelLabel(aaNormalLabel, ContrastLevel.AA_NORMAL.satisfies(ratio));
|
||||
updateContrastLevelLabel(aaLargeLabel, ContrastLevel.AA_LARGE.satisfies(ratio));
|
||||
updateContrastLevelLabel(aaaNormalLabel, ContrastLevel.AAA_NORMAL.satisfies(ratio));
|
||||
updateContrastLevelLabel(aaaLargeLabel, ContrastLevel.AAA_LARGE.satisfies(ratio));
|
||||
});
|
||||
|
||||
// ~
|
||||
@ -266,12 +267,10 @@ class ContrastChecker extends GridPane {
|
||||
});
|
||||
|
||||
var applyBtn = new Button("Apply");
|
||||
applyBtn.setOnAction(e -> {
|
||||
var tm = ThemeManager.getInstance();
|
||||
tm.setColor(getBgColorName(), bgColor.getColor());
|
||||
tm.setColor(getFgColorName(), fgColor.getColor());
|
||||
tm.reloadCustomCSS();
|
||||
});
|
||||
applyBtn.setOnAction(e -> ThemeManager.getInstance().setNamedColors(Map.of(
|
||||
getBgColorName(), bgColor.getColor(),
|
||||
getFgColorName(), fgColor.getColor()
|
||||
)));
|
||||
|
||||
var controlsBox = new HBox(20, new Spacer(), flattenBtn, applyBtn);
|
||||
controlsBox.setAlignment(Pos.CENTER_LEFT);
|
||||
@ -321,8 +320,8 @@ class ContrastChecker extends GridPane {
|
||||
|
||||
private void updateStyle() {
|
||||
setStyle(String.format("-color-contrast-checker-bg:%s;-color-contrast-checker-fg:%s;",
|
||||
JColorUtils.toHexWithAlpha(bgColor.getColor()),
|
||||
JColorUtils.toHexWithAlpha(getSafeFgColor())
|
||||
JColorUtils.toHexWithAlpha(bgColor.getColor()),
|
||||
JColorUtils.toHexWithAlpha(getSafeFgColor())
|
||||
));
|
||||
}
|
||||
|
||||
@ -342,7 +341,7 @@ class ContrastChecker extends GridPane {
|
||||
fgAlphaSlider.setValue(color.getOpacity());
|
||||
}
|
||||
|
||||
private void updateWsagLabel(Label label, boolean success) {
|
||||
private void updateContrastLevelLabel(Label label, boolean success) {
|
||||
FontIcon icon = Objects.requireNonNull((FontIcon) label.getGraphic());
|
||||
if (success) {
|
||||
label.setText(STATE_PASS);
|
||||
|
@ -2,8 +2,9 @@
|
||||
package atlantafx.sampler.page.general;
|
||||
|
||||
import atlantafx.base.theme.Theme;
|
||||
import atlantafx.sampler.event.DefaultEventBus;
|
||||
import atlantafx.sampler.event.ThemeEvent;
|
||||
import atlantafx.sampler.page.AbstractPage;
|
||||
import atlantafx.sampler.theme.ThemeEvent.EventType;
|
||||
import atlantafx.sampler.theme.ThemeManager;
|
||||
import atlantafx.sampler.util.NodeUtils;
|
||||
import javafx.geometry.HPos;
|
||||
@ -19,6 +20,8 @@ import java.net.URI;
|
||||
import java.util.Objects;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import static atlantafx.sampler.event.ThemeEvent.EventType.COLOR_CHANGE;
|
||||
import static atlantafx.sampler.event.ThemeEvent.EventType.THEME_CHANGE;
|
||||
import static atlantafx.sampler.util.Controls.hyperlink;
|
||||
|
||||
public class ThemePage extends AbstractPage {
|
||||
@ -28,9 +31,9 @@ public class ThemePage extends AbstractPage {
|
||||
private final Consumer<ColorPaletteBlock> colorBlockActionHandler = colorBlock -> {
|
||||
ContrastCheckerDialog dialog = getOrCreateContrastCheckerDialog();
|
||||
dialog.getContent().setValues(colorBlock.getFgColorName(),
|
||||
colorBlock.getFgColor(),
|
||||
colorBlock.getBgColorName(),
|
||||
colorBlock.getBgColor()
|
||||
colorBlock.getFgColor(),
|
||||
colorBlock.getBgColorName(),
|
||||
colorBlock.getBgColor()
|
||||
);
|
||||
overlay.setContent(dialog, HPos.CENTER);
|
||||
overlay.toFront();
|
||||
@ -47,9 +50,8 @@ public class ThemePage extends AbstractPage {
|
||||
public ThemePage() {
|
||||
super();
|
||||
createView();
|
||||
ThemeManager.getInstance().addEventListener(e -> {
|
||||
if (e.eventType() == EventType.THEME_CHANGE || e.eventType() == EventType.CUSTOM_CSS_CHANGE) {
|
||||
// only works for managed nodes
|
||||
DefaultEventBus.getInstance().subscribe(ThemeEvent.class, e -> {
|
||||
if (e.getEventType() == THEME_CHANGE || e.getEventType() == COLOR_CHANGE) {
|
||||
colorPalette.updateColorInfo(Duration.seconds(1));
|
||||
colorScale.updateColorInfo(Duration.seconds(1));
|
||||
}
|
||||
@ -67,7 +69,7 @@ public class ThemePage extends AbstractPage {
|
||||
var noteText = new TextFlow(
|
||||
new Text("AtlantaFX follows "),
|
||||
hyperlink("Github Primer interface guidelines",
|
||||
URI.create("https://primer.style/design/foundations/color")
|
||||
URI.create("https://primer.style/design/foundations/color")
|
||||
),
|
||||
new Text(" and color system.")
|
||||
);
|
||||
@ -89,6 +91,8 @@ public class ThemePage extends AbstractPage {
|
||||
ChoiceBox<Theme> themeSelector = themeSelector();
|
||||
themeSelector.setPrefWidth(200);
|
||||
|
||||
var accentSelector = new AccentColorSelector();
|
||||
|
||||
// ~
|
||||
|
||||
var grid = new GridPane();
|
||||
@ -97,6 +101,8 @@ public class ThemePage extends AbstractPage {
|
||||
|
||||
grid.add(new Label("Color theme"), 0, 0);
|
||||
grid.add(themeSelector, 1, 0);
|
||||
grid.add(new Label("Accent color"), 0, 1);
|
||||
grid.add(accentSelector, 1, 1);
|
||||
|
||||
return grid;
|
||||
}
|
||||
@ -109,7 +115,6 @@ public class ThemePage extends AbstractPage {
|
||||
if (val != null && getScene() != null) {
|
||||
var tm = ThemeManager.getInstance();
|
||||
tm.setTheme(val);
|
||||
tm.reloadCustomCSS();
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -1,9 +1,10 @@
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
package atlantafx.sampler.page.general;
|
||||
|
||||
import atlantafx.sampler.event.DefaultEventBus;
|
||||
import atlantafx.sampler.event.ThemeEvent;
|
||||
import atlantafx.sampler.page.AbstractPage;
|
||||
import atlantafx.sampler.page.SampleBlock;
|
||||
import atlantafx.sampler.theme.ThemeEvent.EventType;
|
||||
import atlantafx.sampler.theme.ThemeManager;
|
||||
import atlantafx.sampler.util.NodeUtils;
|
||||
import javafx.animation.KeyFrame;
|
||||
@ -25,6 +26,9 @@ import javafx.scene.text.TextFlow;
|
||||
import javafx.util.Duration;
|
||||
|
||||
import static atlantafx.base.theme.Styles.*;
|
||||
import static atlantafx.sampler.event.ThemeEvent.EventType.FONT_CHANGE;
|
||||
import static atlantafx.sampler.event.ThemeEvent.EventType.THEME_CHANGE;
|
||||
import static atlantafx.sampler.theme.ThemeManager.SUPPORTED_FONT_SIZE;
|
||||
|
||||
public class TypographyPage extends AbstractPage {
|
||||
|
||||
@ -41,9 +45,8 @@ public class TypographyPage extends AbstractPage {
|
||||
public TypographyPage() {
|
||||
super();
|
||||
createView();
|
||||
ThemeManager.getInstance().addEventListener(e -> {
|
||||
if (e.eventType() == EventType.FONT_FAMILY_CHANGE || e.eventType() == EventType.FONT_SIZE_CHANGE) {
|
||||
// only works for managed nodes
|
||||
DefaultEventBus.getInstance().subscribe(ThemeEvent.class, e -> {
|
||||
if (e.getEventType() == THEME_CHANGE || e.getEventType() == FONT_CHANGE) {
|
||||
updateFontInfo(Duration.seconds(1));
|
||||
}
|
||||
});
|
||||
@ -89,7 +92,6 @@ public class TypographyPage extends AbstractPage {
|
||||
comboBox.valueProperty().addListener((obs, old, val) -> {
|
||||
if (val != null) {
|
||||
tm.setFontFamily(DEFAULT_FONT_ID.equals(val) ? ThemeManager.DEFAULT_FONT_FAMILY_NAME : val);
|
||||
tm.reloadCustomCSS();
|
||||
}
|
||||
});
|
||||
|
||||
@ -99,7 +101,11 @@ public class TypographyPage extends AbstractPage {
|
||||
private Spinner<Integer> fontSizeSpinner() {
|
||||
final var tm = ThemeManager.getInstance();
|
||||
|
||||
var spinner = new Spinner<Integer>(10, 24, tm.getFontSize());
|
||||
var spinner = new Spinner<Integer>(
|
||||
SUPPORTED_FONT_SIZE.get(0),
|
||||
SUPPORTED_FONT_SIZE.get(SUPPORTED_FONT_SIZE.size() - 1),
|
||||
tm.getFontSize()
|
||||
);
|
||||
spinner.getStyleClass().add(Spinner.STYLE_CLASS_SPLIT_ARROWS_HORIZONTAL);
|
||||
spinner.setPrefWidth(CONTROL_WIDTH);
|
||||
|
||||
@ -113,7 +119,6 @@ public class TypographyPage extends AbstractPage {
|
||||
spinner.valueProperty().addListener((obs, old, val) -> {
|
||||
if (val != null) {
|
||||
tm.setFontSize(val);
|
||||
tm.reloadCustomCSS();
|
||||
updateFontInfo(Duration.seconds(1));
|
||||
}
|
||||
});
|
||||
|
@ -0,0 +1,18 @@
|
||||
package atlantafx.sampler.theme;
|
||||
|
||||
public enum AccentColor {
|
||||
|
||||
PURPLE(ColorMap.primerPurple()),
|
||||
PINK(ColorMap.primerPink()),
|
||||
CORAL(ColorMap.primerCoral());
|
||||
|
||||
private final ColorMap colorMap;
|
||||
|
||||
AccentColor(ColorMap colorMap) {
|
||||
this.colorMap = colorMap;
|
||||
}
|
||||
|
||||
public ColorMap getColorMap() {
|
||||
return colorMap;
|
||||
}
|
||||
}
|
146
sampler/src/main/java/atlantafx/sampler/theme/ColorMap.java
Normal file
146
sampler/src/main/java/atlantafx/sampler/theme/ColorMap.java
Normal file
@ -0,0 +1,146 @@
|
||||
package atlantafx.sampler.theme;
|
||||
|
||||
import javafx.scene.paint.Color;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
||||
import static atlantafx.sampler.util.JColorUtils.opaqueColor;
|
||||
|
||||
// Theoretically, one should use different accent shades for each color theme
|
||||
// and for both light and dark mode. But since creating color palettes is
|
||||
// pretty time-consuming, dynamic color calculation based on opacity should
|
||||
// suit demo purposes.
|
||||
public class ColorMap {
|
||||
|
||||
private static final String MUTED_COLOR_NAME = "-color-accent-muted";
|
||||
private static final String SUBTLE_COLOR_NAME = "-color-accent-subtle";
|
||||
|
||||
private final Map<String, Color> allColors = new HashMap<>();
|
||||
private final Map<String, Color> dynamicColors = new HashMap<>();
|
||||
|
||||
private final Color primaryColor;
|
||||
|
||||
private ColorMap(Color primaryColor) {
|
||||
this.primaryColor = primaryColor;
|
||||
}
|
||||
|
||||
public Color getPrimaryColor() {
|
||||
return primaryColor;
|
||||
}
|
||||
|
||||
public Map<String, Color> getAll() {
|
||||
return new HashMap<>(allColors);
|
||||
}
|
||||
|
||||
private void setColor(String colorName, Color colorValue) {
|
||||
allColors.put(Objects.requireNonNull(colorName), colorValue);
|
||||
}
|
||||
|
||||
private void setDynamicColor(String colorName, Color colorValue) {
|
||||
dynamicColors.put(Objects.requireNonNull(colorName), colorValue);
|
||||
}
|
||||
|
||||
void update(Color background) {
|
||||
allColors.put(MUTED_COLOR_NAME, opaqueColor(background, dynamicColors.get(MUTED_COLOR_NAME), 0.5));
|
||||
allColors.put(SUBTLE_COLOR_NAME, opaqueColor(background, dynamicColors.get(SUBTLE_COLOR_NAME), 0.4));
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
|
||||
public static ColorMap primerPurple() {
|
||||
var map = new ColorMap(Color.web("#8250df"));
|
||||
|
||||
map.setColor("-color-blue-0", Color.web("#fbefff"));
|
||||
map.setColor("-color-blue-1", Color.web("#ecd8ff"));
|
||||
map.setColor("-color-blue-2", Color.web("#d8b9ff"));
|
||||
map.setColor("-color-blue-3", Color.web("#c297ff"));
|
||||
map.setColor("-color-blue-4", Color.web("#a475f9"));
|
||||
map.setColor("-color-blue-5", Color.web("#8250df"));
|
||||
map.setColor("-color-blue-6", Color.web("#6639ba"));
|
||||
map.setColor("-color-blue-7", Color.web("#512a97"));
|
||||
map.setColor("-color-blue-8", Color.web("#3e1f79"));
|
||||
map.setColor("-color-blue-9", Color.web("#2e1461"));
|
||||
map.setColor("-color-accent-fg", Color.web("#8250df"));
|
||||
map.setColor("-color-accent-emphasis", Color.web("#8250df"));
|
||||
map.setColor(MUTED_COLOR_NAME, Color.web("#d8b9ff"));
|
||||
map.setColor(SUBTLE_COLOR_NAME, Color.web("#fbefff"));
|
||||
|
||||
map.setDynamicColor(MUTED_COLOR_NAME, Color.web("#d8b9ff"));
|
||||
map.setDynamicColor(SUBTLE_COLOR_NAME, Color.web("#fbefff"));
|
||||
|
||||
return map;
|
||||
}
|
||||
|
||||
public static ColorMap primerPink() {
|
||||
var map = new ColorMap(Color.web("#bf3989"));
|
||||
|
||||
map.setColor("-color-blue-0", Color.web("#ffeff7"));
|
||||
map.setColor("-color-blue-1", Color.web("#ffd3eb"));
|
||||
map.setColor("-color-blue-2", Color.web("#ffadda"));
|
||||
map.setColor("-color-blue-3", Color.web("#ff80c8"));
|
||||
map.setColor("-color-blue-4", Color.web("#e85aad"));
|
||||
map.setColor("-color-blue-5", Color.web("#bf3989"));
|
||||
map.setColor("-color-blue-6", Color.web("#99286e"));
|
||||
map.setColor("-color-blue-7", Color.web("#772057"));
|
||||
map.setColor("-color-blue-8", Color.web("#611347"));
|
||||
map.setColor("-color-blue-9", Color.web("#4d0336"));
|
||||
map.setColor("-color-accent-fg", Color.web("#bf3989"));
|
||||
map.setColor("-color-accent-emphasis", Color.web("#bf3989"));
|
||||
map.setColor(MUTED_COLOR_NAME, Color.web("#ffadda"));
|
||||
map.setColor(SUBTLE_COLOR_NAME, Color.web("#ffeff7"));
|
||||
|
||||
map.setDynamicColor(MUTED_COLOR_NAME, Color.web("#ffadda"));
|
||||
map.setDynamicColor(SUBTLE_COLOR_NAME, Color.web("#ffeff7"));
|
||||
|
||||
return map;
|
||||
}
|
||||
|
||||
public static ColorMap primerCoral() {
|
||||
var map = new ColorMap(Color.web("#c4432b"));
|
||||
|
||||
map.setColor("-color-blue-0", Color.web("#fff0eb"));
|
||||
map.setColor("-color-blue-1", Color.web("#ffd6cc"));
|
||||
map.setColor("-color-blue-2", Color.web("#ffb4a1"));
|
||||
map.setColor("-color-blue-3", Color.web("#fd8c73"));
|
||||
map.setColor("-color-blue-4", Color.web("#ec6547"));
|
||||
map.setColor("-color-blue-5", Color.web("#c4432b"));
|
||||
map.setColor("-color-blue-6", Color.web("#9e2f1c"));
|
||||
map.setColor("-color-blue-7", Color.web("#801f0f"));
|
||||
map.setColor("-color-blue-8", Color.web("#691105"));
|
||||
map.setColor("-color-blue-9", Color.web("#510901"));
|
||||
map.setColor("-color-accent-fg", Color.web("#c4432b"));
|
||||
map.setColor("-color-accent-emphasis", Color.web("#c4432b"));
|
||||
map.setColor(MUTED_COLOR_NAME, Color.web("#ffb4a1"));
|
||||
map.setColor(SUBTLE_COLOR_NAME, Color.web("#fff0eb"));
|
||||
|
||||
map.setDynamicColor(MUTED_COLOR_NAME, Color.web("#ffb4a1"));
|
||||
map.setDynamicColor(SUBTLE_COLOR_NAME, Color.web("#fff0eb"));
|
||||
|
||||
return map;
|
||||
}
|
||||
|
||||
// empty map contains only color names and used to reset
|
||||
// accent color scale to its initial state
|
||||
static ColorMap empty() {
|
||||
var map = new ColorMap(Color.web("#bf3989"));
|
||||
|
||||
map.setColor("-color-blue-0", null);
|
||||
map.setColor("-color-blue-1", null);
|
||||
map.setColor("-color-blue-2", null);
|
||||
map.setColor("-color-blue-3", null);
|
||||
map.setColor("-color-blue-4", null);
|
||||
map.setColor("-color-blue-5", null);
|
||||
map.setColor("-color-blue-6", null);
|
||||
map.setColor("-color-blue-7", null);
|
||||
map.setColor("-color-blue-8", null);
|
||||
map.setColor("-color-blue-9", null);
|
||||
map.setColor("-color-accent-fg", null);
|
||||
map.setColor("-color-accent-emphasis", null);
|
||||
map.setColor(MUTED_COLOR_NAME, null);
|
||||
map.setColor(SUBTLE_COLOR_NAME, null);
|
||||
|
||||
return map;
|
||||
}
|
||||
}
|
@ -1,11 +0,0 @@
|
||||
package atlantafx.sampler.theme;
|
||||
|
||||
public record ThemeEvent(EventType eventType) {
|
||||
|
||||
public enum EventType {
|
||||
THEME_CHANGE,
|
||||
FONT_FAMILY_CHANGE,
|
||||
FONT_SIZE_CHANGE,
|
||||
CUSTOM_CSS_CHANGE
|
||||
}
|
||||
}
|
@ -4,7 +4,10 @@ package atlantafx.sampler.theme;
|
||||
import atlantafx.base.theme.*;
|
||||
import atlantafx.sampler.Launcher;
|
||||
import atlantafx.sampler.Resources;
|
||||
import atlantafx.sampler.theme.ThemeEvent.EventType;
|
||||
import atlantafx.sampler.event.DefaultEventBus;
|
||||
import atlantafx.sampler.event.EventBus;
|
||||
import atlantafx.sampler.event.ThemeEvent;
|
||||
import atlantafx.sampler.event.ThemeEvent.EventType;
|
||||
import atlantafx.sampler.util.JColor;
|
||||
import javafx.application.Application;
|
||||
import javafx.css.PseudoClass;
|
||||
@ -13,46 +16,67 @@ import javafx.scene.paint.Color;
|
||||
|
||||
import java.net.URI;
|
||||
import java.util.*;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.IntStream;
|
||||
|
||||
import static atlantafx.sampler.Resources.getResource;
|
||||
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 String DUMMY_STYLESHEET = getResource("assets/styles/empty.css").toString();
|
||||
private static final PseudoClass USER_CUSTOM = PseudoClass.getPseudoClass("user-custom");
|
||||
private static final EventBus EVENT_BUS = DefaultEventBus.getInstance();
|
||||
|
||||
public static final String DEFAULT_FONT_FAMILY_NAME = "Inter";
|
||||
public static final int DEFAULT_FONT_SIZE = 14;
|
||||
public static final int DEFAULT_ZOOM = 100;
|
||||
public static final AccentColor DEFAULT_ACCENT_COLOR = null;
|
||||
public static final List<Integer> SUPPORTED_FONT_SIZE = IntStream.range(8, 29).boxed().collect(Collectors.toList());
|
||||
public static final List<Integer> SUPPORTED_ZOOM = List.of(50, 75, 80, 90, 100, 110, 125, 150, 175, 200);
|
||||
|
||||
// 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 final Map<String, String> customCSSDeclarations = new LinkedHashMap<>(); // -fx-property | value;
|
||||
private final Map<String, String> customCSSRules = new LinkedHashMap<>(); // .foo | -fx-property: value;
|
||||
|
||||
private Scene scene;
|
||||
private Theme currentTheme = null;
|
||||
private String fontFamily = DEFAULT_FONT_FAMILY_NAME;
|
||||
private int fontSize = DEFAULT_FONT_SIZE;
|
||||
private final List<Consumer<ThemeEvent>> eventListeners = new ArrayList<>();
|
||||
private int zoom = DEFAULT_ZOOM;
|
||||
private AccentColor accentColor = DEFAULT_ACCENT_COLOR;
|
||||
|
||||
public Scene getScene() {
|
||||
return scene;
|
||||
}
|
||||
|
||||
public void addEventListener(Consumer<ThemeEvent> listener) {
|
||||
eventListeners.add(Objects.requireNonNull(listener));
|
||||
}
|
||||
|
||||
// MUST BE SET ON STARTUP
|
||||
// (this is supposed to be a constructor arg, but since app don't use DI..., sorry)
|
||||
public void setScene(Scene scene) {
|
||||
this.scene = scene;
|
||||
this.scene = Objects.requireNonNull(scene);
|
||||
}
|
||||
|
||||
public Theme getTheme() {
|
||||
return currentTheme;
|
||||
}
|
||||
|
||||
public List<Theme> getAvailableThemes() {
|
||||
var themes = new ArrayList<Theme>();
|
||||
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(getResource("theme-test/primer-light.css"), appStylesheets), false));
|
||||
themes.add(new ExternalTheme("Primer Dark", DUMMY_STYLESHEET, merge(getResource("theme-test/primer-dark.css"), appStylesheets), true));
|
||||
themes.add(new ExternalTheme("Nord Light", DUMMY_STYLESHEET, merge(getResource("theme-test/nord-light.css"), appStylesheets), false));
|
||||
themes.add(new ExternalTheme("Nord Dark", DUMMY_STYLESHEET, merge(getResource("theme-test/nord-dark.css"), appStylesheets), true));
|
||||
} else {
|
||||
themes.add(new PrimerLight(appStylesheets));
|
||||
themes.add(new PrimerDark(appStylesheets));
|
||||
themes.add(new NordLight(appStylesheets));
|
||||
themes.add(new NordDark(appStylesheets));
|
||||
}
|
||||
return themes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets user agent stylesheet and then adds {@link Theme} styles to the {@link Scene}
|
||||
* stylesheets. This is necessary when we want to reload style changes at runtime, because
|
||||
@ -62,7 +86,6 @@ public final class ThemeManager {
|
||||
* reason they won't be ignored when exactly the same stylesheet is set via {@link Scene#getStylesheets()}.
|
||||
*/
|
||||
public void setTheme(Theme theme) {
|
||||
Objects.requireNonNull(scene);
|
||||
Objects.requireNonNull(theme);
|
||||
|
||||
Application.setUserAgentStylesheet(Objects.requireNonNull(theme.getUserAgentStylesheet()));
|
||||
@ -72,38 +95,10 @@ public final class ThemeManager {
|
||||
}
|
||||
|
||||
theme.getStylesheets().forEach(uri -> scene.getStylesheets().add(uri.toString()));
|
||||
resetCustomCSS();
|
||||
|
||||
currentTheme = theme;
|
||||
notifyEventListeners(EventType.THEME_CHANGE);
|
||||
}
|
||||
|
||||
public List<Theme> getAvailableThemes() {
|
||||
var themes = new ArrayList<Theme>();
|
||||
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(
|
||||
Resources.getResource("theme-test/primer-light.css"),
|
||||
appStylesheets
|
||||
), false));
|
||||
themes.add(new ExternalTheme("Primer Dark", DUMMY_STYLESHEET, merge(
|
||||
Resources.getResource("theme-test/primer-dark.css"),
|
||||
appStylesheets
|
||||
), true));
|
||||
themes.add(new ExternalTheme("Nord Light", DUMMY_STYLESHEET, merge(
|
||||
Resources.getResource("theme-test/nord-light.css"),
|
||||
appStylesheets
|
||||
), false));
|
||||
themes.add(new ExternalTheme("Nord Dark", DUMMY_STYLESHEET, merge(
|
||||
Resources.getResource("theme-test/nord-dark.css"),
|
||||
appStylesheets
|
||||
), true));
|
||||
} else {
|
||||
themes.add(new PrimerLight(appStylesheets));
|
||||
themes.add(new PrimerDark(appStylesheets));
|
||||
themes.add(new NordLight(appStylesheets));
|
||||
themes.add(new NordDark(appStylesheets));
|
||||
}
|
||||
return themes;
|
||||
EVENT_BUS.publish(new ThemeEvent(EventType.THEME_CHANGE));
|
||||
}
|
||||
|
||||
public String getFontFamily() {
|
||||
@ -113,8 +108,11 @@ public final class ThemeManager {
|
||||
public void setFontFamily(String fontFamily) {
|
||||
Objects.requireNonNull(fontFamily);
|
||||
setCustomDeclaration("-fx-font-family", "\"" + fontFamily + "\"");
|
||||
|
||||
this.fontFamily = fontFamily;
|
||||
notifyEventListeners(EventType.FONT_FAMILY_CHANGE);
|
||||
|
||||
reloadCustomCSS();
|
||||
EVENT_BUS.publish(new ThemeEvent(EventType.FONT_CHANGE));
|
||||
}
|
||||
|
||||
public boolean isDefaultFontFamily() {
|
||||
@ -125,26 +123,109 @@ public final class ThemeManager {
|
||||
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));
|
||||
this.fontSize = fontSize;
|
||||
notifyEventListeners(EventType.FONT_SIZE_CHANGE);
|
||||
public void setFontSize(int size) {
|
||||
if (!SUPPORTED_FONT_SIZE.contains(size)) {
|
||||
throw new IllegalArgumentException(String.format("Font size must in the range %d-%dpx. Actual value is %d.",
|
||||
SUPPORTED_FONT_SIZE.get(0),
|
||||
SUPPORTED_FONT_SIZE.get(SUPPORTED_FONT_SIZE.size() - 1),
|
||||
size
|
||||
));
|
||||
}
|
||||
|
||||
setCustomDeclaration("-fx-font-size", size + "px");
|
||||
setCustomRule(".ikonli-font-icon", String.format("-fx-icon-size: %dpx;", size + 2));
|
||||
|
||||
this.fontSize = size;
|
||||
|
||||
var rawZoom = (int) Math.ceil((size * 1.0 / DEFAULT_FONT_SIZE) * 100);
|
||||
this.zoom = SUPPORTED_ZOOM.stream()
|
||||
.min(Comparator.comparingInt(i -> Math.abs(i - rawZoom)))
|
||||
.orElseThrow(NoSuchElementException::new);
|
||||
|
||||
reloadCustomCSS();
|
||||
EVENT_BUS.publish(new ThemeEvent(EventType.FONT_CHANGE));
|
||||
}
|
||||
|
||||
public void setColor(String colorName, Color color) {
|
||||
Objects.requireNonNull(colorName);
|
||||
public boolean isDefaultSize() {
|
||||
return DEFAULT_FONT_SIZE == fontSize;
|
||||
}
|
||||
|
||||
public int getZoom() {
|
||||
return zoom;
|
||||
}
|
||||
|
||||
public void setZoom(int zoom) {
|
||||
if (!SUPPORTED_ZOOM.contains(zoom)) {
|
||||
throw new IllegalArgumentException(String.format("Zoom value must one of %s. Actual value is %d.",
|
||||
SUPPORTED_ZOOM,
|
||||
zoom
|
||||
));
|
||||
}
|
||||
|
||||
setFontSize((int) Math.ceil(zoom != 100 ? (DEFAULT_FONT_SIZE * zoom) / 100.0f : DEFAULT_FONT_SIZE));
|
||||
this.zoom = zoom;
|
||||
}
|
||||
|
||||
public AccentColor getAccentColor() {
|
||||
return accentColor;
|
||||
}
|
||||
|
||||
public void setAccentColor(AccentColor color) {
|
||||
Objects.requireNonNull(color);
|
||||
setCustomDeclaration(colorName, JColor.color(
|
||||
(float) color.getRed(), (float) color.getGreen(), (float) color.getBlue(), (float) color.getOpacity()).getColorHexWithAlpha()
|
||||
);
|
||||
|
||||
var colorMap = color.getColorMap();
|
||||
|
||||
// adapt color map to the current theme
|
||||
if (!getTheme().isDarkMode()) {
|
||||
colorMap.update(Color.WHITE);
|
||||
} else {
|
||||
colorMap.update(Color.BLACK);
|
||||
}
|
||||
|
||||
applyColorMap(colorMap);
|
||||
|
||||
this.accentColor = color;
|
||||
|
||||
reloadCustomCSS();
|
||||
EVENT_BUS.publish(new ThemeEvent(EventType.COLOR_CHANGE));
|
||||
}
|
||||
|
||||
public void resetColor(String colorName) {
|
||||
Objects.requireNonNull(colorName);
|
||||
removeCustomDeclaration(colorName);
|
||||
public void resetAccentColor() {
|
||||
applyColorMap(ColorMap.empty());
|
||||
this.accentColor = null;
|
||||
|
||||
reloadCustomCSS();
|
||||
EVENT_BUS.publish(new ThemeEvent(EventType.COLOR_CHANGE));
|
||||
}
|
||||
|
||||
public void setNamedColors(Map<String, Color> colors) {
|
||||
Objects.requireNonNull(colors).forEach(this::setOrRemoveColor);
|
||||
reloadCustomCSS();
|
||||
EVENT_BUS.publish(new ThemeEvent(EventType.COLOR_CHANGE));
|
||||
}
|
||||
|
||||
public void unsetNamedColors(String... colors) {
|
||||
for (String c : colors) {
|
||||
setOrRemoveColor(c, null);
|
||||
}
|
||||
reloadCustomCSS();
|
||||
EVENT_BUS.publish(new ThemeEvent(EventType.COLOR_CHANGE));
|
||||
}
|
||||
|
||||
public void resetAllChanges() {
|
||||
resetCustomCSS();
|
||||
EVENT_BUS.publish(new ThemeEvent(EventType.THEME_CHANGE));
|
||||
}
|
||||
|
||||
public HighlightJSTheme getMatchingSourceCodeHighlightTheme(Theme theme) {
|
||||
Objects.requireNonNull(theme);
|
||||
if ("Nord Light".equals(theme.getName())) { return HighlightJSTheme.nordLight(); }
|
||||
if ("Nord Dark".equals(theme.getName())) { return HighlightJSTheme.nordDark(); }
|
||||
return theme.isDarkMode() ? HighlightJSTheme.githubDark() : HighlightJSTheme.githubLight();
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
|
||||
private void setCustomDeclaration(String property, String value) {
|
||||
customCSSDeclarations.put(property, value);
|
||||
}
|
||||
@ -161,7 +242,25 @@ public final class ThemeManager {
|
||||
customCSSRules.remove(selector);
|
||||
}
|
||||
|
||||
public void reloadCustomCSS() {
|
||||
private void setOrRemoveColor(String colorName, Color color) {
|
||||
Objects.requireNonNull(colorName);
|
||||
if (color != null) {
|
||||
setCustomDeclaration(colorName, JColor.color(
|
||||
(float) color.getRed(),
|
||||
(float) color.getGreen(),
|
||||
(float) color.getBlue(),
|
||||
(float) color.getOpacity()).getColorHexWithAlpha()
|
||||
);
|
||||
} else {
|
||||
removeCustomDeclaration(colorName);
|
||||
}
|
||||
}
|
||||
|
||||
private void applyColorMap(ColorMap colorMap) {
|
||||
colorMap.getAll().forEach(this::setOrRemoveColor);
|
||||
}
|
||||
|
||||
private void reloadCustomCSS() {
|
||||
Objects.requireNonNull(scene);
|
||||
StringBuilder css = new StringBuilder();
|
||||
|
||||
@ -192,26 +291,12 @@ public final class ThemeManager {
|
||||
"data:text/css;base64," + Base64.getEncoder().encodeToString(css.toString().getBytes(UTF_8))
|
||||
);
|
||||
scene.getRoot().pseudoClassStateChanged(USER_CUSTOM, true);
|
||||
notifyEventListeners(EventType.CUSTOM_CSS_CHANGE);
|
||||
}
|
||||
|
||||
public void resetCustomCSS() {
|
||||
customCSSDeclarations.clear();
|
||||
customCSSRules.clear();
|
||||
scene.getRoot().pseudoClassStateChanged(USER_CUSTOM, false);
|
||||
notifyEventListeners(EventType.CUSTOM_CSS_CHANGE);
|
||||
}
|
||||
|
||||
public HighlightJSTheme getMatchingHighlightJSTheme(Theme theme) {
|
||||
Objects.requireNonNull(theme);
|
||||
if ("Nord Light".equals(theme.getName())) { return HighlightJSTheme.nordLight(); }
|
||||
if ("Nord Dark".equals(theme.getName())) { return HighlightJSTheme.nordDark(); }
|
||||
return theme.isDarkMode() ? HighlightJSTheme.githubDark() : HighlightJSTheme.githubLight();
|
||||
}
|
||||
|
||||
public void notifyEventListeners(EventType eventType) {
|
||||
var e = new ThemeEvent(eventType);
|
||||
eventListeners.forEach(l -> l.accept(e));
|
||||
}
|
||||
|
||||
@SafeVarargs
|
||||
|
@ -26,7 +26,6 @@ package atlantafx.sampler.util;
|
||||
|
||||
import javafx.scene.paint.Color;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
@ -803,13 +802,13 @@ public class JColorUtils {
|
||||
* <br/>
|
||||
* <a href="https://filosophy.org/code/online-tool-to-lighten-color-without-alpha-channel/">Source</a>.
|
||||
*/
|
||||
public static double[] flattenColor(Color bgColor, Color fgColor) {
|
||||
public static double[] flattenColor(Color bg, Color fgColor) {
|
||||
var opacity = fgColor.getOpacity();
|
||||
return opacity < 1 ?
|
||||
new double[] {
|
||||
opacity * fgColor.getRed() + (1 - opacity) * bgColor.getRed(),
|
||||
opacity * fgColor.getGreen() + (1 - opacity) * bgColor.getGreen(),
|
||||
opacity * fgColor.getBlue() + (1 - opacity) * bgColor.getBlue(),
|
||||
opacity * fgColor.getRed() + (1 - opacity) * bg.getRed(),
|
||||
opacity * fgColor.getGreen() + (1 - opacity) * bg.getGreen(),
|
||||
opacity * fgColor.getBlue() + (1 - opacity) * bg.getBlue(),
|
||||
} :
|
||||
new double[] {
|
||||
fgColor.getRed(),
|
||||
@ -818,6 +817,19 @@ public class JColorUtils {
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* The opposite to the {@link JColorUtils#flattenColor(Color, Color)}. It converts target opaque color
|
||||
* to its equivalent with the desired opacity level.
|
||||
*/
|
||||
public static Color opaqueColor(Color bgColor, Color targetColor, double targetOpacity) {
|
||||
return Color.color(
|
||||
bgColor.getRed() + (targetColor.getRed() - bgColor.getRed()) * targetOpacity,
|
||||
bgColor.getGreen() + (targetColor.getGreen() - bgColor.getGreen()) * targetOpacity,
|
||||
bgColor.getBlue() + (targetColor.getBlue() - bgColor.getBlue()) * targetOpacity,
|
||||
targetOpacity
|
||||
);
|
||||
}
|
||||
|
||||
public static float[] toHSL(Color color) {
|
||||
return JColorUtils.toHSL(
|
||||
(float) color.getRed(),
|
||||
|
@ -0,0 +1,19 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
.accent-color-selector {
|
||||
-color-primary: -color-accent-emphasis;
|
||||
|
||||
-fx-spacing: 1em;
|
||||
|
||||
>.color-button {
|
||||
>.icon {
|
||||
-fx-min-width: 1em;
|
||||
-fx-pref-width: 1em;
|
||||
-fx-max-height: 1em;
|
||||
-fx-min-width: 1em;
|
||||
-fx-pref-height: 1em;
|
||||
-fx-max-height: 1em;
|
||||
-fx-background-color: -color-primary;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,8 +1,8 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
$color-wsag-bg-passed: #388e3c;
|
||||
$color-wsag-bg-failed: #ef5350;
|
||||
$color-wsag-fg: white;
|
||||
$color-wcag-bg-passed: #388e3c;
|
||||
$color-wcag-bg-failed: #ef5350;
|
||||
$color-wcag-fg: white;
|
||||
|
||||
#color-palette {
|
||||
|
||||
@ -24,18 +24,18 @@ $color-wsag-fg: white;
|
||||
-fx-cursor: hand;
|
||||
|
||||
&:passed>.contrast-level-label {
|
||||
-fx-background-color: $color-wsag-bg-passed;
|
||||
-fx-background-color: $color-wcag-bg-passed;
|
||||
}
|
||||
|
||||
>.contrast-level-label {
|
||||
-fx-text-fill: $color-wsag-fg;
|
||||
-fx-background-color: $color-wsag-bg-failed;
|
||||
-fx-text-fill: $color-wcag-fg;
|
||||
-fx-background-color: $color-wcag-bg-failed;
|
||||
-fx-background-radius: 6px;
|
||||
-fx-padding: 3px;
|
||||
|
||||
>.ikonli-font-icon {
|
||||
-fx-fill: $color-wsag-fg;
|
||||
-fx-icon-color: $color-wsag-fg;
|
||||
-fx-fill: $color-wcag-fg;
|
||||
-fx-icon-color: $color-wcag-fg;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -79,17 +79,17 @@
|
||||
.contrast-level {
|
||||
>.state {
|
||||
-fx-padding: 0.5em 1em 0.5em 1em;
|
||||
-fx-background-color: palette.$color-wsag-bg-failed;
|
||||
-fx-background-color: palette.$color-wcag-bg-failed;
|
||||
-fx-background-radius: 4px;
|
||||
-fx-text-fill: palette.$color-wsag-fg;
|
||||
-fx-text-fill: palette.$color-wcag-fg;
|
||||
|
||||
&:passed {
|
||||
-fx-background-color: palette.$color-wsag-bg-passed;
|
||||
-fx-background-color: palette.$color-wcag-bg-passed;
|
||||
}
|
||||
|
||||
>.ikonli-font-icon {
|
||||
-fx-fill: palette.$color-wsag-fg;
|
||||
-fx-icon-color: palette.$color-wsag-fg;
|
||||
-fx-fill: palette.$color-wcag-fg;
|
||||
-fx-icon-color: palette.$color-wcag-fg;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -3,4 +3,5 @@
|
||||
@use "color-palette";
|
||||
@use "color-scale";
|
||||
@use "contrast-checker";
|
||||
@use "accent-color-selector";
|
||||
@use "quick-config-menu";
|
Loading…
Reference in New Issue
Block a user