This commit is contained in:
Glavo 2023-02-07 07:56:43 +08:00 committed by Haowei Wen
parent 394a74e566
commit f9eca35422

View File

@ -23,6 +23,7 @@ import javafx.beans.binding.Bindings;
import javafx.beans.property.BooleanProperty; import javafx.beans.property.BooleanProperty;
import javafx.geometry.Insets; import javafx.geometry.Insets;
import javafx.geometry.Pos; import javafx.geometry.Pos;
import javafx.scene.Node;
import javafx.scene.control.Label; import javafx.scene.control.Label;
import javafx.scene.control.SelectionMode; import javafx.scene.control.SelectionMode;
import javafx.scene.control.SkinBase; import javafx.scene.control.SkinBase;
@ -57,18 +58,36 @@ import java.nio.file.FileSystem;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
import java.util.Locale; import java.util.Locale;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.function.Predicate;
import java.util.logging.Level;
import java.util.regex.Pattern;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import static org.jackhuang.hmcl.ui.FXUtils.onEscPressed; import static org.jackhuang.hmcl.ui.FXUtils.onEscPressed;
import static org.jackhuang.hmcl.ui.ToolbarListPageSkin.createToolbarButton2; import static org.jackhuang.hmcl.ui.ToolbarListPageSkin.createToolbarButton2;
import static org.jackhuang.hmcl.ui.ToolbarListPageSkin.wrap;
import static org.jackhuang.hmcl.util.Lang.mapOf; import static org.jackhuang.hmcl.util.Lang.mapOf;
import static org.jackhuang.hmcl.util.Logging.LOG;
import static org.jackhuang.hmcl.util.Pair.pair; import static org.jackhuang.hmcl.util.Pair.pair;
import static org.jackhuang.hmcl.util.StringUtils.isNotBlank; import static org.jackhuang.hmcl.util.StringUtils.isNotBlank;
import static org.jackhuang.hmcl.util.i18n.I18n.i18n; import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
class ModListPageSkin extends SkinBase<ModListPage> { class ModListPageSkin extends SkinBase<ModListPage> {
// FXThread
private ThreadPoolExecutor searchPool;
private final TransitionPane toolbarPane;
private final HBox searchBar;
private final HBox toolbarNormal;
private final HBox toolbarSelecting;
private final JFXListView<ModInfoObject> listView;
private final JFXTextField searchField;
// FXThread
private boolean isSearching = false;
ModListPageSkin(ModListPage skinnable) { ModListPageSkin(ModListPage skinnable) {
super(skinnable); super(skinnable);
@ -78,30 +97,27 @@ class ModListPageSkin extends SkinBase<ModListPage> {
ComponentList root = new ComponentList(); ComponentList root = new ComponentList();
root.getStyleClass().add("no-padding"); root.getStyleClass().add("no-padding");
JFXListView<ModInfoObject> listView = new JFXListView<>(); listView = new JFXListView<>();
{ {
TransitionPane toolBarPane = new TransitionPane(); toolbarPane = new TransitionPane();
HBox searchBar = new HBox(); searchBar = new HBox();
HBox toolbarNormal = new HBox(); toolbarNormal = new HBox();
HBox toolbarSelecting = new HBox(); toolbarSelecting = new HBox();
// Search Bar // Search Bar
searchBar.setAlignment(Pos.CENTER); searchBar.setAlignment(Pos.CENTER);
searchBar.setPadding(new Insets(0, 5, 0, 5)); searchBar.setPadding(new Insets(0, 5, 0, 5));
searchField = new JFXTextField();
JFXTextField searchField = new JFXTextField();
searchField.setPromptText(i18n("search")); searchField.setPromptText(i18n("search"));
HBox.setHgrow(searchField, Priority.ALWAYS); HBox.setHgrow(searchField, Priority.ALWAYS);
searchField.setOnAction(e -> search());
JFXButton cancel = createToolbarButton2(null, SVG::close, JFXButton closeSearchBar = createToolbarButton2(null, SVG::close,
() -> { () -> changeToolbar(toolbarNormal));
searchField.clear();
toolBarPane.setContent(toolbarNormal, ContainerAnimations.FADE.getAnimationProducer());
});
searchBar.getChildren().setAll(searchField, cancel); searchBar.getChildren().setAll(searchField, closeSearchBar);
// Toolbar Normal // Toolbar Normal
toolbarNormal.getChildren().setAll( toolbarNormal.getChildren().setAll(
@ -110,8 +126,7 @@ class ModListPageSkin extends SkinBase<ModListPage> {
createToolbarButton2(i18n("folder.mod"), SVG::folderOpen, skinnable::openModFolder), createToolbarButton2(i18n("folder.mod"), SVG::folderOpen, skinnable::openModFolder),
createToolbarButton2(i18n("mods.check_updates"), SVG::update, skinnable::checkUpdates), createToolbarButton2(i18n("mods.check_updates"), SVG::update, skinnable::checkUpdates),
createToolbarButton2(i18n("download"), SVG::downloadOutline, skinnable::download), createToolbarButton2(i18n("download"), SVG::downloadOutline, skinnable::download),
createToolbarButton2(i18n("search"), SVG::magnify, createToolbarButton2(i18n("search"), SVG::magnify, () -> changeToolbar(searchBar))
() -> toolBarPane.setContent(searchBar, ContainerAnimations.FADE.getAnimationProducer()))
); );
// Toolbar Selecting // Toolbar Selecting
@ -131,14 +146,14 @@ class ModListPageSkin extends SkinBase<ModListPage> {
listView.getSelectionModel().clearSelection()) listView.getSelectionModel().clearSelection())
); );
FXUtils.onChangeAndOperate(listView.getSelectionModel().selectedItemProperty(), selectedItem -> { FXUtils.onChangeAndOperate(listView.getSelectionModel().selectedItemProperty(),
if (selectedItem == null) { selectedItem -> {
toolBarPane.setContent(toolbarNormal, ContainerAnimations.FADE.getAnimationProducer()); if (selectedItem == null)
} else { changeToolbar(isSearching ? searchBar : toolbarNormal);
toolBarPane.setContent(toolbarSelecting, ContainerAnimations.FADE.getAnimationProducer()); else
} changeToolbar(toolbarSelecting);
}); });
root.getContent().add(toolBarPane); root.getContent().add(toolbarPane);
} }
{ {
@ -160,7 +175,6 @@ class ModListPageSkin extends SkinBase<ModListPage> {
label.prefWidthProperty().bind(pane.widthProperty().add(-100)); label.prefWidthProperty().bind(pane.widthProperty().add(-100));
FXUtils.onChangeAndOperate(skinnable.moddedProperty(), modded -> { FXUtils.onChangeAndOperate(skinnable.moddedProperty(), modded -> {
if (modded) pane.getChildren().setAll(root); if (modded) pane.getChildren().setAll(root);
else pane.getChildren().setAll(label); else pane.getChildren().setAll(label);
}); });
@ -168,6 +182,52 @@ class ModListPageSkin extends SkinBase<ModListPage> {
getChildren().setAll(pane); getChildren().setAll(pane);
} }
private void changeToolbar(HBox newToolbar) {
Node oldToolbar = toolbarPane.getCurrentNode();
if (newToolbar != oldToolbar) {
toolbarPane.setContent(newToolbar, ContainerAnimations.FADE.getAnimationProducer());
if (isSearching && newToolbar == toolbarNormal) {
isSearching = false;
searchField.clear();
Bindings.bindContent(listView.getItems(), getSkinnable().getItems());
}
}
}
private void search() {
isSearching = true;
Bindings.unbindContent(listView.getItems(), getSkinnable().getItems());
String queryString = searchField.getText();
if (StringUtils.isBlank(queryString)) {
listView.getItems().setAll(getSkinnable().getItems());
} else {
listView.getItems().clear();
Predicate<String> predicate;
if (queryString.startsWith("regex:")) {
try {
Pattern pattern = Pattern.compile(queryString.substring("regex:".length()));
predicate = s -> pattern.matcher(s).find();
} catch (Throwable e) {
LOG.log(Level.WARNING, "Illegal regular expression", e);
return;
}
} else {
String lowerQueryString = queryString.toLowerCase(Locale.ROOT);
predicate = s -> s.toLowerCase(Locale.ROOT).contains(lowerQueryString);
}
// Do we need to search in the background thread?
for (ModInfoObject item : getSkinnable().getItems()) {
if (predicate.test(item.getModInfo().getFileName())) {
listView.getItems().add(item);
}
}
}
}
static class ModInfoObject extends RecursiveTreeObject<ModInfoObject> implements Comparable<ModInfoObject> { static class ModInfoObject extends RecursiveTreeObject<ModInfoObject> implements Comparable<ModInfoObject> {
private final BooleanProperty active; private final BooleanProperty active;
private final LocalModFile localModFile; private final LocalModFile localModFile;