diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/DataPackListPageSkin.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/DataPackListPageSkin.java index 40fc80daf5..5c4194a3c6 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/DataPackListPageSkin.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/DataPackListPageSkin.java @@ -25,6 +25,7 @@ import javafx.animation.PauseTransition; import javafx.application.Platform; import javafx.beans.InvalidationListener; +import javafx.beans.binding.Bindings; import javafx.beans.property.BooleanProperty; import javafx.beans.property.ObjectProperty; import javafx.beans.property.SimpleBooleanProperty; @@ -33,6 +34,7 @@ import javafx.geometry.Insets; import javafx.geometry.Pos; import javafx.scene.Node; +import javafx.scene.control.Label; import javafx.scene.control.SelectionMode; import javafx.scene.control.SkinBase; import javafx.scene.image.Image; @@ -51,6 +53,7 @@ import org.jackhuang.hmcl.ui.animation.ContainerAnimations; import org.jackhuang.hmcl.ui.animation.TransitionPane; import org.jackhuang.hmcl.ui.construct.*; +import org.jackhuang.hmcl.util.StringUtils; import org.jackhuang.hmcl.util.io.CompressingUtils; import org.jetbrains.annotations.Nullable; @@ -79,10 +82,15 @@ final class DataPackListPageSkin extends SkinBase { private final JFXListView listView; private final FilteredList filteredList; + /// Whether the search mechanism is currently active. private final BooleanProperty isSearching = new SimpleBooleanProperty(false); + private final BooleanProperty isSelecting = new SimpleBooleanProperty(false); private final JFXTextField searchField; + /// Timer for debouncing search input to avoid executing search on every keystroke. + private final PauseTransition searchPause = new PauseTransition(Duration.millis(100)); + private static final AtomicInteger lastShiftClickIndex = new AtomicInteger(-1); final Consumer toggleSelect; @@ -148,16 +156,21 @@ final class DataPackListPageSkin extends SkinBase { searchField = new JFXTextField(); searchField.setPromptText(i18n("search")); HBox.setHgrow(searchField, Priority.ALWAYS); - PauseTransition pause = new PauseTransition(Duration.millis(100)); - pause.setOnFinished(e -> filteredList.setPredicate(skinnable.updateSearchPredicate(searchField.getText()))); + searchPause.setOnFinished(e -> filteredList.setPredicate(skinnable.updateSearchPredicate(searchField.getText()))); searchField.textProperty().addListener((observable, oldValue, newValue) -> { - pause.setRate(1); - pause.playFromStart(); + if (isSearching.get() || !StringUtils.isBlank(newValue)) { + searchPause.setRate(1); + searchPause.playFromStart(); + } }); JFXButton closeSearchBar = createToolbarButton2(null, SVG.CLOSE, () -> { - isSearching.set(false); searchField.clear(); + searchPause.stop(); + + filteredList.setPredicate(null); + + isSearching.set(false); }); FXUtils.onEscPressed(searchField, closeSearchBar::fire); searchBar.getChildren().addAll(searchField, closeSearchBar); @@ -193,6 +206,23 @@ final class DataPackListPageSkin extends SkinBase { listView.setCellFactory(x -> new DataPackInfoListCell(listView, getSkinnable().readOnly)); listView.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE); + + StackPane placeholderContainer = new StackPane(); + placeholderContainer.getStyleClass().add("notice-pane"); + Label placeholderLabel = new Label(i18n("datapack.empty")); + placeholderLabel.textProperty().bind( + Bindings.createStringBinding(() -> { + if (isSearching.get()) { + return i18n("search.no_results_found"); + } else { + return i18n("datapack.empty"); + } + }, + isSearching) + ); + placeholderContainer.getChildren().add(placeholderLabel); + listView.setPlaceholder(placeholderContainer); + this.listView.setItems(filteredList); listView.getItems().addListener(listener); diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/ModListPageSkin.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/ModListPageSkin.java index b9507b84c9..dd47fb1899 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/ModListPageSkin.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/ModListPageSkin.java @@ -23,6 +23,7 @@ import javafx.beans.binding.Bindings; import javafx.beans.property.BooleanProperty; import javafx.beans.property.ObjectProperty; +import javafx.beans.property.SimpleBooleanProperty; import javafx.collections.ListChangeListener; import javafx.css.PseudoClass; import javafx.geometry.Insets; @@ -88,10 +89,14 @@ final class ModListPageSkin extends SkinBase { private final HBox toolbarSelecting; private final JFXListView listView; + + /// Whether the search mechanism is currently active. + private final BooleanProperty isSearching = new SimpleBooleanProperty(false); + private final JFXTextField searchField; - @FXThread - private boolean isSearching = false; + /// Timer for debouncing search input to avoid executing search on every keystroke. + private final PauseTransition searchPause = new PauseTransition(Duration.millis(100)); ModListPageSkin(ModListPage skinnable) { super(skinnable); @@ -118,19 +123,22 @@ final class ModListPageSkin extends SkinBase { searchField = new JFXTextField(); searchField.setPromptText(i18n("search")); HBox.setHgrow(searchField, Priority.ALWAYS); - PauseTransition pause = new PauseTransition(Duration.millis(100)); - pause.setOnFinished(e -> search()); + searchPause.setOnFinished(e -> search()); searchField.textProperty().addListener((observable, oldValue, newValue) -> { - pause.setRate(1); - pause.playFromStart(); + if (isSearching.get() || !StringUtils.isBlank(newValue)) { + searchPause.setRate(1); + searchPause.playFromStart(); + } }); JFXButton closeSearchBar = createToolbarButton2(null, SVG.CLOSE, () -> { changeToolbar(toolbarNormal); - isSearching = false; searchField.clear(); + searchPause.stop(); + + isSearching.set(false); Bindings.bindContent(listView.getItems(), getSkinnable().getItems()); }); @@ -192,7 +200,7 @@ final class ModListPageSkin extends SkinBase { FXUtils.onChangeAndOperate(listView.getSelectionModel().selectedItemProperty(), selectedItem -> { if (selectedItem == null) - changeToolbar(isSearching ? searchBar : toolbarNormal); + changeToolbar(isSearching.get() ? searchBar : toolbarNormal); else changeToolbar(toolbarSelecting); }); @@ -219,9 +227,26 @@ final class ModListPageSkin extends SkinBase { listView.setCellFactory(x -> new ModInfoListCell(listView)); listView.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE); + + StackPane placeholderContainer = new StackPane(); + placeholderContainer.getStyleClass().add("notice-pane"); + Label placeholderLabel = new Label(i18n("mods.empty")); + placeholderLabel.textProperty().bind( + Bindings.createStringBinding(() -> { + if (isSearching.get()) { + return i18n("search.no_results_found"); + } else { + return i18n("mods.empty"); + } + }, + isSearching) + ); + placeholderContainer.getChildren().add(placeholderLabel); + listView.setPlaceholder(placeholderContainer); + Bindings.bindContent(listView.getItems(), skinnable.getItems()); skinnable.getItems().addListener((ListChangeListener) c -> { - if (isSearching) { + if (isSearching.get()) { search(); } }); @@ -264,7 +289,7 @@ private void changeToolbar(HBox newToolbar) { } private void search() { - isSearching = true; + isSearching.set(true); Bindings.unbindContent(listView.getItems(), getSkinnable().getItems()); diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/ResourcePackListPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/ResourcePackListPage.java index 893f508073..637ab6d5ee 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/ResourcePackListPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/ResourcePackListPage.java @@ -266,7 +266,11 @@ private static final class ResourcePackListPageSkin extends SkinBase search()); + searchPause.setOnFinished(e -> search()); FXUtils.onChange(searchField.textProperty(), newValue -> { - pause.setRate(1); - pause.playFromStart(); + if (isSearching.get() || !StringUtils.isBlank(newValue)) { + searchPause.setRate(1); + searchPause.playFromStart(); + } }); JFXButton closeSearchBar = createToolbarButton2(null, SVG.CLOSE, () -> { changeToolbar(toolbarNormal); - isSearching = false; searchField.clear(); + searchPause.stop(); + + isSearching.set(false); Bindings.bindContent(listView.getItems(), getSkinnable().getItems()); }); @@ -346,7 +353,7 @@ private ResourcePackListPageSkin(ResourcePackListPage control) { FXUtils.onChangeAndOperate(listView.getSelectionModel().selectedItemProperty(), selectedItem -> { if (selectedItem == null) - changeToolbar(isSearching ? searchBar : toolbarNormal); + changeToolbar(isSearching.get() ? searchBar : toolbarNormal); else changeToolbar(toolbarSelecting); }); @@ -371,6 +378,23 @@ private ResourcePackListPageSkin(ResourcePackListPage control) { listView.setCellFactory(x -> new ResourcePackListCell(listView, control)); listView.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE); + + StackPane placeholderContainer = new StackPane(); + placeholderContainer.getStyleClass().add("notice-pane"); + Label placeholderLabel = new Label(i18n("resourcepack.empty")); + placeholderLabel.textProperty().bind( + Bindings.createStringBinding(() -> { + if (isSearching.get()) { + return i18n("search.no_results_found"); + } else { + return i18n("resourcepack.empty"); + } + }, + isSearching) + ); + placeholderContainer.getChildren().add(placeholderLabel); + listView.setPlaceholder(placeholderContainer); + Bindings.bindContent(listView.getItems(), control.getItems()); listView.setOnContextMenuRequested(event -> { @@ -403,7 +427,7 @@ private void changeToolbar(HBox newToolbar) { } private void search() { - isSearching = true; + isSearching.set(true); Bindings.unbindContent(listView.getItems(), getSkinnable().getItems()); diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/SchematicsPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/SchematicsPage.java index 241c47a435..d67ad761d1 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/SchematicsPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/SchematicsPage.java @@ -636,6 +636,12 @@ protected void updateItem(Item item, boolean empty) { private final class SchematicsPageSkin extends ToolbarListPageSkin { SchematicsPageSkin() { super(SchematicsPage.this); + + StackPane placeholderContainer = new StackPane(); + placeholderContainer.getStyleClass().add("notice-pane"); + Label placeholderLabel = new Label(i18n("schematics.empty")); + placeholderContainer.getChildren().add(placeholderLabel); + listView.setPlaceholder(placeholderContainer); } @Override diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/WorldBackupsPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/WorldBackupsPage.java index 76a41ce665..db03b7671c 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/WorldBackupsPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/WorldBackupsPage.java @@ -24,6 +24,7 @@ import javafx.geometry.Pos; import javafx.scene.Node; import javafx.scene.control.Control; +import javafx.scene.control.Label; import javafx.scene.control.Skin; import javafx.scene.control.SkinBase; import javafx.scene.layout.BorderPane; @@ -162,6 +163,12 @@ private final class WorldBackupsPageSkin extends ToolbarListPageSkin