diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/setting/ThemeColorType.java b/HMCL/src/main/java/org/jackhuang/hmcl/setting/ThemeColorType.java index ae15616d951..30cf619e729 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/setting/ThemeColorType.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/setting/ThemeColorType.java @@ -32,5 +32,8 @@ public enum ThemeColorType { CUSTOM, /// Extracts the launcher theme color from the current background when possible. - BACKGROUND + BACKGROUND, + + /// Uses time-varying color of oxidizing copper provided by Minecraft + COPPER } diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/theme/ThemePackManager.java b/HMCL/src/main/java/org/jackhuang/hmcl/theme/ThemePackManager.java index c800b1041d1..f4d6a54ebe2 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/theme/ThemePackManager.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/theme/ThemePackManager.java @@ -35,6 +35,7 @@ import org.jackhuang.hmcl.task.CacheFileTask; import org.jackhuang.hmcl.util.MathUtils; import org.jackhuang.hmcl.util.StringUtils; +import org.jackhuang.hmcl.util.aprilfools.TheCopperAge; import org.jackhuang.hmcl.util.gson.JsonUtils; import org.jackhuang.hmcl.util.i18n.LocalizedText; import org.jackhuang.hmcl.util.io.ContentEncoding; @@ -721,6 +722,9 @@ private static ThemeColorSource currentThemeColorSource() throws IOException { } return ThemeColorSource.wallpaper(); } + if (themeColorType == ThemeColorType.COPPER) { + return ThemeColorSource.custom(ThemeColor.of(TheCopperAge.getColor())); + } ThemeColor color = Objects.requireNonNullElse(settings().customThemeColorProperty().get(), ThemeColor.DEFAULT); return ThemeColorSource.custom(color); diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/theme/Themes.java b/HMCL/src/main/java/org/jackhuang/hmcl/theme/Themes.java index df9cfe34b68..845f4702124 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/theme/Themes.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/theme/Themes.java @@ -62,6 +62,7 @@ import org.jackhuang.hmcl.ui.MacOSNativeUtils; import org.jackhuang.hmcl.ui.WindowsNativeUtils; import org.jackhuang.hmcl.util.MathUtils; +import org.jackhuang.hmcl.util.aprilfools.TheCopperAge; import org.jackhuang.hmcl.util.io.FileUtils; import org.jackhuang.hmcl.util.platform.NativeUtils; import org.jackhuang.hmcl.util.platform.OSVersion; @@ -118,6 +119,7 @@ public final class Themes { if (FXUtils.ACCENT_COLOR != null) { observables.add(FXUtils.ACCENT_COLOR); } + observables.add(TheCopperAge.colorProperty()); bind(observables.toArray(new Observable[0])); } @@ -196,11 +198,17 @@ static ThemeColor resolveThemeColor( return fallback; } } + if (themeColorType == ThemeColorType.COPPER) { + TheCopperAge.startOxidation(); + return ThemeColor.of(TheCopperAge.getColor()); + } else { + TheCopperAge.stopAndClearState(); + } return switch (themeColorType) { - case DEFAULT -> ThemeColor.DEFAULT; case SYSTEM -> getSystemThemeColor(); case CUSTOM -> fallback; case BACKGROUND -> resolveWallpaperThemeColor(fallback, backgroundType); + default -> ThemeColor.DEFAULT; }; } @@ -209,6 +217,7 @@ private static ThemeColor resolveThemeColorSource( ThemeColor fallback, @Nullable ThemeColorSource source, BackgroundType backgroundType) { + TheCopperAge.stopAndClearState(); if (source == null) { return fallback; } 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 598e2887906..dd6b20c90a7 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/Controllers.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/Controllers.java @@ -59,7 +59,8 @@ import org.jackhuang.hmcl.ui.instances.Instances; import org.jackhuang.hmcl.upgrade.UpdateChecker; import org.jackhuang.hmcl.util.*; -import org.jackhuang.hmcl.util.i18n.I18n; +import org.jackhuang.hmcl.util.aprilfools.AprilFools; +import org.jackhuang.hmcl.util.aprilfools.Lzh; import org.jackhuang.hmcl.util.i18n.SupportedLocale; import org.jackhuang.hmcl.util.io.FileUtils; import org.jackhuang.hmcl.util.platform.Architecture; @@ -71,7 +72,6 @@ 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; @@ -87,7 +87,6 @@ public final class Controllers { public static final String JAVA_VERSION_TIP = "javaVersion"; public static final String JAVA_INTERPRETED_MODE_TIP = "javaInterpretedMode"; public static final String SOFTWARE_RENDERING = "softwareRendering"; - public static final String APRIL_FOOLS = "aprilFools"; private static GameInstancePage gameInstancePage; private static Lazy gameListPage = new Lazy<>(GameListPage::new); @@ -222,6 +221,7 @@ private static void configureMainStage(Stage stage) { /// Initializes the main application stage, scene graph, and background services. /// /// @param stage the primary application stage, which must not have been shown + @FXThread public static void initialize(Stage stage) { LOG.info("Start initializing application"); @@ -339,32 +339,16 @@ public static void initialize(Stage stage) { 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; - - if (!I18n.getLocale().getLocale().getLanguage().equals("zh")) - break aprilFools; - - 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); - + var aprilFoolsType = AprilFools.getRandomTypeToPrompt(); + if (aprilFoolsType == AprilFools.Type.LZH) { Controllers.confirmWithCountdown(i18n("launcher.april_fools.switch_lzh"), null, 10, MessageType.QUESTION, () -> { Controllers.confirm(i18n("launcher.april_fools.switch_lzh.confirm"), null, MessageType.QUESTION, () -> { + SupportedLocale lzh = Lzh.getLzhLocale(); + LOG.info("Switching locale to " + lzh); - updateShowTips.run(); + AprilFools.updateShownTips(); settings().languageProperty().set(lzh); Controllers.onApplicationStop(); @@ -382,8 +366,17 @@ public static void initialize(Stage stage) { } Platform.exit(); - }, updateShowTips); - }, updateShowTips); + }, AprilFools::updateShownTips); + }, AprilFools::updateShownTips); + } else if (aprilFoolsType == AprilFools.Type.THE_COPPER_AGE) { + Controllers.confirmWithCountdown(i18n("launcher.april_fools.copper"), null, 10, + MessageType.QUESTION, () -> { + Controllers.confirm(i18n("launcher.april_fools.copper.confirm"), null, MessageType.QUESTION, () -> { + AprilFools.updateShownTips(); + settings().getThemeAppearanceOverrides().add(LauncherSettings.THEME_APPEARANCE_COLOR); + settings().themeColorTypeProperty().set(ThemeColorType.COPPER); + }, AprilFools::updateShownTips); + }, AprilFools::updateShownTips); } tryInstallBundledModpack(GameDirectoryManager.getSelectedRepository()); diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/MainPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/MainPage.java index b8888f8242f..636d9e9a244 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/MainPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/MainPage.java @@ -23,6 +23,7 @@ import javafx.animation.KeyValue; import javafx.animation.RotateTransition; import javafx.animation.Timeline; +import javafx.beans.binding.StringBinding; import javafx.beans.property.*; import javafx.beans.value.ObservableValue; import javafx.collections.FXCollections; @@ -37,10 +38,7 @@ import javafx.scene.image.ImageView; import javafx.scene.input.MouseButton; import javafx.scene.input.MouseEvent; -import javafx.scene.layout.BorderPane; -import javafx.scene.layout.HBox; -import javafx.scene.layout.StackPane; -import javafx.scene.layout.VBox; +import javafx.scene.layout.*; import javafx.scene.text.TextFlow; import javafx.util.Duration; import org.jackhuang.hmcl.Metadata; @@ -66,6 +64,7 @@ import org.jackhuang.hmcl.upgrade.UpdateChecker; import org.jackhuang.hmcl.upgrade.UpdateHandler; import org.jackhuang.hmcl.util.*; +import org.jackhuang.hmcl.util.aprilfools.TheCopperAge; import org.jackhuang.hmcl.util.i18n.I18n; import org.jackhuang.hmcl.util.javafx.BindingMapping; import org.jackhuang.hmcl.util.platform.OperatingSystem; @@ -127,6 +126,28 @@ public final class MainPage extends StackPane implements DecoratorPage { titleLabel.textFillProperty().bind(Themes.titleFillProperty()); titleNode.getChildren().setAll(titleIcon, titleLabel); + Label copperStateLabel = new Label(); + copperStateLabel.getStyleClass().add("jfx-decorator-title"); + copperStateLabel.textFillProperty().bind(Themes.titleFillProperty()); + copperStateLabel.textProperty().bind(new StringBinding() { + { + bind(TheCopperAge.displayedState()); + } + + @Override + protected String computeValue() { + var copperState = TheCopperAge.displayedState().get(); + if (copperState.isEmpty()) return ""; + return "(%s)".formatted(copperState); + } + }); + if (I18n.isUpsideDown()) { + // Not rotating because we're already in upside-down English + titleNode.getChildren().add(1, copperStateLabel); // Place it between icon and title + } else { + titleNode.getChildren().add(copperStateLabel); + } + state.setValue(new State(null, titleNode, false, false, true)); setPadding(new Insets(20)); @@ -313,8 +334,28 @@ public void accept(@Nullable HMCLGameInstance currentGame) { launchPane.getChildren().setAll(launchButton, menuButton); } - getChildren().addAll(updatePane, launchPane); + HBox copperUtilities = new HBox(16); + copperUtilities.getStyleClass().add("card"); + copperUtilities.setMaxSize(Region.USE_PREF_SIZE, Region.USE_PREF_SIZE); + copperUtilities.visibleProperty().bind(TheCopperAge.oxidizingProperty()); + StackPane.setAlignment(copperUtilities, Pos.BOTTOM_LEFT); + { + JFXButton btnAxe = new JFXButton(); + btnAxe.getStyleClass().add("toggle-icon4"); + btnAxe.setGraphic(new ImageView(FXUtils.newBuiltinImage("/assets/img/iron_axe.png", 32D, 32D, true, true))); + FXUtils.installFastTooltip(btnAxe, i18n("launcher.april_fools.copper.action.axe")); + btnAxe.setOnAction(e -> TheCopperAge.useAxe()); + + JFXButton btnWax = new JFXButton(); + btnWax.getStyleClass().add("toggle-icon4"); + btnWax.setGraphic(new ImageView(FXUtils.newBuiltinImage("/assets/img/honeycomb.png", 32D, 32D, true, true))); + FXUtils.installFastTooltip(btnWax, i18n("launcher.april_fools.copper.action.wax")); + btnWax.setOnAction(e -> TheCopperAge.wax()); + + copperUtilities.getChildren().setAll(btnAxe, btnWax); + } + getChildren().addAll(updatePane, launchPane, copperUtilities); } private void showUpdateDialog(boolean show) { diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/PersonalizationPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/PersonalizationPage.java index 003ea3f048c..b1ebc96f63d 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/PersonalizationPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/PersonalizationPage.java @@ -504,13 +504,19 @@ protected Node createRightNode() { i18n("settings.launcher.theme_color_type.background"), ThemeColorType.BACKGROUND); + var copperColorChoice = new RadioChoiceList.Choice( + i18n("settings.launcher.theme_color_type.copper"), + ThemeColorType.COPPER); + copperColorChoice.setSubtitle(i18n("settings.launcher.theme_color_type.copper.description")); + RadioChoiceList themeColorChoiceList = new RadioChoiceList<>(); themeColorChoiceList.setFallbackValue(ThemeColorType.DEFAULT); themeColorChoiceList.setChoices(Arrays.asList( defaultColorChoice, systemColorChoice, customColorChoice, - backgroundColorChoice)); + backgroundColorChoice, + copperColorChoice)); JFXButton themeColorOverrideButton = createThemeAppearanceOverrideButton(); themeColorSublist.setTitleRight(themeColorOverrideButton); diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/util/AprilFools.java b/HMCL/src/main/java/org/jackhuang/hmcl/util/aprilfools/AprilFools.java similarity index 54% rename from HMCL/src/main/java/org/jackhuang/hmcl/util/AprilFools.java rename to HMCL/src/main/java/org/jackhuang/hmcl/util/aprilfools/AprilFools.java index 506f3f3bf01..b810d408a9b 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/util/AprilFools.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/util/aprilfools/AprilFools.java @@ -15,28 +15,38 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -package org.jackhuang.hmcl.util; +package org.jackhuang.hmcl.util.aprilfools; import org.jackhuang.hmcl.util.i18n.LocaleUtils; +import org.jetbrains.annotations.Nullable; import java.time.LocalDate; import java.time.Month; +import java.util.Arrays; import java.util.List; +import java.util.Random; +import java.util.function.Supplier; import static org.jackhuang.hmcl.setting.SettingsManager.settings; +import static org.jackhuang.hmcl.setting.SettingsManager.state; /// 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. +/// This class provides methods to check if it is April Fools' Day or if April Fools is enabled. +/// It also provides an enum of different April Fools effect types. /// /// @author Glavo public final class AprilFools { + public static final String APRIL_FOOLS_TIP = "aprilFools"; + private static final boolean ENABLED; + private static final int currentYear; + static { var date = LocalDate.now(); + currentYear = date.getYear(); // Some countries/regions may oppose April Fools' Day for various reasons. // Therefore, we use a regional whitelist to avoid risks. @@ -47,16 +57,16 @@ public final class AprilFools { "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(); + if ("true".equalsIgnoreCase(value)) { + ENABLED = true; + } else if ("false".equalsIgnoreCase(value)) { + ENABLED = false; + } else { + ENABLED = supportedRegion && date.getMonth() == Month.APRIL && date.getDayOfMonth() == 1 + && !settings().disableAprilFoolsProperty().get() + && !(state().getShownTips().get(APRIL_FOOLS_TIP) instanceof Number year && year.intValue() >= currentYear); + } } /// Whether April Fools is enabled. @@ -66,6 +76,28 @@ public static boolean isEnabled() { return ENABLED; } + public static @Nullable Type getRandomTypeToPrompt() { + if (!isEnabled()) return null; + Object[] supportedTypes = Arrays.stream(Type.values()).filter(t -> t.shouldPromptSupplier.get()).toArray(); + if (supportedTypes.length == 0) return null; + return (Type) supportedTypes[new Random().nextInt(supportedTypes.length)]; + } + + public static void updateShownTips() { + state().getShownTips().put(APRIL_FOOLS_TIP, currentYear); + } + private AprilFools() { } + + public enum Type { + LZH(Lzh::shouldPrompt), + THE_COPPER_AGE(() -> !TheCopperAge.isOxidizing()); + + private final Supplier shouldPromptSupplier; + + Type(Supplier supportedLazy) { + this.shouldPromptSupplier = supportedLazy; + } + } } diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/util/aprilfools/Lzh.java b/HMCL/src/main/java/org/jackhuang/hmcl/util/aprilfools/Lzh.java new file mode 100644 index 00000000000..7f7fa033799 --- /dev/null +++ b/HMCL/src/main/java/org/jackhuang/hmcl/util/aprilfools/Lzh.java @@ -0,0 +1,51 @@ +/* + * Hello Minecraft! Launcher + * Copyright (C) 2026 huangyuhui and contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.jackhuang.hmcl.util.aprilfools; + +import org.jackhuang.hmcl.util.Lazy; +import org.jackhuang.hmcl.util.i18n.I18n; +import org.jackhuang.hmcl.util.i18n.SupportedLocale; + +import static org.jackhuang.hmcl.util.logging.Logger.LOG; + +public final class Lzh { + + private static final Lazy lzhLocale = new Lazy<>(() -> { + if (!I18n.getLocale().getLocale().getLanguage().equals("zh")) + return null; + + 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"); + + return lzh; + }); + + public static boolean shouldPrompt() { + return lzhLocale.get() != null; + } + + public static SupportedLocale getLzhLocale() { + return lzhLocale.get(); + } + + private Lzh() { + } +} diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/util/aprilfools/TheCopperAge.java b/HMCL/src/main/java/org/jackhuang/hmcl/util/aprilfools/TheCopperAge.java new file mode 100644 index 00000000000..92d9f9b39da --- /dev/null +++ b/HMCL/src/main/java/org/jackhuang/hmcl/util/aprilfools/TheCopperAge.java @@ -0,0 +1,162 @@ +/* + * Hello Minecraft! Launcher + * Copyright (C) 2026 huangyuhui and contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.jackhuang.hmcl.util.aprilfools; + +import javafx.beans.binding.ObjectBinding; +import javafx.beans.binding.StringBinding; +import javafx.beans.property.*; +import javafx.beans.value.ObservableValue; +import javafx.scene.paint.Color; +import org.jackhuang.hmcl.ui.FXUtils; +import org.jackhuang.hmcl.util.FXThread; +import org.jackhuang.hmcl.util.Lang; + +import java.util.Locale; +import java.util.Random; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; + +import static org.jackhuang.hmcl.util.i18n.I18n.i18n; + +public final class TheCopperAge { + + private static final ReadOnlyObjectWrapper oxidationDegree = new ReadOnlyObjectWrapper<>(Degree.UNOXIDIZED); + private static final ObjectBinding color = new ObjectBinding<>() { + { + bind(oxidationDegree); + } + + @Override + protected Color computeValue() { + return oxidationDegree.get().getColor(); + } + }; + private static final BooleanProperty waxed = new SimpleBooleanProperty(false); + + private static final ReadOnlyBooleanWrapper oxidizing = new ReadOnlyBooleanWrapper(false); + + private static final StringBinding displayedState = new StringBinding() { + { + bind(oxidationDegree, waxed, oxidizing); + } + + @Override + protected String computeValue() { + if (!oxidizing.get()) return ""; + + String waxedString = i18n("launcher.april_fools.copper.state.waxed"); + + var degree = oxidationDegree.get(); + if (degree == Degree.UNOXIDIZED) { + return waxed.get() ? + i18n("launcher.april_fools.copper.state.0", waxedString) + : ""; + } else { + String degreeName = i18n("launcher.april_fools.copper.state." + degree.name().toLowerCase(Locale.ROOT)); + return waxed.get() + ? i18n("launcher.april_fools.copper.state.1", waxedString, degreeName) + : i18n("launcher.april_fools.copper.state.0", degreeName); + } + } + }; + + private static ScheduledExecutorService oxidationScheduler = null; + + public static ObservableValue colorProperty() { + return color; + } + + public static Color getColor() { + return colorProperty().getValue(); + } + + public static ReadOnlyBooleanProperty oxidizingProperty() { + return oxidizing.getReadOnlyProperty(); + } + + public static boolean isOxidizing() { + return oxidizing.get(); + } + + public static StringBinding displayedState() { + return displayedState; + } + + @FXThread + public static void startOxidation() { + if (isOxidizing()) return; + oxidizing.set(true); + oxidationScheduler = Executors.newScheduledThreadPool(1, Lang.daemonThreadFactory("The Copper Age Oxidizer")); + var random = new Random(); + + oxidationScheduler.scheduleAtFixedRate(() -> { + if (random.nextDouble() < 0.2) tryOxidize(); + }, 2, 5, TimeUnit.SECONDS); + } + + @FXThread + public static void stopAndClearState() { + if (!isOxidizing()) return; + oxidizing.set(false); + oxidationScheduler.shutdownNow(); + oxidationScheduler = null; + oxidationDegree.set(Degree.UNOXIDIZED); + waxed.set(false); + } + + private static void tryOxidize() { + FXUtils.runInFX(() -> { + if (!waxed.get()) oxidationDegree.set(Degree.values()[Math.min(oxidationDegree.get().ordinal() + 1, Degree.values().length - 1)]); + }); + } + + @FXThread + public static void wax() { + waxed.set(true); + } + + @FXThread + public static void useAxe() { + if (waxed.get()) { + waxed.set(false); + } else { + oxidationDegree.set(Degree.values()[Math.max(oxidationDegree.get().ordinal() - 1, 0)]); + } + } + + private TheCopperAge() { + } + + public enum Degree { + UNOXIDIZED("#D87F33"), + EXPOSED("#876B62"), + WEATHERED("#3A8E8C"), + OXIDIZED("#167E86"); + + private final Color color; + + Degree(String color) { + this.color = Color.web(color); + } + + public Color getColor() { + return color; + } + } +} diff --git a/HMCL/src/main/resources/assets/img/honeycomb.png b/HMCL/src/main/resources/assets/img/honeycomb.png new file mode 100644 index 00000000000..561b4ab9a9f Binary files /dev/null and b/HMCL/src/main/resources/assets/img/honeycomb.png differ diff --git a/HMCL/src/main/resources/assets/img/iron_axe.png b/HMCL/src/main/resources/assets/img/iron_axe.png new file mode 100644 index 00000000000..43e742c28f7 Binary files /dev/null and b/HMCL/src/main/resources/assets/img/iron_axe.png differ diff --git a/HMCL/src/main/resources/assets/lang/I18N.properties b/HMCL/src/main/resources/assets/lang/I18N.properties index a3acd76c648..f67ea7cd1d2 100644 --- a/HMCL/src/main/resources/assets/lang/I18N.properties +++ b/HMCL/src/main/resources/assets/lang/I18N.properties @@ -929,8 +929,18 @@ 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=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.april_fools.copper=In order to keep up with the updates in Minecraft 1.21.9, HMCL Launcher now supports "The Copper Age" theme. Do you want to switch to "The Copper Age" theme color type?\nYou can switch it back in "Settings → Appearance → Theme Color" +launcher.april_fools.copper.confirm=Confirm switching theme color type to "The Copper Age"?\nClick "OK" to change the theme color, or click "Cancel" to keep the currently selected color. +launcher.april_fools.copper.action.axe=Remove wax or a layer of aerugo +launcher.april_fools.copper.action.wax=Wax +launcher.april_fools.copper.state.0=%1s HMCL +launcher.april_fools.copper.state.1=%1s %2s HMCL +launcher.april_fools.copper.state.exposed=Exposed +launcher.april_fools.copper.state.oxidized=Oxidized +launcher.april_fools.copper.state.waxed=Waxed +launcher.april_fools.copper.state.weathered=Weathered launcher.background=Background Image launcher.background.choose=Choose background image launcher.background.builtin=Built-in @@ -1620,6 +1630,8 @@ settings.launcher.theme_color_style.tonal_spot.desc=Reduces color saturation for settings.launcher.theme_color_style.vibrant=Vibrant settings.launcher.theme_color_style.vibrant.desc=Increases color saturation for a more vivid and impactful look. settings.launcher.theme_color_type.background=Follow Background Wallpaper +settings.launcher.theme_color_type.copper=The Copper Age +settings.launcher.theme_color_type.copper.description=Use a time-varying color of oxidizing copper settings.launcher.theme_color_type.custom=Custom Color settings.launcher.theme_color_type.custom.description=Use the color selected above. settings.launcher.theme_color_type.default=Default diff --git a/HMCL/src/main/resources/assets/lang/I18N_ar.properties b/HMCL/src/main/resources/assets/lang/I18N_ar.properties index c20716521cf..eb030cfe6f8 100644 --- a/HMCL/src/main/resources/assets/lang/I18N_ar.properties +++ b/HMCL/src/main/resources/assets/lang/I18N_ar.properties @@ -931,6 +931,14 @@ launcher.agreement.decline=رفض launcher.agreement.hint=يجب الموافقة على اتفاقية الترخيص لاستخدام هذا البرنامج. # launcher.april_fools.switch_lzh= # launcher.april_fools.switch_lzh.confirm= +# launcher.april_fools.copper= +# launcher.april_fools.copper.confirm= +# launcher.april_fools.copper.state.0= +# launcher.april_fools.copper.state.1= +# launcher.april_fools.copper.state.exposed= +# launcher.april_fools.copper.state.oxidized= +# launcher.april_fools.copper.state.waxed= +# launcher.april_fools.copper.state.weathered= launcher.background=صورة الخلفية launcher.background.choose=اختر صورة الخلفية # launcher.background.builtin= @@ -1617,6 +1625,8 @@ settings.launcher.theme=لون السمة # settings.launcher.theme_color_style.vibrant= # settings.launcher.theme_color_style.vibrant.desc= # settings.launcher.theme_color_type.background= +# settings.launcher.theme_color_type.copper= +# settings.launcher.theme_color_type.copper.description= # settings.launcher.theme_color_type.custom= # settings.launcher.theme_color_type.custom.description= # settings.launcher.theme_color_type.default= diff --git a/HMCL/src/main/resources/assets/lang/I18N_de.properties b/HMCL/src/main/resources/assets/lang/I18N_de.properties index 9889576db02..e589e2777c6 100644 --- a/HMCL/src/main/resources/assets/lang/I18N_de.properties +++ b/HMCL/src/main/resources/assets/lang/I18N_de.properties @@ -930,6 +930,14 @@ launcher.agreement.decline=Ablehnen launcher.agreement.hint=Sie müssen der EULA zustimmen, um diese Software nutzen zu können. launcher.april_fools.switch_lzh=HMCL Launcher unterstützt nun Klassisches Chinesisch. Möchten Sie in den Modus für Klassisches Chinesisch wechseln?\nSie können unter "置設 → 貫用 → 文" zu Englisch zurückkehren. launcher.april_fools.switch_lzh.confirm=Wechsel in den Modus für Klassisches Chinesisch bestätigen?\nKlicken Sie auf "OK", um HMCL zu beenden und im Modus für Klassisches Chinesisch neu zu starten; klicken Sie auf "Abbrechen", um die aktuelle Sprache beizubehalten und zur HMCL-Startseite zu gelangen. +# launcher.april_fools.copper= +# launcher.april_fools.copper.confirm= +launcher.april_fools.copper.state.0=%1s HMCL +launcher.april_fools.copper.state.1=%1s %2s HMCL +launcher.april_fools.copper.state.exposed=Angelaufener +launcher.april_fools.copper.state.oxidized=Oxidierter +launcher.april_fools.copper.state.waxed=Gewachster +launcher.april_fools.copper.state.weathered=Verwitterter launcher.background=Hintergrundbild launcher.background.choose=Hintergrundbild auswählen launcher.background.builtin=Eingebaut @@ -1618,6 +1626,8 @@ settings.launcher.theme_color_style.tonal_spot.desc=Reduziert die Farbsättigung settings.launcher.theme_color_style.vibrant=Lebhaft settings.launcher.theme_color_style.vibrant.desc=Erhöht die Farbsättigung für ein lebendigeres und wirkungsvolleres Erscheinungsbild. settings.launcher.theme_color_type.background=Hintergrundbild folgen +# settings.launcher.theme_color_type.copper= +# settings.launcher.theme_color_type.copper.description= settings.launcher.theme_color_type.custom=Benutzerdefinierte Farbe settings.launcher.theme_color_type.custom.description=Die oben ausgewählte Farbe verwenden. settings.launcher.theme_color_type.default=Standard diff --git a/HMCL/src/main/resources/assets/lang/I18N_es.properties b/HMCL/src/main/resources/assets/lang/I18N_es.properties index 778ee2d6862..21d6fcb0d98 100644 --- a/HMCL/src/main/resources/assets/lang/I18N_es.properties +++ b/HMCL/src/main/resources/assets/lang/I18N_es.properties @@ -923,6 +923,14 @@ launcher.agreement.decline=Rechazar launcher.agreement.hint=Debe aceptar el EULA para utilizar este software. # launcher.april_fools.switch_lzh= # launcher.april_fools.switch_lzh.confirm= +# launcher.april_fools.copper= +# launcher.april_fools.copper.confirm= +# launcher.april_fools.copper.state.0=HMCL %1s +# launcher.april_fools.copper.state.1=HMCL %2s %1s +# launcher.april_fools.copper.state.exposed=Expuesto +# launcher.april_fools.copper.state.oxidized=Oxidado +# launcher.april_fools.copper.state.waxed=Encerado +# launcher.april_fools.copper.state.weathered=Degradado launcher.background=Imagen de fondo launcher.background.choose=Elige una imagen de fondo # launcher.background.builtin= @@ -1609,6 +1617,8 @@ settings.launcher.theme=Tema # settings.launcher.theme_color_style.vibrant= # settings.launcher.theme_color_style.vibrant.desc= # settings.launcher.theme_color_type.background= +# settings.launcher.theme_color_type.copper= +# settings.launcher.theme_color_type.copper.description= # settings.launcher.theme_color_type.custom= # settings.launcher.theme_color_type.custom.description= # settings.launcher.theme_color_type.default= diff --git a/HMCL/src/main/resources/assets/lang/I18N_ja.properties b/HMCL/src/main/resources/assets/lang/I18N_ja.properties index 819101c5c07..077e51d4e50 100644 --- a/HMCL/src/main/resources/assets/lang/I18N_ja.properties +++ b/HMCL/src/main/resources/assets/lang/I18N_ja.properties @@ -733,6 +733,14 @@ launcher.agreement.decline=同意しません launcher.agreement.hint=このソフトウェアを使用するには、EULAに同意する必要があります。 # launcher.april_fools.switch_lzh= # launcher.april_fools.switch_lzh.confirm= +# launcher.april_fools.copper= +# launcher.april_fools.copper.confirm= +# launcher.april_fools.copper.state.0= +# launcher.april_fools.copper.state.1= +# launcher.april_fools.copper.state.exposed= +# launcher.april_fools.copper.state.oxidized= +# launcher.april_fools.copper.state.waxed= +# launcher.april_fools.copper.state.weathered= launcher.background=背景画像 launcher.background.choose=背景画像ファイルを選択してください # launcher.background.builtin= @@ -1413,6 +1421,8 @@ settings.launcher.theme=テーマ # settings.launcher.theme_color_style.vibrant= # settings.launcher.theme_color_style.vibrant.desc= # settings.launcher.theme_color_type.background= +# settings.launcher.theme_color_type.copper= +# settings.launcher.theme_color_type.copper.description= # settings.launcher.theme_color_type.custom= # settings.launcher.theme_color_type.custom.description= # settings.launcher.theme_color_type.default= diff --git a/HMCL/src/main/resources/assets/lang/I18N_lzh.properties b/HMCL/src/main/resources/assets/lang/I18N_lzh.properties index e5ea348632a..204b0c5910a 100644 --- a/HMCL/src/main/resources/assets/lang/I18N_lzh.properties +++ b/HMCL/src/main/resources/assets/lang/I18N_lzh.properties @@ -745,6 +745,14 @@ launcher.agreement.decline=拒 launcher.agreement.hint=將用而須許其客契與免責告示。 # launcher.april_fools.switch_lzh= # launcher.april_fools.switch_lzh.confirm= +# launcher.april_fools.copper= +# launcher.april_fools.copper.confirm= +launcher.april_fools.copper.state.0=%1s HMCL +launcher.april_fools.copper.state.1=%1s%2s HMCL +launcher.april_fools.copper.state.exposed=渡濕 +launcher.april_fools.copper.state.oxidized=風蝕 +launcher.april_fools.copper.state.waxed=蠟引 +launcher.april_fools.copper.state.weathered=鏽刻 launcher.background=繪圖 launcher.background.choose=擇繪圖 # launcher.background.builtin= @@ -1425,6 +1433,8 @@ settings.launcher.theme=主題 # settings.launcher.theme_color_style.vibrant= # settings.launcher.theme_color_style.vibrant.desc= # settings.launcher.theme_color_type.background= +# settings.launcher.theme_color_type.copper= +# settings.launcher.theme_color_type.copper.description= # settings.launcher.theme_color_type.custom= # settings.launcher.theme_color_type.custom.description= # settings.launcher.theme_color_type.default= diff --git a/HMCL/src/main/resources/assets/lang/I18N_ru.properties b/HMCL/src/main/resources/assets/lang/I18N_ru.properties index 9382b3f8f23..8cdb2e0347d 100644 --- a/HMCL/src/main/resources/assets/lang/I18N_ru.properties +++ b/HMCL/src/main/resources/assets/lang/I18N_ru.properties @@ -862,6 +862,14 @@ launcher.agreement.decline=Отклонить launcher.agreement.hint=Нужно принять пользовательское соглашение, чтобы использовать это программное обеспечение. launcher.april_fools.switch_lzh=Лаунчер HMCL теперь поддерживает классический китайский. Переключиться на классический китайский?\nВернуть прежний язык можно в «置設 → 貫用 → 文». launcher.april_fools.switch_lzh.confirm=Переключиться на классический китайский?\nНажмите «ОК», чтобы закрыть HMCL и перезапустить его на классическом китайском; нажмите «Отмена», чтобы оставить текущий язык и перейти на главную страницу HMCL. +# launcher.april_fools.copper= +# launcher.april_fools.copper.confirm= +# launcher.april_fools.copper.state.0= +# launcher.april_fools.copper.state.1= +# launcher.april_fools.copper.state.exposed= +# launcher.april_fools.copper.state.oxidized= +# launcher.april_fools.copper.state.waxed= +# launcher.april_fools.copper.state.weathered= launcher.background=Фоновое изображение launcher.background.choose=Выберите фоновое изображение launcher.background.builtin=Встроенный @@ -1549,6 +1557,8 @@ settings.launcher.theme_color_style.tonal_spot.desc=Сниженная насы settings.launcher.theme_color_style.vibrant=Яркий settings.launcher.theme_color_style.vibrant.desc=Повышенная насыщенность для более яркого и выразительного вида. settings.launcher.theme_color_type.background=Как у фонового изображения +# settings.launcher.theme_color_type.copper= +# settings.launcher.theme_color_type.copper.description= settings.launcher.theme_color_type.custom=Свой цвет settings.launcher.theme_color_type.custom.description=Использовать цвет, выбранный выше. settings.launcher.theme_color_type.default=По умолчанию diff --git a/HMCL/src/main/resources/assets/lang/I18N_uk.properties b/HMCL/src/main/resources/assets/lang/I18N_uk.properties index 7b6ae08f4cf..9e26a77037e 100644 --- a/HMCL/src/main/resources/assets/lang/I18N_uk.properties +++ b/HMCL/src/main/resources/assets/lang/I18N_uk.properties @@ -885,6 +885,14 @@ launcher.agreement.decline=Відхилити launcher.agreement.hint=Ви повинні погодитися з EULA, щоб використовувати це програмне забезпечення. launcher.april_fools.switch_lzh=HMCL Launcher тепер підтримує класичну китайську, ви хочете перемкнутися на класичний китайський режим?\nВи можете повернутися до української у "置設 → 貫用 → 文". launcher.april_fools.switch_lzh.confirm=Підтвердити перемикання на класичний китайський режим?\nНатисніть "ОК", щоб вийти з HMCL і перезапустити його у класичному китайському режимі; натисніть "Скасувати", щоб зберегти поточну мову та увійти на головну сторінку HMCL. +# launcher.april_fools.copper= +# launcher.april_fools.copper.confirm= +# launcher.april_fools.copper.state.0= +# launcher.april_fools.copper.state.1= +# launcher.april_fools.copper.state.exposed= +# launcher.april_fools.copper.state.oxidized= +# launcher.april_fools.copper.state.waxed= +# launcher.april_fools.copper.state.weathered= launcher.background=Фонове зображення launcher.background.choose=Вибрати фонове зображення launcher.background.builtin=Вбудоване @@ -1572,6 +1580,8 @@ settings.launcher.theme_color_style.tonal_spot.desc=Зменшує насиче settings.launcher.theme_color_style.vibrant=Яскравий settings.launcher.theme_color_style.vibrant.desc=Збільшує насиченість кольорів для більш яскравого та ефектного вигляду. settings.launcher.theme_color_type.background=Відповідно до фонових шпалер +# settings.launcher.theme_color_type.copper= +# settings.launcher.theme_color_type.copper.description= settings.launcher.theme_color_type.custom=Власний колір settings.launcher.theme_color_type.custom.description=Використовуйте колір, вибраний вище. settings.launcher.theme_color_type.default=Типово diff --git a/HMCL/src/main/resources/assets/lang/I18N_zh_Hans.properties b/HMCL/src/main/resources/assets/lang/I18N_zh_Hans.properties index c47d6a89555..ea2a53a7ba4 100644 --- a/HMCL/src/main/resources/assets/lang/I18N_zh_Hans.properties +++ b/HMCL/src/main/resources/assets/lang/I18N_zh_Hans.properties @@ -749,6 +749,16 @@ launcher.agreement.decline=拒绝 launcher.agreement.hint=同意本软件的用户协议与免责声明以使用本软件。 launcher.april_fools.switch_lzh=HMCL 启动器现已支持文言文,我们诚邀你参与体验。是否要将界面语言切换至中文 (文言)?\n在切换语言后,你可以在“置設 → 貫用 → 文”中将语言切换回中文 (简体)。 launcher.april_fools.switch_lzh.confirm=确认切换语言?\n点击“确定”后 HMCL 将自动重启并将界面语言切换至中文 (文言);点击“取消”将继续使用中文 (简体) 进入 HMCL 主页。 +launcher.april_fools.copper=为跟进 Minecraft 在 1.21.9 中的更新内容,HMCL 启动器现已支持“铜器时代”主题风格,我们诚邀你参与体验。是否要将主题色切换至“铜器时代”?\n切换后,你可以在“设置 → 外观 → 主题色”中切换回原来的主题色。 +launcher.april_fools.copper.confirm=确认切换主题色?\n点击“确定”后 HMCL 将会变更主题色至随时间氧化的铜制品颜色;点击“取消”继续使用当前主题色。 +launcher.april_fools.copper.state.0=%1s的 HMCL +launcher.april_fools.copper.state.1=%1s的%2s HMCL +launcher.april_fools.copper.action.axe=去除蜡或一层铜锈 +launcher.april_fools.copper.action.wax=涂蜡 +launcher.april_fools.copper.state.exposed=斑驳 +launcher.april_fools.copper.state.oxidized=氧化 +launcher.april_fools.copper.state.waxed=涂蜡 +launcher.april_fools.copper.state.weathered=锈蚀 launcher.background=背景图片 launcher.background.choose=选择背景图片 launcher.background.builtin=内置 @@ -1432,6 +1442,8 @@ settings.launcher.theme_color_style.tonal_spot.desc=降低色彩饱和度,使 settings.launcher.theme_color_style.vibrant=鲜艳 settings.launcher.theme_color_style.vibrant.desc=提高色彩饱和度,使色彩更具冲击力 settings.launcher.theme_color_type.background=跟随背景壁纸 +settings.launcher.theme_color_type.copper=铜器时代 +settings.launcher.theme_color_type.copper.description=使用随时间氧化的铜制品颜色。 settings.launcher.theme_color_type.custom=自定义颜色 settings.launcher.theme_color_type.custom.description=使用上方选择的颜色。 settings.launcher.theme_color_type.default=默认 diff --git a/HMCL/src/main/resources/assets/lang/I18N_zh_Hant.properties b/HMCL/src/main/resources/assets/lang/I18N_zh_Hant.properties index 703cf92a0f2..efb0ba0b959 100644 --- a/HMCL/src/main/resources/assets/lang/I18N_zh_Hant.properties +++ b/HMCL/src/main/resources/assets/lang/I18N_zh_Hant.properties @@ -743,6 +743,16 @@ launcher.agreement.decline=拒絕 launcher.agreement.hint=同意本軟體的使用者協議與免責宣告以使用本軟體。 launcher.april_fools.switch_lzh=HMCL 啟動器現已支援文言文,我們誠邀你參與體驗。是否要將介面語言切換至中文 (文言)?\n在切換語言後,你可以在「置設 → 貫用 → 文」中將語言切換回中文 (繁體)。 launcher.april_fools.switch_lzh.confirm=確認切換語言?\n點擊「確定」後 HMCL 將自動重新啟動並將介面語言切換至中文 (文言);點擊「取消」將繼續使用中文 (繁體) 進入 HMCL 主頁。 +launcher.april_fools.copper=為跟進 Minecraft 在 1.21.9 中的更新內容,HMCL 啟動器現已支援「銅器時代」主題風格,我們誠邀你參與體驗。是否要將主題色切換至「銅器時代」?\n切換後,你可以在「設定 → 外觀 → 主題色」中切換回原來的主題色。 +launcher.april_fools.copper.confirm=確認切換主題色?\n點選「確定」後 HMCL 將會變更主題色至隨時間氧化的銅製品顏色;點選「取消」繼續使用目前主題色。 +launcher.april_fools.copper.action.axe=去除蠟或一層銅鏽 +launcher.april_fools.copper.action.wax=上蠟 +launcher.april_fools.copper.state.0=%1s的 HMCL +launcher.april_fools.copper.state.1=%1s的%2s HMCL +launcher.april_fools.copper.state.exposed=斑駁 +launcher.april_fools.copper.state.oxidized=氧化 +launcher.april_fools.copper.state.waxed=上蠟 +launcher.april_fools.copper.state.weathered=風化 launcher.background=背景圖片 launcher.background.choose=選取背景圖片 launcher.background.builtin=內建 @@ -1426,6 +1436,8 @@ settings.launcher.theme_color_style.tonal_spot.desc=降低色彩飽和度,使 settings.launcher.theme_color_style.vibrant=鮮豔 settings.launcher.theme_color_style.vibrant.desc=提高色彩飽和度,使色彩更具衝擊力 settings.launcher.theme_color_type.background=跟隨背景圖片 +settings.launcher.theme_color_type.copper=銅器時代 +settings.launcher.theme_color_type.copper.description=使用隨時間氧化的銅製品顏色。 settings.launcher.theme_color_type.custom=自訂顏色 settings.launcher.theme_color_type.custom.description=使用上方選擇的顏色。 settings.launcher.theme_color_type.default=預設 diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/util/Lang.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/Lang.java index 586ab45672e..e4d366e53d8 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/util/Lang.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/Lang.java @@ -208,6 +208,14 @@ public static ThreadPoolExecutor threadPool(String name, boolean daemon, int thr return pool; } + public static ThreadFactory daemonThreadFactory(String name) { + return r -> { + Thread t = new Thread(r, name); + t.setDaemon(true); + return t; + }; + } + public static ThreadFactory counterThreadFactory(String name, boolean daemon) { AtomicInteger counter = new AtomicInteger(1); return r -> {