From c2d609cdb4ef5ee00b28f827ad910920eceeec81 Mon Sep 17 00:00:00 2001 From: huanghongxun Date: Fri, 15 Feb 2019 19:39:43 +0800 Subject: [PATCH] Use TableView in ModListPage to allow multiple selection --- .../org/jackhuang/hmcl/ui/ListPageSkin.java | 1 + .../main/java/org/jackhuang/hmcl/ui/SVG.java | 4 + .../construct/JFXCheckBoxTreeTableCell.java | 133 +++++++++++++ .../hmcl/ui/versions/GameListSkin.java | 14 +- .../jackhuang/hmcl/ui/versions/ModItem.java | 73 ------- .../hmcl/ui/versions/ModListPage.java | 105 ++++++---- .../hmcl/ui/versions/ModListPageSkin.java | 187 ++++++++++++++++++ HMCL/src/main/resources/assets/css/root.css | 14 +- .../assets/fxml/download/modpack.fxml | 2 +- .../resources/assets/lang/I18N.properties | 4 + .../resources/assets/lang/I18N_zh.properties | 3 + .../assets/lang/I18N_zh_CN.properties | 3 + .../org/jackhuang/hmcl/mod/ModManager.java | 1 - 13 files changed, 423 insertions(+), 121 deletions(-) create mode 100644 HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/JFXCheckBoxTreeTableCell.java delete mode 100644 HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/ModItem.java create mode 100644 HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/ModListPageSkin.java diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/ListPageSkin.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/ListPageSkin.java index 887afa40c..ae084d9f9 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/ListPageSkin.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/ListPageSkin.java @@ -37,6 +37,7 @@ public class ListPageSkin extends SkinBase> { super(skinnable); SpinnerPane spinnerPane = new SpinnerPane(); + spinnerPane.getStyleClass().add("large-spinner-pane"); Pane placeholder = new Pane(); StackPane contentPane = new StackPane(); diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/SVG.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/SVG.java index 3f18c7678..fdd64a175 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/SVG.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/SVG.java @@ -162,4 +162,8 @@ public final class SVG { public static Node viewList(ObjectBinding fill, double width, double height) { return createSVGPath("M7,5H21V7H7V5M7,13V11H21V13H7M4,4.5A1.5,1.5 0 0,1 5.5,6A1.5,1.5 0 0,1 4,7.5A1.5,1.5 0 0,1 2.5,6A1.5,1.5 0 0,1 4,4.5M4,10.5A1.5,1.5 0 0,1 5.5,12A1.5,1.5 0 0,1 4,13.5A1.5,1.5 0 0,1 2.5,12A1.5,1.5 0 0,1 4,10.5M7,19V17H21V19H7M4,16.5A1.5,1.5 0 0,1 5.5,18A1.5,1.5 0 0,1 4,19.5A1.5,1.5 0 0,1 2.5,18A1.5,1.5 0 0,1 4,16.5Z", fill, width, height); } + + public static Node check(ObjectBinding fill, double width, double height) { + return createSVGPath("M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z", fill, width, height); + } } diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/JFXCheckBoxTreeTableCell.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/JFXCheckBoxTreeTableCell.java new file mode 100644 index 000000000..69d415bb1 --- /dev/null +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/JFXCheckBoxTreeTableCell.java @@ -0,0 +1,133 @@ +/* + * Hello Minecraft! Launcher + * Copyright (C) 2019 huangyuhui and contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.jackhuang.hmcl.ui.construct; + +import com.jfoenix.controls.JFXCheckBox; +import javafx.beans.binding.Bindings; +import javafx.beans.property.BooleanProperty; +import javafx.beans.property.ObjectProperty; +import javafx.beans.property.SimpleObjectProperty; +import javafx.beans.value.ObservableValue; +import javafx.geometry.Pos; +import javafx.scene.control.CheckBox; +import javafx.scene.control.TreeTableCell; +import javafx.util.Callback; +import javafx.util.StringConverter; + +public class JFXCheckBoxTreeTableCell extends TreeTableCell { + + private final CheckBox checkBox; + private boolean showLabel; + private ObservableValue booleanProperty; + + public JFXCheckBoxTreeTableCell() { + this(null, null); + } + + public JFXCheckBoxTreeTableCell( + final Callback> getSelectedProperty) { + this(getSelectedProperty, null); + } + + public JFXCheckBoxTreeTableCell( + final Callback> getSelectedProperty, + final StringConverter converter) { + this.getStyleClass().add("check-box-tree-table-cell"); + this.checkBox = new JFXCheckBox(); + setGraphic(null); + setSelectedStateCallback(getSelectedProperty); + setConverter(converter); + } + + private ObjectProperty> converter = + new SimpleObjectProperty>(this, "converter") { + protected void invalidated() { + updateShowLabel(); + } + }; + + public final ObjectProperty> converterProperty() { + return converter; + } + public final void setConverter(StringConverter value) { + converterProperty().set(value); + } + public final StringConverter getConverter() { + return converterProperty().get(); + } + + private ObjectProperty>> + selectedStateCallback = + new SimpleObjectProperty>>( + this, "selectedStateCallback"); + + public final ObjectProperty>> selectedStateCallbackProperty() { + return selectedStateCallback; + } + + public final void setSelectedStateCallback(Callback> value) { + selectedStateCallbackProperty().set(value); + } + + public final Callback> getSelectedStateCallback() { + return selectedStateCallbackProperty().get(); + } + + @SuppressWarnings("unchecked") + @Override public void updateItem(T item, boolean empty) { + super.updateItem(item, empty); + + if (empty) { + setText(null); + setGraphic(null); + } else { + StringConverter c = getConverter(); + + if (showLabel) { + setText(c.toString(item)); + } + setGraphic(checkBox); + + if (booleanProperty instanceof BooleanProperty) { + checkBox.selectedProperty().unbindBidirectional((BooleanProperty)booleanProperty); + } + ObservableValue obsValue = getSelectedProperty(); + if (obsValue instanceof BooleanProperty) { + booleanProperty = (ObservableValue) obsValue; + checkBox.selectedProperty().bindBidirectional((BooleanProperty)booleanProperty); + } + + checkBox.disableProperty().bind(Bindings.not( + getTreeTableView().editableProperty().and( + getTableColumn().editableProperty()).and( + editableProperty()) + )); + } + } + + private void updateShowLabel() { + this.showLabel = converter != null; + this.checkBox.setAlignment(showLabel ? Pos.CENTER_LEFT : Pos.CENTER); + } + + private ObservableValue getSelectedProperty() { + return getSelectedStateCallback() != null ? + getSelectedStateCallback().call(getIndex()) : + getTableColumn().getCellObservableValue(getIndex()); + } +} \ No newline at end of file diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameListSkin.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameListSkin.java index 4e3dda0cc..c011b2774 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameListSkin.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameListSkin.java @@ -19,7 +19,6 @@ package org.jackhuang.hmcl.ui.versions; import com.jfoenix.controls.JFXButton; import com.jfoenix.controls.JFXScrollPane; -import com.jfoenix.controls.JFXSpinner; import com.jfoenix.effects.JFXDepthManager; import javafx.beans.binding.Bindings; import javafx.geometry.Insets; @@ -31,8 +30,8 @@ import javafx.scene.layout.HBox; import javafx.scene.layout.StackPane; import javafx.scene.layout.VBox; import org.jackhuang.hmcl.setting.Theme; -import org.jackhuang.hmcl.ui.FXUtils; import org.jackhuang.hmcl.ui.SVG; +import org.jackhuang.hmcl.ui.construct.SpinnerPane; import org.jackhuang.hmcl.util.i18n.I18n; public class GameListSkin extends SkinBase { @@ -91,10 +90,9 @@ public class GameListSkin extends SkinBase { } { - StackPane center = new StackPane(); - - JFXSpinner spinner = new JFXSpinner(); - spinner.getStyleClass().setAll("first-spinner"); + SpinnerPane center = new SpinnerPane(); + center.loadingProperty().bind(skinnable.loadingProperty()); + center.getStyleClass().add("large-spinner-pane"); ScrollPane scrollPane = new ScrollPane(); scrollPane.setFitToWidth(true); @@ -109,9 +107,7 @@ public class GameListSkin extends SkinBase { scrollPane.setContent(gameList); JFXScrollPane.smoothScrolling(scrollPane); - FXUtils.onChangeAndOperate(skinnable.loadingProperty(), - loading -> center.getChildren().setAll(loading ? spinner : scrollPane)); - + center.setContent(scrollPane); root.setCenter(center); } diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/ModItem.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/ModItem.java deleted file mode 100644 index d44210d98..000000000 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/ModItem.java +++ /dev/null @@ -1,73 +0,0 @@ -/* - * Hello Minecraft! Launcher - * Copyright (C) 2019 huangyuhui and contributors - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -package org.jackhuang.hmcl.ui.versions; - -import com.jfoenix.concurrency.JFXUtilities; -import com.jfoenix.controls.JFXButton; -import com.jfoenix.controls.JFXCheckBox; -import com.jfoenix.effects.JFXDepthManager; -import javafx.geometry.Pos; -import javafx.scene.layout.BorderPane; -import org.jackhuang.hmcl.mod.ModInfo; -import org.jackhuang.hmcl.setting.Theme; -import org.jackhuang.hmcl.ui.FXUtils; -import org.jackhuang.hmcl.ui.SVG; -import org.jackhuang.hmcl.ui.construct.TwoLineListItem; -import org.jackhuang.hmcl.util.StringUtils; - -import java.util.function.Consumer; - -import static org.jackhuang.hmcl.util.i18n.I18n.i18n; - -public final class ModItem extends BorderPane { - - public ModItem(ModInfo info, Consumer deleteCallback) { - JFXCheckBox chkEnabled = new JFXCheckBox(); - BorderPane.setAlignment(chkEnabled, Pos.CENTER); - setLeft(chkEnabled); - - TwoLineListItem modItem = new TwoLineListItem(); - BorderPane.setAlignment(modItem, Pos.CENTER); - setCenter(modItem); - - JFXButton btnRemove = new JFXButton(); - JFXUtilities.runInFX(() -> { - FXUtils.installFastTooltip(btnRemove, i18n("mods.remove")); - }); - btnRemove.setOnMouseClicked(e -> deleteCallback.accept(this)); - btnRemove.getStyleClass().add("toggle-icon4"); - BorderPane.setAlignment(btnRemove, Pos.CENTER); - btnRemove.setGraphic(SVG.delete(Theme.blackFillBinding(), 15, 15)); - setRight(btnRemove); - - setStyle("-fx-background-radius: 2; -fx-background-color: white; -fx-padding: 8;"); - JFXDepthManager.setDepth(this, 1); - modItem.setTitle(info.getFileName()); - StringBuilder message = new StringBuilder(info.getName()); - if (StringUtils.isNotBlank(info.getVersion())) - message.append(", ").append(i18n("archive.version")).append(": ").append(info.getVersion()); - if (StringUtils.isNotBlank(info.getGameVersion())) - message.append(", ").append(i18n("archive.game_version")).append(": ").append(info.getGameVersion()); - if (StringUtils.isNotBlank(info.getAuthors())) - message.append(", ").append(i18n("archive.author")).append(": ").append(info.getAuthors()); - modItem.setSubtitle(message.toString()); - chkEnabled.setSelected(info.isActive()); - chkEnabled.selectedProperty().addListener((a, b, newValue) -> - info.activeProperty().set(newValue)); - } -} diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/ModListPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/ModListPage.java index f6c20f8c1..9775eba38 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/ModListPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/ModListPage.java @@ -19,6 +19,15 @@ package org.jackhuang.hmcl.ui.versions; import com.jfoenix.concurrency.JFXUtilities; import com.jfoenix.controls.JFXTabPane; +import javafx.beans.property.BooleanProperty; +import javafx.beans.property.ListProperty; +import javafx.beans.property.SimpleBooleanProperty; +import javafx.beans.property.SimpleListProperty; +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; +import javafx.scene.control.Control; +import javafx.scene.control.Skin; +import javafx.scene.control.TreeItem; import javafx.stage.FileChooser; import org.jackhuang.hmcl.mod.ModInfo; import org.jackhuang.hmcl.mod.ModManager; @@ -37,16 +46,18 @@ import java.util.Arrays; import java.util.LinkedList; import java.util.List; import java.util.logging.Level; +import java.util.stream.Collectors; import static org.jackhuang.hmcl.util.i18n.I18n.i18n; -public final class ModListPage extends ListPage { +public final class ModListPage extends Control { + private final ListProperty items = new SimpleListProperty<>(this, "items", FXCollections.observableArrayList()); + private final BooleanProperty loading = new SimpleBooleanProperty(this, "loading", false); private JFXTabPane parentTab; private ModManager modManager; public ModListPage() { - setRefreshable(true); FXUtils.applyDragListener(this, it -> Arrays.asList("jar", "zip", "litemod").contains(FileUtils.getExtension(it)), mods -> { mods.forEach(it -> { @@ -61,6 +72,10 @@ public final class ModListPage extends ListPage { } @Override + protected Skin createDefaultSkin() { + return new ModListPageSkin(this); + } + public void refresh() { loadMods(modManager); } @@ -71,50 +86,22 @@ public final class ModListPage extends ListPage { public void loadMods(ModManager modManager) { this.modManager = modManager; - Task.of(variables -> { + Task.ofResult("list", variables -> { synchronized (ModListPage.this) { JFXUtilities.runInFX(() -> loadingProperty().set(true)); - modManager.refreshMods(); - - // Surprisingly, if there are a great number of mods, this processing will cause a long UI pause, - // constructing UI elements. - // We must do this asynchronously. - LinkedList list = new LinkedList<>(); - for (ModInfo modInfo : modManager.getMods()) { - ModItem item = new ModItem(modInfo, i -> { - try { - modManager.removeMods(modInfo); - } catch (IOException ignore) { - // Fail to remove mods if the game is running or the mod is absent. - } - loadMods(modManager); - }); - modInfo.activeProperty().addListener((a, b, newValue) -> { - if (newValue) - item.getStyleClass().remove("disabled"); - else - item.getStyleClass().add("disabled"); - }); - if (!modInfo.isActive()) - item.getStyleClass().add("disabled"); - - list.add(item); - } - - variables.set("list", list); + return new LinkedList<>(modManager.getMods()); } - }).finalized(Schedulers.javafx(), (variables, isDependentsSucceeded) -> { + }).finalizedResult(Schedulers.javafx(), (list, isDependentsSucceeded) -> { loadingProperty().set(false); if (isDependentsSucceeded) FXUtils.onWeakChangeAndOperate(parentTab.getSelectionModel().selectedItemProperty(), newValue -> { if (newValue != null && newValue.getUserData() == ModListPage.this) - itemsProperty().setAll(variables.>get("list")); + itemsProperty().setAll(list.stream().map(ModListPageSkin.ModInfoObject::new).collect(Collectors.toList())); }); }).start(); } - @Override public void add() { FileChooser chooser = new FileChooser(); chooser.setTitle(i18n("mods.choose_mod")); @@ -151,4 +138,54 @@ public final class ModListPage extends ListPage { public void setParentTab(JFXTabPane parentTab) { this.parentTab = parentTab; } + + public void removeSelectedMods(ObservableList> selectedItems) { + try { + modManager.removeMods(selectedItems.stream() + .map(TreeItem::getValue) + .map(ModListPageSkin.ModInfoObject::getModInfo) + .toArray(ModInfo[]::new)); + loadMods(modManager); + } catch (IOException ignore) { + // Fail to remove mods if the game is running or the mod is absent. + } + } + + public void enableSelectedMods(ObservableList> selectedItems) { + selectedItems.stream() + .map(TreeItem::getValue) + .map(ModListPageSkin.ModInfoObject::getModInfo) + .forEach(info -> info.setActive(true)); + } + + public void disableSelectedMods(ObservableList> selectedItems) { + selectedItems.stream() + .map(TreeItem::getValue) + .map(ModListPageSkin.ModInfoObject::getModInfo) + .forEach(info -> info.setActive(false)); + } + + public ObservableList getItems() { + return items.get(); + } + + public void setItems(ObservableList items) { + this.items.set(items); + } + + public ListProperty itemsProperty() { + return items; + } + + public boolean isLoading() { + return loading.get(); + } + + public void setLoading(boolean loading) { + this.loading.set(loading); + } + + public BooleanProperty loadingProperty() { + return loading; + } } 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 new file mode 100644 index 000000000..224d52673 --- /dev/null +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/ModListPageSkin.java @@ -0,0 +1,187 @@ +/* + * Hello Minecraft! Launcher + * Copyright (C) 2019 huangyuhui and contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.jackhuang.hmcl.ui.versions; + +import com.jfoenix.controls.*; +import com.jfoenix.controls.datamodels.treetable.RecursiveTreeObject; +import com.jfoenix.effects.JFXDepthManager; +import javafx.beans.property.BooleanProperty; +import javafx.beans.property.SimpleStringProperty; +import javafx.beans.property.StringProperty; +import javafx.beans.value.ObservableValue; +import javafx.geometry.Insets; +import javafx.scene.Node; +import javafx.scene.control.*; +import javafx.scene.layout.BorderPane; +import javafx.scene.layout.HBox; +import javafx.scene.layout.StackPane; +import javafx.util.Callback; +import org.jackhuang.hmcl.mod.ModInfo; +import org.jackhuang.hmcl.setting.Theme; +import org.jackhuang.hmcl.ui.SVG; +import org.jackhuang.hmcl.ui.construct.JFXCheckBoxTreeTableCell; +import org.jackhuang.hmcl.ui.construct.SpinnerPane; + +import java.util.function.Function; + +import static org.jackhuang.hmcl.util.i18n.I18n.i18n; + +public class ModListPageSkin extends SkinBase { + + private static Node wrap(Node node) { + StackPane stackPane = new StackPane(); + stackPane.setPadding(new Insets(0, 5, 0, 2)); + stackPane.getChildren().setAll(node); + return stackPane; + } + + public ModListPageSkin(ModListPage skinnable) { + super(skinnable); + + BorderPane root = new BorderPane(); + JFXTreeTableView tableView = new JFXTreeTableView<>(); + + { + HBox toolbar = new HBox(); + toolbar.getStyleClass().setAll("jfx-tool-bar-second"); + JFXDepthManager.setDepth(toolbar, 1); + toolbar.setPickOnBounds(false); + + JFXButton btnRefresh = new JFXButton(); + btnRefresh.getStyleClass().add("jfx-tool-bar-button"); + btnRefresh.textFillProperty().bind(Theme.foregroundFillBinding()); + btnRefresh.setGraphic(wrap(SVG.refresh(Theme.foregroundFillBinding(), -1, -1))); + btnRefresh.setText(i18n("button.refresh")); + btnRefresh.setOnMouseClicked(e -> skinnable.refresh()); + toolbar.getChildren().add(btnRefresh); + + JFXButton btnAddMod = new JFXButton(); + btnAddMod.getStyleClass().add("jfx-tool-bar-button"); + btnAddMod.textFillProperty().bind(Theme.foregroundFillBinding()); + btnAddMod.setGraphic(wrap(SVG.plus(Theme.foregroundFillBinding(), -1, -1))); + btnAddMod.setText(i18n("mods.add")); + btnAddMod.setOnMouseClicked(e -> skinnable.add()); + toolbar.getChildren().add(btnAddMod); + + JFXButton btnRemove = new JFXButton(); + btnRemove.getStyleClass().add("jfx-tool-bar-button"); + btnRemove.textFillProperty().bind(Theme.foregroundFillBinding()); + btnRemove.setGraphic(wrap(SVG.delete(Theme.foregroundFillBinding(), -1, -1))); + btnRemove.setText(i18n("mods.remove")); + btnRemove.setOnMouseClicked(e -> skinnable.removeSelectedMods(tableView.getSelectionModel().getSelectedItems())); + toolbar.getChildren().add(btnRemove); + + JFXButton btnEnable = new JFXButton(); + btnEnable.getStyleClass().add("jfx-tool-bar-button"); + btnEnable.textFillProperty().bind(Theme.foregroundFillBinding()); + btnEnable.setGraphic(wrap(SVG.check(Theme.foregroundFillBinding(), -1, -1))); + btnEnable.setText(i18n("mods.enable")); + btnEnable.setOnMouseClicked(e -> skinnable.enableSelectedMods(tableView.getSelectionModel().getSelectedItems())); + toolbar.getChildren().add(btnEnable); + + JFXButton btnDisable = new JFXButton(); + btnDisable.getStyleClass().add("jfx-tool-bar-button"); + btnDisable.textFillProperty().bind(Theme.foregroundFillBinding()); + btnDisable.setGraphic(wrap(SVG.close(Theme.foregroundFillBinding(), -1, -1))); + btnDisable.setText(i18n("mods.disable")); + btnDisable.setOnMouseClicked(e -> skinnable.disableSelectedMods(tableView.getSelectionModel().getSelectedItems())); + toolbar.getChildren().add(btnDisable); + + root.setTop(toolbar); + } + + { + SpinnerPane center = new SpinnerPane(); + center.getStyleClass().add("large-spinner-pane"); + center.loadingProperty().bind(skinnable.loadingProperty()); + + tableView.setShowRoot(false); + tableView.setEditable(true); + tableView.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE); + tableView.setRoot(new RecursiveTreeItem<>(skinnable.getItems(), RecursiveTreeObject::getChildren)); + + JFXTreeTableColumn activeColumn = new JFXTreeTableColumn<>(); + setupCellValueFactory(activeColumn, ModInfoObject::activeProperty); + activeColumn.setCellFactory(c -> new JFXCheckBoxTreeTableCell<>()); + activeColumn.setEditable(true); + activeColumn.setMaxWidth(40); + activeColumn.setMinWidth(40); + + JFXTreeTableColumn fileNameColumn = new JFXTreeTableColumn<>(); + fileNameColumn.setText(i18n("archive.name")); + setupCellValueFactory(fileNameColumn, ModInfoObject::fileNameProperty); + fileNameColumn.prefWidthProperty().bind(tableView.widthProperty().subtract(40).multiply(0.8)); + + JFXTreeTableColumn versionColumn = new JFXTreeTableColumn<>(); + versionColumn.setText(i18n("archive.version")); + versionColumn.setPrefWidth(100); + setupCellValueFactory(versionColumn, ModInfoObject::versionProperty); + versionColumn.prefWidthProperty().bind(tableView.widthProperty().subtract(40).multiply(0.2)); + + tableView.getColumns().setAll(activeColumn, fileNameColumn, versionColumn); + + tableView.setColumnResizePolicy(JFXTreeTableView.CONSTRAINED_RESIZE_POLICY); + center.setContent(tableView); + root.setCenter(center); + } + + getChildren().setAll(root); + } + + private void setupCellValueFactory(JFXTreeTableColumn column, Function> mapper) { + column.setCellValueFactory(new Callback, ObservableValue>() { + @Override + public ObservableValue call(TreeTableColumn.CellDataFeatures param) { + if (column.validateValue(param)) + return mapper.apply(param.getValue().getValue()); + else + return column.getComputedValue(param); + } + }); + } + + public static class ModInfoObject extends RecursiveTreeObject { + private final BooleanProperty active; + private final StringProperty fileName; + private final StringProperty version; + private final ModInfo modInfo; + + public ModInfoObject(ModInfo modInfo) { + this.modInfo = modInfo; + this.active = modInfo.activeProperty(); + this.fileName = new SimpleStringProperty(modInfo.getFileName()); + this.version = new SimpleStringProperty(modInfo.getVersion()); + } + + public BooleanProperty activeProperty() { + return active; + } + + public StringProperty fileNameProperty() { + return fileName; + } + + public StringProperty versionProperty() { + return version; + } + + public ModInfo getModInfo() { + return modInfo; + } + } +} diff --git a/HMCL/src/main/resources/assets/css/root.css b/HMCL/src/main/resources/assets/css/root.css index 68fcfcf4d..497bfb918 100644 --- a/HMCL/src/main/resources/assets/css/root.css +++ b/HMCL/src/main/resources/assets/css/root.css @@ -876,6 +876,14 @@ -fx-stroke-width: 3.0; } +.large-spinner-pane .jfx-spinner { + -jfx-radius: 20; +} + +.large-spinner-pane .jfx-spinner { + -fx-stroke-width: 5.0; +} + .second-spinner { -jfx-radius: 30; } @@ -1028,7 +1036,7 @@ .tree-table-view .column-header .label { -fx-text-fill: #949494; - -fx-padding: 16 0 16 0; + -fx-padding: 8 0 8 0; } .tree-table-view .column-header .arrow, .tree-table-view .column-header .sort-order-dot { @@ -1046,8 +1054,8 @@ .tree-table-view .tree-table-cell { -fx-border-width: 0 0 0 0; - -fx-padding: 16 0 16 0; - -fx-alignment: top-center; + -fx-padding: 8 0 8 0; + /*-fx-alignment: top-center;*/ } .tree-table-view .column-overlay { diff --git a/HMCL/src/main/resources/assets/fxml/download/modpack.fxml b/HMCL/src/main/resources/assets/fxml/download/modpack.fxml index 0a2d6feb4..52d019123 100644 --- a/HMCL/src/main/resources/assets/fxml/download/modpack.fxml +++ b/HMCL/src/main/resources/assets/fxml/download/modpack.fxml @@ -14,7 +14,7 @@ - + diff --git a/HMCL/src/main/resources/assets/lang/I18N.properties b/HMCL/src/main/resources/assets/lang/I18N.properties index f403a75dd..dea8f5bc7 100644 --- a/HMCL/src/main/resources/assets/lang/I18N.properties +++ b/HMCL/src/main/resources/assets/lang/I18N.properties @@ -66,6 +66,7 @@ account.username=Name archive.author=Authors archive.game_version=Game +archive.name=Name archive.version=Version assets.download=Download assets @@ -252,6 +253,9 @@ mods.add=Add mods mods.add.failed=Failed to add mods %s. mods.add.success=Successfully added mods %s. mods.choose_mod=Choose your mods +mods.enable=Enable +mods.disable=Disable +mods.name=Name mods.remove=Remove datapack=Data packs diff --git a/HMCL/src/main/resources/assets/lang/I18N_zh.properties b/HMCL/src/main/resources/assets/lang/I18N_zh.properties index 0ea29d5f8..92ee6a411 100644 --- a/HMCL/src/main/resources/assets/lang/I18N_zh.properties +++ b/HMCL/src/main/resources/assets/lang/I18N_zh.properties @@ -65,6 +65,7 @@ account.username=使用者名稱 archive.author=作者 archive.game_version=遊戲版本 +archive.name=名稱 archive.version=版本 assets.download=下載資源 @@ -251,6 +252,8 @@ mods.add=新增模組 mods.add.failed=新增模組 %s 失敗。 mods.add.success=成功新增模組 %s。 mods.choose_mod=選擇模組 +mods.enable=啟用 +mods.disable=禁用 mods.remove=刪除 datapack=資料包 diff --git a/HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties b/HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties index 67c611e1e..967ce6417 100644 --- a/HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties +++ b/HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties @@ -65,6 +65,7 @@ account.username=用户名 archive.author=作者 archive.game_version=游戏版本 +archive.name=名称 archive.version=版本 assets.download=下载资源 @@ -251,6 +252,8 @@ mods.add=添加模组 mods.add.failed=添加模组 %s 失败。 mods.add.success=成功添加模组 %s。 mods.choose_mod=选择模组 +mods.enable=启用 +mods.disable=禁用 mods.remove=删除 datapack=数据包 diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/ModManager.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/ModManager.java index 71e8991da..24694d524 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/ModManager.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/ModManager.java @@ -124,7 +124,6 @@ public final class ModManager { for (ModInfo modInfo : modInfos) { Files.deleteIfExists(modInfo.getFile()); } - refreshMods(); } public Path disableMod(Path file) throws IOException {