From 978577dc6a9a785e72241e3d8b3d9a48c2fab6a1 Mon Sep 17 00:00:00 2001 From: mkpaz Date: Mon, 19 Sep 2022 16:22:01 +0400 Subject: [PATCH] Improve Sampler layout - Refactor to MVVM. - Add fancy top bar. - Add keyboard navigation for sidebar. - Add hotkeys support. --- .../main/java/atlantafx/sampler/Launcher.java | 22 +- .../atlantafx/sampler/event/BrowseEvent.java | 2 +- .../atlantafx/sampler/event/HotkeyEvent.java | 24 ++ .../atlantafx/sampler/event/ThemeEvent.java | 7 + .../atlantafx/sampler/layout/HeaderBar.java | 136 ++++++++ .../atlantafx/sampler/layout/MainLayer.java | 161 +++++++-- .../atlantafx/sampler/layout/MainModel.java | 73 ++++ .../atlantafx/sampler/layout/Sidebar.java | 330 ++++++++++-------- .../atlantafx/sampler/page/AbstractPage.java | 156 +-------- .../java/atlantafx/sampler/page/Page.java | 4 + .../sampler/page/general/ThemePage.java | 22 +- .../sampler/page/general/TypographyPage.java | 18 +- .../sampler/page/showcase/ShowcasePage.java | 8 +- .../src/main/resources/application.properties | 3 +- .../assets/styles/scss/general/_root.scss | 5 +- .../assets/styles/scss/layout/_index.scss | 6 +- .../assets/styles/scss/layout/_main.scss | 125 +++++++ .../assets/styles/scss/layout/_page.scss | 49 +-- .../assets/styles/scss/layout/_sidebar.scss | 45 --- 19 files changed, 775 insertions(+), 421 deletions(-) create mode 100644 sampler/src/main/java/atlantafx/sampler/event/HotkeyEvent.java create mode 100644 sampler/src/main/java/atlantafx/sampler/layout/HeaderBar.java create mode 100644 sampler/src/main/java/atlantafx/sampler/layout/MainModel.java create mode 100644 sampler/src/main/resources/assets/styles/scss/layout/_main.scss delete mode 100644 sampler/src/main/resources/assets/styles/scss/layout/_sidebar.scss diff --git a/sampler/src/main/java/atlantafx/sampler/Launcher.java b/sampler/src/main/java/atlantafx/sampler/Launcher.java index 7d0e4a0..9b086f6 100755 --- a/sampler/src/main/java/atlantafx/sampler/Launcher.java +++ b/sampler/src/main/java/atlantafx/sampler/Launcher.java @@ -3,6 +3,7 @@ package atlantafx.sampler; import atlantafx.sampler.event.BrowseEvent; import atlantafx.sampler.event.DefaultEventBus; +import atlantafx.sampler.event.HotkeyEvent; import atlantafx.sampler.event.Listener; import atlantafx.sampler.layout.ApplicationWindow; import atlantafx.sampler.theme.ThemeManager; @@ -14,12 +15,17 @@ import javafx.application.Application; import javafx.application.Platform; import javafx.scene.Scene; import javafx.scene.image.Image; +import javafx.scene.input.KeyCode; +import javafx.scene.input.KeyCodeCombination; +import javafx.scene.input.KeyCombination; +import javafx.scene.input.KeyEvent; import javafx.stage.Stage; import java.io.IOException; import java.io.InputStreamReader; import java.net.URI; import java.nio.file.Paths; +import java.util.List; import java.util.Properties; import static java.nio.charset.StandardCharsets.UTF_8; @@ -30,6 +36,10 @@ public class Launcher extends Application { Resources.getPropertyOrEnv("atlantafx.mode", "ATLANTAFX_MODE") ); + public static final List SUPPORTED_HOTKEYS = List.of( + new KeyCodeCombination(KeyCode.F, KeyCombination.CONTROL_DOWN) + ); + private static class DefaultExceptionHandler implements Thread.UncaughtExceptionHandler { @Override @@ -45,14 +55,15 @@ public class Launcher extends Application { @Override public void start(Stage stage) { Thread.currentThread().setUncaughtExceptionHandler(new DefaultExceptionHandler()); + loadApplicationProperties(); if (IS_DEV_MODE) { System.out.println("[WARNING] Application is running in development mode."); } - loadApplicationProperties(); var root = new ApplicationWindow(); var scene = new Scene(root, 1200, 768); + scene.setOnKeyPressed(this::dispatchHotkeys); var tm = ThemeManager.getInstance(); tm.setScene(scene); @@ -119,6 +130,15 @@ public class Launcher extends Application { CSSFX.start(scene); } + private void dispatchHotkeys(KeyEvent event) { + for (KeyCodeCombination k : SUPPORTED_HOTKEYS) { + if (k.match(event)) { + DefaultEventBus.getInstance().publish(new HotkeyEvent(k)); + return; + } + } + } + @Listener private void onBrowseEvent(BrowseEvent event) { getHostServices().showDocument(event.getUri().toString()); diff --git a/sampler/src/main/java/atlantafx/sampler/event/BrowseEvent.java b/sampler/src/main/java/atlantafx/sampler/event/BrowseEvent.java index f3b288a..3dec733 100644 --- a/sampler/src/main/java/atlantafx/sampler/event/BrowseEvent.java +++ b/sampler/src/main/java/atlantafx/sampler/event/BrowseEvent.java @@ -19,6 +19,6 @@ public class BrowseEvent extends Event { public String toString() { return "BrowseEvent{" + "uri=" + uri + - '}'; + "} " + super.toString(); } } diff --git a/sampler/src/main/java/atlantafx/sampler/event/HotkeyEvent.java b/sampler/src/main/java/atlantafx/sampler/event/HotkeyEvent.java new file mode 100644 index 0000000..25e6088 --- /dev/null +++ b/sampler/src/main/java/atlantafx/sampler/event/HotkeyEvent.java @@ -0,0 +1,24 @@ +/* SPDX-License-Identifier: MIT */ +package atlantafx.sampler.event; + +import javafx.scene.input.KeyCodeCombination; + +public class HotkeyEvent extends Event { + + private final KeyCodeCombination keys; + + public HotkeyEvent(KeyCodeCombination keys) { + this.keys = keys; + } + + public KeyCodeCombination getKeys() { + return keys; + } + + @Override + public String toString() { + return "HotkeyEvent{" + + "keys=" + keys + + "} " + super.toString(); + } +} diff --git a/sampler/src/main/java/atlantafx/sampler/event/ThemeEvent.java b/sampler/src/main/java/atlantafx/sampler/event/ThemeEvent.java index 332e8fc..8e928af 100644 --- a/sampler/src/main/java/atlantafx/sampler/event/ThemeEvent.java +++ b/sampler/src/main/java/atlantafx/sampler/event/ThemeEvent.java @@ -23,4 +23,11 @@ public class ThemeEvent extends Event { THEME_ADD, THEME_REMOVE } + + @Override + public String toString() { + return "ThemeEvent{" + + "eventType=" + eventType + + "} " + super.toString(); + } } diff --git a/sampler/src/main/java/atlantafx/sampler/layout/HeaderBar.java b/sampler/src/main/java/atlantafx/sampler/layout/HeaderBar.java new file mode 100644 index 0000000..c9aceec --- /dev/null +++ b/sampler/src/main/java/atlantafx/sampler/layout/HeaderBar.java @@ -0,0 +1,136 @@ +/* SPDX-License-Identifier: MIT */ +package atlantafx.sampler.layout; + +import atlantafx.base.controls.CustomTextField; +import atlantafx.base.controls.Spacer; +import atlantafx.sampler.event.BrowseEvent; +import atlantafx.sampler.event.DefaultEventBus; +import atlantafx.sampler.event.HotkeyEvent; +import javafx.beans.binding.Bindings; +import javafx.geometry.Pos; +import javafx.scene.Node; +import javafx.scene.control.Label; +import javafx.scene.input.KeyCode; +import javafx.scene.input.KeyCombination.ModifierValue; +import javafx.scene.layout.HBox; +import javafx.scene.layout.Region; +import org.kordamp.ikonli.Ikon; +import org.kordamp.ikonli.feather.Feather; +import org.kordamp.ikonli.javafx.FontIcon; +import org.kordamp.ikonli.material2.Material2MZ; +import org.kordamp.ikonli.material2.Material2OutlinedMZ; + +import java.net.URI; +import java.util.function.Consumer; + +import static atlantafx.base.theme.Styles.*; +import static atlantafx.sampler.Launcher.IS_DEV_MODE; +import static atlantafx.sampler.layout.MainLayer.SIDEBAR_WIDTH; + +class HeaderBar extends HBox { + + private static final int HEADER_HEIGHT = 50; + private static final Ikon ICON_CODE = Feather.CODE; + private static final Ikon ICON_SAMPLE = Feather.LAYOUT; + + private final MainModel model; + private Consumer quickConfigActionHandler; + + public HeaderBar(MainModel model) { + super(); + this.model = model; + createView(); + } + + private void createView() { + var logoLabel = new Label("AtlantaFX"); + logoLabel.getStyleClass().addAll(TITLE_3); + + var versionLabel = new Label(System.getProperty("app.version")); + versionLabel.getStyleClass().addAll("version", TEXT_SMALL); + + var githubLink = new FontIcon(Feather.GITHUB); + githubLink.getStyleClass().addAll("github"); + githubLink.setOnMouseClicked(e -> { + var homepage = System.getProperty("app.homepage"); + if (homepage != null) { + DefaultEventBus.getInstance().publish(new BrowseEvent(URI.create(homepage))); + } + }); + + var logoBox = new HBox(10, logoLabel, versionLabel, new Spacer(), githubLink); + logoBox.getStyleClass().add("logo"); + logoBox.setAlignment(Pos.CENTER_LEFT); + logoBox.setMinWidth(SIDEBAR_WIDTH); + logoBox.setPrefWidth(SIDEBAR_WIDTH); + logoBox.setMaxWidth(SIDEBAR_WIDTH); + + var titleLabel = new Label(); + titleLabel.getStyleClass().addAll("page-title", TITLE_4); + titleLabel.textProperty().bind(model.titleProperty()); + + var searchField = new CustomTextField(); + searchField.setLeft(new FontIcon(Material2MZ.SEARCH)); + searchField.setPromptText("Search"); + model.searchTextProperty().bind(searchField.textProperty()); + + DefaultEventBus.getInstance().subscribe(HotkeyEvent.class, e -> { + if (e.getKeys().getControl() == ModifierValue.DOWN && e.getKeys().getCode() == KeyCode.F) { + searchField.requestFocus(); + } + }); + + // this dummy anchor prevents popover from inheriting + // unwanted styles from the owner node + var popoverAnchor = new Region(); + + var quickConfigBtn = new FontIcon(Material2OutlinedMZ.STYLE); + quickConfigBtn.mouseTransparentProperty().bind(model.themeChangeToggleProperty().not()); + quickConfigBtn.opacityProperty().bind(Bindings.createDoubleBinding( + () -> model.themeChangeToggleProperty().get() ? 1.0 : 0.5, model.themeChangeToggleProperty() + )); + quickConfigBtn.setOnMouseClicked(e -> { + if (quickConfigActionHandler != null) { quickConfigActionHandler.accept(popoverAnchor); } + }); + + var sourceCodeBtn = new FontIcon(ICON_CODE); + sourceCodeBtn.mouseTransparentProperty().bind(model.sourceCodeToggleProperty().not()); + sourceCodeBtn.opacityProperty().bind(Bindings.createDoubleBinding( + () -> model.sourceCodeToggleProperty().get() ? 1.0 : 0.5, model.sourceCodeToggleProperty() + )); + sourceCodeBtn.setOnMouseClicked(e -> model.nextSubLayer()); + + // ~ + + model.currentSubLayerProperty().addListener((obs, old, val) -> { + switch (val) { + case PAGE -> sourceCodeBtn.setIconCode(ICON_CODE); + case SOURCE_CODE -> sourceCodeBtn.setIconCode(ICON_SAMPLE); + } + }); + + setId("header-bar"); + setMinHeight(HEADER_HEIGHT); + setPrefHeight(HEADER_HEIGHT); + setAlignment(Pos.CENTER_LEFT); + getChildren().setAll( + logoBox, + titleLabel, + new Spacer(), + searchField, + popoverAnchor, + quickConfigBtn, + sourceCodeBtn + ); + + if (IS_DEV_MODE) { + var devModeLabel = new Label("app is running in development mode"); + devModeLabel.getStyleClass().addAll(TEXT_SMALL, "dev-mode-indicator"); + getChildren().add(2, devModeLabel); + } + } + + void setQuickConfigActionHandler(Consumer handler) { + this.quickConfigActionHandler = handler; + } +} diff --git a/sampler/src/main/java/atlantafx/sampler/layout/MainLayer.java b/sampler/src/main/java/atlantafx/sampler/layout/MainLayer.java index 7ca4c61..357a497 100644 --- a/sampler/src/main/java/atlantafx/sampler/layout/MainLayer.java +++ b/sampler/src/main/java/atlantafx/sampler/layout/MainLayer.java @@ -1,67 +1,168 @@ /* SPDX-License-Identifier: MIT */ package atlantafx.sampler.layout; +import atlantafx.base.controls.Popover; +import atlantafx.sampler.event.DefaultEventBus; +import atlantafx.sampler.event.ThemeEvent; +import atlantafx.sampler.layout.MainModel.SubLayer; +import atlantafx.sampler.page.CodeViewer; import atlantafx.sampler.page.Page; +import atlantafx.sampler.page.QuickConfigMenu; import atlantafx.sampler.page.components.OverviewPage; import atlantafx.sampler.theme.ThemeManager; import javafx.animation.FadeTransition; import javafx.application.Platform; +import javafx.scene.Node; import javafx.scene.layout.BorderPane; import javafx.scene.layout.HBox; +import javafx.scene.layout.Pane; import javafx.scene.layout.StackPane; import javafx.util.Duration; +import java.io.IOException; import java.util.Objects; +import static atlantafx.base.controls.Popover.ArrowLocation.TOP_CENTER; import static javafx.scene.layout.Priority.ALWAYS; class MainLayer extends BorderPane { + static final int SIDEBAR_WIDTH = 220; + static final int PAGE_TRANSITION_DURATION = 500; // ms + + private final MainModel model = new MainModel(); + private final HeaderBar headerBar = new HeaderBar(model); + private final Sidebar sidebar = new Sidebar(model); + private final StackPane subLayerPane = new StackPane(); + + private Popover quickConfigPopover; + private CodeViewer codeViewer; + private StackPane codeViewerWrapper; + public MainLayer() { super(); + createView(); + initListeners(); + + model.navigate(OverviewPage.class); + // keyboard navigation won't work without focus + Platform.runLater(sidebar::begForFocus); } private void createView() { - var sidebar = new Sidebar(); - sidebar.setMinWidth(200); + sidebar.setMinWidth(SIDEBAR_WIDTH); - final var pageContainer = new StackPane(); - HBox.setHgrow(pageContainer, ALWAYS); + codeViewer = new CodeViewer(); - sidebar.setOnSelect(pageClass -> { - try { - final Page prevPage = (!pageContainer.getChildren().isEmpty() && pageContainer.getChildren().get(0) instanceof Page page) ? page : null; - final Page nextPage = pageClass.getDeclaredConstructor().newInstance(); + codeViewerWrapper = new StackPane(); + codeViewerWrapper.getStyleClass().add("source-code"); + codeViewerWrapper.getChildren().setAll(codeViewer); - // startup, no animation - if (getScene() == null) { - pageContainer.getChildren().add(nextPage.getView()); - return; - } + subLayerPane.getChildren().setAll(codeViewerWrapper); + HBox.setHgrow(subLayerPane, ALWAYS); - Objects.requireNonNull(prevPage); + // ~ - // reset previous page, e.g. to free resources - prevPage.reset(); + setId("main"); + setTop(headerBar); + setLeft(sidebar); + setCenter(subLayerPane); + } - // animate switching between pages - pageContainer.getChildren().add(nextPage.getView()); - FadeTransition transition = new FadeTransition(Duration.millis(300), nextPage.getView()); - transition.setFromValue(0.0); - transition.setToValue(1.0); - transition.setOnFinished(t -> pageContainer.getChildren().remove(prevPage.getView())); - transition.play(); - } catch (Exception e) { - throw new RuntimeException(e); + private void initListeners() { + headerBar.setQuickConfigActionHandler(this::showThemeConfigPopover); + + model.selectedPageProperty().addListener((obs, old, val) -> { + if (val != null) { loadPage(val); } + }); + + model.currentSubLayerProperty().addListener((obs, old, val) -> { + switch (val) { + case PAGE -> hideSourceCode(); + case SOURCE_CODE -> showSourceCode(); } }); - // ~ - setLeft(sidebar); - setCenter(pageContainer); + // update code view color theme on app theme change + DefaultEventBus.getInstance().subscribe(ThemeEvent.class, e -> { + if (ThemeManager.getInstance().getTheme() != null && model.currentSubLayerProperty().get() == SubLayer.SOURCE_CODE) { + showSourceCode(); + } + }); + } - sidebar.select(OverviewPage.class); - Platform.runLater(sidebar::requestFocus); + private void loadPage(Class pageClass) { + try { + final Page prevPage = (Page) subLayerPane.getChildren().stream() + .filter(c -> c instanceof Page) + .findFirst() + .orElse(null); + final Page nextPage = pageClass.getDeclaredConstructor().newInstance(); + + model.setPageData( + nextPage.getName(), + nextPage.canChangeThemeSettings(), + nextPage.canDisplaySourceCode() + ); + + // startup, no prev page, no animation + if (getScene() == null) { + subLayerPane.getChildren().add(nextPage.getView()); + return; + } + + Objects.requireNonNull(prevPage); + + // reset previous page, e.g. to free resources + prevPage.reset(); + + // animate switching between pages + subLayerPane.getChildren().add(nextPage.getView()); + var transition = new FadeTransition(Duration.millis(PAGE_TRANSITION_DURATION), nextPage.getView()); + transition.setFromValue(0.0); + transition.setToValue(1.0); + transition.setOnFinished(t -> { + subLayerPane.getChildren().remove(prevPage.getView()); + if (nextPage instanceof Pane nextPane) { nextPane.toFront(); } + }); + transition.play(); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + private void showThemeConfigPopover(Node source) { + 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.setOnShowing(e -> content.update()); + } + + quickConfigPopover.show(source); + } + + private void showSourceCode() { + var sourceClass = Objects.requireNonNull(model.selectedPageProperty().get()); + var sourceFileName = sourceClass.getSimpleName() + ".java"; + try (var stream = sourceClass.getResourceAsStream(sourceFileName)) { + Objects.requireNonNull(stream, "Missing source file '" + sourceFileName + "';"); + + // set syntax highlight theme according to JavaFX theme + ThemeManager tm = ThemeManager.getInstance(); + codeViewer.setContent(stream, tm.getMatchingSourceCodeHighlightTheme(tm.getTheme())); + codeViewerWrapper.toFront(); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + private void hideSourceCode() { + codeViewerWrapper.toBack(); } } diff --git a/sampler/src/main/java/atlantafx/sampler/layout/MainModel.java b/sampler/src/main/java/atlantafx/sampler/layout/MainModel.java new file mode 100644 index 0000000..83ca1a1 --- /dev/null +++ b/sampler/src/main/java/atlantafx/sampler/layout/MainModel.java @@ -0,0 +1,73 @@ +/* SPDX-License-Identifier: MIT */ +package atlantafx.sampler.layout; + +import atlantafx.sampler.page.Page; +import javafx.beans.property.*; + +import java.util.Objects; + +import static atlantafx.sampler.layout.MainModel.SubLayer.PAGE; +import static atlantafx.sampler.layout.MainModel.SubLayer.SOURCE_CODE; + +public class MainModel { + + public enum SubLayer { + PAGE, + SOURCE_CODE + } + + private final ReadOnlyStringWrapper title = new ReadOnlyStringWrapper(); + private final StringProperty searchText = new SimpleStringProperty(); + private final ReadOnlyObjectWrapper> selectedPage = new ReadOnlyObjectWrapper<>(); + private final ReadOnlyBooleanWrapper themeChangeToggle = new ReadOnlyBooleanWrapper(); + private final ReadOnlyBooleanWrapper sourceCodeToggle = new ReadOnlyBooleanWrapper(); + private final ReadOnlyObjectWrapper currentSubLayer = new ReadOnlyObjectWrapper<>(PAGE); + + /////////////////////////////////////////////////////////////////////////// + // Properties // + /////////////////////////////////////////////////////////////////////////// + + public StringProperty searchTextProperty() { + return searchText; + } + + public ReadOnlyStringProperty titleProperty() { + return title.getReadOnlyProperty(); + } + + public ReadOnlyBooleanProperty themeChangeToggleProperty() { + return themeChangeToggle.getReadOnlyProperty(); + } + + public ReadOnlyBooleanProperty sourceCodeToggleProperty() { + return sourceCodeToggle.getReadOnlyProperty(); + } + + public ReadOnlyObjectProperty> selectedPageProperty() { + return selectedPage.getReadOnlyProperty(); + } + + public ReadOnlyObjectProperty currentSubLayerProperty() { + return currentSubLayer.getReadOnlyProperty(); + } + + /////////////////////////////////////////////////////////////////////////// + // Commands // + /////////////////////////////////////////////////////////////////////////// + + public void setPageData(String text, boolean canChangeTheme, boolean canDisplaySource) { + title.set(Objects.requireNonNull(text)); + themeChangeToggle.set(canChangeTheme); + sourceCodeToggle.set(canDisplaySource); + } + + public void navigate(Class page) { + selectedPage.set(Objects.requireNonNull(page)); + currentSubLayer.set(PAGE); + } + + public void nextSubLayer() { + var old = currentSubLayer.get(); + currentSubLayer.set(old == PAGE ? SOURCE_CODE : PAGE); + } +} diff --git a/sampler/src/main/java/atlantafx/sampler/layout/Sidebar.java b/sampler/src/main/java/atlantafx/sampler/layout/Sidebar.java index b325539..8eb6167 100644 --- a/sampler/src/main/java/atlantafx/sampler/layout/Sidebar.java +++ b/sampler/src/main/java/atlantafx/sampler/layout/Sidebar.java @@ -11,193 +11,243 @@ import atlantafx.sampler.page.showcase.filemanager.FileManagerPage; import atlantafx.sampler.page.showcase.musicplayer.MusicPlayerPage; import atlantafx.sampler.util.Containers; import javafx.beans.binding.Bindings; -import javafx.beans.property.ReadOnlyObjectWrapper; import javafx.collections.FXCollections; +import javafx.collections.ListChangeListener; +import javafx.collections.ObservableList; import javafx.collections.transformation.FilteredList; import javafx.css.PseudoClass; import javafx.geometry.Orientation; -import javafx.geometry.Pos; -import javafx.scene.control.*; -import javafx.scene.layout.HBox; +import javafx.scene.control.Label; +import javafx.scene.control.ScrollPane; +import javafx.scene.input.KeyCode; +import javafx.scene.input.KeyEvent; import javafx.scene.layout.Region; +import javafx.scene.layout.StackPane; import javafx.scene.layout.VBox; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; -import java.util.Objects; -import java.util.function.Consumer; +import java.util.*; +import java.util.function.Predicate; +import static javafx.scene.control.ScrollPane.ScrollBarPolicy.AS_NEEDED; import static javafx.scene.layout.Priority.ALWAYS; -class Sidebar extends VBox { +class Sidebar extends StackPane { private static final PseudoClass SELECTED = PseudoClass.getPseudoClass("selected"); + private static final PseudoClass FILTERED = PseudoClass.getPseudoClass("filtered"); + private static final Predicate PREDICATE_ANY = region -> true; - private final FilteredList navigationMenu = navigationMenu(); - private final ReadOnlyObjectWrapper selectedLink = new ReadOnlyObjectWrapper<>(); - private Consumer> navigationHandler; + private final MainModel model; + private final NavMenu navMenu; + private ScrollPane navScroll; - public Sidebar() { + public Sidebar(MainModel model) { super(); - setId("sidebar"); + + this.model = model; + this.navMenu = new NavMenu(model); createView(); - - selectedLink.addListener((obs, old, val) -> { - if (navigationHandler != null) { - navigationHandler.accept(val != null ? val.getPageClass() : null); - } - }); - } - - public void select(Class pageClass) { - navigationMenu.stream() - .filter(region -> region instanceof NavLink link && pageClass.equals(link.getPageClass())) - .findFirst() - .ifPresent(link -> navigate((NavLink) link)); - } - - public void setOnSelect(Consumer> c) { - navigationHandler = c; } private void createView() { + var placeholder = new Label("No content"); + placeholder.getStyleClass().add(Styles.TITLE_4); + var navContainer = new VBox(); navContainer.getStyleClass().add("nav-menu"); - Bindings.bindContent(navContainer.getChildren(), navigationMenu); + Bindings.bindContent(navContainer.getChildren(), navMenu.getContent()); - var navScroll = new ScrollPane(navContainer); - Containers.setScrollConstraints(navScroll, - ScrollPane.ScrollBarPolicy.AS_NEEDED, true, - ScrollPane.ScrollBarPolicy.AS_NEEDED, true - ); + navScroll = new ScrollPane(navContainer); + Containers.setScrollConstraints(navScroll, AS_NEEDED, true, AS_NEEDED, true); VBox.setVgrow(navScroll, ALWAYS); - // == SEARCH FORM == + model.searchTextProperty().addListener((obs, old, val) -> { + var empty = val == null || val.isBlank(); + pseudoClassStateChanged(FILTERED, !empty); + navMenu.setPredicate(empty ? PREDICATE_ANY : region -> region instanceof NavLink link && link.matches(val)); + }); - var searchField = new TextField(); - searchField.setPromptText("Search"); - HBox.setHgrow(searchField, ALWAYS); - searchField.textProperty().addListener((obs, old, val) -> { - if (val == null || val.isBlank()) { - navigationMenu.setPredicate(c -> true); - return; + model.selectedPageProperty().addListener((obs, old, val) -> { + navMenu.findLink(old).ifPresent(link -> link.pseudoClassStateChanged(SELECTED, false)); + navMenu.findLink(val).ifPresent(link -> link.pseudoClassStateChanged(SELECTED, true)); + }); + + navScroll.addEventFilter(KeyEvent.KEY_PRESSED, e -> { + var offset = 1 / (navContainer.getHeight() - navScroll.getViewportBounds().getHeight()); + if (e.getCode() == KeyCode.UP) { + navMenu.getPrevious().ifPresentOrElse(link -> { + navScroll.setVvalue(link.getLayoutY() * offset / 2); + model.navigate(link.getPageClass()); + }, () -> navScroll.setVvalue(0)); + e.consume(); + } + if (e.getCode() == KeyCode.DOWN) { + navMenu.getNext().ifPresentOrElse(link -> { + navScroll.setVvalue(link.getLayoutY() * offset / 2); + model.navigate(link.getPageClass()); + }, () -> navScroll.setVvalue(1.0)); + e.consume(); } - navigationMenu.setPredicate(c -> c instanceof NavLink link && link.matches(val)); }); - var searchForm = new HBox(searchField); - searchForm.setId("search-form"); - searchForm.setAlignment(Pos.CENTER_LEFT); - - // ~ - - getChildren().addAll(searchForm, navScroll); - } - - private Label caption(String text) { - var label = new Label(text); - label.getStyleClass().add("caption"); - label.setMaxWidth(Double.MAX_VALUE); - return label; - } - - private FilteredList navigationMenu() { - return new FilteredList<>(FXCollections.observableArrayList( - caption("GENERAL"), - navLink(ThemePage.NAME, ThemePage.class), - navLink(TypographyPage.NAME, TypographyPage.class), - new Separator(), - caption("COMPONENTS"), - navLink(OverviewPage.NAME, OverviewPage.class), - navLink(InputGroupPage.NAME, InputGroupPage.class), - new Spacer(10, Orientation.VERTICAL), - navLink(AccordionPage.NAME, AccordionPage.class), - navLink(BreadcrumbsPage.NAME, BreadcrumbsPage.class), - navLink(ButtonPage.NAME, ButtonPage.class), - navLink(ChartPage.NAME, ChartPage.class), - navLink(CheckBoxPage.NAME, CheckBoxPage.class), - navLink(ColorPickerPage.NAME, ColorPickerPage.class), - navLink(ComboBoxPage.NAME, ComboBoxPage.class, "ChoiceBox"), - navLink(CustomTextFieldPage.NAME, CustomTextFieldPage.class), - navLink(DatePickerPage.NAME, DatePickerPage.class), - navLink(DialogPage.NAME, DialogPage.class), - navLink(HTMLEditorPage.NAME, HTMLEditorPage.class), - navLink(ListPage.NAME, ListPage.class), - navLink(MenuPage.NAME, MenuPage.class), - navLink(MenuButtonPage.NAME, MenuButtonPage.class, "SplitMenuButton"), - navLink(PaginationPage.NAME, PaginationPage.class), - navLink(PopoverPage.NAME, PopoverPage.class), - navLink(ProgressPage.NAME, ProgressPage.class), - navLink(RadioButtonPage.NAME, RadioButtonPage.class), - navLink(ScrollPanePage.NAME, ScrollPanePage.class), - navLink(SeparatorPage.NAME, SeparatorPage.class), - navLink(SliderPage.NAME, SliderPage.class), - navLink(SpinnerPage.NAME, SpinnerPage.class), - navLink(SplitPanePage.NAME, SplitPanePage.class), - navLink(TablePage.NAME, TablePage.class), - navLink(TabPanePage.NAME, TabPanePage.class), - navLink(TextAreaPage.NAME, TextAreaPage.class), - navLink(TextFieldPage.NAME, TextFieldPage.class, "PasswordField"), - navLink(TitledPanePage.NAME, TitledPanePage.class), - navLink(ToggleButtonPage.NAME, ToggleButtonPage.class), - navLink(ToggleSwitchPage.NAME, ToggleSwitchPage.class), - navLink(ToolBarPage.NAME, ToolBarPage.class), - navLink(TooltipPage.NAME, TooltipPage.class), - navLink(TreePage.NAME, TreePage.class), - navLink(TreeTablePage.NAME, TreeTablePage.class), - caption("SHOWCASE"), - navLink(FileManagerPage.NAME, FileManagerPage.class), - navLink(MusicPlayerPage.NAME, MusicPlayerPage.class) - )); - } - - private NavLink navLink(String text, Class pageClass, String... keywords) { - return navLink(text, pageClass, false, keywords); - } - - @SuppressWarnings("SameParameterValue") - private NavLink navLink(String text, Class pageClass, boolean isNew, String... keywords) { - var link = new NavLink(text, pageClass, isNew); - - if (keywords != null && keywords.length > 0) { - link.getSearchKeywords().addAll(Arrays.asList(keywords)); - } - - link.setOnMouseClicked(e -> { - if (e.getSource() instanceof NavLink dest) { navigate(dest); } + navMenu.getContent().addListener((ListChangeListener) c -> { + if (navMenu.getContent().isEmpty()) { + placeholder.toFront(); + } else { + placeholder.toBack(); + } }); - return link; + setId("sidebar"); + getChildren().addAll(placeholder, navScroll); } - private void navigate(NavLink link) { - if (selectedLink.get() != null) { selectedLink.get().pseudoClassStateChanged(SELECTED, false); } - link.pseudoClassStateChanged(SELECTED, true); - selectedLink.set(link); + void begForFocus() { + navScroll.requestFocus(); } /////////////////////////////////////////////////////////////////////////// + private static class NavMenu { + + private final MainModel model; + private final FilteredList content; + private final Map, NavLink> registry = new HashMap<>(); + + public NavMenu(MainModel model) { + var links = create(); + + this.model = model; + this.content = new FilteredList<>(links); + links.forEach(c -> { + if (c instanceof NavLink link) { + registry.put(link.getPageClass(), link); + } + }); + } + + public FilteredList getContent() { + return content; + } + + public void setPredicate(Predicate predicate) { + content.setPredicate(predicate); + } + + public Optional findLink(Class pageClass) { + if (pageClass == null) { return Optional.empty(); } + return Optional.ofNullable(registry.get(pageClass)); + } + + public Optional getPrevious() { + var current = content.indexOf(registry.get(model.selectedPageProperty().get())); + if (!(current > 0)) { return Optional.empty(); } + + for (int i = current - 1; i >= 0; i--) { + var r = content.get(i); + if (r instanceof NavLink link) { return Optional.of(link); } + } + + return Optional.empty(); + } + + public Optional getNext() { + var current = content.indexOf(registry.get(model.selectedPageProperty().get())); + if (!(current >= 0 && current < content.size() - 1)) { return Optional.empty(); } // has next + + for (int i = current + 1; i < content.size(); i++) { + var r = content.get(i); + if (r instanceof NavLink link) { return Optional.of(link); } + } + + return Optional.empty(); + } + + private ObservableList create() { + return FXCollections.observableArrayList( + caption("GENERAL"), + navLink(ThemePage.NAME, ThemePage.class), + navLink(TypographyPage.NAME, TypographyPage.class), + caption("COMPONENTS"), + navLink(OverviewPage.NAME, OverviewPage.class), + navLink(InputGroupPage.NAME, InputGroupPage.class), + new Spacer(10, Orientation.VERTICAL), + navLink(AccordionPage.NAME, AccordionPage.class), + navLink(BreadcrumbsPage.NAME, BreadcrumbsPage.class), + navLink(ButtonPage.NAME, ButtonPage.class), + navLink(ChartPage.NAME, ChartPage.class), + navLink(CheckBoxPage.NAME, CheckBoxPage.class), + navLink(ColorPickerPage.NAME, ColorPickerPage.class), + navLink(ComboBoxPage.NAME, ComboBoxPage.class, "ChoiceBox"), + navLink(CustomTextFieldPage.NAME, CustomTextFieldPage.class), + navLink(DatePickerPage.NAME, DatePickerPage.class), + navLink(DialogPage.NAME, DialogPage.class), + navLink(HTMLEditorPage.NAME, HTMLEditorPage.class), + navLink(ListPage.NAME, ListPage.class), + navLink(MenuPage.NAME, MenuPage.class), + navLink(MenuButtonPage.NAME, MenuButtonPage.class, "SplitMenuButton"), + navLink(PaginationPage.NAME, PaginationPage.class), + navLink(PopoverPage.NAME, PopoverPage.class), + navLink(ProgressPage.NAME, ProgressPage.class), + navLink(RadioButtonPage.NAME, RadioButtonPage.class), + navLink(ScrollPanePage.NAME, ScrollPanePage.class), + navLink(SeparatorPage.NAME, SeparatorPage.class), + navLink(SliderPage.NAME, SliderPage.class), + navLink(SpinnerPage.NAME, SpinnerPage.class), + navLink(SplitPanePage.NAME, SplitPanePage.class), + navLink(TablePage.NAME, TablePage.class), + navLink(TabPanePage.NAME, TabPanePage.class), + navLink(TextAreaPage.NAME, TextAreaPage.class), + navLink(TextFieldPage.NAME, TextFieldPage.class, "PasswordField"), + navLink(TitledPanePage.NAME, TitledPanePage.class), + navLink(ToggleButtonPage.NAME, ToggleButtonPage.class), + navLink(ToggleSwitchPage.NAME, ToggleSwitchPage.class), + navLink(ToolBarPage.NAME, ToolBarPage.class), + navLink(TooltipPage.NAME, TooltipPage.class), + navLink(TreePage.NAME, TreePage.class), + navLink(TreeTablePage.NAME, TreeTablePage.class), + caption("SHOWCASE"), + navLink(FileManagerPage.NAME, FileManagerPage.class), + navLink(MusicPlayerPage.NAME, MusicPlayerPage.class) + ); + } + + private Label caption(String text) { + var label = new Label(text); + label.getStyleClass().add("caption"); + label.setMaxWidth(Double.MAX_VALUE); + return label; + } + + private NavLink navLink(String text, Class pageClass, String... keywords) { + var link = new NavLink(text, pageClass); + + if (keywords != null && keywords.length > 0) { + link.getSearchKeywords().addAll(Arrays.asList(keywords)); + } + + link.setOnMouseClicked(e -> { + if (e.getSource() instanceof NavLink target) { + model.navigate(target.getPageClass()); + } + }); + + return link; + } + } + private static class NavLink extends Label { private final Class pageClass; private final List searchKeywords = new ArrayList<>(); - public NavLink(String text, Class pageClass, boolean isNew) { + public NavLink(String text, Class pageClass) { super(Objects.requireNonNull(text)); this.pageClass = Objects.requireNonNull(pageClass); getStyleClass().add("nav-link"); setMaxWidth(Double.MAX_VALUE); - setContentDisplay(ContentDisplay.RIGHT); - - if (isNew) { - var tag = new Label("new"); - tag.getStyleClass().addAll("tag", Styles.TEXT_SMALL); - setGraphic(tag); - } } public Class getPageClass() { diff --git a/sampler/src/main/java/atlantafx/sampler/page/AbstractPage.java b/sampler/src/main/java/atlantafx/sampler/page/AbstractPage.java index ef83909..ba365dd 100755 --- a/sampler/src/main/java/atlantafx/sampler/page/AbstractPage.java +++ b/sampler/src/main/java/atlantafx/sampler/page/AbstractPage.java @@ -1,55 +1,32 @@ /* 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.event.DefaultEventBus; -import atlantafx.sampler.event.ThemeEvent; import atlantafx.sampler.layout.Overlay; -import atlantafx.sampler.theme.ThemeManager; import javafx.event.ActionEvent; import javafx.event.EventHandler; -import javafx.geometry.Pos; -import javafx.scene.control.Button; -import javafx.scene.control.Label; import javafx.scene.control.ScrollPane; -import javafx.scene.control.Tooltip; -import javafx.scene.layout.*; +import javafx.scene.layout.BorderPane; +import javafx.scene.layout.Pane; +import javafx.scene.layout.StackPane; +import javafx.scene.layout.VBox; 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; -import java.util.Objects; import java.util.Random; 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.*; -import static atlantafx.sampler.Launcher.IS_DEV_MODE; import static atlantafx.sampler.util.Containers.setScrollConstraints; +import static javafx.scene.control.ScrollPane.ScrollBarPolicy.AS_NEEDED; public abstract class AbstractPage extends BorderPane implements Page { - protected static final int HEADER_HEIGHT = 50; - protected static final Faker FAKER = new Faker(); protected static final Random RANDOM = new Random(); protected static final EventHandler PRINT_SOURCE = System.out::println; - 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; protected VBox userContent; protected Overlay overlay; protected boolean isRendered = false; @@ -59,50 +36,9 @@ public abstract class AbstractPage extends BorderPane implements Page { getStyleClass().add("page"); createPageLayout(); - - // update code view color theme on app theme change - DefaultEventBus.getInstance().subscribe(ThemeEvent.class, e -> { - if (ThemeManager.getInstance().getTheme() != null && isCodeViewActive()) { - loadSourceCodeAndMoveToFront(); - } - }); } protected void createPageLayout() { - // == header == - - var titleLabel = new Label(getName()); - titleLabel.getStyleClass().addAll(Styles.TITLE_4); - - codeViewer = new CodeViewer(); - - codeViewerWrapper = new StackPane(); - 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, FLAT); - sourceCodeToggleBtn.setTooltip(new Tooltip("Source code")); - sourceCodeToggleBtn.setOnAction(e -> toggleSourceCode()); - - var header = new HBox(30); - header.getStyleClass().add("header"); - header.setMinHeight(HEADER_HEIGHT); - header.setAlignment(Pos.CENTER_LEFT); - header.getChildren().setAll(titleLabel, new Spacer(), quickConfigBtn, sourceCodeToggleBtn); - if (IS_DEV_MODE) { - var devModeLabel = new Label("App is running in development mode"); - devModeLabel.getStyleClass().addAll(TEXT_SMALL, "dev-mode-indicator"); - header.getChildren().add(1, devModeLabel); - } - - // == user content == - userContent = new VBox(); userContent.getStyleClass().add("user-content"); @@ -111,20 +47,10 @@ public abstract class AbstractPage extends BorderPane implements Page { userContentWrapper.getChildren().setAll(userContent); var scrollPane = new ScrollPane(userContentWrapper); - setScrollConstraints(scrollPane, - ScrollPane.ScrollBarPolicy.AS_NEEDED, true, - ScrollPane.ScrollBarPolicy.AS_NEEDED, true - ); + setScrollConstraints(scrollPane, AS_NEEDED, true, AS_NEEDED, true); scrollPane.setMaxHeight(10_000); - // == layout == - - var stackPane = new StackPane(); - stackPane.getStyleClass().add("stack"); - stackPane.getChildren().addAll(codeViewerWrapper, scrollPane); - - setTop(header); - setCenter(stackPane); + setCenter(scrollPane); } @Override @@ -132,6 +58,16 @@ public abstract class AbstractPage extends BorderPane implements Page { return this; } + @Override + public boolean canDisplaySourceCode() { + return true; + } + + @Override + public boolean canChangeThemeSettings() { + return true; + } + @Override public void reset() { } @@ -150,65 +86,9 @@ public abstract class AbstractPage extends BorderPane implements Page { } protected Overlay lookupOverlay() { - return getScene() != null && getScene().lookup("." + Overlay.STYLE_CLASS) instanceof Overlay overlay ? - overlay : null; + return getScene() != null && getScene().lookup("." + Overlay.STYLE_CLASS) instanceof Overlay overlay ? overlay : null; } - protected 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.setOnShowing(e -> content.update()); - } - - quickConfigPopover.show(quickConfigBtn); - } - - protected void toggleSourceCode() { - if (isCodeViewActive()) { - codeViewerWrapper.toBack(); - ((FontIcon) sourceCodeToggleBtn.getGraphic()).setIconCode(ICON_CODE); - return; - } - - loadSourceCodeAndMoveToFront(); - } - - protected void loadSourceCodeAndMoveToFront() { - var sourceFileName = getClass().getSimpleName() + ".java"; - try (var stream = getClass().getResourceAsStream(sourceFileName)) { - Objects.requireNonNull(stream, "Missing source file '" + sourceFileName + "';"); - - // set syntax highlight theme according to JavaFX theme - ThemeManager tm = ThemeManager.getInstance(); - codeViewer.setContent(stream, tm.getMatchingSourceCodeHighlightTheme(tm.getTheme())); - - var graphic = (FontIcon) sourceCodeToggleBtn.getGraphic(); - graphic.setIconCode(ICON_SAMPLE); - - codeViewerWrapper.toFront(); - } catch (IOException e) { - throw new RuntimeException(e); - } - } - - protected final boolean isSampleViewActive() { - var graphic = (FontIcon) sourceCodeToggleBtn.getGraphic(); - return graphic.getIconCode() == ICON_CODE; - } - - protected final boolean isCodeViewActive() { - var graphic = (FontIcon) sourceCodeToggleBtn.getGraphic(); - return graphic.getIconCode() == ICON_SAMPLE; - } - - /////////////////////////////////////////////////////////////////////////// - // Helpers // /////////////////////////////////////////////////////////////////////////// protected List generate(Supplier supplier, int count) { diff --git a/sampler/src/main/java/atlantafx/sampler/page/Page.java b/sampler/src/main/java/atlantafx/sampler/page/Page.java index e84a1b7..ba3da5f 100755 --- a/sampler/src/main/java/atlantafx/sampler/page/Page.java +++ b/sampler/src/main/java/atlantafx/sampler/page/Page.java @@ -9,5 +9,9 @@ public interface Page { Parent getView(); + boolean canDisplaySourceCode(); + + boolean canChangeThemeSettings(); + void reset(); } 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 716c2c8..d4c245e 100755 --- a/sampler/src/main/java/atlantafx/sampler/page/general/ThemePage.java +++ b/sampler/src/main/java/atlantafx/sampler/page/general/ThemePage.java @@ -7,7 +7,6 @@ import atlantafx.sampler.event.ThemeEvent; import atlantafx.sampler.page.AbstractPage; import atlantafx.sampler.theme.SamplerTheme; import atlantafx.sampler.theme.ThemeManager; -import atlantafx.sampler.util.NodeUtils; import javafx.geometry.HPos; import javafx.scene.control.Button; import javafx.scene.control.ChoiceBox; @@ -53,7 +52,19 @@ public class ThemePage extends AbstractPage { private ContrastCheckerDialog contrastCheckerDialog; @Override - public String getName() { return NAME; } + public String getName() { + return NAME; + } + + @Override + public boolean canDisplaySourceCode() { + return false; + } + + @Override + public boolean canChangeThemeSettings() { + return false; + } public ThemePage() { super(); @@ -81,7 +92,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.") ); @@ -94,11 +105,6 @@ public class ThemePage extends AbstractPage { ); selectCurrentTheme(); - - // if you want to enable quick menu don't forget that - // theme selection choice box have to be updated accordingly - NodeUtils.toggleVisibility(quickConfigBtn, false); - NodeUtils.toggleVisibility(sourceCodeToggleBtn, false); } private GridPane optionsGrid() { diff --git a/sampler/src/main/java/atlantafx/sampler/page/general/TypographyPage.java b/sampler/src/main/java/atlantafx/sampler/page/general/TypographyPage.java index 6c84ba6..77de1be 100755 --- a/sampler/src/main/java/atlantafx/sampler/page/general/TypographyPage.java +++ b/sampler/src/main/java/atlantafx/sampler/page/general/TypographyPage.java @@ -38,7 +38,19 @@ public class TypographyPage extends AbstractPage { public static final String NAME = "Typography"; @Override - public String getName() { return NAME; } + public String getName() { + return NAME; + } + + @Override + public boolean canDisplaySourceCode() { + return false; + } + + @Override + public boolean canChangeThemeSettings() { + return false; + } private Pane fontSizeSampleContent; @@ -74,10 +86,6 @@ public class TypographyPage extends AbstractPage { textColorSample().getRoot(), textFlowSample().getRoot() ); - // if you want to enable quick menu don't forget that - // font size spinner value have to be updated accordingly - NodeUtils.toggleVisibility(quickConfigBtn, false); - NodeUtils.toggleVisibility(sourceCodeToggleBtn, false); } private ComboBox fontFamilyChooser() { diff --git a/sampler/src/main/java/atlantafx/sampler/page/showcase/ShowcasePage.java b/sampler/src/main/java/atlantafx/sampler/page/showcase/ShowcasePage.java index f62b8b1..65fe562 100644 --- a/sampler/src/main/java/atlantafx/sampler/page/showcase/ShowcasePage.java +++ b/sampler/src/main/java/atlantafx/sampler/page/showcase/ShowcasePage.java @@ -29,6 +29,11 @@ public abstract class ShowcasePage extends AbstractPage { createShowcaseLayout(); } + @Override + public boolean canDisplaySourceCode() { + return false; + } + protected void createShowcaseLayout() { var expandBtn = new Button("Expand"); expandBtn.setGraphic(new FontIcon(Feather.MAXIMIZE_2)); @@ -59,9 +64,6 @@ public abstract class ShowcasePage extends AbstractPage { collapseBox.setVisible(false); collapseBox.setManaged(false); - sourceCodeToggleBtn.setVisible(false); - sourceCodeToggleBtn.setManaged(false); - userContent.getChildren().setAll(showcase, expandBox, collapseBox); } diff --git a/sampler/src/main/resources/application.properties b/sampler/src/main/resources/application.properties index 8a0318d..b92b245 100755 --- a/sampler/src/main/resources/application.properties +++ b/sampler/src/main/resources/application.properties @@ -1,4 +1,3 @@ app.name=AtlantaFX Sampler -app.description=${project.description} -app.homepage=${project.url} +app.homepage=${project.parent.url} app.version=${project.version} diff --git a/sampler/src/main/resources/assets/styles/scss/general/_root.scss b/sampler/src/main/resources/assets/styles/scss/general/_root.scss index e51cab5..7ba43c3 100644 --- a/sampler/src/main/resources/assets/styles/scss/general/_root.scss +++ b/sampler/src/main/resources/assets/styles/scss/general/_root.scss @@ -22,11 +22,8 @@ } .page { - >.header { - @include hide(); - } - >.stack>.scroll-pane>.viewport>*>.wrapper>.user-content { + >.scroll-pane>.viewport>*>.wrapper>.user-content { -fx-max-width: 4096px; -fx-padding: 0; -fx-spacing: 0; diff --git a/sampler/src/main/resources/assets/styles/scss/layout/_index.scss b/sampler/src/main/resources/assets/styles/scss/layout/_index.scss index 93186fc..ea258ff 100644 --- a/sampler/src/main/resources/assets/styles/scss/layout/_index.scss +++ b/sampler/src/main/resources/assets/styles/scss/layout/_index.scss @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT -@use "sidebar"; +@use "main"; +@use "overlay"; @use "page"; -@use "components"; -@use "overlay"; \ No newline at end of file +@use "components"; \ No newline at end of file diff --git a/sampler/src/main/resources/assets/styles/scss/layout/_main.scss b/sampler/src/main/resources/assets/styles/scss/layout/_main.scss new file mode 100644 index 0000000..b926e39 --- /dev/null +++ b/sampler/src/main/resources/assets/styles/scss/layout/_main.scss @@ -0,0 +1,125 @@ +@mixin link-button() { + -fx-icon-color: -color-fg-emphasis; + -fx-fill: -color-fg-emphasis; + -fx-icon-size: 20px; + + &:hover { + -fx-opacity: 0.75; + } +} + +#main { + .source-code { + -fx-background-color: -color-bg-default; + -fx-alignment: TOP_CENTER; + + >.code-viewer { + -fx-background-color: transparent; + -fx-padding: 0 0 0 20px; + -fx-max-width: 1000px; + + .web-view { + -fx-padding: 0; + -fx-background-color: transparent; + } + } + } +} + +#header-bar { + -fx-background-color: -color-accent-emphasis; + -fx-padding: 0px 30px 0px 0; + -fx-spacing: 30px; + + >.logo { + -fx-padding: 0px 0 0px 20px; + + >.label { + -fx-text-fill: -color-fg-emphasis; + } + + >.version { + -fx-padding: -1em 0 0 0; + } + + >.ikonli-font-icon { + @include link-button(); + } + } + + >.text-field { + -fx-background-insets: 0; + -fx-padding: 0; + -fx-opacity: 0.85; + + -color-input-bg: -color-accent-emphasis; + -color-input-bg-focused: -color-accent-emphasis; + -color-input-fg: -color-fg-emphasis; + -color-input-border: -color-fg-emphasis; + -color-input-border-focused: -color-fg-emphasis; + -fx-prompt-text-fill: -color-fg-emphasis; + + .ikonli-font-icon { + -fx-icon-color: -color-fg-emphasis; + -fx-fill: -color-fg-emphasis; + } + } + + >.page-title { + -fx-text-fill: -color-fg-emphasis; + -fx-padding: 0 0 0 30px; + } + + >.ikonli-font-icon { + @include link-button(); + + &:disabled { + -fx-opacity: 0.1; + } + } + + >.dev-mode-indicator { + -fx-background-color: -color-warning-emphasis; + -fx-text-fill: -color-fg-emphasis; + -fx-background-radius: 10px; + -fx-padding: 5px 10px 5px 10px; + } +} + +#sidebar { + -fx-border-color: -color-border-default; + -fx-border-width: 0 1px 0 0; + + >.scroll-pane { + -fx-background-color: -color-bg-inset; + -fx-padding: 0 16px 10px 16px; + } + + &:filtered { + >.scroll-pane { + -fx-padding: 10px 16px 10px 16px; + } + } + + .nav-menu { + >.caption { + -fx-padding: 18px 0 10px 0; + -fx-font-weight: bold; + -fx-text-fill: -color-fg-muted; + } + + >.nav-link { + -fx-padding: 6px 8px 6px 8px; + + &:hover { + -fx-background-color: -color-accent-muted; + -fx-background-radius: 6px; + } + + &:selected { + -fx-text-fill: -color-accent-fg; + -fx-font-weight: bold; + } + } + } +} \ No newline at end of file diff --git a/sampler/src/main/resources/assets/styles/scss/layout/_page.scss b/sampler/src/main/resources/assets/styles/scss/layout/_page.scss index cd31aa3..a93132c 100644 --- a/sampler/src/main/resources/assets/styles/scss/layout/_page.scss +++ b/sampler/src/main/resources/assets/styles/scss/layout/_page.scss @@ -1,51 +1,18 @@ // SPDX-License-Identifier: MIT .page { - >.header { - -fx-padding: 10px 20px 14px 20px; - -fx-spacing: 10px; + + >.scroll-pane { -fx-background-color: -color-bg-default; - >.dev-mode-indicator { - -fx-background-color: -color-warning-subtle; - -fx-background-radius: 10px; - -fx-text-fill: -color-warning-fg; - -fx-padding: 5px; - -fx-border-color: -color-warning-muted; - -fx-border-width: 1px; - -fx-border-radius: 10px; - } - } - - >.stack { - >.scroll-pane { - -fx-background-color: -color-bg-default; - - /* wrapper is used to center the content and also guarantees some minimum paddings via min-width */ - >.viewport>*>.wrapper { - -fx-min-width: 880px; - -fx-alignment: TOP_CENTER; - - >.user-content { - -fx-padding: 40px 0 40px 0; - -fx-spacing: 40px; - -fx-max-width: 800px; - } - } - } - - >.wrapper { - -fx-background-color: -color-bg-default; + >.viewport>*>.wrapper { + -fx-min-width: 880px; -fx-alignment: TOP_CENTER; - >.code-viewer { - -fx-background-color: transparent; - -fx-padding: 0px 0px 20px 20px; - -fx-max-width: 1000px; - - >.web-view { - -fx-background-color: transparent; - } + >.user-content { + -fx-padding: 40px 0 40px 0; + -fx-spacing: 40px; + -fx-max-width: 800px; } } } diff --git a/sampler/src/main/resources/assets/styles/scss/layout/_sidebar.scss b/sampler/src/main/resources/assets/styles/scss/layout/_sidebar.scss deleted file mode 100644 index 646ee53..0000000 --- a/sampler/src/main/resources/assets/styles/scss/layout/_sidebar.scss +++ /dev/null @@ -1,45 +0,0 @@ -// SPDX-License-Identifier: MIT - -#sidebar { - -fx-padding: 0 0 12px 0; - -fx-background-color: -color-bg-inset; - -fx-border-color: -color-border-default; - -fx-border-width: 0 1px 0 0; - - #search-form { - -fx-padding: 12px; - -fx-border-color: -color-border-muted; - -fx-border-width: 0 0 1px 0; - } - - .nav-menu { - -fx-padding: 0 6px 0 6px; - - >.caption { - -fx-padding: 10px 0 10px 6px; - -fx-font-weight: bold; - -fx-text-fill: -color-fg-muted; - } - - >.nav-link { - -fx-padding: 6px 12px 6px 12px; - - >.tag { - -fx-background-color: -color-accent-emphasis; - -fx-text-fill: -color-fg-emphasis; - -fx-padding: 2px; - -fx-background-radius: 4px; - } - - &:hover { - -fx-background-color: -color-accent-muted; - -fx-background-radius: 6px; - } - - &:selected { - -fx-text-fill: -color-accent-fg; - -fx-font-weight: bold; - } - } - } -} \ No newline at end of file