From 1423329c267f8119550de85aa64dd3fb33975e4c Mon Sep 17 00:00:00 2001 From: lokins Date: Wed, 4 Feb 2026 23:15:35 +0800 Subject: [PATCH 01/20] update --- .../org/jackhuang/hmcl/ui/Controllers.java | 26 +++ .../ui/construct/TaskExecutorDialogPane.java | 49 ++++- .../hmcl/ui/construct/TaskListPane.java | 12 + .../hmcl/ui/download/DownloadPage.java | 53 +++-- .../ModpackInstallWizardProvider.java | 6 + .../ui/download/ModpackSelectionPage.java | 7 +- .../UpdateInstallerWizardProvider.java | 13 ++ .../VanillaInstallWizardProvider.java | 3 + .../hmcl/ui/main/JavaDownloadDialog.java | 7 +- .../org/jackhuang/hmcl/ui/main/RootPage.java | 11 + .../jackhuang/hmcl/ui/task/TaskCenter.java | 120 ++++++++++ .../hmcl/ui/task/TaskCenterPage.java | 207 ++++++++++++++++++ .../jackhuang/hmcl/ui/versions/Versions.java | 5 +- .../TaskExecutorDialogWizardDisplayer.java | 26 ++- .../resources/assets/lang/I18N.properties | 7 + 15 files changed, 525 insertions(+), 27 deletions(-) create mode 100644 HMCL/src/main/java/org/jackhuang/hmcl/ui/task/TaskCenter.java create mode 100644 HMCL/src/main/java/org/jackhuang/hmcl/ui/task/TaskCenterPage.java 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 4993bb7019..5f2e54702a 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/Controllers.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/Controllers.java @@ -59,6 +59,7 @@ import org.jackhuang.hmcl.ui.download.ModpackInstallWizardProvider; import org.jackhuang.hmcl.ui.main.LauncherSettingsPage; import org.jackhuang.hmcl.ui.main.RootPage; +import org.jackhuang.hmcl.ui.task.TaskCenter; import org.jackhuang.hmcl.ui.terracotta.TerracottaPage; import org.jackhuang.hmcl.ui.versions.GameListPage; import org.jackhuang.hmcl.ui.versions.VersionPage; @@ -534,6 +535,31 @@ public static TaskExecutorDialogPane taskDialog(Task task, String title, Task return pane; } + public static TaskExecutorDialogPane downloadTaskDialog(Task task, String title, TaskCancellationAction onCancel, String detail) { + TaskExecutor executor = task.executor(); + TaskExecutorDialogPane pane = taskDialog(executor, title, onCancel); + + pane.setBackgroundAction(() -> { + pane.fireEvent(new DialogCloseEvent()); + TaskCenter.getInstance().enqueue(executor, title, detail); + }); + + TaskCenter.getInstance().enqueue(executor, title, detail); + return pane; + } + + public static TaskExecutorDialogPane downloadTaskDialog(TaskExecutor executor, String title, TaskCancellationAction onCancel,String detail) { + TaskExecutorDialogPane pane = taskDialog(executor, title, onCancel); + + pane.setBackgroundAction(() -> { + pane.fireEvent(new DialogCloseEvent()); + TaskCenter.getInstance().enqueue(executor, title,detail); + }); + + TaskCenter.getInstance().enqueue(executor, title, detail); + return pane; + } + public static void navigate(Node node) { decorator.navigate(node, ContainerAnimations.NAVIGATION, Motion.SHORT4, Motion.EASE); } diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/TaskExecutorDialogPane.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/TaskExecutorDialogPane.java index cf4bd06c7b..c268ce173d 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/TaskExecutorDialogPane.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/TaskExecutorDialogPane.java @@ -21,14 +21,17 @@ import javafx.application.Platform; import javafx.beans.property.StringProperty; import javafx.geometry.Insets; +import javafx.geometry.Pos; import javafx.scene.control.Label; import javafx.scene.layout.BorderPane; +import javafx.scene.layout.HBox; import javafx.scene.layout.Priority; import javafx.scene.layout.VBox; import org.jackhuang.hmcl.task.*; import org.jackhuang.hmcl.ui.FXUtils; import org.jackhuang.hmcl.util.TaskCancellationAction; import org.jackhuang.hmcl.util.i18n.I18n; +import org.jackhuang.hmcl.ui.SVG; import org.jetbrains.annotations.NotNull; import java.util.Optional; @@ -48,6 +51,13 @@ public class TaskExecutorDialogPane extends BorderPane { private final Label lblProgress; private final JFXButton btnCancel; private final TaskListPane taskListPane; + private final JFXButton btnBackground; + private Runnable onBackground; + private Runnable escAction; + + public void setEscAction(Runnable action) { + this.escAction = action; + } public TaskExecutorDialogPane(@NotNull TaskCancellationAction cancel) { this.getStyleClass().add("task-executor-dialog-layout"); @@ -59,13 +69,34 @@ public TaskExecutorDialogPane(@NotNull TaskCancellationAction cancel) { this.setCenter(center); center.setPadding(new Insets(16)); { + HBox titleBar = new HBox(); + titleBar.setAlignment(Pos.CENTER_LEFT); + titleBar.setSpacing(8); + lblTitle = new Label(); lblTitle.setStyle("-fx-font-size: 14px; -fx-font-weight: BOLD;"); + btnBackground = new JFXButton(); + btnBackground.setGraphic(SVG.DOWNLOAD.createIcon(16)); // TODO: 可替换为更合适的后台图标 + btnBackground.getStyleClass().add("toggle-icon4"); + FXUtils.installFastTooltip(btnBackground, i18n("task.move_to_background")); + btnBackground.setOnAction(e -> { + if (onBackground != null) { + onBackground.run(); + } + }); + btnBackground.setVisible(false); + btnBackground.setManaged(false); + + HBox spacer = new HBox(); + HBox.setHgrow(spacer, Priority.ALWAYS); + + titleBar.getChildren().setAll(lblTitle, spacer, btnBackground); + taskListPane = new TaskListPane(); VBox.setVgrow(taskListPane, Priority.ALWAYS); - center.getChildren().setAll(lblTitle, taskListPane); + center.getChildren().setAll(titleBar, taskListPane); } BorderPane bottom = new BorderPane(); @@ -94,7 +125,12 @@ public TaskExecutorDialogPane(@NotNull TaskCancellationAction cancel) { Platform.runLater(() -> lblProgress.setText(message)); }); - onEscPressed(this, btnCancel::fire); + escAction = btnCancel::fire; + onEscPressed(this, () -> { + if (escAction != null) { + escAction.run(); + } + }); } public void setExecutor(TaskExecutor executor) { @@ -134,4 +170,13 @@ public void setCancel(TaskCancellationAction onCancel) { runInFX(() -> btnCancel.setDisable(onCancel == null)); } + + public void setBackgroundAction(Runnable action) { + this.onBackground = action; + btnBackground.setVisible(action != null); + btnBackground.setManaged(action != null); + } + public void refreshTaskList() { + taskListPane.refresh(); + } } diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/TaskListPane.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/TaskListPane.java index 0f5fd1be46..a24aac56cb 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/TaskListPane.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/TaskListPane.java @@ -156,6 +156,9 @@ public void onReady(Task task) { public void onRunning(Task task) { if (!task.getSignificance().shouldShow() || task.getName() == null) return; + if (task.getName() == null) { + task.setName(i18n("task.unnamed")); + } if (task instanceof GameAssetDownloadTask) { task.setName(i18n("assets.download_all")); @@ -504,4 +507,13 @@ public void setThrowable(Throwable throwable) { progress.set(0.); } } + public void refresh() { + if (executor == null) return; + Platform.runLater(() -> { + stageNodes.clear(); + listView.getItems().clear(); + addStages(executor.getStages()); + updateProgressNodePadding(); + }); + } } 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 ff3f237f80..38a845928a 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 @@ -134,24 +134,44 @@ public static void download(Profile profile, @Nullable String version, RemoteMod Path runDirectory = profile.getRepository().hasVersion(version) ? profile.getRepository().getRunDirectory(version) : profile.getRepository().getBaseDirectory(); + String detailPrefix; + switch (subdirectoryName) { + case "mods": + detailPrefix = "安装模组"; + break; + case "resourcepacks": + detailPrefix = "安装资源包"; + break; + case "shaderpacks": + detailPrefix = "安装光影"; + break; + case "saves": + detailPrefix = "安装世界"; + break; + default: + detailPrefix = "下载"; + break; + } + Controllers.prompt(i18n("archive.file.name"), (result, handler) -> { Path dest = runDirectory.resolve(subdirectoryName).resolve(result); - Controllers.taskDialog(Task.composeAsync(() -> { - var task = new FileDownloadTask(file.getFile().getUrl(), dest); - task.setName(file.getName()); - return task; - }).whenComplete(Schedulers.javafx(), exception -> { - if (exception != null) { - if (exception instanceof CancellationException) { - Controllers.showToast(i18n("message.cancelled")); - } else { - Controllers.dialog(DownloadProviders.localizeErrorMessage(exception), i18n("install.failed.downloading"), MessageDialogPane.MessageType.ERROR); - } - } else { - Controllers.showToast(i18n("install.success")); - } - }), i18n("message.downloading"), TaskCancellationAction.NORMAL); + Controllers.downloadTaskDialog(Task.composeAsync(() -> { + var task = new FileDownloadTask(file.getFile().getUrl(), dest); + task.setName(file.getName()); + return task; + }).whenComplete(Schedulers.javafx(), exception -> { + if (exception != null) { + if (exception instanceof CancellationException) { + Controllers.showToast(i18n("message.cancelled")); + } else { + Controllers.dialog(DownloadProviders.localizeErrorMessage(exception), i18n("install.failed.downloading"), MessageDialogPane.MessageType.ERROR); + } + } else { + Controllers.showToast(i18n("install.success")); + } + }), i18n("message.downloading"), TaskCancellationAction.NORMAL, + detailPrefix + "-[" + file.getName() + "]"); handler.resolve(); }, file.getFile().getFilename(), new Validator(i18n("install.new_game.malformed"), FileUtils::isNameValid)); @@ -301,6 +321,9 @@ public Object finish(SettingsMap settings) { settings.put("success_message", i18n("install.success")); settings.put(FailureCallback.KEY, (settings1, exception, next) -> UpdateInstallerWizardProvider.alertFailureMessage(exception, next)); + settings.put("task_detail", "安装游戏-[" + settings.get("name") + "]"); + settings.put("backgroundable", true); + return finishVersionDownloadingAsync(settings); } diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/ModpackInstallWizardProvider.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/ModpackInstallWizardProvider.java index bd6a40012d..cd9c382168 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/ModpackInstallWizardProvider.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/ModpackInstallWizardProvider.java @@ -142,6 +142,12 @@ public Object finish(SettingsMap settings) { } }); + Object name = settings.get("name"); + if (name != null) { + settings.put("task_detail", "安装整合包-[" + name + "]"); + } + settings.put("backgroundable", true); + return finishModpackInstallingAsync(settings); } diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/ModpackSelectionPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/ModpackSelectionPage.java index 50090a1935..76ce27d22a 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/ModpackSelectionPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/ModpackSelectionPage.java @@ -146,7 +146,7 @@ private void onChooseRemoteFile() { Path modpack = Files.createTempFile("modpack", ".zip"); handler.resolve(); - Controllers.taskDialog( + Controllers.downloadTaskDialog( new FileDownloadTask(url, modpack) .whenComplete(Schedulers.javafx(), e -> { if (e == null) { @@ -156,9 +156,10 @@ private void onChooseRemoteFile() { } else { handler.reject(e.getMessage()); } - }).executor(true), + }), i18n("message.downloading"), - TaskCancellationAction.NORMAL + TaskCancellationAction.NORMAL, + "安装整合包-[" + modpack.getFileName() + "]" ); } } catch (IOException e) { diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/UpdateInstallerWizardProvider.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/UpdateInstallerWizardProvider.java index 0b4f8dbf70..dbb2356591 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/UpdateInstallerWizardProvider.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/UpdateInstallerWizardProvider.java @@ -75,6 +75,19 @@ public Object finish(SettingsMap settings) { settings.put("success_message", i18n("install.success")); settings.put(FailureCallback.KEY, (settings1, exception, next) -> alertFailureMessage(exception, next)); + String detail = null; + for (Object value : settings.asStringMap().values()) { + if (value instanceof RemoteVersion remoteVersion) { + detail = "安装" + remoteVersion.getLibraryId() + "-[" + remoteVersion.getSelfVersion() + "]"; + break; + } + } + if (detail == null) { + detail = "安装" + libraryId; + } + settings.put("task_detail", detail); + settings.put("backgroundable", true); + // We remove library but not save it, // so if installation failed will not break down current version. Task ret = Task.supplyAsync(() -> version); diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/VanillaInstallWizardProvider.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/VanillaInstallWizardProvider.java index 03d26e8723..ef6853e06e 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/VanillaInstallWizardProvider.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/VanillaInstallWizardProvider.java @@ -70,6 +70,9 @@ public Object finish(SettingsMap settings) { settings.put("success_message", i18n("install.success")); settings.put(FailureCallback.KEY, (settings1, exception, next) -> UpdateInstallerWizardProvider.alertFailureMessage(exception, next)); + settings.put("task_detail", "安装游戏-[" + settings.get("name") + "]"); + settings.put("backgroundable", true); + return finishVersionDownloadingAsync(settings); } diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/JavaDownloadDialog.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/JavaDownloadDialog.java index 7d6dd5fc66..85b7e782c3 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/JavaDownloadDialog.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/JavaDownloadDialog.java @@ -159,16 +159,17 @@ protected void onAccept() { if (JavaManager.REPOSITORY.isInstalled(platform, javaVersion)) Controllers.confirm(i18n("download.java.override"), null, () -> { - Controllers.taskDialog(Task.supplyAsync(() -> JavaManager.REPOSITORY.getJavaExecutable(platform, javaVersion)) + String detail = "安装Java-[" + javaVersion.majorVersion() + "]"; + Controllers.downloadTaskDialog(Task.supplyAsync(() -> JavaManager.REPOSITORY.getJavaExecutable(platform, javaVersion)) .thenComposeAsync(Schedulers.javafx(), realPath -> { if (realPath != null) { JavaManager.removeJava(realPath); } return downloadTask(javaVersion); - }), i18n("download.java"), TaskCancellationAction.NORMAL); + }), i18n("download.java"), TaskCancellationAction.NORMAL, detail); }, null); else - Controllers.taskDialog(downloadTask(javaVersion), i18n("download.java.process"), TaskCancellationAction.NORMAL); + Controllers.downloadTaskDialog(downloadTask(javaVersion), i18n("download.java.process"), TaskCancellationAction.NORMAL, "安装Java-[" + javaVersion.majorVersion() + "]"); } } diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/RootPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/RootPage.java index ba086aa166..feb146adb6 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/RootPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/RootPage.java @@ -45,6 +45,7 @@ import org.jackhuang.hmcl.ui.download.ModpackInstallWizardProvider; import org.jackhuang.hmcl.ui.nbt.NBTEditorPage; import org.jackhuang.hmcl.ui.nbt.NBTFileType; +import org.jackhuang.hmcl.ui.task.TaskCenterPage; import org.jackhuang.hmcl.ui.versions.GameAdvancedListItem; import org.jackhuang.hmcl.ui.versions.GameListPopupMenu; import org.jackhuang.hmcl.ui.versions.Versions; @@ -191,6 +192,15 @@ protected Skin(RootPage control) { FXUtils.prepareOnMouseEnter(downloadItem, Controllers::prepareDownloadPage); } + AdvancedListItem taskManagerItem = new AdvancedListItem(); + taskManagerItem.setLeftGraphic(wrap(SVG.LIST)); //SVG待更换 + taskManagerItem.setActionButtonVisible(false); + taskManagerItem.setTitle(i18n("task.manage")); + taskManagerItem.setOnAction(e -> { + Controllers.navigate(new TaskCenterPage()); + }); + + // fifth item in left sidebar AdvancedListItem launcherSettingsItem = new AdvancedListItem(); launcherSettingsItem.setLeftGraphic(wrap(SVG.SETTINGS)); @@ -236,6 +246,7 @@ else if (Platform.SYSTEM_PLATFORM.equals(OperatingSystem.LINUX, Architecture.LOO .add(gameListItem) .add(gameItem) .add(downloadItem) + .add(taskManagerItem) .startCategory(i18n("settings.launcher.general").toUpperCase(Locale.ROOT)) .add(launcherSettingsItem) .add(terracottaItem) diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/task/TaskCenter.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/task/TaskCenter.java new file mode 100644 index 0000000000..b5b0fcbc7e --- /dev/null +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/task/TaskCenter.java @@ -0,0 +1,120 @@ +/* + * Hello Minecraft! Launcher + * Copyright (C) 2021 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.task; + +import java.util.ArrayDeque; +import java.util.Deque; +import java.util.HashMap; +import java.util.Map; +import javafx.application.Platform; +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; +import org.jackhuang.hmcl.task.TaskExecutor; +import org.jackhuang.hmcl.task.TaskListener; + +public final class TaskCenter { + private static final TaskCenter INSTANCE = new TaskCenter(); + + public static TaskCenter getInstance() { + return INSTANCE; + } + + public static final class Entry { + private final TaskExecutor executor; + private final String title; + private final String detail; + + public Entry(TaskExecutor executor, String title, String detail) { + this.executor = executor; + this.title = title; + this.detail = detail; + } + + public TaskExecutor getExecutor() { + return executor; + } + + public String getTitle() { + return title; + } + + public String getDetail() { + return detail; + } + } + + private final ObservableList entries = FXCollections.observableArrayList(); + private final ObservableList completedEntries = FXCollections.observableArrayList(); + private final ObservableList failedEntries = FXCollections.observableArrayList(); + + private final Deque queue = new ArrayDeque<>(); + private final Map entryIndex = new HashMap<>(); + private Entry running; + + public ObservableList getEntries() { + return entries; + } + + public ObservableList getCompletedEntries() { + return completedEntries; + } + + public ObservableList getFailedEntries() { + return failedEntries; + } + + public synchronized void enqueue(TaskExecutor executor, String title, String detail) { + if (entryIndex.containsKey(executor)) { + return; + } + Entry entry = new Entry(executor, title, detail); + entryIndex.put(executor, entry); + entries.add(entry); + queue.add(entry); + tryStartNext(); + } + + private synchronized void tryStartNext() { + if (running != null) return; + Entry next = queue.poll(); + if (next == null) return; + + running = next; + next.getExecutor().addTaskListener(new TaskListener() { + @Override + public void onStop(boolean success, TaskExecutor executor) { + Platform.runLater(() -> { + if (running != null) { + entries.remove(running); + entryIndex.remove(executor); + + if (success) { + completedEntries.add(running); + } else { + failedEntries.add(running); + } + } + + running = null; + tryStartNext(); + }); + } + }); + next.getExecutor().start(); + } +} \ No newline at end of file diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/task/TaskCenterPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/task/TaskCenterPage.java new file mode 100644 index 0000000000..27913b3ae1 --- /dev/null +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/task/TaskCenterPage.java @@ -0,0 +1,207 @@ +/* + * Hello Minecraft! Launcher + * Copyright (C) 2021 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.task; + +import com.jfoenix.controls.JFXButton; +import javafx.beans.property.ReadOnlyObjectProperty; +import javafx.beans.property.ReadOnlyObjectWrapper; +import javafx.collections.ListChangeListener; +import javafx.geometry.Insets; +import javafx.geometry.Pos; +import javafx.scene.Node; +import javafx.scene.control.Label; +import javafx.scene.control.ScrollPane; +import javafx.scene.layout.*; +import org.jackhuang.hmcl.ui.Controllers; +import org.jackhuang.hmcl.ui.FXUtils; +import org.jackhuang.hmcl.ui.SVG; +import org.jackhuang.hmcl.ui.construct.*; +import org.jackhuang.hmcl.ui.decorator.DecoratorAnimatedPage; +import org.jackhuang.hmcl.ui.decorator.DecoratorPage; +import org.jackhuang.hmcl.ui.animation.TransitionPane; +import org.jackhuang.hmcl.util.StringUtils; +import org.jackhuang.hmcl.util.TaskCancellationAction; + +import java.util.concurrent.CancellationException; + +import static org.jackhuang.hmcl.util.i18n.I18n.i18n; + +public final class TaskCenterPage extends DecoratorAnimatedPage implements DecoratorPage { + private final ReadOnlyObjectWrapper state = + new ReadOnlyObjectWrapper<>(State.fromTitle(i18n("task.manage"))); + + private final TransitionPane transitionPane = new TransitionPane(); + private final TabHeader tabHeader; + + private final TabHeader.Tab runningTab = new TabHeader.Tab<>("taskRunningTab"); + private final TabHeader.Tab completedTab = new TabHeader.Tab<>("taskCompletedTab"); + private final TabHeader.Tab failedTab = new TabHeader.Tab<>("taskFailedTab"); + private final TabHeader.Tab settingsTab = new TabHeader.Tab<>("taskSettingsTab"); + + private final VBox runningContainer = new VBox(8); + private final VBox completedContainer = new VBox(8); + private final VBox failedContainer = new VBox(8); + + public TaskCenterPage() { + runningTab.setNodeSupplier(this::createRunningPane); + completedTab.setNodeSupplier(this::createCompletedPane); + failedTab.setNodeSupplier(this::createFailedPane); + settingsTab.setNodeSupplier(() -> createPlaceholderPane(i18n("task.settings"))); + + tabHeader = new TabHeader(transitionPane, runningTab, completedTab, failedTab, settingsTab); + tabHeader.select(runningTab); + + AdvancedListBox sideBar = new AdvancedListBox() + .startCategory(i18n("task.manage").toUpperCase()) + .addNavigationDrawerTab(tabHeader, runningTab, i18n("task.running"), SVG.ARROW_FORWARD) + .addNavigationDrawerTab(tabHeader, completedTab, i18n("task.completed"), SVG.CHECK) + .addNavigationDrawerTab(tabHeader, failedTab, i18n("task.failed"), SVG.CLOSE) + .addNavigationDrawerTab(tabHeader, settingsTab, i18n("task.settings"), SVG.SETTINGS); + + FXUtils.setLimitWidth(sideBar, 200); + setLeft(sideBar); + + BorderPane contentWrapper = new BorderPane(); + contentWrapper.getStyleClass().add("card-non-transparent"); + contentWrapper.setPadding(new Insets(12)); + contentWrapper.setCenter(transitionPane); + + StackPane centerPane = new StackPane(contentWrapper); + centerPane.setPadding(new Insets(12)); + setCenter(centerPane); + } + + private ScrollPane createRunningPane() { + ScrollPane scrollPane = new ScrollPane(runningContainer); + scrollPane.setFitToWidth(true); + runningContainer.setPadding(new Insets(12)); + + TaskCenter.getInstance().getEntries().addListener((ListChangeListener) change -> rebuildRunning()); + rebuildRunning(); + + return scrollPane; + } + + private ScrollPane createCompletedPane() { + ScrollPane scrollPane = new ScrollPane(completedContainer); + scrollPane.setFitToWidth(true); + completedContainer.setPadding(new Insets(12)); + + TaskCenter.getInstance().getCompletedEntries() + .addListener((ListChangeListener) change -> rebuildCompleted()); + rebuildCompleted(); + + return scrollPane; + } + + private ScrollPane createFailedPane() { + ScrollPane scrollPane = new ScrollPane(failedContainer); + scrollPane.setFitToWidth(true); + failedContainer.setPadding(new Insets(12)); + + TaskCenter.getInstance().getFailedEntries() + .addListener((ListChangeListener) change -> rebuildFailed()); + rebuildFailed(); + + return scrollPane; + } + + private void rebuildRunning() { + runningContainer.getChildren().clear(); + for (TaskCenter.Entry entry : TaskCenter.getInstance().getEntries()) { + runningContainer.getChildren().add(createRunningItem(entry)); + } + } + + private ScrollPane createPlaceholderPane(String text) { + VBox box = new VBox(); + box.setPadding(new Insets(12)); + box.getChildren().add(new Label(text)); + ScrollPane scrollPane = new ScrollPane(box); + scrollPane.setFitToWidth(true); + return scrollPane; + } + + private void rebuildCompleted() { + completedContainer.getChildren().clear(); + for (TaskCenter.Entry entry : TaskCenter.getInstance().getCompletedEntries()) { + Label item = new Label(entry.getDetail() != null ? entry.getDetail() : entry.getTitle()); + item.getStyleClass().add("md-list-cell"); + item.setPadding(new Insets(10, 12, 10, 12)); + completedContainer.getChildren().add(item); + } + } + + private void rebuildFailed() { + failedContainer.getChildren().clear(); + for (TaskCenter.Entry entry : TaskCenter.getInstance().getFailedEntries()) { + Label item = new Label(entry.getDetail() != null ? entry.getDetail() : entry.getTitle()); + item.getStyleClass().add("md-list-cell"); + item.setPadding(new Insets(10, 12, 10, 12)); + + item.setOnMouseClicked(e -> { + Throwable ex = entry.getExecutor().getException(); + if (ex instanceof CancellationException) { + Controllers.dialog("任务由用户取消", entry.getTitle(), MessageDialogPane.MessageType.ERROR); + } else if (ex != null) { + Controllers.dialog(StringUtils.getStackTrace(ex), entry.getTitle(), MessageDialogPane.MessageType.ERROR); + } else { + Controllers.dialog("任务失败(无异常信息)", entry.getTitle(), MessageDialogPane.MessageType.ERROR); + } + }); + + failedContainer.getChildren().add(item); + } + } + + private Node createRunningItem(TaskCenter.Entry entry) { + HBox row = new HBox(12); + row.getStyleClass().add("md-list-cell"); + row.setPadding(new Insets(8, 12, 8, 12)); + row.setAlignment(Pos.CENTER_LEFT); + + String text = entry.getDetail() != null ? entry.getDetail() : entry.getTitle(); + Label label = new Label(text); + HBox.setHgrow(label, Priority.ALWAYS); + label.setMaxWidth(Double.MAX_VALUE); + + JFXButton cancelButton = new JFXButton(i18n("button.cancel")); + cancelButton.getStyleClass().add("dialog-cancel"); + + cancelButton.setOnAction(e -> { + entry.getExecutor().cancel(); + e.consume(); + }); + cancelButton.setOnMouseClicked(e -> e.consume()); + + row.getChildren().addAll(label, cancelButton); + + row.setOnMouseClicked(e -> { + TaskExecutorDialogPane pane = Controllers.taskDialog(entry.getExecutor(), entry.getTitle(), TaskCancellationAction.NORMAL); + pane.setEscAction(() -> pane.fireEvent(new DialogCloseEvent())); + pane.refreshTaskList(); + }); + + return row; + } + + @Override + public ReadOnlyObjectProperty stateProperty() { + return state.getReadOnlyProperty(); + } +} \ No newline at end of file diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/Versions.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/Versions.java index 82c3c7a8ac..f422d2a334 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/Versions.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/Versions.java @@ -84,7 +84,7 @@ public static void downloadModpackImpl(Profile profile, String version, RemoteMo i18n("download.failed.no_code"), MessageDialogPane.MessageType.ERROR); return; } - Controllers.taskDialog( + Controllers.downloadTaskDialog( new FileDownloadTask(downloadURL, modpack) .whenComplete(Schedulers.javafx(), e -> { if (e == null) { @@ -105,7 +105,8 @@ public static void downloadModpackImpl(Profile profile, String version, RemoteMo } }).executor(true), i18n("message.downloading"), - TaskCancellationAction.NORMAL + TaskCancellationAction.NORMAL, + "安装整合包-[" + modpack.getFileName() + "]" ); } diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/wizard/TaskExecutorDialogWizardDisplayer.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/wizard/TaskExecutorDialogWizardDisplayer.java index cf5f128ebd..5ec7ddf35e 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/wizard/TaskExecutorDialogWizardDisplayer.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/wizard/TaskExecutorDialogWizardDisplayer.java @@ -19,6 +19,7 @@ import javafx.beans.property.StringProperty; import org.jackhuang.hmcl.task.Task; +import org.jackhuang.hmcl.ui.task.TaskCenter; import org.jackhuang.hmcl.task.TaskExecutor; import org.jackhuang.hmcl.task.TaskListener; import org.jackhuang.hmcl.ui.Controllers; @@ -58,6 +59,8 @@ else if (title instanceof String titleMessage) } runInFX(() -> { + boolean backgroundable = Boolean.TRUE.equals(settings.get("backgroundable")); + TaskExecutor executor = task.executor(new TaskListener() { @Override public void onStop(boolean success, TaskExecutor executor) { @@ -84,13 +87,32 @@ else if (settings.get("failure_message") instanceof String failureMessage) else if (!settings.containsKey("forbid_failure_message")) Controllers.dialog(appendix, i18n("wizard.failed"), MessageType.ERROR, () -> onEnd()); } - }); } }); + pane.setExecutor(executor); + + if (backgroundable) { + String detail = settings.get("task_detail").toString(); + if (detail == null) { + detail = pane.getTitle(); + } + + String finalDetail = detail; + pane.setBackgroundAction(() -> { + pane.fireEvent(new DialogCloseEvent()); + TaskCenter.getInstance().enqueue(executor, pane.getTitle(), finalDetail); + }); + + TaskCenter.getInstance().enqueue(executor, pane.getTitle(), detail); + } + Controllers.dialog(pane); - executor.start(); + + if (!backgroundable) { + executor.start(); + } }); } } diff --git a/HMCL/src/main/resources/assets/lang/I18N.properties b/HMCL/src/main/resources/assets/lang/I18N.properties index 579fdfe5ea..bae6dab535 100644 --- a/HMCL/src/main/resources/assets/lang/I18N.properties +++ b/HMCL/src/main/resources/assets/lang/I18N.properties @@ -1480,6 +1480,13 @@ sponsor.hmcl=Hello Minecraft! Launcher is a FOSS Minecraft launcher that allows system.architecture=Architecture system.operating_system=Operating System +task.completed=Completed +task.failed=Failed +task.manage=Task Manager +task.move_to_background=Move Task to Background +task.running=Running Tasks +task.settings=Task Settings +task.unnamed=Unnamed Task terracotta=Multiplayer terracotta.terracotta=Terracotta | Multiplayer terracotta.status=Lobby From 27eae281e971d9d9e7c1df886ece29a96d4facee Mon Sep 17 00:00:00 2001 From: lokins Date: Wed, 4 Feb 2026 23:18:04 +0800 Subject: [PATCH 02/20] update --- HMCL/src/main/java/org/jackhuang/hmcl/ui/task/TaskCenter.java | 2 +- .../main/java/org/jackhuang/hmcl/ui/task/TaskCenterPage.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/task/TaskCenter.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/task/TaskCenter.java index b5b0fcbc7e..f2b93668f0 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/task/TaskCenter.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/task/TaskCenter.java @@ -117,4 +117,4 @@ public void onStop(boolean success, TaskExecutor executor) { }); next.getExecutor().start(); } -} \ No newline at end of file +} diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/task/TaskCenterPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/task/TaskCenterPage.java index 27913b3ae1..adb8617d0b 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/task/TaskCenterPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/task/TaskCenterPage.java @@ -204,4 +204,4 @@ private Node createRunningItem(TaskCenter.Entry entry) { public ReadOnlyObjectProperty stateProperty() { return state.getReadOnlyProperty(); } -} \ No newline at end of file +} From af8fe52838b8e1d63618da83631a7bf1df9eb3e2 Mon Sep 17 00:00:00 2001 From: lokins Date: Wed, 4 Feb 2026 23:24:30 +0800 Subject: [PATCH 03/20] update --- .../jackhuang/hmcl/ui/construct/TaskExecutorDialogPane.java | 1 + .../java/org/jackhuang/hmcl/ui/construct/TaskListPane.java | 1 + .../java/org/jackhuang/hmcl/ui/download/DownloadPage.java | 4 ++-- .../main/java/org/jackhuang/hmcl/ui/task/TaskCenterPage.java | 3 ++- 4 files changed, 6 insertions(+), 3 deletions(-) diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/TaskExecutorDialogPane.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/TaskExecutorDialogPane.java index c268ce173d..d43d549d15 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/TaskExecutorDialogPane.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/TaskExecutorDialogPane.java @@ -176,6 +176,7 @@ public void setBackgroundAction(Runnable action) { btnBackground.setVisible(action != null); btnBackground.setManaged(action != null); } + public void refreshTaskList() { taskListPane.refresh(); } diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/TaskListPane.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/TaskListPane.java index a24aac56cb..7f27055b21 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/TaskListPane.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/TaskListPane.java @@ -507,6 +507,7 @@ public void setThrowable(Throwable throwable) { progress.set(0.); } } + public void refresh() { if (executor == null) return; Platform.runLater(() -> { 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 38a845928a..6f2b8583a7 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 @@ -158,9 +158,9 @@ public static void download(Profile profile, @Nullable String version, RemoteMod Controllers.downloadTaskDialog(Task.composeAsync(() -> { var task = new FileDownloadTask(file.getFile().getUrl(), dest); - task.setName(file.getName()); + task.setName(file.getName()); return task; - }).whenComplete(Schedulers.javafx(), exception -> { + }).whenComplete(Schedulers.javafx(), exception -> { if (exception != null) { if (exception instanceof CancellationException) { Controllers.showToast(i18n("message.cancelled")); diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/task/TaskCenterPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/task/TaskCenterPage.java index adb8617d0b..93888f1934 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/task/TaskCenterPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/task/TaskCenterPage.java @@ -37,6 +37,7 @@ import org.jackhuang.hmcl.util.StringUtils; import org.jackhuang.hmcl.util.TaskCancellationAction; +import java.util.Locale; import java.util.concurrent.CancellationException; import static org.jackhuang.hmcl.util.i18n.I18n.i18n; @@ -67,7 +68,7 @@ public TaskCenterPage() { tabHeader.select(runningTab); AdvancedListBox sideBar = new AdvancedListBox() - .startCategory(i18n("task.manage").toUpperCase()) + .startCategory(i18n("task.manage").toUpperCase(Locale.ROOT)) .addNavigationDrawerTab(tabHeader, runningTab, i18n("task.running"), SVG.ARROW_FORWARD) .addNavigationDrawerTab(tabHeader, completedTab, i18n("task.completed"), SVG.CHECK) .addNavigationDrawerTab(tabHeader, failedTab, i18n("task.failed"), SVG.CLOSE) From e40d564dbcdbb05be5855919c61b2e3ef76f783f Mon Sep 17 00:00:00 2001 From: lokins Date: Wed, 4 Feb 2026 23:29:00 +0800 Subject: [PATCH 04/20] update --- .../hmcl/ui/download/DownloadPage.java | 27 ++++++++++--------- 1 file changed, 14 insertions(+), 13 deletions(-) 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 6f2b8583a7..9cc36bf3bd 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 @@ -157,21 +157,22 @@ public static void download(Profile profile, @Nullable String version, RemoteMod Path dest = runDirectory.resolve(subdirectoryName).resolve(result); Controllers.downloadTaskDialog(Task.composeAsync(() -> { - var task = new FileDownloadTask(file.getFile().getUrl(), dest); + var task = new FileDownloadTask(file.getFile().getUrl(), dest); task.setName(file.getName()); - return task; + return task; }).whenComplete(Schedulers.javafx(), exception -> { - if (exception != null) { - if (exception instanceof CancellationException) { - Controllers.showToast(i18n("message.cancelled")); - } else { - Controllers.dialog(DownloadProviders.localizeErrorMessage(exception), i18n("install.failed.downloading"), MessageDialogPane.MessageType.ERROR); - } - } else { - Controllers.showToast(i18n("install.success")); - } - }), i18n("message.downloading"), TaskCancellationAction.NORMAL, - detailPrefix + "-[" + file.getName() + "]"); + if (exception != null) { + if (exception instanceof CancellationException) { + Controllers.showToast(i18n("message.cancelled")); + } else { + Controllers.dialog(DownloadProviders.localizeErrorMessage(exception), i18n("install.failed.downloading"), MessageDialogPane.MessageType.ERROR); + } + } + else { + Controllers.showToast(i18n("install.success")); + } + }), i18n("message.downloading"), TaskCancellationAction.NORMAL, + detailPrefix + "-[" + file.getName() + "]"); handler.resolve(); }, file.getFile().getFilename(), new Validator(i18n("install.new_game.malformed"), FileUtils::isNameValid)); From cc25198bcc37705fa7ec903f01d7dfa1050bc6d6 Mon Sep 17 00:00:00 2001 From: lokins Date: Wed, 4 Feb 2026 23:32:16 +0800 Subject: [PATCH 05/20] update --- .../main/java/org/jackhuang/hmcl/ui/download/DownloadPage.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 9cc36bf3bd..fb24559521 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 @@ -172,7 +172,7 @@ public static void download(Profile profile, @Nullable String version, RemoteMod Controllers.showToast(i18n("install.success")); } }), i18n("message.downloading"), TaskCancellationAction.NORMAL, - detailPrefix + "-[" + file.getName() + "]"); + detailPrefix + "-[" + file.getName() + "]"); handler.resolve(); }, file.getFile().getFilename(), new Validator(i18n("install.new_game.malformed"), FileUtils::isNameValid)); From 7f0bbaec78fc07c7a75e0b42070ba96ada89f464 Mon Sep 17 00:00:00 2001 From: lokins Date: Thu, 5 Feb 2026 00:19:52 +0800 Subject: [PATCH 06/20] update --- .../ui/wizard/TaskExecutorDialogWizardDisplayer.java | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/wizard/TaskExecutorDialogWizardDisplayer.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/wizard/TaskExecutorDialogWizardDisplayer.java index 5ec7ddf35e..9996a49fb2 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/wizard/TaskExecutorDialogWizardDisplayer.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/wizard/TaskExecutorDialogWizardDisplayer.java @@ -94,15 +94,12 @@ else if (!settings.containsKey("forbid_failure_message")) pane.setExecutor(executor); if (backgroundable) { - String detail = settings.get("task_detail").toString(); - if (detail == null) { - detail = pane.getTitle(); - } + Object detailObj = settings.get("task_detail"); + String detail = detailObj != null ? detailObj.toString() : pane.getTitle(); - String finalDetail = detail; pane.setBackgroundAction(() -> { pane.fireEvent(new DialogCloseEvent()); - TaskCenter.getInstance().enqueue(executor, pane.getTitle(), finalDetail); + TaskCenter.getInstance().enqueue(executor, pane.getTitle(), detail); }); TaskCenter.getInstance().enqueue(executor, pane.getTitle(), detail); From 98b18a8e4ab3ec4f29359d9fa44a98e8bc3a1531 Mon Sep 17 00:00:00 2001 From: lokins Date: Thu, 5 Feb 2026 22:51:45 +0800 Subject: [PATCH 07/20] update --- .../ui/construct/TaskExecutorDialogPane.java | 37 ++++++++++++++++ .../jackhuang/hmcl/ui/task/TaskCenter.java | 21 +++++++++- .../hmcl/ui/task/TaskCenterPage.java | 42 ++++++++++++------- 3 files changed, 84 insertions(+), 16 deletions(-) diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/TaskExecutorDialogPane.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/TaskExecutorDialogPane.java index d43d549d15..f787240848 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/TaskExecutorDialogPane.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/TaskExecutorDialogPane.java @@ -27,6 +27,7 @@ import javafx.scene.layout.HBox; import javafx.scene.layout.Priority; import javafx.scene.layout.VBox; +import org.jackhuang.hmcl.game.Log; import org.jackhuang.hmcl.task.*; import org.jackhuang.hmcl.ui.FXUtils; import org.jackhuang.hmcl.util.TaskCancellationAction; @@ -34,12 +35,14 @@ import org.jackhuang.hmcl.ui.SVG; import org.jetbrains.annotations.NotNull; +import java.util.concurrent.atomic.AtomicBoolean; import java.util.Optional; import java.util.function.Consumer; import static org.jackhuang.hmcl.ui.FXUtils.onEscPressed; import static org.jackhuang.hmcl.ui.FXUtils.runInFX; import static org.jackhuang.hmcl.util.i18n.I18n.i18n; +import static org.jackhuang.hmcl.util.logging.Logger.LOG; public class TaskExecutorDialogPane extends BorderPane { private TaskExecutor executor; @@ -61,6 +64,7 @@ public void setEscAction(Runnable action) { public TaskExecutorDialogPane(@NotNull TaskCancellationAction cancel) { this.getStyleClass().add("task-executor-dialog-layout"); + cancelAction = null; FXUtils.setLimitWidth(this, 500); FXUtils.setLimitHeight(this, 300); @@ -114,6 +118,11 @@ public TaskExecutorDialogPane(@NotNull TaskCancellationAction cancel) { setCancel(cancel); btnCancel.setOnAction(e -> { + if (cancelAction != null) { + cancelAction.run(); + return; + } + Optional.ofNullable(executor).ifPresent(TaskExecutor::cancel); if (onCancel.getCancellationAction() != null) { onCancel.getCancellationAction().accept(this); @@ -171,13 +180,41 @@ public void setCancel(TaskCancellationAction onCancel) { runInFX(() -> btnCancel.setDisable(onCancel == null)); } + private final AtomicBoolean background = new AtomicBoolean(false); + public void setBackgroundAction(Runnable action) { this.onBackground = action; + background.set(false); + btnBackground.setVisible(action != null); btnBackground.setManaged(action != null); + + if (action != null) { + btnBackground.setDisable(false); + btnBackground.setOnAction(e -> { + if (!background.compareAndSet(false, true)) { + return; + } + btnBackground.setDisable(true); + onBackground.run(); + }); + } else { + btnBackground.setDisable(false); + btnBackground.setOnAction(null); + } } public void refreshTaskList() { taskListPane.refresh(); } + + private Runnable cancelAction; + + public void setCancelAction(Runnable action) { + this.cancelAction = action; + } + + public void setCancelText(String text) { + btnCancel.setText(text); + } } diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/task/TaskCenter.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/task/TaskCenter.java index f2b93668f0..aa53894d7b 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/task/TaskCenter.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/task/TaskCenter.java @@ -64,6 +64,7 @@ public String getDetail() { private final Deque queue = new ArrayDeque<>(); private final Map entryIndex = new HashMap<>(); + private final Map started = new HashMap<>(); private Entry running; public ObservableList getEntries() { @@ -79,9 +80,15 @@ public ObservableList getFailedEntries() { } public synchronized void enqueue(TaskExecutor executor, String title, String detail) { + if (!Platform.isFxApplicationThread()) { + Platform.runLater(() -> enqueue(executor, title, detail)); + return; + } + if (entryIndex.containsKey(executor)) { return; } + Entry entry = new Entry(executor, title, detail); entryIndex.put(executor, entry); entries.add(entry); @@ -94,14 +101,23 @@ private synchronized void tryStartNext() { Entry next = queue.poll(); if (next == null) return; + TaskExecutor executor = next.getExecutor(); + if (Boolean.TRUE.equals(started.get(executor))) { + tryStartNext(); + return; + } + + started.put(executor, true); running = next; - next.getExecutor().addTaskListener(new TaskListener() { + + executor.addTaskListener(new TaskListener() { @Override public void onStop(boolean success, TaskExecutor executor) { Platform.runLater(() -> { if (running != null) { entries.remove(running); entryIndex.remove(executor); + started.remove(executor); if (success) { completedEntries.add(running); @@ -115,6 +131,7 @@ public void onStop(boolean success, TaskExecutor executor) { }); } }); - next.getExecutor().start(); + + executor.start(); } } diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/task/TaskCenterPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/task/TaskCenterPage.java index 93888f1934..5ecd79bf8f 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/task/TaskCenterPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/task/TaskCenterPage.java @@ -26,6 +26,7 @@ import javafx.scene.Node; import javafx.scene.control.Label; import javafx.scene.control.ScrollPane; +import javafx.scene.control.Separator; import javafx.scene.layout.*; import org.jackhuang.hmcl.ui.Controllers; import org.jackhuang.hmcl.ui.FXUtils; @@ -141,32 +142,25 @@ private ScrollPane createPlaceholderPane(String text) { private void rebuildCompleted() { completedContainer.getChildren().clear(); for (TaskCenter.Entry entry : TaskCenter.getInstance().getCompletedEntries()) { - Label item = new Label(entry.getDetail() != null ? entry.getDetail() : entry.getTitle()); - item.getStyleClass().add("md-list-cell"); - item.setPadding(new Insets(10, 12, 10, 12)); - completedContainer.getChildren().add(item); + String text = entry.getDetail() != null ? entry.getDetail() : entry.getTitle(); + completedContainer.getChildren().add(createLineItem(text, null)); } } private void rebuildFailed() { failedContainer.getChildren().clear(); for (TaskCenter.Entry entry : TaskCenter.getInstance().getFailedEntries()) { - Label item = new Label(entry.getDetail() != null ? entry.getDetail() : entry.getTitle()); - item.getStyleClass().add("md-list-cell"); - item.setPadding(new Insets(10, 12, 10, 12)); - - item.setOnMouseClicked(e -> { + String text = entry.getDetail() != null ? entry.getDetail() : entry.getTitle(); + failedContainer.getChildren().add(createLineItem(text, () -> { Throwable ex = entry.getExecutor().getException(); if (ex instanceof CancellationException) { - Controllers.dialog("任务由用户取消", entry.getTitle(), MessageDialogPane.MessageType.ERROR); + Controllers.dialog("任务由用户取消", entry.getTitle(), MessageDialogPane.MessageType.ERROR);//TODO: i18n } else if (ex != null) { Controllers.dialog(StringUtils.getStackTrace(ex), entry.getTitle(), MessageDialogPane.MessageType.ERROR); } else { - Controllers.dialog("任务失败(无异常信息)", entry.getTitle(), MessageDialogPane.MessageType.ERROR); + Controllers.dialog("任务失败(无异常信息)", entry.getTitle(), MessageDialogPane.MessageType.ERROR);//TODO: i18n } - }); - - failedContainer.getChildren().add(item); + })); } } @@ -195,12 +189,32 @@ private Node createRunningItem(TaskCenter.Entry entry) { row.setOnMouseClicked(e -> { TaskExecutorDialogPane pane = Controllers.taskDialog(entry.getExecutor(), entry.getTitle(), TaskCancellationAction.NORMAL); pane.setEscAction(() -> pane.fireEvent(new DialogCloseEvent())); + pane.setCancelText("关闭");//TODO: i18n + pane.setCancelAction(() -> pane.fireEvent(new DialogCloseEvent())); pane.refreshTaskList(); }); return row; } + private Node createLineItem(String text, Runnable onClick) { + Label label = new Label(text); + label.setPadding(new Insets(10, 12, 10, 12)); + label.setMaxWidth(Double.MAX_VALUE); + label.setPrefWidth(Double.MAX_VALUE); + + Separator separator = new Separator(); + separator.setMaxWidth(Double.MAX_VALUE); + separator.setPrefWidth(Double.MAX_VALUE); + + VBox box = new VBox(label, separator); + box.setFillWidth(true); + if (onClick != null) { + box.setOnMouseClicked(e -> onClick.run()); + } + return box; + } + @Override public ReadOnlyObjectProperty stateProperty() { return state.getReadOnlyProperty(); From eaf13c500174244612023e3ff027cefe607c2b60 Mon Sep 17 00:00:00 2001 From: lokins Date: Thu, 5 Feb 2026 22:54:54 +0800 Subject: [PATCH 08/20] update --- .../org/jackhuang/hmcl/ui/construct/TaskExecutorDialogPane.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/TaskExecutorDialogPane.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/TaskExecutorDialogPane.java index f787240848..f301e1197d 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/TaskExecutorDialogPane.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/TaskExecutorDialogPane.java @@ -27,7 +27,6 @@ import javafx.scene.layout.HBox; import javafx.scene.layout.Priority; import javafx.scene.layout.VBox; -import org.jackhuang.hmcl.game.Log; import org.jackhuang.hmcl.task.*; import org.jackhuang.hmcl.ui.FXUtils; import org.jackhuang.hmcl.util.TaskCancellationAction; @@ -42,7 +41,6 @@ import static org.jackhuang.hmcl.ui.FXUtils.onEscPressed; import static org.jackhuang.hmcl.ui.FXUtils.runInFX; import static org.jackhuang.hmcl.util.i18n.I18n.i18n; -import static org.jackhuang.hmcl.util.logging.Logger.LOG; public class TaskExecutorDialogPane extends BorderPane { private TaskExecutor executor; From f779a4fe079d567fcf04d73915e5aa260be4ae62 Mon Sep 17 00:00:00 2001 From: lokins Date: Fri, 6 Feb 2026 00:48:42 +0800 Subject: [PATCH 09/20] update --- .../hmcl/ui/download/DownloadPage.java | 12 +++--- .../ModpackInstallWizardProvider.java | 2 +- .../ui/download/ModpackSelectionPage.java | 2 +- .../UpdateInstallerWizardProvider.java | 4 +- .../VanillaInstallWizardProvider.java | 2 +- .../hmcl/ui/main/JavaDownloadDialog.java | 4 +- .../jackhuang/hmcl/ui/versions/Versions.java | 43 ++++++++++--------- 7 files changed, 36 insertions(+), 33 deletions(-) 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 fb24559521..3c3545aa90 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 @@ -137,19 +137,19 @@ public static void download(Profile profile, @Nullable String version, RemoteMod String detailPrefix; switch (subdirectoryName) { case "mods": - detailPrefix = "安装模组"; + detailPrefix = "安装模组";//TODO i18n break; case "resourcepacks": - detailPrefix = "安装资源包"; + detailPrefix = "安装资源包";//TODO i18n break; case "shaderpacks": - detailPrefix = "安装光影"; + detailPrefix = "安装光影";//TODO i18n break; case "saves": - detailPrefix = "安装世界"; + detailPrefix = "安装世界";//TODO i18n break; default: - detailPrefix = "下载"; + detailPrefix = "下载";//TODO i18n break; } @@ -322,7 +322,7 @@ public Object finish(SettingsMap settings) { settings.put("success_message", i18n("install.success")); settings.put(FailureCallback.KEY, (settings1, exception, next) -> UpdateInstallerWizardProvider.alertFailureMessage(exception, next)); - settings.put("task_detail", "安装游戏-[" + settings.get("name") + "]"); + settings.put("task_detail", "安装游戏-[" + settings.get("name") + "]");//TODO i18n settings.put("backgroundable", true); return finishVersionDownloadingAsync(settings); diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/ModpackInstallWizardProvider.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/ModpackInstallWizardProvider.java index cd9c382168..db4b8c5e72 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/ModpackInstallWizardProvider.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/ModpackInstallWizardProvider.java @@ -144,7 +144,7 @@ public Object finish(SettingsMap settings) { Object name = settings.get("name"); if (name != null) { - settings.put("task_detail", "安装整合包-[" + name + "]"); + settings.put("task_detail", "安装整合包-[" + name + "]");//TODO: i18n } settings.put("backgroundable", true); diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/ModpackSelectionPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/ModpackSelectionPage.java index 76ce27d22a..83a4d2ebe4 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/ModpackSelectionPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/ModpackSelectionPage.java @@ -159,7 +159,7 @@ private void onChooseRemoteFile() { }), i18n("message.downloading"), TaskCancellationAction.NORMAL, - "安装整合包-[" + modpack.getFileName() + "]" + "安装整合包-[" + modpack.getFileName() + "]"//TODO: i18n ); } } catch (IOException e) { diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/UpdateInstallerWizardProvider.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/UpdateInstallerWizardProvider.java index dbb2356591..1b165ecbc4 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/UpdateInstallerWizardProvider.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/UpdateInstallerWizardProvider.java @@ -78,12 +78,12 @@ public Object finish(SettingsMap settings) { String detail = null; for (Object value : settings.asStringMap().values()) { if (value instanceof RemoteVersion remoteVersion) { - detail = "安装" + remoteVersion.getLibraryId() + "-[" + remoteVersion.getSelfVersion() + "]"; + detail = "安装" + remoteVersion.getLibraryId() + "-[" + remoteVersion.getSelfVersion() + "]";//TODO i18n break; } } if (detail == null) { - detail = "安装" + libraryId; + detail = "安装" + libraryId;//TODO i18n } settings.put("task_detail", detail); settings.put("backgroundable", true); diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/VanillaInstallWizardProvider.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/VanillaInstallWizardProvider.java index ef6853e06e..3928f28c8b 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/VanillaInstallWizardProvider.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/VanillaInstallWizardProvider.java @@ -70,7 +70,7 @@ public Object finish(SettingsMap settings) { settings.put("success_message", i18n("install.success")); settings.put(FailureCallback.KEY, (settings1, exception, next) -> UpdateInstallerWizardProvider.alertFailureMessage(exception, next)); - settings.put("task_detail", "安装游戏-[" + settings.get("name") + "]"); + settings.put("task_detail", "安装游戏-[" + settings.get("name") + "]");//TODO i18n settings.put("backgroundable", true); return finishVersionDownloadingAsync(settings); diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/JavaDownloadDialog.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/JavaDownloadDialog.java index 85b7e782c3..779ccac47b 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/JavaDownloadDialog.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/JavaDownloadDialog.java @@ -159,7 +159,7 @@ protected void onAccept() { if (JavaManager.REPOSITORY.isInstalled(platform, javaVersion)) Controllers.confirm(i18n("download.java.override"), null, () -> { - String detail = "安装Java-[" + javaVersion.majorVersion() + "]"; + String detail = "安装Java-[" + javaVersion.majorVersion() + "]";//TODO: i18n Controllers.downloadTaskDialog(Task.supplyAsync(() -> JavaManager.REPOSITORY.getJavaExecutable(platform, javaVersion)) .thenComposeAsync(Schedulers.javafx(), realPath -> { if (realPath != null) { @@ -169,7 +169,7 @@ protected void onAccept() { }), i18n("download.java"), TaskCancellationAction.NORMAL, detail); }, null); else - Controllers.downloadTaskDialog(downloadTask(javaVersion), i18n("download.java.process"), TaskCancellationAction.NORMAL, "安装Java-[" + javaVersion.majorVersion() + "]"); + Controllers.downloadTaskDialog(downloadTask(javaVersion), i18n("download.java.process"), TaskCancellationAction.NORMAL, "安装Java-[" + javaVersion.majorVersion() + "]");//TODO: i18n } } diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/Versions.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/Versions.java index f422d2a334..6ec8fbfa32 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/Versions.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/Versions.java @@ -84,29 +84,32 @@ public static void downloadModpackImpl(Profile profile, String version, RemoteMo i18n("download.failed.no_code"), MessageDialogPane.MessageType.ERROR); return; } + + Task downloadTask = new FileDownloadTask(downloadURL, modpack) + .whenComplete(Schedulers.javafx(), e -> { + if (e == null) { + ModpackInstallWizardProvider installWizardProvider; + if (version != null) + installWizardProvider = new ModpackInstallWizardProvider(profile, modpack, version); + else + installWizardProvider = new ModpackInstallWizardProvider(profile, modpack); + if (StringUtils.isNotBlank(mod.getIconUrl())) + installWizardProvider.setIconUrl(mod.getIconUrl()); + Controllers.getDecorator().startWizard(installWizardProvider); + } else if (e instanceof CancellationException) { + Controllers.showToast(i18n("message.cancelled")); + } else { + Controllers.dialog( + i18n("install.failed.downloading.detail", file.getFile().getUrl()) + "\n" + StringUtils.getStackTrace(e), + i18n("download.failed.no_code"), MessageDialogPane.MessageType.ERROR); + } + }); + Controllers.downloadTaskDialog( - new FileDownloadTask(downloadURL, modpack) - .whenComplete(Schedulers.javafx(), e -> { - if (e == null) { - ModpackInstallWizardProvider installWizardProvider; - if (version != null) - installWizardProvider = new ModpackInstallWizardProvider(profile, modpack, version); - else - installWizardProvider = new ModpackInstallWizardProvider(profile, modpack); - if (StringUtils.isNotBlank(mod.getIconUrl())) - installWizardProvider.setIconUrl(mod.getIconUrl()); - Controllers.getDecorator().startWizard(installWizardProvider); - } else if (e instanceof CancellationException) { - Controllers.showToast(i18n("message.cancelled")); - } else { - Controllers.dialog( - i18n("install.failed.downloading.detail", file.getFile().getUrl()) + "\n" + StringUtils.getStackTrace(e), - i18n("download.failed.no_code"), MessageDialogPane.MessageType.ERROR); - } - }).executor(true), + downloadTask, i18n("message.downloading"), TaskCancellationAction.NORMAL, - "安装整合包-[" + modpack.getFileName() + "]" + "安装整合包-[" + modpack.getFileName() + "]"//TODO:i18n ); } From 82bd0b86ab97003b197492fd50247c8ea6aeff46 Mon Sep 17 00:00:00 2001 From: lokins Date: Sat, 7 Feb 2026 00:28:13 +0800 Subject: [PATCH 10/20] update --- .../ui/construct/TaskExecutorDialogPane.java | 50 +++++++++++++++---- .../hmcl/ui/download/DownloadPage.java | 1 + .../jackhuang/hmcl/ui/task/TaskCenter.java | 12 +++++ .../hmcl/ui/task/TaskCenterPage.java | 5 ++ .../TaskExecutorDialogWizardDisplayer.java | 12 +++++ .../hmcl/task/AsyncTaskExecutor.java | 4 ++ 6 files changed, 73 insertions(+), 11 deletions(-) diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/TaskExecutorDialogPane.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/TaskExecutorDialogPane.java index f301e1197d..fa4371ca28 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/TaskExecutorDialogPane.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/TaskExecutorDialogPane.java @@ -29,6 +29,7 @@ import javafx.scene.layout.VBox; import org.jackhuang.hmcl.task.*; import org.jackhuang.hmcl.ui.FXUtils; +import org.jackhuang.hmcl.ui.task.TaskCenter; import org.jackhuang.hmcl.util.TaskCancellationAction; import org.jackhuang.hmcl.util.i18n.I18n; import org.jackhuang.hmcl.ui.SVG; @@ -115,17 +116,7 @@ public TaskExecutorDialogPane(@NotNull TaskCancellationAction cancel) { setCancel(cancel); - btnCancel.setOnAction(e -> { - if (cancelAction != null) { - cancelAction.run(); - return; - } - - Optional.ofNullable(executor).ifPresent(TaskExecutor::cancel); - if (onCancel.getCancellationAction() != null) { - onCancel.getCancellationAction().accept(this); - } - }); + btnCancel.setOnAction(e -> handleCancelOrClose()); speedEventHandler = FetchTask.SPEED_EVENT.registerWeak(speedEvent -> { String message = I18n.formatSpeed(speedEvent.getSpeed()); @@ -140,6 +131,41 @@ public TaskExecutorDialogPane(@NotNull TaskCancellationAction cancel) { }); } + private boolean isQueuedNotStarted() { + if (executor == null) { + return false; + } + if (executor instanceof AsyncTaskExecutor asyncExecutor) { + return !asyncExecutor.isStarted(); + } + return false; + } + + private void handleCancelOrClose() { + if (isQueuedNotStarted()) { + fireEvent(new DialogCloseEvent()); + return; + } + + if (cancelAction != null) { + cancelAction.run(); + return; + } + + Optional.ofNullable(executor).ifPresent(TaskExecutor::cancel); + if (onCancel.getCancellationAction() != null) { + onCancel.getCancellationAction().accept(this); + } + } + + private void applyQueuedStateIfNeeded() { + if (isQueuedNotStarted()) { + setCancelText("关闭"); // TODO: i18n + } else { + setCancelText(i18n("button.cancel")); + } + } + public void setExecutor(TaskExecutor executor) { setExecutor(executor, true); } @@ -158,6 +184,7 @@ public void onStop(boolean success, TaskExecutor executor) { } }); } + applyQueuedStateIfNeeded(); } public StringProperty titleProperty() { @@ -204,6 +231,7 @@ public void setBackgroundAction(Runnable action) { public void refreshTaskList() { taskListPane.refresh(); + applyQueuedStateIfNeeded(); } private Runnable cancelAction; 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 3c3545aa90..2f26e1441e 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 @@ -324,6 +324,7 @@ public Object finish(SettingsMap settings) { settings.put("task_detail", "安装游戏-[" + settings.get("name") + "]");//TODO i18n settings.put("backgroundable", true); + settings.put("return_to_download_list", true); return finishVersionDownloadingAsync(settings); } diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/task/TaskCenter.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/task/TaskCenter.java index aa53894d7b..4ecc173565 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/task/TaskCenter.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/task/TaskCenter.java @@ -71,6 +71,10 @@ public ObservableList getEntries() { return entries; } + public Entry getRunningEntry() { + return running; + } + public ObservableList getCompletedEntries() { return completedEntries; } @@ -134,4 +138,12 @@ public void onStop(boolean success, TaskExecutor executor) { executor.start(); } + + public synchronized boolean contains(TaskExecutor executor) { + return entryIndex.containsKey(executor); + } + + public synchronized boolean isStarted(TaskExecutor executor) { + return Boolean.TRUE.equals(started.get(executor)); + } } diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/task/TaskCenterPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/task/TaskCenterPage.java index 5ecd79bf8f..0aa3fcf546 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/task/TaskCenterPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/task/TaskCenterPage.java @@ -187,6 +187,11 @@ private Node createRunningItem(TaskCenter.Entry entry) { row.getChildren().addAll(label, cancelButton); row.setOnMouseClicked(e -> { + if (entry != TaskCenter.getInstance().getRunningEntry()) { + Controllers.dialog("任务等待中", entry.getTitle(), MessageDialogPane.MessageType.INFO);//TODO: i18n + return; + } + TaskExecutorDialogPane pane = Controllers.taskDialog(entry.getExecutor(), entry.getTitle(), TaskCancellationAction.NORMAL); pane.setEscAction(() -> pane.fireEvent(new DialogCloseEvent())); pane.setCancelText("关闭");//TODO: i18n diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/wizard/TaskExecutorDialogWizardDisplayer.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/wizard/TaskExecutorDialogWizardDisplayer.java index 9996a49fb2..cf48f44759 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/wizard/TaskExecutorDialogWizardDisplayer.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/wizard/TaskExecutorDialogWizardDisplayer.java @@ -18,6 +18,7 @@ package org.jackhuang.hmcl.ui.wizard; import javafx.beans.property.StringProperty; +import org.jackhuang.hmcl.task.AsyncTaskExecutor; import org.jackhuang.hmcl.task.Task; import org.jackhuang.hmcl.ui.task.TaskCenter; import org.jackhuang.hmcl.task.TaskExecutor; @@ -93,6 +94,14 @@ else if (!settings.containsKey("forbid_failure_message")) pane.setExecutor(executor); + pane.addEventHandler(DialogCloseEvent.CLOSE, event -> { + if (executor instanceof AsyncTaskExecutor asyncExecutor && !asyncExecutor.isStarted()) { + onEnd(); + Controllers.getDownloadPage().showGameDownloads(); + Controllers.navigate(Controllers.getDownloadPage()); + } + }); + if (backgroundable) { Object detailObj = settings.get("task_detail"); String detail = detailObj != null ? detailObj.toString() : pane.getTitle(); @@ -100,15 +109,18 @@ else if (!settings.containsKey("forbid_failure_message")) pane.setBackgroundAction(() -> { pane.fireEvent(new DialogCloseEvent()); TaskCenter.getInstance().enqueue(executor, pane.getTitle(), detail); + pane.refreshTaskList(); }); TaskCenter.getInstance().enqueue(executor, pane.getTitle(), detail); + pane.refreshTaskList(); } Controllers.dialog(pane); if (!backgroundable) { executor.start(); + pane.refreshTaskList(); } }); } 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..526b0a90e0 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/task/AsyncTaskExecutor.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/task/AsyncTaskExecutor.java @@ -334,4 +334,8 @@ private static Exception convertInterruptedException(Exception e) { public static void setUncaughtExceptionHandler(Thread.UncaughtExceptionHandler uncaughtExceptionHandler) { AsyncTaskExecutor.uncaughtExceptionHandler = uncaughtExceptionHandler; } + + public boolean isStarted() { + return future != null; + } } From 7f56b66359df6f408d2ce7da2507399e5ae546d0 Mon Sep 17 00:00:00 2001 From: lokins Date: Sat, 7 Feb 2026 00:34:21 +0800 Subject: [PATCH 11/20] update --- .../java/org/jackhuang/hmcl/ui/main/RootPage.java | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/RootPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/RootPage.java index feb146adb6..8c1951a34b 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/RootPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/RootPage.java @@ -66,7 +66,6 @@ import java.util.stream.Collectors; import static org.jackhuang.hmcl.ui.FXUtils.runInFX; -import static org.jackhuang.hmcl.ui.FXUtils.wrap; import static org.jackhuang.hmcl.util.i18n.I18n.i18n; import static org.jackhuang.hmcl.util.logging.Logger.LOG; @@ -172,7 +171,7 @@ protected Skin(RootPage control) { // third item in left sidebar AdvancedListItem gameItem = new AdvancedListItem(); - gameItem.setLeftGraphic(wrap(SVG.FORMAT_LIST_BULLETED)); + gameItem.setLeftGraphic(FXUtils.wrap(SVG.FORMAT_LIST_BULLETED)); gameItem.setActionButtonVisible(false); gameItem.setTitle(i18n("version.manage")); gameItem.setOnAction(e -> Controllers.navigate(Controllers.getGameListPage())); @@ -180,7 +179,7 @@ protected Skin(RootPage control) { // forth item in left sidebar AdvancedListItem downloadItem = new AdvancedListItem(); - downloadItem.setLeftGraphic(wrap(SVG.DOWNLOAD)); + downloadItem.setLeftGraphic(FXUtils.wrap(SVG.DOWNLOAD)); downloadItem.setActionButtonVisible(false); downloadItem.setTitle(i18n("download")); downloadItem.setOnAction(e -> { @@ -193,7 +192,7 @@ protected Skin(RootPage control) { } AdvancedListItem taskManagerItem = new AdvancedListItem(); - taskManagerItem.setLeftGraphic(wrap(SVG.LIST)); //SVG待更换 + taskManagerItem.setLeftGraphic(FXUtils.wrap(SVG.LIST)); //TODO SVG待更换 taskManagerItem.setActionButtonVisible(false); taskManagerItem.setTitle(i18n("task.manage")); taskManagerItem.setOnAction(e -> { @@ -203,7 +202,7 @@ protected Skin(RootPage control) { // fifth item in left sidebar AdvancedListItem launcherSettingsItem = new AdvancedListItem(); - launcherSettingsItem.setLeftGraphic(wrap(SVG.SETTINGS)); + launcherSettingsItem.setLeftGraphic(FXUtils.wrap(SVG.SETTINGS)); launcherSettingsItem.setActionButtonVisible(false); launcherSettingsItem.setTitle(i18n("settings")); launcherSettingsItem.setOnAction(e -> { @@ -216,7 +215,7 @@ protected Skin(RootPage control) { // sixth item in left sidebar AdvancedListItem terracottaItem = new AdvancedListItem(); - terracottaItem.setLeftGraphic(wrap(SVG.GRAPH2)); + terracottaItem.setLeftGraphic(FXUtils.wrap(SVG.GRAPH2)); terracottaItem.setActionButtonVisible(false); terracottaItem.setTitle(i18n("terracotta")); terracottaItem.setOnAction(e -> { From 051ded909247d44ece067c5035319263ba47c0b9 Mon Sep 17 00:00:00 2001 From: lokins Date: Sat, 7 Feb 2026 00:37:56 +0800 Subject: [PATCH 12/20] update --- .../main/java/org/jackhuang/hmcl/ui/main/RootPage.java | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/RootPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/RootPage.java index 8c1951a34b..3e99ddd410 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/RootPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/RootPage.java @@ -66,6 +66,7 @@ import java.util.stream.Collectors; import static org.jackhuang.hmcl.ui.FXUtils.runInFX; +import static org.jackhuang.hmcl.ui.FXUtils.wrap; import static org.jackhuang.hmcl.util.i18n.I18n.i18n; import static org.jackhuang.hmcl.util.logging.Logger.LOG; @@ -179,7 +180,7 @@ protected Skin(RootPage control) { // forth item in left sidebar AdvancedListItem downloadItem = new AdvancedListItem(); - downloadItem.setLeftGraphic(FXUtils.wrap(SVG.DOWNLOAD)); + downloadItem.setLeftGraphic(wrap(SVG.DOWNLOAD)); downloadItem.setActionButtonVisible(false); downloadItem.setTitle(i18n("download")); downloadItem.setOnAction(e -> { @@ -192,7 +193,7 @@ protected Skin(RootPage control) { } AdvancedListItem taskManagerItem = new AdvancedListItem(); - taskManagerItem.setLeftGraphic(FXUtils.wrap(SVG.LIST)); //TODO SVG待更换 + taskManagerItem.setLeftGraphic(wrap(SVG.LIST)); //SVG待更换 taskManagerItem.setActionButtonVisible(false); taskManagerItem.setTitle(i18n("task.manage")); taskManagerItem.setOnAction(e -> { @@ -202,7 +203,7 @@ protected Skin(RootPage control) { // fifth item in left sidebar AdvancedListItem launcherSettingsItem = new AdvancedListItem(); - launcherSettingsItem.setLeftGraphic(FXUtils.wrap(SVG.SETTINGS)); + launcherSettingsItem.setLeftGraphic(wrap(SVG.SETTINGS)); launcherSettingsItem.setActionButtonVisible(false); launcherSettingsItem.setTitle(i18n("settings")); launcherSettingsItem.setOnAction(e -> { @@ -215,7 +216,7 @@ protected Skin(RootPage control) { // sixth item in left sidebar AdvancedListItem terracottaItem = new AdvancedListItem(); - terracottaItem.setLeftGraphic(FXUtils.wrap(SVG.GRAPH2)); + terracottaItem.setLeftGraphic(wrap(SVG.GRAPH2)); terracottaItem.setActionButtonVisible(false); terracottaItem.setTitle(i18n("terracotta")); terracottaItem.setOnAction(e -> { From 72e0e05eb3ebe393fdf6742b7489fbad471e546b Mon Sep 17 00:00:00 2001 From: lokins Date: Sat, 7 Feb 2026 00:38:27 +0800 Subject: [PATCH 13/20] update --- HMCL/src/main/java/org/jackhuang/hmcl/ui/main/RootPage.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/RootPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/RootPage.java index 3e99ddd410..d7967a8da6 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/RootPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/RootPage.java @@ -172,7 +172,7 @@ protected Skin(RootPage control) { // third item in left sidebar AdvancedListItem gameItem = new AdvancedListItem(); - gameItem.setLeftGraphic(FXUtils.wrap(SVG.FORMAT_LIST_BULLETED)); + gameItem.setLeftGraphic(wrap(SVG.FORMAT_LIST_BULLETED)); gameItem.setActionButtonVisible(false); gameItem.setTitle(i18n("version.manage")); gameItem.setOnAction(e -> Controllers.navigate(Controllers.getGameListPage())); @@ -193,7 +193,7 @@ protected Skin(RootPage control) { } AdvancedListItem taskManagerItem = new AdvancedListItem(); - taskManagerItem.setLeftGraphic(wrap(SVG.LIST)); //SVG待更换 + taskManagerItem.setLeftGraphic(FXUtils.wrap(SVG.LIST)); //TODO SVG待更换 taskManagerItem.setActionButtonVisible(false); taskManagerItem.setTitle(i18n("task.manage")); taskManagerItem.setOnAction(e -> { From 641b02574c08870d53c5da423e740c1d2142afd3 Mon Sep 17 00:00:00 2001 From: lokins Date: Sat, 7 Feb 2026 00:42:19 +0800 Subject: [PATCH 14/20] update --- HMCL/src/main/java/org/jackhuang/hmcl/ui/main/RootPage.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/RootPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/RootPage.java index d7967a8da6..8e97528c01 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/RootPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/RootPage.java @@ -193,7 +193,7 @@ protected Skin(RootPage control) { } AdvancedListItem taskManagerItem = new AdvancedListItem(); - taskManagerItem.setLeftGraphic(FXUtils.wrap(SVG.LIST)); //TODO SVG待更换 + taskManagerItem.setLeftGraphic(wrap(SVG.LIST)); //TODO SVG待更换 taskManagerItem.setActionButtonVisible(false); taskManagerItem.setTitle(i18n("task.manage")); taskManagerItem.setOnAction(e -> { From a949cc149b1e4768987233ac13adde08d55a70d7 Mon Sep 17 00:00:00 2001 From: lokins Date: Sat, 7 Feb 2026 00:52:36 +0800 Subject: [PATCH 15/20] update --- HMCL/src/main/java/org/jackhuang/hmcl/ui/main/RootPage.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/RootPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/RootPage.java index 8e97528c01..66cc6cdeb8 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/RootPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/RootPage.java @@ -193,8 +193,8 @@ protected Skin(RootPage control) { } AdvancedListItem taskManagerItem = new AdvancedListItem(); - taskManagerItem.setLeftGraphic(wrap(SVG.LIST)); //TODO SVG待更换 - taskManagerItem.setActionButtonVisible(false); + taskManagerItem.setLeftGraphic(SVG.LIST.createIcon(16)); // TODO SVG待更换 + taskManagerItem.actionButtonVisibleProperty().set(false); taskManagerItem.setTitle(i18n("task.manage")); taskManagerItem.setOnAction(e -> { Controllers.navigate(new TaskCenterPage()); From 699a52e4659573e9f4847bdd1fcf0d603d83c858 Mon Sep 17 00:00:00 2001 From: lokins Date: Sat, 7 Feb 2026 00:56:05 +0800 Subject: [PATCH 16/20] update --- HMCL/src/main/java/org/jackhuang/hmcl/ui/main/RootPage.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/RootPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/RootPage.java index 66cc6cdeb8..8e97528c01 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/RootPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/RootPage.java @@ -193,8 +193,8 @@ protected Skin(RootPage control) { } AdvancedListItem taskManagerItem = new AdvancedListItem(); - taskManagerItem.setLeftGraphic(SVG.LIST.createIcon(16)); // TODO SVG待更换 - taskManagerItem.actionButtonVisibleProperty().set(false); + taskManagerItem.setLeftGraphic(wrap(SVG.LIST)); //TODO SVG待更换 + taskManagerItem.setActionButtonVisible(false); taskManagerItem.setTitle(i18n("task.manage")); taskManagerItem.setOnAction(e -> { Controllers.navigate(new TaskCenterPage()); From ded3ff864a0db6a03fee33f9fc539601df33282d Mon Sep 17 00:00:00 2001 From: lokins Date: Sat, 7 Feb 2026 17:44:42 +0800 Subject: [PATCH 17/20] update --- HMCL/src/main/java/org/jackhuang/hmcl/ui/main/RootPage.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/RootPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/RootPage.java index 6ddd56b462..bdfdfec2ac 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/RootPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/RootPage.java @@ -191,8 +191,7 @@ protected Skin(RootPage control) { } AdvancedListItem taskManagerItem = new AdvancedListItem(); - taskManagerItem.setLeftGraphic(wrap(SVG.LIST)); //TODO SVG待更换 - taskManagerItem.setActionButtonVisible(false); + taskManagerItem.setLeftIcon(SVG.LIST); //TODO SVG待更换 taskManagerItem.setTitle(i18n("task.manage")); taskManagerItem.setOnAction(e -> { Controllers.navigate(new TaskCenterPage()); From bd632ce6cb3ecba1d07e14e7a8d9fb61c127627c Mon Sep 17 00:00:00 2001 From: lokins Date: Sat, 7 Feb 2026 17:51:26 +0800 Subject: [PATCH 18/20] update --- .../org/jackhuang/hmcl/ui/construct/TaskExecutorDialogPane.java | 1 - 1 file changed, 1 deletion(-) diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/TaskExecutorDialogPane.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/TaskExecutorDialogPane.java index fa4371ca28..7a7f9194ce 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/TaskExecutorDialogPane.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/TaskExecutorDialogPane.java @@ -29,7 +29,6 @@ import javafx.scene.layout.VBox; import org.jackhuang.hmcl.task.*; import org.jackhuang.hmcl.ui.FXUtils; -import org.jackhuang.hmcl.ui.task.TaskCenter; import org.jackhuang.hmcl.util.TaskCancellationAction; import org.jackhuang.hmcl.util.i18n.I18n; import org.jackhuang.hmcl.ui.SVG; From 248704f9c46b1f79cb0b2c84d09be8cd1ef6e498 Mon Sep 17 00:00:00 2001 From: lokins Date: Sat, 7 Feb 2026 18:58:28 +0800 Subject: [PATCH 19/20] update --- .../hmcl/ui/download/DownloadPage.java | 3 + .../hmcl/ui/download/InstallersPage.java | 4 +- .../hmcl/ui/download/LocalModpackPage.java | 6 +- .../ModpackInstallWizardProvider.java | 10 ++- .../hmcl/ui/download/RemoteModpackPage.java | 4 +- .../jackhuang/hmcl/ui/task/TaskCenter.java | 75 ++++++++++++++++++- .../hmcl/ui/task/TaskCenterPage.java | 8 +- .../TaskExecutorDialogWizardDisplayer.java | 20 ++++- 8 files changed, 115 insertions(+), 15 deletions(-) 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 2f26e1441e..c3ea56aa34 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 @@ -42,6 +42,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.task.TaskCenter; import org.jackhuang.hmcl.ui.versions.DownloadListPage; import org.jackhuang.hmcl.ui.versions.HMCLLocalizedDownloadListPage; import org.jackhuang.hmcl.ui.versions.VersionPage; @@ -325,6 +326,8 @@ public Object finish(SettingsMap settings) { settings.put("task_detail", "安装游戏-[" + settings.get("name") + "]");//TODO i18n settings.put("backgroundable", true); settings.put("return_to_download_list", true); + settings.put("task_kind", TaskCenter.TaskKind.GAME_INSTALL); + settings.put("task_name", settings.get("name")); return finishVersionDownloadingAsync(settings); } diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/InstallersPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/InstallersPage.java index d0c4d7b361..7dc6d5fc42 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/InstallersPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/InstallersPage.java @@ -26,6 +26,7 @@ import org.jackhuang.hmcl.ui.construct.MessageDialogPane; import org.jackhuang.hmcl.ui.construct.RequiredValidator; import org.jackhuang.hmcl.ui.construct.Validator; +import org.jackhuang.hmcl.ui.task.TaskCenter; import org.jackhuang.hmcl.ui.wizard.WizardController; import org.jackhuang.hmcl.util.SettingsMap; @@ -41,7 +42,8 @@ public InstallersPage(WizardController controller, HMCLGameRepository repository txtName.getValidators().addAll( new RequiredValidator(), - new Validator(i18n("install.new_game.already_exists"), str -> !repository.versionIdConflicts(str)), + new Validator(i18n("install.new_game.already_exists"), str -> + !repository.versionIdConflicts(str) && !TaskCenter.getInstance().hasQueuedInstallName(TaskCenter.TaskKind.GAME_INSTALL, str)), new Validator(i18n("install.new_game.malformed"), HMCLGameRepository::isValidVersionId)); installable.bind(createBooleanBinding(txtName::validate, txtName.textProperty())); diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/LocalModpackPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/LocalModpackPage.java index 064f580d52..ac19724e1b 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/LocalModpackPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/LocalModpackPage.java @@ -35,6 +35,7 @@ import org.jackhuang.hmcl.ui.construct.MessageDialogPane; import org.jackhuang.hmcl.ui.construct.RequiredValidator; import org.jackhuang.hmcl.ui.construct.Validator; +import org.jackhuang.hmcl.ui.task.TaskCenter; import org.jackhuang.hmcl.ui.wizard.WizardController; import org.jackhuang.hmcl.util.SettingsMap; import org.jackhuang.hmcl.util.StringUtils; @@ -67,12 +68,13 @@ public LocalModpackPage(WizardController controller) { if (installAsVersion) { txtModpackName.getValidators().setAll( new RequiredValidator(), - new Validator(i18n("install.new_game.already_exists"), str -> !profile.getRepository().versionIdConflicts(str)), + new Validator(i18n("install.new_game.already_exists"), str -> + !profile.getRepository().versionIdConflicts(str) && !TaskCenter.getInstance().hasQueuedInstallName(TaskCenter.TaskKind.MODPACK_INSTALL, str)), new Validator(i18n("install.new_game.malformed"), HMCLGameRepository::isValidVersionId)); } else { txtModpackName.getValidators().setAll( new RequiredValidator(), - new Validator(i18n("install.new_game.already_exists"), str -> !ModpackHelper.isExternalGameNameConflicts(str) && Profiles.getProfiles().stream().noneMatch(p -> p.getName().equals(str))), + new Validator(i18n("install.new_game.already_exists"), str -> !ModpackHelper.isExternalGameNameConflicts(str) && Profiles.getProfiles().stream().noneMatch(p -> p.getName().equals(str)) && !TaskCenter.getInstance().hasQueuedInstallName(TaskCenter.TaskKind.MODPACK_INSTALL, str)), new Validator(i18n("install.new_game.malformed"), HMCLGameRepository::isValidVersionId)); } }); diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/ModpackInstallWizardProvider.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/ModpackInstallWizardProvider.java index db4b8c5e72..dde8ce4a45 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/ModpackInstallWizardProvider.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/ModpackInstallWizardProvider.java @@ -30,6 +30,7 @@ import org.jackhuang.hmcl.task.Task; import org.jackhuang.hmcl.ui.Controllers; import org.jackhuang.hmcl.ui.construct.MessageDialogPane.MessageType; +import org.jackhuang.hmcl.ui.task.TaskCenter; import org.jackhuang.hmcl.ui.wizard.WizardController; import org.jackhuang.hmcl.ui.wizard.WizardProvider; import org.jackhuang.hmcl.util.SettingsMap; @@ -142,11 +143,12 @@ public Object finish(SettingsMap settings) { } }); - Object name = settings.get("name"); - if (name != null) { - settings.put("task_detail", "安装整合包-[" + name + "]");//TODO: i18n + String taskName = settings.get(LocalModpackPage.MODPACK_NAME); + if (taskName != null) { + settings.put("task_detail", "安装整合包-[" + taskName + "]");//TODO: i18n } - settings.put("backgroundable", true); + settings.put("task_kind", TaskCenter.TaskKind.MODPACK_INSTALL); + settings.put("task_name", taskName); return finishModpackInstallingAsync(settings); } diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/RemoteModpackPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/RemoteModpackPage.java index 2ace1017fe..9645536e84 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/RemoteModpackPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/RemoteModpackPage.java @@ -27,6 +27,7 @@ import org.jackhuang.hmcl.ui.construct.MessageDialogPane; import org.jackhuang.hmcl.ui.construct.RequiredValidator; import org.jackhuang.hmcl.ui.construct.Validator; +import org.jackhuang.hmcl.ui.task.TaskCenter; import org.jackhuang.hmcl.ui.wizard.WizardController; import org.jackhuang.hmcl.util.SettingsMap; import org.jackhuang.hmcl.util.StringUtils; @@ -67,7 +68,8 @@ public RemoteModpackPage(WizardController controller) { txtModpackName.setText(manifest.getName().trim()); txtModpackName.getValidators().addAll( new RequiredValidator(), - new Validator(i18n("install.new_game.already_exists"), str -> !profile.getRepository().versionIdConflicts(str)), + new Validator(i18n("install.new_game.already_exists"), str -> + !profile.getRepository().versionIdConflicts(str) && !TaskCenter.getInstance().hasQueuedInstallName(TaskCenter.TaskKind.MODPACK_INSTALL, str)), new Validator(i18n("install.new_game.malformed"), HMCLGameRepository::isValidVersionId)); } diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/task/TaskCenter.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/task/TaskCenter.java index 4ecc173565..b8352d1f9d 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/task/TaskCenter.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/task/TaskCenter.java @@ -26,6 +26,7 @@ import javafx.collections.ObservableList; import org.jackhuang.hmcl.task.TaskExecutor; import org.jackhuang.hmcl.task.TaskListener; +import org.jackhuang.hmcl.util.platform.OperatingSystem; public final class TaskCenter { private static final TaskCenter INSTANCE = new TaskCenter(); @@ -34,15 +35,25 @@ public static TaskCenter getInstance() { return INSTANCE; } + public enum TaskKind { + GAME_INSTALL, + MODPACK_INSTALL, + OTHER + } + public static final class Entry { private final TaskExecutor executor; private final String title; private final String detail; + private final TaskKind kind; + private final String name; - public Entry(TaskExecutor executor, String title, String detail) { + public Entry(TaskExecutor executor, String title, String detail, TaskKind kind, String name) { this.executor = executor; this.title = title; this.detail = detail; + this.kind = kind; + this.name = name; } public TaskExecutor getExecutor() { @@ -56,6 +67,14 @@ public String getTitle() { public String getDetail() { return detail; } + + public TaskKind getKind() { + return kind; + } + + public String getName() { + return name; + } } private final ObservableList entries = FXCollections.observableArrayList(); @@ -93,7 +112,24 @@ public synchronized void enqueue(TaskExecutor executor, String title, String det return; } - Entry entry = new Entry(executor, title, detail); + Entry entry = new Entry(executor, title, detail,TaskKind.OTHER, null); + entryIndex.put(executor, entry); + entries.add(entry); + queue.add(entry); + tryStartNext(); + } + + public synchronized void enqueue(TaskExecutor executor, String title, String detail, TaskKind kind, String name) { + if (!Platform.isFxApplicationThread()) { + Platform.runLater(() -> enqueue(executor, title, detail, kind, name)); + return; + } + + if (entryIndex.containsKey(executor)) { + return; + } + + Entry entry = new Entry(executor, title, detail, kind, name); entryIndex.put(executor, entry); entries.add(entry); queue.add(entry); @@ -146,4 +182,39 @@ public synchronized boolean contains(TaskExecutor executor) { public synchronized boolean isStarted(TaskExecutor executor) { return Boolean.TRUE.equals(started.get(executor)); } + + public synchronized boolean hasQueuedInstallName(TaskKind kind, String name) { + if (name == null || kind == null) { + return false; + } + for (Entry entry : entries) { + if (entry.getKind() != kind || entry.getName() == null) { + continue; + } + if (OperatingSystem.CURRENT_OS == OperatingSystem.WINDOWS) { + if (entry.getName().equalsIgnoreCase(name)) return true; + } else { + if (entry.getName().equals(name)) return true; + } + } + return false; + } + + public synchronized boolean cancelQueued(TaskExecutor executor) { + Entry entry = entryIndex.get(executor); + if (entry == null) { + return false; + } + + if (Boolean.TRUE.equals(started.get(executor))) { + } + + queue.remove(entry); + entries.remove(entry); + entryIndex.remove(executor); + started.remove(executor); + failedEntries.add(entry); + return true; + } + } diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/task/TaskCenterPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/task/TaskCenterPage.java index 0aa3fcf546..291dacad80 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/task/TaskCenterPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/task/TaskCenterPage.java @@ -179,9 +179,15 @@ private Node createRunningItem(TaskCenter.Entry entry) { cancelButton.getStyleClass().add("dialog-cancel"); cancelButton.setOnAction(e -> { - entry.getExecutor().cancel(); + TaskCenter taskCenter = TaskCenter.getInstance(); + if (taskCenter.isStarted(entry.getExecutor())) { + entry.getExecutor().cancel(); + } else { + taskCenter.cancelQueued(entry.getExecutor()); + } e.consume(); }); + cancelButton.setOnMouseClicked(e -> e.consume()); row.getChildren().addAll(label, cancelButton); diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/wizard/TaskExecutorDialogWizardDisplayer.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/wizard/TaskExecutorDialogWizardDisplayer.java index cf48f44759..158bad6ea8 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/wizard/TaskExecutorDialogWizardDisplayer.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/wizard/TaskExecutorDialogWizardDisplayer.java @@ -95,7 +95,8 @@ else if (!settings.containsKey("forbid_failure_message")) pane.setExecutor(executor); pane.addEventHandler(DialogCloseEvent.CLOSE, event -> { - if (executor instanceof AsyncTaskExecutor asyncExecutor && !asyncExecutor.isStarted()) { + boolean returnToDownloadList = Boolean.TRUE.equals(settings.get("return_to_download_list")); + if (returnToDownloadList) { onEnd(); Controllers.getDownloadPage().showGameDownloads(); Controllers.navigate(Controllers.getDownloadPage()); @@ -106,13 +107,24 @@ else if (!settings.containsKey("forbid_failure_message")) Object detailObj = settings.get("task_detail"); String detail = detailObj != null ? detailObj.toString() : pane.getTitle(); + TaskCenter.TaskKind kind = (TaskCenter.TaskKind) settings.get("task_kind"); + String taskName = (String) settings.get("task_name"); + pane.setBackgroundAction(() -> { - pane.fireEvent(new DialogCloseEvent()); - TaskCenter.getInstance().enqueue(executor, pane.getTitle(), detail); + TaskCenter.getInstance().enqueue(executor, pane.getTitle(), detail, kind, taskName); pane.refreshTaskList(); + + boolean returnToDownloadList = Boolean.TRUE.equals(settings.get("return_to_download_list")); + onEnd(); + if (returnToDownloadList) { + Controllers.getDownloadPage().showGameDownloads(); + Controllers.navigate(Controllers.getDownloadPage()); + } + + pane.fireEvent(new DialogCloseEvent()); }); - TaskCenter.getInstance().enqueue(executor, pane.getTitle(), detail); + TaskCenter.getInstance().enqueue(executor, pane.getTitle(), detail, kind, taskName); pane.refreshTaskList(); } From ea23b956c13d604f6b855143de468313dee948bb Mon Sep 17 00:00:00 2001 From: lokins Date: Sat, 7 Feb 2026 19:01:04 +0800 Subject: [PATCH 20/20] update --- .../hmcl/ui/wizard/TaskExecutorDialogWizardDisplayer.java | 1 - 1 file changed, 1 deletion(-) diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/wizard/TaskExecutorDialogWizardDisplayer.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/wizard/TaskExecutorDialogWizardDisplayer.java index 158bad6ea8..b04a83bcc1 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/wizard/TaskExecutorDialogWizardDisplayer.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/wizard/TaskExecutorDialogWizardDisplayer.java @@ -18,7 +18,6 @@ package org.jackhuang.hmcl.ui.wizard; import javafx.beans.property.StringProperty; -import org.jackhuang.hmcl.task.AsyncTaskExecutor; import org.jackhuang.hmcl.task.Task; import org.jackhuang.hmcl.ui.task.TaskCenter; import org.jackhuang.hmcl.task.TaskExecutor;