diff --git a/gemsfx-demo/src/main/java/com/dlsc/gemsfx/demo/HistoryManagerApp.java b/gemsfx-demo/src/main/java/com/dlsc/gemsfx/demo/HistoryManagerApp.java index 669742e5..2ce292d4 100644 --- a/gemsfx-demo/src/main/java/com/dlsc/gemsfx/demo/HistoryManagerApp.java +++ b/gemsfx-demo/src/main/java/com/dlsc/gemsfx/demo/HistoryManagerApp.java @@ -1,24 +1,25 @@ package com.dlsc.gemsfx.demo; import com.dlsc.gemsfx.HistoryButton; +import com.dlsc.gemsfx.Spacer; import com.dlsc.gemsfx.util.HistoryManager; import com.dlsc.gemsfx.util.PreferencesHistoryManager; import com.dlsc.gemsfx.util.StringHistoryManager; import javafx.application.Application; +import javafx.beans.binding.Bindings; import javafx.geometry.Insets; import javafx.geometry.Pos; +import javafx.scene.Group; import javafx.scene.Node; import javafx.scene.Scene; -import javafx.scene.control.Label; -import javafx.scene.control.ListView; -import javafx.scene.control.Tab; -import javafx.scene.control.TabPane; -import javafx.scene.control.TextField; +import javafx.scene.control.*; import javafx.scene.input.KeyCode; import javafx.scene.layout.HBox; import javafx.scene.layout.VBox; import javafx.stage.Stage; import javafx.util.StringConverter; +import org.kordamp.ikonli.javafx.FontIcon; +import org.kordamp.ikonli.materialdesign.MaterialDesign; import java.util.List; import java.util.Objects; @@ -48,7 +49,7 @@ public void start(Stage primaryStage) throws Exception { private Node basicDemo() { TextField textField = new TextField(); - HistoryButton historyButton = new HistoryButton<>(); + HistoryButton historyButton = new HistoryButton<>(textField); // Tips: We can set the delimiter and preferencesKey when creating, otherwise use the default value. // StringHistoryManager historyManager = new StringHistoryManager(";", "history-records", @@ -61,6 +62,10 @@ private Node basicDemo() { // Tips: If we want to enable the history function, we need to set the history manager. historyButton.setHistoryManager(historyManager); + historyButton.setOnItemSelected(item -> { + historyButton.hidePopup(); + textField.setText(item); + }); // Add history item to the history when the enter key is pressed. textField.setOnKeyPressed(e -> { @@ -105,6 +110,32 @@ private Node advancedDemo() { HistoryButton historyButton = new HistoryButton<>(); historyButton.setHistoryManager(historyManager); + historyButton.setOnItemSelected(item -> { + historyButton.hidePopup(); + textField.setText(item); + }); + + // create the left node; + VBox leftBox = new VBox(); + Label historyLabel = new Label("History"); + historyLabel.setRotate(90); + Group group = new Group(historyLabel); + + Button clearAll = new Button("", new FontIcon(MaterialDesign.MDI_DELETE)); + clearAll.setPadding(new Insets(2, 4, 2, 4)); + clearAll.setOnAction(e -> { + historyManager.clear(); + historyButton.hidePopup(); + }); + clearAll.managedProperty().bind(clearAll.visibleProperty()); + clearAll.visibleProperty().bind(Bindings.isNotEmpty(historyManager.getAll())); + + leftBox.getChildren().addAll(group, new Spacer(), clearAll); + leftBox.setAlignment(Pos.CENTER); + leftBox.setPadding(new Insets(10, 5, 5, 5)); + leftBox.setPrefWidth(35); + leftBox.setStyle("-fx-background-color: #f4f4f4;"); + historyButton.setListDecorationLeft(leftBox); // add history item to the history when the enter key is pressed. textField.setOnKeyPressed(e -> { @@ -172,13 +203,13 @@ public Student fromString(String string) { HistoryButton historyButton = new HistoryButton<>(); historyButton.setHistoryManager(historyManager); historyButton.setText("History"); - historyButton.setOnHistoryItemSelected(item -> { - if (item != null) { - listView.getSelectionModel().select(item); - } + historyButton.setOnItemSelected(item -> { + if (item != null) { + listView.getSelectionModel().select(item); + } - historyButton.hidePopup(); - }); + historyButton.hidePopup(); + }); Label label = new Label(""" 1. Tips: Double-click the item to add it to the history. diff --git a/gemsfx/src/main/java/com/dlsc/gemsfx/HistoryButton.java b/gemsfx/src/main/java/com/dlsc/gemsfx/HistoryButton.java index cae32aa5..5e4f74fb 100644 --- a/gemsfx/src/main/java/com/dlsc/gemsfx/HistoryButton.java +++ b/gemsfx/src/main/java/com/dlsc/gemsfx/HistoryButton.java @@ -1,15 +1,9 @@ package com.dlsc.gemsfx; import com.dlsc.gemsfx.util.HistoryManager; -import com.dlsc.gemsfx.util.UIUtil; import javafx.beans.binding.Bindings; import javafx.beans.property.*; -import javafx.css.CssMetaData; import javafx.css.PseudoClass; -import javafx.css.Styleable; -import javafx.css.StyleableBooleanProperty; -import javafx.css.StyleableProperty; -import javafx.css.converter.BooleanConverter; import javafx.scene.Node; import javafx.scene.control.*; import javafx.scene.input.KeyCode; @@ -29,10 +23,6 @@ * This button integrates directly with a {@link HistoryManager} to present a list of previously used * items to the user. The user can then pick one of those items to set a value on an input field. * - *

Customization options include enabling or disabling the history popup, and configuring - * how the history items are displayed and managed within the popup. The button's style and behavior - * can be extensively customized through CSS properties and dynamic property bindings.

- * *

Usage scenarios include search fields, form inputs, or any other component where users might benefit * from being able to see and interact with their previous entries. The generic type {@code T} allows for * flexibility, making it suitable for various data types that can represent user input history.

@@ -42,19 +32,18 @@ public class HistoryButton extends Button { private static final String DEFAULT_STYLE_CLASS = "history-button"; - private static final boolean DEFAULT_ROUND_POPUP = false; - private static final boolean DEFAULT_FOCUS_POPUP_OWNER_ON_OPEN = false; private static final PseudoClass DISABLED_POPUP_PSEUDO_CLASS = PseudoClass.getPseudoClass("disabled-popup"); - private static final PseudoClass HISTORY_POPUP_SHOWING_PSEUDO_CLASS = PseudoClass.getPseudoClass("history-popup-showing"); + private static final PseudoClass POPUP_SHOWING_PSEUDO_CLASS = PseudoClass.getPseudoClass("popup-showing"); - private HistoryPopup historyPopup; + private HistoryPopup popup; /** * Creates a new instance of the history button. */ public HistoryButton() { getStyleClass().addAll(DEFAULT_STYLE_CLASS); + setGraphic(new FontIcon(MaterialDesign.MDI_HISTORY)); setOnAction(evt -> showPopup()); setCellFactory(view -> new RemovableListCell<>((listView, item) -> { @@ -76,10 +65,13 @@ public HistoryButton(Node owner) { setOwner(owner); } + /** + * Shows the popup that includes the list view with the items stored by the history manager. + */ public void showPopup() { Node owner = getOwner(); - if (owner != null && owner != this && !owner.isFocused() && getFocusPopupOwnerOnOpen()) { + if (owner != null && owner != this && !owner.isFocused()) { owner.requestFocus(); } @@ -87,18 +79,15 @@ public void showPopup() { return; } - if (historyPopup == null) { - historyPopup = new HistoryPopup(); - UIUtil.toggleClassBasedOnObservable(historyPopup, "round", roundProperty()); - - // basic settings - historyPopupShowing.bind(historyPopup.showingProperty()); + if (popup == null) { + popup = new HistoryPopup(); + popupShowing.bind(popup.showingProperty()); } - if (historyPopup.isShowing()) { + if (popup.isShowing()) { hidePopup(); } else { - historyPopup.show(this); + popup.show(this); } } @@ -106,119 +95,80 @@ public void showPopup() { * Hides the popup that is showing the history items. */ public void hidePopup() { - if (historyPopup != null) { - historyPopup.hide(); + if (popup != null) { + popup.hide(); } } - private ObjectProperty historyPlaceholder = new SimpleObjectProperty<>(this, "historyPlaceholder"); + // placeholder + + private final ObjectProperty placeholder = new SimpleObjectProperty<>(this, "placeholder"); /** * Returns the property representing the history placeholder node. * * @return the property representing the history placeholder node */ - public final ObjectProperty historyPlaceholderProperty() { - if (historyPlaceholder == null) { - historyPlaceholder = new SimpleObjectProperty<>(this, "historyPlaceholder"); - } - return historyPlaceholder; + public final ObjectProperty placeholderProperty() { + return placeholder; } - public final Node getHistoryPlaceholder() { - return historyPlaceholder == null ? null : historyPlaceholder.get(); + public final Node getPlaceholder() { + return placeholder == null ? null : placeholder.get(); } - public final void setHistoryPlaceholder(Node historyPlaceholder) { - historyPlaceholderProperty().set(historyPlaceholder); + public final void setPlaceholder(Node placeholder) { + placeholderProperty().set(placeholder); } - private ObjectProperty> onHistoryItemSelected; + private ObjectProperty> onItemSelected; - public final Consumer getOnHistoryItemSelected() { - return onHistoryItemSelected == null ? null : onHistoryItemSelected.get(); + public final Consumer getOnItemSelected() { + return onItemSelected == null ? null : onItemSelected.get(); } /** - * Returns the property representing the callback function to be executed when a history item within the ListView + * Returns the property representing the callback function to be executed when a history item within the list view * is either clicked directly or selected via the ENTER key press. This property enables setting a custom callback * function that will be invoked with the text of the clicked or selected history item as the argument. * * @return the property storing the "on history item confirmed" callback function. */ - public final ObjectProperty> onHistoryItemSelectedProperty() { - if (onHistoryItemSelected == null) { - onHistoryItemSelected = new SimpleObjectProperty<>(this, "onHistoryItemConfirmed"); - } - return onHistoryItemSelected; - } - - public final void setOnHistoryItemSelected(Consumer onHistoryItemSelected) { - onHistoryItemSelectedProperty().set(onHistoryItemSelected); - } - - private BooleanProperty focusPopupOwnerOnOpen; - - /** - * Controls whether the Popup Owner should gain focus when the popup is displayed after a button click. - *

- * This property determines whether the Popup Owner, which is the reference component for the popup's position, - * should receive focus when the popup is opened. If set to true, the Popup Owner will be focused - * when the popup becomes visible. If set to false, the Popup Owner will retain its current focus state. - *

- * The default value is false. - * - * @return the BooleanProperty that enables or disables focus on the Popup Owner when the popup opens - */ - public final BooleanProperty focusPopupOwnerOnOpenProperty() { - if (focusPopupOwnerOnOpen == null) { - focusPopupOwnerOnOpen = new StyleableBooleanProperty(DEFAULT_FOCUS_POPUP_OWNER_ON_OPEN) { - @Override - public Object getBean() { - return this; - } - - @Override - public String getName() { - return "popupOwnerFocusOnClick"; - } - - @Override - public CssMetaData getCssMetaData() { - return StyleableProperties.FOCUS_POPUP_OWNER_ON_OPEN; - } - }; + public final ObjectProperty> onItemSelectedProperty() { + if (onItemSelected == null) { + onItemSelected = new SimpleObjectProperty<>(this, "onItemSelectedProperty"); } - return focusPopupOwnerOnOpen; + return onItemSelected; } - public final boolean getFocusPopupOwnerOnOpen() { - return focusPopupOwnerOnOpen == null ? DEFAULT_FOCUS_POPUP_OWNER_ON_OPEN : focusPopupOwnerOnOpen.get(); + public final void setOnItemSelected(Consumer onItemSelected) { + onItemSelectedProperty().set(onItemSelected); } - public final void setFocusPopupOwnerOnOpen(boolean focusPopupOwnerOnOpen) { - focusPopupOwnerOnOpenProperty().set(focusPopupOwnerOnOpen); - } + // popup showing - private final ReadOnlyBooleanWrapper historyPopupShowing = new ReadOnlyBooleanWrapper(this, "historyPopupShowing") { + private final ReadOnlyBooleanWrapper popupShowing = new ReadOnlyBooleanWrapper(this, "popupShowing") { @Override protected void invalidated() { - pseudoClassStateChanged(HISTORY_POPUP_SHOWING_PSEUDO_CLASS, get()); + pseudoClassStateChanged(POPUP_SHOWING_PSEUDO_CLASS, get()); } }; - public final boolean isHistoryPopupShowing() { - return historyPopupShowing.get(); + public final boolean isPopupShowing() { + return popupShowing.get(); } + // history manager + private ObjectProperty> historyManager; /** - * The history manager that is used to manage the history of the HistoryButton. + * The history manager that is used for persisting the history of the button. *

* If its value is null, clicking the button will not display the history popup. *

- * If its value is not null, clicking the button will display the history popup. + * If its value is not null, clicking the button will display a popup showing the items previously stored + * for this button. * * @return the property representing the history manager */ @@ -242,6 +192,8 @@ public final HistoryManager getHistoryManager() { return historyManager == null ? null : historyManager.get(); } + // owner + private ObjectProperty owner; /** @@ -264,28 +216,96 @@ public final void setOwner(Node owner) { ownerProperty().set(owner); } - private BooleanProperty round; + // decoration left + + private final ObjectProperty listDecorationLeft = new SimpleObjectProperty<>(this, "listDecorationLeft"); + + public final Node getListDecorationLeft() { + return listDecorationLeft.get(); + } + + /** + * The list used by the popup to show previously used items can be easily decorated by specifying nodes for its + * left, right, top, and / or bottom sides. This property stores an optional node for the left side. + * + * @return the node shown to the left of the list view + */ + public final ObjectProperty listDecorationLeftProperty() { + return listDecorationLeft; + } + + public final void setListDecorationLeft(Node listDecorationLeft) { + this.listDecorationLeft.set(listDecorationLeft); + } + + // decoration right + + private final ObjectProperty listDecorationRight = new SimpleObjectProperty<>(this, "listDecorationRight"); + + public final Node getListDecorationRight() { + return listDecorationRight.get(); + } /** - * Determines whether the text field should have round corners. + * The list used by the popup to show previously used items can be easily decorated by specifying nodes for its + * left, right, top, and / or bottom sides. This property stores an optional node for the right side. * - * @return true if the text field should have round corners, false otherwise + * @return the node shown to the right of the list view */ - public final BooleanProperty roundProperty() { - if (round == null) { - round = new SimpleBooleanProperty(this, "round", DEFAULT_ROUND_POPUP); - } - return round; + public final ObjectProperty listDecorationRightProperty() { + return listDecorationRight; + } + + public final void setListDecorationRight(Node listDecorationRight) { + this.listDecorationRight.set(listDecorationRight); + } + + // decoration top + + private final ObjectProperty listDecorationTop = new SimpleObjectProperty<>(this, "listDecorationTop"); + + public final Node getListDecorationTop() { + return listDecorationTop.get(); + } + + /** + * The list used by the popup to show previously used items can be easily decorated by specifying nodes for its + * left, right, top, and / or bottom sides. This property stores an optional node for the top side. + * + * @return the node shown at the top of the list view + */ + public final ObjectProperty listDecorationTopProperty() { + return listDecorationTop; } - public final boolean isRound() { - return round == null ? DEFAULT_ROUND_POPUP : round.get(); + public final void setListDecorationTop(Node listDecorationTop) { + this.listDecorationTop.set(listDecorationTop); + } + + // decoration bottom + + private final ObjectProperty listDecorationBottom = new SimpleObjectProperty<>(this, "listDecorationBottom"); + + public final Node getListDecorationBottom() { + return listDecorationBottom.get(); + } + + /** + * The list used by the popup to show previously used items can be easily decorated by specifying nodes for its + * left, right, top, and / or bottom sides. This property stores an optional node for the bottom side. + * + * @return the node shown on the bottom of the list view + */ + public final ObjectProperty listDecorationBottomProperty() { + return listDecorationBottom; } - public final void setRound(boolean round) { - roundProperty().set(round); + public final void setListDecorationBottom(Node listDecorationBottom) { + this.listDecorationBottom.set(listDecorationBottom); } + // cell factory + private ObjectProperty, ListCell>> cellFactory; public final Callback, ListCell> getCellFactory() { @@ -313,60 +333,13 @@ public final void setCellFactory(Callback, ListCell> cellFactory) * * @return true if the history popup is showing, false otherwise */ - public final ReadOnlyBooleanProperty historyPopupShowingProperty() { - return historyPopupShowing.getReadOnlyProperty(); - } - - private static class StyleableProperties { - - private static final CssMetaData FOCUS_POPUP_OWNER_ON_OPEN = new CssMetaData<>( - "-fx-focus-popup-owner-on-open", BooleanConverter.getInstance(), DEFAULT_FOCUS_POPUP_OWNER_ON_OPEN) { - - @Override - public StyleableProperty getStyleableProperty(HistoryButton control) { - return (StyleableProperty) control.focusPopupOwnerOnOpenProperty(); - } - - @Override - public boolean isSettable(HistoryButton control) { - return control.focusPopupOwnerOnOpen == null || !control.focusPopupOwnerOnOpen.isBound(); - } - }; - - private static final List> STYLEABLES; - - static { - final List> styleables = new ArrayList<>(Button.getClassCssMetaData()); - styleables.add(FOCUS_POPUP_OWNER_ON_OPEN); - STYLEABLES = Collections.unmodifiableList(styleables); - } - } - - @Override - public List> getControlCssMetaData() { - return getClassCssMetaData(); - } - - public static List> getClassCssMetaData() { - return HistoryButton.StyleableProperties.STYLEABLES; + public final ReadOnlyBooleanProperty popupShowingProperty() { + return popupShowing.getReadOnlyProperty(); } /** - * Represents a custom popup control tailored to display and manage history items of type T. - * This control integrates with a {@link HistoryManager} to provide a user interface for viewing, - * selecting, and managing historical entries directly through a popup window. - * - *

The popup is highly customizable, supporting the addition of custom nodes to its top, bottom, - * left, and right regions. It also allows setting a placeholder for situations where no history items - * are available. The appearance and behavior of the history items can be customized via a cell factory.

- * - *

Key features include:

- *
    - *
  • Automatic binding to a {@link HistoryManager} for dynamic history item management.
  • - *
  • Customizable regions (top, bottom, left, right) for additional UI components or decorations.
  • - *
  • Configurable callbacks for item selection and confirmation actions, enhancing interactive capabilities.
  • - *
  • Support for CSS styling to match the application's design requirements.
  • - *
+ * The popup used by the {@link HistoryButton} to display a list view with the previously used + * items. */ public class HistoryPopup extends CustomPopupControl { @@ -380,88 +353,19 @@ public HistoryPopup() { setHideOnEscape(true); } + @Override protected Skin createDefaultSkin() { return new HistoryPopupSkin(this); } - - private final ObjectProperty left = new SimpleObjectProperty<>(this, "left"); - - public final ObjectProperty leftProperty() { - return left; - } - - public final Node getLeft() { - return leftProperty().get(); - } - - public final void setLeft(Node left) { - leftProperty().set(left); - } - - private final ObjectProperty right = new SimpleObjectProperty<>(this, "right"); - - public final ObjectProperty rightProperty() { - return right; - } - - public final Node getRight() { - return rightProperty().get(); - } - - public final void setRight(Node right) { - rightProperty().set(right); - } - - private final ObjectProperty top = new SimpleObjectProperty<>(this, "top"); - - public final ObjectProperty topProperty() { - return top; - } - - public final Node getTop() { - return topProperty().get(); - } - - public final void setTop(Node top) { - topProperty().set(top); - } - - private final ObjectProperty bottom = new SimpleObjectProperty<>(this, "bottom"); - - public final ObjectProperty bottomProperty() { - return bottom; - } - - public final Node getBottom() { - return bottomProperty().get(); - } - - public final void setBottom(Node bottom) { - bottomProperty().set(bottom); - } } /** - * Provides a concrete implementation of a skin for {@link HistoryPopup}, defining the visual representation - * and interaction handling of the popup. This skin layout includes a {@link ListView} that displays the history - * items, which can be interacted with via mouse or keyboard. - * - *

The skin binds various properties from the {@link HistoryPopup} to configure and customize the layout - * and behavior of the popup elements, including the arrangement of nodes around the central list view (top, - * bottom, left, right).

- * - *

Interactions such as mouse clicks and keyboard inputs are handled to select and confirm history items, - * allowing for a seamless user experience. The history items are displayed using a configurable cell factory, - * and the skin reacts to changes in the popup's properties to update the UI accordingly.

- * - *

This skin ensures that the popup's visual structure is maintained in alignment with the popup's configuration, - * supporting dynamic changes to the content and layout.

+ * The skin used for the {@link HistoryPopup}. */ public class HistoryPopupSkin implements Skin { private final HistoryPopup popup; private final BorderPane root; - private final ListView listView; public HistoryPopupSkin(HistoryPopup popup) { this.popup = popup; @@ -469,22 +373,20 @@ public HistoryPopupSkin(HistoryPopup popup) { root = new BorderPane() { @Override public String getUserAgentStylesheet() { - return Objects.requireNonNull(SearchField.class.getResource("history-popup.css")).toExternalForm(); + return Objects.requireNonNull(HistoryButton.class.getResource("history-button.css")).toExternalForm(); } }; root.getStyleClass().add("content-pane"); + root.setCenter(createListView()); - listView = createHistoryListView(); - root.setCenter(listView); - - root.leftProperty().bind(popup.leftProperty()); - root.rightProperty().bind(popup.rightProperty()); - root.topProperty().bind(popup.topProperty()); - root.bottomProperty().bind(popup.bottomProperty()); + root.leftProperty().bind(listDecorationLeftProperty()); + root.rightProperty().bind(listDecorationRightProperty()); + root.topProperty().bind(listDecorationTopProperty()); + root.bottomProperty().bind(listDecorationBottomProperty()); } - private ListView createHistoryListView() { + private ListView createListView() { ListView listView = new ListView<>(); listView.getStyleClass().add("history-list-view"); @@ -493,22 +395,22 @@ private ListView createHistoryListView() { Bindings.bindContent(listView.getItems(), historyManager.getAll()); } - historyManagerProperty().addListener((observable, oldValue, newValue) -> { - if (oldValue != null) { - Bindings.unbindContent(listView.getItems(), oldValue.getAll()); + historyManagerProperty().addListener((observable, oldManager, newManager) -> { + if (oldManager != null) { + Bindings.unbindContent(listView.getItems(), oldManager.getAll()); } - if (newValue != null) { - Bindings.bindContent(listView.getItems(), newValue.getAll()); + if (newManager != null) { + Bindings.bindContent(listView.getItems(), newManager.getAll()); } }); listView.cellFactoryProperty().bind(cellFactoryProperty()); - listView.placeholderProperty().bind(historyPlaceholderProperty()); + listView.placeholderProperty().bind(placeholderProperty()); // handle mouse clicks on the listView item listView.addEventHandler(MouseEvent.MOUSE_PRESSED, mouseEvent -> { - if (isPrimarySingleClick(mouseEvent) && !mouseEvent.isConsumed()) { - handlerHistoryItemConfirmed(listView); + if (mouseEvent.getButton() == MouseButton.PRIMARY && mouseEvent.getClickCount() == 1 && !mouseEvent.isConsumed()) { + handleItemSelection(listView); mouseEvent.consume(); } }); @@ -516,7 +418,7 @@ private ListView createHistoryListView() { // handle keyboard events on the listView listView.addEventFilter(KeyEvent.KEY_RELEASED, keyEvent -> { if (keyEvent.getCode() == KeyCode.ENTER) { - handlerHistoryItemConfirmed(listView); + handleItemSelection(listView); keyEvent.consume(); } }); @@ -524,39 +426,23 @@ private ListView createHistoryListView() { return listView; } - private void handlerHistoryItemConfirmed(ListView listView) { + private void handleItemSelection(ListView listView) { T historyItem = listView.getSelectionModel().getSelectedItem(); - Optional.ofNullable(getOnHistoryItemSelected()).ifPresent(onItemSelected -> onItemSelected.accept(historyItem)); - } - - private boolean isPrimarySingleClick(MouseEvent mouseEvent) { - return mouseEvent.getButton() == MouseButton.PRIMARY && mouseEvent.getClickCount() == 1; - } - - public final ListView getListView() { - return listView; + Optional.ofNullable(getOnItemSelected()).ifPresent(onItemSelected -> onItemSelected.accept(historyItem)); } + @Override public Node getNode() { return root; } + @Override public HistoryPopup getSkinnable() { return popup; } + @Override public void dispose() { - HistoryManager historyManager = getHistoryManager(); - if (historyManager != null) { - Bindings.unbindContent(listView.getItems(), historyManager.getAll()); - } - - listView.prefWidthProperty().unbind(); - listView.maxWidthProperty().unbind(); - listView.minWidthProperty().unbind(); - - listView.cellFactoryProperty().unbind(); - listView.placeholderProperty().unbind(); } } } diff --git a/gemsfx/src/main/java/com/dlsc/gemsfx/RemovableListCell.java b/gemsfx/src/main/java/com/dlsc/gemsfx/RemovableListCell.java index 3107311d..86cbafde 100644 --- a/gemsfx/src/main/java/com/dlsc/gemsfx/RemovableListCell.java +++ b/gemsfx/src/main/java/com/dlsc/gemsfx/RemovableListCell.java @@ -33,6 +33,7 @@ public RemovableListCell() { label = new Label(); setPrefWidth(0); + StackPane removeBtn = new StackPane(new FontIcon(MaterialDesign.MDI_CLOSE)); removeBtn.getStyleClass().add("remove-button"); removeBtn.addEventHandler(MouseEvent.MOUSE_PRESSED, this::onRemoveAction); @@ -98,11 +99,6 @@ public final BiConsumer, T> getOnRemove() { public final void setOnRemove(BiConsumer, T> onRemove) { onRemoveProperty().set(onRemove); } - - @Override - public String getUserAgentStylesheet() { - return Objects.requireNonNull(RemovableListCell.class.getResource("removable-list-cell.css")).toExternalForm(); - } } diff --git a/gemsfx/src/main/java/com/dlsc/gemsfx/SearchField.java b/gemsfx/src/main/java/com/dlsc/gemsfx/SearchField.java index 32a95081..0f0208ff 100644 --- a/gemsfx/src/main/java/com/dlsc/gemsfx/SearchField.java +++ b/gemsfx/src/main/java/com/dlsc/gemsfx/SearchField.java @@ -137,7 +137,7 @@ public SearchField() { editor.promptTextProperty().bindBidirectional(promptTextProperty()); // history listView placeholder - Label placeholder = new Label("No history available."); + Label placeholder = new Label("No items."); placeholder.getStyleClass().add("history-placeholder"); setHistoryPlaceholder(placeholder); @@ -176,7 +176,7 @@ public SearchField() { KeyCode keyCode = evt.getCode(); // record the history popup showing status before hide. - boolean lastHistoryPopupShowing = historyButton.isHistoryPopupShowing(); + boolean lastHistoryPopupShowing = historyButton.isPopupShowing(); // On key pressed, hide the history popup if the user pressed keys other than UP or DOWN. if (keyCode != KeyCode.UP && keyCode != KeyCode.DOWN) { @@ -342,7 +342,7 @@ private void onHistoryItemConfirmed(String historyItem) { private HistoryButton createHistoryButton() { HistoryButton historyButton = new HistoryButton<>(this); historyButton.historyManagerProperty().bind(historyManagerProperty()); - historyButton.setOnHistoryItemSelected(value -> { + historyButton.setOnItemSelected(value -> { if (StringUtils.isNotBlank(value)) { setText(value); } @@ -356,7 +356,6 @@ private HistoryButton createHistoryButton() { // Configure the history button historyButton.setFocusTraversable(false); - historyButton.setFocusPopupOwnerOnOpen(true); return historyButton; } diff --git a/gemsfx/src/main/java/com/dlsc/gemsfx/SearchTextField.java b/gemsfx/src/main/java/com/dlsc/gemsfx/SearchTextField.java index bf73b5d2..5cd30309 100644 --- a/gemsfx/src/main/java/com/dlsc/gemsfx/SearchTextField.java +++ b/gemsfx/src/main/java/com/dlsc/gemsfx/SearchTextField.java @@ -62,7 +62,7 @@ public SearchTextField() { setPromptText("Search..."); - Label placeholder = new Label("No history available."); + Label placeholder = new Label("No items."); placeholder.getStyleClass().add("default-placeholder"); setHistoryPlaceholder(placeholder); @@ -90,9 +90,9 @@ private void addToHistory() { private HistoryButton createHistoryButton() { HistoryButton historyButton = new HistoryButton<>(this); - historyButton.historyPlaceholderProperty().bind(historyPlaceholderProperty()); + historyButton.placeholderProperty().bind(historyPlaceholderProperty()); historyButton.historyManagerProperty().bind(historyManagerProperty()); - historyButton.setOnHistoryItemSelected(value -> { + historyButton.setOnItemSelected(value -> { if (StringUtils.isNotBlank(value)) { setText(value); } @@ -106,8 +106,6 @@ private HistoryButton createHistoryButton() { // Configure the history button historyButton.setFocusTraversable(false); - historyButton.setFocusPopupOwnerOnOpen(true); - historyButton.roundProperty().bind(roundProperty()); return historyButton; } diff --git a/gemsfx/src/main/resources/com/dlsc/gemsfx/history-button.css b/gemsfx/src/main/resources/com/dlsc/gemsfx/history-button.css new file mode 100644 index 00000000..423d2875 --- /dev/null +++ b/gemsfx/src/main/resources/com/dlsc/gemsfx/history-button.css @@ -0,0 +1,76 @@ +.history-popup.round > .content-pane { + -fx-background-radius: 10px; +} + +.history-popup.round > .content-pane > .history-list-view { + -fx-background-radius: 10px; + -fx-padding: 5px; +} + +.history-popup > .content-pane { + -fx-background-color: linear-gradient(to bottom, derive(-fx-color, -17%), derive(-fx-color, -30%)), -fx-control-inner-background; + -fx-background-insets: 0, 1; + -fx-effect: dropshadow(gaussian, rgba(0, 0, 0, 0.2), 12, 0.0, 0, 8); +} + +.history-popup:above > .content-pane { + -fx-translate-y: -6; +} + +.history-popup > .content-pane .popup-left { + -fx-background-color: linear-gradient(to right, #e0e0e0, #f8f8f8); + -fx-padding: 10px 5px 5px 5px; + -fx-alignment: top-center; +} + +.history-popup > .content-pane > .history-list-view > .placeholder > .label { + -fx-text-fill: derive(-fx-control-inner-background, -30%); + -fx-padding: 0 10px; +} + +.history-popup > .content-pane > .history-list-view { + -fx-pref-height: 200px; + -fx-min-width: 150px; +} + +.history-popup > .content-pane > .history-list-view > .virtual-flow > .clipped-container > .sheet > .list-cell { + /* No alternate highlighting */ + -fx-background: -fx-control-inner-background; + -fx-padding: 2px; +} + +.history-popup > .content-pane > .history-list-view > .virtual-flow > .clipped-container > .sheet > .list-cell:filled:hover { + -fx-background: -fx-accent; + -fx-background-color: -fx-background, -fx-background; + -fx-background-insets: 0, 1; +} + +.history-popup > .content-pane > .history-list-view > .virtual-flow > .clipped-container > .sheet > .list-cell:focused { + -fx-background-color: transparent; +} + +.history-popup > .content-pane > .history-list-view > .virtual-flow > .clipped-container > .sheet > .list-cell:filled:selected, +.history-popup > .content-pane > .history-list-view > .virtual-flow > .clipped-container > .sheet > .list-cell:filled:selected:hover { +} + +.history-popup > .content-pane > .history-list-view > .virtual-flow > .clipped-container > .sheet > .removable-list-cell .remove-button { + visibility: hidden; + -fx-cursor: hand; +} + +.history-popup > .content-pane > .history-list-view > .virtual-flow > .clipped-container > .sheet > .removable-list-cell:hover { +} + +.history-popup > .content-pane > .history-list-view > .virtual-flow > .clipped-container > .sheet > .removable-list-cell:hover .remove-button { + visibility: visible; +} + +.history-popup > .content-pane > .history-list-view > .virtual-flow > .clipped-container > .sheet > removable-list-cell .remove-button .ikonli-font-icon { + -fx-icon-size: 1em; +} + +.history-popup > .content-pane > .history-list-view > .virtual-flow > .clipped-container > .sheet > .removable-list-cell:hover .remove-button .ikonli-font-icon, +.history-popup > .content-pane > .history-list-view > .virtual-flow > .clipped-container > .sheet > .removable-list-cell:selected .remove-button .ikonli-font-icon, +.history-popup > .content-pane > .history-list-view > .virtual-flow > .clipped-container > .sheet > .removable-list-cell .remove-button:pressed .ikonli-font-icon { + -fx-icon-color: -fx-text-background-color; +} \ No newline at end of file diff --git a/gemsfx/src/main/resources/com/dlsc/gemsfx/history-popup.css b/gemsfx/src/main/resources/com/dlsc/gemsfx/history-popup.css deleted file mode 100644 index f1a54cd4..00000000 --- a/gemsfx/src/main/resources/com/dlsc/gemsfx/history-popup.css +++ /dev/null @@ -1,72 +0,0 @@ -.history-popup.round > .content-pane { - -fx-background-radius: 10px; -} - -.history-popup.round > .content-pane > .history-list-view { - -fx-background-radius: 10px; - -fx-padding: 5px; -} - -.history-popup > .content-pane { - -fx-background-color: linear-gradient(to bottom, - derive(-fx-color, -17%), - derive(-fx-color, -30%) - ), - -fx-control-inner-background; - -fx-background-insets: 0, 1; - -fx-effect: dropshadow(gaussian, rgba(0, 0, 0, 0.2), 12, 0.0, 0, 8); - -fx-pref-height: 200px; -} - -.history-popup:above > .content-pane { - -fx-translate-y: -6; -} - -.history-popup > .content-pane .popup-left { - -fx-background-color: linear-gradient(to right, #e0e0e0, #f8f8f8); - -fx-padding: 10px 5px 5px 5px; - -fx-alignment: top-center; -} - -/* ----------------------------------------------------------------------- - * Style based on Modena.css combo-box-popup style - */ -.history-list-view > .virtual-flow > .clipped-container > .sheet > .list-cell { - -fx-padding: 4 0 4 5; - /* No alternate highlighting */ - -fx-background: -fx-control-inner-background; -} - -.history-list-view > .virtual-flow > .clipped-container > .sheet > .list-cell:filled:hover { - -fx-background: -fx-accent; - -fx-background-color: #c9c9c9; -} - -.history-list-view > .virtual-flow > .clipped-container > .sheet > .list-cell:filled:selected, -.history-list-view > .virtual-flow > .clipped-container > .sheet > .list-cell:filled:selected:hover { - -fx-background: -fx-accent; - -fx-background-color: -fx-background, -fx-cell-focus-inner-border, -fx-background; - -fx-background-insets: 0, 1, 2; -} - -.history-list-view > .placeholder > .label { - -fx-text-fill: derive(-fx-control-inner-background, -30%); - -fx-padding: 0 10px; -} - -.history-list-view .search-field-list-cell { -} - -.history-list-view .search-field-list-cell .text { - -fx-fill: -fx-selection-bar-text; -} - -.history-list-view .search-field-list-cell .text.start { -} - -.history-list-view .search-field-list-cell .text.middle { - -fx-underline: true; -} - -.history-list-view .search-field-list-cell .text.end { -} diff --git a/gemsfx/src/main/resources/com/dlsc/gemsfx/removable-list-cell.css b/gemsfx/src/main/resources/com/dlsc/gemsfx/removable-list-cell.css deleted file mode 100644 index 67139a34..00000000 --- a/gemsfx/src/main/resources/com/dlsc/gemsfx/removable-list-cell.css +++ /dev/null @@ -1,36 +0,0 @@ -.removable-list-cell .container-box { - -fx-padding: 2px 3px 2px 5px; -} - -.removable-list-cell .remove-button { - visibility: hidden; - -fx-padding: 2px; - -fx-cursor: hand; -} - -.removable-list-cell:hover { - -fx-background-color: #c9c9c9; -} - -.removable-list-cell:hover .remove-button { - visibility: visible; -} - -.removable-list-cell:empty { - -fx-background-color: transparent; -} - -.removable-list-cell .remove-button .ikonli-font-icon { - -fx-icon-size: 1em; -} - -.removable-list-cell:hover .remove-button .ikonli-font-icon, -.removable-list-cell:selected .remove-button .ikonli-font-icon, -.removable-list-cell .remove-button:pressed .ikonli-font-icon { - -fx-icon-color: -fx-text-background-color; -} - -.removable-list-cell .remove-button:hover .ikonli-font-icon { - -fx-scale-x: 1.2; - -fx-scale-y: 1.2; -} \ No newline at end of file diff --git a/gemsfx/src/main/resources/com/dlsc/gemsfx/search-field.css b/gemsfx/src/main/resources/com/dlsc/gemsfx/search-field.css index f00a5cc8..6fe951ae 100644 --- a/gemsfx/src/main/resources/com/dlsc/gemsfx/search-field.css +++ b/gemsfx/src/main/resources/com/dlsc/gemsfx/search-field.css @@ -23,19 +23,16 @@ .search-field .graphic-wrapper .history-button:hover, .search-field .graphic-wrapper .history-button:pressed, -.search-field .graphic-wrapper .history-button:history-popup-showing { - -fx-background-color: -fx-base; +.search-field .graphic-wrapper .history-button:popup-showing { } .search-field .graphic-wrapper .history-button:disabled-popup, .search-field .graphic-wrapper .history-button:disabled-popup:hover, .search-field .graphic-wrapper .history-button:disabled-popup:pressed { -fx-cursor: text; - -fx-background-color: transparent; } .search-field .graphic-wrapper .history-button:pressed { - -fx-background-color: derive(-fx-base, -10%); } .search-field .graphic-wrapper .history-button > .icon { diff --git a/gemsfx/src/main/resources/com/dlsc/gemsfx/search-text-field.css b/gemsfx/src/main/resources/com/dlsc/gemsfx/search-text-field.css index 273fbcb2..824ee99c 100644 --- a/gemsfx/src/main/resources/com/dlsc/gemsfx/search-text-field.css +++ b/gemsfx/src/main/resources/com/dlsc/gemsfx/search-text-field.css @@ -24,15 +24,14 @@ .search-text-field > .left-pane > .history-button:hover, .search-text-field > .left-pane > .history-button:pressed, -.search-text-field > .left-pane > .history-button:history-popup-showing { - -fx-background-color: -fx-base; +.search-text-field > .left-pane > .history-button:popup-showing { } .search-text-field.round > .left-pane > .history-button, .search-text-field.round > .left-pane > .history-button:hover, .search-text-field.round > .left-pane > .history-button:pressed, .search-text-field.round > .left-pane > .history-button:focused, -.search-text-field.round > .left-pane > .history-button:history-popup-showing { +.search-text-field.round > .left-pane > .history-button:popup-showing { -fx-background-radius: 1000px 0 0 1000px; } @@ -40,11 +39,9 @@ .search-text-field > .left-pane > .history-button:disabled-popup:hover, .search-text-field > .left-pane > .history-button:disabled-popup:pressed { -fx-cursor: text; - -fx-background-color: transparent; } .search-text-field > .left-pane > .history-button:pressed { - -fx-background-color: derive(-fx-base, -10%); } .search-text-field > .left-pane > .history-button > .icon {