diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/FXUtils.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/FXUtils.java index e47a3d945..c2767bdc0 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/FXUtils.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/FXUtils.java @@ -30,6 +30,7 @@ import javafx.beans.value.ObservableValue; import javafx.beans.value.WeakChangeListener; import javafx.event.EventHandler; import javafx.fxml.FXMLLoader; +import javafx.geometry.Insets; import javafx.geometry.Pos; import javafx.scene.Node; import javafx.scene.control.*; @@ -161,6 +162,20 @@ public final class FXUtils { }); } + public static void setupCellValueFactory(JFXTreeTableColumn column, Function> mapper) { + column.setCellValueFactory(param -> { + if (column.validateValue(param)) + return mapper.apply(param.getValue().getValue()); + else + return column.getComputedValue(param); + }); + } + + public static Node wrapMargin(Node node, Insets insets) { + StackPane.setMargin(node, insets); + return new StackPane(node); + } + public static void setValidateWhileTextChanged(Node field, boolean validate) { if (field instanceof JFXTextField) { if (validate) { diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/ListPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/ListPage.java index 5e6efd8c8..d331b4b4f 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/ListPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/ListPage.java @@ -18,18 +18,11 @@ package org.jackhuang.hmcl.ui; 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.Node; -import javafx.scene.control.Control; import javafx.scene.control.Skin; -public abstract class ListPage extends Control { - private final ListProperty items = new SimpleListProperty<>(this, "items", FXCollections.observableArrayList()); - private final BooleanProperty loading = new SimpleBooleanProperty(this, "loading", false); +public abstract class ListPage extends ListPageBase { private final BooleanProperty refreshable = new SimpleBooleanProperty(this, "refreshable", false); public abstract void add(); @@ -42,30 +35,6 @@ public abstract class ListPage extends Control { return new ListPageSkin(this); } - 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; - } - public boolean isRefreshable() { return refreshable.get(); } diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/ListPageBase.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/ListPageBase.java new file mode 100644 index 000000000..4d7fbcbe4 --- /dev/null +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/ListPageBase.java @@ -0,0 +1,55 @@ +/* + * 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; + +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; + +public class ListPageBase extends Control { + private final ListProperty items = new SimpleListProperty<>(this, "items", FXCollections.observableArrayList()); + private final BooleanProperty loading = new SimpleBooleanProperty(this, "loading", 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/SVG.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/SVG.java index 9fde2a673..e02d606b5 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/SVG.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/SVG.java @@ -18,7 +18,6 @@ package org.jackhuang.hmcl.ui; import javafx.beans.binding.ObjectBinding; -import javafx.geometry.Insets; import javafx.geometry.Pos; import javafx.scene.Group; import javafx.scene.Node; @@ -30,11 +29,8 @@ public final class SVG { private SVG() { } - public static Node wrap(Node node) { - StackPane stackPane = new StackPane(); - stackPane.setPadding(new Insets(0, 5, 0, 2)); - stackPane.getChildren().setAll(node); - return stackPane; + public interface SVGIcon { + Node createIcon(ObjectBinding fill, double width, double height); } private static Node createSVGPath(String d, ObjectBinding fill, double width, double height) { diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/WorldListPageSkin.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/ToolbarListPageSkin.java similarity index 52% rename from HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/WorldListPageSkin.java rename to HMCL/src/main/java/org/jackhuang/hmcl/ui/ToolbarListPageSkin.java index 43dcbe3f0..719ec9304 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/WorldListPageSkin.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/ToolbarListPageSkin.java @@ -15,64 +15,42 @@ * 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; +package org.jackhuang.hmcl.ui; import com.jfoenix.controls.JFXButton; -import com.jfoenix.controls.JFXCheckBox; import com.jfoenix.controls.JFXScrollPane; import com.jfoenix.effects.JFXDepthManager; import javafx.beans.binding.Bindings; import javafx.geometry.Insets; +import javafx.scene.Node; import javafx.scene.control.ScrollPane; import javafx.scene.control.SkinBase; -import javafx.scene.layout.*; +import javafx.scene.layout.BorderPane; +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.SVG; import org.jackhuang.hmcl.ui.construct.SpinnerPane; -import static org.jackhuang.hmcl.ui.SVG.wrap; -import static org.jackhuang.hmcl.util.i18n.I18n.i18n; +import java.util.List; -public class WorldListPageSkin extends SkinBase { +public abstract class ToolbarListPageSkin> extends SkinBase { - public WorldListPageSkin(WorldListPage skinnable) { + public ToolbarListPageSkin(T skinnable) { super(skinnable); SpinnerPane spinnerPane = new SpinnerPane(); spinnerPane.getStyleClass().add("large-spinner-pane"); - BorderPane contentPane = new BorderPane(); + BorderPane root = new BorderPane(); { HBox toolbar = new HBox(); toolbar.getStyleClass().add("jfx-tool-bar-second"); JFXDepthManager.setDepth(toolbar, 1); toolbar.setPickOnBounds(false); - - JFXCheckBox chkShowAll = new JFXCheckBox(); - chkShowAll.getStyleClass().add("jfx-tool-bar-checkbox"); - chkShowAll.textFillProperty().bind(Theme.foregroundFillBinding()); - chkShowAll.setText(i18n("world.show_all")); - chkShowAll.selectedProperty().bindBidirectional(skinnable.showAllProperty()); - toolbar.getChildren().add(chkShowAll); - - 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 btnAdd = new JFXButton(); - btnAdd.getStyleClass().add("jfx-tool-bar-button"); - btnAdd.textFillProperty().bind(Theme.foregroundFillBinding()); - btnAdd.setGraphic(wrap(SVG.plus(Theme.foregroundFillBinding(), -1, -1))); - btnAdd.setText(i18n("world.add")); - btnAdd.setOnMouseClicked(e -> skinnable.add()); - toolbar.getChildren().add(btnAdd); - - contentPane.setTop(toolbar); + toolbar.getChildren().setAll(initializeToolbar(skinnable)); + root.setTop(toolbar); } { @@ -88,12 +66,31 @@ public class WorldListPageSkin extends SkinBase { scrollPane.setContent(content); JFXScrollPane.smoothScrolling(scrollPane); - contentPane.setCenter(scrollPane); + root.setCenter(scrollPane); } spinnerPane.loadingProperty().bind(skinnable.loadingProperty()); - spinnerPane.setContent(contentPane); + spinnerPane.setContent(root); getChildren().setAll(spinnerPane); } + + public static Node wrap(Node node) { + StackPane stackPane = new StackPane(); + stackPane.setPadding(new Insets(0, 5, 0, 2)); + stackPane.getChildren().setAll(node); + return stackPane; + } + + public static JFXButton createToolbarButton(String text, SVG.SVGIcon creator, Runnable onClick) { + JFXButton ret = new JFXButton(); + ret.getStyleClass().add("jfx-tool-bar-button"); + ret.textFillProperty().bind(Theme.foregroundFillBinding()); + ret.setGraphic(wrap(creator.createIcon(Theme.foregroundFillBinding(), -1, -1))); + ret.setText(text); + ret.setOnMouseClicked(e -> onClick.run()); + return ret; + } + + protected abstract List initializeToolbar(T skinnable); } 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 index 69d415bb1..a0a257534 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/JFXCheckBoxTreeTableCell.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/JFXCheckBoxTreeTableCell.java @@ -26,11 +26,12 @@ import javafx.beans.value.ObservableValue; import javafx.geometry.Pos; import javafx.scene.control.CheckBox; import javafx.scene.control.TreeTableCell; +import javafx.scene.layout.StackPane; import javafx.util.Callback; import javafx.util.StringConverter; public class JFXCheckBoxTreeTableCell extends TreeTableCell { - + private final StackPane pane; private final CheckBox checkBox; private boolean showLabel; private ObservableValue booleanProperty; @@ -49,6 +50,8 @@ public class JFXCheckBoxTreeTableCell extends TreeTableCell { final StringConverter converter) { this.getStyleClass().add("check-box-tree-table-cell"); this.checkBox = new JFXCheckBox(); + this.pane = new StackPane(checkBox); + this.pane.setAlignment(Pos.CENTER); setGraphic(null); setSelectedStateCallback(getSelectedProperty); setConverter(converter); @@ -101,7 +104,8 @@ public class JFXCheckBoxTreeTableCell extends TreeTableCell { if (showLabel) { setText(c.toString(item)); } - setGraphic(checkBox); + + setGraphic(pane); if (booleanProperty instanceof BooleanProperty) { checkBox.selectedProperty().unbindBidirectional((BooleanProperty)booleanProperty); diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/DatapackListItem.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/DatapackListItem.java deleted file mode 100644 index 7c8501b53..000000000 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/DatapackListItem.java +++ /dev/null @@ -1,60 +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.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.Datapack; -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 java.util.function.Consumer; - -import static org.jackhuang.hmcl.util.i18n.I18n.i18n; - -public class DatapackListItem extends BorderPane { - - public DatapackListItem(Datapack.Pack 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(); - FXUtils.installFastTooltip(btnRemove, i18n("datapack.remove")); - btnRemove.setOnMouseClicked(e -> deleteCallback.accept(this)); - btnRemove.getStyleClass().add("toggle-icon4"); - BorderPane.setAlignment(btnRemove, Pos.CENTER); - btnRemove.setGraphic(SVG.close(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.getId()); - modItem.setSubtitle(info.getDescription()); - chkEnabled.selectedProperty().bindBidirectional(info.activeProperty()); - } -} diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/DatapackListPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/DatapackListPage.java index 7af86e24e..59bf06813 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/DatapackListPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/DatapackListPage.java @@ -19,11 +19,15 @@ package org.jackhuang.hmcl.ui.versions; import javafx.beans.property.SimpleStringProperty; import javafx.beans.property.StringProperty; +import javafx.collections.ObservableList; +import javafx.scene.control.Skin; +import javafx.scene.control.TreeItem; import javafx.stage.FileChooser; import org.jackhuang.hmcl.mod.Datapack; +import org.jackhuang.hmcl.task.Task; import org.jackhuang.hmcl.ui.Controllers; import org.jackhuang.hmcl.ui.FXUtils; -import org.jackhuang.hmcl.ui.ListPage; +import org.jackhuang.hmcl.ui.ListPageBase; import org.jackhuang.hmcl.ui.decorator.DecoratorPage; import org.jackhuang.hmcl.util.Logging; import org.jackhuang.hmcl.util.io.FileUtils; @@ -32,16 +36,19 @@ import org.jackhuang.hmcl.util.javafx.MappedObservableList; import java.io.File; import java.io.IOException; import java.nio.file.Path; -import java.util.*; +import java.util.List; +import java.util.Objects; import java.util.logging.Level; import static org.jackhuang.hmcl.util.i18n.I18n.i18n; -public class DatapackListPage extends ListPage implements DecoratorPage { +public class DatapackListPage extends ListPageBase implements DecoratorPage { private final StringProperty title = new SimpleStringProperty(); private final Path worldDir; private final Datapack datapack; + private final ObservableList items; + public DatapackListPage(String worldName, Path worldDir) { this.worldDir = worldDir; @@ -50,14 +57,7 @@ public class DatapackListPage extends ListPage implements Deco datapack = new Datapack(worldDir.resolve("datapacks")); datapack.loadFromDir(); - setItems(MappedObservableList.create(datapack.getInfo(), - pack -> new DatapackListItem(pack, item -> { - try { - datapack.deletePack(pack); - } catch (IOException e) { - Logging.LOG.warning("Failed to delete datapack"); - } - }))); + setItems(items = MappedObservableList.create(datapack.getInfo(), DatapackListPageSkin.DatapackInfoObject::new)); FXUtils.applyDragListener(this, it -> Objects.equals("zip", FileUtils.getExtension(it)), mods -> { mods.forEach(it -> { @@ -69,7 +69,19 @@ public class DatapackListPage extends ListPage implements Deco Logging.LOG.log(Level.WARNING, "Unable to parse datapack file " + it, e); } }); - }, datapack::loadFromDir); + }, this::refresh); + } + + @Override + protected Skin createDefaultSkin() { + return new DatapackListPageSkin(this); + } + + public void refresh() { + setLoading(true); + Task.of(datapack::loadFromDir) + .with(Task.of(() -> setLoading(false))) + .start(); } @Override @@ -77,7 +89,6 @@ public class DatapackListPage extends ListPage implements Deco return title; } - @Override public void add() { FileChooser chooser = new FileChooser(); chooser.setTitle(i18n("datapack.choose_datapack")); @@ -97,4 +108,32 @@ public class DatapackListPage extends ListPage implements Deco datapack.loadFromDir(); } + + void removeSelected(ObservableList> selectedItems) { + selectedItems.stream() + .map(TreeItem::getValue) + .map(DatapackListPageSkin.DatapackInfoObject::getPackInfo) + .forEach(pack -> { + try { + datapack.deletePack(pack); + } catch (IOException e) { + // Fail to remove mods if the game is running or the datapack is absent. + Logging.LOG.warning("Failed to delete datapack " + pack); + } + }); + } + + void enableSelected(ObservableList> selectedItems) { + selectedItems.stream() + .map(TreeItem::getValue) + .map(DatapackListPageSkin.DatapackInfoObject::getPackInfo) + .forEach(info -> info.setActive(true)); + } + + void disableSelected(ObservableList> selectedItems) { + selectedItems.stream() + .map(TreeItem::getValue) + .map(DatapackListPageSkin.DatapackInfoObject::getPackInfo) + .forEach(info -> info.setActive(false)); + } } 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 new file mode 100644 index 000000000..6f8dca3ec --- /dev/null +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/DatapackListPageSkin.java @@ -0,0 +1,126 @@ +/* + * 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.JFXTreeTableColumn; +import com.jfoenix.controls.JFXTreeTableView; +import com.jfoenix.controls.RecursiveTreeItem; +import com.jfoenix.controls.datamodels.treetable.RecursiveTreeObject; +import com.jfoenix.effects.JFXDepthManager; +import javafx.beans.property.BooleanProperty; +import javafx.beans.property.ObjectProperty; +import javafx.beans.property.SimpleObjectProperty; +import javafx.geometry.Insets; +import javafx.scene.Node; +import javafx.scene.control.SelectionMode; +import javafx.scene.control.SkinBase; +import javafx.scene.layout.BorderPane; +import javafx.scene.layout.HBox; +import org.jackhuang.hmcl.mod.Datapack; +import org.jackhuang.hmcl.ui.SVG; +import org.jackhuang.hmcl.ui.construct.JFXCheckBoxTreeTableCell; +import org.jackhuang.hmcl.ui.construct.SpinnerPane; +import org.jackhuang.hmcl.ui.construct.TwoLineListItem; +import org.jackhuang.hmcl.util.StringUtils; + +import static org.jackhuang.hmcl.ui.FXUtils.setupCellValueFactory; +import static org.jackhuang.hmcl.ui.FXUtils.wrapMargin; +import static org.jackhuang.hmcl.ui.ToolbarListPageSkin.createToolbarButton; +import static org.jackhuang.hmcl.util.i18n.I18n.i18n; + +class DatapackListPageSkin extends SkinBase { + + DatapackListPageSkin(DatapackListPage skinnable) { + super(skinnable); + + BorderPane root = new BorderPane(); + JFXTreeTableView tableView = new JFXTreeTableView<>(); + + { + HBox toolbar = new HBox(); + toolbar.getStyleClass().add("jfx-tool-bar-second"); + JFXDepthManager.setDepth(toolbar, 1); + toolbar.setPickOnBounds(false); + + toolbar.getChildren().add(createToolbarButton(i18n("button.refresh"), SVG::refresh, skinnable::refresh)); + toolbar.getChildren().add(createToolbarButton(i18n("datapack.add"), SVG::plus, skinnable::add)); + toolbar.getChildren().add(createToolbarButton(i18n("mods.remove"), SVG::delete, () -> + skinnable.removeSelected(tableView.getSelectionModel().getSelectedItems()))); + toolbar.getChildren().add(createToolbarButton(i18n("mods.enable"), SVG::check, () -> + skinnable.enableSelected(tableView.getSelectionModel().getSelectedItems()))); + toolbar.getChildren().add(createToolbarButton(i18n("mods.disable"), SVG::close, () -> + skinnable.disableSelected(tableView.getSelectionModel().getSelectedItems()))); + root.setTop(toolbar); + } + + { + SpinnerPane center = new SpinnerPane(); + center.getStyleClass().add("large-spinner-pane"); + center.loadingProperty().bind(skinnable.loadingProperty()); + + tableView.getStyleClass().addAll("no-header"); + 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, DatapackInfoObject::activeProperty); + activeColumn.setCellFactory(c -> new JFXCheckBoxTreeTableCell<>()); + activeColumn.setEditable(true); + activeColumn.setMaxWidth(40); + activeColumn.setMinWidth(40); + + JFXTreeTableColumn detailColumn = new JFXTreeTableColumn<>(); + setupCellValueFactory(detailColumn, DatapackInfoObject::nodeProperty); + + tableView.getColumns().setAll(activeColumn, detailColumn); + + tableView.setColumnResizePolicy(JFXTreeTableView.CONSTRAINED_RESIZE_POLICY); + center.setContent(tableView); + root.setCenter(center); + } + + getChildren().setAll(root); + } + + static class DatapackInfoObject extends RecursiveTreeObject { + private final BooleanProperty active; + private final Datapack.Pack packInfo; + private final ObjectProperty node; + + DatapackInfoObject(Datapack.Pack packInfo) { + this.packInfo = packInfo; + this.active = packInfo.activeProperty(); + this.node = new SimpleObjectProperty<>(wrapMargin(new TwoLineListItem(packInfo.getId(), StringUtils.parseColorEscapes(packInfo.getDescription())), + new Insets(8, 0, 8, 0))); + } + + BooleanProperty activeProperty() { + return active; + } + + ObjectProperty nodeProperty() { + return node; + } + + Datapack.Pack getPackInfo() { + return packInfo; + } + } +} diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameList.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameList.java index a30eff08d..c8af41351 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameList.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameList.java @@ -18,10 +18,9 @@ package org.jackhuang.hmcl.ui.versions; import javafx.application.Platform; -import javafx.beans.property.*; -import javafx.collections.FXCollections; -import javafx.scene.control.Control; -import javafx.scene.control.Skin; +import javafx.beans.property.ReadOnlyStringProperty; +import javafx.beans.property.ReadOnlyStringWrapper; +import javafx.scene.Node; import javafx.scene.control.ToggleGroup; import org.jackhuang.hmcl.event.EventBus; import org.jackhuang.hmcl.event.RefreshingVersionsEvent; @@ -29,14 +28,14 @@ import org.jackhuang.hmcl.game.HMCLGameRepository; import org.jackhuang.hmcl.game.Version; import org.jackhuang.hmcl.setting.Profile; import org.jackhuang.hmcl.setting.Profiles; -import org.jackhuang.hmcl.ui.Controllers; -import org.jackhuang.hmcl.ui.WeakListenerHolder; +import org.jackhuang.hmcl.ui.*; import org.jackhuang.hmcl.ui.decorator.DecoratorPage; import org.jackhuang.hmcl.ui.download.ModpackInstallWizardProvider; import org.jackhuang.hmcl.ui.download.VanillaInstallWizardProvider; import org.jackhuang.hmcl.util.i18n.I18n; import org.jackhuang.hmcl.util.versioning.VersionNumber; +import java.util.Arrays; import java.util.Comparator; import java.util.Date; import java.util.List; @@ -45,17 +44,15 @@ import java.util.stream.Collectors; import static org.jackhuang.hmcl.ui.FXUtils.runInFX; import static org.jackhuang.hmcl.util.i18n.I18n.i18n; -public class GameList extends Control implements DecoratorPage { +public class GameList extends ListPageBase implements DecoratorPage { private final ReadOnlyStringWrapper title = new ReadOnlyStringWrapper(I18n.i18n("version.manage")); - private final BooleanProperty loading = new SimpleBooleanProperty(true); - private final ListProperty items = new SimpleListProperty<>(FXCollections.observableArrayList()); private ToggleGroup toggleGroup; public GameList() { EventBus.EVENT_BUS.channel(RefreshingVersionsEvent.class).register(event -> { if (event.getSource() == Profiles.getSelectedProfile().getRepository()) - runInFX(() -> loading.set(true)); + runInFX(() -> setLoading(true)); }); Profiles.registerVersionsListener(this::loadVersions); @@ -74,8 +71,8 @@ public class GameList extends Control implements DecoratorPage { .collect(Collectors.toList()); runInFX(() -> { if (profile == Profiles.getSelectedProfile()) { - loading.set(false); - items.setAll(children); + setLoading(false); + itemsProperty().setAll(children); children.forEach(GameListItem::checkSelection); profile.selectedVersionProperty().addListener(listenerHolder.weak((a, b, newValue) -> { @@ -97,8 +94,8 @@ public class GameList extends Control implements DecoratorPage { } @Override - protected Skin createDefaultSkin() { - return new GameListSkin(this); + protected GameListSkin createDefaultSkin() { + return new GameListSkin(); } void addNewGame() { @@ -128,11 +125,20 @@ public class GameList extends Control implements DecoratorPage { return title.getReadOnlyProperty(); } - public BooleanProperty loadingProperty() { - return loading; - } + private class GameListSkin extends ToolbarListPageSkin { - public ListProperty itemsProperty() { - return items; + public GameListSkin() { + super(GameList.this); + } + + @Override + protected List initializeToolbar(GameList skinnable) { + return Arrays.asList( + createToolbarButton(i18n("install.new_game"), SVG::plus, skinnable::addNewGame), + createToolbarButton(i18n("install.modpack"), SVG::importIcon, skinnable::importModpack), + createToolbarButton(i18n("button.refresh"), SVG::refresh, skinnable::refresh), + createToolbarButton(i18n("settings.type.global.manage"), SVG::gear, skinnable::modifyGlobalGameSettings) + ); + } } } 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 deleted file mode 100644 index faae0ca74..000000000 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameListSkin.java +++ /dev/null @@ -1,116 +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.controls.JFXButton; -import com.jfoenix.controls.JFXScrollPane; -import com.jfoenix.effects.JFXDepthManager; -import javafx.beans.binding.Bindings; -import javafx.geometry.Insets; -import javafx.scene.Node; -import javafx.scene.control.ScrollPane; -import javafx.scene.control.SkinBase; -import javafx.scene.layout.BorderPane; -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.SVG; -import org.jackhuang.hmcl.ui.construct.SpinnerPane; -import org.jackhuang.hmcl.util.i18n.I18n; - -public class GameListSkin 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 GameListSkin(GameList skinnable) { - super(skinnable); - - BorderPane root = new BorderPane(); - - { - HBox toolbar = new HBox(); - toolbar.getStyleClass().add("jfx-tool-bar-second"); - JFXDepthManager.setDepth(toolbar, 1); - toolbar.setPickOnBounds(false); - - JFXButton btnAddNewGame = new JFXButton(); - btnAddNewGame.getStyleClass().add("jfx-tool-bar-button"); - btnAddNewGame.textFillProperty().bind(Theme.foregroundFillBinding()); - btnAddNewGame.setGraphic(wrap(SVG.plus(Theme.foregroundFillBinding(), -1, -1))); - btnAddNewGame.setText(I18n.i18n("install.new_game")); - btnAddNewGame.setOnMouseClicked(e -> skinnable.addNewGame()); - toolbar.getChildren().add(btnAddNewGame); - - JFXButton btnImportModpack = new JFXButton(); - btnImportModpack.getStyleClass().add("jfx-tool-bar-button"); - btnImportModpack.textFillProperty().bind(Theme.foregroundFillBinding()); - btnImportModpack.setGraphic(wrap(SVG.importIcon(Theme.foregroundFillBinding(), -1, -1))); - btnImportModpack.setText(I18n.i18n("install.modpack")); - btnImportModpack.setOnMouseClicked(e -> skinnable.importModpack()); - toolbar.getChildren().add(btnImportModpack); - - 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.i18n("button.refresh")); - btnRefresh.setOnMouseClicked(e -> skinnable.refresh()); - toolbar.getChildren().add(btnRefresh); - - JFXButton btnModify = new JFXButton(); - btnModify.getStyleClass().add("jfx-tool-bar-button"); - btnModify.textFillProperty().bind(Theme.foregroundFillBinding()); - btnModify.setGraphic(wrap(SVG.gear(Theme.foregroundFillBinding(), -1, -1))); - btnModify.setText(I18n.i18n("settings.type.global.manage")); - btnModify.setOnMouseClicked(e -> skinnable.modifyGlobalGameSettings()); - toolbar.getChildren().add(btnModify); - - root.setTop(toolbar); - } - - { - SpinnerPane center = new SpinnerPane(); - center.loadingProperty().bind(skinnable.loadingProperty()); - center.getStyleClass().add("large-spinner-pane"); - - ScrollPane scrollPane = new ScrollPane(); - scrollPane.setFitToWidth(true); - - VBox gameList = new VBox(); - gameList.maxWidthProperty().bind(scrollPane.widthProperty()); - gameList.setSpacing(10); - gameList.setStyle("-fx-padding: 10 10 10 10;"); - - Bindings.bindContent(gameList.getChildren(), skinnable.itemsProperty()); - - scrollPane.setContent(gameList); - JFXScrollPane.smoothScrolling(scrollPane); - - center.setContent(scrollPane); - root.setCenter(center); - } - - getChildren().setAll(root); - } -} 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 85e15b849..5653b7db7 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,12 +19,8 @@ package org.jackhuang.hmcl.ui.versions; 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; @@ -36,6 +32,7 @@ import org.jackhuang.hmcl.task.Schedulers; import org.jackhuang.hmcl.task.Task; import org.jackhuang.hmcl.ui.Controllers; import org.jackhuang.hmcl.ui.FXUtils; +import org.jackhuang.hmcl.ui.ListPageBase; import org.jackhuang.hmcl.util.Logging; import org.jackhuang.hmcl.util.io.FileUtils; @@ -50,9 +47,7 @@ import java.util.stream.Collectors; import static org.jackhuang.hmcl.ui.FXUtils.runInFX; import static org.jackhuang.hmcl.util.i18n.I18n.i18n; -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); +public final class ModListPage extends ListPageBase { private final BooleanProperty modded = new SimpleBooleanProperty(this, "modded", false); private JFXTabPane parentTab; @@ -143,7 +138,7 @@ public final class ModListPage extends Control { this.parentTab = parentTab; } - public void removeSelectedMods(ObservableList> selectedItems) { + public void removeSelected(ObservableList> selectedItems) { try { modManager.removeMods(selectedItems.stream() .map(TreeItem::getValue) @@ -155,44 +150,20 @@ public final class ModListPage extends Control { } } - public void enableSelectedMods(ObservableList> selectedItems) { + public void enableSelected(ObservableList> selectedItems) { selectedItems.stream() .map(TreeItem::getValue) .map(ModListPageSkin.ModInfoObject::getModInfo) .forEach(info -> info.setActive(true)); } - public void disableSelectedMods(ObservableList> selectedItems) { + public void disableSelected(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; - } - public boolean isModded() { return modded.get(); } 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 dc1ad0a04..654334eea 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 @@ -17,36 +17,38 @@ */ package org.jackhuang.hmcl.ui.versions; -import com.jfoenix.controls.*; +import com.jfoenix.controls.JFXTreeTableColumn; +import com.jfoenix.controls.JFXTreeTableView; +import com.jfoenix.controls.RecursiveTreeItem; 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.beans.property.ObjectProperty; +import javafx.beans.property.SimpleObjectProperty; import javafx.geometry.Insets; import javafx.scene.Node; -import javafx.scene.control.*; +import javafx.scene.control.Label; +import javafx.scene.control.SelectionMode; +import javafx.scene.control.SkinBase; import javafx.scene.layout.BorderPane; import javafx.scene.layout.HBox; import javafx.scene.layout.StackPane; -import javafx.scene.text.TextAlignment; -import javafx.util.Callback; 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.JFXCheckBoxTreeTableCell; import org.jackhuang.hmcl.ui.construct.SpinnerPane; +import org.jackhuang.hmcl.ui.construct.TwoLineListItem; -import java.util.function.Function; - -import static org.jackhuang.hmcl.ui.SVG.wrap; +import static org.jackhuang.hmcl.ui.FXUtils.setupCellValueFactory; +import static org.jackhuang.hmcl.ui.FXUtils.wrapMargin; +import static org.jackhuang.hmcl.ui.ToolbarListPageSkin.createToolbarButton; +import static org.jackhuang.hmcl.util.StringUtils.isNotBlank; import static org.jackhuang.hmcl.util.i18n.I18n.i18n; -public class ModListPageSkin extends SkinBase { +class ModListPageSkin extends SkinBase { - public ModListPageSkin(ModListPage skinnable) { + ModListPageSkin(ModListPage skinnable) { super(skinnable); StackPane pane = new StackPane(); @@ -61,46 +63,14 @@ public class ModListPageSkin extends SkinBase { 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); - + toolbar.getChildren().add(createToolbarButton(i18n("button.refresh"), SVG::refresh, skinnable::refresh)); + toolbar.getChildren().add(createToolbarButton(i18n("mods.add"), SVG::plus, skinnable::add)); + toolbar.getChildren().add(createToolbarButton(i18n("mods.remove"), SVG::delete, () -> + skinnable.removeSelected(tableView.getSelectionModel().getSelectedItems()))); + toolbar.getChildren().add(createToolbarButton(i18n("mods.enable"), SVG::check, () -> + skinnable.enableSelected(tableView.getSelectionModel().getSelectedItems()))); + toolbar.getChildren().add(createToolbarButton(i18n("mods.disable"), SVG::close, () -> + skinnable.disableSelected(tableView.getSelectionModel().getSelectedItems()))); root.setTop(toolbar); } @@ -109,6 +79,7 @@ public class ModListPageSkin extends SkinBase { center.getStyleClass().add("large-spinner-pane"); center.loadingProperty().bind(skinnable.loadingProperty()); + tableView.getStyleClass().add("no-header"); tableView.setShowRoot(false); tableView.setEditable(true); tableView.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE); @@ -121,18 +92,10 @@ public class ModListPageSkin extends SkinBase { 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 detailColumn = new JFXTreeTableColumn<>(); + setupCellValueFactory(detailColumn, ModInfoObject::nodeProperty); - 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.getColumns().setAll(activeColumn, detailColumn); tableView.setColumnResizePolicy(JFXTreeTableView.CONSTRAINED_RESIZE_POLICY); center.setContent(tableView); @@ -150,44 +113,33 @@ public class ModListPageSkin extends SkinBase { getChildren().setAll(pane); } - 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 { + static class ModInfoObject extends RecursiveTreeObject { private final BooleanProperty active; - private final StringProperty fileName; - private final StringProperty version; private final ModInfo modInfo; + private final ObjectProperty node; - public ModInfoObject(ModInfo modInfo) { + ModInfoObject(ModInfo modInfo) { this.modInfo = modInfo; this.active = modInfo.activeProperty(); - this.fileName = new SimpleStringProperty(modInfo.getFileName()); - this.version = new SimpleStringProperty(modInfo.getVersion()); + StringBuilder message = new StringBuilder(modInfo.getName()); + if (isNotBlank(modInfo.getVersion())) + message.append(", ").append(i18n("archive.version")).append(": ").append(modInfo.getVersion()); + if (isNotBlank(modInfo.getGameVersion())) + message.append(", ").append(i18n("archive.game_version")).append(": ").append(modInfo.getGameVersion()); + if (isNotBlank(modInfo.getAuthors())) + message.append(", ").append(i18n("archive.author")).append(": ").append(modInfo.getAuthors()); + this.node = new SimpleObjectProperty<>(wrapMargin(new TwoLineListItem(modInfo.getFileName(), message.toString()), new Insets(8, 0, 8, 0))); } - public BooleanProperty activeProperty() { + BooleanProperty activeProperty() { return active; } - public StringProperty fileNameProperty() { - return fileName; + ObjectProperty nodeProperty() { + return node; } - public StringProperty versionProperty() { - return version; - } - - public ModInfo getModInfo() { + ModInfo getModInfo() { return modInfo; } } diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/WorldListPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/WorldListPage.java index d1cfb0ef2..0db69d146 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/WorldListPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/WorldListPage.java @@ -17,38 +17,32 @@ */ package org.jackhuang.hmcl.ui.versions; +import com.jfoenix.controls.JFXCheckBox; 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.Node; import javafx.stage.FileChooser; import org.jackhuang.hmcl.game.GameVersion; import org.jackhuang.hmcl.game.World; import org.jackhuang.hmcl.setting.Profile; +import org.jackhuang.hmcl.setting.Theme; import org.jackhuang.hmcl.task.Schedulers; import org.jackhuang.hmcl.task.Task; -import org.jackhuang.hmcl.ui.Controllers; -import org.jackhuang.hmcl.ui.FXUtils; -import org.jackhuang.hmcl.ui.ListPage; +import org.jackhuang.hmcl.ui.*; import org.jackhuang.hmcl.util.Logging; import org.jackhuang.hmcl.util.io.FileUtils; import java.io.File; import java.nio.file.FileAlreadyExistsException; import java.nio.file.Path; +import java.util.Arrays; import java.util.List; import java.util.logging.Level; import java.util.stream.Collectors; import static org.jackhuang.hmcl.util.i18n.I18n.i18n; -public class WorldListPage extends Control { - private final ListProperty items = new SimpleListProperty<>(this, "items", FXCollections.observableArrayList()); - private final BooleanProperty loading = new SimpleBooleanProperty(this, "loading", false); +public class WorldListPage extends ListPageBase { private final BooleanProperty showAll = new SimpleBooleanProperty(this, "showAll", false); private Path savesDir; @@ -71,8 +65,8 @@ public class WorldListPage extends Control { } @Override - protected Skin createDefaultSkin() { - return new WorldListPageSkin(this); + protected ToolbarListPageSkin createDefaultSkin() { + return new WorldListPageSkin(); } public void loadVersion(Profile profile, String id) { @@ -145,27 +139,23 @@ public class WorldListPage extends Control { this.showAll.set(showAll); } - public ObservableList getItems() { - return items.get(); - } + private class WorldListPageSkin extends ToolbarListPageSkin { - public ListProperty itemsProperty() { - return items; - } + WorldListPageSkin() { + super(WorldListPage.this); + } - public void setItems(ObservableList items) { - this.items.set(items); - } + @Override + protected List initializeToolbar(WorldListPage skinnable) { + JFXCheckBox chkShowAll = new JFXCheckBox(); + chkShowAll.getStyleClass().add("jfx-tool-bar-checkbox"); + chkShowAll.textFillProperty().bind(Theme.foregroundFillBinding()); + chkShowAll.setText(i18n("world.show_all")); + chkShowAll.selectedProperty().bindBidirectional(skinnable.showAllProperty()); - public boolean isLoading() { - return loading.get(); - } - - public BooleanProperty loadingProperty() { - return loading; - } - - public void setLoading(boolean loading) { - this.loading.set(loading); + return Arrays.asList(chkShowAll, + createToolbarButton(i18n("button.refresh"), SVG::refresh, skinnable::refresh), + createToolbarButton(i18n("world.add"), SVG::plus, skinnable::add)); + } } } diff --git a/HMCL/src/main/resources/assets/css/root.css b/HMCL/src/main/resources/assets/css/root.css index 15168e2bc..9dbd11e18 100644 --- a/HMCL/src/main/resources/assets/css/root.css +++ b/HMCL/src/main/resources/assets/css/root.css @@ -1025,6 +1025,12 @@ * * *******************************************************************************/ +.no-header .column-header-background { + -fx-max-height: 0; + -fx-pref-height: 0; + -fx-min-height: 0; +} + .tree-table-view { -fx-tree-table-color: rgba(82, 100, 174, 0.4); -fx-tree-table-rippler-color: rgba(82, 100, 174, 0.6); @@ -1075,7 +1081,7 @@ .tree-table-view .tree-table-cell { -fx-border-width: 0 0 0 0; - -fx-padding: 8 0 8 0; + -fx-padding: 0 0 0 0; /*-fx-alignment: top-center;*/ } diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/Datapack.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/Datapack.java index fab3e84b4..cfe1d3009 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/Datapack.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/Datapack.java @@ -24,7 +24,8 @@ import javafx.beans.property.BooleanProperty; import javafx.beans.property.SimpleBooleanProperty; import javafx.collections.FXCollections; import javafx.collections.ObservableList; -import org.jackhuang.hmcl.util.*; +import org.jackhuang.hmcl.util.Logging; +import org.jackhuang.hmcl.util.StringUtils; import org.jackhuang.hmcl.util.gson.JsonUtils; import org.jackhuang.hmcl.util.gson.Validation; import org.jackhuang.hmcl.util.io.CompressingUtils; @@ -128,7 +129,7 @@ public class Datapack { isMultiple = false; try { PackMcMeta pack = JsonUtils.fromNonNullJson(FileUtils.readText(mcmeta), PackMcMeta.class); - info.add(new Pack(path, FileUtils.getNameWithoutExtension(path), pack.getPackInfo().getDescription(), this)); + Platform.runLater(() -> info.add(new Pack(path, FileUtils.getNameWithoutExtension(path), pack.getPackInfo().getDescription(), this))); } catch (IOException | JsonParseException e) { Logging.LOG.log(Level.WARNING, "Failed to read datapack " + path, e); } @@ -192,7 +193,7 @@ public class Datapack { } } - this.info.setAll(info); + Platform.runLater(() -> this.info.setAll(info)); } public static class Pack {