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;