Improve showcases

This commit is contained in:
mkpaz 2023-05-20 15:43:47 +04:00
parent 38ce8e9446
commit c97142e9bf
63 changed files with 474 additions and 385 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 195 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 81 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 195 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 106 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

@ -18,7 +18,11 @@ See the <a href="https://mkpaz.github.io/atlantafx/">docs</a> for more info.
</b></p> </b></p>
<p align="center"> <p align="center">
<img src="https://raw.githubusercontent.com/mkpaz/atlantafx/master/.screenshots/demo.gif" alt="Logo"/><br/> <img src="https://raw.githubusercontent.com/mkpaz/atlantafx/master/.screenshots/titlepage/blueprints.png" alt="blueprints"/><br/>
<img src="https://raw.githubusercontent.com/mkpaz/atlantafx/master/.screenshots/titlepage/fm.png" alt="apps"/><br/>
<img src="https://raw.githubusercontent.com/mkpaz/atlantafx/master/.screenshots/titlepage/mp.png" alt="apps"/><br/>
<img src="https://raw.githubusercontent.com/mkpaz/atlantafx/master/.screenshots/titlepage/page.png" alt="page"/><br/>
<img src="https://raw.githubusercontent.com/mkpaz/atlantafx/master/.screenshots/titlepage/themes.png" alt="themes"/><br/>
</p> </p>
* Flat interface inspired by the variety of Web component frameworks. * Flat interface inspired by the variety of Web component frameworks.

@ -59,7 +59,7 @@
<maven.compiler.target>${java.version}</maven.compiler.target> <maven.compiler.target>${java.version}</maven.compiler.target>
<java.version>17</java.version> <java.version>17</java.version>
<openjfx.version>19</openjfx.version> <openjfx.version>20</openjfx.version>
<sass.version>1.54.4</sass.version> <sass.version>1.54.4</sass.version>
<app.name>AtlantaFX</app.name> <app.name>AtlantaFX</app.name>

@ -2,7 +2,7 @@
package atlantafx.sampler.layout; package atlantafx.sampler.layout;
import atlantafx.sampler.util.Containers; import atlantafx.sampler.util.NodeUtils;
import javafx.geometry.Insets; import javafx.geometry.Insets;
import javafx.scene.layout.AnchorPane; import javafx.scene.layout.AnchorPane;
import javafx.scene.layout.StackPane; import javafx.scene.layout.StackPane;
@ -21,7 +21,7 @@ public final class ApplicationWindow extends AnchorPane {
new Overlay(), new Overlay(),
new MainLayer() new MainLayer()
); );
Containers.setAnchors(body, Insets.EMPTY); NodeUtils.setAnchors(body, Insets.EMPTY);
getChildren().setAll(body); getChildren().setAll(body);
} }

@ -9,7 +9,7 @@ import atlantafx.sampler.Resources;
import atlantafx.sampler.event.DefaultEventBus; import atlantafx.sampler.event.DefaultEventBus;
import atlantafx.sampler.event.PageEvent; import atlantafx.sampler.event.PageEvent;
import atlantafx.sampler.theme.HighlightJSTheme; import atlantafx.sampler.theme.HighlightJSTheme;
import atlantafx.sampler.util.Containers; import atlantafx.sampler.util.NodeUtils;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import javafx.geometry.Insets; import javafx.geometry.Insets;
@ -48,7 +48,7 @@ final class CodeViewer extends AnchorPane {
private void lazyLoadWebView() { private void lazyLoadWebView() {
if (webView == null) { if (webView == null) {
webView = new WebView(); webView = new WebView();
Containers.setAnchors(webView, Insets.EMPTY); NodeUtils.setAnchors(webView, Insets.EMPTY);
getChildren().add(0, webView); getChildren().add(0, webView);
} }
} }

@ -77,7 +77,8 @@ class MainLayer extends BorderPane {
} }
try { try {
WritableImage img = page.getSnapshotTarget().snapshot(new SnapshotParameters(), null); SnapshotParameters sp = new SnapshotParameters();
WritableImage img = page.getSnapshotTarget().snapshot(sp, null);
ImageIO.write(SwingFXUtils.fromFXImage(img, null), "png", file); ImageIO.write(SwingFXUtils.fromFXImage(img, null), "png", file);
} catch (IOException ex) { } catch (IOException ex) {
throw new RuntimeException(ex); throw new RuntimeException(ex);

@ -48,7 +48,7 @@ import atlantafx.sampler.page.components.TreeTableViewPage;
import atlantafx.sampler.page.components.TreeViewPage; import atlantafx.sampler.page.components.TreeViewPage;
import atlantafx.sampler.page.general.BBCodePage; import atlantafx.sampler.page.general.BBCodePage;
import atlantafx.sampler.page.general.IconsPage; import atlantafx.sampler.page.general.IconsPage;
import atlantafx.sampler.page.showcase.OverviewPage; import atlantafx.sampler.page.showcase.BlueprintsPage;
import atlantafx.sampler.page.general.ThemePage; import atlantafx.sampler.page.general.ThemePage;
import atlantafx.sampler.page.general.TypographyPage; import atlantafx.sampler.page.general.TypographyPage;
import atlantafx.sampler.page.showcase.filemanager.FileManagerPage; import atlantafx.sampler.page.showcase.filemanager.FileManagerPage;
@ -214,7 +214,7 @@ public class MainModel {
var showcases = NavTree.Item.group("Showcase", new FontIcon(Material2OutlinedMZ.VISIBILITY)); var showcases = NavTree.Item.group("Showcase", new FontIcon(Material2OutlinedMZ.VISIBILITY));
showcases.getChildren().setAll( showcases.getChildren().setAll(
NAV_TREE.get(OverviewPage.class), NAV_TREE.get(BlueprintsPage.class),
NAV_TREE.get(FileManagerPage.class), NAV_TREE.get(FileManagerPage.class),
NAV_TREE.get(MusicPlayerPage.class) NAV_TREE.get(MusicPlayerPage.class)
); );
@ -310,7 +310,7 @@ public class MainModel {
map.put(TreeViewPage.class, NavTree.Item.page(TreeViewPage.NAME, TreeViewPage.class)); map.put(TreeViewPage.class, NavTree.Item.page(TreeViewPage.NAME, TreeViewPage.class));
// showcases // showcases
map.put(OverviewPage.class, NavTree.Item.page(OverviewPage.NAME, OverviewPage.class)); map.put(BlueprintsPage.class, NavTree.Item.page(BlueprintsPage.NAME, BlueprintsPage.class));
map.put(FileManagerPage.class, NavTree.Item.page(FileManagerPage.NAME, FileManagerPage.class)); map.put(FileManagerPage.class, NavTree.Item.page(FileManagerPage.NAME, FileManagerPage.class));
map.put(MusicPlayerPage.class, NavTree.Item.page(MusicPlayerPage.NAME, MusicPlayerPage.class)); map.put(MusicPlayerPage.class, NavTree.Item.page(MusicPlayerPage.NAME, MusicPlayerPage.class));

@ -3,7 +3,6 @@
package atlantafx.sampler.layout; package atlantafx.sampler.layout;
import atlantafx.sampler.util.Animations; import atlantafx.sampler.util.Animations;
import atlantafx.sampler.util.Containers;
import atlantafx.sampler.util.NodeUtils; import atlantafx.sampler.util.NodeUtils;
import java.util.Objects; import java.util.Objects;
import java.util.function.Consumer; import java.util.function.Consumer;
@ -51,7 +50,7 @@ public final class Overlay extends StackPane {
centerContentWrapper.setAlignment(Pos.CENTER); centerContentWrapper.setAlignment(Pos.CENTER);
scrollPane = new ScrollPane(); scrollPane = new ScrollPane();
Containers.setScrollConstraints(scrollPane, NodeUtils.setScrollConstraints(scrollPane,
ScrollPane.ScrollBarPolicy.AS_NEEDED, true, ScrollPane.ScrollBarPolicy.AS_NEEDED, true,
ScrollPane.ScrollBarPolicy.NEVER, true ScrollPane.ScrollBarPolicy.NEVER, true
); );
@ -108,11 +107,11 @@ public final class Overlay extends StackPane {
switch (pos) { switch (pos) {
case LEFT -> { case LEFT -> {
edgeContentWrapper.getChildren().setAll(content); edgeContentWrapper.getChildren().setAll(content);
Containers.setAnchors(content, new Insets(0, -1, 0, 0)); NodeUtils.setAnchors(content, new Insets(0, -1, 0, 0));
} }
case RIGHT -> { case RIGHT -> {
edgeContentWrapper.getChildren().setAll(content); edgeContentWrapper.getChildren().setAll(content);
Containers.setAnchors(content, new Insets(0, 0, 0, -1)); NodeUtils.setAnchors(content, new Insets(0, 0, 0, -1));
} }
case CENTER -> { case CENTER -> {
centerContentWrapper.getChildren().setAll(content); centerContentWrapper.getChildren().setAll(content);

@ -8,7 +8,6 @@ import static atlantafx.base.theme.Styles.FLAT;
import static atlantafx.base.theme.Styles.TITLE_4; import static atlantafx.base.theme.Styles.TITLE_4;
import atlantafx.base.controls.Spacer; import atlantafx.base.controls.Spacer;
import atlantafx.sampler.util.Containers;
import java.util.Objects; import java.util.Objects;
import javafx.geometry.Pos; import javafx.geometry.Pos;
import javafx.scene.control.Button; import javafx.scene.control.Button;
@ -70,8 +69,10 @@ public abstract class OverlayDialog<T extends Region> extends VBox {
VBox.setVgrow(footerBox, Priority.NEVER); VBox.setVgrow(footerBox, Priority.NEVER);
// IMPORTANT: this guarantees client will use correct width and height // IMPORTANT: this guarantees client will use correct width and height
Containers.usePrefWidth(this); setMinWidth(USE_PREF_SIZE);
Containers.usePrefHeight(this); setMaxWidth(USE_PREF_SIZE);
setMinHeight(USE_PREF_SIZE);
setMaxHeight(USE_PREF_SIZE);
// if you're updating this line, update setContent() method as well // if you're updating this line, update setContent() method as well
getChildren().setAll(headerBox, footerBox); getChildren().setAll(headerBox, footerBox);

@ -2,12 +2,12 @@
package atlantafx.sampler.page; 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.AS_NEEDED;
import static javafx.scene.control.ScrollPane.ScrollBarPolicy.NEVER; import static javafx.scene.control.ScrollPane.ScrollBarPolicy.NEVER;
import atlantafx.base.util.BBCodeParser; import atlantafx.base.util.BBCodeParser;
import atlantafx.sampler.layout.Overlay; import atlantafx.sampler.layout.Overlay;
import atlantafx.sampler.util.NodeUtils;
import java.net.URI; import java.net.URI;
import javafx.geometry.Pos; import javafx.geometry.Pos;
import javafx.scene.Node; import javafx.scene.Node;
@ -39,7 +39,7 @@ public abstract class AbstractPage extends StackPane implements Page {
userContent.setMaxWidth(Math.min(Page.MAX_WIDTH, 800)); userContent.setMaxWidth(Math.min(Page.MAX_WIDTH, 800));
var scrollPane = new ScrollPane(userContentArea); var scrollPane = new ScrollPane(userContentArea);
setScrollConstraints(scrollPane, AS_NEEDED, true, NEVER, true); NodeUtils.setScrollConstraints(scrollPane, AS_NEEDED, true, NEVER, true);
scrollPane.setMaxHeight(20_000); scrollPane.setMaxHeight(20_000);
getChildren().setAll(scrollPane); getChildren().setAll(scrollPane);

@ -2,12 +2,12 @@
package atlantafx.sampler.page; 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.AS_NEEDED;
import static javafx.scene.control.ScrollPane.ScrollBarPolicy.NEVER; import static javafx.scene.control.ScrollPane.ScrollBarPolicy.NEVER;
import atlantafx.base.theme.Styles; import atlantafx.base.theme.Styles;
import atlantafx.sampler.layout.Overlay; import atlantafx.sampler.layout.Overlay;
import atlantafx.sampler.util.NodeUtils;
import java.net.URI; import java.net.URI;
import java.util.LinkedHashSet; import java.util.LinkedHashSet;
import java.util.Objects; import java.util.Objects;
@ -58,7 +58,7 @@ public abstract class OutlinePage extends StackPane implements Page {
userContent.setMaxWidth(Page.MAX_WIDTH - OUTLINE_WIDTH - 100); userContent.setMaxWidth(Page.MAX_WIDTH - OUTLINE_WIDTH - 100);
scrollPane.setContent(userContentArea); scrollPane.setContent(userContentArea);
setScrollConstraints(scrollPane, AS_NEEDED, true, NEVER, true); NodeUtils.setScrollConstraints(scrollPane, AS_NEEDED, true, NEVER, true);
scrollPane.setMaxHeight(20_000); scrollPane.setMaxHeight(20_000);
// scroll spy // scroll spy

@ -10,7 +10,6 @@ import static atlantafx.sampler.util.ContrastLevel.getContrastRatioOpacityAware;
import static atlantafx.sampler.util.JColorUtils.flattenColor; import static atlantafx.sampler.util.JColorUtils.flattenColor;
import atlantafx.base.theme.Styles; import atlantafx.base.theme.Styles;
import atlantafx.sampler.util.Containers;
import atlantafx.sampler.util.ContrastLevel; import atlantafx.sampler.util.ContrastLevel;
import atlantafx.sampler.util.NodeUtils; import atlantafx.sampler.util.NodeUtils;
import java.util.function.Consumer; import java.util.function.Consumer;
@ -51,17 +50,17 @@ final class ColorPaletteBlock extends VBox {
contrastRatioText = new Text(); contrastRatioText = new Text();
contrastRatioText.setStyle("-fx-fill:" + fgColorName + ";"); contrastRatioText.setStyle("-fx-fill:" + fgColorName + ";");
contrastRatioText.getStyleClass().addAll("contrast-ratio-text", TITLE_3); contrastRatioText.getStyleClass().addAll("contrast-ratio-text", TITLE_3);
Containers.setAnchors(contrastRatioText, new Insets(5, -1, -1, 5)); NodeUtils.setAnchors(contrastRatioText, new Insets(5, -1, -1, 5));
contrastLevelLabel.setGraphic(contrastLevelIcon); contrastLevelLabel.setGraphic(contrastLevelIcon);
contrastLevelLabel.getStyleClass().add("contrast-level-label"); contrastLevelLabel.getStyleClass().add("contrast-level-label");
contrastLevelLabel.setVisible(false); contrastLevelLabel.setVisible(false);
Containers.setAnchors(contrastLevelLabel, new Insets(-1, 3, 3, -1)); NodeUtils.setAnchors(contrastLevelLabel, new Insets(-1, 3, 3, -1));
editIcon.setIconSize(24); editIcon.setIconSize(24);
editIcon.getStyleClass().add("edit-icon"); editIcon.getStyleClass().add("edit-icon");
NodeUtils.toggleVisibility(editIcon, false); NodeUtils.toggleVisibility(editIcon, false);
Containers.setAnchors(editIcon, new Insets(3, 3, -1, -1)); NodeUtils.setAnchors(editIcon, new Insets(3, 3, -1, -1));
colorRectangle = new AnchorPane(); colorRectangle = new AnchorPane();
colorRectangle.setStyle( colorRectangle.setStyle(

@ -14,7 +14,7 @@ import atlantafx.base.controls.Spacer;
import atlantafx.sampler.theme.SamplerTheme; import atlantafx.sampler.theme.SamplerTheme;
import atlantafx.sampler.theme.ThemeManager; import atlantafx.sampler.theme.ThemeManager;
import atlantafx.sampler.theme.ThemeRepository; import atlantafx.sampler.theme.ThemeRepository;
import atlantafx.sampler.util.Containers; import atlantafx.sampler.util.NodeUtils;
import java.io.File; import java.io.File;
import java.util.Map; import java.util.Map;
import java.util.Objects; import java.util.Objects;
@ -63,7 +63,7 @@ final class ThemeRepoManager extends VBox {
REPO.getAll().forEach(theme -> themeList.getChildren().add(themeCell(theme))); REPO.getAll().forEach(theme -> themeList.getChildren().add(themeCell(theme)));
var scrollPane = new ScrollPane(themeList); var scrollPane = new ScrollPane(themeList);
Containers.setScrollConstraints(scrollPane, AS_NEEDED, true, AS_NEEDED, true); NodeUtils.setScrollConstraints(scrollPane, AS_NEEDED, true, AS_NEEDED, true);
scrollPane.setMaxHeight(4000); scrollPane.setMaxHeight(4000);
VBox.setVgrow(scrollPane, ALWAYS); VBox.setVgrow(scrollPane, ALWAYS);

@ -2,11 +2,11 @@
package atlantafx.sampler.page.showcase; package atlantafx.sampler.page.showcase;
import static atlantafx.sampler.util.Containers.setScrollConstraints;
import static javafx.scene.control.ScrollPane.ScrollBarPolicy.AS_NEEDED; import static javafx.scene.control.ScrollPane.ScrollBarPolicy.AS_NEEDED;
import atlantafx.sampler.Resources; import atlantafx.sampler.Resources;
import atlantafx.sampler.page.Page; import atlantafx.sampler.page.Page;
import atlantafx.sampler.util.NodeUtils;
import java.io.IOException; import java.io.IOException;
import java.net.URI; import java.net.URI;
import javafx.fxml.FXMLLoader; import javafx.fxml.FXMLLoader;
@ -19,17 +19,17 @@ import javafx.scene.layout.Priority;
import javafx.scene.layout.VBox; import javafx.scene.layout.VBox;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
public final class OverviewPage extends ScrollPane implements Page { public final class BlueprintsPage extends ScrollPane implements Page {
public static final String NAME = "Overview"; public static final String NAME = "Blueprints";
private VBox wrapper; private final VBox wrapper;
@Override @Override
public String getName() { public String getName() {
return NAME; return NAME;
} }
public OverviewPage() { public BlueprintsPage() {
super(); super();
try { try {
@ -37,21 +37,21 @@ public final class OverviewPage extends ScrollPane implements Page {
wrapper.setAlignment(Pos.TOP_CENTER); wrapper.setAlignment(Pos.TOP_CENTER);
var loader = new FXMLLoader( var loader = new FXMLLoader(
Resources.getResource("assets/fxml/overview/index.fxml").toURL() Resources.getResource("assets/fxml/blueprints/index.fxml").toURL()
); );
Parent fxmlContent = loader.load(); Parent fxmlContent = loader.load();
((Pane) fxmlContent).setMaxWidth(Page.MAX_WIDTH); ((Pane) fxmlContent).setMaxWidth(Page.MAX_WIDTH);
VBox.setVgrow(fxmlContent, Priority.ALWAYS); VBox.setVgrow(fxmlContent, Priority.ALWAYS);
wrapper.getChildren().setAll(fxmlContent); wrapper.getChildren().setAll(fxmlContent);
setScrollConstraints(this, AS_NEEDED, true, AS_NEEDED, true); NodeUtils.setScrollConstraints(this, AS_NEEDED, true, AS_NEEDED, true);
setMaxHeight(20_000); setMaxHeight(20_000);
setContent(wrapper); setContent(wrapper);
} catch (IOException e) { } catch (IOException e) {
throw new RuntimeException("Unable to load FXML file", e); throw new RuntimeException("Unable to load FXML file", e);
} }
setId("overview"); setId("blueprints");
} }
@Override @Override

@ -11,6 +11,7 @@ import javafx.css.PseudoClass;
import javafx.scene.Node; import javafx.scene.Node;
import javafx.scene.control.Alert; import javafx.scene.control.Alert;
import javafx.scene.control.Label; import javafx.scene.control.Label;
import javafx.scene.control.Tooltip;
import javafx.scene.layout.HBox; import javafx.scene.layout.HBox;
import javafx.scene.layout.Pane; import javafx.scene.layout.Pane;
import javafx.scene.layout.Priority; import javafx.scene.layout.Priority;
@ -30,6 +31,7 @@ public abstract class ShowcasePage extends StackPane implements Page {
protected VBox showcaseWindow = new VBox(); protected VBox showcaseWindow = new VBox();
protected Label windowTitle = new Label(); protected Label windowTitle = new Label();
protected VBox showCaseContent = new VBox(); protected VBox showCaseContent = new VBox();
protected FontIcon aboutBtn = new FontIcon(Feather.HELP_CIRCLE);
protected int windowWidth = DEFAULT_WIDTH; protected int windowWidth = DEFAULT_WIDTH;
protected int windowHeight = DEFAULT_HEIGHT; protected int windowHeight = DEFAULT_HEIGHT;
protected BooleanProperty maximized = new SimpleBooleanProperty(); protected BooleanProperty maximized = new SimpleBooleanProperty();
@ -43,11 +45,15 @@ public abstract class ShowcasePage extends StackPane implements Page {
protected void createShowcaseLayout() { protected void createShowcaseLayout() {
windowTitle.getStyleClass().addAll("title"); windowTitle.getStyleClass().addAll("title");
aboutBtn.getStyleClass().addAll(Styles.SMALL, Styles.FLAT);
var maximizeBtn = new FontIcon(Feather.MAXIMIZE_2); var maximizeBtn = new FontIcon(Feather.MAXIMIZE_2);
maximizeBtn.getStyleClass().addAll(Styles.SMALL, Styles.FLAT); maximizeBtn.getStyleClass().addAll(Styles.SMALL, Styles.FLAT);
maximizeBtn.setOnMouseClicked(e -> maximized.set(!maximized.get())); maximizeBtn.setOnMouseClicked(e -> maximized.set(!maximized.get()));
var windowHeader = new HBox(windowTitle, new Spacer(), maximizeBtn); var windowHeader = new HBox(
20, windowTitle, new Spacer(), aboutBtn, maximizeBtn
);
windowHeader.getStyleClass().add("header"); windowHeader.getStyleClass().add("header");
showcaseWindow.getStyleClass().add("window"); showcaseWindow.getStyleClass().add("window");
@ -75,6 +81,18 @@ public abstract class ShowcasePage extends StackPane implements Page {
} }
} }
protected void setAboutInfo(String text) {
var tooltip = new Tooltip(text);
tooltip.setWrapText(true);
tooltip.setMaxWidth(300);
aboutBtn.setOnMouseEntered(e -> {
var localBounds = aboutBtn.getBoundsInLocal();
var screenBounds = aboutBtn.localToScreen(localBounds);
tooltip.show(getScene().getWindow(), screenBounds.getCenterX(), screenBounds.getMaxY());
});
aboutBtn.setOnMouseExited(e -> tooltip.hide());
}
protected void setShowCaseContent(Node node) { protected void setShowCaseContent(Node node) {
setShowCaseContent(node, DEFAULT_WIDTH, DEFAULT_HEIGHT); setShowCaseContent(node, DEFAULT_WIDTH, DEFAULT_HEIGHT);
} }

@ -1,9 +0,0 @@
/* SPDX-License-Identifier: MIT */
package atlantafx.sampler.page.showcase.filemanager;
import java.nio.file.Path;
import org.kordamp.ikonli.Ikon;
record Bookmark(String title, Path path, Ikon icon) {
}

@ -1,76 +0,0 @@
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;
import javafx.scene.control.ListCell;
import javafx.scene.control.ListView;
import javafx.scene.input.MouseEvent;
import org.kordamp.ikonli.feather.Feather;
import org.kordamp.ikonli.javafx.FontIcon;
final class BookmarkList extends ListView<Bookmark> {
public BookmarkList(Model model) {
getStyleClass().addAll("bookmark-list", Tweaks.EDGE_TO_EDGE);
// this is Linux specific and only for EN locale
getItems().setAll(
new Bookmark("Home", USER_HOME, Feather.HOME),
new Bookmark("Documents", USER_HOME.resolve("Documents"), Feather.FILE),
new Bookmark("Downloads", USER_HOME.resolve("Downloads"), Feather.DOWNLOAD),
new Bookmark("Music", USER_HOME.resolve("Music"), Feather.MUSIC),
new Bookmark("Pictures", USER_HOME.resolve("Pictures"), Feather.IMAGE),
new Bookmark("Videos", USER_HOME.resolve("Videos"), Feather.VIDEO)
);
setCellFactory(param -> {
var cell = new BookmarkCell();
cell.setOnMousePressed((MouseEvent event) -> {
if (cell.isEmpty() || cell.getItem() == null) {
event.consume();
} else {
Path path = cell.getItem().path();
if (!Files.exists(path)) {
var alert = new Alert(Alert.AlertType.WARNING);
alert.setTitle("Error Dialog");
alert.setHeaderText("Sorry, this is just demo.");
alert.setContentText("There's no such directory as \"" + path + "\"");
alert.initOwner(getScene().getWindow());
alert.showAndWait();
return;
}
model.navigate(path, true);
}
});
return cell;
});
}
///////////////////////////////////////////////////////////////////////////
private static class BookmarkCell extends ListCell<Bookmark> {
public BookmarkCell() {
setGraphicTextGap(10);
}
@Override
protected void updateItem(Bookmark bookmark, boolean empty) {
super.updateItem(bookmark, empty);
if (empty) {
setGraphic(null);
setText(null);
} else {
setGraphic(new FontIcon(bookmark.icon()));
setText(bookmark.title());
}
}
}
}

@ -0,0 +1,88 @@
/* SPDX-License-Identifier: MIT */
package atlantafx.sampler.page.showcase.filemanager;
import static atlantafx.sampler.page.showcase.filemanager.Utils.isFileHidden;
import atlantafx.base.theme.Tweaks;
import java.io.File;
import java.util.Comparator;
import javafx.scene.control.TreeCell;
import javafx.scene.control.TreeItem;
import javafx.scene.control.TreeView;
import javafx.scene.image.ImageView;
final class DirectoryTree extends TreeView<File> {
@SuppressWarnings({"unchecked", "rawtypes"})
public DirectoryTree(Model model, File rootPath) {
super();
getStyleClass().add(Tweaks.ALT_ICON);
var root = new TreeItem<>(rootPath);
root.setExpanded(true);
setRoot(root);
// scan file tree two levels deep for starters
scan(root, 2);
setCellFactory(c -> new TreeCell<>() {
@Override
protected void updateItem(File item, boolean empty) {
super.updateItem(item, empty);
if (empty || item == null) {
setText(null);
setGraphic(null);
} else {
setText(item.getName());
var image = new ImageView(FileIconRepository.FOLDER);
image.setFitWidth(20);
image.setFitHeight(20);
setGraphic(image);
pseudoClassStateChanged(Model.HIDDEN, isFileHidden(item.toPath()));
}
}
});
// scan deeper as the user navigates down the tree
root.addEventHandler(TreeItem.branchExpandedEvent(), event -> {
TreeItem parent = event.getTreeItem();
parent.getChildren().forEach(child -> {
var item = (TreeItem<File>) child;
if (item.getChildren().isEmpty()) {
scan(item, 1);
}
});
});
getSelectionModel().selectedItemProperty().addListener((obs, old, item) -> {
if (item != null && item.getValue() != null) {
model.navigate(item.getValue().toPath(), true);
}
});
}
public static void scan(TreeItem<File> parent, int depth) {
File[] files = parent.getValue().listFiles();
depth--;
if (files != null) {
for (File f : files) {
if (f.isDirectory()) {
var item = new TreeItem<>(f);
parent.getChildren().add(item);
if (depth > 0) {
scan(item, depth);
}
}
}
parent.getChildren().sort(
Comparator.comparing(TreeItem::getValue)
);
}
}
}

@ -11,11 +11,13 @@ import javafx.scene.image.Image;
final class FileIconRepository { final class FileIconRepository {
private static final String IMAGE_DIRECTORY = "images/papirus/"; public static final String IMAGE_DIRECTORY = "images/papirus/";
public static final Image UNKNOWN_FILE = public static final Image UNKNOWN_FILE = new Image(
new Image(Resources.getResourceAsStream(IMAGE_DIRECTORY + "mimetypes/text-plain.png")); Resources.getResourceAsStream(IMAGE_DIRECTORY + "mimetypes/text-plain.png")
public static final Image FOLDER = );
new Image(Resources.getResourceAsStream(IMAGE_DIRECTORY + "places/folder-blue.png")); public static final Image FOLDER = new Image(
Resources.getResourceAsStream(IMAGE_DIRECTORY + "places/folder-paleorange.png")
);
private final Map<String, Image> cache = new HashMap<>(); private final Map<String, Image> cache = new HashMap<>();
private final Set<String> unknownMimeTypes = new HashSet<>(); private final Set<String> unknownMimeTypes = new HashSet<>();

@ -21,8 +21,9 @@ import javafx.scene.control.TableView;
final class FileList { final class FileList {
static final Comparator<Path> FILE_TYPE_COMPARATOR = static final Comparator<Path> FILE_TYPE_COMPARATOR = Comparator.comparing(
Comparator.comparing(path -> !Files.isDirectory(path)); path -> !Files.isDirectory(path)
);
static final Predicate<Path> PREDICATE_ANY = path -> true; static final Predicate<Path> PREDICATE_ANY = path -> true;
static final Predicate<Path> PREDICATE_NOT_HIDDEN = path -> !isFileHidden(path); static final Predicate<Path> PREDICATE_NOT_HIDDEN = path -> !isFileHidden(path);

@ -2,19 +2,16 @@
package atlantafx.sampler.page.showcase.filemanager; package atlantafx.sampler.page.showcase.filemanager;
import static atlantafx.base.theme.Styles.BUTTON_ICON;
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_ANY;
import static atlantafx.sampler.page.showcase.filemanager.FileList.PREDICATE_NOT_HIDDEN; import static atlantafx.sampler.page.showcase.filemanager.FileList.PREDICATE_NOT_HIDDEN;
import static atlantafx.sampler.page.showcase.filemanager.Utils.openFile; import static atlantafx.sampler.page.showcase.filemanager.Utils.openFile;
import static atlantafx.sampler.util.Controls.iconButton;
import atlantafx.base.controls.Breadcrumbs; import atlantafx.base.controls.Breadcrumbs;
import atlantafx.base.controls.Breadcrumbs.BreadCrumbItem; import atlantafx.base.controls.Breadcrumbs.BreadCrumbItem;
import atlantafx.base.controls.Spacer; import atlantafx.base.controls.Spacer;
import atlantafx.base.theme.Tweaks; import atlantafx.base.theme.Styles;
import atlantafx.sampler.page.showcase.ShowcasePage; import atlantafx.sampler.page.showcase.ShowcasePage;
import atlantafx.sampler.util.Containers; import atlantafx.sampler.util.NodeUtils;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
import java.util.ArrayList; import java.util.ArrayList;
@ -25,19 +22,22 @@ import javafx.geometry.Insets;
import javafx.scene.Node; import javafx.scene.Node;
import javafx.scene.control.Button; import javafx.scene.control.Button;
import javafx.scene.control.ButtonBase; import javafx.scene.control.ButtonBase;
import javafx.scene.control.CheckMenuItem; import javafx.scene.control.ContentDisplay;
import javafx.scene.control.Hyperlink;
import javafx.scene.control.Label; import javafx.scene.control.Label;
import javafx.scene.control.MenuButton;
import javafx.scene.control.MenuItem;
import javafx.scene.control.SplitPane; import javafx.scene.control.SplitPane;
import javafx.scene.control.ToggleButton;
import javafx.scene.control.ToolBar; import javafx.scene.control.ToolBar;
import javafx.scene.control.Tooltip;
import javafx.scene.layout.BorderPane; import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Priority;
import javafx.scene.layout.VBox; import javafx.scene.layout.VBox;
import javafx.util.Callback; import javafx.util.Callback;
import org.kordamp.ikonli.feather.Feather; import org.kordamp.ikonli.feather.Feather;
import org.kordamp.ikonli.javafx.FontIcon; import org.kordamp.ikonli.javafx.FontIcon;
import org.kordamp.ikonli.material2.Material2AL; import org.kordamp.ikonli.material2.Material2AL;
import org.kordamp.ikonli.material2.Material2MZ; import org.kordamp.ikonli.material2.Material2OutlinedAL;
public class FileManagerPage extends ShowcasePage { public class FileManagerPage extends ShowcasePage {
@ -59,31 +59,40 @@ public class FileManagerPage extends ShowcasePage {
} }
private void createView() { private void createView() {
var backBtn = iconButton(Feather.ARROW_LEFT, false); var backBtn = new Button("", new FontIcon(Feather.ARROW_LEFT));
backBtn.setContentDisplay(ContentDisplay.GRAPHIC_ONLY);
backBtn.getStyleClass().add(Styles.BUTTON_ICON);
backBtn.setOnAction(e -> model.back()); backBtn.setOnAction(e -> model.back());
backBtn.disableProperty().bind(model.getHistory().canGoBackProperty().not()); backBtn.disableProperty().bind(model.getHistory().canGoBackProperty().not());
backBtn.setTooltip(new Tooltip("Back"));
var forthBtn = iconButton(Feather.ARROW_RIGHT, false); var forthBtn = new Button("", new FontIcon(Feather.ARROW_RIGHT));
forthBtn.setContentDisplay(ContentDisplay.GRAPHIC_ONLY);
forthBtn.getStyleClass().add(Styles.BUTTON_ICON);
forthBtn.setOnAction(e -> model.forth()); forthBtn.setOnAction(e -> model.forth());
forthBtn.disableProperty().bind(model.getHistory().canGoForthProperty().not()); forthBtn.disableProperty().bind(model.getHistory().canGoForthProperty().not());
forthBtn.setTooltip(new Tooltip("Forward"));
Breadcrumbs<Path> breadcrumbs = breadCrumbs(); Breadcrumbs<Path> breadcrumbs = createBreadcrumbs();
breadcrumbs.setMaxWidth(Double.MAX_VALUE);
HBox.setHgrow(breadcrumbs, Priority.ALWAYS);
breadcrumbs.setOnCrumbAction(e -> model.navigate(e.getSelectedCrumb().getValue(), true)); breadcrumbs.setOnCrumbAction(e -> model.navigate(e.getSelectedCrumb().getValue(), true));
var createMenuBtn = new MenuButton(); var createBtn = new Button(null, new FontIcon(Feather.FOLDER));
createMenuBtn.setText("New"); createBtn.setContentDisplay(ContentDisplay.GRAPHIC_ONLY);
createMenuBtn.getItems().setAll( createBtn.getStyleClass().add(Styles.BUTTON_ICON);
new MenuItem("New Folder"),
new MenuItem("New Document")
);
var toggleHiddenCheck = new CheckMenuItem("Show Hidden Files"); var treeTgl = new ToggleButton("", new FontIcon(Material2OutlinedAL.ACCOUNT_TREE));
toggleHiddenCheck.setSelected(false); treeTgl.setContentDisplay(ContentDisplay.GRAPHIC_ONLY);
treeTgl.getStyleClass().add(Styles.BUTTON_ICON);
treeTgl.setSelected(true);
treeTgl.setTooltip(new Tooltip("Show tree"));
var menuBtn = new MenuButton(); var hiddenTgl = new ToggleButton("", new FontIcon(Material2OutlinedAL.FILTER_LIST));
menuBtn.setGraphic(new FontIcon(Material2MZ.MENU)); hiddenTgl.setContentDisplay(ContentDisplay.GRAPHIC_ONLY);
menuBtn.getItems().setAll(toggleHiddenCheck); hiddenTgl.getStyleClass().add(Styles.BUTTON_ICON);
menuBtn.getStyleClass().addAll(BUTTON_ICON, Tweaks.NO_ARROW); hiddenTgl.setSelected(true);
hiddenTgl.setTooltip(new Tooltip("Show hidden files"));
var topBar = new ToolBar(); var topBar = new ToolBar();
topBar.getItems().setAll( topBar.getItems().setAll(
@ -91,18 +100,19 @@ public class FileManagerPage extends ShowcasePage {
forthBtn, forthBtn,
new Spacer(10), new Spacer(10),
breadcrumbs, breadcrumbs,
new Spacer(), new Spacer(10),
createMenuBtn, treeTgl,
menuBtn hiddenTgl
); );
// ~ // ~
var dirTree = new DirectoryTree(model, Model.USER_HOME.toFile());
dirTree.setMinWidth(100);
BookmarkList bookmarksList = new BookmarkList(model); var dirView = new TableDirectoryView();
dirView.setMinWidth(300);
DirectoryView directoryView = new TableDirectoryView(); dirView.setDirectory(model.currentPathProperty().get());
directoryView.setDirectory(model.currentPathProperty().get()); dirView.setOnAction(path -> {
directoryView.setOnAction(path -> {
if (Files.isDirectory(path)) { if (Files.isDirectory(path)) {
model.navigate(path, true); model.navigate(path, true);
} else { } else {
@ -111,25 +121,32 @@ public class FileManagerPage extends ShowcasePage {
}); });
// ~ // ~
var splitPane = new SplitPane(dirTree, dirView.getView());
var splitPane = new SplitPane(bookmarksList, directoryView.getView());
splitPane.widthProperty().addListener( splitPane.widthProperty().addListener(
// set sidebar width in pixels depending on split pane width // set sidebar width in pixels depending on split pane width
(obs, old, val) -> splitPane.setDividerPosition(0, 200 / splitPane.getWidth()) (obs, old, val) -> splitPane.setDividerPosition(0, 300 / splitPane.getWidth())
); );
var root = new BorderPane(); var root = new BorderPane();
root.getStyleClass().add("file-manager-showcase"); root.setId("file-manager-showcase");
root.getStylesheets().add(STYLESHEET_URL); root.getStylesheets().add(STYLESHEET_URL);
root.setTop(new VBox(topBar)); root.setTop(new VBox(topBar));
root.setCenter(splitPane); root.setCenter(splitPane);
//root.setBottom(creditsBox);
toggleHiddenCheck.selectedProperty().addListener((obs, old, val) -> directoryView.getFileList() hiddenTgl.selectedProperty().addListener((obs, old, val) -> dirView.getFileList()
.predicateProperty() .predicateProperty()
.set(val ? PREDICATE_ANY : PREDICATE_NOT_HIDDEN) .set(!val ? PREDICATE_ANY : PREDICATE_NOT_HIDDEN)
); );
directoryView.getFileList().predicateProperty().set(PREDICATE_NOT_HIDDEN); dirView.getFileList().predicateProperty().set(PREDICATE_NOT_HIDDEN);
treeTgl.selectedProperty().addListener((obs, old, val) -> {
if (val) {
splitPane.getItems().add(0, dirTree);
splitPane.setDividerPosition(0, 300 / splitPane.getWidth());
} else {
splitPane.getItems().remove(dirTree);
}
});
model.currentPathProperty().addListener((obs, old, val) -> { model.currentPathProperty().addListener((obs, old, val) -> {
if (!Files.isReadable(val)) { if (!Files.isReadable(val)) {
@ -141,25 +158,30 @@ public class FileManagerPage extends ShowcasePage {
breadcrumbs.setSelectedCrumb( breadcrumbs.setSelectedCrumb(
Breadcrumbs.buildTreeModel(getParentPath(val, 4).toArray(Path[]::new)) Breadcrumbs.buildTreeModel(getParentPath(val, 4).toArray(Path[]::new))
); );
directoryView.setDirectory(val); dirView.setDirectory(val);
}); });
setWindowTitle("File Manager", new FontIcon(Material2AL.FOLDER)); setWindowTitle("File Manager", new FontIcon(Material2AL.FOLDER));
setShowCaseContent(root); setAboutInfo("""
This is a simple file manager. You can navigate through \
the file system and open files using the default system application."""
);
setShowCaseContent(root, MAX_WIDTH, 600);
Containers.setAnchors(root, Insets.EMPTY); NodeUtils.setAnchors(root, Insets.EMPTY);
} }
private Breadcrumbs<Path> breadCrumbs() { private Breadcrumbs<Path> createBreadcrumbs() {
Callback<BreadCrumbItem<Path>, ButtonBase> crumbFactory = crumb -> { Callback<BreadCrumbItem<Path>, ButtonBase> crumbFactory = crumb -> {
var btn = new Button(crumb.getValue().getFileName().toString()); var hyperlink = new Hyperlink(crumb.getValue().getFileName().toString());
btn.getStyleClass().add(FLAT); hyperlink.setFocusTraversable(false);
btn.setFocusTraversable(false); return hyperlink;
return btn;
}; };
Callback<BreadCrumbItem<Path>, ? extends Node> dividerFactory = item -> Callback<BreadCrumbItem<Path>, ? extends Node> dividerFactory = item ->
item != null && !item.isLast() ? new Label("", new FontIcon(Material2AL.CHEVRON_RIGHT)) : null; item != null && !item.isLast()
? new Label("", new FontIcon(Material2AL.CHEVRON_RIGHT))
: null;
var breadcrumbs = new Breadcrumbs<Path>(); var breadcrumbs = new Breadcrumbs<Path>();
breadcrumbs.setAutoNavigationEnabled(false); breadcrumbs.setAutoNavigationEnabled(false);

@ -7,9 +7,12 @@ import java.nio.file.Paths;
import java.util.Objects; import java.util.Objects;
import javafx.beans.property.ReadOnlyObjectProperty; import javafx.beans.property.ReadOnlyObjectProperty;
import javafx.beans.property.ReadOnlyObjectWrapper; import javafx.beans.property.ReadOnlyObjectWrapper;
import javafx.css.PseudoClass;
final class Model { final class Model {
public static final PseudoClass HIDDEN = PseudoClass.getPseudoClass("hidden");
public static final PseudoClass FOLDER = PseudoClass.getPseudoClass("folder");
public static final Path USER_HOME = Paths.get(System.getProperty("user.home")); public static final Path USER_HOME = Paths.get(System.getProperty("user.home"));
private final ReadOnlyObjectWrapper<Path> currentPath = new ReadOnlyObjectWrapper<>(); private final ReadOnlyObjectWrapper<Path> currentPath = new ReadOnlyObjectWrapper<>();

@ -17,9 +17,11 @@ final class NavigationHistory {
private final IntegerProperty cursor = new SimpleIntegerProperty(0); private final IntegerProperty cursor = new SimpleIntegerProperty(0);
private final List<Path> history = new ArrayList<>(); private final List<Path> history = new ArrayList<>();
private final BooleanBinding canGoBack = Bindings.createBooleanBinding( private final BooleanBinding canGoBack = Bindings.createBooleanBinding(
() -> cursor.get() > 0 && history.size() > 1, cursor); () -> cursor.get() > 0 && history.size() > 1, cursor
);
private final BooleanBinding canGoForth = Bindings.createBooleanBinding( private final BooleanBinding canGoForth = Bindings.createBooleanBinding(
() -> cursor.get() < history.size() - 1, cursor); () -> cursor.get() < history.size() - 1, cursor
);
public void append(Path path) { public void append(Path path) {
if (path == null) { if (path == null) {

@ -5,6 +5,8 @@ package atlantafx.sampler.page.showcase.filemanager;
import static javafx.scene.input.KeyCombination.CONTROL_DOWN; import static javafx.scene.input.KeyCombination.CONTROL_DOWN;
import javafx.scene.control.ContextMenu; import javafx.scene.control.ContextMenu;
import javafx.scene.control.CustomMenuItem;
import javafx.scene.control.Label;
import javafx.scene.control.MenuItem; import javafx.scene.control.MenuItem;
import javafx.scene.control.SeparatorMenuItem; import javafx.scene.control.SeparatorMenuItem;
import javafx.scene.input.KeyCode; import javafx.scene.input.KeyCode;
@ -18,31 +20,50 @@ final class RightClickMenu extends ContextMenu {
} }
private void createMenu() { private void createMenu() {
var open = new MenuItem("Open"); var openItem = new MenuItem("Open");
var cut = new MenuItem("Cut"); var cutItem = new MenuItem("Cut");
cut.setAccelerator(new KeyCodeCombination(KeyCode.X, CONTROL_DOWN)); cutItem.setAccelerator(new KeyCodeCombination(KeyCode.X, CONTROL_DOWN));
var copy = new MenuItem("Copy"); var copyItem = new MenuItem("Copy");
copy.setAccelerator(new KeyCodeCombination(KeyCode.C, CONTROL_DOWN)); copyItem.setAccelerator(new KeyCodeCombination(KeyCode.C, CONTROL_DOWN));
var rename = new MenuItem("Rename"); var renameItem = new MenuItem("Rename");
rename.setAccelerator(new KeyCodeCombination(KeyCode.F2)); renameItem.setAccelerator(new KeyCodeCombination(KeyCode.F2));
var compress = new MenuItem("Compress"); var compressItem = new MenuItem("Compress");
var properties = new MenuItem("Properties"); var propsItem = new MenuItem("Properties");
getItems().setAll( getItems().setAll(
open, new DemoMenuItem(),
new SeparatorMenuItem(), new SeparatorMenuItem(),
cut, openItem,
copy,
rename,
new SeparatorMenuItem(), new SeparatorMenuItem(),
compress, cutItem,
copyItem,
renameItem,
new SeparatorMenuItem(), new SeparatorMenuItem(),
properties compressItem,
new SeparatorMenuItem(),
propsItem
); );
} }
///////////////////////////////////////////////////////////////////////////
private static class DemoMenuItem extends CustomMenuItem {
public DemoMenuItem() {
super();
var label = new Label("This is a demo menu. None of \nthe options below are working.");
label.setWrapText(true);
label.setStyle("-fx-text-fill:-color-fg-muted");
setContent(label);
setHideOnClick(false);
getStyleClass().add("demo-menu-item");
}
}
} }

@ -11,8 +11,8 @@ import static javafx.scene.control.TableColumn.SortType.ASCENDING;
import atlantafx.base.theme.Styles; import atlantafx.base.theme.Styles;
import atlantafx.base.theme.Tweaks; import atlantafx.base.theme.Tweaks;
import atlantafx.sampler.util.Containers;
import atlantafx.sampler.util.HumanReadableFormat; import atlantafx.sampler.util.HumanReadableFormat;
import atlantafx.sampler.util.NodeUtils;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
import java.nio.file.attribute.FileTime; import java.nio.file.attribute.FileTime;
@ -23,7 +23,6 @@ import java.util.stream.Stream;
import javafx.beans.property.SimpleLongProperty; import javafx.beans.property.SimpleLongProperty;
import javafx.beans.property.SimpleObjectProperty; import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.property.SimpleStringProperty; import javafx.beans.property.SimpleStringProperty;
import javafx.css.PseudoClass;
import javafx.geometry.Insets; import javafx.geometry.Insets;
import javafx.scene.control.TableCell; import javafx.scene.control.TableCell;
import javafx.scene.control.TableColumn; import javafx.scene.control.TableColumn;
@ -35,8 +34,6 @@ import javafx.scene.layout.Pane;
final class TableDirectoryView extends AnchorPane implements DirectoryView { final class TableDirectoryView extends AnchorPane implements DirectoryView {
private static final PseudoClass HIDDEN = PseudoClass.getPseudoClass("hidden");
private static final PseudoClass FOLDER = PseudoClass.getPseudoClass("folder");
private static final FileIconRepository REPO = new FileIconRepository(); private static final FileIconRepository REPO = new FileIconRepository();
private static final String UNKNOWN = "unknown"; private static final String UNKNOWN = "unknown";
@ -49,7 +46,7 @@ final class TableDirectoryView extends AnchorPane implements DirectoryView {
getChildren().setAll(table); getChildren().setAll(table);
getStyleClass().addAll("table-directory-view"); getStyleClass().addAll("table-directory-view");
Containers.setAnchors(table, Insets.EMPTY); NodeUtils.setAnchors(table, Insets.EMPTY);
} }
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
@ -152,8 +149,8 @@ final class TableDirectoryView extends AnchorPane implements DirectoryView {
imageView.setImage(FileIconRepository.FOLDER); imageView.setImage(FileIconRepository.FOLDER);
} }
pseudoClassStateChanged(FOLDER, isDirectory); pseudoClassStateChanged(Model.FOLDER, isDirectory);
getTableRow().pseudoClassStateChanged(HIDDEN, isFileHidden(path)); getTableRow().pseudoClassStateChanged(Model.HIDDEN, isFileHidden(path));
setGraphic(imageView); setGraphic(imageView);
setText(filename); setText(filename);
@ -195,10 +192,12 @@ final class TableDirectoryView extends AnchorPane implements DirectoryView {
if (empty) { if (empty) {
setText(null); setText(null);
} else { } else {
setText(fileTime != null if (fileTime == null) {
? HumanReadableFormat.date(fileTime.toInstant().atZone(ZoneId.systemDefault()).toLocalDateTime()) setText(UNKNOWN);
: UNKNOWN return;
); }
var instant = fileTime.toInstant().atZone(ZoneId.systemDefault());
setText(HumanReadableFormat.date(instant.toLocalDateTime()));
} }
} }
} }

@ -1,21 +1,18 @@
/* SPDX-License-Identifier: MIT */ /* SPDX-License-Identifier: MIT */
.file-manager-showcase .bookmark-list .ikonli-font-icon { #file-manager-showcase .table-directory-view .table-view {
-fx-icon-size: 18px;
}
.file-manager-showcase .breadcrumbs >.divider {
-fx-padding: 0;
}
.file-manager-showcase .table-directory-view .table-view {
-color-header-bg: -color-bg-default; -color-header-bg: -color-bg-default;
-color-cell-bg-selected: -color-accent-emphasis;
-color-cell-fg-selected: -color-fg-emphasis;
} }
/* setting opacity directly to the .table-cell or .table-row-cell /* setting opacity directly to the .table-cell or .table-row-cell
leads to incorrect table row height calculation #javafx-bug */ leads to incorrect table row height calculation #javafx-bug */
.file-manager-showcase .table-row-cell:hidden >.table-cell > * { #file-manager-showcase .table-row-cell:hidden >.table-cell > *,
-fx-opacity: 0.6; #file-manager-showcase .tree-cell:hidden > * {
-fx-opacity: 0.75;
}
.demo-menu-item:hover,
.demo-menu-item:focused,
.demo-menu-item:pressed {
-fx-background-color: transparent;
} }

@ -34,8 +34,9 @@ final class ColorThief {
public static int[] getColor(BufferedImage source) { public static int[] getColor(BufferedImage source) {
int[][] palette = getPalette(source, 5); int[][] palette = getPalette(source, 5);
if (palette == null) if (palette == null) {
return null; return null;
}
return palette[0]; return palette[0];
} }
@ -43,8 +44,9 @@ final class ColorThief {
public static int[][] getPalette(BufferedImage source, int colorCount) { public static int[][] getPalette(BufferedImage source, int colorCount) {
MMCQ.ColorMap colorMap = getColorMap(source, colorCount); MMCQ.ColorMap colorMap = getColorMap(source, colorCount);
if (colorMap == null) if (colorMap == null) {
return null; return null;
}
return colorMap.palette(); return colorMap.palette();
} }
@ -55,10 +57,12 @@ final class ColorThief {
public static MMCQ.ColorMap getColorMap(BufferedImage sourceImage, int colorCount, int quality, public static MMCQ.ColorMap getColorMap(BufferedImage sourceImage, int colorCount, int quality,
boolean ignoreWhite) { boolean ignoreWhite) {
if (colorCount < 2 || colorCount > 256) if (colorCount < 2 || colorCount > 256) {
throw new IllegalArgumentException("Specified colorCount must be between 2 and 256."); throw new IllegalArgumentException("Specified colorCount must be between 2 and 256.");
if (quality < 1) }
if (quality < 1) {
throw new IllegalArgumentException("Specified quality should be greater then 0."); throw new IllegalArgumentException("Specified quality should be greater then 0.");
}
int[][] pixelArray = switch (sourceImage.getType()) { int[][] pixelArray = switch (sourceImage.getType()) {
case TYPE_3BYTE_BGR, TYPE_4BYTE_ABGR -> getPixelsFast(sourceImage, quality, ignoreWhite); case TYPE_3BYTE_BGR, TYPE_4BYTE_ABGR -> getPixelsFast(sourceImage, quality, ignoreWhite);
@ -202,7 +206,9 @@ final class ColorThief {
@Override @Override
public String toString() { public String toString() {
return "r1: " + r1 + " / r2: " + r2 + " / g1: " + g1 + " / g2: " + g2 + " / b1: " + b1 + " / b2: " + b2; return "r1: " + r1 + " / r2: " + r2
+ " / g1: " + g1 + " / g2: " + g2
+ " / b1: " + b1 + " / b2: " + b2;
} }
public int volume(boolean force) { public int volume(boolean force) {
@ -260,10 +266,15 @@ final class ColorThief {
} }
if (ntot > 0) { if (ntot > 0) {
gAvg = new int[]{(rsum / ntot), (gsum / ntot), (bsum / ntot)}; gAvg = new int[] {
(rsum / ntot), (gsum / ntot), (bsum / ntot)
};
} else { } else {
gAvg = new int[]{(MULT * (r1 + r2 + 1) / 2), (MULT * (g1 + g2 + 1) / 2), gAvg = new int[] {
(MULT * (b1 + b2 + 1) / 2)}; (MULT * (r1 + r2 + 1) / 2),
(MULT * (g1 + g2 + 1) / 2),
(MULT * (b1 + b2 + 1) / 2)
};
} }
} }

@ -26,6 +26,7 @@ record MediaFile(File file) {
// is costly and that instance is not even reusable. // is costly and that instance is not even reusable.
public void readMetadata(Consumer<Metadata> callback) { public void readMetadata(Consumer<Metadata> callback) {
var media = new Media(file.toURI().toString()); var media = new Media(file.toURI().toString());
System.out.println(file.toURI().toString());
var mediaPlayer = new MediaPlayer(media); var mediaPlayer = new MediaPlayer(media);
// The media information is obtained asynchronously and so not necessarily // The media information is obtained asynchronously and so not necessarily

@ -2,6 +2,10 @@
package atlantafx.sampler.page.showcase.musicplayer; package atlantafx.sampler.page.showcase.musicplayer;
import atlantafx.sampler.Resources;
import java.io.File;
import java.nio.file.Paths;
import java.util.List;
import java.util.Objects; import java.util.Objects;
import javafx.beans.binding.Bindings; import javafx.beans.binding.Bindings;
import javafx.beans.property.ReadOnlyBooleanProperty; import javafx.beans.property.ReadOnlyBooleanProperty;
@ -14,6 +18,11 @@ import javafx.scene.paint.Color;
final class Model { final class Model {
private static final List<File> DEMO_FILES = List.of(
Paths.get(Resources.getResource("media/Beat Thee.mp3")).toFile(),
Paths.get(Resources.getResource("media/Study and Relax.mp3")).toFile()
);
private final ObservableList<MediaFile> playlist = FXCollections.observableArrayList(); private final ObservableList<MediaFile> playlist = FXCollections.observableArrayList();
private final ReadOnlyBooleanWrapper canGoBack = new ReadOnlyBooleanWrapper(); private final ReadOnlyBooleanWrapper canGoBack = new ReadOnlyBooleanWrapper();
private final ReadOnlyBooleanWrapper canGoForward = new ReadOnlyBooleanWrapper(); private final ReadOnlyBooleanWrapper canGoForward = new ReadOnlyBooleanWrapper();
@ -104,4 +113,9 @@ final class Model {
reset(); reset();
playlist().clear(); playlist().clear();
} }
public void playDemo() {
DEMO_FILES.forEach(f -> addFile(new MediaFile(f)));
play(new MediaFile(DEMO_FILES.get(0)));
}
} }

@ -14,8 +14,8 @@ public class MusicPlayerPage extends ShowcasePage {
public static final String NAME = "Music Player"; public static final String NAME = "Music Player";
public static final Set<String> SUPPORTED_MEDIA_TYPES = Set.of("mp3"); public static final Set<String> SUPPORTED_MEDIA_TYPES = Set.of("mp3");
private static final String STYLESHEET_URL = Objects.requireNonNull( private static final String STYLESHEET_URL =
MusicPlayerPage.class.getResource("music-player.css")).toExternalForm(); Objects.requireNonNull(MusicPlayerPage.class.getResource("music-player.css")).toExternalForm();
@Override @Override
public String getName() { public String getName() {
@ -34,10 +34,9 @@ public class MusicPlayerPage extends ShowcasePage {
var playerScreen = new PlayerScreen(model); var playerScreen = new PlayerScreen(model);
var root = new BorderPane(); var root = new BorderPane();
root.setId("music-player-showcase");
root.setCenter(startScreen); root.setCenter(startScreen);
root.getStylesheets().add(STYLESHEET_URL); root.getStylesheets().add(STYLESHEET_URL);
root.getStyleClass().add("music-player-showcase");
model.playlist().addListener((ListChangeListener<MediaFile>) c -> { model.playlist().addListener((ListChangeListener<MediaFile>) c -> {
if (model.playlist().size() > 0) { if (model.playlist().size() > 0) {
@ -48,6 +47,9 @@ public class MusicPlayerPage extends ShowcasePage {
}); });
setWindowTitle("Music Player", new FontIcon(Feather.MUSIC)); setWindowTitle("Music Player", new FontIcon(Feather.MUSIC));
setAboutInfo("""
Simple music player that able to play MP3 files. Inspired by ©Amberol."""
);
setShowCaseContent(root); setShowCaseContent(root);
} }

@ -184,9 +184,22 @@ final class PlayerPane extends VBox {
setAlignment(CENTER); setAlignment(CENTER);
setSpacing(5); setSpacing(5);
setMinWidth(300); setMinWidth(300);
getChildren().setAll(new Spacer(VERTICAL), new StackPane(coverImage), new Spacer(10, VERTICAL), trackTitle, getChildren().setAll(
trackArtist, trackAlbum, new Spacer(20, VERTICAL), mediaControls, new Spacer(10, VERTICAL), timeSlider, new Spacer(VERTICAL),
timeMarkersBox, new Spacer(10, VERTICAL), extraControls, new Spacer(VERTICAL)); new StackPane(coverImage),
new Spacer(10, VERTICAL),
trackTitle,
trackArtist,
trackAlbum,
new Spacer(20, VERTICAL),
mediaControls,
new Spacer(10, VERTICAL),
timeSlider,
timeMarkersBox,
new Spacer(10, VERTICAL),
extraControls,
new Spacer(VERTICAL)
);
} }
private void init() { private void init() {

@ -31,20 +31,29 @@ final class PlayerScreen extends SplitPane {
var domColor = Objects.equals(Color.TRANSPARENT, val) var domColor = Objects.equals(Color.TRANSPARENT, val)
? Color.TRANSPARENT ? Color.TRANSPARENT
: Color.color(val.getRed(), val.getGreen(), val.getBlue(), 1); : Color.color(val.getRed(), val.getGreen(), val.getBlue(), 1);
var domColor10 = Objects.equals(Color.TRANSPARENT, val)
? Color.TRANSPARENT
: Color.color(val.getRed(), val.getGreen(), val.getBlue(), 0.1);
var domColor20 = Objects.equals(Color.TRANSPARENT, val) var domColor20 = Objects.equals(Color.TRANSPARENT, val)
? Color.TRANSPARENT ? Color.TRANSPARENT
: Color.color(val.getRed(), val.getGreen(), val.getBlue(), 0.2); : Color.color(val.getRed(), val.getGreen(), val.getBlue(), 0.2);
var domColor50 = Objects.equals(Color.TRANSPARENT, val) var domColor50 = Objects.equals(Color.TRANSPARENT, val)
? Color.TRANSPARENT ? Color.TRANSPARENT
: Color.color(val.getRed(), val.getGreen(), val.getBlue(), 0.5); : Color.color(val.getRed(), val.getGreen(), val.getBlue(), 0.5);
var domColor70 = Objects.equals(Color.TRANSPARENT, val) var domColor70 = Objects.equals(Color.TRANSPARENT, val)
? Color.TRANSPARENT ? Color.TRANSPARENT
: Color.color(val.getRed(), val.getGreen(), val.getBlue(), 0.7); : Color.color(val.getRed(), val.getGreen(), val.getBlue(), 0.7);
setStyle("-color-dominant:" + toHexWithAlpha(domColor) + ";" setStyle("-color-dominant:" + toHexWithAlpha(domColor) + ";"
+ "-color-dominant-10:" + toHexWithAlpha(domColor10) + ";"
+ "-color-dominant-20:" + toHexWithAlpha(domColor20) + ";" + "-color-dominant-20:" + toHexWithAlpha(domColor20) + ";"
+ "-color-dominant-50:" + toHexWithAlpha(domColor50) + ";" + "-color-dominant-50:" + toHexWithAlpha(domColor50) + ";"
+ "-color-dominant-70:" + toHexWithAlpha(domColor70) + ";" + "-color-dominant-70:" + toHexWithAlpha(domColor70) + ";"
+ "-color-dominant-border:" + toHexWithAlpha(domColor70) + ";" + "-color-dominant-border:" + toHexWithAlpha(domColor50) + ";"
); );
}); });
} }

@ -37,8 +37,6 @@ final class StartScreen extends BorderPane {
} }
private void createView() { private void createView() {
var jumboIcon = new FontIcon(Feather.MUSIC);
var noteText = new TextFlow(new Text( var noteText = new TextFlow(new Text(
"Select a file or a folder." "Select a file or a folder."
)); ));
@ -54,10 +52,17 @@ final class StartScreen extends BorderPane {
addFileBtn.setPrefWidth(150); addFileBtn.setPrefWidth(150);
addFileBtn.setOnAction(e -> addFile()); addFileBtn.setOnAction(e -> addFile());
var controls = new VBox(10, addFolderBtn, addFileBtn); var addDemoBtn = new Button("Play Demo");
addDemoBtn.getStyleClass().add(Styles.SUCCESS);
addDemoBtn.setPrefWidth(150);
addDemoBtn.setOnAction(e -> model.playDemo());
var controls = new VBox(10, addFolderBtn, addFileBtn, addDemoBtn);
controls.setAlignment(Pos.CENTER); controls.setAlignment(Pos.CENTER);
controls.setFillWidth(true); controls.setFillWidth(true);
var jumboIcon = new FontIcon(Feather.MUSIC);
var content = new VBox(30, jumboIcon, noteText, controls); var content = new VBox(30, jumboIcon, noteText, controls);
content.getStyleClass().add("content"); content.getStyleClass().add("content");
content.setAlignment(Pos.CENTER); content.setAlignment(Pos.CENTER);

@ -1,50 +1,76 @@
/** SPDX-License-Identifier: MIT */ /** SPDX-License-Identifier: MIT */
.music-player-showcase > .start-screen { #music-player-showcase > .start-screen > .content > .ikonli-font-icon {
-fx-border-color: -color-border-muted;
-fx-border-width: 1;
}
.music-player-showcase > .start-screen > .content > .ikonli-font-icon {
-fx-icon-color: -color-fg-muted; -fx-icon-color: -color-fg-muted;
-fx-fill: -color-fg-muted; -fx-fill: -color-fg-muted;
-fx-icon-size: 64px; -fx-icon-size: 64px;
} }
.music-player-showcase > .player-screen { #music-player-showcase > .player-screen {
-fx-border-color: -color-border-muted; /* default colors (when no artwork found) */
-fx-border-width: 1;
-color-dominant: transparent; -color-dominant: transparent;
-color-dominant-10: transparent;
-color-dominant-20: transparent; -color-dominant-20: transparent;
-color-dominant-50: transparent; -color-dominant-50: transparent;
-color-dominant-70: transparent; -color-dominant-70: transparent;
-color-dominant-border: -color-border-muted; -color-dominant-border: -color-border-default;
-fx-background-color: radial-gradient(radius 100%, -color-dominant-20, -color-dominant-50);
} }
.music-player-showcase > .player-screen > * > .player {
-fx-background-color: radial-gradient(radius 100%, -color-dominant-50, -color-dominant-20); #music-player-showcase > .player-screen > .split-pane-divider {
}
.music-player-showcase > .player-screen > * > .player > .media-controls > .play > .ikonli-font-icon {
-fx-icon-size: 32px;
}
.music-player-showcase > .player-screen > .split-pane-divider {
-color-split-divider: -color-dominant-border; -color-split-divider: -color-dominant-border;
-color-split-grabber: -color-dominant-border; -color-split-grabber: -color-dominant-border;
} }
.music-player-showcase > .player-screen > * > .playlist { /* == PLAYER == */
-fx-background-color: radial-gradient(radius 100%, -color-dominant-20, -color-dominant-50);
#music-player-showcase .player > .media-controls > .play > .ikonli-font-icon {
-fx-icon-size: 32px;
} }
.music-player-showcase > .player-screen > * > .playlist > .controls {
#music-player-showcase .player .button {
-color-button-bg: -color-dominant-10;
-color-button-bg-hover: -color-dominant-20;
-color-button-bg-focused: -color-dominant-10;
-color-button-bg-pressed: -color-dominant-10;
-color-button-border: transparent;
-color-button-border-hover: transparent;
-color-button-border-focused: transparent;
-color-button-border-pressed: transparent;
}
#music-player-showcase .player .slider {
-color-slider-track: -color-dominant-50;
}
/* == PLAYLIST == */
#music-player-showcase .playlist > .controls {
-fx-border-color: -color-dominant-border; -fx-border-color: -color-dominant-border;
-fx-border-width: 0 0 0.75px 0; -fx-border-width: 0 0 0.75px 0;
} }
.music-player-showcase > .player-screen > * > .playlist > .controls > .button {
-color-button-bg-hover: transparent; #music-player-showcase .playlist > .controls > .button {
-color-button-bg-hover: -color-dominant-20;
-color-button-bg-focused: transparent; -color-button-bg-focused: transparent;
-color-button-bg-pressed: transparent; -color-button-bg-pressed: transparent;
-color-button-border: transparent;
-color-button-border-hover: transparent;
-color-button-border-focused: transparent;
-color-button-border-pressed: transparent;
} }
.music-player-showcase > .player-screen > * > .playlist > .list-view .list-cell {
#music-player-showcase .playlist .list-cell {
-color-cell-bg: transparent; -color-cell-bg: transparent;
-color-cell-bg-selected: transparent; -color-cell-bg-selected: transparent;
-color-cell-bg-selected-focused: transparent;
-color-cell-border: transparent; -color-cell-border: transparent;
-fx-cell-size: 4em; -fx-cell-size: 4em;
} }
#music-player-showcase .playlist .list-cell:filled:hover {
-color-cell-bg: -color-dominant-20;
-color-cell-bg-selected: -color-dominant-20;
-color-cell-bg-selected-focused: -color-dominant-20;
}

@ -1,65 +0,0 @@
/* SPDX-License-Identifier: MIT */
package atlantafx.sampler.util;
import static javafx.scene.layout.Region.USE_COMPUTED_SIZE;
import static javafx.scene.layout.Region.USE_PREF_SIZE;
import javafx.geometry.Insets;
import javafx.scene.Node;
import javafx.scene.control.ScrollPane;
import javafx.scene.layout.AnchorPane;
import javafx.scene.layout.ColumnConstraints;
import javafx.scene.layout.Priority;
import javafx.scene.layout.Region;
@Deprecated
public final class Containers {
public static final ColumnConstraints H_GROW_NEVER = columnConstraints(Priority.NEVER);
public static void setAnchors(Node node, Insets insets) {
if (insets.getTop() >= 0) {
AnchorPane.setTopAnchor(node, insets.getTop());
}
if (insets.getRight() >= 0) {
AnchorPane.setRightAnchor(node, insets.getRight());
}
if (insets.getBottom() >= 0) {
AnchorPane.setBottomAnchor(node, insets.getBottom());
}
if (insets.getLeft() >= 0) {
AnchorPane.setLeftAnchor(node, insets.getLeft());
}
}
public static void setScrollConstraints(ScrollPane scrollPane,
ScrollPane.ScrollBarPolicy vbarPolicy, boolean fitHeight,
ScrollPane.ScrollBarPolicy hbarPolicy, boolean fitWidth) {
scrollPane.setVbarPolicy(vbarPolicy);
scrollPane.setFitToHeight(fitHeight);
scrollPane.setHbarPolicy(hbarPolicy);
scrollPane.setFitToWidth(fitWidth);
}
public static ColumnConstraints columnConstraints(Priority hgrow) {
return columnConstraints(USE_COMPUTED_SIZE, hgrow);
}
public static ColumnConstraints columnConstraints(double minWidth, Priority hgrow) {
double maxWidth = hgrow == Priority.ALWAYS ? Double.MAX_VALUE : USE_PREF_SIZE;
ColumnConstraints constraints = new ColumnConstraints(minWidth, USE_COMPUTED_SIZE, maxWidth);
constraints.setHgrow(hgrow);
return constraints;
}
public static void usePrefWidth(Region region) {
region.setMinWidth(USE_PREF_SIZE);
region.setMaxWidth(USE_PREF_SIZE);
}
public static void usePrefHeight(Region region) {
region.setMinHeight(USE_PREF_SIZE);
region.setMaxHeight(USE_PREF_SIZE);
}
}

@ -1,27 +0,0 @@
/* SPDX-License-Identifier: MIT */
package atlantafx.sampler.util;
import static atlantafx.base.theme.Styles.BUTTON_ICON;
import javafx.scene.control.Button;
import org.kordamp.ikonli.Ikon;
import org.kordamp.ikonli.javafx.FontIcon;
@Deprecated
public final class Controls {
public static Button iconButton(Ikon icon, boolean disable) {
return button("", icon, disable, BUTTON_ICON);
}
public static Button button(String text, Ikon icon, boolean disable, String... styleClasses) {
var button = new Button(text);
if (icon != null) {
button.setGraphic(new FontIcon(icon));
}
button.setDisable(disable);
button.getStyleClass().addAll(styleClasses);
return button;
}
}

@ -3,10 +3,13 @@
package atlantafx.sampler.util; package atlantafx.sampler.util;
import java.util.List; import java.util.List;
import javafx.geometry.Insets;
import javafx.scene.Node; import javafx.scene.Node;
import javafx.scene.Parent; import javafx.scene.Parent;
import javafx.scene.control.ScrollPane;
import javafx.scene.input.MouseButton; import javafx.scene.input.MouseButton;
import javafx.scene.input.MouseEvent; import javafx.scene.input.MouseEvent;
import javafx.scene.layout.AnchorPane;
public final class NodeUtils { public final class NodeUtils {
@ -15,6 +18,30 @@ public final class NodeUtils {
node.setManaged(on); node.setManaged(on);
} }
public static void setAnchors(Node node, Insets insets) {
if (insets.getTop() >= 0) {
AnchorPane.setTopAnchor(node, insets.getTop());
}
if (insets.getRight() >= 0) {
AnchorPane.setRightAnchor(node, insets.getRight());
}
if (insets.getBottom() >= 0) {
AnchorPane.setBottomAnchor(node, insets.getBottom());
}
if (insets.getLeft() >= 0) {
AnchorPane.setLeftAnchor(node, insets.getLeft());
}
}
public static void setScrollConstraints(ScrollPane scrollPane,
ScrollPane.ScrollBarPolicy vbarPolicy, boolean fitHeight,
ScrollPane.ScrollBarPolicy hbarPolicy, boolean fitWidth) {
scrollPane.setVbarPolicy(vbarPolicy);
scrollPane.setFitToHeight(fitHeight);
scrollPane.setHbarPolicy(hbarPolicy);
scrollPane.setFitToWidth(fitWidth);
}
public static boolean isDoubleClick(MouseEvent e) { public static boolean isDoubleClick(MouseEvent e) {
return e.getButton().equals(MouseButton.PRIMARY) && e.getClickCount() == 2; return e.getButton().equals(MouseButton.PRIMARY) && e.getClickCount() == 2;
} }

@ -1,6 +1,7 @@
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
.showcase-page { .showcase-page {
-fx-background-color: -color-bg-default;
>.window { >.window {
-fx-border-color: -color-accent-emphasis; -fx-border-color: -color-accent-emphasis;
@ -44,7 +45,7 @@
} }
} }
#overview { #blueprints {
-fx-background-color: -color-bg-inset; -fx-background-color: -color-bg-inset;
.sample { .sample {

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB