Rewrite Sampler layout

This commit is contained in:
mkpaz 2023-05-17 20:42:35 +04:00
parent 9438ef66d8
commit bbec330216
113 changed files with 1187 additions and 1015 deletions

@ -2,6 +2,7 @@
package atlantafx.base.theme;
import java.util.Objects;
import javafx.css.PseudoClass;
import javafx.scene.Node;
import javafx.scene.control.TabPane;
@ -147,4 +148,18 @@ public final class Styles {
}
node.pseudoClassStateChanged(pseudoClass, true);
}
public static void appendStyle(Node node, String prop, String value) {
if (prop == null || prop.isBlank() || value == null || value.isBlank()) {
System.err.printf("Ignoring invalid style: property='%s', value='%s'%n", prop, value);
return;
}
var style = Objects.requireNonNullElse(node.getStyle(), "");
if (!style.endsWith(";")) {
style += ";";
}
style = style + prop.trim() + ":" + value.trim() + ";";
node.setStyle(style);
}
}

@ -28,6 +28,7 @@ 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;
@ -38,7 +39,8 @@ public class Launcher extends Application {
);
public static final List<KeyCodeCombination> SUPPORTED_HOTKEYS = List.of(
new KeyCodeCombination(KeyCode.SLASH)
new KeyCodeCombination(KeyCode.SLASH),
new KeyCodeCombination(KeyCode.T, KeyCombination.CONTROL_DOWN)
);
public static void main(String[] args) {

@ -4,7 +4,7 @@ package atlantafx.sampler.event;
import java.net.URI;
public class BrowseEvent extends Event {
public final class BrowseEvent extends Event {
private final URI uri;

@ -4,7 +4,7 @@ package atlantafx.sampler.event;
import javafx.scene.input.KeyCodeCombination;
public class HotkeyEvent extends Event {
public final class HotkeyEvent extends Event {
private final KeyCodeCombination keys;

@ -0,0 +1,28 @@
package atlantafx.sampler.event;
import java.util.Objects;
public final class PageEvent extends Event {
public enum Action {
SOURCE_CODE_ON,
SOURCE_CODE_OFF
}
private final Action action;
public PageEvent(Action action) {
this.action = Objects.requireNonNull(action, "action");
}
public Action getAction() {
return action;
}
@Override
public String toString() {
return "ActionEvent{"
+ "action=" + action
+ "} " + super.toString();
}
}

@ -1,16 +1,6 @@
package atlantafx.sampler.event;
public class ThemeEvent extends Event {
private final EventType eventType;
public ThemeEvent(EventType eventType) {
this.eventType = eventType;
}
public EventType getEventType() {
return eventType;
}
public final class ThemeEvent extends Event {
public enum EventType {
// theme can change both, base font size and colors
@ -24,6 +14,16 @@ public class ThemeEvent extends Event {
THEME_REMOVE
}
private final EventType eventType;
public ThemeEvent(EventType eventType) {
this.eventType = eventType;
}
public EventType getEventType() {
return eventType;
}
@Override
public String toString() {
return "ThemeEvent{"

@ -7,7 +7,7 @@ import javafx.geometry.Insets;
import javafx.scene.layout.AnchorPane;
import javafx.scene.layout.StackPane;
public class ApplicationWindow extends AnchorPane {
public final class ApplicationWindow extends AnchorPane {
public static final int MIN_WIDTH = 1200;
public static final int SIDEBAR_WIDTH = 250;

@ -1,20 +1,26 @@
/* SPDX-License-Identifier: MIT */
package atlantafx.sampler.page;
package atlantafx.sampler.layout;
import static java.nio.charset.StandardCharsets.UTF_8;
import atlantafx.base.theme.Styles;
import atlantafx.sampler.Resources;
import atlantafx.sampler.event.DefaultEventBus;
import atlantafx.sampler.event.PageEvent;
import atlantafx.sampler.theme.HighlightJSTheme;
import atlantafx.sampler.util.Containers;
import java.io.IOException;
import java.io.InputStream;
import javafx.geometry.Insets;
import javafx.scene.control.Button;
import javafx.scene.layout.AnchorPane;
import javafx.scene.paint.Color;
import javafx.scene.web.WebView;
import org.kordamp.ikonli.javafx.FontIcon;
import org.kordamp.ikonli.material2.Material2AL;
public class CodeViewer extends AnchorPane {
final class CodeViewer extends AnchorPane {
private static final String HLJS_LIB = "assets/highlightjs/highlight.min.js";
private static final String HLJS_SCRIPT = "hljs.highlightAll();hljs.initLineNumbersOnLoad();";
@ -25,6 +31,17 @@ public class CodeViewer extends AnchorPane {
private WebView webView;
public CodeViewer() {
super();
var closeBtn = new Button("Return", new FontIcon(Material2AL.ARROW_BACK));
closeBtn.getStyleClass().addAll(Styles.BUTTON_OUTLINED, Styles.SMALL, Styles.ACCENT);
AnchorPane.setTopAnchor(closeBtn, 20d);
AnchorPane.setRightAnchor(closeBtn, 20d);
closeBtn.setOnAction(e ->
DefaultEventBus.getInstance().publish(new PageEvent(PageEvent.Action.SOURCE_CODE_OFF))
);
getChildren().add(closeBtn);
getStyleClass().add("code-viewer");
}
@ -32,7 +49,7 @@ public class CodeViewer extends AnchorPane {
if (webView == null) {
webView = new WebView();
Containers.setAnchors(webView, Insets.EMPTY);
getChildren().setAll(webView);
getChildren().add(0, webView);
}
}

@ -1,198 +0,0 @@
/* SPDX-License-Identifier: MIT */
package atlantafx.sampler.layout;
import static atlantafx.base.theme.Styles.TEXT_SMALL;
import static atlantafx.base.theme.Styles.TITLE_3;
import static atlantafx.base.theme.Styles.TITLE_4;
import static atlantafx.sampler.Launcher.IS_DEV_MODE;
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.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;
import org.kordamp.ikonli.material2.Material2MZ;
import org.kordamp.ikonli.material2.Material2OutlinedMZ;
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<Node> quickConfigActionHandler;
private Overlay overlay;
private SearchDialog searchDialog;
public HeaderBar(MainModel model) {
super();
this.model = model;
createView();
}
private void createView() {
var logoImage = new ImageView(new Image(Resources.getResource("assets/app-icon.png").toString()));
logoImage.setFitWidth(32);
logoImage.setFitHeight(32);
var logoImageBorder = new Insets(1);
var logoImageBox = new StackPane(logoImage);
logoImageBox.getStyleClass().add("image");
logoImageBox.setPadding(logoImageBorder);
logoImageBox.setPrefWidth(logoImage.getFitWidth() + logoImageBorder.getRight() * 2);
logoImageBox.setMaxWidth(logoImage.getFitHeight() + logoImageBorder.getTop() * 2);
logoImageBox.setPrefHeight(logoImage.getFitWidth() + logoImageBorder.getTop() * 2);
logoImageBox.setMaxHeight(logoImage.getFitHeight() + logoImageBorder.getRight() * 2);
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 logoBox = new HBox(10, logoImageBox, logoLabel, versionLabel);
logoBox.getStyleClass().add("logo");
logoBox.setAlignment(Pos.CENTER_LEFT);
logoBox.setMinWidth(ApplicationWindow.SIDEBAR_WIDTH);
logoBox.setPrefWidth(ApplicationWindow.SIDEBAR_WIDTH);
logoBox.setMaxWidth(ApplicationWindow.SIDEBAR_WIDTH);
var titleLabel = new Label();
titleLabel.getStyleClass().addAll("page-title", TITLE_4);
titleLabel.textProperty().bind(model.titleProperty());
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().getCode() == KeyCode.SLASH) {
openSearchDialog();
}
});
// 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());
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)));
}
});
// ~
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(),
searchButton,
popoverAnchor,
quickConfigBtn,
sourceCodeBtn,
githubLink
);
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<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());
}
}

@ -2,22 +2,19 @@
package atlantafx.sampler.layout;
import static atlantafx.base.controls.Popover.ArrowLocation.TOP_CENTER;
import static atlantafx.sampler.event.PageEvent.Action;
import static javafx.scene.layout.Priority.ALWAYS;
import atlantafx.base.controls.Popover;
import atlantafx.sampler.event.DefaultEventBus;
import atlantafx.sampler.event.PageEvent;
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.theme.ThemeManager;
import java.io.IOException;
import java.util.Objects;
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;
@ -29,11 +26,9 @@ class MainLayer extends BorderPane {
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;
@ -65,14 +60,12 @@ class MainLayer extends BorderPane {
// ~
setId("main");
setTop(headerBar);
//setTop(headerBar);
setLeft(sidebar);
setCenter(subLayerPane);
}
private void initListeners() {
headerBar.setQuickConfigActionHandler(this::showThemeConfigPopover);
model.selectedPageProperty().addListener((obs, old, val) -> {
if (val != null) {
loadPage(val);
@ -93,6 +86,16 @@ class MainLayer extends BorderPane {
showSourceCode();
}
});
// switch to the source code and back
DefaultEventBus.getInstance().subscribe(PageEvent.class, e -> {
if (e.getAction() == Action.SOURCE_CODE_ON) {
model.showSourceCode();
}
if (e.getAction() == Action.SOURCE_CODE_OFF) {
model.hideSourceCode();
}
});
}
private void loadPage(Class<? extends Page> pageClass) {
@ -137,21 +140,6 @@ class MainLayer extends BorderPane {
}
}
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";

@ -7,21 +7,27 @@ 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.CalendarPage;
import atlantafx.sampler.page.components.ChartPage;
import atlantafx.sampler.page.components.CheckBoxPage;
import atlantafx.sampler.page.components.ChoiceBoxPage;
import atlantafx.sampler.page.components.ColorPickerPage;
import atlantafx.sampler.page.components.ComboBoxPage;
import atlantafx.sampler.page.components.ContextMenuPage;
import atlantafx.sampler.page.components.CustomTextFieldPage;
import atlantafx.sampler.page.components.DatePickerPage;
import atlantafx.sampler.page.components.DeckPanePage;
import atlantafx.sampler.page.components.DialogPage;
import atlantafx.sampler.page.components.HtmlEditorPage;
import atlantafx.sampler.page.components.LabelPage;
import atlantafx.sampler.page.components.InputGroupPage;
import atlantafx.sampler.page.components.ListViewPage;
import atlantafx.sampler.page.components.MenuBarPage;
import atlantafx.sampler.page.components.MenuButtonPage;
import atlantafx.sampler.page.components.ModalPanePage;
import atlantafx.sampler.page.components.PaginationPage;
import atlantafx.sampler.page.components.PopoverPage;
import atlantafx.sampler.page.components.ProgressIndicatorPage;
import atlantafx.sampler.page.components.RadioButtonPage;
import atlantafx.sampler.page.components.ScrollPanePage;
@ -35,21 +41,14 @@ 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.TreeTableViewPage;
import atlantafx.sampler.page.components.TreeViewPage;
import atlantafx.sampler.page.extras.BBCodePage;
import atlantafx.sampler.page.extras.BreadcrumbsPage;
import atlantafx.sampler.page.extras.CalendarPage;
import atlantafx.sampler.page.extras.CustomTextFieldPage;
import atlantafx.sampler.page.extras.DeckPanePage;
import atlantafx.sampler.page.extras.InputGroupPage;
import atlantafx.sampler.page.extras.ModalPanePage;
import atlantafx.sampler.page.extras.PopoverPage;
import atlantafx.sampler.page.extras.ToggleSwitchPage;
import atlantafx.sampler.page.general.BBCodePage;
import atlantafx.sampler.page.general.IconsPage;
import atlantafx.sampler.page.general.OverviewPage;
import atlantafx.sampler.page.showcase.OverviewPage;
import atlantafx.sampler.page.general.ThemePage;
import atlantafx.sampler.page.general.TypographyPage;
import atlantafx.sampler.page.showcase.filemanager.FileManagerPage;
@ -72,7 +71,7 @@ import org.kordamp.ikonli.material2.Material2OutlinedMZ;
public class MainModel {
public static Class<? extends Page> DEFAULT_PAGE = OverviewPage.class;
public static Class<? extends Page> DEFAULT_PAGE = ThemePage.class;
private static final Map<Class<? extends Page>, NavTree.Item> NAV_TREE = createNavItems();
@ -144,73 +143,92 @@ public class MainModel {
}
private NavTree.Item createTree() {
var general = NavTree.Item.group("General", new FontIcon(Material2OutlinedAL.ARTICLE));
var general = NavTree.Item.group("General", new FontIcon(Material2OutlinedMZ.SPEED));
general.getChildren().setAll(
NAV_TREE.get(OverviewPage.class),
NAV_TREE.get(ThemePage.class),
NAV_TREE.get(TypographyPage.class),
NAV_TREE.get(IconsPage.class)
NAV_TREE.get(IconsPage.class),
NAV_TREE.get(BBCodePage.class)
);
general.setExpanded(true);
var components = NavTree.Item.group("Standard Controls", new FontIcon(Material2OutlinedAL.DASHBOARD));
components.getChildren().setAll(
var containers = NavTree.Item.group("Containers", new FontIcon(Material2OutlinedMZ.TABLE_CHART));
containers.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(ChoiceBoxPage.class),
NAV_TREE.get(ColorPickerPage.class),
NAV_TREE.get(ComboBoxPage.class),
NAV_TREE.get(ContextMenuPage.class),
NAV_TREE.get(DatePickerPage.class),
NAV_TREE.get(DialogPage.class),
NAV_TREE.get(HtmlEditorPage.class),
NAV_TREE.get(LabelPage.class),
NAV_TREE.get(ListViewPage.class),
NAV_TREE.get(MenuBarPage.class),
NAV_TREE.get(MenuButtonPage.class),
NAV_TREE.get(PaginationPage.class),
NAV_TREE.get(ProgressIndicatorPage.class),
NAV_TREE.get(RadioButtonPage.class),
NAV_TREE.get(DeckPanePage.class),
NAV_TREE.get(ModalPanePage.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(TableViewPage.class),
NAV_TREE.get(TabPanePage.class),
NAV_TREE.get(TextAreaPage.class),
NAV_TREE.get(TextFieldPage.class),
NAV_TREE.get(PopoverPage.class),
NAV_TREE.get(TitledPanePage.class),
NAV_TREE.get(ToggleButtonPage.class),
NAV_TREE.get(ToolBarPage.class),
NAV_TREE.get(TooltipPage.class),
NAV_TREE.get(ToolBarPage.class)
);
var dataDisplay = NavTree.Item.group("Data Display", new FontIcon(Material2OutlinedAL.LIST_ALT));
dataDisplay.getChildren().setAll(
NAV_TREE.get(ChartPage.class),
NAV_TREE.get(ListViewPage.class),
NAV_TREE.get(TableViewPage.class),
NAV_TREE.get(TreeTableViewPage.class),
NAV_TREE.get(TreeViewPage.class)
);
var extras = NavTree.Item.group("Extras", new FontIcon(Material2OutlinedMZ.TOGGLE_ON));
extras.getChildren().setAll(
NAV_TREE.get(BBCodePage.class),
NAV_TREE.get(BreadcrumbsPage.class),
var feedback = NavTree.Item.group("Feedback", new FontIcon(Material2OutlinedAL.CHAT_BUBBLE_OUTLINE));
feedback.getChildren().setAll(
NAV_TREE.get(DialogPage.class),
NAV_TREE.get(ProgressIndicatorPage.class),
NAV_TREE.get(TooltipPage.class)
);
var inputs = NavTree.Item.group("Inputs & Controls", new FontIcon(Material2OutlinedAL.EDIT));
inputs.getChildren().setAll(
NAV_TREE.get(ButtonPage.class),
NAV_TREE.get(CalendarPage.class),
NAV_TREE.get(CheckBoxPage.class),
NAV_TREE.get(ChoiceBoxPage.class),
NAV_TREE.get(ColorPickerPage.class),
NAV_TREE.get(ComboBoxPage.class),
NAV_TREE.get(CustomTextFieldPage.class),
NAV_TREE.get(DeckPanePage.class),
NAV_TREE.get(DatePickerPage.class),
NAV_TREE.get(InputGroupPage.class),
NAV_TREE.get(ModalPanePage.class),
NAV_TREE.get(PopoverPage.class),
NAV_TREE.get(HtmlEditorPage.class),
NAV_TREE.get(MenuButtonPage.class),
NAV_TREE.get(RadioButtonPage.class),
NAV_TREE.get(SliderPage.class),
NAV_TREE.get(SpinnerPage.class),
NAV_TREE.get(TextAreaPage.class),
NAV_TREE.get(TextFieldPage.class),
NAV_TREE.get(ToggleButtonPage.class),
NAV_TREE.get(ToggleSwitchPage.class)
);
var navigation = NavTree.Item.group("Navigation", new FontIcon(Material2OutlinedMZ.MENU_OPEN));
navigation.getChildren().setAll(
NAV_TREE.get(BreadcrumbsPage.class),
NAV_TREE.get(MenuBarPage.class),
NAV_TREE.get(PaginationPage.class),
NAV_TREE.get(TabPanePage.class)
);
var showcases = NavTree.Item.group("Showcase", new FontIcon(Material2OutlinedMZ.VISIBILITY));
showcases.getChildren().setAll(
NAV_TREE.get(OverviewPage.class),
NAV_TREE.get(FileManagerPage.class),
NAV_TREE.get(MusicPlayerPage.class)
);
var root = NavTree.Item.root();
root.getChildren().setAll(general, components, extras, showcases);
root.getChildren().setAll(
general,
containers,
dataDisplay,
feedback,
inputs,
navigation,
showcases
);
return root;
}
@ -223,17 +241,19 @@ public class MainModel {
var map = new HashMap<Class<? extends Page>, NavTree.Item>();
// general
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(
TypographyPage.class,
NavTree.Item.page(TypographyPage.NAME, TypographyPage.class, "Label", "Hyperlink")
);
map.put(IconsPage.class, NavTree.Item.page(IconsPage.NAME, IconsPage.class));
map.put(BBCodePage.class, NavTree.Item.page(BBCodePage.NAME, BBCodePage.class));
// components
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(BBCodePage.class, NavTree.Item.page(BBCodePage.NAME, BBCodePage.class));
map.put(CalendarPage.class, NavTree.Item.page(CalendarPage.NAME, CalendarPage.class));
map.put(ChartPage.class, NavTree.Item.page(ChartPage.NAME, ChartPage.class));
map.put(ChoiceBoxPage.class, NavTree.Item.page(ChoiceBoxPage.NAME, ChoiceBoxPage.class));
@ -246,13 +266,15 @@ public class MainModel {
map.put(ContextMenuPage.class, NavTree.Item.page(ContextMenuPage.NAME, ContextMenuPage.class));
map.put(
CustomTextFieldPage.class,
NavTree.Item.page(CustomTextFieldPage.NAME, CustomTextFieldPage.class, "MaskTextField", "PasswordTextField")
NavTree.Item.page(
CustomTextFieldPage.NAME, CustomTextFieldPage.class,
"MaskTextField", "PasswordTextField"
)
);
map.put(DatePickerPage.class, NavTree.Item.page(DatePickerPage.NAME, DatePickerPage.class));
map.put(DeckPanePage.class, NavTree.Item.page(DeckPanePage.NAME, DeckPanePage.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(ListViewPage.class, NavTree.Item.page(ListViewPage.NAME, ListViewPage.class));
map.put(MenuBarPage.class, NavTree.Item.page(MenuBarPage.NAME, MenuBarPage.class));
map.put(MenuButtonPage.class, NavTree.Item.page(
@ -288,6 +310,7 @@ public class MainModel {
map.put(TreeViewPage.class, NavTree.Item.page(TreeViewPage.NAME, TreeViewPage.class));
// showcases
map.put(OverviewPage.class, NavTree.Item.page(OverviewPage.NAME, OverviewPage.class));
map.put(FileManagerPage.class, NavTree.Item.page(FileManagerPage.NAME, FileManagerPage.class));
map.put(MusicPlayerPage.class, NavTree.Item.page(MusicPlayerPage.NAME, MusicPlayerPage.class));
@ -309,8 +332,11 @@ public class MainModel {
currentSubLayer.set(PAGE);
}
public void nextSubLayer() {
var old = currentSubLayer.get();
currentSubLayer.set(old == PAGE ? SOURCE_CODE : PAGE);
public void showSourceCode() {
currentSubLayer.set(SOURCE_CODE);
}
public void hideSourceCode() {
currentSubLayer.set(PAGE);
}
}

@ -9,7 +9,7 @@ import java.util.Objects;
import javafx.scene.Node;
import org.jetbrains.annotations.Nullable;
public record Nav(String title,
record Nav(String title,
@Nullable Node graphic,
@Nullable Class<? extends Page> pageClass,
@Nullable List<String> searchKeywords) {

@ -3,6 +3,7 @@
package atlantafx.sampler.layout;
import atlantafx.base.controls.Spacer;
import atlantafx.base.theme.Tweaks;
import atlantafx.sampler.page.Page;
import java.util.Collections;
import java.util.List;
@ -20,7 +21,7 @@ import javafx.scene.layout.HBox;
import org.jetbrains.annotations.Nullable;
import org.kordamp.ikonli.javafx.FontIcon;
public class NavTree extends TreeView<Nav> {
final class NavTree extends TreeView<Nav> {
public NavTree(MainModel model) {
super();
@ -35,6 +36,7 @@ public class NavTree extends TreeView<Nav> {
}
});
getStyleClass().addAll(Tweaks.EDGE_TO_EDGE);
setShowRoot(false);
rootProperty().bind(model.navTreeProperty());
setCellFactory(p -> new NavTreeCell());

@ -24,7 +24,7 @@ import javafx.scene.layout.Pane;
import javafx.scene.layout.StackPane;
import javafx.util.Duration;
public class Overlay extends StackPane {
public final class Overlay extends StackPane {
public static final String STYLE_CLASS = "overlay";

@ -1,6 +1,6 @@
/* SPDX-License-Identifier: MIT */
package atlantafx.sampler.page;
package atlantafx.sampler.layout;
import static atlantafx.base.theme.Styles.BUTTON_CIRCLE;
import static atlantafx.base.theme.Styles.BUTTON_ICON;

@ -6,7 +6,6 @@ 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.geometry.Insets;
import javafx.geometry.Pos;

@ -2,34 +2,233 @@
package atlantafx.sampler.layout;
import static atlantafx.base.theme.Styles.TEXT_BOLD;
import static atlantafx.base.theme.Styles.TEXT_MUTED;
import static atlantafx.base.theme.Styles.TEXT_SMALL;
import static atlantafx.base.theme.Styles.TEXT_SUBTLE;
import static atlantafx.base.theme.Styles.TITLE_3;
import static atlantafx.sampler.Launcher.IS_DEV_MODE;
import atlantafx.base.controls.Spacer;
import atlantafx.base.theme.Styles;
import atlantafx.sampler.Resources;
import atlantafx.sampler.event.BrowseEvent;
import atlantafx.sampler.event.DefaultEventBus;
import atlantafx.sampler.event.HotkeyEvent;
import atlantafx.sampler.util.Lazy;
import java.net.URI;
import java.util.Objects;
import javafx.application.Platform;
import javafx.geometry.HPos;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Cursor;
import javafx.scene.control.Button;
import javafx.scene.control.ContentDisplay;
import javafx.scene.control.Label;
import javafx.scene.control.Tooltip;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyCodeCombination;
import javafx.scene.input.KeyCombination;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Priority;
import javafx.scene.layout.StackPane;
import javafx.scene.layout.VBox;
import org.kordamp.ikonli.javafx.FontIcon;
import org.kordamp.ikonli.material2.Material2MZ;
import org.kordamp.ikonli.material2.Material2OutlinedAL;
class Sidebar extends StackPane {
final class Sidebar extends VBox {
private final MainModel model;
private final NavTree navTree;
private Overlay overlay;
private final Lazy<SearchDialog> searchDialog;
private final Lazy<ThemeDialog> themeDialog;
public Sidebar(MainModel model) {
super();
this.model = model;
this.navTree = new NavTree(model);
createView();
}
private void createView() {
searchDialog = new Lazy<>(() -> {
var dialog = new SearchDialog(model);
dialog.setOnCloseRequest(() -> {
var overlay = lookupOverlay();
overlay.removeContent();
overlay.toBack();
});
return dialog;
});
themeDialog = new Lazy<>(() -> {
var dialog = new ThemeDialog();
dialog.setOnCloseRequest(() -> {
var overlay = lookupOverlay();
overlay.removeContent();
overlay.toBack();
});
return dialog;
});
model.selectedPageProperty().addListener((obs, old, val) -> {
if (val != null) {
navTree.getSelectionModel().select(model.getTreeItemForPage(val));
}
});
DefaultEventBus.getInstance().subscribe(HotkeyEvent.class, e -> {
if (e.getKeys().getCode() == KeyCode.SLASH) {
openSearchDialog();
}
});
var themeKeys = new KeyCodeCombination(KeyCode.T, KeyCombination.CONTROL_DOWN);
DefaultEventBus.getInstance().subscribe(HotkeyEvent.class, e -> {
if (Objects.equals(e.getKeys(), themeKeys)) {
openThemeDialog();
}
});
}
private void createView() {
var header = new Header();
VBox.setVgrow(navTree, Priority.ALWAYS);
setId("sidebar");
getChildren().addAll(navTree);
getChildren().addAll(header, navTree, createFooter());
}
void begForFocus() {
navTree.requestFocus();
}
private HBox createFooter() {
var versionLbl = new Label("v" + System.getProperty("app.version"));
versionLbl.getStyleClass().addAll(
"version", TEXT_SMALL, TEXT_BOLD, TEXT_SUBTLE
);
versionLbl.setCursor(Cursor.HAND);
versionLbl.setOnMouseClicked(e -> {
var homepage = System.getProperty("app.homepage");
if (homepage != null) {
DefaultEventBus.getInstance().publish(new BrowseEvent(URI.create(homepage)));
}
});
versionLbl.setTooltip(new Tooltip("Visit homepage"));
var footer = new HBox(versionLbl);
footer.getStyleClass().add("footer");
return footer;
}
private void openSearchDialog() {
var dialog = searchDialog.get();
var overlay = lookupOverlay();
overlay.setContent(dialog, HPos.CENTER);
overlay.toFront();
Platform.runLater(dialog::begForFocus);
}
private void openThemeDialog() {
var dialog = themeDialog.get();
var overlay = lookupOverlay();
overlay.setContent(dialog, HPos.CENTER);
overlay.toFront();
Platform.runLater(dialog::requestFocus);
}
private Overlay lookupOverlay() {
return Objects.requireNonNullElse(overlay,
overlay = getScene() != null
&& getScene().lookup("." + Overlay.STYLE_CLASS) instanceof Overlay o ? o : null
);
}
///////////////////////////////////////////////////////////////////////////
private class Header extends VBox {
public Header() {
super();
getStyleClass().add("header");
getChildren().setAll(
createLogo(), createSearchButton()
);
}
private HBox createLogo() {
var image = new ImageView(
new Image(Resources.getResource("assets/app-icon.png").toString())
);
image.setFitWidth(32);
image.setFitHeight(32);
var imageBorder = new Insets(1);
var imageBox = new StackPane(image);
imageBox.getStyleClass().add("image");
imageBox.setPadding(imageBorder);
imageBox.setPrefSize(
image.getFitWidth() + imageBorder.getRight() * 2,
image.getFitWidth() + imageBorder.getTop() * 2
);
imageBox.setMaxSize(
image.getFitHeight() + imageBorder.getTop() * 2,
image.getFitHeight() + imageBorder.getRight() * 2
);
var titleLbl = new Label("AtlantaFX");
titleLbl.getStyleClass().addAll(TITLE_3);
if (IS_DEV_MODE) {
var devLabel = new Label();
devLabel.setGraphic(new FontIcon(Material2OutlinedAL.INFO));
devLabel.getStyleClass().addAll("dev-indicator", Styles.WARNING);
devLabel.setContentDisplay(ContentDisplay.GRAPHIC_ONLY);
devLabel.setTooltip(new Tooltip("App is running in development mode"));
titleLbl.setContentDisplay(ContentDisplay.RIGHT);
titleLbl.setGraphic(devLabel);
}
var themeSwitchBtn = new Button();
themeSwitchBtn.getStyleClass().add("palette");
themeSwitchBtn.setGraphic(new FontIcon(Material2MZ.WB_SUNNY));
themeSwitchBtn.setContentDisplay(ContentDisplay.GRAPHIC_ONLY);
themeSwitchBtn.getStyleClass().addAll(Styles.BUTTON_CIRCLE, Styles.FLAT);
themeSwitchBtn.setAlignment(Pos.CENTER_RIGHT);
themeSwitchBtn.setOnAction(e -> openThemeDialog());
var root = new HBox(10, imageBox, titleLbl, new Spacer(), themeSwitchBtn);
root.getStyleClass().add("logo");
root.setAlignment(Pos.CENTER_LEFT);
return root;
}
private Button createSearchButton() {
var titleLbl = new Label("Search", new FontIcon(Material2MZ.SEARCH));
var hintLbl = new Label("Press /");
hintLbl.getStyleClass().addAll("hint", TEXT_MUTED, TEXT_SMALL);
var searchBox = new HBox(titleLbl, new Spacer(), hintLbl);
searchBox.getStyleClass().add("content");
searchBox.setAlignment(Pos.CENTER_LEFT);
var root = new Button();
root.setContentDisplay(ContentDisplay.GRAPHIC_ONLY);
root.getStyleClass().addAll("search-button");
root.setGraphic(searchBox);
root.setOnAction(e -> openSearchDialog());
root.setMaxWidth(Double.MAX_VALUE);
return root;
}
}
}

@ -0,0 +1,64 @@
package atlantafx.sampler.layout;
import atlantafx.sampler.theme.SamplerTheme;
import atlantafx.sampler.theme.ThemeManager;
import atlantafx.sampler.util.NodeUtils;
import java.util.Objects;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.control.ToggleGroup;
import javafx.scene.layout.TilePane;
import javafx.scene.layout.VBox;
final class ThemeDialog extends OverlayDialog<VBox> {
private final TilePane thumbnailsPane = new TilePane(20, 20);
private final ToggleGroup thumbnailsGroup = new ToggleGroup();
public ThemeDialog() {
super();
setId("theme-dialog");
setTitle("Select a theme");
setContent(createContent());
NodeUtils.toggleVisibility(footerBox, false);
updateThumbnails();
thumbnailsGroup.selectedToggleProperty().addListener((obs, old, val) -> {
System.out.println(0);
System.out.println(val.getUserData().getClass().getName());
if (val != null && val.getUserData() instanceof SamplerTheme theme) {
System.out.println(1);
ThemeManager.getInstance().setTheme(theme);
}
});
}
private VBox createContent() {
thumbnailsPane.setAlignment(Pos.TOP_CENTER);
thumbnailsPane.setPrefColumns(3);
thumbnailsPane.setStyle("-color-thumbnail-border:-color-border-subtle;");
var root = new VBox(thumbnailsPane);
root.setPadding(new Insets(20));
return root;
}
private void updateThumbnails() {
var tm = ThemeManager.getInstance();
thumbnailsPane.getChildren().clear();
tm.getRepository().getAll().forEach(theme -> {
var thumbnail = new ThemeThumbnail(theme);
thumbnail.setToggleGroup(thumbnailsGroup);
thumbnail.setUserData(theme);
thumbnail.setSelected(Objects.equals(
tm.getTheme().getName(),
theme.getName()
));
thumbnailsPane.getChildren().add(thumbnail);
});
}
}

@ -0,0 +1,113 @@
package atlantafx.sampler.layout;
import atlantafx.base.theme.Styles;
import atlantafx.sampler.theme.SamplerTheme;
import java.io.IOException;
import java.util.Map;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.ObjectProperty;
import javafx.css.PseudoClass;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.control.Label;
import javafx.scene.control.RadioButton;
import javafx.scene.control.Toggle;
import javafx.scene.control.ToggleGroup;
import javafx.scene.layout.HBox;
import javafx.scene.layout.VBox;
import javafx.scene.shape.Circle;
public final class ThemeThumbnail extends VBox implements Toggle {
private static final PseudoClass SELECTED = PseudoClass.getPseudoClass("selected");
private final RadioButton toggle; // internal helper node to manage selected state
public ThemeThumbnail(SamplerTheme theme) {
super();
toggle = new RadioButton();
try {
Map<String, String> colors = theme.parseColors();
var circles = new HBox(
createCircle(colors.get("-color-fg-default"), colors.get("-color-fg-default"), false),
createCircle(colors.get("-color-fg-default"), colors.get("-color-accent-emphasis"), true),
createCircle(colors.get("-color-fg-default"), colors.get("-color-success-emphasis"), true),
createCircle(colors.get("-color-fg-default"), colors.get("-color-danger-emphasis"), true),
createCircle(colors.get("-color-fg-default"), colors.get("-color-warning-emphasis"), true)
);
circles.setAlignment(Pos.CENTER);
var nameLbl = new Label(theme.getName());
nameLbl.getStyleClass().add(Styles.TEXT_CAPTION);
Styles.appendStyle(nameLbl, "-fx-text-fill", colors.get("-color-fg-muted"));
setStyle("""
-fx-background-radius: 10px, 8px;
-fx-background-insets: 0, 3px
"""
);
Styles.appendStyle(
this,
"-fx-background-color",
"-color-thumbnail-border," + colors.get("-color-bg-default")
);
setOnMouseClicked(e -> setSelected(true));
getStyleClass().add("theme-thumbnail");
getChildren().setAll(nameLbl, circles);
} catch (IOException e) {
throw new RuntimeException(e.getMessage(), e);
}
selectedProperty().addListener(
(obs, old, val) -> pseudoClassStateChanged(SELECTED, val)
);
}
private Circle createCircle(String borderColor, String bgColor, boolean overlap) {
var circle = new Circle(10);
Styles.appendStyle(circle, "-fx-stroke", borderColor);
Styles.appendStyle(circle, "-fx-fill", bgColor);
if (overlap) {
HBox.setMargin(circle, new Insets(0, 0, 0, -5));
}
return circle;
}
@Override
public ToggleGroup getToggleGroup() {
return toggle.getToggleGroup();
}
@Override
public void setToggleGroup(ToggleGroup toggleGroup) {
toggle.setToggleGroup(toggleGroup);
}
@Override
public ObjectProperty<ToggleGroup> toggleGroupProperty() {
return toggle.toggleGroupProperty();
}
@Override
public boolean isSelected() {
return toggle.isSelected();
}
@Override
public void setSelected(boolean selected) {
toggle.setSelected(selected);
}
@Override
public BooleanProperty selectedProperty() {
return toggle.selectedProperty();
}
@Override
public void setUserData(Object value) {
toggle.setUserData(value);
}
}

@ -8,14 +8,14 @@ import static javafx.scene.control.ScrollPane.ScrollBarPolicy.NEVER;
import atlantafx.base.util.BBCodeParser;
import atlantafx.sampler.layout.Overlay;
import java.net.URI;
import javafx.geometry.Pos;
import javafx.scene.Node;
import javafx.scene.control.ScrollPane;
import javafx.scene.layout.Pane;
import javafx.scene.layout.StackPane;
import javafx.scene.layout.VBox;
import javafx.scene.text.Text;
import javafx.scene.text.TextFlow;
import org.jetbrains.annotations.Nullable;
public abstract class AbstractPage extends StackPane implements Page {
@ -45,10 +45,6 @@ public abstract class AbstractPage extends StackPane implements Page {
getChildren().setAll(scrollPane);
}
protected void setUserContent(Node content) {
userContent.getChildren().setAll(content);
}
@Override
public Pane getView() {
return this;
@ -64,6 +60,11 @@ public abstract class AbstractPage extends StackPane implements Page {
return true;
}
@Override
public @Nullable URI getJavadocUri() {
return URI.create(String.format(JFX_JAVADOC_URI_TEMPLATE, "control/" + getName()));
}
@Override
public void reset() {
}
@ -85,12 +86,13 @@ public abstract class AbstractPage extends StackPane implements Page {
this.overlay = lookupOverlay();
}
protected void addNode(Node node) {
userContent.getChildren().add(node);
protected void addPageHeader() {
var pageHeader = new PageHeader(this);
userContent.getChildren().add(pageHeader);
}
protected void addPlainText(String text) {
userContent.getChildren().add(new TextFlow(new Text(text)));
protected void addNode(Node node) {
userContent.getChildren().add(node);
}
protected void addFormattedText(String text) {

@ -8,7 +8,7 @@ import javafx.scene.text.TextFlow;
// This code is adapted from RichTextFX JavaKeywordsDemo:
// https://github.com/FXMisc/RichTextFX/blob/master/richtextfx-demos
public class BBSyntaxHighlighter {
public final class BBSyntaxHighlighter {
private static final String[] KEYWORDS = new String[] {
"abstract", "assert", "boolean", "break", "byte",

@ -23,7 +23,7 @@ import org.jetbrains.annotations.Nullable;
// This widget emulates TabPane behavior, because TabPane itself doesn't work as it should:
// https://bugs.openjdk.org/browse/JDK-8145490
public class ExampleBox extends VBox {
public final class ExampleBox extends VBox {
private static final PseudoClass SELECTED = PseudoClass.getPseudoClass("selected");
private final ObjectProperty<Label> selectedTab = new SimpleObjectProperty<>();

@ -8,6 +8,7 @@ import static javafx.scene.control.ScrollPane.ScrollBarPolicy.NEVER;
import atlantafx.base.theme.Styles;
import atlantafx.sampler.layout.Overlay;
import java.net.URI;
import java.util.LinkedHashSet;
import java.util.Objects;
import java.util.Set;
@ -26,7 +27,6 @@ import javafx.scene.layout.Pane;
import javafx.scene.layout.StackPane;
import javafx.scene.layout.VBox;
import javafx.scene.text.Text;
import javafx.scene.text.TextFlow;
import org.jetbrains.annotations.Nullable;
import org.kordamp.ikonli.feather.Feather;
import org.kordamp.ikonli.javafx.FontIcon;
@ -126,12 +126,13 @@ public abstract class OutlinePage extends StackPane implements Page {
return lastHeading != null ? lastHeading.getText() : null;
}
protected void addNode(Node node) {
userContent.getChildren().add(node);
protected void addPageHeader() {
var pageHeader = new PageHeader(this);
userContent.getChildren().add(pageHeader);
}
protected void addPlainText(String text) {
userContent.getChildren().add(new TextFlow(new Text(text)));
protected void addNode(Node node) {
userContent.getChildren().add(node);
}
protected void addFormattedText(String text) {
@ -171,6 +172,11 @@ public abstract class OutlinePage extends StackPane implements Page {
return true;
}
@Override
public @Nullable URI getJavadocUri() {
return URI.create(String.format(JFX_JAVADOC_URI_TEMPLATE, "control/" + getName()));
}
@Override
public void reset() {
}

@ -2,12 +2,18 @@
package atlantafx.sampler.page;
import static javafx.scene.input.KeyCombination.ALT_DOWN;
import atlantafx.base.theme.Styles;
import atlantafx.base.theme.Tweaks;
import atlantafx.base.util.BBCodeParser;
import atlantafx.sampler.event.BrowseEvent;
import atlantafx.sampler.event.DefaultEventBus;
import atlantafx.sampler.event.PageEvent;
import atlantafx.sampler.layout.ApplicationWindow;
import java.net.URI;
import java.util.List;
import java.util.Objects;
import java.util.Random;
import java.util.function.Supplier;
import java.util.stream.Stream;
@ -16,8 +22,16 @@ import javafx.scene.Node;
import javafx.scene.Parent;
import javafx.scene.control.Hyperlink;
import javafx.scene.control.Label;
import javafx.scene.control.MenuButton;
import javafx.scene.control.MenuItem;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyCodeCombination;
import javafx.scene.layout.HBox;
import net.datafaker.Faker;
import org.jetbrains.annotations.Nullable;
import org.kordamp.ikonli.feather.Feather;
import org.kordamp.ikonli.javafx.FontIcon;
import org.kordamp.ikonli.material2.Material2AL;
public interface Page {
@ -29,8 +43,10 @@ public interface Page {
Faker FAKER = new Faker();
Random RANDOM = new Random();
int PAGE_HGAP = 30;
int PAGE_VGAP = 30;
String JFX_JAVADOC_URI_TEMPLATE =
"https://openjfx.io/javadoc/20/javafx.controls/javafx/scene/%s.html";
String AFX_JAVADOC_URI_TEMPLATE =
"https://mkpaz.github.io/atlantafx/apidocs/atlantafx.base/atlantafx/base/%s.html";
String getName();
@ -40,6 +56,8 @@ public interface Page {
boolean canChangeThemeSettings();
@Nullable URI getJavadocUri();
void reset();
default <T> List<T> generate(Supplier<T> supplier, int count) {
@ -72,4 +90,43 @@ public interface Page {
label.setStyle("-fx-font-family:monospace");
return label;
}
///////////////////////////////////////////////////////////////////////////
class PageHeader extends HBox {
public PageHeader(Page page) {
super();
Objects.requireNonNull(page, "page");
var titleLbl = new Label(page.getName());
titleLbl.getStyleClass().add(Styles.TITLE_2);
var sourceCodeItem = new MenuItem("Source Code", new FontIcon(Feather.CODE));
sourceCodeItem.setDisable(!page.canDisplaySourceCode());
sourceCodeItem.setAccelerator(new KeyCodeCombination(KeyCode.C, ALT_DOWN));
sourceCodeItem.setOnAction(e ->
DefaultEventBus.getInstance().publish(new PageEvent(PageEvent.Action.SOURCE_CODE_ON))
);
final var uri = page.getJavadocUri();
var javadocItem = new MenuItem("Javadoc", new FontIcon(Feather.COFFEE));
javadocItem.setAccelerator(new KeyCodeCombination(KeyCode.J, ALT_DOWN));
javadocItem.setDisable(uri == null);
javadocItem.setOnAction(e -> {
if (uri != null) {
DefaultEventBus.getInstance().publish(new BrowseEvent(uri));
}
});
var menuBtn = new MenuButton("", new FontIcon(Material2AL.EXPAND_MORE));
menuBtn.getStyleClass().addAll(Styles.FLAT, Styles.BUTTON_ICON, Tweaks.NO_ARROW);
menuBtn.getItems().setAll(sourceCodeItem, javadocItem);
getStyleClass().add("header");
setSpacing(20);
getChildren().setAll(titleLbl, menuBtn);
}
}
}

@ -1,252 +0,0 @@
/* SPDX-License-Identifier: MIT */
package atlantafx.sampler.page;
import static atlantafx.base.theme.Styles.BUTTON_CIRCLE;
import static atlantafx.base.theme.Styles.BUTTON_ICON;
import static atlantafx.base.theme.Styles.FLAT;
import static atlantafx.base.theme.Styles.TEXT_CAPTION;
import static atlantafx.sampler.theme.ThemeManager.DEFAULT_ZOOM;
import static atlantafx.sampler.theme.ThemeManager.SUPPORTED_ZOOM;
import static javafx.geometry.Pos.CENTER_LEFT;
import static org.kordamp.ikonli.material2.Material2AL.ARROW_BACK;
import static org.kordamp.ikonli.material2.Material2AL.ARROW_FORWARD;
import atlantafx.base.controls.Spacer;
import atlantafx.sampler.page.general.AccentColorSelector;
import atlantafx.sampler.theme.ThemeManager;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.function.Consumer;
import javafx.beans.binding.Bindings;
import javafx.beans.binding.BooleanBinding;
import javafx.beans.property.IntegerProperty;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.css.PseudoClass;
import javafx.geometry.HorizontalDirection;
import javafx.geometry.Pos;
import javafx.scene.Node;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.Separator;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Pane;
import javafx.scene.layout.StackPane;
import javafx.scene.layout.VBox;
import org.kordamp.ikonli.feather.Feather;
import org.kordamp.ikonli.javafx.FontIcon;
import org.kordamp.ikonli.material2.Material2AL;
// This should really be refactored to more generic control someday.
// - the whole component to PopoverMenu, that reuses JavaFX MenuItem API
// - font size switcher to flat SpinnerMenuItem
@SuppressWarnings("UnnecessaryLambda")
public class QuickConfigMenu extends StackPane {
private static final PseudoClass SELECTED = PseudoClass.getPseudoClass("selected");
private static final String EXIT_ID = "Exit";
private MainMenu mainMenu;
private ThemeSelectionMenu themeSelectionMenu;
private Runnable exitHandler;
private final Consumer<String> navHandler = s -> {
Menu menu = null;
switch (s) {
case MainMenu.ID -> menu = getOrCreateMainMenu();
case ThemeSelectionMenu.ID -> menu = getOrCreateThemeSelectionMenu();
default -> {
if (exitHandler != null) {
exitHandler.run();
return;
}
}
}
Objects.requireNonNull(menu);
menu.update();
getChildren().setAll(menu.getRoot());
};
public void setExitHandler(Runnable exitHandler) {
this.exitHandler = exitHandler;
}
public QuickConfigMenu() {
super();
getChildren().setAll(getOrCreateMainMenu());
setId("quick-config-menu");
}
private MainMenu getOrCreateMainMenu() {
if (mainMenu == null) {
mainMenu = new MainMenu(navHandler);
}
return mainMenu;
}
private ThemeSelectionMenu getOrCreateThemeSelectionMenu() {
if (themeSelectionMenu == null) {
themeSelectionMenu = new ThemeSelectionMenu(navHandler);
}
return themeSelectionMenu;
}
private static Pane menu(String title, HorizontalDirection direction) {
var label = new Label(title);
label.getStyleClass().add(TEXT_CAPTION);
var root = new HBox();
root.setAlignment(CENTER_LEFT);
root.getStyleClass().addAll("row", "action");
switch (direction) {
case LEFT -> root.getChildren().setAll(new FontIcon(ARROW_BACK), new Spacer(), label, new Spacer());
case RIGHT -> root.getChildren().setAll(label, new Spacer(), new FontIcon(ARROW_FORWARD));
}
return root;
}
public void update() {
getOrCreateMainMenu().update();
}
///////////////////////////////////////////////////////////////////////////
private interface Menu {
void update();
Pane getRoot();
}
private static class MainMenu extends VBox implements Menu {
private static final String ID = "MainMenu";
private final IntegerProperty zoom = new SimpleIntegerProperty(DEFAULT_ZOOM);
private final BooleanBinding canZoomIn = Bindings.createBooleanBinding(
() -> SUPPORTED_ZOOM.indexOf(zoom.get()) < SUPPORTED_ZOOM.size() - 1, zoom
);
private final BooleanBinding canZoomOut = Bindings.createBooleanBinding(
() -> SUPPORTED_ZOOM.indexOf(zoom.get()) >= 1, zoom
);
public MainMenu(Consumer<String> navHandler) {
super();
Objects.requireNonNull(navHandler);
var themeSelectionMenu = menu("Theme", HorizontalDirection.RIGHT);
themeSelectionMenu.setOnMouseClicked(e -> navHandler.accept(ThemeSelectionMenu.ID));
var accentSelector = new AccentColorSelector();
accentSelector.setAlignment(Pos.CENTER);
// ~
var zoomInBtn = new Button("", new FontIcon(Feather.ZOOM_IN));
zoomInBtn.getStyleClass().addAll(BUTTON_CIRCLE, BUTTON_ICON, FLAT);
zoomInBtn.setOnAction(e -> {
if (canZoomIn.get()) {
zoom.set(SUPPORTED_ZOOM.get(SUPPORTED_ZOOM.indexOf(zoom.get()) + 1));
}
});
zoomInBtn.disableProperty().bind(canZoomIn.not());
var zoomOutBtn = new Button("", new FontIcon(Feather.ZOOM_OUT));
zoomOutBtn.getStyleClass().addAll(BUTTON_CIRCLE, BUTTON_ICON, FLAT);
zoomOutBtn.setOnAction(e -> {
if (canZoomOut.get()) {
zoom.set(SUPPORTED_ZOOM.get(SUPPORTED_ZOOM.indexOf(zoom.get()) - 1));
}
});
zoomOutBtn.disableProperty().bind(canZoomOut.not());
var zoomLabel = new Label();
zoomLabel.textProperty().bind(Bindings.createStringBinding(() -> zoom.get() + "%", zoom));
var zoomBox = new HBox(zoomOutBtn, new Spacer(), zoomLabel, new Spacer(), zoomInBtn);
zoomBox.setAlignment(CENTER_LEFT);
zoomBox.getStyleClass().addAll("row");
final var tm = ThemeManager.getInstance();
zoom.addListener((obs, old, val) -> {
if (val != null && tm.getZoom() != val.intValue()) {
tm.setZoom(val.intValue());
}
});
// ~
getChildren().setAll(
themeSelectionMenu,
new Separator(),
accentSelector,
new Separator(),
zoomBox
);
}
@Override
public void update() {
zoom.set(ThemeManager.getInstance().getZoom());
}
@Override
public Pane getRoot() {
return this;
}
}
private static class ThemeSelectionMenu extends VBox implements Menu {
public static final String ID = "ThemeSelectionMenu";
private final List<Node> items = new ArrayList<>();
public ThemeSelectionMenu(Consumer<String> navHandler) {
super();
Objects.requireNonNull(navHandler);
var tm = ThemeManager.getInstance();
var mainMenu = menu("Theme", HorizontalDirection.LEFT);
mainMenu.setOnMouseClicked(e -> navHandler.accept(MainMenu.ID));
getChildren().setAll(mainMenu, new Separator());
tm.getRepository().getAll().forEach(theme -> {
var icon = new FontIcon(Material2AL.CHECK);
var item = new HBox(20, icon, new Label(theme.getName()));
item.getStyleClass().addAll("row", "action", "radio");
item.setUserData(theme.getName());
item.setOnMouseClicked(e -> {
tm.setTheme(theme);
navHandler.accept(MainMenu.ID);
navHandler.accept(QuickConfigMenu.EXIT_ID);
});
items.add(item);
getChildren().add(item);
});
}
@Override
public void update() {
items.forEach(item -> item.pseudoClassStateChanged(
SELECTED,
Objects.equals(item.getUserData(), ThemeManager.getInstance().getTheme().getName())
));
}
@Override
public Pane getRoot() {
return this;
}
}
}

@ -10,7 +10,7 @@ import javafx.scene.layout.Priority;
import javafx.scene.text.Text;
import javafx.scene.text.TextFlow;
public class Snippet {
public final class Snippet {
private final Class<?> sourceClass;
private final int id;

@ -28,7 +28,7 @@ import javafx.scene.layout.VBox;
import javafx.scene.text.Text;
import javafx.scene.text.TextFlow;
public class AccordionPage extends OutlinePage {
public final class AccordionPage extends OutlinePage {
public static final String NAME = "Accordion";
@ -40,6 +40,7 @@ public class AccordionPage extends OutlinePage {
public AccordionPage() {
super();
addPageHeader();
addFormattedText("""
A user interface component that allows you to display a list of expandable \
items and only one item can be open at a time. Each item in the [i]Accordion[/i] \

@ -1,6 +1,6 @@
/* SPDX-License-Identifier: MIT */
package atlantafx.sampler.page.extras;
package atlantafx.sampler.page.components;
import atlantafx.base.controls.Breadcrumbs;
import atlantafx.base.controls.Breadcrumbs.BreadCrumbItem;
@ -9,14 +9,16 @@ import atlantafx.base.util.BBCodeParser;
import atlantafx.sampler.page.ExampleBox;
import atlantafx.sampler.page.OutlinePage;
import atlantafx.sampler.page.Snippet;
import java.net.URI;
import javafx.geometry.Pos;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.layout.HBox;
import org.jetbrains.annotations.Nullable;
import org.kordamp.ikonli.javafx.FontIcon;
import org.kordamp.ikonli.material2.Material2AL;
public class BreadcrumbsPage extends OutlinePage {
public final class BreadcrumbsPage extends OutlinePage {
public static final String NAME = "Breadcrumbs";
@ -25,9 +27,15 @@ public class BreadcrumbsPage extends OutlinePage {
return NAME;
}
@Override
public @Nullable URI getJavadocUri() {
return URI.create(String.format(AFX_JAVADOC_URI_TEMPLATE, "controls/" + getName()));
}
public BreadcrumbsPage() {
super();
addPageHeader();
addFormattedText("""
Represents a bread crumb bar. This control is useful to visualize and navigate \
a hierarchical path structure, such as file systems."""

@ -18,7 +18,7 @@ import org.kordamp.ikonli.feather.Feather;
import org.kordamp.ikonli.javafx.FontIcon;
import org.kordamp.ikonli.material2.Material2AL;
public class ButtonPage extends OutlinePage {
public final class ButtonPage extends OutlinePage {
public static final String NAME = "Button";
@ -30,6 +30,7 @@ public class ButtonPage extends OutlinePage {
public ButtonPage() {
super();
addPageHeader();
addFormattedText("""
A simple button control. The button control can contain text and/or a graphic.
A button control has three different modes:

@ -1,6 +1,6 @@
/* SPDX-License-Identifier: MIT */
package atlantafx.sampler.page.extras;
package atlantafx.sampler.page.components;
import atlantafx.base.controls.InlineDatePicker;
import atlantafx.base.theme.Styles;
@ -9,6 +9,7 @@ import atlantafx.sampler.page.ExampleBox;
import atlantafx.sampler.page.OutlinePage;
import atlantafx.sampler.page.Snippet;
import atlantafx.sampler.theme.CSSFragment;
import java.net.URI;
import java.time.LocalDate;
import java.time.LocalTime;
import java.time.ZoneId;
@ -22,7 +23,7 @@ import javafx.scene.layout.HBox;
import javafx.scene.layout.VBox;
import javafx.util.Duration;
public class CalendarPage extends OutlinePage {
public final class CalendarPage extends OutlinePage {
public static final String NAME = "Calendar";
private static final LocalDate TODAY = LocalDate.now(ZoneId.systemDefault());
@ -32,9 +33,15 @@ public class CalendarPage extends OutlinePage {
return NAME;
}
@Override
public URI getJavadocUri() {
return URI.create(String.format(AFX_JAVADOC_URI_TEMPLATE, "controls/" + getName()));
}
public CalendarPage() {
super();
addPageHeader();
addFormattedText("""
The date picker control that allows the user to select a date. Unlike standard JavaFX \
[font=monospace]javafx.scene.control.DatePicker[/font] the [i]Calendar[/i] is not concealed \

@ -6,6 +6,7 @@ import atlantafx.base.util.BBCodeParser;
import atlantafx.sampler.page.ExampleBox;
import atlantafx.sampler.page.OutlinePage;
import atlantafx.sampler.page.Snippet;
import java.net.URI;
import java.time.Month;
import java.time.format.TextStyle;
import java.util.Locale;
@ -24,7 +25,7 @@ import javafx.scene.chart.StackedAreaChart;
import javafx.scene.chart.StackedBarChart;
import javafx.scene.chart.XYChart;
public class ChartPage extends OutlinePage {
public final class ChartPage extends OutlinePage {
public static final String NAME = "Chart";
@ -33,10 +34,16 @@ public class ChartPage extends OutlinePage {
return NAME;
}
@Override
public URI getJavadocUri() {
return URI.create("https://openjfx.io/javadoc/20/javafx.controls/javafx/scene/chart/package-summary.html");
}
public ChartPage() {
super();
addPlainText("""
addPageHeader();
addFormattedText("""
JavaFX provides a set of chart components specifically designed \
for data visualization. The charts include common types such as \
Bar, Line, Area, Pie, Scatter, and Bubble charts."""

@ -10,7 +10,7 @@ import javafx.scene.control.CheckBox;
import javafx.scene.layout.HBox;
import javafx.scene.layout.StackPane;
public class CheckBoxPage extends OutlinePage {
public final class CheckBoxPage extends OutlinePage {
public static final String NAME = "CheckBox";
@ -25,6 +25,7 @@ public class CheckBoxPage extends OutlinePage {
public CheckBoxPage() {
super();
addPageHeader();
addFormattedText("""
A tri-state selection control is typically skinned as a box \
with a checkmark or tick mark when checked."""

@ -2,7 +2,7 @@
package atlantafx.sampler.page.components;
public class ChoiceBoxPage extends ComboBoxPage {
public final class ChoiceBoxPage extends ComboBoxPage {
public static final String NAME = "ChoiceBox";

@ -11,7 +11,7 @@ import javafx.scene.layout.GridPane;
import javafx.scene.layout.HBox;
import javafx.scene.paint.Color;
public class ColorPickerPage extends OutlinePage {
public final class ColorPickerPage extends OutlinePage {
public static final String NAME = "ColorPicker";
@ -23,6 +23,7 @@ public class ColorPickerPage extends OutlinePage {
public ColorPickerPage() {
super();
addPageHeader();
addFormattedText("""
ColorPicker control allows the user to select a color from either \
a standard palette of colors with a simple one click selection or \

@ -33,6 +33,7 @@ public class ComboBoxPage extends OutlinePage {
public ComboBoxPage() {
super();
addPageHeader();
addFormattedText("""
A user interface component which shows a list of items out of which \
user can select at most one item. JavaFX provides two pretty similar \

@ -19,7 +19,7 @@ import org.kordamp.ikonli.Ikon;
import org.kordamp.ikonli.feather.Feather;
import org.kordamp.ikonli.javafx.FontIcon;
public class ContextMenuPage extends AbstractPage {
public final class ContextMenuPage extends AbstractPage {
public static final String NAME = "ContextMenu";
@ -31,6 +31,7 @@ public class ContextMenuPage extends AbstractPage {
public ContextMenuPage() {
super();
addPageHeader();
addFormattedText("""
A popup control containing a list of menu items. It allows for any [i]MenuItem[/i], \
including its subclasses, to be inserted. A common use case for this class is \

@ -1,6 +1,6 @@
/* SPDX-License-Identifier: MIT */
package atlantafx.sampler.page.extras;
package atlantafx.sampler.page.components;
import atlantafx.base.controls.CustomTextField;
import atlantafx.base.controls.MaskTextField;
@ -10,6 +10,7 @@ import atlantafx.base.util.BBCodeParser;
import atlantafx.sampler.page.ExampleBox;
import atlantafx.sampler.page.OutlinePage;
import atlantafx.sampler.page.Snippet;
import java.net.URI;
import java.time.LocalTime;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
@ -19,12 +20,13 @@ import javafx.scene.Cursor;
import javafx.scene.control.Label;
import javafx.scene.layout.HBox;
import javafx.scene.layout.VBox;
import org.jetbrains.annotations.Nullable;
import org.kordamp.ikonli.feather.Feather;
import org.kordamp.ikonli.javafx.FontIcon;
import org.kordamp.ikonli.material2.Material2OutlinedAL;
import org.kordamp.ikonli.material2.Material2OutlinedMZ;
public class CustomTextFieldPage extends OutlinePage {
public final class CustomTextFieldPage extends OutlinePage {
public static final String NAME = "CustomTextField";
@ -33,9 +35,15 @@ public class CustomTextFieldPage extends OutlinePage {
return NAME;
}
@Override
public URI getJavadocUri() {
return URI.create(String.format(AFX_JAVADOC_URI_TEMPLATE, "controls/" + getName()));
}
public CustomTextFieldPage() {
super();
addPageHeader();
addFormattedText("""
A base class for placing nodes inside the text field itself, without being \
on top of the users typed-in text."""
@ -143,7 +151,6 @@ public class CustomTextFieldPage extends OutlinePage {
timeField.textProperty().addListener((obs, old, val) -> {
if (val != null) {
try {
//noinspection ResultOfMethodCallIgnored
LocalTime.parse(val, timeFormatter);
timeField.pseudoClassStateChanged(Styles.STATE_DANGER, false);
} catch (DateTimeParseException e) {

@ -16,7 +16,7 @@ import javafx.scene.layout.GridPane;
import javafx.scene.layout.HBox;
import javafx.util.StringConverter;
public class DatePickerPage extends OutlinePage {
public final class DatePickerPage extends OutlinePage {
public static final String NAME = "DatePicker";
@ -28,6 +28,7 @@ public class DatePickerPage extends OutlinePage {
public DatePickerPage() {
super();
addPageHeader();
addFormattedText("""
A date picker control that allows the user to enter a date as text or to select \
a date from a calendar popup. The calendar is based on either the standard \

@ -1,11 +1,12 @@
/* SPDX-License-Identifier: MIT */
package atlantafx.sampler.page.extras;
package atlantafx.sampler.page.components;
import atlantafx.base.layout.DeckPane;
import atlantafx.base.theme.Styles;
import atlantafx.sampler.Resources;
import atlantafx.sampler.page.AbstractPage;
import java.net.URI;
import java.util.function.Supplier;
import javafx.geometry.Pos;
import javafx.scene.Node;
@ -23,7 +24,7 @@ import org.kordamp.ikonli.javafx.FontIcon;
import org.kordamp.ikonli.material2.Material2AL;
import org.kordamp.ikonli.material2.Material2MZ;
public class DeckPanePage extends AbstractPage {
public final class DeckPanePage extends AbstractPage {
public static final String NAME = "DeckPane";
@ -32,18 +33,25 @@ public class DeckPanePage extends AbstractPage {
return NAME;
}
@Override
public URI getJavadocUri() {
return URI.create(String.format(AFX_JAVADOC_URI_TEMPLATE, "layout/" + getName()));
}
public DeckPanePage() {
super();
addPageHeader();
addFormattedText("""
[i]DeckPane[/i] represents a pane that displays all of its child nodes in a deck, \
where only one node can be visible at a time. It does not maintain any sequence \
(model), but only cares about the top node, which can be changed by various \
transition effects.
Using the control is as simple as calling the [code]swipeX(Node)[/code] and \
[code]slideX(Node)[/code] methods, where [code]X[/code] represents the direction \
of the transition that you want to achieve.""");
of the transition that you want to achieve."""
);
addNode(createGallery());
}

@ -25,7 +25,7 @@ import javafx.scene.layout.Priority;
import org.kordamp.ikonli.feather.Feather;
import org.kordamp.ikonli.javafx.FontIcon;
public class DialogPage extends OutlinePage {
public final class DialogPage extends OutlinePage {
public static final String NAME = "Dialog";
@ -37,6 +37,7 @@ public class DialogPage extends OutlinePage {
public DialogPage() {
super();
addPageHeader();
addFormattedText("""
Dialog is a user interface component that allows to create dialog windows \
that can be used to prompt users for information or to display messages or warnings."""

@ -10,13 +10,14 @@ import atlantafx.sampler.event.ThemeEvent;
import atlantafx.sampler.page.AbstractPage;
import atlantafx.sampler.theme.HighlightJSTheme;
import atlantafx.sampler.theme.ThemeManager;
import java.net.URI;
import javafx.css.PseudoClass;
import javafx.geometry.Pos;
import javafx.scene.layout.Priority;
import javafx.scene.layout.VBox;
import javafx.scene.web.HTMLEditor;
public class HtmlEditorPage extends AbstractPage {
public final class HtmlEditorPage extends AbstractPage {
private static final PseudoClass USE_LOCAL_URL = PseudoClass.getPseudoClass("use-local-url");
@ -27,11 +28,17 @@ public class HtmlEditorPage extends AbstractPage {
return NAME;
}
private HTMLEditor editor = createHTMLEditor();
@Override
public URI getJavadocUri() {
return URI.create("https://openjfx.io/javadoc/20/javafx.web/javafx/scene/web/HTMLEditor.html");
}
private HTMLEditor editor = createHtmlEditor();
public HtmlEditorPage() {
super();
addPageHeader();
addFormattedText("""
A control that allows for users to edit text, and apply styling to this text. \
The underlying data model is HTML, although this is not shown visually to the end-user."""
@ -67,7 +74,7 @@ public class HtmlEditorPage extends AbstractPage {
fixToggle.selectedProperty().addListener((obs, old, val) -> {
// toolbar icons can't be changed back without creating new editor instance #javafx-bug
try {
editor = createHTMLEditor();
editor = createHtmlEditor();
editor.pseudoClassStateChanged(USE_LOCAL_URL, val);
content.getChildren().set(0, editor);
editor.requestFocus();
@ -80,7 +87,7 @@ public class HtmlEditorPage extends AbstractPage {
return content;
}
private HTMLEditor createHTMLEditor() {
private HTMLEditor createHtmlEditor() {
var editor = new HTMLEditor();
editor.setPrefHeight(400);
editor.setHtmlText(generateContent());

@ -1,12 +1,13 @@
/* SPDX-License-Identifier: MIT */
package atlantafx.sampler.page.extras;
package atlantafx.sampler.page.components;
import atlantafx.base.theme.Styles;
import atlantafx.base.util.BBCodeParser;
import atlantafx.sampler.page.ExampleBox;
import atlantafx.sampler.page.OutlinePage;
import atlantafx.sampler.page.Snippet;
import java.net.URI;
import javafx.geometry.Pos;
import javafx.scene.control.Button;
import javafx.scene.control.CheckBox;
@ -18,10 +19,11 @@ import javafx.scene.control.TextField;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Priority;
import javafx.scene.layout.VBox;
import org.jetbrains.annotations.Nullable;
import org.kordamp.ikonli.feather.Feather;
import org.kordamp.ikonli.javafx.FontIcon;
public class InputGroupPage extends OutlinePage {
public final class InputGroupPage extends OutlinePage {
public static final String NAME = "Input Group";
@ -30,14 +32,21 @@ public class InputGroupPage extends OutlinePage {
return NAME;
}
@Override
public @Nullable URI getJavadocUri() {
return null;
}
public InputGroupPage() {
super();
addPageHeader();
addFormattedText("""
You can use the following utility classes: [code]Styles.LEFT_PILL[/code], \
[code]Styles.CENTER_PILL[/code], and [code]Styles.RIGHT_PILL[/code] to combine \
various input controls into input groups that allow them to appear as a single \
control. This is entirely a CSS feature and does not require any additional wrappers.""");
control. This is entirely a CSS feature and does not require any additional wrappers."""
);
addSection("ComboBox", comboBoxExample());
addSection("Button", buttonExample());
addSection("Text Field", textFieldExample());

@ -1,79 +0,0 @@
/* SPDX-License-Identifier: MIT */
package atlantafx.sampler.page.components;
import atlantafx.base.theme.Styles;
import atlantafx.base.util.BBCodeParser;
import atlantafx.sampler.page.AbstractPage;
import javafx.scene.control.Label;
import javafx.scene.layout.FlowPane;
import javafx.scene.layout.VBox;
import org.kordamp.ikonli.javafx.FontIcon;
import org.kordamp.ikonli.material2.Material2AL;
public class LabelPage extends AbstractPage {
public static final String NAME = "Label";
@Override
public String getName() {
return NAME;
}
public LabelPage() {
super();
addFormattedText("""
Label is a non-editable text control. A [i]Label[/i] is useful for displaying text that \
is required to fit within a specific space, and thus may need to use an ellipsis \
or truncation to size the string to fit."""
);
addNode(colorExample());
}
private VBox colorExample() {
var defaultLabel = new Label("default", createFontIcon());
var accentLabel = new Label("accent", createFontIcon());
accentLabel.getStyleClass().add(Styles.ACCENT);
var successLabel = new Label("success", createFontIcon());
successLabel.getStyleClass().add(Styles.SUCCESS);
var warningLabel = new Label("warning", createFontIcon());
warningLabel.getStyleClass().add(Styles.WARNING);
var dangerLabel = new Label("danger", createFontIcon());
dangerLabel.getStyleClass().add(Styles.DANGER);
var mutedLabel = new Label("muted", createFontIcon());
mutedLabel.getStyleClass().add(Styles.TEXT_MUTED);
var subtleLabel = new Label("subtle", createFontIcon());
subtleLabel.getStyleClass().add(Styles.TEXT_SUBTLE);
var description = BBCodeParser.createFormattedText("""
You can use pseudo-classes to set the [i]Label[/i] color. Note that icon \
inherits label color by default."""
);
var labels = new FlowPane(
20, 20,
defaultLabel,
accentLabel,
successLabel,
warningLabel,
dangerLabel,
mutedLabel,
subtleLabel
);
return new VBox(20, description, labels);
}
private FontIcon createFontIcon(String... stylesClass) {
var icon = new FontIcon(Material2AL.LABEL);
icon.getStyleClass().addAll(stylesClass);
return icon;
}
}

@ -37,7 +37,7 @@ import javafx.util.StringConverter;
import org.kordamp.ikonli.feather.Feather;
import org.kordamp.ikonli.javafx.FontIcon;
public class ListViewPage extends OutlinePage {
public final class ListViewPage extends OutlinePage {
public static final String NAME = "ListView";
@ -49,10 +49,12 @@ public class ListViewPage extends OutlinePage {
public ListViewPage() {
super();
addPageHeader();
addFormattedText("""
A [i]ListView[/i] displays a horizontal or vertical list of items from which the \
user may select, or with which the user may interact. A ListView is able to \
have its generic type set to represent the type of data in the backing model.""");
have its generic type set to represent the type of data in the backing model."""
);
addSection("Usage", usageExample());
addSection("Row Style", rowStyleExample());
addSection("Selection Color", selectionColorExample());

@ -27,7 +27,7 @@ import org.kordamp.ikonli.feather.Feather;
import org.kordamp.ikonli.javafx.FontIcon;
import org.kordamp.ikonli.material2.Material2OutlinedAL;
public class MenuBarPage extends AbstractPage {
public final class MenuBarPage extends AbstractPage {
public static final String NAME = "MenuBar";
@ -39,6 +39,7 @@ public class MenuBarPage extends AbstractPage {
public MenuBarPage() {
super();
addPageHeader();
addFormattedText("""
A menu bar is a user interface component that typically appears at the top of \
an application window or screen, and provides a series of drop-down menus that \

@ -19,7 +19,7 @@ import javafx.scene.layout.HBox;
import org.kordamp.ikonli.feather.Feather;
import org.kordamp.ikonli.javafx.FontIcon;
public class MenuButtonPage extends OutlinePage {
public final class MenuButtonPage extends OutlinePage {
public static final String NAME = "MenuButton";
@ -31,6 +31,7 @@ public class MenuButtonPage extends OutlinePage {
public MenuButtonPage() {
super();
addPageHeader();
addFormattedText("""
[i]MenuButton[/i] is a button which, when clicked or pressed, will \
show a context (dropdown) menu.

@ -1,6 +1,6 @@
/* SPDX-License-Identifier: MIT */
package atlantafx.sampler.page.extras;
package atlantafx.sampler.page.components;
import atlantafx.base.controls.ModalPane;
import atlantafx.base.util.BBCodeParser;
@ -23,9 +23,9 @@ import javafx.scene.layout.VBox;
import javafx.scene.text.Text;
import javafx.scene.text.TextFlow;
public class ModalPanePage extends OutlinePage {
public final class ModalPanePage extends OutlinePage {
public static final String NAME = "Modal Pane";
public static final String NAME = "ModalPane";
private final ModalPane modalPane = new ModalPane();
private final ModalPane modalPaneTop = new ModalPane(-15);
@ -50,10 +50,12 @@ public class ModalPanePage extends OutlinePage {
}
});
addPageHeader();
addFormattedText("""
A container for displaying application dialogs ot top of the current scene \
without opening a modal {@link javafx.stage.Stage}. It's a translucent (glass) pane \
that can hold arbitrary content as well as animate its appearance.""");
without opening a modal [code]javafx.stage.Stage[/code]. It's a translucent \
(glass) pane that can hold arbitrary content as well as animate its appearance."""
);
addSection("Usage", usageExample());
addSection("Content Position", contentPositionExample());
addSection("Persistent", persistentExample());
@ -346,7 +348,10 @@ public class ModalPanePage extends OutlinePage {
implement the famous JS lightbox effect."""
);
return new ExampleBox(box, new Snippet(getClass(), 7), description);
var example = new ExampleBox(box, new Snippet(getClass(), 7), description);
example.setAllowDisable(false);
return example;
}
///////////////////////////////////////////////////////////////////////////

@ -20,7 +20,7 @@ import javafx.scene.layout.GridPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.VBox;
public class PaginationPage extends OutlinePage {
public final class PaginationPage extends OutlinePage {
public static final String NAME = "Pagination";
@ -32,6 +32,7 @@ public class PaginationPage extends OutlinePage {
public PaginationPage() {
super();
addPageHeader();
addFormattedText("""
A [i]Pagination[/i] control is used for navigation between pages of a single content, \
which has been divided into smaller parts."""

@ -1,6 +1,6 @@
/* SPDX-License-Identifier: MIT */
package atlantafx.sampler.page.extras;
package atlantafx.sampler.page.components;
import atlantafx.base.controls.InlineDatePicker;
import atlantafx.base.controls.Popover;
@ -10,6 +10,7 @@ import atlantafx.sampler.page.ExampleBox;
import atlantafx.sampler.page.OutlinePage;
import atlantafx.sampler.page.Snippet;
import atlantafx.sampler.theme.CSSFragment;
import java.net.URI;
import java.time.LocalDate;
import java.time.ZoneId;
import javafx.geometry.Insets;
@ -20,7 +21,7 @@ import javafx.scene.layout.HBox;
import javafx.scene.text.Text;
import javafx.scene.text.TextFlow;
public class PopoverPage extends OutlinePage {
public final class PopoverPage extends OutlinePage {
public static final String NAME = "Popover";
@ -29,15 +30,22 @@ public class PopoverPage extends OutlinePage {
return NAME;
}
@Override
public URI getJavadocUri() {
return URI.create(String.format(AFX_JAVADOC_URI_TEMPLATE, "controls/" + getName()));
}
public PopoverPage() {
super();
addPageHeader();
addFormattedText("""
The [i]Popover[/i] is a control used to display additional information \
or perform actions. It appears as a small popup window that overlays the \
main interface, triggered by a user action such as a mouseover or tap. \
It provides contextual information or options related to a specific object \
or feature on the interface.""");
or feature on the interface."""
);
addSection("Usage", usageExample());
addSection("Position", positionExample());
}

@ -33,7 +33,7 @@ import org.kordamp.ikonli.javafx.FontIcon;
// #javafx-bug Indeterminate (animated) progress bar and also progress indicator
// are very resource expensive. It consumes a single CPU core and a lot of memory.
public class ProgressIndicatorPage extends OutlinePage {
public final class ProgressIndicatorPage extends OutlinePage {
public static final String NAME = "ProgressIndicator";
@ -45,6 +45,7 @@ public class ProgressIndicatorPage extends OutlinePage {
public ProgressIndicatorPage() {
super();
addPageHeader();
addFormattedText("""
JavaFX provides the two types of progress indicators: the [i]ProgressIndicator[/i] and \
the [i]ProgressBar[/i]. In addition to them AtlantaFX also provides the \

@ -10,7 +10,7 @@ import javafx.scene.control.RadioButton;
import javafx.scene.control.ToggleGroup;
import javafx.scene.layout.VBox;
public class RadioButtonPage extends OutlinePage {
public final class RadioButtonPage extends OutlinePage {
public static final String NAME = "RadioButton";
@ -22,8 +22,10 @@ public class RadioButtonPage extends OutlinePage {
public RadioButtonPage() {
super();
addPageHeader();
addFormattedText("""
[i]RadioButton[/i]'s create a series of items where only one item can be selected.""");
[i]RadioButton[/i]'s create a series of items where only one item can be selected."""
);
addSection("Usage", usageExample());
addSection("Toggle Group", toggleGroupExample());
}

@ -10,7 +10,7 @@ import javafx.scene.layout.HBox;
import javafx.scene.layout.Region;
import javafx.scene.layout.VBox;
public class ScrollPanePage extends AbstractPage {
public final class ScrollPanePage extends AbstractPage {
public static final String NAME = "ScrollPane";
@ -22,6 +22,7 @@ public class ScrollPanePage extends AbstractPage {
public ScrollPanePage() {
super();
addPageHeader();
addFormattedText("""
A control that provides a scrolled, clipped viewport of its contents. \
It allows the user to scroll the content around either directly (panning) \

@ -27,6 +27,7 @@ public final class SeparatorPage extends OutlinePage {
public SeparatorPage() {
super();
addPageHeader();
addFormattedText("""
A horizontal or vertical separator line. A horizontal separator occupies \
the full horizontal space allocated to it (less padding), and a vertical \

@ -11,7 +11,7 @@ import javafx.geometry.Orientation;
import javafx.scene.control.Slider;
import javafx.scene.layout.GridPane;
public class SliderPage extends OutlinePage {
public final class SliderPage extends OutlinePage {
public static final String NAME = "Slider";
@ -23,6 +23,7 @@ public class SliderPage extends OutlinePage {
public SliderPage() {
super();
addPageHeader();
addFormattedText("""
The [i]Slider[/i] control is used to display a continuous or discrete range of valid numeric \
choices. It is typically represented visually as having a [i]track[/i] and a [i]knob[/i] \

@ -24,7 +24,8 @@ public final class SpinnerPage extends OutlinePage {
public SpinnerPage() {
super();
addPlainText("""
addPageHeader();
addFormattedText("""
A single line text field that lets the user select a number or an object \
value from an ordered sequence. The user may also be allowed to type a (legal) \
value directly into the spinner."""

@ -13,7 +13,7 @@ import javafx.scene.layout.HBox;
import javafx.scene.layout.Priority;
import javafx.scene.layout.VBox;
public class SplitPanePage extends OutlinePage {
public final class SplitPanePage extends OutlinePage {
public static final String NAME = "SplitPane";
@ -25,6 +25,7 @@ public class SplitPanePage extends OutlinePage {
public SplitPanePage() {
super();
addPageHeader();
addFormattedText("""
A control that has two or more sides, each separated by a divider, which can be \
dragged by the user to give more space to one of the sides, resulting in the other \

@ -33,7 +33,7 @@ import javafx.scene.layout.VBox;
import org.kordamp.ikonli.feather.Feather;
import org.kordamp.ikonli.javafx.FontIcon;
public class TabPanePage extends OutlinePage {
public final class TabPanePage extends OutlinePage {
public static final String NAME = "TabPane";
@ -45,6 +45,7 @@ public class TabPanePage extends OutlinePage {
public TabPanePage() {
super();
addPageHeader();
addFormattedText("""
[i]TabPane[/i] is a control that provides a container for a group of tabs. By clicking \
on a tab, the content of that tab becomes visible, while the content of the previously \

@ -58,7 +58,7 @@ import org.kordamp.ikonli.feather.Feather;
import org.kordamp.ikonli.javafx.FontIconTableCell;
@SuppressWarnings("unchecked")
public class TableViewPage extends OutlinePage {
public final class TableViewPage extends OutlinePage {
public static final String NAME = "TableView";
@ -117,6 +117,7 @@ public class TableViewPage extends OutlinePage {
public TableViewPage() {
super();
addPageHeader();
addFormattedText("""
The [i]TableView[/i] control is designed to visualize an unlimited number of rows of data, \
broken out into columns."""

@ -12,7 +12,7 @@ import java.util.stream.Stream;
import javafx.scene.control.TextArea;
import javafx.scene.layout.HBox;
public class TextAreaPage extends OutlinePage {
public final class TextAreaPage extends OutlinePage {
public static final String NAME = "TextArea";
@ -24,6 +24,7 @@ public class TextAreaPage extends OutlinePage {
public TextAreaPage() {
super();
addPageHeader();
addFormattedText(
"Text input component that allows a user to enter multiple lines of plain text."
);

@ -12,7 +12,7 @@ import javafx.scene.control.PasswordField;
import javafx.scene.control.TextField;
import javafx.scene.layout.HBox;
public class TextFieldPage extends OutlinePage {
public final class TextFieldPage extends OutlinePage {
public static final String NAME = "TextField";
@ -24,6 +24,7 @@ public class TextFieldPage extends OutlinePage {
public TextFieldPage() {
super();
addPageHeader();
addFormattedText("""
Text input component that allows a user to enter a single line of unformatted text."""
);

@ -27,7 +27,7 @@ import javafx.scene.layout.VBox;
import javafx.scene.text.Text;
import javafx.scene.text.TextFlow;
public class TitledPanePage extends OutlinePage {
public final class TitledPanePage extends OutlinePage {
public static final String NAME = "TitledPane";
@ -39,6 +39,7 @@ public class TitledPanePage extends OutlinePage {
public TitledPanePage() {
super();
addPageHeader();
addFormattedText("""
[i]TitledPane[/i] is a panel with a title that can be opened and closed. \
It holds one or more user interface elements and you can expand and collapse it. \

@ -16,7 +16,7 @@ import javafx.scene.layout.HBox;
import org.kordamp.ikonli.feather.Feather;
import org.kordamp.ikonli.javafx.FontIcon;
public class ToggleButtonPage extends OutlinePage {
public final class ToggleButtonPage extends OutlinePage {
public static final String NAME = "ToggleButton";
@ -28,6 +28,7 @@ public class ToggleButtonPage extends OutlinePage {
public ToggleButtonPage() {
super();
addPageHeader();
addFormattedText("""
A [i]ToggleButton[/i] is a special control having the ability to be selected."""
);

@ -1,6 +1,6 @@
/* SPDX-License-Identifier: MIT */
package atlantafx.sampler.page.extras;
package atlantafx.sampler.page.components;
import atlantafx.base.controls.ToggleSwitch;
import atlantafx.base.theme.Styles;
@ -8,11 +8,12 @@ import atlantafx.base.util.BBCodeParser;
import atlantafx.sampler.page.ExampleBox;
import atlantafx.sampler.page.OutlinePage;
import atlantafx.sampler.page.Snippet;
import java.net.URI;
import javafx.geometry.HorizontalDirection;
import javafx.geometry.Pos;
import javafx.scene.layout.HBox;
public class ToggleSwitchPage extends OutlinePage {
public final class ToggleSwitchPage extends OutlinePage {
public static final String NAME = "ToggleSwitch";
@ -21,9 +22,15 @@ public class ToggleSwitchPage extends OutlinePage {
return NAME;
}
@Override
public URI getJavadocUri() {
return URI.create(String.format(AFX_JAVADOC_URI_TEMPLATE, "controls/" + getName()));
}
public ToggleSwitchPage() {
super();
addPageHeader();
addFormattedText("""
The [i]ToggleSwitch[/i] is a control used to activate or deactivate a feature. \
It consists of a horizontal bar with a small knob that can be moved to turn on \

@ -57,7 +57,7 @@ import org.kordamp.ikonli.javafx.FontIcon;
import org.kordamp.ikonli.material2.Material2OutlinedAL;
import org.kordamp.ikonli.material2.Material2OutlinedMZ;
public class ToolBarPage extends OutlinePage {
public final class ToolBarPage extends OutlinePage {
public static final String NAME = "ToolBar";
@ -69,6 +69,7 @@ public class ToolBarPage extends OutlinePage {
public ToolBarPage() {
super();
addPageHeader();
addFormattedText("""
A [i]ToolBar[/i] is a control which displays items horizontally or vertically."""
);

@ -14,7 +14,7 @@ import javafx.scene.layout.HBox;
import javafx.stage.PopupWindow.AnchorLocation;
import javafx.util.Duration;
public class TooltipPage extends OutlinePage {
public final class TooltipPage extends OutlinePage {
public static final String NAME = "Tooltip";
@ -26,13 +26,15 @@ public class TooltipPage extends OutlinePage {
public TooltipPage() {
super();
addPageHeader();
addFormattedText("""
Tooltips are used for showing additional information when the node is hovered over by the mouse.
[ul]
[li]Any node can show a tooltip.[/li]
[li]A Tooltip is able to show within it an arbitrary scenegraph of nodes.[/li]
[li]A single tooltip can be installed on multiple target nodes or multiple controls.[/li][/ul]""");
[li]A single tooltip can be installed on multiple target nodes or multiple controls.[/li][/ul]"""
);
addSection("Usage", usageExample());
addSection("Position", positionExample());
}

@ -43,7 +43,7 @@ import javafx.scene.layout.Priority;
import javafx.scene.layout.VBox;
import javafx.util.Callback;
public class TreeTableViewPage extends OutlinePage {
public final class TreeTableViewPage extends OutlinePage {
public static final String NAME = "TreeTableView";
@ -57,10 +57,12 @@ public class TreeTableViewPage extends OutlinePage {
public TreeTableViewPage() {
super();
addPageHeader();
addFormattedText("""
The [i]TreeTableView[/i] control is conceptually very similar to the \
[i]TreeView[/i] and [i]TableView[/i] controls and basically supports the \
same features. Please, see the corresponding pages for more examples.""");
same features. Please, see the corresponding pages for more examples."""
);
addSection("Usage", usageExample());
addSection("Playground", playground());
}

@ -37,7 +37,7 @@ import javafx.util.StringConverter;
import org.kordamp.ikonli.feather.Feather;
import org.kordamp.ikonli.javafx.FontIcon;
public class TreeViewPage extends OutlinePage {
public final class TreeViewPage extends OutlinePage {
public static final String NAME = "TreeView";
@ -49,6 +49,7 @@ public class TreeViewPage extends OutlinePage {
public TreeViewPage() {
super();
addPageHeader();
addFormattedText("""
The [i]TreeView[/i] provides a way to present tree structures. A tree has \
a root node which contains all the hierarchical values."""

@ -15,7 +15,7 @@ import javafx.scene.layout.Region;
import org.kordamp.ikonli.javafx.FontIcon;
import org.kordamp.ikonli.material2.Material2AL;
public class AccentColorSelector extends HBox {
final class AccentColorSelector extends HBox {
public AccentColorSelector() {
super();

@ -1,18 +1,20 @@
/* SPDX-License-Identifier: MIT */
package atlantafx.sampler.page.extras;
package atlantafx.sampler.page.general;
import atlantafx.base.theme.Styles;
import atlantafx.base.util.BBCodeParser;
import atlantafx.sampler.page.OutlinePage;
import java.net.URI;
import javafx.geometry.Insets;
import javafx.scene.control.Label;
import javafx.scene.layout.HBox;
import javafx.scene.layout.VBox;
import javafx.scene.text.Text;
import javafx.scene.text.TextFlow;
import org.jetbrains.annotations.Nullable;
public class BBCodePage extends OutlinePage {
public final class BBCodePage extends OutlinePage {
public static final String NAME = "BBCode Markup";
@ -21,15 +23,21 @@ public class BBCodePage extends OutlinePage {
return NAME;
}
@Override
public @Nullable URI getJavadocUri() {
return URI.create(String.format(AFX_JAVADOC_URI_TEMPLATE, "util/BBCodeParser"));
}
public BBCodePage() {
super();
addPageHeader();
addFormattedText("""
BBCode (Bulletin Board Code) is a lightweight markup language used to \
format messages in many Internet forum software. The available tags of \
BBCode are indicated by square brackets surrounding a keyword, and are \
parsed before being translated into [s]HTML[/s] JavaFX layout :)""");
parsed before being translated into [s]HTML[/s] JavaFX layout :)"""
);
addSection("Text Type", textTypeReference());
addSection("Text Style", textStyleReference());
addSection("Subscript and Superscript", subscriptReference());

@ -14,7 +14,7 @@ import javafx.scene.layout.GridPane;
import javafx.scene.paint.Color;
import javafx.util.Duration;
class ColorPalette extends GridPane {
final class ColorPalette extends GridPane {
private final List<ColorPaletteBlock> blocks = new ArrayList<>();
private final ReadOnlyObjectWrapper<Color> bgBaseColor = new ReadOnlyObjectWrapper<>(Color.WHITE);

@ -24,7 +24,7 @@ import javafx.scene.text.Text;
import org.kordamp.ikonli.javafx.FontIcon;
import org.kordamp.ikonli.material2.Material2AL;
class ColorPaletteBlock extends VBox {
final class ColorPaletteBlock extends VBox {
private final String fgColorName;
private final String bgColorName;

@ -11,7 +11,7 @@ import javafx.scene.layout.FlowPane;
import javafx.scene.paint.Color;
import javafx.util.Duration;
class ColorScale extends FlowPane {
final class ColorScale extends FlowPane {
private final ReadOnlyObjectWrapper<Color> bgBaseColor = new ReadOnlyObjectWrapper<>(Color.WHITE);
private final List<ColorScaleBlock> blocks = Arrays.asList(

@ -12,7 +12,7 @@ import javafx.scene.control.Label;
import javafx.scene.layout.VBox;
import javafx.scene.paint.Color;
class ColorScaleBlock extends VBox {
final class ColorScaleBlock extends VBox {
private static final double BLOCK_WIDTH = 200;
private static final double BLOCK_HEIGHT = 40;

@ -43,7 +43,7 @@ import org.kordamp.ikonli.javafx.FontIcon;
import org.kordamp.ikonli.material2.Material2AL;
// Inspired by the https://colourcontrast.cc/
class ContrastChecker extends GridPane {
final class ContrastChecker extends GridPane {
public static final double CONTRAST_RATIO_THRESHOLD = 1.5;
public static final double LUMINANCE_THRESHOLD = 0.55;

@ -2,7 +2,7 @@
package atlantafx.sampler.page.general;
import atlantafx.sampler.page.OverlayDialog;
import atlantafx.sampler.layout.OverlayDialog;
import atlantafx.sampler.util.JColorUtils;
import atlantafx.sampler.util.NodeUtils;
import javafx.beans.property.ReadOnlyObjectProperty;

@ -17,7 +17,7 @@ import javafx.scene.control.TableView;
import org.kordamp.ikonli.Ikon;
import org.kordamp.ikonli.javafx.FontIcon;
public class IconBrowser extends TableView<List<Ikon>> {
final class IconBrowser extends TableView<List<Ikon>> {
static final int FILTER_LEN = 2;

@ -9,6 +9,7 @@ import atlantafx.sampler.page.ExampleBox;
import atlantafx.sampler.page.OutlinePage;
import atlantafx.sampler.page.Snippet;
import atlantafx.sampler.theme.CSSFragment;
import java.net.URI;
import java.util.ArrayList;
import java.util.Arrays;
import javafx.geometry.Pos;
@ -16,6 +17,7 @@ import javafx.scene.Node;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Priority;
import javafx.scene.layout.VBox;
import org.jetbrains.annotations.Nullable;
import org.kordamp.ikonli.Ikon;
import org.kordamp.ikonli.javafx.FontIcon;
import org.kordamp.ikonli.javafx.StackedFontIcon;
@ -23,7 +25,7 @@ import org.kordamp.ikonli.material2.Material2AL;
import org.kordamp.ikonli.material2.Material2MZ;
import org.kordamp.ikonli.material2.Material2OutlinedAL;
public class IconsPage extends OutlinePage {
public final class IconsPage extends OutlinePage {
public static final String NAME = "Icons";
@ -32,12 +34,20 @@ public class IconsPage extends OutlinePage {
return NAME;
}
@Override
public @Nullable URI getJavadocUri() {
return null;
}
public IconsPage() {
super();
addPageHeader();
addFormattedText("""
AtlantaFX supports [url=https://kordamp.org/ikonli]Ikonli[/url] iconic fonts out \
of the box, which can be used in conjunction with certain JavaFX components.""", true);
of the box, which can be used in conjunction with certain JavaFX components.""",
true
);
addSection("Color", colorExample());
addSection("Stacking", stackingExample());
addSection("Icon Pack", iconBrowser());

@ -11,7 +11,7 @@ import atlantafx.base.theme.Styles;
import atlantafx.sampler.Resources;
import atlantafx.sampler.event.BrowseEvent;
import atlantafx.sampler.event.DefaultEventBus;
import atlantafx.sampler.page.OverlayDialog;
import atlantafx.sampler.layout.OverlayDialog;
import atlantafx.sampler.page.general.SceneBuilderDialogModel.Screen;
import atlantafx.sampler.util.NodeUtils;
import java.io.File;

@ -20,7 +20,7 @@ import javafx.concurrent.Task;
import javafx.scene.control.ToggleGroup;
import org.jetbrains.annotations.Nullable;
class SceneBuilderDialogModel {
final class SceneBuilderDialogModel {
public enum Screen {
// order matters as it determines swipe direction when switching between screens

@ -21,7 +21,7 @@ import java.util.Objects;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
class SceneBuilderInstaller {
final class SceneBuilderInstaller {
private static final String THEME_PACK_FILE_NAME = "atlantafx-scene-builder.zip";

@ -13,7 +13,9 @@ import atlantafx.sampler.page.OutlinePage;
import atlantafx.sampler.theme.SamplerTheme;
import atlantafx.sampler.theme.ThemeManager;
import atlantafx.sampler.util.Lazy;
import java.net.URI;
import java.util.Objects;
import javafx.application.Platform;
import javafx.beans.property.ReadOnlyObjectProperty;
import javafx.collections.FXCollections;
import javafx.geometry.HPos;
@ -33,11 +35,12 @@ import javafx.scene.paint.Color;
import javafx.scene.text.Font;
import javafx.util.Duration;
import javafx.util.StringConverter;
import org.jetbrains.annotations.Nullable;
import org.kordamp.ikonli.javafx.FontIcon;
import org.kordamp.ikonli.material2.Material2OutlinedMZ;
@SuppressWarnings("UnnecessaryLambda")
public class ThemePage extends OutlinePage {
public final class ThemePage extends OutlinePage {
public static final String NAME = "Theme";
@ -62,6 +65,11 @@ public class ThemePage extends OutlinePage {
return false;
}
@Override
public @Nullable URI getJavadocUri() {
return null;
}
///////////////////////////////////////////////////////////////////////////
private final Lazy<ThemeRepoManagerDialog> themeRepoManagerDialog;
@ -131,12 +139,13 @@ public class ThemePage extends OutlinePage {
}
});
addSection("Theme", createThemeManagementSection());
addPageHeader();
addNode(createThemeManagementSection());
addSection("Scene Builder", createSceneBuilderSection());
addSection("Color Palette", createColorPaletteSection());
addSection("Color Scale", createColorScaleSection());
selectCurrentTheme();
Platform.runLater(this::selectCurrentTheme);
}
@Override

@ -35,7 +35,7 @@ import org.kordamp.ikonli.javafx.FontIcon;
import org.kordamp.ikonli.material2.Material2OutlinedAL;
@SuppressWarnings("UnnecessaryLambda")
class ThemeRepoManager extends VBox {
final class ThemeRepoManager extends VBox {
private static final Executor THREAD_POOL = Executors.newFixedThreadPool(3);
private static final ThemeRepository REPO = ThemeManager.getInstance().getRepository();

@ -3,7 +3,7 @@
package atlantafx.sampler.page.general;
import atlantafx.base.theme.Styles;
import atlantafx.sampler.page.OverlayDialog;
import atlantafx.sampler.layout.OverlayDialog;
import java.io.File;
import javafx.geometry.Pos;
import javafx.scene.control.Button;

@ -11,6 +11,7 @@ import atlantafx.sampler.event.ThemeEvent;
import atlantafx.sampler.page.ExampleBox;
import atlantafx.sampler.page.OutlinePage;
import atlantafx.sampler.page.Snippet;
import java.net.URI;
import java.util.Map;
import java.util.stream.Collectors;
import javafx.geometry.Insets;
@ -23,9 +24,11 @@ import javafx.scene.layout.GridPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.VBox;
import javafx.scene.text.Text;
import javafx.util.Duration;
import org.jetbrains.annotations.Nullable;
import org.kordamp.ikonli.javafx.FontIcon;
import org.kordamp.ikonli.material2.Material2AL;
public class TypographyPage extends OutlinePage {
public final class TypographyPage extends OutlinePage {
public static final String NAME = "Typography";
@ -36,24 +39,32 @@ public class TypographyPage extends OutlinePage {
return NAME;
}
@Override
public @Nullable URI getJavadocUri() {
return null;
}
public TypographyPage() {
super();
addPageHeader();
addFormattedText("""
Because AtlantaFX is also distributed as a single CSS file, it does not come \
with any fonts. However, it does support several utility classes demonstrated \
below that can be used to manipulate font properties. If you need a formatted \
text support have a look at [i]BBCodeParser[/i].""");
text support have a look at [i]BBCodeParser[/i]."""
);
addSection("Font Size", fontSizeExample());
addSection("Font Weight", fontWeightExample());
addSection("Font Style", fontStyleExample());
addSection("Text Color", textColorExample());
addSection("Label", labelExample());
addSection("Hyperlink", hyperlinkExample());
DefaultEventBus.getInstance().subscribe(ThemeEvent.class, e -> {
var eventType = e.getEventType();
if (eventType == EventType.THEME_CHANGE || eventType == EventType.FONT_CHANGE) {
updateFontInfo(Duration.seconds(1));
updateFontInfo();
}
});
}
@ -62,10 +73,10 @@ public class TypographyPage extends OutlinePage {
protected void onRendered() {
super.onRendered();
// font metrics can only be obtained by requesting from a rendered node
updateFontInfo(Duration.seconds(1));
updateFontInfo();
}
private void updateFontInfo(Duration delay) {
private void updateFontInfo() {
if (fontSizeGridPane == null) {
return;
}
@ -309,7 +320,62 @@ public class TypographyPage extends OutlinePage {
);
box.setAlignment(Pos.BASELINE_LEFT);
var example = new ExampleBox(box, new Snippet(getClass(), 5));
var description = BBCodeParser.createFormattedText("""
An HTML like label which can be a graphic and/or text which responds to \
rollovers and clicks. When a hyperlink is clicked/pressed [code]#isVisited[/code] \
becomes "true". A Hyperlink behaves just like a [i]Button[/i]."""
);
var example = new ExampleBox(box, new Snippet(getClass(), 5), description);
example.setAllowDisable(false);
return example;
}
private ExampleBox labelExample() {
//snippet_6:start
var defaultLabel = new Label("default", createFontIcon());
var accentLabel = new Label("accent", createFontIcon());
accentLabel.getStyleClass().add(Styles.ACCENT);
var successLabel = new Label("success", createFontIcon());
successLabel.getStyleClass().add(Styles.SUCCESS);
var warningLabel = new Label("warning", createFontIcon());
warningLabel.getStyleClass().add(Styles.WARNING);
var dangerLabel = new Label("danger", createFontIcon());
dangerLabel.getStyleClass().add(Styles.DANGER);
var mutedLabel = new Label("muted", createFontIcon());
mutedLabel.getStyleClass().add(Styles.TEXT_MUTED);
var subtleLabel = new Label("subtle", createFontIcon());
subtleLabel.getStyleClass().add(Styles.TEXT_SUBTLE);
//snippet_6:end
var box = new FlowPane(
20, 20,
defaultLabel,
accentLabel,
successLabel,
warningLabel,
dangerLabel,
mutedLabel,
subtleLabel
);
var description = BBCodeParser.createFormattedText("""
Label is a non-editable text control. A [i]Label[/i] is useful for displaying text \
that is required to fit within a specific space, and thus may need to use an ellipsis \
or truncation to size the string to fit.
You can use pseudo-classes to set the [i]Label[/i] color. Note that icon \
inherits label color by default."""
);
var example = new ExampleBox(box, new Snippet(getClass(), 6), description);
example.setAllowDisable(false);
return example;
@ -321,4 +387,10 @@ public class TypographyPage extends OutlinePage {
label.setStyle("-fx-font-family:monospace;");
return label;
}
private FontIcon createFontIcon(String... stylesClass) {
var icon = new FontIcon(Material2AL.LABEL);
icon.getStyleClass().addAll(stylesClass);
return icon;
}
}

@ -1,6 +1,6 @@
/* SPDX-License-Identifier: MIT */
package atlantafx.sampler.page.general;
package atlantafx.sampler.page.showcase;
import static atlantafx.sampler.util.Containers.setScrollConstraints;
import static javafx.scene.control.ScrollPane.ScrollBarPolicy.AS_NEEDED;
@ -8,6 +8,7 @@ import static javafx.scene.control.ScrollPane.ScrollBarPolicy.AS_NEEDED;
import atlantafx.sampler.Resources;
import atlantafx.sampler.page.Page;
import java.io.IOException;
import java.net.URI;
import javafx.fxml.FXMLLoader;
import javafx.geometry.Pos;
import javafx.scene.Parent;
@ -15,8 +16,9 @@ import javafx.scene.control.ScrollPane;
import javafx.scene.layout.Pane;
import javafx.scene.layout.Priority;
import javafx.scene.layout.VBox;
import org.jetbrains.annotations.Nullable;
public class OverviewPage extends ScrollPane implements Page {
public final class OverviewPage extends ScrollPane implements Page {
public static final String NAME = "Overview";
@ -65,6 +67,11 @@ public class OverviewPage extends ScrollPane implements Page {
return true;
}
@Override
public @Nullable URI getJavadocUri() {
return null;
}
@Override
public void reset() {
}

@ -3,6 +3,7 @@ package atlantafx.sampler.page.showcase;
import atlantafx.base.controls.Spacer;
import atlantafx.base.theme.Styles;
import atlantafx.sampler.page.Page;
import java.net.URI;
import java.util.Objects;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.SimpleBooleanProperty;
@ -105,6 +106,11 @@ public abstract class ShowcasePage extends StackPane implements Page {
return true;
}
@Override
public @Nullable URI getJavadocUri() {
return null;
}
@Override
public void reset() {
}

@ -58,7 +58,7 @@ public final class SamplerTheme implements Theme {
private static final int PARSE_LIMIT = 250;
private static final Pattern COLOR_PATTERN =
Pattern.compile("\s*?(-color-(fg|bg|accent|success|danger)-.+?):\s*?(.+?);");
Pattern.compile("\s*?(-color-(fg|bg|accent|success|danger|warning)-.+?):\s*?(.+?);");
private final Theme theme;

@ -9,6 +9,7 @@ import javafx.animation.Timeline;
import javafx.scene.Node;
import javafx.util.Duration;
@Deprecated
public final class Animations {
public static final Interpolator EASE = Interpolator.SPLINE(0.25, 0.1, 0.25, 1);

@ -13,6 +13,7 @@ import javafx.scene.layout.ColumnConstraints;
import javafx.scene.layout.Priority;
import javafx.scene.layout.Region;
@Deprecated
public final class Containers {
public static final ColumnConstraints H_GROW_NEVER = columnConstraints(Priority.NEVER);

@ -27,7 +27,6 @@ module atlantafx.sampler {
exports atlantafx.sampler.page;
exports atlantafx.sampler.page.general;
exports atlantafx.sampler.page.components;
exports atlantafx.sampler.page.extras;
exports atlantafx.sampler.page.showcase;
exports atlantafx.sampler.theme;
exports atlantafx.sampler.util;
@ -41,4 +40,5 @@ module atlantafx.sampler {
opens atlantafx.sampler.images;
opens atlantafx.sampler.images.modena;
opens atlantafx.sampler.page.general;
opens atlantafx.sampler.page.showcase;
}

@ -1,6 +0,0 @@
// SPDX-License-Identifier: MIT
@use "fonts";
@use "root";
@use "scene-builder-wizard";
@use "util";

@ -1,6 +1,6 @@
// SPDX-License-Identifier: MIT
@use "general";
@use "layout";
@use "widgets";
@use "components"
@use "page";
@use "theme";
@use "util"

@ -1,5 +1,7 @@
// SPDX-License-Identifier: MIT
@use "fonts";
@use "root";
@use "main";
@use "sidebar";
@use "overlay";
@use "page";

@ -1,12 +1,4 @@
@mixin link-button() {
-fx-icon-color: -color-fg-emphasis;
-fx-fill: -color-fg-emphasis;
-fx-icon-size: 20px;
&:hover {
-fx-effect: dropshadow(gaussian, -color-fg-emphasis, 20, 0.1, 0, 0)
}
}
// SPDX-License-Identifier: MIT
#main {
.source-code {
@ -25,153 +17,3 @@
}
}
}
#header-bar {
-fx-background-color: -color-accent-emphasis;
-fx-padding: 0px 20px 0px 0;
-fx-spacing: 30px;
>.logo {
-fx-padding: 0px 0 0px 10px;
>.image {
-fx-background-color:-color-success-emphasis;
-fx-background-radius: 6px;
}
>.label {
-fx-text-fill: -color-fg-emphasis;
}
>.version {
-fx-padding: -1em 0 0 0;
}
}
>.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;
}
}
>.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;
}
>.ikonli-font-icon {
@include link-button();
}
>.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 {
.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;
>.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;
}
>.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-accent-fg;
}
&:hover:filled {
-color-cell-bg: -color-accent-subtle;
}
&: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;
}
}
&:expanded>.container>.arrow {
-fx-icon-code: mdal-chevron_right;
}
}
}
}

@ -1,6 +1,6 @@
// SPDX-License-Identifier: MIT
@use "accent-colors" as ac;
@use "../theme/accent-colors" as ac;
@mixin hide() {
-fx-min-width: 0;
@ -67,4 +67,4 @@
@include ac.primerCoralDark();
}
}
}
}

@ -0,0 +1,110 @@
// SPDX-License-Identifier: MIT
#sidebar {
-fx-background-color: -color-bg-inset;
-fx-border-color: -color-border-default;
-fx-border-width: 0 1px 0 0;
>.header {
-fx-padding: 20px 10px 20px 10px;
-fx-spacing: 20px;
>.logo {
.label {
-fx-text-fill: -color-fg-muted;
}
>.palette {
>.ikonli-font-icon {
-fx-fill: -color-fg-muted;
-fx-icon-color: -color-fg-muted;
}
}
.dev-indicator {
-fx-padding: -0.75em 0 0 0;
-fx-font-size: 12px;
-fx-font-weight: normal;
-fx-cursor: hand;
}
}
>.search-button {
-color-button-bg: -color-bg-default;
-color-button-border: -color-border-default;
-color-button-border-hover: -color-border-default;
-color-button-border-focused: -color-border-default;
-color-button-border-pressed: -color-border-default;
-color-button-shadow: transparent;
}
}
>.footer {
-fx-padding: 4px 10px 4px 10px;
-fx-alignment: CENTER;
}
.nav-tree-cell {
-fx-padding: 0;
-fx-indent: 0;
-color-cell-bg: -color-bg-inset;
-color-cell-bg-selected: -color-accent-subtle;
-color-cell-bg-selected-focused: -color-accent-subtle;
-color-cell-border: -color-bg-inset;
-fx-background-radius: 5px;
>.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;
}
>.container {
-fx-min-height: 2.1em;
-fx-pref-height: 2.1em;
-fx-max-height: 2.1em;
-fx-padding: 0 0 0 1.2em;
-fx-border-color: -color-border-muted;
-fx-border-width: 0 0 0 1;
}
&:selected>.container>.title {
-fx-font-weight: bold;
-fx-text-fill: -color-accent-fg;
}
&:hover:filled {
-color-cell-bg: -color-accent-subtle;
-color-cell-bg-selected: -color-accent-subtle;
-color-cell-bg-selected-focused: -color-accent-subtle;
}
&: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-fg-muted;
-fx-icon-color: -color-fg-muted;
}
>.arrow {
-fx-icon-code: mdal-add;
}
}
&:expanded>.container>.arrow {
-fx-icon-code: mdmz-remove;
}
}
}
}

@ -1,4 +1,6 @@
// SPDX-License-Identifier: MIT
@use "layout";
@use "html-editor-fix";
@use "icon-browser";
@use "icon-browser";
@use "showcase";

Some files were not shown because too many files have changed in this diff Show More