Add theme quick switching menu

This commit is contained in:
mkpaz 2022-08-30 22:42:21 +04:00
parent 69ba0ed7af
commit 13563c6118
5 changed files with 223 additions and 5 deletions

@ -68,7 +68,7 @@
<lib.commons-lang.version>3.12.0</lib.commons-lang.version>
<lib.cssfx.version>11.5.1</lib.cssfx.version>
<lib.ikonli.version>12.2.0</lib.ikonli.version>
<lib.ikonli.version>12.3.1</lib.ikonli.version>
<lib.datafaker.version>1.3.0</lib.datafaker.version>
<lib.jetbrains-annotations.version>22.0.0</lib.jetbrains-annotations.version>
<test.assertj.version>3.21.0</test.assertj.version>

@ -1,6 +1,7 @@
/* SPDX-License-Identifier: MIT */
package atlantafx.sampler.page;
import atlantafx.base.controls.Popover;
import atlantafx.base.controls.Spacer;
import atlantafx.base.theme.Styles;
import atlantafx.sampler.theme.ThemeManager;
@ -16,6 +17,7 @@ import net.datafaker.Faker;
import org.kordamp.ikonli.Ikon;
import org.kordamp.ikonli.feather.Feather;
import org.kordamp.ikonli.javafx.FontIcon;
import org.kordamp.ikonli.material2.Material2OutlinedMZ;
import java.io.IOException;
import java.util.List;
@ -25,7 +27,9 @@ import java.util.function.Supplier;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import static atlantafx.base.controls.Popover.ArrowLocation.TOP_CENTER;
import static atlantafx.base.theme.Styles.BUTTON_ICON;
import static atlantafx.base.theme.Styles.FLAT;
import static atlantafx.sampler.util.Containers.setScrollConstraints;
public abstract class AbstractPage extends BorderPane implements Page {
@ -38,6 +42,8 @@ public abstract class AbstractPage extends BorderPane implements Page {
private static final Ikon ICON_CODE = Feather.CODE;
private static final Ikon ICON_SAMPLE = Feather.LAYOUT;
protected Button quickConfigBtn;
protected Popover quickConfigPopover;
protected Button sourceCodeToggleBtn;
protected StackPane codeViewerWrapper;
protected CodeViewer codeViewer;
@ -63,16 +69,21 @@ public abstract class AbstractPage extends BorderPane implements Page {
codeViewerWrapper.getStyleClass().add("wrapper");
codeViewerWrapper.getChildren().setAll(codeViewer);
quickConfigBtn = new Button("", new FontIcon(Material2OutlinedMZ.STYLE));
quickConfigBtn.getStyleClass().addAll(BUTTON_ICON, FLAT);
quickConfigBtn.setTooltip(new Tooltip("Change theme"));
quickConfigBtn.setOnAction(e -> showThemeConfigPopover());
sourceCodeToggleBtn = new Button("", new FontIcon(ICON_CODE));
sourceCodeToggleBtn.getStyleClass().addAll(BUTTON_ICON);
sourceCodeToggleBtn.setTooltip(new Tooltip("Source Code"));
sourceCodeToggleBtn.getStyleClass().addAll(BUTTON_ICON, FLAT);
sourceCodeToggleBtn.setTooltip(new Tooltip("Source code"));
sourceCodeToggleBtn.setOnAction(e -> toggleSourceCode());
var header = new HBox();
var header = new HBox(30);
header.getStyleClass().add("header");
header.setMinHeight(HEADER_HEIGHT);
header.setAlignment(Pos.CENTER_LEFT);
header.getChildren().setAll(titleLabel, new Spacer(), sourceCodeToggleBtn);
header.getChildren().setAll(titleLabel, new Spacer(), quickConfigBtn, sourceCodeToggleBtn);
// == user content ==
@ -120,6 +131,20 @@ public abstract class AbstractPage extends BorderPane implements Page {
// to the scene graph and here is the place do this.
protected void onRendered() { }
private void showThemeConfigPopover() {
if (quickConfigPopover == null) {
var content = new QuickConfigMenu();
content.setExitHandler(() -> quickConfigPopover.hide());
quickConfigPopover = new Popover(content);
quickConfigPopover.setHeaderAlwaysVisible(false);
quickConfigPopover.setDetachable(false);
quickConfigPopover.setArrowLocation(TOP_CENTER);
}
quickConfigPopover.show(quickConfigBtn);
}
protected void toggleSourceCode() {
var graphic = (FontIcon) sourceCodeToggleBtn.getGraphic();

@ -0,0 +1,172 @@
/* SPDX-License-Identifier: MIT */
package atlantafx.sampler.page;
import atlantafx.base.controls.Spacer;
import atlantafx.sampler.theme.ThemeManager;
import javafx.css.PseudoClass;
import javafx.geometry.HorizontalDirection;
import javafx.scene.Node;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.Separator;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Pane;
import javafx.scene.layout.StackPane;
import javafx.scene.layout.VBox;
import org.kordamp.ikonli.javafx.FontIcon;
import org.kordamp.ikonli.material2.Material2AL;
import org.kordamp.ikonli.material2.Material2MZ;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.function.Consumer;
import static atlantafx.base.theme.Styles.*;
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;
public class QuickConfigMenu extends StackPane {
private static final PseudoClass SELECTED = PseudoClass.getPseudoClass("selected");
private static final String EXIT_ID = "Exit";
private MainMenu mainMenu;
private ThemeSelectionMenu themeSelectionMenu;
private Runnable exitHandler;
private final Consumer<String> navHandler = s -> {
Pane pane = null;
switch (s) {
case MainMenu.ID -> pane = getOrCreateMainMenu();
case ThemeSelectionMenu.ID -> {
ThemeSelectionMenu menu = getOrCreateThemeSelectionMenu();
menu.update();
pane = menu;
}
default -> {
if (exitHandler != null) {
exitHandler.run();
return;
}
}
}
getChildren().setAll(Objects.requireNonNull(pane));
};
public void setExitHandler(Runnable exitHandler) {
this.exitHandler = exitHandler;
}
public QuickConfigMenu() {
super();
getChildren().setAll(getOrCreateMainMenu());
setId("quick-config-menu");
}
private MainMenu getOrCreateMainMenu() {
if (mainMenu == null) { mainMenu = new MainMenu(navHandler); }
return mainMenu;
}
private ThemeSelectionMenu getOrCreateThemeSelectionMenu() {
if (themeSelectionMenu == null) { themeSelectionMenu = new ThemeSelectionMenu(navHandler); }
return themeSelectionMenu;
}
private static Pane menu(String title, HorizontalDirection direction) {
var label = new Label(title);
label.getStyleClass().add(TEXT_CAPTION);
var root = new HBox();
root.setAlignment(CENTER_LEFT);
root.getStyleClass().addAll("row", "action");
switch (direction) {
case LEFT -> root.getChildren().setAll(new FontIcon(ARROW_BACK), new Spacer(), label, new Spacer());
case RIGHT -> root.getChildren().setAll(label, new Spacer(), new FontIcon(ARROW_FORWARD));
}
return root;
}
///////////////////////////////////////////////////////////////////////////
private static class MainMenu extends VBox {
private static final String ID = "MainMenu";
public MainMenu(Consumer<String> navHandler) {
super();
Objects.requireNonNull(navHandler);
var themeSelectionMenu = menu("Theme", HorizontalDirection.RIGHT);
themeSelectionMenu.setOnMouseClicked(e -> navHandler.accept(ThemeSelectionMenu.ID));
// ~
var zoomInBtn = new Button("", new FontIcon(Material2MZ.MINUS));
zoomInBtn.getStyleClass().addAll(BUTTON_CIRCLE, BUTTON_ICON, FLAT);
var zoomOutBtn = new Button("", new FontIcon(Material2MZ.PLUS));
zoomOutBtn.getStyleClass().addAll(BUTTON_CIRCLE, BUTTON_ICON, FLAT);
var zoomLabel = new Label("100%");
var zoomBox = new HBox(zoomInBtn, new Spacer(), zoomLabel, new Spacer(), zoomOutBtn);
zoomBox.setAlignment(CENTER_LEFT);
zoomBox.getStyleClass().addAll("row");
zoomBox.setDisable(true); // not yet implemented
// !
getChildren().setAll(themeSelectionMenu, new Separator(), zoomBox);
}
}
private static class ThemeSelectionMenu extends VBox {
public static final String ID = "ThemeSelectionMenu";
private final List<Node> items = new ArrayList<>();
public ThemeSelectionMenu(Consumer<String> navHandler) {
super();
Objects.requireNonNull(navHandler);
var tm = ThemeManager.getInstance();
var mainMenu = menu("Theme", HorizontalDirection.LEFT);
mainMenu.setOnMouseClicked(e -> navHandler.accept(MainMenu.ID));
getChildren().setAll(mainMenu, new Separator());
tm.getAvailableThemes().forEach(theme -> {
var icon = new FontIcon(Material2AL.CHECK);
var item = new HBox(20, icon, new Label(theme.getName()));
item.getStyleClass().addAll("row", "action", "radio");
item.setUserData(theme.getName());
item.setOnMouseClicked(e -> {
tm.setTheme(theme);
tm.reloadCustomCSS();
navHandler.accept(MainMenu.ID);
navHandler.accept(QuickConfigMenu.EXIT_ID);
});
items.add(item);
getChildren().add(item);
});
}
public void update() {
items.forEach(item -> item.pseudoClassStateChanged(
SELECTED,
Objects.equals(item.getUserData(), ThemeManager.getInstance().getTheme().getName())
));
}
}
}

@ -37,7 +37,10 @@ public class ThemePage extends AbstractPage {
optionsGrid(),
colorPalette
);
quickConfigBtn.setVisible(false);
quickConfigBtn.setManaged(false);
sourceCodeToggleBtn.setVisible(false);
sourceCodeToggleBtn.setManaged(false);
}
private GridPane optionsGrid() {

@ -306,6 +306,24 @@
-fx-background-color: #388e3c;
}
#quick-config-menu {
-fx-min-width: 200px;
}
#quick-config-menu .row {
-fx-padding: 6px;
-fx-background-radius: 4px;
}
#quick-config-menu .action:hover {
/* FIXME: Not visible in some themes (subtle over subtle) */
-fx-background-color: -color-bg-subtle;
}
#quick-config-menu .radio > .ikonli-font-icon {
visibility: hidden;
}
#quick-config-menu .radio:selected > .ikonli-font-icon {
visibility: visible;
}
.popover .date-picker-popup {
-fx-background-color: transparent;
-fx-background-insets: 0;