Rewrite sidebar to TreeView
This commit is contained in:
parent
188ee75986
commit
6584c53906
@ -28,7 +28,6 @@ import javafx.scene.SceneAntialiasing;
|
||||
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;
|
||||
|
||||
@ -39,7 +38,7 @@ public class Launcher extends Application {
|
||||
);
|
||||
|
||||
public static final List<KeyCodeCombination> SUPPORTED_HOTKEYS = List.of(
|
||||
new KeyCodeCombination(KeyCode.F, KeyCombination.CONTROL_DOWN)
|
||||
new KeyCodeCombination(KeyCode.SLASH)
|
||||
);
|
||||
|
||||
public static void main(String[] args) {
|
||||
|
@ -8,26 +8,30 @@ import static atlantafx.base.theme.Styles.TITLE_4;
|
||||
import static atlantafx.sampler.Launcher.IS_DEV_MODE;
|
||||
import static atlantafx.sampler.layout.MainLayer.SIDEBAR_WIDTH;
|
||||
|
||||
import atlantafx.base.controls.CustomTextField;
|
||||
import atlantafx.base.controls.Spacer;
|
||||
import atlantafx.sampler.Resources;
|
||||
import atlantafx.sampler.event.BrowseEvent;
|
||||
import atlantafx.sampler.event.DefaultEventBus;
|
||||
import atlantafx.sampler.event.HotkeyEvent;
|
||||
import java.net.URI;
|
||||
import java.util.Objects;
|
||||
import java.util.function.Consumer;
|
||||
import javafx.application.Platform;
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.geometry.HPos;
|
||||
import javafx.geometry.Insets;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.control.Button;
|
||||
import javafx.scene.control.ContentDisplay;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.image.Image;
|
||||
import javafx.scene.image.ImageView;
|
||||
import javafx.scene.input.KeyCode;
|
||||
import javafx.scene.input.KeyCombination.ModifierValue;
|
||||
import javafx.scene.layout.HBox;
|
||||
import javafx.scene.layout.Region;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import javafx.scene.text.Text;
|
||||
import org.kordamp.ikonli.Ikon;
|
||||
import org.kordamp.ikonli.feather.Feather;
|
||||
import org.kordamp.ikonli.javafx.FontIcon;
|
||||
@ -42,10 +46,14 @@ class HeaderBar extends HBox {
|
||||
|
||||
private final MainModel model;
|
||||
private Consumer<Node> quickConfigActionHandler;
|
||||
private Overlay overlay;
|
||||
private SearchDialog searchDialog;
|
||||
|
||||
public HeaderBar(MainModel model) {
|
||||
super();
|
||||
|
||||
this.model = model;
|
||||
|
||||
createView();
|
||||
}
|
||||
|
||||
@ -80,14 +88,24 @@ class HeaderBar extends HBox {
|
||||
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());
|
||||
var searchBox = new HBox(10,
|
||||
new FontIcon(Material2MZ.SEARCH),
|
||||
new Text("Search"),
|
||||
new Spacer(5),
|
||||
new Label("/")
|
||||
);
|
||||
searchBox.getStyleClass().add("box");
|
||||
searchBox.setAlignment(Pos.CENTER_LEFT);
|
||||
|
||||
var searchButton = new Button();
|
||||
searchButton.setContentDisplay(ContentDisplay.GRAPHIC_ONLY);
|
||||
searchButton.getStyleClass().add("search-button");
|
||||
searchButton.setGraphic(searchBox);
|
||||
searchButton.setOnAction(e -> openSearchDialog());
|
||||
|
||||
DefaultEventBus.getInstance().subscribe(HotkeyEvent.class, e -> {
|
||||
if (e.getKeys().getControl() == ModifierValue.DOWN && e.getKeys().getCode() == KeyCode.F) {
|
||||
searchField.requestFocus();
|
||||
if (e.getKeys().getCode() == KeyCode.SLASH) {
|
||||
openSearchDialog();
|
||||
}
|
||||
});
|
||||
|
||||
@ -139,7 +157,7 @@ class HeaderBar extends HBox {
|
||||
logoBox,
|
||||
titleLabel,
|
||||
new Spacer(),
|
||||
searchField,
|
||||
searchButton,
|
||||
popoverAnchor,
|
||||
quickConfigBtn,
|
||||
sourceCodeBtn,
|
||||
@ -156,4 +174,26 @@ class HeaderBar extends HBox {
|
||||
void setQuickConfigActionHandler(Consumer<Node> handler) {
|
||||
this.quickConfigActionHandler = handler;
|
||||
}
|
||||
|
||||
private Overlay lookupOverlay() {
|
||||
return Objects.requireNonNullElse(overlay,
|
||||
overlay = getScene() != null && getScene().lookup("." + Overlay.STYLE_CLASS) instanceof Overlay o ? o : null
|
||||
);
|
||||
}
|
||||
|
||||
private void openSearchDialog() {
|
||||
if (searchDialog == null) {
|
||||
searchDialog = new SearchDialog(model);
|
||||
searchDialog.setOnCloseRequest(() -> {
|
||||
var overlay = lookupOverlay();
|
||||
overlay.removeContent();
|
||||
overlay.toBack();
|
||||
});
|
||||
}
|
||||
|
||||
var overlay = lookupOverlay();
|
||||
overlay.setContent(searchDialog, HPos.CENTER);
|
||||
overlay.toFront();
|
||||
Platform.runLater(() -> searchDialog.begForFocus());
|
||||
}
|
||||
}
|
||||
|
@ -12,7 +12,6 @@ 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 java.io.IOException;
|
||||
import java.util.Objects;
|
||||
@ -27,7 +26,7 @@ import javafx.util.Duration;
|
||||
|
||||
class MainLayer extends BorderPane {
|
||||
|
||||
static final int SIDEBAR_WIDTH = 220;
|
||||
static final int SIDEBAR_WIDTH = 250;
|
||||
static final int PAGE_TRANSITION_DURATION = 500; // ms
|
||||
|
||||
private final MainModel model = new MainModel();
|
||||
@ -45,13 +44,15 @@ class MainLayer extends BorderPane {
|
||||
createView();
|
||||
initListeners();
|
||||
|
||||
model.navigate(OverviewPage.class);
|
||||
model.navigate(MainModel.DEFAULT_PAGE);
|
||||
|
||||
// keyboard navigation won't work without focus
|
||||
Platform.runLater(sidebar::begForFocus);
|
||||
}
|
||||
|
||||
private void createView() {
|
||||
sidebar.setMinWidth(SIDEBAR_WIDTH);
|
||||
sidebar.setMaxWidth(SIDEBAR_WIDTH);
|
||||
|
||||
codeViewer = new CodeViewer();
|
||||
|
||||
|
@ -6,6 +6,52 @@ import static atlantafx.sampler.layout.MainModel.SubLayer.PAGE;
|
||||
import static atlantafx.sampler.layout.MainModel.SubLayer.SOURCE_CODE;
|
||||
|
||||
import atlantafx.sampler.page.Page;
|
||||
import atlantafx.sampler.page.components.AccordionPage;
|
||||
import atlantafx.sampler.page.components.BreadcrumbsPage;
|
||||
import atlantafx.sampler.page.components.ButtonPage;
|
||||
import atlantafx.sampler.page.components.ChartPage;
|
||||
import atlantafx.sampler.page.components.CheckBoxPage;
|
||||
import atlantafx.sampler.page.components.ColorPickerPage;
|
||||
import atlantafx.sampler.page.components.ComboBoxPage;
|
||||
import atlantafx.sampler.page.components.CustomTextFieldPage;
|
||||
import atlantafx.sampler.page.components.DatePickerPage;
|
||||
import atlantafx.sampler.page.components.DialogPage;
|
||||
import atlantafx.sampler.page.components.HtmlEditorPage;
|
||||
import atlantafx.sampler.page.components.InputGroupPage;
|
||||
import atlantafx.sampler.page.components.LabelPage;
|
||||
import atlantafx.sampler.page.components.ListPage;
|
||||
import atlantafx.sampler.page.components.MenuButtonPage;
|
||||
import atlantafx.sampler.page.components.MenuPage;
|
||||
import atlantafx.sampler.page.components.OverviewPage;
|
||||
import atlantafx.sampler.page.components.PaginationPage;
|
||||
import atlantafx.sampler.page.components.PopoverPage;
|
||||
import atlantafx.sampler.page.components.ProgressPage;
|
||||
import atlantafx.sampler.page.components.RadioButtonPage;
|
||||
import atlantafx.sampler.page.components.ScrollPanePage;
|
||||
import atlantafx.sampler.page.components.SeparatorPage;
|
||||
import atlantafx.sampler.page.components.SliderPage;
|
||||
import atlantafx.sampler.page.components.SpinnerPage;
|
||||
import atlantafx.sampler.page.components.SplitPanePage;
|
||||
import atlantafx.sampler.page.components.TabPanePage;
|
||||
import atlantafx.sampler.page.components.TablePage;
|
||||
import atlantafx.sampler.page.components.TextAreaPage;
|
||||
import atlantafx.sampler.page.components.TextFieldPage;
|
||||
import atlantafx.sampler.page.components.TitledPanePage;
|
||||
import atlantafx.sampler.page.components.ToggleButtonPage;
|
||||
import atlantafx.sampler.page.components.ToggleSwitchPage;
|
||||
import atlantafx.sampler.page.components.ToolBarPage;
|
||||
import atlantafx.sampler.page.components.TooltipPage;
|
||||
import atlantafx.sampler.page.components.TreePage;
|
||||
import atlantafx.sampler.page.components.TreeTablePage;
|
||||
import atlantafx.sampler.page.general.IconsPage;
|
||||
import atlantafx.sampler.page.general.ThemePage;
|
||||
import atlantafx.sampler.page.general.TypographyPage;
|
||||
import atlantafx.sampler.page.showcase.filemanager.FileManagerPage;
|
||||
import atlantafx.sampler.page.showcase.musicplayer.MusicPlayerPage;
|
||||
import atlantafx.sampler.page.showcase.widget.WidgetCollectionPage;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import javafx.beans.property.ReadOnlyBooleanProperty;
|
||||
import javafx.beans.property.ReadOnlyBooleanWrapper;
|
||||
@ -15,49 +61,221 @@ import javafx.beans.property.ReadOnlyStringProperty;
|
||||
import javafx.beans.property.ReadOnlyStringWrapper;
|
||||
import javafx.beans.property.SimpleStringProperty;
|
||||
import javafx.beans.property.StringProperty;
|
||||
import org.kordamp.ikonli.javafx.FontIcon;
|
||||
import org.kordamp.ikonli.material2.Material2OutlinedAL;
|
||||
import org.kordamp.ikonli.material2.Material2OutlinedMZ;
|
||||
|
||||
public class MainModel {
|
||||
|
||||
public static Class<? extends Page> DEFAULT_PAGE = OverviewPage.class;
|
||||
|
||||
private static final Map<Class<? extends Page>, NavTree.Item> NAV_TREE = createNavItems();
|
||||
|
||||
public enum SubLayer {
|
||||
PAGE,
|
||||
SOURCE_CODE
|
||||
}
|
||||
|
||||
private final ReadOnlyStringWrapper title = new ReadOnlyStringWrapper();
|
||||
private final StringProperty searchText = new SimpleStringProperty();
|
||||
private final ReadOnlyObjectWrapper<Class<? extends Page>> selectedPage = new ReadOnlyObjectWrapper<>();
|
||||
private final ReadOnlyBooleanWrapper themeChangeToggle = new ReadOnlyBooleanWrapper();
|
||||
private final ReadOnlyBooleanWrapper sourceCodeToggle = new ReadOnlyBooleanWrapper();
|
||||
private final ReadOnlyObjectWrapper<SubLayer> currentSubLayer = new ReadOnlyObjectWrapper<>(PAGE);
|
||||
NavTree.Item getTreeItemForPage(Class<? extends Page> pageClass) {
|
||||
return NAV_TREE.getOrDefault(pageClass, NAV_TREE.get(DEFAULT_PAGE));
|
||||
}
|
||||
|
||||
List<NavTree.Item> findPages(String filter) {
|
||||
return NAV_TREE.values().stream()
|
||||
.filter(item -> item.getValue() != null && item.getValue().matches(filter))
|
||||
.toList();
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
// Properties //
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
|
||||
private final StringProperty searchText = new SimpleStringProperty();
|
||||
|
||||
public StringProperty searchTextProperty() {
|
||||
return searchText;
|
||||
}
|
||||
|
||||
// ~
|
||||
private final ReadOnlyStringWrapper title = new ReadOnlyStringWrapper();
|
||||
|
||||
public ReadOnlyStringProperty titleProperty() {
|
||||
return title.getReadOnlyProperty();
|
||||
}
|
||||
|
||||
// ~
|
||||
private final ReadOnlyBooleanWrapper themeChangeToggle = new ReadOnlyBooleanWrapper();
|
||||
|
||||
public ReadOnlyBooleanProperty themeChangeToggleProperty() {
|
||||
return themeChangeToggle.getReadOnlyProperty();
|
||||
}
|
||||
|
||||
// ~
|
||||
private final ReadOnlyBooleanWrapper sourceCodeToggle = new ReadOnlyBooleanWrapper();
|
||||
|
||||
public ReadOnlyBooleanProperty sourceCodeToggleProperty() {
|
||||
return sourceCodeToggle.getReadOnlyProperty();
|
||||
}
|
||||
|
||||
// ~
|
||||
private final ReadOnlyObjectWrapper<Class<? extends Page>> selectedPage = new ReadOnlyObjectWrapper<>();
|
||||
|
||||
public ReadOnlyObjectProperty<Class<? extends Page>> selectedPageProperty() {
|
||||
return selectedPage.getReadOnlyProperty();
|
||||
}
|
||||
|
||||
// ~
|
||||
private final ReadOnlyObjectWrapper<SubLayer> currentSubLayer = new ReadOnlyObjectWrapper<>(PAGE);
|
||||
|
||||
public ReadOnlyObjectProperty<SubLayer> currentSubLayerProperty() {
|
||||
return currentSubLayer.getReadOnlyProperty();
|
||||
}
|
||||
|
||||
// ~
|
||||
private final ReadOnlyObjectWrapper<NavTree.Item> navTree = new ReadOnlyObjectWrapper<>(createTree());
|
||||
|
||||
public ReadOnlyObjectProperty<NavTree.Item> navTreeProperty() {
|
||||
return navTree.getReadOnlyProperty();
|
||||
}
|
||||
|
||||
private NavTree.Item createTree() {
|
||||
var general = NavTree.Item.group("General", new FontIcon(Material2OutlinedAL.ARTICLE));
|
||||
general.getChildren().setAll(
|
||||
NAV_TREE.get(OverviewPage.class),
|
||||
NAV_TREE.get(ThemePage.class),
|
||||
NAV_TREE.get(TypographyPage.class),
|
||||
NAV_TREE.get(IconsPage.class)
|
||||
);
|
||||
general.setExpanded(true);
|
||||
|
||||
var components = NavTree.Item.group("Standard Controls", new FontIcon(Material2OutlinedAL.DASHBOARD));
|
||||
components.getChildren().setAll(
|
||||
NAV_TREE.get(AccordionPage.class),
|
||||
NAV_TREE.get(ButtonPage.class),
|
||||
NAV_TREE.get(ChartPage.class),
|
||||
NAV_TREE.get(CheckBoxPage.class),
|
||||
NAV_TREE.get(ColorPickerPage.class),
|
||||
NAV_TREE.get(ComboBoxPage.class),
|
||||
NAV_TREE.get(DatePickerPage.class),
|
||||
NAV_TREE.get(DialogPage.class),
|
||||
NAV_TREE.get(HtmlEditorPage.class),
|
||||
NAV_TREE.get(LabelPage.class),
|
||||
NAV_TREE.get(ListPage.class),
|
||||
NAV_TREE.get(MenuPage.class),
|
||||
NAV_TREE.get(MenuButtonPage.class),
|
||||
NAV_TREE.get(PaginationPage.class),
|
||||
NAV_TREE.get(ProgressPage.class),
|
||||
NAV_TREE.get(RadioButtonPage.class),
|
||||
NAV_TREE.get(ScrollPanePage.class),
|
||||
NAV_TREE.get(SeparatorPage.class),
|
||||
NAV_TREE.get(SliderPage.class),
|
||||
NAV_TREE.get(SpinnerPage.class),
|
||||
NAV_TREE.get(SplitPanePage.class),
|
||||
NAV_TREE.get(TablePage.class),
|
||||
NAV_TREE.get(TabPanePage.class),
|
||||
NAV_TREE.get(TextAreaPage.class),
|
||||
NAV_TREE.get(TextFieldPage.class),
|
||||
NAV_TREE.get(TitledPanePage.class),
|
||||
NAV_TREE.get(ToggleButtonPage.class),
|
||||
NAV_TREE.get(ToolBarPage.class),
|
||||
NAV_TREE.get(TooltipPage.class),
|
||||
NAV_TREE.get(TreePage.class),
|
||||
NAV_TREE.get(TreeTablePage.class)
|
||||
);
|
||||
|
||||
var extras = NavTree.Item.group("Extras", new FontIcon(Material2OutlinedMZ.TOGGLE_ON));
|
||||
extras.getChildren().setAll(
|
||||
NAV_TREE.get(InputGroupPage.class),
|
||||
NAV_TREE.get(BreadcrumbsPage.class),
|
||||
NAV_TREE.get(CustomTextFieldPage.class),
|
||||
NAV_TREE.get(PopoverPage.class),
|
||||
NAV_TREE.get(ToggleSwitchPage.class)
|
||||
);
|
||||
|
||||
var showcases = NavTree.Item.group("Showcase", new FontIcon(Material2OutlinedMZ.VISIBILITY));
|
||||
showcases.getChildren().setAll(
|
||||
NAV_TREE.get(FileManagerPage.class),
|
||||
NAV_TREE.get(MusicPlayerPage.class),
|
||||
NAV_TREE.get(WidgetCollectionPage.class)
|
||||
);
|
||||
|
||||
var root = NavTree.Item.root();
|
||||
root.getChildren().setAll(general, components, extras, showcases);
|
||||
|
||||
return root;
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
// Nav Tree //
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
|
||||
public static Map<Class<? extends Page>, NavTree.Item> createNavItems() {
|
||||
var map = new HashMap<Class<? extends Page>, NavTree.Item>();
|
||||
|
||||
map.put(OverviewPage.class, NavTree.Item.page(OverviewPage.NAME, OverviewPage.class));
|
||||
map.put(ThemePage.class, NavTree.Item.page(ThemePage.NAME, ThemePage.class));
|
||||
map.put(TypographyPage.class, NavTree.Item.page(TypographyPage.NAME, TypographyPage.class));
|
||||
map.put(IconsPage.class, NavTree.Item.page(IconsPage.NAME, IconsPage.class));
|
||||
|
||||
map.put(InputGroupPage.class, NavTree.Item.page(InputGroupPage.NAME, InputGroupPage.class));
|
||||
map.put(AccordionPage.class, NavTree.Item.page(AccordionPage.NAME, AccordionPage.class));
|
||||
map.put(BreadcrumbsPage.class, NavTree.Item.page(BreadcrumbsPage.NAME, BreadcrumbsPage.class));
|
||||
map.put(ButtonPage.class, NavTree.Item.page(ButtonPage.NAME, ButtonPage.class));
|
||||
map.put(ChartPage.class, NavTree.Item.page(ChartPage.NAME, ChartPage.class));
|
||||
map.put(CheckBoxPage.class, NavTree.Item.page(CheckBoxPage.NAME, CheckBoxPage.class));
|
||||
map.put(ColorPickerPage.class, NavTree.Item.page(ColorPickerPage.NAME, ColorPickerPage.class));
|
||||
map.put(
|
||||
ComboBoxPage.class,
|
||||
NavTree.Item.page(ComboBoxPage.NAME, ComboBoxPage.class, "ChoiceBox")
|
||||
);
|
||||
map.put(
|
||||
CustomTextFieldPage.class,
|
||||
NavTree.Item.page(CustomTextFieldPage.NAME, CustomTextFieldPage.class, "MaskTextField", "PasswordTextField")
|
||||
);
|
||||
map.put(DatePickerPage.class, NavTree.Item.page(DatePickerPage.NAME, DatePickerPage.class));
|
||||
map.put(DialogPage.class, NavTree.Item.page(DialogPage.NAME, DialogPage.class));
|
||||
map.put(HtmlEditorPage.class, NavTree.Item.page(HtmlEditorPage.NAME, HtmlEditorPage.class));
|
||||
map.put(LabelPage.class, NavTree.Item.page(LabelPage.NAME, LabelPage.class));
|
||||
map.put(ListPage.class, NavTree.Item.page(ListPage.NAME, ListPage.class));
|
||||
map.put(MenuPage.class, NavTree.Item.page(MenuPage.NAME, MenuPage.class));
|
||||
map.put(MenuButtonPage.class, NavTree.Item.page(
|
||||
MenuButtonPage.NAME,
|
||||
MenuButtonPage.class, "SplitMenuButton")
|
||||
);
|
||||
map.put(PaginationPage.class, NavTree.Item.page(PaginationPage.NAME, PaginationPage.class));
|
||||
map.put(PopoverPage.class, NavTree.Item.page(PopoverPage.NAME, PopoverPage.class));
|
||||
map.put(ProgressPage.class, NavTree.Item.page(ProgressPage.NAME, ProgressPage.class));
|
||||
map.put(RadioButtonPage.class, NavTree.Item.page(RadioButtonPage.NAME, RadioButtonPage.class));
|
||||
map.put(ScrollPanePage.class, NavTree.Item.page(ScrollPanePage.NAME, ScrollPanePage.class));
|
||||
map.put(SeparatorPage.class, NavTree.Item.page(SeparatorPage.NAME, SeparatorPage.class));
|
||||
map.put(SliderPage.class, NavTree.Item.page(SliderPage.NAME, SliderPage.class));
|
||||
map.put(SpinnerPage.class, NavTree.Item.page(SpinnerPage.NAME, SpinnerPage.class));
|
||||
map.put(SplitPanePage.class, NavTree.Item.page(SplitPanePage.NAME, SplitPanePage.class));
|
||||
map.put(TablePage.class, NavTree.Item.page(TablePage.NAME, TablePage.class));
|
||||
map.put(TabPanePage.class, NavTree.Item.page(TabPanePage.NAME, TabPanePage.class));
|
||||
map.put(TextAreaPage.class, NavTree.Item.page(TextAreaPage.NAME, TextAreaPage.class));
|
||||
map.put(TextFieldPage.class, NavTree.Item.page(
|
||||
TextFieldPage.NAME,
|
||||
TextFieldPage.class, "PasswordField")
|
||||
);
|
||||
map.put(TitledPanePage.class, NavTree.Item.page(TitledPanePage.NAME, TitledPanePage.class));
|
||||
map.put(ToggleButtonPage.class, NavTree.Item.page(ToggleButtonPage.NAME, ToggleButtonPage.class));
|
||||
map.put(ToggleSwitchPage.class, NavTree.Item.page(ToggleSwitchPage.NAME, ToggleSwitchPage.class));
|
||||
map.put(ToolBarPage.class, NavTree.Item.page(ToolBarPage.NAME, ToolBarPage.class));
|
||||
map.put(TooltipPage.class, NavTree.Item.page(TooltipPage.NAME, TooltipPage.class));
|
||||
map.put(TreePage.class, NavTree.Item.page(TreePage.NAME, TreePage.class));
|
||||
map.put(TreeTablePage.class, NavTree.Item.page(TreeTablePage.NAME, TreeTablePage.class));
|
||||
|
||||
map.put(FileManagerPage.class, NavTree.Item.page(FileManagerPage.NAME, FileManagerPage.class));
|
||||
map.put(MusicPlayerPage.class, NavTree.Item.page(FileManagerPage.NAME, MusicPlayerPage.class));
|
||||
map.put(WidgetCollectionPage.class, NavTree.Item.page(
|
||||
FileManagerPage.NAME,
|
||||
WidgetCollectionPage.class, "Card", "Message", "Stepper", "Tag")
|
||||
);
|
||||
|
||||
return map;
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
// Commands //
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
|
37
sampler/src/main/java/atlantafx/sampler/layout/Nav.java
Normal file
37
sampler/src/main/java/atlantafx/sampler/layout/Nav.java
Normal file
@ -0,0 +1,37 @@
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
|
||||
package atlantafx.sampler.layout;
|
||||
|
||||
import atlantafx.sampler.page.Page;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import javafx.scene.Node;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
public record Nav(String title,
|
||||
@Nullable Node graphic,
|
||||
@Nullable Class<? extends Page> pageClass,
|
||||
@Nullable List<String> searchKeywords) {
|
||||
|
||||
public static final Nav ROOT = new Nav("ROOT", null, null, null);
|
||||
|
||||
public Nav {
|
||||
Objects.requireNonNull(title, "title");
|
||||
searchKeywords = Objects.requireNonNullElse(searchKeywords, Collections.emptyList());
|
||||
}
|
||||
|
||||
public boolean isGroup() {
|
||||
return pageClass == null;
|
||||
}
|
||||
|
||||
public boolean matches(String filter) {
|
||||
Objects.requireNonNull(filter);
|
||||
return contains(title, filter)
|
||||
|| (searchKeywords != null && searchKeywords.stream().anyMatch(keyword -> contains(keyword, filter)));
|
||||
}
|
||||
|
||||
private boolean contains(String text, String filter) {
|
||||
return text.toLowerCase().contains(filter.toLowerCase());
|
||||
}
|
||||
}
|
143
sampler/src/main/java/atlantafx/sampler/layout/NavTree.java
Normal file
143
sampler/src/main/java/atlantafx/sampler/layout/NavTree.java
Normal file
@ -0,0 +1,143 @@
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
|
||||
package atlantafx.sampler.layout;
|
||||
|
||||
import atlantafx.base.controls.Spacer;
|
||||
import atlantafx.sampler.page.Page;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import javafx.css.PseudoClass;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.scene.Cursor;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.control.TreeCell;
|
||||
import javafx.scene.control.TreeItem;
|
||||
import javafx.scene.control.TreeView;
|
||||
import javafx.scene.input.MouseButton;
|
||||
import javafx.scene.layout.HBox;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.kordamp.ikonli.javafx.FontIcon;
|
||||
|
||||
public class NavTree extends TreeView<Nav> {
|
||||
|
||||
public NavTree(MainModel model) {
|
||||
super();
|
||||
|
||||
getSelectionModel().selectedItemProperty().addListener((obs, old, val) -> {
|
||||
if (!(val instanceof Item item)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!item.isGroup()) {
|
||||
model.navigate(item.pageClass());
|
||||
}
|
||||
});
|
||||
|
||||
setShowRoot(false);
|
||||
rootProperty().bind(model.navTreeProperty());
|
||||
setCellFactory(p -> new NavTreeCell());
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
|
||||
public static final class NavTreeCell extends TreeCell<Nav> {
|
||||
|
||||
private static final PseudoClass GROUP = PseudoClass.getPseudoClass("group");
|
||||
|
||||
private final HBox root;
|
||||
private final Label titleLabel;
|
||||
private final Node arrowIcon;
|
||||
|
||||
public NavTreeCell() {
|
||||
super();
|
||||
|
||||
titleLabel = new Label();
|
||||
titleLabel.setGraphicTextGap(10);
|
||||
titleLabel.getStyleClass().add("title");
|
||||
|
||||
arrowIcon = new FontIcon();
|
||||
arrowIcon.getStyleClass().add("arrow");
|
||||
|
||||
root = new HBox();
|
||||
root.setAlignment(Pos.CENTER_LEFT);
|
||||
root.getChildren().setAll(titleLabel, new Spacer(), arrowIcon);
|
||||
root.setCursor(Cursor.HAND);
|
||||
root.getStyleClass().add("container");
|
||||
root.setMaxWidth(MainLayer.SIDEBAR_WIDTH - 10);
|
||||
|
||||
root.setOnMouseClicked(e -> {
|
||||
if (!(getTreeItem() instanceof Item item)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (item.isGroup() && e.getButton() == MouseButton.PRIMARY) {
|
||||
item.setExpanded(!item.isExpanded());
|
||||
// scroll slightly above the target
|
||||
getTreeView().scrollTo(getTreeView().getRow(item) - 10);
|
||||
}
|
||||
});
|
||||
|
||||
getStyleClass().add("nav-tree-cell");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void updateItem(Nav nav, boolean empty) {
|
||||
super.updateItem(nav, empty);
|
||||
|
||||
if (nav == null || empty) {
|
||||
setGraphic(null);
|
||||
titleLabel.setText(null);
|
||||
titleLabel.setGraphic(null);
|
||||
} else {
|
||||
setGraphic(root);
|
||||
|
||||
titleLabel.setText(nav.title());
|
||||
titleLabel.setGraphic(nav.graphic());
|
||||
|
||||
pseudoClassStateChanged(GROUP, nav.isGroup());
|
||||
arrowIcon.setVisible(nav.isGroup());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static final class Item extends TreeItem<Nav> {
|
||||
|
||||
private final Nav nav;
|
||||
|
||||
private Item(Nav nav) {
|
||||
this.nav = Objects.requireNonNull(nav, "nav");
|
||||
setValue(nav);
|
||||
}
|
||||
|
||||
public boolean isGroup() {
|
||||
return nav.isGroup();
|
||||
}
|
||||
|
||||
public @Nullable Class<? extends Page> pageClass() {
|
||||
return nav.pageClass();
|
||||
}
|
||||
|
||||
public static Item root() {
|
||||
return new Item(Nav.ROOT);
|
||||
}
|
||||
|
||||
public static Item group(String title, Node graphic) {
|
||||
return new Item(new Nav(title, graphic, null, null));
|
||||
}
|
||||
|
||||
public static Item page(String title,
|
||||
@Nullable Class<? extends Page> pageClass) {
|
||||
Objects.requireNonNull(pageClass, "pageClass");
|
||||
return new Item(new Nav(title, null, pageClass, Collections.emptyList()));
|
||||
}
|
||||
|
||||
public static Item page(String title,
|
||||
@Nullable Class<? extends Page> pageClass,
|
||||
String... searchKeywords) {
|
||||
Objects.requireNonNull(pageClass, "pageClass");
|
||||
return new Item(new Nav(title, null, pageClass, List.of(searchKeywords)));
|
||||
}
|
||||
}
|
||||
}
|
149
sampler/src/main/java/atlantafx/sampler/layout/SearchDialog.java
Normal file
149
sampler/src/main/java/atlantafx/sampler/layout/SearchDialog.java
Normal file
@ -0,0 +1,149 @@
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
|
||||
package atlantafx.sampler.layout;
|
||||
|
||||
import atlantafx.base.controls.CustomTextField;
|
||||
import atlantafx.base.controls.Spacer;
|
||||
import atlantafx.base.theme.Styles;
|
||||
import atlantafx.base.theme.Tweaks;
|
||||
import atlantafx.sampler.page.OverlayDialog;
|
||||
import java.util.function.Consumer;
|
||||
import javafx.event.EventType;
|
||||
import javafx.geometry.Insets;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.control.ListCell;
|
||||
import javafx.scene.control.ListView;
|
||||
import javafx.scene.input.KeyCode;
|
||||
import javafx.scene.input.KeyEvent;
|
||||
import javafx.scene.input.MouseButton;
|
||||
import javafx.scene.layout.HBox;
|
||||
import javafx.scene.layout.Priority;
|
||||
import javafx.scene.layout.VBox;
|
||||
import org.kordamp.ikonli.javafx.FontIcon;
|
||||
import org.kordamp.ikonli.material2.Material2AL;
|
||||
import org.kordamp.ikonli.material2.Material2MZ;
|
||||
|
||||
final class SearchDialog extends OverlayDialog<VBox> {
|
||||
|
||||
private final MainModel model;
|
||||
|
||||
private CustomTextField searchField;
|
||||
private ListView<NavTree.Item> resultList;
|
||||
|
||||
public SearchDialog(MainModel model) {
|
||||
super();
|
||||
|
||||
this.model = model;
|
||||
|
||||
setId("search-dialog");
|
||||
setTitle("Search");
|
||||
setContent(createContent());
|
||||
init();
|
||||
}
|
||||
|
||||
private VBox createContent() {
|
||||
var placeholder = new Label("Your search results will appear here");
|
||||
placeholder.getStyleClass().add(Styles.TITLE_4);
|
||||
|
||||
searchField = new CustomTextField();
|
||||
searchField.setLeft(new FontIcon(Material2MZ.SEARCH));
|
||||
VBox.setVgrow(searchField, Priority.NEVER);
|
||||
|
||||
Consumer<NavTree.Item> clickHandler = item -> {
|
||||
if (item.pageClass() != null) {
|
||||
close();
|
||||
model.navigate(item.pageClass());
|
||||
}
|
||||
};
|
||||
|
||||
resultList = new ListView<>();
|
||||
resultList.setPlaceholder(placeholder);
|
||||
resultList.getStyleClass().add(Tweaks.EDGE_TO_EDGE);
|
||||
resultList.setCellFactory(c -> new ResultListCell(clickHandler));
|
||||
VBox.setVgrow(resultList, Priority.ALWAYS);
|
||||
|
||||
var content = new VBox(10, searchField, resultList);
|
||||
content.setPadding(new Insets(10, 20, 10, 20));
|
||||
content.setPrefSize(600, 440);
|
||||
|
||||
return content;
|
||||
}
|
||||
|
||||
private void init() {
|
||||
searchField.textProperty().addListener((obs, old, val) -> {
|
||||
if (val == null || val.length() <= 2) {
|
||||
resultList.getItems().clear();
|
||||
return;
|
||||
}
|
||||
|
||||
resultList.getItems().setAll(model.findPages(val));
|
||||
});
|
||||
|
||||
searchField.addEventFilter(KeyEvent.KEY_RELEASED, e -> {
|
||||
if (e.getCode() == KeyCode.DOWN && !resultList.getItems().isEmpty()) {
|
||||
resultList.getSelectionModel().selectFirst();
|
||||
resultList.requestFocus();
|
||||
}
|
||||
});
|
||||
|
||||
resultList.setOnKeyPressed(e -> {
|
||||
var selectionModel = resultList.getSelectionModel();
|
||||
if (e.getCode() == KeyCode.ENTER && !selectionModel.isEmpty()) {
|
||||
close();
|
||||
model.navigate(selectionModel.getSelectedItem().pageClass());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void begForFocus() {
|
||||
searchField.requestFocus();
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
|
||||
public static final class ResultListCell extends ListCell<NavTree.Item> {
|
||||
|
||||
private final HBox root;
|
||||
private final Label parentLabel;
|
||||
private final Label targetLabel;
|
||||
|
||||
public ResultListCell(Consumer<NavTree.Item> clickHandler) {
|
||||
super();
|
||||
|
||||
parentLabel = new Label();
|
||||
parentLabel.getStyleClass().add(Styles.TEXT_MUTED);
|
||||
|
||||
var separatorIcon = new FontIcon(Material2AL.CHEVRON_RIGHT);
|
||||
separatorIcon.getStyleClass().add("icon-subtle");
|
||||
|
||||
var returnIcon = new FontIcon(Material2AL.KEYBOARD_RETURN);
|
||||
returnIcon.getStyleClass().add("icon-subtle");
|
||||
|
||||
targetLabel = new Label();
|
||||
targetLabel.getStyleClass().add(Styles.TEXT_BOLD);
|
||||
|
||||
root = new HBox(parentLabel, separatorIcon, targetLabel, new Spacer(), returnIcon);
|
||||
root.setAlignment(Pos.CENTER_LEFT);
|
||||
|
||||
setOnMouseClicked(e -> {
|
||||
if (e.getButton() == MouseButton.PRIMARY && e.getClickCount() == 2) {
|
||||
clickHandler.accept(getItem());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void updateItem(NavTree.Item item, boolean empty) {
|
||||
super.updateItem(item, empty);
|
||||
|
||||
if (item == null || empty) {
|
||||
setGraphic(null);
|
||||
} else {
|
||||
parentLabel.setText(item.getParent().getValue().title());
|
||||
targetLabel.setText(item.getValue().title());
|
||||
setGraphic(root);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -2,331 +2,34 @@
|
||||
|
||||
package atlantafx.sampler.layout;
|
||||
|
||||
import static javafx.scene.control.ScrollPane.ScrollBarPolicy.AS_NEEDED;
|
||||
import static javafx.scene.layout.Priority.ALWAYS;
|
||||
|
||||
import atlantafx.base.controls.Spacer;
|
||||
import atlantafx.base.theme.Styles;
|
||||
import atlantafx.sampler.page.Page;
|
||||
import atlantafx.sampler.page.components.AccordionPage;
|
||||
import atlantafx.sampler.page.components.BreadcrumbsPage;
|
||||
import atlantafx.sampler.page.components.ButtonPage;
|
||||
import atlantafx.sampler.page.components.ChartPage;
|
||||
import atlantafx.sampler.page.components.CheckBoxPage;
|
||||
import atlantafx.sampler.page.components.ColorPickerPage;
|
||||
import atlantafx.sampler.page.components.ComboBoxPage;
|
||||
import atlantafx.sampler.page.components.CustomTextFieldPage;
|
||||
import atlantafx.sampler.page.components.DatePickerPage;
|
||||
import atlantafx.sampler.page.components.DialogPage;
|
||||
import atlantafx.sampler.page.components.HtmlEditorPage;
|
||||
import atlantafx.sampler.page.components.InputGroupPage;
|
||||
import atlantafx.sampler.page.components.LabelPage;
|
||||
import atlantafx.sampler.page.components.ListPage;
|
||||
import atlantafx.sampler.page.components.MenuButtonPage;
|
||||
import atlantafx.sampler.page.components.MenuPage;
|
||||
import atlantafx.sampler.page.components.OverviewPage;
|
||||
import atlantafx.sampler.page.components.PaginationPage;
|
||||
import atlantafx.sampler.page.components.PopoverPage;
|
||||
import atlantafx.sampler.page.components.ProgressPage;
|
||||
import atlantafx.sampler.page.components.RadioButtonPage;
|
||||
import atlantafx.sampler.page.components.ScrollPanePage;
|
||||
import atlantafx.sampler.page.components.SeparatorPage;
|
||||
import atlantafx.sampler.page.components.SliderPage;
|
||||
import atlantafx.sampler.page.components.SpinnerPage;
|
||||
import atlantafx.sampler.page.components.SplitPanePage;
|
||||
import atlantafx.sampler.page.components.TabPanePage;
|
||||
import atlantafx.sampler.page.components.TablePage;
|
||||
import atlantafx.sampler.page.components.TextAreaPage;
|
||||
import atlantafx.sampler.page.components.TextFieldPage;
|
||||
import atlantafx.sampler.page.components.TitledPanePage;
|
||||
import atlantafx.sampler.page.components.ToggleButtonPage;
|
||||
import atlantafx.sampler.page.components.ToggleSwitchPage;
|
||||
import atlantafx.sampler.page.components.ToolBarPage;
|
||||
import atlantafx.sampler.page.components.TooltipPage;
|
||||
import atlantafx.sampler.page.components.TreePage;
|
||||
import atlantafx.sampler.page.components.TreeTablePage;
|
||||
import atlantafx.sampler.page.general.IconsPage;
|
||||
import atlantafx.sampler.page.general.ThemePage;
|
||||
import atlantafx.sampler.page.general.TypographyPage;
|
||||
import atlantafx.sampler.page.showcase.filemanager.FileManagerPage;
|
||||
import atlantafx.sampler.page.showcase.musicplayer.MusicPlayerPage;
|
||||
import atlantafx.sampler.page.showcase.widget.WidgetCollectionPage;
|
||||
import atlantafx.sampler.util.Containers;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.function.Predicate;
|
||||
import javafx.beans.binding.Bindings;
|
||||
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.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;
|
||||
|
||||
@SuppressWarnings("UnnecessaryLambda")
|
||||
class Sidebar extends StackPane {
|
||||
|
||||
private static final PseudoClass SELECTED = PseudoClass.getPseudoClass("selected");
|
||||
private static final PseudoClass FILTERED = PseudoClass.getPseudoClass("filtered");
|
||||
private static final Predicate<Region> PREDICATE_ANY = region -> true;
|
||||
|
||||
private final MainModel model;
|
||||
private final NavMenu navMenu;
|
||||
private ScrollPane navScroll;
|
||||
private final NavTree navTree;
|
||||
|
||||
public Sidebar(MainModel model) {
|
||||
super();
|
||||
|
||||
this.model = model;
|
||||
this.navMenu = new NavMenu(model);
|
||||
this.navTree = new NavTree(model);
|
||||
|
||||
createView();
|
||||
}
|
||||
|
||||
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(), navMenu.getContent());
|
||||
|
||||
navScroll = new ScrollPane(navContainer);
|
||||
Containers.setScrollConstraints(navScroll, AS_NEEDED, true, AS_NEEDED, true);
|
||||
VBox.setVgrow(navScroll, ALWAYS);
|
||||
|
||||
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));
|
||||
});
|
||||
|
||||
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();
|
||||
}
|
||||
});
|
||||
|
||||
navMenu.getContent().addListener((ListChangeListener<Region>) c -> {
|
||||
if (navMenu.getContent().isEmpty()) {
|
||||
placeholder.toFront();
|
||||
} else {
|
||||
placeholder.toBack();
|
||||
if (val != null) {
|
||||
navTree.getSelectionModel().select(model.getTreeItemForPage(val));
|
||||
}
|
||||
});
|
||||
|
||||
setId("sidebar");
|
||||
getChildren().addAll(placeholder, navScroll);
|
||||
getChildren().addAll(navTree);
|
||||
}
|
||||
|
||||
void begForFocus() {
|
||||
navScroll.requestFocus();
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
|
||||
private static class NavMenu {
|
||||
|
||||
private final MainModel model;
|
||||
private final FilteredList<Region> content;
|
||||
private final Map<Class<? extends Page>, 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<Region> getContent() {
|
||||
return content;
|
||||
}
|
||||
|
||||
public void setPredicate(Predicate<Region> predicate) {
|
||||
content.setPredicate(predicate);
|
||||
}
|
||||
|
||||
public Optional<NavLink> findLink(Class<? extends Page> pageClass) {
|
||||
if (pageClass == null) {
|
||||
return Optional.empty();
|
||||
}
|
||||
return Optional.ofNullable(registry.get(pageClass));
|
||||
}
|
||||
|
||||
public Optional<NavLink> 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<NavLink> 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<Region> create() {
|
||||
return FXCollections.observableArrayList(
|
||||
caption("GENERAL"),
|
||||
navLink(ThemePage.NAME, ThemePage.class),
|
||||
navLink(TypographyPage.NAME, TypographyPage.class),
|
||||
navLink(IconsPage.NAME, IconsPage.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, "MaskTextField", "PasswordTextField"),
|
||||
navLink(DatePickerPage.NAME, DatePickerPage.class),
|
||||
navLink(DialogPage.NAME, DialogPage.class),
|
||||
navLink(HtmlEditorPage.NAME, HtmlEditorPage.class),
|
||||
navLink(LabelPage.NAME, LabelPage.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),
|
||||
navLink(WidgetCollectionPage.NAME,
|
||||
WidgetCollectionPage.class,
|
||||
"Card", "Message", "Stepper", "Tag"
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
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<? extends Page> 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<? extends Page> pageClass;
|
||||
private final List<String> searchKeywords = new ArrayList<>();
|
||||
|
||||
public NavLink(String text, Class<? extends Page> pageClass) {
|
||||
super(Objects.requireNonNull(text));
|
||||
this.pageClass = Objects.requireNonNull(pageClass);
|
||||
|
||||
getStyleClass().add("nav-link");
|
||||
setMaxWidth(Double.MAX_VALUE);
|
||||
}
|
||||
|
||||
public Class<? extends Page> getPageClass() {
|
||||
return pageClass;
|
||||
}
|
||||
|
||||
public List<String> getSearchKeywords() {
|
||||
return searchKeywords;
|
||||
}
|
||||
|
||||
public boolean matches(String filter) {
|
||||
Objects.requireNonNull(filter);
|
||||
return contains(getText(), filter)
|
||||
|| searchKeywords.stream().anyMatch(keyword -> contains(keyword, filter));
|
||||
}
|
||||
|
||||
private boolean contains(String text, String filter) {
|
||||
return text.toLowerCase().contains(filter.toLowerCase());
|
||||
}
|
||||
navTree.requestFocus();
|
||||
}
|
||||
}
|
||||
|
@ -12,6 +12,7 @@ import atlantafx.base.controls.PasswordTextField;
|
||||
import atlantafx.sampler.page.AbstractPage;
|
||||
import atlantafx.sampler.page.SampleBlock;
|
||||
import java.time.LocalTime;
|
||||
import java.time.ZoneId;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.scene.Cursor;
|
||||
@ -120,7 +121,7 @@ public class CustomTextFieldPage extends AbstractPage {
|
||||
|
||||
var timeFormatter = DateTimeFormatter.ofPattern("HH:mm");
|
||||
var timeField = new MaskTextField("29:59");
|
||||
timeField.setText(LocalTime.now().format(timeFormatter));
|
||||
timeField.setText(LocalTime.now(ZoneId.systemDefault()).format(timeFormatter));
|
||||
timeField.setLeft(new FontIcon(Material2OutlinedMZ.TIMER));
|
||||
timeField.setPrefWidth(120);
|
||||
timeField.textProperty().addListener((obs, old, val) -> {
|
||||
|
@ -3,4 +3,9 @@
|
||||
.bordered {
|
||||
-fx-border-width: 1px;
|
||||
-fx-border-color: -color-border-muted;
|
||||
}
|
||||
}
|
||||
|
||||
.icon-subtle {
|
||||
-fx-fill: -color-fg-subtle;
|
||||
-fx-icon-color: -color-fg-subtle;
|
||||
}
|
||||
|
@ -66,6 +66,38 @@
|
||||
}
|
||||
}
|
||||
|
||||
>.search-button {
|
||||
-color-button-bg: -color-accent-emphasis;
|
||||
-color-button-fg: -color-fg-emphasis;
|
||||
-color-button-border: transparent;
|
||||
-color-button-bg-hover: -color-accent-6;
|
||||
-color-button-fg-hover: -color-fg-emphasis;
|
||||
-color-button-border-hover: transparent;
|
||||
-color-button-bg-focused: -color-accent-emphasis;
|
||||
-color-button-fg-focused: -color-fg-emphasis;
|
||||
-color-button-border-focused: transparent;
|
||||
-color-button-bg-pressed: -color-accent-emphasis;
|
||||
-color-button-fg-pressed: -color-fg-emphasis;
|
||||
-color-button-border-pressed: transparent;
|
||||
-color-button-shadow: transparent;
|
||||
|
||||
-fx-padding: 6px 16px 6px 12px;
|
||||
|
||||
>.box {
|
||||
>* {
|
||||
-fx-fill: -color-fg-emphasis;
|
||||
}
|
||||
|
||||
>.label {
|
||||
-fx-text-fill: -color-fg-emphasis;
|
||||
-fx-border-color: -color-fg-emphasis;
|
||||
-fx-border-width: 1;
|
||||
-fx-padding: 2px 6px 2px 6px;
|
||||
-fx-opacity: 0.5;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
>.page-title {
|
||||
-fx-text-fill: -color-fg-emphasis;
|
||||
-fx-padding: 0 0 0 30px;
|
||||
@ -84,40 +116,61 @@
|
||||
}
|
||||
|
||||
#sidebar {
|
||||
// border is necessary when scrollbar is hidden
|
||||
// e.g. when displaying search result
|
||||
-fx-border-color: -color-border-subtle;
|
||||
-fx-border-width: 0 1px 0 0;
|
||||
|
||||
>.scroll-pane {
|
||||
-fx-background-color: -color-bg-inset;
|
||||
-fx-padding: 0 16px 10px 16px;
|
||||
}
|
||||
.nav-tree-cell {
|
||||
-fx-padding: 0;
|
||||
-fx-indent: 8px;
|
||||
-color-cell-bg: -color-bg-inset;
|
||||
-color-cell-bg-selected: -color-bg-inset;
|
||||
-color-cell-bg-selected-focused: -color-bg-inset;
|
||||
-fx-background-radius: 5px;
|
||||
|
||||
&:filtered {
|
||||
>.scroll-pane {
|
||||
-fx-padding: 10px 16px 10px 16px;
|
||||
>.tree-disclosure-node,
|
||||
>.tree-disclosure-node>.arrow {
|
||||
-fx-min-width: 0;
|
||||
-fx-pref-width: 0;
|
||||
-fx-max-width: 0;
|
||||
-fx-min-height: 0;
|
||||
-fx-pref-height: 0;
|
||||
-fx-max-height: 0;
|
||||
visibility: hidden;
|
||||
}
|
||||
}
|
||||
|
||||
.nav-menu {
|
||||
>.caption {
|
||||
-fx-padding: 18px 0 10px 0;
|
||||
>.container {
|
||||
-fx-min-height: 2.1em;
|
||||
-fx-pref-height: 2.1em;
|
||||
-fx-max-height: 2.1em;
|
||||
-fx-padding: 0 0 0 8px;
|
||||
}
|
||||
|
||||
&:selected >.container >.title {
|
||||
-fx-font-weight: bold;
|
||||
-fx-text-fill: -color-fg-muted;
|
||||
-fx-text-fill: -color-accent-fg;
|
||||
}
|
||||
|
||||
>.nav-link {
|
||||
-fx-padding: 6px 8px 6px 8px;
|
||||
&:hover:filled {
|
||||
-color-cell-bg: -color-accent-subtle;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
-fx-background-color: -color-accent-muted;
|
||||
-fx-background-radius: 6px;
|
||||
&:group {
|
||||
>.container {
|
||||
-fx-min-height: 2.5em;
|
||||
-fx-pref-height: 2.5em;
|
||||
-fx-max-height: 2.5em;
|
||||
-fx-padding: 0 0 0 8px;
|
||||
|
||||
.ikonli-font-icon {
|
||||
-fx-fill: -color-accent-fg;
|
||||
-fx-icon-color: -color-accent-fg;
|
||||
}
|
||||
|
||||
>.arrow {
|
||||
-fx-icon-code: mdal-expand_more;
|
||||
}
|
||||
}
|
||||
|
||||
&:selected {
|
||||
-fx-text-fill: -color-accent-fg;
|
||||
-fx-font-weight: bold;
|
||||
&:expanded>.container>.arrow {
|
||||
-fx-icon-code: mdal-chevron_right;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user