diff --git a/base/src/main/java/atlantafx/base/controls/Breadcrumbs.java b/base/src/main/java/atlantafx/base/controls/Breadcrumbs.java
index 9be791f..8047775 100755
--- a/base/src/main/java/atlantafx/base/controls/Breadcrumbs.java
+++ b/base/src/main/java/atlantafx/base/controls/Breadcrumbs.java
@@ -28,18 +28,13 @@
*/
package atlantafx.base.controls;
-import javafx.beans.InvalidationListener;
import javafx.beans.property.*;
import javafx.event.Event;
import javafx.event.EventHandler;
import javafx.event.EventType;
import javafx.scene.Node;
-import javafx.scene.control.Button;
-import javafx.scene.control.Control;
-import javafx.scene.control.Skin;
-import javafx.scene.control.TreeItem;
-import javafx.scene.paint.Color;
-import javafx.scene.shape.*;
+import javafx.scene.control.*;
+import javafx.scene.layout.Region;
import javafx.util.Callback;
import java.util.UUID;
@@ -47,35 +42,48 @@ import java.util.UUID;
/**
* Represents a bread crumb bar. This control is useful to visualize and navigate
* a hierarchical path structure, such as file systems.
+ *
+ * A breadcrumbs consist of two types of elements: a button (default is a hyperlink)
+ * and a divider (default is for Label). You can customize both by providing the
+ * corresponding control factory.
*/
@SuppressWarnings("unused")
public class Breadcrumbs extends Control {
- private static final String STYLE_CLASS_FIRST = "first";
+ protected static final String DEFAULT_STYLE_CLASS = "breadcrumbs";
- /** Represents an Event which is fired when a bread crumb was activated. */
- public static class BreadCrumbActionEvent extends Event {
+ protected final Callback, ButtonBase> defaultCrumbNodeFactory =
+ item -> new Hyperlink(item.getStringValue());
+ protected final Callback, ? extends Node> defaultDividerFactory =
+ item -> item != null && !item.isLast() ? new Label("/") : null;
- /**
- * The event type that should be listened to by people interested in
- * knowing when the {@link Breadcrumbs#selectedCrumbProperty() selected crumb}
- * has changed.
- */
- public static final EventType> CRUMB_ACTION
- = new EventType<>("CRUMB_ACTION" + UUID.randomUUID());
+ /** Creates an empty bread crumb bar. */
+ public Breadcrumbs() {
+ this(null);
+ }
- private final TreeItem selectedCrumb;
+ /**
+ * Creates a bread crumb bar with the given BreadCrumbItem as the currently
+ * selected crumb.
+ */
+ public Breadcrumbs(BreadCrumbItem selectedCrumb) {
+ getStyleClass().add(DEFAULT_STYLE_CLASS);
- /** Creates a new event that can subsequently be fired. */
- public BreadCrumbActionEvent(TreeItem selectedCrumb) {
- super(CRUMB_ACTION);
- this.selectedCrumb = selectedCrumb;
- }
+ // breadcrumbs should be the size of its content
+ setPrefWidth(Region.USE_COMPUTED_SIZE);
+ setMaxWidth(Region.USE_PREF_SIZE);
+ setPrefHeight(Region.USE_COMPUTED_SIZE);
+ setMaxHeight(Region.USE_PREF_SIZE);
- /** Returns the crumb which was the action target. */
- public TreeItem getSelectedCrumb() {
- return selectedCrumb;
- }
+ setSelectedCrumb(selectedCrumb);
+ setCrumbFactory(defaultCrumbNodeFactory);
+ setDividerFactory(defaultDividerFactory);
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ protected Skin> createDefaultSkin() {
+ return new BreadcrumbsSkin<>(this);
}
/**
@@ -83,10 +91,10 @@ public class Breadcrumbs extends Control {
* as selectedCrumb node to be shown
*/
@SafeVarargs
- public static TreeItem buildTreeModel(T... crumbs) {
- TreeItem subRoot = null;
+ public static BreadCrumbItem buildTreeModel(T... crumbs) {
+ BreadCrumbItem subRoot = null;
for (T crumb : crumbs) {
- TreeItem currentNode = new TreeItem<>(crumb);
+ BreadCrumbItem currentNode = new BreadCrumbItem<>(crumb);
if (subRoot != null) {
subRoot.getChildren().add(currentNode);
}
@@ -95,24 +103,9 @@ public class Breadcrumbs extends Control {
return subRoot;
}
- /** Default crumb node factory. This factory is used when no custom factory is specified by the user. */
- private final Callback, Button> defaultCrumbNodeFactory =
- crumb -> new BreadCrumbButton(crumb.getValue() != null ? crumb.getValue().toString() : "");
-
- /** Creates an empty bread crumb bar. */
- public Breadcrumbs() {
- this(null);
- }
-
- /**
- * Creates a bread crumb bar with the given TreeItem as the currently
- * selected crumb.
- */
- public Breadcrumbs(TreeItem selectedCrumb) {
- getStyleClass().add(DEFAULT_STYLE_CLASS);
- setSelectedCrumb(selectedCrumb);
- setCrumbFactory(defaultCrumbNodeFactory);
- }
+ ///////////////////////////////////////////////////////////////////////////
+ // Properties //
+ ///////////////////////////////////////////////////////////////////////////
/**
* Represents the bottom-most path node (the node on the most-right side in
@@ -125,26 +118,25 @@ public class Breadcrumbs extends Control {
*
* To show the above bread crumb bar, you have to set the [file.txt] tree-node as selected crumb.
*/
- public final ObjectProperty> selectedCrumbProperty() {
+ public final ObjectProperty> selectedCrumbProperty() {
return selectedCrumb;
}
- private final ObjectProperty> selectedCrumb =
+ protected final ObjectProperty> selectedCrumb =
new SimpleObjectProperty<>(this, "selectedCrumb");
- /** Get the current target path. */
- public final TreeItem getSelectedCrumb() {
+ public final BreadCrumbItem getSelectedCrumb() {
return selectedCrumb.get();
}
- /** Select one node in the BreadCrumbBar for being the bottom-most path node. */
- public final void setSelectedCrumb(TreeItem selectedCrumb) {
+ public final void setSelectedCrumb(BreadCrumbItem selectedCrumb) {
this.selectedCrumb.set(selectedCrumb);
}
/**
- * Enable or disable auto navigation (default is enabled).
- * If auto navigation is enabled, it will automatically navigate to the crumb which was clicked by the user.
+ * Enables or disables auto navigation (default is enabled).
+ * If auto navigation is enabled, it will automatically navigate to the crumb which was
+ * clicked by the user.
*
* @return a {@link BooleanProperty}
*/
@@ -152,59 +144,77 @@ public class Breadcrumbs extends Control {
return autoNavigation;
}
- private final BooleanProperty autoNavigation =
+ protected final BooleanProperty autoNavigation =
new SimpleBooleanProperty(this, "autoNavigationEnabled", true);
- /**
- * Return whether auto-navigation is enabled.
- *
- * @return whether auto-navigation is enabled.
- */
public final boolean isAutoNavigationEnabled() {
return autoNavigation.get();
}
- /**
- * Enable or disable auto navigation (default is enabled).
- * If auto navigation is enabled, it will automatically navigate to the crumb which was clicked by the user.
- */
public final void setAutoNavigationEnabled(boolean enabled) {
autoNavigation.set(enabled);
}
/**
- * Return an ObjectProperty of the CrumbFactory.
- *
- * @return an ObjectProperty of the CrumbFactory.
+ * Crumb factory is used to create custom bread crumb instances.
+ * null
is not allowed and will result in a fallback to the default factory.
+ *
+ * BreadCrumbItem
specifies the tree item for creating bread crumb. Use
+ * {@link BreadCrumbItem#isFirst()} and {@link BreadCrumbItem#isLast()} to create bread crumb
+ * depending on item position.
+ *
+ * ButtonBase
stands for resulting bread crumb node. It CAN NOT be null
.
*/
- public final ObjectProperty, Button>> crumbFactoryProperty() {
+ public final ObjectProperty, ButtonBase>> crumbFactoryProperty() {
return crumbFactory;
}
- private final ObjectProperty, Button>> crumbFactory =
+ protected final ObjectProperty, ButtonBase>> crumbFactory =
new SimpleObjectProperty<>(this, "crumbFactory");
- /**
- * Sets the crumb factory to create (custom) {@link BreadCrumbButton} instances.
- * null
is not allowed and will result in a fallback to the default factory.
- */
- public final void setCrumbFactory(Callback, Button> value) {
+ public final void setCrumbFactory(Callback, ButtonBase> value) {
if (value == null) {
value = defaultCrumbNodeFactory;
}
crumbFactoryProperty().set(value);
}
- /**
- * Returns the cell factory that will be used to create {@link BreadCrumbButton}
- * instances
- */
- public final Callback, Button> getCrumbFactory() {
+ public final Callback, ButtonBase> getCrumbFactory() {
return crumbFactory.get();
}
/**
- * @return an ObjectProperty representing the crumbAction EventHandler being used.
+ * Divider factory is used to create custom divider instances.
+ * null
is not allowed and will result in a fallback to the default factory.
+ *
+ * BreadCrumbItem
specifies the preceding tree item. It can be null, because this way
+ * you can insert divider before the first bread crumb, which can be used e.g. for creating a Unix path.
+ * Use {@link BreadCrumbItem#isFirst()} and {@link BreadCrumbItem#isLast()} to create divider
+ * depending on item position.
+ *
+ * ? extends Node
stands for resulting divider node. It CAN be null
, which
+ * means there will be no divider inserted after the specified bread crumb.
+ */
+ public final ObjectProperty, ? extends Node>> dividerFactoryProperty() {
+ return dividerFactory;
+ }
+
+ protected final ObjectProperty, ? extends Node>> dividerFactory =
+ new SimpleObjectProperty<>(this, "dividerFactory");
+
+ public final void setDividerFactory(Callback, ? extends Node> value) {
+ if (value == null) {
+ value = defaultDividerFactory;
+ }
+ dividerFactoryProperty().set(value);
+ }
+
+ public final Callback, ? extends Node> getDividerFactory() {
+ return dividerFactory.get();
+ }
+
+ /**
+ * The EventHandler is called when a user selects a bread crumb.
*/
public final ObjectProperty>> onCrumbActionProperty() {
return onCrumbAction;
@@ -215,16 +225,11 @@ public class Breadcrumbs extends Control {
onCrumbActionProperty().set(value);
}
- /**
- * Return the EventHandler currently used when a user selects a crumb.
- *
- * @return the EventHandler currently used when a user selects a crumb.
- */
public final EventHandler> getOnCrumbAction() {
return onCrumbActionProperty().get();
}
- private final ObjectProperty>> onCrumbAction = new ObjectPropertyBase<>() {
+ protected final ObjectProperty>> onCrumbAction = new ObjectPropertyBase<>() {
@SuppressWarnings({ "unchecked", "rawtypes" })
@Override
@@ -243,117 +248,60 @@ public class Breadcrumbs extends Control {
}
};
- private static final String DEFAULT_STYLE_CLASS = "bread-crumb-bar";
+ ///////////////////////////////////////////////////////////////////////////
- /** {@inheritDoc} */
- @Override
- protected Skin> createDefaultSkin() {
- return new BreadcrumbsSkin<>(this);
+ public static class BreadCrumbItem extends TreeItem {
+
+ // setters must not be exposed to user
+ private boolean first;
+ private boolean last;
+
+ public BreadCrumbItem(T value) {
+ super(value);
+ }
+
+ public boolean isFirst() {
+ return first;
+ }
+
+ void setFirst(boolean first) {
+ this.first = first;
+ }
+
+ public boolean isLast() {
+ return last;
+ }
+
+ void setLast(boolean last) {
+ this.last = last;
+ }
+
+ String getStringValue() {
+ return getValue() != null ? getValue().toString() : "";
+ }
}
- @SuppressWarnings("FieldCanBeLocal")
- public static class BreadCrumbButton extends Button {
-
- private final ObjectProperty first = new SimpleObjectProperty<>(this, STYLE_CLASS_FIRST);
-
- private final double arrowWidth = 5;
- private final double arrowHeight = 20;
+ /** Represents an Event which is fired when a bread crumb was activated. */
+ public static class BreadCrumbActionEvent extends Event {
/**
- * Create a BreadCrumbButton
- *
- * @param text Buttons text
+ * The event type that should be listened to by people interested in
+ * knowing when the {@link Breadcrumbs#selectedCrumbProperty() selected crumb}
+ * has changed.
*/
- public BreadCrumbButton(String text) {
- this(text, null);
+ public static final EventType> CRUMB_ACTION
+ = new EventType<>("CRUMB_ACTION" + UUID.randomUUID());
+
+ private final BreadCrumbItem selectedCrumb;
+
+ /** Creates a new event that can subsequently be fired. */
+ public BreadCrumbActionEvent(BreadCrumbItem selectedCrumb) {
+ super(CRUMB_ACTION);
+ this.selectedCrumb = selectedCrumb;
}
- /**
- * Create a BreadCrumbButton
- *
- * @param text Buttons text
- * @param gfx Gfx of the Button
- */
- public BreadCrumbButton(String text, Node gfx) {
- super(text, gfx);
- first.set(false);
-
- getStyleClass().addListener((InvalidationListener) obs -> updateShape());
-
- updateShape();
- }
-
- private void updateShape() {
- this.setShape(createButtonShape());
- }
-
- /** Returns the crumb arrow width. */
- public double getArrowWidth() {
- return arrowWidth;
- }
-
- /** Creates an arrow path. */
- private Path createButtonShape() {
- // build the following shape (or home without left arrow)
-
- // --------
- // \ \
- // / /
- // --------
- Path path = new Path();
-
- // begin in the upper left corner
- MoveTo e1 = new MoveTo(0, 0);
- path.getElements().add(e1);
-
- // draw a horizontal line that defines the width of the shape
- HLineTo e2 = new HLineTo();
- // bind the width of the shape to the width of the button
- e2.xProperty().bind(this.widthProperty().subtract(arrowWidth));
- path.getElements().add(e2);
-
- // draw upper part of right arrow
- LineTo e3 = new LineTo();
- // the x endpoint of this line depends on the x property of line e2
- e3.xProperty().bind(e2.xProperty().add(arrowWidth));
- e3.setY(arrowHeight / 2.0);
- path.getElements().add(e3);
-
- // draw lower part of right arrow
- LineTo e4 = new LineTo();
- // the x endpoint of this line depends on the x property of line e2
- e4.xProperty().bind(e2.xProperty());
- e4.setY(arrowHeight);
- path.getElements().add(e4);
-
- // draw lower horizontal line
- HLineTo e5 = new HLineTo(0);
- path.getElements().add(e5);
-
- if (!getStyleClass().contains(STYLE_CLASS_FIRST)) {
- // draw lower part of left arrow
- // we simply can omit it for the first Button
- LineTo e6 = new LineTo(arrowWidth, arrowHeight / 2.0);
- path.getElements().add(e6);
- } else {
- // draw an arc for the first bread crumb
- ArcTo arcTo = new ArcTo();
- arcTo.setSweepFlag(true);
- arcTo.setX(0);
- arcTo.setY(0);
- arcTo.setRadiusX(15.0f);
- arcTo.setRadiusY(15.0f);
- path.getElements().add(arcTo);
- }
-
- // close path
- ClosePath e7 = new ClosePath();
- path.getElements().add(e7);
-
- // this is a dummy color to fill the shape, it won't be visible
- path.setFill(Color.BLACK);
-
- return path;
+ public BreadCrumbItem getSelectedCrumb() {
+ return selectedCrumb;
}
}
}
diff --git a/base/src/main/java/atlantafx/base/controls/BreadcrumbsSkin.java b/base/src/main/java/atlantafx/base/controls/BreadcrumbsSkin.java
index aed37ef..63c67e4 100755
--- a/base/src/main/java/atlantafx/base/controls/BreadcrumbsSkin.java
+++ b/base/src/main/java/atlantafx/base/controls/BreadcrumbsSkin.java
@@ -28,15 +28,14 @@
*/
package atlantafx.base.controls;
-import javafx.beans.value.ChangeListener;
+import atlantafx.base.controls.Breadcrumbs.BreadCrumbItem;
+import javafx.css.PseudoClass;
import javafx.event.Event;
import javafx.event.EventHandler;
import javafx.scene.Node;
-import javafx.scene.control.Button;
+import javafx.scene.control.ButtonBase;
import javafx.scene.control.SkinBase;
-import javafx.scene.control.TreeItem;
import javafx.scene.control.TreeItem.TreeModificationEvent;
-import javafx.util.Callback;
import java.util.ArrayList;
import java.util.Collections;
@@ -44,89 +43,90 @@ import java.util.List;
public class BreadcrumbsSkin extends SkinBase> {
- private static final String STYLE_CLASS_FIRST = "first";
- private static final String STYLE_CLASS_LAST = "last";
+ protected static final PseudoClass FIRST = PseudoClass.getPseudoClass("first");
+ protected static final PseudoClass LAST = PseudoClass.getPseudoClass("last");
+
+ protected final EventHandler> treeChildrenModifiedHandler = e -> updateBreadCrumbs();
public BreadcrumbsSkin(final Breadcrumbs control) {
super(control);
- control.selectedCrumbProperty().addListener(selectedPathChangeListener);
+
+ control.selectedCrumbProperty().addListener((obs, old, val) -> updateSelectedPath(old, val));
updateSelectedPath(getSkinnable().selectedCrumbProperty().get(), null);
}
- @SuppressWarnings("FieldCanBeLocal")
- private final ChangeListener> selectedPathChangeListener =
- (obs, oldItem, newItem) -> updateSelectedPath(newItem, oldItem);
+ @Override
+ protected void layoutChildren(double x, double y, double width, double height) {
+ double controlHeight = getSkinnable().getHeight();
+ double nodeX = x, nodeY;
- private void updateSelectedPath(TreeItem newTarget, TreeItem oldTarget) {
- if (oldTarget != null) {
- // remove old listener
- oldTarget.removeEventHandler(TreeItem.childrenModificationEvent(), treeChildrenModifiedHandler);
- }
- if (newTarget != null) {
- // add new listener
- newTarget.addEventHandler(TreeItem.childrenModificationEvent(), treeChildrenModifiedHandler);
- }
- updateBreadCrumbs();
- }
+ for (int i = 0; i < getChildren().size(); i++) {
+ Node node = getChildren().get(i);
- private final EventHandler> treeChildrenModifiedHandler =
- args -> updateBreadCrumbs();
+ double nodeWidth = snapSizeX(node.prefWidth(height));
+ double nodeHeight = snapSizeY(node.prefHeight(-1));
- private void updateBreadCrumbs() {
- final Breadcrumbs buttonBar = getSkinnable();
- final TreeItem pathTarget = buttonBar.getSelectedCrumb();
- final Callback, Button> factory = buttonBar.getCrumbFactory();
+ // center node within the breadcrumbs
+ nodeY = nodeHeight < controlHeight ? (controlHeight - nodeHeight) / 2 : y;
- getChildren().clear();
-
- if (pathTarget != null) {
- List> crumbs = constructFlatPath(pathTarget);
-
- for (int i = 0; i < crumbs.size(); i++) {
- Button crumb = createCrumb(factory, crumbs.get(i));
- crumb.setMnemonicParsing(false);
-
- boolean first = crumbs.size() > 1 && i == 0;
- boolean last = crumbs.size() > 1 && i == crumbs.size() - 1;
-
- if (first) {
- crumb.getStyleClass().remove(STYLE_CLASS_LAST);
- addStyleClass(crumb, STYLE_CLASS_FIRST);
- } else if (last) {
- crumb.getStyleClass().remove(STYLE_CLASS_FIRST);
- addStyleClass(crumb, STYLE_CLASS_LAST);
- } else {
- crumb.getStyleClass().removeAll(STYLE_CLASS_FIRST, STYLE_CLASS_LAST);
- }
-
- getChildren().add(crumb);
- }
- }
- }
-
- private void addStyleClass(Node node, String styleClass) {
- if (!node.getStyleClass().contains(styleClass)) {
- node.getStyleClass().add(styleClass);
+ node.resizeRelocate(nodeX, nodeY, nodeWidth, nodeHeight);
+ nodeX += nodeWidth;
}
}
@Override
- protected void layoutChildren(double x, double y, double w, double h) {
- for (int i = 0; i < getChildren().size(); i++) {
- Node n = getChildren().get(i);
+ protected double computeMinWidth(double height, double topInset, double rightInset,
+ double bottomInset, double leftInset) {
+ double width = 0;
+ for (Node node : getChildren()) {
+ if (!node.isManaged()) { continue; }
+ width += snapSizeX(node.prefWidth(height));
+ }
- double nw = snapSizeX(n.prefWidth(h));
- double nh = snapSizeY(n.prefHeight(-1));
+ return width + rightInset + leftInset;
+ }
- if (i > 0) {
- // we have to position the bread crumbs slightly overlapping
- double ins = n instanceof Breadcrumbs.BreadCrumbButton d ? d.getArrowWidth() : 0;
- x = snapPositionX(x - ins);
+ protected void updateSelectedPath(BreadCrumbItem old, BreadCrumbItem val) {
+ if (old != null) {
+ old.removeEventHandler(BreadCrumbItem.childrenModificationEvent(), treeChildrenModifiedHandler);
+ }
+ if (val != null) {
+ val.addEventHandler(BreadCrumbItem.childrenModificationEvent(), treeChildrenModifiedHandler);
+ }
+ updateBreadCrumbs();
+ }
+
+ protected void updateBreadCrumbs() {
+ getChildren().clear();
+ BreadCrumbItem selectedTreeItem = getSkinnable().getSelectedCrumb();
+ Node divider;
+ if (selectedTreeItem != null) {
+ // optionally insert divider before the first node
+ divider = createDivider(null);
+ if (divider != null) {
+ divider.pseudoClassStateChanged(FIRST, true);
+ divider.pseudoClassStateChanged(LAST, false);
+ getChildren().add(divider);
}
- n.resize(nw, nh);
- n.relocate(x, y);
- x += nw;
+ List> crumbs = constructFlatPath(selectedTreeItem);
+ for (BreadCrumbItem treeItem : crumbs) {
+ ButtonBase crumb = createCrumb(treeItem);
+ crumb.pseudoClassStateChanged(FIRST, treeItem.isFirst());
+ crumb.pseudoClassStateChanged(LAST, treeItem.isLast());
+ getChildren().add(crumb);
+
+ // for the sake of flexibility, it's user responsibility to decide
+ // whether insert divider after the last node or not
+ divider = createDivider(treeItem);
+ if (divider != null) {
+ if (treeItem.isLast()) {
+ divider.pseudoClassStateChanged(FIRST, false);
+ divider.pseudoClassStateChanged(LAST, true);
+ }
+ getChildren().add(divider);
+ }
+ }
}
}
@@ -135,39 +135,59 @@ public class BreadcrumbsSkin extends SkinBase> {
*
* @param bottomMost The crumb node at the end of the path
*/
- private List> constructFlatPath(TreeItem bottomMost) {
- List> path = new ArrayList<>();
+ protected List> constructFlatPath(BreadCrumbItem bottomMost) {
+ List> path = new ArrayList<>();
- TreeItem current = bottomMost;
+ BreadCrumbItem current = bottomMost;
do {
path.add(current);
- current = current.getParent();
+ current.setFirst(false);
+ current.setLast(false);
+ current = (BreadCrumbItem) current.getParent();
} while (current != null);
Collections.reverse(path);
+
+ // if the path consists of a single item it considered as first, but not last
+ if (path.size() > 0) { path.get(0).setFirst(true); }
+ if (path.size() > 1) { path.get(path.size() - 1).setLast(true); }
+
return path;
}
- private Button createCrumb(
- final Callback, Button> factory,
- final TreeItem selectedCrumb) {
+ protected ButtonBase createCrumb(BreadCrumbItem treeItem) {
+ ButtonBase crumb = getSkinnable().getCrumbFactory().call(treeItem);
+ crumb.setMnemonicParsing(false);
- Button crumb = factory.call(selectedCrumb);
-
- crumb.getStyleClass().add("crumb"); //$NON-NLS-1$
+ if (!crumb.getStyleClass().contains("crumb")) {
+ crumb.getStyleClass().add("crumb");
+ }
// listen to the action event of each bread crumb
- crumb.setOnAction(ae -> onBreadCrumbAction(selectedCrumb));
+ crumb.setOnAction(e -> onBreadCrumbAction(treeItem));
return crumb;
}
+ protected Node createDivider(BreadCrumbItem treeItem) {
+ Node divider = getSkinnable().getDividerFactory().call(treeItem);
+ if (divider == null) {
+ return null;
+ }
+
+ if (!divider.getStyleClass().contains("divider")) {
+ divider.getStyleClass().add("divider");
+ }
+
+ return divider;
+ }
+
/**
* Occurs when a bread crumb gets the action event
*
* @param crumbModel The crumb which received the action event
*/
- protected void onBreadCrumbAction(final TreeItem crumbModel) {
+ protected void onBreadCrumbAction(BreadCrumbItem crumbModel) {
final Breadcrumbs breadCrumbBar = getSkinnable();
// fire the composite event in the breadCrumbBar
diff --git a/sampler/src/main/java/atlantafx/sampler/page/components/BreadcrumbsPage.java b/sampler/src/main/java/atlantafx/sampler/page/components/BreadcrumbsPage.java
index 40cdc21..a9aee37 100644
--- a/sampler/src/main/java/atlantafx/sampler/page/components/BreadcrumbsPage.java
+++ b/sampler/src/main/java/atlantafx/sampler/page/components/BreadcrumbsPage.java
@@ -2,69 +2,83 @@
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.TreeItem;
+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.feather.Feather;
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,
- defaultSample(),
- customCrumbSample()
+ setUserContent(new VBox(
+ Page.PAGE_VGAP,
+ basicSample(),
+ customCrumbSample(),
+ customDividerSample()
));
}
- private SampleBlock defaultSample() {
- return new SampleBlock("Basic", createBreadcrumbs(null));
+ private SampleBlock basicSample() {
+ return new SampleBlock("Basic", createBreadcrumbs(null, null));
}
private SampleBlock customCrumbSample() {
- Callback, Button> crumbFactory = crumb -> {
- var btn = new Button(crumb.getValue());
+ Callback, ButtonBase> crumbFactory = crumb -> {
+ var btn = new Button(crumb.getValue(), new FontIcon(randomIcon()));
btn.getStyleClass().add(Styles.FLAT);
btn.setFocusTraversable(false);
- if (!crumb.getChildren().isEmpty()) {
- btn.setGraphic(new FontIcon(Feather.CHEVRON_RIGHT));
- }
return btn;
};
- return new SampleBlock("Flat", createBreadcrumbs(crumbFactory));
+ return new SampleBlock("Flat Button", createBreadcrumbs(crumbFactory, null));
}
- private HBox createBreadcrumbs(Callback, Button> crumbFactory) {
- int count = 5;
- TreeItem model = Breadcrumbs.buildTreeModel(
- generate(() -> FAKER.science().element(), count).toArray(String[]::new)
+ private SampleBlock customDividerSample() {
+ Callback, ? 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, ButtonBase> crumbFactory,
+ Callback, ? extends Node> dividerFactory) {
+ BreadCrumbItem model = Breadcrumbs.buildTreeModel(
+ generate(() -> FAKER.science().element(), CRUMB_COUNT).toArray(String[]::new)
);
var nextBtn = new Button("Next");
- nextBtn.getStyleClass().add(Styles.ACCENT);
+ nextBtn.getStyleClass().addAll(Styles.ACCENT);
var breadcrumbs = new Breadcrumbs<>(model);
- breadcrumbs.setSelectedCrumb(getAncestor(model, count / 2));
+ breadcrumbs.setSelectedCrumb(getAncestor(model, CRUMB_COUNT / 2));
if (crumbFactory != null) { breadcrumbs.setCrumbFactory(crumbFactory); }
+ if (dividerFactory != null) { breadcrumbs.setDividerFactory(dividerFactory); }
nextBtn.setOnAction(e -> {
- TreeItem selected = breadcrumbs.getSelectedCrumb();
+ BreadCrumbItem selected = breadcrumbs.getSelectedCrumb();
if (selected.getChildren().size() > 0) {
- breadcrumbs.setSelectedCrumb(selected.getChildren().get(0));
+ breadcrumbs.setSelectedCrumb((BreadCrumbItem) selected.getChildren().get(0));
}
});
@@ -80,13 +94,19 @@ public class BreadcrumbsPage extends AbstractPage {
return box;
}
- private TreeItem getAncestor(TreeItem node, int height) {
+ private BreadCrumbItem getAncestor(BreadCrumbItem node, int height) {
var counter = height;
var current = node;
while (counter > 0 && current.getParent() != null) {
- current = current.getParent();
+ current = (BreadCrumbItem) current.getParent();
counter--;
}
return current;
}
+
+ @Override
+ protected double computePrefHeight(double width) {
+ System.out.println("pref height" + super.computePrefHeight(width));
+ return super.computePrefHeight(width);
+ }
}
diff --git a/sampler/src/main/java/atlantafx/sampler/page/showcase/filemanager/FileManagerPage.java b/sampler/src/main/java/atlantafx/sampler/page/showcase/filemanager/FileManagerPage.java
index 1ddb417..6e5cbeb 100644
--- a/sampler/src/main/java/atlantafx/sampler/page/showcase/filemanager/FileManagerPage.java
+++ b/sampler/src/main/java/atlantafx/sampler/page/showcase/filemanager/FileManagerPage.java
@@ -2,21 +2,23 @@
package atlantafx.sampler.page.showcase.filemanager;
import atlantafx.base.controls.Breadcrumbs;
+import atlantafx.base.controls.Breadcrumbs.BreadCrumbItem;
import atlantafx.base.controls.Spacer;
import atlantafx.base.theme.Tweaks;
import atlantafx.sampler.page.showcase.ShowcasePage;
import atlantafx.sampler.util.Containers;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
+import javafx.scene.Node;
import javafx.scene.control.*;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
-import javafx.scene.layout.Region;
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;
+import org.kordamp.ikonli.material2.Material2AL;
import org.kordamp.ikonli.material2.Material2MZ;
import java.net.URI;
@@ -156,24 +158,23 @@ public class FileManagerPage extends ShowcasePage {
}
private Breadcrumbs breadCrumbs() {
- Callback, Button> crumbFactory = crumb -> {
+ Callback, ButtonBase> crumbFactory = crumb -> {
var btn = new Button(crumb.getValue().getFileName().toString());
btn.getStyleClass().add(FLAT);
btn.setFocusTraversable(false);
- if (!crumb.getChildren().isEmpty()) {
- btn.setGraphic(new FontIcon(Feather.CHEVRON_RIGHT));
- }
return btn;
};
+ Callback, ? extends Node> dividerFactory = item ->
+ item != null && !item.isLast() ? new Label("", new FontIcon(Material2AL.CHEVRON_RIGHT)) : null;
+
var breadcrumbs = new Breadcrumbs();
breadcrumbs.setAutoNavigationEnabled(false);
breadcrumbs.setCrumbFactory(crumbFactory);
- breadcrumbs.setSelectedCrumb(
- Breadcrumbs.buildTreeModel(getParentPath(model.currentPathProperty().get(), 4).toArray(Path[]::new))
+ breadcrumbs.setDividerFactory(dividerFactory);
+ breadcrumbs.setSelectedCrumb(Breadcrumbs.buildTreeModel(
+ getParentPath(model.currentPathProperty().get(), 4).toArray(Path[]::new))
);
- breadcrumbs.setMaxWidth(Region.USE_PREF_SIZE);
- breadcrumbs.setMaxHeight(Region.USE_PREF_SIZE);
return breadcrumbs;
}
diff --git a/sampler/src/main/java/atlantafx/sampler/page/showcase/filemanager/file-manager.css b/sampler/src/main/java/atlantafx/sampler/page/showcase/filemanager/file-manager.css
index ee136b5..17e4ba1 100644
--- a/sampler/src/main/java/atlantafx/sampler/page/showcase/filemanager/file-manager.css
+++ b/sampler/src/main/java/atlantafx/sampler/page/showcase/filemanager/file-manager.css
@@ -11,6 +11,10 @@
-fx-background-insets: 0, 1;
}
+.file-manager-showcase .breadcrumbs >.divider {
+ -fx-padding: 0;
+}
+
.file-manager-showcase .table-directory-view .table-view {
-color-header-bg: -color-bg-default;
-color-cell-bg-selected: -color-accent-emphasis;
diff --git a/styles/src/components/_breadcrumbs.scss b/styles/src/components/_breadcrumbs.scss
index c9a7415..f7aad42 100755
--- a/styles/src/components/_breadcrumbs.scss
+++ b/styles/src/components/_breadcrumbs.scss
@@ -2,36 +2,27 @@
@use "../settings/config" as cfg;
-// node hierarchy
-// .bread-crumb-bar {
-// >.crumb { ... }
-// }
+$padding-x: cfg.$padding-x !default;
+$padding-y: cfg.$padding-y !default;
-// right gap is small, because we have crumbs divider on the right,
-// which gives us additional padding, at least visually.
-$crumb-right-gap: 2px !default;
-$crumb-left-gap: calc(cfg.$graphic-gap * 1.5) !default;
+$divider-spacing: 0.5em !default;
-.bread-crumb-bar {
- >.button.flat {
- -fx-padding: cfg.$padding-y $crumb-right-gap cfg.$padding-y $crumb-left-gap;
- -fx-content-display: RIGHT;
+/**
+== Structure ==
+.breadcrumbs {
+ >.crumb[:first|:last] { ... }
+ >.divider { ... }
+}
+*/
- &.first {
- -fx-padding: cfg.$padding-y $crumb-right-gap cfg.$padding-y cfg.$padding-x;
- }
+.breadcrumbs {
+ -fx-padding: $padding-y $padding-x $padding-y $padding-x;
- &.last {
- -fx-padding: cfg.$padding-y cfg.$padding-x cfg.$padding-y $crumb-left-gap;
+ >.hyperlink {
+ -color-link-fg-visited: -color-link-fg;
+ }
- #{cfg.$font-icon-selector} {
- // hiding font icon doesn't work as it should,
- // and have to be replaced by '-fx-managed' when supported
- -fx-min-width: 0;
- -fx-pref-width: 0;
- -fx-max-width: 0;
- visibility: hidden;
- }
- }
+ >.label.divider {
+ -fx-padding: 0 $divider-spacing 0 $divider-spacing;
}
}