feat: auto-fetch Mojang Java 25; strip April Fools, task list, unused locales
Mirror Repository / Mirror to CNB (push) Skipped
Mirror Repository / Mirror to Gitee (push) Skipped
Check Codes / build (push) Successful in 3m50s
Java CI / build (push) Failing after 3h0m1s

On first-capable startup, download java-runtime-epsilon via the existing
BMCLAPI-mirrored Mojang path when missing (progress dialog only; no prompt).

Remove launcher April Fools prompts/settings/UI tags, the top-right task
center page/button, dead NBT/April Fools images, and unused locale packs
(ar/de/es/ja/ru/uk) to shrink the distributable.
This commit is contained in:
浩 刘 committed 2026-11-07 20:19:40 +00:00
1 parent bcc5cda5c0
commit 4118030d9c
48 files changed
+49 -10515

No files matched your search

@@ -455,9 +455,9 @@ public class HMCLGameInstance extends DefaultGameInstance {
return GameInstanceIconType.OPTIFINE.getIcon();
GameVersionNumber version = getVersion();
if (version.isAprilFools())
return GameInstanceIconType.APRIL_FOOLS.getIcon();
else if (version instanceof GameVersionNumber.LegacySnapshot || version instanceof GameVersionNumber.Release release && release.getEaType() != GameVersionNumber.Release.ReleaseType.GA)
if (version instanceof GameVersionNumber.LegacySnapshot
|| version.isAprilFools()
|| version instanceof GameVersionNumber.Release release && release.getEaType() != GameVersionNumber.Release.ReleaseType.GA)
return GameInstanceIconType.COMMAND.getIcon();
else if (version instanceof GameVersionNumber.Old)
return GameInstanceIconType.CRAFT_TABLE.getIcon();
@@ -22,7 +22,8 @@ public enum GameInstanceIconType {
NEO_FORGE("/assets/img/neoforge.png"),
FURNACE("/assets/img/furnace.png"),
QUILT("/assets/img/quilt.png"),
APRIL_FOOLS("/assets/img/april_fools.png"),
/// Retained for settings migration; UI no longer offers this icon.
APRIL_FOOLS("/assets/img/command.png"),
CLEANROOM("/assets/img/cleanroom.png"),
LEGACY_FABRIC("/assets/img/legacyfabric.png")
;
@@ -179,15 +179,6 @@ public final class LauncherSettings extends ObservableSetting implements JsonSch
/// Whether April Fools features are disabled.
@SerializedName("disableAprilFools")
private final BooleanProperty disableAprilFools = new SimpleBooleanProperty(false);
/// Returns the April Fools disable property.
public BooleanProperty disableAprilFoolsProperty() {
return disableAprilFools;
}
/// The common Minecraft directory selection mode.
@SerializedName("commonDirectoryType")
private final ObjectProperty<EnumCommonDirectory> commonDirectoryType = new RawPreservingObjectProperty<>(EnumCommonDirectory.DEFAULT);
@@ -8,7 +8,6 @@ import com.jfoenix.validation.base.ValidatorBase;
import javafx.animation.KeyFrame;
import javafx.animation.KeyValue;
import javafx.animation.Timeline;
import javafx.application.Platform;
import javafx.beans.property.ReadOnlyStringProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
@@ -22,6 +21,7 @@ import javafx.util.Duration;
import org.jackhuang.hmcl.Launcher;
import org.jackhuang.hmcl.Metadata;
import org.jackhuang.hmcl.game.GameInstanceID;
import org.jackhuang.hmcl.game.GameJavaVersion;
import org.jackhuang.hmcl.game.HMCLGameRepository;
import org.jackhuang.hmcl.game.LauncherHelper;
import org.jackhuang.hmcl.game.ModpackHelper;
@@ -45,8 +45,6 @@ import org.jackhuang.hmcl.ui.instances.Instances;
import org.jackhuang.hmcl.upgrade.IntegrityChecker;
import org.jackhuang.hmcl.util.*;
import org.jackhuang.hmcl.util.function.ExceptionalRunnable;
import org.jackhuang.hmcl.util.i18n.I18n;
import org.jackhuang.hmcl.util.i18n.SupportedLocale;
import org.jackhuang.hmcl.util.io.FileUtils;
import org.jackhuang.hmcl.util.platform.Architecture;
import org.jackhuang.hmcl.util.platform.OperatingSystem;
@@ -57,7 +55,6 @@ import java.io.IOException;
import java.net.URI;
import java.nio.file.Files;
import java.nio.file.Path;
import java.time.LocalDate;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.CompletableFuture;
@@ -78,8 +75,6 @@ public final class Controllers {
public static final String JAVA_INTERPRETED_MODE_TIP = "javaInterpretedMode";
/// The shown-tip key for the software-rendering warning.
public static final String SOFTWARE_RENDERING = "softwareRendering";
/// The shown-tip key storing the year of the April Fools prompt.
public static final String APRIL_FOOLS = "aprilFools";
/// The coordinator for the main window's scene graph and navigation stack.
private static @Nullable Decorator decorator;
@@ -259,8 +254,6 @@ public final class Controllers {
public static void initialize(Stage stage) {
LOG.info("Start initializing application");
LOG.info("April Fools: " + AprilFools.isEnabled());
if (System.getProperty("prism.lcdtext") == null) {
@Nullable String fontAntiAliasing = SettingsManager.userSettings().fontAntiAliasingProperty().get();
if ("lcd".equalsIgnoreCase(fontAntiAliasing)) {
@@ -280,7 +273,10 @@ public final class Controllers {
// Dedicated server client: always use the fixed default cache directory.
settings().commonDirectoryTypeProperty().set(EnumCommonDirectory.DEFAULT);
Lang.thread(JavaManager::initialize, "Search Java", true);
Lang.thread(() -> {
JavaManager.initialize();
FXUtils.runInFX(Controllers::tryEnsureRecommendedMojangJava);
}, "Search Java", true);
StyleSheets.init(mainScene);
@@ -363,54 +359,42 @@ public final class Controllers {
Controllers.dialog(agreementPane);
}
aprilFools:
if (AprilFools.isEnabled()) {
int currentYear = LocalDate.now().getYear();
if (state().getShownTips().get(APRIL_FOOLS) instanceof Number year && year.intValue() >= currentYear)
break aprilFools;
tryInstallBundledModpack(GameDirectoryManager.getSelectedRepository());
}
if (!I18n.getLocale().getLocale().getLanguage().equals("zh"))
break aprilFools;
/// Downloads Mojang Runtime Java 25 when missing on supported platforms.
///
/// No confirm dialog: if already installed, returns quietly; otherwise shows a progress dialog.
private static void tryEnsureRecommendedMojangJava() {
org.jackhuang.hmcl.util.platform.Platform platform =
org.jackhuang.hmcl.util.platform.Platform.SYSTEM_PLATFORM;
GameJavaVersion target = GameJavaVersion.JAVA_25;
@Nullable SupportedLocale lzh = SupportedLocale.getSupportedLocales().stream()
.filter(locale -> "lzh".equals(locale.getName()))
.findFirst().orElse(null);
if (lzh == null) {
LOG.warning("No supported locale found for lzh");
break aprilFools;
}
Runnable updateShowTips = () -> state().getShownTips().put(APRIL_FOOLS, currentYear);
Controllers.confirmWithCountdown(i18n("launcher.april_fools.switch_lzh"), null, 10,
MessageType.QUESTION, () -> {
Controllers.confirm(i18n("launcher.april_fools.switch_lzh.confirm"), null, MessageType.QUESTION, () -> {
LOG.info("Switching locale to " + lzh);
updateShowTips.run();
settings().languageProperty().set(lzh);
Controllers.onApplicationStop();
try {
FileSaver.waitForAllSaves();
} catch (InterruptedException ignored) {
// Ignore
}
try {
Restarter.restartSelf();
} catch (IOException e) {
LOG.warning("Failed to restart self", e);
}
Platform.exit();
}, updateShowTips);
}, updateShowTips);
if (!GameJavaVersion.getSupportedVersions(platform).contains(target)) {
return;
}
if (JavaManager.getMojangJavaPlatform(platform) == null) {
return;
}
if (JavaManager.REPOSITORY.isInstalled(platform, target)) {
return;
}
tryInstallBundledModpack(GameDirectoryManager.getSelectedRepository());
LOG.info("Recommended Mojang Java " + target.majorVersion()
+ " (" + target.component() + ") missing; starting automatic download");
Controllers.taskDialog(
JavaManager.getDownloadJavaTask(DownloadProviders.getDownloadProvider(), platform, target)
.whenComplete(Schedulers.javafx(), (result, exception) -> {
if (exception != null) {
LOG.warning("Failed to download recommended Mojang Java " + target.majorVersion(), exception);
} else {
LOG.info("Installed Mojang Java " + target.majorVersion() + " at " + result.getBinary());
}
}),
i18n("java.download"),
TaskCancellationAction.NO_CANCEL
);
}
/// Installs the package found by [Metadata#findBundledModpackFile()] asynchronously, if present.
@@ -1,182 +0,0 @@
// SPDX-FileCopyrightText: Copyright (C) 2026 The HMCL Authors
// SPDX-License-Identifier: GPL-3.0-or-later WITH AdditionRef-HMCL
package org.jackhuang.hmcl.ui;
import com.jfoenix.controls.JFXButton;
import com.jfoenix.controls.JFXListView;
import com.jfoenix.controls.JFXSpinner;
import javafx.beans.property.ReadOnlyObjectProperty;
import javafx.beans.property.ReadOnlyObjectWrapper;
import javafx.geometry.Insets;
import javafx.scene.Cursor;
import javafx.scene.control.Control;
import javafx.scene.control.Label;
import javafx.scene.control.ListCell;
import javafx.scene.control.SkinBase;
import javafx.scene.control.Tooltip;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyEvent;
import javafx.scene.layout.Priority;
import javafx.scene.layout.StackPane;
import org.jackhuang.hmcl.ui.construct.ComponentList;
import org.jackhuang.hmcl.ui.construct.RipplerContainer;
import org.jackhuang.hmcl.ui.construct.TaskExecutorDialogPane;
import org.jackhuang.hmcl.ui.construct.TwoLineListItem;
import org.jackhuang.hmcl.ui.decorator.DecoratorPage;
import org.jackhuang.hmcl.util.TaskManager;
import org.jetbrains.annotations.NotNullByDefault;
import org.jetbrains.annotations.Nullable;
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
/// Displays active tasks and pending completion actions.
@NotNullByDefault
public final class TaskManagerPage extends Control implements DecoratorPage {
/// Navigation metadata for the task list.
private final ReadOnlyObjectWrapper<State> state =
new ReadOnlyObjectWrapper<>(State.fromTitle(i18n("task.list")));
/// Creates a page backed by the application-wide task manager.
public TaskManagerPage() {
getStyleClass().add("content-background");
}
@Override
public ReadOnlyObjectProperty<State> stateProperty() {
return state.getReadOnlyProperty();
}
@Override
protected SkinBase<TaskManagerPage> createDefaultSkin() {
return new Skin(this);
}
/// Binds a list view to active dialogs and pending completion actions.
@NotNullByDefault
private static final class Skin extends SkinBase<TaskManagerPage> {
/// Creates the task list and its empty-state message.
private Skin(TaskManagerPage control) {
super(control);
StackPane container = new StackPane();
container.setPadding(new Insets(10));
container.getStyleClass().add("notice-pane");
ComponentList root = new ComponentList();
root.getStyleClass().add("no-padding");
JFXListView<TaskExecutorDialogPane> list = new JFXListView<>();
list.setItems(TaskManager.getInstance().getTasks());
list.setCellFactory(view -> new TaskCell());
list.setPadding(Insets.EMPTY);
list.getStyleClass().add("no-horizontal-scrollbar");
FXUtils.ignoreEvent(list, KeyEvent.KEY_PRESSED, event -> event.getCode() == KeyCode.ESCAPE);
StackPane placeholder = new StackPane(new Label(i18n("task.list.empty")));
placeholder.getStyleClass().add("notice-pane");
list.setPlaceholder(placeholder);
ComponentList.setVgrow(list, Priority.ALWAYS);
root.getContent().setAll(list);
container.getChildren().setAll(root);
getChildren().setAll(container);
}
}
/// Displays a live task title and an action to restore progress or continue a completed task.
@NotNullByDefault
private static final class TaskCell extends ListCell<@Nullable TaskExecutorDialogPane> {
/// The list item whose title is bound to the current dialog.
private final TwoLineListItem content = new TwoLineListItem();
/// The row's shared hover and ripple feedback.
private final RipplerContainer graphic;
/// Centers running and pending indicators in a fixed-size slot to keep row titles aligned.
private final StackPane statusIcon = new StackPane();
/// Restores progress or invokes the pending completion action.
private final JFXButton actionButton = FXUtils.newToggleButton4(SVG.ARROW_FORWARD);
/// Describes the current row action.
private final Tooltip actionTooltip = new Tooltip();
/// Creates a row that activates the current task through its body or trailing action.
private TaskCell() {
content.getStyleClass().add("md-list-cell");
content.setPadding(new Insets(8));
statusIcon.setMinSize(32, 32);
statusIcon.setPrefSize(32, 32);
statusIcon.setMaxSize(32, 32);
statusIcon.setMouseTransparent(true);
content.setMinWidth(0);
TwoLineListItem.setMargin(statusIcon, new Insets(0, 8, 0, 0));
content.setLeading(statusIcon);
FXUtils.installFastTooltip(actionButton, actionTooltip);
actionButton.setOnAction(event -> activateTask());
TwoLineListItem.setMargin(actionButton, new Insets(0, 0, 0, 8));
content.setTrailing(actionButton);
graphic = new RipplerContainer(content);
graphic.setCursor(Cursor.HAND);
graphic.setOnAction(event -> activateTask());
}
/// Invokes a pending action or restores the current task's progress dialog.
private void activateTask() {
@Nullable TaskExecutorDialogPane pane = getItem();
if (pane != null) {
if (pane.pendingActionProperty().get() != null)
pane.runPendingAction();
else
pane.restore();
}
}
/// Rebinds a reused cell and updates its progress or pending-action presentation.
@Override
protected void updateItem(@Nullable TaskExecutorDialogPane pane, boolean empty) {
super.updateItem(pane, empty);
graphic.resetFeedback();
content.titleProperty().unbind();
content.setSubtitle(null);
if (empty || pane == null) {
content.setTitle("");
statusIcon.getChildren().clear();
setGraphic(null);
} else {
content.titleProperty().bind(pane.titleProperty());
@Nullable TaskExecutorDialogPane.PendingAction action = pane.pendingActionProperty().get();
actionButton.getStyleClass().removeAll("toggle-icon4", "jfx-button-border");
if (action != null) {
actionButton.getStyleClass().add("jfx-button-border");
statusIcon.getChildren().setAll(SVG.PENDING.createIcon(24));
content.setSubtitle(action.description());
actionButton.setGraphic(null);
actionButton.setText(action.text());
actionButton.setAccessibleText(action.text());
actionTooltip.setText(action.text());
} else {
actionButton.getStyleClass().add("toggle-icon4");
if (pane.queuedProperty().get()) {
statusIcon.getChildren().setAll(SVG.PENDING.createIcon(24));
content.setSubtitle(i18n("task.queued"));
} else if (statusIcon.getChildren().isEmpty() || !(statusIcon.getChildren().get(0) instanceof JFXSpinner)) {
JFXSpinner spinner = new JFXSpinner();
spinner.setRadius(12);
statusIcon.getChildren().setAll(spinner);
}
actionButton.setGraphic(SVG.ARROW_FORWARD.createIcon());
actionButton.setText(null);
actionButton.setAccessibleText(i18n("task.show"));
actionTooltip.setText(i18n("task.show"));
}
setGraphic(graphic);
}
}
}
}
@@ -3,7 +3,6 @@
package org.jackhuang.hmcl.ui.decorator;
import com.jfoenix.controls.JFXButton;
import com.jfoenix.controls.JFXSpinner;
import javafx.animation.KeyFrame;
import javafx.animation.KeyValue;
import javafx.animation.Timeline;
@@ -24,7 +23,6 @@ import javafx.scene.layout.Region;
import javafx.scene.layout.StackPane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Rectangle;
import javafx.scene.shape.SVGPath;
import javafx.util.Duration;
import org.glavo.monetfx.ColorRole;
import org.glavo.monetfx.ColorScheme;
@@ -33,13 +31,11 @@ import org.jackhuang.hmcl.ui.*;
import org.jackhuang.hmcl.ui.animation.ContainerAnimations;
import org.jackhuang.hmcl.ui.animation.TransitionPane;
import org.jackhuang.hmcl.ui.wizard.Navigation;
import org.jackhuang.hmcl.util.TaskManager;
import org.jetbrains.annotations.NotNullByDefault;
import org.jetbrains.annotations.Nullable;
import static org.jackhuang.hmcl.ui.animation.TransitionPane.DEFAULT_DURATION;
import static org.jackhuang.hmcl.ui.animation.TransitionPane.DEFAULT_INTERPOLATOR;
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
/// Renders the clipped background, title bar, and navigation content owned by a [Decorator].
@NotNullByDefault
@@ -170,7 +166,7 @@ final class MainWindowPane extends StackPane {
return backgroundNode;
}
/// Creates the task, minimize, and application-close buttons.
/// Creates the minimize and application-close buttons.
///
/// @return a size-limited top-right button row
private HBox createWindowButtons() {
@@ -179,34 +175,6 @@ final class MainWindowPane extends StackPane {
decorator.capableDraggingWindow(buttons);
buttons.setMaxSize(Region.USE_PREF_SIZE, 40);
JFXButton tasksButton = new JFXButton();
tasksButton.setFocusTraversable(false);
tasksButton.setAccessibleText(i18n("task.list"));
tasksButton.getStyleClass().add("jfx-decorator-button");
tasksButton.setOnAction(event -> {
if (!(decorator.getNavigator().getCurrentPage() instanceof TaskManagerPage))
Controllers.navigate(new TaskManagerPage());
});
decorator.forbidDraggingWindow(tasksButton);
SVGPath tasksEmptyIcon = SVG.LIST_ALT.createIcon(Themes.titleFillProperty());
holder.onWeakChangeAndOperate(TaskManager.getInstance().statusProperty(), status -> {
switch (status) {
case IDLE -> tasksButton.setGraphic(tasksEmptyIcon);
case PENDING -> tasksButton.setGraphic(SVG.PENDING.createIcon(Themes.titleFillProperty()));
case RUNNING -> {
JFXSpinner tasksSpinner = new JFXSpinner();
tasksSpinner.getStyleClass().add("small-spinner");
tasksSpinner.setRadius(9);
tasksSpinner.setMouseTransparent(true);
FXUtils.setLimitWidth(tasksSpinner, 20);
FXUtils.setLimitHeight(tasksSpinner, 20);
tasksButton.setGraphic(tasksSpinner);
}
}
});
JFXButton minimizeButton = new JFXButton();
minimizeButton.setFocusTraversable(false);
minimizeButton.setGraphic(SVG.MINIMIZE_CENTER.createIcon(Themes.titleFillProperty()));
@@ -221,7 +189,7 @@ final class MainWindowPane extends StackPane {
closeButton.setOnAction(event -> decorator.closeWindow());
decorator.forbidDraggingWindow(closeButton);
buttons.getChildren().setAll(tasksButton, minimizeButton, closeButton);
buttons.getChildren().setAll(minimizeButton, closeButton);
return buttons;
}
@@ -132,7 +132,6 @@ public final class VersionsPage extends Control implements WizardPage, Refreshab
ALL,
RELEASE,
SNAPSHOTS,
APRIL_FOOLS,
OLD
}
@@ -234,13 +233,8 @@ public final class VersionsPage extends Control implements WizardPage, Refreshab
imageView.setImage(GameInstanceIconType.GRASS.getIcon());
}
case SNAPSHOT, PENDING, UNOBFUSCATED -> {
if (versionType == ComponentRemoteVersion.Type.SNAPSHOT && gameVersion.isAprilFools()) {
tags.add(new Tag(i18n("instance.game.april_fools")));
imageView.setImage(GameInstanceIconType.APRIL_FOOLS.getIcon());
} else {
tags.add(new Tag(i18n("instance.game.snapshot")));
imageView.setImage(GameInstanceIconType.COMMAND.getIcon());
}
tags.add(new Tag(i18n("instance.game.snapshot")));
imageView.setImage(GameInstanceIconType.COMMAND.getIcon());
}
default -> {
tags.add(new Tag(i18n("instance.game.old")));
@@ -334,7 +328,6 @@ public final class VersionsPage extends Control implements WizardPage, Refreshab
VersionTypeFilter.ALL,
VersionTypeFilter.RELEASE,
VersionTypeFilter.SNAPSHOTS,
VersionTypeFilter.APRIL_FOOLS,
VersionTypeFilter.OLD
);
categoryField.getSelectionModel().select(VersionTypeFilter.RELEASE);
@@ -461,8 +454,6 @@ public final class VersionsPage extends Control implements WizardPage, Refreshab
case SNAPSHOTS -> versionType == ComponentRemoteVersion.Type.SNAPSHOT
|| versionType == ComponentRemoteVersion.Type.PENDING
|| versionType == ComponentRemoteVersion.Type.UNOBFUSCATED;
case APRIL_FOOLS -> versionType == ComponentRemoteVersion.Type.SNAPSHOT
&& it.getGameVersion().isAprilFools();
case OLD -> versionType == ComponentRemoteVersion.Type.OLD;
// case ALL,
default -> true;
@@ -45,7 +45,6 @@ public class GameInstanceIconDialog extends JFXDialogLayout {
createIcon(GameInstanceIconType.CHEST),
createIcon(GameInstanceIconType.CHICKEN),
createIcon(GameInstanceIconType.COMMAND),
createIcon(GameInstanceIconType.APRIL_FOOLS),
createIcon(GameInstanceIconType.OPTIFINE),
createIcon(GameInstanceIconType.CRAFT_TABLE),
createIcon(GameInstanceIconType.FABRIC),
@@ -82,14 +82,6 @@ public final class SettingsPage extends ScrollPane {
{
ComponentList miscPaneList = new ComponentList();
{
LineSwitch disableAprilFools = new LineSwitch();
disableAprilFools.setTitle(i18n("settings.launcher.disable_april_fools"));
disableAprilFools.setSubtitle(i18n("settings.take_effect_after_restart"));
disableAprilFools.selectedProperty().bindBidirectional(settings().disableAprilFoolsProperty());
miscPaneList.getContent().add(disableAprilFools);
}
{
BorderPane debugPane = new BorderPane();
@@ -1,56 +0,0 @@
// SPDX-FileCopyrightText: Copyright (C) 2026 The HMCL Authors
// SPDX-License-Identifier: GPL-3.0-or-later WITH AdditionRef-HMCL
package org.jackhuang.hmcl.util;
import org.jackhuang.hmcl.util.i18n.LocaleUtils;
import java.time.LocalDate;
import java.time.Month;
import java.util.List;
import static org.jackhuang.hmcl.setting.SettingsManager.settings;
/// April Fools' Day utilities.
///
/// This class provides methods to check if it is April Fools' Day or near April Fools' Day.
/// It also provides a method to check if April Fools is enabled.
///
/// @author Glavo
public final class AprilFools {
private static final boolean ENABLED;
static {
var date = LocalDate.now();
// Some countries/regions may oppose April Fools' Day for various reasons.
// Therefore, we use a regional whitelist to avoid risks.
// Currently, we have only listed a limited set of countries/regions for testing.
// We will investigate more countries/regions in the future to expand this list.
boolean supportedRegion = List.of(
"CN", "TW", "HK", "MO", "JP", "KR", "VN", "SG", "MY",
"ES", "DE", "FR", "GB", "RU", "UA", "US"
).contains(LocaleUtils.SYSTEM_DEFAULT.getCountry());
boolean aprilFoolsMode;
String value = System.getProperty("hmcl.april_fools", System.getenv("HMCL_APRIL_FOOLS"));
if ("true".equalsIgnoreCase(value))
aprilFoolsMode = true;
else if ("false".equalsIgnoreCase(value) || !supportedRegion)
aprilFoolsMode = false;
else
aprilFoolsMode = date.getMonth() == Month.APRIL && date.getDayOfMonth() == 1;
ENABLED = aprilFoolsMode && !settings().disableAprilFoolsProperty().get();
}
/// Whether April Fools is enabled.
///
/// This method returns true if April Fools is enabled.
public static boolean isEnabled() {
return ENABLED;
}
private AprilFools() {
}
}
Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 523 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 768 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 441 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 677 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 656 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 468 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 689 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 276 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 300 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 236 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 279 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 264 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 306 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 185 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 193 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 222 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 248 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 253 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 274 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 602 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1003 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 374 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 627 B

@@ -782,7 +782,6 @@ instance.empty.launch=No available instances.\nInstall the bundled server modpac
instance.empty.launch.goto_download=Install Modpack
instance.empty.hint=There are no Minecraft instances here.\nYou can try switching to another game directory or clicking here to install the server modpack.
instance.game.all=All
instance.game.april_fools=April Fools
instance.game.old=Historical
instance.game.release=Release
instance.game.releases=Releases
@@ -911,8 +910,6 @@ launcher.agreement=ToS and EULA
launcher.agreement.accept=Accept
launcher.agreement.decline=Decline
launcher.agreement.hint=You must agree to the EULA to use this software.
launcher.april_fools.switch_lzh=HMCL Launcher now supports Classical Chinese, do you want to switch to Classical Chinese mode?\nYou can switch back to English in "置設 → 貫用 → 文".
launcher.april_fools.switch_lzh.confirm=Confirm switching to Classical Chinese mode?\nClick "OK" to exit HMCL and restart it in Classical Chinese mode; click "Cancel" to keep the current language and enter HMCL homepage.
launcher.background=Background Image
launcher.background.choose=Choose background image
launcher.background.builtin=Built-in
@@ -1538,7 +1535,6 @@ settings.launcher.brightness.auto=Follow System Settings
settings.launcher.brightness.dark=Dark Mode
settings.launcher.brightness.light=Light Mode
settings.launcher.debug=Debug
settings.launcher.disable_april_fools=Do not enable April Fools features
settings.launcher.disable_auto_game_options=Do not switch game language
settings.launcher.download=Download
settings.launcher.download.threads=Download Threads
@@ -1643,8 +1639,6 @@ system.architecture=Architecture
system.operating_system=Operating System
task.background=Run in Background
task.list=Tasks
task.list.empty=No tasks
task.queued=Waiting for other tasks to finish
task.show=Show Progress
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
@@ -602,7 +602,6 @@ instance.empty.add=裝伺服器整合包
# instance.empty.launch.goto_download=
instance.empty.hint=無畢裝者。\n君且更他戲夾,抑擊斯以裝伺服器整合包。
instance.game.all=全覽
instance.game.april_fools=愚人節
instance.game.old=古典版
instance.game.release=當版
instance.game.releases=當版
@@ -731,8 +730,6 @@ launcher.agreement=客契與免責告示
launcher.agreement.accept=許
launcher.agreement.decline=拒
launcher.agreement.hint=將用而須許其客契與免責告示。
# launcher.april_fools.switch_lzh=
# launcher.april_fools.switch_lzh.confirm=
launcher.background=繪圖
launcher.background.choose=擇繪圖
# launcher.background.builtin=
@@ -1351,7 +1348,6 @@ settings.launcher.appearance=外觀
# settings.launcher.brightness.dark=
# settings.launcher.brightness.light=
settings.launcher.debug=勘誤
# settings.launcher.disable_april_fools=
settings.launcher.disable_auto_game_options=弗自改戲文
settings.launcher.download=引
settings.launcher.download.threads=綫程數
@@ -1455,8 +1451,6 @@ system.architecture=大構
system.operating_system=械網
# task.background=
# task.list=
# task.list.empty=
# task.queued=
# task.show=
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
@@ -605,7 +605,6 @@ instance.empty.launch=没有可启动的游戏。\n请安装自带的服务器
instance.empty.launch.goto_download=安装整合包
instance.empty.hint=没有已安装的游戏。\n你可以切换其他游戏文件夹,或者点击此处安装服务器整合包。
instance.game.all=全部
instance.game.april_fools=愚人节
instance.game.old=远古版
instance.game.release=正式版
instance.game.releases=正式版
@@ -734,8 +733,6 @@ launcher.agreement=用户协议与免责声明
launcher.agreement.accept=同意
launcher.agreement.decline=拒绝
launcher.agreement.hint=同意本软件的用户协议与免责声明以使用本软件。
launcher.april_fools.switch_lzh=HMCL 启动器现已支持文言文,我们诚邀你参与体验。是否要将界面语言切换至中文 (文言)?\n在切换语言后,你可以在“置設 → 貫用 → 文”中将语言切换回中文 (简体)。
launcher.april_fools.switch_lzh.confirm=确认切换语言?\n点击“确定”后 HMCL 将自动重启并将界面语言切换至中文 (文言);点击“取消”将继续使用中文 (简体) 进入 HMCL 主页。
launcher.background=背景图片
launcher.background.choose=选择背景图片
launcher.background.builtin=内置
@@ -1355,7 +1352,6 @@ settings.launcher.brightness.auto=跟随系统设置
settings.launcher.brightness.dark=深色模式
settings.launcher.brightness.light=浅色模式
settings.launcher.debug=调试
settings.launcher.disable_april_fools=不启用愚人节功能
settings.launcher.disable_auto_game_options=不自动切换游戏语言
settings.launcher.download=下载
settings.launcher.download.threads=下载线程数
@@ -1460,8 +1456,6 @@ system.architecture=架构
system.operating_system=操作系统
task.background=在后台运行
task.list=任务列表
task.list.empty=当前没有任务。
task.queued=等待其他任务完成
task.show=显示进度
@@ -599,7 +599,6 @@ instance.empty.launch=沒有可啟動的遊戲。\n請安裝內建的伺服器
instance.empty.launch.goto_download=安裝整合包
instance.empty.hint=沒有已安裝的遊戲。\n你可以切換其他遊戲目錄,或者點擊此處安裝伺服器整合包。
instance.game.all=全部
instance.game.april_fools=愚人節
instance.game.old=遠古版
instance.game.release=正式版
instance.game.releases=正式版
@@ -728,8 +727,6 @@ launcher.agreement=使用者協議與免責宣告
launcher.agreement.accept=同意
launcher.agreement.decline=拒絕
launcher.agreement.hint=同意本軟體的使用者協議與免責宣告以使用本軟體。
launcher.april_fools.switch_lzh=HMCL 啟動器現已支援文言文,我們誠邀你參與體驗。是否要將介面語言切換至中文 (文言)?\n在切換語言後,你可以在「置設 → 貫用 → 文」中將語言切換回中文 (繁體)。
launcher.april_fools.switch_lzh.confirm=確認切換語言?\n點擊「確定」後 HMCL 將自動重新啟動並將介面語言切換至中文 (文言);點擊「取消」將繼續使用中文 (繁體) 進入 HMCL 主頁。
launcher.background=背景圖片
launcher.background.choose=選取背景圖片
launcher.background.builtin=內建
@@ -1349,7 +1346,6 @@ settings.launcher.brightness.auto=跟隨系統設定
settings.launcher.brightness.dark=深色模式
settings.launcher.brightness.light=淺色模式
settings.launcher.debug=除錯
settings.launcher.disable_april_fools=不啟用愚人節功能
settings.launcher.disable_auto_game_options=不自動切換遊戲語言
settings.launcher.download=下載
settings.launcher.download.threads=下載執行緒數
@@ -1454,8 +1450,6 @@ system.architecture=架構
system.operating_system=作業系統
task.background=在背景執行
task.list=任務列表
task.list.empty=目前沒有任務。
task.queued=等待其他任務完成
task.show=顯示進度
@@ -2,14 +2,12 @@
// SPDX-License-Identifier: GPL-3.0-or-later WITH AdditionRef-HMCL
package org.jackhuang.hmcl.ui.construct;
import com.jfoenix.controls.JFXSpinner;
import javafx.application.Platform;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.control.ButtonBase;
import javafx.scene.control.Label;
import javafx.scene.layout.StackPane;
import javafx.scene.shape.SVGPath;
import org.jackhuang.hmcl.setting.LauncherSettings;
import org.jackhuang.hmcl.setting.SettingsManager;
import org.jackhuang.hmcl.task.Task;
@@ -17,8 +15,6 @@ import org.jackhuang.hmcl.task.TaskExecutor;
import org.jackhuang.hmcl.task.TaskListener;
import org.jackhuang.hmcl.ui.Controllers;
import org.jackhuang.hmcl.ui.DialogUtils;
import org.jackhuang.hmcl.ui.SVG;
import org.jackhuang.hmcl.ui.TaskManagerPage;
import org.jackhuang.hmcl.ui.decorator.Decorator;
import org.jackhuang.hmcl.util.TaskCancellationAction;
import org.jackhuang.hmcl.util.TaskManager;
@@ -34,7 +30,6 @@ import org.junit.jupiter.params.provider.ValueSource;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.Callable;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CountDownLatch;
@@ -73,7 +68,7 @@ final class TaskExecutorDialogPaneTest {
field.set(null, previousSettings);
}
/// Verifies dialog reuse and the task button's idle, running, and pending presentations.
/// Verifies dialog reuse and TaskManager status transitions without a task-list chrome button.
@Test
void restoresThroughMainWindow() throws Exception {
onFxThread(() -> {
@@ -86,25 +81,17 @@ final class TaskExecutorDialogPaneTest {
TaskExecutorDialogPane pane = new TaskExecutorDialogPane(TaskCancellationAction.NORMAL);
TaskExecutorDialogPane second = new TaskExecutorDialogPane(TaskCancellationAction.NORMAL);
try {
ButtonBase tasksButton = decorator.getRoot().lookupAll(".jfx-decorator-button").stream()
.filter(node -> i18n("task.list").equals(node.getAccessibleText()))
.map(ButtonBase.class::cast).findFirst().orElseThrow();
Node icon = Objects.requireNonNull(tasksButton.getGraphic());
ControlledExecutor executor = new ControlledExecutor();
pane.setExecutor(executor, false);
executor.start();
Controllers.dialog(pane);
assertEquals(TaskManager.Status.IDLE, TaskManager.getInstance().statusProperty().get());
assertSame(icon, tasksButton.getGraphic());
Node progress = pane.getCenter();
pane.runInBackground();
assertEquals(TaskManager.Status.RUNNING, TaskManager.getInstance().statusProperty().get());
Node spinner = Objects.requireNonNull(tasksButton.getGraphic());
assertInstanceOf(JFXSpinner.class, spinner);
pane.restore();
pane.restore();
assertEquals(TaskManager.Status.IDLE, TaskManager.getInstance().statusProperty().get());
assertSame(icon, tasksButton.getGraphic());
assertTrue(DialogUtils.isShowing(pane));
assertSame(progress, pane.getCenter());
@Nullable JFXDialogPane stack = (JFXDialogPane) pane.getProperties()
@@ -119,38 +106,26 @@ final class TaskExecutorDialogPaneTest {
secondExecutor.start();
Controllers.dialog(second);
second.runInBackground();
Node nextSpinner = Objects.requireNonNull(tasksButton.getGraphic());
assertInstanceOf(JFXSpinner.class, nextSpinner);
assertNotSame(spinner, nextSpinner);
pane.runInBackground();
pane.restore();
assertEquals(TaskManager.Status.RUNNING, TaskManager.getInstance().statusProperty().get());
assertSame(nextSpinner, tasksButton.getGraphic());
AtomicInteger actions = new AtomicInteger();
pane.runInBackground();
assertFalse(pane.runOrDeferCompletionAction("Install", "Ready to install", actions::incrementAndGet));
assertEquals(TaskManager.Status.RUNNING, TaskManager.getInstance().statusProperty().get());
assertSame(nextSpinner, tasksButton.getGraphic());
second.restore();
assertEquals(TaskManager.Status.PENDING, TaskManager.getInstance().statusProperty().get());
SVGPath pendingIcon = assertInstanceOf(SVGPath.class, tasksButton.getGraphic());
assertEquals(SVG.PENDING.getPath(), pendingIcon.getContent());
second.runInBackground();
assertEquals(TaskManager.Status.RUNNING, TaskManager.getInstance().statusProperty().get());
Node resumedSpinner = assertInstanceOf(JFXSpinner.class, tasksButton.getGraphic());
assertNotSame(nextSpinner, resumedSpinner);
pane.runPendingAction();
assertEquals(1, actions.get());
assertEquals(TaskManager.Status.RUNNING, TaskManager.getInstance().statusProperty().get());
assertSame(resumedSpinner, tasksButton.getGraphic());
assertFalse(second.runOrDeferCompletionAction("Install", "Ready to install", actions::incrementAndGet));
assertEquals(TaskManager.Status.PENDING, TaskManager.getInstance().statusProperty().get());
assertEquals(SVG.PENDING.getPath(), assertInstanceOf(SVGPath.class, tasksButton.getGraphic()).getContent());
second.setExecutor(null);
assertEquals(TaskManager.Status.IDLE, TaskManager.getInstance().statusProperty().get());
assertSame(icon, tasksButton.getGraphic());
} finally {
DialogUtils.close(pane);
pane.setExecutor(null);
@@ -351,15 +326,10 @@ final class TaskExecutorDialogPaneTest {
});
}
/// Verifies a visible task list switches from progress to a retained, single-use install action.
/// Verifies a background task retains a single-use install action until runPendingAction.
@Test
void backgroundCompletionWaitsForInstallAction() throws Exception {
AtomicInteger installations = new AtomicInteger();
TaskManagerPage page = onFxThread(() -> {
TaskManagerPage result = new TaskManagerPage();
new Scene(result, 600, 400);
return result;
});
TaskExecutorDialogPane pane = onFxThread(() -> {
TaskExecutorDialogPane dialog = new TaskExecutorDialogPane(TaskCancellationAction.NORMAL);
dialog.setTitle("Example modpack");
@@ -370,9 +340,6 @@ final class TaskExecutorDialogPaneTest {
DialogUtils.show(container, dialog);
executor.start();
dialog.runInBackground();
page.applyCss();
page.layout();
assertFalse(page.lookupAll(".jfx-spinner").isEmpty());
assertFalse(dialog.runOrDeferCompletionAction(i18n("button.install"), i18n("modpack.install.ready"),
installations::incrementAndGet));
executor.stop(true);
@@ -384,17 +351,7 @@ final class TaskExecutorDialogPaneTest {
assertFalse(DialogUtils.isShowing(pane));
assertTrue(TaskManager.getInstance().getTasks().contains(pane));
assertEquals(TaskManager.Status.PENDING, TaskManager.getInstance().statusProperty().get());
page.applyCss();
page.layout();
ButtonBase install = page.lookupAll(".jfx-button").stream()
.filter(ButtonBase.class::isInstance)
.map(ButtonBase.class::cast)
.filter(button -> i18n("button.install").equals(button.getText()))
.findFirst().orElseThrow();
assertTrue(page.lookupAll(".label").stream().anyMatch(node -> node instanceof Label label
&& i18n("modpack.install.ready").equals(label.getText())));
assertTrue(page.lookupAll(".jfx-spinner").isEmpty());
install.fire();
assertNotNull(pane.pendingActionProperty().get());
pane.runPendingAction();
assertEquals(1, installations.get());
assertNull(pane.pendingActionProperty().get());
@@ -18,9 +18,6 @@
"language": {
"type": "string"
},
"disableAprilFools": {
"type": "boolean"
},
"commonDirectoryType": {
"enum": [
"DEFAULT",