diff --git a/pom.xml b/pom.xml index 51a4be1..d4ab205 100755 --- a/pom.xml +++ b/pom.xml @@ -68,7 +68,7 @@ 3.12.0 11.5.1 - 12.2.0 + 12.3.1 1.3.0 22.0.0 3.21.0 diff --git a/sampler/src/main/java/atlantafx/sampler/page/AbstractPage.java b/sampler/src/main/java/atlantafx/sampler/page/AbstractPage.java index e1bc882..84242f0 100755 --- a/sampler/src/main/java/atlantafx/sampler/page/AbstractPage.java +++ b/sampler/src/main/java/atlantafx/sampler/page/AbstractPage.java @@ -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(); diff --git a/sampler/src/main/java/atlantafx/sampler/page/QuickConfigMenu.java b/sampler/src/main/java/atlantafx/sampler/page/QuickConfigMenu.java new file mode 100644 index 0000000..6ed233e --- /dev/null +++ b/sampler/src/main/java/atlantafx/sampler/page/QuickConfigMenu.java @@ -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 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 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 items = new ArrayList<>(); + + public ThemeSelectionMenu(Consumer 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()) + )); + } + } +} diff --git a/sampler/src/main/java/atlantafx/sampler/page/general/ThemePage.java b/sampler/src/main/java/atlantafx/sampler/page/general/ThemePage.java index 3dac181..efc4155 100755 --- a/sampler/src/main/java/atlantafx/sampler/page/general/ThemePage.java +++ b/sampler/src/main/java/atlantafx/sampler/page/general/ThemePage.java @@ -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() { diff --git a/sampler/src/main/resources/assets/styles/index.css b/sampler/src/main/resources/assets/styles/index.css index 13203cd..219410e 100755 --- a/sampler/src/main/resources/assets/styles/index.css +++ b/sampler/src/main/resources/assets/styles/index.css @@ -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;