diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/setting/StyleSheets.java b/HMCL/src/main/java/org/jackhuang/hmcl/setting/StyleSheets.java index f9d2d54902..dd401cb5ce 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/setting/StyleSheets.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/setting/StyleSheets.java @@ -56,7 +56,8 @@ public final class StyleSheets { getFontStyleSheet(), getThemeStyleSheet(), getBrightnessStyleSheet(), - "/assets/css/root.css" + "/assets/css/root.css", + "/assets/css/download-task-list.css" }; stylesheets = FXCollections.observableList(Arrays.asList(array)); diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/Controllers.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/Controllers.java index 1754584948..6aac9d8dd8 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/Controllers.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/Controllers.java @@ -56,6 +56,7 @@ import org.jackhuang.hmcl.ui.construct.*; import org.jackhuang.hmcl.ui.construct.MessageDialogPane.MessageType; import org.jackhuang.hmcl.ui.decorator.DecoratorController; +import org.jackhuang.hmcl.ui.download.DifferentDownloadTask2OneTask; import org.jackhuang.hmcl.ui.download.DownloadPage; import org.jackhuang.hmcl.ui.download.ModpackInstallWizardProvider; import org.jackhuang.hmcl.ui.main.LauncherSettingsPage; @@ -580,18 +581,20 @@ public static CompletableFuture>> prom } public static TaskExecutorDialogPane taskDialog(TaskExecutor executor, String title, TaskCancellationAction onCancel) { - TaskExecutorDialogPane pane = new TaskExecutorDialogPane(onCancel); - pane.setTitle(title); - pane.setExecutor(executor); - dialog(pane); - return pane; +// TaskExecutorDialogPane pane = new TaskExecutorDialogPane(onCancel); +// pane.setExecutor(executor); +// dialog(pane); +// pane.setTitle(title); + return null; } public static TaskExecutorDialogPane taskDialog(Task task, String title, TaskCancellationAction onCancel) { TaskExecutor executor = task.executor(); - TaskExecutorDialogPane pane = taskDialog(executor, title, onCancel); - executor.start(); - return pane; + String name = task.getName(); +// TaskExecutorDialogPane pane = taskDialog(executor, title, onCancel); +// TaskExecutorDialogPane pane = new TaskExecutorDialogPane(onCancel); + new DifferentDownloadTask2OneTask().ExecutorTask2OneTask(executor, name); + return null; } public static void navigate(Node node) { diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/DifferentDownloadTask2OneTask.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/DifferentDownloadTask2OneTask.java new file mode 100644 index 0000000000..36a07f2f42 --- /dev/null +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/DifferentDownloadTask2OneTask.java @@ -0,0 +1,47 @@ +/* + * Hello Minecraft! Launcher + * Copyright (C) 2026 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.download; + +import org.jackhuang.hmcl.task.Task; +import org.jackhuang.hmcl.task.TaskExecutor; +import org.jackhuang.hmcl.ui.versions.DownloadTaskList; + +public class DifferentDownloadTask2OneTask{ + + private static DownloadTaskList activeDownloadTaskList; + + public static void setActiveDownloadTaskList(DownloadTaskList list) { + activeDownloadTaskList = list; + } + + public void WizardTask2OneTask(Task task) { + TaskExecutor executor = task.executor(); + if (activeDownloadTaskList != null) { + activeDownloadTaskList.addDownloadEntry(executor, task.getName()); + System.out.println("gotolist"); + } + } + + public void ExecutorTask2OneTask(TaskExecutor executor, String name) { + if (activeDownloadTaskList != null) { + activeDownloadTaskList.addDownloadEntry(executor, name); + System.out.println("gotolist"); + } + } + +} diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/DownloadEntry.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/DownloadEntry.java new file mode 100644 index 0000000000..39c6f13d73 --- /dev/null +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/DownloadEntry.java @@ -0,0 +1,72 @@ +/* + * Hello Minecraft! Launcher + * Copyright (C) 2026 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.download; + +import javafx.beans.binding.Bindings; +import javafx.beans.binding.StringBinding; +import javafx.beans.property.DoubleProperty; +import javafx.beans.property.SimpleDoubleProperty; +import javafx.beans.property.SimpleStringProperty; +import javafx.beans.property.StringProperty; +import org.jackhuang.hmcl.task.TaskExecutor; + +import static org.jackhuang.hmcl.util.i18n.I18n.i18n; + +public class DownloadEntry { + private final short ProgressIsUncertain = -1; + + public static final String STATUS_WAITING = "waiting"; + public static final String STATUS_RUNNING = "running"; + public static final String STATUS_DONE = "done"; + public static final String STATUS_FAILED = "failed"; + + private final StringProperty name = new SimpleStringProperty(); + private final StringProperty status = new SimpleStringProperty(); + private final DoubleProperty progress = new SimpleDoubleProperty(ProgressIsUncertain); + private final TaskExecutor executor; + + + public DownloadEntry(TaskExecutor executor, String name) { + this.executor = executor; + this.name.set(name); + this.status.set(STATUS_WAITING); + } + + + public String getName() { return name.get(); } + public StringProperty nameProperty() { return name; } + + public String getStatus() { return status.get(); } + public StringProperty statusProperty() { return status; } + + public StringBinding statusI18NBinding() { + return Bindings.createStringBinding(() -> { + String s = status.get(); + if (STATUS_DONE.equals(s)) return i18n("download.task.status.done"); + if (STATUS_FAILED.equals(s)) return i18n("download.task.status.failed"); + if (STATUS_RUNNING.equals(s)) return i18n("download.task.status.running"); + if (STATUS_WAITING.equals(s)) return i18n("download.task.status.waiting"); + return i18n("download.task.status.null"); + }, status); + } + + public double getProgress() { return progress.get(); } + public DoubleProperty progressProperty() { return progress; } + + public TaskExecutor getExecutor() { return executor; } +} diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/DownloadPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/DownloadPage.java index 8a7d913e9d..444da7e15f 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/DownloadPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/DownloadPage.java @@ -31,6 +31,7 @@ import org.jackhuang.hmcl.task.FileDownloadTask; import org.jackhuang.hmcl.task.Schedulers; import org.jackhuang.hmcl.task.Task; +import org.jackhuang.hmcl.task.TaskExecutor; import org.jackhuang.hmcl.ui.Controllers; import org.jackhuang.hmcl.ui.FXUtils; import org.jackhuang.hmcl.ui.SVG; @@ -42,10 +43,7 @@ import org.jackhuang.hmcl.ui.construct.Validator; import org.jackhuang.hmcl.ui.decorator.DecoratorAnimatedPage; import org.jackhuang.hmcl.ui.decorator.DecoratorPage; -import org.jackhuang.hmcl.ui.versions.DownloadListPage; -import org.jackhuang.hmcl.ui.versions.HMCLLocalizedDownloadListPage; -import org.jackhuang.hmcl.ui.versions.VersionPage; -import org.jackhuang.hmcl.ui.versions.Versions; +import org.jackhuang.hmcl.ui.versions.*; import org.jackhuang.hmcl.ui.wizard.Navigation; import org.jackhuang.hmcl.ui.wizard.WizardController; import org.jackhuang.hmcl.ui.wizard.WizardProvider; @@ -76,8 +74,10 @@ public class DownloadPage extends DecoratorAnimatedPage implements DecoratorPage private final TabHeader.Tab resourcePackTab = new TabHeader.Tab<>("resourcePackTab"); private final TabHeader.Tab shaderTab = new TabHeader.Tab<>("shaderTab"); private final TabHeader.Tab worldTab = new TabHeader.Tab<>("worldTab"); + private final TabHeader.Tab downloadListTab = new TabHeader.Tab<>("downloadListTab"); private final TransitionPane transitionPane = new TransitionPane(); private final DownloadNavigator versionPageNavigator = new DownloadNavigator(); + private final DownloadTaskList downloadTaskList; private WeakListenerHolder listenerHolder; @@ -86,6 +86,9 @@ public DownloadPage() { } public DownloadPage(String uploadVersion) { + this.downloadTaskList = new DownloadTaskList(); + DifferentDownloadTask2OneTask.setActiveDownloadTaskList(downloadTaskList); + newGameTab.setNodeSupplier(loadVersionFor(() -> new VersionsPage(versionPageNavigator, i18n("install.installer.choose", i18n("install.installer.game")), "", DownloadProviders.getDownloadProvider(), "game", versionPageNavigator::onGameSelected))); modpackTab.setNodeSupplier(loadVersionFor(() -> { @@ -99,31 +102,52 @@ public DownloadPage(String uploadVersion) { page.getActions().add(installLocalModpackButton); return page; })); + + //下面是设置搜索界面的 modTab.setNodeSupplier(loadVersionFor(() -> HMCLLocalizedDownloadListPage.ofMod((downloadProvider, profile, version, mod, file) -> download(downloadProvider, profile, version, file, "mods"), true))); resourcePackTab.setNodeSupplier(loadVersionFor(() -> HMCLLocalizedDownloadListPage.ofResourcePack((downloadProvider, profile, version, mod, file) -> download(downloadProvider, profile, version, file, "resourcepacks"), true))); shaderTab.setNodeSupplier(loadVersionFor(() -> HMCLLocalizedDownloadListPage.ofShaderPack((downloadProvider, profile, version, mod, file) -> download(downloadProvider, profile, version, file, "shaderpacks"), true))); worldTab.setNodeSupplier(loadVersionFor(() -> new DownloadListPage(CurseForgeRemoteModRepository.WORLDS))); tab = new TabHeader(transitionPane, newGameTab, modpackTab, modTab, resourcePackTab, shaderTab, worldTab); + downloadListTab.setNodeSupplier(() -> downloadTaskList); Profiles.registerVersionsListener(this::loadVersions); tab.select(newGameTab); + //这一堆规划了侧边栏的哪个按钮在哪个栏目 AdvancedListBox sideBar = new AdvancedListBox() .startCategory(i18n("download.game").toUpperCase(Locale.ROOT)) .addNavigationDrawerTab(tab, newGameTab, i18n("game"), SVG.STADIA_CONTROLLER, SVG.STADIA_CONTROLLER_FILL) .addNavigationDrawerTab(tab, modpackTab, i18n("modpack"), SVG.PACKAGE2, SVG.PACKAGE2_FILL) + .startCategory(i18n("download.content").toUpperCase(Locale.ROOT)) .addNavigationDrawerTab(tab, modTab, i18n("mods"), SVG.EXTENSION, SVG.EXTENSION_FILL) .addNavigationDrawerTab(tab, resourcePackTab, i18n("resourcepack"), SVG.TEXTURE) .addNavigationDrawerTab(tab, shaderTab, i18n("download.shader"), SVG.WB_SUNNY, SVG.WB_SUNNY_FILL) - .addNavigationDrawerTab(tab, worldTab, i18n("world"), SVG.PUBLIC); + .addNavigationDrawerTab(tab, worldTab, i18n("world"), SVG.PUBLIC) + + .startCategory(i18n("download.others").toUpperCase(Locale.ROOT)) + .addNavigationDrawerTab(tab, downloadListTab, i18n("download.task"), SVG.DOWNLOAD); + FXUtils.setLimitWidth(sideBar, 200); setLeft(sideBar); setCenter(transitionPane); } + public DownloadTaskList getDownloadTaskList() { + return downloadTaskList; + } + + public void addDownloadTask(TaskExecutor executor, String name) { + downloadTaskList.addDownloadEntry(executor, name); + } + + public void addDownloadTask(Task task) { + addDownloadTask(task.executor(), task.getName()); + } + private static Supplier loadVersionFor(Supplier nodeSupplier) { return () -> { T node = nodeSupplier.get(); diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/DownloadTaskList.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/DownloadTaskList.java new file mode 100644 index 0000000000..8147f89cb9 --- /dev/null +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/DownloadTaskList.java @@ -0,0 +1,303 @@ +/* + * Hello Minecraft! Launcher + * Copyright (C) 2026 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 javafx.application.Platform; +import javafx.beans.binding.Bindings; +import javafx.beans.property.ReadOnlyObjectProperty; +import javafx.beans.property.ReadOnlyObjectWrapper; +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; +import javafx.event.ActionEvent; +import javafx.event.EventHandler; +import javafx.geometry.Insets; +import javafx.geometry.Pos; +import javafx.scene.control.*; +import javafx.scene.layout.*; +import org.jackhuang.hmcl.setting.Profile; +import org.jackhuang.hmcl.task.FetchTask; +import org.jackhuang.hmcl.task.Task; +import org.jackhuang.hmcl.task.TaskExecutor; +import org.jackhuang.hmcl.task.TaskListener; +import org.jackhuang.hmcl.ui.download.DifferentDownloadTask2OneTask; +import org.jackhuang.hmcl.ui.construct.PageAware; +import org.jackhuang.hmcl.ui.download.DownloadEntry; + +import static org.jackhuang.hmcl.util.i18n.I18n.formatSpeed; +import static org.jackhuang.hmcl.util.i18n.I18n.i18n; + +public class DownloadTaskList extends DownloadListPage implements PageAware{ + private final ObservableList entries = FXCollections.observableArrayList(); + private final ListView listView = new ListView<>(entries); + protected final ReadOnlyObjectWrapper state = new ReadOnlyObjectWrapper<>(); + + public ObservableList getEntries() { + return entries; + } + + public DownloadTaskList() { + super(null); + listView.setCellFactory(lv -> new DownloadEntryCell()); + } + + public void cleanupFinishedTasks() { + Platform.runLater(() -> { + entries.removeIf(entry -> { + String status = entry.getStatus(); + return DownloadEntry.STATUS_DONE.equals(status) || (status != null && status.startsWith(DownloadEntry.STATUS_FAILED)); + }); + }); + } + + @Override + public void onPageShown() { + DifferentDownloadTask2OneTask.setActiveDownloadTaskList(this); + } + + @Override + public void onPageHidden() { + //打开页面时运行 +// cleanupFinishedTasks(); + // DifferentDownloadTask2OneTask.setActiveDownloadTaskList(null); + } + + @Override + public void loadVersion(Profile profile, String version) {} + + @Override + public ReadOnlyObjectProperty stateProperty() { + return state.getReadOnlyProperty(); + } + + @Override + protected Skin createDefaultSkin() { + return new DownloadTaskListSkin(this); + } + + private static class DownloadTaskListSkin extends SkinBase { + protected DownloadTaskListSkin(DownloadTaskList control) { + super(control); + BorderPane pane = new BorderPane(); + pane.setCenter(control.listView); + + HBox bottomBar = new HBox(10); + HBox labelBar = new HBox(10); + + Insets insets = new Insets(10); + + bottomBar.setAlignment(Pos.CENTER_RIGHT); + bottomBar.setPadding(insets); + + labelBar.setAlignment(Pos.CENTER_LEFT); + labelBar.setPadding(new Insets(5, 10, 5, 10)); + + Label globalSpeedLabel = new Label(); + globalSpeedLabel.setAlignment(Pos.CENTER_LEFT); + globalSpeedLabel.setStyle("-fx-text-fill: black; -fx-font-size: 1em;"); + + Button clearBtn = new JFXButton(i18n("download.task.button.clear_uesless")); + clearBtn.getStyleClass().add("small-button"); + clearBtn.setOnAction(e -> control.cleanupFinishedTasks()); + + Button pauseAllBtn = new JFXButton(i18n("download.task.button.pause_all")); + pauseAllBtn.setOnAction(e -> control.getEntries().stream() + .filter(entry -> DownloadEntry.STATUS_RUNNING.equals(entry.getStatus())) + .forEach(entry -> entry.getExecutor().cancel() + )); + + pauseAllBtn.disableProperty().bind( + Bindings.createBooleanBinding( + () -> control.getEntries().stream().noneMatch(e -> DownloadEntry.STATUS_RUNNING.equals(e.getStatus())), + control.getEntries() + ) + ); + + Button retryFailedBtn = new JFXButton(i18n("download.task.button.retry_all")); + retryFailedBtn.setOnAction(e -> control.getEntries().stream() + .filter(entry -> entry.getStatus().startsWith(DownloadEntry.STATUS_FAILED)) + .forEach(entry -> { + entry.getExecutor().start(); + entry.statusProperty().set(DownloadEntry.STATUS_WAITING); + })); + + + FetchTask.SPEED_EVENT.register(event -> { + long bytesPerSec = event.getSpeed(); + Platform.runLater(() -> globalSpeedLabel.setText(formatSpeed(bytesPerSec))); + }); + + bottomBar.getChildren().addAll(pauseAllBtn, retryFailedBtn, clearBtn); + labelBar.getChildren().addAll(globalSpeedLabel); + + VBox bottomContainer = new VBox(5); + bottomContainer.getChildren().addAll(labelBar, bottomBar); + pane.setBottom(bottomContainer); + + getChildren().add(pane); + } + } + + private static class DownloadEntryCell extends ListCell { + private final JFXButton actionButton = new JFXButton(); + private final TitledPane titledPane = new TitledPane(); + private final ProgressBar progressBar = new ProgressBar(); + private final Label nameLabel = new Label(); + private final Label statusLabel = new Label(); + private final HBox titleBox = new HBox(8); // 间距8px + private final Region spacer = new Region(); + + + private javafx.beans.binding.Binding maxWidthBinding; + private EventHandler actionHandler; + + public DownloadEntryCell() { + nameLabel.setMaxWidth(350); + nameLabel.setTextOverrun(OverrunStyle.ELLIPSIS); + nameLabel.setStyle("-fx-font-weight: bold;"); + + HBox.setHgrow(nameLabel, Priority.ALWAYS); + HBox.setHgrow(spacer, Priority.ALWAYS); + HBox.setHgrow(actionButton, Priority.NEVER); + + statusLabel.setStyle("-fx-text-fill: gray;"); + + progressBar.setMaxWidth(Double.MAX_VALUE); + + titledPane.setContent(progressBar); + titledPane.setExpanded(false); + titledPane.setAnimated(false); + titledPane.setGraphic(titleBox); + titledPane.setText(null); + + actionButton.getStyleClass().add("small-button"); + actionButton.setFocusTraversable(false); + actionButton.setMinWidth(60); + actionButton.setVisible(false); + + titleBox.getChildren().setAll(nameLabel, spacer, statusLabel, actionButton); + titleBox.setAlignment(Pos.CENTER_LEFT); + + getStyleClass().add("download-task-cell"); + } + @Override + protected void updateItem(DownloadEntry entry, boolean empty) { + super.updateItem(entry, empty); + + if (actionHandler != null) { + actionButton.setOnAction(null); + actionHandler = null; + } + + if (maxWidthBinding != null) { + nameLabel.maxWidthProperty().unbind(); + maxWidthBinding = null; + } + + if (empty || entry == null) { + setGraphic(null); + progressBar.progressProperty().unbind(); + nameLabel.textProperty().unbind(); + statusLabel.textProperty().unbind(); + actionButton.setVisible(false); + } else { + nameLabel.textProperty().bind(entry.nameProperty()); + statusLabel.textProperty().bind(entry.statusI18NBinding()); + progressBar.progressProperty().bind(entry.progressProperty()); + + String status = entry.getStatus(); + + if (DownloadEntry.STATUS_RUNNING.equals(status) || DownloadEntry.STATUS_WAITING.equals(status)) { + actionButton.setText(i18n("button.cancel")); + actionButton.setVisible(true); + actionHandler = e -> entry.getExecutor().cancel(); + actionButton.setOnAction(actionHandler); + } else if (DownloadEntry.STATUS_FAILED.equals(status)) { + actionButton.setText(i18n("button.retry")); + actionButton.setVisible(true); + actionHandler = e -> { + // 重试 + entry.getExecutor().start(); + entry.statusProperty().set(DownloadEntry.STATUS_WAITING); + }; + actionButton.setOnAction(actionHandler); + } else { + actionButton.setVisible(false); + } + + ListView listView = getListView(); + if (listView != null) { + maxWidthBinding = Bindings.createDoubleBinding( + () -> { + double listWidth = listView.getWidth(); + double reserved = 160; + return Math.max(50, listWidth - reserved); + }, + listView.widthProperty() + ); + nameLabel.maxWidthProperty().bind(maxWidthBinding); + } + + titledPane.setExpanded(DownloadEntry.STATUS_RUNNING.equals(entry.getStatus())); + + setGraphic(titledPane); + } + } + } + + public void addDownloadEntry(TaskExecutor executor, String name) { + DownloadEntry entry = new DownloadEntry(executor, name); + executor.addTaskListener(new TaskListener() { + @Override + public void onRunning(Task task) { + Platform.runLater(() -> entry.statusProperty().set(DownloadEntry.STATUS_RUNNING)); + } + + @Override + public void onPropertiesUpdate(Task task) { + Object progressObj = task.getProperties().get("progress"); + if (progressObj instanceof Number) { + double progress = ((Number) progressObj).doubleValue(); + Platform.runLater(() -> entry.progressProperty().set(progress)); + } + } + + @Override + public void onStop(boolean success, TaskExecutor executor) { + Platform.runLater(() -> { + if (success) { + entry.statusProperty().set(DownloadEntry.STATUS_DONE); + entry.progressProperty().set(1.0); + } else { + entry.statusProperty().set(DownloadEntry.STATUS_FAILED); + } + }); + } + + @Override + public void onFailed(Task task, Throwable throwable) { + Platform.runLater(() -> { + entry.statusProperty().set(DownloadEntry.STATUS_FAILED + ": " + throwable.getMessage()); + entry.progressProperty().set(1.0); + }); + } + }); + executor.start(); + Platform.runLater(() -> entries.add(entry)); + } +} \ No newline at end of file diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/wizard/AbstractWizardDisplayer.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/wizard/AbstractWizardDisplayer.java index eb7905df85..98430b4444 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/wizard/AbstractWizardDisplayer.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/wizard/AbstractWizardDisplayer.java @@ -21,7 +21,6 @@ import org.jackhuang.hmcl.task.Schedulers; import org.jackhuang.hmcl.task.Task; import org.jackhuang.hmcl.task.TaskExecutor; -import org.jackhuang.hmcl.ui.construct.TaskListPane; import org.jackhuang.hmcl.util.SettingsMap; import java.util.Queue; @@ -35,12 +34,12 @@ public AbstractWizardDisplayer(Queue cancelQueue) { @Override public void handleTask(SettingsMap settings, Task task) { - TaskExecutor executor = task.withRunAsync(Schedulers.javafx(), this::navigateToSuccess).executor(); - TaskListPane pane = new TaskListPane(); - pane.setExecutor(executor); - navigateTo(pane, Navigation.NavigationDirection.FINISH); - cancelQueue.add(executor); - executor.start(); +// TaskExecutor executor = task.withRunAsync(Schedulers.javafx(), this::navigateToSuccess).executor(); +// TaskListPane pane = new TaskListPane(); +// pane.setExecutor(executor); +// navigateTo(pane, Navigation.NavigationDirection.FINISH); +// cancelQueue.add(executor); +// executor.start(); } @Override diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/wizard/WizardController.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/wizard/WizardController.java index c1a0b0bf28..0ea3e0d5b7 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/wizard/WizardController.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/wizard/WizardController.java @@ -19,6 +19,7 @@ import javafx.scene.Node; import org.jackhuang.hmcl.task.Task; +import org.jackhuang.hmcl.ui.download.DifferentDownloadTask2OneTask; import org.jackhuang.hmcl.util.SettingsMap; import java.util.*; @@ -31,6 +32,7 @@ public class WizardController implements Navigation { private final SettingsMap settings = new SettingsMap(); private final Stack pages = new Stack<>(); private boolean stopped = false; + DifferentDownloadTask2OneTask differentDownloadTask2OneTask = new DifferentDownloadTask2OneTask(); public WizardController(WizardDisplayer displayer) { this.displayer = displayer; @@ -136,7 +138,12 @@ public boolean canPrev() { public void onFinish() { Object result = provider.finish(settings); if (result instanceof Summary) displayer.navigateTo(((Summary) result).getComponent(), NavigationDirection.NEXT); - else if (result instanceof Task) displayer.handleTask(settings, ((Task) result)); + else if (result instanceof Task) { + Task task = (Task) result; + differentDownloadTask2OneTask.WizardTask2OneTask(task); + displayer.onEnd(); +// displayer.handleTask(settings, ((Task) result)); + } else if (result != null) throw new IllegalStateException("Unrecognized wizard result: " + result); } diff --git a/HMCL/src/main/resources/assets/css/download-task-list.css b/HMCL/src/main/resources/assets/css/download-task-list.css new file mode 100644 index 0000000000..be02dc5ed4 --- /dev/null +++ b/HMCL/src/main/resources/assets/css/download-task-list.css @@ -0,0 +1,38 @@ +/* 下载任务列表单元格 */ +.download-task-cell { + -fx-background-color: transparent; +} + +.download-task-cell .accordion { + -fx-background-color: transparent; + -fx-box-border: transparent; +} + +.download-task-cell .titled-pane { + -fx-background-color: transparent; + -fx-border-color: transparent; + -fx-text-fill: -fx-text-base-color; +} + +.download-task-cell .titled-pane > .title { + -fx-background-color: transparent; +} + +.download-task-cell .titled-pane > .content { + -fx-background-color: transparent; + -fx-border-color: transparent; +} + +.download-task-cell .progress-bar { + -fx-background-color: transparent; +} + +.download-task-cell .progress-bar > .track { + -fx-background-color: rgba(0, 0, 0, 0.1); +} + +.small-button { + -fx-padding: 2 8 2 8; + -fx-background-radius: 4; + -fx-font-size: 0.9em; +} diff --git a/HMCL/src/main/resources/assets/lang/I18N.properties b/HMCL/src/main/resources/assets/lang/I18N.properties index 6c12a0ffb3..ee51a0c823 100644 --- a/HMCL/src/main/resources/assets/lang/I18N.properties +++ b/HMCL/src/main/resources/assets/lang/I18N.properties @@ -385,6 +385,16 @@ download.javafx.prepare=Preparing to download download.speed.byte_per_second=%d B/s download.speed.kibibyte_per_second=%.1f KiB/s download.speed.megabyte_per_second=%.1f MiB/s +download.others=Others +download.task=Download Task +download.task.status.done=Done +download.task.status.failed=Failed +download.task.status.running=Running +download.task.status.waiting=Waiting +download.task.status.null=unknown +download.task.button.clear_uesless=clear +download.task.button.retry_all=Retry All +download.task.button.pause_all=Cancel All exception.access_denied=HMCL is unable to access the file "%s". It may be locked by another process.\n\ \n\ diff --git a/HMCL/src/main/resources/assets/lang/I18N_ar.properties b/HMCL/src/main/resources/assets/lang/I18N_ar.properties index f82e27343d..fcff3398fe 100644 --- a/HMCL/src/main/resources/assets/lang/I18N_ar.properties +++ b/HMCL/src/main/resources/assets/lang/I18N_ar.properties @@ -378,6 +378,16 @@ download.javafx.prepare=جارٍ التحضير للتنزيل download.speed.byte_per_second=%d B/s download.speed.kibibyte_per_second=%.1f KiB/s download.speed.megabyte_per_second=%.1f MiB/s +download.others=آخر +download.task=مهمة التنزيل +download.task.status.done=مكتمل +download.task.status.failed=فشل +download.task.status.running=جاري التنزيل +download.task.status.waiting=قيد الانتظار +download.task.status.null=غير معروف +download.task.button.clear_uesless=تنظيف +download.task.button.retry_all=إعادة المحاولة للكل +download.task.button.pause_all=إلغاء الكل exception.access_denied=لا يستطيع HMCL الوصول إلى الملف "%s". قد يكون مقفلاً بواسطة عملية أخرى.\n\ \n\ diff --git a/HMCL/src/main/resources/assets/lang/I18N_es.properties b/HMCL/src/main/resources/assets/lang/I18N_es.properties index b502a5121b..8db8eb0b86 100644 --- a/HMCL/src/main/resources/assets/lang/I18N_es.properties +++ b/HMCL/src/main/resources/assets/lang/I18N_es.properties @@ -352,6 +352,16 @@ download.javafx.notes=Estamos descargando dependencias para HMCL desde Internet. Nota: Si su velocidad de descarga es demasiado lenta, puede intentar cambiar a otro espejo. download.javafx.component=Descargando módulo %s download.javafx.prepare=Preparando la descarga +download.others=otro +download.task=tarea de descarga +download.task.status.done=completado +download.task.status.failed=fallido +download.task.status.running=descargando +download.task.status.waiting=esperando +download.task.status.null=desconocido +download.task.button.clear_uesless=limpiar +download.task.button.retry_all=Reintentar todo +download.task.button.pause_all=Cancelar todo exception.access_denied=HMCL no puede acceder al archivo %s. Puede estar bloqueado por otro proceso.\n\ \n\ diff --git a/HMCL/src/main/resources/assets/lang/I18N_ja.properties b/HMCL/src/main/resources/assets/lang/I18N_ja.properties index bd299ee5ac..7fe4e7d776 100644 --- a/HMCL/src/main/resources/assets/lang/I18N_ja.properties +++ b/HMCL/src/main/resources/assets/lang/I18N_ja.properties @@ -304,6 +304,16 @@ download.javafx=必要なランタイムコンポーネントのダウンロー download.javafx.notes=ネットワークを介したHMCLに必要なコンポーネントのダウンロード。\n [ダウンロードソースの変更]ボタンをクリックして詳細を表示し、ダウンロードソースを選択するか、[キャンセル]ボタンをクリックして停止して終了します。\n注:ダウンロード速度が遅すぎます。ダウンロードソースを変更してみてください。 download.javafx.component=ダウンロード中のモジュール %s download.javafx.prepare=ダウンロードする準備ができました +download.others=その他 +download.task=ダウンロードタスク +download.task.status.done=完了 +download.task.status.failed=失敗 +download.task.status.running=ダウンロード中 +download.task.status.waiting=待機中 +download.task.status.null=不明 +download.task.button.clear_uesless=クリア +download.task.button.retry_all=すべて再試行 +download.task.button.pause_all=すべてキャンセル exception.access_denied=ファイル %s にアクセスできないので、HMCL がファイルにアクセスできないか、ファイルが他のプログラムによって開かれています。\n\ 例えば、管理者でないユーザーは、他のアカウントの個人フォルダーにあるファイルにアクセスできない場合があります。\n\ diff --git a/HMCL/src/main/resources/assets/lang/I18N_ru.properties b/HMCL/src/main/resources/assets/lang/I18N_ru.properties index fefd634e1b..78581bb154 100644 --- a/HMCL/src/main/resources/assets/lang/I18N_ru.properties +++ b/HMCL/src/main/resources/assets/lang/I18N_ru.properties @@ -352,6 +352,16 @@ download.javafx.prepare=Подготовка к скачиванию download.speed.byte_per_second=%d Б/сек download.speed.kibibyte_per_second=%.1f КиБ/сек download.speed.megabyte_per_second=%.1f МиБ/сек +download.others=другой +download.task=задача на скачивание +download.task.status.done=завершён +download.task.status.failed=невдача +download.task.status.running=загрузка… +download.task.status.waiting=ожидание +download.task.status.null=неизвестно +download.task.button.clear_uesless=очистить +download.task.button.retry_all=Повторить всё +download.task.button.pause_all=Отменить всё exception.access_denied=Лаунчер не может получить доступ к файлу «%s», возможно он занят другим процессом.\n\ \n\ diff --git a/HMCL/src/main/resources/assets/lang/I18N_uk.properties b/HMCL/src/main/resources/assets/lang/I18N_uk.properties index 9920651da2..85df236249 100644 --- a/HMCL/src/main/resources/assets/lang/I18N_uk.properties +++ b/HMCL/src/main/resources/assets/lang/I18N_uk.properties @@ -350,6 +350,16 @@ download.javafx.prepare=Підготовка до завантаження download.speed.byte_per_second=%d Б/сек download.speed.kibibyte_per_second=%.1f КiБ/сек download.speed.megabyte_per_second=%.1f МiБ/сек +download.others=інший +download.task=завдання на завантаження +download.task.status.done=завершено +download.task.status.failed=невдача +download.task.status.running=завантажується +download.task.status.waiting=очікується +download.task.status.null=невідомо +download.task.button.clear_uesless=очистити +download.task.button.retry_all=Повторити все +download.task.button.pause_all=Скасувати все exception.access_denied=HMCL не може отримати доступ до файлу "%s". Можливо, він заблокований іншим процесом.\n\ \n\ diff --git a/HMCL/src/main/resources/assets/lang/I18N_zh.properties b/HMCL/src/main/resources/assets/lang/I18N_zh.properties index 69234dc765..2d0fedb0fc 100644 --- a/HMCL/src/main/resources/assets/lang/I18N_zh.properties +++ b/HMCL/src/main/resources/assets/lang/I18N_zh.properties @@ -378,6 +378,16 @@ download.javafx.prepare=準備開始下載 download.speed.byte_per_second=%d B/s download.speed.kibibyte_per_second=%.1f KiB/s download.speed.megabyte_per_second=%.1f MiB/s +download.others=其他 +download.task=下載任務 +download.task.status.done=成 +download.task.status.failed=敗 +download.task.status.running=下載中 +download.task.status.waiting=等待中 +download.task.status.null=未知 +download.task.button.clear_uesless=清理 +download.task.button.retry_all=重試所有 +download.task.button.pause_all=取消所有 exception.access_denied=無法存取檔案「%s」。因為 HMCL 沒有對該檔案的訪問權限,或者該檔案已被其他程式開啟。\n\ 請你檢查目前作業系統帳戶是否能訪存取檔案,比如非管理員使用者可能不能訪問其他帳戶的個人目錄內的檔案。\n\ 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 46827e9ae1..af5bd4c487 100644 --- a/HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties +++ b/HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties @@ -380,6 +380,16 @@ download.javafx.prepare=准备开始下载 download.speed.byte_per_second=%d B/s download.speed.kibibyte_per_second=%.1f KiB/s download.speed.megabyte_per_second=%.1f MiB/s +download.others=其他 +download.task=下载任务 +download.task.status.done=完成 +download.task.status.failed=失败 +download.task.status.running=下载中 +download.task.status.waiting=等待中 +download.task.status.null=未知 +download.task.button.clear_uesless=清理 +download.task.button.retry_all=重试所有 +download.task.button.pause_all=取消所有 exception.access_denied=无法访问文件“%s”。HMCL 没有对该文件的访问权限,或者该文件已被其他程序打开。\n\ 请你检查当前操作系统账户是否能访问该文件,比如非管理员用户可能无法访问其他账户的个人文件夹内的文件。\n\ diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/task/AsyncTaskExecutor.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/task/AsyncTaskExecutor.java index fe16a8a2a1..b5759874e0 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/task/AsyncTaskExecutor.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/task/AsyncTaskExecutor.java @@ -91,8 +91,8 @@ public synchronized void cancel() { if (future == null) { throw new IllegalStateException("Cannot cancel a not started TaskExecutor"); } - cancelled = true; + future.cancel(true); } private CompletableFuture executeTasksExceptionally(Task parentTask, Collection> tasks) { diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 3c4f0c2cde..2bcc9d09af 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-9.4.0-bin.zip +distributionUrl=https\://mirrors.cloud.tencent.com/gradle/gradle-9.4.0-bin.zip networkTimeout=120000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME