Rewrite all Sampler pages to better UI design
This commit is contained in:
parent
7ebfa7ac0e
commit
35d1107b3d
@ -184,8 +184,9 @@ public class ModalPane extends Control {
|
||||
|
||||
/**
|
||||
* Specifies whether content should be treated as persistent or not.
|
||||
* By default, modal pane exits when on ESC button or mouse click outside the contenbt are.
|
||||
* This property prevents this behavior and plays bouncing animation instead.
|
||||
* By default, the modal pane exits when the ESC button is pressed or when
|
||||
* the mouse is clicked outside the content area. This property prevents
|
||||
* this behavior and plays bouncing animation instead.
|
||||
*/
|
||||
public BooleanProperty persistentProperty() {
|
||||
return persistent;
|
||||
|
5
pom.xml
5
pom.xml
@ -94,6 +94,11 @@
|
||||
<artifactId>javafx-controls</artifactId>
|
||||
<version>${openjfx.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.openjfx</groupId>
|
||||
<artifactId>javafx-fxml</artifactId>
|
||||
<version>${openjfx.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.openjfx</groupId>
|
||||
<artifactId>javafx-swing</artifactId>
|
||||
|
@ -43,6 +43,10 @@
|
||||
<groupId>org.openjfx</groupId>
|
||||
<artifactId>javafx-controls</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.openjfx</groupId>
|
||||
<artifactId>javafx-fxml</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.openjfx</groupId>
|
||||
<artifactId>javafx-swing</artifactId>
|
||||
@ -162,7 +166,7 @@
|
||||
<executions>
|
||||
<execution>
|
||||
<id>validate</id>
|
||||
<phase>validate</phase>
|
||||
<phase>verify</phase>
|
||||
<goals>
|
||||
<goal>check</goal>
|
||||
</goals>
|
||||
@ -243,7 +247,7 @@
|
||||
<configuration>
|
||||
<toolName>jlink</toolName>
|
||||
<addModules>
|
||||
java.base,java.logging,jdk.localedata,java.desktop,java.prefs,javafx.controls,javafx.swing,javafx.web
|
||||
java.base,java.logging,jdk.localedata,java.desktop,java.prefs,javafx.controls,javafx.fxml,javafx.swing,javafx.web
|
||||
</addModules>
|
||||
<modulePath>${build.platformModulesDir}</modulePath>
|
||||
<output>${build.package.runtimeImageDir}</output>
|
||||
|
@ -59,7 +59,7 @@ public class Launcher extends Application {
|
||||
var antialiasing = Platform.isSupported(ConditionalFeature.SCENE3D)
|
||||
? SceneAntialiasing.BALANCED
|
||||
: SceneAntialiasing.DISABLED;
|
||||
var scene = new Scene(root, 1200, 768, false, antialiasing);
|
||||
var scene = new Scene(root, ApplicationWindow.MIN_WIDTH + 80, 768, false, antialiasing);
|
||||
scene.setOnKeyPressed(this::dispatchHotkeys);
|
||||
|
||||
var tm = ThemeManager.getInstance();
|
||||
|
@ -1,156 +0,0 @@
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
|
||||
package atlantafx.sampler.fake;
|
||||
|
||||
import static atlantafx.sampler.util.Controls.menuItem;
|
||||
import static javafx.scene.input.KeyCombination.CONTROL_DOWN;
|
||||
import static javafx.scene.input.KeyCombination.SHIFT_DOWN;
|
||||
|
||||
import atlantafx.base.controls.CaptionMenuItem;
|
||||
import java.util.stream.IntStream;
|
||||
import javafx.event.ActionEvent;
|
||||
import javafx.event.EventHandler;
|
||||
import javafx.scene.control.CheckMenuItem;
|
||||
import javafx.scene.control.Menu;
|
||||
import javafx.scene.control.MenuBar;
|
||||
import javafx.scene.control.MenuItem;
|
||||
import javafx.scene.control.RadioMenuItem;
|
||||
import javafx.scene.control.SeparatorMenuItem;
|
||||
import javafx.scene.control.ToggleGroup;
|
||||
import javafx.scene.input.KeyCode;
|
||||
import javafx.scene.input.KeyCodeCombination;
|
||||
import net.datafaker.Faker;
|
||||
import org.kordamp.ikonli.feather.Feather;
|
||||
import org.kordamp.ikonli.javafx.FontIcon;
|
||||
import org.kordamp.ikonli.material2.Material2OutlinedAL;
|
||||
|
||||
public class SampleMenuBar extends MenuBar {
|
||||
|
||||
private static final EventHandler<ActionEvent> PRINT_SOURCE = System.out::println;
|
||||
|
||||
public SampleMenuBar(Faker faker) {
|
||||
getMenus().addAll(
|
||||
fileMenu(faker),
|
||||
editMenu(),
|
||||
viewMenu(),
|
||||
toolsMenu(),
|
||||
aboutMenu()
|
||||
);
|
||||
}
|
||||
|
||||
private Menu fileMenu(Faker faker) {
|
||||
var fileMenu = new Menu("_File");
|
||||
fileMenu.setMnemonicParsing(true);
|
||||
fileMenu.setOnAction(PRINT_SOURCE);
|
||||
|
||||
var newMenu = menuItem("_New", null, new KeyCodeCombination(KeyCode.N, CONTROL_DOWN));
|
||||
newMenu.setMnemonicParsing(true);
|
||||
newMenu.setOnAction(PRINT_SOURCE);
|
||||
|
||||
var openRecentMenu = new Menu("Open _Recent");
|
||||
openRecentMenu.setMnemonicParsing(true);
|
||||
openRecentMenu.setOnAction(PRINT_SOURCE);
|
||||
openRecentMenu.getItems().addAll(
|
||||
IntStream.range(0, 10).mapToObj(x -> new MenuItem(faker.file().fileName())).toList()
|
||||
);
|
||||
|
||||
fileMenu.getItems().addAll(
|
||||
newMenu,
|
||||
new SeparatorMenuItem(),
|
||||
menuItem("Open", Feather.FOLDER, new KeyCodeCombination(KeyCode.O, CONTROL_DOWN)),
|
||||
openRecentMenu,
|
||||
new SeparatorMenuItem(),
|
||||
menuItem("Save", Feather.SAVE, new KeyCodeCombination(KeyCode.S, CONTROL_DOWN)),
|
||||
new MenuItem("Save As"),
|
||||
new SeparatorMenuItem(),
|
||||
new MenuItem("Exit")
|
||||
);
|
||||
return fileMenu;
|
||||
}
|
||||
|
||||
private Menu editMenu() {
|
||||
var editMenu = new Menu("_Edit");
|
||||
editMenu.setMnemonicParsing(true);
|
||||
editMenu.setOnAction(PRINT_SOURCE);
|
||||
|
||||
editMenu.getItems().addAll(
|
||||
menuItem("Undo", Feather.CORNER_DOWN_LEFT, new KeyCodeCombination(KeyCode.Z, CONTROL_DOWN)),
|
||||
menuItem("Redo", Feather.CORNER_DOWN_RIGHT, new KeyCodeCombination(KeyCode.Y, CONTROL_DOWN)),
|
||||
new SeparatorMenuItem(),
|
||||
menuItem("Cut", Feather.SCISSORS, new KeyCodeCombination(KeyCode.X, CONTROL_DOWN)),
|
||||
menuItem("Copy", Feather.COPY, new KeyCodeCombination(KeyCode.C, CONTROL_DOWN), true),
|
||||
menuItem("Paste", Feather.CORNER_DOWN_LEFT, new KeyCodeCombination(KeyCode.V, CONTROL_DOWN))
|
||||
);
|
||||
return editMenu;
|
||||
}
|
||||
|
||||
private Menu viewMenu() {
|
||||
var viewMenu = new Menu("_View");
|
||||
viewMenu.setMnemonicParsing(true);
|
||||
viewMenu.setOnAction(PRINT_SOURCE);
|
||||
|
||||
var showToolbarItem = new CheckMenuItem("Show Toolbar", new FontIcon(Feather.TOOL));
|
||||
showToolbarItem.setSelected(true);
|
||||
showToolbarItem.setAccelerator(new KeyCodeCombination(KeyCode.T, CONTROL_DOWN));
|
||||
|
||||
var showGridItem = new CheckMenuItem("Show Grid", new FontIcon(Feather.GRID));
|
||||
|
||||
var captionItem = new CaptionMenuItem("Layout");
|
||||
|
||||
var viewToggleGroup = new ToggleGroup();
|
||||
|
||||
var toggleItem1 = new RadioMenuItem("Single", new FontIcon(Material2OutlinedAL.LOOKS_ONE));
|
||||
toggleItem1.setSelected(true);
|
||||
toggleItem1.setToggleGroup(viewToggleGroup);
|
||||
|
||||
var toggleItem2 = new RadioMenuItem("Two Columns", new FontIcon(Material2OutlinedAL.LOOKS_TWO));
|
||||
toggleItem2.setToggleGroup(viewToggleGroup);
|
||||
|
||||
var toggleItem3 = new RadioMenuItem("Three Columns", new FontIcon(Material2OutlinedAL.LOOKS_3));
|
||||
toggleItem3.setToggleGroup(viewToggleGroup);
|
||||
|
||||
viewMenu.getItems().addAll(
|
||||
showToolbarItem,
|
||||
showGridItem,
|
||||
new SeparatorMenuItem(),
|
||||
captionItem,
|
||||
toggleItem1,
|
||||
toggleItem2,
|
||||
toggleItem3
|
||||
);
|
||||
return viewMenu;
|
||||
}
|
||||
|
||||
private Menu toolsMenu() {
|
||||
var toolsMenu = new Menu("_Tools");
|
||||
toolsMenu.setMnemonicParsing(true);
|
||||
toolsMenu.setOnAction(PRINT_SOURCE);
|
||||
toolsMenu.setDisable(true);
|
||||
return toolsMenu;
|
||||
}
|
||||
|
||||
private Menu aboutMenu() {
|
||||
var aboutMenu = new Menu("_About", new FontIcon(Feather.HELP_CIRCLE));
|
||||
aboutMenu.setMnemonicParsing(true);
|
||||
aboutMenu.setOnAction(PRINT_SOURCE);
|
||||
|
||||
var deeplyNestedMenu = new Menu("Very...", null,
|
||||
new Menu("Very...", null,
|
||||
new Menu("Deeply", null,
|
||||
new Menu("Nested", null,
|
||||
new MenuItem("Menu")
|
||||
))));
|
||||
// NOTE: this won't be displayed because right container is reserved for submenu indication
|
||||
deeplyNestedMenu.setAccelerator(new KeyCodeCombination(
|
||||
KeyCode.DIGIT1, SHIFT_DOWN, CONTROL_DOWN)
|
||||
);
|
||||
|
||||
aboutMenu.getItems().addAll(
|
||||
new MenuItem("Help"),
|
||||
new MenuItem("About"),
|
||||
new SeparatorMenuItem(),
|
||||
deeplyNestedMenu
|
||||
);
|
||||
return aboutMenu;
|
||||
}
|
||||
}
|
@ -0,0 +1,61 @@
|
||||
package atlantafx.sampler.fake.domain;
|
||||
|
||||
import javafx.beans.property.SimpleStringProperty;
|
||||
|
||||
public final class Metric {
|
||||
|
||||
private final SimpleStringProperty queries = new SimpleStringProperty("");
|
||||
private final SimpleStringProperty cacheHitRate = new SimpleStringProperty("");
|
||||
private final SimpleStringProperty latency = new SimpleStringProperty("");
|
||||
private final SimpleStringProperty requests = new SimpleStringProperty("");
|
||||
|
||||
public Metric() { }
|
||||
|
||||
public String getQueries() {
|
||||
return queries.get();
|
||||
}
|
||||
|
||||
public SimpleStringProperty queriesProperty() {
|
||||
return queries;
|
||||
}
|
||||
|
||||
public void setQueries(String queries) {
|
||||
this.queries.set(queries);
|
||||
}
|
||||
|
||||
public String getCacheHitRate() {
|
||||
return cacheHitRate.get();
|
||||
}
|
||||
|
||||
public SimpleStringProperty cacheHitRateProperty() {
|
||||
return cacheHitRate;
|
||||
}
|
||||
|
||||
public void setCacheHitRate(String cacheHitRate) {
|
||||
this.cacheHitRate.set(cacheHitRate);
|
||||
}
|
||||
|
||||
public String getLatency() {
|
||||
return latency.get();
|
||||
}
|
||||
|
||||
public SimpleStringProperty latencyProperty() {
|
||||
return latency;
|
||||
}
|
||||
|
||||
public void setLatency(String latency) {
|
||||
this.latency.set(latency);
|
||||
}
|
||||
|
||||
public String getRequests() {
|
||||
return requests.get();
|
||||
}
|
||||
|
||||
public SimpleStringProperty requestsProperty() {
|
||||
return requests;
|
||||
}
|
||||
|
||||
public void setRequests(String requests) {
|
||||
this.requests.set(requests);
|
||||
}
|
||||
}
|
@ -9,6 +9,9 @@ import javafx.scene.layout.StackPane;
|
||||
|
||||
public class ApplicationWindow extends AnchorPane {
|
||||
|
||||
public static final int MIN_WIDTH = 1200;
|
||||
public static final int SIDEBAR_WIDTH = 250;
|
||||
|
||||
public ApplicationWindow() {
|
||||
// this is the place to apply user custom CSS,
|
||||
// one level below the ':root'
|
||||
|
@ -6,7 +6,6 @@ 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 static atlantafx.sampler.layout.MainLayer.SIDEBAR_WIDTH;
|
||||
|
||||
import atlantafx.base.controls.Spacer;
|
||||
import atlantafx.sampler.Resources;
|
||||
@ -80,9 +79,9 @@ class HeaderBar extends HBox {
|
||||
var logoBox = new HBox(10, logoImageBox, logoLabel, versionLabel);
|
||||
logoBox.getStyleClass().add("logo");
|
||||
logoBox.setAlignment(Pos.CENTER_LEFT);
|
||||
logoBox.setMinWidth(SIDEBAR_WIDTH);
|
||||
logoBox.setPrefWidth(SIDEBAR_WIDTH);
|
||||
logoBox.setMaxWidth(SIDEBAR_WIDTH);
|
||||
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);
|
||||
|
@ -26,7 +26,6 @@ import javafx.util.Duration;
|
||||
|
||||
class MainLayer extends BorderPane {
|
||||
|
||||
static final int SIDEBAR_WIDTH = 250;
|
||||
static final int PAGE_TRANSITION_DURATION = 500; // ms
|
||||
|
||||
private final MainModel model = new MainModel();
|
||||
@ -51,8 +50,8 @@ class MainLayer extends BorderPane {
|
||||
}
|
||||
|
||||
private void createView() {
|
||||
sidebar.setMinWidth(SIDEBAR_WIDTH);
|
||||
sidebar.setMaxWidth(SIDEBAR_WIDTH);
|
||||
sidebar.setMinWidth(ApplicationWindow.SIDEBAR_WIDTH);
|
||||
sidebar.setMaxWidth(ApplicationWindow.SIDEBAR_WIDTH);
|
||||
|
||||
codeViewer = new CodeViewer();
|
||||
|
||||
|
@ -7,27 +7,22 @@ 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.BBCodePage;
|
||||
import atlantafx.sampler.page.components.BreadcrumbsPage;
|
||||
import atlantafx.sampler.page.components.ButtonPage;
|
||||
import atlantafx.sampler.page.components.ChartPage;
|
||||
import atlantafx.sampler.page.components.CheckBoxPage;
|
||||
import atlantafx.sampler.page.components.ChoiceBoxPage;
|
||||
import atlantafx.sampler.page.components.ColorPickerPage;
|
||||
import atlantafx.sampler.page.components.ComboBoxPage;
|
||||
import atlantafx.sampler.page.components.CustomTextFieldPage;
|
||||
import atlantafx.sampler.page.components.ContextMenuPage;
|
||||
import atlantafx.sampler.page.components.DatePickerPage;
|
||||
import atlantafx.sampler.page.components.DialogPage;
|
||||
import atlantafx.sampler.page.components.HtmlEditorPage;
|
||||
import atlantafx.sampler.page.components.InputGroupPage;
|
||||
import atlantafx.sampler.page.components.LabelPage;
|
||||
import atlantafx.sampler.page.components.ListPage;
|
||||
import atlantafx.sampler.page.components.ListViewPage;
|
||||
import atlantafx.sampler.page.components.MenuBarPage;
|
||||
import atlantafx.sampler.page.components.MenuButtonPage;
|
||||
import atlantafx.sampler.page.components.MenuPage;
|
||||
import atlantafx.sampler.page.components.ModalPanePage;
|
||||
import atlantafx.sampler.page.components.OverviewPage;
|
||||
import atlantafx.sampler.page.components.PaginationPage;
|
||||
import atlantafx.sampler.page.components.PopoverPage;
|
||||
import atlantafx.sampler.page.components.ProgressPage;
|
||||
import atlantafx.sampler.page.components.ProgressIndicatorPage;
|
||||
import atlantafx.sampler.page.components.RadioButtonPage;
|
||||
import atlantafx.sampler.page.components.ScrollPanePage;
|
||||
import atlantafx.sampler.page.components.SeparatorPage;
|
||||
@ -35,22 +30,30 @@ import atlantafx.sampler.page.components.SliderPage;
|
||||
import atlantafx.sampler.page.components.SpinnerPage;
|
||||
import atlantafx.sampler.page.components.SplitPanePage;
|
||||
import atlantafx.sampler.page.components.TabPanePage;
|
||||
import atlantafx.sampler.page.components.TablePage;
|
||||
import atlantafx.sampler.page.components.TableViewPage;
|
||||
import atlantafx.sampler.page.components.TextAreaPage;
|
||||
import atlantafx.sampler.page.components.TextFieldPage;
|
||||
import atlantafx.sampler.page.components.TitledPanePage;
|
||||
import atlantafx.sampler.page.components.ToggleButtonPage;
|
||||
import atlantafx.sampler.page.components.ToggleSwitchPage;
|
||||
import atlantafx.sampler.page.components.ToolBarPage;
|
||||
import atlantafx.sampler.page.components.TooltipPage;
|
||||
import atlantafx.sampler.page.components.TreePage;
|
||||
import atlantafx.sampler.page.components.TreeTablePage;
|
||||
import atlantafx.sampler.page.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.IconsPage;
|
||||
import atlantafx.sampler.page.general.OverviewPage;
|
||||
import atlantafx.sampler.page.general.ThemePage;
|
||||
import atlantafx.sampler.page.general.TypographyPage;
|
||||
import atlantafx.sampler.page.showcase.filemanager.FileManagerPage;
|
||||
import atlantafx.sampler.page.showcase.musicplayer.MusicPlayerPage;
|
||||
import atlantafx.sampler.page.showcase.widget.WidgetCollectionPage;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
@ -156,24 +159,26 @@ public class MainModel {
|
||||
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(ListPage.class),
|
||||
NAV_TREE.get(MenuPage.class),
|
||||
NAV_TREE.get(ListViewPage.class),
|
||||
NAV_TREE.get(MenuBarPage.class),
|
||||
NAV_TREE.get(MenuButtonPage.class),
|
||||
NAV_TREE.get(PaginationPage.class),
|
||||
NAV_TREE.get(ProgressPage.class),
|
||||
NAV_TREE.get(ProgressIndicatorPage.class),
|
||||
NAV_TREE.get(RadioButtonPage.class),
|
||||
NAV_TREE.get(ScrollPanePage.class),
|
||||
NAV_TREE.get(SeparatorPage.class),
|
||||
NAV_TREE.get(SliderPage.class),
|
||||
NAV_TREE.get(SpinnerPage.class),
|
||||
NAV_TREE.get(SplitPanePage.class),
|
||||
NAV_TREE.get(TablePage.class),
|
||||
NAV_TREE.get(TableViewPage.class),
|
||||
NAV_TREE.get(TabPanePage.class),
|
||||
NAV_TREE.get(TextAreaPage.class),
|
||||
NAV_TREE.get(TextFieldPage.class),
|
||||
@ -181,16 +186,18 @@ public class MainModel {
|
||||
NAV_TREE.get(ToggleButtonPage.class),
|
||||
NAV_TREE.get(ToolBarPage.class),
|
||||
NAV_TREE.get(TooltipPage.class),
|
||||
NAV_TREE.get(TreePage.class),
|
||||
NAV_TREE.get(TreeTablePage.class)
|
||||
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(InputGroupPage.class),
|
||||
NAV_TREE.get(BBCodePage.class),
|
||||
NAV_TREE.get(BreadcrumbsPage.class),
|
||||
NAV_TREE.get(CalendarPage.class),
|
||||
NAV_TREE.get(CustomTextFieldPage.class),
|
||||
NAV_TREE.get(DeckPanePage.class),
|
||||
NAV_TREE.get(InputGroupPage.class),
|
||||
NAV_TREE.get(ModalPanePage.class),
|
||||
NAV_TREE.get(PopoverPage.class),
|
||||
NAV_TREE.get(ToggleSwitchPage.class)
|
||||
@ -199,8 +206,7 @@ public class MainModel {
|
||||
var showcases = NavTree.Item.group("Showcase", new FontIcon(Material2OutlinedMZ.VISIBILITY));
|
||||
showcases.getChildren().setAll(
|
||||
NAV_TREE.get(FileManagerPage.class),
|
||||
NAV_TREE.get(MusicPlayerPage.class),
|
||||
NAV_TREE.get(WidgetCollectionPage.class)
|
||||
NAV_TREE.get(MusicPlayerPage.class)
|
||||
);
|
||||
|
||||
var root = NavTree.Item.root();
|
||||
@ -216,33 +222,39 @@ public class MainModel {
|
||||
public static Map<Class<? extends Page>, NavTree.Item> createNavItems() {
|
||||
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(IconsPage.class, NavTree.Item.page(IconsPage.NAME, IconsPage.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));
|
||||
map.put(CheckBoxPage.class, NavTree.Item.page(CheckBoxPage.NAME, CheckBoxPage.class));
|
||||
map.put(ColorPickerPage.class, NavTree.Item.page(ColorPickerPage.NAME, ColorPickerPage.class));
|
||||
map.put(
|
||||
ComboBoxPage.class,
|
||||
NavTree.Item.page(ComboBoxPage.NAME, ComboBoxPage.class, "ChoiceBox")
|
||||
);
|
||||
map.put(ContextMenuPage.class, NavTree.Item.page(ContextMenuPage.NAME, ContextMenuPage.class));
|
||||
map.put(
|
||||
CustomTextFieldPage.class,
|
||||
NavTree.Item.page(CustomTextFieldPage.NAME, CustomTextFieldPage.class, "MaskTextField", "PasswordTextField")
|
||||
);
|
||||
map.put(DatePickerPage.class, NavTree.Item.page(DatePickerPage.NAME, DatePickerPage.class));
|
||||
map.put(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(ListPage.class, NavTree.Item.page(ListPage.NAME, ListPage.class));
|
||||
map.put(MenuPage.class, NavTree.Item.page(MenuPage.NAME, MenuPage.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(
|
||||
MenuButtonPage.NAME,
|
||||
MenuButtonPage.class, "SplitMenuButton")
|
||||
@ -250,14 +262,17 @@ public class MainModel {
|
||||
map.put(ModalPanePage.class, NavTree.Item.page(ModalPanePage.NAME, ModalPanePage.class));
|
||||
map.put(PaginationPage.class, NavTree.Item.page(PaginationPage.NAME, PaginationPage.class));
|
||||
map.put(PopoverPage.class, NavTree.Item.page(PopoverPage.NAME, PopoverPage.class));
|
||||
map.put(ProgressPage.class, NavTree.Item.page(ProgressPage.NAME, ProgressPage.class));
|
||||
map.put(ProgressIndicatorPage.class, NavTree.Item.page(
|
||||
ProgressIndicatorPage.NAME,
|
||||
ProgressIndicatorPage.class, "ProgressBar")
|
||||
);
|
||||
map.put(RadioButtonPage.class, NavTree.Item.page(RadioButtonPage.NAME, RadioButtonPage.class));
|
||||
map.put(ScrollPanePage.class, NavTree.Item.page(ScrollPanePage.NAME, ScrollPanePage.class));
|
||||
map.put(SeparatorPage.class, NavTree.Item.page(SeparatorPage.NAME, SeparatorPage.class));
|
||||
map.put(SliderPage.class, NavTree.Item.page(SliderPage.NAME, SliderPage.class));
|
||||
map.put(SpinnerPage.class, NavTree.Item.page(SpinnerPage.NAME, SpinnerPage.class));
|
||||
map.put(SplitPanePage.class, NavTree.Item.page(SplitPanePage.NAME, SplitPanePage.class));
|
||||
map.put(TablePage.class, NavTree.Item.page(TablePage.NAME, TablePage.class));
|
||||
map.put(TableViewPage.class, NavTree.Item.page(TableViewPage.NAME, TableViewPage.class));
|
||||
map.put(TabPanePage.class, NavTree.Item.page(TabPanePage.NAME, TabPanePage.class));
|
||||
map.put(TextAreaPage.class, NavTree.Item.page(TextAreaPage.NAME, TextAreaPage.class));
|
||||
map.put(TextFieldPage.class, NavTree.Item.page(
|
||||
@ -269,15 +284,12 @@ public class MainModel {
|
||||
map.put(ToggleSwitchPage.class, NavTree.Item.page(ToggleSwitchPage.NAME, ToggleSwitchPage.class));
|
||||
map.put(ToolBarPage.class, NavTree.Item.page(ToolBarPage.NAME, ToolBarPage.class));
|
||||
map.put(TooltipPage.class, NavTree.Item.page(TooltipPage.NAME, TooltipPage.class));
|
||||
map.put(TreePage.class, NavTree.Item.page(TreePage.NAME, TreePage.class));
|
||||
map.put(TreeTablePage.class, NavTree.Item.page(TreeTablePage.NAME, TreeTablePage.class));
|
||||
map.put(TreeTableViewPage.class, NavTree.Item.page(TreeTableViewPage.NAME, TreeTableViewPage.class));
|
||||
map.put(TreeViewPage.class, NavTree.Item.page(TreeViewPage.NAME, TreeViewPage.class));
|
||||
|
||||
// showcases
|
||||
map.put(FileManagerPage.class, NavTree.Item.page(FileManagerPage.NAME, FileManagerPage.class));
|
||||
map.put(MusicPlayerPage.class, NavTree.Item.page(MusicPlayerPage.NAME, MusicPlayerPage.class));
|
||||
map.put(WidgetCollectionPage.class, NavTree.Item.page(
|
||||
WidgetCollectionPage.NAME,
|
||||
WidgetCollectionPage.class, "Card", "Message", "Stepper", "Tag")
|
||||
);
|
||||
|
||||
return map;
|
||||
}
|
||||
|
@ -65,7 +65,7 @@ public class NavTree extends TreeView<Nav> {
|
||||
root.getChildren().setAll(titleLabel, new Spacer(), arrowIcon);
|
||||
root.setCursor(Cursor.HAND);
|
||||
root.getStyleClass().add("container");
|
||||
root.setMaxWidth(MainLayer.SIDEBAR_WIDTH - 10);
|
||||
root.setMaxWidth(ApplicationWindow.SIDEBAR_WIDTH - 10);
|
||||
|
||||
root.setOnMouseClicked(e -> {
|
||||
if (!(getTreeItem() instanceof Item item)) {
|
||||
|
@ -8,7 +8,6 @@ import atlantafx.base.theme.Styles;
|
||||
import atlantafx.base.theme.Tweaks;
|
||||
import atlantafx.sampler.page.OverlayDialog;
|
||||
import java.util.function.Consumer;
|
||||
import javafx.event.EventType;
|
||||
import javafx.geometry.Insets;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.scene.control.Label;
|
||||
|
@ -4,29 +4,22 @@ package atlantafx.sampler.page;
|
||||
|
||||
import static atlantafx.sampler.util.Containers.setScrollConstraints;
|
||||
import static javafx.scene.control.ScrollPane.ScrollBarPolicy.AS_NEEDED;
|
||||
import static javafx.scene.control.ScrollPane.ScrollBarPolicy.NEVER;
|
||||
|
||||
import atlantafx.base.util.BBCodeParser;
|
||||
import atlantafx.sampler.layout.Overlay;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Random;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.stream.Stream;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.control.ScrollPane;
|
||||
import javafx.scene.layout.BorderPane;
|
||||
import javafx.scene.layout.HBox;
|
||||
import javafx.scene.layout.Pane;
|
||||
import javafx.scene.layout.Priority;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import net.datafaker.Faker;
|
||||
import org.kordamp.ikonli.feather.Feather;
|
||||
import javafx.scene.layout.VBox;
|
||||
import javafx.scene.text.Text;
|
||||
import javafx.scene.text.TextFlow;
|
||||
|
||||
public abstract class AbstractPage extends BorderPane implements Page {
|
||||
public abstract class AbstractPage extends StackPane implements Page {
|
||||
|
||||
protected static final Faker FAKER = new Faker();
|
||||
protected static final Random RANDOM = new Random();
|
||||
|
||||
protected final StackPane userContent = new StackPane();
|
||||
protected final VBox userContent = new VBox();
|
||||
protected Overlay overlay;
|
||||
protected boolean isRendered = false;
|
||||
|
||||
@ -40,11 +33,16 @@ public abstract class AbstractPage extends BorderPane implements Page {
|
||||
}
|
||||
|
||||
protected void createPageLayout() {
|
||||
var scrollPane = new ScrollPane(userContent);
|
||||
setScrollConstraints(scrollPane, AS_NEEDED, true, AS_NEEDED, true);
|
||||
scrollPane.setMaxHeight(10_000);
|
||||
var userContentArea = new StackPane(userContent);
|
||||
userContentArea.setAlignment(Pos.TOP_CENTER);
|
||||
userContent.setMinWidth(Math.min(Page.MAX_WIDTH, 800));
|
||||
userContent.setMaxWidth(Math.min(Page.MAX_WIDTH, 800));
|
||||
|
||||
setCenter(scrollPane);
|
||||
var scrollPane = new ScrollPane(userContentArea);
|
||||
setScrollConstraints(scrollPane, AS_NEEDED, true, NEVER, true);
|
||||
scrollPane.setMaxHeight(20_000);
|
||||
|
||||
getChildren().setAll(scrollPane);
|
||||
}
|
||||
|
||||
protected void setUserContent(Node content) {
|
||||
@ -87,23 +85,20 @@ public abstract class AbstractPage extends BorderPane implements Page {
|
||||
this.overlay = lookupOverlay();
|
||||
}
|
||||
|
||||
protected void addNode(Node node) {
|
||||
userContent.getChildren().add(node);
|
||||
}
|
||||
|
||||
protected void addPlainText(String text) {
|
||||
userContent.getChildren().add(new TextFlow(new Text(text)));
|
||||
}
|
||||
|
||||
protected void addFormattedText(String text) {
|
||||
userContent.getChildren().add(BBCodeParser.createFormattedText(text));
|
||||
}
|
||||
|
||||
protected Overlay lookupOverlay() {
|
||||
return getScene() != null && getScene().lookup("." + Overlay.STYLE_CLASS) instanceof Overlay ov ? ov : null;
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
|
||||
protected HBox expandingHBox(Node... nodes) {
|
||||
var box = new HBox(PAGE_HGAP, nodes);
|
||||
Arrays.stream(nodes).forEach(n -> HBox.setHgrow(n, Priority.ALWAYS));
|
||||
return box;
|
||||
}
|
||||
|
||||
protected <T> List<T> generate(Supplier<T> supplier, int count) {
|
||||
return Stream.generate(supplier).limit(count).toList();
|
||||
}
|
||||
|
||||
protected Feather randomIcon() {
|
||||
return Feather.values()[RANDOM.nextInt(Feather.values().length)];
|
||||
return getScene() != null
|
||||
&& getScene().lookup("." + Overlay.STYLE_CLASS) instanceof Overlay ov ? ov : null;
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,70 @@
|
||||
package atlantafx.sampler.page;
|
||||
|
||||
import atlantafx.base.util.BBCodeParser;
|
||||
import java.util.Objects;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
import javafx.scene.text.TextFlow;
|
||||
|
||||
// This code is adapted from RichTextFX JavaKeywordsDemo:
|
||||
// https://github.com/FXMisc/RichTextFX/blob/master/richtextfx-demos
|
||||
public class BBSyntaxHighlighter {
|
||||
|
||||
private static final String[] KEYWORDS = new String[] {
|
||||
"abstract", "assert", "boolean", "break", "byte",
|
||||
"case", "catch", "char", "class", "const",
|
||||
"continue", "default", "do", "double", "else",
|
||||
"enum", "extends", "final", "finally", "float",
|
||||
"for", "if", "implements", "import",
|
||||
"instanceof", "int", "interface", "long", "native",
|
||||
"new", "package", "private", "protected", "public",
|
||||
"return", "short", "static", "super",
|
||||
"switch", "synchronized", "this", "throw", "throws",
|
||||
"transient", "try", "void", "volatile", "while",
|
||||
"var", "record", "with", "yield", "sealed", "non-sealed"
|
||||
};
|
||||
|
||||
private static final String KEYWORD_PATTERN = "\\b(" + String.join("|", KEYWORDS) + ")\\b";
|
||||
private static final String PAREN_PATTERN = "\\(|\\)";
|
||||
private static final String STRING_PATTERN = "\"([^\"\\\\]|\\\\.)*\"";
|
||||
private static final String COMMENT_PATTERN = "//[^\n]*" + "|" + "/\\*(.|\\R)*?\\*/"
|
||||
+ "|" + "/\\*[^\\v]*" + "|" + "^\\h*\\*([^\\v]*|/)";
|
||||
|
||||
private static final Pattern PATTERN = Pattern.compile(
|
||||
"(?<KEYWORD>" + KEYWORD_PATTERN + ")"
|
||||
+ "|(?<STRING>" + STRING_PATTERN + ")"
|
||||
+ "|(?<PAREN>" + PAREN_PATTERN + ")"
|
||||
+ "|(?<COMMENT>" + COMMENT_PATTERN + ")"
|
||||
);
|
||||
|
||||
// Enclose keywords to BBCode tags.
|
||||
public static String format(String text) {
|
||||
Matcher m = PATTERN.matcher(text);
|
||||
StringBuilder sb = new StringBuilder();
|
||||
int lastKwEnd = 0;
|
||||
|
||||
while (m.find()) {
|
||||
String styleClass =
|
||||
m.group("KEYWORD") != null ? "keyword" :
|
||||
m.group("PAREN") != null ? "paren" :
|
||||
m.group("STRING") != null ? "string" :
|
||||
m.group("COMMENT") != null ? "comment" :
|
||||
null;
|
||||
|
||||
sb.append(text, lastKwEnd, m.start())
|
||||
.append("[span='")
|
||||
.append(Objects.requireNonNullElse(styleClass, "absent"))
|
||||
.append("']")
|
||||
.append(text, m.start(), m.end())
|
||||
.append("[/span]");
|
||||
lastKwEnd = m.end();
|
||||
}
|
||||
sb.append(text, lastKwEnd, text.length());
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
public static TextFlow highlight(String text) {
|
||||
return BBCodeParser.createFormattedText(format(text));
|
||||
}
|
||||
}
|
117
sampler/src/main/java/atlantafx/sampler/page/ExampleBox.java
Normal file
117
sampler/src/main/java/atlantafx/sampler/page/ExampleBox.java
Normal file
@ -0,0 +1,117 @@
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
|
||||
package atlantafx.sampler.page;
|
||||
|
||||
import atlantafx.base.controls.Spacer;
|
||||
import atlantafx.base.controls.ToggleSwitch;
|
||||
import atlantafx.base.theme.Styles;
|
||||
import java.util.Objects;
|
||||
import javafx.beans.property.ObjectProperty;
|
||||
import javafx.beans.property.SimpleObjectProperty;
|
||||
import javafx.css.PseudoClass;
|
||||
import javafx.geometry.Insets;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.control.Button;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.control.Tooltip;
|
||||
import javafx.scene.input.Clipboard;
|
||||
import javafx.scene.input.ClipboardContent;
|
||||
import javafx.scene.layout.HBox;
|
||||
import javafx.scene.layout.VBox;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.kordamp.ikonli.javafx.FontIcon;
|
||||
import org.kordamp.ikonli.material2.Material2OutlinedAL;
|
||||
|
||||
// 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 {
|
||||
|
||||
private static final PseudoClass SELECTED = PseudoClass.getPseudoClass("selected");
|
||||
private final ObjectProperty<Label> selectedTab = new SimpleObjectProperty<>();
|
||||
private final ToggleSwitch stateToggle;
|
||||
|
||||
public ExampleBox(Node preview, Snippet snippet) {
|
||||
this(preview, snippet, null);
|
||||
}
|
||||
|
||||
public ExampleBox(Node preview, Snippet snippet, @Nullable Node description) {
|
||||
super();
|
||||
|
||||
Objects.requireNonNull(preview, "preview");
|
||||
Objects.requireNonNull(snippet, "snippet");
|
||||
|
||||
var previewTab = createTabLabel("Preview");
|
||||
var codeTab = createTabLabel("Code");
|
||||
|
||||
var copyBtn = new Button("", new FontIcon(Material2OutlinedAL.CODE));
|
||||
copyBtn.getStyleClass().addAll(Styles.BUTTON_CIRCLE, Styles.FLAT, Styles.SMALL);
|
||||
copyBtn.setTooltip(new Tooltip("Copy source code"));
|
||||
|
||||
stateToggle = new ToggleSwitch();
|
||||
HBox.setMargin(stateToggle, new Insets(0, 0, 0, 10));
|
||||
|
||||
var tabs = new HBox(
|
||||
previewTab, codeTab,
|
||||
new Spacer(), copyBtn, stateToggle
|
||||
);
|
||||
tabs.getStyleClass().add("tabs");
|
||||
tabs.setAlignment(Pos.CENTER_LEFT);
|
||||
|
||||
var content = new VBox();
|
||||
|
||||
getStyleClass().add("example-box");
|
||||
if (description != null) {
|
||||
getChildren().add(description);
|
||||
}
|
||||
|
||||
getChildren().addAll(tabs, content);
|
||||
|
||||
// == INIT ==
|
||||
|
||||
stateToggle.selectedProperty().addListener((obs, old, val) -> {
|
||||
if (selectedTab.get() == previewTab) {
|
||||
content.getChildren().forEach(c -> c.setDisable(val));
|
||||
}
|
||||
});
|
||||
|
||||
copyBtn.setOnAction(e -> {
|
||||
var cc = new ClipboardContent();
|
||||
cc.putString(snippet.getSourceCode());
|
||||
Clipboard.getSystemClipboard().setContent(cc);
|
||||
});
|
||||
|
||||
selectedTab.addListener((obs, old, val) -> {
|
||||
if (val == codeTab) {
|
||||
stateToggle.setDisable(true);
|
||||
content.getChildren().setAll(snippet.render());
|
||||
} else {
|
||||
stateToggle.setDisable(false);
|
||||
content.getChildren().setAll(preview);
|
||||
}
|
||||
|
||||
if (old != null) {
|
||||
old.pseudoClassStateChanged(SELECTED, false);
|
||||
}
|
||||
if (val != null) {
|
||||
val.pseudoClassStateChanged(SELECTED, true);
|
||||
}
|
||||
});
|
||||
|
||||
selectedTab.set(previewTab);
|
||||
}
|
||||
|
||||
public void setAllowDisable(boolean allow) {
|
||||
stateToggle.setDisable(!allow);
|
||||
stateToggle.setVisible(allow);
|
||||
stateToggle.setManaged(allow);
|
||||
}
|
||||
|
||||
private Label createTabLabel(String title) {
|
||||
var label = new Label(title);
|
||||
label.setOnMouseClicked(e -> selectedTab.set(label));
|
||||
label.setPrefWidth(120);
|
||||
label.setAlignment(Pos.CENTER);
|
||||
return label;
|
||||
}
|
||||
}
|
277
sampler/src/main/java/atlantafx/sampler/page/OutlinePage.java
Executable file
277
sampler/src/main/java/atlantafx/sampler/page/OutlinePage.java
Executable file
@ -0,0 +1,277 @@
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
|
||||
package atlantafx.sampler.page;
|
||||
|
||||
import static atlantafx.sampler.util.Containers.setScrollConstraints;
|
||||
import static javafx.scene.control.ScrollPane.ScrollBarPolicy.AS_NEEDED;
|
||||
import static javafx.scene.control.ScrollPane.ScrollBarPolicy.NEVER;
|
||||
|
||||
import atlantafx.base.theme.Styles;
|
||||
import atlantafx.sampler.layout.Overlay;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.function.Consumer;
|
||||
import javafx.application.Platform;
|
||||
import javafx.css.PseudoClass;
|
||||
import javafx.geometry.Insets;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.Parent;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.control.ScrollPane;
|
||||
import javafx.scene.control.Separator;
|
||||
import javafx.scene.input.MouseButton;
|
||||
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;
|
||||
|
||||
public abstract class OutlinePage extends StackPane implements Page {
|
||||
|
||||
protected static final int OUTLINE_WIDTH = 200;
|
||||
|
||||
protected final ScrollPane scrollPane = new ScrollPane();
|
||||
protected final VBox userContent = new VBox();
|
||||
protected final Outline outline = new Outline(createOutlineHandler());
|
||||
|
||||
protected Overlay overlay;
|
||||
protected boolean isRendered = false;
|
||||
|
||||
protected OutlinePage() {
|
||||
super();
|
||||
|
||||
userContent.getStyleClass().add("user-content");
|
||||
getStyleClass().add("outline-page");
|
||||
|
||||
createPageLayout();
|
||||
}
|
||||
|
||||
protected void createPageLayout() {
|
||||
var userContentArea = new StackPane(userContent);
|
||||
StackPane.setMargin(userContent, new Insets(0, OUTLINE_WIDTH, 0, 0));
|
||||
userContent.setMinWidth(Page.MAX_WIDTH - OUTLINE_WIDTH - 100);
|
||||
userContent.setMaxWidth(Page.MAX_WIDTH - OUTLINE_WIDTH - 100);
|
||||
|
||||
scrollPane.setContent(userContentArea);
|
||||
setScrollConstraints(scrollPane, AS_NEEDED, true, NEVER, true);
|
||||
scrollPane.setMaxHeight(20_000);
|
||||
|
||||
// scroll spy
|
||||
scrollPane.vvalueProperty().addListener((obs, old, val) ->
|
||||
// we need a little gap between changing vValue and fetching header bounds
|
||||
Platform.runLater(() -> outline.select(getFirstVisibleHeader()))
|
||||
);
|
||||
|
||||
var pageBody = new StackPane();
|
||||
pageBody.getChildren().setAll(scrollPane, outline);
|
||||
pageBody.getStyleClass().add("body");
|
||||
StackPane.setAlignment(outline, Pos.TOP_RIGHT);
|
||||
StackPane.setMargin(outline, new Insets(50, 20, 0, 0));
|
||||
|
||||
setMinWidth(Page.MAX_WIDTH);
|
||||
getChildren().setAll(pageBody);
|
||||
}
|
||||
|
||||
protected Consumer<Heading> createOutlineHandler() {
|
||||
return heading -> {
|
||||
if (heading != Heading.TOP) {
|
||||
Parent container = heading.anchor().getParent();
|
||||
int indexInParent = container.getChildrenUnmodifiable().indexOf(heading.anchor());
|
||||
|
||||
Node target;
|
||||
double targetY;
|
||||
|
||||
if (container.getChildrenUnmodifiable().size() > indexInParent + 1) {
|
||||
// aims to the middle of the content node (the one that is next to header)
|
||||
target = container.getChildrenUnmodifiable().get(indexInParent + 1);
|
||||
var bounds = target.getBoundsInParent();
|
||||
targetY = bounds.getMaxY() - (bounds.getHeight() / 2);
|
||||
} else {
|
||||
target = heading.anchor();
|
||||
targetY = target.getBoundsInParent().getMaxY();
|
||||
}
|
||||
|
||||
double height = scrollPane.getContent().getBoundsInLocal().getHeight();
|
||||
scrollPane.setVvalue(targetY / height);
|
||||
} else {
|
||||
scrollPane.setVvalue(0);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private @Nullable String getFirstVisibleHeader() {
|
||||
var scrollBounds = scrollPane.localToScene(scrollPane.getBoundsInParent());
|
||||
Label lastHeading = null;
|
||||
for (var node : userContent.getChildren()) {
|
||||
if (!(node instanceof Label heading)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
var headingBounds = heading.localToScene(heading.getBoundsInLocal());
|
||||
if (outline.contains(heading.getText())) {
|
||||
// viewport should fully contain heading bounds, not just a part of it
|
||||
if (scrollBounds.contains(headingBounds)) {
|
||||
return heading.getText();
|
||||
} else {
|
||||
lastHeading = heading;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return lastHeading != null ? lastHeading.getText() : null;
|
||||
}
|
||||
|
||||
protected void addNode(Node node) {
|
||||
userContent.getChildren().add(node);
|
||||
}
|
||||
|
||||
protected void addPlainText(String text) {
|
||||
userContent.getChildren().add(new TextFlow(new Text(text)));
|
||||
}
|
||||
|
||||
protected void addFormattedText(String text) {
|
||||
addFormattedText(text, false);
|
||||
}
|
||||
|
||||
protected void addFormattedText(String text, boolean handleUrl) {
|
||||
userContent.getChildren().add(createFormattedText(text, handleUrl));
|
||||
}
|
||||
|
||||
protected void addSection(String title, Node content) {
|
||||
var titleIcon = new FontIcon(Feather.HASH);
|
||||
titleIcon.getStyleClass().add("icon-subtle");
|
||||
|
||||
var titleLabel = new Label(title);
|
||||
titleLabel.getStyleClass().add(Styles.TITLE_3);
|
||||
titleLabel.setGraphic(titleIcon);
|
||||
titleLabel.setGraphicTextGap(10);
|
||||
titleLabel.setPadding(new Insets(20, 0, 0, 0));
|
||||
|
||||
userContent.getChildren().addAll(titleLabel, content);
|
||||
outline.add(new Heading(title, titleLabel));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Pane getView() {
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canDisplaySourceCode() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canChangeThemeSettings() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reset() {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void layoutChildren() {
|
||||
super.layoutChildren();
|
||||
if (isRendered) {
|
||||
return;
|
||||
}
|
||||
|
||||
isRendered = true;
|
||||
onRendered();
|
||||
}
|
||||
|
||||
// Some properties can only be obtained after node placed
|
||||
// to the scene graph and here is the place do this.
|
||||
protected void onRendered() {
|
||||
this.overlay = lookupOverlay();
|
||||
}
|
||||
|
||||
protected Overlay lookupOverlay() {
|
||||
var scene = getScene();
|
||||
return scene != null && scene.lookup("." + Overlay.STYLE_CLASS) instanceof Overlay o ? o : null;
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
|
||||
public record Heading(String title, Node anchor) {
|
||||
|
||||
private static final Heading TOP = new Heading("Top", new Text());
|
||||
|
||||
public Heading {
|
||||
Objects.requireNonNull(title, "title");
|
||||
Objects.requireNonNull(anchor, "anchor");
|
||||
}
|
||||
}
|
||||
|
||||
public static class Outline extends VBox {
|
||||
|
||||
private static final PseudoClass SELECTED = PseudoClass.getPseudoClass("selected");
|
||||
|
||||
private final Set<String> toc = new LinkedHashSet<>();
|
||||
private final Consumer<Heading> clickHandler;
|
||||
private Label selected;
|
||||
|
||||
public Outline(Consumer<Heading> clickHandler) {
|
||||
super();
|
||||
|
||||
this.clickHandler = Objects.requireNonNull(clickHandler, "clickHandler");
|
||||
|
||||
// outline has two items by default at the bottom
|
||||
getChildren().add(new Separator());
|
||||
getChildren().add(createEntry(Heading.TOP));
|
||||
setPickOnBounds(false); // do not consume scroll events on transparent pixels
|
||||
|
||||
getStyleClass().add("outline");
|
||||
setMinWidth(OUTLINE_WIDTH);
|
||||
setMaxWidth(OUTLINE_WIDTH);
|
||||
}
|
||||
|
||||
public void add(Heading heading) {
|
||||
var label = createEntry(heading);
|
||||
if (getChildren().size() == 2) { // top entry
|
||||
label.pseudoClassStateChanged(SELECTED, true);
|
||||
this.selected = label;
|
||||
}
|
||||
|
||||
Objects.requireNonNull(heading, "heading");
|
||||
getChildren().add(getChildren().size() - 2, label);
|
||||
toc.add(heading.title());
|
||||
}
|
||||
|
||||
public boolean contains(String title) {
|
||||
return title != null && toc.contains(title);
|
||||
}
|
||||
|
||||
public void select(String title) {
|
||||
if (selected != null) {
|
||||
selected.pseudoClassStateChanged(SELECTED, false);
|
||||
}
|
||||
|
||||
for (var node : getChildren()) {
|
||||
if (node instanceof Label label && Objects.equals(label.getText(), title)) {
|
||||
label.pseudoClassStateChanged(SELECTED, true);
|
||||
selected = label;
|
||||
break; // handle multiple entries share the same title
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private Label createEntry(Heading heading) {
|
||||
var label = new Label(heading.title());
|
||||
label.setWrapText(true);
|
||||
label.setMaxWidth(Double.MAX_VALUE);
|
||||
label.setOnMouseClicked(e -> {
|
||||
if (e.getClickCount() == 1 && e.getButton() == MouseButton.PRIMARY) {
|
||||
clickHandler.accept(heading);
|
||||
}
|
||||
});
|
||||
return label;
|
||||
}
|
||||
}
|
||||
}
|
@ -2,10 +2,33 @@
|
||||
|
||||
package atlantafx.sampler.page;
|
||||
|
||||
import atlantafx.base.util.BBCodeParser;
|
||||
import atlantafx.sampler.event.BrowseEvent;
|
||||
import atlantafx.sampler.event.DefaultEventBus;
|
||||
import atlantafx.sampler.layout.ApplicationWindow;
|
||||
import java.net.URI;
|
||||
import java.util.List;
|
||||
import java.util.Random;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.stream.Stream;
|
||||
import javafx.event.ActionEvent;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.Parent;
|
||||
import javafx.scene.control.Hyperlink;
|
||||
import javafx.scene.control.Label;
|
||||
import net.datafaker.Faker;
|
||||
import org.kordamp.ikonli.feather.Feather;
|
||||
|
||||
public interface Page {
|
||||
|
||||
int MAX_WIDTH = ApplicationWindow.MIN_WIDTH - ApplicationWindow.SIDEBAR_WIDTH;
|
||||
int HGAP_20 = 20;
|
||||
int HGAP_30 = 30;
|
||||
int VGAP_10 = 10;
|
||||
int VGAP_20 = 20;
|
||||
|
||||
Faker FAKER = new Faker();
|
||||
Random RANDOM = new Random();
|
||||
int PAGE_HGAP = 30;
|
||||
int PAGE_VGAP = 30;
|
||||
|
||||
@ -18,4 +41,35 @@ public interface Page {
|
||||
boolean canChangeThemeSettings();
|
||||
|
||||
void reset();
|
||||
|
||||
default <T> List<T> generate(Supplier<T> supplier, int count) {
|
||||
return Stream.generate(supplier).limit(count).toList();
|
||||
}
|
||||
|
||||
default Feather randomIcon() {
|
||||
return Feather.values()[RANDOM.nextInt(Feather.values().length)];
|
||||
}
|
||||
|
||||
default Node createFormattedText(String text, boolean handleUrl) {
|
||||
var node = BBCodeParser.createFormattedText(text);
|
||||
|
||||
if (handleUrl) {
|
||||
node.addEventFilter(ActionEvent.ACTION, e -> {
|
||||
if (e.getTarget() instanceof Hyperlink link && link.getUserData() != null) {
|
||||
DefaultEventBus.getInstance().publish(
|
||||
new BrowseEvent(URI.create((String) link.getUserData()))
|
||||
);
|
||||
}
|
||||
e.consume();
|
||||
});
|
||||
}
|
||||
|
||||
return node;
|
||||
}
|
||||
|
||||
default Label captionLabel(String text) {
|
||||
var label = new Label(text);
|
||||
label.setStyle("-fx-font-family:monospace");
|
||||
return label;
|
||||
}
|
||||
}
|
||||
|
@ -1,80 +0,0 @@
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
|
||||
package atlantafx.sampler.page;
|
||||
|
||||
import java.util.Objects;
|
||||
import java.util.Random;
|
||||
import javafx.geometry.Insets;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.layout.Priority;
|
||||
import javafx.scene.layout.VBox;
|
||||
import javafx.scene.shape.Polygon;
|
||||
import javafx.scene.text.Text;
|
||||
import javafx.scene.text.TextFlow;
|
||||
import net.datafaker.Faker;
|
||||
import org.kordamp.ikonli.feather.Feather;
|
||||
|
||||
public class SampleBlock extends VBox {
|
||||
|
||||
public static final int BLOCK_HGAP = 20;
|
||||
public static final int BLOCK_VGAP = 10;
|
||||
|
||||
protected static final Faker FAKER = new Faker();
|
||||
protected static final Random RANDOM = new Random();
|
||||
|
||||
protected final Label titleLabel;
|
||||
protected final Node content; // can be either Pane or Control
|
||||
protected TextFlow descriptionText;
|
||||
|
||||
public SampleBlock(String title, Node content) {
|
||||
this(title, content, null);
|
||||
}
|
||||
|
||||
public SampleBlock(String title, Node content, String description) {
|
||||
titleLabel = new Label(Objects.requireNonNull(title));
|
||||
|
||||
Polygon polygon = new Polygon();
|
||||
polygon.getPoints().addAll(0.0, 0.0, 20.0, 10.0, 20.0, 0.0);
|
||||
polygon.getStyleClass().add("polygon");
|
||||
|
||||
var titleBox = new VBox(titleLabel, polygon);
|
||||
titleBox.getStyleClass().add("title");
|
||||
VBox.setMargin(titleBox, new Insets(-5, 0, 0, -40));
|
||||
|
||||
this.content = Objects.requireNonNull(content);
|
||||
content.getStyleClass().add("content");
|
||||
|
||||
getChildren().setAll(titleBox, content);
|
||||
if (description != null && !description.isBlank()) {
|
||||
descriptionText = new TextFlow(new Text(description));
|
||||
getChildren().add(descriptionText);
|
||||
}
|
||||
|
||||
getStyleClass().add("sample-block");
|
||||
}
|
||||
|
||||
public String getTitle() {
|
||||
return titleLabel.getText();
|
||||
}
|
||||
|
||||
public void setTitle(String text) {
|
||||
titleLabel.setText(text);
|
||||
}
|
||||
|
||||
public Node getContent() {
|
||||
return content;
|
||||
}
|
||||
|
||||
public void setFillHeight(boolean fillHeight) {
|
||||
if (fillHeight) {
|
||||
VBox.setVgrow(content, Priority.ALWAYS);
|
||||
} else {
|
||||
VBox.setVgrow(content, Priority.NEVER);
|
||||
}
|
||||
}
|
||||
|
||||
protected static Feather randomIcon() {
|
||||
return Feather.values()[RANDOM.nextInt(Feather.values().length)];
|
||||
}
|
||||
}
|
59
sampler/src/main/java/atlantafx/sampler/page/Snippet.java
Normal file
59
sampler/src/main/java/atlantafx/sampler/page/Snippet.java
Normal file
@ -0,0 +1,59 @@
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
|
||||
package atlantafx.sampler.page;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Objects;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.layout.HBox;
|
||||
import javafx.scene.layout.Priority;
|
||||
import javafx.scene.text.Text;
|
||||
import javafx.scene.text.TextFlow;
|
||||
|
||||
public class Snippet {
|
||||
|
||||
private final Class<?> sourceClass;
|
||||
private final int id;
|
||||
private HBox container = null;
|
||||
|
||||
public Snippet(Class<?> sourceClass, int id) {
|
||||
this.sourceClass = Objects.requireNonNull(sourceClass, "sourceClass");
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public Node render() {
|
||||
var snippet = getSourceCode();
|
||||
if (container == null && !snippet.isBlank()) {
|
||||
var textFlow = new TextFlow(BBSyntaxHighlighter.highlight(snippet));
|
||||
HBox.setHgrow(textFlow, Priority.ALWAYS);
|
||||
container = new HBox(textFlow);
|
||||
container.getStyleClass().add("snippet");
|
||||
}
|
||||
|
||||
return Objects.requireNonNullElse(container, new TextFlow(new Text("Code snippet not found.")));
|
||||
}
|
||||
|
||||
public String getSourceCode() {
|
||||
var sourceFileName = sourceClass.getSimpleName() + ".java";
|
||||
try (var stream = sourceClass.getResourceAsStream(sourceFileName)) {
|
||||
Objects.requireNonNull(stream, "Missing source file '" + sourceFileName + "';");
|
||||
|
||||
var sourceCode = new String(stream.readAllBytes());
|
||||
var startTag = "//snippet_" + id + ":start";
|
||||
var endTag = "//snippet_" + id + ":end";
|
||||
var start = sourceCode.indexOf(startTag);
|
||||
var end = sourceCode.indexOf(endTag);
|
||||
|
||||
var snippet = "";
|
||||
if (start >= 0 && end >= 0) {
|
||||
snippet = sourceCode.substring(start + startTag.length(), end)
|
||||
.stripIndent()
|
||||
.trim();
|
||||
}
|
||||
|
||||
return snippet;
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
}
|
@ -2,20 +2,20 @@
|
||||
|
||||
package atlantafx.sampler.page.components;
|
||||
|
||||
import static atlantafx.base.theme.Styles.DENSE;
|
||||
import static atlantafx.base.theme.Styles.toggleStyleClass;
|
||||
import static atlantafx.sampler.page.SampleBlock.BLOCK_HGAP;
|
||||
|
||||
import atlantafx.base.controls.ToggleSwitch;
|
||||
import atlantafx.base.theme.Styles;
|
||||
import atlantafx.base.theme.Tweaks;
|
||||
import atlantafx.base.util.BBCodeParser;
|
||||
import atlantafx.sampler.Resources;
|
||||
import atlantafx.sampler.page.AbstractPage;
|
||||
import atlantafx.sampler.page.SampleBlock;
|
||||
import atlantafx.sampler.page.ExampleBox;
|
||||
import atlantafx.sampler.page.OutlinePage;
|
||||
import atlantafx.sampler.page.Snippet;
|
||||
import java.util.function.Supplier;
|
||||
import javafx.application.Platform;
|
||||
import javafx.beans.property.BooleanProperty;
|
||||
import javafx.beans.property.SimpleBooleanProperty;
|
||||
import javafx.geometry.Insets;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.control.Accordion;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.control.ScrollPane;
|
||||
@ -23,11 +23,12 @@ import javafx.scene.control.TitledPane;
|
||||
import javafx.scene.image.Image;
|
||||
import javafx.scene.image.ImageView;
|
||||
import javafx.scene.layout.HBox;
|
||||
import javafx.scene.layout.Priority;
|
||||
import javafx.scene.layout.VBox;
|
||||
import javafx.scene.text.Text;
|
||||
import javafx.scene.text.TextFlow;
|
||||
|
||||
public class AccordionPage extends AbstractPage {
|
||||
public class AccordionPage extends OutlinePage {
|
||||
|
||||
public static final String NAME = "Accordion";
|
||||
|
||||
@ -36,70 +37,130 @@ public class AccordionPage extends AbstractPage {
|
||||
return NAME;
|
||||
}
|
||||
|
||||
private final BooleanProperty expandedProperty = new SimpleBooleanProperty(true);
|
||||
private final BooleanProperty animatedProperty = new SimpleBooleanProperty(true);
|
||||
|
||||
private final Accordion accordion;
|
||||
|
||||
public AccordionPage() {
|
||||
super();
|
||||
|
||||
accordion = createPlayground();
|
||||
var sample = new SampleBlock(
|
||||
"Playground",
|
||||
new VBox(SampleBlock.BLOCK_VGAP, createControls(), accordion)
|
||||
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] \
|
||||
is made up of two parts, the header, and the content. The header is typically \
|
||||
a text or graphic and the content can be any valid JavaFX node. When a user \
|
||||
clicks on a header of an item, it will expand or collapse its content."""
|
||||
);
|
||||
sample.setFillHeight(true);
|
||||
setUserContent(sample);
|
||||
addSection("Usage", usageExample());
|
||||
addSection("Dense", denseExample());
|
||||
addSection("Alternative Icon", altIconExample());
|
||||
addSection("Playground", playground());
|
||||
}
|
||||
|
||||
private HBox createControls() {
|
||||
var animatedToggle = new ToggleSwitch("Animated");
|
||||
animatedProperty.bind(animatedToggle.selectedProperty());
|
||||
animatedToggle.setSelected(true);
|
||||
private ExampleBox usageExample() {
|
||||
//snippet_1:start
|
||||
Supplier<Node> gen = () -> {
|
||||
var text = FAKER.lorem().paragraph();
|
||||
var textFlow = new TextFlow(new Text(text));
|
||||
textFlow.setMinHeight(100);
|
||||
VBox.setVgrow(textFlow, Priority.ALWAYS);
|
||||
return new VBox(textFlow);
|
||||
};
|
||||
|
||||
var expandedToggle = new ToggleSwitch("Always expanded");
|
||||
expandedProperty.bind(expandedToggle.selectedProperty());
|
||||
expandedToggle.setSelected(true);
|
||||
var tp1 = new TitledPane("Item 1", gen.get());
|
||||
var tp2 = new TitledPane("Item 2", gen.get());
|
||||
var tp3 = new TitledPane("Item 3", gen.get());
|
||||
var accordion = new Accordion(tp1, tp2, tp3);
|
||||
//snippet_1:end
|
||||
|
||||
var denseToggle = new ToggleSwitch("Dense");
|
||||
denseToggle.selectedProperty().addListener(
|
||||
(obs, old, val) -> accordion.getPanes().forEach(p -> toggleStyleClass(p, DENSE))
|
||||
var description = BBCodeParser.createFormattedText("""
|
||||
An [i]Accordion[/i] consists of a group of [i]TitlePanes[/i], \
|
||||
each of which can have its content expanded or collapsed."""
|
||||
);
|
||||
|
||||
var altIconToggle = new ToggleSwitch("Alt icon");
|
||||
altIconToggle.selectedProperty().addListener(
|
||||
(obs, old, val) -> accordion.getPanes().forEach(p -> toggleStyleClass(p, Tweaks.ALT_ICON))
|
||||
);
|
||||
|
||||
var controls = new HBox(
|
||||
BLOCK_HGAP,
|
||||
animatedToggle,
|
||||
expandedToggle,
|
||||
denseToggle,
|
||||
altIconToggle
|
||||
);
|
||||
controls.setAlignment(Pos.CENTER);
|
||||
controls.setPadding(new Insets(0, 0, 0, 2));
|
||||
|
||||
return controls;
|
||||
return new ExampleBox(accordion, new Snippet(getClass(), 1), description);
|
||||
}
|
||||
|
||||
private Accordion createPlayground() {
|
||||
var textBlockContent = new Label(FAKER.chuckNorris().fact());
|
||||
var textBlock = new TitledPane("_Quote", textBlockContent);
|
||||
private ExampleBox denseExample() {
|
||||
//snippet_2:start
|
||||
Supplier<Node> gen = () -> {
|
||||
var text = FAKER.lorem().paragraph();
|
||||
var textFlow = new TextFlow(new Text(text));
|
||||
textFlow.setMinHeight(100);
|
||||
VBox.setVgrow(textFlow, Priority.ALWAYS);
|
||||
return new VBox(textFlow);
|
||||
};
|
||||
|
||||
var tp1 = new TitledPane("Item 1", gen.get());
|
||||
tp1.getStyleClass().add(Styles.DENSE);
|
||||
|
||||
var tp2 = new TitledPane("Item 2", gen.get());
|
||||
tp2.getStyleClass().add(Styles.DENSE);
|
||||
|
||||
var tp3 = new TitledPane("Item 3", gen.get());
|
||||
tp3.getStyleClass().add(Styles.DENSE);
|
||||
|
||||
var accordion = new Accordion(tp1, tp2, tp3);
|
||||
//snippet_2:end
|
||||
|
||||
var description = BBCodeParser.createFormattedText("""
|
||||
If you need more compact view there's [code]Styles.DENSE[/code] for that."""
|
||||
);
|
||||
|
||||
return new ExampleBox(accordion, new Snippet(getClass(), 2), description);
|
||||
}
|
||||
|
||||
private ExampleBox altIconExample() {
|
||||
//snippet_3:start
|
||||
Supplier<Node> gen = () -> {
|
||||
var text = FAKER.lorem().paragraph();
|
||||
var textFlow = new TextFlow(new Text(text));
|
||||
textFlow.setMinHeight(100);
|
||||
VBox.setVgrow(textFlow, Priority.ALWAYS);
|
||||
return new VBox(textFlow);
|
||||
};
|
||||
|
||||
var tp1 = new TitledPane("Item 1", gen.get());
|
||||
tp1.getStyleClass().add(Tweaks.ALT_ICON);
|
||||
|
||||
var tp2 = new TitledPane("Item 2", gen.get());
|
||||
tp2.getStyleClass().add(Tweaks.ALT_ICON);
|
||||
|
||||
var tp3 = new TitledPane("Item 3", gen.get());
|
||||
tp3.getStyleClass().add(Tweaks.ALT_ICON);
|
||||
|
||||
var accordion = new Accordion(tp1, tp2, tp3);
|
||||
//snippet_3:end
|
||||
|
||||
var description = BBCodeParser.createFormattedText("""
|
||||
There's also additional tweak [code]Tweaks.ALT_ICON[/code] to change header \
|
||||
arrow icon to the classic style."""
|
||||
);
|
||||
|
||||
return new ExampleBox(accordion, new Snippet(getClass(), 3), description);
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
// Playground //
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
|
||||
private VBox playground() {
|
||||
final var expandedProperty = new SimpleBooleanProperty(true);
|
||||
final var animatedProperty = new SimpleBooleanProperty(true);
|
||||
|
||||
// == ACCORDION ==
|
||||
|
||||
var labelBlock = new TitledPane("_Quote", new Label(FAKER.chuckNorris().fact()));
|
||||
labelBlock.setMnemonicParsing(true);
|
||||
labelBlock.animatedProperty().bind(animatedProperty);
|
||||
|
||||
var textFlow = new TextFlow(new Text(
|
||||
String.join("\n\n", FAKER.lorem().paragraphs(10)))
|
||||
);
|
||||
textFlow.setPadding(new Insets(0, 10, 0, 0));
|
||||
var textScroll = new ScrollPane(textFlow);
|
||||
textScroll.setMinHeight(200);
|
||||
textScroll.setFitToWidth(true);
|
||||
var textBlock = new TitledPane("_Scrollable Text", textScroll);
|
||||
textBlock.setMnemonicParsing(true);
|
||||
textBlock.animatedProperty().bind(animatedProperty);
|
||||
|
||||
var textFlow = new TextFlow(new Text(String.join("\n\n", FAKER.lorem().paragraphs(10))));
|
||||
textFlow.setPadding(new Insets(0, 10, 0, 0));
|
||||
var scrollTextBlockContent = new ScrollPane(textFlow);
|
||||
scrollTextBlockContent.setMinHeight(200);
|
||||
scrollTextBlockContent.setFitToWidth(true);
|
||||
var scrollableTextBlock = new TitledPane("_Scrollable Text", scrollTextBlockContent);
|
||||
scrollableTextBlock.setMnemonicParsing(true);
|
||||
scrollableTextBlock.animatedProperty().bind(animatedProperty);
|
||||
|
||||
var disabledBlock = new TitledPane("Disabled Block", null);
|
||||
disabledBlock.setDisable(true);
|
||||
|
||||
@ -110,14 +171,7 @@ public class AccordionPage extends AbstractPage {
|
||||
imageBlock.animatedProperty().bind(animatedProperty);
|
||||
imageBlock.setMnemonicParsing(true);
|
||||
|
||||
// ~
|
||||
|
||||
var accordion = new Accordion(
|
||||
textBlock,
|
||||
scrollableTextBlock,
|
||||
disabledBlock,
|
||||
imageBlock
|
||||
);
|
||||
var accordion = new Accordion(labelBlock, textBlock, disabledBlock, imageBlock);
|
||||
|
||||
// prevents accordion from being completely collapsed
|
||||
accordion.expandedPaneProperty().addListener((obs, old, val) -> {
|
||||
@ -128,6 +182,35 @@ public class AccordionPage extends AbstractPage {
|
||||
});
|
||||
accordion.setExpandedPane(accordion.getPanes().get(1));
|
||||
|
||||
return accordion;
|
||||
// == TOGGLES ==
|
||||
|
||||
var animatedToggle = new ToggleSwitch("Animated");
|
||||
animatedProperty.bind(animatedToggle.selectedProperty());
|
||||
animatedToggle.setSelected(true);
|
||||
|
||||
var expandedToggle = new ToggleSwitch("Always expanded");
|
||||
expandedProperty.bind(expandedToggle.selectedProperty());
|
||||
expandedToggle.setSelected(true);
|
||||
|
||||
var denseToggle = new ToggleSwitch("Dense");
|
||||
denseToggle.selectedProperty().addListener(
|
||||
(obs, old, val) -> accordion.getPanes().forEach(p -> Styles.toggleStyleClass(p, Styles.DENSE))
|
||||
);
|
||||
|
||||
var altIconToggle = new ToggleSwitch("Alt icon");
|
||||
altIconToggle.selectedProperty().addListener(
|
||||
(obs, old, val) -> accordion.getPanes().forEach(p -> Styles.toggleStyleClass(p, Tweaks.ALT_ICON))
|
||||
);
|
||||
|
||||
var controls = new HBox(HGAP_20, animatedToggle, expandedToggle, denseToggle, altIconToggle);
|
||||
controls.setAlignment(Pos.CENTER);
|
||||
controls.setPadding(new Insets(0, 0, 0, 2));
|
||||
|
||||
var description = BBCodeParser.createFormattedText("""
|
||||
The playground demonstrates the most important [i]Accordion[/i] features \
|
||||
and also serves as an object for monkey testing."""
|
||||
);
|
||||
|
||||
return new VBox(VGAP_10, description, accordion, controls);
|
||||
}
|
||||
}
|
||||
|
@ -1,115 +0,0 @@
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
|
||||
package atlantafx.sampler.page.components;
|
||||
|
||||
import atlantafx.base.controls.Breadcrumbs;
|
||||
import atlantafx.base.controls.Breadcrumbs.BreadCrumbItem;
|
||||
import atlantafx.base.theme.Styles;
|
||||
import atlantafx.sampler.page.AbstractPage;
|
||||
import atlantafx.sampler.page.Page;
|
||||
import atlantafx.sampler.page.SampleBlock;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.control.Button;
|
||||
import javafx.scene.control.ButtonBase;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.layout.HBox;
|
||||
import javafx.scene.layout.VBox;
|
||||
import javafx.util.Callback;
|
||||
import org.kordamp.ikonli.javafx.FontIcon;
|
||||
import org.kordamp.ikonli.material2.Material2AL;
|
||||
|
||||
public class BreadcrumbsPage extends AbstractPage {
|
||||
|
||||
public static final String NAME = "Breadcrumbs";
|
||||
|
||||
private static final int CRUMB_COUNT = 5;
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return NAME;
|
||||
}
|
||||
|
||||
public BreadcrumbsPage() {
|
||||
super();
|
||||
setUserContent(new VBox(
|
||||
Page.PAGE_VGAP,
|
||||
basicSample(),
|
||||
customCrumbSample(),
|
||||
customDividerSample()
|
||||
));
|
||||
}
|
||||
|
||||
private SampleBlock basicSample() {
|
||||
return new SampleBlock("Basic", createBreadcrumbs(null, null));
|
||||
}
|
||||
|
||||
private SampleBlock customCrumbSample() {
|
||||
Callback<BreadCrumbItem<String>, ButtonBase> crumbFactory = crumb -> {
|
||||
var btn = new Button(crumb.getValue(), new FontIcon(randomIcon()));
|
||||
btn.getStyleClass().add(Styles.FLAT);
|
||||
btn.setFocusTraversable(false);
|
||||
return btn;
|
||||
};
|
||||
|
||||
return new SampleBlock("Flat Button", createBreadcrumbs(crumbFactory, null));
|
||||
}
|
||||
|
||||
private SampleBlock customDividerSample() {
|
||||
Callback<BreadCrumbItem<String>, ? extends Node> dividerFactory = item -> {
|
||||
if (item == null) {
|
||||
return new Label("", new FontIcon(Material2AL.HOME));
|
||||
}
|
||||
return !item.isLast() ? new Label("", new FontIcon(Material2AL.CHEVRON_RIGHT)) : null;
|
||||
};
|
||||
|
||||
return new SampleBlock("Custom Divider", createBreadcrumbs(null, dividerFactory));
|
||||
}
|
||||
|
||||
private HBox createBreadcrumbs(Callback<BreadCrumbItem<String>, ButtonBase> crumbFactory,
|
||||
Callback<BreadCrumbItem<String>, ? extends Node> dividerFactory) {
|
||||
BreadCrumbItem<String> model = Breadcrumbs.buildTreeModel(
|
||||
generate(() -> FAKER.science().element(), CRUMB_COUNT).toArray(String[]::new)
|
||||
);
|
||||
|
||||
var nextBtn = new Button("Next");
|
||||
nextBtn.getStyleClass().addAll(Styles.ACCENT);
|
||||
|
||||
var breadcrumbs = new Breadcrumbs<>(model);
|
||||
breadcrumbs.setSelectedCrumb(getAncestor(model, CRUMB_COUNT / 2));
|
||||
if (crumbFactory != null) {
|
||||
breadcrumbs.setCrumbFactory(crumbFactory);
|
||||
}
|
||||
if (dividerFactory != null) {
|
||||
breadcrumbs.setDividerFactory(dividerFactory);
|
||||
}
|
||||
|
||||
nextBtn.setOnAction(e -> {
|
||||
BreadCrumbItem<String> selected = breadcrumbs.getSelectedCrumb();
|
||||
if (selected.getChildren().size() > 0) {
|
||||
breadcrumbs.setSelectedCrumb((BreadCrumbItem<String>) selected.getChildren().get(0));
|
||||
}
|
||||
});
|
||||
|
||||
breadcrumbs.selectedCrumbProperty().addListener((obs, old, val) -> {
|
||||
if (val != null) {
|
||||
nextBtn.setDisable(val.getChildren().isEmpty());
|
||||
}
|
||||
});
|
||||
|
||||
var box = new HBox(40, nextBtn, breadcrumbs);
|
||||
box.setAlignment(Pos.CENTER_LEFT);
|
||||
|
||||
return box;
|
||||
}
|
||||
|
||||
private <T> BreadCrumbItem<T> getAncestor(BreadCrumbItem<T> node, int height) {
|
||||
var counter = height;
|
||||
var current = node;
|
||||
while (counter > 0 && current.getParent() != null) {
|
||||
current = (BreadCrumbItem<T>) current.getParent();
|
||||
counter--;
|
||||
}
|
||||
return current;
|
||||
}
|
||||
}
|
@ -2,33 +2,23 @@
|
||||
|
||||
package atlantafx.sampler.page.components;
|
||||
|
||||
import static atlantafx.base.theme.Styles.ACCENT;
|
||||
import static atlantafx.base.theme.Styles.BUTTON_CIRCLE;
|
||||
import static atlantafx.base.theme.Styles.BUTTON_ICON;
|
||||
import static atlantafx.base.theme.Styles.BUTTON_OUTLINED;
|
||||
import static atlantafx.base.theme.Styles.DANGER;
|
||||
import static atlantafx.base.theme.Styles.FLAT;
|
||||
import static atlantafx.base.theme.Styles.LARGE;
|
||||
import static atlantafx.base.theme.Styles.ROUNDED;
|
||||
import static atlantafx.base.theme.Styles.SMALL;
|
||||
import static atlantafx.base.theme.Styles.SUCCESS;
|
||||
import static atlantafx.sampler.page.SampleBlock.BLOCK_HGAP;
|
||||
|
||||
import atlantafx.sampler.page.AbstractPage;
|
||||
import atlantafx.sampler.page.Page;
|
||||
import atlantafx.sampler.page.SampleBlock;
|
||||
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 atlantafx.sampler.theme.CSSFragment;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.scene.control.Button;
|
||||
import javafx.scene.control.ContentDisplay;
|
||||
import javafx.scene.layout.GridPane;
|
||||
import javafx.scene.layout.HBox;
|
||||
import javafx.scene.layout.VBox;
|
||||
import javafx.scene.shape.Circle;
|
||||
import org.kordamp.ikonli.feather.Feather;
|
||||
import org.kordamp.ikonli.javafx.FontIcon;
|
||||
import org.kordamp.ikonli.material2.Material2AL;
|
||||
|
||||
public class ButtonPage extends AbstractPage {
|
||||
public class ButtonPage extends OutlinePage {
|
||||
|
||||
public static final String NAME = "Button";
|
||||
|
||||
@ -39,223 +29,382 @@ public class ButtonPage extends AbstractPage {
|
||||
|
||||
public ButtonPage() {
|
||||
super();
|
||||
createView();
|
||||
|
||||
addFormattedText("""
|
||||
A simple button control. The button control can contain text and/or a graphic.
|
||||
A button control has three different modes:
|
||||
|
||||
[ul]
|
||||
[li]Normal: A normal push button.[/li]
|
||||
[li]Default: A default button is the button that receives a keyboard [code]VK_ENTER[/code] press,
|
||||
if no other node in the scene consumes it.[/li]
|
||||
[li]Cancel: A cancel button is the button that receives a keyboard [code]VK_ESC[/code] press,
|
||||
if no other node in the scene consumes it.[/li][/ul]"""
|
||||
);
|
||||
addSection("Usage", usageExample());
|
||||
addSection("Colored", coloredButtonExample());
|
||||
addSection("Icon Button", iconButtonExample());
|
||||
addSection("Circular", circularButtonExample());
|
||||
addSection("Outlined", outlinedButtonExample());
|
||||
addSection("Rounded", roundedButtonExample());
|
||||
addSection("Button Size", buttonSizeExample());
|
||||
addSection("Custom Styles", customColorExample());
|
||||
}
|
||||
|
||||
private void createView() {
|
||||
var grid = new GridPane();
|
||||
grid.setHgap(Page.PAGE_HGAP);
|
||||
grid.setVgap(Page.PAGE_VGAP);
|
||||
|
||||
grid.add(basicSample(), 0, 0);
|
||||
grid.add(iconButtonSample(), 1, 0);
|
||||
|
||||
grid.add(coloredSample(), 0, 1);
|
||||
grid.add(circularSample(), 1, 1);
|
||||
|
||||
grid.add(outlinedSample(), 0, 2);
|
||||
grid.add(sizeSample(), 1, 2);
|
||||
|
||||
grid.add(roundedSample(), 0, 3);
|
||||
grid.add(customColorSample(), 1, 3);
|
||||
|
||||
grid.add(disabledSample(), 0, 4);
|
||||
|
||||
setUserContent(grid);
|
||||
}
|
||||
|
||||
private SampleBlock basicSample() {
|
||||
var basicBtn = new Button("_Basic");
|
||||
basicBtn.setMnemonicParsing(true);
|
||||
private ExampleBox usageExample() {
|
||||
//snippet_1:start
|
||||
var normalBtn = new Button("_Normal");
|
||||
normalBtn.setMnemonicParsing(true);
|
||||
|
||||
var defaultBtn = new Button("_Default");
|
||||
defaultBtn.setDefaultButton(true);
|
||||
defaultBtn.setMnemonicParsing(true);
|
||||
|
||||
var flatBtn = new Button("_Flat");
|
||||
flatBtn.getStyleClass().add(FLAT);
|
||||
var outlinedBtn = new Button("Out_lined");
|
||||
outlinedBtn.getStyleClass().addAll(
|
||||
Styles.BUTTON_OUTLINED, Styles.ACCENT
|
||||
);
|
||||
outlinedBtn.setMnemonicParsing(true);
|
||||
|
||||
var content = new HBox(BLOCK_HGAP, basicBtn, defaultBtn, flatBtn);
|
||||
return new SampleBlock("Basic", content);
|
||||
var flatBtn = new Button("_Flat");
|
||||
flatBtn.getStyleClass().add(Styles.FLAT);
|
||||
//snippet_1:end
|
||||
|
||||
var box = new HBox(HGAP_20, normalBtn, defaultBtn, outlinedBtn, flatBtn);
|
||||
var description = BBCodeParser.createFormattedText("""
|
||||
The [i]Button[/i] comes with four CSS variants: normal (default), colored, \
|
||||
outlined, and flat (or text). To change the appearance of the [i]Button[/i], \
|
||||
you set the corresponding style classes that work as modifiers."""
|
||||
);
|
||||
|
||||
return new ExampleBox(box, new Snippet(getClass(), 1), description);
|
||||
}
|
||||
|
||||
private SampleBlock coloredSample() {
|
||||
private ExampleBox coloredButtonExample() {
|
||||
//snippet_2:start
|
||||
var accentBtn = new Button("_Accent");
|
||||
accentBtn.getStyleClass().add(ACCENT);
|
||||
accentBtn.getStyleClass().add(Styles.ACCENT);
|
||||
accentBtn.setMnemonicParsing(true);
|
||||
|
||||
var successBtn = new Button("_Success", new FontIcon(Feather.CHECK));
|
||||
successBtn.getStyleClass().add(SUCCESS);
|
||||
var successBtn = new Button(
|
||||
"_Success", new FontIcon(Feather.CHECK)
|
||||
);
|
||||
successBtn.getStyleClass().add(Styles.SUCCESS);
|
||||
successBtn.setMnemonicParsing(true);
|
||||
|
||||
var dangerBtn = new Button("Da_nger", new FontIcon(Feather.TRASH));
|
||||
dangerBtn.getStyleClass().add(DANGER);
|
||||
var dangerBtn = new Button(
|
||||
"Da_nger", new FontIcon(Feather.TRASH)
|
||||
);
|
||||
dangerBtn.getStyleClass().add(Styles.DANGER);
|
||||
dangerBtn.setContentDisplay(ContentDisplay.RIGHT);
|
||||
dangerBtn.setMnemonicParsing(true);
|
||||
|
||||
var content = new HBox(BLOCK_HGAP, accentBtn, successBtn, dangerBtn);
|
||||
return new SampleBlock("Colored", content);
|
||||
// ~
|
||||
var accentOutBtn = new Button("Accen_t");
|
||||
accentOutBtn.getStyleClass().addAll(
|
||||
Styles.BUTTON_OUTLINED, Styles.ACCENT
|
||||
);
|
||||
accentOutBtn.setMnemonicParsing(true);
|
||||
|
||||
var successOutBtn = new Button(
|
||||
"S_uccess", new FontIcon(Feather.CHECK)
|
||||
);
|
||||
successOutBtn.getStyleClass().addAll(
|
||||
Styles.BUTTON_OUTLINED, Styles.SUCCESS
|
||||
);
|
||||
successOutBtn.setMnemonicParsing(true);
|
||||
|
||||
var dangerOutBtn = new Button(
|
||||
"Dan_ger", new FontIcon(Feather.TRASH)
|
||||
);
|
||||
dangerOutBtn.getStyleClass().addAll(
|
||||
Styles.BUTTON_OUTLINED, Styles.DANGER
|
||||
);
|
||||
dangerOutBtn.setContentDisplay(ContentDisplay.RIGHT);
|
||||
dangerOutBtn.setMnemonicParsing(true);
|
||||
|
||||
// ~
|
||||
var accentFlatBtn = new Button("Accen_t");
|
||||
accentFlatBtn.getStyleClass().addAll(
|
||||
Styles.FLAT, Styles.ACCENT
|
||||
);
|
||||
accentFlatBtn.setMnemonicParsing(true);
|
||||
|
||||
var successFlatBtn = new Button(
|
||||
"S_uccess", new FontIcon(Feather.CHECK)
|
||||
);
|
||||
successFlatBtn.getStyleClass().addAll(
|
||||
Styles.FLAT, Styles.SUCCESS
|
||||
);
|
||||
successFlatBtn.setMnemonicParsing(true);
|
||||
|
||||
var dangerFlatBtn = new Button(
|
||||
"Dan_ger", new FontIcon(Feather.TRASH)
|
||||
);
|
||||
dangerFlatBtn.getStyleClass().addAll(
|
||||
Styles.FLAT, Styles.DANGER
|
||||
);
|
||||
dangerFlatBtn.setContentDisplay(ContentDisplay.RIGHT);
|
||||
dangerFlatBtn.setMnemonicParsing(true);
|
||||
//snippet_2:end
|
||||
|
||||
var box = new VBox(
|
||||
VGAP_20,
|
||||
new HBox(HGAP_20, accentBtn, successBtn, dangerBtn),
|
||||
new HBox(HGAP_20, accentOutBtn, successOutBtn, dangerOutBtn),
|
||||
new HBox(HGAP_20, accentFlatBtn, successFlatBtn, dangerFlatBtn)
|
||||
);
|
||||
var description = BBCodeParser.createFormattedText("""
|
||||
You can change the [i]Button[/i] color simply by using predefined style class modifiers."""
|
||||
);
|
||||
|
||||
return new ExampleBox(box, new Snippet(getClass(), 2), description);
|
||||
}
|
||||
|
||||
private SampleBlock iconButtonSample() {
|
||||
var basicBtn = new Button("", new FontIcon(Feather.MORE_HORIZONTAL));
|
||||
basicBtn.getStyleClass().addAll(BUTTON_ICON);
|
||||
private ExampleBox iconButtonExample() {
|
||||
//snippet_3:start
|
||||
var normalBtn = new Button("", new FontIcon(Feather.MORE_HORIZONTAL));
|
||||
normalBtn.getStyleClass().addAll(Styles.BUTTON_ICON);
|
||||
|
||||
var accentBtn = new Button("", new FontIcon(Feather.MENU));
|
||||
accentBtn.getStyleClass().addAll(BUTTON_ICON, ACCENT);
|
||||
accentBtn.getStyleClass().addAll(
|
||||
Styles.BUTTON_ICON, Styles.ACCENT
|
||||
);
|
||||
|
||||
var successBtn = new Button("", new FontIcon(Feather.CHECK));
|
||||
successBtn.getStyleClass().addAll(BUTTON_ICON, SUCCESS);
|
||||
successBtn.getStyleClass().addAll(
|
||||
Styles.BUTTON_ICON, Styles.SUCCESS
|
||||
);
|
||||
|
||||
var dangerBtn = new Button("", new FontIcon(Feather.TRASH));
|
||||
dangerBtn.getStyleClass().addAll(BUTTON_ICON, BUTTON_OUTLINED, DANGER);
|
||||
dangerBtn.getStyleClass().addAll(
|
||||
Styles.BUTTON_ICON, Styles.BUTTON_OUTLINED, Styles.DANGER
|
||||
);
|
||||
|
||||
var flatAccentBtn = new Button("", new FontIcon(Feather.MIC));
|
||||
flatAccentBtn.getStyleClass().addAll(BUTTON_ICON, FLAT, ACCENT);
|
||||
flatAccentBtn.getStyleClass().addAll(
|
||||
Styles.BUTTON_ICON, Styles.FLAT, Styles.ACCENT
|
||||
);
|
||||
|
||||
var flatSuccessBtn = new Button("", new FontIcon(Feather.USER));
|
||||
flatSuccessBtn.getStyleClass().addAll(BUTTON_ICON, FLAT, SUCCESS);
|
||||
flatSuccessBtn.getStyleClass().addAll(
|
||||
Styles.BUTTON_ICON, Styles.FLAT, Styles.SUCCESS
|
||||
);
|
||||
|
||||
var flatDangerBtn = new Button("", new FontIcon(Feather.CROSSHAIR));
|
||||
flatDangerBtn.getStyleClass().addAll(BUTTON_ICON, FLAT, DANGER);
|
||||
flatDangerBtn.getStyleClass().addAll(
|
||||
Styles.BUTTON_ICON, Styles.FLAT, Styles.DANGER
|
||||
);
|
||||
//snippet_3:end
|
||||
|
||||
var content = new HBox(BLOCK_HGAP,
|
||||
basicBtn, accentBtn, successBtn, dangerBtn,
|
||||
var box = new HBox(HGAP_20,
|
||||
normalBtn, accentBtn, successBtn, dangerBtn,
|
||||
flatAccentBtn, flatSuccessBtn, flatDangerBtn
|
||||
);
|
||||
return new SampleBlock("Icon", content);
|
||||
var description = BBCodeParser.createFormattedText("""
|
||||
Icon buttons are present in two variants. The first one is just a \
|
||||
normal [i]Button[/i] but with no text and the second one is a flat button - \
|
||||
suitable for toolbars and similar controls."""
|
||||
);
|
||||
|
||||
return new ExampleBox(box, new Snippet(getClass(), 3), description);
|
||||
}
|
||||
|
||||
private SampleBlock circularSample() {
|
||||
var basicBtn = new Button("", new FontIcon(Feather.MORE_HORIZONTAL));
|
||||
basicBtn.getStyleClass().addAll(BUTTON_CIRCLE);
|
||||
basicBtn.setShape(new Circle(50));
|
||||
private ExampleBox circularButtonExample() {
|
||||
//snippet_4:start
|
||||
var normalBtn = new Button("", new FontIcon(Feather.MORE_HORIZONTAL));
|
||||
normalBtn.getStyleClass().addAll(Styles.BUTTON_CIRCLE);
|
||||
normalBtn.setShape(new Circle(50));
|
||||
|
||||
var accentBtn = new Button("", new FontIcon(Feather.MENU));
|
||||
accentBtn.getStyleClass().addAll(BUTTON_CIRCLE, ACCENT);
|
||||
accentBtn.getStyleClass().addAll(
|
||||
Styles.BUTTON_CIRCLE, Styles.ACCENT
|
||||
);
|
||||
accentBtn.setShape(new Circle(50));
|
||||
|
||||
var successBtn = new Button("", new FontIcon(Feather.CHECK));
|
||||
successBtn.getStyleClass().addAll(BUTTON_CIRCLE, SUCCESS);
|
||||
successBtn.getStyleClass().addAll(
|
||||
Styles.BUTTON_CIRCLE, Styles.SUCCESS
|
||||
);
|
||||
successBtn.setShape(new Circle(50));
|
||||
|
||||
var dangerBtn = new Button("", new FontIcon(Feather.TRASH));
|
||||
dangerBtn.getStyleClass().addAll(BUTTON_CIRCLE, BUTTON_OUTLINED, DANGER);
|
||||
dangerBtn.getStyleClass().addAll(
|
||||
Styles.BUTTON_CIRCLE, Styles.BUTTON_OUTLINED, Styles.DANGER
|
||||
);
|
||||
dangerBtn.setShape(new Circle(50));
|
||||
|
||||
var flatAccentBtn = new Button("", new FontIcon(Feather.MIC));
|
||||
flatAccentBtn.getStyleClass().addAll(BUTTON_CIRCLE, FLAT, ACCENT);
|
||||
flatAccentBtn.getStyleClass().addAll(
|
||||
Styles.BUTTON_CIRCLE, Styles.FLAT, Styles.ACCENT
|
||||
);
|
||||
flatAccentBtn.setShape(new Circle(50));
|
||||
|
||||
var flatSuccessBtn = new Button("", new FontIcon(Feather.USER));
|
||||
flatSuccessBtn.getStyleClass().addAll(BUTTON_CIRCLE, FLAT, SUCCESS);
|
||||
flatSuccessBtn.getStyleClass().addAll(
|
||||
Styles.BUTTON_CIRCLE, Styles.FLAT, Styles.SUCCESS
|
||||
);
|
||||
flatSuccessBtn.setShape(new Circle(50));
|
||||
|
||||
var flatDangerBtn = new Button("", new FontIcon(Feather.CROSSHAIR));
|
||||
flatDangerBtn.getStyleClass().addAll(BUTTON_CIRCLE, FLAT, DANGER);
|
||||
flatDangerBtn.getStyleClass().addAll(
|
||||
Styles.BUTTON_CIRCLE, Styles.FLAT, Styles.DANGER
|
||||
);
|
||||
flatDangerBtn.setShape(new Circle(50));
|
||||
//snippet_4:end
|
||||
|
||||
var content = new HBox(BLOCK_HGAP,
|
||||
basicBtn, accentBtn, successBtn, dangerBtn,
|
||||
var box = new HBox(HGAP_20,
|
||||
normalBtn, accentBtn, successBtn, dangerBtn,
|
||||
flatAccentBtn, flatSuccessBtn, flatDangerBtn
|
||||
);
|
||||
return new SampleBlock("Circular", content);
|
||||
|
||||
var description = BBCodeParser.createFormattedText("""
|
||||
You can also apply the [code]setShape()[/code] method to make the \
|
||||
[i]Button[/i] look circular.""");
|
||||
|
||||
return new ExampleBox(box, new Snippet(getClass(), 4), description);
|
||||
}
|
||||
|
||||
private SampleBlock outlinedSample() {
|
||||
var accentBtn = new Button("Accen_t");
|
||||
accentBtn.getStyleClass().addAll(BUTTON_OUTLINED, ACCENT);
|
||||
accentBtn.setMnemonicParsing(true);
|
||||
private ExampleBox outlinedButtonExample() {
|
||||
//snippet_5:start
|
||||
var accentOutBtn = new Button("Accen_t");
|
||||
accentOutBtn.getStyleClass().addAll(
|
||||
Styles.BUTTON_OUTLINED, Styles.ACCENT
|
||||
);
|
||||
accentOutBtn.setMnemonicParsing(true);
|
||||
|
||||
var successBtn = new Button("S_uccess", new FontIcon(Feather.CHECK));
|
||||
successBtn.getStyleClass().addAll(BUTTON_OUTLINED, SUCCESS);
|
||||
successBtn.setMnemonicParsing(true);
|
||||
var successOutBtn = new Button("S_uccess", new FontIcon(Feather.CHECK));
|
||||
successOutBtn.getStyleClass().addAll(
|
||||
Styles.BUTTON_OUTLINED, Styles.SUCCESS
|
||||
);
|
||||
successOutBtn.setMnemonicParsing(true);
|
||||
|
||||
var dangerBtn = new Button("Dan_ger", new FontIcon(Feather.TRASH));
|
||||
dangerBtn.getStyleClass().addAll(BUTTON_OUTLINED, DANGER);
|
||||
dangerBtn.setContentDisplay(ContentDisplay.RIGHT);
|
||||
dangerBtn.setMnemonicParsing(true);
|
||||
var dangerOutBtn = new Button("Dan_ger", new FontIcon(Feather.TRASH));
|
||||
dangerOutBtn.getStyleClass().addAll(
|
||||
Styles.BUTTON_OUTLINED, Styles.DANGER
|
||||
);
|
||||
dangerOutBtn.setContentDisplay(ContentDisplay.RIGHT);
|
||||
dangerOutBtn.setMnemonicParsing(true);
|
||||
//snippet_5:end
|
||||
|
||||
var content = new HBox(BLOCK_HGAP, accentBtn, successBtn, dangerBtn);
|
||||
var box = new HBox(HGAP_20, accentOutBtn, successOutBtn, dangerOutBtn);
|
||||
var description = BBCodeParser.createFormattedText("""
|
||||
Outlined buttons are medium-emphasis buttons. They contain actions that are \
|
||||
important but aren't the primary action in an app."""
|
||||
);
|
||||
|
||||
return new SampleBlock("Outlined", content);
|
||||
return new ExampleBox(box, new Snippet(getClass(), 5), description);
|
||||
}
|
||||
|
||||
private SampleBlock roundedSample() {
|
||||
var basicBtn = new Button("Basic");
|
||||
basicBtn.getStyleClass().addAll(SMALL, ROUNDED);
|
||||
private ExampleBox roundedButtonExample() {
|
||||
//snippet_6:start
|
||||
var normalBtn = new Button("Normal");
|
||||
normalBtn.getStyleClass().addAll(
|
||||
Styles.SMALL, Styles.ROUNDED
|
||||
);
|
||||
|
||||
var accentBtn = new Button("Accent");
|
||||
accentBtn.getStyleClass().addAll(ROUNDED, ACCENT);
|
||||
accentBtn.getStyleClass().addAll(
|
||||
Styles.ROUNDED, Styles.ACCENT
|
||||
);
|
||||
|
||||
var successBtn = new Button("Success", new FontIcon(Feather.CHECK));
|
||||
successBtn.getStyleClass().addAll(LARGE, ROUNDED, BUTTON_OUTLINED, SUCCESS);
|
||||
successBtn.getStyleClass().addAll(
|
||||
Styles.LARGE, Styles.ROUNDED, Styles.BUTTON_OUTLINED, Styles.SUCCESS
|
||||
);
|
||||
//snippet_6:end
|
||||
|
||||
var content = new HBox(BLOCK_HGAP, basicBtn, accentBtn, successBtn);
|
||||
content.setAlignment(Pos.CENTER_LEFT);
|
||||
var box = new HBox(HGAP_20, normalBtn, accentBtn, successBtn);
|
||||
box.setAlignment(Pos.CENTER_LEFT);
|
||||
|
||||
return new SampleBlock("Rounded", content);
|
||||
var description = BBCodeParser.createFormattedText("""
|
||||
[i]Button[/i] corners can be rounded with the [code]Styles.ROUNDED[/code] \
|
||||
style class modifier."""
|
||||
);
|
||||
|
||||
return new ExampleBox(box, new Snippet(getClass(), 6), description);
|
||||
}
|
||||
|
||||
private SampleBlock sizeSample() {
|
||||
private ExampleBox buttonSizeExample() {
|
||||
//snippet_7:start
|
||||
var smallBtn = new Button("Small");
|
||||
smallBtn.getStyleClass().addAll(SMALL);
|
||||
smallBtn.getStyleClass().addAll(Styles.SMALL);
|
||||
|
||||
var normalBtn = new Button("Normal");
|
||||
|
||||
var largeBtn = new Button("Large");
|
||||
largeBtn.getStyleClass().addAll(LARGE);
|
||||
largeBtn.getStyleClass().addAll(Styles.LARGE);
|
||||
//snippet_7:end
|
||||
|
||||
var content = new HBox(BLOCK_HGAP, smallBtn, normalBtn, largeBtn);
|
||||
content.setAlignment(Pos.CENTER_LEFT);
|
||||
var box = new HBox(HGAP_20, smallBtn, normalBtn, largeBtn);
|
||||
box.setAlignment(Pos.CENTER_LEFT);
|
||||
|
||||
return new SampleBlock("Size", content);
|
||||
var description = BBCodeParser.createFormattedText("""
|
||||
For larger or smaller buttons, use the [code]Styles.SMALL[/code] or \
|
||||
[code]Styles.LARGE[/code] style classes, respectively."""
|
||||
);
|
||||
|
||||
return new ExampleBox(box, new Snippet(getClass(), 7), description);
|
||||
}
|
||||
|
||||
private SampleBlock customColorSample() {
|
||||
var btn = new Button("DO SOMETHING!");
|
||||
btn.getStyleClass().addAll(SUCCESS, LARGE);
|
||||
btn.setStyle("""
|
||||
-color-button-bg: linear-gradient(to bottom right, -color-success-emphasis, darkblue);
|
||||
private ExampleBox customColorExample() {
|
||||
var customStyle = """
|
||||
-color-button-bg: linear-gradient(
|
||||
to bottom right, -color-success-emphasis, darkblue
|
||||
);
|
||||
-color-button-bg-hover: -color-button-bg;
|
||||
-color-button-bg-focused: -color-button-bg;
|
||||
-color-button-bg-pressed: -color-button-bg;
|
||||
""");
|
||||
-color-button-bg-pressed: -color-button-bg;""";
|
||||
|
||||
var iconBtn = new Button("", new FontIcon(Material2AL.FAVORITE));
|
||||
iconBtn.getStyleClass().addAll("favorite-button", BUTTON_CIRCLE, FLAT, DANGER);
|
||||
new CSSFragment("""
|
||||
String dataClass = """
|
||||
.favorite-button.button >.ikonli-font-icon {
|
||||
-fx-fill: linear-gradient(to bottom right, pink, -color-danger-emphasis);
|
||||
-fx-icon-color: linear-gradient(to bottom right, pink, -color-danger-emphasis);
|
||||
-fx-fill: linear-gradient(
|
||||
to bottom right, pink, -color-danger-emphasis
|
||||
);
|
||||
-fx-icon-color: linear-gradient(
|
||||
to bottom right, pink, -color-danger-emphasis
|
||||
);
|
||||
-fx-font-size: 32px;
|
||||
-fx-icon-size: 32px;
|
||||
}
|
||||
""").addTo(iconBtn);
|
||||
}""";
|
||||
|
||||
var content = new HBox(BLOCK_HGAP, btn, iconBtn);
|
||||
content.setAlignment(Pos.CENTER_LEFT);
|
||||
//snippet_8:start
|
||||
var btn = new Button("DO SOMETHING!");
|
||||
btn.getStyleClass().addAll(Styles.SUCCESS, Styles.LARGE);
|
||||
// -color-button-bg: linear-gradient(
|
||||
// to bottom right, -color-success-emphasis, darkblue
|
||||
// );
|
||||
// -color-button-bg-hover: -color-button-bg;
|
||||
// -color-button-bg-focused: -color-button-bg;
|
||||
// -color-button-bg-pressed: -color-button-bg;
|
||||
btn.setStyle(customStyle);
|
||||
|
||||
return new SampleBlock("Custom Color", content);
|
||||
}
|
||||
var iconBtn = new Button("", new FontIcon(Material2AL.FAVORITE));
|
||||
iconBtn.getStyleClass().addAll(
|
||||
"favorite-button",
|
||||
Styles.BUTTON_CIRCLE, Styles.FLAT, Styles.DANGER
|
||||
);
|
||||
// .favorite-button.button >.ikonli-font-icon {
|
||||
// -fx-fill: linear-gradient(
|
||||
// to bottom right, pink, -color-danger-emphasis
|
||||
// );
|
||||
// -fx-icon-color: linear-gradient(
|
||||
// to bottom right, pink, -color-danger-emphasis
|
||||
// );
|
||||
// -fx-font-size: 32px;
|
||||
// -fx-icon-size: 32px;
|
||||
// }
|
||||
new CSSFragment(dataClass).addTo(iconBtn);
|
||||
//snippet_8:end
|
||||
|
||||
private SampleBlock disabledSample() {
|
||||
var basicBtn = new Button("Basic");
|
||||
basicBtn.setDisable(true);
|
||||
var box = new HBox(HGAP_20, btn, iconBtn);
|
||||
box.setAlignment(Pos.CENTER_LEFT);
|
||||
|
||||
var defaultBtn = new Button("Default");
|
||||
defaultBtn.setDefaultButton(true);
|
||||
defaultBtn.setDisable(true);
|
||||
var description = BBCodeParser.createFormattedText("""
|
||||
In addition to using the predefined [i]Button[/i] colors, you can add custom ones \
|
||||
by manipulating the looked-up color variables."""
|
||||
);
|
||||
|
||||
var flatBtn = new Button("Flat");
|
||||
flatBtn.getStyleClass().addAll(FLAT);
|
||||
flatBtn.setDisable(true);
|
||||
|
||||
var iconBtn = new Button("", new FontIcon(Feather.TAG));
|
||||
iconBtn.getStyleClass().addAll(BUTTON_ICON);
|
||||
iconBtn.setDisable(true);
|
||||
|
||||
var content = new HBox(BLOCK_HGAP, basicBtn, defaultBtn, flatBtn, iconBtn);
|
||||
return new SampleBlock("Disabled", content);
|
||||
return new ExampleBox(box, new Snippet(getClass(), 8), description);
|
||||
}
|
||||
}
|
||||
|
@ -2,25 +2,20 @@
|
||||
|
||||
package atlantafx.sampler.page.components;
|
||||
|
||||
import atlantafx.base.controls.Spacer;
|
||||
import atlantafx.base.controls.ToggleSwitch;
|
||||
import atlantafx.sampler.page.AbstractPage;
|
||||
import atlantafx.sampler.page.SampleBlock;
|
||||
import atlantafx.base.util.BBCodeParser;
|
||||
import atlantafx.sampler.page.ExampleBox;
|
||||
import atlantafx.sampler.page.OutlinePage;
|
||||
import atlantafx.sampler.page.Snippet;
|
||||
import java.time.Month;
|
||||
import java.time.format.TextStyle;
|
||||
import java.util.Arrays;
|
||||
import java.util.Locale;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.stream.IntStream;
|
||||
import javafx.collections.FXCollections;
|
||||
import javafx.collections.ObservableList;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.scene.chart.AreaChart;
|
||||
import javafx.scene.chart.BarChart;
|
||||
import javafx.scene.chart.BubbleChart;
|
||||
import javafx.scene.chart.CategoryAxis;
|
||||
import javafx.scene.chart.Chart;
|
||||
import javafx.scene.chart.LineChart;
|
||||
import javafx.scene.chart.NumberAxis;
|
||||
import javafx.scene.chart.PieChart;
|
||||
@ -28,14 +23,8 @@ import javafx.scene.chart.ScatterChart;
|
||||
import javafx.scene.chart.StackedAreaChart;
|
||||
import javafx.scene.chart.StackedBarChart;
|
||||
import javafx.scene.chart.XYChart;
|
||||
import javafx.scene.control.ComboBox;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.layout.BorderPane;
|
||||
import javafx.scene.layout.HBox;
|
||||
import javafx.scene.layout.VBox;
|
||||
import javafx.util.StringConverter;
|
||||
|
||||
public class ChartPage extends AbstractPage {
|
||||
public class ChartPage extends OutlinePage {
|
||||
|
||||
public static final String NAME = "Chart";
|
||||
|
||||
@ -44,107 +33,27 @@ public class ChartPage extends AbstractPage {
|
||||
return NAME;
|
||||
}
|
||||
|
||||
private final BorderPane chartWrapper = new BorderPane();
|
||||
private final ComboBox<Example> exampleSelect = new ComboBox<>();
|
||||
|
||||
public ChartPage() {
|
||||
super();
|
||||
setUserContent(new VBox(
|
||||
new SampleBlock("Playground", createPlayground())
|
||||
));
|
||||
}
|
||||
|
||||
private VBox createPlayground() {
|
||||
exampleSelect.setMaxWidth(Double.MAX_VALUE);
|
||||
exampleSelect.getItems().setAll(Example.values());
|
||||
exampleSelect.getSelectionModel().selectedItemProperty().addListener((obs, old, val) -> {
|
||||
if (val == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
Chart newChart = createChart(val);
|
||||
|
||||
// copy existing properties to the new chart
|
||||
findDisplayedChart().ifPresent(chart -> newChart.setDisable(chart.isDisable()));
|
||||
|
||||
chartWrapper.setCenter(newChart);
|
||||
});
|
||||
exampleSelect.setConverter(new StringConverter<>() {
|
||||
@Override
|
||||
public String toString(Example example) {
|
||||
return example == null ? "" : example.getName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Example fromString(String s) {
|
||||
return Example.find(s);
|
||||
}
|
||||
});
|
||||
|
||||
var disableToggle = new ToggleSwitch("Disable");
|
||||
disableToggle.selectedProperty().addListener((obs, old, val) -> findDisplayedChart().ifPresent(ch -> {
|
||||
if (val != null) {
|
||||
ch.setDisable(val);
|
||||
}
|
||||
}));
|
||||
|
||||
var controls = new HBox(disableToggle);
|
||||
controls.setAlignment(Pos.CENTER);
|
||||
|
||||
VBox playground = new VBox(SampleBlock.BLOCK_VGAP);
|
||||
playground.getChildren().setAll(
|
||||
new HBox(new Label("Select an example:"), new Spacer(), disableToggle),
|
||||
exampleSelect,
|
||||
chartWrapper
|
||||
addPlainText("""
|
||||
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."""
|
||||
);
|
||||
|
||||
return playground;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onRendered() {
|
||||
super.onRendered();
|
||||
exampleSelect.getSelectionModel().selectFirst();
|
||||
}
|
||||
|
||||
private Optional<Chart> findDisplayedChart() {
|
||||
return chartWrapper.getChildren().size() > 0
|
||||
? Optional.of((Chart) chartWrapper.getChildren().get(0))
|
||||
: Optional.empty();
|
||||
}
|
||||
|
||||
private Chart createChart(Example example) {
|
||||
switch (example) {
|
||||
case AREA_CHART -> {
|
||||
return areaChart(false);
|
||||
}
|
||||
case BAR_CHART -> {
|
||||
return barChart(false);
|
||||
}
|
||||
case BUBBLE_CHART -> {
|
||||
return bubbleChart();
|
||||
}
|
||||
case LINE_CHART -> {
|
||||
return lineChart();
|
||||
}
|
||||
case PIE_CHART -> {
|
||||
return pieChart();
|
||||
}
|
||||
case SCATTER_CHART -> {
|
||||
return scatterChart();
|
||||
}
|
||||
case STACKED_AREA_CHART -> {
|
||||
return areaChart(true);
|
||||
}
|
||||
case STACKED_BAR_CHART -> {
|
||||
return barChart(true);
|
||||
}
|
||||
default -> throw new IllegalArgumentException("Unexpected enum value: " + example);
|
||||
}
|
||||
addSection("Area Chart", areaChart());
|
||||
addSection("Stacked Area Chart", stackedAreaChart());
|
||||
addSection("Bar Chart", barChart());
|
||||
addSection("Stacked Bar Chart", stackedBarChart());
|
||||
addSection("Bubble Chart", bubbleChart());
|
||||
addSection("Line Chart", lineChart());
|
||||
addSection("Pie Chart", pieChart());
|
||||
addSection("Scatter Chart", scatterChart());
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private Chart areaChart(boolean stacked) {
|
||||
private ExampleBox areaChart() {
|
||||
//snippet_1:start
|
||||
var x = new NumberAxis(1, 31, 1);
|
||||
x.setLabel("Day");
|
||||
|
||||
@ -163,15 +72,56 @@ public class ChartPage extends AbstractPage {
|
||||
new XYChart.Data<>(i, FAKER.random().nextInt(15, 30))
|
||||
));
|
||||
|
||||
var chart = stacked ? new StackedAreaChart<>(x, y) : new AreaChart<>(x, y);
|
||||
var chart = new AreaChart<>(x, y);
|
||||
chart.setTitle("Temperature Monitoring");
|
||||
chart.setMinHeight(300);
|
||||
chart.getData().addAll(april, may);
|
||||
//snippet_1:end
|
||||
|
||||
return chart;
|
||||
var description = BBCodeParser.createFormattedText("""
|
||||
Plots the area between the line that connects the data points and the axis. \
|
||||
Good for comparing cumulated totals over time.""");
|
||||
|
||||
return new ExampleBox(chart, new Snippet(getClass(), 1), description);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private Chart barChart(boolean stacked) {
|
||||
private ExampleBox stackedAreaChart() {
|
||||
//snippet_2:start
|
||||
var x = new NumberAxis(1, 31, 1);
|
||||
x.setLabel("Day");
|
||||
|
||||
var y = new NumberAxis();
|
||||
y.setLabel("Temperature");
|
||||
|
||||
var april = new XYChart.Series<Number, Number>();
|
||||
april.setName("April");
|
||||
IntStream.range(1, 30).forEach(i -> april.getData().add(
|
||||
new XYChart.Data<>(i, FAKER.random().nextInt(15, 30))
|
||||
));
|
||||
|
||||
var may = new XYChart.Series<Number, Number>();
|
||||
may.setName("May");
|
||||
IntStream.range(1, 30).forEach(i -> may.getData().add(
|
||||
new XYChart.Data<>(i, FAKER.random().nextInt(15, 30))
|
||||
));
|
||||
|
||||
var chart = new StackedAreaChart<>(x, y);
|
||||
chart.setTitle("Temperature Monitoring");
|
||||
chart.setMinHeight(300);
|
||||
chart.getData().addAll(april, may);
|
||||
//snippet_2:end
|
||||
|
||||
var description = BBCodeParser.createFormattedText("""
|
||||
A variation of [i]AreaChart[/i] that displays trends of the contribution of each value."""
|
||||
);
|
||||
|
||||
return new ExampleBox(chart, new Snippet(getClass(), 2), description);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private ExampleBox barChart() {
|
||||
//snippet_3:start
|
||||
final var rnd = FAKER.random();
|
||||
final var countries = IntStream.range(0, 5).boxed()
|
||||
.map(i -> FAKER.country().countryCode3().toUpperCase())
|
||||
@ -201,15 +151,69 @@ public class ChartPage extends AbstractPage {
|
||||
new XYChart.Data<>(countries.get(i), rnd.nextInt(10, 80))
|
||||
));
|
||||
|
||||
var chart = stacked ? new StackedBarChart<>(x, y) : new BarChart<>(x, y);
|
||||
var chart = new BarChart<>(x, y);
|
||||
chart.setTitle("Country Summary");
|
||||
chart.setMinHeight(300);
|
||||
chart.getData().addAll(january, february, march);
|
||||
//snippet_3:end
|
||||
|
||||
return chart;
|
||||
var description = BBCodeParser.createFormattedText("""
|
||||
Plots rectangular bars with heights indicating data values they represent, \
|
||||
and corresponding to the categories they belongs to. Used for displaying \
|
||||
discontinuous or discrete data."""
|
||||
);
|
||||
|
||||
return new ExampleBox(chart, new Snippet(getClass(), 3), description);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private Chart bubbleChart() {
|
||||
private ExampleBox stackedBarChart() {
|
||||
//snippet_4:start
|
||||
final var rnd = FAKER.random();
|
||||
final var countries = IntStream.range(0, 5).boxed()
|
||||
.map(i -> FAKER.country().countryCode3().toUpperCase())
|
||||
.toList();
|
||||
|
||||
var x = new CategoryAxis();
|
||||
x.setLabel("Country");
|
||||
|
||||
var y = new NumberAxis(0, 80, 10);
|
||||
y.setLabel("Value");
|
||||
|
||||
var january = new XYChart.Series<String, Number>();
|
||||
january.setName("January");
|
||||
IntStream.range(0, countries.size()).forEach(i -> january.getData().add(
|
||||
new XYChart.Data<>(countries.get(i), rnd.nextInt(10, 80))
|
||||
));
|
||||
|
||||
var february = new XYChart.Series<String, Number>();
|
||||
february.setName("February");
|
||||
IntStream.range(0, countries.size()).forEach(i -> february.getData().add(
|
||||
new XYChart.Data<>(countries.get(i), rnd.nextInt(10, 80))
|
||||
));
|
||||
|
||||
var march = new XYChart.Series<String, Number>();
|
||||
march.setName("March");
|
||||
IntStream.range(0, countries.size()).forEach(i -> march.getData().add(
|
||||
new XYChart.Data<>(countries.get(i), rnd.nextInt(10, 80))
|
||||
));
|
||||
|
||||
var chart = new StackedBarChart<>(x, y);
|
||||
chart.setTitle("Country Summary");
|
||||
chart.setMinHeight(300);
|
||||
chart.getData().addAll(january, february, march);
|
||||
//snippet_4:end
|
||||
|
||||
var description = BBCodeParser.createFormattedText("""
|
||||
A variation of [i]BarChart[/i] that plots bars indicating data values for a category."""
|
||||
);
|
||||
|
||||
return new ExampleBox(chart, new Snippet(getClass(), 4), description);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private ExampleBox bubbleChart() {
|
||||
//snippet_5:start
|
||||
final var rnd = FAKER.random();
|
||||
|
||||
var x = new NumberAxis(1, 53, 4);
|
||||
@ -221,24 +225,40 @@ public class ChartPage extends AbstractPage {
|
||||
var series1 = new XYChart.Series<Number, Number>();
|
||||
series1.setName(FAKER.commerce().productName());
|
||||
IntStream.range(1, 10).forEach(i -> series1.getData().add(
|
||||
new XYChart.Data<>(rnd.nextInt(1, 53), rnd.nextInt(10, 80), rnd.nextDouble(1, 10))
|
||||
new XYChart.Data<>(
|
||||
rnd.nextInt(1, 53),
|
||||
rnd.nextInt(10, 80),
|
||||
rnd.nextDouble(1, 10)
|
||||
)
|
||||
));
|
||||
|
||||
var series2 = new XYChart.Series<Number, Number>();
|
||||
series2.setName(FAKER.commerce().productName());
|
||||
IntStream.range(1, 10).forEach(i -> series2.getData().add(
|
||||
new XYChart.Data<>(rnd.nextInt(1, 53), rnd.nextInt(10, 80), rnd.nextDouble(1, 10))
|
||||
new XYChart.Data<>(
|
||||
rnd.nextInt(1, 53),
|
||||
rnd.nextInt(10, 80),
|
||||
rnd.nextDouble(1, 10)
|
||||
)
|
||||
));
|
||||
|
||||
var chart = new BubbleChart<>(x, y);
|
||||
chart.setTitle("Budget Monitoring");
|
||||
chart.setMinHeight(300);
|
||||
chart.getData().addAll(series1, series2);
|
||||
//snippet_5:end
|
||||
|
||||
return chart;
|
||||
var description = BBCodeParser.createFormattedText("""
|
||||
Plots bubbles for data points in a series. Each plotted entity depicts \
|
||||
three parameters in a 2D chart and hence a unique chart type."""
|
||||
);
|
||||
|
||||
return new ExampleBox(chart, new Snippet(getClass(), 5), description);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private Chart lineChart() {
|
||||
private ExampleBox lineChart() {
|
||||
//snippet_6:start
|
||||
final var rnd = FAKER.random();
|
||||
|
||||
var x = new CategoryAxis();
|
||||
@ -250,23 +270,36 @@ public class ChartPage extends AbstractPage {
|
||||
var series1 = new XYChart.Series<String, Number>();
|
||||
series1.setName(FAKER.stock().nsdqSymbol());
|
||||
IntStream.range(1, 12).forEach(i -> series1.getData().add(
|
||||
new XYChart.Data<>(Month.of(i).getDisplayName(TextStyle.SHORT, Locale.getDefault()), rnd.nextInt(10, 80))
|
||||
new XYChart.Data<>(
|
||||
Month.of(i).getDisplayName(TextStyle.SHORT, Locale.getDefault()),
|
||||
rnd.nextInt(10, 80)
|
||||
)
|
||||
));
|
||||
|
||||
var series2 = new XYChart.Series<String, Number>();
|
||||
series2.setName(FAKER.stock().nsdqSymbol());
|
||||
IntStream.range(1, 12).forEach(i -> series2.getData().add(
|
||||
new XYChart.Data<>(Month.of(i).getDisplayName(TextStyle.SHORT, Locale.getDefault()), rnd.nextInt(10, 80))
|
||||
new XYChart.Data<>(
|
||||
Month.of(i).getDisplayName(TextStyle.SHORT, Locale.getDefault()),
|
||||
rnd.nextInt(10, 80)
|
||||
)
|
||||
));
|
||||
|
||||
var chart = new LineChart<>(x, y);
|
||||
chart.setTitle("Stock Monitoring");
|
||||
chart.setMinHeight(300);
|
||||
chart.getData().addAll(series1, series2);
|
||||
//snippet_6:end
|
||||
|
||||
return chart;
|
||||
var description = BBCodeParser.createFormattedText("""
|
||||
Plots line between the data points in a series. Used usually to view data trends over time."""
|
||||
);
|
||||
|
||||
return new ExampleBox(chart, new Snippet(getClass(), 6), description);
|
||||
}
|
||||
|
||||
private Chart pieChart() {
|
||||
private ExampleBox pieChart() {
|
||||
//snippet_7:start
|
||||
final var rnd = FAKER.random();
|
||||
|
||||
ObservableList<PieChart.Data> data = FXCollections.observableArrayList(
|
||||
@ -278,13 +311,21 @@ public class ChartPage extends AbstractPage {
|
||||
);
|
||||
|
||||
var chart = new PieChart(data);
|
||||
chart.setMinHeight(300);
|
||||
chart.setTitle("Imported Fruits");
|
||||
//snippet_7:end
|
||||
|
||||
return chart;
|
||||
var description = BBCodeParser.createFormattedText("""
|
||||
Plots circular chart divided into segments with each segment representing a value \
|
||||
as a proportion of the total. It looks like a Pie and hence the name."""
|
||||
);
|
||||
|
||||
return new ExampleBox(chart, new Snippet(getClass(), 7), description);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private Chart scatterChart() {
|
||||
private ExampleBox scatterChart() {
|
||||
//snippet_8:start
|
||||
final var rnd = FAKER.random();
|
||||
|
||||
var x = new NumberAxis(0, 10, 1);
|
||||
@ -296,47 +337,32 @@ public class ChartPage extends AbstractPage {
|
||||
var series1 = new XYChart.Series<Number, Number>();
|
||||
series1.setName("Equities");
|
||||
IntStream.range(1, 10).forEach(i -> series1.getData().add(
|
||||
new XYChart.Data<>(rnd.nextDouble(0, 10), rnd.nextDouble(-100, 500))
|
||||
new XYChart.Data<>(
|
||||
rnd.nextDouble(0, 10),
|
||||
rnd.nextDouble(-100, 500)
|
||||
)
|
||||
));
|
||||
|
||||
var series2 = new XYChart.Series<Number, Number>();
|
||||
series2.setName("Mutual funds");
|
||||
IntStream.range(1, 10).forEach(i -> series2.getData().add(
|
||||
new XYChart.Data<>(rnd.nextDouble(0, 10), rnd.nextDouble(-100, 500))
|
||||
new XYChart.Data<>(
|
||||
rnd.nextDouble(0, 10),
|
||||
rnd.nextDouble(-100, 500)
|
||||
)
|
||||
));
|
||||
|
||||
var chart = new ScatterChart<>(x, y);
|
||||
chart.setTitle("Investment Overview");
|
||||
chart.setMinHeight(300);
|
||||
chart.getData().addAll(series1, series2);
|
||||
//snippet_8:end
|
||||
|
||||
return chart;
|
||||
}
|
||||
var description = BBCodeParser.createFormattedText("""
|
||||
Plots symbols for the data points in a series. This type of chart is useful in viewing \
|
||||
distribution of data and its correlation, if there is any clustering."""
|
||||
);
|
||||
|
||||
private enum Example {
|
||||
AREA_CHART("Area Chart"),
|
||||
BAR_CHART("Bar Chart"),
|
||||
BUBBLE_CHART("Bubble Chart"),
|
||||
LINE_CHART("Line Chart"),
|
||||
PIE_CHART("Pie Chart"),
|
||||
SCATTER_CHART("Scatter Chart"),
|
||||
STACKED_AREA_CHART("Stacked Area Chart"),
|
||||
STACKED_BAR_CHART("Stacked Bar Chart");
|
||||
|
||||
private final String name;
|
||||
|
||||
Example(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public static Example find(String name) {
|
||||
return Arrays.stream(Example.values())
|
||||
.filter(example -> Objects.equals(example.getName(), name))
|
||||
.findFirst()
|
||||
.orElse(null);
|
||||
}
|
||||
return new ExampleBox(chart, new Snippet(getClass(), 8), description);
|
||||
}
|
||||
}
|
||||
|
@ -2,14 +2,15 @@
|
||||
|
||||
package atlantafx.sampler.page.components;
|
||||
|
||||
import atlantafx.sampler.page.AbstractPage;
|
||||
import atlantafx.sampler.page.SampleBlock;
|
||||
import atlantafx.base.util.BBCodeParser;
|
||||
import atlantafx.sampler.page.ExampleBox;
|
||||
import atlantafx.sampler.page.OutlinePage;
|
||||
import atlantafx.sampler.page.Snippet;
|
||||
import javafx.scene.control.CheckBox;
|
||||
import javafx.scene.layout.FlowPane;
|
||||
import javafx.scene.layout.HBox;
|
||||
import javafx.scene.layout.StackPane;
|
||||
|
||||
public class CheckBoxPage extends AbstractPage {
|
||||
public class CheckBoxPage extends OutlinePage {
|
||||
|
||||
public static final String NAME = "CheckBox";
|
||||
|
||||
@ -23,46 +24,66 @@ public class CheckBoxPage extends AbstractPage {
|
||||
|
||||
public CheckBoxPage() {
|
||||
super();
|
||||
createView();
|
||||
}
|
||||
|
||||
private void createView() {
|
||||
setUserContent(new FlowPane(
|
||||
PAGE_HGAP, PAGE_VGAP,
|
||||
basicSample(),
|
||||
indeterminateSample(),
|
||||
disabledSample()
|
||||
));
|
||||
}
|
||||
|
||||
private SampleBlock basicSample() {
|
||||
basicCheck = new CheckBox("_Check Me");
|
||||
basicCheck.setMnemonicParsing(true);
|
||||
return new SampleBlock("Basic", basicCheck);
|
||||
}
|
||||
|
||||
private SampleBlock indeterminateSample() {
|
||||
indeterminateCheck = new CheckBox("C_heck Me");
|
||||
indeterminateCheck.setAllowIndeterminate(true);
|
||||
indeterminateCheck.setIndeterminate(true);
|
||||
indeterminateCheck.setMnemonicParsing(true);
|
||||
return new SampleBlock("Indeterminate", indeterminateCheck);
|
||||
}
|
||||
|
||||
private SampleBlock disabledSample() {
|
||||
var basicCheck = new CheckBox("Check Me");
|
||||
basicCheck.setSelected(true);
|
||||
basicCheck.setDisable(true);
|
||||
|
||||
var indeterminateCheck = new CheckBox("Check Me");
|
||||
indeterminateCheck.setAllowIndeterminate(true);
|
||||
indeterminateCheck.setIndeterminate(true);
|
||||
indeterminateCheck.setDisable(true);
|
||||
|
||||
return new SampleBlock(
|
||||
"Disabled",
|
||||
new HBox(SampleBlock.BLOCK_HGAP, basicCheck, indeterminateCheck)
|
||||
addFormattedText("""
|
||||
A tri-state selection control is typically skinned as a box \
|
||||
with a checkmark or tick mark when checked."""
|
||||
);
|
||||
|
||||
addSection("Usage", usageExample());
|
||||
addSection("Indeterminate", indeterminateExample());
|
||||
}
|
||||
|
||||
private ExampleBox usageExample() {
|
||||
//snippet_1:start
|
||||
var cb1 = new CheckBox("_Unchecked");
|
||||
cb1.setMnemonicParsing(true);
|
||||
|
||||
var cb2 = new CheckBox("Checked");
|
||||
cb2.setSelected(true);
|
||||
//snippet_1:end
|
||||
|
||||
var box = new HBox(40, cb1, cb2);
|
||||
basicCheck = cb1;
|
||||
|
||||
var description = BBCodeParser.createFormattedText("""
|
||||
A [i]CheckBox[/i] can be in one of three states:
|
||||
|
||||
[ul]
|
||||
[li][b]checked[/b]: indeterminate == false, checked == true[/li]
|
||||
[li][b]unchecked[/b]: indeterminate == false, checked == false[/li]
|
||||
[li][b]undefined[/b]: indeterminate == true[/li][/ul]"""
|
||||
);
|
||||
|
||||
return new ExampleBox(box, new Snippet(getClass(), 1), description);
|
||||
}
|
||||
|
||||
private ExampleBox indeterminateExample() {
|
||||
//snippet_2:start
|
||||
var cb1 = new CheckBox("I_ndeterminate");
|
||||
cb1.setAllowIndeterminate(true);
|
||||
cb1.setIndeterminate(true);
|
||||
cb1.setMnemonicParsing(true);
|
||||
|
||||
var cb2 = new CheckBox("Indeterminate + Checked");
|
||||
cb2.setAllowIndeterminate(true);
|
||||
cb2.setIndeterminate(false);
|
||||
cb2.setSelected(true);
|
||||
|
||||
var cb3 = new CheckBox("Indeterminate + Unchecked");
|
||||
cb3.setAllowIndeterminate(true);
|
||||
//snippet_2:end
|
||||
|
||||
var box = new HBox(40, cb1, cb2, cb3);
|
||||
indeterminateCheck = cb1;
|
||||
|
||||
var description = BBCodeParser.createFormattedText("""
|
||||
The [code]allowIndeterminate[/code] variable, if true, allows the user to \
|
||||
cycle through the undefined state. A [i]CheckBox[/i] is undefined if \
|
||||
indeterminate is true, regardless of the state of selected."""
|
||||
);
|
||||
|
||||
return new ExampleBox(box, new Snippet(getClass(), 2), description);
|
||||
}
|
||||
|
||||
// visually compare normal and indeterminate checkboxes size
|
||||
|
@ -0,0 +1,13 @@
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
|
||||
package atlantafx.sampler.page.components;
|
||||
|
||||
public class ChoiceBoxPage extends ComboBoxPage {
|
||||
|
||||
public static final String NAME = "ChoiceBox";
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return NAME;
|
||||
}
|
||||
}
|
@ -2,22 +2,16 @@
|
||||
|
||||
package atlantafx.sampler.page.components;
|
||||
|
||||
import static atlantafx.sampler.page.SampleBlock.BLOCK_HGAP;
|
||||
import static atlantafx.sampler.page.SampleBlock.BLOCK_VGAP;
|
||||
|
||||
import atlantafx.base.controls.ToggleSwitch;
|
||||
import atlantafx.sampler.page.AbstractPage;
|
||||
import atlantafx.sampler.page.SampleBlock;
|
||||
import javafx.geometry.HPos;
|
||||
import javafx.scene.control.ChoiceBox;
|
||||
import atlantafx.base.util.BBCodeParser;
|
||||
import atlantafx.sampler.page.ExampleBox;
|
||||
import atlantafx.sampler.page.OutlinePage;
|
||||
import atlantafx.sampler.page.Snippet;
|
||||
import javafx.scene.control.ColorPicker;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.layout.ColumnConstraints;
|
||||
import javafx.scene.layout.GridPane;
|
||||
import javafx.scene.layout.VBox;
|
||||
import javafx.scene.layout.HBox;
|
||||
import javafx.scene.paint.Color;
|
||||
|
||||
public class ColorPickerPage extends AbstractPage {
|
||||
public class ColorPickerPage extends OutlinePage {
|
||||
|
||||
public static final String NAME = "ColorPicker";
|
||||
|
||||
@ -28,79 +22,68 @@ public class ColorPickerPage extends AbstractPage {
|
||||
|
||||
public ColorPickerPage() {
|
||||
super();
|
||||
setUserContent(new VBox(
|
||||
new SampleBlock("Playground", createPlayground())
|
||||
));
|
||||
|
||||
addFormattedText("""
|
||||
ColorPicker control allows the user to select a color from either \
|
||||
a standard palette of colors with a simple one click selection or \
|
||||
define their own custom color."""
|
||||
);
|
||||
addSection("Usage", usageExample());
|
||||
addSection("Style", styleExample());
|
||||
}
|
||||
|
||||
private GridPane createPlayground() {
|
||||
var colorPicker = new ColorPicker();
|
||||
colorPicker.setValue(Color.DEEPSKYBLUE);
|
||||
private ExampleBox usageExample() {
|
||||
//snippet_1:start
|
||||
var cp = new ColorPicker();
|
||||
cp.setValue(Color.RED);
|
||||
//snippet_1:end
|
||||
|
||||
var labelToggle = new ToggleSwitch();
|
||||
labelToggle.setSelected(true);
|
||||
labelToggle.selectedProperty().addListener((obs, old, val) -> {
|
||||
colorPicker.setStyle("-fx-color-label-visible: false;");
|
||||
if (val) {
|
||||
colorPicker.setStyle("-fx-color-label-visible: true;");
|
||||
var box = new HBox(cp);
|
||||
box.setMinHeight(50);
|
||||
|
||||
var description = BBCodeParser.createFormattedText("""
|
||||
The [i]ColorPicker[/i] control provides a color palette with a predefined \
|
||||
set of colors. If the user does not want to choose from the predefined set, \
|
||||
they can create a custom color by interacting with a custom color dialog. \
|
||||
This dialog provides RGB, HSB and Web modes of interaction, to create new \
|
||||
colors. It also lets the opacity of the color to be modified."""
|
||||
);
|
||||
|
||||
return new ExampleBox(box, new Snippet(getClass(), 1), description);
|
||||
}
|
||||
});
|
||||
|
||||
var disableToggle = new ToggleSwitch();
|
||||
colorPicker.disableProperty().bind(disableToggle.selectedProperty());
|
||||
private ExampleBox styleExample() {
|
||||
//snippet_2:start
|
||||
var cp1 = new ColorPicker();
|
||||
cp1.setValue(Color.RED);
|
||||
cp1.getStyleClass().add(ColorPicker.STYLE_CLASS_BUTTON);
|
||||
|
||||
var cp2 = new ColorPicker();
|
||||
cp2.setValue(Color.GREEN);
|
||||
cp2.getStyleClass().add(ColorPicker.STYLE_CLASS_SPLIT_BUTTON);
|
||||
|
||||
var cp3 = new ColorPicker();
|
||||
cp3.setValue(Color.BLUE);
|
||||
cp3.setStyle("-fx-color-label-visible: false");
|
||||
//snippet_2:end
|
||||
|
||||
var grid = new GridPane();
|
||||
grid.setHgap(BLOCK_HGAP);
|
||||
grid.setVgap(BLOCK_VGAP);
|
||||
grid.add(colorPicker, 0, 0, 1, GridPane.REMAINING);
|
||||
grid.add(createLabel("Show label"), 1, 0);
|
||||
grid.add(labelToggle, 2, 0);
|
||||
grid.add(createLabel("Picker style"), 1, 1);
|
||||
grid.add(createPickerStyleChoice(colorPicker), 2, 1);
|
||||
grid.add(createLabel("Disable"), 1, 2);
|
||||
grid.add(disableToggle, 2, 2);
|
||||
grid.setHgap(30);
|
||||
grid.setVgap(10);
|
||||
grid.addRow(0,
|
||||
captionLabel("STYLE_CLASS_BUTTON"),
|
||||
captionLabel("STYLE_CLASS_SPLIT_BUTTON"),
|
||||
captionLabel("-fx-color-label-visible")
|
||||
);
|
||||
grid.addRow(1, cp1, cp2, cp3);
|
||||
|
||||
grid.getColumnConstraints().setAll(
|
||||
new ColumnConstraints(200),
|
||||
new ColumnConstraints(),
|
||||
new ColumnConstraints()
|
||||
var description = BBCodeParser.createFormattedText("""
|
||||
The [i]ColorPicker[/i] control can be styled in two ways: a simple \
|
||||
[i]Button[/i] mode or the default [i]MenuButton[/i] mode. While there \
|
||||
is also a [i]SplitMenuButton[/i] mode available, it is not supported by \
|
||||
AtlantaFX and looks the same as the default option."""
|
||||
);
|
||||
|
||||
return grid;
|
||||
}
|
||||
|
||||
private Label createLabel(String text) {
|
||||
var label = new Label(text);
|
||||
GridPane.setHalignment(label, HPos.RIGHT);
|
||||
return label;
|
||||
}
|
||||
|
||||
private ChoiceBox<String> createPickerStyleChoice(ColorPicker colorPicker) {
|
||||
var optDefault = "Default";
|
||||
var optButton = "Button";
|
||||
var optSplitButton = "Split Button";
|
||||
|
||||
var choice = new ChoiceBox<String>();
|
||||
choice.getItems().setAll(optDefault, optButton, optSplitButton);
|
||||
choice.getSelectionModel().selectedItemProperty().addListener((obs, old, val) -> {
|
||||
if (val == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
colorPicker.getStyleClass().removeAll(
|
||||
ColorPicker.STYLE_CLASS_BUTTON,
|
||||
ColorPicker.STYLE_CLASS_SPLIT_BUTTON
|
||||
);
|
||||
|
||||
if (optButton.equals(val)) {
|
||||
colorPicker.getStyleClass().add(ColorPicker.STYLE_CLASS_BUTTON);
|
||||
}
|
||||
if (optSplitButton.equals(val)) {
|
||||
colorPicker.getStyleClass().add(ColorPicker.STYLE_CLASS_SPLIT_BUTTON);
|
||||
}
|
||||
});
|
||||
choice.getSelectionModel().select(optDefault);
|
||||
|
||||
return choice;
|
||||
return new ExampleBox(grid, new Snippet(getClass(), 2), description);
|
||||
}
|
||||
}
|
||||
|
@ -2,32 +2,25 @@
|
||||
|
||||
package atlantafx.sampler.page.components;
|
||||
|
||||
import static atlantafx.base.theme.Styles.STATE_DANGER;
|
||||
import static atlantafx.base.theme.Styles.STATE_SUCCESS;
|
||||
import static atlantafx.sampler.page.SampleBlock.BLOCK_HGAP;
|
||||
import static atlantafx.sampler.page.SampleBlock.BLOCK_VGAP;
|
||||
import static atlantafx.sampler.util.Containers.H_GROW_NEVER;
|
||||
import static javafx.collections.FXCollections.observableArrayList;
|
||||
|
||||
import atlantafx.base.theme.Styles;
|
||||
import atlantafx.base.theme.Tweaks;
|
||||
import atlantafx.sampler.page.AbstractPage;
|
||||
import atlantafx.sampler.page.SampleBlock;
|
||||
import java.util.function.Consumer;
|
||||
import atlantafx.base.util.BBCodeParser;
|
||||
import atlantafx.sampler.page.ExampleBox;
|
||||
import atlantafx.sampler.page.OutlinePage;
|
||||
import atlantafx.sampler.page.Snippet;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.IntStream;
|
||||
import javafx.collections.FXCollections;
|
||||
import javafx.collections.ObservableList;
|
||||
import javafx.geometry.HPos;
|
||||
import javafx.scene.control.ChoiceBox;
|
||||
import javafx.scene.control.ComboBox;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.control.ListCell;
|
||||
import javafx.scene.layout.GridPane;
|
||||
import javafx.scene.layout.VBox;
|
||||
import javafx.scene.layout.HBox;
|
||||
import org.kordamp.ikonli.Ikon;
|
||||
import org.kordamp.ikonli.javafx.FontIcon;
|
||||
|
||||
public class ComboBoxPage extends AbstractPage {
|
||||
public class ComboBoxPage extends OutlinePage {
|
||||
|
||||
public static final String NAME = "ComboBox";
|
||||
private static final int PREF_WIDTH = 200;
|
||||
@ -39,177 +32,258 @@ public class ComboBoxPage extends AbstractPage {
|
||||
|
||||
public ComboBoxPage() {
|
||||
super();
|
||||
setUserContent(new VBox(
|
||||
new SampleBlock("Examples", createPlayground())
|
||||
|
||||
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 \
|
||||
controls for that purpose, namely the [i]ComboBox[/i] and [i]ChoiceBox[/i]."""
|
||||
);
|
||||
addSection("Usage", usageExample());
|
||||
addSection("Editable", editableExample());
|
||||
addSection("Placeholder", placeholderExample());
|
||||
addSection("Custom Items", customItemsExample());
|
||||
addSection("Color", colorExample());
|
||||
addSection("Overflow", overflowExample());
|
||||
addSection("Alternative Icon", altIconExample());
|
||||
}
|
||||
|
||||
private ExampleBox usageExample() {
|
||||
//snippet_1:start
|
||||
var cmb1 = new ComboBox<String>();
|
||||
cmb1.setPrefWidth(PREF_WIDTH);
|
||||
cmb1.setItems(FXCollections.observableArrayList(
|
||||
generate(() -> FAKER.hipster().word(), 5)
|
||||
));
|
||||
}
|
||||
cmb1.getSelectionModel().selectFirst();
|
||||
|
||||
var cmb2 = new ComboBox<String>();
|
||||
cmb2.setPrefWidth(PREF_WIDTH);
|
||||
|
||||
var chb1 = new ChoiceBox<String>();
|
||||
chb1.setPrefWidth(PREF_WIDTH);
|
||||
chb1.setItems(FXCollections.observableArrayList(
|
||||
generate(() -> FAKER.hipster().word(), 5)
|
||||
));
|
||||
chb1.getSelectionModel().selectFirst();
|
||||
|
||||
var chb2 = new ChoiceBox<String>();
|
||||
chb2.setPrefWidth(PREF_WIDTH);
|
||||
//snippet_1:end
|
||||
|
||||
private GridPane createPlayground() {
|
||||
var grid = new GridPane();
|
||||
grid.setHgap(BLOCK_HGAP);
|
||||
grid.setVgap(BLOCK_VGAP);
|
||||
grid.getColumnConstraints().setAll(H_GROW_NEVER, H_GROW_NEVER, H_GROW_NEVER);
|
||||
grid.setHgap(30);
|
||||
grid.setVgap(10);
|
||||
grid.addRow(0, captionLabel("ComboBox"), cmb1, cmb2);
|
||||
grid.addRow(1, captionLabel("ChoiceBox"), chb1, chb2);
|
||||
|
||||
var comboLabel = new Label("C_omboBox");
|
||||
comboLabel.setMnemonicParsing(true);
|
||||
grid.add(comboLabel, 0, 0);
|
||||
var description = BBCodeParser.createFormattedText("""
|
||||
The [i]ComboBox[/i] is an implementation of the [i]ComboBoxBase[/i] abstract class, \
|
||||
whereas the [i]CheckBox[/i] is more similar to the [i]MenuButton[/i]. Both controls \
|
||||
provide a selection model to manage the selected state."""
|
||||
);
|
||||
|
||||
var choiceLabel = new Label("C_hoiceBox");
|
||||
choiceLabel.setMnemonicParsing(true);
|
||||
grid.add(choiceLabel, 2, 0);
|
||||
|
||||
// default
|
||||
grid.add(createComboBox(), 0, 1);
|
||||
grid.add(createLabel("empty"), 1, 1);
|
||||
grid.add(createChoiceBox(), 2, 1);
|
||||
|
||||
// editable
|
||||
grid.add(createComboBoxWith(c -> {
|
||||
c.setItems(createItems(5));
|
||||
c.setEditable(true);
|
||||
}), 0, 2);
|
||||
grid.add(createLabel("editable"), 1, 2);
|
||||
|
||||
// placeholder
|
||||
grid.add(createComboBoxWith(c -> c.setPlaceholder(new Label("Loading..."))), 0, 3);
|
||||
grid.add(createLabel("placeholder"), 1, 3);
|
||||
|
||||
// with icons
|
||||
var badges = IntStream.range(0, 5).boxed()
|
||||
.map(i -> new Badge(FAKER.hipster().word(), randomIcon()))
|
||||
.collect(Collectors.toCollection(FXCollections::observableArrayList));
|
||||
var badgeCombo = new ComboBox<>(badges);
|
||||
badgeCombo.setPrefWidth(PREF_WIDTH);
|
||||
badgeCombo.setButtonCell(new BadgeCell());
|
||||
badgeCombo.setCellFactory(lv -> new BadgeCell());
|
||||
badgeCombo.getSelectionModel().selectFirst();
|
||||
grid.add(badgeCombo, 0, 4);
|
||||
grid.add(createLabel("graphic"), 1, 4);
|
||||
|
||||
// success
|
||||
grid.add(createComboBoxWith(c -> {
|
||||
c.setItems(createItems(5));
|
||||
c.pseudoClassStateChanged(STATE_SUCCESS, true);
|
||||
c.getSelectionModel().selectFirst();
|
||||
}), 0, 5);
|
||||
grid.add(createLabel("success"), 1, 5);
|
||||
grid.add(createChoiceBoxWith(c -> {
|
||||
c.setItems(createItems(5));
|
||||
c.pseudoClassStateChanged(STATE_SUCCESS, true);
|
||||
c.getSelectionModel().selectFirst();
|
||||
}), 2, 5);
|
||||
|
||||
// negative
|
||||
grid.add(createComboBoxWith(c -> {
|
||||
c.setItems(createItems(5));
|
||||
c.pseudoClassStateChanged(STATE_DANGER, true);
|
||||
c.getSelectionModel().selectFirst();
|
||||
}), 0, 6);
|
||||
grid.add(createLabel("success"), 1, 6);
|
||||
grid.add(createChoiceBoxWith(c -> {
|
||||
c.setItems(createItems(5));
|
||||
c.pseudoClassStateChanged(STATE_DANGER, true);
|
||||
c.getSelectionModel().selectFirst();
|
||||
}), 2, 6);
|
||||
|
||||
// alt icon
|
||||
grid.add(createComboBoxWith(c -> {
|
||||
c.setItems(createItems(5));
|
||||
c.getStyleClass().add(Tweaks.ALT_ICON);
|
||||
c.getSelectionModel().selectFirst();
|
||||
}), 0, 7);
|
||||
grid.add(createLabel("alt icon"), 1, 7);
|
||||
grid.add(createChoiceBoxWith(c -> {
|
||||
c.setItems(createItems(5));
|
||||
c.getStyleClass().add(Tweaks.ALT_ICON);
|
||||
c.getSelectionModel().selectFirst();
|
||||
}), 2, 7);
|
||||
|
||||
// disabled
|
||||
grid.add(createComboBoxWith(c -> c.setDisable(true)), 0, 8);
|
||||
grid.add(createLabel("disabled"), 1, 8);
|
||||
grid.add(createChoiceBoxWith(c -> c.setDisable(true)), 2, 8);
|
||||
|
||||
// vertical overflow
|
||||
grid.add(createComboBoxWith(c -> {
|
||||
c.setItems(createItems(50));
|
||||
c.getSelectionModel().selectFirst();
|
||||
}), 0, 9);
|
||||
grid.add(createLabel("large list"), 1, 9);
|
||||
grid.add(createChoiceBoxWith(c -> {
|
||||
c.setItems(createItems(50));
|
||||
c.getSelectionModel().selectFirst();
|
||||
}), 2, 9);
|
||||
|
||||
// horizontal overflow
|
||||
grid.add(createComboBoxWith(c -> {
|
||||
c.setItems(observableArrayList(generate(() -> FAKER.chuckNorris().fact(), 5)));
|
||||
c.getSelectionModel().selectFirst();
|
||||
}), 0, 10);
|
||||
grid.add(createLabel("wide text"), 1, 10);
|
||||
grid.add(createChoiceBoxWith(c -> {
|
||||
c.setItems(observableArrayList(generate(() -> FAKER.chuckNorris().fact(), 5)));
|
||||
c.getSelectionModel().selectFirst();
|
||||
}), 2, 10);
|
||||
|
||||
return grid;
|
||||
return new ExampleBox(grid, new Snippet(ComboBoxPage.class, 1), description);
|
||||
}
|
||||
|
||||
private Label createLabel(String text) {
|
||||
var label = new Label(text);
|
||||
GridPane.setHalignment(label, HPos.CENTER);
|
||||
return label;
|
||||
private ExampleBox editableExample() {
|
||||
//snippet_2:start
|
||||
var cmb = new ComboBox<String>();
|
||||
cmb.setPrefWidth(PREF_WIDTH);
|
||||
cmb.setItems(FXCollections.observableArrayList(
|
||||
generate(() -> FAKER.hipster().word(), 5)
|
||||
));
|
||||
cmb.getSelectionModel().selectFirst();
|
||||
cmb.setEditable(true);
|
||||
//snippet_2:end
|
||||
|
||||
var box = new HBox(cmb);
|
||||
var description = BBCodeParser.createFormattedText("""
|
||||
The [i]ComboBox[/i] provides a way for end-users to input values \
|
||||
that are not available as options."""
|
||||
);
|
||||
|
||||
return new ExampleBox(box, new Snippet(ComboBoxPage.class, 2), description);
|
||||
}
|
||||
|
||||
private ComboBox<String> createComboBox() {
|
||||
return createComboBoxWith(null);
|
||||
private ExampleBox placeholderExample() {
|
||||
//snippet_3:start
|
||||
var cmb = new ComboBox<String>();
|
||||
cmb.setPrefWidth(PREF_WIDTH);
|
||||
cmb.setPlaceholder(new Label("Loading..."));
|
||||
//snippet_3:end
|
||||
|
||||
var box = new HBox(cmb);
|
||||
var description = BBCodeParser.createFormattedText("""
|
||||
Placeholder is a node that is shown to the user when the [i]ComboBox[/i] \
|
||||
has no content to display."""
|
||||
);
|
||||
|
||||
return new ExampleBox(box, new Snippet(ComboBoxPage.class, 3), description);
|
||||
}
|
||||
|
||||
private ComboBox<String> createComboBoxWith(Consumer<ComboBox<String>> mutator) {
|
||||
var c = new ComboBox<String>();
|
||||
c.setPrefWidth(PREF_WIDTH);
|
||||
if (mutator != null) {
|
||||
mutator.accept(c);
|
||||
}
|
||||
return c;
|
||||
private ExampleBox customItemsExample() {
|
||||
//snippet_4:start
|
||||
record Badge(String text, Ikon icon) {
|
||||
}
|
||||
|
||||
private ChoiceBox<String> createChoiceBox() {
|
||||
return createChoiceBoxWith(null);
|
||||
}
|
||||
|
||||
private ChoiceBox<String> createChoiceBoxWith(Consumer<ChoiceBox<String>> mutator) {
|
||||
var c = new ChoiceBox<String>();
|
||||
c.setPrefWidth(PREF_WIDTH);
|
||||
if (mutator != null) {
|
||||
mutator.accept(c);
|
||||
}
|
||||
return c;
|
||||
}
|
||||
|
||||
private ObservableList<String> createItems(int count) {
|
||||
return observableArrayList(generate(() -> FAKER.hipster().word(), count));
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
private record Badge(String text, Ikon icon) {
|
||||
}
|
||||
|
||||
private static class BadgeCell extends ListCell<Badge> {
|
||||
class BadgeCell extends ListCell<Badge> {
|
||||
|
||||
@Override
|
||||
protected void updateItem(Badge item, boolean isEmpty) {
|
||||
super.updateItem(item, isEmpty);
|
||||
protected void updateItem(Badge badge, boolean isEmpty) {
|
||||
super.updateItem(badge, isEmpty);
|
||||
|
||||
if (isEmpty) {
|
||||
setGraphic(null);
|
||||
setText(null);
|
||||
} else {
|
||||
setGraphic(new FontIcon(item.icon()));
|
||||
setText(item.text());
|
||||
setGraphic(new FontIcon(badge.icon()));
|
||||
setText(badge.text());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var items = IntStream.range(0, 5).boxed()
|
||||
.map(i -> new Badge(FAKER.hipster().word(), randomIcon()))
|
||||
.collect(Collectors.toCollection(
|
||||
FXCollections::observableArrayList
|
||||
));
|
||||
|
||||
var cmb = new ComboBox<Badge>(items);
|
||||
cmb.setPrefWidth(PREF_WIDTH);
|
||||
cmb.setButtonCell(new BadgeCell());
|
||||
cmb.setCellFactory(c -> new BadgeCell());
|
||||
cmb.getSelectionModel().selectFirst();
|
||||
//snippet_4:end
|
||||
|
||||
var box = new HBox(cmb);
|
||||
var description = BBCodeParser.createFormattedText("""
|
||||
The [i]ComboBox[/i] provides a custom cell factory that allows for \
|
||||
complete customization of how the items are rendered."""
|
||||
);
|
||||
|
||||
return new ExampleBox(box, new Snippet(ComboBoxPage.class, 4), description);
|
||||
}
|
||||
|
||||
private ExampleBox colorExample() {
|
||||
//snippet_5:start
|
||||
var cmb1 = new ComboBox<String>();
|
||||
cmb1.setPrefWidth(PREF_WIDTH);
|
||||
cmb1.setItems(FXCollections.observableArrayList(
|
||||
generate(() -> FAKER.hipster().word(), 5)
|
||||
));
|
||||
cmb1.getSelectionModel().selectFirst();
|
||||
cmb1.pseudoClassStateChanged(Styles.STATE_SUCCESS, true);
|
||||
|
||||
var cmb2 = new ComboBox<String>();
|
||||
cmb2.setPrefWidth(PREF_WIDTH);
|
||||
cmb2.setItems(FXCollections.observableArrayList(
|
||||
generate(() -> FAKER.hipster().word(), 5)
|
||||
));
|
||||
cmb2.getSelectionModel().selectFirst();
|
||||
cmb2.pseudoClassStateChanged(Styles.STATE_DANGER, true);
|
||||
|
||||
var chb1 = new ChoiceBox<String>();
|
||||
chb1.setPrefWidth(PREF_WIDTH);
|
||||
chb1.setItems(FXCollections.observableArrayList(
|
||||
generate(() -> FAKER.hipster().word(), 5)
|
||||
));
|
||||
chb1.getSelectionModel().selectFirst();
|
||||
chb1.pseudoClassStateChanged(Styles.STATE_SUCCESS, true);
|
||||
|
||||
var chb2 = new ChoiceBox<String>();
|
||||
chb2.setPrefWidth(PREF_WIDTH);
|
||||
chb2.setItems(FXCollections.observableArrayList(
|
||||
generate(() -> FAKER.hipster().word(), 5)
|
||||
));
|
||||
chb2.getSelectionModel().selectFirst();
|
||||
chb2.pseudoClassStateChanged(Styles.STATE_DANGER, true);
|
||||
//snippet_5:end
|
||||
|
||||
var grid = new GridPane();
|
||||
grid.setHgap(30);
|
||||
grid.setVgap(10);
|
||||
grid.addRow(0, captionLabel("ComboBox"), cmb1, cmb2);
|
||||
grid.addRow(1, captionLabel("ChoiceBox"), chb1, chb2);
|
||||
|
||||
var description = BBCodeParser.createFormattedText("""
|
||||
You can use [code]Styles.STATE_SUCCESS[/code] or [code]Styles.STATE_DANGER[/code] \
|
||||
pseudo-classes to change the control color. This especially useful to indicate \
|
||||
the validation result."""
|
||||
);
|
||||
|
||||
return new ExampleBox(grid, new Snippet(ComboBoxPage.class, 5), description);
|
||||
}
|
||||
|
||||
private ExampleBox overflowExample() {
|
||||
//snippet_7:start
|
||||
var cmb1 = new ComboBox<String>();
|
||||
cmb1.setPrefWidth(PREF_WIDTH);
|
||||
cmb1.setItems(FXCollections.observableArrayList(
|
||||
generate(() -> FAKER.hipster().word(), 50)
|
||||
));
|
||||
cmb1.getSelectionModel().selectFirst();
|
||||
|
||||
var cmb2 = new ComboBox<String>();
|
||||
cmb2.setPrefWidth(PREF_WIDTH);
|
||||
cmb2.setItems(FXCollections.observableArrayList(
|
||||
generate(() -> FAKER.chuckNorris().fact(), 10)
|
||||
));
|
||||
cmb2.getSelectionModel().selectFirst();
|
||||
|
||||
var chb1 = new ChoiceBox<String>();
|
||||
chb1.setPrefWidth(PREF_WIDTH);
|
||||
chb1.setItems(FXCollections.observableArrayList(
|
||||
generate(() -> FAKER.hipster().word(), 50)
|
||||
));
|
||||
chb1.getSelectionModel().selectFirst();
|
||||
|
||||
var chb2 = new ChoiceBox<String>();
|
||||
chb2.setPrefWidth(PREF_WIDTH);
|
||||
chb2.setItems(FXCollections.observableArrayList(
|
||||
generate(() -> FAKER.chuckNorris().fact(), 10)
|
||||
));
|
||||
chb2.getSelectionModel().selectFirst();
|
||||
//snippet_7:end
|
||||
|
||||
var grid = new GridPane();
|
||||
grid.setHgap(30);
|
||||
grid.setVgap(10);
|
||||
grid.addRow(0, captionLabel("ComboBox"), cmb1, cmb2);
|
||||
grid.addRow(1, captionLabel("ChoiceBox"), chb1, chb2);
|
||||
|
||||
var description = BBCodeParser.createFormattedText("""
|
||||
This is just a simple example to test what happens when the item size \
|
||||
exceeds the limits of the popup window."""
|
||||
);
|
||||
|
||||
return new ExampleBox(grid, new Snippet(ComboBoxPage.class, 7), description);
|
||||
}
|
||||
|
||||
private ExampleBox altIconExample() {
|
||||
//snippet_6:start
|
||||
var cmb = new ComboBox<String>();
|
||||
cmb.setPrefWidth(PREF_WIDTH);
|
||||
cmb.getStyleClass().add(Tweaks.ALT_ICON);
|
||||
cmb.setItems(FXCollections.observableArrayList(
|
||||
generate(() -> FAKER.hipster().word(), 5)
|
||||
));
|
||||
cmb.getSelectionModel().selectFirst();
|
||||
|
||||
var chb = new ChoiceBox<String>();
|
||||
chb.setPrefWidth(PREF_WIDTH);
|
||||
chb.getStyleClass().add(Tweaks.ALT_ICON);
|
||||
chb.setItems(FXCollections.observableArrayList(
|
||||
generate(() -> FAKER.hipster().word(), 5)
|
||||
));
|
||||
chb.getSelectionModel().selectFirst();
|
||||
//snippet_6:end
|
||||
|
||||
var box = new HBox(30, cmb, chb);
|
||||
var description = BBCodeParser.createFormattedText("""
|
||||
There's additional tweak [code]Tweaks.ALT_ICON[/code] to change the control \
|
||||
dropdown icon."""
|
||||
);
|
||||
|
||||
return new ExampleBox(box, new Snippet(ComboBoxPage.class, 6), description);
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,88 @@
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
|
||||
package atlantafx.sampler.page.components;
|
||||
|
||||
import static javafx.scene.input.KeyCombination.CONTROL_DOWN;
|
||||
|
||||
import atlantafx.base.theme.Styles;
|
||||
import atlantafx.sampler.page.AbstractPage;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.scene.control.ContextMenu;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.control.MenuItem;
|
||||
import javafx.scene.control.SeparatorMenuItem;
|
||||
import javafx.scene.input.KeyCode;
|
||||
import javafx.scene.input.KeyCodeCombination;
|
||||
import javafx.scene.input.KeyCombination;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.kordamp.ikonli.Ikon;
|
||||
import org.kordamp.ikonli.feather.Feather;
|
||||
import org.kordamp.ikonli.javafx.FontIcon;
|
||||
|
||||
public class ContextMenuPage extends AbstractPage {
|
||||
|
||||
public static final String NAME = "ContextMenu";
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return NAME;
|
||||
}
|
||||
|
||||
public ContextMenuPage() {
|
||||
super();
|
||||
|
||||
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 \
|
||||
creating and showing context menus to users."""
|
||||
);
|
||||
addNode(contextMenuExample());
|
||||
}
|
||||
|
||||
private Label contextMenuExample() {
|
||||
var contextMenu = new ContextMenu();
|
||||
|
||||
var undoItem = createItem("_Undo", Feather.CORNER_DOWN_LEFT,
|
||||
new KeyCodeCombination(KeyCode.Z, CONTROL_DOWN));
|
||||
undoItem.setMnemonicParsing(true);
|
||||
|
||||
var redoItem = createItem("_Redo", Feather.CORNER_DOWN_RIGHT,
|
||||
new KeyCodeCombination(KeyCode.Y, CONTROL_DOWN));
|
||||
redoItem.setMnemonicParsing(true);
|
||||
|
||||
contextMenu.getItems().addAll(
|
||||
undoItem,
|
||||
redoItem,
|
||||
new SeparatorMenuItem(),
|
||||
createItem("Cut", Feather.SCISSORS, new KeyCodeCombination(KeyCode.X, CONTROL_DOWN)),
|
||||
createItem("Copy", Feather.COPY, new KeyCodeCombination(KeyCode.C, CONTROL_DOWN)),
|
||||
createItem("Paste", null, new KeyCodeCombination(KeyCode.V, CONTROL_DOWN))
|
||||
);
|
||||
|
||||
var clickArea = new Label("Right-Click Here");
|
||||
clickArea.setAlignment(Pos.CENTER);
|
||||
clickArea.setMinSize(MAX_WIDTH / 2d, 200);
|
||||
clickArea.setMaxSize(MAX_WIDTH / 2d, 200);
|
||||
clickArea.setContextMenu(contextMenu);
|
||||
clickArea.getStyleClass().add(Styles.BORDERED);
|
||||
|
||||
return clickArea;
|
||||
}
|
||||
|
||||
private MenuItem createItem(@Nullable String text,
|
||||
@Nullable Ikon graphic,
|
||||
@Nullable KeyCombination accelerator) {
|
||||
|
||||
var item = new MenuItem(text);
|
||||
|
||||
if (graphic != null) {
|
||||
item.setGraphic(new FontIcon(graphic));
|
||||
}
|
||||
|
||||
if (accelerator != null) {
|
||||
item.setAccelerator(accelerator);
|
||||
}
|
||||
|
||||
return item;
|
||||
}
|
||||
}
|
@ -1,151 +0,0 @@
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
|
||||
package atlantafx.sampler.page.components;
|
||||
|
||||
import static atlantafx.base.theme.Styles.STATE_DANGER;
|
||||
import static atlantafx.base.theme.Styles.STATE_SUCCESS;
|
||||
import static atlantafx.sampler.page.SampleBlock.BLOCK_HGAP;
|
||||
|
||||
import atlantafx.base.controls.CustomTextField;
|
||||
import atlantafx.base.controls.MaskTextField;
|
||||
import atlantafx.base.controls.PasswordTextField;
|
||||
import atlantafx.sampler.page.AbstractPage;
|
||||
import atlantafx.sampler.page.SampleBlock;
|
||||
import java.time.LocalTime;
|
||||
import java.time.ZoneId;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.time.format.DateTimeParseException;
|
||||
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.scene.Cursor;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.layout.FlowPane;
|
||||
import javafx.scene.layout.HBox;
|
||||
import javafx.scene.layout.VBox;
|
||||
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 AbstractPage {
|
||||
|
||||
public static final String NAME = "CustomTextField";
|
||||
private static final int PREF_WIDTH = 120;
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return NAME;
|
||||
}
|
||||
|
||||
public CustomTextFieldPage() {
|
||||
super();
|
||||
setUserContent(new FlowPane(
|
||||
PAGE_HGAP, PAGE_VGAP,
|
||||
leftIconSample(),
|
||||
rightIconSample(),
|
||||
bothIconsSample(),
|
||||
successSample(),
|
||||
dangerSample(),
|
||||
passwordSample(),
|
||||
maskSample()
|
||||
));
|
||||
}
|
||||
|
||||
private SampleBlock leftIconSample() {
|
||||
var tf = new CustomTextField();
|
||||
tf.setPromptText("Prompt text");
|
||||
tf.setRight(new FontIcon(Feather.X));
|
||||
tf.setPrefWidth(PREF_WIDTH);
|
||||
return new SampleBlock("Left", tf);
|
||||
}
|
||||
|
||||
private SampleBlock rightIconSample() {
|
||||
var tf = new CustomTextField();
|
||||
tf.setPromptText("Prompt text");
|
||||
tf.setLeft(new FontIcon(Feather.MAP_PIN));
|
||||
tf.setPrefWidth(PREF_WIDTH);
|
||||
return new SampleBlock("Right", tf);
|
||||
}
|
||||
|
||||
private SampleBlock bothIconsSample() {
|
||||
var tf = new CustomTextField("Text");
|
||||
tf.setLeft(new FontIcon(Feather.MAP_PIN));
|
||||
tf.setRight(new FontIcon(Feather.X));
|
||||
tf.setPrefWidth(PREF_WIDTH);
|
||||
return new SampleBlock("Both Sides", tf);
|
||||
}
|
||||
|
||||
private SampleBlock successSample() {
|
||||
var tf = new CustomTextField("Text");
|
||||
tf.pseudoClassStateChanged(STATE_SUCCESS, true);
|
||||
tf.setRight(new FontIcon(Feather.X));
|
||||
tf.setPrefWidth(PREF_WIDTH);
|
||||
return new SampleBlock("Success", tf);
|
||||
}
|
||||
|
||||
private SampleBlock dangerSample() {
|
||||
var tf = new CustomTextField();
|
||||
tf.pseudoClassStateChanged(STATE_DANGER, true);
|
||||
tf.setLeft(new FontIcon(Feather.MAP_PIN));
|
||||
tf.setPrefWidth(PREF_WIDTH);
|
||||
return new SampleBlock("Danger", tf);
|
||||
}
|
||||
|
||||
private SampleBlock passwordSample() {
|
||||
var tf = new PasswordTextField("qwerty");
|
||||
tf.setPrefWidth(PREF_WIDTH);
|
||||
|
||||
var icon = new FontIcon(Feather.EYE_OFF);
|
||||
icon.setCursor(Cursor.HAND);
|
||||
icon.setOnMouseClicked(e -> {
|
||||
if (tf.revealPasswordProperty().get()) {
|
||||
tf.revealPasswordProperty().set(false);
|
||||
icon.setIconCode(Feather.EYE_OFF);
|
||||
} else {
|
||||
tf.revealPasswordProperty().set(true);
|
||||
icon.setIconCode(Feather.EYE);
|
||||
}
|
||||
});
|
||||
tf.setRight(icon);
|
||||
|
||||
return new SampleBlock("Password", tf);
|
||||
}
|
||||
|
||||
private SampleBlock maskSample() {
|
||||
var phoneField = new MaskTextField("(999) 999 99 99");
|
||||
phoneField.setPromptText("(999) 999 99 99");
|
||||
phoneField.setLeft(new FontIcon(Material2OutlinedMZ.PHONE));
|
||||
phoneField.setPrefWidth(180);
|
||||
|
||||
var cardField = new MaskTextField("9999-9999-9999-9999");
|
||||
cardField.setLeft(new FontIcon(Material2OutlinedAL.CREDIT_CARD));
|
||||
cardField.setPrefWidth(200);
|
||||
|
||||
var timeFormatter = DateTimeFormatter.ofPattern("HH:mm");
|
||||
var timeField = new MaskTextField("29:59");
|
||||
timeField.setText(LocalTime.now(ZoneId.systemDefault()).format(timeFormatter));
|
||||
timeField.setLeft(new FontIcon(Material2OutlinedMZ.TIMER));
|
||||
timeField.setPrefWidth(120);
|
||||
timeField.textProperty().addListener((obs, old, val) -> {
|
||||
if (val != null) {
|
||||
try {
|
||||
//noinspection ResultOfMethodCallIgnored
|
||||
LocalTime.parse(val, timeFormatter);
|
||||
timeField.pseudoClassStateChanged(STATE_DANGER, false);
|
||||
} catch (DateTimeParseException e) {
|
||||
timeField.pseudoClassStateChanged(STATE_DANGER, true);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
var content = new HBox(
|
||||
BLOCK_HGAP,
|
||||
new VBox(5, new Label("Phone Number"), phoneField),
|
||||
new VBox(5, new Label("Bank Card"), cardField),
|
||||
new VBox(5, new Label("Time"), timeField)
|
||||
);
|
||||
content.setAlignment(Pos.CENTER_LEFT);
|
||||
|
||||
return new SampleBlock("Input Mask", content);
|
||||
}
|
||||
}
|
@ -2,334 +2,111 @@
|
||||
|
||||
package atlantafx.sampler.page.components;
|
||||
|
||||
import static atlantafx.base.theme.Styles.BUTTON_ICON;
|
||||
import static atlantafx.base.theme.Styles.FLAT;
|
||||
import static atlantafx.sampler.page.SampleBlock.BLOCK_VGAP;
|
||||
import static javafx.scene.layout.GridPane.REMAINING;
|
||||
|
||||
import atlantafx.base.controls.InlineDatePicker;
|
||||
import atlantafx.base.controls.Spacer;
|
||||
import atlantafx.base.controls.ToggleSwitch;
|
||||
import atlantafx.base.theme.Styles;
|
||||
import atlantafx.sampler.page.AbstractPage;
|
||||
import atlantafx.sampler.page.SampleBlock;
|
||||
import atlantafx.sampler.theme.CSSFragment;
|
||||
import atlantafx.base.util.BBCodeParser;
|
||||
import atlantafx.sampler.page.ExampleBox;
|
||||
import atlantafx.sampler.page.OutlinePage;
|
||||
import atlantafx.sampler.page.Snippet;
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalTime;
|
||||
import java.time.ZoneId;
|
||||
import java.time.chrono.HijrahChronology;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.Objects;
|
||||
import javafx.animation.Animation;
|
||||
import javafx.animation.KeyFrame;
|
||||
import javafx.animation.Timeline;
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.beans.property.BooleanProperty;
|
||||
import javafx.beans.property.ObjectProperty;
|
||||
import javafx.beans.property.SimpleBooleanProperty;
|
||||
import javafx.beans.property.SimpleObjectProperty;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.scene.control.Button;
|
||||
import javafx.scene.control.DateCell;
|
||||
import java.time.format.DateTimeParseException;
|
||||
import javafx.scene.control.DatePicker;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.layout.GridPane;
|
||||
import javafx.scene.layout.HBox;
|
||||
import javafx.scene.layout.Pane;
|
||||
import javafx.scene.layout.Region;
|
||||
import javafx.scene.layout.VBox;
|
||||
import javafx.util.Callback;
|
||||
import javafx.util.Duration;
|
||||
import javafx.util.StringConverter;
|
||||
import org.kordamp.ikonli.javafx.FontIcon;
|
||||
import org.kordamp.ikonli.material2.Material2AL;
|
||||
|
||||
public class DatePickerPage extends AbstractPage {
|
||||
public class DatePickerPage extends OutlinePage {
|
||||
|
||||
public static final String NAME = "DatePicker";
|
||||
private static final LocalDate TODAY = LocalDate.now(ZoneId.systemDefault());
|
||||
private static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ISO_DATE;
|
||||
private static final DateTimeFormatter TIME_FORMATTER = DateTimeFormatter.ofPattern("HH:mm:ss");
|
||||
private static final String DATE_FORMATTER_PROMPT = "yyyy-MM-dd";
|
||||
private static final int INLINE_DATE_PICKER_COL = 0;
|
||||
private static final int INLINE_DATE_PICKER_ROW = 4;
|
||||
private static final CSSFragment NO_YEAR_MONTH_STYLE = new CSSFragment("""
|
||||
.date-picker-popup >.month-year-pane {
|
||||
visibility: hidden;
|
||||
-fx-min-width: 0;
|
||||
-fx-pref-width: 0;
|
||||
-fx-max-width: 0;
|
||||
-fx-min-height: 0;
|
||||
-fx-pref-height: 0;
|
||||
-fx-max-height: 0;
|
||||
}
|
||||
""");
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return NAME;
|
||||
}
|
||||
|
||||
private final BooleanProperty weekNumProperty = new SimpleBooleanProperty();
|
||||
private final BooleanProperty showClockProperty = new SimpleBooleanProperty();
|
||||
private final BooleanProperty editableProperty = new SimpleBooleanProperty();
|
||||
private final BooleanProperty offPastDatesProperty = new SimpleBooleanProperty();
|
||||
private final BooleanProperty disableProperty = new SimpleBooleanProperty();
|
||||
private final Clock clock = new Clock();
|
||||
private DatePickerColorSelector colorSelector;
|
||||
|
||||
public DatePickerPage() {
|
||||
super();
|
||||
setUserContent(new VBox(
|
||||
new SampleBlock("Playground", createPlayground())
|
||||
));
|
||||
|
||||
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 \
|
||||
ISO-8601 chronology or any of the other chronology classes defined in the \
|
||||
[code]java.time.chrono[/code] package."""
|
||||
);
|
||||
addSection("Usage", usageExample());
|
||||
addSection("Editable", editableExample());
|
||||
}
|
||||
|
||||
private GridPane createPlayground() {
|
||||
public ExampleBox usageExample() {
|
||||
//snippet_1:start
|
||||
var today = LocalDate.now(ZoneId.systemDefault());
|
||||
|
||||
var dp1 = new DatePicker(today);
|
||||
dp1.setPrefWidth(200);
|
||||
|
||||
var dp2 = new DatePicker(today.plusDays(10));
|
||||
dp2.setShowWeekNumbers(true);
|
||||
dp2.setPrefWidth(200);
|
||||
|
||||
var dp3 = new DatePicker(today.plusMonths(1));
|
||||
dp3.setChronology(HijrahChronology.INSTANCE);
|
||||
dp3.setPrefWidth(200);
|
||||
//snippet_1:end
|
||||
|
||||
var grid = new GridPane();
|
||||
grid.setHgap(40);
|
||||
grid.setVgap(BLOCK_VGAP);
|
||||
grid.setHgap(50);
|
||||
grid.setVgap(10);
|
||||
grid.addRow(0, captionLabel("Default"), dp1);
|
||||
grid.addRow(1, captionLabel("Week Numbers"), dp2);
|
||||
grid.addRow(2, captionLabel("Second Chronology"), dp3);
|
||||
|
||||
final var popupDatePicker = createPopupDatePicker();
|
||||
|
||||
colorSelector = new DatePickerColorSelector(grid);
|
||||
final var inlineDatePicker = createInlineDatePicker(null);
|
||||
|
||||
// == CONTROLS ==
|
||||
|
||||
var weekNumToggle = new ToggleSwitch("Week numbers");
|
||||
weekNumProperty.bind(weekNumToggle.selectedProperty());
|
||||
weekNumToggle.setSelected(true);
|
||||
|
||||
var showClockToggle = new ToggleSwitch("Show clock");
|
||||
showClockProperty.bind(showClockToggle.selectedProperty());
|
||||
showClockToggle.setSelected(true);
|
||||
|
||||
var showYearMonthToggle = new ToggleSwitch("Show header");
|
||||
showYearMonthToggle.setSelected(true);
|
||||
showYearMonthToggle.selectedProperty().addListener((obs, old, val) -> {
|
||||
if (!val) {
|
||||
NO_YEAR_MONTH_STYLE.addTo(grid);
|
||||
} else {
|
||||
NO_YEAR_MONTH_STYLE.removeFrom(grid);
|
||||
}
|
||||
});
|
||||
|
||||
var chronologyToggle = new ToggleSwitch("Second chronology");
|
||||
chronologyToggle.selectedProperty().addListener(
|
||||
(obs, old, val) -> popupDatePicker.setChronology(val ? HijrahChronology.INSTANCE : null)
|
||||
var description = BBCodeParser.createFormattedText("""
|
||||
The [i]DatePicker[/i] control consists of a combo box with a date \
|
||||
field and a date chooser."""
|
||||
);
|
||||
|
||||
var editableToggle = new ToggleSwitch("Editable");
|
||||
editableProperty.bind(editableToggle.selectedProperty());
|
||||
// clear selected value to demonstrate prompt text
|
||||
editableProperty.addListener(
|
||||
(obs, old, val) -> popupDatePicker.setValue(val ? null : TODAY)
|
||||
);
|
||||
|
||||
var offPastDatesToggle = new ToggleSwitch("No past dates");
|
||||
offPastDatesProperty.bind(offPastDatesToggle.selectedProperty());
|
||||
offPastDatesProperty.addListener((obs, old, val) -> {
|
||||
popupDatePicker.setDayCellFactory(val ? dp -> new FutureDateCell() : null);
|
||||
popupDatePicker.setValue(TODAY);
|
||||
|
||||
// we have to create new date picker, because changing cell factory won't update existing cells
|
||||
var datePicker = createInlineDatePicker(val ? dp -> new FutureDateCell() : null);
|
||||
grid.getChildren().removeIf(n -> n instanceof InlineDatePicker);
|
||||
grid.add(datePicker, INLINE_DATE_PICKER_COL, INLINE_DATE_PICKER_ROW);
|
||||
});
|
||||
|
||||
var disablePickerToggle = new ToggleSwitch("Disable");
|
||||
disableProperty.bind(disablePickerToggle.selectedProperty());
|
||||
|
||||
var controls = new VBox(
|
||||
BLOCK_VGAP,
|
||||
weekNumToggle,
|
||||
showClockToggle,
|
||||
showYearMonthToggle,
|
||||
chronologyToggle,
|
||||
editableToggle,
|
||||
offPastDatesToggle,
|
||||
disablePickerToggle
|
||||
);
|
||||
controls.setAlignment(Pos.CENTER_RIGHT);
|
||||
|
||||
// == GRID ==
|
||||
|
||||
var defaultLabel = new Label("Default");
|
||||
defaultLabel.getStyleClass().add(Styles.TEXT_BOLD);
|
||||
|
||||
var inlineLabel = new Label("Inline");
|
||||
inlineLabel.getStyleClass().add(Styles.TEXT_BOLD);
|
||||
|
||||
grid.add(defaultLabel, 0, 0);
|
||||
grid.add(popupDatePicker, 0, 1);
|
||||
grid.add(new Spacer(20), 0, 2);
|
||||
grid.add(inlineLabel, 0, 3);
|
||||
grid.add(inlineDatePicker, INLINE_DATE_PICKER_COL, INLINE_DATE_PICKER_ROW);
|
||||
grid.add(controls, 1, 0, 1, REMAINING);
|
||||
|
||||
return grid;
|
||||
return new ExampleBox(grid, new Snippet(getClass(), 1), description);
|
||||
}
|
||||
|
||||
private DatePicker createPopupDatePicker() {
|
||||
var datePicker = new DatePicker();
|
||||
datePicker.setConverter(DATE_CONVERTER);
|
||||
datePicker.setPromptText(DATE_FORMATTER_PROMPT);
|
||||
datePicker.setMaxWidth(Double.MAX_VALUE);
|
||||
datePicker.setValue(TODAY);
|
||||
datePicker.showWeekNumbersProperty().bind(weekNumProperty);
|
||||
datePicker.editableProperty().bind(editableProperty);
|
||||
datePicker.disableProperty().bind(disableProperty);
|
||||
return datePicker;
|
||||
}
|
||||
|
||||
private InlineDatePicker createInlineDatePicker(Callback<InlineDatePicker, DateCell> dayCellFactory) {
|
||||
var datePicker = new InlineDatePicker();
|
||||
datePicker.setMaxSize(USE_PREF_SIZE, USE_PREF_SIZE);
|
||||
datePicker.setDayCellFactory(dayCellFactory);
|
||||
datePicker.setValue(TODAY);
|
||||
datePicker.showWeekNumbersProperty().bind(weekNumProperty);
|
||||
datePicker.disableProperty().bind(disableProperty);
|
||||
|
||||
datePicker.setTopNode(clock);
|
||||
datePicker.setBottomNode(colorSelector);
|
||||
|
||||
datePicker.topNodeProperty().bind(Bindings.createObjectBinding(
|
||||
() -> showClockProperty.get() ? clock : null, showClockProperty)
|
||||
);
|
||||
|
||||
return datePicker;
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
|
||||
private static final StringConverter<LocalDate> DATE_CONVERTER = new StringConverter<>() {
|
||||
public ExampleBox editableExample() {
|
||||
//snippet_2:start
|
||||
final var today = LocalDate.now(ZoneId.systemDefault());
|
||||
final var formatter = DateTimeFormatter.ISO_DATE;
|
||||
|
||||
var dp = new DatePicker(today);
|
||||
dp.setPromptText("yyyy-MM-dd");
|
||||
dp.setEditable(true);
|
||||
dp.setPrefWidth(200);
|
||||
dp.setConverter(new StringConverter<>() {
|
||||
@Override
|
||||
public String toString(LocalDate localDate) {
|
||||
if (localDate == null) {
|
||||
return "";
|
||||
}
|
||||
return DATE_FORMATTER.format(localDate);
|
||||
return formatter.format(localDate);
|
||||
}
|
||||
|
||||
@Override
|
||||
public LocalDate fromString(String dateString) {
|
||||
if (dateString == null || dateString.trim().isEmpty()) {
|
||||
return null;
|
||||
return today;
|
||||
}
|
||||
try {
|
||||
return LocalDate.parse(dateString, DATE_FORMATTER);
|
||||
} catch (Exception e) {
|
||||
return null;
|
||||
return LocalDate.parse(dateString, formatter);
|
||||
} catch (DateTimeParseException e) {
|
||||
return today;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
private static class FutureDateCell extends DateCell {
|
||||
|
||||
@Override
|
||||
public void updateItem(LocalDate date, boolean empty) {
|
||||
super.updateItem(date, empty);
|
||||
setDisable(empty || date.isBefore(TODAY));
|
||||
}
|
||||
}
|
||||
|
||||
private static class Clock extends VBox {
|
||||
|
||||
public Clock() {
|
||||
var clockLabel = new Label(
|
||||
TIME_FORMATTER.format(LocalTime.now(ZoneId.systemDefault()))
|
||||
);
|
||||
clockLabel.getStyleClass().add(Styles.TITLE_2);
|
||||
|
||||
var dateLabel = new Label(
|
||||
DateTimeFormatter.ofPattern("EEEE, LLLL dd, yyyy").format(LocalDate.now(ZoneId.systemDefault()))
|
||||
);
|
||||
|
||||
setStyle("""
|
||||
-fx-border-width: 0 0 0.5 0;
|
||||
-fx-border-color: -color-border-default;"""
|
||||
);
|
||||
setSpacing(BLOCK_VGAP);
|
||||
getChildren().setAll(clockLabel, dateLabel);
|
||||
|
||||
var t = new Timeline(new KeyFrame(
|
||||
Duration.seconds(1),
|
||||
e -> clockLabel.setText(
|
||||
TIME_FORMATTER.format(LocalTime.now(ZoneId.systemDefault()))
|
||||
)
|
||||
));
|
||||
t.setCycleCount(Animation.INDEFINITE);
|
||||
t.playFromStart();
|
||||
}
|
||||
}
|
||||
|
||||
// This class shares stylesheet with the AccentColorSelector
|
||||
private static class DatePickerColorSelector extends HBox {
|
||||
|
||||
private final Pane parent;
|
||||
private final ObjectProperty<CSSFragment> style = new SimpleObjectProperty<>();
|
||||
|
||||
public DatePickerColorSelector(Pane parent) {
|
||||
super();
|
||||
this.parent = Objects.requireNonNull(parent);
|
||||
createView();
|
||||
}
|
||||
|
||||
private void createView() {
|
||||
var resetBtn = new Button("", new FontIcon(Material2AL.CLEAR));
|
||||
resetBtn.getStyleClass().addAll(BUTTON_ICON, FLAT);
|
||||
resetBtn.setOnAction(e -> style.set(null));
|
||||
|
||||
style.addListener((obs, old, val) -> {
|
||||
if (old != null) {
|
||||
old.removeFrom(parent);
|
||||
}
|
||||
if (val != null) {
|
||||
val.addTo(parent);
|
||||
}
|
||||
});
|
||||
//snippet_2:end
|
||||
|
||||
setAlignment(Pos.CENTER);
|
||||
getChildren().setAll(
|
||||
colorButton("-color-accent-emphasis", "-color-fg-emphasis"),
|
||||
colorButton("-color-success-emphasis", "-color-fg-emphasis"),
|
||||
colorButton("-color-danger-emphasis", "-color-fg-emphasis"),
|
||||
resetBtn
|
||||
var box = new HBox(dp);
|
||||
var description = BBCodeParser.createFormattedText("""
|
||||
The [code]editable[/code] property controls whether the [i]DatePicker[/i] \
|
||||
allows users to manually input a date."""
|
||||
);
|
||||
getStyleClass().add("color-selector");
|
||||
}
|
||||
|
||||
private Button colorButton(String bgColorName, String fgColorName) {
|
||||
var icon = new Region();
|
||||
icon.getStyleClass().add("icon");
|
||||
icon.setStyle("-color-primary:" + bgColorName + ";");
|
||||
|
||||
var btn = new Button("", icon);
|
||||
btn.getStyleClass().addAll(BUTTON_ICON, FLAT, "color-button");
|
||||
btn.setOnAction(e -> updateStyle(bgColorName, fgColorName));
|
||||
|
||||
return btn;
|
||||
}
|
||||
|
||||
private void updateStyle(String bgColorName, String fgColorName) {
|
||||
style.set(new CSSFragment(String.format("""
|
||||
.date-picker-popup {
|
||||
-color-date-border: %s;
|
||||
-color-date-month-year-bg: %s;
|
||||
-color-date-month-year-fg: %s;
|
||||
}
|
||||
.date-picker-popup >.top-node {
|
||||
-fx-background-color: %s;
|
||||
-color-fg-default: %s;
|
||||
-color-border-default: %s;
|
||||
-fx-border-color: %s;
|
||||
}""",
|
||||
bgColorName, bgColorName, fgColorName,
|
||||
bgColorName, fgColorName, fgColorName, fgColorName
|
||||
)));
|
||||
}
|
||||
return new ExampleBox(box, new Snippet(getClass(), 2), description);
|
||||
}
|
||||
}
|
||||
|
@ -2,38 +2,30 @@
|
||||
|
||||
package atlantafx.sampler.page.components;
|
||||
|
||||
import static atlantafx.sampler.page.SampleBlock.BLOCK_HGAP;
|
||||
import static javafx.scene.control.Alert.AlertType;
|
||||
import static javafx.scene.control.ButtonBar.ButtonData;
|
||||
|
||||
import atlantafx.base.controls.ToggleSwitch;
|
||||
import atlantafx.sampler.page.AbstractPage;
|
||||
import atlantafx.sampler.page.SampleBlock;
|
||||
import atlantafx.base.util.BBCodeParser;
|
||||
import atlantafx.sampler.page.ExampleBox;
|
||||
import atlantafx.sampler.page.OutlinePage;
|
||||
import atlantafx.sampler.page.Snippet;
|
||||
import java.io.PrintWriter;
|
||||
import java.io.StringWriter;
|
||||
import java.util.ArrayList;
|
||||
import javafx.beans.property.BooleanProperty;
|
||||
import javafx.beans.property.SimpleBooleanProperty;
|
||||
import javafx.geometry.Orientation;
|
||||
import javafx.geometry.Pos;
|
||||
import java.util.List;
|
||||
import javafx.scene.control.Alert;
|
||||
import javafx.scene.control.Button;
|
||||
import javafx.scene.control.ButtonType;
|
||||
import javafx.scene.control.ChoiceDialog;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.control.Separator;
|
||||
import javafx.scene.control.TextArea;
|
||||
import javafx.scene.control.TextInputDialog;
|
||||
import javafx.scene.layout.FlowPane;
|
||||
import javafx.scene.layout.GridPane;
|
||||
import javafx.scene.layout.HBox;
|
||||
import javafx.scene.layout.Priority;
|
||||
import javafx.scene.layout.VBox;
|
||||
import javafx.stage.StageStyle;
|
||||
import org.kordamp.ikonli.feather.Feather;
|
||||
import org.kordamp.ikonli.javafx.FontIcon;
|
||||
|
||||
public class DialogPage extends AbstractPage {
|
||||
public class DialogPage extends OutlinePage {
|
||||
|
||||
public static final String NAME = "Dialog";
|
||||
|
||||
@ -42,96 +34,69 @@ public class DialogPage extends AbstractPage {
|
||||
return NAME;
|
||||
}
|
||||
|
||||
private final BooleanProperty showHeaderProperty = new SimpleBooleanProperty(true);
|
||||
private final BooleanProperty minDecorationsProperty = new SimpleBooleanProperty(true);
|
||||
|
||||
public DialogPage() {
|
||||
super();
|
||||
createView();
|
||||
}
|
||||
|
||||
private void createView() {
|
||||
var showHeaderToggle = new ToggleSwitch("Show header");
|
||||
showHeaderProperty.bind(showHeaderToggle.selectedProperty());
|
||||
showHeaderToggle.setSelected(true);
|
||||
|
||||
var minDecorationsToggle = new ToggleSwitch("Minimum decorations");
|
||||
minDecorationsProperty.bind(minDecorationsToggle.selectedProperty());
|
||||
minDecorationsToggle.setSelected(true);
|
||||
|
||||
var controls = new HBox(BLOCK_HGAP, showHeaderToggle, minDecorationsToggle);
|
||||
controls.setAlignment(Pos.CENTER);
|
||||
|
||||
var samples = new FlowPane(
|
||||
PAGE_HGAP, PAGE_VGAP,
|
||||
infoDialogSample(),
|
||||
warningDialogSample(),
|
||||
errorDialogSample(),
|
||||
exceptionDialogSample(),
|
||||
confirmationDialogSample(),
|
||||
textInputDialogSample(),
|
||||
choiceDialogSample()
|
||||
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."""
|
||||
);
|
||||
|
||||
setUserContent(new VBox(
|
||||
10,
|
||||
controls,
|
||||
new Separator(Orientation.HORIZONTAL),
|
||||
samples
|
||||
));
|
||||
addSection("Notifications", notificationDialogExample());
|
||||
addSection("Exception Dialog", exceptionDialogExample());
|
||||
addSection("Confirmation Dialog", confirmationDialogExample());
|
||||
addSection("Text Input Dialog", textInputDialogExample());
|
||||
addSection("Choice Dialog", choiceDialogExample());
|
||||
addSection("No Header", notificationNoHeaderDialogExample());
|
||||
}
|
||||
|
||||
private SampleBlock infoDialogSample() {
|
||||
var button = new Button("Click", new FontIcon(Feather.INFO));
|
||||
button.setOnAction(e -> {
|
||||
private ExampleBox notificationDialogExample() {
|
||||
//snippet_1:start
|
||||
var infoBtn = new Button("Info", new FontIcon(Feather.INFO));
|
||||
infoBtn.setOnAction(e -> {
|
||||
var alert = new Alert(AlertType.INFORMATION);
|
||||
alert.setTitle("Information Dialog");
|
||||
alert.setHeaderText(randomHeader());
|
||||
alert.setHeaderText(FAKER.chuckNorris().fact());
|
||||
alert.setContentText(FAKER.lorem().paragraph(3));
|
||||
alert.initOwner(getScene().getWindow());
|
||||
alert.initStyle(getModality());
|
||||
alert.showAndWait();
|
||||
});
|
||||
|
||||
return new SampleBlock("Information", button);
|
||||
}
|
||||
|
||||
private SampleBlock warningDialogSample() {
|
||||
var button = new Button("Click", new FontIcon(Feather.ALERT_TRIANGLE));
|
||||
button.setOnAction(e -> {
|
||||
var warnBtn = new Button("Click", new FontIcon(Feather.ALERT_TRIANGLE));
|
||||
warnBtn.setOnAction(e -> {
|
||||
var alert = new Alert(AlertType.WARNING);
|
||||
alert.setTitle("Warning Dialog");
|
||||
alert.setHeaderText(randomHeader());
|
||||
alert.setHeaderText(FAKER.chuckNorris().fact());
|
||||
alert.setContentText(FAKER.lorem().paragraph(3));
|
||||
alert.initOwner(getScene().getWindow());
|
||||
alert.initStyle(getModality());
|
||||
alert.showAndWait();
|
||||
});
|
||||
|
||||
return new SampleBlock("Warning", button);
|
||||
}
|
||||
|
||||
private SampleBlock errorDialogSample() {
|
||||
var button = new Button("Click", new FontIcon(Feather.X_CIRCLE));
|
||||
button.setOnAction(e -> {
|
||||
var errorBtn = new Button("Click", new FontIcon(Feather.X_CIRCLE));
|
||||
errorBtn.setOnAction(e -> {
|
||||
var alert = new Alert(AlertType.ERROR);
|
||||
alert.setTitle("Error Dialog");
|
||||
alert.setHeaderText(randomHeader());
|
||||
alert.setHeaderText(FAKER.chuckNorris().fact());
|
||||
alert.setContentText(FAKER.lorem().paragraph(3));
|
||||
alert.initOwner(getScene().getWindow());
|
||||
alert.initStyle(getModality());
|
||||
alert.showAndWait();
|
||||
});
|
||||
//snippet_1:end
|
||||
|
||||
return new SampleBlock("Error", button);
|
||||
var box = new HBox(30, infoBtn, warnBtn, errorBtn);
|
||||
var description = BBCodeParser.createFormattedText("""
|
||||
Pre-built dialog types for displaying information, warnings, and errors."""
|
||||
);
|
||||
|
||||
return new ExampleBox(box, new Snippet(getClass(), 1), description);
|
||||
}
|
||||
|
||||
private SampleBlock exceptionDialogSample() {
|
||||
private ExampleBox exceptionDialogExample() {
|
||||
//snippet_2:start
|
||||
var button = new Button("Click", new FontIcon(Feather.MEH));
|
||||
button.setOnAction(e -> {
|
||||
var alert = new Alert(AlertType.ERROR);
|
||||
alert.setTitle("Exception Dialog");
|
||||
alert.setHeaderText(randomHeader());
|
||||
alert.setHeaderText(FAKER.chuckNorris().fact());
|
||||
alert.setContentText(FAKER.lorem().paragraph(3));
|
||||
|
||||
var exception = new RuntimeException(FAKER.chuckNorris().fact());
|
||||
@ -155,75 +120,128 @@ public class DialogPage extends AbstractPage {
|
||||
|
||||
alert.getDialogPane().setExpandableContent(content);
|
||||
alert.initOwner(getScene().getWindow());
|
||||
alert.initStyle(getModality());
|
||||
alert.showAndWait();
|
||||
});
|
||||
//snippet_2:end
|
||||
|
||||
return new SampleBlock("Exception", button);
|
||||
var description = BBCodeParser.createFormattedText("""
|
||||
A custom dialog that is designed to display information about exceptions that \
|
||||
are thrown in JavaFX applications."""
|
||||
);
|
||||
|
||||
return new ExampleBox(new HBox(button), new Snippet(getClass(), 2), description);
|
||||
}
|
||||
|
||||
private SampleBlock confirmationDialogSample() {
|
||||
var button = new Button("Click", new FontIcon(Feather.CHECK_SQUARE));
|
||||
private ExampleBox confirmationDialogExample() {
|
||||
//snippet_3:start
|
||||
var button = new Button(
|
||||
"Click", new FontIcon(Feather.CHECK_SQUARE)
|
||||
);
|
||||
button.setOnAction(e -> {
|
||||
var alert = new Alert(AlertType.CONFIRMATION);
|
||||
alert.setTitle("Confirmation Dialog");
|
||||
alert.setHeaderText(randomHeader());
|
||||
alert.setHeaderText(FAKER.chuckNorris().fact());
|
||||
alert.setContentText(FAKER.lorem().paragraph(3));
|
||||
|
||||
ButtonType yesBtn = new ButtonType("Yes", ButtonData.YES);
|
||||
ButtonType noBtn = new ButtonType("No", ButtonData.NO);
|
||||
ButtonType cancelBtn = new ButtonType("Cancel", ButtonData.CANCEL_CLOSE);
|
||||
ButtonType cancelBtn = new ButtonType(
|
||||
"Cancel", ButtonData.CANCEL_CLOSE
|
||||
);
|
||||
|
||||
alert.getButtonTypes().setAll(yesBtn, noBtn, cancelBtn);
|
||||
|
||||
alert.initOwner(getScene().getWindow());
|
||||
alert.initStyle(getModality());
|
||||
alert.showAndWait();
|
||||
});
|
||||
//snippet_3:end
|
||||
|
||||
return new SampleBlock("Confirmation", button);
|
||||
var description = BBCodeParser.createFormattedText("""
|
||||
The confirmation alert type configures the [i]Alert[/i] dialog to appear in a way that \
|
||||
suggests the content of the dialog is seeking confirmation from the user."""
|
||||
);
|
||||
|
||||
return new ExampleBox(new HBox(button), new Snippet(getClass(), 3), description);
|
||||
}
|
||||
|
||||
private SampleBlock textInputDialogSample() {
|
||||
private ExampleBox textInputDialogExample() {
|
||||
//snippet_4:start
|
||||
var button = new Button("Click", new FontIcon(Feather.EDIT_2));
|
||||
button.setOnAction(e -> {
|
||||
var dialog = new TextInputDialog();
|
||||
dialog.setTitle("Text Input Dialog");
|
||||
dialog.setHeaderText(randomHeader());
|
||||
dialog.setHeaderText(FAKER.chuckNorris().fact());
|
||||
dialog.setContentText("Enter your name:");
|
||||
dialog.initOwner(getScene().getWindow());
|
||||
dialog.initStyle(getModality());
|
||||
dialog.showAndWait();
|
||||
});
|
||||
//snippet_4:end
|
||||
|
||||
return new SampleBlock("Text Input", button);
|
||||
var description = BBCodeParser.createFormattedText(
|
||||
"A dialog that shows a text input control to the user."
|
||||
);
|
||||
|
||||
return new ExampleBox(new HBox(button), new Snippet(getClass(), 4), description);
|
||||
}
|
||||
|
||||
private SampleBlock choiceDialogSample() {
|
||||
private ExampleBox choiceDialogExample() {
|
||||
//snippet_5:start
|
||||
var button = new Button("Click", new FontIcon(Feather.LIST));
|
||||
button.setOnAction(e -> {
|
||||
var choices = new ArrayList<>();
|
||||
choices.add("A");
|
||||
choices.add("B");
|
||||
choices.add("C");
|
||||
|
||||
var choices = List.of("A", "B", "C");
|
||||
var dialog = new ChoiceDialog<>(choices.get(0), choices);
|
||||
dialog.setTitle("Choice Dialog");
|
||||
dialog.setHeaderText(randomHeader());
|
||||
dialog.setHeaderText(FAKER.chuckNorris().fact());
|
||||
dialog.setContentText("Choose your letter:");
|
||||
dialog.initOwner(getScene().getWindow());
|
||||
dialog.initStyle(getModality());
|
||||
dialog.showAndWait();
|
||||
});
|
||||
//snippet_5:end
|
||||
|
||||
return new SampleBlock("Choice", button);
|
||||
var description = BBCodeParser.createFormattedText("""
|
||||
A dialog that shows a list of choices to the user, from which they can pick one item at most."""
|
||||
);
|
||||
|
||||
return new ExampleBox(new HBox(button), new Snippet(getClass(), 5), description);
|
||||
}
|
||||
|
||||
private String randomHeader() {
|
||||
return showHeaderProperty.get() ? FAKER.chuckNorris().fact() : null;
|
||||
}
|
||||
private ExampleBox notificationNoHeaderDialogExample() {
|
||||
//snippet_6:start
|
||||
var infoBtn = new Button("Info", new FontIcon(Feather.INFO));
|
||||
infoBtn.setOnAction(e -> {
|
||||
var alert = new Alert(AlertType.INFORMATION);
|
||||
alert.setTitle("Information Dialog");
|
||||
alert.setHeaderText(null);
|
||||
alert.setContentText(FAKER.lorem().paragraph(3));
|
||||
alert.initOwner(getScene().getWindow());
|
||||
alert.showAndWait();
|
||||
});
|
||||
|
||||
private StageStyle getModality() {
|
||||
return minDecorationsProperty.get() ? StageStyle.UTILITY : StageStyle.DECORATED;
|
||||
var warnBtn = new Button("Click", new FontIcon(Feather.ALERT_TRIANGLE));
|
||||
warnBtn.setOnAction(e -> {
|
||||
var alert = new Alert(AlertType.WARNING);
|
||||
alert.setTitle("Warning Dialog");
|
||||
alert.setHeaderText(null);
|
||||
alert.setContentText(FAKER.lorem().paragraph(3));
|
||||
alert.initOwner(getScene().getWindow());
|
||||
alert.showAndWait();
|
||||
});
|
||||
|
||||
var errorBtn = new Button("Click", new FontIcon(Feather.X_CIRCLE));
|
||||
errorBtn.setOnAction(e -> {
|
||||
var alert = new Alert(AlertType.ERROR);
|
||||
alert.setTitle("Error Dialog");
|
||||
alert.setHeaderText(null);
|
||||
alert.setContentText(FAKER.lorem().paragraph(3));
|
||||
alert.initOwner(getScene().getWindow());
|
||||
alert.showAndWait();
|
||||
});
|
||||
//snippet_6:end
|
||||
|
||||
var box = new HBox(30, infoBtn, warnBtn, errorBtn);
|
||||
var description = BBCodeParser.createFormattedText(
|
||||
"The header text can be hidden."
|
||||
);
|
||||
|
||||
return new ExampleBox(box, new Snippet(getClass(), 6), description);
|
||||
}
|
||||
}
|
||||
|
@ -2,21 +2,18 @@
|
||||
|
||||
package atlantafx.sampler.page.components;
|
||||
|
||||
import static atlantafx.sampler.page.SampleBlock.BLOCK_VGAP;
|
||||
|
||||
import atlantafx.base.controls.ToggleSwitch;
|
||||
import atlantafx.base.theme.Theme;
|
||||
import atlantafx.base.util.BBCodeParser;
|
||||
import atlantafx.sampler.event.DefaultEventBus;
|
||||
import atlantafx.sampler.event.ThemeEvent;
|
||||
import atlantafx.sampler.page.AbstractPage;
|
||||
import atlantafx.sampler.page.SampleBlock;
|
||||
import atlantafx.sampler.theme.HighlightJSTheme;
|
||||
import atlantafx.sampler.theme.ThemeManager;
|
||||
import javafx.css.PseudoClass;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.scene.layout.Priority;
|
||||
import javafx.scene.layout.VBox;
|
||||
import javafx.scene.text.Text;
|
||||
import javafx.scene.text.TextFlow;
|
||||
import javafx.scene.web.HTMLEditor;
|
||||
|
||||
public class HtmlEditorPage extends AbstractPage {
|
||||
@ -30,13 +27,16 @@ public class HtmlEditorPage extends AbstractPage {
|
||||
return NAME;
|
||||
}
|
||||
|
||||
private HTMLEditor editor = createEditor();
|
||||
private HTMLEditor editor = createHTMLEditor();
|
||||
|
||||
public HtmlEditorPage() {
|
||||
super();
|
||||
|
||||
setUserContent(new VBox(editorSample()));
|
||||
editor.requestFocus();
|
||||
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."""
|
||||
);
|
||||
addNode(editorSample());
|
||||
|
||||
// update editor colors on app theme change
|
||||
DefaultEventBus.getInstance().subscribe(ThemeEvent.class, e -> {
|
||||
@ -45,42 +45,46 @@ public class HtmlEditorPage extends AbstractPage {
|
||||
editor.requestFocus();
|
||||
}
|
||||
});
|
||||
|
||||
editor.requestFocus();
|
||||
}
|
||||
|
||||
private SampleBlock editorSample() {
|
||||
var description = new Text("""
|
||||
HTMLEditor toolbar buttons use images from 'com/sun/javafx/scene/control/skin/modena'.
|
||||
In opposite, since AtlantaFX themes are also distributed as single CSS files, it contains no images.
|
||||
Unfortunately reusing Modena resources isn't possible, because the package isn't opened in OpenJFX
|
||||
'module-info'.
|
||||
"""
|
||||
private VBox editorSample() {
|
||||
var description = BBCodeParser.createFormattedText("""
|
||||
Since AtlantaFX themes are also distributed as CSS files, they can't contain any images. \
|
||||
Unfortunately, reusing Modena resources in theme also isn't possible, because the they \
|
||||
are located in [font=monospace]'com/sun/javafx/*'[/font] package, which isn't opened in \
|
||||
OpenJFX [font=monospace]'module-info'[/font]. But you can still copy Modena images and \
|
||||
overwrite [i]HMTLEditor[/i] CSS in your app."""
|
||||
);
|
||||
|
||||
var fixToggle = new ToggleSwitch("Apply Fix");
|
||||
|
||||
var content = new VBox(BLOCK_VGAP, editor, new TextFlow(description), fixToggle);
|
||||
content.setAlignment(Pos.CENTER);
|
||||
var content = new VBox(20, editor, description, fixToggle);
|
||||
content.setAlignment(Pos.TOP_CENTER);
|
||||
VBox.setVgrow(content, Priority.ALWAYS);
|
||||
|
||||
fixToggle.selectedProperty().addListener((obs, old, val) -> {
|
||||
// toolbar icons can't be changed back without creating new editor instance #javafx-bug
|
||||
try {
|
||||
editor = createEditor();
|
||||
editor = createHTMLEditor();
|
||||
editor.pseudoClassStateChanged(USE_LOCAL_URL, val);
|
||||
content.getChildren().set(0, editor);
|
||||
editor.requestFocus();
|
||||
} catch (Exception ignored) {
|
||||
// hush internal HTML editor errors, because everything
|
||||
// we do here is ugly hacks around legacy control anyway
|
||||
// we do here is an ugly hack around legacy control anyway
|
||||
}
|
||||
});
|
||||
|
||||
return new SampleBlock("Playground", content);
|
||||
return content;
|
||||
}
|
||||
|
||||
private HTMLEditor createEditor() {
|
||||
private HTMLEditor createHTMLEditor() {
|
||||
var editor = new HTMLEditor();
|
||||
editor.setPrefHeight(400);
|
||||
editor.setHtmlText(generateContent());
|
||||
VBox.setVgrow(editor, Priority.ALWAYS);
|
||||
return editor;
|
||||
}
|
||||
|
||||
@ -88,6 +92,10 @@ public class HtmlEditorPage extends AbstractPage {
|
||||
var tm = ThemeManager.getInstance();
|
||||
Theme samplerTheme = tm.getTheme();
|
||||
HighlightJSTheme hlTheme = tm.getMatchingSourceCodeHighlightTheme(samplerTheme);
|
||||
var text = String.join("<br/><br/>", generate(
|
||||
() -> String.join(" ", FAKER.lorem().paragraphs(5)), 5)
|
||||
);
|
||||
|
||||
return "<!DOCTYPE html>"
|
||||
+ "<html>"
|
||||
+ "<body style=\""
|
||||
@ -96,7 +104,7 @@ public class HtmlEditorPage extends AbstractPage {
|
||||
+ "font-family:" + tm.getFontFamily() + ";"
|
||||
+ "font-size:" + tm.getFontSize() + "px;"
|
||||
+ "\">"
|
||||
+ String.join("<br/><br/>", FAKER.lorem().paragraphs(10))
|
||||
+ text
|
||||
+ "</body>"
|
||||
+ "</html>";
|
||||
}
|
||||
|
@ -1,171 +0,0 @@
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
|
||||
package atlantafx.sampler.page.components;
|
||||
|
||||
import static atlantafx.base.theme.Styles.BUTTON_ICON;
|
||||
import static atlantafx.sampler.page.SampleBlock.BLOCK_HGAP;
|
||||
import static atlantafx.sampler.page.SampleBlock.BLOCK_VGAP;
|
||||
|
||||
import atlantafx.base.theme.Styles;
|
||||
import atlantafx.sampler.page.AbstractPage;
|
||||
import atlantafx.sampler.page.Page;
|
||||
import atlantafx.sampler.page.SampleBlock;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.scene.control.Button;
|
||||
import javafx.scene.control.CheckBox;
|
||||
import javafx.scene.control.ComboBox;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.control.MenuButton;
|
||||
import javafx.scene.control.MenuItem;
|
||||
import javafx.scene.control.TextField;
|
||||
import javafx.scene.layout.FlowPane;
|
||||
import javafx.scene.layout.HBox;
|
||||
import javafx.scene.layout.VBox;
|
||||
import org.kordamp.ikonli.feather.Feather;
|
||||
import org.kordamp.ikonli.javafx.FontIcon;
|
||||
|
||||
public class InputGroupPage extends AbstractPage {
|
||||
|
||||
public static final String NAME = "Input Group";
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return NAME;
|
||||
}
|
||||
|
||||
public InputGroupPage() {
|
||||
super();
|
||||
setUserContent(new VBox(
|
||||
Page.PAGE_VGAP,
|
||||
expandingHBox(httpMethodSample(), passwordSample()),
|
||||
expandingHBox(networkSample(), dropdownSample()),
|
||||
labelSample()
|
||||
));
|
||||
}
|
||||
|
||||
private SampleBlock httpMethodSample() {
|
||||
var leftCombo = new ComboBox<>();
|
||||
leftCombo.getItems().addAll("POST", "GET", "PUT", "PATCH", "DELETE");
|
||||
leftCombo.getStyleClass().add(Styles.LEFT_PILL);
|
||||
leftCombo.getSelectionModel().selectFirst();
|
||||
|
||||
var rightText = new TextField("https://example.org");
|
||||
rightText.getStyleClass().add(Styles.RIGHT_PILL);
|
||||
|
||||
var box = new HBox(leftCombo, rightText);
|
||||
box.setAlignment(Pos.CENTER_LEFT);
|
||||
|
||||
return new SampleBlock("ComboBox & TextField", box);
|
||||
}
|
||||
|
||||
private SampleBlock passwordSample() {
|
||||
var leftPassword = new TextField();
|
||||
leftPassword.setText(FAKER.internet().password());
|
||||
leftPassword.getStyleClass().add(Styles.LEFT_PILL);
|
||||
|
||||
var rightBtn = new Button("", new FontIcon(Feather.REFRESH_CW));
|
||||
rightBtn.getStyleClass().addAll(BUTTON_ICON);
|
||||
rightBtn.setOnAction(e -> leftPassword.setText(FAKER.internet().password()));
|
||||
rightBtn.getStyleClass().add(Styles.RIGHT_PILL);
|
||||
|
||||
var box = new HBox(leftPassword, rightBtn);
|
||||
box.setAlignment(Pos.CENTER_LEFT);
|
||||
|
||||
return new SampleBlock("Text Field & Button", box);
|
||||
}
|
||||
|
||||
private SampleBlock networkSample() {
|
||||
var leftText = new TextField("192.168.1.10");
|
||||
leftText.getStyleClass().add(Styles.LEFT_PILL);
|
||||
leftText.setPrefWidth(140);
|
||||
|
||||
var centerText = new TextField("24");
|
||||
centerText.getStyleClass().add(Styles.CENTER_PILL);
|
||||
centerText.setPrefWidth(70);
|
||||
|
||||
var rightText = new TextField("192.168.1.1");
|
||||
rightText.getStyleClass().add(Styles.RIGHT_PILL);
|
||||
rightText.setPrefWidth(140);
|
||||
|
||||
var box = new HBox(leftText, centerText, rightText);
|
||||
box.setAlignment(Pos.CENTER_LEFT);
|
||||
|
||||
return new SampleBlock("Text Fields", box);
|
||||
}
|
||||
|
||||
private SampleBlock dropdownSample() {
|
||||
var rightText = new TextField(FAKER.harryPotter().spell());
|
||||
rightText.getStyleClass().add(Styles.RIGHT_PILL);
|
||||
|
||||
var spellItem = new MenuItem("Spell");
|
||||
spellItem.setOnAction(e -> rightText.setText(FAKER.harryPotter().spell()));
|
||||
|
||||
var characterItem = new MenuItem("Character");
|
||||
characterItem.setOnAction(e -> rightText.setText(FAKER.harryPotter().character()));
|
||||
|
||||
var locationItem = new MenuItem("Location");
|
||||
locationItem.setOnAction(e -> rightText.setText(FAKER.harryPotter().location()));
|
||||
|
||||
var leftMenu = new MenuButton("Generate");
|
||||
leftMenu.getItems().addAll(spellItem, characterItem, locationItem);
|
||||
leftMenu.getStyleClass().add(Styles.LEFT_PILL);
|
||||
|
||||
var box = new HBox(leftMenu, rightText);
|
||||
box.setAlignment(Pos.CENTER_LEFT);
|
||||
|
||||
return new SampleBlock("MenuButton & TextField", box);
|
||||
}
|
||||
|
||||
private SampleBlock labelSample() {
|
||||
var leftLabel1 = new Label("", new CheckBox());
|
||||
leftLabel1.getStyleClass().add(Styles.LEFT_PILL);
|
||||
|
||||
var rightText1 = new TextField();
|
||||
rightText1.setPromptText("Username");
|
||||
rightText1.getStyleClass().add(Styles.RIGHT_PILL);
|
||||
rightText1.setPrefWidth(100);
|
||||
|
||||
var sample1 = new HBox(leftLabel1, rightText1);
|
||||
sample1.setAlignment(Pos.CENTER_LEFT);
|
||||
|
||||
// ~
|
||||
|
||||
var leftText2 = new TextField("johndoe");
|
||||
leftText2.getStyleClass().add(Styles.LEFT_PILL);
|
||||
leftText2.setPrefWidth(100);
|
||||
|
||||
var centerLabel2 = new Label("@");
|
||||
centerLabel2.getStyleClass().add(Styles.CENTER_PILL);
|
||||
|
||||
var rightText2 = new TextField("gmail.com");
|
||||
rightText2.getStyleClass().add(Styles.RIGHT_PILL);
|
||||
rightText2.setPrefWidth(100);
|
||||
|
||||
var sample2 = new HBox(leftText2, centerLabel2, rightText2);
|
||||
sample2.setAlignment(Pos.CENTER_LEFT);
|
||||
|
||||
// ~
|
||||
|
||||
var leftText3 = new TextField("+123456");
|
||||
leftText3.getStyleClass().add(Styles.LEFT_PILL);
|
||||
leftText3.setPrefWidth(100);
|
||||
|
||||
var rightLabel3 = new Label("", new FontIcon(Feather.DOLLAR_SIGN));
|
||||
rightLabel3.getStyleClass().add(Styles.RIGHT_PILL);
|
||||
|
||||
var sample3 = new HBox(leftText3, rightLabel3);
|
||||
sample3.setAlignment(Pos.CENTER_LEFT);
|
||||
|
||||
// ~
|
||||
|
||||
var flowPane = new FlowPane(
|
||||
BLOCK_HGAP, BLOCK_VGAP,
|
||||
sample1,
|
||||
sample2,
|
||||
sample3
|
||||
);
|
||||
|
||||
return new SampleBlock("Label & TextField", flowPane);
|
||||
}
|
||||
}
|
||||
|
@ -2,12 +2,9 @@
|
||||
|
||||
package atlantafx.sampler.page.components;
|
||||
|
||||
import static atlantafx.sampler.page.SampleBlock.BLOCK_HGAP;
|
||||
import static atlantafx.sampler.page.SampleBlock.BLOCK_VGAP;
|
||||
|
||||
import atlantafx.base.theme.Styles;
|
||||
import atlantafx.base.util.BBCodeParser;
|
||||
import atlantafx.sampler.page.AbstractPage;
|
||||
import atlantafx.sampler.page.SampleBlock;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.layout.FlowPane;
|
||||
import javafx.scene.layout.VBox;
|
||||
@ -25,17 +22,16 @@ public class LabelPage extends AbstractPage {
|
||||
|
||||
public LabelPage() {
|
||||
super();
|
||||
createView();
|
||||
|
||||
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 void createView() {
|
||||
setUserContent(new VBox(
|
||||
PAGE_VGAP,
|
||||
expandingHBox(colorSample())
|
||||
));
|
||||
}
|
||||
|
||||
private SampleBlock colorSample() {
|
||||
private VBox colorExample() {
|
||||
var defaultLabel = new Label("default", createFontIcon());
|
||||
|
||||
var accentLabel = new Label("accent", createFontIcon());
|
||||
@ -56,17 +52,23 @@ public class LabelPage extends AbstractPage {
|
||||
var subtleLabel = new Label("subtle", createFontIcon());
|
||||
subtleLabel.getStyleClass().add(Styles.TEXT_SUBTLE);
|
||||
|
||||
var content = new VBox(
|
||||
BLOCK_VGAP,
|
||||
new Label("You can also use pseudo-classes to set Label color."),
|
||||
new Label("Note that icon inherits label color by default."),
|
||||
new FlowPane(
|
||||
BLOCK_HGAP, BLOCK_VGAP,
|
||||
defaultLabel, accentLabel, successLabel, warningLabel, dangerLabel,
|
||||
mutedLabel, subtleLabel
|
||||
));
|
||||
var description = BBCodeParser.createFormattedText("""
|
||||
You can use pseudo-classes to set the [i]Label[/i] color. Note that icon \
|
||||
inherits label color by default."""
|
||||
);
|
||||
|
||||
return new SampleBlock("Colors", content);
|
||||
var labels = new FlowPane(
|
||||
20, 20,
|
||||
defaultLabel,
|
||||
accentLabel,
|
||||
successLabel,
|
||||
warningLabel,
|
||||
dangerLabel,
|
||||
mutedLabel,
|
||||
subtleLabel
|
||||
);
|
||||
|
||||
return new VBox(20, description, labels);
|
||||
}
|
||||
|
||||
private FontIcon createFontIcon(String... stylesClass) {
|
||||
|
@ -1,337 +0,0 @@
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
|
||||
package atlantafx.sampler.page.components;
|
||||
|
||||
import static atlantafx.base.theme.Styles.ACCENT;
|
||||
import static atlantafx.base.theme.Styles.BORDERED;
|
||||
import static atlantafx.base.theme.Styles.DENSE;
|
||||
import static atlantafx.base.theme.Styles.STRIPED;
|
||||
import static atlantafx.base.theme.Styles.toggleStyleClass;
|
||||
import static atlantafx.sampler.page.SampleBlock.BLOCK_HGAP;
|
||||
import static atlantafx.sampler.page.SampleBlock.BLOCK_VGAP;
|
||||
|
||||
import atlantafx.base.controls.Spacer;
|
||||
import atlantafx.base.controls.ToggleSwitch;
|
||||
import atlantafx.base.theme.Tweaks;
|
||||
import atlantafx.sampler.fake.domain.Book;
|
||||
import atlantafx.sampler.page.AbstractPage;
|
||||
import atlantafx.sampler.page.SampleBlock;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.stream.Collectors;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.scene.control.Button;
|
||||
import javafx.scene.control.ComboBox;
|
||||
import javafx.scene.control.Hyperlink;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.control.ListCell;
|
||||
import javafx.scene.control.ListView;
|
||||
import javafx.scene.control.cell.CheckBoxListCell;
|
||||
import javafx.scene.control.cell.ChoiceBoxListCell;
|
||||
import javafx.scene.control.cell.ComboBoxListCell;
|
||||
import javafx.scene.control.cell.TextFieldListCell;
|
||||
import javafx.scene.layout.BorderPane;
|
||||
import javafx.scene.layout.HBox;
|
||||
import javafx.scene.layout.Priority;
|
||||
import javafx.scene.layout.VBox;
|
||||
import javafx.util.StringConverter;
|
||||
import org.kordamp.ikonli.feather.Feather;
|
||||
import org.kordamp.ikonli.javafx.FontIcon;
|
||||
|
||||
public class ListPage extends AbstractPage {
|
||||
|
||||
public static final String NAME = "ListView";
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return NAME;
|
||||
}
|
||||
|
||||
private final List<Book> dataList = generate(() -> Book.random(FAKER), 50);
|
||||
private final StringConverter<Book> bookStringConverter = new BookStringConverter(dataList);
|
||||
private final BorderPane listWrapper = new BorderPane();
|
||||
private final ComboBox<Example> exampleSelect = createExampleSelect();
|
||||
|
||||
public ListPage() {
|
||||
super();
|
||||
|
||||
var sample = new SampleBlock("Playground", createPlayground());
|
||||
sample.setFillHeight(true);
|
||||
setUserContent(sample);
|
||||
}
|
||||
|
||||
private VBox createPlayground() {
|
||||
var borderedToggle = new ToggleSwitch("Bordered");
|
||||
borderedToggle.selectedProperty()
|
||||
.addListener((obs, old, value) -> toggleListProperty(lv -> toggleStyleClass(lv, BORDERED)));
|
||||
|
||||
var denseToggle = new ToggleSwitch("Dense");
|
||||
denseToggle.selectedProperty()
|
||||
.addListener((obs, old, value) -> toggleListProperty(lv -> toggleStyleClass(lv, DENSE)));
|
||||
|
||||
var stripedToggle = new ToggleSwitch("Striped");
|
||||
stripedToggle.selectedProperty()
|
||||
.addListener((obs, old, value) -> toggleListProperty(lv -> toggleStyleClass(lv, STRIPED)));
|
||||
|
||||
var edge2edgeToggle = new ToggleSwitch("Edge to edge");
|
||||
edge2edgeToggle.selectedProperty()
|
||||
.addListener((obs, old, value) -> toggleListProperty(lv -> toggleStyleClass(lv, Tweaks.EDGE_TO_EDGE)));
|
||||
|
||||
var disableToggle = new ToggleSwitch("Disable");
|
||||
disableToggle.selectedProperty().addListener((obs, old, val) -> findDisplayedList().ifPresent(lv -> {
|
||||
if (val != null) {
|
||||
lv.setDisable(val);
|
||||
}
|
||||
}));
|
||||
|
||||
var controls = new HBox(BLOCK_HGAP, borderedToggle, denseToggle, stripedToggle, edge2edgeToggle);
|
||||
controls.setAlignment(Pos.CENTER);
|
||||
|
||||
VBox.setVgrow(listWrapper, Priority.ALWAYS);
|
||||
|
||||
var playground = new VBox(
|
||||
BLOCK_VGAP,
|
||||
new HBox(new Label("Select an example:"), new Spacer(), disableToggle),
|
||||
exampleSelect,
|
||||
listWrapper,
|
||||
controls
|
||||
);
|
||||
playground.setMinHeight(100);
|
||||
|
||||
return playground;
|
||||
}
|
||||
|
||||
private ComboBox<Example> createExampleSelect() {
|
||||
var select = new ComboBox<Example>();
|
||||
select.setMaxWidth(Double.MAX_VALUE);
|
||||
select.getItems().setAll(Example.values());
|
||||
|
||||
select.getSelectionModel().selectedItemProperty().addListener((obs, old, val) -> {
|
||||
if (val == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
ListView<?> newList = createList(val);
|
||||
|
||||
// copy existing style classes and properties to the new list
|
||||
findDisplayedList().ifPresent(lv -> {
|
||||
List<String> currentStyles = lv.getStyleClass();
|
||||
currentStyles.remove("list-view");
|
||||
newList.getStyleClass().addAll(currentStyles);
|
||||
newList.setDisable(lv.isDisable());
|
||||
});
|
||||
|
||||
listWrapper.setCenter(newList);
|
||||
});
|
||||
|
||||
select.setConverter(new StringConverter<>() {
|
||||
@Override
|
||||
public String toString(Example example) {
|
||||
return example == null ? "" : example.getName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Example fromString(String s) {
|
||||
return Example.find(s);
|
||||
}
|
||||
});
|
||||
|
||||
return select;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onRendered() {
|
||||
super.onRendered();
|
||||
exampleSelect.getSelectionModel().selectFirst();
|
||||
}
|
||||
|
||||
private Optional<ListView<?>> findDisplayedList() {
|
||||
return listWrapper.getChildren().size() > 0
|
||||
? Optional.of((ListView<?>) listWrapper.getChildren().get(0))
|
||||
: Optional.empty();
|
||||
}
|
||||
|
||||
private void toggleListProperty(Consumer<ListView<?>> consumer) {
|
||||
findDisplayedList().ifPresent(lv -> {
|
||||
if (consumer != null) {
|
||||
consumer.accept(lv);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private ListView<?> createList(Example example) {
|
||||
switch (example) {
|
||||
case TEXT -> {
|
||||
return stringList();
|
||||
}
|
||||
case EDITABLE -> {
|
||||
return editableList();
|
||||
}
|
||||
case CHECK_BOX -> {
|
||||
return checkBoxList();
|
||||
}
|
||||
case CHOICE_BOX -> {
|
||||
return choiceBoxList();
|
||||
}
|
||||
case COMBO_BOX -> {
|
||||
return comboBoxList();
|
||||
}
|
||||
case NESTED_CONTROLS -> {
|
||||
return nestedControlsList();
|
||||
}
|
||||
default -> throw new IllegalArgumentException("Unexpected enum value: " + example);
|
||||
}
|
||||
}
|
||||
|
||||
private ListView<String> stringList() {
|
||||
var lv = new ListView<String>();
|
||||
lv.getItems().setAll(dataList.stream().map(bookStringConverter::toString).collect(Collectors.toList()));
|
||||
return lv;
|
||||
}
|
||||
|
||||
private ListView<String> editableList() {
|
||||
var lv = new ListView<String>();
|
||||
lv.setEditable(true);
|
||||
lv.setCellFactory(TextFieldListCell.forListView());
|
||||
lv.getItems().setAll(
|
||||
// small size to see the empty cells
|
||||
dataList.stream().limit(5).map(bookStringConverter::toString).collect(Collectors.toList())
|
||||
);
|
||||
return lv;
|
||||
}
|
||||
|
||||
private ListView<Book> checkBoxList() {
|
||||
var lv = new ListView<Book>();
|
||||
lv.setCellFactory(CheckBoxListCell.forListView(Book::stateProperty, bookStringConverter));
|
||||
lv.getItems().setAll(dataList.stream().limit(10).collect(Collectors.toList()));
|
||||
return lv;
|
||||
}
|
||||
|
||||
private ListView<Book> choiceBoxList() {
|
||||
var lv = new ListView<Book>();
|
||||
lv.setEditable(true);
|
||||
lv.setCellFactory(
|
||||
ChoiceBoxListCell.forListView(bookStringConverter, dataList.subList(0, 10).toArray(Book[]::new)));
|
||||
lv.getItems().setAll(dataList.stream().limit(10).collect(Collectors.toList()));
|
||||
return lv;
|
||||
}
|
||||
|
||||
private ListView<Book> comboBoxList() {
|
||||
var lv = new ListView<Book>();
|
||||
lv.setEditable(true);
|
||||
lv.setCellFactory(
|
||||
ComboBoxListCell.forListView(bookStringConverter, dataList.subList(0, 10).toArray(Book[]::new)));
|
||||
lv.getItems().setAll(dataList.stream().limit(10).collect(Collectors.toList()));
|
||||
return lv;
|
||||
}
|
||||
|
||||
private ListView<Book> nestedControlsList() {
|
||||
var lv = new ListView<Book>();
|
||||
lv.setCellFactory(book -> new NestedControlsListCell());
|
||||
lv.getItems().setAll(dataList.stream().limit(10).collect(Collectors.toList()));
|
||||
return lv;
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
|
||||
private enum Example {
|
||||
TEXT("Text"),
|
||||
EDITABLE("TextFieldListCell"),
|
||||
CHECK_BOX("CheckBoxListCell"),
|
||||
CHOICE_BOX("ChoiceBoxListCell"),
|
||||
COMBO_BOX("ComboBoxListCell"),
|
||||
NESTED_CONTROLS("Nested controls");
|
||||
|
||||
private final String name;
|
||||
|
||||
Example(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public static Example find(String name) {
|
||||
return Arrays.stream(Example.values())
|
||||
.filter(example -> Objects.equals(example.getName(), name))
|
||||
.findFirst()
|
||||
.orElse(null);
|
||||
}
|
||||
}
|
||||
|
||||
private static class BookStringConverter extends StringConverter<Book> {
|
||||
|
||||
private final List<Book> dataList;
|
||||
|
||||
public BookStringConverter(List<Book> dataList) {
|
||||
this.dataList = dataList;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString(Book book) {
|
||||
if (book == null) {
|
||||
return null;
|
||||
}
|
||||
return String.format("\"%s\" by %s", book.getTitle(), book.getAuthor());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Book fromString(String s) {
|
||||
if (s == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
int sep = s.indexOf("\" by");
|
||||
String title = s.substring(1, sep);
|
||||
String author = s.substring(sep + "\" by".length());
|
||||
|
||||
return dataList.stream()
|
||||
.filter(b -> Objects.equals(b.getTitle(), title) && Objects.equals(b.getAuthor(), author))
|
||||
.findFirst()
|
||||
.orElse(null);
|
||||
}
|
||||
}
|
||||
|
||||
private static class NestedControlsListCell extends ListCell<Book> {
|
||||
|
||||
private final HBox root;
|
||||
private final Label titleLabel;
|
||||
private final Hyperlink authorLink;
|
||||
|
||||
public NestedControlsListCell() {
|
||||
titleLabel = new Label();
|
||||
authorLink = new Hyperlink();
|
||||
|
||||
var purchaseBtn = new Button("Purchase");
|
||||
purchaseBtn.getStyleClass().addAll(ACCENT);
|
||||
purchaseBtn.setGraphic(new FontIcon(Feather.SHOPPING_CART));
|
||||
|
||||
root = new HBox(5,
|
||||
titleLabel,
|
||||
new Label(" by"),
|
||||
authorLink,
|
||||
new Spacer(),
|
||||
purchaseBtn
|
||||
);
|
||||
root.setAlignment(Pos.CENTER_LEFT);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateItem(Book book, boolean empty) {
|
||||
super.updateItem(book, empty);
|
||||
|
||||
if (empty) {
|
||||
setGraphic(null);
|
||||
return;
|
||||
}
|
||||
|
||||
titleLabel.setText("\"" + book.getTitle() + "\"");
|
||||
authorLink.setText(book.getAuthor());
|
||||
setGraphic(root);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,477 @@
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
|
||||
package atlantafx.sampler.page.components;
|
||||
|
||||
import atlantafx.base.controls.Spacer;
|
||||
import atlantafx.base.controls.ToggleSwitch;
|
||||
import atlantafx.base.theme.Styles;
|
||||
import atlantafx.base.theme.Tweaks;
|
||||
import atlantafx.base.util.BBCodeParser;
|
||||
import atlantafx.sampler.fake.domain.Book;
|
||||
import atlantafx.sampler.page.ExampleBox;
|
||||
import atlantafx.sampler.page.OutlinePage;
|
||||
import atlantafx.sampler.page.Snippet;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.stream.Collectors;
|
||||
import javafx.collections.FXCollections;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.scene.control.Button;
|
||||
import javafx.scene.control.ComboBox;
|
||||
import javafx.scene.control.Hyperlink;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.control.ListCell;
|
||||
import javafx.scene.control.ListView;
|
||||
import javafx.scene.control.cell.CheckBoxListCell;
|
||||
import javafx.scene.control.cell.ChoiceBoxListCell;
|
||||
import javafx.scene.control.cell.ComboBoxListCell;
|
||||
import javafx.scene.control.cell.TextFieldListCell;
|
||||
import javafx.scene.layout.HBox;
|
||||
import javafx.scene.layout.Priority;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import javafx.scene.layout.VBox;
|
||||
import javafx.util.StringConverter;
|
||||
import org.kordamp.ikonli.feather.Feather;
|
||||
import org.kordamp.ikonli.javafx.FontIcon;
|
||||
|
||||
public class ListViewPage extends OutlinePage {
|
||||
|
||||
public static final String NAME = "ListView";
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return NAME;
|
||||
}
|
||||
|
||||
public ListViewPage() {
|
||||
super();
|
||||
|
||||
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.""");
|
||||
addSection("Usage", usageExample());
|
||||
addSection("Row Style", rowStyleExample());
|
||||
addSection("Selection Color", selectionColorExample());
|
||||
addSection("Edge-to-Edge", edge2EdgeExample());
|
||||
addSection("Playground", playground());
|
||||
}
|
||||
|
||||
private ExampleBox usageExample() {
|
||||
//snippet_1:start
|
||||
var names = FXCollections.observableArrayList("Julia", "Ian", "Sue");
|
||||
var lv = new ListView<>(names);
|
||||
lv.setMinHeight(200);
|
||||
//snippet_1:end
|
||||
|
||||
var box = new VBox(20, lv);
|
||||
var description = BBCodeParser.createFormattedText("""
|
||||
You can create a list view by instantiating the \
|
||||
[font=monospace]javafx.scene.control.ListView[/font] class."""
|
||||
);
|
||||
|
||||
return new ExampleBox(box, new Snippet(getClass(), 1), description);
|
||||
}
|
||||
|
||||
private ExampleBox rowStyleExample() {
|
||||
//snippet_2:start
|
||||
var names = FXCollections.observableArrayList("Julia", "Ian", "Sue");
|
||||
var lv = new ListView<>(names);
|
||||
lv.setMinHeight(200);
|
||||
|
||||
var borderToggle = new ToggleSwitch("Bordered");
|
||||
borderToggle.selectedProperty().addListener(
|
||||
(obs, old, val) -> Styles.toggleStyleClass(lv, Styles.BORDERED)
|
||||
);
|
||||
|
||||
var stripeToggle = new ToggleSwitch("Striped");
|
||||
stripeToggle.selectedProperty().addListener(
|
||||
(obs, old, val) -> Styles.toggleStyleClass(lv, Styles.STRIPED)
|
||||
);
|
||||
|
||||
var denseToggle = new ToggleSwitch("Dense");
|
||||
denseToggle.selectedProperty().addListener(
|
||||
(obs, old, val) -> Styles.toggleStyleClass(lv, Styles.DENSE)
|
||||
);
|
||||
//snippet_2:end
|
||||
|
||||
var togglesBox = new HBox(HGAP_20, borderToggle, stripeToggle, denseToggle);
|
||||
togglesBox.setAlignment(Pos.CENTER);
|
||||
|
||||
var box = new VBox(VGAP_10, lv, togglesBox);
|
||||
|
||||
var description = BBCodeParser.createFormattedText("""
|
||||
The [i]ListView[/i] rows can be styled simply by adding CSS classes:
|
||||
|
||||
[ul]
|
||||
[li][code]Styles.BORDERED[/code] - adds borders between the rows.[/li]
|
||||
[li][code]Styles.STRIPED[/code] - adds zebra-striping.[/li]
|
||||
[li][code]Styles.DENSE[/code] - makes the [i]ListView[/i] more compact by cutting \
|
||||
cell padding.[/li][/ul]"""
|
||||
);
|
||||
|
||||
return new ExampleBox(box, new Snippet(getClass(), 2), description);
|
||||
}
|
||||
|
||||
private ExampleBox selectionColorExample() {
|
||||
var style = """
|
||||
-color-cell-bg-selected: -color-accent-emphasis;
|
||||
-color-cell-fg-selected: -color-fg-emphasis;
|
||||
-color-cell-bg-selected-focused: -color-accent-emphasis;
|
||||
-color-cell-fg-selected-focused: -color-fg-emphasis;""";
|
||||
|
||||
//snippet_3:start
|
||||
var names = FXCollections.observableArrayList("Julia", "Ian", "Sue");
|
||||
var lv = new ListView<>(names);
|
||||
lv.setMinHeight(200);
|
||||
// -color-cell-bg-selected: -color-accent-emphasis;
|
||||
// -color-cell-fg-selected: -color-fg-emphasis;
|
||||
// -color-cell-bg-selected-focused: -color-accent-emphasis;
|
||||
// -color-cell-fg-selected-focused: -color-fg-emphasis;
|
||||
lv.setStyle(style);
|
||||
lv.getSelectionModel().selectFirst();
|
||||
//snippet_3:end
|
||||
|
||||
var box = new HBox(lv);
|
||||
var description = BBCodeParser.createFormattedText("""
|
||||
Cell selection color (and more) can be changed via looked-up color variables. \
|
||||
You can find all supported color variables in the docs."""
|
||||
);
|
||||
|
||||
return new ExampleBox(box, new Snippet(getClass(), 3), description);
|
||||
}
|
||||
|
||||
private ExampleBox edge2EdgeExample() {
|
||||
//snippet_5:start
|
||||
var names = FXCollections.observableArrayList("Julia", "Ian", "Sue");
|
||||
var lv = new ListView<>(names);
|
||||
lv.setMinHeight(150);
|
||||
lv.getStyleClass().add(Tweaks.EDGE_TO_EDGE);
|
||||
//snippet_5:end
|
||||
|
||||
var box = new VBox(lv);
|
||||
box.setStyle("""
|
||||
-fx-border-color: -color-accent-emphasis;
|
||||
-fx-border-width: 2px;"""
|
||||
);
|
||||
|
||||
var description = BBCodeParser.createFormattedText("""
|
||||
Use [code]Tweaks.EDGE_TO_EDGE[/code] style class to remove the [i]ListView[/i] \
|
||||
outer borders. This is useful if you want to place the table into external \
|
||||
container that already has its own borders."""
|
||||
);
|
||||
|
||||
return new ExampleBox(box, new Snippet(getClass(), 5), description);
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
// Playground //
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
|
||||
private final List<Book> dataList = generate(() -> Book.random(FAKER), 50);
|
||||
private final StringConverter<Book> bookStringConverter = new BookStringConverter(dataList);
|
||||
private final StackPane listWrapper = new StackPane();
|
||||
private final ComboBox<Example> exampleSelect = createExampleSelect();
|
||||
|
||||
private VBox playground() {
|
||||
var borderToggle = new ToggleSwitch("Bordered");
|
||||
borderToggle.selectedProperty().addListener(
|
||||
(obs, old, value) -> toggleListProperty(lv ->
|
||||
Styles.toggleStyleClass(lv, Styles.BORDERED)
|
||||
));
|
||||
|
||||
var denseToggle = new ToggleSwitch("Dense");
|
||||
denseToggle.selectedProperty().addListener(
|
||||
(obs, old, value) -> toggleListProperty(lv ->
|
||||
Styles.toggleStyleClass(lv, Styles.DENSE)
|
||||
));
|
||||
|
||||
var stripeToggle = new ToggleSwitch("Striped");
|
||||
stripeToggle.selectedProperty().addListener(
|
||||
(obs, old, value) -> toggleListProperty(lv ->
|
||||
Styles.toggleStyleClass(lv, Styles.STRIPED)
|
||||
));
|
||||
|
||||
var edge2edgeToggle = new ToggleSwitch("Edge to edge");
|
||||
edge2edgeToggle.selectedProperty().addListener(
|
||||
(obs, old, value) -> toggleListProperty(lv ->
|
||||
Styles.toggleStyleClass(lv, Tweaks.EDGE_TO_EDGE)
|
||||
));
|
||||
|
||||
var controls = new HBox(
|
||||
HGAP_20,
|
||||
borderToggle, denseToggle, stripeToggle, edge2edgeToggle
|
||||
);
|
||||
controls.setAlignment(Pos.CENTER);
|
||||
|
||||
listWrapper.setMinHeight(400);
|
||||
VBox.setVgrow(listWrapper, Priority.ALWAYS);
|
||||
|
||||
var description = BBCodeParser.createFormattedText("""
|
||||
The playground demonstrates the most important [i]ListView[/i] features \
|
||||
and also serves as an object for monkey testing."""
|
||||
);
|
||||
|
||||
return new VBox(
|
||||
VGAP_10,
|
||||
description,
|
||||
new HBox(new Label("Select an example:")),
|
||||
exampleSelect,
|
||||
listWrapper,
|
||||
controls
|
||||
);
|
||||
}
|
||||
|
||||
private ComboBox<Example> createExampleSelect() {
|
||||
var select = new ComboBox<Example>();
|
||||
select.setMaxWidth(Double.MAX_VALUE);
|
||||
select.getItems().setAll(Example.values());
|
||||
|
||||
select.getSelectionModel().selectedItemProperty().addListener((obs, old, val) -> {
|
||||
if (val == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
ListView<?> newList = createList(val);
|
||||
|
||||
// copy existing style classes and properties to the new list
|
||||
findDisplayedList().ifPresent(lv -> {
|
||||
List<String> currentStyles = lv.getStyleClass();
|
||||
currentStyles.remove("list-view");
|
||||
newList.getStyleClass().addAll(currentStyles);
|
||||
newList.setDisable(lv.isDisable());
|
||||
});
|
||||
|
||||
listWrapper.getChildren().setAll(newList);
|
||||
});
|
||||
|
||||
select.setConverter(new StringConverter<>() {
|
||||
@Override
|
||||
public String toString(Example example) {
|
||||
return example == null ? "" : example.getName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Example fromString(String s) {
|
||||
return Example.find(s);
|
||||
}
|
||||
});
|
||||
|
||||
return select;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onRendered() {
|
||||
super.onRendered();
|
||||
exampleSelect.getSelectionModel().selectFirst();
|
||||
}
|
||||
|
||||
private Optional<ListView<?>> findDisplayedList() {
|
||||
return listWrapper.getChildren().size() > 0
|
||||
? Optional.of((ListView<?>) listWrapper.getChildren().get(0))
|
||||
: Optional.empty();
|
||||
}
|
||||
|
||||
private void toggleListProperty(Consumer<ListView<?>> consumer) {
|
||||
findDisplayedList().ifPresent(lv -> {
|
||||
if (consumer != null) {
|
||||
consumer.accept(lv);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private ListView<?> createList(Example example) {
|
||||
switch (example) {
|
||||
case TEXT -> {
|
||||
return stringList();
|
||||
}
|
||||
case EDITABLE -> {
|
||||
return editableList();
|
||||
}
|
||||
case CHECK_BOX -> {
|
||||
return checkBoxList();
|
||||
}
|
||||
case CHOICE_BOX -> {
|
||||
return choiceBoxList();
|
||||
}
|
||||
case COMBO_BOX -> {
|
||||
return comboBoxList();
|
||||
}
|
||||
case NESTED_CONTROLS -> {
|
||||
return nestedControlsList();
|
||||
}
|
||||
default -> throw new IllegalArgumentException("Unexpected enum value: " + example);
|
||||
}
|
||||
}
|
||||
|
||||
private ListView<String> stringList() {
|
||||
var lv = new ListView<String>();
|
||||
lv.getItems().setAll(dataList.stream()
|
||||
.map(bookStringConverter::toString).collect(Collectors.toList())
|
||||
);
|
||||
return lv;
|
||||
}
|
||||
|
||||
private ListView<String> editableList() {
|
||||
var lv = new ListView<String>();
|
||||
lv.setEditable(true);
|
||||
lv.setCellFactory(TextFieldListCell.forListView());
|
||||
lv.getItems().setAll(
|
||||
// small size to see the empty cells
|
||||
dataList.stream().limit(5).map(
|
||||
bookStringConverter::toString).collect(Collectors.toList())
|
||||
);
|
||||
return lv;
|
||||
}
|
||||
|
||||
private ListView<Book> checkBoxList() {
|
||||
var lv = new ListView<Book>();
|
||||
lv.setCellFactory(
|
||||
CheckBoxListCell.forListView(Book::stateProperty, bookStringConverter)
|
||||
);
|
||||
lv.getItems().setAll(
|
||||
dataList.stream().limit(10).collect(Collectors.toList())
|
||||
);
|
||||
return lv;
|
||||
}
|
||||
|
||||
private ListView<Book> choiceBoxList() {
|
||||
var lv = new ListView<Book>();
|
||||
lv.setEditable(true);
|
||||
lv.setCellFactory(
|
||||
ChoiceBoxListCell.forListView(
|
||||
bookStringConverter,
|
||||
dataList.subList(0, 10).toArray(Book[]::new))
|
||||
);
|
||||
lv.getItems().setAll(
|
||||
dataList.stream().limit(10).collect(Collectors.toList())
|
||||
);
|
||||
return lv;
|
||||
}
|
||||
|
||||
private ListView<Book> comboBoxList() {
|
||||
var lv = new ListView<Book>();
|
||||
lv.setEditable(true);
|
||||
lv.setCellFactory(
|
||||
ComboBoxListCell.forListView(
|
||||
bookStringConverter,
|
||||
dataList.subList(0, 10).toArray(Book[]::new))
|
||||
);
|
||||
lv.getItems().setAll(
|
||||
dataList.stream().limit(10).collect(Collectors.toList())
|
||||
);
|
||||
return lv;
|
||||
}
|
||||
|
||||
private ListView<Book> nestedControlsList() {
|
||||
var lv = new ListView<Book>();
|
||||
lv.setCellFactory(book -> new NestedControlsListCell());
|
||||
lv.getItems().setAll(
|
||||
dataList.stream().limit(10).collect(Collectors.toList())
|
||||
);
|
||||
return lv;
|
||||
}
|
||||
|
||||
private enum Example {
|
||||
TEXT("Text"),
|
||||
EDITABLE("TextFieldListCell"),
|
||||
CHECK_BOX("CheckBoxListCell"),
|
||||
CHOICE_BOX("ChoiceBoxListCell"),
|
||||
COMBO_BOX("ComboBoxListCell"),
|
||||
NESTED_CONTROLS("Nested controls");
|
||||
|
||||
private final String name;
|
||||
|
||||
Example(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public static Example find(String name) {
|
||||
return Arrays.stream(Example.values())
|
||||
.filter(example -> Objects.equals(example.getName(), name))
|
||||
.findFirst()
|
||||
.orElse(null);
|
||||
}
|
||||
}
|
||||
|
||||
private static class BookStringConverter extends StringConverter<Book> {
|
||||
|
||||
private final List<Book> dataList;
|
||||
|
||||
public BookStringConverter(List<Book> dataList) {
|
||||
this.dataList = dataList;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString(Book book) {
|
||||
if (book == null) {
|
||||
return null;
|
||||
}
|
||||
return String.format("\"%s\" by %s", book.getTitle(), book.getAuthor());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Book fromString(String s) {
|
||||
if (s == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
int sep = s.indexOf("\" by");
|
||||
String title = s.substring(1, sep);
|
||||
String author = s.substring(sep + "\" by".length());
|
||||
|
||||
return dataList.stream()
|
||||
.filter(b -> Objects.equals(b.getTitle(), title)
|
||||
&& Objects.equals(b.getAuthor(), author)
|
||||
)
|
||||
.findFirst()
|
||||
.orElse(null);
|
||||
}
|
||||
}
|
||||
|
||||
private static class NestedControlsListCell extends ListCell<Book> {
|
||||
|
||||
private final HBox root;
|
||||
private final Label titleLabel;
|
||||
private final Hyperlink authorLink;
|
||||
|
||||
public NestedControlsListCell() {
|
||||
titleLabel = new Label();
|
||||
authorLink = new Hyperlink();
|
||||
|
||||
var purchaseBtn = new Button("Purchase");
|
||||
purchaseBtn.getStyleClass().addAll(Styles.ACCENT);
|
||||
purchaseBtn.setGraphic(new FontIcon(Feather.SHOPPING_CART));
|
||||
|
||||
root = new HBox(5,
|
||||
titleLabel,
|
||||
new Label(" by"),
|
||||
authorLink,
|
||||
new Spacer(),
|
||||
purchaseBtn
|
||||
);
|
||||
root.setAlignment(Pos.CENTER_LEFT);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateItem(Book book, boolean empty) {
|
||||
super.updateItem(book, empty);
|
||||
|
||||
if (empty) {
|
||||
setGraphic(null);
|
||||
return;
|
||||
}
|
||||
|
||||
titleLabel.setText("\"" + book.getTitle() + "\"");
|
||||
authorLink.setText(book.getAuthor());
|
||||
setGraphic(root);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,236 @@
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
|
||||
package atlantafx.sampler.page.components;
|
||||
|
||||
import static javafx.scene.input.KeyCombination.CONTROL_DOWN;
|
||||
import static javafx.scene.input.KeyCombination.SHIFT_DOWN;
|
||||
|
||||
import atlantafx.base.controls.CaptionMenuItem;
|
||||
import atlantafx.sampler.page.AbstractPage;
|
||||
import java.util.stream.IntStream;
|
||||
import javafx.event.ActionEvent;
|
||||
import javafx.event.EventHandler;
|
||||
import javafx.scene.control.CheckMenuItem;
|
||||
import javafx.scene.control.Menu;
|
||||
import javafx.scene.control.MenuBar;
|
||||
import javafx.scene.control.MenuItem;
|
||||
import javafx.scene.control.RadioMenuItem;
|
||||
import javafx.scene.control.SeparatorMenuItem;
|
||||
import javafx.scene.control.ToggleGroup;
|
||||
import javafx.scene.input.KeyCode;
|
||||
import javafx.scene.input.KeyCodeCombination;
|
||||
import javafx.scene.input.KeyCombination;
|
||||
import net.datafaker.Faker;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.kordamp.ikonli.Ikon;
|
||||
import org.kordamp.ikonli.feather.Feather;
|
||||
import org.kordamp.ikonli.javafx.FontIcon;
|
||||
import org.kordamp.ikonli.material2.Material2OutlinedAL;
|
||||
|
||||
public class MenuBarPage extends AbstractPage {
|
||||
|
||||
public static final String NAME = "MenuBar";
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return NAME;
|
||||
}
|
||||
|
||||
public MenuBarPage() {
|
||||
super();
|
||||
|
||||
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 \
|
||||
allow users to access various features and functions of the application."""
|
||||
);
|
||||
addNode(menuBarSample());
|
||||
}
|
||||
|
||||
private ExampleMenuBar menuBarSample() {
|
||||
return new ExampleMenuBar();
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
|
||||
public static class ExampleMenuBar extends MenuBar {
|
||||
|
||||
private static final Faker FAKER = new Faker();
|
||||
private static final EventHandler<ActionEvent> SYSTEM_OUT = System.out::println;
|
||||
|
||||
public ExampleMenuBar() {
|
||||
getMenus().addAll(
|
||||
fileMenu(),
|
||||
editMenu(),
|
||||
viewMenu(),
|
||||
toolsMenu(),
|
||||
aboutMenu()
|
||||
);
|
||||
}
|
||||
|
||||
private Menu fileMenu() {
|
||||
var menu = new Menu("_File");
|
||||
menu.setMnemonicParsing(true);
|
||||
menu.setOnAction(SYSTEM_OUT);
|
||||
|
||||
var newMenu = createItem(
|
||||
"_New", null, new KeyCodeCombination(KeyCode.N, CONTROL_DOWN)
|
||||
);
|
||||
newMenu.setMnemonicParsing(true);
|
||||
newMenu.setOnAction(SYSTEM_OUT);
|
||||
|
||||
var openRecentMenu = new Menu("Open _Recent");
|
||||
openRecentMenu.setMnemonicParsing(true);
|
||||
openRecentMenu.setOnAction(SYSTEM_OUT);
|
||||
openRecentMenu.getItems().addAll(
|
||||
IntStream.range(0, 10)
|
||||
.mapToObj(x -> new MenuItem(FAKER.file().fileName()))
|
||||
.toList()
|
||||
);
|
||||
|
||||
menu.getItems().addAll(
|
||||
newMenu,
|
||||
new SeparatorMenuItem(),
|
||||
createItem(
|
||||
"Open", Feather.FOLDER, new KeyCodeCombination(KeyCode.O, CONTROL_DOWN)
|
||||
),
|
||||
openRecentMenu,
|
||||
new SeparatorMenuItem(),
|
||||
createItem(
|
||||
"Save", Feather.SAVE, new KeyCodeCombination(KeyCode.S, CONTROL_DOWN)
|
||||
),
|
||||
new MenuItem("Save As"),
|
||||
new SeparatorMenuItem(),
|
||||
new MenuItem("Exit")
|
||||
);
|
||||
return menu;
|
||||
}
|
||||
|
||||
private Menu editMenu() {
|
||||
var menu = new Menu("_Edit");
|
||||
menu.setMnemonicParsing(true);
|
||||
menu.setOnAction(SYSTEM_OUT);
|
||||
menu.getItems().addAll(
|
||||
createItem(
|
||||
"Undo", Feather.CORNER_DOWN_LEFT, new KeyCodeCombination(KeyCode.Z, CONTROL_DOWN)
|
||||
),
|
||||
createItem(
|
||||
"Redo", Feather.CORNER_DOWN_RIGHT, new KeyCodeCombination(KeyCode.Y, CONTROL_DOWN)
|
||||
),
|
||||
new SeparatorMenuItem(),
|
||||
createItem(
|
||||
"Cut", Feather.SCISSORS, new KeyCodeCombination(KeyCode.X, CONTROL_DOWN)
|
||||
),
|
||||
createItem(
|
||||
"Copy", Feather.COPY, new KeyCodeCombination(KeyCode.C, CONTROL_DOWN)
|
||||
),
|
||||
createItem(
|
||||
"Paste", Feather.CORNER_DOWN_LEFT, new KeyCodeCombination(KeyCode.V, CONTROL_DOWN)
|
||||
)
|
||||
);
|
||||
return menu;
|
||||
}
|
||||
|
||||
private Menu viewMenu() {
|
||||
var menu = new Menu("_View");
|
||||
menu.setMnemonicParsing(true);
|
||||
menu.setOnAction(SYSTEM_OUT);
|
||||
|
||||
var showToolbarItem = new CheckMenuItem(
|
||||
"Show Toolbar", new FontIcon(Feather.TOOL)
|
||||
);
|
||||
showToolbarItem.setSelected(true);
|
||||
showToolbarItem.setAccelerator(
|
||||
new KeyCodeCombination(KeyCode.T, CONTROL_DOWN)
|
||||
);
|
||||
|
||||
var showGridItem = new CheckMenuItem(
|
||||
"Show Grid", new FontIcon(Feather.GRID));
|
||||
|
||||
var captionItem = new CaptionMenuItem("Layout");
|
||||
|
||||
var viewToggleGroup = new ToggleGroup();
|
||||
|
||||
var toggleItem1 = new RadioMenuItem(
|
||||
"Single", new FontIcon(Material2OutlinedAL.LOOKS_ONE)
|
||||
);
|
||||
toggleItem1.setSelected(true);
|
||||
toggleItem1.setToggleGroup(viewToggleGroup);
|
||||
|
||||
var toggleItem2 = new RadioMenuItem(
|
||||
"Two Columns", new FontIcon(Material2OutlinedAL.LOOKS_TWO)
|
||||
);
|
||||
toggleItem2.setToggleGroup(viewToggleGroup);
|
||||
|
||||
var toggleItem3 = new RadioMenuItem(
|
||||
"Three Columns", new FontIcon(Material2OutlinedAL.LOOKS_3)
|
||||
);
|
||||
toggleItem3.setToggleGroup(viewToggleGroup);
|
||||
|
||||
menu.getItems().addAll(
|
||||
showToolbarItem,
|
||||
showGridItem,
|
||||
new SeparatorMenuItem(),
|
||||
captionItem,
|
||||
toggleItem1,
|
||||
toggleItem2,
|
||||
toggleItem3
|
||||
);
|
||||
return menu;
|
||||
}
|
||||
|
||||
private Menu toolsMenu() {
|
||||
var menu = new Menu("_Tools");
|
||||
menu.setMnemonicParsing(true);
|
||||
menu.setOnAction(SYSTEM_OUT);
|
||||
menu.setDisable(true);
|
||||
return menu;
|
||||
}
|
||||
|
||||
private Menu aboutMenu() {
|
||||
var menu = new Menu(
|
||||
"_About", new FontIcon(Feather.HELP_CIRCLE)
|
||||
);
|
||||
menu.setMnemonicParsing(true);
|
||||
menu.setOnAction(SYSTEM_OUT);
|
||||
|
||||
var deeplyNestedMenu = new Menu(
|
||||
"Very...", null,
|
||||
new Menu("Very...", null,
|
||||
new Menu("Deeply", null,
|
||||
new Menu("Nested", null,
|
||||
new MenuItem("Menu")
|
||||
))));
|
||||
|
||||
// NOTE: this won't be displayed because right container is reserved for submenu indication
|
||||
deeplyNestedMenu.setAccelerator(
|
||||
new KeyCodeCombination(KeyCode.DIGIT1, SHIFT_DOWN, CONTROL_DOWN)
|
||||
);
|
||||
|
||||
menu.getItems().addAll(
|
||||
new MenuItem("Help"),
|
||||
new MenuItem("About"),
|
||||
new SeparatorMenuItem(),
|
||||
deeplyNestedMenu
|
||||
);
|
||||
return menu;
|
||||
}
|
||||
|
||||
private MenuItem createItem(@Nullable String text,
|
||||
@Nullable Ikon graphic,
|
||||
@Nullable KeyCombination accelerator) {
|
||||
|
||||
var item = new MenuItem(text);
|
||||
|
||||
if (graphic != null) {
|
||||
item.setGraphic(new FontIcon(graphic));
|
||||
}
|
||||
|
||||
if (accelerator != null) {
|
||||
item.setAccelerator(accelerator);
|
||||
}
|
||||
|
||||
return item;
|
||||
}
|
||||
}
|
||||
}
|
@ -2,19 +2,12 @@
|
||||
|
||||
package atlantafx.sampler.page.components;
|
||||
|
||||
import static atlantafx.base.theme.Styles.ACCENT;
|
||||
import static atlantafx.base.theme.Styles.BUTTON_ICON;
|
||||
import static atlantafx.base.theme.Styles.BUTTON_OUTLINED;
|
||||
import static atlantafx.base.theme.Styles.DANGER;
|
||||
import static atlantafx.base.theme.Styles.FLAT;
|
||||
import static atlantafx.base.theme.Styles.SUCCESS;
|
||||
import static atlantafx.sampler.page.SampleBlock.BLOCK_HGAP;
|
||||
import static atlantafx.sampler.page.SampleBlock.BLOCK_VGAP;
|
||||
|
||||
import atlantafx.base.theme.Styles;
|
||||
import atlantafx.base.theme.Tweaks;
|
||||
import atlantafx.sampler.page.AbstractPage;
|
||||
import atlantafx.sampler.page.Page;
|
||||
import atlantafx.sampler.page.SampleBlock;
|
||||
import atlantafx.base.util.BBCodeParser;
|
||||
import atlantafx.sampler.page.ExampleBox;
|
||||
import atlantafx.sampler.page.OutlinePage;
|
||||
import atlantafx.sampler.page.Snippet;
|
||||
import java.util.stream.IntStream;
|
||||
import javafx.geometry.Side;
|
||||
import javafx.scene.control.MenuButton;
|
||||
@ -23,14 +16,12 @@ import javafx.scene.control.SplitMenuButton;
|
||||
import javafx.scene.layout.FlowPane;
|
||||
import javafx.scene.layout.GridPane;
|
||||
import javafx.scene.layout.HBox;
|
||||
import javafx.scene.layout.VBox;
|
||||
import org.kordamp.ikonli.feather.Feather;
|
||||
import org.kordamp.ikonli.javafx.FontIcon;
|
||||
|
||||
public class MenuButtonPage extends AbstractPage {
|
||||
public class MenuButtonPage extends OutlinePage {
|
||||
|
||||
public static final String NAME = "MenuButton";
|
||||
private static final int PREF_WIDTH = 150;
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
@ -39,91 +30,114 @@ public class MenuButtonPage extends AbstractPage {
|
||||
|
||||
public MenuButtonPage() {
|
||||
super();
|
||||
setUserContent(new VBox(
|
||||
Page.PAGE_VGAP,
|
||||
expandingHBox(basicSample(), iconOnlySample()),
|
||||
expandingHBox(coloredSample(), outlinedSample()),
|
||||
expandingHBox(popupSideSample(), noArrowSample()),
|
||||
disabledSample()
|
||||
|
||||
));
|
||||
addFormattedText("""
|
||||
[i]MenuButton[/i] is a button which, when clicked or pressed, will \
|
||||
show a context (dropdown) menu.
|
||||
|
||||
[i]SplitMenuButton[/i] is a variation of menu button. It is broken into two \
|
||||
pieces, the [i]action[/i] area and the [i]menu open[/i] area. If the user clicks \
|
||||
in the action area, the [i]SplitMenuButton[/i] will act similarly to [i]Button[/i], \
|
||||
while the menu open area of the control will show a context menu if clicked."""
|
||||
);
|
||||
addSection("Usage", usageExample());
|
||||
addSection("Colored", coloredExample());
|
||||
addSection("Popup Side", popupSideExample());
|
||||
addSection("Icon Button", iconButtonExample());
|
||||
addSection("Outlined", outlinedExample());
|
||||
addSection("No Arrow", noArrowExample());
|
||||
}
|
||||
|
||||
private SampleBlock basicSample() {
|
||||
var basicMenuBtn = new MenuButton("_Menu Button");
|
||||
basicMenuBtn.getItems().setAll(createItems(5));
|
||||
basicMenuBtn.setMnemonicParsing(true);
|
||||
private ExampleBox usageExample() {
|
||||
//snippet_1:start
|
||||
var normalMenuBtn = new MenuButton("_Menu Button");
|
||||
normalMenuBtn.getItems().setAll(createItems(5));
|
||||
normalMenuBtn.setMnemonicParsing(true);
|
||||
|
||||
var basicSplitBtn = new SplitMenuButton(createItems(5));
|
||||
basicSplitBtn.setText("_Split Menu Button");
|
||||
basicSplitBtn.setMnemonicParsing(true);
|
||||
var normalSplitBtn = new SplitMenuButton(createItems(5));
|
||||
normalSplitBtn.setText("_Split Menu Button");
|
||||
normalSplitBtn.setMnemonicParsing(true);
|
||||
|
||||
var flatMenuBtn = new MenuButton("Flat");
|
||||
flatMenuBtn.getItems().setAll(createItems(5));
|
||||
flatMenuBtn.getStyleClass().add(FLAT);
|
||||
flatMenuBtn.getStyleClass().add(Styles.FLAT);
|
||||
|
||||
var flatSplitBtn = new SplitMenuButton(createItems(5));
|
||||
flatSplitBtn.setText("Flat");
|
||||
flatSplitBtn.getStyleClass().add(FLAT);
|
||||
flatSplitBtn.getStyleClass().add(Styles.FLAT);
|
||||
//snippet_1:end
|
||||
|
||||
var content = new FlowPane(BLOCK_HGAP, BLOCK_VGAP);
|
||||
content.getChildren().addAll(basicMenuBtn, basicSplitBtn, flatMenuBtn, flatSplitBtn);
|
||||
var box = new FlowPane(
|
||||
HGAP_20, VGAP_10,
|
||||
normalMenuBtn, normalSplitBtn, flatMenuBtn, flatSplitBtn
|
||||
);
|
||||
|
||||
return new SampleBlock("Basic", content);
|
||||
var description = BBCodeParser.createFormattedText("""
|
||||
Just like the [i]Button[/i], [i]MenuButton[/i] comes with four \
|
||||
CSS variants: normal (default), colored, outlined, and flat (or text). \
|
||||
To change the appearance of the [i]Button[/i], you set the corresponding \
|
||||
style classes that work as modifiers."""
|
||||
);
|
||||
|
||||
return new ExampleBox(box, new Snippet(getClass(), 1), description);
|
||||
}
|
||||
|
||||
private SampleBlock coloredSample() {
|
||||
private ExampleBox coloredExample() {
|
||||
//snippet_2:start
|
||||
var accentMenuBtn = new MenuButton("_Accent");
|
||||
accentMenuBtn.getItems().setAll(createItems(5));
|
||||
accentMenuBtn.getStyleClass().add(ACCENT);
|
||||
accentMenuBtn.getStyleClass().add(Styles.ACCENT);
|
||||
accentMenuBtn.setMnemonicParsing(true);
|
||||
accentMenuBtn.setPrefWidth(PREF_WIDTH);
|
||||
accentMenuBtn.setPrefWidth(150);
|
||||
|
||||
var accentSplitBtn = new SplitMenuButton(createItems(5));
|
||||
accentSplitBtn.setText("Accent");
|
||||
accentSplitBtn.getStyleClass().add(ACCENT);
|
||||
accentSplitBtn.setPrefWidth(PREF_WIDTH);
|
||||
accentSplitBtn.getStyleClass().add(Styles.ACCENT);
|
||||
accentSplitBtn.setPrefWidth(150);
|
||||
|
||||
var successMenuBtn = new MenuButton("Success");
|
||||
successMenuBtn.getItems().setAll(createItems(5));
|
||||
successMenuBtn.setGraphic(new FontIcon(Feather.CHECK));
|
||||
successMenuBtn.getStyleClass().add(SUCCESS);
|
||||
successMenuBtn.setPrefWidth(PREF_WIDTH);
|
||||
successMenuBtn.getStyleClass().add(Styles.SUCCESS);
|
||||
successMenuBtn.setPrefWidth(150);
|
||||
|
||||
var successSplitBtn = new SplitMenuButton(createItems(5));
|
||||
successSplitBtn.setGraphic(new FontIcon(Feather.CHECK));
|
||||
successSplitBtn.setText("_Success");
|
||||
successSplitBtn.getStyleClass().add(SUCCESS);
|
||||
successSplitBtn.getStyleClass().add(Styles.SUCCESS);
|
||||
successSplitBtn.setMnemonicParsing(true);
|
||||
successSplitBtn.setPrefWidth(PREF_WIDTH);
|
||||
successSplitBtn.setPrefWidth(150);
|
||||
|
||||
var dangerMenuBtn = new MenuButton("Danger");
|
||||
dangerMenuBtn.setGraphic(new FontIcon(Feather.TRASH));
|
||||
dangerMenuBtn.getItems().setAll(createItems(5));
|
||||
dangerMenuBtn.getStyleClass().add(DANGER);
|
||||
dangerMenuBtn.setPrefWidth(PREF_WIDTH);
|
||||
dangerMenuBtn.getStyleClass().add(Styles.DANGER);
|
||||
dangerMenuBtn.setPrefWidth(150);
|
||||
|
||||
var dangerSplitBtn = new SplitMenuButton(createItems(5));
|
||||
dangerSplitBtn.setGraphic(new FontIcon(Feather.TRASH));
|
||||
dangerSplitBtn.setText("_Danger");
|
||||
dangerSplitBtn.getStyleClass().add(DANGER);
|
||||
dangerSplitBtn.getStyleClass().add(Styles.DANGER);
|
||||
dangerSplitBtn.setMnemonicParsing(true);
|
||||
dangerSplitBtn.setPrefWidth(PREF_WIDTH);
|
||||
dangerSplitBtn.setPrefWidth(150);
|
||||
//snippet_2:end
|
||||
|
||||
var content = new GridPane();
|
||||
content.setHgap(BLOCK_HGAP);
|
||||
content.setVgap(BLOCK_VGAP);
|
||||
content.add(accentMenuBtn, 0, 0);
|
||||
content.add(accentSplitBtn, 1, 0);
|
||||
content.add(successMenuBtn, 0, 1);
|
||||
content.add(successSplitBtn, 1, 1);
|
||||
content.add(dangerMenuBtn, 0, 2);
|
||||
content.add(dangerSplitBtn, 1, 2);
|
||||
var grid = new GridPane();
|
||||
grid.setHgap(HGAP_20);
|
||||
grid.setVgap(VGAP_10);
|
||||
grid.addColumn(0, accentMenuBtn, successMenuBtn, dangerMenuBtn);
|
||||
grid.addColumn(1, accentSplitBtn, successSplitBtn, dangerSplitBtn);
|
||||
|
||||
return new SampleBlock("Colored", content);
|
||||
var description = BBCodeParser.createFormattedText("""
|
||||
You can change the [i]MenuButton[/i] color simply by using predefined \
|
||||
style class modifiers."""
|
||||
);
|
||||
|
||||
return new ExampleBox(grid, new Snippet(getClass(), 2), description);
|
||||
}
|
||||
|
||||
private SampleBlock popupSideSample() {
|
||||
private ExampleBox popupSideExample() {
|
||||
//snippet_3:start
|
||||
var topMenuBtn = new MenuButton("Top");
|
||||
topMenuBtn.getItems().setAll(createItems(5));
|
||||
topMenuBtn.setPopupSide(Side.TOP);
|
||||
@ -131,145 +145,165 @@ public class MenuButtonPage extends AbstractPage {
|
||||
var rightMenuBtn = new MenuButton("Right");
|
||||
rightMenuBtn.getItems().setAll(createItems(5));
|
||||
rightMenuBtn.setPopupSide(Side.RIGHT);
|
||||
rightMenuBtn.getStyleClass().add(ACCENT);
|
||||
rightMenuBtn.getStyleClass().add(Styles.ACCENT);
|
||||
|
||||
var bottomMenuBtn = new MenuButton("Bottom");
|
||||
bottomMenuBtn.getItems().setAll(createItems(5));
|
||||
bottomMenuBtn.setPopupSide(Side.BOTTOM);
|
||||
bottomMenuBtn.getStyleClass().add(SUCCESS);
|
||||
bottomMenuBtn.getStyleClass().add(Styles.SUCCESS);
|
||||
|
||||
var leftMenuBtn = new MenuButton("Left");
|
||||
leftMenuBtn.getItems().setAll(createItems(5));
|
||||
leftMenuBtn.setPopupSide(Side.LEFT);
|
||||
leftMenuBtn.getStyleClass().add(DANGER);
|
||||
leftMenuBtn.getStyleClass().add(Styles.DANGER);
|
||||
//snippet_3:end
|
||||
|
||||
var content = new FlowPane(BLOCK_HGAP, BLOCK_VGAP);
|
||||
content.getChildren().addAll(topMenuBtn, rightMenuBtn, bottomMenuBtn, leftMenuBtn);
|
||||
var box = new FlowPane(
|
||||
HGAP_20, VGAP_10,
|
||||
topMenuBtn, rightMenuBtn, bottomMenuBtn, leftMenuBtn
|
||||
);
|
||||
var description = BBCodeParser.createFormattedText("""
|
||||
A [i]MenuButton[/i] can be set to show its menu on any side of the button. \
|
||||
This is specified using the [code]popupSide[/code] property. By default the \
|
||||
menu appears below the button. However, regardless of the popupSide specified, \
|
||||
if there is not enough room, the [i]ContextMenu[/i] will be smartly repositioned, \
|
||||
most probably to be on the opposite side of the [i]MenuButton[/i].""");
|
||||
|
||||
return new SampleBlock("Popup Side", content);
|
||||
return new ExampleBox(box, new Snippet(getClass(), 3), description);
|
||||
}
|
||||
|
||||
private SampleBlock iconOnlySample() {
|
||||
var basicMenuBtn = new MenuButton();
|
||||
basicMenuBtn.setGraphic(new FontIcon(Feather.MORE_HORIZONTAL));
|
||||
basicMenuBtn.getItems().setAll(createItems(5));
|
||||
basicMenuBtn.getStyleClass().addAll(BUTTON_ICON);
|
||||
private ExampleBox iconButtonExample() {
|
||||
//snippet_4:start
|
||||
var normalMenuBtn = new MenuButton();
|
||||
normalMenuBtn.setGraphic(new FontIcon(Feather.MORE_HORIZONTAL));
|
||||
normalMenuBtn.getItems().setAll(createItems(5));
|
||||
normalMenuBtn.getStyleClass().addAll(Styles.BUTTON_ICON);
|
||||
|
||||
var basicSplitBtn = new SplitMenuButton(createItems(5));
|
||||
basicSplitBtn.setGraphic(new FontIcon(Feather.MORE_HORIZONTAL));
|
||||
basicSplitBtn.getStyleClass().addAll(BUTTON_ICON);
|
||||
var normalSplitBtn = new SplitMenuButton(createItems(5));
|
||||
normalSplitBtn.setGraphic(new FontIcon(Feather.MORE_HORIZONTAL));
|
||||
normalSplitBtn.getStyleClass().addAll(Styles.BUTTON_ICON);
|
||||
|
||||
var accentMenuBtn = new MenuButton();
|
||||
accentMenuBtn.setGraphic(new FontIcon(Feather.MENU));
|
||||
accentMenuBtn.getItems().setAll(createItems(5));
|
||||
accentMenuBtn.getStyleClass().addAll(BUTTON_ICON, ACCENT);
|
||||
accentMenuBtn.getStyleClass().addAll(
|
||||
Styles.BUTTON_ICON, Styles.ACCENT
|
||||
);
|
||||
|
||||
var accentSplitBtn = new SplitMenuButton(createItems(5));
|
||||
accentSplitBtn.setGraphic(new FontIcon(Feather.MENU));
|
||||
accentSplitBtn.getStyleClass().addAll(BUTTON_ICON, ACCENT);
|
||||
accentSplitBtn.getStyleClass().addAll(
|
||||
Styles.BUTTON_ICON, Styles.ACCENT
|
||||
);
|
||||
//snippet_4:end
|
||||
|
||||
var content = new FlowPane(BLOCK_HGAP, BLOCK_VGAP);
|
||||
content.getChildren().addAll(basicMenuBtn, basicSplitBtn, accentMenuBtn, accentSplitBtn);
|
||||
var box = new FlowPane(
|
||||
HGAP_20, VGAP_10,
|
||||
normalMenuBtn, normalSplitBtn, accentMenuBtn, accentSplitBtn
|
||||
);
|
||||
var description = BBCodeParser.createFormattedText("""
|
||||
You can hide the [i]MenuButton[/i] text by applying [code]Styles.BUTTON_ICON[/code] \
|
||||
style class modifier."""
|
||||
);
|
||||
|
||||
return new SampleBlock("Icons", content);
|
||||
return new ExampleBox(box, new Snippet(getClass(), 4), description);
|
||||
}
|
||||
|
||||
private SampleBlock outlinedSample() {
|
||||
private ExampleBox outlinedExample() {
|
||||
//snippet_5:start
|
||||
var accentMenuBtn = new MenuButton("Accen_t");
|
||||
accentMenuBtn.getItems().setAll(createItems(5));
|
||||
accentMenuBtn.getStyleClass().addAll(BUTTON_OUTLINED, ACCENT);
|
||||
accentMenuBtn.getStyleClass().addAll(
|
||||
Styles.BUTTON_OUTLINED, Styles.ACCENT
|
||||
);
|
||||
accentMenuBtn.setMnemonicParsing(true);
|
||||
accentMenuBtn.setPrefWidth(PREF_WIDTH);
|
||||
accentMenuBtn.setPrefWidth(150);
|
||||
|
||||
var accentSplitBtn = new SplitMenuButton(createItems(5));
|
||||
accentSplitBtn.setText("Accent");
|
||||
accentSplitBtn.getStyleClass().addAll(BUTTON_OUTLINED, ACCENT);
|
||||
accentSplitBtn.setPrefWidth(PREF_WIDTH);
|
||||
accentSplitBtn.getStyleClass().addAll(
|
||||
Styles.BUTTON_OUTLINED, Styles.ACCENT
|
||||
);
|
||||
accentSplitBtn.setPrefWidth(150);
|
||||
|
||||
var successMenuBtn = new MenuButton("S_uccess");
|
||||
successMenuBtn.getItems().setAll(createItems(5));
|
||||
successMenuBtn.setGraphic(new FontIcon(Feather.CHECK));
|
||||
successMenuBtn.getStyleClass().addAll(BUTTON_OUTLINED, SUCCESS);
|
||||
successMenuBtn.getStyleClass().addAll(
|
||||
Styles.BUTTON_OUTLINED, Styles.SUCCESS
|
||||
);
|
||||
successMenuBtn.setMnemonicParsing(true);
|
||||
successMenuBtn.setPrefWidth(PREF_WIDTH);
|
||||
successMenuBtn.setPrefWidth(150);
|
||||
|
||||
var successSplitBtn = new SplitMenuButton(createItems(5));
|
||||
successSplitBtn.setGraphic(new FontIcon(Feather.CHECK));
|
||||
successSplitBtn.setText("Success");
|
||||
successSplitBtn.getStyleClass().addAll(BUTTON_OUTLINED, SUCCESS);
|
||||
successSplitBtn.setPrefWidth(PREF_WIDTH);
|
||||
successSplitBtn.getStyleClass().addAll(
|
||||
Styles.BUTTON_OUTLINED, Styles.SUCCESS
|
||||
);
|
||||
successSplitBtn.setPrefWidth(150);
|
||||
|
||||
var dangerMenuBtn = new MenuButton("Danger");
|
||||
dangerMenuBtn.setGraphic(new FontIcon(Feather.TRASH));
|
||||
dangerMenuBtn.getItems().setAll(createItems(5));
|
||||
dangerMenuBtn.getStyleClass().addAll(BUTTON_OUTLINED, DANGER);
|
||||
dangerMenuBtn.setPrefWidth(PREF_WIDTH);
|
||||
dangerMenuBtn.getStyleClass().addAll(
|
||||
Styles.BUTTON_OUTLINED, Styles.DANGER
|
||||
);
|
||||
dangerMenuBtn.setPrefWidth(150);
|
||||
|
||||
var dangerSplitBtn = new SplitMenuButton(createItems(5));
|
||||
dangerSplitBtn.setGraphic(new FontIcon(Feather.TRASH));
|
||||
dangerSplitBtn.setText("Dan_ger");
|
||||
dangerSplitBtn.getStyleClass().addAll(BUTTON_OUTLINED, DANGER);
|
||||
dangerSplitBtn.getStyleClass().addAll(
|
||||
Styles.BUTTON_OUTLINED, Styles.DANGER
|
||||
);
|
||||
dangerSplitBtn.setMnemonicParsing(true);
|
||||
dangerSplitBtn.setPrefWidth(PREF_WIDTH);
|
||||
dangerSplitBtn.setPrefWidth(150);
|
||||
//snippet_5:end
|
||||
|
||||
var content = new GridPane();
|
||||
content.setHgap(BLOCK_HGAP);
|
||||
content.setVgap(BLOCK_VGAP);
|
||||
content.add(accentMenuBtn, 0, 0);
|
||||
content.add(accentSplitBtn, 1, 0);
|
||||
content.add(successMenuBtn, 0, 1);
|
||||
content.add(successSplitBtn, 1, 1);
|
||||
content.add(dangerMenuBtn, 0, 2);
|
||||
content.add(dangerSplitBtn, 1, 2);
|
||||
var grid = new GridPane();
|
||||
grid.setHgap(HGAP_20);
|
||||
grid.setVgap(VGAP_10);
|
||||
grid.addColumn(0, accentMenuBtn, successMenuBtn, dangerMenuBtn);
|
||||
grid.addColumn(1, accentSplitBtn, successSplitBtn, dangerSplitBtn);
|
||||
|
||||
return new SampleBlock("Outlined", content);
|
||||
var description = BBCodeParser.createFormattedText("""
|
||||
Outlined buttons are medium-emphasis buttons. They contain actions that are \
|
||||
important but aren't the primary action in an app."""
|
||||
);
|
||||
|
||||
return new ExampleBox(grid, new Snippet(getClass(), 5), description);
|
||||
}
|
||||
|
||||
private SampleBlock disabledSample() {
|
||||
var basicMenuBtn = new MenuButton("Menu Button");
|
||||
basicMenuBtn.getItems().setAll(createItems(5));
|
||||
basicMenuBtn.setDisable(true);
|
||||
|
||||
var accentSplitBtn = new SplitMenuButton();
|
||||
accentSplitBtn.setText("Accent");
|
||||
accentSplitBtn.getItems().setAll(createItems(5));
|
||||
accentSplitBtn.getStyleClass().addAll(ACCENT);
|
||||
accentSplitBtn.setDisable(true);
|
||||
private ExampleBox noArrowExample() {
|
||||
//snippet_6:start
|
||||
var normalMenuBtn = new MenuButton("_Menu Button");
|
||||
normalMenuBtn.getItems().setAll(createItems(5));
|
||||
normalMenuBtn.getStyleClass().addAll(Tweaks.NO_ARROW);
|
||||
|
||||
var flatMenuBtn = new MenuButton("Flat");
|
||||
flatMenuBtn.getItems().setAll(createItems(5));
|
||||
flatMenuBtn.getStyleClass().addAll(FLAT);
|
||||
flatMenuBtn.setDisable(true);
|
||||
|
||||
var iconMenuBtn = new MenuButton();
|
||||
iconMenuBtn.getItems().setAll(createItems(5));
|
||||
iconMenuBtn.getStyleClass().addAll(BUTTON_ICON);
|
||||
iconMenuBtn.setDisable(true);
|
||||
|
||||
var sample = new HBox(BLOCK_HGAP);
|
||||
sample.getChildren().addAll(basicMenuBtn, accentSplitBtn, flatMenuBtn, iconMenuBtn);
|
||||
|
||||
return new SampleBlock("Disabled", sample);
|
||||
}
|
||||
|
||||
private SampleBlock noArrowSample() {
|
||||
var basicMenuBtn = new MenuButton("_Menu Button");
|
||||
basicMenuBtn.getItems().setAll(createItems(5));
|
||||
basicMenuBtn.getStyleClass().addAll(Tweaks.NO_ARROW);
|
||||
|
||||
var flatMenuBtn = new MenuButton("Flat");
|
||||
flatMenuBtn.getItems().setAll(createItems(5));
|
||||
flatMenuBtn.getStyleClass().addAll(FLAT, Tweaks.NO_ARROW);
|
||||
flatMenuBtn.getStyleClass().addAll(
|
||||
Styles.FLAT, Tweaks.NO_ARROW
|
||||
);
|
||||
|
||||
var iconMenuBtn = new MenuButton();
|
||||
iconMenuBtn.setGraphic(new FontIcon(Feather.MORE_HORIZONTAL));
|
||||
iconMenuBtn.getItems().setAll(createItems(5));
|
||||
iconMenuBtn.getStyleClass().addAll(BUTTON_ICON, Tweaks.NO_ARROW);
|
||||
iconMenuBtn.getStyleClass().addAll(
|
||||
Styles.BUTTON_ICON, Tweaks.NO_ARROW
|
||||
);
|
||||
//snippet_6:end
|
||||
|
||||
var content = new HBox(BLOCK_HGAP);
|
||||
content.getChildren().addAll(basicMenuBtn, flatMenuBtn, iconMenuBtn);
|
||||
var box = new HBox(HGAP_20);
|
||||
box.getChildren().addAll(normalMenuBtn, flatMenuBtn, iconMenuBtn);
|
||||
|
||||
return new SampleBlock("No Arrow", content);
|
||||
var description = BBCodeParser.createFormattedText("""
|
||||
To hide the [i]MenuButton[/i] arrow, use the [code]Tweak.NO_ARROW[/code] \
|
||||
style class modifier."""
|
||||
);
|
||||
|
||||
return new ExampleBox(box, new Snippet(getClass(), 6), description);
|
||||
}
|
||||
|
||||
@SuppressWarnings("SameParameterValue")
|
||||
|
@ -1,72 +0,0 @@
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
|
||||
package atlantafx.sampler.page.components;
|
||||
|
||||
import static javafx.scene.input.KeyCombination.CONTROL_DOWN;
|
||||
|
||||
import atlantafx.base.theme.Styles;
|
||||
import atlantafx.sampler.fake.SampleMenuBar;
|
||||
import atlantafx.sampler.page.AbstractPage;
|
||||
import atlantafx.sampler.page.Page;
|
||||
import atlantafx.sampler.page.SampleBlock;
|
||||
import atlantafx.sampler.util.Controls;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.scene.control.ContextMenu;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.control.SeparatorMenuItem;
|
||||
import javafx.scene.input.KeyCode;
|
||||
import javafx.scene.input.KeyCodeCombination;
|
||||
import javafx.scene.layout.VBox;
|
||||
import org.kordamp.ikonli.feather.Feather;
|
||||
|
||||
public class MenuPage extends AbstractPage {
|
||||
|
||||
public static final String NAME = "Menu";
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return NAME;
|
||||
}
|
||||
|
||||
public MenuPage() {
|
||||
super();
|
||||
setUserContent(new VBox(Page.PAGE_VGAP,
|
||||
menuBarSample(),
|
||||
contextMenuExample()
|
||||
));
|
||||
}
|
||||
|
||||
private SampleBlock menuBarSample() {
|
||||
return new SampleBlock("Menu Bar", new SampleMenuBar(FAKER));
|
||||
}
|
||||
|
||||
private SampleBlock contextMenuExample() {
|
||||
var contextMenu = new ContextMenu();
|
||||
|
||||
var undoItem =
|
||||
Controls.menuItem("_Undo", Feather.CORNER_DOWN_LEFT, new KeyCodeCombination(KeyCode.Z, CONTROL_DOWN));
|
||||
undoItem.setMnemonicParsing(true);
|
||||
|
||||
var redoItem =
|
||||
Controls.menuItem("_Redo", Feather.CORNER_DOWN_RIGHT, new KeyCodeCombination(KeyCode.Y, CONTROL_DOWN));
|
||||
redoItem.setMnemonicParsing(true);
|
||||
|
||||
contextMenu.getItems().addAll(
|
||||
undoItem,
|
||||
redoItem,
|
||||
new SeparatorMenuItem(),
|
||||
Controls.menuItem("Cut", Feather.SCISSORS, new KeyCodeCombination(KeyCode.X, CONTROL_DOWN)),
|
||||
Controls.menuItem("Copy", Feather.COPY, new KeyCodeCombination(KeyCode.C, CONTROL_DOWN)),
|
||||
Controls.menuItem("Paste", null, new KeyCodeCombination(KeyCode.V, CONTROL_DOWN))
|
||||
);
|
||||
|
||||
var content = new Label("Right-Click Here");
|
||||
content.setAlignment(Pos.CENTER);
|
||||
content.setMinSize(400, 80);
|
||||
content.setMaxSize(400, 80);
|
||||
content.setContextMenu(contextMenu);
|
||||
content.getStyleClass().add(Styles.BORDERED);
|
||||
|
||||
return new SampleBlock("Context Menu", content);
|
||||
}
|
||||
}
|
@ -1,239 +0,0 @@
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
|
||||
package atlantafx.sampler.page.components;
|
||||
|
||||
import atlantafx.base.controls.ModalPane;
|
||||
import atlantafx.sampler.page.AbstractPage;
|
||||
import atlantafx.sampler.page.SampleBlock;
|
||||
import javafx.event.ActionEvent;
|
||||
import javafx.event.EventHandler;
|
||||
import javafx.geometry.Insets;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.geometry.Side;
|
||||
import javafx.geometry.VPos;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.control.Button;
|
||||
import javafx.scene.control.ScrollPane;
|
||||
import javafx.scene.layout.Background;
|
||||
import javafx.scene.layout.BackgroundFill;
|
||||
import javafx.scene.layout.CornerRadii;
|
||||
import javafx.scene.layout.GridPane;
|
||||
import javafx.scene.layout.Pane;
|
||||
import javafx.scene.layout.Priority;
|
||||
import javafx.scene.layout.RowConstraints;
|
||||
import javafx.scene.layout.VBox;
|
||||
import javafx.scene.paint.Color;
|
||||
import javafx.scene.shape.Rectangle;
|
||||
|
||||
public class ModalPanePage extends AbstractPage {
|
||||
|
||||
public static final String NAME = "Modal Pane";
|
||||
|
||||
private final ModalPane modalPaneL1 = new ModalPane();
|
||||
private final ModalPane modalPaneL2 = new ModalPane(-15);
|
||||
private final ModalPane modalPaneL3 = new ModalPane(-20);
|
||||
private VBox centerDialog;
|
||||
private VBox topDialog;
|
||||
private VBox rightDialog;
|
||||
private VBox bottomDialog;
|
||||
private VBox leftDialog;
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return NAME;
|
||||
}
|
||||
|
||||
public ModalPanePage() {
|
||||
super();
|
||||
|
||||
userContent.getChildren().setAll(
|
||||
modalPaneL1,
|
||||
modalPaneL2,
|
||||
modalPaneL3,
|
||||
new SampleBlock("Playground", createPlayground())
|
||||
);
|
||||
}
|
||||
|
||||
private VBox createPlayground() {
|
||||
var controlPane = new GridPane();
|
||||
controlPane.setBackground(new Background(new BackgroundFill(Color.WHITE, CornerRadii.EMPTY, Insets.EMPTY)));
|
||||
controlPane.setMaxSize(300, 300);
|
||||
controlPane.setHgap(20);
|
||||
controlPane.setVgap(20);
|
||||
controlPane.getRowConstraints().addAll(
|
||||
new RowConstraints(50, 50, 50, Priority.NEVER, VPos.CENTER, false),
|
||||
new RowConstraints(50, 50, 50, Priority.NEVER, VPos.CENTER, false),
|
||||
new RowConstraints(50, 50, 50, Priority.NEVER, VPos.CENTER, false)
|
||||
);
|
||||
|
||||
var topBtn = new Button("Top");
|
||||
topBtn.setOnAction(e -> {
|
||||
modalPaneL1.setAlignment(Pos.TOP_CENTER);
|
||||
modalPaneL1.usePredefinedTransitionFactories(Side.TOP);
|
||||
modalPaneL1.show(getOrCreateTopDialog());
|
||||
});
|
||||
controlPane.add(topBtn, 1, 0);
|
||||
|
||||
var rightBtn = new Button("Right");
|
||||
rightBtn.setOnAction(e -> {
|
||||
modalPaneL1.setAlignment(Pos.TOP_RIGHT);
|
||||
modalPaneL1.usePredefinedTransitionFactories(Side.RIGHT);
|
||||
modalPaneL1.show(getOrCreateRightDialog());
|
||||
});
|
||||
controlPane.add(rightBtn, 2, 1);
|
||||
|
||||
var centerBtn = new Button("Center");
|
||||
centerBtn.setOnAction(e -> {
|
||||
modalPaneL1.setAlignment(Pos.CENTER);
|
||||
modalPaneL1.usePredefinedTransitionFactories(null);
|
||||
modalPaneL1.show(getOrCreateCenterDialog());
|
||||
});
|
||||
controlPane.add(centerBtn, 1, 1);
|
||||
|
||||
var bottomBtn = new Button("Bottom");
|
||||
bottomBtn.setOnAction(e -> {
|
||||
modalPaneL1.setAlignment(Pos.BOTTOM_CENTER);
|
||||
modalPaneL1.usePredefinedTransitionFactories(Side.BOTTOM);
|
||||
modalPaneL1.show(getOrCreateBottomDialog());
|
||||
});
|
||||
controlPane.add(bottomBtn, 1, 2);
|
||||
|
||||
var leftBtn = new Button("Left");
|
||||
leftBtn.setOnAction(e -> {
|
||||
modalPaneL1.setAlignment(Pos.TOP_LEFT);
|
||||
modalPaneL1.usePredefinedTransitionFactories(Side.LEFT);
|
||||
modalPaneL1.show(getOrCreateLeftDialog());
|
||||
});
|
||||
controlPane.add(leftBtn, 0, 1);
|
||||
|
||||
controlPane.getChildren().forEach(c -> ((Button) c).setPrefWidth(100));
|
||||
|
||||
var root = new VBox(controlPane);
|
||||
root.setAlignment(Pos.CENTER);
|
||||
|
||||
return root;
|
||||
}
|
||||
|
||||
private Pane getOrCreateCenterDialog() {
|
||||
if (centerDialog != null) {
|
||||
return centerDialog;
|
||||
}
|
||||
|
||||
centerDialog = createGenericDialog(450, 450, e -> modalPaneL1.hide(true));
|
||||
|
||||
return centerDialog;
|
||||
}
|
||||
|
||||
private Node getOrCreateTopDialog() {
|
||||
if (topDialog != null) {
|
||||
return topDialog;
|
||||
}
|
||||
|
||||
topDialog = createGenericDialog(-1, 150, e -> modalPaneL1.hide(true));
|
||||
return topDialog;
|
||||
}
|
||||
|
||||
private Node getOrCreateRightDialog() {
|
||||
if (rightDialog != null) {
|
||||
return rightDialog;
|
||||
}
|
||||
|
||||
rightDialog = createGenericDialog(250, -1, e -> modalPaneL1.hide(true));
|
||||
return rightDialog;
|
||||
}
|
||||
|
||||
private Node getOrCreateBottomDialog() {
|
||||
if (bottomDialog != null) {
|
||||
return bottomDialog;
|
||||
}
|
||||
|
||||
bottomDialog = createGenericDialog(-1, 150, e -> modalPaneL1.hide(true));
|
||||
return bottomDialog;
|
||||
}
|
||||
|
||||
private Node getOrCreateLeftDialog() {
|
||||
if (leftDialog != null) {
|
||||
return leftDialog;
|
||||
}
|
||||
|
||||
leftDialog = createGenericDialog(250, -1, e -> modalPaneL1.hide(true));
|
||||
return leftDialog;
|
||||
}
|
||||
|
||||
private VBox createOverflowDialog() {
|
||||
var dialog = createGenericDialog(400, 400, e1 -> modalPaneL1.hide(true));
|
||||
|
||||
var content = new VBox();
|
||||
for (int i = 0; i < 10; i++) {
|
||||
var r = new Rectangle(600, 100);
|
||||
if (i % 2 == 0) {
|
||||
r.setFill(Color.AZURE);
|
||||
} else {
|
||||
r.setFill(Color.TOMATO);
|
||||
}
|
||||
content.getChildren().add(r);
|
||||
}
|
||||
|
||||
var scrollPane = new ScrollPane();
|
||||
scrollPane.setVbarPolicy(ScrollPane.ScrollBarPolicy.AS_NEEDED);
|
||||
scrollPane.setFitToHeight(true);
|
||||
scrollPane.setHbarPolicy(ScrollPane.ScrollBarPolicy.AS_NEEDED);
|
||||
scrollPane.setFitToWidth(true);
|
||||
scrollPane.setMaxHeight(20_000);
|
||||
scrollPane.setContent(content);
|
||||
|
||||
dialog.getChildren().setAll(scrollPane);
|
||||
|
||||
return dialog;
|
||||
}
|
||||
|
||||
private VBox createFullScreenDialog() {
|
||||
return createGenericDialog(-1, -1, e1 -> modalPaneL1.hide(true));
|
||||
}
|
||||
|
||||
private VBox createLevel1Dialog() {
|
||||
var dialog = createGenericDialog(600, 600, e1 -> modalPaneL1.hide(true));
|
||||
|
||||
var nextDialogBtn = new Button("Dialog 2");
|
||||
nextDialogBtn.setOnAction(e -> {
|
||||
modalPaneL2.setAlignment(Pos.CENTER);
|
||||
modalPaneL2.usePredefinedTransitionFactories(null);
|
||||
modalPaneL2.show(createLevel2Dialog());
|
||||
});
|
||||
dialog.getChildren().add(nextDialogBtn);
|
||||
|
||||
return dialog;
|
||||
}
|
||||
|
||||
private VBox createLevel2Dialog() {
|
||||
var dialog = createGenericDialog(450, 450, e2 -> modalPaneL2.hide(true));
|
||||
|
||||
var nextDialogBtn = new Button("Dialog 3");
|
||||
nextDialogBtn.setOnAction(e -> {
|
||||
modalPaneL3.setAlignment(Pos.CENTER);
|
||||
modalPaneL3.usePredefinedTransitionFactories(null);
|
||||
modalPaneL3.show(createLevel3Dialog());
|
||||
});
|
||||
dialog.getChildren().add(nextDialogBtn);
|
||||
|
||||
return dialog;
|
||||
}
|
||||
|
||||
private VBox createLevel3Dialog() {
|
||||
return createGenericDialog(300, 300, e2 -> modalPaneL3.hide(true));
|
||||
}
|
||||
|
||||
private VBox createGenericDialog(double width, double height, EventHandler<ActionEvent> closeHandler) {
|
||||
var dialog = new VBox(10);
|
||||
dialog.setBackground(new Background(new BackgroundFill(Color.WHITE, CornerRadii.EMPTY, Insets.EMPTY)));
|
||||
dialog.setAlignment(Pos.CENTER);
|
||||
dialog.setMinSize(width, height);
|
||||
dialog.setMaxSize(width, height);
|
||||
|
||||
var closeBtn = new Button("Close");
|
||||
closeBtn.setOnAction(closeHandler);
|
||||
dialog.getChildren().add(closeBtn);
|
||||
|
||||
return dialog;
|
||||
}
|
||||
}
|
@ -1,326 +0,0 @@
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
|
||||
package atlantafx.sampler.page.components;
|
||||
|
||||
import static atlantafx.base.theme.Styles.ACCENT;
|
||||
import static atlantafx.base.theme.Styles.BUTTON_CIRCLE;
|
||||
import static atlantafx.base.theme.Styles.BUTTON_ICON;
|
||||
import static atlantafx.base.theme.Styles.BUTTON_OUTLINED;
|
||||
import static atlantafx.base.theme.Styles.DANGER;
|
||||
import static atlantafx.base.theme.Styles.FLAT;
|
||||
import static atlantafx.base.theme.Styles.LEFT_PILL;
|
||||
import static atlantafx.base.theme.Styles.RIGHT_PILL;
|
||||
import static atlantafx.base.theme.Styles.SUCCESS;
|
||||
import static atlantafx.sampler.page.SampleBlock.BLOCK_HGAP;
|
||||
import static atlantafx.sampler.page.SampleBlock.BLOCK_VGAP;
|
||||
|
||||
import atlantafx.base.controls.ProgressSliderSkin;
|
||||
import atlantafx.base.controls.ToggleSwitch;
|
||||
import atlantafx.base.theme.Tweaks;
|
||||
import atlantafx.base.util.IntegerStringConverter;
|
||||
import atlantafx.sampler.page.AbstractPage;
|
||||
import atlantafx.sampler.page.SampleBlock;
|
||||
import java.time.LocalDate;
|
||||
import java.time.ZoneId;
|
||||
import java.util.stream.IntStream;
|
||||
import javafx.scene.control.Button;
|
||||
import javafx.scene.control.CheckBox;
|
||||
import javafx.scene.control.ChoiceBox;
|
||||
import javafx.scene.control.ColorPicker;
|
||||
import javafx.scene.control.ComboBox;
|
||||
import javafx.scene.control.DatePicker;
|
||||
import javafx.scene.control.MenuButton;
|
||||
import javafx.scene.control.MenuItem;
|
||||
import javafx.scene.control.PasswordField;
|
||||
import javafx.scene.control.RadioButton;
|
||||
import javafx.scene.control.Slider;
|
||||
import javafx.scene.control.Spinner;
|
||||
import javafx.scene.control.SplitMenuButton;
|
||||
import javafx.scene.control.TextArea;
|
||||
import javafx.scene.control.TextField;
|
||||
import javafx.scene.control.ToggleButton;
|
||||
import javafx.scene.control.ToggleGroup;
|
||||
import javafx.scene.layout.GridPane;
|
||||
import javafx.scene.layout.HBox;
|
||||
import javafx.scene.layout.VBox;
|
||||
import javafx.scene.paint.Color;
|
||||
import javafx.scene.shape.Circle;
|
||||
import org.kordamp.ikonli.feather.Feather;
|
||||
import org.kordamp.ikonli.javafx.FontIcon;
|
||||
|
||||
public class OverviewPage extends AbstractPage {
|
||||
|
||||
public static final String NAME = "Overview";
|
||||
|
||||
private static final int BUTTON_WIDTH = 120;
|
||||
private static final int COMBO_BOX_WIDTH = 150;
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return NAME;
|
||||
}
|
||||
|
||||
public OverviewPage() {
|
||||
super();
|
||||
setUserContent(new VBox(
|
||||
PAGE_VGAP,
|
||||
buttonSample(),
|
||||
expandingHBox(iconButtonSample(), dropdownMenuSample()),
|
||||
expandingHBox(checkBoxSample(), radioButtonSample(), toggleSwitchSample()),
|
||||
comboBoxSample(),
|
||||
sliderSample(),
|
||||
expandingHBox(textFieldSample(), spinnerSample()),
|
||||
textAreaSample()
|
||||
));
|
||||
}
|
||||
|
||||
private SampleBlock buttonSample() {
|
||||
var basicBtn = new Button("Basic");
|
||||
basicBtn.setPrefWidth(BUTTON_WIDTH);
|
||||
|
||||
var defaultBtn = new Button("Default");
|
||||
defaultBtn.setDefaultButton(true);
|
||||
defaultBtn.setPrefWidth(BUTTON_WIDTH);
|
||||
|
||||
var successBtn = new Button("Success");
|
||||
successBtn.getStyleClass().add(SUCCESS);
|
||||
successBtn.setPrefWidth(BUTTON_WIDTH);
|
||||
|
||||
var dangerBtn = new Button("Danger");
|
||||
dangerBtn.getStyleClass().add(DANGER);
|
||||
dangerBtn.setPrefWidth(BUTTON_WIDTH);
|
||||
|
||||
var flatBtn = new Button("Flat");
|
||||
flatBtn.getStyleClass().add(FLAT);
|
||||
flatBtn.setPrefWidth(BUTTON_WIDTH);
|
||||
|
||||
var outlinedBtn = new Button("Outlined");
|
||||
outlinedBtn.getStyleClass().addAll(BUTTON_OUTLINED, ACCENT);
|
||||
outlinedBtn.setPrefWidth(BUTTON_WIDTH);
|
||||
|
||||
var twoButtonGroup = new ToggleGroup();
|
||||
var leftPill = createToggleButton("Toggle 1", twoButtonGroup, true, LEFT_PILL);
|
||||
leftPill.setPrefWidth(BUTTON_WIDTH + BLOCK_HGAP / 2.0);
|
||||
var rightPill = createToggleButton("Toggle 2", twoButtonGroup, false, RIGHT_PILL);
|
||||
rightPill.setPrefWidth(BUTTON_WIDTH + BLOCK_HGAP / 2.0);
|
||||
var twoButtonBox = new HBox(leftPill, rightPill);
|
||||
|
||||
var grid = new GridPane();
|
||||
grid.setVgap(BLOCK_VGAP);
|
||||
grid.setHgap(BLOCK_HGAP);
|
||||
grid.add(basicBtn, 0, 0);
|
||||
grid.add(flatBtn, 1, 0);
|
||||
grid.add(successBtn, 2, 0);
|
||||
grid.add(dangerBtn, 3, 0);
|
||||
|
||||
grid.add(defaultBtn, 0, 1);
|
||||
grid.add(outlinedBtn, 1, 1);
|
||||
grid.add(twoButtonBox, 2, 1, 2, 1);
|
||||
|
||||
return new SampleBlock("Buttons", grid);
|
||||
}
|
||||
|
||||
private SampleBlock iconButtonSample() {
|
||||
var basicBtn = new Button("", new FontIcon(Feather.MORE_HORIZONTAL));
|
||||
basicBtn.getStyleClass().addAll(BUTTON_ICON);
|
||||
|
||||
var successBtn = new Button("", new FontIcon(Feather.PLUS));
|
||||
successBtn.getStyleClass().addAll(BUTTON_ICON, ACCENT);
|
||||
|
||||
var dangerBtn = new Button("", new FontIcon(Feather.TRASH));
|
||||
dangerBtn.getStyleClass().addAll(BUTTON_ICON, DANGER);
|
||||
|
||||
var basicCircularBtn = new Button("", new FontIcon(Feather.MORE_VERTICAL));
|
||||
basicCircularBtn.getStyleClass().addAll(BUTTON_CIRCLE);
|
||||
basicCircularBtn.setShape(new Circle(50));
|
||||
|
||||
var flatBtn = new Button("", new FontIcon(Feather.MIC));
|
||||
flatBtn.getStyleClass().addAll(BUTTON_CIRCLE, FLAT);
|
||||
flatBtn.setShape(new Circle(50));
|
||||
|
||||
var grid = new GridPane();
|
||||
grid.setVgap(BLOCK_VGAP);
|
||||
grid.setHgap(BLOCK_HGAP);
|
||||
grid.add(basicBtn, 0, 0);
|
||||
grid.add(successBtn, 1, 0);
|
||||
grid.add(dangerBtn, 2, 0);
|
||||
grid.add(basicCircularBtn, 0, 1);
|
||||
grid.add(flatBtn, 1, 1);
|
||||
|
||||
return new SampleBlock("Icon Buttons", grid);
|
||||
}
|
||||
|
||||
private SampleBlock dropdownMenuSample() {
|
||||
var basicMenuBtn = new MenuButton("Menu Button");
|
||||
basicMenuBtn.getItems().setAll(createMenuItems());
|
||||
basicMenuBtn.setPrefWidth(COMBO_BOX_WIDTH);
|
||||
|
||||
var basicIconBtn = new MenuButton();
|
||||
basicIconBtn.setGraphic(new FontIcon(Feather.MORE_HORIZONTAL));
|
||||
basicIconBtn.getItems().setAll(createMenuItems());
|
||||
basicIconBtn.getStyleClass().addAll(BUTTON_ICON);
|
||||
|
||||
var accentIconBtn = new MenuButton();
|
||||
accentIconBtn.setGraphic(new FontIcon(Feather.MENU));
|
||||
accentIconBtn.getItems().setAll(createMenuItems());
|
||||
accentIconBtn.getStyleClass().addAll(BUTTON_ICON, ACCENT);
|
||||
|
||||
var basicSplitBtn = new SplitMenuButton(createMenuItems());
|
||||
basicSplitBtn.setText("Split Menu Button");
|
||||
|
||||
var outlinedSplitBtn = new SplitMenuButton(createMenuItems());
|
||||
outlinedSplitBtn.setGraphic(new FontIcon(Feather.TRASH));
|
||||
outlinedSplitBtn.setText("Danger");
|
||||
outlinedSplitBtn.getStyleClass().addAll(BUTTON_OUTLINED, DANGER);
|
||||
outlinedSplitBtn.setPrefWidth(COMBO_BOX_WIDTH);
|
||||
|
||||
var grid = new GridPane();
|
||||
grid.setVgap(BLOCK_VGAP);
|
||||
grid.setHgap(BLOCK_HGAP);
|
||||
grid.add(basicMenuBtn, 0, 0);
|
||||
grid.add(basicIconBtn, 1, 0);
|
||||
grid.add(accentIconBtn, 2, 0);
|
||||
grid.add(basicSplitBtn, 1, 1, 2, 1);
|
||||
grid.add(outlinedSplitBtn, 0, 1);
|
||||
|
||||
return new SampleBlock("Dropdown Menus", grid);
|
||||
}
|
||||
|
||||
private SampleBlock checkBoxSample() {
|
||||
var opt1 = new CheckBox("Option 1");
|
||||
|
||||
var opt2 = new CheckBox("Option 2");
|
||||
opt2.setSelected(true);
|
||||
|
||||
var opt3 = new CheckBox("Option 3");
|
||||
opt3.setAllowIndeterminate(true);
|
||||
opt3.setIndeterminate(true);
|
||||
|
||||
var container = new VBox(BLOCK_VGAP, opt1, opt2, opt3);
|
||||
return new SampleBlock("Check Boxes", container);
|
||||
}
|
||||
|
||||
private SampleBlock radioButtonSample() {
|
||||
var group = new ToggleGroup();
|
||||
|
||||
var opt1 = new RadioButton("Option 1");
|
||||
opt1.setToggleGroup(group);
|
||||
|
||||
var opt2 = new RadioButton("Option 2");
|
||||
opt2.setToggleGroup(group);
|
||||
opt2.setSelected(true);
|
||||
|
||||
var opt3 = new RadioButton("Option 3");
|
||||
opt3.setToggleGroup(group);
|
||||
|
||||
var container = new VBox(BLOCK_VGAP, opt1, opt2, opt3);
|
||||
return new SampleBlock("Radio Buttons", container);
|
||||
}
|
||||
|
||||
private SampleBlock toggleSwitchSample() {
|
||||
var switch1 = new ToggleSwitch();
|
||||
|
||||
var switch2 = new ToggleSwitch();
|
||||
switch2.setSelected(true);
|
||||
|
||||
var container = new VBox(BLOCK_VGAP, switch1, switch2);
|
||||
return new SampleBlock("Switches", container);
|
||||
}
|
||||
|
||||
private SampleBlock comboBoxSample() {
|
||||
var comboBox = new ComboBox<String>();
|
||||
comboBox.getItems().setAll("Option 1", "Option 2", "Option 3");
|
||||
comboBox.getStyleClass().add(Tweaks.ALT_ICON);
|
||||
comboBox.getSelectionModel().selectFirst();
|
||||
comboBox.setPrefWidth(COMBO_BOX_WIDTH);
|
||||
|
||||
var choiceBox = new ChoiceBox<String>();
|
||||
choiceBox.getItems().setAll("Option 1", "Option 2", "Option 3");
|
||||
choiceBox.getSelectionModel().selectFirst();
|
||||
choiceBox.setPrefWidth(COMBO_BOX_WIDTH);
|
||||
|
||||
var datePicker = new DatePicker();
|
||||
datePicker.setPrefWidth(COMBO_BOX_WIDTH);
|
||||
datePicker.setValue(LocalDate.now(ZoneId.systemDefault()));
|
||||
|
||||
var colorPicker = new ColorPicker();
|
||||
colorPicker.setPrefWidth(COMBO_BOX_WIDTH);
|
||||
colorPicker.setValue(Color.ORANGE);
|
||||
|
||||
var container = new HBox(BLOCK_HGAP, comboBox, choiceBox, datePicker, colorPicker);
|
||||
return new SampleBlock("Combo Boxes", container);
|
||||
}
|
||||
|
||||
private SampleBlock sliderSample() {
|
||||
var slider = new Slider(1, 5, 3);
|
||||
slider.setPrefWidth(BUTTON_WIDTH * 2);
|
||||
|
||||
var tickSlider = new Slider(0, 5, 3);
|
||||
tickSlider.setShowTickLabels(true);
|
||||
tickSlider.setShowTickMarks(true);
|
||||
tickSlider.setMajorTickUnit(1);
|
||||
tickSlider.setBlockIncrement(1);
|
||||
tickSlider.setMinorTickCount(5);
|
||||
tickSlider.setSnapToTicks(true);
|
||||
tickSlider.setPrefWidth(BUTTON_WIDTH * 2);
|
||||
tickSlider.setSkin(new ProgressSliderSkin(tickSlider));
|
||||
|
||||
var container = new HBox(BLOCK_HGAP, slider, tickSlider);
|
||||
return new SampleBlock("Sliders", container);
|
||||
}
|
||||
|
||||
private SampleBlock textFieldSample() {
|
||||
var textField = new TextField("Text");
|
||||
textField.setPrefWidth(COMBO_BOX_WIDTH);
|
||||
|
||||
var passwordField = new PasswordField();
|
||||
passwordField.setText(FAKER.internet().password());
|
||||
passwordField.setPrefWidth(COMBO_BOX_WIDTH);
|
||||
|
||||
var container = new HBox(BLOCK_HGAP, textField, passwordField);
|
||||
return new SampleBlock("Text Fields", container);
|
||||
}
|
||||
|
||||
private SampleBlock spinnerSample() {
|
||||
var spinner1 = new Spinner<Integer>(1, 10, 1);
|
||||
IntegerStringConverter.createFor(spinner1);
|
||||
spinner1.setPrefWidth(COMBO_BOX_WIDTH);
|
||||
|
||||
var spinner2 = new Spinner<Integer>(1, 10, 1);
|
||||
IntegerStringConverter.createFor(spinner2);
|
||||
spinner2.getStyleClass().add(Spinner.STYLE_CLASS_SPLIT_ARROWS_HORIZONTAL);
|
||||
spinner2.setPrefWidth(COMBO_BOX_WIDTH);
|
||||
|
||||
var container = new HBox(BLOCK_HGAP, spinner1, spinner2);
|
||||
return new SampleBlock("Spinners", container);
|
||||
}
|
||||
|
||||
private SampleBlock textAreaSample() {
|
||||
var textArea = new TextArea(String.join("\n\n", FAKER.lorem().paragraphs(3)));
|
||||
textArea.setWrapText(true);
|
||||
textArea.setMaxWidth(Double.MAX_VALUE);
|
||||
textArea.setMinHeight(100);
|
||||
|
||||
return new SampleBlock("Text Area", textArea);
|
||||
}
|
||||
|
||||
private ToggleButton createToggleButton(String text,
|
||||
ToggleGroup group,
|
||||
boolean selected,
|
||||
String... styleClasses) {
|
||||
var toggleButton = new ToggleButton(text);
|
||||
if (group != null) {
|
||||
toggleButton.setToggleGroup(group);
|
||||
}
|
||||
toggleButton.setSelected(selected);
|
||||
toggleButton.getStyleClass().addAll(styleClasses);
|
||||
|
||||
return toggleButton;
|
||||
}
|
||||
|
||||
private MenuItem[] createMenuItems() {
|
||||
return IntStream.range(0, 5)
|
||||
.mapToObj(i -> new MenuItem(FAKER.babylon5().character()))
|
||||
.toArray(MenuItem[]::new);
|
||||
}
|
||||
}
|
@ -2,13 +2,12 @@
|
||||
|
||||
package atlantafx.sampler.page.components;
|
||||
|
||||
import static atlantafx.sampler.page.SampleBlock.BLOCK_HGAP;
|
||||
import static atlantafx.sampler.page.SampleBlock.BLOCK_VGAP;
|
||||
|
||||
import atlantafx.base.controls.ToggleSwitch;
|
||||
import atlantafx.base.theme.Styles;
|
||||
import atlantafx.sampler.page.AbstractPage;
|
||||
import atlantafx.sampler.page.SampleBlock;
|
||||
import atlantafx.base.util.BBCodeParser;
|
||||
import atlantafx.sampler.page.ExampleBox;
|
||||
import atlantafx.sampler.page.OutlinePage;
|
||||
import atlantafx.sampler.page.Snippet;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.control.Pagination;
|
||||
@ -16,12 +15,12 @@ import javafx.scene.control.Separator;
|
||||
import javafx.scene.control.Spinner;
|
||||
import javafx.scene.layout.BorderPane;
|
||||
import javafx.scene.layout.GridPane;
|
||||
import javafx.scene.layout.HBox;
|
||||
import javafx.scene.layout.VBox;
|
||||
|
||||
public class PaginationPage extends AbstractPage {
|
||||
public class PaginationPage extends OutlinePage {
|
||||
|
||||
public static final String NAME = "Pagination";
|
||||
private static final int PREF_CONTROL_WIDTH = 120;
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
@ -30,43 +29,113 @@ public class PaginationPage extends AbstractPage {
|
||||
|
||||
public PaginationPage() {
|
||||
super();
|
||||
setUserContent(new VBox(
|
||||
new SampleBlock("Playground", createPlayground())
|
||||
));
|
||||
|
||||
addFormattedText("""
|
||||
A [i]Pagination[/i] control is used for navigation between pages of a single content, \
|
||||
which has been divided into smaller parts."""
|
||||
);
|
||||
addSection("Usage", usageExample());
|
||||
addSection("Bullet Style", bulletStyleExample());
|
||||
addSection("No Arrows", noArrowsExample());
|
||||
addSection("Playground", playground());
|
||||
}
|
||||
|
||||
private VBox createPlayground() {
|
||||
var pagination = new Pagination();
|
||||
pagination.setCurrentPageIndex(1);
|
||||
pagination.setPageFactory(index -> {
|
||||
private ExampleBox usageExample() {
|
||||
//snippet_1:start
|
||||
var pg = new Pagination(25, 0);
|
||||
pg.setMaxPageIndicatorCount(5);
|
||||
pg.setPageFactory(index -> {
|
||||
var label = new Label("Page #" + (index + 1));
|
||||
label.setStyle("-fx-font-size: 2em;");
|
||||
return new BorderPane(label);
|
||||
});
|
||||
//snippet_1:end
|
||||
|
||||
var box = new HBox(pg);
|
||||
var description = BBCodeParser.createFormattedText("""
|
||||
A pagination control consists of the page content and the page navigation areas. \
|
||||
The [i]Page[/i] content area renders and lays out the content according to the \
|
||||
application logic. The [i]Page[/i] navigation area contains a prefabricated control \
|
||||
to preview a particular part of the content."""
|
||||
);
|
||||
|
||||
return new ExampleBox(box, new Snippet(getClass(), 1), description);
|
||||
}
|
||||
|
||||
private ExampleBox bulletStyleExample() {
|
||||
//snippet_2:start
|
||||
var pg = new Pagination(25, 0);
|
||||
pg.setMaxPageIndicatorCount(5);
|
||||
pg.getStyleClass().add(Pagination.STYLE_CLASS_BULLET);
|
||||
pg.setPageFactory(index -> {
|
||||
var label = new Label("Page #" + (index + 1));
|
||||
label.setStyle("-fx-font-size: 2em;");
|
||||
return new BorderPane(label);
|
||||
});
|
||||
//snippet_2:end
|
||||
|
||||
var box = new HBox(pg);
|
||||
var description = BBCodeParser.createFormattedText("""
|
||||
The control can be customized to display bullet style indicators by setting \
|
||||
the style class [code]STYLE_CLASS_BULLET[/code]."""
|
||||
);
|
||||
|
||||
return new ExampleBox(box, new Snippet(getClass(), 2), description);
|
||||
}
|
||||
|
||||
private ExampleBox noArrowsExample() {
|
||||
//snippet_3:start
|
||||
var pg = new Pagination(25, 0);
|
||||
pg.setMaxPageIndicatorCount(5);
|
||||
pg.setStyle("-fx-arrows-visible:false;");
|
||||
pg.setPageFactory(index -> {
|
||||
var label = new Label("Page #" + (index + 1));
|
||||
label.setStyle("-fx-font-size: 2em;");
|
||||
return new BorderPane(label);
|
||||
});
|
||||
//snippet_3:end
|
||||
|
||||
var box = new HBox(pg);
|
||||
var description = BBCodeParser.createFormattedText("""
|
||||
[code]-fx-arrows-visible[/code] can be used to to toggle the visibility of the [i]Next[/i] \
|
||||
and [i]Previous[/i] button arrows."""
|
||||
);
|
||||
|
||||
return new ExampleBox(box, new Snippet(getClass(), 3), description);
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
// Playground //
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
|
||||
private VBox playground() {
|
||||
var pg = new Pagination();
|
||||
pg.setCurrentPageIndex(0);
|
||||
pg.setPageFactory(index -> {
|
||||
var label = new Label("Page #" + (index + 1));
|
||||
label.setStyle("-fx-font-size: 3em;");
|
||||
|
||||
var page = new BorderPane();
|
||||
page.setCenter(label);
|
||||
|
||||
return page;
|
||||
return new BorderPane(label);
|
||||
});
|
||||
|
||||
// == CONTROLS ==
|
||||
|
||||
var pageCountSpinner = new Spinner<Integer>(0, 50, 25);
|
||||
pageCountSpinner.setPrefWidth(PREF_CONTROL_WIDTH);
|
||||
pagination.pageCountProperty().bind(pageCountSpinner.valueProperty());
|
||||
pageCountSpinner.setPrefWidth(120);
|
||||
pg.pageCountProperty().bind(pageCountSpinner.valueProperty());
|
||||
|
||||
var visibleCountSpinner = new Spinner<Integer>(3, 10, 5);
|
||||
visibleCountSpinner.setPrefWidth(PREF_CONTROL_WIDTH);
|
||||
pagination.maxPageIndicatorCountProperty().bind(visibleCountSpinner.valueProperty());
|
||||
visibleCountSpinner.setPrefWidth(120);
|
||||
pg.maxPageIndicatorCountProperty().bind(visibleCountSpinner.valueProperty());
|
||||
|
||||
var bulletToggle = new ToggleSwitch();
|
||||
bulletToggle.selectedProperty().addListener(
|
||||
(obs, old, val) -> Styles.toggleStyleClass(pagination, Pagination.STYLE_CLASS_BULLET)
|
||||
(obs, old, val) -> Styles.toggleStyleClass(pg, Pagination.STYLE_CLASS_BULLET)
|
||||
);
|
||||
|
||||
var showArrowsToggle = new ToggleSwitch();
|
||||
showArrowsToggle.selectedProperty().addListener((obs, old, val) -> {
|
||||
if (val != null) {
|
||||
pagination.setStyle(String.format("-fx-arrows-visible: %s;", val));
|
||||
pg.setStyle(String.format("-fx-arrows-visible: %s;", val));
|
||||
}
|
||||
});
|
||||
showArrowsToggle.setSelected(true);
|
||||
@ -74,17 +143,17 @@ public class PaginationPage extends AbstractPage {
|
||||
var showPageInfoToggle = new ToggleSwitch();
|
||||
showPageInfoToggle.selectedProperty().addListener((obs, old, val) -> {
|
||||
if (val != null) {
|
||||
pagination.setStyle(String.format("-fx-page-information-visible: %s;", val));
|
||||
pg.setStyle(String.format("-fx-page-information-visible: %s;", val));
|
||||
}
|
||||
});
|
||||
showPageInfoToggle.setSelected(true);
|
||||
|
||||
var disableToggle = new ToggleSwitch();
|
||||
pagination.disableProperty().bind(disableToggle.selectedProperty());
|
||||
pg.disableProperty().bind(disableToggle.selectedProperty());
|
||||
|
||||
var controls = new GridPane();
|
||||
controls.setHgap(BLOCK_HGAP);
|
||||
controls.setVgap(BLOCK_VGAP);
|
||||
controls.setHgap(20);
|
||||
controls.setVgap(10);
|
||||
controls.setAlignment(Pos.CENTER);
|
||||
|
||||
controls.add(new Label("Page count"), 0, 0);
|
||||
@ -107,8 +176,13 @@ public class PaginationPage extends AbstractPage {
|
||||
|
||||
// ~
|
||||
|
||||
var playground = new VBox(BLOCK_VGAP, pagination, new Separator(), controls);
|
||||
playground.setMinHeight(100);
|
||||
var description = BBCodeParser.createFormattedText("""
|
||||
The playground demonstrates the most important [i]Pagination[/i] features \
|
||||
and also serves as an object for monkey testing."""
|
||||
);
|
||||
|
||||
var playground = new VBox(VGAP_10, description, pg, new Separator(), controls);
|
||||
playground.setPrefHeight(200);
|
||||
|
||||
return playground;
|
||||
}
|
||||
|
@ -1,168 +0,0 @@
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
|
||||
package atlantafx.sampler.page.components;
|
||||
|
||||
import static atlantafx.sampler.page.SampleBlock.BLOCK_HGAP;
|
||||
import static atlantafx.sampler.page.SampleBlock.BLOCK_VGAP;
|
||||
|
||||
import atlantafx.base.controls.InlineDatePicker;
|
||||
import atlantafx.base.controls.Popover;
|
||||
import atlantafx.base.controls.Popover.ArrowLocation;
|
||||
import atlantafx.sampler.page.AbstractPage;
|
||||
import atlantafx.sampler.page.Page;
|
||||
import atlantafx.sampler.page.SampleBlock;
|
||||
import atlantafx.sampler.theme.CSSFragment;
|
||||
import java.time.LocalDate;
|
||||
import java.time.ZoneId;
|
||||
import javafx.geometry.Insets;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.scene.control.Button;
|
||||
import javafx.scene.control.Hyperlink;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.layout.GridPane;
|
||||
import javafx.scene.layout.HBox;
|
||||
import javafx.scene.layout.VBox;
|
||||
import javafx.scene.text.Text;
|
||||
import javafx.scene.text.TextFlow;
|
||||
import org.kordamp.ikonli.feather.Feather;
|
||||
import org.kordamp.ikonli.javafx.FontIcon;
|
||||
|
||||
public class PopoverPage extends AbstractPage {
|
||||
|
||||
public static final String NAME = "Popover";
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return NAME;
|
||||
}
|
||||
|
||||
public PopoverPage() {
|
||||
super();
|
||||
setUserContent(new VBox(Page.PAGE_VGAP,
|
||||
new HBox(PAGE_HGAP, textSample(), datePickerSample(), dialogSample()),
|
||||
positionSample()
|
||||
));
|
||||
}
|
||||
|
||||
private SampleBlock textSample() {
|
||||
var popover = new Popover(createTextFlow(30));
|
||||
popover.setTitle("Lorem Ipsum");
|
||||
popover.setHeaderAlwaysVisible(true);
|
||||
popover.setDetachable(true);
|
||||
|
||||
var link = createHyperlink("Click me");
|
||||
link.setOnAction(e -> popover.show(link));
|
||||
|
||||
return new SampleBlock("Text", link);
|
||||
}
|
||||
|
||||
private SampleBlock datePickerSample() {
|
||||
var datePicker = new InlineDatePicker();
|
||||
datePicker.setValue(LocalDate.now(ZoneId.systemDefault()));
|
||||
|
||||
var popover = new Popover(datePicker);
|
||||
popover.setHeaderAlwaysVisible(false);
|
||||
popover.setDetachable(true);
|
||||
|
||||
var link = createHyperlink("Click me");
|
||||
link.setOnAction(e -> popover.show(link));
|
||||
new CSSFragment("""
|
||||
.popover .date-picker-popup {
|
||||
-color-date-border: transparent;
|
||||
-color-date-bg: transparent;
|
||||
-color-date-day-bg: transparent;
|
||||
-color-date-month-year-bg: transparent;
|
||||
-color-date-day-bg-hover: -color-bg-subtle;
|
||||
}
|
||||
"""
|
||||
).addTo(link);
|
||||
|
||||
return new SampleBlock("Date Picker", link);
|
||||
}
|
||||
|
||||
private SampleBlock dialogSample() {
|
||||
var root = new VBox(BLOCK_VGAP);
|
||||
|
||||
var popover = new Popover(root);
|
||||
popover.setHeaderAlwaysVisible(false);
|
||||
popover.setDetachable(true);
|
||||
|
||||
var icon = new FontIcon(Feather.ALERT_TRIANGLE);
|
||||
icon.setIconSize(32); // not always works
|
||||
icon.setStyle(
|
||||
"-fx-icon-size:32px;-fx-icon-color:-color-warning-fg;-fx-fill:-color-warning-fg;" + icon.getStyle());
|
||||
|
||||
var label = new Label(FAKER.chuckNorris().fact(), icon);
|
||||
label.setStyle("-fx-graphic-text-gap:10;");
|
||||
label.setWrapText(true);
|
||||
label.setMaxWidth(300);
|
||||
label.setMaxHeight(Double.MAX_VALUE);
|
||||
root.getChildren().add(label);
|
||||
|
||||
var yesBtn = new Button("Yes");
|
||||
yesBtn.setDefaultButton(true);
|
||||
yesBtn.setOnAction(e -> popover.hide());
|
||||
|
||||
var noBtn = new Button("No");
|
||||
noBtn.setOnAction(e -> popover.hide());
|
||||
|
||||
var box = new HBox(10, yesBtn, noBtn);
|
||||
box.setPadding(new Insets(0, 0, 0, 42));
|
||||
box.setAlignment(Pos.CENTER_LEFT);
|
||||
root.getChildren().add(box);
|
||||
|
||||
var link = createHyperlink("Click me");
|
||||
link.setOnAction(e -> popover.show(link));
|
||||
|
||||
return new SampleBlock("Dialog", link);
|
||||
}
|
||||
|
||||
private SampleBlock positionSample() {
|
||||
var grid = new GridPane();
|
||||
grid.setHgap(BLOCK_HGAP);
|
||||
grid.setVgap(BLOCK_VGAP);
|
||||
|
||||
grid.add(createArrowPositionBlock(ArrowLocation.TOP_LEFT), 0, 0);
|
||||
grid.add(createArrowPositionBlock(ArrowLocation.TOP_CENTER), 0, 1);
|
||||
grid.add(createArrowPositionBlock(ArrowLocation.TOP_RIGHT), 0, 2);
|
||||
|
||||
grid.add(createArrowPositionBlock(ArrowLocation.RIGHT_TOP), 1, 0);
|
||||
grid.add(createArrowPositionBlock(ArrowLocation.RIGHT_CENTER), 1, 1);
|
||||
grid.add(createArrowPositionBlock(ArrowLocation.RIGHT_BOTTOM), 1, 2);
|
||||
|
||||
grid.add(createArrowPositionBlock(ArrowLocation.BOTTOM_LEFT), 2, 0);
|
||||
grid.add(createArrowPositionBlock(ArrowLocation.BOTTOM_CENTER), 2, 1);
|
||||
grid.add(createArrowPositionBlock(ArrowLocation.BOTTOM_RIGHT), 2, 2);
|
||||
|
||||
grid.add(createArrowPositionBlock(ArrowLocation.LEFT_TOP), 3, 0);
|
||||
grid.add(createArrowPositionBlock(ArrowLocation.LEFT_CENTER), 3, 1);
|
||||
grid.add(createArrowPositionBlock(ArrowLocation.LEFT_BOTTOM), 3, 2);
|
||||
|
||||
return new SampleBlock("Position", grid);
|
||||
}
|
||||
|
||||
private Hyperlink createHyperlink(String text) {
|
||||
Hyperlink hyperlink = new Hyperlink(text);
|
||||
hyperlink.setMinWidth(50);
|
||||
hyperlink.setMinHeight(50);
|
||||
hyperlink.setAlignment(Pos.CENTER_LEFT);
|
||||
return hyperlink;
|
||||
}
|
||||
|
||||
private TextFlow createTextFlow(int wordCount) {
|
||||
var textFlow = new TextFlow(new Text(FAKER.lorem().sentence(wordCount)));
|
||||
textFlow.setPrefWidth(300);
|
||||
return textFlow;
|
||||
}
|
||||
|
||||
private Hyperlink createArrowPositionBlock(ArrowLocation arrowLocation) {
|
||||
var popover = new Popover(createTextFlow(50));
|
||||
popover.setHeaderAlwaysVisible(false);
|
||||
popover.setArrowLocation(arrowLocation);
|
||||
|
||||
var link = createHyperlink(String.valueOf(arrowLocation));
|
||||
link.setOnAction(e -> popover.show(link));
|
||||
|
||||
return link;
|
||||
}
|
||||
}
|
@ -0,0 +1,372 @@
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
|
||||
package atlantafx.sampler.page.components;
|
||||
|
||||
import atlantafx.base.controls.RingProgressIndicator;
|
||||
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 atlantafx.sampler.theme.CSSFragment;
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.concurrent.Task;
|
||||
import javafx.geometry.HPos;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.geometry.VPos;
|
||||
import javafx.scene.control.Button;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.control.ProgressBar;
|
||||
import javafx.scene.control.ProgressIndicator;
|
||||
import javafx.scene.control.ToggleButton;
|
||||
import javafx.scene.layout.ColumnConstraints;
|
||||
import javafx.scene.layout.GridPane;
|
||||
import javafx.scene.layout.HBox;
|
||||
import javafx.scene.layout.Priority;
|
||||
import javafx.scene.layout.RowConstraints;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import javafx.scene.layout.VBox;
|
||||
import javafx.scene.text.Text;
|
||||
import javafx.util.StringConverter;
|
||||
import org.kordamp.ikonli.feather.Feather;
|
||||
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 static final String NAME = "ProgressIndicator";
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return NAME;
|
||||
}
|
||||
|
||||
public ProgressIndicatorPage() {
|
||||
super();
|
||||
|
||||
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 \
|
||||
[i]RingProgressIndicator[/i]."""
|
||||
);
|
||||
addSection("Progress Bar", basicBarExample());
|
||||
addSection("Progress Indicator", basicIndicatorExample());
|
||||
addSection("Size", barSizeSample());
|
||||
addSection("Ring Indicator", ringIndicatorExample());
|
||||
addSection("Indeterminate", indeterminateExample());
|
||||
addSection("Dynamic Color Change", colorChangeExample());
|
||||
}
|
||||
|
||||
private ExampleBox basicBarExample() {
|
||||
//snippet_1:start
|
||||
var bar1 = new ProgressBar(0);
|
||||
bar1.setPrefWidth(250);
|
||||
|
||||
var bar2 = new ProgressBar(0.5);
|
||||
bar2.setPrefWidth(250);
|
||||
|
||||
var bar3 = new ProgressBar(1);
|
||||
bar3.setPrefWidth(250);
|
||||
//snippet_1:end
|
||||
|
||||
var box = new VBox(VGAP_20, bar1, bar2, bar3);
|
||||
box.setMinHeight(100);
|
||||
|
||||
var description = BBCodeParser.createFormattedText("""
|
||||
[i]ProgressBar[/i] is a specialization of the [i]ProgressIndicator[/i] \
|
||||
which is represented as a horizontal bar."""
|
||||
);
|
||||
|
||||
return new ExampleBox(box, new Snippet(getClass(), 1), description);
|
||||
}
|
||||
|
||||
private ExampleBox basicIndicatorExample() {
|
||||
//snippet_2:start
|
||||
var ind1 = new ProgressIndicator(0);
|
||||
ind1.setMinSize(50, 50);
|
||||
|
||||
var ind2 = new ProgressIndicator(0.5);
|
||||
ind2.setMinSize(50, 50);
|
||||
|
||||
var ind3 = new ProgressIndicator(1);
|
||||
ind3.setMinSize(50, 50);
|
||||
//snippet_2:end
|
||||
|
||||
var box = new HBox(VGAP_20, ind1, ind2, ind3);
|
||||
var description = BBCodeParser.createFormattedText("""
|
||||
[i]ProgressIndicator[/i] is a circular control which is used for indicating progress, \
|
||||
either infinite (aka indeterminate) or finite."""
|
||||
);
|
||||
|
||||
return new ExampleBox(box, new Snippet(getClass(), 2), description);
|
||||
}
|
||||
|
||||
private ExampleBox barSizeSample() {
|
||||
//snippet_3:start
|
||||
var smallBar = new ProgressBar(0.5);
|
||||
smallBar.setPrefWidth(250);
|
||||
smallBar.getStyleClass().add(Styles.SMALL);
|
||||
|
||||
var mediumBar = new ProgressBar(0.5);
|
||||
mediumBar.setPrefWidth(250);
|
||||
mediumBar.getStyleClass().add(Styles.MEDIUM);
|
||||
|
||||
var largeBar = new ProgressBar(0.5);
|
||||
largeBar.setPrefWidth(250);
|
||||
largeBar.getStyleClass().add(Styles.LARGE);
|
||||
//snippet_3:end
|
||||
|
||||
var box = new VBox(
|
||||
VGAP_20,
|
||||
new HBox(HGAP_30, smallBar, new Text("small")),
|
||||
new HBox(HGAP_30, mediumBar, new Text("medium")),
|
||||
new HBox(HGAP_30, largeBar, new Text("large"))
|
||||
);
|
||||
box.setAlignment(Pos.TOP_LEFT);
|
||||
box.getChildren().forEach(c -> ((HBox) c).setAlignment(Pos.CENTER_LEFT));
|
||||
|
||||
var description = BBCodeParser.createFormattedText("""
|
||||
To change the height of the [i]ProgressBar[/i], you can apply the \
|
||||
[code]Styles.SMALL[/code] or [code]Styles.LARGE[/code] style classes, respectively. \
|
||||
Use the large variant if you want to display a text on top of the [i]ProgressBar[/i]."""
|
||||
);
|
||||
|
||||
return new ExampleBox(box, new Snippet(getClass(), 3), description);
|
||||
}
|
||||
|
||||
private ExampleBox ringIndicatorExample() {
|
||||
//snippet_4:start
|
||||
var basicInd = new RingProgressIndicator(0, false);
|
||||
|
||||
var customTextInd = new RingProgressIndicator(0.5, false);
|
||||
customTextInd.setMinSize(75, 75);
|
||||
customTextInd.setStringConverter(new StringConverter<>() {
|
||||
@Override
|
||||
public String toString(Double progress) {
|
||||
return (int) Math.ceil(progress * 100) + "°";
|
||||
}
|
||||
|
||||
@Override
|
||||
public Double fromString(String progress) {
|
||||
return 0d;
|
||||
}
|
||||
});
|
||||
|
||||
var reverseInd = new RingProgressIndicator(0.25, true);
|
||||
reverseInd.setMinSize(150, 150);
|
||||
|
||||
var reverseLabel = new Label("25%");
|
||||
reverseLabel.getStyleClass().add(Styles.TITLE_4);
|
||||
|
||||
var reverseBtn = new Button("", new FontIcon(Feather.PLAY));
|
||||
reverseBtn.getStyleClass().addAll(
|
||||
Styles.BUTTON_CIRCLE, Styles.FLAT
|
||||
);
|
||||
reverseBtn.disableProperty().bind(
|
||||
reverseInd.progressProperty().greaterThan(0.25)
|
||||
);
|
||||
reverseBtn.setOnAction(evt1 -> {
|
||||
var task = new Task<Void>() {
|
||||
@Override
|
||||
protected Void call() throws Exception {
|
||||
int steps = 100;
|
||||
for (int i = 25; i <= steps; i++) {
|
||||
Thread.sleep(100);
|
||||
updateProgress(i, steps);
|
||||
updateMessage(i + "%");
|
||||
}
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
// reset properties, so we can start a new task
|
||||
task.setOnSucceeded(evt2 -> {
|
||||
reverseInd.progressProperty().unbind();
|
||||
reverseLabel.textProperty().unbind();
|
||||
reverseInd.setProgress(0.25);
|
||||
reverseLabel.setText("25%");
|
||||
});
|
||||
|
||||
reverseInd.progressProperty().bind(task.progressProperty());
|
||||
reverseLabel.textProperty().bind(task.messageProperty());
|
||||
|
||||
new Thread(task).start();
|
||||
});
|
||||
|
||||
var reverseGraphic = new VBox(10, reverseLabel, reverseBtn);
|
||||
reverseGraphic.setAlignment(Pos.CENTER);
|
||||
reverseInd.setGraphic(reverseGraphic);
|
||||
//snippet_4:end
|
||||
|
||||
var box = new HBox(HGAP_20, basicInd, customTextInd, reverseInd);
|
||||
box.setAlignment(Pos.CENTER_LEFT);
|
||||
|
||||
var description = BBCodeParser.createFormattedText("""
|
||||
The [i]RingProgressIndicator[/i] is a type of progress indicator \
|
||||
that displays progress as a ring that gradually empties out as a task is completed."""
|
||||
);
|
||||
|
||||
return new ExampleBox(box, new Snippet(getClass(), 4), description);
|
||||
}
|
||||
|
||||
private ExampleBox indeterminateExample() {
|
||||
//snippet_5:start
|
||||
var barToggle = new ToggleButton("Start");
|
||||
barToggle.textProperty().bind(Bindings.createStringBinding(
|
||||
() -> barToggle.isSelected() ? "Stop" : "Start",
|
||||
barToggle.selectedProperty()
|
||||
));
|
||||
var bar = new ProgressBar(0);
|
||||
bar.progressProperty().bind(Bindings.createDoubleBinding(
|
||||
() -> barToggle.isSelected() ? -1d : 0d,
|
||||
barToggle.selectedProperty()
|
||||
));
|
||||
|
||||
var indicatorToggle = new ToggleButton("Start");
|
||||
indicatorToggle.textProperty().bind(Bindings.createStringBinding(
|
||||
() -> indicatorToggle.isSelected() ? "Stop" : "Start",
|
||||
indicatorToggle.selectedProperty()
|
||||
));
|
||||
var indicator = new ProgressIndicator(0);
|
||||
indicator.setMinSize(60, 60);
|
||||
indicator.progressProperty().bind(Bindings.createDoubleBinding(
|
||||
() -> indicatorToggle.isSelected() ? -1d : 0d,
|
||||
indicatorToggle.selectedProperty()
|
||||
));
|
||||
|
||||
var ringToggle = new ToggleButton("Start");
|
||||
ringToggle.textProperty().bind(Bindings.createStringBinding(
|
||||
() -> ringToggle.isSelected() ? "Stop" : "Start",
|
||||
ringToggle.selectedProperty()
|
||||
));
|
||||
var ring = new RingProgressIndicator(0, false);
|
||||
ring.setMinSize(75, 75);
|
||||
ring.progressProperty().bind(Bindings.createDoubleBinding(
|
||||
() -> ringToggle.isSelected() ? -1d : 0d,
|
||||
ringToggle.selectedProperty()
|
||||
));
|
||||
//snippet_5:end
|
||||
|
||||
var grid = new GridPane();
|
||||
grid.setHgap(60);
|
||||
grid.setVgap(10);
|
||||
grid.getColumnConstraints().setAll(
|
||||
new ColumnConstraints(-1, -1, -1, Priority.NEVER, HPos.CENTER, true),
|
||||
new ColumnConstraints(-1, -1, -1, Priority.NEVER, HPos.CENTER, true),
|
||||
new ColumnConstraints(-1, -1, -1, Priority.NEVER, HPos.CENTER, true)
|
||||
);
|
||||
grid.getRowConstraints().setAll(
|
||||
new RowConstraints(-1, -1, -1, Priority.ALWAYS, VPos.CENTER, true),
|
||||
new RowConstraints(-1, -1, -1, Priority.NEVER, VPos.CENTER, true)
|
||||
);
|
||||
grid.addColumn(0, bar, barToggle);
|
||||
grid.addColumn(1, indicator, indicatorToggle);
|
||||
grid.addColumn(2, ring, ringToggle);
|
||||
|
||||
var description = BBCodeParser.createFormattedText("""
|
||||
Animated JavaFX progress indicators aren't cheap. They can consume quite a lot of CPU time."""
|
||||
);
|
||||
|
||||
return new ExampleBox(grid, new Snippet(getClass(), 5), description);
|
||||
}
|
||||
|
||||
private ExampleBox colorChangeExample() {
|
||||
var dataClass = """
|
||||
.example:success .progress-bar {
|
||||
-color-progress-bar-fill: -color-success-emphasis;
|
||||
}
|
||||
.example:danger .progress-bar {
|
||||
-color-progress-bar-fill: -color-danger-emphasis;
|
||||
}
|
||||
.example:success .label,
|
||||
.example:danger .label {
|
||||
-fx-text-fill: -color-fg-emphasis;
|
||||
}""";
|
||||
|
||||
//snippet_6:start
|
||||
var bar = new ProgressBar(0);
|
||||
bar.getStyleClass().add(Styles.LARGE);
|
||||
bar.setPrefWidth(300);
|
||||
bar.setMaxWidth(300);
|
||||
|
||||
var barText = new Label();
|
||||
|
||||
var barStack = new StackPane(bar, barText);
|
||||
barStack.getStyleClass().add("example");
|
||||
barStack.setPrefWidth(300);
|
||||
barStack.setMaxWidth(300);
|
||||
|
||||
var runBtn = new Button("Start");
|
||||
runBtn.disableProperty().bind(
|
||||
bar.progressProperty().greaterThan(0)
|
||||
);
|
||||
|
||||
var content = new VBox(VGAP_10, barStack, runBtn);
|
||||
content.setAlignment(Pos.CENTER_LEFT);
|
||||
content.setPrefHeight(200);
|
||||
// .example:success .progress-bar {
|
||||
// -color-progress-bar-fill: -color-success-emphasis;
|
||||
// }
|
||||
// .example:danger .progress-bar {
|
||||
// -color-progress-bar-fill: -color-danger-emphasis;
|
||||
// }
|
||||
// .example:success .label,
|
||||
// .example:danger .label {
|
||||
// -fx-text-fill: -color-fg-emphasis;
|
||||
// }
|
||||
new CSSFragment(dataClass).addTo(content);
|
||||
|
||||
bar.progressProperty().addListener((obs, old, val) -> {
|
||||
if (val == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (val.floatValue() > 0.80) {
|
||||
barStack.pseudoClassStateChanged(Styles.STATE_DANGER, true);
|
||||
} else if (val.floatValue() > 0.47) {
|
||||
barStack.pseudoClassStateChanged(Styles.STATE_SUCCESS, true);
|
||||
}
|
||||
});
|
||||
|
||||
runBtn.setOnAction(evt1 -> {
|
||||
var task = new Task<Void>() {
|
||||
@Override
|
||||
protected Void call() throws Exception {
|
||||
int steps = 1_000;
|
||||
for (int i = 0; i < steps; i++) {
|
||||
Thread.sleep(10);
|
||||
updateProgress(i, steps);
|
||||
updateMessage(String.valueOf(i));
|
||||
}
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
// reset properties, so we can start a new task
|
||||
task.setOnSucceeded(evt2 -> {
|
||||
bar.progressProperty().unbind();
|
||||
barText.textProperty().unbind();
|
||||
|
||||
bar.setProgress(0);
|
||||
barText.setText(null);
|
||||
|
||||
barStack.pseudoClassStateChanged(Styles.STATE_SUCCESS, false);
|
||||
barStack.pseudoClassStateChanged(Styles.STATE_DANGER, false);
|
||||
});
|
||||
|
||||
bar.progressProperty().bind(task.progressProperty());
|
||||
barText.textProperty().bind(task.messageProperty());
|
||||
|
||||
new Thread(task).start();
|
||||
});
|
||||
//snippet_6:end
|
||||
|
||||
var description = BBCodeParser.createFormattedText("""
|
||||
This example demonstrates how looked-up color variables can be used to \
|
||||
change the progress bar color while the task is in progress."""
|
||||
);
|
||||
|
||||
return new ExampleBox(content, new Snippet(getClass(), 6), description);
|
||||
}
|
||||
}
|
@ -1,336 +0,0 @@
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
|
||||
package atlantafx.sampler.page.components;
|
||||
|
||||
import static atlantafx.base.theme.Styles.BUTTON_CIRCLE;
|
||||
import static atlantafx.base.theme.Styles.FLAT;
|
||||
import static atlantafx.base.theme.Styles.LARGE;
|
||||
import static atlantafx.base.theme.Styles.MEDIUM;
|
||||
import static atlantafx.base.theme.Styles.SMALL;
|
||||
import static atlantafx.base.theme.Styles.TITLE_4;
|
||||
import static atlantafx.sampler.page.SampleBlock.BLOCK_HGAP;
|
||||
import static atlantafx.sampler.page.SampleBlock.BLOCK_VGAP;
|
||||
|
||||
import atlantafx.base.controls.RingProgressIndicator;
|
||||
import atlantafx.sampler.page.AbstractPage;
|
||||
import atlantafx.sampler.page.Page;
|
||||
import atlantafx.sampler.page.SampleBlock;
|
||||
import atlantafx.sampler.theme.CSSFragment;
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.concurrent.Task;
|
||||
import javafx.css.PseudoClass;
|
||||
import javafx.geometry.HPos;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.geometry.VPos;
|
||||
import javafx.scene.control.Button;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.control.ProgressBar;
|
||||
import javafx.scene.control.ProgressIndicator;
|
||||
import javafx.scene.control.ToggleButton;
|
||||
import javafx.scene.layout.ColumnConstraints;
|
||||
import javafx.scene.layout.FlowPane;
|
||||
import javafx.scene.layout.GridPane;
|
||||
import javafx.scene.layout.HBox;
|
||||
import javafx.scene.layout.Priority;
|
||||
import javafx.scene.layout.RowConstraints;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import javafx.scene.layout.VBox;
|
||||
import javafx.scene.text.Text;
|
||||
import javafx.util.StringConverter;
|
||||
import org.kordamp.ikonli.feather.Feather;
|
||||
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 ProgressPage extends AbstractPage {
|
||||
|
||||
public static final String NAME = "Progress";
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return NAME;
|
||||
}
|
||||
|
||||
public ProgressPage() {
|
||||
super();
|
||||
|
||||
var grid = new GridPane();
|
||||
grid.setHgap(Page.PAGE_HGAP);
|
||||
grid.setVgap(Page.PAGE_VGAP);
|
||||
|
||||
grid.add(basicBarSample(), 0, 0);
|
||||
grid.add(basicIndicatorSample(), 1, 0);
|
||||
|
||||
grid.add(ringIndicatorSample(), 0, 1);
|
||||
grid.add(barSizeSample(), 1, 1);
|
||||
|
||||
grid.add(indeterminateSample(), 0, 2);
|
||||
grid.add(colorChangeSample(), 1, 2);
|
||||
|
||||
setUserContent(grid);
|
||||
}
|
||||
|
||||
private SampleBlock basicBarSample() {
|
||||
var flowPane = new FlowPane(
|
||||
BLOCK_HGAP, BLOCK_VGAP,
|
||||
createBar(0, false),
|
||||
createBar(0.5, false),
|
||||
createBar(1, false),
|
||||
createBar(0.5, true)
|
||||
);
|
||||
flowPane.setAlignment(Pos.CENTER_LEFT);
|
||||
|
||||
return new SampleBlock("Progress Bar", flowPane);
|
||||
}
|
||||
|
||||
private SampleBlock basicIndicatorSample() {
|
||||
var flowPane = new FlowPane(
|
||||
BLOCK_HGAP, BLOCK_VGAP,
|
||||
createIndicator(0, false),
|
||||
createIndicator(0.5, false),
|
||||
createIndicator(1, false),
|
||||
createIndicator(0.5, true)
|
||||
);
|
||||
flowPane.setAlignment(Pos.TOP_LEFT);
|
||||
|
||||
return new SampleBlock("Progress Indicator", flowPane);
|
||||
}
|
||||
|
||||
private SampleBlock barSizeSample() {
|
||||
var container = new VBox(
|
||||
BLOCK_VGAP,
|
||||
new HBox(20, createBar(0.5, false, SMALL), new Text("small")),
|
||||
new HBox(20, createBar(0.5, false, MEDIUM), new Text("medium")),
|
||||
new HBox(20, createBar(0.5, false, LARGE), new Text("large"))
|
||||
);
|
||||
container.setAlignment(Pos.TOP_LEFT);
|
||||
container.getChildren().forEach(c -> ((HBox) c).setAlignment(Pos.CENTER_LEFT));
|
||||
|
||||
return new SampleBlock("Size", container);
|
||||
}
|
||||
|
||||
private SampleBlock ringIndicatorSample() {
|
||||
var basicIndicator = new RingProgressIndicator(0, false);
|
||||
|
||||
var customTextIndicator = new RingProgressIndicator(0.5, false);
|
||||
customTextIndicator.setMinSize(75, 75);
|
||||
customTextIndicator.setStringConverter(new StringConverter<>() {
|
||||
@Override
|
||||
public String toString(Double progress) {
|
||||
return (int) Math.ceil(progress * 100) + "°";
|
||||
}
|
||||
|
||||
@Override
|
||||
public Double fromString(String progress) {
|
||||
return 0d;
|
||||
}
|
||||
});
|
||||
|
||||
var reverseIndicator = new RingProgressIndicator(0.25, true);
|
||||
reverseIndicator.setMinSize(150, 150);
|
||||
|
||||
var reverseIndicatorLabel = new Label("25%");
|
||||
reverseIndicatorLabel.getStyleClass().add(TITLE_4);
|
||||
|
||||
var reversePlayButton = new Button("", new FontIcon(Feather.PLAY));
|
||||
reversePlayButton.getStyleClass().addAll(BUTTON_CIRCLE, FLAT);
|
||||
reversePlayButton.disableProperty().bind(reverseIndicator.progressProperty().greaterThan(0.25));
|
||||
reversePlayButton.setOnAction(e1 -> {
|
||||
var task = new Task<Void>() {
|
||||
@Override
|
||||
protected Void call() throws Exception {
|
||||
int steps = 100;
|
||||
|
||||
for (int i = 25; i <= steps; i++) {
|
||||
Thread.sleep(100);
|
||||
updateProgress(i, steps);
|
||||
updateMessage(i + "%");
|
||||
}
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
// reset properties, so we can start a new task
|
||||
task.setOnSucceeded(e2 -> {
|
||||
reverseIndicator.progressProperty().unbind();
|
||||
reverseIndicatorLabel.textProperty().unbind();
|
||||
|
||||
reverseIndicator.setProgress(0.25);
|
||||
reverseIndicatorLabel.setText("25%");
|
||||
});
|
||||
|
||||
reverseIndicator.progressProperty().bind(task.progressProperty());
|
||||
reverseIndicatorLabel.textProperty().bind(task.messageProperty());
|
||||
|
||||
new Thread(task).start();
|
||||
});
|
||||
|
||||
var reverseBox = new VBox(10, reverseIndicatorLabel, reversePlayButton);
|
||||
reverseBox.setAlignment(Pos.CENTER);
|
||||
reverseIndicator.setGraphic(reverseBox);
|
||||
|
||||
// ~
|
||||
|
||||
var box = new HBox(BLOCK_HGAP, basicIndicator, customTextIndicator, reverseIndicator);
|
||||
box.setAlignment(Pos.CENTER_LEFT);
|
||||
|
||||
return new SampleBlock("Ring Indicator", box);
|
||||
}
|
||||
|
||||
private SampleBlock indeterminateSample() {
|
||||
var grid = new GridPane();
|
||||
grid.setHgap(40);
|
||||
grid.setVgap(BLOCK_VGAP);
|
||||
grid.getColumnConstraints().setAll(
|
||||
new ColumnConstraints(-1, -1, -1, Priority.NEVER, HPos.CENTER, true),
|
||||
new ColumnConstraints(-1, -1, -1, Priority.NEVER, HPos.CENTER, true),
|
||||
new ColumnConstraints(-1, -1, -1, Priority.NEVER, HPos.CENTER, true)
|
||||
);
|
||||
grid.getRowConstraints().setAll(
|
||||
new RowConstraints(-1, -1, -1, Priority.ALWAYS, VPos.CENTER, true),
|
||||
new RowConstraints(-1, -1, -1, Priority.NEVER, VPos.CENTER, true)
|
||||
);
|
||||
|
||||
var barToggle = new ToggleButton("Start");
|
||||
barToggle.textProperty().bind(Bindings.createStringBinding(
|
||||
() -> barToggle.isSelected() ? "Stop" : "Start", barToggle.selectedProperty())
|
||||
);
|
||||
var bar = createBar(0, false);
|
||||
bar.progressProperty().bind(Bindings.createDoubleBinding(
|
||||
() -> barToggle.isSelected() ? -1d : 0d, barToggle.selectedProperty())
|
||||
);
|
||||
grid.add(bar, 0, 0);
|
||||
grid.add(barToggle, 0, 1);
|
||||
|
||||
var indicatorToggle = new ToggleButton("Start");
|
||||
indicatorToggle.textProperty().bind(Bindings.createStringBinding(
|
||||
() -> indicatorToggle.isSelected() ? "Stop" : "Start", indicatorToggle.selectedProperty())
|
||||
);
|
||||
var indicator = createIndicator(0, false);
|
||||
indicator.setPrefSize(75, 75);
|
||||
indicator.progressProperty().bind(Bindings.createDoubleBinding(
|
||||
() -> indicatorToggle.isSelected() ? -1d : 0d, indicatorToggle.selectedProperty())
|
||||
);
|
||||
grid.add(indicator, 1, 0);
|
||||
grid.add(indicatorToggle, 1, 1);
|
||||
|
||||
var ringToggle = new ToggleButton("Start");
|
||||
ringToggle.textProperty().bind(Bindings.createStringBinding(
|
||||
() -> ringToggle.isSelected() ? "Stop" : "Start", ringToggle.selectedProperty())
|
||||
);
|
||||
var ring = new RingProgressIndicator(0, false);
|
||||
ring.setMinSize(75, 75);
|
||||
ring.progressProperty().bind(Bindings.createDoubleBinding(
|
||||
() -> ringToggle.isSelected() ? -1d : 0d, ringToggle.selectedProperty())
|
||||
);
|
||||
grid.add(ring, 2, 0);
|
||||
grid.add(ringToggle, 2, 1);
|
||||
|
||||
return new SampleBlock("Indeterminate", grid,
|
||||
"Animated JavaFX progress indicators aren't cheap. They can consume quite a lot of CPU time."
|
||||
);
|
||||
}
|
||||
|
||||
private SampleBlock colorChangeSample() {
|
||||
final var stateSuccess = PseudoClass.getPseudoClass("state-success");
|
||||
final var stateDanger = PseudoClass.getPseudoClass("state-danger");
|
||||
final var width = 300;
|
||||
|
||||
var bar = new ProgressBar(0);
|
||||
bar.getStyleClass().add(LARGE);
|
||||
bar.setPrefWidth(width);
|
||||
bar.setMaxWidth(width);
|
||||
|
||||
var barText = new Label();
|
||||
|
||||
var barStack = new StackPane(bar, barText);
|
||||
barStack.getStyleClass().add("example");
|
||||
barStack.setPrefWidth(width);
|
||||
barStack.setMaxWidth(width);
|
||||
|
||||
var runBtn = new Button("Start");
|
||||
runBtn.disableProperty().bind(bar.progressProperty().greaterThan(0));
|
||||
|
||||
// ~
|
||||
|
||||
var content = new VBox(BLOCK_VGAP);
|
||||
content.getChildren().setAll(barStack, runBtn);
|
||||
content.setAlignment(Pos.CENTER_LEFT);
|
||||
content.setPrefHeight(200);
|
||||
|
||||
bar.progressProperty().addListener((obs, old, val) -> {
|
||||
if (val == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (val.floatValue() > 0.80) {
|
||||
barStack.pseudoClassStateChanged(stateDanger, true);
|
||||
} else if (val.floatValue() > 0.47) {
|
||||
barStack.pseudoClassStateChanged(stateSuccess, true);
|
||||
}
|
||||
});
|
||||
|
||||
new CSSFragment("""
|
||||
.example:state-success .progress-bar {
|
||||
-color-progress-bar-fill: -color-success-emphasis;
|
||||
}
|
||||
.example:state-danger .progress-bar {
|
||||
-color-progress-bar-fill: -color-danger-emphasis;
|
||||
}
|
||||
.example:state-success .label,
|
||||
.example:state-danger .label {
|
||||
-fx-text-fill: -color-fg-emphasis;
|
||||
}
|
||||
""").addTo(content);
|
||||
|
||||
runBtn.setOnAction(e1 -> {
|
||||
var task = new Task<Void>() {
|
||||
@Override
|
||||
protected Void call() throws Exception {
|
||||
int steps = 1_000;
|
||||
|
||||
for (int i = 0; i < steps; i++) {
|
||||
Thread.sleep(10);
|
||||
updateProgress(i, steps);
|
||||
updateMessage(String.valueOf(i));
|
||||
}
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
// reset properties, so we can start a new task
|
||||
task.setOnSucceeded(e2 -> {
|
||||
bar.progressProperty().unbind();
|
||||
barText.textProperty().unbind();
|
||||
|
||||
bar.setProgress(0);
|
||||
barText.setText(null);
|
||||
|
||||
barStack.pseudoClassStateChanged(stateSuccess, false);
|
||||
barStack.pseudoClassStateChanged(stateDanger, false);
|
||||
});
|
||||
|
||||
bar.progressProperty().bind(task.progressProperty());
|
||||
barText.textProperty().bind(task.messageProperty());
|
||||
|
||||
new Thread(task).start();
|
||||
});
|
||||
|
||||
return new SampleBlock("Dynamic Color Change", content);
|
||||
}
|
||||
|
||||
private ProgressBar createBar(double progress, boolean disabled, String... styleClasses) {
|
||||
var bar = new ProgressBar(progress);
|
||||
bar.getStyleClass().addAll(styleClasses);
|
||||
bar.setDisable(disabled);
|
||||
return bar;
|
||||
}
|
||||
|
||||
private ProgressIndicator createIndicator(double progress, boolean disabled) {
|
||||
var indicator = new ProgressIndicator(progress);
|
||||
indicator.setMinSize(50, 50);
|
||||
indicator.setMaxSize(50, 50);
|
||||
indicator.setDisable(disabled);
|
||||
return indicator;
|
||||
}
|
||||
}
|
@ -2,17 +2,15 @@
|
||||
|
||||
package atlantafx.sampler.page.components;
|
||||
|
||||
import static atlantafx.sampler.page.SampleBlock.BLOCK_VGAP;
|
||||
|
||||
import atlantafx.sampler.page.AbstractPage;
|
||||
import atlantafx.sampler.page.Page;
|
||||
import atlantafx.sampler.page.SampleBlock;
|
||||
import atlantafx.base.util.BBCodeParser;
|
||||
import atlantafx.sampler.page.ExampleBox;
|
||||
import atlantafx.sampler.page.OutlinePage;
|
||||
import atlantafx.sampler.page.Snippet;
|
||||
import javafx.scene.control.RadioButton;
|
||||
import javafx.scene.control.ToggleGroup;
|
||||
import javafx.scene.layout.FlowPane;
|
||||
import javafx.scene.layout.VBox;
|
||||
|
||||
public class RadioButtonPage extends AbstractPage {
|
||||
public class RadioButtonPage extends OutlinePage {
|
||||
|
||||
public static final String NAME = "RadioButton";
|
||||
|
||||
@ -23,30 +21,30 @@ public class RadioButtonPage extends AbstractPage {
|
||||
|
||||
public RadioButtonPage() {
|
||||
super();
|
||||
setUserContent(new FlowPane(
|
||||
Page.PAGE_HGAP, Page.PAGE_VGAP,
|
||||
basicSample(),
|
||||
groupSample(),
|
||||
disabledSample()
|
||||
));
|
||||
|
||||
addFormattedText("""
|
||||
[i]RadioButton[/i]'s create a series of items where only one item can be selected.""");
|
||||
addSection("Usage", usageExample());
|
||||
addSection("Toggle Group", toggleGroupExample());
|
||||
}
|
||||
|
||||
private SampleBlock basicSample() {
|
||||
private ExampleBox usageExample() {
|
||||
//snippet_1:start
|
||||
var radio1 = new RadioButton("_Check Me");
|
||||
radio1.setMnemonicParsing(true);
|
||||
|
||||
var radio2 = new RadioButton("Check Me");
|
||||
//snippet_1:end
|
||||
|
||||
return new SampleBlock("Basic", new VBox(BLOCK_VGAP, radio1, radio2));
|
||||
var box = new VBox(VGAP_10, radio1, radio2);
|
||||
var description = BBCodeParser.createFormattedText("""
|
||||
A radio button control can be either selected or deselected.""");
|
||||
|
||||
return new ExampleBox(box, new Snippet(getClass(), 1), description);
|
||||
}
|
||||
|
||||
private SampleBlock disabledSample() {
|
||||
var radio = new RadioButton("Check Me");
|
||||
radio.setDisable(true);
|
||||
return new SampleBlock("Disabled", radio);
|
||||
}
|
||||
|
||||
private SampleBlock groupSample() {
|
||||
private ExampleBox toggleGroupExample() {
|
||||
//snippet_2:start
|
||||
var group = new ToggleGroup();
|
||||
|
||||
var musicRadio = new RadioButton("Music");
|
||||
@ -58,7 +56,14 @@ public class RadioButtonPage extends AbstractPage {
|
||||
|
||||
var videosRadio = new RadioButton("Videos");
|
||||
videosRadio.setToggleGroup(group);
|
||||
//snippet_2:end
|
||||
|
||||
return new SampleBlock("Toggle Group", new VBox(5, musicRadio, imagesRadio, videosRadio));
|
||||
var box = new VBox(VGAP_10, musicRadio, imagesRadio, videosRadio);
|
||||
var description = BBCodeParser.createFormattedText("""
|
||||
Typically radio buttons are combined into a group where only one button \
|
||||
at a time can be selected. This behavior distinguishes them from toggle buttons, \
|
||||
because all toggle buttons in a group can be in a deselected state.""");
|
||||
|
||||
return new ExampleBox(box, new Snippet(getClass(), 2), description);
|
||||
}
|
||||
}
|
||||
|
@ -3,8 +3,6 @@
|
||||
package atlantafx.sampler.page.components;
|
||||
|
||||
import atlantafx.sampler.page.AbstractPage;
|
||||
import atlantafx.sampler.page.Page;
|
||||
import atlantafx.sampler.page.SampleBlock;
|
||||
import javafx.scene.control.ScrollPane;
|
||||
import javafx.scene.layout.FlowPane;
|
||||
import javafx.scene.layout.GridPane;
|
||||
@ -15,7 +13,6 @@ import javafx.scene.layout.VBox;
|
||||
public class ScrollPanePage extends AbstractPage {
|
||||
|
||||
public static final String NAME = "ScrollPane";
|
||||
private static final int SPACING = 1;
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
@ -24,63 +21,69 @@ public class ScrollPanePage extends AbstractPage {
|
||||
|
||||
public ScrollPanePage() {
|
||||
super();
|
||||
setUserContent(new FlowPane(
|
||||
Page.PAGE_VGAP, Page.PAGE_HGAP,
|
||||
horizontalScrollSample(),
|
||||
verticalScrollSample(),
|
||||
gridScrollSample(),
|
||||
disabledSample()
|
||||
|
||||
addFormattedText("""
|
||||
A control that provides a scrolled, clipped viewport of its contents. \
|
||||
It allows the user to scroll the content around either directly (panning) \
|
||||
or by using scroll bars.
|
||||
|
||||
The [i]ScrollPane[/i] allows specification of the scroll bar policy, which \
|
||||
determines when scroll bars are displayed: always, never, or only when they \
|
||||
are needed. The scroll bar policy can be specified independently for the \
|
||||
horizontal and vertical scroll bars."""
|
||||
);
|
||||
addNode(new FlowPane(
|
||||
40, 40,
|
||||
horizontalScrollExample(),
|
||||
verticalScrollExample(),
|
||||
gridScrollExample()
|
||||
));
|
||||
}
|
||||
|
||||
private SampleBlock horizontalScrollSample() {
|
||||
private ScrollPane horizontalScrollExample() {
|
||||
var scrollPane = new ScrollPane();
|
||||
scrollPane.setMaxHeight(100);
|
||||
scrollPane.setMaxWidth(300);
|
||||
scrollPane.setContent(new HBox(SPACING,
|
||||
scrollPane.setContent(new HBox(1,
|
||||
createRegion(200, 100, "-color-success-emphasis"),
|
||||
createRegion(200, 100, "-color-danger-emphasis")
|
||||
));
|
||||
scrollPane.setVbarPolicy(ScrollPane.ScrollBarPolicy.NEVER);
|
||||
|
||||
return new SampleBlock("Horizontal Scrolling", scrollPane);
|
||||
return scrollPane;
|
||||
}
|
||||
|
||||
private SampleBlock verticalScrollSample() {
|
||||
private ScrollPane verticalScrollExample() {
|
||||
var scrollPane = new ScrollPane();
|
||||
scrollPane.setMaxHeight(100);
|
||||
scrollPane.setMaxWidth(300);
|
||||
scrollPane.setContent(new VBox(SPACING,
|
||||
scrollPane.setContent(new VBox(1,
|
||||
createRegion(300, 75, "-color-success-emphasis"),
|
||||
createRegion(300, 75, "-color-danger-emphasis")
|
||||
));
|
||||
scrollPane.setHbarPolicy(ScrollPane.ScrollBarPolicy.NEVER);
|
||||
|
||||
return new SampleBlock("Vertical Scrolling", scrollPane);
|
||||
return scrollPane;
|
||||
}
|
||||
|
||||
private SampleBlock gridScrollSample() {
|
||||
private ScrollPane gridScrollExample() {
|
||||
var grid = new GridPane();
|
||||
grid.add(createRegion(200, 75, "-color-success-emphasis"), 0, 0);
|
||||
grid.add(createRegion(200, 75, "-color-danger-emphasis"), 1, 0);
|
||||
grid.add(createRegion(200, 75, "-color-danger-emphasis"), 0, 1);
|
||||
grid.add(createRegion(200, 75, "-color-success-emphasis"), 1, 1);
|
||||
grid.setHgap(SPACING);
|
||||
grid.setVgap(SPACING);
|
||||
grid.setHgap(1);
|
||||
grid.setVgap(1);
|
||||
grid.addColumn(0,
|
||||
createRegion(200, 75, "-color-success-emphasis"),
|
||||
createRegion(200, 75, "-color-danger-emphasis")
|
||||
);
|
||||
grid.addColumn(1,
|
||||
createRegion(200, 75, "-color-danger-emphasis"),
|
||||
createRegion(200, 75, "-color-success-emphasis")
|
||||
);
|
||||
|
||||
var gridScroll = new ScrollPane();
|
||||
var gridScroll = new ScrollPane(grid);
|
||||
gridScroll.setMaxHeight(100);
|
||||
gridScroll.setMaxWidth(300);
|
||||
gridScroll.setContent(grid);
|
||||
|
||||
return new SampleBlock("Scrolling", gridScroll);
|
||||
}
|
||||
|
||||
private SampleBlock disabledSample() {
|
||||
var block = gridScrollSample();
|
||||
block.setTitle("Disabled");
|
||||
block.getContent().setDisable(true);
|
||||
return block;
|
||||
return gridScroll;
|
||||
}
|
||||
|
||||
private Region createRegion(int width, int height, String bg) {
|
||||
|
@ -2,28 +2,22 @@
|
||||
|
||||
package atlantafx.sampler.page.components;
|
||||
|
||||
import static javafx.geometry.Orientation.HORIZONTAL;
|
||||
import static javafx.geometry.Orientation.VERTICAL;
|
||||
import static javafx.geometry.Pos.CENTER;
|
||||
|
||||
import atlantafx.base.theme.Styles;
|
||||
import atlantafx.sampler.page.AbstractPage;
|
||||
import atlantafx.sampler.page.Page;
|
||||
import atlantafx.sampler.page.SampleBlock;
|
||||
import atlantafx.sampler.page.ExampleBox;
|
||||
import atlantafx.sampler.page.OutlinePage;
|
||||
import atlantafx.sampler.page.Snippet;
|
||||
import javafx.geometry.Orientation;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.control.Separator;
|
||||
import javafx.scene.layout.FlowPane;
|
||||
import javafx.scene.layout.GridPane;
|
||||
import javafx.scene.layout.HBox;
|
||||
import javafx.scene.layout.Pane;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import javafx.scene.layout.VBox;
|
||||
|
||||
public final class SeparatorPage extends AbstractPage {
|
||||
public final class SeparatorPage extends OutlinePage {
|
||||
|
||||
public static final String NAME = "Separator";
|
||||
private static final int SPACING = 50;
|
||||
private static final int PANE_SIZE = 100;
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
@ -32,79 +26,85 @@ public final class SeparatorPage extends AbstractPage {
|
||||
|
||||
public SeparatorPage() {
|
||||
super();
|
||||
setUserContent(new FlowPane(
|
||||
Page.PAGE_HGAP, Page.PAGE_VGAP,
|
||||
orientationSample(),
|
||||
sizeSample()
|
||||
));
|
||||
|
||||
addFormattedText("""
|
||||
A horizontal or vertical separator line. A horizontal separator occupies \
|
||||
the full horizontal space allocated to it (less padding), and a vertical \
|
||||
separator occupies the full vertical space allocated to it (less padding)."""
|
||||
);
|
||||
addSection("Orientation", orientationExample());
|
||||
addSection("Size", sizeExample());
|
||||
}
|
||||
|
||||
private SampleBlock orientationSample() {
|
||||
var hBox = new HBox(
|
||||
createPane("Left", VERTICAL),
|
||||
new Separator(VERTICAL),
|
||||
createPane("Right", VERTICAL)
|
||||
private ExampleBox orientationExample() {
|
||||
//snippet_1:start
|
||||
var hbox = new HBox(
|
||||
createPane("Left", Orientation.VERTICAL),
|
||||
new Separator(Orientation.VERTICAL),
|
||||
createPane("Right", Orientation.VERTICAL)
|
||||
);
|
||||
hBox.setAlignment(CENTER);
|
||||
|
||||
var vBox = new VBox(
|
||||
createPane("Top", HORIZONTAL),
|
||||
new Separator(HORIZONTAL),
|
||||
createPane("Bottom", HORIZONTAL)
|
||||
var vbox = new VBox(
|
||||
createPane("Top", Orientation.HORIZONTAL),
|
||||
new Separator(Orientation.HORIZONTAL),
|
||||
createPane("Bottom", Orientation.HORIZONTAL)
|
||||
);
|
||||
vBox.setAlignment(CENTER);
|
||||
//snippet_1:end
|
||||
|
||||
return new SampleBlock("Orientation", new HBox(SPACING, hBox, vBox));
|
||||
var box = new HBox(50, hbox, vbox);
|
||||
|
||||
return new ExampleBox(box, new Snippet(getClass(), 1));
|
||||
}
|
||||
|
||||
private SampleBlock sizeSample() {
|
||||
var smallSep = new Separator(VERTICAL);
|
||||
private ExampleBox sizeExample() {
|
||||
//snippet_2:start
|
||||
var smallSep = new Separator(Orientation.VERTICAL);
|
||||
smallSep.getStyleClass().add(Styles.SMALL);
|
||||
var smallBox = new HBox(
|
||||
createPane("Left", VERTICAL),
|
||||
createPane("Left", Orientation.VERTICAL),
|
||||
smallSep,
|
||||
createPane("Right", VERTICAL)
|
||||
createPane("Right", Orientation.VERTICAL)
|
||||
);
|
||||
smallBox.setAlignment(CENTER);
|
||||
|
||||
var mediumSep = new Separator(VERTICAL);
|
||||
var mediumSep = new Separator(Orientation.VERTICAL);
|
||||
mediumSep.getStyleClass().add(Styles.MEDIUM);
|
||||
var mediumBox = new HBox(
|
||||
createPane("Left", VERTICAL),
|
||||
createPane("Left", Orientation.VERTICAL),
|
||||
mediumSep,
|
||||
createPane("Right", VERTICAL)
|
||||
createPane("Right", Orientation.VERTICAL)
|
||||
);
|
||||
mediumBox.setAlignment(CENTER);
|
||||
|
||||
var largeSep = new Separator(VERTICAL);
|
||||
var largeSep = new Separator(Orientation.VERTICAL);
|
||||
largeSep.getStyleClass().add(Styles.LARGE);
|
||||
var largeBox = new HBox(
|
||||
createPane("Left", VERTICAL),
|
||||
createPane("Left", Orientation.VERTICAL),
|
||||
largeSep,
|
||||
createPane("Right", VERTICAL)
|
||||
createPane("Right", Orientation.VERTICAL)
|
||||
);
|
||||
largeBox.setAlignment(CENTER);
|
||||
//snippet_2:end
|
||||
|
||||
return new SampleBlock("Size", new HBox(SPACING, smallBox, mediumBox, largeBox));
|
||||
var grid = new GridPane();
|
||||
grid.setHgap(50);
|
||||
grid.setVgap(50);
|
||||
grid.addRow(0, smallBox, mediumBox);
|
||||
grid.addRow(1, largeBox);
|
||||
|
||||
return new ExampleBox(grid, new Snippet(getClass(), 2));
|
||||
}
|
||||
|
||||
private Pane createPane(String text, Orientation orientation) {
|
||||
var pane = new StackPane();
|
||||
pane.getChildren().setAll(new Label(text));
|
||||
var pane = new StackPane(new Label(text));
|
||||
pane.getStyleClass().add("bordered");
|
||||
pane.setMinSize(100, 100);
|
||||
|
||||
if (orientation == HORIZONTAL) {
|
||||
pane.setMinHeight(PANE_SIZE);
|
||||
pane.setPrefHeight(PANE_SIZE);
|
||||
pane.setMaxHeight(PANE_SIZE);
|
||||
pane.setMinWidth(PANE_SIZE);
|
||||
if (orientation == Orientation.HORIZONTAL) {
|
||||
pane.setPrefHeight(100);
|
||||
pane.setMaxHeight(100);
|
||||
}
|
||||
|
||||
if (orientation == VERTICAL) {
|
||||
pane.setMinWidth(PANE_SIZE);
|
||||
pane.setPrefWidth(PANE_SIZE);
|
||||
pane.setMaxWidth(PANE_SIZE);
|
||||
pane.setMinHeight(PANE_SIZE);
|
||||
if (orientation == Orientation.VERTICAL) {
|
||||
pane.setPrefWidth(100);
|
||||
pane.setMaxWidth(100);
|
||||
}
|
||||
|
||||
return pane;
|
||||
|
@ -2,23 +2,18 @@
|
||||
|
||||
package atlantafx.sampler.page.components;
|
||||
|
||||
import static javafx.geometry.Orientation.VERTICAL;
|
||||
|
||||
import atlantafx.base.controls.ProgressSliderSkin;
|
||||
import atlantafx.base.theme.Styles;
|
||||
import atlantafx.sampler.page.AbstractPage;
|
||||
import atlantafx.sampler.page.Page;
|
||||
import atlantafx.sampler.page.SampleBlock;
|
||||
import atlantafx.sampler.page.ExampleBox;
|
||||
import atlantafx.sampler.page.OutlinePage;
|
||||
import atlantafx.sampler.page.Snippet;
|
||||
import javafx.geometry.Orientation;
|
||||
import javafx.scene.control.Slider;
|
||||
import javafx.scene.layout.FlowPane;
|
||||
import javafx.scene.layout.GridPane;
|
||||
import javafx.scene.layout.Pane;
|
||||
|
||||
public class SliderPage extends AbstractPage {
|
||||
public class SliderPage extends OutlinePage {
|
||||
|
||||
public static final String NAME = "Slider";
|
||||
private static final int SLIDER_SIZE = 180;
|
||||
private static final int SPACING = 20;
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
@ -27,32 +22,44 @@ public class SliderPage extends AbstractPage {
|
||||
|
||||
public SliderPage() {
|
||||
super();
|
||||
setUserContent(new FlowPane(
|
||||
Page.PAGE_HGAP, Page.PAGE_VGAP,
|
||||
basicSample(),
|
||||
smallSample(),
|
||||
largeSample(),
|
||||
disabledSample()
|
||||
));
|
||||
|
||||
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] \
|
||||
or [i]thumb[/i] which is dragged within the track. The [i]Slider[/i] can optionally show tick \
|
||||
marks and labels indicating the different slider position values.
|
||||
|
||||
AtlantaFX also provides the [b]ProgressSliderSkin[/b], which implements color support for \
|
||||
[i]Slider[/i] progress indication. Additionally, it adds the [code]Styles.SMALL[/code] and \
|
||||
[code]Styles.LARGE[/code] style class modifiers to change the [i]Slider[/i] size."""
|
||||
);
|
||||
addSection("Usage", usageExample());
|
||||
addSection("Small", smallExample());
|
||||
addSection("Large", largeExample());
|
||||
}
|
||||
|
||||
private SampleBlock basicSample() {
|
||||
private ExampleBox usageExample() {
|
||||
//snippet_1:start
|
||||
var hSlider = new Slider(1, 5, 3);
|
||||
|
||||
var hTickSlider = createTickSlider();
|
||||
hTickSlider.setSkin(new ProgressSliderSkin(hTickSlider));
|
||||
|
||||
var vSlider = new Slider(1, 5, 3);
|
||||
vSlider.setOrientation(VERTICAL);
|
||||
vSlider.setOrientation(Orientation.VERTICAL);
|
||||
|
||||
var vTickSlider = createTickSlider();
|
||||
vTickSlider.setOrientation(VERTICAL);
|
||||
vTickSlider.setOrientation(Orientation.VERTICAL);
|
||||
vTickSlider.setSkin(new ProgressSliderSkin(vTickSlider));
|
||||
//snippet_1:end
|
||||
|
||||
return new SampleBlock("Basic", createContent(hSlider, hTickSlider, vSlider, vTickSlider));
|
||||
var box = createGrid(hSlider, hTickSlider, vSlider, vTickSlider);
|
||||
|
||||
return new ExampleBox(box, new Snippet(getClass(), 1));
|
||||
}
|
||||
|
||||
private Pane smallSample() {
|
||||
private ExampleBox smallExample() {
|
||||
//snippet_2:start
|
||||
var hSlider = new Slider(1, 5, 3);
|
||||
hSlider.getStyleClass().add(Styles.SMALL);
|
||||
|
||||
@ -61,18 +68,22 @@ public class SliderPage extends AbstractPage {
|
||||
hTickSlider.setSkin(new ProgressSliderSkin(hTickSlider));
|
||||
|
||||
var vSlider = new Slider(1, 5, 3);
|
||||
vSlider.setOrientation(VERTICAL);
|
||||
vSlider.setOrientation(Orientation.VERTICAL);
|
||||
vSlider.getStyleClass().add(Styles.SMALL);
|
||||
|
||||
var vTickSlider = createTickSlider();
|
||||
vTickSlider.setOrientation(VERTICAL);
|
||||
vTickSlider.setOrientation(Orientation.VERTICAL);
|
||||
vTickSlider.getStyleClass().add(Styles.SMALL);
|
||||
vTickSlider.setSkin(new ProgressSliderSkin(vTickSlider));
|
||||
//snippet_2:end
|
||||
|
||||
return new SampleBlock("Small", createContent(hSlider, hTickSlider, vSlider, vTickSlider));
|
||||
var box = createGrid(hSlider, hTickSlider, vSlider, vTickSlider);
|
||||
|
||||
return new ExampleBox(box, new Snippet(getClass(), 2));
|
||||
}
|
||||
|
||||
private Pane largeSample() {
|
||||
private ExampleBox largeExample() {
|
||||
//snippet_3:start
|
||||
var hSlider = new Slider(1, 5, 3);
|
||||
hSlider.getStyleClass().add(Styles.LARGE);
|
||||
|
||||
@ -81,35 +92,18 @@ public class SliderPage extends AbstractPage {
|
||||
hTickSlider.setSkin(new ProgressSliderSkin(hTickSlider));
|
||||
|
||||
var vSlider = new Slider(1, 5, 3);
|
||||
vSlider.setOrientation(VERTICAL);
|
||||
vSlider.setOrientation(Orientation.VERTICAL);
|
||||
vSlider.getStyleClass().add(Styles.LARGE);
|
||||
|
||||
var vTickSlider = createTickSlider();
|
||||
vTickSlider.setOrientation(VERTICAL);
|
||||
vTickSlider.setOrientation(Orientation.VERTICAL);
|
||||
vTickSlider.getStyleClass().add(Styles.LARGE);
|
||||
vTickSlider.setSkin(new ProgressSliderSkin(vTickSlider));
|
||||
//snippet_3:end
|
||||
|
||||
return new SampleBlock("Large", createContent(hSlider, hTickSlider, vSlider, vTickSlider));
|
||||
}
|
||||
var box = createGrid(hSlider, hTickSlider, vSlider, vTickSlider);
|
||||
|
||||
private Pane disabledSample() {
|
||||
var hSlider = new Slider(1, 5, 3);
|
||||
hSlider.setDisable(true);
|
||||
|
||||
var hTickSlider = createTickSlider();
|
||||
hTickSlider.setSkin(new ProgressSliderSkin(hTickSlider));
|
||||
hTickSlider.setDisable(true);
|
||||
|
||||
var vSlider = new Slider(1, 5, 3);
|
||||
vSlider.setOrientation(VERTICAL);
|
||||
vSlider.setDisable(true);
|
||||
|
||||
var vTickSlider = createTickSlider();
|
||||
vTickSlider.setOrientation(VERTICAL);
|
||||
vTickSlider.setSkin(new ProgressSliderSkin(vTickSlider));
|
||||
vTickSlider.setDisable(true);
|
||||
|
||||
return new SampleBlock("Disabled", createContent(hSlider, hTickSlider, vSlider, vTickSlider));
|
||||
return new ExampleBox(box, new Snippet(getClass(), 3));
|
||||
}
|
||||
|
||||
private Slider createTickSlider() {
|
||||
@ -123,22 +117,20 @@ public class SliderPage extends AbstractPage {
|
||||
return slider;
|
||||
}
|
||||
|
||||
private GridPane createContent(Slider h1, Slider h2, Slider v1, Slider v2) {
|
||||
private GridPane createGrid(Slider hs1, Slider hs2, Slider vs1, Slider vs2) {
|
||||
var grid = new GridPane();
|
||||
grid.setVgap(SPACING);
|
||||
grid.setHgap(SPACING);
|
||||
grid.setVgap(20);
|
||||
grid.setHgap(20);
|
||||
|
||||
h1.setPrefWidth(SLIDER_SIZE);
|
||||
h2.setPrefWidth(SLIDER_SIZE);
|
||||
hs1.setMinWidth(200);
|
||||
hs2.setMinWidth(200);
|
||||
vs1.setMinHeight(200);
|
||||
vs2.setMinHeight(200);
|
||||
|
||||
v1.setPrefHeight(SLIDER_SIZE);
|
||||
v2.setPrefHeight(SLIDER_SIZE);
|
||||
|
||||
grid.add(h1, 0, 0);
|
||||
grid.add(h2, 0, 1);
|
||||
|
||||
grid.add(v1, 1, 0, 1, GridPane.REMAINING);
|
||||
grid.add(v2, 2, 0, 1, GridPane.REMAINING);
|
||||
grid.add(hs1, 0, 0);
|
||||
grid.add(hs2, 0, 1);
|
||||
grid.add(vs1, 1, 0, 1, GridPane.REMAINING);
|
||||
grid.add(vs2, 2, 0, 1, GridPane.REMAINING);
|
||||
|
||||
return grid;
|
||||
}
|
||||
|
@ -2,17 +2,19 @@
|
||||
|
||||
package atlantafx.sampler.page.components;
|
||||
|
||||
import atlantafx.base.util.BBCodeParser;
|
||||
import atlantafx.base.util.IntegerStringConverter;
|
||||
import atlantafx.sampler.page.AbstractPage;
|
||||
import atlantafx.sampler.page.Page;
|
||||
import atlantafx.sampler.page.SampleBlock;
|
||||
import atlantafx.sampler.page.ExampleBox;
|
||||
import atlantafx.sampler.page.OutlinePage;
|
||||
import atlantafx.sampler.page.Snippet;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.scene.control.Spinner;
|
||||
import javafx.scene.layout.FlowPane;
|
||||
import javafx.scene.layout.GridPane;
|
||||
import javafx.scene.layout.HBox;
|
||||
|
||||
public final class SpinnerPage extends AbstractPage {
|
||||
public final class SpinnerPage extends OutlinePage {
|
||||
|
||||
public static final String NAME = "Spinner";
|
||||
private static final int PREF_WIDTH = 120;
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
@ -21,75 +23,97 @@ public final class SpinnerPage extends AbstractPage {
|
||||
|
||||
public SpinnerPage() {
|
||||
super();
|
||||
setUserContent(new FlowPane(
|
||||
Page.PAGE_HGAP, Page.PAGE_VGAP,
|
||||
basicSample(),
|
||||
arrowsLeftVerticalSample(),
|
||||
arrowsLeftHorizontalSample(),
|
||||
arrowsRightHorizontalSample(),
|
||||
arrowsSplitHorizontalSample(),
|
||||
arrowsSplitVerticalSample(),
|
||||
disabledSample()
|
||||
));
|
||||
|
||||
addPlainText("""
|
||||
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."""
|
||||
);
|
||||
addSection("Usage", usageExample());
|
||||
addSection("Horizontal", horizontalExample());
|
||||
addSection("Vertical", verticalExample());
|
||||
}
|
||||
|
||||
private SampleBlock basicSample() {
|
||||
private ExampleBox usageExample() {
|
||||
//snippet_1:start
|
||||
var spinner = new Spinner<Integer>(1, 10, 1);
|
||||
IntegerStringConverter.createFor(spinner);
|
||||
spinner.setPrefWidth(PREF_WIDTH);
|
||||
spinner.setEditable(true);
|
||||
return new SampleBlock("Basic", spinner);
|
||||
spinner.setPrefWidth(120);
|
||||
//snippet_1:end
|
||||
|
||||
var box = new HBox(spinner);
|
||||
var description = BBCodeParser.createFormattedText("""
|
||||
A [i]Spinner[/i] has a [i]TextField[/i] child component that is responsible \
|
||||
for displaying and potentially changing the current value of the [i]Spinner[/i], \
|
||||
which is called the editor. By default the Spinner is non-editable, but input \
|
||||
can be accepted if the editable property is set to true. The [i]Spinner[/i] editor stays \
|
||||
in sync with the value factory by listening for changes to the value property \
|
||||
of the value factory."""
|
||||
);
|
||||
|
||||
return new ExampleBox(box, new Snippet(getClass(), 1), description);
|
||||
}
|
||||
|
||||
private SampleBlock disabledSample() {
|
||||
var spinner = new Spinner<Integer>(1, 10, 1);
|
||||
spinner.setPrefWidth(PREF_WIDTH);
|
||||
spinner.setDisable(true);
|
||||
return new SampleBlock("Disabled", spinner);
|
||||
private ExampleBox horizontalExample() {
|
||||
//snippet_2:start
|
||||
var sp1 = new Spinner<Integer>(1, 10, 1);
|
||||
IntegerStringConverter.createFor(sp1);
|
||||
sp1.getStyleClass().add(Spinner.STYLE_CLASS_ARROWS_ON_LEFT_VERTICAL);
|
||||
sp1.setPrefWidth(120);
|
||||
sp1.setEditable(true);
|
||||
|
||||
var sp2 = new Spinner<Integer>(1, 10, 1);
|
||||
IntegerStringConverter.createFor(sp2);
|
||||
sp2.getStyleClass().add(Spinner.STYLE_CLASS_ARROWS_ON_LEFT_HORIZONTAL);
|
||||
sp2.setPrefWidth(120);
|
||||
sp2.setEditable(true);
|
||||
|
||||
var sp3 = new Spinner<Integer>(1, 10, 1);
|
||||
IntegerStringConverter.createFor(sp3);
|
||||
sp3.getStyleClass().add(Spinner.STYLE_CLASS_ARROWS_ON_RIGHT_HORIZONTAL);
|
||||
sp3.setPrefWidth(120);
|
||||
sp3.setEditable(true);
|
||||
|
||||
var sp4 = new Spinner<Integer>(1, 10, 1);
|
||||
IntegerStringConverter.createFor(sp4);
|
||||
sp4.getStyleClass().add(Spinner.STYLE_CLASS_SPLIT_ARROWS_HORIZONTAL);
|
||||
sp4.setPrefWidth(120);
|
||||
sp4.setEditable(true);
|
||||
//snippet_2:end
|
||||
|
||||
var grid = new GridPane();
|
||||
grid.setVgap(VGAP_20);
|
||||
grid.setHgap(HGAP_30);
|
||||
grid.addRow(0, captionLabel("STYLE_CLASS_ARROWS_ON_LEFT_VERTICAL"), sp1);
|
||||
grid.addRow(1, captionLabel("STYLE_CLASS_ARROWS_ON_LEFT_HORIZONTAL"), sp2);
|
||||
grid.addRow(2, captionLabel("STYLE_CLASS_ARROWS_ON_RIGHT_HORIZONTAL"), sp3);
|
||||
grid.addRow(3, captionLabel("STYLE_CLASS_SPLIT_ARROWS_HORIZONTAL"), sp4);
|
||||
|
||||
var description = BBCodeParser.createFormattedText("""
|
||||
The [i]Spinner[/i] also supports several style class modifiers that determine \
|
||||
the arrow’s position."""
|
||||
);
|
||||
|
||||
return new ExampleBox(grid, new Snippet(getClass(), 2), description);
|
||||
}
|
||||
|
||||
private SampleBlock arrowsLeftVerticalSample() {
|
||||
var spinner = new Spinner<Integer>(1, 10, 1);
|
||||
IntegerStringConverter.createFor(spinner);
|
||||
spinner.getStyleClass().add(Spinner.STYLE_CLASS_ARROWS_ON_LEFT_VERTICAL);
|
||||
spinner.setPrefWidth(PREF_WIDTH);
|
||||
spinner.setEditable(true);
|
||||
return new SampleBlock("Left & Vertical", spinner);
|
||||
}
|
||||
private ExampleBox verticalExample() {
|
||||
//snippet_3:start
|
||||
var sp = new Spinner<Integer>(1, 10, 1);
|
||||
IntegerStringConverter.createFor(sp);
|
||||
sp.getStyleClass().add(Spinner.STYLE_CLASS_SPLIT_ARROWS_VERTICAL);
|
||||
sp.setEditable(true);
|
||||
sp.setPrefWidth(40);
|
||||
//snippet_3:end
|
||||
|
||||
private SampleBlock arrowsLeftHorizontalSample() {
|
||||
var spinner = new Spinner<Integer>(1, 10, 1);
|
||||
IntegerStringConverter.createFor(spinner);
|
||||
spinner.getStyleClass().add(Spinner.STYLE_CLASS_ARROWS_ON_LEFT_HORIZONTAL);
|
||||
spinner.setPrefWidth(PREF_WIDTH);
|
||||
spinner.setEditable(true);
|
||||
return new SampleBlock("Left & Horizontal", spinner);
|
||||
}
|
||||
var box = new HBox(HGAP_30, captionLabel("STYLE_CLASS_SPLIT_ARROWS_HORIZONTAL"), sp);
|
||||
box.setAlignment(Pos.CENTER_LEFT);
|
||||
|
||||
private SampleBlock arrowsRightHorizontalSample() {
|
||||
var spinner = new Spinner<Integer>(1, 10, 1);
|
||||
IntegerStringConverter.createFor(spinner);
|
||||
spinner.getStyleClass().add(Spinner.STYLE_CLASS_ARROWS_ON_RIGHT_HORIZONTAL);
|
||||
spinner.setPrefWidth(PREF_WIDTH);
|
||||
spinner.setEditable(true);
|
||||
return new SampleBlock("Right & Horizontal", spinner);
|
||||
}
|
||||
var description = BBCodeParser.createFormattedText("""
|
||||
Similar to the previous example, but for the vertical direction."""
|
||||
);
|
||||
|
||||
private SampleBlock arrowsSplitHorizontalSample() {
|
||||
var spinner = new Spinner<Integer>(1, 10, 1);
|
||||
IntegerStringConverter.createFor(spinner);
|
||||
spinner.getStyleClass().add(Spinner.STYLE_CLASS_SPLIT_ARROWS_HORIZONTAL);
|
||||
spinner.setPrefWidth(PREF_WIDTH);
|
||||
spinner.setEditable(true);
|
||||
return new SampleBlock("Split & Horizontal", spinner);
|
||||
}
|
||||
|
||||
private SampleBlock arrowsSplitVerticalSample() {
|
||||
var spinner = new Spinner<Integer>(1, 10, 1);
|
||||
IntegerStringConverter.createFor(spinner);
|
||||
spinner.getStyleClass().add(Spinner.STYLE_CLASS_SPLIT_ARROWS_VERTICAL);
|
||||
spinner.setEditable(true);
|
||||
spinner.setPrefWidth(40);
|
||||
return new SampleBlock("Split & Vertical", spinner);
|
||||
return new ExampleBox(box, new Snippet(getClass(), 3), description);
|
||||
}
|
||||
}
|
||||
|
@ -2,19 +2,18 @@
|
||||
|
||||
package atlantafx.sampler.page.components;
|
||||
|
||||
import atlantafx.sampler.page.AbstractPage;
|
||||
import atlantafx.sampler.page.Page;
|
||||
import atlantafx.sampler.page.SampleBlock;
|
||||
import atlantafx.sampler.page.ExampleBox;
|
||||
import atlantafx.sampler.page.OutlinePage;
|
||||
import atlantafx.sampler.page.Snippet;
|
||||
import javafx.geometry.Orientation;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.control.SplitPane;
|
||||
import javafx.scene.layout.FlowPane;
|
||||
import javafx.scene.layout.HBox;
|
||||
import javafx.scene.layout.Priority;
|
||||
import javafx.scene.layout.VBox;
|
||||
import javafx.scene.text.Text;
|
||||
|
||||
public class SplitPanePage extends AbstractPage {
|
||||
public class SplitPanePage extends OutlinePage {
|
||||
|
||||
public static final String NAME = "SplitPane";
|
||||
|
||||
@ -25,75 +24,116 @@ public class SplitPanePage extends AbstractPage {
|
||||
|
||||
public SplitPanePage() {
|
||||
super();
|
||||
setUserContent(new FlowPane(
|
||||
Page.PAGE_VGAP, Page.PAGE_HGAP,
|
||||
hSplitSample(),
|
||||
vSplitSample(),
|
||||
disabledSample(),
|
||||
gridSample()
|
||||
));
|
||||
|
||||
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 \
|
||||
side shrinking by an equal amount."""
|
||||
);
|
||||
addSection("Horizontal", hSplitExample());
|
||||
addSection("Vertical", vSplitExample());
|
||||
addSection("Nested", nestedSplitExample());
|
||||
addSection("Multiple Dividers", multiDividersSplitExample());
|
||||
}
|
||||
|
||||
private SampleBlock hSplitSample() {
|
||||
var splitPane = new SplitPane();
|
||||
splitPane.setOrientation(Orientation.HORIZONTAL);
|
||||
splitPane.setDividerPositions(0.5);
|
||||
splitPane.getItems().setAll(createBox("Left"), createBox("Right"));
|
||||
splitPane.setMinSize(200, 100);
|
||||
splitPane.setMaxSize(200, 100);
|
||||
return new SampleBlock("Horizontal", splitPane);
|
||||
private ExampleBox hSplitExample() {
|
||||
//snippet_1:start
|
||||
var sp = new SplitPane(
|
||||
createBox("Left"),
|
||||
createBox("Right")
|
||||
);
|
||||
sp.setOrientation(Orientation.HORIZONTAL);
|
||||
sp.setDividerPositions(0.5);
|
||||
//snippet_1:end
|
||||
|
||||
sp.setMinSize(400, 100);
|
||||
sp.setMaxSize(400, 100);
|
||||
var box = new HBox(sp);
|
||||
|
||||
return new ExampleBox(box, new Snippet(getClass(), 1));
|
||||
}
|
||||
|
||||
private SampleBlock vSplitSample() {
|
||||
var splitPane = new SplitPane();
|
||||
splitPane.setOrientation(Orientation.VERTICAL);
|
||||
splitPane.setDividerPositions(0.5);
|
||||
splitPane.getItems().setAll(createBox("Top"), createBox("Bottom"));
|
||||
splitPane.setMinSize(100, 200);
|
||||
splitPane.setMaxSize(100, 200);
|
||||
return new SampleBlock("Vertical", splitPane);
|
||||
private ExampleBox vSplitExample() {
|
||||
//snippet_2:start
|
||||
var sp = new SplitPane(
|
||||
createBox("Top"),
|
||||
createBox("Bottom")
|
||||
);
|
||||
sp.setOrientation(Orientation.VERTICAL);
|
||||
sp.setDividerPositions(0.5);
|
||||
//snippet_2:end
|
||||
|
||||
sp.setMinSize(400, 200);
|
||||
sp.setMaxSize(400, 200);
|
||||
var box = new HBox(sp);
|
||||
|
||||
return new ExampleBox(box, new Snippet(getClass(), 2));
|
||||
}
|
||||
|
||||
private SampleBlock gridSample() {
|
||||
var topSplitPane = new SplitPane();
|
||||
private ExampleBox nestedSplitExample() {
|
||||
//snippet_3:start
|
||||
var topSplitPane = new SplitPane(
|
||||
createBox("Quarter 4"),
|
||||
createBox("Quarter 1")
|
||||
);
|
||||
topSplitPane.setOrientation(Orientation.HORIZONTAL);
|
||||
topSplitPane.setDividerPositions(0.5);
|
||||
topSplitPane.getItems().setAll(createBox("Quarter 4"), createBox("Quarter 1"));
|
||||
VBox.setVgrow(topSplitPane, Priority.ALWAYS);
|
||||
|
||||
var topBox = new VBox(topSplitPane);
|
||||
topBox.setAlignment(Pos.CENTER);
|
||||
|
||||
var bottomSplitPane = new SplitPane();
|
||||
var bottomSplitPane = new SplitPane(
|
||||
createBox("Quarter 3"),
|
||||
createBox("Quarter 2")
|
||||
);
|
||||
bottomSplitPane.setOrientation(Orientation.HORIZONTAL);
|
||||
bottomSplitPane.setDividerPositions(0.5);
|
||||
bottomSplitPane.getItems().setAll(createBox("Quarter 3"), createBox("Quarter 2"));
|
||||
VBox.setVgrow(bottomSplitPane, Priority.ALWAYS);
|
||||
|
||||
var bottomBox = new VBox(bottomSplitPane);
|
||||
bottomBox.setAlignment(Pos.CENTER);
|
||||
|
||||
var doubleSplitPane = new SplitPane();
|
||||
var doubleSplitPane = new SplitPane(topBox, bottomBox);
|
||||
doubleSplitPane.setOrientation(Orientation.VERTICAL);
|
||||
doubleSplitPane.setDividerPositions(0.5);
|
||||
doubleSplitPane.getItems().setAll(topBox, bottomBox);
|
||||
//snippet_3:end
|
||||
|
||||
doubleSplitPane.setMinSize(400, 200);
|
||||
doubleSplitPane.setMaxSize(400, 200);
|
||||
var box = new HBox(doubleSplitPane);
|
||||
|
||||
return new SampleBlock("Nested", doubleSplitPane);
|
||||
return new ExampleBox(box, new Snippet(getClass(), 3));
|
||||
}
|
||||
|
||||
private SampleBlock disabledSample() {
|
||||
var block = hSplitSample();
|
||||
block.setTitle("Disabled");
|
||||
block.getContent().setDisable(true);
|
||||
private ExampleBox multiDividersSplitExample() {
|
||||
//snippet_4:start
|
||||
var sp = new SplitPane(
|
||||
createBox("First"),
|
||||
createBox("Second"),
|
||||
createBox("Third"),
|
||||
createBox("Fourth")
|
||||
);
|
||||
sp.setOrientation(Orientation.HORIZONTAL);
|
||||
sp.setDividerPositions(0.25, 0.5, 0.75);
|
||||
//snippet_4:end
|
||||
|
||||
return block;
|
||||
sp.setMinSize(600, 200);
|
||||
sp.setMaxSize(600, 200);
|
||||
var box = new HBox(sp);
|
||||
|
||||
return new ExampleBox(box, new Snippet(getClass(), 4));
|
||||
}
|
||||
|
||||
private HBox createBox(String text) {
|
||||
var brick = new HBox(new Text(text));
|
||||
brick.setAlignment(Pos.CENTER);
|
||||
return brick;
|
||||
private HBox createBox(String s) {
|
||||
var label = new Label(s);
|
||||
label.setMinSize(120, 80);
|
||||
label.setAlignment(Pos.CENTER);
|
||||
label.setStyle("-fx-background-color:-color-accent-subtle;");
|
||||
|
||||
var box = new HBox(label);
|
||||
box.setAlignment(Pos.CENTER);
|
||||
|
||||
return box;
|
||||
}
|
||||
}
|
||||
|
@ -2,18 +2,15 @@
|
||||
|
||||
package atlantafx.sampler.page.components;
|
||||
|
||||
import static atlantafx.base.theme.Styles.ACCENT;
|
||||
import static atlantafx.base.theme.Styles.BUTTON_ICON;
|
||||
import static atlantafx.base.theme.Styles.DENSE;
|
||||
import static atlantafx.base.theme.Styles.toggleStyleClass;
|
||||
import static javafx.scene.control.TabPane.TabClosingPolicy.ALL_TABS;
|
||||
import static javafx.scene.control.TabPane.TabClosingPolicy.UNAVAILABLE;
|
||||
import static javafx.scene.control.TabPane.TabClosingPolicy;
|
||||
|
||||
import atlantafx.base.controls.Spacer;
|
||||
import atlantafx.base.controls.ToggleSwitch;
|
||||
import atlantafx.base.theme.Styles;
|
||||
import atlantafx.sampler.page.AbstractPage;
|
||||
import atlantafx.sampler.page.SampleBlock;
|
||||
import atlantafx.base.util.BBCodeParser;
|
||||
import atlantafx.sampler.page.ExampleBox;
|
||||
import atlantafx.sampler.page.OutlinePage;
|
||||
import atlantafx.sampler.page.Snippet;
|
||||
import java.util.List;
|
||||
import javafx.application.Platform;
|
||||
import javafx.collections.ListChangeListener;
|
||||
@ -29,49 +26,219 @@ import javafx.scene.control.ToggleGroup;
|
||||
import javafx.scene.layout.BorderPane;
|
||||
import javafx.scene.layout.GridPane;
|
||||
import javafx.scene.layout.HBox;
|
||||
import javafx.scene.layout.Priority;
|
||||
import javafx.scene.layout.Pane;
|
||||
import javafx.scene.layout.Region;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import javafx.scene.layout.VBox;
|
||||
import org.kordamp.ikonli.feather.Feather;
|
||||
import org.kordamp.ikonli.javafx.FontIcon;
|
||||
|
||||
public class TabPanePage extends AbstractPage {
|
||||
public class TabPanePage extends OutlinePage {
|
||||
|
||||
public static final String NAME = "TabPane";
|
||||
private static final double TAB_MIN_HEIGHT = 60;
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return NAME;
|
||||
}
|
||||
|
||||
public TabPanePage() {
|
||||
super();
|
||||
|
||||
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 \
|
||||
selected tab gets hidden."""
|
||||
);
|
||||
addSection("Usage", usageExample());
|
||||
addSection("Tab Style", tabStyleExample());
|
||||
addSection("Vertical Tabs", verticalTabsExample());
|
||||
addSection("Dense", denseExample());
|
||||
addSection("Playground", playground());
|
||||
}
|
||||
|
||||
private ExampleBox usageExample() {
|
||||
//snippet_1:start
|
||||
var tab1 = new Tab("One");
|
||||
tab1.setGraphic(new FontIcon(randomIcon()));
|
||||
|
||||
var tab2 = new Tab("Two");
|
||||
tab2.setGraphic(new FontIcon(randomIcon()));
|
||||
|
||||
var tab3 = new Tab("Three");
|
||||
tab3.setGraphic(new FontIcon(randomIcon()));
|
||||
|
||||
var tabs = new TabPane(tab1, tab2, tab3);
|
||||
tabs.setTabClosingPolicy(TabClosingPolicy.UNAVAILABLE);
|
||||
tabs.setMinWidth(450);
|
||||
//snippet_1:end
|
||||
|
||||
var box = new HBox(tabs);
|
||||
box.setMinHeight(50);
|
||||
|
||||
var description = BBCodeParser.createFormattedText("""
|
||||
Tabs are placed within a [font=monospace]TabPane[/font], where each tab represents a single \
|
||||
"page". Any node such as controls or groups of nodes added to a tab layout container. When \
|
||||
the user clicks on a tab in the tab content becomes visible."""
|
||||
);
|
||||
|
||||
return new ExampleBox(box, new Snippet(getClass(), 1), description);
|
||||
}
|
||||
|
||||
private ExampleBox tabStyleExample() {
|
||||
//snippet_2:start
|
||||
var defaultTabs = new TabPane(
|
||||
new Tab("One"), new Tab("Two"), new Tab("Three")
|
||||
);
|
||||
defaultTabs.setTabClosingPolicy(TabClosingPolicy.UNAVAILABLE);
|
||||
defaultTabs.setMinWidth(450);
|
||||
|
||||
var floatingTabs = new TabPane(
|
||||
new Tab("One"), new Tab("Two"), new Tab("Three")
|
||||
);
|
||||
floatingTabs.getStyleClass().add(Styles.TABS_FLOATING);
|
||||
floatingTabs.setTabClosingPolicy(TabClosingPolicy.UNAVAILABLE);
|
||||
floatingTabs.setMinWidth(450);
|
||||
|
||||
var classicTabs = new TabPane(
|
||||
new Tab("One"), new Tab("Two"), new Tab("Three")
|
||||
);
|
||||
classicTabs.getStyleClass().add(Styles.TABS_CLASSIC);
|
||||
classicTabs.setTabClosingPolicy(TabClosingPolicy.UNAVAILABLE);
|
||||
classicTabs.setMinWidth(450);
|
||||
//snippet_2:end
|
||||
|
||||
var box = new VBox(VGAP_20, defaultTabs, floatingTabs, classicTabs);
|
||||
box.setMinHeight(200);
|
||||
|
||||
var description = BBCodeParser.createFormattedText("""
|
||||
You can use two additional styles classes to modify tab pane style:
|
||||
|
||||
[ul]
|
||||
[li][code]Styles.TABS_FLOATING[/code][/li]
|
||||
[li][code]Styles.TABS_CLASSIC[/code][/li][/ul]"""
|
||||
);
|
||||
|
||||
return new ExampleBox(box, new Snippet(getClass(), 2), description);
|
||||
}
|
||||
|
||||
private ExampleBox verticalTabsExample() {
|
||||
//snippet_3:start
|
||||
var defaultTabsLeft = new TabPane(
|
||||
new Tab("One"), new Tab("Two")
|
||||
);
|
||||
defaultTabsLeft.setSide(Side.LEFT);
|
||||
defaultTabsLeft.setTabClosingPolicy(TabClosingPolicy.UNAVAILABLE);
|
||||
|
||||
var floatingTabsLeft = new TabPane(
|
||||
new Tab("One"), new Tab("Two")
|
||||
);
|
||||
floatingTabsLeft.setSide(Side.LEFT);
|
||||
floatingTabsLeft.getStyleClass().add(Styles.TABS_FLOATING);
|
||||
floatingTabsLeft.setTabClosingPolicy(TabClosingPolicy.UNAVAILABLE);
|
||||
|
||||
var classicTabsLeft = new TabPane(
|
||||
new Tab("One"), new Tab("Two")
|
||||
);
|
||||
classicTabsLeft.setSide(Side.LEFT);
|
||||
classicTabsLeft.getStyleClass().add(Styles.TABS_CLASSIC);
|
||||
classicTabsLeft.setTabClosingPolicy(TabClosingPolicy.UNAVAILABLE);
|
||||
|
||||
var defaultTabsRight = new TabPane(
|
||||
new Tab("One"), new Tab("Two")
|
||||
);
|
||||
defaultTabsRight.setSide(Side.RIGHT);
|
||||
defaultTabsRight.setTabClosingPolicy(TabClosingPolicy.UNAVAILABLE);
|
||||
|
||||
var floatingTabsRight = new TabPane(
|
||||
new Tab("One"), new Tab("Two")
|
||||
);
|
||||
floatingTabsRight.setSide(Side.RIGHT);
|
||||
floatingTabsRight.getStyleClass().add(Styles.TABS_FLOATING);
|
||||
floatingTabsRight.setTabClosingPolicy(TabClosingPolicy.UNAVAILABLE);
|
||||
|
||||
var classicTabsRight = new TabPane(
|
||||
new Tab("One"), new Tab("Two")
|
||||
);
|
||||
classicTabsRight.setSide(Side.RIGHT);
|
||||
classicTabsRight.getStyleClass().add(Styles.TABS_CLASSIC);
|
||||
classicTabsRight.setTabClosingPolicy(TabClosingPolicy.UNAVAILABLE);
|
||||
//snippet_3:end
|
||||
|
||||
var box = new HBox(
|
||||
HGAP_20,
|
||||
defaultTabsLeft, floatingTabsLeft, classicTabsLeft,
|
||||
new Spacer(),
|
||||
classicTabsRight, floatingTabsRight, defaultTabsRight
|
||||
);
|
||||
box.setMinHeight(450);
|
||||
|
||||
var description = BBCodeParser.createFormattedText("""
|
||||
Tabs in a tab pane can be positioned at any of the four sides by specifying the side."""
|
||||
);
|
||||
|
||||
return new ExampleBox(box, new Snippet(getClass(), 3), description);
|
||||
}
|
||||
|
||||
private ExampleBox denseExample() {
|
||||
//snippet_4:start
|
||||
var tab1 = new Tab("One");
|
||||
tab1.setGraphic(new FontIcon(randomIcon()));
|
||||
|
||||
var tab2 = new Tab("Two");
|
||||
tab2.setGraphic(new FontIcon(randomIcon()));
|
||||
|
||||
var tab3 = new Tab("Three");
|
||||
tab3.setGraphic(new FontIcon(randomIcon()));
|
||||
|
||||
var tabs = new TabPane(tab1, tab2, tab3);
|
||||
tabs.getStyleClass().add(Styles.DENSE);
|
||||
tabs.setTabClosingPolicy(TabClosingPolicy.UNAVAILABLE);
|
||||
tabs.setMinWidth(450);
|
||||
//snippet_4:end
|
||||
|
||||
var box = new HBox(tabs);
|
||||
box.setMinHeight(50);
|
||||
|
||||
var description = BBCodeParser.createFormattedText("""
|
||||
There's also [code]Styles.DENSE[/code] to make tabs look more compact by cutting label padding."""
|
||||
);
|
||||
|
||||
return new ExampleBox(box, new Snippet(getClass(), 4), description);
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
// Playground //
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
|
||||
private Side tabSide = Side.TOP;
|
||||
private boolean fullWidth = false;
|
||||
|
||||
public TabPanePage() {
|
||||
super();
|
||||
createView();
|
||||
}
|
||||
|
||||
private void createView() {
|
||||
private Pane playground() {
|
||||
var tabs = createTabPane();
|
||||
|
||||
var tabsLayer = new BorderPane();
|
||||
tabsLayer.setTop(tabs);
|
||||
tabs.getTabs().addListener((ListChangeListener<Tab>) c -> updateTabsWidth(tabsLayer, tabs, fullWidth));
|
||||
tabs.getTabs().addListener((ListChangeListener<Tab>) c ->
|
||||
updateTabsWidth(tabsLayer, tabs, fullWidth)
|
||||
);
|
||||
|
||||
var controller = createController(tabsLayer, tabs);
|
||||
controller.setPrefSize(500, 300);
|
||||
var controllerLayer = new BorderPane();
|
||||
controllerLayer.setCenter(controller);
|
||||
|
||||
var controllerLayer = new BorderPane(controller);
|
||||
controllerLayer.setMinSize(500, 300);
|
||||
controllerLayer.setMaxSize(500, 300);
|
||||
|
||||
var root = new StackPane();
|
||||
root.getStyleClass().add(Styles.BORDERED);
|
||||
root.getChildren().addAll(tabsLayer, controllerLayer);
|
||||
VBox.setVgrow(root, Priority.ALWAYS);
|
||||
var description = BBCodeParser.createFormattedText("""
|
||||
The playground demonstrates the most important [i]TabPane[/i] features \
|
||||
and also serves as an object for monkey testing."""
|
||||
);
|
||||
|
||||
setUserContent(new SampleBlock("Playground", root));
|
||||
var stack = new StackPane(tabsLayer, controllerLayer);
|
||||
stack.getStyleClass().add(Styles.BORDERED);
|
||||
stack.setMinSize(600, 500);
|
||||
|
||||
return new VBox(VGAP_10, description, stack);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@ -79,29 +246,31 @@ public class TabPanePage extends AbstractPage {
|
||||
// == BUTTONS ==
|
||||
|
||||
var toTopBtn = new Button("", new FontIcon(Feather.ARROW_UP));
|
||||
toTopBtn.getStyleClass().addAll(BUTTON_ICON);
|
||||
toTopBtn.getStyleClass().addAll(Styles.BUTTON_ICON);
|
||||
toTopBtn.setOnAction(e -> rotateTabs(borderPane, tabs, Side.TOP));
|
||||
|
||||
var toRightBtn = new Button("", new FontIcon(Feather.ARROW_RIGHT));
|
||||
toRightBtn.getStyleClass().addAll(BUTTON_ICON);
|
||||
toRightBtn.getStyleClass().addAll(Styles.BUTTON_ICON);
|
||||
toRightBtn.setOnAction(e -> rotateTabs(borderPane, tabs, Side.RIGHT));
|
||||
|
||||
var toBottomBtn = new Button("", new FontIcon(Feather.ARROW_DOWN));
|
||||
toBottomBtn.getStyleClass().addAll(BUTTON_ICON);
|
||||
toBottomBtn.getStyleClass().addAll(Styles.BUTTON_ICON);
|
||||
toBottomBtn.setOnAction(e -> rotateTabs(borderPane, tabs, Side.BOTTOM));
|
||||
|
||||
var toLeftBtn = new Button("", new FontIcon(Feather.ARROW_LEFT));
|
||||
toLeftBtn.getStyleClass().addAll(BUTTON_ICON);
|
||||
toLeftBtn.getStyleClass().addAll(Styles.BUTTON_ICON);
|
||||
toLeftBtn.setOnAction(e -> rotateTabs(borderPane, tabs, Side.LEFT));
|
||||
|
||||
var appendBtn = new Button("", new FontIcon(Feather.PLUS));
|
||||
appendBtn.getStyleClass().addAll(BUTTON_ICON, ACCENT);
|
||||
appendBtn.getStyleClass().addAll(Styles.BUTTON_ICON, Styles.ACCENT);
|
||||
appendBtn.setOnAction(e -> tabs.getTabs().add(createRandomTab()));
|
||||
|
||||
var buttonsPane = new BorderPane();
|
||||
buttonsPane.setMinSize(120, 120);
|
||||
buttonsPane.setMaxSize(120, 120);
|
||||
|
||||
buttonsPane.setCenter(appendBtn);
|
||||
|
||||
buttonsPane.setTop(toTopBtn);
|
||||
BorderPane.setAlignment(toTopBtn, Pos.CENTER);
|
||||
|
||||
@ -114,16 +283,14 @@ public class TabPanePage extends AbstractPage {
|
||||
buttonsPane.setLeft(toLeftBtn);
|
||||
BorderPane.setAlignment(toLeftBtn, Pos.CENTER);
|
||||
|
||||
buttonsPane.setCenter(appendBtn);
|
||||
|
||||
// == TOGGLES ==
|
||||
|
||||
var closeableToggle = new ToggleSwitch();
|
||||
closeableToggle.selectedProperty().addListener((obs, old, val) -> {
|
||||
if (val) {
|
||||
tabs.setTabClosingPolicy(ALL_TABS);
|
||||
tabs.setTabClosingPolicy(TabClosingPolicy.ALL_TABS);
|
||||
} else {
|
||||
tabs.setTabClosingPolicy(UNAVAILABLE);
|
||||
tabs.setTabClosingPolicy(TabClosingPolicy.UNAVAILABLE);
|
||||
}
|
||||
});
|
||||
|
||||
@ -133,7 +300,10 @@ public class TabPanePage extends AbstractPage {
|
||||
if (val != null && val) {
|
||||
tabs.setStyle("");
|
||||
} else {
|
||||
tabs.setStyle("-fx-open-tab-animation:none;-fx-close-tab-animation:none;");
|
||||
tabs.setStyle("""
|
||||
-fx-open-tab-animation:none;\
|
||||
-fx-close-tab-animation:none;"""
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
@ -146,7 +316,9 @@ public class TabPanePage extends AbstractPage {
|
||||
});
|
||||
|
||||
var denseToggle = new ToggleSwitch();
|
||||
denseToggle.selectedProperty().addListener((obs, old, val) -> toggleStyleClass(tabs, DENSE));
|
||||
denseToggle.selectedProperty().addListener(
|
||||
(obs, old, val) -> Styles.toggleStyleClass(tabs, Styles.DENSE)
|
||||
);
|
||||
|
||||
var disableToggle = new ToggleSwitch();
|
||||
disableToggle.selectedProperty().addListener((obs, old, val) -> {
|
||||
@ -158,21 +330,11 @@ public class TabPanePage extends AbstractPage {
|
||||
var togglesGrid = new GridPane();
|
||||
togglesGrid.setHgap(10);
|
||||
togglesGrid.setVgap(10);
|
||||
|
||||
togglesGrid.add(createGridLabel("Closeable"), 0, 0);
|
||||
togglesGrid.add(closeableToggle, 1, 0);
|
||||
|
||||
togglesGrid.add(createGridLabel("Animated"), 0, 1);
|
||||
togglesGrid.add(animatedToggle, 1, 1);
|
||||
|
||||
togglesGrid.add(createGridLabel("Full width"), 0, 2);
|
||||
togglesGrid.add(fullWidthToggle, 1, 2);
|
||||
|
||||
togglesGrid.add(createGridLabel("Dense"), 0, 3);
|
||||
togglesGrid.add(denseToggle, 1, 3);
|
||||
|
||||
togglesGrid.add(createGridLabel("Disable"), 0, 4);
|
||||
togglesGrid.add(disableToggle, 1, 4);
|
||||
togglesGrid.addRow(0, createGridLabel("Closeable"), closeableToggle);
|
||||
togglesGrid.addRow(1, createGridLabel("Animated"), animatedToggle);
|
||||
togglesGrid.addRow(2, createGridLabel("Full width"), fullWidthToggle);
|
||||
togglesGrid.addRow(3, createGridLabel("Dense"), denseToggle);
|
||||
togglesGrid.addRow(4, createGridLabel("Disable"), disableToggle);
|
||||
|
||||
// == TAB STYLE ==
|
||||
|
||||
@ -180,18 +342,24 @@ public class TabPanePage extends AbstractPage {
|
||||
|
||||
var defaultStyleToggle = new ToggleButton("Default");
|
||||
defaultStyleToggle.setToggleGroup(styleToggleGroup);
|
||||
defaultStyleToggle.setUserData(List.of("whatever", Styles.TABS_FLOATING, Styles.TABS_CLASSIC));
|
||||
defaultStyleToggle.setUserData(
|
||||
List.of("whatever", Styles.TABS_FLOATING, Styles.TABS_CLASSIC)
|
||||
);
|
||||
defaultStyleToggle.getStyleClass().add(Styles.LEFT_PILL);
|
||||
defaultStyleToggle.setSelected(true);
|
||||
|
||||
var floatingStyleToggle = new ToggleButton("Floating");
|
||||
floatingStyleToggle.setToggleGroup(styleToggleGroup);
|
||||
floatingStyleToggle.setUserData(List.of(Styles.TABS_FLOATING, "whatever", Styles.TABS_CLASSIC));
|
||||
floatingStyleToggle.setUserData(
|
||||
List.of(Styles.TABS_FLOATING, "whatever", Styles.TABS_CLASSIC)
|
||||
);
|
||||
floatingStyleToggle.getStyleClass().add(Styles.CENTER_PILL);
|
||||
|
||||
var classicStyleToggle = new ToggleButton("Classic");
|
||||
classicStyleToggle.setToggleGroup(styleToggleGroup);
|
||||
classicStyleToggle.setUserData(List.of(Styles.TABS_CLASSIC, "whatever", Styles.TABS_FLOATING));
|
||||
classicStyleToggle.setUserData(
|
||||
List.of(Styles.TABS_CLASSIC, "whatever", Styles.TABS_FLOATING)
|
||||
);
|
||||
classicStyleToggle.getStyleClass().add(Styles.RIGHT_PILL);
|
||||
|
||||
styleToggleGroup.selectedToggleProperty().addListener((obs, old, val) -> {
|
||||
@ -201,17 +369,13 @@ public class TabPanePage extends AbstractPage {
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
var styleBox = new HBox(defaultStyleToggle, floatingStyleToggle, classicStyleToggle);
|
||||
styleBox.setAlignment(Pos.CENTER);
|
||||
|
||||
// == LAYOUT ==
|
||||
|
||||
var controls = new HBox(40,
|
||||
new Spacer(),
|
||||
buttonsPane,
|
||||
togglesGrid,
|
||||
new Spacer()
|
||||
var controls = new HBox(
|
||||
40, new Spacer(), buttonsPane, togglesGrid, new Spacer()
|
||||
);
|
||||
controls.setAlignment(Pos.CENTER);
|
||||
|
||||
@ -256,8 +420,8 @@ public class TabPanePage extends AbstractPage {
|
||||
|
||||
private TabPane createTabPane() {
|
||||
var tabs = new TabPane();
|
||||
tabs.setTabClosingPolicy(UNAVAILABLE);
|
||||
tabs.setMinHeight(TAB_MIN_HEIGHT);
|
||||
tabs.setTabClosingPolicy(TabClosingPolicy.UNAVAILABLE);
|
||||
tabs.setMinHeight(60);
|
||||
|
||||
// NOTE: Individually disabled tab is still closeable even while it looks
|
||||
// like disabled. To prevent it from closing one can use "black hole"
|
||||
|
@ -1,331 +0,0 @@
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
|
||||
package atlantafx.sampler.page.components;
|
||||
|
||||
import static atlantafx.base.theme.Styles.BORDERED;
|
||||
import static atlantafx.base.theme.Styles.DENSE;
|
||||
import static atlantafx.base.theme.Styles.STRIPED;
|
||||
import static atlantafx.base.theme.Styles.toggleStyleClass;
|
||||
import static atlantafx.base.theme.Tweaks.ALIGN_CENTER;
|
||||
import static atlantafx.base.theme.Tweaks.ALIGN_LEFT;
|
||||
import static atlantafx.base.theme.Tweaks.ALIGN_RIGHT;
|
||||
import static atlantafx.sampler.page.SampleBlock.BLOCK_HGAP;
|
||||
import static atlantafx.sampler.page.SampleBlock.BLOCK_VGAP;
|
||||
import static javafx.collections.FXCollections.observableArrayList;
|
||||
|
||||
import atlantafx.base.controls.CaptionMenuItem;
|
||||
import atlantafx.base.controls.Spacer;
|
||||
import atlantafx.base.controls.ToggleSwitch;
|
||||
import atlantafx.base.theme.Styles;
|
||||
import atlantafx.base.theme.Tweaks;
|
||||
import atlantafx.sampler.fake.domain.Product;
|
||||
import atlantafx.sampler.page.AbstractPage;
|
||||
import atlantafx.sampler.page.SampleBlock;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.stream.IntStream;
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.beans.binding.StringBinding;
|
||||
import javafx.beans.property.SimpleObjectProperty;
|
||||
import javafx.collections.transformation.FilteredList;
|
||||
import javafx.collections.transformation.SortedList;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.scene.control.CheckMenuItem;
|
||||
import javafx.scene.control.ComboBox;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.control.MenuButton;
|
||||
import javafx.scene.control.RadioMenuItem;
|
||||
import javafx.scene.control.SelectionMode;
|
||||
import javafx.scene.control.SeparatorMenuItem;
|
||||
import javafx.scene.control.TableCell;
|
||||
import javafx.scene.control.TableColumn;
|
||||
import javafx.scene.control.TableView;
|
||||
import javafx.scene.control.ToggleButton;
|
||||
import javafx.scene.control.ToggleGroup;
|
||||
import javafx.scene.control.cell.CheckBoxTableCell;
|
||||
import javafx.scene.control.cell.ChoiceBoxTableCell;
|
||||
import javafx.scene.control.cell.ComboBoxTableCell;
|
||||
import javafx.scene.control.cell.ProgressBarTableCell;
|
||||
import javafx.scene.control.cell.PropertyValueFactory;
|
||||
import javafx.scene.control.cell.TextFieldTableCell;
|
||||
import javafx.scene.layout.HBox;
|
||||
import javafx.scene.layout.Priority;
|
||||
import javafx.scene.layout.VBox;
|
||||
import javafx.util.Callback;
|
||||
import org.kordamp.ikonli.feather.Feather;
|
||||
import org.kordamp.ikonli.javafx.FontIcon;
|
||||
import org.kordamp.ikonli.javafx.FontIconTableCell;
|
||||
|
||||
public class TablePage extends AbstractPage {
|
||||
|
||||
public static final String NAME = "TableView";
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return NAME;
|
||||
}
|
||||
|
||||
private TableView<Product> table;
|
||||
private final List<Product> dataList = IntStream.range(1, 51).boxed()
|
||||
.map(i -> Product.random(i, FAKER))
|
||||
.toList();
|
||||
|
||||
public TablePage() {
|
||||
super();
|
||||
|
||||
var sample = new SampleBlock("Playground", createPlayground());
|
||||
sample.setFillHeight(true);
|
||||
setUserContent(sample);
|
||||
}
|
||||
|
||||
private VBox createPlayground() {
|
||||
// == FOOTER ==
|
||||
|
||||
var bordersToggle = new ToggleSwitch("Bordered");
|
||||
bordersToggle.selectedProperty().addListener((obs, old, val) -> toggleStyleClass(table, BORDERED));
|
||||
|
||||
var denseToggle = new ToggleSwitch("Dense");
|
||||
denseToggle.selectedProperty().addListener((obs, old, val) -> toggleStyleClass(table, DENSE));
|
||||
|
||||
var stripesToggle = new ToggleSwitch("Striped");
|
||||
stripesToggle.selectedProperty().addListener((obs, old, val) -> toggleStyleClass(table, STRIPED));
|
||||
|
||||
var edge2edgeToggle = new ToggleSwitch("Edge to edge");
|
||||
edge2edgeToggle.selectedProperty().addListener(
|
||||
(obs, old, value) -> toggleStyleClass(table, Tweaks.EDGE_TO_EDGE)
|
||||
);
|
||||
|
||||
var maxRowCount = 100;
|
||||
var rowCountChoice = new ComboBox<>(observableArrayList(0, 5, 10, 25, maxRowCount));
|
||||
rowCountChoice.setValue(maxRowCount);
|
||||
|
||||
var rowCountBox = new HBox(BLOCK_HGAP, new Label("rows"), rowCountChoice);
|
||||
rowCountBox.setAlignment(Pos.CENTER_LEFT);
|
||||
|
||||
var footer = new HBox(
|
||||
BLOCK_HGAP,
|
||||
new Spacer(),
|
||||
bordersToggle,
|
||||
denseToggle,
|
||||
stripesToggle,
|
||||
edge2edgeToggle,
|
||||
new Spacer(),
|
||||
rowCountBox
|
||||
);
|
||||
footer.setAlignment(Pos.CENTER_LEFT);
|
||||
|
||||
// == TABLE ==
|
||||
|
||||
var filteredData = new FilteredList<>(observableArrayList(dataList));
|
||||
filteredData.predicateProperty().bind(Bindings.createObjectBinding(
|
||||
() -> product -> product.getId() <= rowCountChoice.getValue(),
|
||||
rowCountChoice.valueProperty()
|
||||
));
|
||||
|
||||
var sortedData = new SortedList<>(filteredData);
|
||||
|
||||
table = createTable();
|
||||
table.setItems(sortedData);
|
||||
sortedData.comparatorProperty().bind(table.comparatorProperty());
|
||||
VBox.setVgrow(table, Priority.ALWAYS);
|
||||
|
||||
// == HEADER ==
|
||||
|
||||
var alignGroup = new ToggleGroup();
|
||||
|
||||
var alignLeftBtn = new ToggleButton("", new FontIcon(Feather.ALIGN_LEFT));
|
||||
alignLeftBtn.getStyleClass().add(Styles.LEFT_PILL);
|
||||
alignLeftBtn.setToggleGroup(alignGroup);
|
||||
alignLeftBtn.setSelected(true);
|
||||
alignLeftBtn.setOnAction(e -> {
|
||||
for (TableColumn<?, ?> c : table.getColumns()) {
|
||||
c.getStyleClass().removeAll(ALIGN_LEFT, ALIGN_CENTER, ALIGN_RIGHT);
|
||||
}
|
||||
});
|
||||
|
||||
var alignCenterBtn = new ToggleButton("", new FontIcon(Feather.ALIGN_CENTER));
|
||||
alignCenterBtn.getStyleClass().add(Styles.CENTER_PILL);
|
||||
alignCenterBtn.setToggleGroup(alignGroup);
|
||||
alignCenterBtn.selectedProperty().addListener((obs, old, val) -> {
|
||||
for (TableColumn<?, ?> c : table.getColumns()) {
|
||||
addStyleClass(c, ALIGN_CENTER, ALIGN_LEFT, ALIGN_RIGHT);
|
||||
}
|
||||
});
|
||||
|
||||
var alignRightBtn = new ToggleButton("", new FontIcon(Feather.ALIGN_RIGHT));
|
||||
alignRightBtn.getStyleClass().add(Styles.RIGHT_PILL);
|
||||
alignRightBtn.setToggleGroup(alignGroup);
|
||||
alignRightBtn.selectedProperty().addListener((obs, old, val) -> {
|
||||
for (TableColumn<?, ?> c : table.getColumns()) {
|
||||
addStyleClass(c, ALIGN_RIGHT, ALIGN_LEFT, ALIGN_CENTER);
|
||||
}
|
||||
});
|
||||
|
||||
var alignBox = new HBox(alignLeftBtn, alignCenterBtn, alignRightBtn);
|
||||
|
||||
var disableToggle = new ToggleSwitch("Disable");
|
||||
disableToggle.selectedProperty().addListener((obs, old, val) -> {
|
||||
if (val != null) {
|
||||
table.setDisable(val);
|
||||
}
|
||||
});
|
||||
|
||||
var header = new HBox(
|
||||
createTablePropertiesMenu(table),
|
||||
new Spacer(),
|
||||
alignBox,
|
||||
new Spacer(),
|
||||
disableToggle
|
||||
);
|
||||
header.setAlignment(Pos.CENTER_LEFT);
|
||||
|
||||
// ~
|
||||
|
||||
var playground = new VBox(BLOCK_VGAP, header, table, footer);
|
||||
playground.setMinHeight(100);
|
||||
|
||||
return playground;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private TableView<Product> createTable() {
|
||||
var stateCol = new TableColumn<Product, Boolean>("Selected");
|
||||
stateCol.setCellValueFactory(new PropertyValueFactory<>("state"));
|
||||
stateCol.setCellFactory(CheckBoxTableCell.forTableColumn(stateCol));
|
||||
stateCol.setEditable(true);
|
||||
|
||||
// an example of creating index column if data object
|
||||
// doesn't provide index property
|
||||
var indexCol = new TableColumn<Product, String>("Index");
|
||||
indexCol.setCellFactory(col -> {
|
||||
TableCell<Product, String> cell = new TableCell<>();
|
||||
StringBinding value = Bindings.when(cell.emptyProperty())
|
||||
.then("")
|
||||
.otherwise(cell.indexProperty().add(1).asString());
|
||||
cell.textProperty().bind(value);
|
||||
return cell;
|
||||
});
|
||||
|
||||
var iconCol = new TableColumn<Product, Feather>("Logo");
|
||||
iconCol.setCellValueFactory(c -> new SimpleObjectProperty<>(randomIcon()));
|
||||
iconCol.setCellFactory(FontIconTableCell.forTableColumn());
|
||||
iconCol.setEditable(false);
|
||||
|
||||
var brandCol = new TableColumn<Product, String>("Brand 🖉");
|
||||
brandCol.setCellValueFactory(new PropertyValueFactory<>("brand"));
|
||||
brandCol.setCellFactory(ChoiceBoxTableCell.forTableColumn(
|
||||
generate(() -> FAKER.commerce().brand(), 10).toArray(String[]::new)
|
||||
));
|
||||
brandCol.setEditable(true);
|
||||
|
||||
var nameCol = new TableColumn<Product, String>("Name 🖉");
|
||||
nameCol.setCellValueFactory(new PropertyValueFactory<>("name"));
|
||||
nameCol.setCellFactory(ComboBoxTableCell.forTableColumn(
|
||||
generate(() -> FAKER.commerce().productName(), 10).toArray(String[]::new)
|
||||
));
|
||||
nameCol.setEditable(true);
|
||||
|
||||
var priceCol = new TableColumn<Product, String>("Price 🖉");
|
||||
priceCol.setCellValueFactory(new PropertyValueFactory<>("price"));
|
||||
priceCol.setCellFactory(TextFieldTableCell.forTableColumn());
|
||||
priceCol.setEditable(true);
|
||||
|
||||
var stockCountCol = new TableColumn<Product, Integer>("Count");
|
||||
stockCountCol.setCellValueFactory(new PropertyValueFactory<>("count"));
|
||||
stockCountCol.setEditable(false);
|
||||
|
||||
var stockAvailCol = new TableColumn<Product, Double>("Available");
|
||||
stockAvailCol.setCellValueFactory(new PropertyValueFactory<>("availability"));
|
||||
stockAvailCol.setCellFactory(ProgressBarTableCell.forTableColumn());
|
||||
stockAvailCol.setEditable(false);
|
||||
|
||||
var stockCol = new TableColumn<Product, Double>("Stock");
|
||||
stockCol.getColumns().setAll(stockCountCol, stockAvailCol);
|
||||
|
||||
var tableView = new TableView<Product>();
|
||||
tableView.getColumns().setAll(stateCol, indexCol, iconCol, brandCol, nameCol, priceCol, stockCol);
|
||||
|
||||
return tableView;
|
||||
}
|
||||
|
||||
private MenuButton createTablePropertiesMenu(TableView<Product> table) {
|
||||
final var resizePolicyCaption = new CaptionMenuItem("Resize Policy");
|
||||
final var resizePolicyGroup = new ToggleGroup();
|
||||
resizePolicyGroup.selectedToggleProperty().addListener((obs, old, val) -> {
|
||||
if (val != null && val.getUserData() instanceof Callback<?, ?> policy) {
|
||||
//noinspection rawtypes,unchecked
|
||||
table.setColumnResizePolicy((Callback<TableView.ResizeFeatures, Boolean>) policy);
|
||||
}
|
||||
});
|
||||
|
||||
final var unconstrainedResizeItem = new RadioMenuItem("Unconstrained");
|
||||
unconstrainedResizeItem.setToggleGroup(resizePolicyGroup);
|
||||
unconstrainedResizeItem.setUserData(TableView.UNCONSTRAINED_RESIZE_POLICY);
|
||||
unconstrainedResizeItem.setSelected(true);
|
||||
|
||||
final var constrainedResizeItem = new RadioMenuItem("Constrained");
|
||||
constrainedResizeItem.setToggleGroup(resizePolicyGroup);
|
||||
constrainedResizeItem.setUserData(TableView.CONSTRAINED_RESIZE_POLICY);
|
||||
|
||||
// ~
|
||||
|
||||
final var selectionModeCaption = new CaptionMenuItem("Selection Mode");
|
||||
final var selectionModeGroup = new ToggleGroup();
|
||||
selectionModeGroup.selectedToggleProperty().addListener((obs, old, val) -> {
|
||||
if (val != null && val.getUserData() instanceof SelectionMode mode) {
|
||||
table.getSelectionModel().setSelectionMode(mode);
|
||||
}
|
||||
});
|
||||
|
||||
final var singleSelectionItem = new RadioMenuItem("Single");
|
||||
singleSelectionItem.setToggleGroup(selectionModeGroup);
|
||||
singleSelectionItem.setUserData(SelectionMode.SINGLE);
|
||||
|
||||
final var multiSelectionItem = new RadioMenuItem("Multiple");
|
||||
multiSelectionItem.setToggleGroup(selectionModeGroup);
|
||||
multiSelectionItem.setUserData(SelectionMode.MULTIPLE);
|
||||
multiSelectionItem.setSelected(true);
|
||||
|
||||
// ~
|
||||
|
||||
final var editCellsItem = new CheckMenuItem("Editable");
|
||||
table.editableProperty().bind(editCellsItem.selectedProperty());
|
||||
editCellsItem.setSelected(true);
|
||||
|
||||
final var cellSelectionItem = new CheckMenuItem("Enable cell selection");
|
||||
table.getSelectionModel().cellSelectionEnabledProperty().bind(cellSelectionItem.selectedProperty());
|
||||
cellSelectionItem.setSelected(false);
|
||||
|
||||
// ~
|
||||
|
||||
final var menuButtonItem = new CheckMenuItem("Show menu button");
|
||||
table.tableMenuButtonVisibleProperty().bind(menuButtonItem.selectedProperty());
|
||||
menuButtonItem.setSelected(true);
|
||||
|
||||
final var propertiesMenu = new MenuButton("Properties");
|
||||
propertiesMenu.getItems().setAll(
|
||||
resizePolicyCaption,
|
||||
unconstrainedResizeItem,
|
||||
constrainedResizeItem,
|
||||
selectionModeCaption,
|
||||
singleSelectionItem,
|
||||
multiSelectionItem,
|
||||
new SeparatorMenuItem(),
|
||||
editCellsItem,
|
||||
cellSelectionItem,
|
||||
menuButtonItem
|
||||
);
|
||||
|
||||
return propertiesMenu;
|
||||
}
|
||||
|
||||
private static void addStyleClass(TableColumn<?, ?> c, String styleClass, String... excludes) {
|
||||
Objects.requireNonNull(c);
|
||||
Objects.requireNonNull(styleClass);
|
||||
|
||||
if (excludes != null && excludes.length > 0) {
|
||||
c.getStyleClass().removeAll(excludes);
|
||||
}
|
||||
c.getStyleClass().add(styleClass);
|
||||
}
|
||||
}
|
@ -0,0 +1,735 @@
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
|
||||
package atlantafx.sampler.page.components;
|
||||
|
||||
import static javafx.collections.FXCollections.observableArrayList;
|
||||
|
||||
import atlantafx.base.controls.CaptionMenuItem;
|
||||
import atlantafx.base.controls.Spacer;
|
||||
import atlantafx.base.controls.ToggleSwitch;
|
||||
import atlantafx.base.theme.Styles;
|
||||
import atlantafx.base.theme.Tweaks;
|
||||
import atlantafx.base.util.BBCodeParser;
|
||||
import atlantafx.sampler.fake.domain.Product;
|
||||
import atlantafx.sampler.page.ExampleBox;
|
||||
import atlantafx.sampler.page.OutlinePage;
|
||||
import atlantafx.sampler.page.Snippet;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.IntStream;
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.beans.binding.StringBinding;
|
||||
import javafx.beans.property.SimpleBooleanProperty;
|
||||
import javafx.beans.property.SimpleObjectProperty;
|
||||
import javafx.beans.property.SimpleStringProperty;
|
||||
import javafx.collections.FXCollections;
|
||||
import javafx.collections.ObservableList;
|
||||
import javafx.collections.transformation.FilteredList;
|
||||
import javafx.collections.transformation.SortedList;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.scene.control.CheckBox;
|
||||
import javafx.scene.control.CheckMenuItem;
|
||||
import javafx.scene.control.ComboBox;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.control.MenuButton;
|
||||
import javafx.scene.control.Pagination;
|
||||
import javafx.scene.control.RadioMenuItem;
|
||||
import javafx.scene.control.SelectionMode;
|
||||
import javafx.scene.control.SeparatorMenuItem;
|
||||
import javafx.scene.control.TableCell;
|
||||
import javafx.scene.control.TableColumn;
|
||||
import javafx.scene.control.TableView;
|
||||
import javafx.scene.control.ToggleGroup;
|
||||
import javafx.scene.control.cell.CheckBoxTableCell;
|
||||
import javafx.scene.control.cell.ChoiceBoxTableCell;
|
||||
import javafx.scene.control.cell.ComboBoxTableCell;
|
||||
import javafx.scene.control.cell.ProgressBarTableCell;
|
||||
import javafx.scene.control.cell.PropertyValueFactory;
|
||||
import javafx.scene.control.cell.TextFieldTableCell;
|
||||
import javafx.scene.layout.HBox;
|
||||
import javafx.scene.layout.Priority;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import javafx.scene.layout.VBox;
|
||||
import javafx.util.Callback;
|
||||
import org.kordamp.ikonli.feather.Feather;
|
||||
import org.kordamp.ikonli.javafx.FontIconTableCell;
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public class TableViewPage extends OutlinePage {
|
||||
|
||||
public static final String NAME = "TableView";
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return NAME;
|
||||
}
|
||||
|
||||
public record Book(String author,
|
||||
String title,
|
||||
String genre,
|
||||
String publisher,
|
||||
SimpleBooleanProperty status) {
|
||||
|
||||
public static Book random() {
|
||||
return new Book(
|
||||
FAKER.book().author(),
|
||||
FAKER.book().title(),
|
||||
FAKER.book().genre(),
|
||||
FAKER.book().publisher(),
|
||||
new SimpleBooleanProperty()
|
||||
);
|
||||
}
|
||||
|
||||
public static ObservableList<Book> random(int count) {
|
||||
return IntStream.range(0, count).mapToObj(i -> Book.random())
|
||||
.collect(Collectors.toCollection(FXCollections::observableArrayList));
|
||||
}
|
||||
}
|
||||
|
||||
public record Flight(String arrival,
|
||||
String city,
|
||||
String aircraft,
|
||||
String airline,
|
||||
String flight) {
|
||||
|
||||
public static Flight random() {
|
||||
return new Flight(
|
||||
FAKER.date().past(12, TimeUnit.HOURS, "HH:mm"),
|
||||
FAKER.country().capital(),
|
||||
FAKER.aviation().aircraft().toUpperCase(Locale.ROOT),
|
||||
FAKER.aviation().airline(),
|
||||
FAKER.aviation().flight()
|
||||
);
|
||||
}
|
||||
|
||||
public static ObservableList<Flight> random(int count) {
|
||||
return IntStream.range(0, count).mapToObj(i -> Flight.random())
|
||||
.sorted(Comparator.comparing(Flight::arrival))
|
||||
.collect(Collectors.toCollection(FXCollections::observableArrayList));
|
||||
}
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
|
||||
public TableViewPage() {
|
||||
super();
|
||||
|
||||
addFormattedText("""
|
||||
The [i]TableView[/i] control is designed to visualize an unlimited number of rows of data, \
|
||||
broken out into columns."""
|
||||
);
|
||||
addSection("Usage", usageExample());
|
||||
addSection("Row Style", rowStyleExample());
|
||||
addSection("Selection Color", selectionColorExample());
|
||||
addSection("Edge-to-Edge", edge2EdgeExample());
|
||||
addSection("Alignment", alignmentExample());
|
||||
addSection("Editable", editableExample());
|
||||
addSection("Column Grouping", columnGroupingExample());
|
||||
addSection("Pagination", paginationExample());
|
||||
addSection("Playground", playground());
|
||||
}
|
||||
|
||||
public ExampleBox usageExample() {
|
||||
//snippet_1:start
|
||||
var col1 = new TableColumn<Flight, String>("Arrival");
|
||||
col1.setCellValueFactory(
|
||||
c -> new SimpleStringProperty(c.getValue().arrival())
|
||||
);
|
||||
|
||||
var col2 = new TableColumn<Flight, String>("City");
|
||||
col2.setCellValueFactory(
|
||||
c -> new SimpleStringProperty(c.getValue().city())
|
||||
);
|
||||
|
||||
var col3 = new TableColumn<Flight, String>("Airline");
|
||||
col3.setCellValueFactory(
|
||||
c -> new SimpleStringProperty(c.getValue().airline())
|
||||
);
|
||||
|
||||
var col4 = new TableColumn<Flight, String>("Flight");
|
||||
col4.setCellValueFactory(
|
||||
c -> new SimpleStringProperty(c.getValue().flight())
|
||||
);
|
||||
|
||||
var table = new TableView<>(Flight.random(5));
|
||||
table.getColumns().setAll(col1, col2, col3, col4);
|
||||
table.setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY);
|
||||
table.getSelectionModel().selectFirst();
|
||||
//snippet_1:end
|
||||
|
||||
table.setMaxWidth(Double.MAX_VALUE);
|
||||
table.setMinHeight(300);
|
||||
HBox.setHgrow(table, Priority.ALWAYS);
|
||||
|
||||
var box = new HBox(table);
|
||||
var description = BBCodeParser.createFormattedText("""
|
||||
You can create a table view by instantiating the \
|
||||
[font=monospace]javafx.scene.control.TableView[/font] class."""
|
||||
);
|
||||
|
||||
return new ExampleBox(box, new Snippet(getClass(), 1), description);
|
||||
}
|
||||
|
||||
private ExampleBox rowStyleExample() {
|
||||
//snippet_2:start
|
||||
var col1 = new TableColumn<Flight, String>("Arrival");
|
||||
col1.setCellValueFactory(
|
||||
c -> new SimpleStringProperty(c.getValue().arrival())
|
||||
);
|
||||
|
||||
var col2 = new TableColumn<Flight, String>("City");
|
||||
col2.setCellValueFactory(
|
||||
c -> new SimpleStringProperty(c.getValue().city())
|
||||
);
|
||||
|
||||
var col3 = new TableColumn<Flight, String>("Airline");
|
||||
col3.setCellValueFactory(
|
||||
c -> new SimpleStringProperty(c.getValue().airline())
|
||||
);
|
||||
|
||||
var col4 = new TableColumn<Flight, String>("Flight");
|
||||
col4.setCellValueFactory(
|
||||
c -> new SimpleStringProperty(c.getValue().flight())
|
||||
);
|
||||
|
||||
var table = new TableView<>(Flight.random(5));
|
||||
table.getColumns().setAll(col1, col2, col3, col4);
|
||||
table.setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY);
|
||||
table.getSelectionModel().selectFirst();
|
||||
|
||||
var borderToggle = new ToggleSwitch("Bordered");
|
||||
borderToggle.selectedProperty().addListener(
|
||||
(obs, old, val) -> Styles.toggleStyleClass(table, Styles.BORDERED)
|
||||
);
|
||||
|
||||
var stripeToggle = new ToggleSwitch("Striped");
|
||||
stripeToggle.selectedProperty().addListener(
|
||||
(obs, old, val) -> Styles.toggleStyleClass(table, Styles.STRIPED)
|
||||
);
|
||||
|
||||
var denseToggle = new ToggleSwitch("Dense");
|
||||
denseToggle.selectedProperty().addListener(
|
||||
(obs, old, val) -> Styles.toggleStyleClass(table, Styles.DENSE)
|
||||
);
|
||||
//snippet_2:end
|
||||
|
||||
table.setMaxWidth(Double.MAX_VALUE);
|
||||
table.setMinHeight(300);
|
||||
HBox.setHgrow(table, Priority.ALWAYS);
|
||||
|
||||
var togglesBox = new HBox(HGAP_20, borderToggle, stripeToggle, denseToggle);
|
||||
togglesBox.setAlignment(Pos.CENTER);
|
||||
|
||||
var box = new VBox(VGAP_10, table, togglesBox);
|
||||
var description = BBCodeParser.createFormattedText("""
|
||||
The [i]TableView[/i] rows can be styled simply by adding CSS classes:
|
||||
|
||||
[ul]
|
||||
[li][code]Styles.BORDERED[/code] - adds borders between the columns.[/li]
|
||||
[li][code]Styles.STRIPED[/code] - adds zebra-striping.[/li]
|
||||
[li][code]Styles.DENSE[/code] - makes the table more compact by cutting cell padding.[/li][/ul]"""
|
||||
);
|
||||
|
||||
return new ExampleBox(box, new Snippet(getClass(), 2), description);
|
||||
}
|
||||
|
||||
private ExampleBox selectionColorExample() {
|
||||
var style = """
|
||||
-color-cell-bg-selected: -color-accent-emphasis;
|
||||
-color-cell-fg-selected: -color-fg-emphasis;
|
||||
-color-cell-bg-selected-focused: -color-accent-emphasis;
|
||||
-color-cell-fg-selected-focused: -color-fg-emphasis;""";
|
||||
|
||||
//snippet_3:start
|
||||
var col1 = new TableColumn<Flight, String>("Arrival");
|
||||
col1.setCellValueFactory(
|
||||
c -> new SimpleStringProperty(c.getValue().arrival())
|
||||
);
|
||||
|
||||
var col2 = new TableColumn<Flight, String>("City");
|
||||
col2.setCellValueFactory(
|
||||
c -> new SimpleStringProperty(c.getValue().city())
|
||||
);
|
||||
|
||||
var col3 = new TableColumn<Flight, String>("Airline");
|
||||
col3.setCellValueFactory(
|
||||
c -> new SimpleStringProperty(c.getValue().airline())
|
||||
);
|
||||
|
||||
var col4 = new TableColumn<Flight, String>("Flight");
|
||||
col4.setCellValueFactory(
|
||||
c -> new SimpleStringProperty(c.getValue().flight())
|
||||
);
|
||||
|
||||
var table = new TableView<>(Flight.random(5));
|
||||
table.getColumns().setAll(col1, col2, col3, col4);
|
||||
// -color-cell-bg-selected: -color-accent-emphasis;
|
||||
// -color-cell-fg-selected: -color-fg-emphasis;
|
||||
// -color-cell-bg-selected-focused: -color-accent-emphasis;
|
||||
// -color-cell-fg-selected-focused: -color-fg-emphasis;
|
||||
table.setStyle(style);
|
||||
table.setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY);
|
||||
table.getSelectionModel().selectFirst();
|
||||
//snippet_3:end
|
||||
|
||||
table.setMaxWidth(Double.MAX_VALUE);
|
||||
table.setMinHeight(250);
|
||||
HBox.setHgrow(table, Priority.ALWAYS);
|
||||
|
||||
var box = new HBox(table);
|
||||
var description = BBCodeParser.createFormattedText("""
|
||||
Cell selection color (and more) can be changed via looked-up color variables. \
|
||||
You can find all supported color variables in the docs."""
|
||||
);
|
||||
|
||||
return new ExampleBox(box, new Snippet(getClass(), 3), description);
|
||||
}
|
||||
|
||||
private ExampleBox edge2EdgeExample() {
|
||||
//snippet_5:start
|
||||
var col1 = new TableColumn<Flight, String>("Arrival");
|
||||
col1.setCellValueFactory(
|
||||
c -> new SimpleStringProperty(c.getValue().arrival())
|
||||
);
|
||||
|
||||
var col2 = new TableColumn<Flight, String>("City");
|
||||
col2.setCellValueFactory(
|
||||
c -> new SimpleStringProperty(c.getValue().city())
|
||||
);
|
||||
|
||||
var col3 = new TableColumn<Flight, String>("Airline");
|
||||
col3.setCellValueFactory(
|
||||
c -> new SimpleStringProperty(c.getValue().airline())
|
||||
);
|
||||
|
||||
var col4 = new TableColumn<Flight, String>("Flight");
|
||||
col4.setCellValueFactory(
|
||||
c -> new SimpleStringProperty(c.getValue().flight())
|
||||
);
|
||||
|
||||
var table = new TableView<>(Flight.random(5));
|
||||
table.getColumns().setAll(col1, col2, col3, col4);
|
||||
table.getStyleClass().add(Tweaks.EDGE_TO_EDGE);
|
||||
table.setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY);
|
||||
table.getSelectionModel().selectFirst();
|
||||
//snippet_5:end
|
||||
|
||||
table.setMaxWidth(Double.MAX_VALUE);
|
||||
table.setMinHeight(250);
|
||||
HBox.setHgrow(table, Priority.ALWAYS);
|
||||
|
||||
var box = new HBox(table);
|
||||
box.setStyle("""
|
||||
-fx-border-color: -color-accent-emphasis;
|
||||
-fx-border-width: 2px;"""
|
||||
);
|
||||
var description = BBCodeParser.createFormattedText("""
|
||||
Use [code]Tweaks.EDGE_TO_EDGE[/code] style class to remove the [i]TableView[/i] outer borders. \
|
||||
This is useful if you want to place the table into external container that already has its \
|
||||
own borders."""
|
||||
);
|
||||
|
||||
return new ExampleBox(box, new Snippet(getClass(), 5), description);
|
||||
}
|
||||
|
||||
private ExampleBox alignmentExample() {
|
||||
//snippet_6:start
|
||||
var col1 = new TableColumn<Flight, String>("Arrival");
|
||||
col1.getStyleClass().add(Tweaks.ALIGN_CENTER);
|
||||
col1.setCellValueFactory(
|
||||
c -> new SimpleStringProperty(c.getValue().arrival())
|
||||
);
|
||||
|
||||
var col2 = new TableColumn<Flight, String>("City");
|
||||
col2.getStyleClass().add(Tweaks.ALIGN_RIGHT);
|
||||
col2.setCellValueFactory(
|
||||
c -> new SimpleStringProperty(c.getValue().city())
|
||||
);
|
||||
|
||||
var col3 = new TableColumn<Flight, String>("Airline");
|
||||
col3.getStyleClass().add(Tweaks.ALIGN_RIGHT);
|
||||
col3.setCellValueFactory(
|
||||
c -> new SimpleStringProperty(c.getValue().airline())
|
||||
);
|
||||
|
||||
var col4 = new TableColumn<Flight, String>("Flight");
|
||||
col4.getStyleClass().add(Tweaks.ALIGN_CENTER);
|
||||
col4.setCellValueFactory(
|
||||
c -> new SimpleStringProperty(c.getValue().flight())
|
||||
);
|
||||
|
||||
var table = new TableView<>(Flight.random(5));
|
||||
table.getColumns().setAll(col1, col2, col3, col4);
|
||||
table.setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY);
|
||||
table.getSelectionModel().selectFirst();
|
||||
//snippet_6:end
|
||||
|
||||
table.setMaxWidth(Double.MAX_VALUE);
|
||||
table.setMinHeight(300);
|
||||
HBox.setHgrow(table, Priority.ALWAYS);
|
||||
|
||||
var box = new HBox(table);
|
||||
var description = BBCodeParser.createFormattedText("""
|
||||
Column content can be aligned by adding one of the following style class modifiers:
|
||||
|
||||
[ul]
|
||||
[li][code]Tweaks.ALIGN_LEFT[/code] (default)[/li]
|
||||
[li][code]Tweaks.ALIGN_CENTER[/code][/li]
|
||||
[li][code]Tweaks.ALIGN_RIGHT[/code][/li][/ul]"""
|
||||
);
|
||||
|
||||
return new ExampleBox(box, new Snippet(getClass(), 6), description);
|
||||
}
|
||||
|
||||
private ExampleBox editableExample() {
|
||||
//snippet_7:start
|
||||
var selectAll = new CheckBox();
|
||||
|
||||
var col0 = new TableColumn<Book, Boolean>();
|
||||
col0.setGraphic(selectAll);
|
||||
col0.setSortable(false);
|
||||
col0.setCellValueFactory(c -> c.getValue().status());
|
||||
col0.setCellFactory(CheckBoxTableCell.forTableColumn(col0));
|
||||
col0.setEditable(true);
|
||||
|
||||
var col1 = new TableColumn<Book, String>("Author");
|
||||
col1.setCellFactory(TextFieldTableCell.forTableColumn());
|
||||
col1.setCellValueFactory(
|
||||
c -> new SimpleStringProperty(c.getValue().author())
|
||||
);
|
||||
|
||||
var col2 = new TableColumn<Book, String>("Title");
|
||||
col2.setCellFactory(TextFieldTableCell.forTableColumn());
|
||||
col2.setCellValueFactory(
|
||||
c -> new SimpleStringProperty(c.getValue().title())
|
||||
);
|
||||
|
||||
var col3 = new TableColumn<Book, String>("Genre");
|
||||
col3.setCellValueFactory(
|
||||
c -> new SimpleStringProperty(c.getValue().genre())
|
||||
);
|
||||
col3.setCellFactory(ChoiceBoxTableCell.forTableColumn(
|
||||
generate(() -> FAKER.book().genre(), 10).toArray(String[]::new)
|
||||
));
|
||||
|
||||
var col4 = new TableColumn<Book, String>("Publisher");
|
||||
col4.setCellValueFactory(
|
||||
c -> new SimpleStringProperty(c.getValue().publisher())
|
||||
);
|
||||
col4.setCellFactory(ComboBoxTableCell.forTableColumn(
|
||||
generate(() -> FAKER.book().publisher(), 10).toArray(String[]::new)
|
||||
));
|
||||
|
||||
var table = new TableView<>(Book.random(5));
|
||||
table.getColumns().setAll(col0, col1, col2, col3, col4);
|
||||
table.setColumnResizePolicy(TableView.UNCONSTRAINED_RESIZE_POLICY);
|
||||
table.getSelectionModel().selectFirst();
|
||||
selectAll.setOnAction(evt -> {
|
||||
table.getItems().forEach(
|
||||
item -> item.status().set(selectAll.isSelected())
|
||||
);
|
||||
evt.consume();
|
||||
});
|
||||
//snippet_7:end
|
||||
|
||||
table.setMaxWidth(Double.MAX_VALUE);
|
||||
table.setMinHeight(300);
|
||||
table.setEditable(true);
|
||||
HBox.setHgrow(table, Priority.ALWAYS);
|
||||
|
||||
var box = new HBox(table);
|
||||
var description = BBCodeParser.createFormattedText(
|
||||
"the [i]TableView[/i] cells can be made editable by setting an appropriate cell factory."
|
||||
);
|
||||
|
||||
return new ExampleBox(box, new Snippet(getClass(), 7), description);
|
||||
}
|
||||
|
||||
private ExampleBox columnGroupingExample() {
|
||||
//snippet_8:start
|
||||
var col11 = new TableColumn<Flight, String>("Time");
|
||||
col11.setCellValueFactory(
|
||||
c -> new SimpleStringProperty(c.getValue().arrival())
|
||||
);
|
||||
|
||||
var col12 = new TableColumn<Flight, String>("City");
|
||||
col12.setCellValueFactory(
|
||||
c -> new SimpleStringProperty(c.getValue().city())
|
||||
);
|
||||
|
||||
var col1 = new TableColumn<Flight, String>("Arrival");
|
||||
col1.getColumns().setAll(col11, col12);
|
||||
|
||||
var col21 = new TableColumn<Flight, String>("Airline");
|
||||
col21.setCellValueFactory(
|
||||
c -> new SimpleStringProperty(c.getValue().airline())
|
||||
);
|
||||
|
||||
var col22 = new TableColumn<Flight, String>("Aircraft");
|
||||
col22.setCellValueFactory(
|
||||
c -> new SimpleStringProperty(c.getValue().aircraft())
|
||||
);
|
||||
|
||||
var col2 = new TableColumn<Flight, String>("Jet");
|
||||
col2.getColumns().setAll(col21, col22);
|
||||
|
||||
var col3 = new TableColumn<Flight, String>("Flight");
|
||||
col3.setCellValueFactory(
|
||||
c -> new SimpleStringProperty(c.getValue().flight())
|
||||
);
|
||||
|
||||
var table = new TableView<>(Flight.random(5));
|
||||
table.getColumns().setAll(col1, col2, col3);
|
||||
table.setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY);
|
||||
table.getSelectionModel().selectFirst();
|
||||
//snippet_8:end
|
||||
|
||||
table.setMaxWidth(Double.MAX_VALUE);
|
||||
table.setMinHeight(300);
|
||||
HBox.setHgrow(table, Priority.ALWAYS);
|
||||
|
||||
var box = new HBox(table);
|
||||
var description = BBCodeParser.createFormattedText(
|
||||
"Any table column can contain nested columns which allows to group common data properties."
|
||||
);
|
||||
|
||||
return new ExampleBox(box, new Snippet(getClass(), 8), description);
|
||||
}
|
||||
|
||||
private ExampleBox paginationExample() {
|
||||
//snippet_9:start
|
||||
var col1 = new TableColumn<Flight, String>("Arrival");
|
||||
col1.setCellValueFactory(
|
||||
c -> new SimpleStringProperty(c.getValue().arrival())
|
||||
);
|
||||
|
||||
var col2 = new TableColumn<Flight, String>("City");
|
||||
col2.setCellValueFactory(
|
||||
c -> new SimpleStringProperty(c.getValue().city())
|
||||
);
|
||||
|
||||
var col3 = new TableColumn<Flight, String>("Airline");
|
||||
col3.setCellValueFactory(
|
||||
c -> new SimpleStringProperty(c.getValue().airline())
|
||||
);
|
||||
|
||||
var col4 = new TableColumn<Flight, String>("Flight");
|
||||
col4.setCellValueFactory(
|
||||
c -> new SimpleStringProperty(c.getValue().flight())
|
||||
);
|
||||
|
||||
var table = new TableView<>(Flight.random(50));
|
||||
table.getColumns().setAll(col1, col2, col3, col4);
|
||||
table.setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY);
|
||||
table.getSelectionModel().selectFirst();
|
||||
|
||||
var pg = new Pagination(25, 0);
|
||||
pg.setMaxPageIndicatorCount(5);
|
||||
pg.setPageFactory(pageNum -> {
|
||||
table.getItems().setAll(Flight.random(50));
|
||||
return new StackPane(); // null isn't allowed
|
||||
});
|
||||
//snippet_9:end
|
||||
|
||||
table.setMaxWidth(Double.MAX_VALUE);
|
||||
table.setMinHeight(300);
|
||||
HBox.setHgrow(table, Priority.ALWAYS);
|
||||
|
||||
var box = new VBox(table, pg);
|
||||
var description = BBCodeParser.createFormattedText(
|
||||
"Pagination can be used to split up large amounts of data into smaller chunks."
|
||||
);
|
||||
|
||||
return new ExampleBox(box, new Snippet(getClass(), 9), description);
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
// Playground //
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
|
||||
private TableView<Product> table;
|
||||
private final List<Product> dataList = IntStream.range(1, 51).boxed()
|
||||
.map(i -> atlantafx.sampler.fake.domain.Product.random(i, FAKER))
|
||||
.toList();
|
||||
|
||||
private VBox playground() {
|
||||
var maxRowCount = 200;
|
||||
var rowCountChoice = new ComboBox<>(
|
||||
observableArrayList(0, 1, 5, 10, 25, maxRowCount)
|
||||
);
|
||||
rowCountChoice.setValue(5);
|
||||
|
||||
var rowCountBox = new HBox(HGAP_20, new Label("rows"), rowCountChoice);
|
||||
rowCountBox.setAlignment(Pos.CENTER_LEFT);
|
||||
|
||||
// == FOOTER ==
|
||||
|
||||
var bordersToggle = new ToggleSwitch("Bordered");
|
||||
bordersToggle.selectedProperty().addListener(
|
||||
(obs, old, val) -> Styles.toggleStyleClass(table, Styles.BORDERED)
|
||||
);
|
||||
|
||||
var denseToggle = new ToggleSwitch("Dense");
|
||||
denseToggle.selectedProperty().addListener(
|
||||
(obs, old, val) -> Styles.toggleStyleClass(table, Styles.DENSE)
|
||||
);
|
||||
|
||||
var stripesToggle = new ToggleSwitch("Striped");
|
||||
stripesToggle.selectedProperty().addListener(
|
||||
(obs, old, val) -> Styles.toggleStyleClass(table, Styles.STRIPED)
|
||||
);
|
||||
|
||||
var edge2edgeToggle = new ToggleSwitch("Edge to edge");
|
||||
edge2edgeToggle.selectedProperty().addListener(
|
||||
(obs, old, value) -> Styles.toggleStyleClass(table, Tweaks.EDGE_TO_EDGE)
|
||||
);
|
||||
|
||||
var footer = new HBox(HGAP_20, bordersToggle, denseToggle, stripesToggle, edge2edgeToggle);
|
||||
footer.setAlignment(Pos.CENTER);
|
||||
|
||||
// == TABLE ==
|
||||
|
||||
var filteredData = new FilteredList<>(observableArrayList(dataList));
|
||||
filteredData.predicateProperty().bind(Bindings.createObjectBinding(
|
||||
() -> product -> product.getId() <= rowCountChoice.getValue(),
|
||||
rowCountChoice.valueProperty()
|
||||
));
|
||||
|
||||
var sortedData = new SortedList<>(filteredData);
|
||||
|
||||
table = createTable();
|
||||
table.setItems(sortedData);
|
||||
table.setColumnResizePolicy(TableView.UNCONSTRAINED_RESIZE_POLICY);
|
||||
sortedData.comparatorProperty().bind(table.comparatorProperty());
|
||||
VBox.setVgrow(table, Priority.ALWAYS);
|
||||
|
||||
// == HEADER ==
|
||||
|
||||
var header = new HBox(createTablePropertiesMenu(table), new Spacer(), rowCountBox);
|
||||
header.setAlignment(Pos.CENTER_LEFT);
|
||||
|
||||
// ~
|
||||
|
||||
var description = BBCodeParser.createFormattedText("""
|
||||
The playground demonstrates the most important [i]TableView[/i] features \
|
||||
and also serves as an object for monkey testing."""
|
||||
);
|
||||
|
||||
var playground = new VBox(VGAP_10, description, header, table, footer);
|
||||
playground.setMinHeight(500);
|
||||
|
||||
return playground;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private TableView<Product> createTable() {
|
||||
// an example of creating index column if data object
|
||||
// doesn't provide index property
|
||||
var indexCol = new TableColumn<Product, String>("Index");
|
||||
indexCol.setCellFactory(col -> {
|
||||
var cell = new TableCell<Product, String>();
|
||||
StringBinding value = Bindings.when(cell.emptyProperty())
|
||||
.then("")
|
||||
.otherwise(cell.indexProperty().add(1).asString());
|
||||
cell.textProperty().bind(value);
|
||||
return cell;
|
||||
});
|
||||
|
||||
var iconCol = new TableColumn<Product, Feather>("Logo");
|
||||
iconCol.setCellValueFactory(c -> new SimpleObjectProperty<>(randomIcon()));
|
||||
iconCol.setCellFactory(FontIconTableCell.forTableColumn());
|
||||
|
||||
var brandCol = new TableColumn<Product, String>("Brand");
|
||||
brandCol.setCellValueFactory(new PropertyValueFactory<>("brand"));
|
||||
|
||||
var nameCol = new TableColumn<Product, String>("Name");
|
||||
nameCol.setCellValueFactory(new PropertyValueFactory<>("name"));
|
||||
|
||||
var priceCol = new TableColumn<Product, String>("Price");
|
||||
priceCol.setCellValueFactory(new PropertyValueFactory<>("price"));
|
||||
|
||||
var stockCountCol = new TableColumn<Product, Integer>("Count");
|
||||
stockCountCol.setCellValueFactory(new PropertyValueFactory<>("count"));
|
||||
|
||||
var stockAvailCol = new TableColumn<Product, Double>("Available");
|
||||
stockAvailCol.setCellValueFactory(new PropertyValueFactory<>("availability"));
|
||||
stockAvailCol.setCellFactory(ProgressBarTableCell.forTableColumn());
|
||||
|
||||
var stockCol = new TableColumn<Product, Double>("Stock");
|
||||
stockCol.getColumns().setAll(stockCountCol, stockAvailCol);
|
||||
|
||||
var tableView = new TableView<Product>();
|
||||
tableView.getColumns().setAll(indexCol, iconCol, brandCol, nameCol, priceCol, stockCol);
|
||||
|
||||
return tableView;
|
||||
}
|
||||
|
||||
private MenuButton createTablePropertiesMenu(TableView<Product> table) {
|
||||
final var resizePolCaption = new CaptionMenuItem("Resize Policy");
|
||||
final var resizePolGroup = new ToggleGroup();
|
||||
resizePolGroup.selectedToggleProperty().addListener((obs, old, val) -> {
|
||||
if (val != null && val.getUserData() instanceof Callback<?, ?> policy) {
|
||||
//noinspection rawtypes,unchecked
|
||||
table.setColumnResizePolicy((Callback<TableView.ResizeFeatures, Boolean>) policy);
|
||||
}
|
||||
});
|
||||
|
||||
final var unconResizeItem = new RadioMenuItem("Unconstrained");
|
||||
unconResizeItem.setToggleGroup(resizePolGroup);
|
||||
unconResizeItem.setUserData(TableView.UNCONSTRAINED_RESIZE_POLICY);
|
||||
unconResizeItem.setSelected(true);
|
||||
|
||||
final var conResizeItem = new RadioMenuItem("Constrained");
|
||||
conResizeItem.setToggleGroup(resizePolGroup);
|
||||
conResizeItem.setUserData(TableView.CONSTRAINED_RESIZE_POLICY);
|
||||
|
||||
// ~
|
||||
|
||||
final var selModeCaption = new CaptionMenuItem("Selection Mode");
|
||||
final var selModeGroup = new ToggleGroup();
|
||||
selModeGroup.selectedToggleProperty().addListener((obs, old, val) -> {
|
||||
if (val != null && val.getUserData() instanceof SelectionMode mode) {
|
||||
table.getSelectionModel().setSelectionMode(mode);
|
||||
}
|
||||
});
|
||||
|
||||
final var singleSelItem = new RadioMenuItem("Single");
|
||||
singleSelItem.setToggleGroup(selModeGroup);
|
||||
singleSelItem.setUserData(SelectionMode.SINGLE);
|
||||
|
||||
final var multiSelItem = new RadioMenuItem("Multiple");
|
||||
multiSelItem.setToggleGroup(selModeGroup);
|
||||
multiSelItem.setUserData(SelectionMode.MULTIPLE);
|
||||
multiSelItem.setSelected(true);
|
||||
|
||||
// ~
|
||||
|
||||
final var cellSelItem = new CheckMenuItem("Enable cell selection");
|
||||
table.getSelectionModel().cellSelectionEnabledProperty().bind(cellSelItem.selectedProperty());
|
||||
cellSelItem.setSelected(false);
|
||||
|
||||
// ~
|
||||
|
||||
final var menuBtnItem = new CheckMenuItem("Show menu button");
|
||||
table.tableMenuButtonVisibleProperty().bind(menuBtnItem.selectedProperty());
|
||||
menuBtnItem.setSelected(true);
|
||||
|
||||
final var propsMenu = new MenuButton("Properties");
|
||||
propsMenu.getItems().setAll(
|
||||
resizePolCaption,
|
||||
unconResizeItem,
|
||||
conResizeItem,
|
||||
selModeCaption,
|
||||
singleSelItem,
|
||||
multiSelItem,
|
||||
new SeparatorMenuItem(),
|
||||
cellSelItem,
|
||||
menuBtnItem
|
||||
);
|
||||
|
||||
return propsMenu;
|
||||
}
|
||||
}
|
@ -2,21 +2,19 @@
|
||||
|
||||
package atlantafx.sampler.page.components;
|
||||
|
||||
import static atlantafx.base.theme.Styles.STATE_DANGER;
|
||||
import static atlantafx.base.theme.Styles.STATE_SUCCESS;
|
||||
|
||||
import atlantafx.sampler.page.AbstractPage;
|
||||
import atlantafx.sampler.page.SampleBlock;
|
||||
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.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
import javafx.scene.control.TextArea;
|
||||
import javafx.scene.layout.FlowPane;
|
||||
import javafx.scene.layout.HBox;
|
||||
|
||||
public class TextAreaPage extends AbstractPage {
|
||||
public class TextAreaPage extends OutlinePage {
|
||||
|
||||
public static final String NAME = "TextArea";
|
||||
private static final double CONTROL_WIDTH = 200;
|
||||
private static final double CONTROL_HEIGHT = 120;
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
@ -25,68 +23,109 @@ public class TextAreaPage extends AbstractPage {
|
||||
|
||||
public TextAreaPage() {
|
||||
super();
|
||||
setUserContent(new FlowPane(
|
||||
PAGE_HGAP, PAGE_VGAP,
|
||||
basicSample(),
|
||||
promptSample(),
|
||||
scrollSample(),
|
||||
readonlySample(),
|
||||
successSample(),
|
||||
dangerSample(),
|
||||
disabledSample()
|
||||
));
|
||||
}
|
||||
|
||||
private SampleBlock basicSample() {
|
||||
var textArea = createTextArea("Text");
|
||||
textArea.setWrapText(true);
|
||||
return new SampleBlock("Basic", textArea);
|
||||
}
|
||||
|
||||
private SampleBlock promptSample() {
|
||||
var textArea = createTextArea(null);
|
||||
textArea.setPromptText("Prompt text");
|
||||
return new SampleBlock("Prompt", textArea);
|
||||
}
|
||||
|
||||
private SampleBlock scrollSample() {
|
||||
var textArea = createTextArea(
|
||||
Stream.generate(() -> FAKER.lorem().paragraph()).limit(10).collect(Collectors.joining("\n"))
|
||||
addFormattedText(
|
||||
"Text input component that allows a user to enter multiple lines of plain text."
|
||||
);
|
||||
textArea.setWrapText(false);
|
||||
return new SampleBlock("Scrolling", textArea);
|
||||
addSection("Usage", usageExample());
|
||||
addSection("Prompt Text", promptTextExample());
|
||||
addSection("Readonly", readonlyExample());
|
||||
addSection("Color", colorExample());
|
||||
}
|
||||
|
||||
private SampleBlock readonlySample() {
|
||||
var textArea = createTextArea("Text");
|
||||
textArea.setEditable(false);
|
||||
return new SampleBlock("Readonly", textArea);
|
||||
private ExampleBox usageExample() {
|
||||
//snippet_1:start
|
||||
var ta1 = new TextArea("Text");
|
||||
|
||||
var ta2 = new TextArea(
|
||||
Stream.generate(() -> FAKER.lorem().paragraph())
|
||||
.limit(10)
|
||||
.collect(Collectors.joining("\n"))
|
||||
);
|
||||
ta2.setWrapText(true);
|
||||
//snippet_1:end
|
||||
|
||||
ta1.setMinSize(300, 120);
|
||||
ta1.setMaxSize(300, 120);
|
||||
|
||||
ta2.setMinSize(300, 120);
|
||||
ta2.setMaxSize(300, 120);
|
||||
|
||||
var box = new HBox(HGAP_20, ta1, ta2);
|
||||
var description = BBCodeParser.createFormattedText("""
|
||||
You create a text area control by creating an instance of the \
|
||||
[font=monospace]javafx.scene.control.TextArea[/font] class. \
|
||||
By default long text won't be wrapped. You should set [code]setWrapText(true)[/code] \
|
||||
to use this feature."""
|
||||
);
|
||||
|
||||
return new ExampleBox(box, new Snippet(getClass(), 1), description);
|
||||
}
|
||||
|
||||
private SampleBlock successSample() {
|
||||
var textArea = createTextArea("Text");
|
||||
textArea.pseudoClassStateChanged(STATE_SUCCESS, true);
|
||||
return new SampleBlock("Success", textArea);
|
||||
private ExampleBox promptTextExample() {
|
||||
//snippet_2:start
|
||||
var ta = new TextArea();
|
||||
ta.setPromptText("Prompt text");
|
||||
ta.setWrapText(true);
|
||||
//snippet_2:end
|
||||
|
||||
ta.setMaxSize(300, 120);
|
||||
ta.setMinSize(300, 120);
|
||||
|
||||
var box = new HBox(ta);
|
||||
var description = BBCodeParser.createFormattedText("""
|
||||
The [i]TextArea[/i] supports the notion of showing prompt text to the user when there \
|
||||
is no text already in the text area (either via the user, or set programmatically). \
|
||||
This is a useful way of informing the user as to what is expected in the [i]TextArea[/i], \
|
||||
without having to resort to tooltips or on-screen labels."""
|
||||
);
|
||||
|
||||
return new ExampleBox(box, new Snippet(getClass(), 2), description);
|
||||
}
|
||||
|
||||
private SampleBlock dangerSample() {
|
||||
var textArea = createTextArea("Text");
|
||||
textArea.pseudoClassStateChanged(STATE_DANGER, true);
|
||||
return new SampleBlock("Danger", textArea);
|
||||
private ExampleBox readonlyExample() {
|
||||
//snippet_3:start
|
||||
var ta = new TextArea("This text can't be modified");
|
||||
ta.setWrapText(true);
|
||||
ta.setEditable(false);
|
||||
//snippet_3:end
|
||||
|
||||
ta.setMaxSize(300, 120);
|
||||
ta.setMinSize(300, 120);
|
||||
|
||||
var box = new HBox(ta);
|
||||
var description = BBCodeParser.createFormattedText("""
|
||||
The [i]TextArea[/i]'s [code]editable[/code] property indicates whether it \
|
||||
can be edited by the user."""
|
||||
);
|
||||
|
||||
return new ExampleBox(box, new Snippet(getClass(), 3), description);
|
||||
}
|
||||
|
||||
private SampleBlock disabledSample() {
|
||||
var textArea = createTextArea("Text");
|
||||
textArea.setDisable(true);
|
||||
return new SampleBlock("Disabled", textArea);
|
||||
}
|
||||
private ExampleBox colorExample() {
|
||||
//snippet_4:start
|
||||
var ta1 = new TextArea("Text");
|
||||
ta1.pseudoClassStateChanged(Styles.STATE_SUCCESS, true);
|
||||
ta1.setWrapText(true);
|
||||
|
||||
private TextArea createTextArea(String text) {
|
||||
var textArea = new TextArea(text);
|
||||
textArea.setMinWidth(CONTROL_WIDTH);
|
||||
textArea.setMinHeight(CONTROL_HEIGHT);
|
||||
textArea.setMaxWidth(CONTROL_WIDTH);
|
||||
textArea.setMaxHeight(CONTROL_HEIGHT);
|
||||
return textArea;
|
||||
var ta2 = new TextArea("Text");
|
||||
ta2.pseudoClassStateChanged(Styles.STATE_DANGER, true);
|
||||
ta2.setWrapText(true);
|
||||
//snippet_4:end
|
||||
|
||||
ta1.setMaxSize(300, 120);
|
||||
ta1.setMinSize(300, 120);
|
||||
|
||||
ta2.setMaxSize(300, 120);
|
||||
ta2.setMinSize(300, 120);
|
||||
|
||||
var box = new HBox(HGAP_20, ta1, ta2);
|
||||
var description = BBCodeParser.createFormattedText("""
|
||||
You can use [code]Styles.STATE_SUCCESS[/code] or [code]Styles.STATE_DANGER[/code] \
|
||||
pseudo-classes to change the [i]TextArea[/i] color. This especially useful to indicate \
|
||||
the validation result."""
|
||||
);
|
||||
|
||||
return new ExampleBox(box, new Snippet(getClass(), 4), description);
|
||||
}
|
||||
}
|
||||
|
@ -2,22 +2,17 @@
|
||||
|
||||
package atlantafx.sampler.page.components;
|
||||
|
||||
import static atlantafx.base.theme.Styles.LARGE;
|
||||
import static atlantafx.base.theme.Styles.ROUNDED;
|
||||
import static atlantafx.base.theme.Styles.SMALL;
|
||||
import static atlantafx.base.theme.Styles.STATE_DANGER;
|
||||
import static atlantafx.base.theme.Styles.STATE_SUCCESS;
|
||||
import static atlantafx.sampler.page.SampleBlock.BLOCK_HGAP;
|
||||
|
||||
import atlantafx.sampler.page.AbstractPage;
|
||||
import atlantafx.sampler.page.SampleBlock;
|
||||
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 javafx.geometry.Pos;
|
||||
import javafx.scene.control.PasswordField;
|
||||
import javafx.scene.control.TextField;
|
||||
import javafx.scene.layout.HBox;
|
||||
import javafx.scene.layout.VBox;
|
||||
|
||||
public class TextFieldPage extends AbstractPage {
|
||||
public class TextFieldPage extends OutlinePage {
|
||||
|
||||
public static final String NAME = "TextField";
|
||||
|
||||
@ -28,81 +23,165 @@ public class TextFieldPage extends AbstractPage {
|
||||
|
||||
public TextFieldPage() {
|
||||
super();
|
||||
setUserContent(new VBox(
|
||||
PAGE_VGAP,
|
||||
expandingHBox(basicSample(), promptSample(), passwordSample()),
|
||||
expandingHBox(readonlySample(), successSample(), dangerSample()),
|
||||
expandingHBox(sizeSample(), roundedSample()),
|
||||
disabledSample()
|
||||
));
|
||||
|
||||
addFormattedText("""
|
||||
Text input component that allows a user to enter a single line of unformatted text."""
|
||||
);
|
||||
addSection("Usage", usageExample());
|
||||
addSection("Prompt Text", promptTextExample());
|
||||
addSection("Readonly", readonlyExample());
|
||||
addSection("Color", colorExample());
|
||||
addSection("Password Field", passwordFieldExample());
|
||||
addSection("Size", sizeExample());
|
||||
addSection("Rounded", roundedExample());
|
||||
}
|
||||
|
||||
private SampleBlock basicSample() {
|
||||
var field = new TextField("Text");
|
||||
return new SampleBlock("Basic", field);
|
||||
private ExampleBox usageExample() {
|
||||
//snippet_1:start
|
||||
var tf1 = new TextField("Text");
|
||||
var tf2 = new TextField(FAKER.lorem().sentence(20));
|
||||
//snippet_1:end
|
||||
|
||||
tf1.setPrefWidth(200);
|
||||
tf2.setPrefWidth(200);
|
||||
|
||||
var box = new HBox(HGAP_20, tf1, tf2);
|
||||
var description = BBCodeParser.createFormattedText("""
|
||||
You create a text field control by creating an instance of the \
|
||||
[font=monospace]javafx.scene.control.TextField[/font] class."""
|
||||
);
|
||||
|
||||
return new ExampleBox(box, new Snippet(getClass(), 1), description);
|
||||
}
|
||||
|
||||
private SampleBlock passwordSample() {
|
||||
var field = new PasswordField();
|
||||
field.setText("qwerty");
|
||||
return new SampleBlock("Password", field);
|
||||
private ExampleBox promptTextExample() {
|
||||
//snippet_2:start
|
||||
var tf = new TextField();
|
||||
tf.setPromptText("Prompt text");
|
||||
//snippet_2:end
|
||||
|
||||
tf.setPrefWidth(200);
|
||||
|
||||
var box = new HBox(tf);
|
||||
var description = BBCodeParser.createFormattedText("""
|
||||
The [i]TextField[/i] supports the notion of showing prompt text to the user when there \
|
||||
is no text already in the text field (either via the user, or set programmatically). \
|
||||
This is a useful way of informing the user as to what is expected in the text field, \
|
||||
without having to resort to tooltips or on-screen labels."""
|
||||
);
|
||||
|
||||
return new ExampleBox(box, new Snippet(getClass(), 2), description);
|
||||
}
|
||||
|
||||
private SampleBlock promptSample() {
|
||||
var field = new TextField();
|
||||
field.setPromptText("Prompt text");
|
||||
return new SampleBlock("Prompt", field);
|
||||
private ExampleBox readonlyExample() {
|
||||
//snippet_3:start
|
||||
var tf = new TextField("This text can't be modified");
|
||||
tf.setEditable(false);
|
||||
//snippet_3:end
|
||||
|
||||
tf.setPrefWidth(200);
|
||||
|
||||
var box = new HBox(tf);
|
||||
var description = BBCodeParser.createFormattedText("""
|
||||
[i]TextField[/i]'s [code]editable[/code] property indicates whether it can be edited by the user."""
|
||||
);
|
||||
|
||||
return new ExampleBox(box, new Snippet(getClass(), 3), description);
|
||||
}
|
||||
|
||||
private SampleBlock readonlySample() {
|
||||
var field = new TextField("Text");
|
||||
field.setEditable(false);
|
||||
return new SampleBlock("Readonly", field);
|
||||
private ExampleBox colorExample() {
|
||||
//snippet_4:start
|
||||
var tf1 = new TextField("Text");
|
||||
tf1.pseudoClassStateChanged(Styles.STATE_SUCCESS, true);
|
||||
|
||||
var tf2 = new TextField("Text");
|
||||
tf2.pseudoClassStateChanged(Styles.STATE_DANGER, true);
|
||||
//snippet_4:end
|
||||
|
||||
tf1.setPrefWidth(200);
|
||||
tf2.setPrefWidth(200);
|
||||
|
||||
var box = new HBox(HGAP_20, tf1, tf2);
|
||||
var description = BBCodeParser.createFormattedText("""
|
||||
You can use [code]Styles.STATE_SUCCESS[/code] or [code]Styles.STATE_DANGER[/code] \
|
||||
pseudo-classes to change the [i]TextField[/i] color. This especially useful to indicate \
|
||||
the validation result."""
|
||||
);
|
||||
|
||||
return new ExampleBox(box, new Snippet(getClass(), 4), description);
|
||||
}
|
||||
|
||||
private SampleBlock successSample() {
|
||||
var field = new TextField("Text");
|
||||
field.pseudoClassStateChanged(STATE_SUCCESS, true);
|
||||
return new SampleBlock("Success", field);
|
||||
private ExampleBox passwordFieldExample() {
|
||||
//snippet_5:start
|
||||
var tf = new PasswordField();
|
||||
tf.setText("qwerty");
|
||||
//snippet_5:end
|
||||
|
||||
tf.setPrefWidth(200);
|
||||
|
||||
var box = new HBox(tf);
|
||||
var description = BBCodeParser.createFormattedText("""
|
||||
The [i]TextField[/i] flavor that masks entered characters. Note that password can \
|
||||
not be revealed. If you need this particular feature try [code]PasswordTextField[/code]. \
|
||||
It's the [code]CustomTextField[/code] flavor that does the same thing."""
|
||||
);
|
||||
|
||||
return new ExampleBox(box, new Snippet(getClass(), 5), description);
|
||||
}
|
||||
|
||||
private SampleBlock dangerSample() {
|
||||
var field = new TextField("Text");
|
||||
field.pseudoClassStateChanged(STATE_DANGER, true);
|
||||
return new SampleBlock("Danger", field);
|
||||
}
|
||||
|
||||
private SampleBlock sizeSample() {
|
||||
private ExampleBox sizeExample() {
|
||||
//snippet_6:start
|
||||
var smallField = new TextField("Small");
|
||||
smallField.getStyleClass().add(SMALL);
|
||||
smallField.setPrefWidth(70);
|
||||
smallField.getStyleClass().add(Styles.SMALL);
|
||||
|
||||
var normalField = new TextField("Normal");
|
||||
normalField.setPrefWidth(120);
|
||||
|
||||
var largeField = new TextField("Large");
|
||||
largeField.getStyleClass().add(LARGE);
|
||||
largeField.getStyleClass().add(Styles.LARGE);
|
||||
//snippet_6:end
|
||||
|
||||
smallField.setPrefWidth(70);
|
||||
normalField.setPrefWidth(120);
|
||||
largeField.setPrefWidth(200);
|
||||
|
||||
var content = new HBox(BLOCK_HGAP, smallField, normalField, largeField);
|
||||
content.setAlignment(Pos.CENTER_LEFT);
|
||||
var box = new HBox(HGAP_20, smallField, normalField, largeField);
|
||||
box.setAlignment(Pos.CENTER_LEFT);
|
||||
|
||||
return new SampleBlock("Size", content);
|
||||
var description = BBCodeParser.createFormattedText("""
|
||||
Use [code]Styles.SMALL[/code] and [code]Styles.LARGE[/code] style classes \
|
||||
to change the [i]TextField[/i] size."""
|
||||
);
|
||||
|
||||
return new ExampleBox(box, new Snippet(getClass(), 6), description);
|
||||
}
|
||||
|
||||
private SampleBlock roundedSample() {
|
||||
var field = new TextField("Text");
|
||||
field.getStyleClass().add(ROUNDED);
|
||||
return new SampleBlock("Rounded", field);
|
||||
}
|
||||
private ExampleBox roundedExample() {
|
||||
//snippet_7:start
|
||||
var smallField = new TextField("Small");
|
||||
smallField.getStyleClass().addAll(
|
||||
Styles.SMALL, Styles.ROUNDED
|
||||
);
|
||||
|
||||
private SampleBlock disabledSample() {
|
||||
var field = new TextField("Text");
|
||||
field.setDisable(true);
|
||||
var normalField = new TextField("Normal");
|
||||
normalField.getStyleClass().addAll(Styles.ROUNDED);
|
||||
|
||||
var block = new SampleBlock("Disabled", field);
|
||||
block.setMaxWidth(250);
|
||||
var largeField = new TextField("Large");
|
||||
largeField.getStyleClass().addAll(
|
||||
Styles.LARGE, Styles.ROUNDED
|
||||
);
|
||||
//snippet_7:end
|
||||
|
||||
return block;
|
||||
smallField.setPrefWidth(70);
|
||||
normalField.setPrefWidth(120);
|
||||
largeField.setPrefWidth(200);
|
||||
|
||||
var box = new HBox(HGAP_20, smallField, normalField, largeField);
|
||||
box.setAlignment(Pos.CENTER_LEFT);
|
||||
|
||||
var description = BBCodeParser.createFormattedText("""
|
||||
Use [code]Styles.ROUNDED[/code] style class to round the [i]TextField[/i] corners."""
|
||||
);
|
||||
|
||||
return new ExampleBox(box, new Snippet(getClass(), 7), description);
|
||||
}
|
||||
}
|
||||
|
@ -2,26 +2,23 @@
|
||||
|
||||
package atlantafx.sampler.page.components;
|
||||
|
||||
import static atlantafx.base.theme.Styles.DENSE;
|
||||
import static atlantafx.base.theme.Styles.ELEVATED_2;
|
||||
import static atlantafx.base.theme.Styles.INTERACTIVE;
|
||||
import static atlantafx.base.theme.Styles.toggleStyleClass;
|
||||
import static atlantafx.sampler.page.SampleBlock.BLOCK_HGAP;
|
||||
import static atlantafx.sampler.page.SampleBlock.BLOCK_VGAP;
|
||||
import static javafx.geometry.HPos.RIGHT;
|
||||
import static javafx.scene.layout.Priority.NEVER;
|
||||
|
||||
import atlantafx.base.controls.Spacer;
|
||||
import atlantafx.base.controls.ToggleSwitch;
|
||||
import atlantafx.base.theme.Styles;
|
||||
import atlantafx.base.theme.Tweaks;
|
||||
import atlantafx.sampler.page.AbstractPage;
|
||||
import atlantafx.sampler.page.Page;
|
||||
import atlantafx.base.util.BBCodeParser;
|
||||
import atlantafx.sampler.page.ExampleBox;
|
||||
import atlantafx.sampler.page.OutlinePage;
|
||||
import atlantafx.sampler.page.Snippet;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.scene.control.CheckBox;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.control.Slider;
|
||||
import javafx.scene.control.TitledPane;
|
||||
import javafx.scene.layout.ColumnConstraints;
|
||||
import javafx.scene.layout.FlowPane;
|
||||
import javafx.scene.layout.GridPane;
|
||||
import javafx.scene.layout.HBox;
|
||||
import javafx.scene.layout.Priority;
|
||||
@ -30,10 +27,9 @@ import javafx.scene.layout.VBox;
|
||||
import javafx.scene.text.Text;
|
||||
import javafx.scene.text.TextFlow;
|
||||
|
||||
public class TitledPanePage extends AbstractPage {
|
||||
public class TitledPanePage extends OutlinePage {
|
||||
|
||||
public static final String NAME = "TitledPane";
|
||||
private static final String ELEVATED_PREFIX = "elevated-";
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
@ -42,36 +38,160 @@ public class TitledPanePage extends AbstractPage {
|
||||
|
||||
public TitledPanePage() {
|
||||
super();
|
||||
createView();
|
||||
}
|
||||
|
||||
private void createView() {
|
||||
var samples = new HBox(
|
||||
PAGE_HGAP,
|
||||
interactivePane(),
|
||||
disabledPane(),
|
||||
untitledPane()
|
||||
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. \
|
||||
Some of the [i]TitledPane[/i] can be applied to the [i]Accordion[/i] as well."""
|
||||
);
|
||||
samples.getChildren().forEach(c -> ((TitledPane) c).setPrefSize(500, 120));
|
||||
addSection("Usage", usageExample());
|
||||
addSection("Elevation", elevationExample());
|
||||
addSection("Dense", denseExample());
|
||||
addSection("Alternative Icon", altIconExample());
|
||||
addSection("Playground", playground());
|
||||
|
||||
setUserContent(new VBox(
|
||||
Page.PAGE_VGAP,
|
||||
createPlayground(),
|
||||
samples
|
||||
));
|
||||
var dummyBox = new HBox();
|
||||
dummyBox.setMinHeight(10);
|
||||
addNode(dummyBox);
|
||||
}
|
||||
|
||||
private TitledPane createPlayground() {
|
||||
var playground = new TitledPane();
|
||||
playground.setText("_Playground");
|
||||
playground.setMnemonicParsing(true);
|
||||
playground.getStyleClass().add(ELEVATED_2);
|
||||
private ExampleBox usageExample() {
|
||||
//snippet_1:start
|
||||
var tp1 = new TitledPane(
|
||||
"Header",
|
||||
new Label("Content")
|
||||
);
|
||||
|
||||
var textFlow = new TextFlow(new Text(FAKER.lorem().paragraph(10)));
|
||||
var tp2 = new TitledPane("Header", new Label("Content"));
|
||||
tp2.setCollapsible(false);
|
||||
//snippet_1:end
|
||||
|
||||
tp1.setPrefWidth(200);
|
||||
tp2.setPrefWidth(200);
|
||||
|
||||
var box = new HBox(HGAP_20, tp1, tp2);
|
||||
var description = BBCodeParser.createFormattedText("""
|
||||
The panel in a [i]TitledPane[/i] can be any node such as controls or groups of nodes \
|
||||
added to a layout container. Note that whilst [i]TitledPane[/i] extends from [i]Labeled[/i], \
|
||||
the inherited properties are used to manipulate the [i]TitledPane[/i] header, not the \
|
||||
content area itself."""
|
||||
);
|
||||
|
||||
return new ExampleBox(box, new Snippet(getClass(), 1), description);
|
||||
}
|
||||
|
||||
private ExampleBox elevationExample() {
|
||||
//snippet_2:start
|
||||
var tp0 = new TitledPane(
|
||||
"Default",
|
||||
new Label("Content")
|
||||
);
|
||||
|
||||
var tp1 = new TitledPane(
|
||||
"Styles.ELEVATED_1",
|
||||
new Label("Content")
|
||||
);
|
||||
tp1.getStyleClass().add(Styles.ELEVATED_1);
|
||||
|
||||
var tp2 = new TitledPane(
|
||||
"Styles.ELEVATED_2",
|
||||
new Label("Content")
|
||||
);
|
||||
tp2.getStyleClass().add(Styles.ELEVATED_2);
|
||||
|
||||
var tp3 = new TitledPane(
|
||||
"Styles.ELEVATED_3",
|
||||
new Label("Content")
|
||||
);
|
||||
tp3.getStyleClass().add(Styles.ELEVATED_3);
|
||||
|
||||
var tp4 = new TitledPane(
|
||||
"Styles.ELEVATED_4",
|
||||
new Label("Content")
|
||||
);
|
||||
tp4.getStyleClass().add(Styles.ELEVATED_4);
|
||||
|
||||
var tp5 = new TitledPane(
|
||||
"Styles.INTERACTIVE",
|
||||
new Label("Hover here")
|
||||
);
|
||||
tp5.getStyleClass().add(Styles.INTERACTIVE);
|
||||
//snippet_2:end
|
||||
|
||||
tp0.setPrefWidth(300);
|
||||
tp1.setPrefWidth(300);
|
||||
tp2.setPrefWidth(300);
|
||||
tp3.setPrefWidth(300);
|
||||
tp4.setPrefWidth(300);
|
||||
tp5.setPrefWidth(300);
|
||||
|
||||
var box = new FlowPane(HGAP_30, VGAP_20, tp0, tp1, tp2, tp3, tp4, tp5);
|
||||
var description = BBCodeParser.createFormattedText("""
|
||||
With [code]Styles.ELEVATED_N[/code] or [code]Styles.INTERACTIVE[/code] styles classes \
|
||||
you can add raised shadow effect to the [i]TitledPane[/i]."""
|
||||
);
|
||||
|
||||
return new ExampleBox(box, new Snippet(getClass(), 2), description);
|
||||
}
|
||||
|
||||
private ExampleBox denseExample() {
|
||||
//snippet_3:start
|
||||
var tp = new TitledPane(
|
||||
"Header",
|
||||
new Label("Content")
|
||||
);
|
||||
tp.getStyleClass().add(Styles.DENSE);
|
||||
//snippet_3:end
|
||||
|
||||
tp.setPrefWidth(200);
|
||||
|
||||
var box = new HBox(tp);
|
||||
var description = BBCodeParser.createFormattedText("""
|
||||
If you need more compact view there's [code]Styles.DENSE[/code] for that."""
|
||||
);
|
||||
|
||||
return new ExampleBox(box, new Snippet(getClass(), 3), description);
|
||||
}
|
||||
|
||||
private ExampleBox altIconExample() {
|
||||
//snippet_3:start
|
||||
var tp = new TitledPane(
|
||||
"Header",
|
||||
new Label("Content")
|
||||
);
|
||||
tp.getStyleClass().add(Tweaks.ALT_ICON);
|
||||
//snippet_3:end
|
||||
|
||||
tp.setPrefWidth(200);
|
||||
|
||||
var box = new HBox(tp);
|
||||
var description = BBCodeParser.createFormattedText("""
|
||||
There's additional tweak [code]Tweaks.ALT_ICON[/code] to change header \
|
||||
arrow icon to the classic style."""
|
||||
);
|
||||
|
||||
return new ExampleBox(box, new Snippet(getClass(), 3), description);
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
// Playground //
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
|
||||
private VBox playground() {
|
||||
var titledPane = new TitledPane();
|
||||
titledPane.setText("_Header");
|
||||
titledPane.setMnemonicParsing(true);
|
||||
titledPane.getStyleClass().add(Styles.ELEVATED_2);
|
||||
|
||||
var textFlow = new TextFlow(new Text(
|
||||
FAKER.lorem().paragraph(10))
|
||||
);
|
||||
textFlow.setMinHeight(Region.USE_PREF_SIZE);
|
||||
textFlow.setMaxHeight(Region.USE_PREF_SIZE);
|
||||
VBox.setVgrow(textFlow, Priority.ALWAYS);
|
||||
textFlow.setLineSpacing(5);
|
||||
|
||||
String elevationPrefix = "elevated-";
|
||||
var elevationSlider = new Slider(0, 4, 2);
|
||||
elevationSlider.setShowTickLabels(true);
|
||||
elevationSlider.setShowTickMarks(true);
|
||||
@ -82,15 +202,17 @@ public class TitledPanePage extends AbstractPage {
|
||||
elevationSlider.setMinWidth(150);
|
||||
elevationSlider.setMaxWidth(150);
|
||||
elevationSlider.valueProperty().addListener((obs, old, val) -> {
|
||||
playground.getStyleClass().removeAll(
|
||||
playground.getStyleClass().stream().filter(c -> c.startsWith(ELEVATED_PREFIX)).toList()
|
||||
titledPane.getStyleClass().removeAll(
|
||||
titledPane.getStyleClass().stream()
|
||||
.filter(c -> c.startsWith(elevationPrefix))
|
||||
.toList()
|
||||
);
|
||||
if (val == null) {
|
||||
return;
|
||||
}
|
||||
int level = val.intValue();
|
||||
if (level > 0) {
|
||||
playground.getStyleClass().add(ELEVATED_PREFIX + level);
|
||||
titledPane.getStyleClass().add(elevationPrefix + level);
|
||||
}
|
||||
});
|
||||
|
||||
@ -99,65 +221,46 @@ public class TitledPanePage extends AbstractPage {
|
||||
// for some reason it still preserves arrow button gap. #javafx-bug
|
||||
var collapseToggle = new ToggleSwitch("Collapsible");
|
||||
collapseToggle.setSelected(true);
|
||||
playground.collapsibleProperty().bind(collapseToggle.selectedProperty());
|
||||
titledPane.collapsibleProperty().bind(collapseToggle.selectedProperty());
|
||||
|
||||
var animateToggle = new ToggleSwitch("Animated");
|
||||
animateToggle.setSelected(true);
|
||||
playground.animatedProperty().bind(animateToggle.selectedProperty());
|
||||
titledPane.animatedProperty().bind(animateToggle.selectedProperty());
|
||||
|
||||
var denseToggle = new ToggleSwitch("Dense");
|
||||
denseToggle.selectedProperty().addListener((obs, old, val) -> toggleStyleClass(playground, DENSE));
|
||||
denseToggle.selectedProperty()
|
||||
.addListener((obs, old, val) -> Styles.toggleStyleClass(titledPane, Styles.DENSE));
|
||||
|
||||
var altIconToggle = new ToggleSwitch("Alt icon");
|
||||
altIconToggle.selectedProperty().addListener((obs, old, val) -> toggleStyleClass(playground, Tweaks.ALT_ICON));
|
||||
altIconToggle.selectedProperty()
|
||||
.addListener((obs, old, val) -> Styles.toggleStyleClass(titledPane, Tweaks.ALT_ICON));
|
||||
|
||||
var toggles = new GridPane();
|
||||
toggles.setVgap(10);
|
||||
toggles.setHgap(BLOCK_HGAP);
|
||||
toggles.setHgap(HGAP_20);
|
||||
toggles.setVgap(VGAP_10);
|
||||
toggles.getColumnConstraints().setAll(
|
||||
new ColumnConstraints(-1, -1, -1, NEVER, RIGHT, false),
|
||||
new ColumnConstraints(-1, -1, -1, NEVER, RIGHT, false)
|
||||
);
|
||||
toggles.add(collapseToggle, 0, 0);
|
||||
toggles.add(animateToggle, 1, 0);
|
||||
toggles.add(denseToggle, 0, 1);
|
||||
toggles.add(altIconToggle, 1, 1);
|
||||
toggles.addRow(0, collapseToggle, animateToggle);
|
||||
toggles.addRow(1, denseToggle, altIconToggle);
|
||||
|
||||
var controls = new HBox(BLOCK_HGAP);
|
||||
controls.setMinHeight(80);
|
||||
var elevationBox = new HBox(10, new Label("Elevation"), elevationSlider);
|
||||
|
||||
var controls = new HBox(VGAP_10);
|
||||
controls.setMinHeight(100);
|
||||
controls.setFillHeight(false);
|
||||
controls.setAlignment(Pos.CENTER_LEFT);
|
||||
controls.getChildren().setAll(
|
||||
new Label("Elevation"),
|
||||
elevationSlider,
|
||||
new Spacer(),
|
||||
toggles
|
||||
controls.getChildren().setAll(elevationBox, new Spacer(), toggles);
|
||||
|
||||
var content = new VBox(VGAP_10, textFlow, controls);
|
||||
titledPane.setContent(content);
|
||||
|
||||
var description = BBCodeParser.createFormattedText("""
|
||||
The playground demonstrates the most important [i]TitledPane[/i] features \
|
||||
and also serves as an object for monkey testing."""
|
||||
);
|
||||
|
||||
var content = new VBox(BLOCK_VGAP, textFlow, controls);
|
||||
VBox.setVgrow(textFlow, Priority.ALWAYS);
|
||||
playground.setContent(content);
|
||||
|
||||
return playground;
|
||||
}
|
||||
|
||||
private TitledPane interactivePane() {
|
||||
var titledPane = new TitledPane("Interactive", new Text("Hover here."));
|
||||
titledPane.setCollapsible(false);
|
||||
titledPane.getStyleClass().add(INTERACTIVE);
|
||||
return titledPane;
|
||||
}
|
||||
|
||||
private TitledPane disabledPane() {
|
||||
var titledPane = new TitledPane("Disabled", new CheckBox("This checkbox is disabled."));
|
||||
titledPane.setCollapsible(false);
|
||||
titledPane.setDisable(true);
|
||||
return titledPane;
|
||||
}
|
||||
|
||||
private TitledPane untitledPane() {
|
||||
var titledPane = new TitledPane("This pane has no title.", new Text());
|
||||
titledPane.setCollapsible(false);
|
||||
return titledPane;
|
||||
return new VBox(VGAP_10, description, titledPane);
|
||||
}
|
||||
}
|
||||
|
@ -2,26 +2,21 @@
|
||||
|
||||
package atlantafx.sampler.page.components;
|
||||
|
||||
import static atlantafx.base.theme.Styles.BUTTON_ICON;
|
||||
import static atlantafx.base.theme.Styles.CENTER_PILL;
|
||||
import static atlantafx.base.theme.Styles.LEFT_PILL;
|
||||
import static atlantafx.base.theme.Styles.RIGHT_PILL;
|
||||
import static atlantafx.sampler.page.SampleBlock.BLOCK_HGAP;
|
||||
import static atlantafx.sampler.util.Controls.toggleButton;
|
||||
import static javafx.scene.layout.GridPane.REMAINING;
|
||||
|
||||
import atlantafx.sampler.page.AbstractPage;
|
||||
import atlantafx.sampler.page.Page;
|
||||
import atlantafx.sampler.page.SampleBlock;
|
||||
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 javafx.geometry.Pos;
|
||||
import javafx.scene.control.Button;
|
||||
import javafx.scene.control.ContentDisplay;
|
||||
import javafx.scene.control.ToggleButton;
|
||||
import javafx.scene.control.ToggleGroup;
|
||||
import javafx.scene.layout.GridPane;
|
||||
import javafx.scene.layout.HBox;
|
||||
import org.kordamp.ikonli.feather.Feather;
|
||||
import org.kordamp.ikonli.javafx.FontIcon;
|
||||
|
||||
public class ToggleButtonPage extends AbstractPage {
|
||||
public class ToggleButtonPage extends OutlinePage {
|
||||
|
||||
public static final String NAME = "ToggleButton";
|
||||
|
||||
@ -32,103 +27,158 @@ public class ToggleButtonPage extends AbstractPage {
|
||||
|
||||
public ToggleButtonPage() {
|
||||
super();
|
||||
createView();
|
||||
|
||||
addFormattedText("""
|
||||
A [i]ToggleButton[/i] is a special control having the ability to be selected."""
|
||||
);
|
||||
addSection("Usage", usageExample());
|
||||
addSection("Icon Only", iconOnlyExample());
|
||||
addSection("Segmented Group", segmentedGroupExample());
|
||||
}
|
||||
|
||||
private void createView() {
|
||||
var grid = new GridPane();
|
||||
grid.setHgap(Page.PAGE_HGAP);
|
||||
grid.setVgap(Page.PAGE_VGAP);
|
||||
private ExampleBox usageExample() {
|
||||
//snippet_1:start
|
||||
var singleBtn = new ToggleButton("Toggle");
|
||||
singleBtn.setSelected(true);
|
||||
|
||||
grid.add(basicSample(), 0, 0, REMAINING, 1);
|
||||
grid.add(wizardSample(), 0, 1);
|
||||
grid.add(iconOnlySample(), 1, 1);
|
||||
grid.add(disabledSample(), 0, 2);
|
||||
// ~
|
||||
|
||||
setUserContent(grid);
|
||||
}
|
||||
var leftBtn1 = new ToggleButton("._left-pill");
|
||||
leftBtn1.getStyleClass().add(Styles.LEFT_PILL);
|
||||
leftBtn1.setSelected(true);
|
||||
|
||||
private SampleBlock basicSample() {
|
||||
var threeButtonGroup = new ToggleGroup();
|
||||
var rightBtn1 = new ToggleButton("._right-pill");
|
||||
rightBtn1.getStyleClass().add(Styles.RIGHT_PILL);
|
||||
|
||||
var leftPill = toggleButton("._left-pill", null, threeButtonGroup, true, LEFT_PILL);
|
||||
leftPill.setMnemonicParsing(true);
|
||||
var twoBtnGroup = new ToggleGroup();
|
||||
twoBtnGroup.getToggles().addAll(leftBtn1, rightBtn1);
|
||||
|
||||
var centerPill = toggleButton("._center-pill", null, threeButtonGroup, false, CENTER_PILL);
|
||||
centerPill.setMnemonicParsing(true);
|
||||
// ~
|
||||
|
||||
var rightPill = toggleButton("._right-pill", null, threeButtonGroup, false, RIGHT_PILL);
|
||||
rightPill.setMnemonicParsing(true);
|
||||
var leftBtn2 = new ToggleButton("._left-pill");
|
||||
leftBtn2.getStyleClass().add(Styles.LEFT_PILL);
|
||||
leftBtn2.setMnemonicParsing(true);
|
||||
leftBtn2.setSelected(true);
|
||||
|
||||
var threeButtonBox = new HBox(leftPill, centerPill, rightPill);
|
||||
var centerBtn2 = new ToggleButton("._center-pill");
|
||||
centerBtn2.getStyleClass().add(Styles.CENTER_PILL);
|
||||
centerBtn2.setMnemonicParsing(true);
|
||||
|
||||
var twoButtonGroup = new ToggleGroup();
|
||||
var twoButtonBox = new HBox(
|
||||
toggleButton(".left-pill", null, twoButtonGroup, true, LEFT_PILL),
|
||||
toggleButton(".right-pill", null, twoButtonGroup, false, RIGHT_PILL)
|
||||
var rightBtn2 = new ToggleButton("._right-pill");
|
||||
rightBtn2.getStyleClass().add(Styles.RIGHT_PILL);
|
||||
rightBtn2.setMnemonicParsing(true);
|
||||
|
||||
var threeBtnGroup = new ToggleGroup();
|
||||
threeBtnGroup.getToggles().addAll(leftBtn2, centerBtn2, rightBtn2);
|
||||
//snippet_1:end
|
||||
|
||||
var twoBtnBox = new HBox(leftBtn1, rightBtn1);
|
||||
twoBtnBox.setAlignment(Pos.CENTER_LEFT);
|
||||
|
||||
var threeBtnBox = new HBox(leftBtn2, centerBtn2, rightBtn2);
|
||||
threeBtnBox.setAlignment(Pos.CENTER_LEFT);
|
||||
|
||||
var box = new HBox(HGAP_30, singleBtn, twoBtnBox, threeBtnBox);
|
||||
box.setAlignment(Pos.CENTER_LEFT);
|
||||
|
||||
var description = BBCodeParser.createFormattedText("""
|
||||
The [i]ToggleButtons[/i] can also be placed in groups. When in groups, only one button \
|
||||
at a time within that group can be selected. Use [code]Styles.*_PILL[/code] style \
|
||||
classes if you want to toggle group to look like a single "segmented" button."""
|
||||
);
|
||||
|
||||
return new SampleBlock("Basic", new HBox(BLOCK_HGAP, threeButtonBox, twoButtonBox));
|
||||
return new ExampleBox(box, new Snippet(getClass(), 1), description);
|
||||
}
|
||||
|
||||
private SampleBlock wizardSample() {
|
||||
private ExampleBox iconOnlyExample() {
|
||||
//snippet_2:start
|
||||
var leftBtn = new ToggleButton("", new FontIcon(Feather.BOLD));
|
||||
leftBtn.getStyleClass().addAll(
|
||||
Styles.BUTTON_ICON, Styles.LEFT_PILL
|
||||
);
|
||||
leftBtn.setSelected(true);
|
||||
|
||||
var centerBtn = new ToggleButton("", new FontIcon(Feather.ITALIC));
|
||||
centerBtn.getStyleClass().addAll(
|
||||
Styles.BUTTON_ICON, Styles.CENTER_PILL
|
||||
);
|
||||
|
||||
var rightBtn = new ToggleButton("", new FontIcon(Feather.UNDERLINE));
|
||||
rightBtn.getStyleClass().addAll(
|
||||
Styles.BUTTON_ICON, Styles.RIGHT_PILL
|
||||
);
|
||||
//snippet_2:end
|
||||
|
||||
var box = new HBox(leftBtn, centerBtn, rightBtn);
|
||||
box.setAlignment(Pos.CENTER_LEFT);
|
||||
|
||||
var description = BBCodeParser.createFormattedText("""
|
||||
Sometimes it's desirable to create a button group that consists from icon \
|
||||
buttons only. This is also supported."""
|
||||
);
|
||||
|
||||
return new ExampleBox(box, new Snippet(getClass(), 2), description);
|
||||
}
|
||||
|
||||
private ExampleBox segmentedGroupExample() {
|
||||
//snippet_3:start
|
||||
var musicBtn = new ToggleButton(
|
||||
"Music", new FontIcon(Feather.MUSIC)
|
||||
);
|
||||
musicBtn.getStyleClass().add(Styles.LEFT_PILL);
|
||||
musicBtn.setSelected(true);
|
||||
|
||||
var imagesBtn = new ToggleButton(
|
||||
"Images", new FontIcon(Feather.IMAGE)
|
||||
);
|
||||
imagesBtn.getStyleClass().add(Styles.CENTER_PILL);
|
||||
|
||||
var videosBtn = new ToggleButton(
|
||||
"Videos", new FontIcon(Feather.VIDEO)
|
||||
);
|
||||
videosBtn.getStyleClass().add(Styles.RIGHT_PILL);
|
||||
|
||||
var group = new ToggleGroup();
|
||||
|
||||
var prevBtn = new Button("\f", new FontIcon(Feather.CHEVRON_LEFT));
|
||||
prevBtn.getStyleClass().addAll(BUTTON_ICON, LEFT_PILL, "toggle-button");
|
||||
prevBtn.setOnAction(e -> {
|
||||
int selected = group.getToggles().indexOf(group.getSelectedToggle());
|
||||
if (selected > 0) {
|
||||
group.selectToggle(group.getToggles().get(selected - 1));
|
||||
}
|
||||
});
|
||||
|
||||
var nextBtn = new Button("\f", new FontIcon(Feather.CHEVRON_RIGHT));
|
||||
nextBtn.getStyleClass().addAll(BUTTON_ICON, RIGHT_PILL, "toggle-button");
|
||||
nextBtn.setContentDisplay(ContentDisplay.RIGHT);
|
||||
nextBtn.setOnAction(e -> {
|
||||
int selected = group.getToggles().indexOf(group.getSelectedToggle());
|
||||
if (selected < group.getToggles().size() - 1) {
|
||||
group.selectToggle(group.getToggles().get(selected + 1));
|
||||
}
|
||||
});
|
||||
|
||||
var wizard = new HBox(
|
||||
prevBtn,
|
||||
toggleButton("Music", Feather.MUSIC, group, true, CENTER_PILL),
|
||||
toggleButton("Images", Feather.IMAGE, group, false, CENTER_PILL),
|
||||
toggleButton("Videos", Feather.VIDEO, group, false, CENTER_PILL),
|
||||
nextBtn
|
||||
);
|
||||
group.getToggles().addAll(musicBtn, imagesBtn, videosBtn);
|
||||
group.selectedToggleProperty().addListener((obs, old, val) -> {
|
||||
if (val == null) {
|
||||
old.setSelected(true);
|
||||
}
|
||||
});
|
||||
|
||||
return new SampleBlock("Wizard", wizard);
|
||||
var prevBtn = new Button("\f", new FontIcon(Feather.CHEVRON_LEFT));
|
||||
prevBtn.getStyleClass().addAll(
|
||||
Styles.BUTTON_ICON, Styles.LEFT_PILL, "toggle-button"
|
||||
);
|
||||
prevBtn.setOnAction(evt -> {
|
||||
int sel = group.getToggles().indexOf(group.getSelectedToggle());
|
||||
if (sel > 0) {
|
||||
group.selectToggle(group.getToggles().get(sel - 1));
|
||||
}
|
||||
});
|
||||
|
||||
private SampleBlock iconOnlySample() {
|
||||
var icons = new HBox(
|
||||
toggleButton("", Feather.BOLD, null, true, BUTTON_ICON, LEFT_PILL),
|
||||
toggleButton("", Feather.ITALIC, null, false, BUTTON_ICON, CENTER_PILL),
|
||||
toggleButton("", Feather.UNDERLINE, null, false, BUTTON_ICON, RIGHT_PILL)
|
||||
var nextBtn = new Button("\f", new FontIcon(Feather.CHEVRON_RIGHT));
|
||||
nextBtn.getStyleClass().addAll(
|
||||
Styles.BUTTON_ICON, Styles.RIGHT_PILL, "toggle-button"
|
||||
);
|
||||
nextBtn.setContentDisplay(ContentDisplay.RIGHT);
|
||||
nextBtn.setOnAction(evt -> {
|
||||
int sel = group.getToggles().indexOf(group.getSelectedToggle());
|
||||
if (sel < group.getToggles().size() - 1) {
|
||||
group.selectToggle(group.getToggles().get(sel + 1));
|
||||
}
|
||||
});
|
||||
|
||||
var groupBox = new HBox(
|
||||
prevBtn, musicBtn, imagesBtn, videosBtn, nextBtn
|
||||
);
|
||||
groupBox.setAlignment(Pos.CENTER_LEFT);
|
||||
//snippet_3:end
|
||||
|
||||
var description = BBCodeParser.createFormattedText("""
|
||||
This example demonstrates how to create a complex segmented button group."""
|
||||
);
|
||||
|
||||
return new SampleBlock("Icon only", icons);
|
||||
}
|
||||
|
||||
private SampleBlock disabledSample() {
|
||||
var group = new ToggleGroup();
|
||||
var content = new HBox(
|
||||
toggleButton(".left-pill", null, group, false, LEFT_PILL),
|
||||
toggleButton(".center-pill", null, group, false, CENTER_PILL),
|
||||
toggleButton(".right-pill", null, group, true, RIGHT_PILL)
|
||||
);
|
||||
content.getChildren().get(0).setDisable(true);
|
||||
content.getChildren().get(1).setDisable(true);
|
||||
|
||||
return new SampleBlock("Disabled", content);
|
||||
return new ExampleBox(groupBox, new Snippet(getClass(), 3), description);
|
||||
}
|
||||
}
|
||||
|
@ -1,70 +0,0 @@
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
|
||||
package atlantafx.sampler.page.components;
|
||||
|
||||
import atlantafx.base.controls.ToggleSwitch;
|
||||
import atlantafx.base.theme.Styles;
|
||||
import atlantafx.sampler.page.AbstractPage;
|
||||
import atlantafx.sampler.page.Page;
|
||||
import atlantafx.sampler.page.SampleBlock;
|
||||
import javafx.geometry.HorizontalDirection;
|
||||
import javafx.scene.layout.FlowPane;
|
||||
import javafx.scene.layout.VBox;
|
||||
|
||||
public class ToggleSwitchPage extends AbstractPage {
|
||||
|
||||
public static final String NAME = "ToggleSwitch";
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return NAME;
|
||||
}
|
||||
|
||||
public ToggleSwitchPage() {
|
||||
super();
|
||||
setUserContent(new FlowPane(
|
||||
Page.PAGE_HGAP, Page.PAGE_VGAP,
|
||||
basicSample(),
|
||||
stateSample()
|
||||
));
|
||||
}
|
||||
|
||||
private SampleBlock basicSample() {
|
||||
var leftToggle = new ToggleSwitch("Enabled");
|
||||
leftToggle.selectedProperty().addListener(
|
||||
(obs, old, val) -> leftToggle.setText(val ? "Enabled" : "Disabled")
|
||||
);
|
||||
leftToggle.setSelected(true);
|
||||
|
||||
var rightToggle = new ToggleSwitch("Disabled");
|
||||
rightToggle.selectedProperty().addListener(
|
||||
(obs, old, val) -> rightToggle.setText(val ? "Enabled" : "Disabled")
|
||||
);
|
||||
rightToggle.setLabelPosition(HorizontalDirection.RIGHT);
|
||||
rightToggle.setSelected(false);
|
||||
|
||||
return new SampleBlock("Basic", new VBox(SampleBlock.BLOCK_VGAP, leftToggle, rightToggle));
|
||||
}
|
||||
|
||||
private SampleBlock stateSample() {
|
||||
var successToggle = new ToggleSwitch("Enabled");
|
||||
successToggle.selectedProperty().addListener((obs, old, val) -> {
|
||||
successToggle.setText(val ? "Enabled" : "Disabled");
|
||||
successToggle.pseudoClassStateChanged(Styles.STATE_SUCCESS, val);
|
||||
}
|
||||
);
|
||||
successToggle.setSelected(true);
|
||||
successToggle.pseudoClassStateChanged(Styles.STATE_SUCCESS, true);
|
||||
|
||||
var dangerToggle = new ToggleSwitch("Disabled");
|
||||
dangerToggle.selectedProperty().addListener((obs, old, val) -> {
|
||||
dangerToggle.setText(val ? "Enabled" : "Disabled");
|
||||
dangerToggle.pseudoClassStateChanged(Styles.STATE_DANGER, val);
|
||||
}
|
||||
);
|
||||
dangerToggle.setLabelPosition(HorizontalDirection.RIGHT);
|
||||
dangerToggle.setSelected(false);
|
||||
|
||||
return new SampleBlock("State", new VBox(SampleBlock.BLOCK_VGAP, successToggle, dangerToggle));
|
||||
}
|
||||
}
|
@ -2,46 +2,44 @@
|
||||
|
||||
package atlantafx.sampler.page.components;
|
||||
|
||||
import static atlantafx.base.theme.Styles.ACCENT;
|
||||
import static atlantafx.base.theme.Styles.BOTTOM;
|
||||
import static atlantafx.base.theme.Styles.BUTTON_ICON;
|
||||
import static atlantafx.base.theme.Styles.CENTER_PILL;
|
||||
import static atlantafx.base.theme.Styles.FLAT;
|
||||
import static atlantafx.base.theme.Styles.LEFT;
|
||||
import static atlantafx.base.theme.Styles.LEFT_PILL;
|
||||
import static atlantafx.base.theme.Styles.RIGHT;
|
||||
import static atlantafx.base.theme.Styles.RIGHT_PILL;
|
||||
import static atlantafx.base.theme.Styles.TOP;
|
||||
import static atlantafx.sampler.page.SampleBlock.BLOCK_VGAP;
|
||||
import static atlantafx.sampler.util.Controls.button;
|
||||
import static atlantafx.sampler.util.Controls.iconButton;
|
||||
import static atlantafx.sampler.util.Controls.toggleButton;
|
||||
import static javafx.geometry.Orientation.HORIZONTAL;
|
||||
import static javafx.geometry.Orientation.VERTICAL;
|
||||
|
||||
import atlantafx.base.controls.CaptionMenuItem;
|
||||
import atlantafx.base.controls.CustomTextField;
|
||||
import atlantafx.base.controls.Spacer;
|
||||
import atlantafx.base.controls.ToggleSwitch;
|
||||
import atlantafx.base.theme.Styles;
|
||||
import atlantafx.sampler.fake.SampleMenuBar;
|
||||
import atlantafx.sampler.page.AbstractPage;
|
||||
import atlantafx.sampler.page.SampleBlock;
|
||||
import atlantafx.base.util.BBCodeParser;
|
||||
import atlantafx.sampler.page.ExampleBox;
|
||||
import atlantafx.sampler.page.OutlinePage;
|
||||
import atlantafx.sampler.page.Snippet;
|
||||
import java.util.ArrayList;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.IntStream;
|
||||
import javafx.application.Platform;
|
||||
import javafx.collections.FXCollections;
|
||||
import javafx.event.ActionEvent;
|
||||
import javafx.event.EventHandler;
|
||||
import javafx.geometry.Orientation;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.geometry.Side;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.control.Button;
|
||||
import javafx.scene.control.CheckMenuItem;
|
||||
import javafx.scene.control.ComboBox;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.control.Menu;
|
||||
import javafx.scene.control.MenuBar;
|
||||
import javafx.scene.control.MenuButton;
|
||||
import javafx.scene.control.MenuItem;
|
||||
import javafx.scene.control.RadioMenuItem;
|
||||
import javafx.scene.control.Separator;
|
||||
import javafx.scene.control.SeparatorMenuItem;
|
||||
import javafx.scene.control.TitledPane;
|
||||
import javafx.scene.control.ToggleButton;
|
||||
import javafx.scene.control.ToggleGroup;
|
||||
import javafx.scene.control.ToolBar;
|
||||
import javafx.scene.input.KeyCode;
|
||||
import javafx.scene.input.KeyCodeCombination;
|
||||
import javafx.scene.input.KeyCombination;
|
||||
import javafx.scene.layout.BorderPane;
|
||||
import javafx.scene.layout.GridPane;
|
||||
import javafx.scene.layout.HBox;
|
||||
@ -50,10 +48,14 @@ import javafx.scene.layout.Region;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import javafx.scene.layout.VBox;
|
||||
import javafx.scene.text.Font;
|
||||
import net.datafaker.Faker;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.kordamp.ikonli.Ikon;
|
||||
import org.kordamp.ikonli.feather.Feather;
|
||||
import org.kordamp.ikonli.javafx.FontIcon;
|
||||
import org.kordamp.ikonli.material2.Material2OutlinedAL;
|
||||
|
||||
public class ToolBarPage extends AbstractPage {
|
||||
public class ToolBarPage extends OutlinePage {
|
||||
|
||||
public static final String NAME = "ToolBar";
|
||||
|
||||
@ -62,60 +64,152 @@ public class ToolBarPage extends AbstractPage {
|
||||
return NAME;
|
||||
}
|
||||
|
||||
private Side toolbarPos = Side.TOP;
|
||||
|
||||
public ToolBarPage() {
|
||||
super();
|
||||
createView();
|
||||
|
||||
addFormattedText("""
|
||||
A [i]ToolBar[/i] is a control which displays items horizontally or vertically."""
|
||||
);
|
||||
addSection("Usage", usageExample());
|
||||
addSection("Playground", playground());
|
||||
}
|
||||
|
||||
private void createView() {
|
||||
var toolbar = new ToolBar(createButtons(HORIZONTAL));
|
||||
private ExampleBox usageExample() {
|
||||
//snippet_1:start
|
||||
var toolbar1 = new ToolBar(
|
||||
new Button("New", new FontIcon(Feather.PLUS)),
|
||||
new Button("Open", new FontIcon(Feather.FILE)),
|
||||
new Button("Save", new FontIcon(Feather.SAVE)),
|
||||
new Separator(Orientation.VERTICAL),
|
||||
new Button("Clean", new FontIcon(Feather.ROTATE_CCW)),
|
||||
new Button("Compile", new FontIcon(Feather.LAYERS)),
|
||||
new Button("Run", new FontIcon(Feather.PLAY))
|
||||
);
|
||||
|
||||
// ~
|
||||
var fontFamilyCmb = new ComboBox<>(
|
||||
FXCollections.observableArrayList(Font.getFamilies())
|
||||
);
|
||||
fontFamilyCmb.setPrefWidth(150);
|
||||
fontFamilyCmb.getSelectionModel().selectFirst();
|
||||
|
||||
var fontSizeCmb = new ComboBox<>(
|
||||
IntStream.range(6, 32).mapToObj(String::valueOf).collect(
|
||||
Collectors.toCollection(FXCollections::observableArrayList)
|
||||
));
|
||||
fontSizeCmb.getSelectionModel().select(6);
|
||||
|
||||
var toolbar2 = new ToolBar(
|
||||
fontFamilyCmb,
|
||||
fontSizeCmb,
|
||||
new Separator(Orientation.VERTICAL),
|
||||
toggleIconButton(Feather.BOLD, true),
|
||||
toggleIconButton(Feather.ITALIC),
|
||||
toggleIconButton(Feather.UNDERLINE),
|
||||
new Separator(Orientation.VERTICAL),
|
||||
toggleIconButton(Feather.ALIGN_LEFT),
|
||||
toggleIconButton(Feather.ALIGN_CENTER),
|
||||
toggleIconButton(Feather.ALIGN_RIGHT),
|
||||
new Separator(Orientation.VERTICAL),
|
||||
iconButton(Feather.IMAGE)
|
||||
);
|
||||
|
||||
// ~
|
||||
var textField = new CustomTextField("https://example.com");
|
||||
textField.setPromptText("Search Doodle of type an URL");
|
||||
textField.setLeft(new FontIcon(Feather.LOCK));
|
||||
textField.setRight(new FontIcon(Feather.STAR));
|
||||
HBox.setHgrow(textField, Priority.ALWAYS);
|
||||
|
||||
var dropdown = new MenuButton("", new FontIcon(Feather.MENU));
|
||||
dropdown.getItems().setAll(
|
||||
new MenuItem("Action 1"),
|
||||
new MenuItem("Action 2"),
|
||||
new MenuItem("Action 3")
|
||||
);
|
||||
|
||||
var toolbar3 = new ToolBar(
|
||||
iconButton(Feather.CHEVRON_LEFT),
|
||||
iconButton(Feather.CHEVRON_RIGHT),
|
||||
new Separator(Orientation.VERTICAL),
|
||||
iconButton(Feather.REFRESH_CW),
|
||||
new Spacer(10),
|
||||
textField,
|
||||
new Spacer(10),
|
||||
iconButton(Feather.BOOKMARK),
|
||||
iconButton(Feather.USER),
|
||||
dropdown
|
||||
);
|
||||
//snippet_1:end
|
||||
|
||||
var box = new VBox(VGAP_20, toolbar1, toolbar2, toolbar3);
|
||||
|
||||
var description = BBCodeParser.createFormattedText("""
|
||||
The most common items to place within a toolbar are [i]Button[/i], [i]ToggleButtons[/i] \
|
||||
and [i]Separator[/i], but you are not restricted to just these, and can insert any node \
|
||||
into it."""
|
||||
);
|
||||
|
||||
return new ExampleBox(box, new Snippet(getClass(), 1), description);
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
// Playground //
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
|
||||
private Side toolbarPos = Side.TOP;
|
||||
|
||||
private VBox playground() {
|
||||
var toolbar = new ToolBar(createButtons(Orientation.HORIZONTAL));
|
||||
var toolbarLayer = new BorderPane();
|
||||
toolbarLayer.setTop(new TopBar(toolbar));
|
||||
|
||||
var controller = createController(toolbarLayer, toolbar);
|
||||
controller.setPrefSize(500, 300);
|
||||
var controllerLayer = new BorderPane();
|
||||
controllerLayer.setCenter(controller);
|
||||
|
||||
var controllerLayer = new BorderPane(controller);
|
||||
controllerLayer.setMaxSize(500, 300);
|
||||
|
||||
var root = new StackPane();
|
||||
root.getStyleClass().add(Styles.BORDERED);
|
||||
root.getChildren().addAll(toolbarLayer, controllerLayer);
|
||||
VBox.setVgrow(root, Priority.ALWAYS);
|
||||
var description = BBCodeParser.createFormattedText("""
|
||||
The playground demonstrates the most important [i]Toolbar[/i] features \
|
||||
and also serves as an object for monkey testing."""
|
||||
);
|
||||
|
||||
setUserContent(new SampleBlock("Playground", root));
|
||||
var stack = new StackPane(toolbarLayer, controllerLayer);
|
||||
stack.getStyleClass().add(Styles.BORDERED);
|
||||
stack.setMinSize(600, 500);
|
||||
|
||||
return new VBox(VGAP_10, description, stack);
|
||||
}
|
||||
|
||||
private TitledPane createController(BorderPane borderPane, ToolBar toolbar) {
|
||||
// == BUTTONS ==
|
||||
|
||||
var toTopBtn = new Button("", new FontIcon(Feather.ARROW_UP));
|
||||
toTopBtn.getStyleClass().addAll(BUTTON_ICON);
|
||||
toTopBtn.getStyleClass().addAll(Styles.BUTTON_ICON);
|
||||
toTopBtn.setOnAction(e -> rotateToolbar(borderPane, toolbar, Side.TOP));
|
||||
|
||||
var toRightBtn = new Button("", new FontIcon(Feather.ARROW_RIGHT));
|
||||
toRightBtn.getStyleClass().addAll(BUTTON_ICON);
|
||||
toRightBtn.getStyleClass().addAll(Styles.BUTTON_ICON);
|
||||
toRightBtn.setOnAction(e -> rotateToolbar(borderPane, toolbar, Side.RIGHT));
|
||||
|
||||
var toBottomBtn = new Button("", new FontIcon(Feather.ARROW_DOWN));
|
||||
toBottomBtn.getStyleClass().addAll(BUTTON_ICON);
|
||||
toBottomBtn.getStyleClass().addAll(Styles.BUTTON_ICON);
|
||||
toBottomBtn.setOnAction(e -> rotateToolbar(borderPane, toolbar, Side.BOTTOM));
|
||||
|
||||
var toLeftBtn = new Button("", new FontIcon(Feather.ARROW_LEFT));
|
||||
toLeftBtn.getStyleClass().addAll(BUTTON_ICON);
|
||||
toLeftBtn.getStyleClass().addAll(Styles.BUTTON_ICON);
|
||||
toLeftBtn.setOnAction(e -> rotateToolbar(borderPane, toolbar, Side.LEFT));
|
||||
|
||||
var appendBtn = new Button("", new FontIcon(Feather.PLUS));
|
||||
appendBtn.getStyleClass().addAll(BUTTON_ICON, ACCENT);
|
||||
appendBtn.getStyleClass().addAll(Styles.BUTTON_ICON, Styles.ACCENT);
|
||||
appendBtn.setOnAction(e -> {
|
||||
if (toolbar.getOrientation() == HORIZONTAL) {
|
||||
if (toolbar.getOrientation() == Orientation.HORIZONTAL) {
|
||||
var textBtn = new Button(FAKER.animal().name(), new FontIcon(randomIcon()));
|
||||
toolbar.getItems().add(textBtn);
|
||||
} else {
|
||||
var iconBtn = new Button("", new FontIcon(randomIcon()));
|
||||
iconBtn.getStyleClass().addAll(BUTTON_ICON);
|
||||
iconBtn.getStyleClass().addAll(Styles.BUTTON_ICON);
|
||||
toolbar.getItems().add(iconBtn);
|
||||
}
|
||||
});
|
||||
@ -124,6 +218,8 @@ public class ToolBarPage extends AbstractPage {
|
||||
buttonsPane.setMinSize(120, 120);
|
||||
buttonsPane.setMaxSize(120, 120);
|
||||
|
||||
buttonsPane.setCenter(appendBtn);
|
||||
|
||||
buttonsPane.setTop(toTopBtn);
|
||||
BorderPane.setAlignment(toTopBtn, Pos.CENTER);
|
||||
|
||||
@ -136,14 +232,12 @@ public class ToolBarPage extends AbstractPage {
|
||||
buttonsPane.setLeft(toLeftBtn);
|
||||
BorderPane.setAlignment(toLeftBtn, Pos.CENTER);
|
||||
|
||||
buttonsPane.setCenter(appendBtn);
|
||||
|
||||
// == TOGGLES ==
|
||||
|
||||
var menuBarToggle = new ToggleSwitch();
|
||||
menuBarToggle.selectedProperty().addListener((obs, old, value) -> {
|
||||
menuBarToggle.selectedProperty().addListener((obs, old, val) -> {
|
||||
TopBar topBar = (TopBar) borderPane.getTop();
|
||||
if (value) {
|
||||
if (val) {
|
||||
topBar.showOrCreateMenuBar();
|
||||
} else {
|
||||
topBar.hideMenuBar();
|
||||
@ -160,26 +254,22 @@ public class ToolBarPage extends AbstractPage {
|
||||
var togglesGrid = new GridPane();
|
||||
togglesGrid.setHgap(10);
|
||||
togglesGrid.setVgap(10);
|
||||
|
||||
togglesGrid.add(createLabel("Show menu bar"), 0, 0);
|
||||
togglesGrid.add(menuBarToggle, 1, 0);
|
||||
|
||||
togglesGrid.add(createLabel("Disable"), 0, 1);
|
||||
togglesGrid.add(disableToggle, 1, 1);
|
||||
togglesGrid.addRow(0, createLabel("Show menu bar"), menuBarToggle);
|
||||
togglesGrid.addRow(1, createLabel("Disable"), disableToggle);
|
||||
|
||||
// == LAYOUT ==
|
||||
|
||||
var controls = new HBox(40, new Spacer(), buttonsPane, togglesGrid, new Spacer());
|
||||
var controls = new HBox(40, buttonsPane, togglesGrid);
|
||||
controls.setAlignment(Pos.CENTER);
|
||||
controls.setFillHeight(true);
|
||||
|
||||
var content = new VBox(BLOCK_VGAP);
|
||||
content.getChildren().setAll(controls);
|
||||
content.setAlignment(Pos.CENTER);
|
||||
var wrapper = new VBox(VGAP_20, controls);
|
||||
wrapper.setAlignment(Pos.CENTER);
|
||||
|
||||
var root = new TitledPane("Controller", content);
|
||||
root.setCollapsible(false);
|
||||
var controller = new TitledPane("Controller", wrapper);
|
||||
controller.setCollapsible(false);
|
||||
|
||||
return root;
|
||||
return controller;
|
||||
}
|
||||
|
||||
private void rotateToolbar(BorderPane borderPane, ToolBar toolbar, Side pos) {
|
||||
@ -204,27 +294,35 @@ public class ToolBarPage extends AbstractPage {
|
||||
Platform.runLater(() -> {
|
||||
switch (pos) {
|
||||
case TOP -> {
|
||||
toolbar.setOrientation(HORIZONTAL);
|
||||
Styles.addStyleClass(toolbar, TOP, RIGHT, BOTTOM, LEFT);
|
||||
toolbar.getItems().setAll(createButtons(HORIZONTAL));
|
||||
toolbar.setOrientation(Orientation.HORIZONTAL);
|
||||
Styles.addStyleClass(
|
||||
toolbar, Styles.TOP, Styles.RIGHT, Styles.BOTTOM, Styles.LEFT
|
||||
);
|
||||
toolbar.getItems().setAll(createButtons(Orientation.HORIZONTAL));
|
||||
topBar.setToolBar(toolbar);
|
||||
}
|
||||
case RIGHT -> {
|
||||
toolbar.setOrientation(VERTICAL);
|
||||
Styles.addStyleClass(toolbar, RIGHT, TOP, BOTTOM, LEFT);
|
||||
toolbar.getItems().setAll(createButtons(VERTICAL));
|
||||
toolbar.setOrientation(Orientation.VERTICAL);
|
||||
Styles.addStyleClass(
|
||||
toolbar, Styles.RIGHT, Styles.TOP, Styles.BOTTOM, Styles.LEFT
|
||||
);
|
||||
toolbar.getItems().setAll(createButtons(Orientation.VERTICAL));
|
||||
borderPane.setRight(toolbar);
|
||||
}
|
||||
case BOTTOM -> {
|
||||
toolbar.setOrientation(HORIZONTAL);
|
||||
Styles.addStyleClass(toolbar, BOTTOM, TOP, RIGHT, LEFT);
|
||||
toolbar.getItems().setAll(createButtons(HORIZONTAL));
|
||||
toolbar.setOrientation(Orientation.HORIZONTAL);
|
||||
Styles.addStyleClass(
|
||||
toolbar, Styles.BOTTOM, Styles.TOP, Styles.RIGHT, Styles.LEFT
|
||||
);
|
||||
toolbar.getItems().setAll(createButtons(Orientation.HORIZONTAL));
|
||||
borderPane.setBottom(toolbar);
|
||||
}
|
||||
case LEFT -> {
|
||||
toolbar.setOrientation(VERTICAL);
|
||||
Styles.addStyleClass(toolbar, LEFT, RIGHT, TOP, BOTTOM);
|
||||
toolbar.getItems().setAll(createButtons(VERTICAL));
|
||||
toolbar.setOrientation(Orientation.VERTICAL);
|
||||
Styles.addStyleClass(
|
||||
toolbar, Styles.LEFT, Styles.RIGHT, Styles.TOP, Styles.BOTTOM
|
||||
);
|
||||
toolbar.getItems().setAll(createButtons(Orientation.VERTICAL));
|
||||
borderPane.setLeft(toolbar);
|
||||
}
|
||||
}
|
||||
@ -233,39 +331,25 @@ public class ToolBarPage extends AbstractPage {
|
||||
|
||||
public Node[] createButtons(Orientation orientation) {
|
||||
var result = new ArrayList<Node>();
|
||||
result.add(iconButton(Feather.FILE, false));
|
||||
result.add(iconButton(Feather.FOLDER, false));
|
||||
result.add(iconButton(Feather.SAVE, false));
|
||||
result.add(iconButton(Feather.FILE));
|
||||
result.add(iconButton(Feather.FOLDER));
|
||||
result.add(iconButton(Feather.SAVE));
|
||||
result.add(new Separator());
|
||||
|
||||
if (orientation == HORIZONTAL) {
|
||||
result.add(button("Undo", null, false));
|
||||
result.add(button("Redo", null, true));
|
||||
|
||||
if (orientation == Orientation.HORIZONTAL) {
|
||||
result.add(new Button("Undo"));
|
||||
result.add(new Button("Redo"));
|
||||
result.add(new Separator());
|
||||
|
||||
result.add(toggleButton("", Feather.BOLD, null, true, BUTTON_ICON, LEFT_PILL));
|
||||
result.add(toggleButton("", Feather.ITALIC, null, false, BUTTON_ICON, CENTER_PILL));
|
||||
result.add(toggleButton("", Feather.UNDERLINE, null, false, BUTTON_ICON, RIGHT_PILL));
|
||||
|
||||
result.add(new Spacer(5));
|
||||
|
||||
var fontCombo = new ComboBox<>(FXCollections.observableArrayList(Font.getFamilies()));
|
||||
fontCombo.setPrefWidth(150);
|
||||
fontCombo.getSelectionModel().selectFirst();
|
||||
result.add(fontCombo);
|
||||
|
||||
var settingsMenu = new MenuButton("Settings", new FontIcon(Feather.SETTINGS), createItems(5));
|
||||
settingsMenu.getStyleClass().add(FLAT);
|
||||
result.add(new Spacer());
|
||||
result.add(settingsMenu);
|
||||
result.add(toggleIconButton(Feather.BOLD, true, Styles.LEFT_PILL));
|
||||
result.add(toggleIconButton(Feather.ITALIC, Styles.CENTER_PILL));
|
||||
result.add(toggleIconButton(Feather.UNDERLINE, Styles.RIGHT_PILL));
|
||||
}
|
||||
|
||||
if (orientation == VERTICAL) {
|
||||
result.add(iconButton(Feather.CORNER_DOWN_LEFT, false));
|
||||
result.add(iconButton(Feather.CORNER_DOWN_RIGHT, true));
|
||||
if (orientation == Orientation.VERTICAL) {
|
||||
result.add(iconButton(Feather.CORNER_DOWN_LEFT));
|
||||
result.add(iconButton(Feather.CORNER_DOWN_RIGHT));
|
||||
result.add(new Spacer(orientation));
|
||||
result.add(iconButton(Feather.SETTINGS, false));
|
||||
result.add(iconButton(Feather.SETTINGS));
|
||||
}
|
||||
|
||||
return result.toArray(Node[]::new);
|
||||
@ -278,9 +362,42 @@ public class ToolBarPage extends AbstractPage {
|
||||
return label;
|
||||
}
|
||||
|
||||
public static MenuItem[] createItems(int count) {
|
||||
return IntStream.range(0, count).mapToObj(i -> new MenuItem(FAKER.babylon5().character()))
|
||||
.toArray(MenuItem[]::new);
|
||||
public static Button iconButton(Ikon icon) {
|
||||
var btn = new Button("");
|
||||
if (icon != null) {
|
||||
btn.setGraphic(new FontIcon(icon));
|
||||
}
|
||||
btn.getStyleClass().addAll(Styles.BUTTON_ICON);
|
||||
|
||||
return btn;
|
||||
}
|
||||
|
||||
public ToggleButton toggleIconButton(@Nullable Ikon icon,
|
||||
String... styleClasses) {
|
||||
return toggleIconButton(icon, null, false, styleClasses);
|
||||
}
|
||||
|
||||
public ToggleButton toggleIconButton(@Nullable Ikon icon,
|
||||
boolean selected,
|
||||
String... styleClasses) {
|
||||
return toggleIconButton(icon, null, selected, styleClasses);
|
||||
}
|
||||
|
||||
public ToggleButton toggleIconButton(@Nullable Ikon icon,
|
||||
@Nullable ToggleGroup group,
|
||||
boolean selected,
|
||||
String... styleClasses) {
|
||||
var btn = new ToggleButton("");
|
||||
if (icon != null) {
|
||||
btn.setGraphic(new FontIcon(icon));
|
||||
}
|
||||
if (group != null) {
|
||||
btn.setToggleGroup(group);
|
||||
}
|
||||
btn.getStyleClass().addAll(styleClasses);
|
||||
btn.setSelected(selected);
|
||||
|
||||
return btn;
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
@ -318,4 +435,188 @@ public class ToolBarPage extends AbstractPage {
|
||||
getChildren().set(1, DUMMY_TOOLBAR);
|
||||
}
|
||||
}
|
||||
|
||||
public static class SampleMenuBar extends MenuBar {
|
||||
|
||||
private static final EventHandler<ActionEvent> PRINT_SOURCE = System.out::println;
|
||||
|
||||
public SampleMenuBar(Faker faker) {
|
||||
getMenus().addAll(
|
||||
fileMenu(faker),
|
||||
editMenu(),
|
||||
viewMenu(),
|
||||
toolsMenu(),
|
||||
aboutMenu()
|
||||
);
|
||||
}
|
||||
|
||||
private Menu fileMenu(Faker faker) {
|
||||
var fileMenu = new Menu("_File");
|
||||
fileMenu.setMnemonicParsing(true);
|
||||
fileMenu.setOnAction(PRINT_SOURCE);
|
||||
|
||||
var newMenu = menuItem(
|
||||
"_New", null,
|
||||
new KeyCodeCombination(KeyCode.N, KeyCombination.CONTROL_DOWN)
|
||||
);
|
||||
newMenu.setMnemonicParsing(true);
|
||||
newMenu.setOnAction(PRINT_SOURCE);
|
||||
|
||||
var openRecentMenu = new Menu("Open _Recent");
|
||||
openRecentMenu.setMnemonicParsing(true);
|
||||
openRecentMenu.setOnAction(PRINT_SOURCE);
|
||||
openRecentMenu.getItems().addAll(
|
||||
IntStream.range(0, 10)
|
||||
.mapToObj(x -> new MenuItem(faker.file().fileName()))
|
||||
.toList()
|
||||
);
|
||||
|
||||
fileMenu.getItems().addAll(
|
||||
newMenu,
|
||||
new SeparatorMenuItem(),
|
||||
menuItem(
|
||||
"Open", Feather.FOLDER,
|
||||
new KeyCodeCombination(KeyCode.O, KeyCombination.CONTROL_DOWN)
|
||||
),
|
||||
openRecentMenu,
|
||||
new SeparatorMenuItem(),
|
||||
menuItem(
|
||||
"Save", Feather.SAVE,
|
||||
new KeyCodeCombination(KeyCode.S, KeyCombination.CONTROL_DOWN)
|
||||
),
|
||||
new MenuItem("Save As"),
|
||||
new SeparatorMenuItem(),
|
||||
new MenuItem("Exit")
|
||||
);
|
||||
return fileMenu;
|
||||
}
|
||||
|
||||
private Menu editMenu() {
|
||||
var editMenu = new Menu("_Edit");
|
||||
editMenu.setMnemonicParsing(true);
|
||||
editMenu.setOnAction(PRINT_SOURCE);
|
||||
|
||||
var copyItem = menuItem(
|
||||
"Copy", Feather.COPY,
|
||||
new KeyCodeCombination(KeyCode.C, KeyCombination.CONTROL_DOWN)
|
||||
);
|
||||
copyItem.setDisable(true);
|
||||
|
||||
editMenu.getItems().addAll(
|
||||
menuItem(
|
||||
"Undo", Feather.CORNER_DOWN_LEFT,
|
||||
new KeyCodeCombination(KeyCode.Z, KeyCombination.CONTROL_DOWN)
|
||||
),
|
||||
menuItem("Redo", Feather.CORNER_DOWN_RIGHT,
|
||||
new KeyCodeCombination(KeyCode.Y, KeyCombination.CONTROL_DOWN)
|
||||
),
|
||||
new SeparatorMenuItem(),
|
||||
menuItem(
|
||||
"Cut", Feather.SCISSORS,
|
||||
new KeyCodeCombination(KeyCode.X, KeyCombination.CONTROL_DOWN)
|
||||
),
|
||||
copyItem,
|
||||
menuItem(
|
||||
"Paste", Feather.CORNER_DOWN_LEFT,
|
||||
new KeyCodeCombination(KeyCode.V, KeyCombination.CONTROL_DOWN)
|
||||
)
|
||||
);
|
||||
return editMenu;
|
||||
}
|
||||
|
||||
private Menu viewMenu() {
|
||||
var viewMenu = new Menu("_View");
|
||||
viewMenu.setMnemonicParsing(true);
|
||||
viewMenu.setOnAction(PRINT_SOURCE);
|
||||
|
||||
var showToolbarItem = new CheckMenuItem(
|
||||
"Show Toolbar", new FontIcon(Feather.TOOL)
|
||||
);
|
||||
showToolbarItem.setSelected(true);
|
||||
showToolbarItem.setAccelerator(
|
||||
new KeyCodeCombination(KeyCode.T, KeyCombination.CONTROL_DOWN)
|
||||
);
|
||||
|
||||
var showGridItem = new CheckMenuItem(
|
||||
"Show Grid", new FontIcon(Feather.GRID)
|
||||
);
|
||||
|
||||
var captionItem = new CaptionMenuItem("Layout");
|
||||
|
||||
var viewToggleGroup = new ToggleGroup();
|
||||
|
||||
var toggleItem1 = new RadioMenuItem(
|
||||
"Single", new FontIcon(Material2OutlinedAL.LOOKS_ONE)
|
||||
);
|
||||
toggleItem1.setSelected(true);
|
||||
toggleItem1.setToggleGroup(viewToggleGroup);
|
||||
|
||||
var toggleItem2 = new RadioMenuItem(
|
||||
"Two Columns", new FontIcon(Material2OutlinedAL.LOOKS_TWO)
|
||||
);
|
||||
toggleItem2.setToggleGroup(viewToggleGroup);
|
||||
|
||||
var toggleItem3 = new RadioMenuItem(
|
||||
"Three Columns", new FontIcon(Material2OutlinedAL.LOOKS_3)
|
||||
);
|
||||
toggleItem3.setToggleGroup(viewToggleGroup);
|
||||
|
||||
viewMenu.getItems().addAll(
|
||||
showToolbarItem,
|
||||
showGridItem,
|
||||
new SeparatorMenuItem(),
|
||||
captionItem,
|
||||
toggleItem1,
|
||||
toggleItem2,
|
||||
toggleItem3
|
||||
);
|
||||
return viewMenu;
|
||||
}
|
||||
|
||||
private Menu toolsMenu() {
|
||||
var toolsMenu = new Menu("_Tools");
|
||||
toolsMenu.setMnemonicParsing(true);
|
||||
toolsMenu.setOnAction(PRINT_SOURCE);
|
||||
toolsMenu.setDisable(true);
|
||||
return toolsMenu;
|
||||
}
|
||||
|
||||
private Menu aboutMenu() {
|
||||
var aboutMenu = new Menu("_About", new FontIcon(Feather.HELP_CIRCLE));
|
||||
aboutMenu.setMnemonicParsing(true);
|
||||
aboutMenu.setOnAction(PRINT_SOURCE);
|
||||
|
||||
var deeplyNestedMenu = new Menu("Very...", null,
|
||||
new Menu("Very...", null,
|
||||
new Menu("Deeply", null,
|
||||
new Menu("Nested", null,
|
||||
new MenuItem("Menu")
|
||||
))));
|
||||
// NOTE: this won't be displayed because right container is reserved for submenu indication
|
||||
deeplyNestedMenu.setAccelerator(new KeyCodeCombination(
|
||||
KeyCode.DIGIT1, KeyCombination.SHIFT_DOWN, KeyCombination.CONTROL_DOWN)
|
||||
);
|
||||
|
||||
aboutMenu.getItems().addAll(
|
||||
new MenuItem("Help"),
|
||||
new MenuItem("About"),
|
||||
new SeparatorMenuItem(),
|
||||
deeplyNestedMenu
|
||||
);
|
||||
return aboutMenu;
|
||||
}
|
||||
|
||||
public MenuItem menuItem(String text, Ikon graphic, KeyCombination accelerator) {
|
||||
var item = new MenuItem(text);
|
||||
|
||||
if (graphic != null) {
|
||||
item.setGraphic(new FontIcon(graphic));
|
||||
}
|
||||
if (accelerator != null) {
|
||||
item.setAccelerator(accelerator);
|
||||
}
|
||||
|
||||
return item;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2,24 +2,19 @@
|
||||
|
||||
package atlantafx.sampler.page.components;
|
||||
|
||||
import static atlantafx.sampler.page.SampleBlock.BLOCK_HGAP;
|
||||
import static atlantafx.sampler.page.SampleBlock.BLOCK_VGAP;
|
||||
import static javafx.geometry.Orientation.VERTICAL;
|
||||
|
||||
import atlantafx.sampler.page.AbstractPage;
|
||||
import atlantafx.sampler.page.Page;
|
||||
import atlantafx.sampler.page.SampleBlock;
|
||||
import atlantafx.base.util.BBCodeParser;
|
||||
import atlantafx.sampler.page.ExampleBox;
|
||||
import atlantafx.sampler.page.OutlinePage;
|
||||
import atlantafx.sampler.page.Snippet;
|
||||
import javafx.geometry.Insets;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.control.Separator;
|
||||
import javafx.scene.control.Tooltip;
|
||||
import javafx.scene.layout.FlowPane;
|
||||
import javafx.scene.layout.VBox;
|
||||
import javafx.scene.layout.HBox;
|
||||
import javafx.stage.PopupWindow.AnchorLocation;
|
||||
import javafx.util.Duration;
|
||||
|
||||
public class TooltipPage extends AbstractPage {
|
||||
public class TooltipPage extends OutlinePage {
|
||||
|
||||
public static final String NAME = "Tooltip";
|
||||
|
||||
@ -30,88 +25,87 @@ public class TooltipPage extends AbstractPage {
|
||||
|
||||
public TooltipPage() {
|
||||
super();
|
||||
setUserContent(new VBox(Page.PAGE_VGAP,
|
||||
expandingHBox(
|
||||
basicSample(),
|
||||
textWrapSample(),
|
||||
indefiniteSample()
|
||||
),
|
||||
positionSample()
|
||||
));
|
||||
|
||||
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]""");
|
||||
addSection("Usage", usageExample());
|
||||
addSection("Position", positionExample());
|
||||
}
|
||||
|
||||
private SampleBlock basicSample() {
|
||||
var tooltip = new Tooltip(FAKER.harryPotter().spell());
|
||||
tooltip.setHideDelay(Duration.seconds(3));
|
||||
private ExampleBox usageExample() {
|
||||
//snippet_1:start
|
||||
var basicTtp = new Tooltip(FAKER.harryPotter().spell());
|
||||
basicTtp.setHideDelay(Duration.seconds(3));
|
||||
|
||||
var label = createLabel("Hover me");
|
||||
label.setTooltip(tooltip);
|
||||
var basicLbl = createLabel("Basic");
|
||||
basicLbl.setTooltip(basicTtp);
|
||||
|
||||
return new SampleBlock("Basic", label);
|
||||
}
|
||||
var longTtp = new Tooltip(FAKER.lorem().paragraph(5));
|
||||
longTtp.setHideDelay(Duration.seconds(3));
|
||||
longTtp.setPrefWidth(200);
|
||||
longTtp.setWrapText(true);
|
||||
|
||||
private SampleBlock textWrapSample() {
|
||||
var tooltip = new Tooltip(FAKER.lorem().paragraph(5));
|
||||
tooltip.setHideDelay(Duration.seconds(3));
|
||||
tooltip.setPrefWidth(200);
|
||||
tooltip.setWrapText(true);
|
||||
var longLbl = createLabel("Long Text");
|
||||
longLbl.setTooltip(longTtp);
|
||||
//snippet_1:end
|
||||
|
||||
var label = createLabel("Hover me");
|
||||
label.setTooltip(tooltip);
|
||||
var box = new HBox(HGAP_20, basicLbl, longLbl);
|
||||
|
||||
return new SampleBlock("Text Wrap", label);
|
||||
}
|
||||
|
||||
private SampleBlock indefiniteSample() {
|
||||
var tooltip = new Tooltip(FAKER.harryPotter().spell());
|
||||
tooltip.setHideDelay(Duration.INDEFINITE);
|
||||
|
||||
var label = createLabel("Hover me");
|
||||
label.setTooltip(tooltip);
|
||||
|
||||
return new SampleBlock("Indefinite", label);
|
||||
}
|
||||
|
||||
private SampleBlock positionSample() {
|
||||
var topLeftLabel = createLabel("Top Left");
|
||||
topLeftLabel.setTooltip(createTooltip("Top Left", AnchorLocation.WINDOW_BOTTOM_RIGHT));
|
||||
|
||||
var topRightLabel = createLabel("Top Right");
|
||||
topRightLabel.setTooltip(createTooltip("Top Right", AnchorLocation.WINDOW_BOTTOM_LEFT));
|
||||
|
||||
var bottomLeftLabel = createLabel("Bottom Left");
|
||||
bottomLeftLabel.setTooltip(createTooltip("Bottom Left", AnchorLocation.WINDOW_TOP_RIGHT));
|
||||
|
||||
var bottomRightLabel = createLabel("Bottom Right");
|
||||
bottomRightLabel.setTooltip(createTooltip("Bottom Right", AnchorLocation.WINDOW_TOP_LEFT));
|
||||
|
||||
var flowPane = new FlowPane(
|
||||
BLOCK_HGAP, BLOCK_VGAP,
|
||||
topLeftLabel,
|
||||
new Separator(VERTICAL),
|
||||
topRightLabel,
|
||||
new Separator(VERTICAL),
|
||||
bottomLeftLabel,
|
||||
new Separator(VERTICAL),
|
||||
bottomRightLabel
|
||||
var description = BBCodeParser.createFormattedText("""
|
||||
In most cases a [i]Tooltip[/i] is created and its text property is \
|
||||
modified to show plain text to the user."""
|
||||
);
|
||||
|
||||
return new SampleBlock("Position", flowPane);
|
||||
return new ExampleBox(box, new Snippet(getClass(), 1), description);
|
||||
}
|
||||
|
||||
private ExampleBox positionExample() {
|
||||
//snippet_2:start
|
||||
// if the tooltip disappears almost immediately,
|
||||
// it's a #javafx-bug (you'll never walk alone)
|
||||
var topLeftTtp = new Tooltip(FAKER.chuckNorris().fact());
|
||||
topLeftTtp.setAnchorLocation(AnchorLocation.WINDOW_BOTTOM_RIGHT);
|
||||
var topLeftLbl = createLabel("Top Left");
|
||||
topLeftLbl.setTooltip(topLeftTtp);
|
||||
|
||||
var topRightTtp = new Tooltip(FAKER.chuckNorris().fact());
|
||||
topRightTtp.setAnchorLocation(AnchorLocation.WINDOW_BOTTOM_LEFT);
|
||||
var topRightLbl = createLabel("Top Right");
|
||||
topRightLbl.setTooltip(topRightTtp);
|
||||
|
||||
var bottomLeftTtp = new Tooltip(FAKER.chuckNorris().fact());
|
||||
bottomLeftTtp.setAnchorLocation(AnchorLocation.WINDOW_TOP_RIGHT);
|
||||
var bottomLeftLbl = createLabel("Bottom Left");
|
||||
bottomLeftLbl.setTooltip(bottomLeftTtp);
|
||||
|
||||
var bottomRightTtp = new Tooltip(FAKER.chuckNorris().fact());
|
||||
bottomRightTtp.setAnchorLocation(AnchorLocation.WINDOW_TOP_LEFT);
|
||||
var bottomRightLbl = createLabel("Bottom Right");
|
||||
bottomRightLbl.setTooltip(bottomRightTtp);
|
||||
//snippet_2:end
|
||||
|
||||
var box = new HBox(HGAP_20, topLeftLbl, topRightLbl, bottomLeftLbl, bottomRightLbl);
|
||||
|
||||
var description = BBCodeParser.createFormattedText("""
|
||||
You can specify the popup anchor point which is used in [i]Tooltip[/i] positioning. \
|
||||
The point can be set to a corner of the popup window or a corner of its content."""
|
||||
);
|
||||
|
||||
return new ExampleBox(box, new Snippet(getClass(), 2), description);
|
||||
}
|
||||
|
||||
private Label createLabel(String text) {
|
||||
Label label = new Label(text);
|
||||
label.setMinWidth(50);
|
||||
label.setMinWidth(100);
|
||||
label.setMinHeight(50);
|
||||
label.setPadding(new Insets(10));
|
||||
label.setStyle("-fx-background-color:-color-accent-subtle;");
|
||||
label.setAlignment(Pos.CENTER_LEFT);
|
||||
label.setAlignment(Pos.CENTER);
|
||||
return label;
|
||||
}
|
||||
|
||||
private Tooltip createTooltip(String text, AnchorLocation arrowPos) {
|
||||
var tooltip = new Tooltip(text);
|
||||
tooltip.setAnchorLocation(arrowPos);
|
||||
return tooltip;
|
||||
}
|
||||
}
|
||||
|
@ -1,318 +0,0 @@
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
|
||||
package atlantafx.sampler.page.components;
|
||||
|
||||
import static atlantafx.base.theme.Styles.DENSE;
|
||||
import static atlantafx.base.theme.Styles.toggleStyleClass;
|
||||
import static atlantafx.sampler.page.SampleBlock.BLOCK_HGAP;
|
||||
import static atlantafx.sampler.page.SampleBlock.BLOCK_VGAP;
|
||||
|
||||
import atlantafx.base.controls.Spacer;
|
||||
import atlantafx.base.controls.ToggleSwitch;
|
||||
import atlantafx.base.theme.Tweaks;
|
||||
import atlantafx.sampler.page.AbstractPage;
|
||||
import atlantafx.sampler.page.SampleBlock;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.function.Supplier;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.scene.control.CheckBoxTreeItem;
|
||||
import javafx.scene.control.ComboBox;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.control.TreeItem;
|
||||
import javafx.scene.control.TreeView;
|
||||
import javafx.scene.control.cell.CheckBoxTreeCell;
|
||||
import javafx.scene.control.cell.ChoiceBoxTreeCell;
|
||||
import javafx.scene.control.cell.ComboBoxTreeCell;
|
||||
import javafx.scene.control.cell.TextFieldTreeCell;
|
||||
import javafx.scene.layout.BorderPane;
|
||||
import javafx.scene.layout.HBox;
|
||||
import javafx.scene.layout.Priority;
|
||||
import javafx.scene.layout.VBox;
|
||||
import javafx.util.StringConverter;
|
||||
import org.kordamp.ikonli.feather.Feather;
|
||||
import org.kordamp.ikonli.javafx.FontIcon;
|
||||
|
||||
public class TreePage extends AbstractPage {
|
||||
|
||||
public static final String NAME = "TreeView";
|
||||
private static final int MAX_TREE_DEPTH = 3;
|
||||
private static final int[] TREE_DICE = {-1, 0, 1};
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return NAME;
|
||||
}
|
||||
|
||||
private final BorderPane treeWrapper = new BorderPane();
|
||||
private final ComboBox<Example> exampleSelect = createExampleSelect();
|
||||
|
||||
public TreePage() {
|
||||
super();
|
||||
|
||||
var sample = new SampleBlock("Playground", createPlayground());
|
||||
sample.setFillHeight(true);
|
||||
setUserContent(sample);
|
||||
}
|
||||
|
||||
private VBox createPlayground() {
|
||||
var denseToggle = new ToggleSwitch("Dense");
|
||||
denseToggle.selectedProperty().addListener(
|
||||
(obs, old, value) -> findDisplayedTree().ifPresent(tv -> toggleStyleClass(tv, DENSE))
|
||||
);
|
||||
|
||||
var showRootToggle = new ToggleSwitch("Show root");
|
||||
showRootToggle.selectedProperty().addListener((obs, old, val) -> findDisplayedTree().ifPresent(tv -> {
|
||||
if (val != null) {
|
||||
tv.setShowRoot(val);
|
||||
}
|
||||
}));
|
||||
showRootToggle.setSelected(true);
|
||||
|
||||
var altIconToggle = new ToggleSwitch("Alt icon");
|
||||
altIconToggle.selectedProperty().addListener((obs, old, val) ->
|
||||
findDisplayedTree().ifPresent(tv -> toggleStyleClass(tv, Tweaks.ALT_ICON))
|
||||
);
|
||||
|
||||
var edge2edgeToggle = new ToggleSwitch("Edge to edge");
|
||||
edge2edgeToggle.selectedProperty().addListener(
|
||||
(obs, old, val) -> findDisplayedTree().ifPresent(tv -> toggleStyleClass(tv, Tweaks.EDGE_TO_EDGE))
|
||||
);
|
||||
|
||||
var disableToggle = new ToggleSwitch("Disable");
|
||||
disableToggle.selectedProperty().addListener((obs, old, val) -> findDisplayedTree().ifPresent(tv -> {
|
||||
if (val != null) {
|
||||
tv.setDisable(val);
|
||||
}
|
||||
}));
|
||||
|
||||
var controls = new HBox(BLOCK_HGAP, denseToggle, showRootToggle, altIconToggle, edge2edgeToggle);
|
||||
controls.setAlignment(Pos.CENTER);
|
||||
|
||||
VBox.setVgrow(treeWrapper, Priority.ALWAYS);
|
||||
|
||||
var playground = new VBox(
|
||||
BLOCK_VGAP,
|
||||
new HBox(new Label("Select an example:"), new Spacer(), disableToggle),
|
||||
exampleSelect,
|
||||
treeWrapper,
|
||||
controls
|
||||
);
|
||||
playground.setMinHeight(100);
|
||||
|
||||
return playground;
|
||||
}
|
||||
|
||||
private ComboBox<Example> createExampleSelect() {
|
||||
var select = new ComboBox<Example>();
|
||||
select.setMaxWidth(Double.MAX_VALUE);
|
||||
select.getItems().setAll(Example.values());
|
||||
select.getSelectionModel().selectedItemProperty().addListener((obs, old, val) -> {
|
||||
if (val == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
TreeView<String> newTree = createTree(val);
|
||||
|
||||
// copy existing style classes and properties to the new tree
|
||||
findDisplayedTree().ifPresent(tv -> {
|
||||
List<String> currentStyles = tv.getStyleClass();
|
||||
currentStyles.remove("tree-view");
|
||||
newTree.getStyleClass().addAll(currentStyles);
|
||||
|
||||
newTree.setShowRoot(tv.isShowRoot());
|
||||
newTree.setDisable(tv.isDisable());
|
||||
});
|
||||
|
||||
treeWrapper.setCenter(newTree);
|
||||
});
|
||||
select.setConverter(new StringConverter<>() {
|
||||
@Override
|
||||
public String toString(Example example) {
|
||||
return example == null ? "" : example.getName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Example fromString(String s) {
|
||||
return Example.find(s);
|
||||
}
|
||||
});
|
||||
|
||||
return select;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onRendered() {
|
||||
super.onRendered();
|
||||
exampleSelect.getSelectionModel().selectFirst();
|
||||
}
|
||||
|
||||
private Optional<TreeView<?>> findDisplayedTree() {
|
||||
return treeWrapper.getChildren().size() > 0
|
||||
? Optional.of((TreeView<?>) treeWrapper.getChildren().get(0))
|
||||
: Optional.empty();
|
||||
}
|
||||
|
||||
private TreeView<String> createTree(Example example) {
|
||||
switch (example) {
|
||||
case TEXT -> {
|
||||
return stringTree();
|
||||
}
|
||||
case GRAPHIC -> {
|
||||
return graphicTree();
|
||||
}
|
||||
case EDITABLE -> {
|
||||
return editableTree();
|
||||
}
|
||||
case CHECK_BOX -> {
|
||||
return checkBoxTree();
|
||||
}
|
||||
case CHOICE_BOX -> {
|
||||
return choiceBoxTree();
|
||||
}
|
||||
case COMBO_BOX -> {
|
||||
return comboBoxTree();
|
||||
}
|
||||
default -> throw new IllegalArgumentException("Unexpected enum value: " + example);
|
||||
}
|
||||
}
|
||||
|
||||
private <T> void generateTree(TreeItem<T> parent, Supplier<TreeItem<T>> supplier, int limit, int depth) {
|
||||
if (limit == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
var item = supplier.get();
|
||||
parent.getChildren().add(item);
|
||||
|
||||
TreeItem<T> nextParent = parent; // sibling
|
||||
int nextDepth = depth;
|
||||
int rand = TREE_DICE[RANDOM.nextInt(TREE_DICE.length)];
|
||||
|
||||
if (rand < 0 && parent.getParent() != null) { // go up
|
||||
nextParent = parent.getParent();
|
||||
nextDepth = --depth;
|
||||
}
|
||||
if (rand > 0 && depth < MAX_TREE_DEPTH) { // go down
|
||||
nextParent = item;
|
||||
nextDepth = ++depth;
|
||||
}
|
||||
|
||||
generateTree(nextParent, supplier, --limit, nextDepth);
|
||||
}
|
||||
|
||||
private TreeView<String> stringTree() {
|
||||
var root = new TreeItem<>("root");
|
||||
root.setExpanded(true);
|
||||
|
||||
var tree = new TreeView<String>();
|
||||
|
||||
generateTree(root, () -> new TreeItem<>(FAKER.internet().domainWord()), 30, 1);
|
||||
tree.setRoot(root);
|
||||
|
||||
return tree;
|
||||
}
|
||||
|
||||
private TreeView<String> graphicTree() {
|
||||
var root = new TreeItem<>("root", new FontIcon(Feather.FOLDER));
|
||||
root.setExpanded(true);
|
||||
|
||||
var tree = new TreeView<String>();
|
||||
|
||||
generateTree(root, () -> new TreeItem<>(FAKER.internet().domainWord(), new FontIcon(Feather.FILE)), 30, 1);
|
||||
tree.setRoot(root);
|
||||
|
||||
return tree;
|
||||
}
|
||||
|
||||
private TreeView<String> editableTree() {
|
||||
var root = new TreeItem<>("root", new FontIcon(Feather.FOLDER));
|
||||
root.setExpanded(true);
|
||||
|
||||
var tree = new TreeView<String>();
|
||||
tree.setCellFactory(TextFieldTreeCell.forTreeView());
|
||||
tree.setEditable(true);
|
||||
|
||||
generateTree(root, () -> new TreeItem<>(FAKER.internet().domainWord(), new FontIcon(Feather.FILE)), 30, 1);
|
||||
tree.setRoot(root);
|
||||
|
||||
return tree;
|
||||
}
|
||||
|
||||
// Note that CheckBoxTreeCell is incompatible with user graphic,
|
||||
// because it adds graphic inside .checkbox container. #javafx-bug
|
||||
private TreeView<String> checkBoxTree() {
|
||||
var root = new CheckBoxTreeItem<>("root");
|
||||
root.setExpanded(true);
|
||||
|
||||
var tree = new TreeView<String>();
|
||||
tree.setCellFactory(CheckBoxTreeCell.forTreeView());
|
||||
|
||||
generateTree(root, () -> new CheckBoxTreeItem<>(FAKER.internet().domainWord()), 30, 1);
|
||||
tree.setRoot(root);
|
||||
|
||||
return tree;
|
||||
}
|
||||
|
||||
private TreeView<String> choiceBoxTree() {
|
||||
var root = new TreeItem<>("root");
|
||||
root.setExpanded(true);
|
||||
|
||||
var tree = new TreeView<String>();
|
||||
tree.setCellFactory(ChoiceBoxTreeCell.forTreeView(
|
||||
generate(() -> FAKER.internet().domainWord(), 10).toArray(String[]::new)
|
||||
));
|
||||
tree.setEditable(true);
|
||||
|
||||
generateTree(root, () -> new TreeItem<>(FAKER.internet().domainWord()), 30, 1);
|
||||
tree.setRoot(root);
|
||||
|
||||
return tree;
|
||||
}
|
||||
|
||||
private TreeView<String> comboBoxTree() {
|
||||
var root = new TreeItem<>("root", new FontIcon(Feather.FOLDER));
|
||||
root.setExpanded(true);
|
||||
|
||||
var tree = new TreeView<String>();
|
||||
tree.setCellFactory(ComboBoxTreeCell.forTreeView(
|
||||
generate(() -> FAKER.internet().domainWord(), 10).toArray(String[]::new)
|
||||
));
|
||||
tree.setEditable(true);
|
||||
|
||||
generateTree(root, () -> new TreeItem<>(FAKER.internet().domainWord(), new FontIcon(Feather.FILE)), 30, 1);
|
||||
tree.setRoot(root);
|
||||
|
||||
return tree;
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
|
||||
private enum Example {
|
||||
TEXT("Text"),
|
||||
GRAPHIC("Text with icons"),
|
||||
EDITABLE("TextFieldTreeCell"),
|
||||
CHECK_BOX("CheckBoxTreeCell"),
|
||||
CHOICE_BOX("ChoiceBoxTreeCell"),
|
||||
COMBO_BOX("ComboBoxTreeCell");
|
||||
|
||||
private final String name;
|
||||
|
||||
Example(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public static Example find(String name) {
|
||||
return Arrays.stream(Example.values())
|
||||
.filter(example -> Objects.equals(example.getName(), name))
|
||||
.findFirst()
|
||||
.orElse(null);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,315 +0,0 @@
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
|
||||
package atlantafx.sampler.page.components;
|
||||
|
||||
import static atlantafx.base.theme.Styles.BORDERED;
|
||||
import static atlantafx.base.theme.Styles.DENSE;
|
||||
import static atlantafx.base.theme.Styles.STRIPED;
|
||||
import static atlantafx.base.theme.Styles.toggleStyleClass;
|
||||
import static atlantafx.base.theme.Tweaks.ALIGN_CENTER;
|
||||
import static atlantafx.base.theme.Tweaks.ALIGN_LEFT;
|
||||
import static atlantafx.base.theme.Tweaks.ALIGN_RIGHT;
|
||||
import static atlantafx.sampler.page.SampleBlock.BLOCK_HGAP;
|
||||
import static atlantafx.sampler.page.SampleBlock.BLOCK_VGAP;
|
||||
|
||||
import atlantafx.base.controls.CaptionMenuItem;
|
||||
import atlantafx.base.controls.Spacer;
|
||||
import atlantafx.base.controls.ToggleSwitch;
|
||||
import atlantafx.base.theme.Styles;
|
||||
import atlantafx.base.theme.Tweaks;
|
||||
import atlantafx.sampler.fake.domain.Product;
|
||||
import atlantafx.sampler.page.AbstractPage;
|
||||
import atlantafx.sampler.page.SampleBlock;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.stream.IntStream;
|
||||
import javafx.beans.property.SimpleStringProperty;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.scene.control.CheckMenuItem;
|
||||
import javafx.scene.control.MenuButton;
|
||||
import javafx.scene.control.RadioMenuItem;
|
||||
import javafx.scene.control.SelectionMode;
|
||||
import javafx.scene.control.SeparatorMenuItem;
|
||||
import javafx.scene.control.ToggleButton;
|
||||
import javafx.scene.control.ToggleGroup;
|
||||
import javafx.scene.control.TreeItem;
|
||||
import javafx.scene.control.TreeTableColumn;
|
||||
import javafx.scene.control.TreeTableView;
|
||||
import javafx.scene.control.cell.CheckBoxTreeTableCell;
|
||||
import javafx.scene.control.cell.ChoiceBoxTreeTableCell;
|
||||
import javafx.scene.control.cell.ComboBoxTreeTableCell;
|
||||
import javafx.scene.control.cell.TextFieldTreeTableCell;
|
||||
import javafx.scene.control.cell.TreeItemPropertyValueFactory;
|
||||
import javafx.scene.layout.HBox;
|
||||
import javafx.scene.layout.Priority;
|
||||
import javafx.scene.layout.VBox;
|
||||
import javafx.util.Callback;
|
||||
import org.kordamp.ikonli.feather.Feather;
|
||||
import org.kordamp.ikonli.javafx.FontIcon;
|
||||
|
||||
public class TreeTablePage extends AbstractPage {
|
||||
|
||||
public static final String NAME = "TreeTableView";
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return NAME;
|
||||
}
|
||||
|
||||
private TreeTableView<Product> treeTable;
|
||||
|
||||
public TreeTablePage() {
|
||||
super();
|
||||
|
||||
var sample = new SampleBlock("Playground", createPlayground());
|
||||
sample.setFillHeight(true);
|
||||
setUserContent(sample);
|
||||
}
|
||||
|
||||
private VBox createPlayground() {
|
||||
// == FOOTER ==
|
||||
|
||||
var bordersToggle = new ToggleSwitch("Bordered");
|
||||
bordersToggle.selectedProperty().addListener((obs, old, val) -> toggleStyleClass(treeTable, BORDERED));
|
||||
|
||||
var denseToggle = new ToggleSwitch("Dense");
|
||||
denseToggle.selectedProperty().addListener((obs, old, val) -> toggleStyleClass(treeTable, DENSE));
|
||||
|
||||
var stripesToggle = new ToggleSwitch("Striped");
|
||||
stripesToggle.selectedProperty().addListener((obs, old, val) -> toggleStyleClass(treeTable, STRIPED));
|
||||
|
||||
var altIconToggle = new ToggleSwitch("Alt icon");
|
||||
altIconToggle.selectedProperty().addListener((obs, old, val) -> toggleStyleClass(treeTable, Tweaks.ALT_ICON));
|
||||
|
||||
var edge2edgeToggle = new ToggleSwitch("Edge to edge");
|
||||
edge2edgeToggle.selectedProperty().addListener(
|
||||
(obs, old, value) -> toggleStyleClass(treeTable, Tweaks.EDGE_TO_EDGE)
|
||||
);
|
||||
|
||||
var footer = new HBox(BLOCK_HGAP, bordersToggle, denseToggle, stripesToggle, altIconToggle, edge2edgeToggle);
|
||||
footer.setAlignment(Pos.CENTER);
|
||||
|
||||
// == TREE TABLE ==
|
||||
|
||||
var rootVal = Product.empty(0);
|
||||
rootVal.setBrand("Root");
|
||||
var root = new TreeItem<>(rootVal);
|
||||
|
||||
for (int idx = 1; idx <= FAKER.random().nextInt(5, 10); idx++) {
|
||||
String brand = FAKER.commerce().brand();
|
||||
var groupVal = Product.empty(0);
|
||||
groupVal.setBrand(brand);
|
||||
|
||||
var group = new TreeItem<>(groupVal);
|
||||
group.getChildren().setAll(
|
||||
createTreeItems(idx * 100, FAKER.random().nextInt(5, 10), brand)
|
||||
);
|
||||
root.getChildren().add(group);
|
||||
}
|
||||
|
||||
treeTable = createTreeTable();
|
||||
treeTable.setRoot(root);
|
||||
VBox.setVgrow(treeTable, Priority.ALWAYS);
|
||||
|
||||
// == HEADER ==
|
||||
|
||||
var alignGroup = new ToggleGroup();
|
||||
|
||||
var alignLeftBtn = new ToggleButton("", new FontIcon(Feather.ALIGN_LEFT));
|
||||
alignLeftBtn.getStyleClass().add(Styles.LEFT_PILL);
|
||||
alignLeftBtn.setToggleGroup(alignGroup);
|
||||
alignLeftBtn.setSelected(true);
|
||||
alignLeftBtn.setOnAction(e -> {
|
||||
for (TreeTableColumn<?, ?> c : treeTable.getColumns()) {
|
||||
c.getStyleClass().removeAll(ALIGN_LEFT, ALIGN_CENTER, ALIGN_RIGHT);
|
||||
}
|
||||
});
|
||||
|
||||
var alignCenterBtn = new ToggleButton("", new FontIcon(Feather.ALIGN_CENTER));
|
||||
alignCenterBtn.getStyleClass().add(Styles.CENTER_PILL);
|
||||
alignCenterBtn.setToggleGroup(alignGroup);
|
||||
alignCenterBtn.selectedProperty().addListener((obs, old, val) -> {
|
||||
for (TreeTableColumn<?, ?> c : treeTable.getColumns()) {
|
||||
addStyleClass(c, ALIGN_CENTER, ALIGN_LEFT, ALIGN_RIGHT);
|
||||
}
|
||||
});
|
||||
|
||||
var alignRightBtn = new ToggleButton("", new FontIcon(Feather.ALIGN_RIGHT));
|
||||
alignRightBtn.getStyleClass().add(Styles.RIGHT_PILL);
|
||||
alignRightBtn.setToggleGroup(alignGroup);
|
||||
alignRightBtn.selectedProperty().addListener((obs, old, val) -> {
|
||||
for (TreeTableColumn<?, ?> c : treeTable.getColumns()) {
|
||||
addStyleClass(c, ALIGN_RIGHT, ALIGN_LEFT, ALIGN_CENTER);
|
||||
}
|
||||
});
|
||||
|
||||
var alignBox = new HBox(alignLeftBtn, alignCenterBtn, alignRightBtn);
|
||||
|
||||
var disableToggle = new ToggleSwitch("Disable");
|
||||
disableToggle.selectedProperty().addListener((obs, old, val) -> {
|
||||
if (val != null) {
|
||||
treeTable.setDisable(val);
|
||||
}
|
||||
});
|
||||
|
||||
var header = new HBox(
|
||||
createPropertiesMenu(treeTable),
|
||||
new Spacer(),
|
||||
alignBox,
|
||||
new Spacer(),
|
||||
disableToggle
|
||||
);
|
||||
header.setAlignment(Pos.CENTER_LEFT);
|
||||
|
||||
// ~
|
||||
|
||||
var playground = new VBox(BLOCK_VGAP, header, treeTable, footer);
|
||||
playground.setMinHeight(100);
|
||||
|
||||
return playground;
|
||||
}
|
||||
|
||||
private List<TreeItem<Product>> createTreeItems(int startId, int count, String brand) {
|
||||
return IntStream.range(startId, startId + count + 1).boxed()
|
||||
.map(id -> Product.random(id, brand, FAKER))
|
||||
.map(TreeItem::new)
|
||||
.toList();
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private TreeTableView<Product> createTreeTable() {
|
||||
var arrowCol = new TreeTableColumn<Product, String>("#");
|
||||
// This is placeholder column for disclosure nodes. We need to fill it
|
||||
// with empty strings or all .tree-table-cell will be marked as :empty,
|
||||
// which in turn leads to absent borders.
|
||||
arrowCol.setCellValueFactory(cell -> new SimpleStringProperty(""));
|
||||
arrowCol.setMinWidth(50);
|
||||
arrowCol.setMaxWidth(50);
|
||||
|
||||
var stateCol = new TreeTableColumn<Product, Boolean>("Selected");
|
||||
stateCol.setCellValueFactory(new TreeItemPropertyValueFactory<>("state"));
|
||||
stateCol.setCellFactory(CheckBoxTreeTableCell.forTreeTableColumn(stateCol));
|
||||
stateCol.setEditable(true);
|
||||
|
||||
var idCol = new TreeTableColumn<Product, String>("ID");
|
||||
idCol.setCellValueFactory(cell -> {
|
||||
Product product = cell.getValue().getValue();
|
||||
return new SimpleStringProperty(
|
||||
product != null && product.getId() != 0 ? String.valueOf(product.getId()) : ""
|
||||
);
|
||||
});
|
||||
idCol.setEditable(false);
|
||||
idCol.setMinWidth(80);
|
||||
|
||||
var brandCol = new TreeTableColumn<Product, String>("Brand 🖉");
|
||||
brandCol.setCellValueFactory(new TreeItemPropertyValueFactory<>("brand"));
|
||||
brandCol.setCellFactory(ChoiceBoxTreeTableCell.forTreeTableColumn(
|
||||
generate(() -> FAKER.commerce().brand(), 10).toArray(String[]::new)
|
||||
));
|
||||
brandCol.setEditable(true);
|
||||
|
||||
var nameCol = new TreeTableColumn<Product, String>("Name 🖉");
|
||||
nameCol.setCellValueFactory(new TreeItemPropertyValueFactory<>("name"));
|
||||
nameCol.setCellFactory(ComboBoxTreeTableCell.forTreeTableColumn(
|
||||
generate(() -> FAKER.commerce().productName(), 10).toArray(String[]::new)
|
||||
));
|
||||
nameCol.setEditable(true);
|
||||
nameCol.setMinWidth(200);
|
||||
|
||||
var priceCol = new TreeTableColumn<Product, String>("Price 🖉");
|
||||
priceCol.setCellValueFactory(new TreeItemPropertyValueFactory<>("price"));
|
||||
priceCol.setCellFactory(TextFieldTreeTableCell.forTreeTableColumn());
|
||||
priceCol.setEditable(true);
|
||||
|
||||
var table = new TreeTableView<Product>();
|
||||
table.getColumns().setAll(arrowCol, stateCol, brandCol, idCol, nameCol, priceCol);
|
||||
|
||||
return table;
|
||||
}
|
||||
|
||||
private MenuButton createPropertiesMenu(TreeTableView<Product> treeTable) {
|
||||
final var resizePolicyCaption = new CaptionMenuItem("Resize Policy");
|
||||
final var resizePolicyGroup = new ToggleGroup();
|
||||
resizePolicyGroup.selectedToggleProperty().addListener((obs, old, val) -> {
|
||||
if (val != null && val.getUserData() instanceof Callback<?, ?> policy) {
|
||||
//noinspection rawtypes,unchecked
|
||||
treeTable.setColumnResizePolicy((Callback<TreeTableView.ResizeFeatures, Boolean>) policy);
|
||||
}
|
||||
});
|
||||
|
||||
final var unconstrainedResizeItem = new RadioMenuItem("Unconstrained");
|
||||
unconstrainedResizeItem.setToggleGroup(resizePolicyGroup);
|
||||
unconstrainedResizeItem.setUserData(TreeTableView.UNCONSTRAINED_RESIZE_POLICY);
|
||||
unconstrainedResizeItem.setSelected(true);
|
||||
|
||||
final var constrainedResizeItem = new RadioMenuItem("Constrained");
|
||||
constrainedResizeItem.setToggleGroup(resizePolicyGroup);
|
||||
constrainedResizeItem.setUserData(TreeTableView.CONSTRAINED_RESIZE_POLICY);
|
||||
|
||||
// ~
|
||||
|
||||
final var selectionModeCaption = new CaptionMenuItem("Selection Mode");
|
||||
final var selectionModeGroup = new ToggleGroup();
|
||||
selectionModeGroup.selectedToggleProperty().addListener((obs, old, val) -> {
|
||||
if (val != null && val.getUserData() instanceof SelectionMode mode) {
|
||||
treeTable.getSelectionModel().setSelectionMode(mode);
|
||||
}
|
||||
});
|
||||
|
||||
final var singleSelectionItem = new RadioMenuItem("Single");
|
||||
singleSelectionItem.setToggleGroup(selectionModeGroup);
|
||||
singleSelectionItem.setUserData(SelectionMode.SINGLE);
|
||||
|
||||
final var multiSelectionItem = new RadioMenuItem("Multiple");
|
||||
multiSelectionItem.setToggleGroup(selectionModeGroup);
|
||||
multiSelectionItem.setUserData(SelectionMode.MULTIPLE);
|
||||
multiSelectionItem.setSelected(true);
|
||||
|
||||
// ~
|
||||
|
||||
final var showRootItem = new CheckMenuItem("Show root");
|
||||
treeTable.showRootProperty().bind(showRootItem.selectedProperty());
|
||||
showRootItem.setSelected(true);
|
||||
|
||||
final var editCellsItem = new CheckMenuItem("Editable");
|
||||
treeTable.editableProperty().bind(editCellsItem.selectedProperty());
|
||||
editCellsItem.setSelected(true);
|
||||
|
||||
final var cellSelectionItem = new CheckMenuItem("Enable cell selection");
|
||||
treeTable.getSelectionModel().cellSelectionEnabledProperty().bind(cellSelectionItem.selectedProperty());
|
||||
cellSelectionItem.setSelected(false);
|
||||
|
||||
// ~
|
||||
|
||||
final var menuButtonItem = new CheckMenuItem("Show menu button");
|
||||
treeTable.tableMenuButtonVisibleProperty().bind(menuButtonItem.selectedProperty());
|
||||
menuButtonItem.setSelected(true);
|
||||
|
||||
final var propertiesMenu = new MenuButton("Properties");
|
||||
propertiesMenu.getItems().setAll(
|
||||
resizePolicyCaption,
|
||||
unconstrainedResizeItem,
|
||||
constrainedResizeItem,
|
||||
selectionModeCaption,
|
||||
singleSelectionItem,
|
||||
multiSelectionItem,
|
||||
new SeparatorMenuItem(),
|
||||
showRootItem,
|
||||
editCellsItem,
|
||||
cellSelectionItem,
|
||||
menuButtonItem
|
||||
);
|
||||
|
||||
return propertiesMenu;
|
||||
}
|
||||
|
||||
private static void addStyleClass(TreeTableColumn<?, ?> c, String styleClass, String... excludes) {
|
||||
Objects.requireNonNull(c);
|
||||
Objects.requireNonNull(styleClass);
|
||||
|
||||
if (excludes != null && excludes.length > 0) {
|
||||
c.getStyleClass().removeAll(excludes);
|
||||
}
|
||||
c.getStyleClass().add(styleClass);
|
||||
}
|
||||
}
|
@ -0,0 +1,384 @@
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
|
||||
package atlantafx.sampler.page.components;
|
||||
|
||||
import static atlantafx.base.theme.Styles.BORDERED;
|
||||
import static atlantafx.base.theme.Styles.DENSE;
|
||||
import static atlantafx.base.theme.Styles.STRIPED;
|
||||
import static atlantafx.base.theme.Styles.toggleStyleClass;
|
||||
import static atlantafx.base.theme.Tweaks.ALIGN_CENTER;
|
||||
import static atlantafx.base.theme.Tweaks.ALIGN_LEFT;
|
||||
import static atlantafx.base.theme.Tweaks.ALIGN_RIGHT;
|
||||
|
||||
import atlantafx.base.controls.CaptionMenuItem;
|
||||
import atlantafx.base.controls.ToggleSwitch;
|
||||
import atlantafx.base.theme.Tweaks;
|
||||
import atlantafx.base.util.BBCodeParser;
|
||||
import atlantafx.sampler.fake.domain.Product;
|
||||
import atlantafx.sampler.page.ExampleBox;
|
||||
import atlantafx.sampler.page.OutlinePage;
|
||||
import atlantafx.sampler.page.Snippet;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.stream.IntStream;
|
||||
import javafx.beans.property.SimpleStringProperty;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.scene.control.CheckMenuItem;
|
||||
import javafx.scene.control.Menu;
|
||||
import javafx.scene.control.MenuButton;
|
||||
import javafx.scene.control.RadioMenuItem;
|
||||
import javafx.scene.control.SelectionMode;
|
||||
import javafx.scene.control.SeparatorMenuItem;
|
||||
import javafx.scene.control.ToggleGroup;
|
||||
import javafx.scene.control.TreeItem;
|
||||
import javafx.scene.control.TreeTableColumn;
|
||||
import javafx.scene.control.TreeTableView;
|
||||
import javafx.scene.control.cell.CheckBoxTreeTableCell;
|
||||
import javafx.scene.control.cell.ChoiceBoxTreeTableCell;
|
||||
import javafx.scene.control.cell.ComboBoxTreeTableCell;
|
||||
import javafx.scene.control.cell.TextFieldTreeTableCell;
|
||||
import javafx.scene.control.cell.TreeItemPropertyValueFactory;
|
||||
import javafx.scene.layout.HBox;
|
||||
import javafx.scene.layout.Priority;
|
||||
import javafx.scene.layout.VBox;
|
||||
import javafx.util.Callback;
|
||||
|
||||
public class TreeTableViewPage extends OutlinePage {
|
||||
|
||||
public static final String NAME = "TreeTableView";
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return NAME;
|
||||
}
|
||||
|
||||
private TreeTableView<Product> treeTable;
|
||||
|
||||
public TreeTableViewPage() {
|
||||
super();
|
||||
|
||||
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.""");
|
||||
addSection("Usage", usageExample());
|
||||
addSection("Playground", playground());
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private ExampleBox usageExample() {
|
||||
//snippet_1:start
|
||||
record Vehicle(String brand, String model) {
|
||||
|
||||
public static Vehicle random(String make) {
|
||||
return new Vehicle(make, FAKER.vehicle().model());
|
||||
}
|
||||
}
|
||||
|
||||
var col1 = new TreeTableColumn<Vehicle, String>("Brand");
|
||||
var col2 = new TreeTableColumn<Vehicle, String>("Model");
|
||||
|
||||
col1.setCellValueFactory(
|
||||
c -> new SimpleStringProperty(c.getValue().getValue().brand())
|
||||
);
|
||||
col2.setCellValueFactory(
|
||||
c -> new SimpleStringProperty(c.getValue().getValue().model())
|
||||
);
|
||||
|
||||
var treeTable = new TreeTableView<Vehicle>();
|
||||
treeTable.setColumnResizePolicy(TreeTableView.CONSTRAINED_RESIZE_POLICY);
|
||||
treeTable.getColumns().setAll(col1, col2);
|
||||
|
||||
var make1 = FAKER.vehicle().make();
|
||||
var treeItem1 = new TreeItem<Vehicle>(new Vehicle(make1, "..."));
|
||||
treeItem1.getChildren().setAll(
|
||||
new TreeItem<>(Vehicle.random(make1)),
|
||||
new TreeItem<>(Vehicle.random(make1)),
|
||||
new TreeItem<>(Vehicle.random(make1))
|
||||
);
|
||||
|
||||
var make2 = FAKER.vehicle().make();
|
||||
var treeItem2 = new TreeItem<Vehicle>(new Vehicle(make2, "..."));
|
||||
treeItem2.getChildren().setAll(
|
||||
new TreeItem<>(Vehicle.random(make1)),
|
||||
new TreeItem<>(Vehicle.random(make1)),
|
||||
new TreeItem<>(Vehicle.random(make1))
|
||||
);
|
||||
|
||||
var rootItem = new TreeItem<Vehicle>(new Vehicle("Vehicles", "..."));
|
||||
rootItem.getChildren().setAll(treeItem1, treeItem2);
|
||||
rootItem.setExpanded(true);
|
||||
|
||||
treeTable.setRoot(rootItem);
|
||||
//snippet_1:end
|
||||
|
||||
treeTable.setMaxWidth(Double.MAX_VALUE);
|
||||
treeTable.setMinHeight(300);
|
||||
HBox.setHgrow(treeTable, Priority.ALWAYS);
|
||||
|
||||
var box = new HBox(treeTable);
|
||||
var description = BBCodeParser.createFormattedText("""
|
||||
You can create a table view by instantiating the \
|
||||
[font=monospace]javafx.scene.control.TreeTableView[/font] class."""
|
||||
);
|
||||
|
||||
return new ExampleBox(box, new Snippet(getClass(), 1), description);
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
// Playground //
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
|
||||
private VBox playground() {
|
||||
// == FOOTER ==
|
||||
|
||||
var borderToggle = new ToggleSwitch("Bordered");
|
||||
borderToggle.selectedProperty().addListener(
|
||||
(obs, old, val) -> toggleStyleClass(treeTable, BORDERED)
|
||||
);
|
||||
|
||||
var denseToggle = new ToggleSwitch("Dense");
|
||||
denseToggle.selectedProperty().addListener(
|
||||
(obs, old, val) -> toggleStyleClass(treeTable, DENSE)
|
||||
);
|
||||
|
||||
var stripeToggle = new ToggleSwitch("Striped");
|
||||
stripeToggle.selectedProperty().addListener(
|
||||
(obs, old, val) -> toggleStyleClass(treeTable, STRIPED)
|
||||
);
|
||||
|
||||
var altIconToggle = new ToggleSwitch("Alt icon");
|
||||
altIconToggle.selectedProperty().addListener(
|
||||
(obs, old, val) -> toggleStyleClass(treeTable, Tweaks.ALT_ICON)
|
||||
);
|
||||
|
||||
var edge2edgeToggle = new ToggleSwitch("Edge to edge");
|
||||
edge2edgeToggle.selectedProperty().addListener(
|
||||
(obs, old, value) -> toggleStyleClass(treeTable, Tweaks.EDGE_TO_EDGE)
|
||||
);
|
||||
|
||||
var footer = new HBox(
|
||||
HGAP_20, borderToggle, denseToggle, stripeToggle,
|
||||
altIconToggle, edge2edgeToggle
|
||||
);
|
||||
footer.setAlignment(Pos.CENTER);
|
||||
|
||||
// == TREE TABLE ==
|
||||
|
||||
var rootVal = Product.empty(0);
|
||||
rootVal.setBrand("Root");
|
||||
var root = new TreeItem<>(rootVal);
|
||||
|
||||
for (int idx = 1; idx <= FAKER.random().nextInt(5, 10); idx++) {
|
||||
String brand = FAKER.commerce().brand();
|
||||
var groupVal = Product.empty(0);
|
||||
groupVal.setBrand(brand);
|
||||
|
||||
var group = new TreeItem<>(groupVal);
|
||||
group.getChildren().setAll(
|
||||
createTreeItems(idx * 100, FAKER.random().nextInt(5, 10), brand)
|
||||
);
|
||||
root.getChildren().add(group);
|
||||
}
|
||||
|
||||
treeTable = createTreeTable();
|
||||
treeTable.setColumnResizePolicy(TreeTableView.UNCONSTRAINED_RESIZE_POLICY);
|
||||
treeTable.setRoot(root);
|
||||
VBox.setVgrow(treeTable, Priority.ALWAYS);
|
||||
|
||||
// == HEADER ==
|
||||
|
||||
var header = new HBox(createPropertiesMenu(treeTable));
|
||||
header.setAlignment(Pos.CENTER_LEFT);
|
||||
|
||||
// ~
|
||||
|
||||
var description = BBCodeParser.createFormattedText("""
|
||||
The playground demonstrates the most important [i]TreeTableView[/i] features \
|
||||
and also serves as an object for monkey testing."""
|
||||
);
|
||||
|
||||
var playground = new VBox(VGAP_10, description, header, treeTable, footer);
|
||||
playground.setMinHeight(500);
|
||||
|
||||
return playground;
|
||||
}
|
||||
|
||||
private List<TreeItem<Product>> createTreeItems(int startId, int count, String brand) {
|
||||
return IntStream.range(startId, startId + count + 1).boxed()
|
||||
.map(id -> Product.random(id, brand, FAKER))
|
||||
.map(TreeItem::new)
|
||||
.toList();
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private TreeTableView<Product> createTreeTable() {
|
||||
var arrowCol = new TreeTableColumn<Product, String>("#");
|
||||
// This is placeholder column for disclosure nodes. We need to fill it
|
||||
// with empty strings or each .tree-table-cell will be marked as :empty,
|
||||
// which in turn leads to absent borders.
|
||||
arrowCol.setCellValueFactory(cell -> new SimpleStringProperty(""));
|
||||
arrowCol.setMinWidth(50);
|
||||
arrowCol.setMaxWidth(50);
|
||||
|
||||
var stateCol = new TreeTableColumn<Product, Boolean>("State");
|
||||
stateCol.setCellValueFactory(new TreeItemPropertyValueFactory<>("state"));
|
||||
stateCol.setCellFactory(CheckBoxTreeTableCell.forTreeTableColumn(stateCol));
|
||||
stateCol.setEditable(true);
|
||||
|
||||
var idCol = new TreeTableColumn<Product, String>("ID");
|
||||
idCol.setCellValueFactory(cell -> {
|
||||
Product product = cell.getValue().getValue();
|
||||
return new SimpleStringProperty(
|
||||
product != null && product.getId() != 0 ? String.valueOf(product.getId()) : ""
|
||||
);
|
||||
});
|
||||
idCol.setEditable(false);
|
||||
idCol.setMinWidth(80);
|
||||
|
||||
var brandCol = new TreeTableColumn<Product, String>("Brand");
|
||||
brandCol.setCellValueFactory(new TreeItemPropertyValueFactory<>("brand"));
|
||||
brandCol.setCellFactory(ChoiceBoxTreeTableCell.forTreeTableColumn(
|
||||
generate(() -> FAKER.commerce().brand(), 10).toArray(String[]::new)
|
||||
));
|
||||
brandCol.setEditable(true);
|
||||
|
||||
var nameCol = new TreeTableColumn<Product, String>("Name");
|
||||
nameCol.setCellValueFactory(new TreeItemPropertyValueFactory<>("name"));
|
||||
nameCol.setCellFactory(ComboBoxTreeTableCell.forTreeTableColumn(
|
||||
generate(() -> FAKER.commerce().productName(), 10).toArray(String[]::new)
|
||||
));
|
||||
nameCol.setEditable(true);
|
||||
nameCol.setMinWidth(200);
|
||||
|
||||
var priceCol = new TreeTableColumn<Product, String>("Price");
|
||||
priceCol.setCellValueFactory(new TreeItemPropertyValueFactory<>("price"));
|
||||
priceCol.setCellFactory(TextFieldTreeTableCell.forTreeTableColumn());
|
||||
priceCol.setEditable(true);
|
||||
|
||||
var table = new TreeTableView<Product>();
|
||||
table.getColumns().setAll(
|
||||
arrowCol, stateCol, brandCol, idCol, nameCol, priceCol
|
||||
);
|
||||
|
||||
return table;
|
||||
}
|
||||
|
||||
private MenuButton createPropertiesMenu(TreeTableView<Product> treeTable) {
|
||||
final var resizePolCaption = new CaptionMenuItem("Resize Policy");
|
||||
final var resizePolicyGroup = new ToggleGroup();
|
||||
resizePolicyGroup.selectedToggleProperty().addListener((obs, old, val) -> {
|
||||
if (val != null && val.getUserData() instanceof Callback<?, ?> policy) {
|
||||
//noinspection rawtypes,unchecked
|
||||
treeTable.setColumnResizePolicy((Callback<TreeTableView.ResizeFeatures, Boolean>) policy);
|
||||
}
|
||||
});
|
||||
|
||||
final var unconResizeItem = new RadioMenuItem("Unconstrained");
|
||||
unconResizeItem.setToggleGroup(resizePolicyGroup);
|
||||
unconResizeItem.setUserData(TreeTableView.UNCONSTRAINED_RESIZE_POLICY);
|
||||
unconResizeItem.setSelected(true);
|
||||
|
||||
final var conResizeItem = new RadioMenuItem("Constrained");
|
||||
conResizeItem.setToggleGroup(resizePolicyGroup);
|
||||
conResizeItem.setUserData(TreeTableView.CONSTRAINED_RESIZE_POLICY);
|
||||
|
||||
// ~
|
||||
|
||||
final var selModeCaption = new CaptionMenuItem("Selection Mode");
|
||||
final var selModeGroup = new ToggleGroup();
|
||||
selModeGroup.selectedToggleProperty().addListener((obs, old, val) -> {
|
||||
if (val != null && val.getUserData() instanceof SelectionMode mode) {
|
||||
treeTable.getSelectionModel().setSelectionMode(mode);
|
||||
}
|
||||
});
|
||||
|
||||
final var singleSelItem = new RadioMenuItem("Single");
|
||||
singleSelItem.setToggleGroup(selModeGroup);
|
||||
singleSelItem.setUserData(SelectionMode.SINGLE);
|
||||
|
||||
final var multiSelItem = new RadioMenuItem("Multiple");
|
||||
multiSelItem.setToggleGroup(selModeGroup);
|
||||
multiSelItem.setUserData(SelectionMode.MULTIPLE);
|
||||
multiSelItem.setSelected(true);
|
||||
|
||||
// ~
|
||||
|
||||
final var alignLeftItem = new RadioMenuItem("Left");
|
||||
alignLeftItem.setSelected(true);
|
||||
alignLeftItem.setOnAction(e -> {
|
||||
for (TreeTableColumn<?, ?> c : treeTable.getColumns()) {
|
||||
c.getStyleClass().removeAll(ALIGN_LEFT, ALIGN_CENTER, ALIGN_RIGHT);
|
||||
}
|
||||
});
|
||||
|
||||
final var alignCenterItem = new RadioMenuItem("Center");
|
||||
alignCenterItem.setOnAction(e -> {
|
||||
for (TreeTableColumn<?, ?> c : treeTable.getColumns()) {
|
||||
addStyleClass(c, ALIGN_CENTER, ALIGN_LEFT, ALIGN_RIGHT);
|
||||
}
|
||||
});
|
||||
|
||||
final var alignRightItem = new RadioMenuItem("Right");
|
||||
alignRightItem.setOnAction(e -> {
|
||||
for (TreeTableColumn<?, ?> c : treeTable.getColumns()) {
|
||||
addStyleClass(c, ALIGN_RIGHT, ALIGN_LEFT, ALIGN_CENTER);
|
||||
}
|
||||
});
|
||||
|
||||
final var alignItem = new Menu("Alignment");
|
||||
alignItem.getItems().setAll(alignLeftItem, alignCenterItem, alignRightItem);
|
||||
|
||||
final var alignGroup = new ToggleGroup();
|
||||
alignGroup.getToggles().setAll(alignLeftItem, alignCenterItem, alignRightItem);
|
||||
|
||||
// ~
|
||||
|
||||
final var showRootItem = new CheckMenuItem("Show root");
|
||||
treeTable.showRootProperty().bind(showRootItem.selectedProperty());
|
||||
|
||||
final var editItem = new CheckMenuItem("Editable");
|
||||
treeTable.editableProperty().bind(editItem.selectedProperty());
|
||||
editItem.setSelected(true);
|
||||
|
||||
final var cellSelItem = new CheckMenuItem("Enable cell selection");
|
||||
treeTable.getSelectionModel().cellSelectionEnabledProperty().bind(cellSelItem.selectedProperty());
|
||||
cellSelItem.setSelected(false);
|
||||
|
||||
// ~
|
||||
|
||||
final var menuBtnItem = new CheckMenuItem("Show menu button");
|
||||
treeTable.tableMenuButtonVisibleProperty().bind(menuBtnItem.selectedProperty());
|
||||
menuBtnItem.setSelected(true);
|
||||
|
||||
final var propsMenu = new MenuButton("Properties");
|
||||
propsMenu.getItems().setAll(
|
||||
resizePolCaption,
|
||||
unconResizeItem,
|
||||
conResizeItem,
|
||||
selModeCaption,
|
||||
singleSelItem,
|
||||
multiSelItem,
|
||||
new SeparatorMenuItem(),
|
||||
alignItem,
|
||||
showRootItem,
|
||||
editItem,
|
||||
cellSelItem,
|
||||
menuBtnItem
|
||||
);
|
||||
|
||||
return propsMenu;
|
||||
}
|
||||
|
||||
private void addStyleClass(TreeTableColumn<?, ?> c,
|
||||
String styleClass,
|
||||
String... excludes) {
|
||||
Objects.requireNonNull(c);
|
||||
Objects.requireNonNull(styleClass);
|
||||
|
||||
if (excludes != null && excludes.length > 0) {
|
||||
c.getStyleClass().removeAll(excludes);
|
||||
}
|
||||
c.getStyleClass().add(styleClass);
|
||||
}
|
||||
}
|
@ -0,0 +1,542 @@
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
|
||||
package atlantafx.sampler.page.components;
|
||||
|
||||
import atlantafx.base.controls.ToggleSwitch;
|
||||
import atlantafx.base.theme.Styles;
|
||||
import atlantafx.base.theme.Tweaks;
|
||||
import atlantafx.base.util.BBCodeParser;
|
||||
import atlantafx.sampler.Resources;
|
||||
import atlantafx.sampler.page.ExampleBox;
|
||||
import atlantafx.sampler.page.OutlinePage;
|
||||
import atlantafx.sampler.page.Snippet;
|
||||
import java.io.File;
|
||||
import java.util.Arrays;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.function.Supplier;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.scene.control.CheckBoxTreeItem;
|
||||
import javafx.scene.control.ComboBox;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.control.TreeItem;
|
||||
import javafx.scene.control.TreeView;
|
||||
import javafx.scene.control.cell.CheckBoxTreeCell;
|
||||
import javafx.scene.control.cell.ChoiceBoxTreeCell;
|
||||
import javafx.scene.control.cell.ComboBoxTreeCell;
|
||||
import javafx.scene.control.cell.TextFieldTreeCell;
|
||||
import javafx.scene.image.Image;
|
||||
import javafx.scene.image.ImageView;
|
||||
import javafx.scene.layout.BorderPane;
|
||||
import javafx.scene.layout.HBox;
|
||||
import javafx.scene.layout.Priority;
|
||||
import javafx.scene.layout.VBox;
|
||||
import javafx.util.StringConverter;
|
||||
import org.kordamp.ikonli.feather.Feather;
|
||||
import org.kordamp.ikonli.javafx.FontIcon;
|
||||
|
||||
public class TreeViewPage extends OutlinePage {
|
||||
|
||||
public static final String NAME = "TreeView";
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return NAME;
|
||||
}
|
||||
|
||||
public TreeViewPage() {
|
||||
super();
|
||||
|
||||
addFormattedText("""
|
||||
The [i]TreeView[/i] provides a way to present tree structures. A tree has \
|
||||
a root node which contains all the hierarchical values."""
|
||||
);
|
||||
addSection("Usage", usageExample());
|
||||
addSection("Dense", denseExample());
|
||||
addSection("Alt Icon", altIconExample());
|
||||
addSection("Edge-to-Edge", edge2EdgeExample());
|
||||
addSection("Playground", playground());
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private ExampleBox usageExample() {
|
||||
//snippet_1:start
|
||||
var item1 = new TreeItem<>("Programming", icon("code"));
|
||||
item1.getChildren().setAll(
|
||||
new TreeItem<>("Kotlin", icon("kotlin")),
|
||||
new TreeItem<>("Python", icon("python")),
|
||||
new TreeItem<>("C++", icon("cplusplus"))
|
||||
);
|
||||
|
||||
var item2 = new TreeItem<>("Databases", icon("database"));
|
||||
item2.getChildren().setAll(
|
||||
new TreeItem<>("PostgreSQL", icon("postgresql")),
|
||||
new TreeItem<>("Redis", icon("redis")),
|
||||
new TreeItem<>("MongoDB", icon("mongodb"))
|
||||
);
|
||||
|
||||
var item3 = new TreeItem<>("Cloud", icon("cloud"));
|
||||
item3.getChildren().setAll(
|
||||
new TreeItem<>("Terraform", icon("terraform")),
|
||||
new TreeItem<>("Azure", icon("azure")),
|
||||
new TreeItem<>("Kubernetes", icon("kubernetes"))
|
||||
);
|
||||
|
||||
var root = new TreeItem<>("Technologies");
|
||||
root.setExpanded(true);
|
||||
root.getChildren().addAll(item1, item2, item3);
|
||||
|
||||
var tree = new TreeView<>(root);
|
||||
//snippet_1:end
|
||||
|
||||
tree.setMaxWidth(Double.MAX_VALUE);
|
||||
tree.setMinHeight(250);
|
||||
HBox.setHgrow(tree, Priority.ALWAYS);
|
||||
|
||||
var box = new HBox(tree);
|
||||
var description = BBCodeParser.createFormattedText("""
|
||||
In each tree the highest object in the hierarchy is called the "root". \
|
||||
The root contains several child items, which can have children as well. \
|
||||
An item without children is called "leaf".""");
|
||||
|
||||
return new ExampleBox(box, new Snippet(getClass(), 1), description);
|
||||
}
|
||||
|
||||
private ExampleBox denseExample() {
|
||||
//snippet_2:start
|
||||
class Helper {
|
||||
public static TreeItem<String> scan(File dir, int depth) {
|
||||
var parent = new TreeItem<>(
|
||||
dir.getName(),
|
||||
new FontIcon(Feather.FOLDER)
|
||||
);
|
||||
File[] files = dir.listFiles();
|
||||
depth--;
|
||||
|
||||
if (files != null) {
|
||||
for (File f : files) {
|
||||
if (f.isDirectory() && depth > 0) {
|
||||
parent.getChildren().add(scan(f, depth));
|
||||
} else {
|
||||
var leaf = new TreeItem<>(
|
||||
f.getName(),
|
||||
new FontIcon(Feather.FILE)
|
||||
);
|
||||
parent.getChildren().add(leaf);
|
||||
}
|
||||
}
|
||||
parent.getChildren().sort(
|
||||
Comparator.comparing(TreeItem::getValue)
|
||||
);
|
||||
}
|
||||
|
||||
return parent;
|
||||
}
|
||||
}
|
||||
|
||||
var root = Helper.scan(new File(System.getProperty("user.home")), 3);
|
||||
|
||||
var tree = new TreeView<>(root);
|
||||
tree.getStyleClass().add(Styles.DENSE);
|
||||
tree.setShowRoot(false);
|
||||
//snippet_2:end
|
||||
|
||||
tree.setMaxWidth(Double.MAX_VALUE);
|
||||
tree.setMinHeight(350);
|
||||
HBox.setHgrow(tree, Priority.ALWAYS);
|
||||
|
||||
var box = new HBox(tree);
|
||||
var description = BBCodeParser.createFormattedText(
|
||||
"The [i]TreeView[/i] rows can be made more compact by cutting tree cell padding."
|
||||
);
|
||||
|
||||
return new ExampleBox(box, new Snippet(getClass(), 2), description);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private ExampleBox altIconExample() {
|
||||
//snippet_3:start
|
||||
var item1 = new TreeItem<>("Algorithms");
|
||||
item1.getChildren().setAll(
|
||||
new TreeItem<>("Bubble Sort"),
|
||||
new TreeItem<>("Selection Sort"),
|
||||
new TreeItem<>("Merge Sort")
|
||||
);
|
||||
|
||||
var item2 = new TreeItem<>("Data Structures");
|
||||
item2.getChildren().setAll(
|
||||
new TreeItem<>("Stack"),
|
||||
new TreeItem<>("Queue"),
|
||||
new TreeItem<>("Linked List")
|
||||
);
|
||||
|
||||
var item3 = new TreeItem<>("Artificial Intelligence");
|
||||
item3.getChildren().setAll(
|
||||
new TreeItem<>("Machine Learning"),
|
||||
new TreeItem<>("Natural Language Processing"),
|
||||
new TreeItem<>("Robotics")
|
||||
);
|
||||
|
||||
var root = new TreeItem<>("Computer Science");
|
||||
root.setExpanded(true);
|
||||
root.getChildren().addAll(item1, item2, item3);
|
||||
|
||||
var tree = new TreeView<>(root);
|
||||
tree.getStyleClass().add(Tweaks.ALT_ICON);
|
||||
//snippet_3:end
|
||||
|
||||
tree.setMaxWidth(Double.MAX_VALUE);
|
||||
tree.setMinHeight(250);
|
||||
HBox.setHgrow(tree, Priority.ALWAYS);
|
||||
|
||||
var box = new HBox(tree);
|
||||
var description = BBCodeParser.createFormattedText("""
|
||||
There's additional tweak [code]Tweaks.ALT_ICON[/code] to change the [i]TreeView[/i] \
|
||||
arrow icon."""
|
||||
);
|
||||
|
||||
return new ExampleBox(box, new Snippet(getClass(), 3), description);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private ExampleBox edge2EdgeExample() {
|
||||
//snippet_4:start
|
||||
var item1 = new TreeItem<>("Action");
|
||||
item1.getChildren().setAll(
|
||||
new TreeItem<>("Grand Theft Auto V"),
|
||||
new TreeItem<>("Call of Duty: Modern Warfare"),
|
||||
new TreeItem<>("Uncharted 4: A Thief's End")
|
||||
);
|
||||
|
||||
var item2 = new TreeItem<>("Adventure");
|
||||
item2.getChildren().setAll(
|
||||
new TreeItem<>("The Legend of Zelda: Breath of the Wild"),
|
||||
new TreeItem<>("Tomb Raider"),
|
||||
new TreeItem<>("Minecraft")
|
||||
);
|
||||
|
||||
var item3 = new TreeItem<>("Role-playing");
|
||||
item3.getChildren().setAll(
|
||||
new TreeItem<>("The Elder Scrolls V: Skyrim"),
|
||||
new TreeItem<>("Fallout 4"),
|
||||
new TreeItem<>("Final Fantasy XV")
|
||||
);
|
||||
|
||||
var root = new TreeItem<>("Games");
|
||||
root.setExpanded(true);
|
||||
root.getChildren().addAll(item1, item2, item3);
|
||||
|
||||
var tree = new TreeView<>(root);
|
||||
//snippet_4:end
|
||||
|
||||
tree.setMaxWidth(Double.MAX_VALUE);
|
||||
tree.setMinHeight(250);
|
||||
HBox.setHgrow(tree, Priority.ALWAYS);
|
||||
|
||||
var box = new HBox(tree);
|
||||
box.setStyle("""
|
||||
-fx-border-color: -color-accent-emphasis;
|
||||
-fx-border-width: 2px;"""
|
||||
);
|
||||
|
||||
var description = BBCodeParser.createFormattedText("""
|
||||
Use [code]Tweaks.EDGE_TO_EDGE[/code] style class to remove the [i]TreeView[/i] outer borders. \
|
||||
This is useful if you want to place the table into external container that already has its \
|
||||
own borders."""
|
||||
);
|
||||
|
||||
return new ExampleBox(box, new Snippet(getClass(), 4), description);
|
||||
}
|
||||
|
||||
private ImageView icon(String name) {
|
||||
var img = new Image(Resources.getResourceAsStream("images/devicons/" + name + ".png"));
|
||||
return new ImageView(img);
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
// Playground //
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
|
||||
private static final int MAX_TREE_DEPTH = 3;
|
||||
private static final int[] TREE_DICE = {-1, 0, 1};
|
||||
|
||||
private final BorderPane treeWrapper = new BorderPane();
|
||||
private final ComboBox<Example> exampleSelect = createExampleSelect();
|
||||
|
||||
private VBox playground() {
|
||||
var denseToggle = new ToggleSwitch("Dense");
|
||||
denseToggle.selectedProperty().addListener((obs, old, value) ->
|
||||
findDisplayedTree().ifPresent(tv -> Styles.toggleStyleClass(tv, Styles.DENSE))
|
||||
);
|
||||
|
||||
var showRootToggle = new ToggleSwitch("Show root");
|
||||
showRootToggle.selectedProperty().addListener((obs, old, val) ->
|
||||
findDisplayedTree().ifPresent(tv -> {
|
||||
if (val != null) {
|
||||
tv.setShowRoot(val);
|
||||
}
|
||||
})
|
||||
);
|
||||
showRootToggle.setSelected(true);
|
||||
|
||||
var altIconToggle = new ToggleSwitch("Alt icon");
|
||||
altIconToggle.selectedProperty().addListener((obs, old, val) ->
|
||||
findDisplayedTree().ifPresent(tv -> Styles.toggleStyleClass(tv, Tweaks.ALT_ICON))
|
||||
);
|
||||
|
||||
var edge2edgeToggle = new ToggleSwitch("Edge to edge");
|
||||
edge2edgeToggle.selectedProperty().addListener((obs, old, val) ->
|
||||
findDisplayedTree().ifPresent(tv -> Styles.toggleStyleClass(tv, Tweaks.EDGE_TO_EDGE))
|
||||
);
|
||||
|
||||
var toggles = new HBox(VGAP_20, denseToggle, showRootToggle, altIconToggle, edge2edgeToggle);
|
||||
toggles.setAlignment(Pos.CENTER);
|
||||
|
||||
var description = BBCodeParser.createFormattedText("""
|
||||
The playground demonstrates the most important [i]TreeView[/i] features \
|
||||
and also serves as an object for monkey testing."""
|
||||
);
|
||||
|
||||
VBox.setVgrow(treeWrapper, Priority.ALWAYS);
|
||||
|
||||
var playground = new VBox(
|
||||
VGAP_10,
|
||||
description,
|
||||
new Label("Select an example:"),
|
||||
exampleSelect,
|
||||
treeWrapper,
|
||||
toggles
|
||||
);
|
||||
playground.setMinHeight(500);
|
||||
|
||||
return playground;
|
||||
}
|
||||
|
||||
private ComboBox<Example> createExampleSelect() {
|
||||
var select = new ComboBox<Example>();
|
||||
select.setMaxWidth(Double.MAX_VALUE);
|
||||
select.getItems().setAll(Example.values());
|
||||
|
||||
select.getSelectionModel().selectedItemProperty().addListener((obs, old, val) -> {
|
||||
if (val == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
TreeView<String> newTree = createTree(val);
|
||||
|
||||
// copy existing style classes and properties to the new tree
|
||||
findDisplayedTree().ifPresent(tv -> {
|
||||
List<String> currentStyles = tv.getStyleClass();
|
||||
currentStyles.remove("tree-view");
|
||||
newTree.getStyleClass().addAll(currentStyles);
|
||||
newTree.setShowRoot(tv.isShowRoot());
|
||||
newTree.setDisable(tv.isDisable());
|
||||
});
|
||||
|
||||
treeWrapper.setCenter(newTree);
|
||||
});
|
||||
|
||||
select.setConverter(new StringConverter<>() {
|
||||
@Override
|
||||
public String toString(Example example) {
|
||||
return example == null ? "" : example.getName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Example fromString(String s) {
|
||||
return Example.find(s);
|
||||
}
|
||||
});
|
||||
|
||||
return select;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onRendered() {
|
||||
super.onRendered();
|
||||
exampleSelect.getSelectionModel().selectFirst();
|
||||
}
|
||||
|
||||
private Optional<TreeView<?>> findDisplayedTree() {
|
||||
return treeWrapper.getChildren().size() > 0
|
||||
? Optional.of((TreeView<?>) treeWrapper.getChildren().get(0))
|
||||
: Optional.empty();
|
||||
}
|
||||
|
||||
private TreeView<String> createTree(Example example) {
|
||||
switch (example) {
|
||||
case TEXT -> {
|
||||
return stringTree();
|
||||
}
|
||||
case GRAPHIC -> {
|
||||
return graphicTree();
|
||||
}
|
||||
case EDITABLE -> {
|
||||
return editableTree();
|
||||
}
|
||||
case CHECK_BOX -> {
|
||||
return checkBoxTree();
|
||||
}
|
||||
case CHOICE_BOX -> {
|
||||
return choiceBoxTree();
|
||||
}
|
||||
case COMBO_BOX -> {
|
||||
return comboBoxTree();
|
||||
}
|
||||
default -> throw new IllegalArgumentException("Unexpected enum value: " + example);
|
||||
}
|
||||
}
|
||||
|
||||
private <T> void generateTree(TreeItem<T> parent,
|
||||
Supplier<TreeItem<T>> itemFactory,
|
||||
int childLimit,
|
||||
int depth) {
|
||||
if (childLimit == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
var item = itemFactory.get();
|
||||
parent.getChildren().add(item);
|
||||
|
||||
TreeItem<T> nextParent = parent; // sibling
|
||||
int nextDepth = depth;
|
||||
int rand = TREE_DICE[RANDOM.nextInt(TREE_DICE.length)];
|
||||
|
||||
if (rand < 0 && parent.getParent() != null) { // go up
|
||||
nextParent = parent.getParent();
|
||||
nextDepth = --depth;
|
||||
}
|
||||
if (rand > 0 && depth < MAX_TREE_DEPTH) { // go down
|
||||
nextParent = item;
|
||||
nextDepth = ++depth;
|
||||
}
|
||||
|
||||
generateTree(nextParent, itemFactory, --childLimit, nextDepth);
|
||||
}
|
||||
|
||||
private TreeView<String> stringTree() {
|
||||
var root = new TreeItem<>("root");
|
||||
root.setExpanded(true);
|
||||
|
||||
var tree = new TreeView<String>();
|
||||
|
||||
generateTree(root, () -> new TreeItem<>(
|
||||
FAKER.internet().domainWord()), 30, 1
|
||||
);
|
||||
tree.setRoot(root);
|
||||
|
||||
return tree;
|
||||
}
|
||||
|
||||
private TreeView<String> graphicTree() {
|
||||
var root = new TreeItem<>("root", new FontIcon(Feather.FOLDER));
|
||||
root.setExpanded(true);
|
||||
|
||||
var tree = new TreeView<String>();
|
||||
|
||||
generateTree(root, () -> new TreeItem<>(
|
||||
FAKER.internet().domainWord(), new FontIcon(Feather.FILE)), 30, 1
|
||||
);
|
||||
tree.setRoot(root);
|
||||
|
||||
return tree;
|
||||
}
|
||||
|
||||
private TreeView<String> editableTree() {
|
||||
var root = new TreeItem<>("root", new FontIcon(Feather.FOLDER));
|
||||
root.setExpanded(true);
|
||||
|
||||
var tree = new TreeView<String>();
|
||||
tree.setCellFactory(TextFieldTreeCell.forTreeView());
|
||||
tree.setEditable(true);
|
||||
|
||||
generateTree(root, () -> new TreeItem<>(
|
||||
FAKER.internet().domainWord(), new FontIcon(Feather.FILE)), 30, 1
|
||||
);
|
||||
tree.setRoot(root);
|
||||
|
||||
return tree;
|
||||
}
|
||||
|
||||
// Note that CheckBoxTreeCell is incompatible with user graphic,
|
||||
// because it adds graphic inside .checkbox container. #javafx-bug
|
||||
private TreeView<String> checkBoxTree() {
|
||||
var root = new CheckBoxTreeItem<>("root");
|
||||
root.setExpanded(true);
|
||||
|
||||
var tree = new TreeView<String>();
|
||||
tree.setCellFactory(CheckBoxTreeCell.forTreeView());
|
||||
|
||||
generateTree(root, () -> new CheckBoxTreeItem<>(
|
||||
FAKER.internet().domainWord()), 30, 1
|
||||
);
|
||||
tree.setRoot(root);
|
||||
|
||||
return tree;
|
||||
}
|
||||
|
||||
private TreeView<String> choiceBoxTree() {
|
||||
var root = new TreeItem<>("root");
|
||||
root.setExpanded(true);
|
||||
|
||||
var tree = new TreeView<String>();
|
||||
tree.setCellFactory(ChoiceBoxTreeCell.forTreeView(
|
||||
generate(() -> FAKER.internet().domainWord(), 10).toArray(String[]::new)
|
||||
));
|
||||
tree.setEditable(true);
|
||||
|
||||
generateTree(root, () -> new TreeItem<>(
|
||||
FAKER.internet().domainWord()), 30, 1
|
||||
);
|
||||
tree.setRoot(root);
|
||||
|
||||
return tree;
|
||||
}
|
||||
|
||||
private TreeView<String> comboBoxTree() {
|
||||
var root = new TreeItem<>("root", new FontIcon(Feather.FOLDER));
|
||||
root.setExpanded(true);
|
||||
|
||||
var tree = new TreeView<String>();
|
||||
tree.setCellFactory(ComboBoxTreeCell.forTreeView(
|
||||
generate(() -> FAKER.internet().domainWord(), 10).toArray(String[]::new)
|
||||
));
|
||||
tree.setEditable(true);
|
||||
|
||||
generateTree(root, () -> new TreeItem<>(
|
||||
FAKER.internet().domainWord(), new FontIcon(Feather.FILE)), 30, 1
|
||||
);
|
||||
tree.setRoot(root);
|
||||
|
||||
return tree;
|
||||
}
|
||||
|
||||
private enum Example {
|
||||
|
||||
TEXT("Text"),
|
||||
GRAPHIC("Text with icons"),
|
||||
EDITABLE("TextFieldTreeCell"),
|
||||
CHECK_BOX("CheckBoxTreeCell"),
|
||||
CHOICE_BOX("ChoiceBoxTreeCell"),
|
||||
COMBO_BOX("ComboBoxTreeCell");
|
||||
|
||||
private final String name;
|
||||
|
||||
Example(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public static Example find(String name) {
|
||||
return Arrays.stream(Example.values())
|
||||
.filter(example -> Objects.equals(example.getName(), name))
|
||||
.findFirst()
|
||||
.orElse(null);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,19 +1,18 @@
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
|
||||
package atlantafx.sampler.page.components;
|
||||
package atlantafx.sampler.page.extras;
|
||||
|
||||
import atlantafx.base.theme.Styles;
|
||||
import atlantafx.base.util.BBCodeParser;
|
||||
import atlantafx.sampler.page.AbstractPage;
|
||||
import atlantafx.sampler.page.OutlinePage;
|
||||
import javafx.geometry.Insets;
|
||||
import javafx.geometry.Pos;
|
||||
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;
|
||||
|
||||
public class BBCodePage extends AbstractPage {
|
||||
public class BBCodePage extends OutlinePage {
|
||||
|
||||
public static final String NAME = "BBCode Markup";
|
||||
|
||||
@ -25,59 +24,59 @@ public class BBCodePage extends AbstractPage {
|
||||
public BBCodePage() {
|
||||
super();
|
||||
|
||||
setUserContent(new VBox(20,
|
||||
reference(),
|
||||
article()
|
||||
));
|
||||
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 :)""");
|
||||
|
||||
addSection("Text Type", textTypeReference());
|
||||
addSection("Text Style", textStyleReference());
|
||||
addSection("Subscript and Superscript", subscriptReference());
|
||||
addSection("Headings", headingReference());
|
||||
addSection("Links", linksReference());
|
||||
addSection("Lists", listsReference());
|
||||
addSection("Alignment", alignmentReference());
|
||||
addSection("Indentation", indentReference());
|
||||
addSection("Abbreviation", abbreviationReference());
|
||||
addSection("Separator", hrReference());
|
||||
addSection("Test Article", article());
|
||||
}
|
||||
|
||||
private VBox reference() {
|
||||
var root = new VBox(20);
|
||||
root.setAlignment(Pos.TOP_LEFT);
|
||||
|
||||
var header = BBCodeParser.createFormattedText("""
|
||||
[left][heading=1]Reference[/heading][/left]\
|
||||
|
||||
BBCode ("Bulletin Board Code") is a lightweight markup language used to format messages \
|
||||
in many Internet forum software. It was first introduced in 1998. The available tags of BBCode \
|
||||
are usually indicated by square brackets surrounding a keyword, and are parsed before \
|
||||
being translated into [s]HTML[/s] JavaFX layout.""");
|
||||
|
||||
root.getChildren().add(header);
|
||||
|
||||
private ReferenceBlock textTypeReference() {
|
||||
ReferenceBlock block = new ReferenceBlock(
|
||||
"Bold, italics, underline and strikethrough",
|
||||
"Makes the wrapped text bold, italic, underlined, or strikethrough."
|
||||
);
|
||||
block.addFormattedText("This is [b]bold[/b] text.");
|
||||
block.addFormattedText("This is [i]italic[/i] text.");
|
||||
block.addFormattedText("This is [u]underlined[/u] text.");
|
||||
block.addFormattedText("This is [s]strikethrough[/s] text.");
|
||||
root.getChildren().add(block);
|
||||
return block;
|
||||
}
|
||||
|
||||
block = new ReferenceBlock(
|
||||
"Text color, font and size",
|
||||
"Changes the color, font, or size of the wrapped text."
|
||||
);
|
||||
private ReferenceBlock textStyleReference() {
|
||||
ReferenceBlock block = new ReferenceBlock("Changes the color, font, or size of the wrapped text.");
|
||||
block.addFormattedText("This is [color=red]red[/color] text.");
|
||||
block.addFormattedText("This is [color=-color-accent-emphasis]accent[/color] text.");
|
||||
block.addFormattedText("This is [label style='-fx-background-color: yellow']background[/label] color.");
|
||||
block.addFormattedText(
|
||||
"This is [label style='-fx-background-color: -color-warning-muted']background[/label] color.");
|
||||
block.addFormattedText("This is [font=monospace]monospaced[/font] font.");
|
||||
block.addFormattedText("This is a [code]public[/code] Java keyword.");
|
||||
block.addFormattedText("This is [small]small[/small] and [size=1.5em]big[/size] text.");
|
||||
root.getChildren().add(block);
|
||||
return block;
|
||||
}
|
||||
|
||||
block = new ReferenceBlock(
|
||||
"Subscript and superscript",
|
||||
private ReferenceBlock subscriptReference() {
|
||||
ReferenceBlock block = new ReferenceBlock(
|
||||
"A text that is set slightly below or above the normal line of type, respectively."
|
||||
);
|
||||
block.addLayout("log[sub][small]2[/small][/sub](256) = 8");
|
||||
block.addLayout("10[sup][small]2[/small][/sup] = 100");
|
||||
root.getChildren().add(block);
|
||||
return block;
|
||||
}
|
||||
|
||||
block = new ReferenceBlock(
|
||||
"Headings levels 1 to 5",
|
||||
"Marks text as a structured heading."
|
||||
);
|
||||
private ReferenceBlock headingReference() {
|
||||
ReferenceBlock block = new ReferenceBlock("Marks text as a structured heading.");
|
||||
block.addFormattedText("""
|
||||
[heading=1]H1 headline[/heading]
|
||||
|
||||
@ -98,12 +97,11 @@ public class BBCodePage extends AbstractPage {
|
||||
[caption]Caption[/caption]
|
||||
|
||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed non purus a nisi ornare facilisis.""");
|
||||
root.getChildren().add(block);
|
||||
return block;
|
||||
}
|
||||
|
||||
block = new ReferenceBlock(
|
||||
"Lists",
|
||||
"Displays a bulleted or numbered list."
|
||||
);
|
||||
private ReferenceBlock listsReference() {
|
||||
ReferenceBlock block = new ReferenceBlock("Displays a bulleted or numbered list.");
|
||||
block.addLayout("""
|
||||
[ul]
|
||||
[li]Entry 1[/li]
|
||||
@ -129,56 +127,51 @@ public class BBCodePage extends AbstractPage {
|
||||
[li]Entry 1[/li]
|
||||
[li]Entry 2[/li]
|
||||
[/ol]""");
|
||||
root.getChildren().add(block);
|
||||
return block;
|
||||
}
|
||||
|
||||
block = new ReferenceBlock(
|
||||
"Linking",
|
||||
private ReferenceBlock linksReference() {
|
||||
ReferenceBlock block = new ReferenceBlock(
|
||||
"Links the wrapped text to the specified web page or email address."
|
||||
);
|
||||
block.addFormattedText("[url=https://www.example.com]Go to example.com[/url]");
|
||||
block.addFormattedText("[email=johndoe@example.com]Email me[/email]");
|
||||
root.getChildren().add(block);
|
||||
return block;
|
||||
}
|
||||
|
||||
block = new ReferenceBlock(
|
||||
"Alignment",
|
||||
"Changes the alignment of the wrapped text."
|
||||
);
|
||||
private ReferenceBlock alignmentReference() {
|
||||
ReferenceBlock block = new ReferenceBlock("Changes the alignment of the wrapped text.");
|
||||
block.addLayout("[left]Left-aligned[/left]");
|
||||
block.addLayout("[center]Center-aligned[/center]");
|
||||
block.addLayout("[right]Right-aligned[/right]");
|
||||
block.addLayout("[align=center]Center-aligned[/align]");
|
||||
root.getChildren().add(block);
|
||||
return block;
|
||||
}
|
||||
|
||||
block = new ReferenceBlock(
|
||||
"Text indent",
|
||||
"Indents the wrapped text."
|
||||
);
|
||||
private ReferenceBlock indentReference() {
|
||||
ReferenceBlock block = new ReferenceBlock("Indents the wrapped text.");
|
||||
block.addLayout("[indent]Indented text[/indent]");
|
||||
block.addLayout("[indent=3]More indented[/indent]");
|
||||
root.getChildren().add(block);
|
||||
return block;
|
||||
}
|
||||
|
||||
block = new ReferenceBlock(
|
||||
"Horizontal line",
|
||||
"A horizontal separator line."
|
||||
);
|
||||
private ReferenceBlock hrReference() {
|
||||
ReferenceBlock block = new ReferenceBlock("A horizontal separator line.");
|
||||
block.addLayout("Default line: [hr/]");
|
||||
block.addLayout("Thick line: [hr=5/]");
|
||||
root.getChildren().add(block);
|
||||
return block;
|
||||
}
|
||||
|
||||
block = new ReferenceBlock(
|
||||
"Abbreviation",
|
||||
"An abbreviation, with mouse-over expansion."
|
||||
);
|
||||
private ReferenceBlock abbreviationReference() {
|
||||
ReferenceBlock block = new ReferenceBlock("An abbreviation, with mouse-over expansion.");
|
||||
block.addLayout("[abbr='on hover text']text[/abbr]");
|
||||
block.addLayout("[abbr]text[/abbr]");
|
||||
root.getChildren().add(block);
|
||||
|
||||
return root;
|
||||
return block;
|
||||
}
|
||||
|
||||
private VBox article() {
|
||||
var article = """
|
||||
[left][heading=1]Example[/heading][/left]\
|
||||
[left][heading=1]JavaFX - Overview[/heading][/left]\
|
||||
|
||||
[b]JavaFX[/b] is a Java library used to build Rich Internet Applications. \
|
||||
The applications written using this library can run consistently across multiple \
|
||||
@ -208,7 +201,8 @@ public class BBCodePage extends AbstractPage {
|
||||
full-featured application.[/li]\
|
||||
[li][b]CSS like Styling[/b] − JavaFX provides a CSS like styling. By using this, you can improve \
|
||||
the design of your application with a simple knowledge of CSS.[/li]\
|
||||
[li][b]Canvas and Printing[/b] − JavaFX provides [label style=-fx-background-color:yellow]Canvas[/label], \
|
||||
[li][b]Canvas and Printing[/b] − JavaFX provides \
|
||||
[label style=-fx-background-color:-color-warning-muted]Canvas[/label], \
|
||||
an immediate mode style of rendering API. Within the package [font=monospace]javafx.scene.canvas[/font] \
|
||||
it holds a set of classes for canvas, using which we can draw directly within an area of the \
|
||||
JavaFX scene. JavaFX also provides classes for Printing purposes in the package javafx.print.[/li]\
|
||||
@ -231,10 +225,10 @@ public class BBCodePage extends AbstractPage {
|
||||
private final VBox leftBox;
|
||||
private final VBox rightBox;
|
||||
|
||||
public ReferenceBlock(String title, String description) {
|
||||
public ReferenceBlock(String description) {
|
||||
super();
|
||||
|
||||
var titleLabel = new Label(title);
|
||||
var titleLabel = new Label(description);
|
||||
titleLabel.getStyleClass().add(Styles.TITLE_4);
|
||||
|
||||
leftBox = new VBox(15);
|
||||
@ -260,7 +254,7 @@ public class BBCodePage extends AbstractPage {
|
||||
splitBox.setSpacing(20);
|
||||
|
||||
setSpacing(10);
|
||||
getChildren().addAll(titleLabel, new Label(description), splitBox);
|
||||
getChildren().addAll(new Label(description), splitBox);
|
||||
}
|
||||
|
||||
public void addFormattedText(String markup) {
|
@ -0,0 +1,182 @@
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
|
||||
package atlantafx.sampler.page.extras;
|
||||
|
||||
import atlantafx.base.controls.Breadcrumbs;
|
||||
import atlantafx.base.controls.Breadcrumbs.BreadCrumbItem;
|
||||
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 javafx.geometry.Pos;
|
||||
import javafx.scene.control.Button;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.layout.HBox;
|
||||
import org.kordamp.ikonli.javafx.FontIcon;
|
||||
import org.kordamp.ikonli.material2.Material2AL;
|
||||
|
||||
public class BreadcrumbsPage extends OutlinePage {
|
||||
|
||||
public static final String NAME = "Breadcrumbs";
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return NAME;
|
||||
}
|
||||
|
||||
public BreadcrumbsPage() {
|
||||
super();
|
||||
|
||||
addFormattedText("""
|
||||
Represents a bread crumb bar. This control is useful to visualize and navigate \
|
||||
a hierarchical path structure, such as file systems."""
|
||||
);
|
||||
addSection("Usage", usageExample());
|
||||
addSection("Custom Item", customItemExample());
|
||||
addSection("Custom Divider", customDividerExample());
|
||||
}
|
||||
|
||||
private ExampleBox usageExample() {
|
||||
//snippet_1:start
|
||||
var items = generate(() -> FAKER.science().element(), 4);
|
||||
BreadCrumbItem<String> root = Breadcrumbs.buildTreeModel(
|
||||
items.toArray(String[]::new)
|
||||
);
|
||||
|
||||
var crumbs = new Breadcrumbs<>(root);
|
||||
crumbs.setSelectedCrumb(getTreeItemByIndex(root, 2));
|
||||
//snippet_1:end
|
||||
|
||||
var nextBtn = new Button("Next");
|
||||
nextBtn.getStyleClass().addAll(Styles.ACCENT);
|
||||
nextBtn.setOnAction(e -> {
|
||||
var selected = crumbs.getSelectedCrumb();
|
||||
if (selected.getChildren().size() > 0) {
|
||||
var next = selected.getChildren().get(0);
|
||||
crumbs.setSelectedCrumb((BreadCrumbItem<String>) next);
|
||||
}
|
||||
});
|
||||
|
||||
crumbs.selectedCrumbProperty().addListener((obs, old, val) -> {
|
||||
if (val == null) {
|
||||
return;
|
||||
}
|
||||
nextBtn.setDisable(val.getChildren().isEmpty());
|
||||
});
|
||||
|
||||
var box = new HBox(40, nextBtn, crumbs);
|
||||
box.setAlignment(Pos.CENTER_LEFT);
|
||||
|
||||
var description = BBCodeParser.createFormattedText("""
|
||||
The [i]BreadCrumbs[/i] uses the [i]TreeItem[/i] API to maintain its model. \
|
||||
You can create a tree model from a flat list by using the static \
|
||||
[code]buildTreeModel()[/code] method."""
|
||||
);
|
||||
|
||||
return new ExampleBox(box, new Snippet(getClass(), 1), description);
|
||||
}
|
||||
|
||||
private ExampleBox customItemExample() {
|
||||
//snippet_2:start
|
||||
var items = generate(() -> FAKER.science().element(), 4);
|
||||
BreadCrumbItem<String> root = Breadcrumbs.buildTreeModel(
|
||||
items.toArray(String[]::new)
|
||||
);
|
||||
|
||||
var crumbs = new Breadcrumbs<>(root);
|
||||
crumbs.setCrumbFactory(crumb -> {
|
||||
var btn = new Button(crumb.getValue(), new FontIcon(randomIcon()));
|
||||
btn.getStyleClass().add(Styles.FLAT);
|
||||
btn.setFocusTraversable(false);
|
||||
return btn;
|
||||
});
|
||||
crumbs.setSelectedCrumb(getTreeItemByIndex(root, 2));
|
||||
//snippet_2:end
|
||||
|
||||
var nextBtn = new Button("Next");
|
||||
nextBtn.getStyleClass().addAll(Styles.ACCENT);
|
||||
nextBtn.setOnAction(e -> {
|
||||
var selected = crumbs.getSelectedCrumb();
|
||||
if (selected.getChildren().size() > 0) {
|
||||
var next = selected.getChildren().get(0);
|
||||
crumbs.setSelectedCrumb((BreadCrumbItem<String>) next);
|
||||
}
|
||||
});
|
||||
|
||||
crumbs.selectedCrumbProperty().addListener((obs, old, val) -> {
|
||||
if (val == null) {
|
||||
return;
|
||||
}
|
||||
nextBtn.setDisable(val.getChildren().isEmpty());
|
||||
});
|
||||
|
||||
var box = new HBox(40, nextBtn, crumbs);
|
||||
box.setAlignment(Pos.CENTER_LEFT);
|
||||
|
||||
var description = BBCodeParser.createFormattedText("""
|
||||
By default, the [i]Breadcrumbs[/i] uses [i]Hyperlink[/i] to represent \
|
||||
its items. If you want to use a different kind of item, you can provide \
|
||||
a [code]crumbFactory[/code] to insert an arbitrary [i]Node[/i]."""
|
||||
);
|
||||
|
||||
return new ExampleBox(box, new Snippet(getClass(), 2), description);
|
||||
}
|
||||
|
||||
private ExampleBox customDividerExample() {
|
||||
//snippet_3:start
|
||||
var items = generate(() -> FAKER.science().element(), 4);
|
||||
BreadCrumbItem<String> root = Breadcrumbs.buildTreeModel(
|
||||
items.toArray(String[]::new)
|
||||
);
|
||||
|
||||
var crumbs = new Breadcrumbs<>(root);
|
||||
crumbs.setDividerFactory(item -> {
|
||||
if (item == null) {
|
||||
return new Label("", new FontIcon(Material2AL.HOME));
|
||||
}
|
||||
return !item.isLast()
|
||||
? new Label("", new FontIcon(Material2AL.CHEVRON_RIGHT))
|
||||
: null;
|
||||
});
|
||||
crumbs.setSelectedCrumb(getTreeItemByIndex(root, 2));
|
||||
//snippet_3:end
|
||||
|
||||
var nextBtn = new Button("Next");
|
||||
nextBtn.getStyleClass().addAll(Styles.ACCENT);
|
||||
nextBtn.setOnAction(e -> {
|
||||
var selected = crumbs.getSelectedCrumb();
|
||||
if (selected.getChildren().size() > 0) {
|
||||
var next = selected.getChildren().get(0);
|
||||
crumbs.setSelectedCrumb((BreadCrumbItem<String>) next);
|
||||
}
|
||||
});
|
||||
|
||||
crumbs.selectedCrumbProperty().addListener((obs, old, val) -> {
|
||||
if (val == null) {
|
||||
return;
|
||||
}
|
||||
nextBtn.setDisable(val.getChildren().isEmpty());
|
||||
});
|
||||
|
||||
var box = new HBox(40, nextBtn, crumbs);
|
||||
box.setAlignment(Pos.CENTER_LEFT);
|
||||
|
||||
var description = BBCodeParser.createFormattedText("""
|
||||
Similarly, you can customize the [i]Breadcrumbs[/i] divider by \
|
||||
providing a [code]dividerFactory[/code]."""
|
||||
);
|
||||
|
||||
return new ExampleBox(box, new Snippet(getClass(), 3), description);
|
||||
}
|
||||
|
||||
private <T> BreadCrumbItem<T> getTreeItemByIndex(BreadCrumbItem<T> node, int index) {
|
||||
var counter = index;
|
||||
var current = node;
|
||||
while (counter > 0 && current.getParent() != null) {
|
||||
current = (BreadCrumbItem<T>) current.getParent();
|
||||
counter--;
|
||||
}
|
||||
return current;
|
||||
}
|
||||
}
|
@ -0,0 +1,167 @@
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
|
||||
package atlantafx.sampler.page.extras;
|
||||
|
||||
import atlantafx.base.controls.InlineDatePicker;
|
||||
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 atlantafx.sampler.theme.CSSFragment;
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalTime;
|
||||
import java.time.ZoneId;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import javafx.animation.Animation;
|
||||
import javafx.animation.KeyFrame;
|
||||
import javafx.animation.Timeline;
|
||||
import javafx.scene.control.DateCell;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.layout.HBox;
|
||||
import javafx.scene.layout.VBox;
|
||||
import javafx.util.Duration;
|
||||
|
||||
public class CalendarPage extends OutlinePage {
|
||||
|
||||
public static final String NAME = "Calendar";
|
||||
private static final LocalDate TODAY = LocalDate.now(ZoneId.systemDefault());
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return NAME;
|
||||
}
|
||||
|
||||
public CalendarPage() {
|
||||
super();
|
||||
|
||||
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 \
|
||||
within a popup window."""
|
||||
);
|
||||
addSection("Usage", usageExample());
|
||||
addSection("No past dates", noPastDatesExample());
|
||||
addSection("User slots", clockExample());
|
||||
addSection("Style", styleExample());
|
||||
}
|
||||
|
||||
private ExampleBox usageExample() {
|
||||
//snippet_1:start
|
||||
var dp = new InlineDatePicker(TODAY);
|
||||
dp.setShowWeekNumbers(true);
|
||||
//snippet_1:end
|
||||
|
||||
var box = new HBox(dp);
|
||||
var description = BBCodeParser.createFormattedText("""
|
||||
In the default state, no date is selected. You can modify this behavior \
|
||||
either by using the constructor or by utilizing the [font=monospace]setValue()[/font] \
|
||||
method.""");
|
||||
|
||||
return new ExampleBox(box, new Snippet(getClass(), 1), description);
|
||||
}
|
||||
|
||||
private ExampleBox noPastDatesExample() {
|
||||
//snippet_2:start
|
||||
class FutureDateCell extends DateCell {
|
||||
|
||||
@Override
|
||||
public void updateItem(LocalDate date, boolean empty) {
|
||||
super.updateItem(date, empty);
|
||||
setDisable(empty || date.isBefore(TODAY));
|
||||
}
|
||||
}
|
||||
|
||||
var dp = new InlineDatePicker(TODAY);
|
||||
dp.setDayCellFactory(c -> new FutureDateCell());
|
||||
//snippet_2:end
|
||||
|
||||
var box = new HBox(dp);
|
||||
var description = BBCodeParser.createFormattedText("""
|
||||
This example demonstrates how you can disable past dates in the [i]Calendar[/i]."""
|
||||
);
|
||||
|
||||
return new ExampleBox(box, new Snippet(getClass(), 2), description);
|
||||
}
|
||||
|
||||
private ExampleBox clockExample() {
|
||||
final var style = """
|
||||
-fx-border-width: 0 0 0.5 0;
|
||||
-fx-border-color: -color-border-default;""";
|
||||
|
||||
//snippet_3:start
|
||||
class Clock extends VBox {
|
||||
|
||||
static final DateTimeFormatter DATE_FORMATTER =
|
||||
DateTimeFormatter.ofPattern("EEEE, LLLL dd, yyyy");
|
||||
static final DateTimeFormatter TIME_FORMATTER =
|
||||
DateTimeFormatter.ofPattern("HH:mm:ss");
|
||||
|
||||
public Clock() {
|
||||
var clockLbl = new Label(TIME_FORMATTER.format(
|
||||
LocalTime.now(ZoneId.systemDefault()))
|
||||
);
|
||||
clockLbl.getStyleClass().add(Styles.TITLE_2);
|
||||
|
||||
var dateLbl = new Label(DATE_FORMATTER.format(
|
||||
LocalDate.now(ZoneId.systemDefault()))
|
||||
);
|
||||
|
||||
// -fx-border-width: 0 0 0.5 0;
|
||||
// -fx-border-color: -color-border-default;
|
||||
setStyle(style);
|
||||
setSpacing(10);
|
||||
getChildren().setAll(clockLbl, dateLbl);
|
||||
|
||||
var t = new Timeline(new KeyFrame(
|
||||
Duration.seconds(1),
|
||||
e -> {
|
||||
var time = LocalTime.now(ZoneId.systemDefault());
|
||||
clockLbl.setText(TIME_FORMATTER.format(time));
|
||||
}
|
||||
));
|
||||
t.setCycleCount(Animation.INDEFINITE);
|
||||
t.playFromStart();
|
||||
}
|
||||
}
|
||||
|
||||
var dp = new InlineDatePicker(TODAY);
|
||||
dp.setTopNode(new Clock());
|
||||
dp.setShowWeekNumbers(true);
|
||||
//snippet_3:end
|
||||
|
||||
var box = new HBox(dp);
|
||||
var description = BBCodeParser.createFormattedText("""
|
||||
The [i]Calendar[/i] comes equipped with two slots (top and bottom) where \
|
||||
users can place their own content. For example, you can use these slots to \
|
||||
display a clock widget on top of the [i]Calendar[/i]."""
|
||||
);
|
||||
|
||||
return new ExampleBox(box, new Snippet(getClass(), 3), description);
|
||||
}
|
||||
|
||||
private ExampleBox styleExample() {
|
||||
var style = """
|
||||
.date-picker-popup {
|
||||
-color-date-border: -color-accent-emphasis;
|
||||
-color-date-month-year-bg: -color-accent-emphasis;
|
||||
-color-date-month-year-fg: -color-fg-emphasis;
|
||||
}""";
|
||||
//snippet_4:start
|
||||
var dp = new InlineDatePicker(TODAY);
|
||||
dp.setShowWeekNumbers(true);
|
||||
|
||||
// -color-date-border: -color-accent-emphasis;
|
||||
// -color-date-month-year-bg: -color-accent-emphasis;
|
||||
// -color-date-month-year-fg: -color-fg-emphasis;
|
||||
new CSSFragment(style).addTo(dp);
|
||||
//snippet_4:end
|
||||
|
||||
var box = new HBox(dp);
|
||||
var description = BBCodeParser.createFormattedText("""
|
||||
You can alter the style of the [i]Calendar[/i] by using looked-up color variables."""
|
||||
);
|
||||
|
||||
return new ExampleBox(box, new Snippet(getClass(), 4), description);
|
||||
}
|
||||
}
|
@ -0,0 +1,172 @@
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
|
||||
package atlantafx.sampler.page.extras;
|
||||
|
||||
import atlantafx.base.controls.CustomTextField;
|
||||
import atlantafx.base.controls.MaskTextField;
|
||||
import atlantafx.base.controls.PasswordTextField;
|
||||
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.time.LocalTime;
|
||||
import java.time.ZoneId;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.time.format.DateTimeParseException;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.scene.Cursor;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.layout.HBox;
|
||||
import javafx.scene.layout.VBox;
|
||||
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 static final String NAME = "CustomTextField";
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return NAME;
|
||||
}
|
||||
|
||||
public CustomTextFieldPage() {
|
||||
super();
|
||||
|
||||
addFormattedText("""
|
||||
A base class for placing nodes inside the text field itself, without being \
|
||||
on top of the users typed-in text."""
|
||||
);
|
||||
addSection("Usage", usageExample());
|
||||
addSection("Color", colorExample());
|
||||
addSection("Password", passwordSample());
|
||||
addSection("Input Mask", inputMaskExample());
|
||||
}
|
||||
|
||||
private ExampleBox usageExample() {
|
||||
//snippet_1:start
|
||||
var tf1 = new CustomTextField();
|
||||
tf1.setPromptText("Prompt text");
|
||||
tf1.setRight(new FontIcon(Feather.X));
|
||||
tf1.setPrefWidth(150);
|
||||
|
||||
var tf2 = new CustomTextField();
|
||||
tf2.setPromptText("Prompt text");
|
||||
tf2.setLeft(new FontIcon(Feather.MAP_PIN));
|
||||
tf2.setPrefWidth(150);
|
||||
|
||||
var tf3 = new CustomTextField("Text");
|
||||
tf3.setLeft(new FontIcon(Feather.MAP_PIN));
|
||||
tf3.setRight(new FontIcon(Feather.X));
|
||||
tf3.setPrefWidth(150);
|
||||
//snippet_1:end
|
||||
|
||||
var box = new HBox(30, tf1, tf2, tf3);
|
||||
var description = BBCodeParser.createFormattedText("""
|
||||
You can add arbitrary nodes to the [i]CustomTextField[/i] by setting \
|
||||
the [code]left[/code] and [code]right[/code] properties, respectively."""
|
||||
);
|
||||
|
||||
return new ExampleBox(box, new Snippet(getClass(), 1), description);
|
||||
}
|
||||
|
||||
private ExampleBox colorExample() {
|
||||
//snippet_2:start
|
||||
var tf1 = new CustomTextField("Text");
|
||||
tf1.pseudoClassStateChanged(Styles.STATE_SUCCESS, true);
|
||||
tf1.setRight(new FontIcon(Feather.X));
|
||||
tf1.setPrefWidth(150);
|
||||
|
||||
var tf2 = new CustomTextField();
|
||||
tf2.pseudoClassStateChanged(Styles.STATE_DANGER, true);
|
||||
tf2.setLeft(new FontIcon(Feather.MAP_PIN));
|
||||
tf2.setPrefWidth(150);
|
||||
//snippet_2:end
|
||||
|
||||
var box = new HBox(30, tf1, tf2);
|
||||
var description = BBCodeParser.createFormattedText("""
|
||||
Use [code]Styles.STATE_SUCCESS[/code] or [code]Styles.STATE_DANGER[/code] \
|
||||
pseudo-classes to change the CustomTextField/i] color. This especially useful to indicate \
|
||||
the validation result."""
|
||||
);
|
||||
|
||||
return new ExampleBox(box, new Snippet(getClass(), 2), description);
|
||||
}
|
||||
|
||||
private ExampleBox passwordSample() {
|
||||
//snippet_3:start
|
||||
var tf = new PasswordTextField("qwerty");
|
||||
tf.setPrefWidth(250);
|
||||
|
||||
var icon = new FontIcon(Feather.EYE_OFF);
|
||||
icon.setCursor(Cursor.HAND);
|
||||
icon.setOnMouseClicked(e -> {
|
||||
icon.setIconCode(tf.isRevealPassword()
|
||||
? Feather.EYE_OFF : Feather.EYE
|
||||
);
|
||||
tf.setRevealPassword(!tf.isRevealPassword());
|
||||
});
|
||||
tf.setRight(icon);
|
||||
//snippet_3:end
|
||||
|
||||
var box = new HBox(30, tf);
|
||||
var description = BBCodeParser.createFormattedText("""
|
||||
The [i]PasswordTextField[/i] is a variant of [i]CustomTextField[/i] that allows users \
|
||||
to input passwords. Unlike the standard JavaFX [i]PasswordField[/i], the content of \
|
||||
the [i]PasswordTextField[/i] can be revealed."""
|
||||
);
|
||||
|
||||
return new ExampleBox(box, new Snippet(getClass(), 3), description);
|
||||
}
|
||||
|
||||
private ExampleBox inputMaskExample() {
|
||||
//snippet_4:start
|
||||
var phoneField = new MaskTextField("(999) 999 99 99");
|
||||
phoneField.setPromptText("(999) 999 99 99");
|
||||
phoneField.setLeft(new FontIcon(Material2OutlinedMZ.PHONE));
|
||||
phoneField.setPrefWidth(180);
|
||||
|
||||
var cardField = new MaskTextField("9999-9999-9999-9999");
|
||||
cardField.setLeft(new FontIcon(Material2OutlinedAL.CREDIT_CARD));
|
||||
cardField.setPrefWidth(200);
|
||||
|
||||
var timeFormatter = DateTimeFormatter.ofPattern("HH:mm");
|
||||
var timeField = new MaskTextField("29:59");
|
||||
timeField.setText(
|
||||
LocalTime.now(ZoneId.systemDefault()).format(timeFormatter)
|
||||
);
|
||||
timeField.setLeft(new FontIcon(Material2OutlinedMZ.TIMER));
|
||||
timeField.setPrefWidth(120);
|
||||
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) {
|
||||
timeField.pseudoClassStateChanged(Styles.STATE_DANGER, true);
|
||||
}
|
||||
}
|
||||
});
|
||||
//snippet_4:end
|
||||
|
||||
var box = new HBox(
|
||||
HGAP_20,
|
||||
new VBox(5, new Label("Phone Number"), phoneField),
|
||||
new VBox(5, new Label("Bank Card"), cardField),
|
||||
new VBox(5, new Label("Time"), timeField)
|
||||
);
|
||||
box.setAlignment(Pos.CENTER_LEFT);
|
||||
|
||||
var description = BBCodeParser.createFormattedText("""
|
||||
The [i]MaskTextField[/i] allows to restrict user input by applying a \
|
||||
position-based mask. This is useful for editing cases where the input \
|
||||
string has a fixed length and each character can be restricted based on its position."""
|
||||
);
|
||||
|
||||
return new ExampleBox(box, new Snippet(getClass(), 4), description);
|
||||
}
|
||||
}
|
@ -0,0 +1,161 @@
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
|
||||
package atlantafx.sampler.page.extras;
|
||||
|
||||
import atlantafx.base.layout.DeckPane;
|
||||
import atlantafx.base.theme.Styles;
|
||||
import atlantafx.sampler.Resources;
|
||||
import atlantafx.sampler.page.AbstractPage;
|
||||
import java.util.function.Supplier;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.control.Button;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.control.RadioButton;
|
||||
import javafx.scene.control.ToggleGroup;
|
||||
import javafx.scene.image.Image;
|
||||
import javafx.scene.image.ImageView;
|
||||
import javafx.scene.layout.BorderPane;
|
||||
import javafx.scene.layout.HBox;
|
||||
import javafx.scene.layout.VBox;
|
||||
import javafx.util.Duration;
|
||||
import org.kordamp.ikonli.javafx.FontIcon;
|
||||
import org.kordamp.ikonli.material2.Material2AL;
|
||||
import org.kordamp.ikonli.material2.Material2MZ;
|
||||
|
||||
public class DeckPanePage extends AbstractPage {
|
||||
|
||||
public static final String NAME = "DeckPane";
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return NAME;
|
||||
}
|
||||
|
||||
public DeckPanePage() {
|
||||
super();
|
||||
|
||||
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.""");
|
||||
addNode(createGallery());
|
||||
}
|
||||
|
||||
private Node createGallery() {
|
||||
var galleryPane = new BorderPane();
|
||||
galleryPane.setMinSize(570, 400);
|
||||
galleryPane.setMaxSize(570, 400);
|
||||
|
||||
var image1 = new ImageView(new Image(
|
||||
Resources.getResourceAsStream("images/gallery/kush-dwivedi-unsplash.jpg"))
|
||||
);
|
||||
image1.setFitWidth(450);
|
||||
image1.setFitHeight(300);
|
||||
|
||||
var image2 = new ImageView(new Image(
|
||||
Resources.getResourceAsStream("images/gallery/markus-spiske-unsplash.jpg"))
|
||||
);
|
||||
image2.setFitWidth(450);
|
||||
image2.setFitHeight(300);
|
||||
|
||||
var image3 = new ImageView(new Image(
|
||||
Resources.getResourceAsStream("images/gallery/r0m0_4-unsplash.jpg"))
|
||||
);
|
||||
image3.setFitWidth(450);
|
||||
image3.setFitHeight(300);
|
||||
|
||||
// ~
|
||||
|
||||
var transitionTypeBox = new VBox(VGAP_10);
|
||||
transitionTypeBox.setAlignment(Pos.CENTER);
|
||||
|
||||
var label = new Label("Transition Type");
|
||||
label.getStyleClass().addAll(Styles.TEXT_CAPTION, Styles.TEXT_MUTED);
|
||||
|
||||
var swipeRadio = new RadioButton("Swipe");
|
||||
swipeRadio.setSelected(true);
|
||||
swipeRadio.setUserData(-1);
|
||||
|
||||
var slideRadio = new RadioButton("Slide");
|
||||
slideRadio.setUserData(1);
|
||||
|
||||
var transitionType = new ToggleGroup();
|
||||
transitionType.getToggles().setAll(swipeRadio, slideRadio);
|
||||
|
||||
var radioBox = new HBox(HGAP_20, swipeRadio, slideRadio);
|
||||
radioBox.setAlignment(Pos.CENTER);
|
||||
|
||||
transitionTypeBox.getChildren().setAll(label, radioBox);
|
||||
|
||||
// ~
|
||||
|
||||
var deck = new DeckPane(image1, image2, image3);
|
||||
deck.setMinSize(450, 300);
|
||||
deck.setMaxSize(450, 300);
|
||||
deck.setAnimationDuration(Duration.millis(350));
|
||||
galleryPane.setCenter(deck);
|
||||
|
||||
// circularly returns the next item from the deck
|
||||
Supplier<Node> nextItem = () -> {
|
||||
var next = (deck.getChildren().indexOf(deck.getTopNode()) + 1)
|
||||
% deck.getChildren().size();
|
||||
return deck.getChildren().get(next);
|
||||
};
|
||||
|
||||
var topBtn = new Button("", new FontIcon(Material2MZ.NORTH));
|
||||
topBtn.setOnAction(e -> {
|
||||
if ((int) transitionType.getSelectedToggle().getUserData() < 0) {
|
||||
deck.swipeUp(nextItem.get());
|
||||
} else {
|
||||
deck.slideUp(nextItem.get());
|
||||
}
|
||||
});
|
||||
galleryPane.setTop(topBtn);
|
||||
BorderPane.setAlignment(topBtn, Pos.CENTER);
|
||||
|
||||
var rightBtn = new Button("", new FontIcon(Material2AL.EAST));
|
||||
rightBtn.setOnAction(e -> {
|
||||
if ((int) transitionType.getSelectedToggle().getUserData() < 0) {
|
||||
deck.swipeRight(nextItem.get());
|
||||
} else {
|
||||
deck.slideRight(nextItem.get());
|
||||
}
|
||||
});
|
||||
galleryPane.setRight(rightBtn);
|
||||
BorderPane.setAlignment(rightBtn, Pos.CENTER);
|
||||
|
||||
var bottomBtn = new Button("", new FontIcon(Material2MZ.SOUTH));
|
||||
bottomBtn.setOnAction(e -> {
|
||||
if ((int) transitionType.getSelectedToggle().getUserData() < 0) {
|
||||
deck.swipeDown(nextItem.get());
|
||||
} else {
|
||||
deck.slideDown(nextItem.get());
|
||||
}
|
||||
});
|
||||
galleryPane.setBottom(bottomBtn);
|
||||
BorderPane.setAlignment(bottomBtn, Pos.CENTER);
|
||||
|
||||
var leftBtn = new Button("", new FontIcon(Material2MZ.WEST));
|
||||
leftBtn.setOnAction(e -> {
|
||||
if ((int) transitionType.getSelectedToggle().getUserData() < 0) {
|
||||
deck.swipeLeft(nextItem.get());
|
||||
} else {
|
||||
deck.slideLeft(nextItem.get());
|
||||
}
|
||||
});
|
||||
galleryPane.setLeft(leftBtn);
|
||||
BorderPane.setAlignment(leftBtn, Pos.CENTER);
|
||||
|
||||
// ~
|
||||
var root = new VBox(VGAP_20, galleryPane, transitionTypeBox);
|
||||
root.setAlignment(Pos.CENTER);
|
||||
|
||||
return root;
|
||||
}
|
||||
}
|
224
sampler/src/main/java/atlantafx/sampler/page/extras/InputGroupPage.java
Executable file
224
sampler/src/main/java/atlantafx/sampler/page/extras/InputGroupPage.java
Executable file
@ -0,0 +1,224 @@
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
|
||||
package atlantafx.sampler.page.extras;
|
||||
|
||||
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 javafx.geometry.Pos;
|
||||
import javafx.scene.control.Button;
|
||||
import javafx.scene.control.CheckBox;
|
||||
import javafx.scene.control.ComboBox;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.control.MenuButton;
|
||||
import javafx.scene.control.MenuItem;
|
||||
import javafx.scene.control.TextField;
|
||||
import javafx.scene.layout.HBox;
|
||||
import javafx.scene.layout.Priority;
|
||||
import javafx.scene.layout.VBox;
|
||||
import org.kordamp.ikonli.feather.Feather;
|
||||
import org.kordamp.ikonli.javafx.FontIcon;
|
||||
|
||||
public class InputGroupPage extends OutlinePage {
|
||||
|
||||
public static final String NAME = "Input Group";
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return NAME;
|
||||
}
|
||||
|
||||
public InputGroupPage() {
|
||||
super();
|
||||
|
||||
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.""");
|
||||
addSection("ComboBox", comboBoxExample());
|
||||
addSection("Button", buttonExample());
|
||||
addSection("Text Field", textFieldExample());
|
||||
addSection("MenuButton", menuButtonExample());
|
||||
addSection("Label", labelExample());
|
||||
}
|
||||
|
||||
private ExampleBox comboBoxExample() {
|
||||
//snippet_1:start
|
||||
var leftCmb = new ComboBox<>();
|
||||
leftCmb.getItems().addAll("POST", "GET", "PUT", "PATCH", "DELETE");
|
||||
leftCmb.getStyleClass().add(Styles.LEFT_PILL);
|
||||
leftCmb.getSelectionModel().selectFirst();
|
||||
|
||||
var rightTfd = new TextField("https://example.org");
|
||||
rightTfd.getStyleClass().add(Styles.RIGHT_PILL);
|
||||
HBox.setHgrow(rightTfd, Priority.ALWAYS);
|
||||
//snippet_1:end
|
||||
|
||||
var box = new HBox(leftCmb, rightTfd);
|
||||
box.setAlignment(Pos.CENTER_LEFT);
|
||||
box.setMinWidth(400);
|
||||
box.setMaxWidth(400);
|
||||
|
||||
var description = BBCodeParser.createFormattedText("""
|
||||
This example demonstrates how a [i]ComboBox[/i] can be combined \
|
||||
with a [i]TextField[/i].""");
|
||||
|
||||
return new ExampleBox(box, new Snippet(getClass(), 1), description);
|
||||
}
|
||||
|
||||
private ExampleBox buttonExample() {
|
||||
//snippet_2:start
|
||||
var leftTfd = new TextField();
|
||||
leftTfd.setText(FAKER.internet().password());
|
||||
leftTfd.getStyleClass().add(Styles.LEFT_PILL);
|
||||
HBox.setHgrow(leftTfd, Priority.ALWAYS);
|
||||
|
||||
var rightBtn = new Button(
|
||||
"", new FontIcon(Feather.REFRESH_CW)
|
||||
);
|
||||
rightBtn.getStyleClass().addAll(Styles.BUTTON_ICON);
|
||||
rightBtn.setOnAction(
|
||||
e -> leftTfd.setText(FAKER.internet().password())
|
||||
);
|
||||
rightBtn.getStyleClass().add(Styles.RIGHT_PILL);
|
||||
//snippet_2:end
|
||||
|
||||
var box = new HBox(leftTfd, rightBtn);
|
||||
box.setAlignment(Pos.CENTER_LEFT);
|
||||
box.setMinWidth(400);
|
||||
box.setMaxWidth(400);
|
||||
|
||||
var description = BBCodeParser.createFormattedText("""
|
||||
This example demonstrates how a [i]Button[/i] can be combined \
|
||||
with a [i]TextField[/i].""");
|
||||
|
||||
return new ExampleBox(box, new Snippet(getClass(), 2), description);
|
||||
}
|
||||
|
||||
private ExampleBox textFieldExample() {
|
||||
//snippet_3:start
|
||||
var leftTfd = new TextField("192.168.1.10");
|
||||
leftTfd.getStyleClass().add(Styles.LEFT_PILL);
|
||||
|
||||
var centerTfd = new TextField("24");
|
||||
centerTfd.getStyleClass().add(Styles.CENTER_PILL);
|
||||
centerTfd.setPrefWidth(70);
|
||||
|
||||
var rightTfd = new TextField("192.168.1.1");
|
||||
rightTfd.getStyleClass().add(Styles.RIGHT_PILL);
|
||||
//snippet_3:end
|
||||
|
||||
var box = new HBox(leftTfd, centerTfd, rightTfd);
|
||||
box.setAlignment(Pos.CENTER_LEFT);
|
||||
box.setMinWidth(400);
|
||||
box.setMaxWidth(400);
|
||||
|
||||
var description = BBCodeParser.createFormattedText("""
|
||||
This example demonstrates how a multiple [i]TextField[/i]'s can be \
|
||||
combined into a input group.""");
|
||||
|
||||
return new ExampleBox(box, new Snippet(getClass(), 3), description);
|
||||
}
|
||||
|
||||
private ExampleBox menuButtonExample() {
|
||||
//snippet_4:start
|
||||
var rightTfd = new TextField(FAKER.harryPotter().spell());
|
||||
rightTfd.getStyleClass().add(Styles.RIGHT_PILL);
|
||||
HBox.setHgrow(rightTfd, Priority.ALWAYS);
|
||||
|
||||
var spellItem = new MenuItem("Spell");
|
||||
spellItem.setOnAction(
|
||||
e -> rightTfd.setText(FAKER.harryPotter().spell())
|
||||
);
|
||||
|
||||
var characterItem = new MenuItem("Character");
|
||||
characterItem.setOnAction(
|
||||
e -> rightTfd.setText(FAKER.harryPotter().character())
|
||||
);
|
||||
|
||||
var locationItem = new MenuItem("Location");
|
||||
locationItem.setOnAction(
|
||||
e -> rightTfd.setText(FAKER.harryPotter().location())
|
||||
);
|
||||
|
||||
var leftMenu = new MenuButton("Dropdown");
|
||||
leftMenu.getItems().addAll(spellItem, characterItem, locationItem);
|
||||
leftMenu.getStyleClass().add(Styles.LEFT_PILL);
|
||||
//snippet_4:end
|
||||
|
||||
var box = new HBox(leftMenu, rightTfd);
|
||||
box.setAlignment(Pos.CENTER_LEFT);
|
||||
box.setMinWidth(400);
|
||||
box.setMaxWidth(400);
|
||||
|
||||
var description = BBCodeParser.createFormattedText("""
|
||||
This example demonstrates how a [i]MenuButton[/i] can be combined \
|
||||
with a [i]TextField[/i].""");
|
||||
|
||||
return new ExampleBox(box, new Snippet(getClass(), 4), description);
|
||||
}
|
||||
|
||||
private ExampleBox labelExample() {
|
||||
//snippet_5:start
|
||||
var leftLbl1 = new Label("", new CheckBox());
|
||||
leftLbl1.getStyleClass().add(Styles.LEFT_PILL);
|
||||
|
||||
var rightTfd1 = new TextField();
|
||||
rightTfd1.setPromptText("Username");
|
||||
rightTfd1.getStyleClass().add(Styles.RIGHT_PILL);
|
||||
HBox.setHgrow(rightTfd1, Priority.ALWAYS);
|
||||
|
||||
var sample1 = new HBox(leftLbl1, rightTfd1);
|
||||
sample1.setAlignment(Pos.CENTER_LEFT);
|
||||
|
||||
// ~
|
||||
var leftTfd2 = new TextField("johndoe");
|
||||
leftTfd2.getStyleClass().add(Styles.LEFT_PILL);
|
||||
HBox.setHgrow(leftTfd2, Priority.ALWAYS);
|
||||
|
||||
var centerLbl2 = new Label("@");
|
||||
centerLbl2.setMinWidth(50);
|
||||
centerLbl2.setAlignment(Pos.CENTER);
|
||||
centerLbl2.getStyleClass().add(Styles.CENTER_PILL);
|
||||
|
||||
var rightTfd2 = new TextField("gmail.com");
|
||||
rightTfd2.getStyleClass().add(Styles.RIGHT_PILL);
|
||||
HBox.setHgrow(rightTfd2, Priority.ALWAYS);
|
||||
|
||||
var sample2 = new HBox(leftTfd2, centerLbl2, rightTfd2);
|
||||
sample2.setAlignment(Pos.CENTER_LEFT);
|
||||
|
||||
// ~
|
||||
var leftTfd3 = new TextField("+123456");
|
||||
leftTfd3.getStyleClass().add(Styles.LEFT_PILL);
|
||||
HBox.setHgrow(leftTfd3, Priority.ALWAYS);
|
||||
|
||||
var rightLbl3 = new Label("", new FontIcon(Feather.DOLLAR_SIGN));
|
||||
rightLbl3.getStyleClass().add(Styles.RIGHT_PILL);
|
||||
|
||||
var sample3 = new HBox(leftTfd3, rightLbl3);
|
||||
sample3.setAlignment(Pos.CENTER_LEFT);
|
||||
//snippet_5:end
|
||||
|
||||
sample1.setMinWidth(400);
|
||||
sample1.setMaxWidth(400);
|
||||
|
||||
sample2.setMinWidth(400);
|
||||
sample2.setMaxWidth(400);
|
||||
|
||||
sample3.setMinWidth(400);
|
||||
sample3.setMaxWidth(400);
|
||||
|
||||
var box = new VBox(VGAP_20, sample1, sample2, sample3);
|
||||
|
||||
var description = BBCodeParser.createFormattedText("""
|
||||
This example demonstrates how a [i]Label[/i] can be used \
|
||||
in combination with various controls.""");
|
||||
|
||||
return new ExampleBox(box, new Snippet(getClass(), 5), description);
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,366 @@
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
|
||||
package atlantafx.sampler.page.extras;
|
||||
|
||||
import atlantafx.base.controls.ModalPane;
|
||||
import atlantafx.base.util.BBCodeParser;
|
||||
import atlantafx.sampler.Resources;
|
||||
import atlantafx.sampler.page.ExampleBox;
|
||||
import atlantafx.sampler.page.OutlinePage;
|
||||
import atlantafx.sampler.page.Snippet;
|
||||
import javafx.geometry.Insets;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.geometry.Side;
|
||||
import javafx.scene.Cursor;
|
||||
import javafx.scene.control.Button;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.control.ScrollPane;
|
||||
import javafx.scene.image.Image;
|
||||
import javafx.scene.image.ImageView;
|
||||
import javafx.scene.layout.HBox;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import javafx.scene.layout.VBox;
|
||||
import javafx.scene.text.Text;
|
||||
import javafx.scene.text.TextFlow;
|
||||
|
||||
public class ModalPanePage extends OutlinePage {
|
||||
|
||||
public static final String NAME = "Modal Pane";
|
||||
|
||||
private final ModalPane modalPane = new ModalPane();
|
||||
private final ModalPane modalPaneTop = new ModalPane(-15);
|
||||
private final ModalPane modalPaneTopmost = new ModalPane(-20);
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return NAME;
|
||||
}
|
||||
|
||||
public ModalPanePage() {
|
||||
super();
|
||||
|
||||
// add modal pane to the root container, which is StackPane
|
||||
getChildren().addAll(modalPane, modalPaneTop, modalPaneTopmost);
|
||||
|
||||
// reset side and transition to reuse a single modal pane between different examples
|
||||
modalPane.displayProperty().addListener((obs, old, val) -> {
|
||||
if (!val) {
|
||||
modalPane.setAlignment(Pos.CENTER);
|
||||
modalPane.usePredefinedTransitionFactories(null);
|
||||
}
|
||||
});
|
||||
|
||||
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.""");
|
||||
addSection("Usage", usageExample());
|
||||
addSection("Content Position", contentPositionExample());
|
||||
addSection("Persistent", persistentExample());
|
||||
addSection("Nesting", nestingExample());
|
||||
addSection("Maximized", maximizedExample());
|
||||
addSection("Overflowed", overflowedExample());
|
||||
addSection("Lightbox", lightboxExample());
|
||||
}
|
||||
|
||||
private ExampleBox usageExample() {
|
||||
//snippet_1:start
|
||||
var dialog = new Dialog(450, 450);
|
||||
|
||||
var openBtn = new Button("Open Dialog");
|
||||
openBtn.setOnAction(evt -> modalPane.show(dialog));
|
||||
|
||||
var closeBtn = new Button("Close");
|
||||
closeBtn.setOnAction(evt -> modalPane.hide(true));
|
||||
dialog.getChildren().setAll(closeBtn);
|
||||
//snippet_1:end
|
||||
|
||||
var box = new HBox(openBtn);
|
||||
box.setAlignment(Pos.CENTER);
|
||||
|
||||
var description = BBCodeParser.createFormattedText("""
|
||||
A [i]ModalPane[/i] can hold any content. By default, you just need to call the \
|
||||
[code]show()[/code] method, which is a convenience method for setting the content \
|
||||
of the modal pane and triggering its display state at the same time.""");
|
||||
|
||||
return new ExampleBox(box, new Snippet(getClass(), 1), description);
|
||||
}
|
||||
|
||||
private ExampleBox contentPositionExample() {
|
||||
//snippet_2:start
|
||||
var topDialog = new Dialog(-1, 150);
|
||||
topDialog.getChildren().setAll(new Label(
|
||||
FAKER.country().name()
|
||||
));
|
||||
|
||||
var openTopBtn = new Button("Top");
|
||||
openTopBtn.setOnAction(evt -> {
|
||||
modalPane.setAlignment(Pos.TOP_CENTER);
|
||||
modalPane.usePredefinedTransitionFactories(Side.TOP);
|
||||
modalPane.show(topDialog);
|
||||
});
|
||||
|
||||
// ~
|
||||
var rightDialog = new Dialog(250, -1);
|
||||
rightDialog.getChildren().setAll(new Label(
|
||||
FAKER.country().name()
|
||||
));
|
||||
|
||||
var openRightBtn = new Button("Right");
|
||||
openRightBtn.setOnAction(evt -> {
|
||||
modalPane.setAlignment(Pos.TOP_RIGHT);
|
||||
modalPane.usePredefinedTransitionFactories(Side.RIGHT);
|
||||
modalPane.show(rightDialog);
|
||||
});
|
||||
|
||||
// ~
|
||||
var bottomDialog = new Dialog(-1, 150);
|
||||
bottomDialog.getChildren().setAll(new Label(
|
||||
FAKER.country().name()
|
||||
));
|
||||
|
||||
var openBottomBtn = new Button("Bottom");
|
||||
openBottomBtn.setOnAction(evt -> {
|
||||
modalPane.setAlignment(Pos.BOTTOM_CENTER);
|
||||
modalPane.usePredefinedTransitionFactories(Side.BOTTOM);
|
||||
modalPane.show(bottomDialog);
|
||||
});
|
||||
|
||||
// ~
|
||||
var leftDialog = new Dialog(250, -1);
|
||||
leftDialog.getChildren().setAll(new Label(
|
||||
FAKER.country().name()
|
||||
));
|
||||
|
||||
var openLeftBtn = new Button("Left");
|
||||
openLeftBtn.setOnAction(evt -> {
|
||||
modalPane.setAlignment(Pos.TOP_LEFT);
|
||||
modalPane.usePredefinedTransitionFactories(Side.LEFT);
|
||||
modalPane.show(leftDialog);
|
||||
});
|
||||
//snippet_2:end
|
||||
|
||||
openTopBtn.setPrefWidth(100);
|
||||
openRightBtn.setPrefWidth(100);
|
||||
openBottomBtn.setPrefWidth(100);
|
||||
openLeftBtn.setPrefWidth(100);
|
||||
|
||||
var box = new HBox(HGAP_20, openTopBtn, openRightBtn, openBottomBtn, openLeftBtn);
|
||||
box.setAlignment(Pos.CENTER);
|
||||
|
||||
var description = BBCodeParser.createFormattedText("""
|
||||
The alignment and animated appearance of modal content can be changed \
|
||||
via corresponding properties.""");
|
||||
|
||||
return new ExampleBox(box, new Snippet(getClass(), 1), description);
|
||||
}
|
||||
|
||||
private ExampleBox persistentExample() {
|
||||
//snippet_3:start
|
||||
var dialog = new Dialog(450, 450);
|
||||
|
||||
var openBtn = new Button("Open Dialog");
|
||||
openBtn.setOnAction(evt -> {
|
||||
modalPane.setPersistent(true);
|
||||
modalPane.show(dialog);
|
||||
});
|
||||
|
||||
var closeBtn = new Button("Close");
|
||||
closeBtn.setOnAction(evt -> {
|
||||
modalPane.hide(true);
|
||||
modalPane.setPersistent(false);
|
||||
});
|
||||
dialog.getChildren().setAll(closeBtn);
|
||||
//snippet_3:end
|
||||
|
||||
var box = new HBox(openBtn);
|
||||
box.setAlignment(Pos.CENTER);
|
||||
|
||||
var description = BBCodeParser.createFormattedText("""
|
||||
By default, the [i]ModalPane[/i] exits when the ESC button is pressed \
|
||||
or when the mouse is clicked outside the content area. [code]setPersistent()[/code] \
|
||||
property prevents this behavior and instead plays a bouncing animation.""");
|
||||
|
||||
return new ExampleBox(box, new Snippet(getClass(), 3), description);
|
||||
}
|
||||
|
||||
private ExampleBox nestingExample() {
|
||||
//snippet_4:start
|
||||
var dialog = new Dialog(600, 600);
|
||||
var topDialog = new Dialog(450, 450);
|
||||
var topmostDialog = new Dialog(300, 300);
|
||||
|
||||
var openBtn = new Button("Open Dialog 1");
|
||||
// topViewOrder = -10 (default)
|
||||
openBtn.setOnAction(evt -> modalPane.show(dialog));
|
||||
|
||||
var topDialogBtn = new Button("Open Dialog 2");
|
||||
topDialogBtn.setOnAction(// topViewOrder = -15
|
||||
evt -> modalPaneTop.show(topDialog)
|
||||
);
|
||||
dialog.getChildren().add(topDialogBtn);
|
||||
|
||||
var topmostDialogBtn = new Button("Open Dialog 3");
|
||||
topmostDialogBtn.setOnAction(// topViewOrder = -20
|
||||
evt -> modalPaneTopmost.show(topmostDialog)
|
||||
);
|
||||
topDialog.getChildren().add(topmostDialogBtn);
|
||||
//snippet_4:end
|
||||
|
||||
var box = new HBox(openBtn);
|
||||
box.setAlignment(Pos.CENTER);
|
||||
|
||||
var description = BBCodeParser.createFormattedText("""
|
||||
There is no a specific support for nested dialogs. But, you can achieve \
|
||||
the same behavior by stacking multiple modal panes and using the corresponding \
|
||||
[code]topViewOrder[/code] property value.""");
|
||||
|
||||
return new ExampleBox(box, new Snippet(getClass(), 4), description);
|
||||
}
|
||||
|
||||
private ExampleBox maximizedExample() {
|
||||
//snippet_5:start
|
||||
var dialog = new Dialog(-1, -1);
|
||||
|
||||
var openBtn = new Button("Open Dialog");
|
||||
openBtn.setOnAction(evt -> modalPane.show(dialog));
|
||||
|
||||
var closeBtn = new Button("Close");
|
||||
closeBtn.setOnAction(evt -> modalPane.hide(true));
|
||||
dialog.getChildren().setAll(closeBtn);
|
||||
//snippet_5:end
|
||||
|
||||
var box = new HBox(openBtn);
|
||||
box.setAlignment(Pos.CENTER);
|
||||
|
||||
var description = BBCodeParser.createFormattedText("""
|
||||
To create a maximized dialog, simply use a content node such as [i]VBox[/i] \
|
||||
that expands itself in both the horizontal and vertical directions.""");
|
||||
|
||||
return new ExampleBox(box, new Snippet(getClass(), 5), description);
|
||||
}
|
||||
|
||||
private ExampleBox overflowedExample() {
|
||||
//snippet_6:start
|
||||
var dialog1 = new Dialog(450, -1);
|
||||
dialog1.setPadding(new Insets(20));
|
||||
|
||||
var openBtn1 = new Button("Open Dialog 1");
|
||||
openBtn1.setOnAction(evt -> {
|
||||
StackPane.setMargin(dialog1, new Insets(20));
|
||||
modalPane.show(dialog1);
|
||||
});
|
||||
|
||||
var textFlow1 = new TextFlow();
|
||||
dialog1.getChildren().setAll(textFlow1);
|
||||
for (int i = 0; i < 30; i++) {
|
||||
textFlow1.getChildren().add(
|
||||
new Text(FAKER.lorem().paragraph() + "\n\n")
|
||||
);
|
||||
}
|
||||
|
||||
// ~
|
||||
var dialog2 = new Dialog(450, -1);
|
||||
dialog2.setPadding(new Insets(10, 0, 10, 0));
|
||||
|
||||
var openBtn2 = new Button("Open Dialog 2");
|
||||
openBtn2.setOnAction(evt -> {
|
||||
StackPane.setMargin(dialog2, new Insets(20));
|
||||
modalPane.show(dialog2);
|
||||
});
|
||||
|
||||
var textFlow2 = new TextFlow();
|
||||
textFlow2.setMaxWidth(430);
|
||||
textFlow2.setPadding(new Insets(10, 20, 10, 20));
|
||||
|
||||
var scrollPane2 = new ScrollPane(textFlow2);
|
||||
scrollPane2.setMaxHeight(10_000);
|
||||
dialog2.getChildren().setAll(scrollPane2);
|
||||
|
||||
for (int i = 0; i < 30; i++) {
|
||||
textFlow2.getChildren().add(
|
||||
new Text(FAKER.lorem().paragraph() + "\n\n")
|
||||
);
|
||||
}
|
||||
//snippet_6:end
|
||||
|
||||
var box = new HBox(HGAP_20, openBtn1, openBtn2);
|
||||
box.setAlignment(Pos.CENTER);
|
||||
|
||||
var description = BBCodeParser.createFormattedText("""
|
||||
The [i]ModalPane[/i] is already scrollable by default, but you can also use a \
|
||||
[i]ScrollPane[/i] for the content node if needed.""");
|
||||
|
||||
return new ExampleBox(box, new Snippet(getClass(), 6), description);
|
||||
}
|
||||
|
||||
private ExampleBox lightboxExample() {
|
||||
//snippet_7:start
|
||||
var modalImage = new ImageView();
|
||||
|
||||
var thumbnail1 = new ImageView(new Image(
|
||||
Resources.getResourceAsStream(
|
||||
"images/gallery/kush-dwivedi-unsplash.jpg"
|
||||
)));
|
||||
thumbnail1.setFitWidth(180);
|
||||
thumbnail1.setFitHeight(120);
|
||||
thumbnail1.setCursor(Cursor.HAND);
|
||||
thumbnail1.setOnMouseClicked(evt -> {
|
||||
modalImage.setImage(thumbnail1.getImage());
|
||||
modalPane.show(modalImage);
|
||||
modalImage.requestFocus();
|
||||
});
|
||||
|
||||
var thumbnail2 = new ImageView(new Image(
|
||||
Resources.getResourceAsStream(
|
||||
"images/gallery/markus-spiske-unsplash.jpg"
|
||||
)));
|
||||
thumbnail2.setFitWidth(180);
|
||||
thumbnail2.setFitHeight(120);
|
||||
thumbnail2.setCursor(Cursor.HAND);
|
||||
thumbnail2.setOnMouseClicked(evt -> {
|
||||
modalImage.setImage(thumbnail2.getImage());
|
||||
modalPane.show(modalImage);
|
||||
modalImage.requestFocus();
|
||||
});
|
||||
|
||||
var thumbnail3 = new ImageView(new Image(
|
||||
Resources.getResourceAsStream(
|
||||
"images/gallery/r0m0_4-unsplash.jpg"
|
||||
)));
|
||||
thumbnail3.setFitWidth(180);
|
||||
thumbnail3.setFitHeight(120);
|
||||
thumbnail3.setCursor(Cursor.HAND);
|
||||
thumbnail3.setOnMouseClicked(evt -> {
|
||||
modalImage.setImage(thumbnail3.getImage());
|
||||
modalPane.show(modalImage);
|
||||
modalImage.requestFocus();
|
||||
});
|
||||
//snippet_7:end
|
||||
|
||||
var box = new HBox(5, thumbnail1, thumbnail2, thumbnail3);
|
||||
box.setAlignment(Pos.CENTER);
|
||||
|
||||
var description = BBCodeParser.createFormattedText("""
|
||||
This simple example demonstrates how [i]ModalPane[/i] can be used to \
|
||||
implement the famous JS lightbox effect."""
|
||||
);
|
||||
|
||||
return new ExampleBox(box, new Snippet(getClass(), 7), description);
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
|
||||
private static class Dialog extends VBox {
|
||||
|
||||
public Dialog(int width, int height) {
|
||||
super();
|
||||
|
||||
setSpacing(10);
|
||||
setAlignment(Pos.CENTER);
|
||||
setMinSize(width, height);
|
||||
setMaxSize(width, height);
|
||||
setStyle("-fx-background-color: -color-bg-default;");
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,155 @@
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
|
||||
package atlantafx.sampler.page.extras;
|
||||
|
||||
import atlantafx.base.controls.InlineDatePicker;
|
||||
import atlantafx.base.controls.Popover;
|
||||
import atlantafx.base.controls.Popover.ArrowLocation;
|
||||
import atlantafx.base.util.BBCodeParser;
|
||||
import atlantafx.sampler.page.ExampleBox;
|
||||
import atlantafx.sampler.page.OutlinePage;
|
||||
import atlantafx.sampler.page.Snippet;
|
||||
import atlantafx.sampler.theme.CSSFragment;
|
||||
import java.time.LocalDate;
|
||||
import java.time.ZoneId;
|
||||
import javafx.geometry.Insets;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.scene.control.Hyperlink;
|
||||
import javafx.scene.layout.GridPane;
|
||||
import javafx.scene.layout.HBox;
|
||||
import javafx.scene.text.Text;
|
||||
import javafx.scene.text.TextFlow;
|
||||
|
||||
public class PopoverPage extends OutlinePage {
|
||||
|
||||
public static final String NAME = "Popover";
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return NAME;
|
||||
}
|
||||
|
||||
public PopoverPage() {
|
||||
super();
|
||||
|
||||
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.""");
|
||||
addSection("Usage", usageExample());
|
||||
addSection("Position", positionExample());
|
||||
}
|
||||
|
||||
private ExampleBox usageExample() {
|
||||
var datePickerStyle = """
|
||||
.popover .date-picker-popup {
|
||||
-color-date-border: transparent;
|
||||
-color-date-bg: transparent;
|
||||
-color-date-day-bg: transparent;
|
||||
-color-date-month-year-bg: transparent;
|
||||
-color-date-day-bg-hover: -color-bg-subtle;
|
||||
}
|
||||
""";
|
||||
|
||||
//snippet_1:start
|
||||
var textFlow = new TextFlow(new Text(
|
||||
FAKER.lorem().sentence(30)
|
||||
));
|
||||
textFlow.setPrefWidth(300);
|
||||
textFlow.setPadding(new Insets(10, 0, 10, 0));
|
||||
|
||||
var pop1 = new Popover(textFlow);
|
||||
pop1.setTitle("Lorem Ipsum");
|
||||
pop1.setHeaderAlwaysVisible(true);
|
||||
pop1.setDetachable(true);
|
||||
|
||||
var link1 = new Hyperlink("Text");
|
||||
link1.setOnAction(e -> pop1.show(link1));
|
||||
|
||||
// ~
|
||||
var datePicker = new InlineDatePicker();
|
||||
datePicker.setValue(LocalDate.now(ZoneId.systemDefault()));
|
||||
// -color-date-border: transparent;
|
||||
// -color-date-bg: transparent;
|
||||
// -color-date-day-bg: transparent;
|
||||
// -color-date-month-year-bg: transparent;
|
||||
// -color-date-day-bg-hover: -color-bg-subtle;
|
||||
new CSSFragment(datePickerStyle).addTo(datePicker);
|
||||
|
||||
var pop2 = new Popover(datePicker);
|
||||
pop2.setHeaderAlwaysVisible(false);
|
||||
pop2.setDetachable(true);
|
||||
|
||||
var link2 = new Hyperlink("DatePicker");
|
||||
link2.setOnAction(e -> pop2.show(link2));
|
||||
//snippet_1:end
|
||||
|
||||
var box = new HBox(HGAP_30, link1, link2);
|
||||
box.setAlignment(Pos.CENTER_LEFT);
|
||||
|
||||
var description = BBCodeParser.createFormattedText("""
|
||||
The popup window has a very lightweight appearance (no default window decorations) \
|
||||
and an arrow pointing at the owner. Due to the nature of popup windows the \
|
||||
[i]Popover[/i] will move around with the parent window when the user drags it."""
|
||||
);
|
||||
|
||||
return new ExampleBox(box, new Snippet(getClass(), 1), description);
|
||||
}
|
||||
|
||||
private ExampleBox positionExample() {
|
||||
//snippet_2:start
|
||||
class PopoverLink extends Hyperlink {
|
||||
|
||||
public PopoverLink(ArrowLocation arrowLocation) {
|
||||
super();
|
||||
|
||||
var textFlow = new TextFlow(new Text(
|
||||
FAKER.lorem().sentence(30)
|
||||
));
|
||||
textFlow.setPrefWidth(300);
|
||||
textFlow.setPadding(new Insets(10, 0, 10, 0));
|
||||
|
||||
var pop = new Popover(textFlow);
|
||||
pop.setHeaderAlwaysVisible(false);
|
||||
pop.setArrowLocation(arrowLocation);
|
||||
|
||||
setText(String.valueOf(arrowLocation));
|
||||
setOnAction(e -> pop.show(this));
|
||||
}
|
||||
}
|
||||
|
||||
var grid = new GridPane();
|
||||
grid.setHgap(20);
|
||||
grid.setVgap(20);
|
||||
grid.addColumn(0,
|
||||
new PopoverLink(ArrowLocation.TOP_LEFT),
|
||||
new PopoverLink(ArrowLocation.TOP_CENTER),
|
||||
new PopoverLink(ArrowLocation.TOP_RIGHT)
|
||||
);
|
||||
grid.addColumn(1,
|
||||
new PopoverLink(ArrowLocation.RIGHT_TOP),
|
||||
new PopoverLink(ArrowLocation.RIGHT_CENTER),
|
||||
new PopoverLink(ArrowLocation.RIGHT_BOTTOM)
|
||||
);
|
||||
grid.addColumn(2,
|
||||
new PopoverLink(ArrowLocation.BOTTOM_LEFT),
|
||||
new PopoverLink(ArrowLocation.BOTTOM_CENTER),
|
||||
new PopoverLink(ArrowLocation.BOTTOM_RIGHT)
|
||||
);
|
||||
grid.addColumn(3,
|
||||
new PopoverLink(ArrowLocation.LEFT_TOP),
|
||||
new PopoverLink(ArrowLocation.LEFT_CENTER),
|
||||
new PopoverLink(ArrowLocation.LEFT_BOTTOM)
|
||||
);
|
||||
//snippet_2:end
|
||||
|
||||
var description = BBCodeParser.createFormattedText("""
|
||||
The [i]Popover[/i] popup window can be positioned by setting an \
|
||||
appropriate ArrowLocation value.."""
|
||||
);
|
||||
|
||||
return new ExampleBox(grid, new Snippet(getClass(), 2), description);
|
||||
}
|
||||
}
|
@ -0,0 +1,93 @@
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
|
||||
package atlantafx.sampler.page.extras;
|
||||
|
||||
import atlantafx.base.controls.ToggleSwitch;
|
||||
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 javafx.geometry.HorizontalDirection;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.scene.layout.HBox;
|
||||
|
||||
public class ToggleSwitchPage extends OutlinePage {
|
||||
|
||||
public static final String NAME = "ToggleSwitch";
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return NAME;
|
||||
}
|
||||
|
||||
public ToggleSwitchPage() {
|
||||
super();
|
||||
|
||||
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 \
|
||||
or off a function."""
|
||||
);
|
||||
addSection("Usage", usageExample());
|
||||
addSection("Color", colorExample());
|
||||
}
|
||||
|
||||
private ExampleBox usageExample() {
|
||||
//snippet_1:start
|
||||
var toggle1 = new ToggleSwitch("Enabled");
|
||||
toggle1.selectedProperty().addListener(
|
||||
(obs, old, val) -> toggle1.setText(val ? "Enabled" : "Disabled")
|
||||
);
|
||||
toggle1.setSelected(true);
|
||||
|
||||
var toggle2 = new ToggleSwitch("Disabled");
|
||||
toggle2.selectedProperty().addListener(
|
||||
(obs, old, val) -> toggle2.setText(val ? "Enabled" : "Disabled")
|
||||
);
|
||||
toggle2.setLabelPosition(HorizontalDirection.RIGHT);
|
||||
toggle2.setSelected(false);
|
||||
//snippet_1:end
|
||||
|
||||
var box = new HBox(HGAP_30, toggle1, toggle2);
|
||||
box.setAlignment(Pos.CENTER_LEFT);
|
||||
|
||||
var description = BBCodeParser.createFormattedText("""
|
||||
The text position can be changed by setting the [code]labelPosition[/code] \
|
||||
property value."""
|
||||
);
|
||||
|
||||
return new ExampleBox(box, new Snippet(getClass(), 1), description);
|
||||
}
|
||||
|
||||
private ExampleBox colorExample() {
|
||||
//snippet_2:start
|
||||
var successToggle = new ToggleSwitch("Enabled");
|
||||
successToggle.selectedProperty().addListener((obs, old, val) -> {
|
||||
successToggle.setText(val ? "Enabled" : "Disabled");
|
||||
successToggle.pseudoClassStateChanged(Styles.STATE_SUCCESS, val);
|
||||
}
|
||||
);
|
||||
successToggle.setSelected(true);
|
||||
successToggle.pseudoClassStateChanged(Styles.STATE_SUCCESS, true);
|
||||
|
||||
var dangerToggle = new ToggleSwitch("Disabled");
|
||||
dangerToggle.selectedProperty().addListener((obs, old, val) -> {
|
||||
dangerToggle.setText(val ? "Enabled" : "Disabled");
|
||||
dangerToggle.pseudoClassStateChanged(Styles.STATE_DANGER, val);
|
||||
}
|
||||
);
|
||||
dangerToggle.setLabelPosition(HorizontalDirection.RIGHT);
|
||||
dangerToggle.setSelected(false);
|
||||
//snippet_2:end
|
||||
|
||||
var box = new HBox(HGAP_30, successToggle, dangerToggle);
|
||||
box.setAlignment(Pos.CENTER_LEFT);
|
||||
|
||||
var description = BBCodeParser.createFormattedText("""
|
||||
Use [code]Styles.STATE_SUCCESS[/code] or [code]Styles.STATE_DANGER[/code] \
|
||||
pseudo-classes to change the [i]ToggleSwitch[/i] color.""");
|
||||
|
||||
return new ExampleBox(box, new Snippet(getClass(), 2), description);
|
||||
}
|
||||
}
|
@ -2,10 +2,6 @@
|
||||
|
||||
package atlantafx.sampler.page.general;
|
||||
|
||||
import static atlantafx.sampler.util.Controls.hyperlink;
|
||||
|
||||
import atlantafx.base.theme.Styles;
|
||||
import java.net.URI;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
@ -14,105 +10,54 @@ import javafx.animation.KeyFrame;
|
||||
import javafx.animation.Timeline;
|
||||
import javafx.beans.property.ReadOnlyObjectProperty;
|
||||
import javafx.beans.property.ReadOnlyObjectWrapper;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.layout.GridPane;
|
||||
import javafx.scene.layout.HBox;
|
||||
import javafx.scene.layout.VBox;
|
||||
import javafx.scene.paint.Color;
|
||||
import javafx.scene.text.Text;
|
||||
import javafx.scene.text.TextFlow;
|
||||
import javafx.util.Duration;
|
||||
|
||||
class ColorPalette extends VBox {
|
||||
class ColorPalette extends GridPane {
|
||||
|
||||
private final List<ColorPaletteBlock> blocks = new ArrayList<>();
|
||||
private final ReadOnlyObjectWrapper<Color> bgBaseColor = new ReadOnlyObjectWrapper<>(Color.WHITE);
|
||||
private final Consumer<ColorPaletteBlock> colorBlockActionHandler;
|
||||
|
||||
public ColorPalette(Consumer<ColorPaletteBlock> blockClickedHandler) {
|
||||
public ColorPalette(Consumer<ColorPaletteBlock> actionHandler) {
|
||||
super();
|
||||
|
||||
this.colorBlockActionHandler = Objects.requireNonNull(blockClickedHandler);
|
||||
createView();
|
||||
}
|
||||
this.colorBlockActionHandler = Objects.requireNonNull(actionHandler, "actionHandler");
|
||||
|
||||
private void createView() {
|
||||
var headerLabel = new Label("Color Palette");
|
||||
headerLabel.getStyleClass().add(Styles.TITLE_4);
|
||||
add(colorBlock("-color-fg-default", "-color-bg-default", "-color-border-default"), 0, 0);
|
||||
add(colorBlock("-color-fg-default", "-color-bg-overlay", "-color-border-default"), 1, 0);
|
||||
add(colorBlock("-color-fg-muted", "-color-bg-default", "-color-border-muted"), 2, 0);
|
||||
add(colorBlock("-color-fg-subtle", "-color-bg-default", "-color-border-subtle"), 3, 0);
|
||||
|
||||
var headerBox = new HBox();
|
||||
headerBox.getChildren().setAll(headerLabel);
|
||||
headerBox.setAlignment(Pos.CENTER_LEFT);
|
||||
headerBox.getStyleClass().add("header");
|
||||
add(colorBlock("-color-fg-emphasis", "-color-accent-emphasis", "-color-accent-emphasis"), 0, 1);
|
||||
add(colorBlock("-color-accent-fg", "-color-bg-default", "-color-accent-emphasis"), 1, 1);
|
||||
add(colorBlock("-color-fg-default", "-color-accent-muted", "-color-accent-emphasis"), 2, 1);
|
||||
add(colorBlock("-color-accent-fg", "-color-accent-subtle", "-color-accent-emphasis"), 3, 1);
|
||||
|
||||
var noteText = new VBox(6);
|
||||
noteText.getChildren().setAll(
|
||||
new TextFlow(
|
||||
new Text("Color contrast between text and its background must meet "),
|
||||
hyperlink(
|
||||
"required WCAG standards",
|
||||
URI.create("https://www.w3.org/WAI/WCAG21/Understanding/contrast-minimum.html")
|
||||
),
|
||||
new Text(":")
|
||||
),
|
||||
new Text(" • 4.5:1 for normal text"),
|
||||
new Text(" • 3:1 for large text (>24px)"),
|
||||
new Text(" • 3:1 for UI elements and graphics"),
|
||||
new Text(" • no contrast requirement for decorative and disabled elements"),
|
||||
new Text(),
|
||||
new Text("Click on any color block to observe and modify color combination via built-in contrast checker.")
|
||||
);
|
||||
add(colorBlock("-color-fg-emphasis", "-color-neutral-emphasis-plus", "-color-neutral-emphasis-plus"), 0, 2);
|
||||
add(colorBlock("-color-fg-emphasis", "-color-neutral-emphasis", "-color-neutral-emphasis"), 1, 2);
|
||||
add(colorBlock("-color-fg-default", "-color-neutral-muted", "-color-neutral-emphasis"), 2, 2);
|
||||
add(colorBlock("-color-fg-default", "-color-neutral-subtle", "-color-neutral-emphasis"), 3, 2);
|
||||
|
||||
var colorGrid = colorGrid();
|
||||
add(colorBlock("-color-fg-emphasis", "-color-success-emphasis", "-color-success-emphasis"), 0, 3);
|
||||
add(colorBlock("-color-success-fg", "-color-bg-default", "-color-success-emphasis"), 1, 3);
|
||||
add(colorBlock("-color-fg-default", "-color-success-muted", "-color-success-emphasis"), 2, 3);
|
||||
add(colorBlock("-color-success-fg", "-color-success-subtle", "-color-success-emphasis"), 3, 3);
|
||||
|
||||
backgroundProperty().addListener((obs, old, val) -> bgBaseColor.set(
|
||||
val != null && !val.getFills().isEmpty() ? (Color) val.getFills().get(0).getFill() : Color.WHITE
|
||||
));
|
||||
add(colorBlock("-color-fg-emphasis", "-color-warning-emphasis", "-color-warning-emphasis"), 0, 4);
|
||||
add(colorBlock("-color-warning-fg", "-color-bg-default", "-color-warning-emphasis"), 1, 4);
|
||||
add(colorBlock("-color-fg-default", "-color-warning-muted", "-color-warning-emphasis"), 2, 4);
|
||||
add(colorBlock("-color-warning-fg", "-color-warning-subtle", "-color-warning-emphasis"), 3, 4);
|
||||
|
||||
add(colorBlock("-color-fg-emphasis", "-color-danger-emphasis", "-color-danger-emphasis"), 0, 5);
|
||||
add(colorBlock("-color-danger-fg", "-color-bg-default", "-color-danger-emphasis"), 1, 5);
|
||||
add(colorBlock("-color-fg-default", "-color-danger-muted", "-color-danger-emphasis"), 2, 5);
|
||||
add(colorBlock("-color-danger-fg", "-color-danger-subtle", "-color-danger-emphasis"), 3, 5);
|
||||
|
||||
getChildren().setAll(headerBox, noteText, colorGrid);
|
||||
setId("color-palette");
|
||||
}
|
||||
|
||||
private GridPane colorGrid() {
|
||||
var grid = new GridPane();
|
||||
grid.getStyleClass().add("grid");
|
||||
|
||||
grid.add(colorBlock("-color-fg-default", "-color-bg-default", "-color-border-default"), 0, 0);
|
||||
grid.add(colorBlock("-color-fg-default", "-color-bg-overlay", "-color-border-default"), 1, 0);
|
||||
grid.add(colorBlock("-color-fg-muted", "-color-bg-default", "-color-border-muted"), 2, 0);
|
||||
grid.add(colorBlock("-color-fg-subtle", "-color-bg-default", "-color-border-subtle"), 3, 0);
|
||||
|
||||
grid.add(colorBlock("-color-fg-emphasis", "-color-accent-emphasis", "-color-accent-emphasis"), 0, 1);
|
||||
grid.add(colorBlock("-color-accent-fg", "-color-bg-default", "-color-accent-emphasis"), 1, 1);
|
||||
grid.add(colorBlock("-color-fg-default", "-color-accent-muted", "-color-accent-emphasis"), 2, 1);
|
||||
grid.add(colorBlock("-color-accent-fg", "-color-accent-subtle", "-color-accent-emphasis"), 3, 1);
|
||||
|
||||
grid.add(
|
||||
colorBlock("-color-fg-emphasis", "-color-neutral-emphasis-plus", "-color-neutral-emphasis-plus"), 0, 2
|
||||
);
|
||||
grid.add(colorBlock("-color-fg-emphasis", "-color-neutral-emphasis", "-color-neutral-emphasis"), 1, 2);
|
||||
grid.add(colorBlock("-color-fg-default", "-color-neutral-muted", "-color-neutral-emphasis"), 2, 2);
|
||||
grid.add(colorBlock("-color-fg-default", "-color-neutral-subtle", "-color-neutral-emphasis"), 3, 2);
|
||||
|
||||
grid.add(colorBlock("-color-fg-emphasis", "-color-success-emphasis", "-color-success-emphasis"), 0, 3);
|
||||
grid.add(colorBlock("-color-success-fg", "-color-bg-default", "-color-success-emphasis"), 1, 3);
|
||||
grid.add(colorBlock("-color-fg-default", "-color-success-muted", "-color-success-emphasis"), 2, 3);
|
||||
grid.add(colorBlock("-color-success-fg", "-color-success-subtle", "-color-success-emphasis"), 3, 3);
|
||||
|
||||
grid.add(colorBlock("-color-fg-emphasis", "-color-warning-emphasis", "-color-warning-emphasis"), 0, 4);
|
||||
grid.add(colorBlock("-color-warning-fg", "-color-bg-default", "-color-warning-emphasis"), 1, 4);
|
||||
grid.add(colorBlock("-color-fg-default", "-color-warning-muted", "-color-warning-emphasis"), 2, 4);
|
||||
grid.add(colorBlock("-color-warning-fg", "-color-warning-subtle", "-color-warning-emphasis"), 3, 4);
|
||||
|
||||
grid.add(colorBlock("-color-fg-emphasis", "-color-danger-emphasis", "-color-danger-emphasis"), 0, 5);
|
||||
grid.add(colorBlock("-color-danger-fg", "-color-bg-default", "-color-danger-emphasis"), 1, 5);
|
||||
grid.add(colorBlock("-color-fg-default", "-color-danger-muted", "-color-danger-emphasis"), 2, 5);
|
||||
grid.add(colorBlock("-color-danger-fg", "-color-danger-subtle", "-color-danger-emphasis"), 3, 5);
|
||||
|
||||
return grid;
|
||||
}
|
||||
|
||||
private ColorPaletteBlock colorBlock(String fgColor, String bgColor, String borderColor) {
|
||||
var block = new ColorPaletteBlock(fgColor, bgColor, borderColor, bgBaseColor.getReadOnlyProperty());
|
||||
block.setOnAction(colorBlockActionHandler);
|
||||
|
@ -2,23 +2,16 @@
|
||||
|
||||
package atlantafx.sampler.page.general;
|
||||
|
||||
import atlantafx.base.theme.Styles;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import javafx.animation.KeyFrame;
|
||||
import javafx.animation.Timeline;
|
||||
import javafx.beans.property.ReadOnlyObjectWrapper;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.layout.FlowPane;
|
||||
import javafx.scene.layout.HBox;
|
||||
import javafx.scene.layout.VBox;
|
||||
import javafx.scene.paint.Color;
|
||||
import javafx.scene.text.Text;
|
||||
import javafx.scene.text.TextFlow;
|
||||
import javafx.util.Duration;
|
||||
|
||||
class ColorScale extends VBox {
|
||||
class ColorScale extends FlowPane {
|
||||
|
||||
private final ReadOnlyObjectWrapper<Color> bgBaseColor = new ReadOnlyObjectWrapper<>(Color.WHITE);
|
||||
private final List<ColorScaleBlock> blocks = Arrays.asList(
|
||||
@ -32,35 +25,15 @@ class ColorScale extends VBox {
|
||||
|
||||
public ColorScale() {
|
||||
super();
|
||||
createView();
|
||||
}
|
||||
|
||||
private void createView() {
|
||||
var headerLabel = new Label("Color Scale");
|
||||
headerLabel.getStyleClass().add(Styles.TITLE_4);
|
||||
|
||||
var headerBox = new HBox();
|
||||
headerBox.getChildren().setAll(headerLabel);
|
||||
headerBox.setAlignment(Pos.CENTER_LEFT);
|
||||
headerBox.getStyleClass().add("header");
|
||||
|
||||
var noteText = new TextFlow(
|
||||
new Text(
|
||||
"Avoid referencing scale variables directly when building UI that needs "
|
||||
+ "to adapt to different color themes. Instead, use the functional variables listed above."
|
||||
)
|
||||
);
|
||||
|
||||
backgroundProperty().addListener((obs, old, val) -> bgBaseColor.set(
|
||||
val != null && !val.getFills().isEmpty() ? (Color) val.getFills().get(0).getFill() : Color.WHITE
|
||||
val != null && !val.getFills().isEmpty()
|
||||
? (Color) val.getFills().get(0).getFill()
|
||||
: Color.WHITE
|
||||
));
|
||||
|
||||
setId("color-scale");
|
||||
getChildren().setAll(
|
||||
headerBox,
|
||||
noteText,
|
||||
colorTable()
|
||||
);
|
||||
getChildren().setAll(blocks);
|
||||
}
|
||||
|
||||
public void updateColorInfo(Duration delay) {
|
||||
@ -68,11 +41,4 @@ class ColorScale extends VBox {
|
||||
t.setOnFinished(e -> blocks.forEach(ColorScaleBlock::update));
|
||||
t.play();
|
||||
}
|
||||
|
||||
private FlowPane colorTable() {
|
||||
var root = new FlowPane(20, 20);
|
||||
root.getStyleClass().add("table");
|
||||
root.getChildren().setAll(blocks);
|
||||
return root;
|
||||
}
|
||||
}
|
||||
|
@ -14,7 +14,7 @@ import javafx.scene.paint.Color;
|
||||
|
||||
class ColorScaleBlock extends VBox {
|
||||
|
||||
private static final double BLOCK_WIDTH = 250;
|
||||
private static final double BLOCK_WIDTH = 200;
|
||||
private static final double BLOCK_HEIGHT = 40;
|
||||
|
||||
private final ReadOnlyObjectProperty<Color> bgBaseColor;
|
||||
|
@ -2,25 +2,20 @@
|
||||
|
||||
package atlantafx.sampler.page.general;
|
||||
|
||||
import static atlantafx.sampler.page.SampleBlock.BLOCK_HGAP;
|
||||
import static atlantafx.sampler.page.SampleBlock.BLOCK_VGAP;
|
||||
import static atlantafx.sampler.util.Controls.hyperlink;
|
||||
|
||||
import atlantafx.base.controls.CustomTextField;
|
||||
import atlantafx.base.theme.Styles;
|
||||
import atlantafx.sampler.page.AbstractPage;
|
||||
import atlantafx.sampler.page.SampleBlock;
|
||||
import atlantafx.base.util.BBCodeParser;
|
||||
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;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.layout.HBox;
|
||||
import javafx.scene.layout.Priority;
|
||||
import javafx.scene.layout.VBox;
|
||||
import javafx.scene.text.Text;
|
||||
import javafx.scene.text.TextFlow;
|
||||
import org.kordamp.ikonli.Ikon;
|
||||
import org.kordamp.ikonli.javafx.FontIcon;
|
||||
import org.kordamp.ikonli.javafx.StackedFontIcon;
|
||||
@ -28,7 +23,7 @@ import org.kordamp.ikonli.material2.Material2AL;
|
||||
import org.kordamp.ikonli.material2.Material2MZ;
|
||||
import org.kordamp.ikonli.material2.Material2OutlinedAL;
|
||||
|
||||
public class IconsPage extends AbstractPage {
|
||||
public class IconsPage extends OutlinePage {
|
||||
|
||||
public static final String NAME = "Icons";
|
||||
|
||||
@ -39,21 +34,116 @@ public class IconsPage extends AbstractPage {
|
||||
|
||||
public IconsPage() {
|
||||
super();
|
||||
createView();
|
||||
|
||||
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);
|
||||
addSection("Color", colorExample());
|
||||
addSection("Stacking", stackingExample());
|
||||
addSection("Icon Pack", iconBrowser());
|
||||
}
|
||||
|
||||
private void createView() {
|
||||
var headerText = new TextFlow(
|
||||
new Text("AtlantaFX supports "),
|
||||
hyperlink("Ikonli", URI.create("https://kordamp.org/ikonli")),
|
||||
new Text(" iconic fonts that can be used together with some JavaFX components.")
|
||||
);
|
||||
private ExampleBox colorExample() {
|
||||
//snippet_1:start
|
||||
var accentIcon = new FontIcon(Material2MZ.THUMB_UP);
|
||||
accentIcon.getStyleClass().add(Styles.ACCENT);
|
||||
|
||||
var browserText = new TextFlow(
|
||||
new Text("There's a variety of icon packs. Sampler app uses "),
|
||||
hyperlink("Material Icons", URI.create("https://kordamp.org/ikonli/cheat-sheet-material2.html")),
|
||||
new Text(" you can preview below.")
|
||||
var successIcon = new FontIcon(Material2MZ.THUMB_UP);
|
||||
successIcon.getStyleClass().add(Styles.SUCCESS);
|
||||
|
||||
var warningIcon = new FontIcon(Material2MZ.THUMB_UP);
|
||||
warningIcon.getStyleClass().add(Styles.WARNING);
|
||||
|
||||
var dangerIcon = new FontIcon(Material2MZ.THUMB_UP);
|
||||
dangerIcon.getStyleClass().add(Styles.DANGER);
|
||||
//snippet_1:end
|
||||
|
||||
var box = new HBox(HGAP_20, accentIcon, successIcon, warningIcon, dangerIcon);
|
||||
|
||||
var description = BBCodeParser.createFormattedText("""
|
||||
You can use pseudo-classes to set the icon color.""");
|
||||
|
||||
var example = new ExampleBox(box, new Snippet(getClass(), 1), description);
|
||||
example.setAllowDisable(false);
|
||||
|
||||
return example;
|
||||
}
|
||||
|
||||
private ExampleBox stackingExample() {
|
||||
var style1 = """
|
||||
.stacked-ikonli-font-icon > .outer-icon {
|
||||
-fx-icon-size: 48px;
|
||||
-fx-icon-color: -color-danger-emphasis;
|
||||
}
|
||||
.stacked-ikonli-font-icon > .inner-icon {
|
||||
-fx-icon-size: 24px;
|
||||
}
|
||||
""";
|
||||
|
||||
var style2 = """
|
||||
.stacked-ikonli-font-icon > .outer-icon {
|
||||
-fx-icon-size: 48px;
|
||||
}
|
||||
.stacked-ikonli-font-icon > .inner-icon {
|
||||
-fx-icon-size: 24px;
|
||||
}
|
||||
""";
|
||||
|
||||
//snippet_2:start
|
||||
var outerIcon1 = new FontIcon(Material2OutlinedAL.BLOCK);
|
||||
outerIcon1.getStyleClass().add("outer-icon");
|
||||
|
||||
var innerIcon1 = new FontIcon(Material2MZ.PHOTO_CAMERA);
|
||||
innerIcon1.getStyleClass().add("inner-icon");
|
||||
|
||||
var stackIcon1 = new StackedFontIcon();
|
||||
stackIcon1.getChildren().addAll(innerIcon1, outerIcon1);
|
||||
// .stacked-ikonli-font-icon > .outer-icon {
|
||||
// -fx-icon-size: 48px;
|
||||
// -fx-icon-color: -color-danger-emphasis;
|
||||
// }
|
||||
// .stacked-ikonli-font-icon > .inner-icon {
|
||||
// -fx-icon-size: 24px;
|
||||
// }
|
||||
new CSSFragment(style1).addTo(stackIcon1);
|
||||
|
||||
var outerIcon2 = new FontIcon(
|
||||
Material2OutlinedAL.CHECK_BOX_OUTLINE_BLANK
|
||||
);
|
||||
outerIcon2.getStyleClass().add("outer-icon");
|
||||
|
||||
var innerIcon2 = new FontIcon(Material2AL.LOCK);
|
||||
innerIcon2.getStyleClass().add("inner-icon");
|
||||
|
||||
var stackIcon2 = new StackedFontIcon();
|
||||
stackIcon2.getChildren().addAll(outerIcon2, innerIcon2);
|
||||
// .stacked-ikonli-font-icon > .outer-icon {
|
||||
// -fx-icon-size: 48px;
|
||||
// }
|
||||
// .stacked-ikonli-font-icon > .inner-icon {
|
||||
// -fx-icon-size: 24px;
|
||||
// }
|
||||
new CSSFragment(style2).addTo(stackIcon2);
|
||||
//snippet_2:end
|
||||
|
||||
var box = new HBox(HGAP_20, stackIcon1, stackIcon2);
|
||||
|
||||
var description = BBCodeParser.createFormattedText("""
|
||||
Ikonli also supports icon stacking, although it is currently in an \
|
||||
initial state and requires some manual styling. However, it can be \
|
||||
useful in certain cases.""");
|
||||
|
||||
var example = new ExampleBox(box, new Snippet(getClass(), 2), description);
|
||||
example.setAllowDisable(false);
|
||||
|
||||
return example;
|
||||
}
|
||||
|
||||
private Node iconBrowser() {
|
||||
var description = createFormattedText("""
|
||||
There are various icon packs available. The Sampler app uses \
|
||||
[url=https://kordamp.org/ikonli/cheat-sheet-material2.html]Material Icons[/url] \
|
||||
which you can preview below.""", true);
|
||||
|
||||
var filterText = new CustomTextField();
|
||||
filterText.setLeft(new FontIcon(Material2MZ.SEARCH));
|
||||
@ -69,77 +159,8 @@ public class IconsPage extends AbstractPage {
|
||||
var iconBrowser = new IconBrowser(5, icons);
|
||||
VBox.setVgrow(iconBrowser, Priority.ALWAYS);
|
||||
iconBrowser.filterProperty().bind(filterText.textProperty());
|
||||
iconBrowser.setMinHeight(500);
|
||||
|
||||
setUserContent(new VBox(
|
||||
PAGE_VGAP,
|
||||
headerText,
|
||||
expandingHBox(colorSample(), stackingSample()),
|
||||
browserText,
|
||||
filterBox,
|
||||
iconBrowser
|
||||
));
|
||||
}
|
||||
|
||||
private SampleBlock colorSample() {
|
||||
var accentIcon = new FontIcon(Material2MZ.THUMB_UP);
|
||||
accentIcon.getStyleClass().add(Styles.ACCENT);
|
||||
|
||||
var successIcon = new FontIcon(Material2MZ.THUMB_UP);
|
||||
successIcon.getStyleClass().add(Styles.SUCCESS);
|
||||
|
||||
var warningIcon = new FontIcon(Material2MZ.THUMB_UP);
|
||||
warningIcon.getStyleClass().add(Styles.WARNING);
|
||||
|
||||
var dangerIcon = new FontIcon(Material2MZ.THUMB_UP);
|
||||
dangerIcon.getStyleClass().add(Styles.DANGER);
|
||||
|
||||
var content = new VBox(
|
||||
BLOCK_VGAP,
|
||||
new Label("You can also use pseudo-classes to set icon color."),
|
||||
new HBox(BLOCK_HGAP, accentIcon, successIcon, warningIcon, dangerIcon)
|
||||
);
|
||||
|
||||
return new SampleBlock("Colors", content);
|
||||
}
|
||||
|
||||
private SampleBlock stackingSample() {
|
||||
var outerIcon1 = new FontIcon(Material2OutlinedAL.BLOCK);
|
||||
outerIcon1.getStyleClass().add("outer-icon");
|
||||
|
||||
var innerIcon1 = new FontIcon(Material2MZ.PHOTO_CAMERA);
|
||||
innerIcon1.getStyleClass().add("inner-icon");
|
||||
|
||||
var stackIcon1 = new StackedFontIcon();
|
||||
stackIcon1.getChildren().addAll(innerIcon1, outerIcon1);
|
||||
new CSSFragment("""
|
||||
.stacked-ikonli-font-icon > .outer-icon {
|
||||
-fx-icon-size: 48px;
|
||||
-fx-icon-color: -color-danger-emphasis;
|
||||
}
|
||||
.stacked-ikonli-font-icon > .inner-icon {
|
||||
-fx-icon-size: 24px;
|
||||
}
|
||||
""").addTo(stackIcon1);
|
||||
|
||||
var outerIcon2 = new FontIcon(Material2OutlinedAL.CHECK_BOX_OUTLINE_BLANK);
|
||||
outerIcon2.getStyleClass().add("outer-icon");
|
||||
|
||||
var innerIcon2 = new FontIcon(Material2AL.LOCK);
|
||||
innerIcon2.getStyleClass().add("inner-icon");
|
||||
|
||||
var stackIcon2 = new StackedFontIcon();
|
||||
stackIcon2.getChildren().addAll(outerIcon2, innerIcon2);
|
||||
new CSSFragment("""
|
||||
.stacked-ikonli-font-icon > .outer-icon {
|
||||
-fx-icon-size: 48px;
|
||||
}
|
||||
.stacked-ikonli-font-icon > .inner-icon {
|
||||
-fx-icon-size: 24px;
|
||||
}
|
||||
""").addTo(stackIcon2);
|
||||
|
||||
var content = new HBox(BLOCK_HGAP, stackIcon1, stackIcon2);
|
||||
|
||||
return new SampleBlock("Stacking Icons", content);
|
||||
return new VBox(VGAP_10, description, filterBox, iconBrowser);
|
||||
}
|
||||
}
|
||||
|
71
sampler/src/main/java/atlantafx/sampler/page/general/OverviewPage.java
Executable file
71
sampler/src/main/java/atlantafx/sampler/page/general/OverviewPage.java
Executable file
@ -0,0 +1,71 @@
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
|
||||
package atlantafx.sampler.page.general;
|
||||
|
||||
import static atlantafx.sampler.util.Containers.setScrollConstraints;
|
||||
import static javafx.scene.control.ScrollPane.ScrollBarPolicy.AS_NEEDED;
|
||||
|
||||
import atlantafx.sampler.Resources;
|
||||
import atlantafx.sampler.page.Page;
|
||||
import java.io.IOException;
|
||||
import javafx.fxml.FXMLLoader;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.scene.Parent;
|
||||
import javafx.scene.control.ScrollPane;
|
||||
import javafx.scene.layout.Pane;
|
||||
import javafx.scene.layout.Priority;
|
||||
import javafx.scene.layout.VBox;
|
||||
|
||||
public class OverviewPage extends ScrollPane implements Page {
|
||||
|
||||
public static final String NAME = "Overview";
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return NAME;
|
||||
}
|
||||
|
||||
public OverviewPage() {
|
||||
super();
|
||||
|
||||
try {
|
||||
var wrapper = new VBox();
|
||||
wrapper.setAlignment(Pos.TOP_CENTER);
|
||||
|
||||
var loader = new FXMLLoader(
|
||||
Resources.getResource("assets/fxml/overview/index.fxml").toURL()
|
||||
);
|
||||
Parent fxmlContent = loader.load();
|
||||
((Pane) fxmlContent).setMaxWidth(Page.MAX_WIDTH);
|
||||
VBox.setVgrow(fxmlContent, Priority.ALWAYS);
|
||||
wrapper.getChildren().setAll(fxmlContent);
|
||||
|
||||
setScrollConstraints(this, AS_NEEDED, true, AS_NEEDED, true);
|
||||
setMaxHeight(20_000);
|
||||
setContent(wrapper);
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException("Unable to load FXML file", e);
|
||||
}
|
||||
|
||||
setId("overview");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Parent getView() {
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canDisplaySourceCode() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canChangeThemeSettings() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reset() {
|
||||
}
|
||||
}
|
@ -2,71 +2,51 @@
|
||||
|
||||
package atlantafx.sampler.page.general;
|
||||
|
||||
import static atlantafx.sampler.event.ThemeEvent.EventType.COLOR_CHANGE;
|
||||
import static atlantafx.sampler.event.ThemeEvent.EventType.THEME_ADD;
|
||||
import static atlantafx.sampler.event.ThemeEvent.EventType.THEME_CHANGE;
|
||||
import static atlantafx.sampler.event.ThemeEvent.EventType.THEME_REMOVE;
|
||||
import static atlantafx.sampler.page.SampleBlock.BLOCK_HGAP;
|
||||
import static atlantafx.sampler.page.SampleBlock.BLOCK_VGAP;
|
||||
import static atlantafx.sampler.util.Controls.hyperlink;
|
||||
import static atlantafx.sampler.event.ThemeEvent.EventType;
|
||||
|
||||
import atlantafx.base.theme.Styles;
|
||||
import atlantafx.base.util.BBCodeParser;
|
||||
import atlantafx.sampler.Resources;
|
||||
import atlantafx.sampler.event.DefaultEventBus;
|
||||
import atlantafx.sampler.event.ThemeEvent;
|
||||
import atlantafx.sampler.page.AbstractPage;
|
||||
import atlantafx.sampler.page.Page;
|
||||
import atlantafx.sampler.page.OutlinePage;
|
||||
import atlantafx.sampler.theme.SamplerTheme;
|
||||
import atlantafx.sampler.theme.ThemeManager;
|
||||
import java.net.URI;
|
||||
import atlantafx.sampler.util.Lazy;
|
||||
import java.util.Objects;
|
||||
import java.util.function.Consumer;
|
||||
import javafx.beans.property.ReadOnlyObjectProperty;
|
||||
import javafx.collections.FXCollections;
|
||||
import javafx.geometry.HPos;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.control.Button;
|
||||
import javafx.scene.control.ChoiceBox;
|
||||
import javafx.scene.control.ComboBox;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.control.Spinner;
|
||||
import javafx.scene.control.Tooltip;
|
||||
import javafx.scene.image.Image;
|
||||
import javafx.scene.image.ImageView;
|
||||
import javafx.scene.layout.GridPane;
|
||||
import javafx.scene.layout.HBox;
|
||||
import javafx.scene.layout.VBox;
|
||||
import javafx.scene.text.Text;
|
||||
import javafx.scene.text.TextFlow;
|
||||
import javafx.scene.paint.Color;
|
||||
import javafx.scene.text.Font;
|
||||
import javafx.util.Duration;
|
||||
import javafx.util.StringConverter;
|
||||
import org.kordamp.ikonli.javafx.FontIcon;
|
||||
import org.kordamp.ikonli.material2.Material2OutlinedMZ;
|
||||
|
||||
@SuppressWarnings("UnnecessaryLambda")
|
||||
public class ThemePage extends AbstractPage {
|
||||
public class ThemePage extends OutlinePage {
|
||||
|
||||
public static final String NAME = "Theme";
|
||||
|
||||
private static final ThemeManager TM = ThemeManager.getInstance();
|
||||
private static final String DEFAULT_FONT_ID = "Default";
|
||||
private static final Image SCENE_BUILDER_ICON = new Image(
|
||||
Resources.getResourceAsStream("images/scene-builder_32.png")
|
||||
);
|
||||
|
||||
private final Consumer<ColorPaletteBlock> colorBlockActionHandler = colorBlock -> {
|
||||
ContrastCheckerDialog dialog = getOrCreateContrastCheckerDialog();
|
||||
dialog.getContent().setValues(
|
||||
colorBlock.getFgColorName(),
|
||||
colorBlock.getFgColor(),
|
||||
colorBlock.getBgColorName(),
|
||||
colorBlock.getBgColor()
|
||||
);
|
||||
overlay.setContent(dialog, HPos.CENTER);
|
||||
overlay.toFront();
|
||||
};
|
||||
|
||||
private final ColorPalette colorPalette = new ColorPalette(colorBlockActionHandler);
|
||||
private final ColorScale colorScale = new ColorScale();
|
||||
private final ChoiceBox<SamplerTheme> themeSelector = createThemeSelector();
|
||||
|
||||
private ThemeRepoManagerDialog themeRepoManagerDialog;
|
||||
private ContrastCheckerDialog contrastCheckerDialog;
|
||||
private SceneBuilderDialog sceneBuilderDialog;
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return NAME;
|
||||
@ -82,21 +62,81 @@ public class ThemePage extends AbstractPage {
|
||||
return false;
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
|
||||
private final Lazy<ThemeRepoManagerDialog> themeRepoManagerDialog;
|
||||
private final Lazy<ContrastCheckerDialog> contrastCheckerDialog;
|
||||
private final Lazy<SceneBuilderDialog> sceneBuilderDialog;
|
||||
private final ColorPalette colorPalette;
|
||||
private final ColorScale colorScale = new ColorScale();
|
||||
private final ChoiceBox<SamplerTheme> themeSelector = createThemeSelector();
|
||||
private final ComboBox<String> fontFamilyChooser = createFontFamilyChooser();
|
||||
private final Spinner<Integer> fontSizeSpinner = createFontSizeSpinner();
|
||||
|
||||
public ThemePage() {
|
||||
super();
|
||||
|
||||
createView();
|
||||
colorPalette = new ColorPalette(colorBlock -> {
|
||||
ContrastCheckerDialog dialog = getContrastCheckerDialog();
|
||||
dialog.getContent().setValues(
|
||||
colorBlock.getFgColorName(),
|
||||
colorBlock.getFgColor(),
|
||||
colorBlock.getBgColorName(),
|
||||
colorBlock.getBgColor()
|
||||
);
|
||||
overlay.setContent(dialog, HPos.CENTER);
|
||||
overlay.toFront();
|
||||
});
|
||||
|
||||
themeRepoManagerDialog = new Lazy<>(() -> {
|
||||
var dialog = new ThemeRepoManagerDialog();
|
||||
dialog.setOnCloseRequest(() -> {
|
||||
overlay.removeContent();
|
||||
overlay.toBack();
|
||||
});
|
||||
|
||||
return dialog;
|
||||
});
|
||||
|
||||
contrastCheckerDialog = new Lazy<>(() -> {
|
||||
var dialog = new ContrastCheckerDialog(getBgBaseColorProperty());
|
||||
dialog.setOnCloseRequest(() -> {
|
||||
overlay.removeContent();
|
||||
overlay.toBack();
|
||||
});
|
||||
|
||||
return dialog;
|
||||
});
|
||||
|
||||
sceneBuilderDialog = new Lazy<>(() -> {
|
||||
var dialog = new SceneBuilderDialog();
|
||||
dialog.setOnCloseRequest(() -> {
|
||||
overlay.removeContent();
|
||||
overlay.toBack();
|
||||
dialog.reset();
|
||||
});
|
||||
|
||||
return dialog;
|
||||
});
|
||||
|
||||
DefaultEventBus.getInstance().subscribe(ThemeEvent.class, e -> {
|
||||
if (e.getEventType() == THEME_ADD || e.getEventType() == THEME_REMOVE) {
|
||||
var eventType = e.getEventType();
|
||||
if (eventType == EventType.THEME_ADD || eventType == EventType.THEME_REMOVE) {
|
||||
themeSelector.getItems().setAll(TM.getRepository().getAll());
|
||||
selectCurrentTheme();
|
||||
}
|
||||
if (e.getEventType() == THEME_CHANGE || e.getEventType() == COLOR_CHANGE) {
|
||||
if (eventType == EventType.THEME_CHANGE || eventType == EventType.COLOR_CHANGE) {
|
||||
colorPalette.updateColorInfo(Duration.seconds(1));
|
||||
colorScale.updateColorInfo(Duration.seconds(1));
|
||||
}
|
||||
});
|
||||
|
||||
addSection("Theme", createThemeManagementSection());
|
||||
addSection("Scene Builder", createSceneBuilderSection());
|
||||
addSection("Color Palette", createColorPaletteSection());
|
||||
addSection("Color Scale", createColorScaleSection());
|
||||
|
||||
selectCurrentTheme();
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -106,31 +146,12 @@ public class ThemePage extends AbstractPage {
|
||||
colorScale.updateColorInfo(Duration.ZERO);
|
||||
}
|
||||
|
||||
private void createView() {
|
||||
var noteText = new TextFlow(
|
||||
new Text("AtlantaFX follows "),
|
||||
hyperlink("Github Primer interface guidelines",
|
||||
URI.create("https://primer.style/design/foundations/color")),
|
||||
new Text(" and color system.")
|
||||
);
|
||||
|
||||
setUserContent(new VBox(
|
||||
Page.PAGE_VGAP,
|
||||
createOptionsGrid(),
|
||||
noteText,
|
||||
colorPalette,
|
||||
colorScale
|
||||
));
|
||||
|
||||
selectCurrentTheme();
|
||||
}
|
||||
|
||||
private GridPane createOptionsGrid() {
|
||||
private Node createThemeManagementSection() {
|
||||
var themeRepoBtn = new Button("", new FontIcon(Material2OutlinedMZ.SETTINGS));
|
||||
themeRepoBtn.getStyleClass().addAll(Styles.BUTTON_ICON, Styles.FLAT);
|
||||
themeRepoBtn.setTooltip(new Tooltip("Settings"));
|
||||
themeRepoBtn.setOnAction(e -> {
|
||||
ThemeRepoManagerDialog dialog = getOrCreateThemeRepoManagerDialog();
|
||||
ThemeRepoManagerDialog dialog = themeRepoManagerDialog.get();
|
||||
overlay.setContent(dialog, HPos.CENTER);
|
||||
dialog.getContent().update();
|
||||
overlay.toFront();
|
||||
@ -138,41 +159,78 @@ public class ThemePage extends AbstractPage {
|
||||
|
||||
var accentSelector = new AccentColorSelector();
|
||||
|
||||
var sceneBuilderBtn = new Button("SceneBuilder Integration");
|
||||
sceneBuilderBtn.setGraphic(new ImageView(SCENE_BUILDER_ICON));
|
||||
sceneBuilderBtn.setOnAction(e -> {
|
||||
SceneBuilderDialog dialog = getOrCreateScneBuilderDialog();
|
||||
overlay.setContent(dialog, HPos.CENTER);
|
||||
overlay.toFront();
|
||||
});
|
||||
|
||||
// ~
|
||||
|
||||
var grid = new GridPane();
|
||||
grid.setHgap(BLOCK_HGAP);
|
||||
grid.setVgap(BLOCK_VGAP);
|
||||
|
||||
grid.add(new Label("Color theme"), 0, 0);
|
||||
grid.add(themeSelector, 1, 0);
|
||||
grid.add(themeRepoBtn, 2, 0);
|
||||
grid.add(new Label("Accent color"), 0, 1);
|
||||
grid.add(accentSelector, 1, 1);
|
||||
grid.add(sceneBuilderBtn, 0, 2, GridPane.REMAINING, 1);
|
||||
grid.setHgap(HGAP_20);
|
||||
grid.setVgap(VGAP_10);
|
||||
grid.addRow(0, new Label("Color theme"), themeSelector, themeRepoBtn);
|
||||
grid.addRow(1, new Label("Accent color"), accentSelector);
|
||||
grid.addRow(2, new Label("Font"), new HBox(10, fontFamilyChooser, fontSizeSpinner));
|
||||
|
||||
return grid;
|
||||
}
|
||||
|
||||
private Node createSceneBuilderSection() {
|
||||
var sceneBuilderBtn = new Button("SceneBuilder Integration");
|
||||
sceneBuilderBtn.setGraphic(new ImageView(SCENE_BUILDER_ICON));
|
||||
sceneBuilderBtn.setOnAction(e -> {
|
||||
SceneBuilderDialog dialog = sceneBuilderDialog.get();
|
||||
overlay.setContent(dialog, HPos.CENTER);
|
||||
overlay.toFront();
|
||||
});
|
||||
|
||||
var description = BBCodeParser.createFormattedText("""
|
||||
While SceneBuilder does not support adding custom themes, it is \
|
||||
possible to overwrite looked-up CSS paths to make the existing \
|
||||
SceneBuilder menu options load custom CSS files."""
|
||||
);
|
||||
|
||||
return new VBox(VGAP_20, description, sceneBuilderBtn);
|
||||
}
|
||||
|
||||
private Node createColorPaletteSection() {
|
||||
var description = createFormattedText("""
|
||||
AtlantaFX follows [url=https://primer.style/design/foundations/color]GitHub \
|
||||
Primer interface guidelines[/url] and color system.
|
||||
|
||||
Color contrast between text and its background must meet \
|
||||
[url=https://www.w3.org/WAI/WCAG21/Understanding/contrast-minimum.html]required WCAG standards[/url]:
|
||||
|
||||
[ul]
|
||||
[li]4.5:1 for normal text[/li]
|
||||
[li]3:1 for large text (>24px)[/li]
|
||||
[li]3:1 for UI elements and graphics[/li]
|
||||
[li]no contrast requirement for decorative and disabled elements[/li][/ul]
|
||||
|
||||
Click on any color block to observe and modify color combination via built-in contrast checker.
|
||||
""", true
|
||||
);
|
||||
|
||||
return new VBox(VGAP_10, description, colorPalette);
|
||||
}
|
||||
|
||||
private Node createColorScaleSection() {
|
||||
var description = createFormattedText("""
|
||||
Avoid referencing scale variables directly when building UI that needs \
|
||||
to adapt to different color themes. Instead, use the functional variables \
|
||||
listed above.""", false
|
||||
);
|
||||
|
||||
return new VBox(VGAP_10, description, colorScale);
|
||||
}
|
||||
|
||||
private ChoiceBox<SamplerTheme> createThemeSelector() {
|
||||
var selector = new ChoiceBox<SamplerTheme>();
|
||||
selector.getItems().setAll(TM.getRepository().getAll());
|
||||
selector.getSelectionModel().selectedItemProperty().addListener((obs, old, val) -> {
|
||||
var choiceBox = new ChoiceBox<SamplerTheme>();
|
||||
choiceBox.getItems().setAll(TM.getRepository().getAll());
|
||||
choiceBox.getSelectionModel().selectedItemProperty().addListener((obs, old, val) -> {
|
||||
if (val != null && getScene() != null) {
|
||||
TM.setTheme(val);
|
||||
}
|
||||
});
|
||||
selector.setPrefWidth(250);
|
||||
choiceBox.setPrefWidth(310);
|
||||
|
||||
selector.setConverter(new StringConverter<>() {
|
||||
choiceBox.setConverter(new StringConverter<>() {
|
||||
@Override
|
||||
public String toString(SamplerTheme theme) {
|
||||
return theme != null ? theme.getName() : "";
|
||||
@ -187,56 +245,66 @@ public class ThemePage extends AbstractPage {
|
||||
}
|
||||
});
|
||||
|
||||
return selector;
|
||||
return choiceBox;
|
||||
}
|
||||
|
||||
private ComboBox<String> createFontFamilyChooser() {
|
||||
var comboBox = new ComboBox<String>();
|
||||
comboBox.setPrefWidth(200);
|
||||
|
||||
// keyword to reset font family to its default value
|
||||
comboBox.getItems().add(DEFAULT_FONT_ID);
|
||||
comboBox.getItems().addAll(FXCollections.observableArrayList(Font.getFamilies()));
|
||||
|
||||
// select active font family value on page load
|
||||
comboBox.getSelectionModel().select(TM.getFontFamily());
|
||||
comboBox.valueProperty().addListener((obs, old, val) -> {
|
||||
if (val != null) {
|
||||
TM.setFontFamily(DEFAULT_FONT_ID.equals(val) ? ThemeManager.DEFAULT_FONT_FAMILY_NAME : val);
|
||||
}
|
||||
});
|
||||
|
||||
return comboBox;
|
||||
}
|
||||
|
||||
private Spinner<Integer> createFontSizeSpinner() {
|
||||
var spinner = new Spinner<Integer>(
|
||||
ThemeManager.SUPPORTED_FONT_SIZE.get(0),
|
||||
ThemeManager.SUPPORTED_FONT_SIZE.get(ThemeManager.SUPPORTED_FONT_SIZE.size() - 1),
|
||||
TM.getFontSize()
|
||||
);
|
||||
spinner.setPrefWidth(100);
|
||||
|
||||
// Instead of this we should obtain font size from a rendered node.
|
||||
// But since it's not trivial (thanks to JavaFX doesn't expose relevant API)
|
||||
// we just keep current font size inside ThemeManager singleton.
|
||||
// It works fine if ThemeManager default font size value matches
|
||||
// default theme font size value.
|
||||
spinner.getValueFactory().setValue(TM.getFontSize());
|
||||
|
||||
spinner.valueProperty().addListener((obs, old, val) -> {
|
||||
if (val != null) {
|
||||
TM.setFontSize(val);
|
||||
}
|
||||
});
|
||||
|
||||
return spinner;
|
||||
}
|
||||
|
||||
private void selectCurrentTheme() {
|
||||
if (TM.getTheme() == null) {
|
||||
return;
|
||||
}
|
||||
if (TM.getTheme() != null) {
|
||||
themeSelector.getItems().stream()
|
||||
.filter(t -> Objects.equals(TM.getTheme().getName(), t.getName()))
|
||||
.findFirst()
|
||||
.ifPresent(t -> themeSelector.getSelectionModel().select(t));
|
||||
}
|
||||
|
||||
private ThemeRepoManagerDialog getOrCreateThemeRepoManagerDialog() {
|
||||
if (themeRepoManagerDialog == null) {
|
||||
themeRepoManagerDialog = new ThemeRepoManagerDialog();
|
||||
}
|
||||
|
||||
themeRepoManagerDialog.setOnCloseRequest(() -> {
|
||||
overlay.removeContent();
|
||||
overlay.toBack();
|
||||
});
|
||||
|
||||
return themeRepoManagerDialog;
|
||||
private ContrastCheckerDialog getContrastCheckerDialog() {
|
||||
return contrastCheckerDialog.get();
|
||||
}
|
||||
|
||||
private ContrastCheckerDialog getOrCreateContrastCheckerDialog() {
|
||||
if (contrastCheckerDialog == null) {
|
||||
contrastCheckerDialog = new ContrastCheckerDialog(colorPalette.bgBaseColorProperty());
|
||||
}
|
||||
|
||||
contrastCheckerDialog.setOnCloseRequest(() -> {
|
||||
overlay.removeContent();
|
||||
overlay.toBack();
|
||||
});
|
||||
|
||||
return contrastCheckerDialog;
|
||||
}
|
||||
|
||||
private SceneBuilderDialog getOrCreateScneBuilderDialog() {
|
||||
if (sceneBuilderDialog == null) {
|
||||
sceneBuilderDialog = new SceneBuilderDialog();
|
||||
}
|
||||
|
||||
sceneBuilderDialog.setOnCloseRequest(() -> {
|
||||
overlay.removeContent();
|
||||
overlay.toBack();
|
||||
sceneBuilderDialog.reset();
|
||||
});
|
||||
|
||||
return sceneBuilderDialog;
|
||||
private ReadOnlyObjectProperty<Color> getBgBaseColorProperty() {
|
||||
return colorPalette.bgBaseColorProperty();
|
||||
}
|
||||
}
|
||||
|
@ -2,360 +2,323 @@
|
||||
|
||||
package atlantafx.sampler.page.general;
|
||||
|
||||
import static atlantafx.base.theme.Styles.ACCENT;
|
||||
import static atlantafx.base.theme.Styles.DANGER;
|
||||
import static atlantafx.base.theme.Styles.SUCCESS;
|
||||
import static atlantafx.base.theme.Styles.TEXT;
|
||||
import static atlantafx.base.theme.Styles.TEXT_BOLD;
|
||||
import static atlantafx.base.theme.Styles.TEXT_BOLDER;
|
||||
import static atlantafx.base.theme.Styles.TEXT_CAPTION;
|
||||
import static atlantafx.base.theme.Styles.TEXT_ITALIC;
|
||||
import static atlantafx.base.theme.Styles.TEXT_LIGHTER;
|
||||
import static atlantafx.base.theme.Styles.TEXT_MUTED;
|
||||
import static atlantafx.base.theme.Styles.TEXT_NORMAL;
|
||||
import static atlantafx.base.theme.Styles.TEXT_OBLIQUE;
|
||||
import static atlantafx.base.theme.Styles.TEXT_SMALL;
|
||||
import static atlantafx.base.theme.Styles.TEXT_STRIKETHROUGH;
|
||||
import static atlantafx.base.theme.Styles.TEXT_SUBTLE;
|
||||
import static atlantafx.base.theme.Styles.TEXT_UNDERLINED;
|
||||
import static atlantafx.base.theme.Styles.TITLE_1;
|
||||
import static atlantafx.base.theme.Styles.TITLE_2;
|
||||
import static atlantafx.base.theme.Styles.TITLE_3;
|
||||
import static atlantafx.base.theme.Styles.TITLE_4;
|
||||
import static atlantafx.base.theme.Styles.WARNING;
|
||||
import static atlantafx.sampler.event.ThemeEvent.EventType.FONT_CHANGE;
|
||||
import static atlantafx.sampler.event.ThemeEvent.EventType.THEME_CHANGE;
|
||||
import static atlantafx.sampler.page.SampleBlock.BLOCK_HGAP;
|
||||
import static atlantafx.sampler.page.SampleBlock.BLOCK_VGAP;
|
||||
import static atlantafx.sampler.theme.ThemeManager.DEFAULT_FONT_FAMILY_NAME;
|
||||
import static atlantafx.sampler.theme.ThemeManager.SUPPORTED_FONT_SIZE;
|
||||
import static atlantafx.sampler.event.ThemeEvent.EventType;
|
||||
|
||||
import atlantafx.base.theme.Styles;
|
||||
import atlantafx.base.util.BBCodeParser;
|
||||
import atlantafx.sampler.event.DefaultEventBus;
|
||||
import atlantafx.sampler.event.ThemeEvent;
|
||||
import atlantafx.sampler.page.AbstractPage;
|
||||
import atlantafx.sampler.page.SampleBlock;
|
||||
import atlantafx.sampler.theme.ThemeManager;
|
||||
import atlantafx.sampler.page.ExampleBox;
|
||||
import atlantafx.sampler.page.OutlinePage;
|
||||
import atlantafx.sampler.page.Snippet;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
import javafx.animation.KeyFrame;
|
||||
import javafx.animation.Timeline;
|
||||
import javafx.collections.FXCollections;
|
||||
import javafx.geometry.Insets;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.control.ComboBox;
|
||||
import javafx.scene.control.Hyperlink;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.control.Spinner;
|
||||
import javafx.scene.layout.FlowPane;
|
||||
import javafx.scene.layout.GridPane;
|
||||
import javafx.scene.layout.HBox;
|
||||
import javafx.scene.layout.Pane;
|
||||
import javafx.scene.layout.VBox;
|
||||
import javafx.scene.text.Font;
|
||||
import javafx.scene.text.Text;
|
||||
import javafx.scene.text.TextFlow;
|
||||
import javafx.util.Duration;
|
||||
|
||||
public class TypographyPage extends AbstractPage {
|
||||
public class TypographyPage extends OutlinePage {
|
||||
|
||||
public static final String NAME = "Typography";
|
||||
private static final int CONTROL_WIDTH = 200;
|
||||
private static final String DEFAULT_FONT_ID = "Default";
|
||||
private static final ThemeManager TM = ThemeManager.getInstance();
|
||||
|
||||
private Pane fontSizeSampleContent;
|
||||
private GridPane fontSizeGridPane;
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return NAME;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canDisplaySourceCode() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canChangeThemeSettings() {
|
||||
return false;
|
||||
}
|
||||
|
||||
public TypographyPage() {
|
||||
super();
|
||||
|
||||
createView();
|
||||
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].""");
|
||||
addSection("Font Size", fontSizeExample());
|
||||
addSection("Font Weight", fontWeightExample());
|
||||
addSection("Font Style", fontStyleExample());
|
||||
addSection("Text Color", textColorExample());
|
||||
addSection("Hyperlink", hyperlinkExample());
|
||||
|
||||
DefaultEventBus.getInstance().subscribe(ThemeEvent.class, e -> {
|
||||
if (e.getEventType() == THEME_CHANGE || e.getEventType() == FONT_CHANGE) {
|
||||
var eventType = e.getEventType();
|
||||
if (eventType == EventType.THEME_CHANGE || eventType == EventType.FONT_CHANGE) {
|
||||
updateFontInfo(Duration.seconds(1));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void createView() {
|
||||
var controlsGrid = new GridPane();
|
||||
controlsGrid.setHgap(BLOCK_HGAP);
|
||||
controlsGrid.setVgap(BLOCK_VGAP);
|
||||
controlsGrid.add(new Label("Font family"), 0, 0);
|
||||
controlsGrid.add(createFontFamilyChooser(), 1, 0);
|
||||
controlsGrid.add(new Label("Font size"), 0, 1);
|
||||
controlsGrid.add(crateFontSizeSpinner(), 1, 1);
|
||||
|
||||
var fontSizeSample = fontSizeSample();
|
||||
fontSizeSampleContent = (Pane) fontSizeSample.getContent();
|
||||
|
||||
setUserContent(new VBox(
|
||||
PAGE_VGAP,
|
||||
controlsGrid,
|
||||
fontSizeSample,
|
||||
fontWeightSample(),
|
||||
expandingHBox(fontStyleSample(), textColorSample(), hyperlinkSample()),
|
||||
textFlowSample()
|
||||
));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onRendered() {
|
||||
super.onRendered();
|
||||
// font metrics can only be obtained by requesting from a rendered node
|
||||
updateFontInfo(Duration.ZERO);
|
||||
}
|
||||
|
||||
private ComboBox<String> createFontFamilyChooser() {
|
||||
ComboBox<String> comboBox = new ComboBox<>();
|
||||
comboBox.getItems().add(DEFAULT_FONT_ID); // keyword to reset font family to its default value
|
||||
comboBox.getItems().addAll(FXCollections.observableArrayList(Font.getFamilies()));
|
||||
comboBox.setPrefWidth(CONTROL_WIDTH);
|
||||
comboBox.getSelectionModel().select(TM.getFontFamily()); // select active font family value on page load
|
||||
comboBox.valueProperty().addListener((obs, old, val) -> {
|
||||
if (val != null) {
|
||||
TM.setFontFamily(DEFAULT_FONT_ID.equals(val) ? DEFAULT_FONT_FAMILY_NAME : val);
|
||||
}
|
||||
});
|
||||
|
||||
return comboBox;
|
||||
}
|
||||
|
||||
private Spinner<Integer> crateFontSizeSpinner() {
|
||||
var spinner = new Spinner<Integer>(
|
||||
SUPPORTED_FONT_SIZE.get(0),
|
||||
SUPPORTED_FONT_SIZE.get(SUPPORTED_FONT_SIZE.size() - 1),
|
||||
TM.getFontSize()
|
||||
);
|
||||
spinner.getStyleClass().add(Spinner.STYLE_CLASS_SPLIT_ARROWS_HORIZONTAL);
|
||||
spinner.setPrefWidth(CONTROL_WIDTH);
|
||||
|
||||
// Instead of this we should obtain font size from a rendered node.
|
||||
// But since it's not trivial (thanks to JavaFX doesn't expose relevant API)
|
||||
// we just keep current font size inside ThemeManager singleton.
|
||||
// It works fine if ThemeManager default font size value matches
|
||||
// default theme font size value.
|
||||
spinner.getValueFactory().setValue(TM.getFontSize());
|
||||
|
||||
spinner.valueProperty().addListener((obs, old, val) -> {
|
||||
if (val != null) {
|
||||
TM.setFontSize(val);
|
||||
updateFontInfo(Duration.seconds(1));
|
||||
}
|
||||
});
|
||||
|
||||
return spinner;
|
||||
}
|
||||
|
||||
private void updateFontInfo(Duration delay) {
|
||||
var t = new Timeline(new KeyFrame(delay));
|
||||
t.setOnFinished(e -> {
|
||||
Map<String, Node> map = fontSizeSampleContent.getChildren().stream()
|
||||
if (fontSizeGridPane == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
Map<String, Node> map = fontSizeGridPane.getChildren().stream()
|
||||
.collect(Collectors.toMap(
|
||||
n -> GridPane.getColumnIndex(n).toString() + GridPane.getRowIndex(n).toString(),
|
||||
n -> n
|
||||
));
|
||||
((Label) map.get("10")).setText(String.format("%.0fpx", getFontSize(map.get("00"))));
|
||||
((Label) map.get("11")).setText(String.format("%.0fpx", getFontSize(map.get("01"))));
|
||||
((Label) map.get("12")).setText(String.format("%.0fpx", getFontSize(map.get("02"))));
|
||||
((Label) map.get("13")).setText(String.format("%.0fpx", getFontSize(map.get("03"))));
|
||||
((Label) map.get("30")).setText(String.format("%.0fpx", getFontSize(map.get("20"))));
|
||||
((Label) map.get("31")).setText(String.format("%.0fpx", getFontSize(map.get("21"))));
|
||||
((Label) map.get("32")).setText(String.format("%.0fpx", getFontSize(map.get("22"))));
|
||||
});
|
||||
t.play();
|
||||
((Label) map.get("10")).setText(String.format("=%.0fpx", getFontSize(map.get("00"))));
|
||||
((Label) map.get("11")).setText(String.format("=%.0fpx", getFontSize(map.get("01"))));
|
||||
((Label) map.get("12")).setText(String.format("=%.0fpx", getFontSize(map.get("02"))));
|
||||
((Label) map.get("13")).setText(String.format("=%.0fpx", getFontSize(map.get("03"))));
|
||||
((Label) map.get("30")).setText(String.format("=%.0fpx", getFontSize(map.get("20"))));
|
||||
((Label) map.get("31")).setText(String.format("=%.0fpx", getFontSize(map.get("21"))));
|
||||
((Label) map.get("32")).setText(String.format("=%.0fpx", getFontSize(map.get("22"))));
|
||||
}
|
||||
|
||||
private double getFontSize(Node node) {
|
||||
return (node instanceof Text text) ? Math.ceil(text.getFont().getSize()) : 0;
|
||||
}
|
||||
|
||||
private SampleBlock fontSizeSample() {
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
|
||||
private ExampleBox fontSizeExample() {
|
||||
//snippet_1:start
|
||||
var title1Text = new Text("Title 1");
|
||||
title1Text.getStyleClass().addAll(Styles.TITLE_1);
|
||||
|
||||
var title2Text = new Text("Title 2");
|
||||
title2Text.getStyleClass().addAll(Styles.TITLE_2);
|
||||
|
||||
var title3Text = new Text("Title 3");
|
||||
title3Text.getStyleClass().addAll(Styles.TITLE_3);
|
||||
|
||||
var title4Text = new Text("Title 4");
|
||||
title4Text.getStyleClass().addAll(Styles.TITLE_4);
|
||||
|
||||
var captionText = new Text("Caption");
|
||||
captionText.getStyleClass().addAll(Styles.TEXT_CAPTION);
|
||||
|
||||
var defaultText = new Text("Default");
|
||||
|
||||
var smallText = new Text("Small");
|
||||
smallText.getStyleClass().addAll(Styles.TEXT_SMALL);
|
||||
//snippet_1:end
|
||||
|
||||
var grid = new GridPane();
|
||||
grid.setHgap(BLOCK_HGAP);
|
||||
grid.setVgap(BLOCK_VGAP);
|
||||
|
||||
grid.add(createText("Title 1", TITLE_1), 0, 0);
|
||||
grid.add(createFontSizeLabel(), 1, 0);
|
||||
grid.add(createText("Title 2", TITLE_2), 0, 1);
|
||||
grid.add(createFontSizeLabel(), 1, 1);
|
||||
grid.add(createText("Title 3", TITLE_3), 0, 2);
|
||||
grid.add(createFontSizeLabel(), 1, 2);
|
||||
grid.add(createText("Title 4", TITLE_4), 0, 3);
|
||||
grid.add(createFontSizeLabel(), 1, 3);
|
||||
|
||||
grid.add(createText("Caption", TEXT_CAPTION), 2, 0);
|
||||
grid.add(createFontSizeLabel(), 3, 0);
|
||||
grid.add(createText("Default"), 2, 1);
|
||||
grid.add(createFontSizeLabel(), 3, 1);
|
||||
grid.add(createText("Small", TEXT_SMALL), 2, 2);
|
||||
grid.add(createFontSizeLabel(), 3, 2);
|
||||
|
||||
grid.setHgap(HGAP_20);
|
||||
grid.setVgap(VGAP_20);
|
||||
grid.addRow(0,
|
||||
title1Text, createFontSizeLabel(),
|
||||
captionText, createFontSizeLabel()
|
||||
);
|
||||
grid.addRow(1,
|
||||
title2Text, createFontSizeLabel(),
|
||||
defaultText, createFontSizeLabel()
|
||||
);
|
||||
grid.addRow(2,
|
||||
title3Text, createFontSizeLabel(),
|
||||
smallText, createFontSizeLabel()
|
||||
);
|
||||
grid.addRow(3, title4Text, createFontSizeLabel());
|
||||
grid.setAlignment(Pos.BASELINE_LEFT);
|
||||
|
||||
return new SampleBlock("Font Size", grid);
|
||||
fontSizeGridPane = grid;
|
||||
|
||||
var example = new ExampleBox(grid, new Snippet(getClass(), 1));
|
||||
example.setAllowDisable(false);
|
||||
|
||||
return example;
|
||||
}
|
||||
|
||||
private SampleBlock fontWeightSample() {
|
||||
private ExampleBox fontWeightExample() {
|
||||
//snippet_2:start
|
||||
class StyledText extends Text {
|
||||
|
||||
public StyledText(String text, String style) {
|
||||
super();
|
||||
setText(text);
|
||||
setStyle(style + "-fx-fill:-color-fg-default;");
|
||||
}
|
||||
}
|
||||
|
||||
var boldText = new Text("Bold");
|
||||
boldText.getStyleClass().addAll(Styles.TEXT_BOLD);
|
||||
|
||||
var bolderText = new Text("Bolder");
|
||||
bolderText.getStyleClass().addAll(Styles.TEXT_BOLDER);
|
||||
|
||||
var normalText = new Text("Normal");
|
||||
normalText.getStyleClass().addAll(Styles.TEXT_NORMAL);
|
||||
|
||||
var lighterText = new Text("Lighter");
|
||||
lighterText.getStyleClass().addAll(Styles.TEXT_LIGHTER);
|
||||
|
||||
var sample1 = new HBox(
|
||||
BLOCK_HGAP,
|
||||
createText("Bold", TEXT_BOLD),
|
||||
createText("Bolder", TEXT_BOLDER),
|
||||
createText("Normal", TEXT_NORMAL),
|
||||
createText("Lighter", TEXT_LIGHTER)
|
||||
HGAP_20, boldText, bolderText, normalText, lighterText
|
||||
);
|
||||
sample1.setAlignment(Pos.BASELINE_LEFT);
|
||||
|
||||
// ~
|
||||
var sample2 = new HBox(
|
||||
BLOCK_HGAP,
|
||||
createStyledText("900", "-fx-font-weight:900;"),
|
||||
createStyledText("800", "-fx-font-weight:800;"),
|
||||
createStyledText("700", "-fx-font-weight:700;"),
|
||||
createStyledText("600", "-fx-font-weight:600;"),
|
||||
createStyledText("500", "-fx-font-weight:500;"),
|
||||
createStyledText("400", "-fx-font-weight:400;"),
|
||||
createStyledText("300", "-fx-font-weight:300;"),
|
||||
createStyledText("200", "-fx-font-weight:200;"),
|
||||
createStyledText("100", "-fx-font-weight:100;")
|
||||
HGAP_20,
|
||||
new StyledText("900", "-fx-font-weight:900;"),
|
||||
new StyledText("800", "-fx-font-weight:800;"),
|
||||
new StyledText("700", "-fx-font-weight:700;"),
|
||||
new StyledText("600", "-fx-font-weight:600;"),
|
||||
new StyledText("500", "-fx-font-weight:500;"),
|
||||
new StyledText("400", "-fx-font-weight:400;"),
|
||||
new StyledText("300", "-fx-font-weight:300;"),
|
||||
new StyledText("200", "-fx-font-weight:200;"),
|
||||
new StyledText("100", "-fx-font-weight:100;"),
|
||||
new Text("\uD83E\uDC60 no difference")
|
||||
);
|
||||
sample2.setAlignment(Pos.BASELINE_LEFT);
|
||||
|
||||
// JDK-8090423:
|
||||
// https://bugs.openjdk.org/browse/JDK-8090423
|
||||
// Workaround:
|
||||
// https://edencoding.com/resources/css_properties/fx-font-weight/
|
||||
var sample3 = new HBox(
|
||||
BLOCK_HGAP,
|
||||
createStyledText("900", "-fx-font-family:'Inter Black';"),
|
||||
createStyledText("800", "-fx-font-family:'Inter Extra Bold';"),
|
||||
createStyledText("700", "-fx-font-family:'Inter Bold';"),
|
||||
createStyledText("600", "-fx-font-family:'Inter Semi Bold';"),
|
||||
createStyledText("500", "-fx-font-family:'Inter Medium';"),
|
||||
createStyledText("400", "-fx-font-family:'Inter Regular';"),
|
||||
createStyledText("300", "-fx-font-family:'Inter Light';"),
|
||||
createStyledText("200", "-fx-font-family:'Inter Extra Light';"),
|
||||
createStyledText("100", "-fx-font-family:'Inter Thin';")
|
||||
HGAP_20,
|
||||
new StyledText("900", "-fx-font-family:'Inter Black';"),
|
||||
new StyledText("800", "-fx-font-family:'Inter Extra Bold';"),
|
||||
new StyledText("700", "-fx-font-family:'Inter Bold';"),
|
||||
new StyledText("600", "-fx-font-family:'Inter Semi Bold';"),
|
||||
new StyledText("500", "-fx-font-family:'Inter Medium';"),
|
||||
new StyledText("400", "-fx-font-family:'Inter Regular';"),
|
||||
new StyledText("300", "-fx-font-family:'Inter Light';"),
|
||||
new StyledText("200", "-fx-font-family:'Inter Extra Light';"),
|
||||
new StyledText("100", "-fx-font-family:'Inter Thin';"),
|
||||
new Text("\uD83E\uDC60 workaround")
|
||||
);
|
||||
sample3.setAlignment(Pos.BASELINE_LEFT);
|
||||
//snippet_2:end
|
||||
|
||||
// JDK-8090423: https://bugs.openjdk.org/browse/JDK-8090423
|
||||
// Workaround: https://edencoding.com/resources/css_properties/fx-font-weight/
|
||||
return new SampleBlock("Font Weight", new VBox(
|
||||
BLOCK_VGAP,
|
||||
sample1,
|
||||
sample2,
|
||||
sample3,
|
||||
createText("JavaFX only supports Bold or Regular font weight. See the source code for workaround.", TEXT,
|
||||
WARNING)
|
||||
));
|
||||
var box = new VBox(VGAP_20, sample1, sample2, sample3);
|
||||
box.setAlignment(Pos.CENTER_LEFT);
|
||||
|
||||
var description = BBCodeParser.createFormattedText("""
|
||||
JavaFX [color="-color-danger-fg"]only supports Bold or Regular[/color] font weight. \
|
||||
See the source code for workaround."""
|
||||
);
|
||||
|
||||
var example = new ExampleBox(box, new Snippet(getClass(), 2), description);
|
||||
example.setAllowDisable(false);
|
||||
|
||||
return example;
|
||||
}
|
||||
|
||||
private SampleBlock fontStyleSample() {
|
||||
private ExampleBox fontStyleExample() {
|
||||
//snippet_3:start
|
||||
var italicText = new Text("Italic");
|
||||
italicText.getStyleClass().addAll(
|
||||
Styles.TEXT, Styles.TEXT_ITALIC
|
||||
);
|
||||
|
||||
var obliqueText = new Text("Oblique");
|
||||
obliqueText.getStyleClass().addAll(
|
||||
Styles.TEXT, Styles.TEXT_OBLIQUE
|
||||
);
|
||||
|
||||
var underlinedText = new Text("Underlined");
|
||||
underlinedText.getStyleClass().addAll(
|
||||
Styles.TEXT, Styles.TEXT_UNDERLINED
|
||||
);
|
||||
|
||||
var strikethroughText = new Text("Strikethrough");
|
||||
strikethroughText.getStyleClass().addAll(
|
||||
Styles.TEXT, Styles.TEXT_STRIKETHROUGH
|
||||
);
|
||||
//snippet_3:end
|
||||
|
||||
var box = new FlowPane(
|
||||
BLOCK_HGAP, BLOCK_VGAP,
|
||||
createText("Italic", TEXT_ITALIC),
|
||||
createText("Oblique", TEXT_OBLIQUE),
|
||||
createText("Underlined", TEXT_UNDERLINED),
|
||||
createText("Strikethrough", TEXT_STRIKETHROUGH)
|
||||
HGAP_20, VGAP_20,
|
||||
italicText, obliqueText, underlinedText, strikethroughText
|
||||
);
|
||||
box.setAlignment(Pos.BASELINE_LEFT);
|
||||
|
||||
return new SampleBlock("Font Style", box);
|
||||
var example = new ExampleBox(box, new Snippet(getClass(), 3));
|
||||
example.setAllowDisable(false);
|
||||
|
||||
return example;
|
||||
}
|
||||
|
||||
private SampleBlock textColorSample() {
|
||||
private ExampleBox textColorExample() {
|
||||
//snippet_4:start
|
||||
var accentText = new Text("Accent");
|
||||
accentText.getStyleClass().addAll(Styles.TEXT, Styles.ACCENT);
|
||||
|
||||
var successText = new Text("Success");
|
||||
successText.getStyleClass().addAll(Styles.TEXT, Styles.SUCCESS);
|
||||
|
||||
var warningText = new Text("Warning");
|
||||
warningText.getStyleClass().addAll(Styles.TEXT, Styles.WARNING);
|
||||
|
||||
var dangerText = new Text("Danger");
|
||||
dangerText.getStyleClass().addAll(Styles.TEXT, Styles.DANGER);
|
||||
|
||||
var mutedText = new Text("Muted");
|
||||
mutedText.getStyleClass().addAll(Styles.TEXT, Styles.TEXT_MUTED);
|
||||
|
||||
var subtleText = new Text("Subtle");
|
||||
subtleText.getStyleClass().addAll(Styles.TEXT, Styles.TEXT_SUBTLE);
|
||||
//snippet_4:end
|
||||
|
||||
var box = new FlowPane(
|
||||
BLOCK_HGAP, BLOCK_VGAP,
|
||||
createText("Accent", TEXT, ACCENT),
|
||||
createText("Success", TEXT, SUCCESS),
|
||||
createText("Warning", TEXT, WARNING),
|
||||
createText("Danger", TEXT, DANGER),
|
||||
createText("Muted", TEXT, TEXT_MUTED),
|
||||
createText("Subtle", TEXT, TEXT_SUBTLE)
|
||||
HGAP_20, VGAP_20,
|
||||
accentText, successText, warningText, dangerText, mutedText, subtleText
|
||||
);
|
||||
box.setAlignment(Pos.BASELINE_LEFT);
|
||||
|
||||
return new SampleBlock("Text Color", box);
|
||||
var example = new ExampleBox(box, new Snippet(getClass(), 4));
|
||||
example.setAllowDisable(false);
|
||||
|
||||
return example;
|
||||
}
|
||||
|
||||
private SampleBlock hyperlinkSample() {
|
||||
var linkNormal = createHyperlink("_Normal", false, false);
|
||||
private ExampleBox hyperlinkExample() {
|
||||
//snippet_5:start
|
||||
var linkNormal = new Hyperlink("_Normal");
|
||||
linkNormal.setMnemonicParsing(true);
|
||||
|
||||
var linkVisited = createHyperlink("_Visited", true, false);
|
||||
var linkVisited = new Hyperlink("_Visited");
|
||||
linkVisited.setVisited(true);
|
||||
linkVisited.setMnemonicParsing(true);
|
||||
|
||||
var linkBroken = createHyperlink("_Broken", true, false);
|
||||
var linkBroken = new Hyperlink("_Broken");
|
||||
linkBroken.setStyle("-color-link-fg-visited:-color-danger-fg;");
|
||||
linkBroken.setVisited(true);
|
||||
linkBroken.setMnemonicParsing(true);
|
||||
|
||||
var linkDisabled = new Hyperlink("Disabled");
|
||||
linkDisabled.setDisable(true);
|
||||
//snippet_5:end
|
||||
|
||||
var box = new FlowPane(
|
||||
BLOCK_HGAP, BLOCK_VGAP,
|
||||
linkNormal,
|
||||
linkVisited,
|
||||
linkBroken,
|
||||
createHyperlink("Disabled", false, true)
|
||||
HGAP_20, VGAP_20,
|
||||
linkNormal, linkVisited, linkBroken, linkDisabled
|
||||
);
|
||||
box.setAlignment(Pos.BASELINE_LEFT);
|
||||
|
||||
return new SampleBlock("Hyperlink", box);
|
||||
}
|
||||
var example = new ExampleBox(box, new Snippet(getClass(), 5));
|
||||
example.setAllowDisable(false);
|
||||
|
||||
private SampleBlock textFlowSample() {
|
||||
var textFlow = new TextFlow(
|
||||
new Text("Lorem ipsum dolor sit amet, consectetur adipiscing elit. "),
|
||||
new Hyperlink("Vivamus at lorem"),
|
||||
new Text(" in urna facilisis aliquam. Morbi ut "),
|
||||
new Hyperlink("velit"),
|
||||
new Text(" iaculis erat cursus molestie eget laoreet quam. "),
|
||||
new Text(" Vivamus eu nulla sapien. Sed et malesuada augue. Nullam nec "),
|
||||
new Hyperlink("consectetur"),
|
||||
new Text(" "),
|
||||
new Hyperlink("ipsum"),
|
||||
new Text(", eget facilisis enim. Suspendisse potenti. Nulla euismod, nisl sed dapibus pretium,"
|
||||
+ " augue ligula finibus arcu, in iaculis nulla neque a est. Sed in rutrum diam."
|
||||
+ " Donec quis arcu molestie, facilisis ex fringilla, "),
|
||||
new Hyperlink("volutpat velit"),
|
||||
new Text(".")
|
||||
);
|
||||
|
||||
return new SampleBlock("Text Flow", textFlow);
|
||||
}
|
||||
|
||||
private Text createText(String text, String... styleClasses) {
|
||||
var t = new Text(text);
|
||||
t.getStyleClass().addAll(styleClasses);
|
||||
t.setUserData(text);
|
||||
return t;
|
||||
}
|
||||
|
||||
private Text createStyledText(String text, String style) {
|
||||
var t = new Text(text);
|
||||
t.setStyle(style);
|
||||
return t;
|
||||
return example;
|
||||
}
|
||||
|
||||
private Label createFontSizeLabel() {
|
||||
var label = new Label();
|
||||
label.setPadding(new Insets(5, 40, 5, 10));
|
||||
label.setStyle("-fx-font-family:monospace;");
|
||||
return label;
|
||||
}
|
||||
|
||||
private Hyperlink createHyperlink(String text, boolean visited, boolean disabled) {
|
||||
var h = new Hyperlink(text);
|
||||
h.setVisited(visited);
|
||||
h.setDisable(disabled);
|
||||
return h;
|
||||
}
|
||||
}
|
||||
|
@ -1,68 +1,114 @@
|
||||
package atlantafx.sampler.page.showcase;
|
||||
|
||||
import static atlantafx.base.theme.Styles.ACCENT;
|
||||
|
||||
import atlantafx.sampler.page.AbstractPage;
|
||||
import atlantafx.sampler.util.NodeUtils;
|
||||
import atlantafx.base.controls.Spacer;
|
||||
import atlantafx.base.theme.Styles;
|
||||
import atlantafx.sampler.page.Page;
|
||||
import java.util.Objects;
|
||||
import javafx.beans.property.BooleanProperty;
|
||||
import javafx.beans.property.SimpleBooleanProperty;
|
||||
import javafx.css.PseudoClass;
|
||||
import javafx.geometry.Insets;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.control.Alert;
|
||||
import javafx.scene.control.Button;
|
||||
import javafx.scene.control.Hyperlink;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.layout.HBox;
|
||||
import javafx.scene.layout.Pane;
|
||||
import javafx.scene.layout.Priority;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import javafx.scene.layout.VBox;
|
||||
import javafx.stage.StageStyle;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.kordamp.ikonli.feather.Feather;
|
||||
import org.kordamp.ikonli.javafx.FontIcon;
|
||||
|
||||
public abstract class ShowcasePage extends AbstractPage {
|
||||
public abstract class ShowcasePage extends StackPane implements Page {
|
||||
|
||||
protected static final PseudoClass SHOWCASE_MODE = PseudoClass.getPseudoClass("showcase-mode");
|
||||
protected final StackPane showcase = new StackPane();
|
||||
protected final HBox expandBox = new HBox(10);
|
||||
protected final HBox collapseBox = new HBox(10);
|
||||
protected static final int DEFAULT_WIDTH = 800;
|
||||
protected static final int DEFAULT_HEIGHT = 600;
|
||||
|
||||
protected VBox showcaseWindow = new VBox();
|
||||
protected Label windowTitle = new Label();
|
||||
protected VBox showCaseContent = new VBox();
|
||||
protected int windowWidth = DEFAULT_WIDTH;
|
||||
protected int windowHeight = DEFAULT_HEIGHT;
|
||||
protected BooleanProperty maximized = new SimpleBooleanProperty();
|
||||
|
||||
public ShowcasePage() {
|
||||
super();
|
||||
|
||||
createShowcaseLayout();
|
||||
}
|
||||
|
||||
protected void createShowcaseLayout() {
|
||||
windowTitle.getStyleClass().addAll("title");
|
||||
|
||||
var maximizeBtn = new FontIcon(Feather.MAXIMIZE_2);
|
||||
maximizeBtn.getStyleClass().addAll(Styles.SMALL, Styles.FLAT);
|
||||
maximizeBtn.setOnMouseClicked(e -> maximized.set(!maximized.get()));
|
||||
|
||||
var windowHeader = new HBox(windowTitle, new Spacer(), maximizeBtn);
|
||||
windowHeader.getStyleClass().add("header");
|
||||
|
||||
showcaseWindow.getStyleClass().add("window");
|
||||
showcaseWindow.getChildren().setAll(windowHeader, showCaseContent);
|
||||
VBox.setVgrow(showCaseContent, Priority.ALWAYS);
|
||||
|
||||
getStyleClass().add("showcase-page");
|
||||
getChildren().setAll(showcaseWindow);
|
||||
|
||||
maximized.addListener((obs, old, val) -> {
|
||||
getScene().getRoot().pseudoClassStateChanged(SHOWCASE_MODE, val);
|
||||
maximizeBtn.setIconCode(val ? Feather.MINIMIZE_2 : Feather.MAXIMIZE_2);
|
||||
|
||||
var width = val ? -1 : windowWidth;
|
||||
var height = val ? -1 : windowHeight;
|
||||
showcaseWindow.setMinSize(width, height);
|
||||
showcaseWindow.setMaxSize(width, height);
|
||||
});
|
||||
}
|
||||
|
||||
protected void setWindowTitle(String text, @Nullable Node graphic) {
|
||||
windowTitle.setText(Objects.requireNonNull(text, "text"));
|
||||
if (graphic != null) {
|
||||
windowTitle.setGraphic(graphic);
|
||||
}
|
||||
}
|
||||
|
||||
protected void setShowCaseContent(Node node) {
|
||||
setShowCaseContent(node, DEFAULT_WIDTH, DEFAULT_HEIGHT);
|
||||
}
|
||||
|
||||
protected void setShowCaseContent(Node node, int width, int height) {
|
||||
Objects.requireNonNull(node, "node");
|
||||
|
||||
this.windowWidth = width > 0 ? width : DEFAULT_WIDTH;
|
||||
this.windowHeight = height > 0 ? height : DEFAULT_HEIGHT;
|
||||
showcaseWindow.setMinSize(width, height);
|
||||
showcaseWindow.setMaxSize(width, height);
|
||||
|
||||
showCaseContent.getChildren().setAll(node);
|
||||
VBox.setVgrow(node, Priority.ALWAYS);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Pane getView() {
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canDisplaySourceCode() {
|
||||
return false;
|
||||
}
|
||||
|
||||
protected void createShowcaseLayout() {
|
||||
var expandBtn = new Button("Expand");
|
||||
expandBtn.setGraphic(new FontIcon(Feather.MAXIMIZE_2));
|
||||
expandBtn.getStyleClass().add(ACCENT);
|
||||
expandBtn.setOnAction(e -> {
|
||||
expandBtn.getScene().getRoot().pseudoClassStateChanged(SHOWCASE_MODE, true);
|
||||
VBox.setVgrow(showcase, Priority.ALWAYS);
|
||||
NodeUtils.toggleVisibility(expandBox, false);
|
||||
NodeUtils.toggleVisibility(collapseBox, true);
|
||||
});
|
||||
expandBox.getChildren().setAll(expandBtn);
|
||||
expandBox.setAlignment(Pos.CENTER);
|
||||
|
||||
var collapseBtn = new Hyperlink("Exit showcase mode");
|
||||
collapseBtn.setOnAction(e -> {
|
||||
expandBtn.getScene().getRoot().pseudoClassStateChanged(SHOWCASE_MODE, false);
|
||||
VBox.setVgrow(showcase, Priority.NEVER);
|
||||
NodeUtils.toggleVisibility(expandBox, true);
|
||||
NodeUtils.toggleVisibility(collapseBox, false);
|
||||
});
|
||||
collapseBox.getChildren().setAll(new FontIcon(Feather.MINIMIZE_2), collapseBtn);
|
||||
collapseBox.setAlignment(Pos.CENTER_LEFT);
|
||||
collapseBox.setPadding(new Insets(5));
|
||||
NodeUtils.toggleVisibility(collapseBox, false);
|
||||
|
||||
setUserContent(new VBox(showcase, expandBox, collapseBox));
|
||||
@Override
|
||||
public boolean canChangeThemeSettings() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reset() {
|
||||
}
|
||||
|
||||
@SuppressWarnings("SameParameterValue")
|
||||
protected void showWarning(String header, String description) {
|
||||
var alert = new Alert(Alert.AlertType.WARNING);
|
||||
alert.setTitle("Error Dialog");
|
||||
|
@ -2,6 +2,7 @@ package atlantafx.sampler.page.showcase.filemanager;
|
||||
|
||||
import static atlantafx.sampler.page.showcase.filemanager.Model.USER_HOME;
|
||||
|
||||
import atlantafx.base.theme.Tweaks;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import javafx.scene.control.Alert;
|
||||
@ -14,7 +15,7 @@ import org.kordamp.ikonli.javafx.FontIcon;
|
||||
final class BookmarkList extends ListView<Bookmark> {
|
||||
|
||||
public BookmarkList(Model model) {
|
||||
getStyleClass().add("bookmark-list");
|
||||
getStyleClass().addAll("bookmark-list", Tweaks.EDGE_TO_EDGE);
|
||||
|
||||
// this is Linux specific and only for EN locale
|
||||
getItems().setAll(
|
||||
|
@ -7,7 +7,6 @@ import static atlantafx.base.theme.Styles.FLAT;
|
||||
import static atlantafx.sampler.page.showcase.filemanager.FileList.PREDICATE_ANY;
|
||||
import static atlantafx.sampler.page.showcase.filemanager.FileList.PREDICATE_NOT_HIDDEN;
|
||||
import static atlantafx.sampler.page.showcase.filemanager.Utils.openFile;
|
||||
import static atlantafx.sampler.util.Controls.hyperlink;
|
||||
import static atlantafx.sampler.util.Controls.iconButton;
|
||||
|
||||
import atlantafx.base.controls.Breadcrumbs;
|
||||
@ -16,7 +15,6 @@ import atlantafx.base.controls.Spacer;
|
||||
import atlantafx.base.theme.Tweaks;
|
||||
import atlantafx.sampler.page.showcase.ShowcasePage;
|
||||
import atlantafx.sampler.util.Containers;
|
||||
import java.net.URI;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.ArrayList;
|
||||
@ -24,7 +22,6 @@ import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import javafx.geometry.Insets;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.control.Button;
|
||||
import javafx.scene.control.ButtonBase;
|
||||
@ -35,9 +32,7 @@ import javafx.scene.control.MenuItem;
|
||||
import javafx.scene.control.SplitPane;
|
||||
import javafx.scene.control.ToolBar;
|
||||
import javafx.scene.layout.BorderPane;
|
||||
import javafx.scene.layout.HBox;
|
||||
import javafx.scene.layout.VBox;
|
||||
import javafx.scene.text.Text;
|
||||
import javafx.util.Callback;
|
||||
import org.kordamp.ikonli.feather.Feather;
|
||||
import org.kordamp.ikonli.javafx.FontIcon;
|
||||
@ -123,27 +118,12 @@ public class FileManagerPage extends ShowcasePage {
|
||||
(obs, old, val) -> splitPane.setDividerPosition(0, 200 / splitPane.getWidth())
|
||||
);
|
||||
|
||||
var aboutBox = new HBox(new Text(
|
||||
"Simple file manager. You can traverse through the file system and open files"
|
||||
+ " via default system application."
|
||||
));
|
||||
aboutBox.setPadding(new Insets(5, 0, 5, 0));
|
||||
aboutBox.getStyleClass().add("about");
|
||||
|
||||
var creditsBox = new HBox(5,
|
||||
new Text("Inspired by ©"),
|
||||
hyperlink("Gnome Files", URI.create("https://gitlab.gnome.org/GNOME/nautilus"))
|
||||
);
|
||||
creditsBox.getStyleClass().add("credits");
|
||||
creditsBox.setAlignment(Pos.CENTER_RIGHT);
|
||||
creditsBox.setPadding(new Insets(5, 0, 5, 0));
|
||||
|
||||
var root = new BorderPane();
|
||||
root.getStyleClass().add("file-manager-showcase");
|
||||
root.getStylesheets().add(STYLESHEET_URL);
|
||||
root.setTop(new VBox(aboutBox, topBar));
|
||||
root.setTop(new VBox(topBar));
|
||||
root.setCenter(splitPane);
|
||||
root.setBottom(creditsBox);
|
||||
//root.setBottom(creditsBox);
|
||||
|
||||
toggleHiddenCheck.selectedProperty().addListener((obs, old, val) -> directoryView.getFileList()
|
||||
.predicateProperty()
|
||||
@ -164,7 +144,9 @@ public class FileManagerPage extends ShowcasePage {
|
||||
directoryView.setDirectory(val);
|
||||
});
|
||||
|
||||
showcase.getChildren().setAll(root);
|
||||
setWindowTitle("File Manager", new FontIcon(Material2AL.FOLDER));
|
||||
setShowCaseContent(root);
|
||||
|
||||
Containers.setAnchors(root, Insets.EMPTY);
|
||||
}
|
||||
|
||||
|
@ -74,7 +74,7 @@ final class TableDirectoryView extends AnchorPane implements DirectoryView {
|
||||
// ~
|
||||
|
||||
var table = new TableView<Path>();
|
||||
table.getStyleClass().add(Styles.STRIPED);
|
||||
table.getStyleClass().addAll(Styles.STRIPED, Tweaks.EDGE_TO_EDGE);
|
||||
table.getColumns().setAll(filenameCol, sizeCol, mtimeCol);
|
||||
table.getSortOrder().add(filenameCol);
|
||||
table.setSortPolicy(param -> true);
|
||||
|
@ -1,16 +1,9 @@
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
|
||||
.file-manager-showcase .bookmark-list {
|
||||
-fx-border-width: 0 0 1 1;
|
||||
}
|
||||
.file-manager-showcase .bookmark-list .ikonli-font-icon {
|
||||
-fx-icon-size: 18px;
|
||||
}
|
||||
|
||||
.file-manager-showcase .tool-bar {
|
||||
-fx-background-insets: 0, 1;
|
||||
}
|
||||
|
||||
.file-manager-showcase .breadcrumbs >.divider {
|
||||
-fx-padding: 0;
|
||||
}
|
||||
@ -19,7 +12,6 @@
|
||||
-color-header-bg: -color-bg-default;
|
||||
-color-cell-bg-selected: -color-accent-emphasis;
|
||||
-color-cell-fg-selected: -color-fg-emphasis;
|
||||
-fx-border-width: 0 1 1 0;
|
||||
}
|
||||
|
||||
/* setting opacity directly to the .table-cell or .table-row-cell
|
||||
|
@ -2,23 +2,17 @@
|
||||
|
||||
package atlantafx.sampler.page.showcase.musicplayer;
|
||||
|
||||
import static atlantafx.sampler.util.Controls.hyperlink;
|
||||
|
||||
import atlantafx.sampler.page.showcase.ShowcasePage;
|
||||
import java.net.URI;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import javafx.collections.ListChangeListener;
|
||||
import javafx.geometry.Insets;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.scene.layout.BorderPane;
|
||||
import javafx.scene.layout.HBox;
|
||||
import javafx.scene.text.Text;
|
||||
import org.kordamp.ikonli.feather.Feather;
|
||||
import org.kordamp.ikonli.javafx.FontIcon;
|
||||
|
||||
public class MusicPlayerPage extends ShowcasePage {
|
||||
|
||||
public static final String NAME = "Music Player";
|
||||
public static final double BACKGROUND_OPACITY = 0.2;
|
||||
public static final Set<String> SUPPORTED_MEDIA_TYPES = Set.of("mp3");
|
||||
private static final String STYLESHEET_URL = Objects.requireNonNull(
|
||||
MusicPlayerPage.class.getResource("music-player.css")).toExternalForm();
|
||||
@ -36,26 +30,11 @@ public class MusicPlayerPage extends ShowcasePage {
|
||||
}
|
||||
|
||||
private void createView() {
|
||||
var aboutBox = new HBox(new Text("Simple music player that able to play MP3 files."));
|
||||
aboutBox.setPadding(new Insets(5, 0, 5, 0));
|
||||
aboutBox.getStyleClass().add("about");
|
||||
|
||||
var creditsBox = new HBox(5,
|
||||
new Text("Inspired by ©"),
|
||||
hyperlink("Amberol", URI.create("https://gitlab.gnome.org/World/amberol"))
|
||||
);
|
||||
creditsBox.getStyleClass().add("credits");
|
||||
creditsBox.setAlignment(Pos.CENTER_RIGHT);
|
||||
creditsBox.setPadding(new Insets(5, 0, 5, 0));
|
||||
|
||||
// ~
|
||||
var startScreen = new StartScreen(model);
|
||||
var playerScreen = new PlayerScreen(model);
|
||||
|
||||
var root = new BorderPane();
|
||||
root.setCenter(startScreen);
|
||||
root.setTop(aboutBox);
|
||||
root.setBottom(creditsBox);
|
||||
|
||||
root.getStylesheets().add(STYLESHEET_URL);
|
||||
root.getStyleClass().add("music-player-showcase");
|
||||
@ -68,7 +47,8 @@ public class MusicPlayerPage extends ShowcasePage {
|
||||
}
|
||||
});
|
||||
|
||||
showcase.getChildren().setAll(root);
|
||||
setWindowTitle("Music Player", new FontIcon(Feather.MUSIC));
|
||||
setShowCaseContent(root);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -1,160 +0,0 @@
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
|
||||
package atlantafx.sampler.page.showcase.widget;
|
||||
|
||||
import atlantafx.base.theme.Styles;
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.beans.property.ObjectProperty;
|
||||
import javafx.beans.property.SimpleObjectProperty;
|
||||
import javafx.beans.property.SimpleStringProperty;
|
||||
import javafx.beans.property.StringProperty;
|
||||
import javafx.scene.Group;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.Parent;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.control.Separator;
|
||||
import javafx.scene.image.ImageView;
|
||||
import javafx.scene.layout.VBox;
|
||||
|
||||
public class Card extends VBox {
|
||||
|
||||
public static final String CSS = """
|
||||
.card {
|
||||
-fx-background-color: -color-bg-default;
|
||||
}
|
||||
.card > .subtitle {
|
||||
-fx-text-fill: -color-fg-muted;
|
||||
-fx-padding: 0px 15px 10px 15px;
|
||||
}
|
||||
.card > .title,
|
||||
.card > .body,
|
||||
.card > .footer {
|
||||
-fx-padding: 10px 15px 10px 15px;
|
||||
}
|
||||
""";
|
||||
|
||||
private final StringProperty title = new SimpleStringProperty();
|
||||
private final StringProperty subtitle = new SimpleStringProperty();
|
||||
private final ObjectProperty<ImageView> image = new SimpleObjectProperty<>();
|
||||
private final ObjectProperty<Parent> body = new SimpleObjectProperty<>();
|
||||
private final ObjectProperty<Parent> footer = new SimpleObjectProperty<>();
|
||||
|
||||
public Card() {
|
||||
super();
|
||||
createView();
|
||||
}
|
||||
|
||||
private void createView() {
|
||||
var footerSep = new Separator();
|
||||
footerSep.getStyleClass().add(Styles.SMALL);
|
||||
footerSep.managedProperty().bind(Bindings.createObjectBinding(
|
||||
() -> footer.get() != null && footer.get().isManaged(), footer
|
||||
));
|
||||
|
||||
getChildren().setAll(
|
||||
createPlaceholder(), // title
|
||||
createPlaceholder(), // subtitle
|
||||
createPlaceholder(), // image
|
||||
createPlaceholder(), // body
|
||||
footerSep,
|
||||
createPlaceholder() // footer
|
||||
);
|
||||
|
||||
image.addListener(
|
||||
(obs, old, val) -> setChild(0, val, "image")
|
||||
);
|
||||
title.addListener(
|
||||
(obs, old, val) -> setChild(1, val != null ? new Label(val) : null, "title", Styles.TITLE_4)
|
||||
);
|
||||
subtitle.addListener(
|
||||
(obs, old, val) -> setChild(2, val != null ? new Label(val) : null, "subtitle")
|
||||
);
|
||||
body.addListener(
|
||||
(obs, old, val) -> setChild(3, val, "body")
|
||||
);
|
||||
footer.addListener(
|
||||
(obs, old, val) -> setChild(5, val, "footer")
|
||||
);
|
||||
|
||||
getStyleClass().addAll("card", Styles.BORDERED);
|
||||
}
|
||||
|
||||
private void setChild(int index, Node node, String... styleClass) {
|
||||
if (node != null) {
|
||||
for (var s : styleClass) {
|
||||
if (!node.getStyleClass().contains(s)) {
|
||||
node.getStyleClass().add(s);
|
||||
}
|
||||
}
|
||||
getChildren().set(index, node);
|
||||
} else {
|
||||
getChildren().set(index, createPlaceholder());
|
||||
}
|
||||
}
|
||||
|
||||
public String getTitle() {
|
||||
return title.get();
|
||||
}
|
||||
|
||||
public StringProperty titleProperty() {
|
||||
return title;
|
||||
}
|
||||
|
||||
public void setTitle(String title) {
|
||||
this.title.set(title);
|
||||
}
|
||||
|
||||
public String getSubtitle() {
|
||||
return subtitle.get();
|
||||
}
|
||||
|
||||
public StringProperty subtitleProperty() {
|
||||
return subtitle;
|
||||
}
|
||||
|
||||
public void setSubtitle(String subtitle) {
|
||||
this.subtitle.set(subtitle);
|
||||
}
|
||||
|
||||
public ImageView getImage() {
|
||||
return image.get();
|
||||
}
|
||||
|
||||
public ObjectProperty<ImageView> imageProperty() {
|
||||
return image;
|
||||
}
|
||||
|
||||
public void setImage(ImageView image) {
|
||||
this.image.set(image);
|
||||
}
|
||||
|
||||
public Parent getBody() {
|
||||
return body.get();
|
||||
}
|
||||
|
||||
public ObjectProperty<Parent> bodyProperty() {
|
||||
return body;
|
||||
}
|
||||
|
||||
public void setBody(Parent body) {
|
||||
this.body.set(body);
|
||||
}
|
||||
|
||||
public Parent getFooter() {
|
||||
return footer.get();
|
||||
}
|
||||
|
||||
public ObjectProperty<Parent> footerProperty() {
|
||||
return footer;
|
||||
}
|
||||
|
||||
public void setFooter(Parent footer) {
|
||||
this.footer.set(footer);
|
||||
}
|
||||
|
||||
private Parent createPlaceholder() {
|
||||
var g = new Group();
|
||||
g.setManaged(false);
|
||||
return g;
|
||||
}
|
||||
}
|
@ -1,180 +0,0 @@
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
|
||||
package atlantafx.sampler.page.showcase.widget;
|
||||
|
||||
import static atlantafx.sampler.page.Page.PAGE_HGAP;
|
||||
import static atlantafx.sampler.page.Page.PAGE_VGAP;
|
||||
|
||||
import atlantafx.base.theme.Styles;
|
||||
import atlantafx.sampler.Resources;
|
||||
import atlantafx.sampler.theme.CSSFragment;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.scene.control.Button;
|
||||
import javafx.scene.control.Hyperlink;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.image.Image;
|
||||
import javafx.scene.image.ImageView;
|
||||
import javafx.scene.image.PixelReader;
|
||||
import javafx.scene.image.WritableImage;
|
||||
import javafx.scene.layout.GridPane;
|
||||
import javafx.scene.layout.HBox;
|
||||
import javafx.scene.layout.VBox;
|
||||
import javafx.scene.text.Text;
|
||||
import javafx.scene.text.TextFlow;
|
||||
import net.datafaker.Faker;
|
||||
import org.kordamp.ikonli.javafx.FontIcon;
|
||||
import org.kordamp.ikonli.material2.Material2AL;
|
||||
|
||||
public class CardSample extends HBox {
|
||||
|
||||
private static final Faker FAKER = new Faker();
|
||||
private static final int CARD_WIDTH = 300;
|
||||
|
||||
public CardSample() {
|
||||
new CSSFragment(Card.CSS).addTo(this);
|
||||
|
||||
setSpacing(PAGE_HGAP);
|
||||
setAlignment(Pos.TOP_CENTER);
|
||||
setMinWidth(CARD_WIDTH * 2 + PAGE_HGAP);
|
||||
getChildren().setAll(
|
||||
// column 0
|
||||
new VBox(
|
||||
PAGE_VGAP,
|
||||
textFooterCard(),
|
||||
titleTextCard(),
|
||||
quoteCard()
|
||||
),
|
||||
// column 1
|
||||
new VBox(
|
||||
PAGE_VGAP,
|
||||
imageTextCard(),
|
||||
titleImageCard(),
|
||||
statisticCard()
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
private Card textFooterCard() {
|
||||
var card = new Card();
|
||||
card.getStyleClass().add(Styles.ELEVATED_1);
|
||||
card.setMinWidth(CARD_WIDTH);
|
||||
card.setMaxWidth(CARD_WIDTH);
|
||||
|
||||
var text = new Text(FAKER.chuckNorris().fact());
|
||||
card.setBody(new TextFlow(text));
|
||||
|
||||
var btn = new Button("More!");
|
||||
btn.getStyleClass().addAll(Styles.ACCENT, Styles.BUTTON_OUTLINED);
|
||||
btn.setOnAction(e -> text.setText(FAKER.chuckNorris().fact()));
|
||||
|
||||
card.setFooter(new HBox(btn));
|
||||
|
||||
return card;
|
||||
}
|
||||
|
||||
private Card imageTextCard() {
|
||||
var card = new Card();
|
||||
card.getStyleClass().add(Styles.ELEVATED_4);
|
||||
card.setMinWidth(CARD_WIDTH);
|
||||
card.setMaxWidth(CARD_WIDTH);
|
||||
|
||||
var image = new ImageView(new Image(Resources.getResourceAsStream("images/20_min_adventure.jpg")));
|
||||
image.setFitWidth(300);
|
||||
image.setPreserveRatio(true);
|
||||
card.setImage(image);
|
||||
|
||||
var text = new Text(FAKER.rickAndMorty().quote());
|
||||
card.setBody(new TextFlow(text));
|
||||
|
||||
return card;
|
||||
}
|
||||
|
||||
private Card titleTextCard() {
|
||||
var card = new Card();
|
||||
card.getStyleClass().add(Styles.INTERACTIVE);
|
||||
card.setMinWidth(CARD_WIDTH);
|
||||
card.setMaxWidth(CARD_WIDTH);
|
||||
|
||||
card.setTitle("Interactive");
|
||||
card.setSubtitle("Subtitle");
|
||||
|
||||
var text = new Text(FAKER.lorem().paragraph());
|
||||
card.setBody(new TextFlow(text));
|
||||
|
||||
return card;
|
||||
}
|
||||
|
||||
private Card titleImageCard() {
|
||||
var card = new Card();
|
||||
card.getStyleClass().add(Styles.ELEVATED_2);
|
||||
card.setMinWidth(CARD_WIDTH);
|
||||
card.setMaxWidth(CARD_WIDTH);
|
||||
|
||||
var image = new Image(Resources.getResourceAsStream("images/pattern.jpg"));
|
||||
PixelReader pixelReader = image.getPixelReader();
|
||||
var cropImage = new WritableImage(pixelReader, 0, 0, 300, 100);
|
||||
|
||||
card.setImage(new ImageView(cropImage));
|
||||
card.setTitle("Title");
|
||||
|
||||
var text = new Text(FAKER.lorem().sentence());
|
||||
card.setBody(new TextFlow(text));
|
||||
|
||||
return card;
|
||||
}
|
||||
|
||||
private Card quoteCard() {
|
||||
var card = new Card();
|
||||
card.getStyleClass().add(Styles.ELEVATED_3);
|
||||
card.setMinWidth(CARD_WIDTH);
|
||||
card.setMaxWidth(CARD_WIDTH);
|
||||
|
||||
var quoteText = new Text(FAKER.bojackHorseman().quotes());
|
||||
quoteText.getStyleClass().add(Styles.TITLE_3);
|
||||
|
||||
var authorText = new Text("Bojack Horseman");
|
||||
|
||||
card.setBody(new VBox(
|
||||
10,
|
||||
new TextFlow(quoteText),
|
||||
authorText
|
||||
));
|
||||
|
||||
card.setFooter(new TextFlow(
|
||||
new Text("Share on "),
|
||||
new Hyperlink("Twitter")
|
||||
));
|
||||
|
||||
return card;
|
||||
}
|
||||
|
||||
private Card statisticCard() {
|
||||
var card = new Card();
|
||||
card.setMinWidth(CARD_WIDTH);
|
||||
card.setMaxWidth(CARD_WIDTH);
|
||||
card.setTitle("Statistic");
|
||||
|
||||
var grid = new GridPane();
|
||||
grid.setHgap(40);
|
||||
grid.setVgap(10);
|
||||
card.setBody(grid);
|
||||
|
||||
var leftHead = new Text("Active");
|
||||
leftHead.getStyleClass().add(Styles.TEXT_MUTED);
|
||||
grid.add(leftHead, 0, 0);
|
||||
|
||||
var leftData = new Label("12.87", new FontIcon(Material2AL.ARROW_UPWARD));
|
||||
leftData.getStyleClass().addAll(Styles.SUCCESS, Styles.TITLE_2);
|
||||
grid.add(leftData, 0, 1);
|
||||
|
||||
var rightHead = new Text("Idle");
|
||||
rightHead.getStyleClass().add(Styles.TEXT_MUTED);
|
||||
grid.add(rightHead, 1, 0);
|
||||
|
||||
var rightData = new Label("3.74", new FontIcon(Material2AL.ARROW_DOWNWARD));
|
||||
rightData.getStyleClass().addAll(Styles.DANGER, Styles.TITLE_2);
|
||||
grid.add(rightData, 1, 1);
|
||||
|
||||
return card;
|
||||
}
|
||||
}
|
@ -1,147 +0,0 @@
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
|
||||
package atlantafx.sampler.page.showcase.widget;
|
||||
|
||||
import atlantafx.base.theme.Styles;
|
||||
import java.util.Objects;
|
||||
import java.util.function.Consumer;
|
||||
import javafx.animation.FadeTransition;
|
||||
import javafx.geometry.Insets;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.scene.control.Button;
|
||||
import javafx.scene.layout.Pane;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import javafx.scene.text.Text;
|
||||
import javafx.scene.text.TextFlow;
|
||||
import javafx.util.Duration;
|
||||
import org.kordamp.ikonli.javafx.FontIcon;
|
||||
import org.kordamp.ikonli.material2.Material2AL;
|
||||
|
||||
public class Message extends StackPane {
|
||||
|
||||
private static final int ANIMATION_DURATION = 500;
|
||||
|
||||
public enum Type {
|
||||
INFO, SUCCESS, WARNING, DANGER
|
||||
}
|
||||
|
||||
public static final String CSS = """
|
||||
.message {
|
||||
-color-message-bg: -color-bg-default;
|
||||
-color-message-fg: -color-fg-default;
|
||||
-fx-background-color: -color-message-bg;
|
||||
-fx-border-color: -color-message-fg;
|
||||
-fx-border-width: 0 0 0 5px;
|
||||
-fx-pref-width: 600px;
|
||||
-fx-alignment: TOP_LEFT;
|
||||
}
|
||||
.message > .header {
|
||||
-fx-font-weight: bold;
|
||||
}
|
||||
.message Text {
|
||||
-fx-fill: -color-message-fg;
|
||||
}
|
||||
.message > .button {
|
||||
-color-button-fg: -color-message-fg;
|
||||
}
|
||||
.message.info {
|
||||
-color-message-bg: -color-accent-subtle;
|
||||
-color-message-fg: -color-accent-fg;
|
||||
}
|
||||
.message.success {
|
||||
-color-message-bg: -color-success-subtle;
|
||||
-color-message-fg: -color-success-fg;
|
||||
}
|
||||
.message.warning {
|
||||
-color-message-bg: -color-warning-subtle;
|
||||
-color-message-fg: -color-warning-fg;
|
||||
}
|
||||
.message.danger {
|
||||
-color-message-bg: -color-danger-subtle;
|
||||
-color-message-fg: -color-danger-fg;
|
||||
}
|
||||
""";
|
||||
|
||||
private final Type type;
|
||||
private final String header;
|
||||
private final String text;
|
||||
private Consumer<Message> closeHandler;
|
||||
|
||||
public Message(Type type, String header, String text) {
|
||||
this.type = Objects.requireNonNull(type);
|
||||
this.header = header;
|
||||
this.text = Objects.requireNonNull(text);
|
||||
createView();
|
||||
}
|
||||
|
||||
private void createView() {
|
||||
if (header != null) {
|
||||
var headerText = new Text(header);
|
||||
headerText.getStyleClass().addAll("header");
|
||||
StackPane.setMargin(headerText, new Insets(10, 10, 0, 15));
|
||||
getChildren().add(headerText);
|
||||
}
|
||||
|
||||
var messageText = new TextFlow(new Text(text));
|
||||
if (header != null) {
|
||||
StackPane.setMargin(messageText, new Insets(40, 10, 10, 15));
|
||||
} else {
|
||||
StackPane.setMargin(messageText, new Insets(10, 10, 10, 15));
|
||||
}
|
||||
messageText.maxWidthProperty().bind(widthProperty().subtract(50));
|
||||
getChildren().add(messageText);
|
||||
|
||||
var closeBtn = new Button("", new FontIcon(Material2AL.CLOSE));
|
||||
closeBtn.getStyleClass().addAll(Styles.BUTTON_CIRCLE, Styles.FLAT);
|
||||
closeBtn.setOnAction(e -> handleClose());
|
||||
StackPane.setMargin(closeBtn, new Insets(2));
|
||||
StackPane.setAlignment(closeBtn, Pos.TOP_RIGHT);
|
||||
getChildren().add(closeBtn);
|
||||
|
||||
parentProperty().addListener((obs, old, val) -> {
|
||||
if (val != null) {
|
||||
handleOpen();
|
||||
}
|
||||
});
|
||||
|
||||
getStyleClass().setAll("message", type.name().toLowerCase());
|
||||
}
|
||||
|
||||
public Type getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
public String getHeader() {
|
||||
return header;
|
||||
}
|
||||
|
||||
public String getText() {
|
||||
return text;
|
||||
}
|
||||
|
||||
public void setCloseHandler(Consumer<Message> closeHandler) {
|
||||
this.closeHandler = closeHandler;
|
||||
}
|
||||
|
||||
private void handleOpen() {
|
||||
var transition = new FadeTransition(new Duration(500), this);
|
||||
transition.setFromValue(0);
|
||||
transition.setToValue(1);
|
||||
transition.play();
|
||||
}
|
||||
|
||||
private void handleClose() {
|
||||
var transition = new FadeTransition(new Duration(ANIMATION_DURATION), this);
|
||||
transition.setFromValue(1);
|
||||
transition.setToValue(0);
|
||||
transition.setOnFinished(e -> {
|
||||
if (getParent() != null && getParent() instanceof Pane pane) {
|
||||
pane.getChildren().remove(this);
|
||||
}
|
||||
if (closeHandler != null) {
|
||||
closeHandler.accept(this);
|
||||
}
|
||||
});
|
||||
transition.play();
|
||||
}
|
||||
}
|
@ -1,42 +0,0 @@
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
|
||||
package atlantafx.sampler.page.showcase.widget;
|
||||
|
||||
import atlantafx.sampler.page.SampleBlock;
|
||||
import atlantafx.sampler.theme.CSSFragment;
|
||||
import java.util.function.Consumer;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.layout.Priority;
|
||||
import javafx.scene.layout.VBox;
|
||||
|
||||
public class MessageSample extends SampleBlock {
|
||||
|
||||
public MessageSample() {
|
||||
super("Message", createContent());
|
||||
}
|
||||
|
||||
private static Node createContent() {
|
||||
var content = new VBox(BLOCK_VGAP);
|
||||
content.setAlignment(Pos.TOP_CENTER);
|
||||
VBox.setVgrow(content, Priority.ALWAYS);
|
||||
new CSSFragment(Message.CSS).addTo(content);
|
||||
|
||||
var closeHandler = new Consumer<Message>() {
|
||||
@Override
|
||||
public void accept(Message msg) {
|
||||
var newMsg = new Message(msg.getType(), msg.getHeader(), FAKER.chuckNorris().fact());
|
||||
newMsg.setCloseHandler(this);
|
||||
content.getChildren().add(newMsg);
|
||||
}
|
||||
};
|
||||
|
||||
for (Message.Type type : Message.Type.values()) {
|
||||
var msg = new Message(type, type.name(), FAKER.chuckNorris().fact());
|
||||
msg.setCloseHandler(closeHandler);
|
||||
content.getChildren().add(msg);
|
||||
}
|
||||
|
||||
return content;
|
||||
}
|
||||
}
|
@ -1,259 +0,0 @@
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
|
||||
package atlantafx.sampler.page.showcase.widget;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.beans.binding.BooleanBinding;
|
||||
import javafx.beans.property.BooleanProperty;
|
||||
import javafx.beans.property.ObjectProperty;
|
||||
import javafx.beans.property.SimpleBooleanProperty;
|
||||
import javafx.beans.property.SimpleObjectProperty;
|
||||
import javafx.css.PseudoClass;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.geometry.Side;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.control.ContentDisplay;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.control.Separator;
|
||||
import javafx.scene.layout.HBox;
|
||||
import javafx.scene.layout.Priority;
|
||||
import org.kordamp.ikonli.Ikon;
|
||||
import org.kordamp.ikonli.javafx.FontIcon;
|
||||
|
||||
public class Stepper extends HBox {
|
||||
|
||||
public static final String CSS = """
|
||||
.stepper {
|
||||
-color-stepper-bg: -color-bg-subtle;
|
||||
-color-stepper-fg: -color-fg-default;
|
||||
-color-stepper-border: -color-border-default;
|
||||
-fx-pref-width: 600px;
|
||||
-fx-spacing: 10px;
|
||||
}
|
||||
.stepper > .item {
|
||||
-fx-graphic-text-gap: 10px;
|
||||
}
|
||||
.stepper > .item > .graphic {
|
||||
-fx-font-size: 1.1em;
|
||||
-fx-min-width: 2.2em;
|
||||
-fx-max-width: 2.2em;
|
||||
-fx-min-height: 2.2em;
|
||||
-fx-max-height: 2.2em;
|
||||
-fx-text-fill: -color-stepper-fg;
|
||||
-fx-background-color: -color-stepper-bg;
|
||||
-fx-background-radius: 10em;
|
||||
-fx-border-color: -color-stepper-border;
|
||||
-fx-border-radius: 10em;
|
||||
-fx-border-width: 1;
|
||||
-fx-alignment: CENTER;
|
||||
}
|
||||
.stepper > .item .ikonli-font-icon {
|
||||
-fx-fill: -color-stepper-fg;
|
||||
-fx-icon-color: -color-stepper-fg;
|
||||
}
|
||||
.stepper > .item:selected {
|
||||
-color-stepper-bg: -color-bg-default;
|
||||
-color-stepper-fg: -color-accent-fg;
|
||||
-color-stepper-border: -color-accent-emphasis;
|
||||
}
|
||||
.stepper > .item:selected > .graphic {
|
||||
-color-stepper-bg: -color-bg-default;
|
||||
-color-stepper-fg: -color-accent-fg;
|
||||
-color-stepper-border: -color-accent-emphasis;
|
||||
-fx-border-width: 2;
|
||||
}
|
||||
.stepper > .item:completed {
|
||||
-color-stepper-bg: -color-accent-emphasis;
|
||||
-color-stepper-fg: -color-fg-emphasis;
|
||||
-color-stepper-border: -color-accent-emphasis;
|
||||
}
|
||||
""";
|
||||
|
||||
private static final PseudoClass SELECTED = PseudoClass.getPseudoClass("selected");
|
||||
private static final PseudoClass COMPLETED = PseudoClass.getPseudoClass("completed");
|
||||
|
||||
private final List<Item> items;
|
||||
private final ObjectProperty<Side> textPosition = new SimpleObjectProperty<>(Side.LEFT);
|
||||
private final ObjectProperty<Item> selectedItem = new SimpleObjectProperty<>();
|
||||
private final BooleanBinding canGoBack;
|
||||
private final BooleanBinding canGoForward;
|
||||
|
||||
public Stepper(Item... items) {
|
||||
this(Arrays.asList(items));
|
||||
}
|
||||
|
||||
public Stepper(List<Item> items) {
|
||||
if (items == null || items.isEmpty()) {
|
||||
throw new IllegalArgumentException("Item list can't be null or empty.");
|
||||
}
|
||||
|
||||
this.items = Collections.unmodifiableList(items);
|
||||
|
||||
canGoBack = Bindings.createBooleanBinding(() -> {
|
||||
if (selectedItem.get() == null) {
|
||||
return false;
|
||||
}
|
||||
var idx = items.indexOf(selectedItem.get());
|
||||
return idx > 0 && idx <= items.size() - 1;
|
||||
}, selectedItem);
|
||||
|
||||
canGoForward = Bindings.createBooleanBinding(() -> {
|
||||
if (selectedItem.get() == null) {
|
||||
return false;
|
||||
}
|
||||
var idx = items.indexOf(selectedItem.get());
|
||||
return idx >= 0 && idx < items.size() - 1;
|
||||
}, selectedItem);
|
||||
|
||||
selectedItem.addListener((obs, old, val) -> {
|
||||
if (old != null) {
|
||||
old.pseudoClassStateChanged(SELECTED, false);
|
||||
}
|
||||
if (val != null) {
|
||||
val.pseudoClassStateChanged(SELECTED, true);
|
||||
}
|
||||
});
|
||||
|
||||
createView();
|
||||
}
|
||||
|
||||
private void createView() {
|
||||
alignmentProperty().bind(Bindings.createObjectBinding(
|
||||
() -> switch (textPositionProperty().get()) {
|
||||
case TOP -> Pos.TOP_LEFT;
|
||||
case BOTTOM -> Pos.BOTTOM_LEFT;
|
||||
default -> Pos.CENTER_LEFT;
|
||||
}, textPositionProperty())
|
||||
);
|
||||
|
||||
updateItems();
|
||||
getStyleClass().add("stepper");
|
||||
}
|
||||
|
||||
private void updateItems() {
|
||||
var children = new ArrayList<Node>();
|
||||
for (int i = 0; i < items.size(); i++) {
|
||||
var item = items.get(i);
|
||||
item.contentDisplayProperty().bind(Bindings.createObjectBinding(
|
||||
() -> switch (textPositionProperty().get()) {
|
||||
case TOP -> ContentDisplay.TOP;
|
||||
case BOTTOM -> ContentDisplay.BOTTOM;
|
||||
case LEFT -> ContentDisplay.LEFT;
|
||||
case RIGHT -> ContentDisplay.RIGHT;
|
||||
}, textPositionProperty())
|
||||
);
|
||||
|
||||
children.add(item);
|
||||
|
||||
if (i < items.size() - 1) {
|
||||
var sep = new Separator();
|
||||
HBox.setHgrow(sep, Priority.ALWAYS);
|
||||
children.add(sep);
|
||||
}
|
||||
}
|
||||
getChildren().setAll(children);
|
||||
}
|
||||
|
||||
public List<Item> getItems() {
|
||||
return items;
|
||||
}
|
||||
|
||||
public Side getTextPosition() {
|
||||
return textPosition.get();
|
||||
}
|
||||
|
||||
public void setTextPosition(Side textPosition) {
|
||||
this.textPosition.set(textPosition);
|
||||
}
|
||||
|
||||
public ObjectProperty<Side> textPositionProperty() {
|
||||
return textPosition;
|
||||
}
|
||||
|
||||
public Item getSelectedItem() {
|
||||
return selectedItem.get();
|
||||
}
|
||||
|
||||
public ObjectProperty<Item> selectedItemProperty() {
|
||||
return selectedItem;
|
||||
}
|
||||
|
||||
public void setSelectedItem(Item selectedItem) {
|
||||
this.selectedItem.set(selectedItem);
|
||||
}
|
||||
|
||||
public BooleanBinding canGoBackProperty() {
|
||||
return canGoBack;
|
||||
}
|
||||
|
||||
public void backward() {
|
||||
if (!canGoBack.get()) {
|
||||
return;
|
||||
}
|
||||
var idx = items.indexOf(selectedItem.get());
|
||||
selectedItem.set(items.get(idx - 1));
|
||||
}
|
||||
|
||||
public BooleanBinding canGoForwardProperty() {
|
||||
return canGoForward;
|
||||
}
|
||||
|
||||
public void forward() {
|
||||
if (!canGoForward.get()) {
|
||||
return;
|
||||
}
|
||||
var idx = items.indexOf(selectedItem.get());
|
||||
selectedItem.set(items.get(idx + 1));
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
|
||||
public static class Item extends Label {
|
||||
|
||||
private final BooleanProperty completed = new SimpleBooleanProperty();
|
||||
|
||||
public Item(String text) {
|
||||
super(text);
|
||||
|
||||
var graphicLabel = new Label();
|
||||
graphicLabel.getStyleClass().add("graphic");
|
||||
setGraphic(graphicLabel);
|
||||
|
||||
completed.addListener((obs, old, val) -> pseudoClassStateChanged(COMPLETED, val));
|
||||
getStyleClass().add("item");
|
||||
setContentDisplay(ContentDisplay.LEFT);
|
||||
}
|
||||
|
||||
public void setGraphic(Ikon icon) {
|
||||
var graphicLabel = ((Label) getGraphic());
|
||||
if (icon != null) {
|
||||
graphicLabel.setText(null);
|
||||
graphicLabel.setGraphic(new FontIcon(icon));
|
||||
}
|
||||
}
|
||||
|
||||
public void setGraphic(String text) {
|
||||
var graphicLabel = ((Label) getGraphic());
|
||||
if (text != null) {
|
||||
graphicLabel.setText(text);
|
||||
graphicLabel.setGraphic(null);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isCompleted() {
|
||||
return completed.get();
|
||||
}
|
||||
|
||||
public void setCompleted(boolean state) {
|
||||
completed.set(state);
|
||||
}
|
||||
|
||||
public BooleanProperty completedProperty() {
|
||||
return completed;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,123 +0,0 @@
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
|
||||
package atlantafx.sampler.page.showcase.widget;
|
||||
|
||||
import atlantafx.base.controls.Spacer;
|
||||
import atlantafx.base.controls.ToggleSwitch;
|
||||
import atlantafx.base.theme.Styles;
|
||||
import atlantafx.sampler.page.SampleBlock;
|
||||
import atlantafx.sampler.page.showcase.widget.Stepper.Item;
|
||||
import atlantafx.sampler.theme.CSSFragment;
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.geometry.Side;
|
||||
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.StackPane;
|
||||
import javafx.scene.layout.VBox;
|
||||
import org.kordamp.ikonli.javafx.FontIcon;
|
||||
import org.kordamp.ikonli.material2.Material2MZ;
|
||||
|
||||
public class StepperSample extends SampleBlock {
|
||||
|
||||
public StepperSample() {
|
||||
super("Stepper", createContent());
|
||||
}
|
||||
|
||||
private static Node createContent() {
|
||||
var content = new VBox(BLOCK_VGAP);
|
||||
new CSSFragment(Stepper.CSS).addTo(content);
|
||||
|
||||
// == STEPPER CONTENT ==
|
||||
|
||||
var stackContent = new Label();
|
||||
stackContent.getStyleClass().add(Styles.TITLE_1);
|
||||
stackContent.setStyle("-fx-background-color:-color-bg-default;");
|
||||
stackContent.setWrapText(true);
|
||||
stackContent.setMinHeight(200);
|
||||
stackContent.setMaxWidth(400);
|
||||
stackContent.setAlignment(Pos.CENTER);
|
||||
|
||||
var stack = new StackPane(stackContent);
|
||||
stack.setPrefHeight(200);
|
||||
|
||||
// == STEPPER ==
|
||||
|
||||
var firstItem = new Item("First");
|
||||
firstItem.setGraphic("A");
|
||||
|
||||
var secondItem = new Item("Second");
|
||||
secondItem.setGraphic("B");
|
||||
|
||||
var thirdItem = new Item("Third");
|
||||
thirdItem.setGraphic("C");
|
||||
|
||||
var stepper = new Stepper(firstItem, secondItem, thirdItem);
|
||||
stepper.selectedItemProperty().addListener(
|
||||
(obs, old, val) -> stackContent.setText(val != null ? val.getText() : null)
|
||||
);
|
||||
stepper.setSelectedItem(stepper.getItems().get(0));
|
||||
|
||||
// == CONTROLS ==
|
||||
|
||||
var nextBtn = new Button("Next");
|
||||
nextBtn.setDefaultButton(true);
|
||||
nextBtn.setOnAction(e -> {
|
||||
// you can validate user input before moving forward here
|
||||
stepper.getSelectedItem().setCompleted(true);
|
||||
stepper.forward();
|
||||
});
|
||||
nextBtn.textProperty().bind(Bindings.createStringBinding(
|
||||
() -> stepper.canGoForwardProperty().get() ? "Next" : "Done", stepper.canGoForwardProperty())
|
||||
);
|
||||
|
||||
var prevBtn = new Button("Previous");
|
||||
prevBtn.getStyleClass().addAll(Styles.FLAT);
|
||||
prevBtn.setOnAction(e -> {
|
||||
stepper.getSelectedItem().setCompleted(false);
|
||||
stepper.backward();
|
||||
});
|
||||
prevBtn.disableProperty().bind(stepper.canGoBackProperty().not());
|
||||
|
||||
var iconToggle = new ToggleSwitch("Icons");
|
||||
iconToggle.selectedProperty().addListener((obs, old, val) -> {
|
||||
for (int i = 0; i < stepper.getItems().size(); i++) {
|
||||
var item = stepper.getItems().get(i);
|
||||
if (val) {
|
||||
item.setGraphic(randomIcon());
|
||||
} else {
|
||||
item.setGraphic(String.valueOf(i + 1));
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
var rotateBtn = new Button("Rotate", new FontIcon(Material2MZ.ROTATE_RIGHT));
|
||||
rotateBtn.setOnAction(e -> {
|
||||
Side nextSide = switch (stepper.getTextPosition()) {
|
||||
case LEFT -> Side.TOP;
|
||||
case TOP -> Side.RIGHT;
|
||||
case RIGHT -> Side.BOTTOM;
|
||||
case BOTTOM -> Side.LEFT;
|
||||
};
|
||||
stepper.setTextPosition(nextSide);
|
||||
});
|
||||
|
||||
var controls = new HBox(
|
||||
BLOCK_HGAP,
|
||||
nextBtn,
|
||||
prevBtn,
|
||||
new Spacer(),
|
||||
iconToggle,
|
||||
rotateBtn
|
||||
);
|
||||
controls.setAlignment(Pos.CENTER_LEFT);
|
||||
|
||||
// ~
|
||||
|
||||
content.getChildren().setAll(stepper, stack, new Separator(), controls);
|
||||
return content;
|
||||
}
|
||||
}
|
@ -1,52 +0,0 @@
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
|
||||
package atlantafx.sampler.page.showcase.widget;
|
||||
|
||||
import static javafx.scene.control.ContentDisplay.RIGHT;
|
||||
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.control.Button;
|
||||
import javafx.scene.layout.Pane;
|
||||
|
||||
public class Tag extends Button {
|
||||
|
||||
public static final String CSS = """
|
||||
.tag {
|
||||
-fx-padding: 4px 6px 4px 6px;
|
||||
-fx-cursor: hand;
|
||||
-color-button-border-hover: -color-button-border;
|
||||
-color-button-border-focused: -color-button-border;
|
||||
-color-button-border-pressed: -color-button-border;
|
||||
}
|
||||
""";
|
||||
|
||||
public Tag(String text) {
|
||||
this(text, null);
|
||||
}
|
||||
|
||||
public Tag(String text, Node graphic) {
|
||||
super(text, graphic);
|
||||
|
||||
if (graphic != null) {
|
||||
graphic.setOnMouseEntered(e -> {
|
||||
if (getContentDisplay() == RIGHT) {
|
||||
graphic.setScaleX(1.2);
|
||||
graphic.setScaleY(1.2);
|
||||
}
|
||||
});
|
||||
graphic.setOnMouseExited(e -> {
|
||||
if (getContentDisplay() == RIGHT) {
|
||||
graphic.setScaleX(1);
|
||||
graphic.setScaleY(1);
|
||||
}
|
||||
});
|
||||
graphic.setOnMouseClicked(e -> {
|
||||
if (getContentDisplay() == RIGHT && getParent() != null && getParent() instanceof Pane pane) {
|
||||
pane.getChildren().remove(this);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
getStyleClass().add("tag");
|
||||
}
|
||||
}
|
@ -1,157 +0,0 @@
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
|
||||
package atlantafx.sampler.page.showcase.widget;
|
||||
|
||||
import static atlantafx.sampler.page.Page.PAGE_HGAP;
|
||||
import static atlantafx.sampler.page.Page.PAGE_VGAP;
|
||||
import static atlantafx.sampler.page.SampleBlock.BLOCK_HGAP;
|
||||
import static atlantafx.sampler.page.SampleBlock.BLOCK_VGAP;
|
||||
|
||||
import atlantafx.base.theme.Styles;
|
||||
import atlantafx.sampler.page.SampleBlock;
|
||||
import atlantafx.sampler.theme.CSSFragment;
|
||||
import javafx.scene.control.ContentDisplay;
|
||||
import javafx.scene.layout.FlowPane;
|
||||
import javafx.scene.layout.GridPane;
|
||||
import org.kordamp.ikonli.feather.Feather;
|
||||
import org.kordamp.ikonli.javafx.FontIcon;
|
||||
import org.kordamp.ikonli.material2.Material2AL;
|
||||
|
||||
public class TagSample extends GridPane {
|
||||
|
||||
private static final int PREF_WIDTH = 300;
|
||||
|
||||
public TagSample() {
|
||||
new CSSFragment(Tag.CSS).addTo(this);
|
||||
|
||||
setHgap(PAGE_HGAP);
|
||||
setVgap(PAGE_VGAP);
|
||||
|
||||
add(filledTagSample(), 0, 0);
|
||||
add(iconTagSample(), 1, 0);
|
||||
add(outlinedTagSample(), 0, 1);
|
||||
add(closeableTagSample(), 1, 1);
|
||||
add(customColorTagSample(), 0, 2);
|
||||
}
|
||||
|
||||
private SampleBlock filledTagSample() {
|
||||
var content = new FlowPane(BLOCK_HGAP, BLOCK_VGAP);
|
||||
content.setPrefWidth(PREF_WIDTH);
|
||||
|
||||
var basicTag = new Tag("basic");
|
||||
content.getChildren().add(basicTag);
|
||||
|
||||
var accentTag = new Tag("accent");
|
||||
accentTag.getStyleClass().add(Styles.ACCENT);
|
||||
content.getChildren().add(accentTag);
|
||||
|
||||
var successTag = new Tag("success");
|
||||
successTag.getStyleClass().add(Styles.SUCCESS);
|
||||
content.getChildren().add(successTag);
|
||||
|
||||
var dangerTag = new Tag("danger");
|
||||
dangerTag.getStyleClass().add(Styles.DANGER);
|
||||
content.getChildren().add(dangerTag);
|
||||
|
||||
return new SampleBlock("Filled", content);
|
||||
}
|
||||
|
||||
private SampleBlock iconTagSample() {
|
||||
var content = new FlowPane(BLOCK_HGAP, BLOCK_VGAP);
|
||||
content.setPrefWidth(PREF_WIDTH);
|
||||
|
||||
var basicTag = new Tag("image", new FontIcon(Feather.IMAGE));
|
||||
content.getChildren().add(basicTag);
|
||||
|
||||
var accentTag = new Tag("music", new FontIcon(Feather.MUSIC));
|
||||
content.getChildren().add(accentTag);
|
||||
|
||||
var successTag = new Tag("video", new FontIcon(Feather.VIDEO));
|
||||
content.getChildren().add(successTag);
|
||||
|
||||
return new SampleBlock("Icon", content);
|
||||
}
|
||||
|
||||
private SampleBlock outlinedTagSample() {
|
||||
var content = new FlowPane(BLOCK_HGAP, BLOCK_VGAP);
|
||||
content.setPrefWidth(PREF_WIDTH);
|
||||
|
||||
var accentTag = new Tag("accent");
|
||||
accentTag.getStyleClass().addAll(Styles.ACCENT, Styles.BUTTON_OUTLINED);
|
||||
content.getChildren().add(accentTag);
|
||||
|
||||
var successTag = new Tag("success");
|
||||
successTag.getStyleClass().addAll(Styles.SUCCESS, Styles.BUTTON_OUTLINED);
|
||||
content.getChildren().add(successTag);
|
||||
|
||||
var dangerTag = new Tag("danger");
|
||||
dangerTag.getStyleClass().addAll(Styles.DANGER, Styles.BUTTON_OUTLINED);
|
||||
content.getChildren().add(dangerTag);
|
||||
|
||||
return new SampleBlock("Outlined", content);
|
||||
}
|
||||
|
||||
private SampleBlock closeableTagSample() {
|
||||
var content = new FlowPane(BLOCK_HGAP, BLOCK_VGAP);
|
||||
content.setPrefWidth(PREF_WIDTH);
|
||||
|
||||
var basicTag = new Tag("basic", new FontIcon(Material2AL.CLOSE));
|
||||
basicTag.setContentDisplay(ContentDisplay.RIGHT);
|
||||
content.getChildren().add(basicTag);
|
||||
|
||||
var accentTag = new Tag("accent", new FontIcon(Material2AL.CANCEL));
|
||||
accentTag.setContentDisplay(ContentDisplay.RIGHT);
|
||||
accentTag.getStyleClass().add(Styles.ACCENT);
|
||||
content.getChildren().add(accentTag);
|
||||
|
||||
var successTag = new Tag("success", new FontIcon(Material2AL.CANCEL));
|
||||
successTag.setContentDisplay(ContentDisplay.RIGHT);
|
||||
successTag.getStyleClass().add(Styles.SUCCESS);
|
||||
content.getChildren().add(successTag);
|
||||
|
||||
var dangerTag = new Tag("danger", new FontIcon(Material2AL.CANCEL));
|
||||
dangerTag.setContentDisplay(ContentDisplay.RIGHT);
|
||||
dangerTag.getStyleClass().add(Styles.DANGER);
|
||||
content.getChildren().add(dangerTag);
|
||||
|
||||
return new SampleBlock("Removable", content);
|
||||
}
|
||||
|
||||
private SampleBlock customColorTagSample() {
|
||||
var content = new FlowPane(BLOCK_HGAP, BLOCK_VGAP);
|
||||
content.setPrefWidth(PREF_WIDTH);
|
||||
new CSSFragment("""
|
||||
.brand {
|
||||
-color-button-fg: -color-fg-emphasis;
|
||||
-color-button-bg-hover: -color-button-bg;
|
||||
-color-button-bg-pressed: -color-button-bg;
|
||||
}
|
||||
.twitter {
|
||||
-color-button-bg: rgb(85, 172, 238);
|
||||
-color-button-border: rgb(85, 172, 238);
|
||||
}
|
||||
.youtube {
|
||||
-color-button-bg: rgb(205, 32, 31);
|
||||
-color-button-border: rgb(205, 32, 31);
|
||||
}
|
||||
.facebook {
|
||||
-color-button-bg: rgb(59, 89, 153);
|
||||
-color-button-border: rgb(59, 89, 153);
|
||||
}
|
||||
""").addTo(content);
|
||||
|
||||
var twitterTag = new Tag("Twitter", new FontIcon(Feather.TWITTER));
|
||||
twitterTag.getStyleClass().addAll("brand", "twitter");
|
||||
content.getChildren().add(twitterTag);
|
||||
|
||||
var youtubeTag = new Tag("YouTube", new FontIcon(Feather.YOUTUBE));
|
||||
youtubeTag.getStyleClass().addAll("brand", "youtube");
|
||||
content.getChildren().add(youtubeTag);
|
||||
|
||||
var facebookTag = new Tag("Facebook", new FontIcon(Feather.FACEBOOK));
|
||||
facebookTag.getStyleClass().addAll("brand", "facebook");
|
||||
content.getChildren().add(facebookTag);
|
||||
|
||||
return new SampleBlock("Custom Color", content);
|
||||
}
|
||||
}
|
@ -1,133 +0,0 @@
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
|
||||
package atlantafx.sampler.page.showcase.widget;
|
||||
|
||||
import atlantafx.base.theme.Styles;
|
||||
import atlantafx.base.theme.Tweaks;
|
||||
import atlantafx.sampler.page.Page;
|
||||
import java.util.function.Supplier;
|
||||
import javafx.geometry.Insets;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.scene.Parent;
|
||||
import javafx.scene.control.ListCell;
|
||||
import javafx.scene.control.ListView;
|
||||
import javafx.scene.layout.BorderPane;
|
||||
import javafx.scene.layout.Pane;
|
||||
import javafx.scene.layout.VBox;
|
||||
|
||||
// JavaFX Skin API is very complex and almost undocumented. In many cases it's much simpler
|
||||
// to create a small widget that just do the job than wasting hours to debug control behaviour.
|
||||
// Consider this as a cookbook of those widgets.
|
||||
public class WidgetCollectionPage extends BorderPane implements Page {
|
||||
|
||||
public static final String NAME = "Widgets";
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return NAME;
|
||||
}
|
||||
|
||||
private final ListView<Example> toc = new ListView<>();
|
||||
private final VBox widgetWrapper = new VBox(PAGE_HGAP);
|
||||
private boolean isRendered = false;
|
||||
|
||||
public WidgetCollectionPage() {
|
||||
super();
|
||||
createView();
|
||||
}
|
||||
|
||||
private void createView() {
|
||||
widgetWrapper.getStyleClass().add("widget");
|
||||
widgetWrapper.setAlignment(Pos.TOP_CENTER);
|
||||
widgetWrapper.setFillWidth(false);
|
||||
|
||||
toc.setCellFactory(c -> new TocCell());
|
||||
toc.getStyleClass().addAll("toc", Styles.DENSE, Tweaks.EDGE_TO_EDGE);
|
||||
toc.getItems().setAll(Example.values());
|
||||
toc.getSelectionModel().selectedItemProperty().addListener((obs, old, val) -> {
|
||||
if (val == null) {
|
||||
return;
|
||||
}
|
||||
widgetWrapper.getChildren().setAll(val.getSupplier().get());
|
||||
});
|
||||
|
||||
// ~
|
||||
|
||||
setCenter(widgetWrapper);
|
||||
setRight(toc);
|
||||
BorderPane.setMargin(toc, new Insets(0, 0, 0, PAGE_HGAP));
|
||||
getStyleClass().setAll("page", "widget-collection");
|
||||
|
||||
toc.getSelectionModel().selectFirst();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Parent getView() {
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canDisplaySourceCode() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canChangeThemeSettings() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reset() {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void layoutChildren() {
|
||||
super.layoutChildren();
|
||||
if (isRendered) {
|
||||
return;
|
||||
}
|
||||
|
||||
isRendered = true;
|
||||
toc.getSelectionModel().selectFirst();
|
||||
toc.requestFocus();
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@SuppressWarnings("ImmutableEnumChecker")
|
||||
public enum Example {
|
||||
CARD("Card", CardSample::new),
|
||||
MESSAGE("Message", MessageSample::new),
|
||||
STEPPER("Stepper", StepperSample::new),
|
||||
TAG("Tag", TagSample::new);
|
||||
|
||||
private final String name;
|
||||
private final Supplier<Pane> supplier;
|
||||
|
||||
Example(String name, Supplier<Pane> supplier) {
|
||||
this.name = name;
|
||||
this.supplier = supplier;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public Supplier<Pane> getSupplier() {
|
||||
return supplier;
|
||||
}
|
||||
}
|
||||
|
||||
private static class TocCell extends ListCell<Example> {
|
||||
|
||||
@Override
|
||||
protected void updateItem(Example item, boolean empty) {
|
||||
super.updateItem(item, empty);
|
||||
if (empty || item == null) {
|
||||
setText(null);
|
||||
} else {
|
||||
setText(item.getName());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -4,18 +4,11 @@ package atlantafx.sampler.util;
|
||||
|
||||
import static atlantafx.base.theme.Styles.BUTTON_ICON;
|
||||
|
||||
import atlantafx.sampler.event.BrowseEvent;
|
||||
import atlantafx.sampler.event.DefaultEventBus;
|
||||
import java.net.URI;
|
||||
import javafx.scene.control.Button;
|
||||
import javafx.scene.control.Hyperlink;
|
||||
import javafx.scene.control.MenuItem;
|
||||
import javafx.scene.control.ToggleButton;
|
||||
import javafx.scene.control.ToggleGroup;
|
||||
import javafx.scene.input.KeyCombination;
|
||||
import org.kordamp.ikonli.Ikon;
|
||||
import org.kordamp.ikonli.javafx.FontIcon;
|
||||
|
||||
@Deprecated
|
||||
public final class Controls {
|
||||
|
||||
public static Button iconButton(Ikon icon, boolean disable) {
|
||||
@ -31,48 +24,4 @@ public final class Controls {
|
||||
button.getStyleClass().addAll(styleClasses);
|
||||
return button;
|
||||
}
|
||||
|
||||
public static MenuItem menuItem(String text, Ikon graphic, KeyCombination accelerator) {
|
||||
return menuItem(text, graphic, accelerator, false);
|
||||
}
|
||||
|
||||
public static MenuItem menuItem(String text, Ikon graphic, KeyCombination accelerator, boolean disable) {
|
||||
var item = new MenuItem(text);
|
||||
|
||||
if (graphic != null) {
|
||||
item.setGraphic(new FontIcon(graphic));
|
||||
}
|
||||
if (accelerator != null) {
|
||||
item.setAccelerator(accelerator);
|
||||
}
|
||||
item.setDisable(disable);
|
||||
|
||||
return item;
|
||||
}
|
||||
|
||||
public static ToggleButton toggleButton(String text,
|
||||
Ikon icon,
|
||||
ToggleGroup group,
|
||||
boolean selected,
|
||||
String... styleClasses) {
|
||||
var toggleButton = new ToggleButton(text);
|
||||
if (icon != null) {
|
||||
toggleButton.setGraphic(new FontIcon(icon));
|
||||
}
|
||||
if (group != null) {
|
||||
toggleButton.setToggleGroup(group);
|
||||
}
|
||||
toggleButton.setSelected(selected);
|
||||
toggleButton.getStyleClass().addAll(styleClasses);
|
||||
|
||||
return toggleButton;
|
||||
}
|
||||
|
||||
public static Hyperlink hyperlink(String text, URI uri) {
|
||||
var hyperlink = new Hyperlink(text);
|
||||
if (uri != null) {
|
||||
hyperlink.setOnAction(event -> DefaultEventBus.getInstance().publish(new BrowseEvent(uri)));
|
||||
}
|
||||
return hyperlink;
|
||||
}
|
||||
}
|
||||
|
39
sampler/src/main/java/atlantafx/sampler/util/Lazy.java
Normal file
39
sampler/src/main/java/atlantafx/sampler/util/Lazy.java
Normal file
@ -0,0 +1,39 @@
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
|
||||
package atlantafx.sampler.util;
|
||||
|
||||
import java.util.Objects;
|
||||
import java.util.function.Supplier;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
/**
|
||||
* Auxiliary object wrapper to support lazy initialization.
|
||||
* DO NOT override {@code hashCode()} / {@code equals()}, because each instance
|
||||
* of this object must remain unique.
|
||||
*/
|
||||
public class Lazy<T> implements Supplier<T> {
|
||||
|
||||
protected final Supplier<T> supplier;
|
||||
protected @Nullable T value = null;
|
||||
|
||||
public Lazy(Supplier<T> supplier) {
|
||||
this.supplier = Objects.requireNonNull(supplier, "supplier");
|
||||
}
|
||||
|
||||
@Override
|
||||
public T get() {
|
||||
if (value == null) {
|
||||
value = supplier.get();
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
public boolean initialized() {
|
||||
return value != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.valueOf(value);
|
||||
}
|
||||
}
|
@ -9,6 +9,7 @@ module atlantafx.sampler {
|
||||
requires javafx.swing;
|
||||
requires javafx.media;
|
||||
requires javafx.web;
|
||||
requires javafx.fxml;
|
||||
|
||||
requires org.kordamp.ikonli.core;
|
||||
requires org.kordamp.ikonli.javafx;
|
||||
@ -20,13 +21,13 @@ module atlantafx.sampler {
|
||||
requires datafaker;
|
||||
|
||||
exports atlantafx.sampler;
|
||||
exports atlantafx.sampler.fake;
|
||||
exports atlantafx.sampler.fake.domain;
|
||||
exports atlantafx.sampler.event;
|
||||
exports atlantafx.sampler.layout;
|
||||
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;
|
||||
@ -39,4 +40,5 @@ module atlantafx.sampler {
|
||||
opens atlantafx.sampler.assets.styles;
|
||||
opens atlantafx.sampler.images;
|
||||
opens atlantafx.sampler.images.modena;
|
||||
opens atlantafx.sampler.page.general;
|
||||
}
|
||||
|
@ -0,0 +1,39 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<?import javafx.geometry.Insets?>
|
||||
<?import javafx.scene.control.Button?>
|
||||
<?import javafx.scene.layout.VBox?>
|
||||
<?import org.kordamp.ikonli.javafx.FontIcon?>
|
||||
|
||||
<VBox alignment="CENTER" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefWidth="180.0" spacing="10.0" styleClass="sample" xmlns="http://javafx.com/javafx/19" xmlns:fx="http://javafx.com/fxml/1">
|
||||
<children>
|
||||
<Button defaultButton="true" mnemonicParsing="false" prefWidth="150.0" text="Button">
|
||||
<graphic>
|
||||
<FontIcon iconLiteral="mdoal-bookmark_border" />
|
||||
</graphic>
|
||||
</Button>
|
||||
<Button layoutX="235.0" layoutY="20.0" mnemonicParsing="false" prefWidth="150.0" styleClass="success" text="Button">
|
||||
<graphic>
|
||||
<FontIcon iconLiteral="mdoal-bookmark_border" />
|
||||
</graphic>
|
||||
</Button>
|
||||
<Button layoutX="30.0" layoutY="76.0" mnemonicParsing="false" prefWidth="150.0" styleClass="danger" text="Button">
|
||||
<graphic>
|
||||
<FontIcon iconLiteral="mdoal-bookmark_border" />
|
||||
</graphic>
|
||||
</Button>
|
||||
<Button layoutX="235.0" layoutY="66.0" mnemonicParsing="false" prefWidth="150.0" text="Button">
|
||||
<graphic>
|
||||
<FontIcon iconLiteral="mdoal-bookmark_border" />
|
||||
</graphic>
|
||||
</Button>
|
||||
<Button layoutX="235.0" layoutY="112.0" mnemonicParsing="false" prefWidth="150.0" styleClass="flat" text="Button">
|
||||
<graphic>
|
||||
<FontIcon iconLiteral="mdoal-bookmark_border" />
|
||||
</graphic>
|
||||
</Button>
|
||||
</children>
|
||||
<padding>
|
||||
<Insets bottom="20.0" left="20.0" right="20.0" top="20.0" />
|
||||
</padding>
|
||||
</VBox>
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user