Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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);
Expand Down
11 changes: 10 additions & 1 deletion HMCL/src/main/java/org/jackhuang/hmcl/theme/Themes.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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]));
}

Expand Down Expand Up @@ -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;
};
}

Expand All @@ -209,6 +217,7 @@ private static ThemeColor resolveThemeColorSource(
ThemeColor fallback,
@Nullable ThemeColorSource source,
BackgroundType backgroundType) {
TheCopperAge.stopAndClearState();
if (source == null) {
return fallback;
}
Expand Down
45 changes: 19 additions & 26 deletions HMCL/src/main/java/org/jackhuang/hmcl/ui/Controllers.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand All @@ -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> gameListPage = new Lazy<>(GameListPage::new);
Expand Down Expand Up @@ -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");

Expand Down Expand Up @@ -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();
Expand All @@ -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());
Expand Down
51 changes: 46 additions & 5 deletions HMCL/src/main/java/org/jackhuang/hmcl/ui/main/MainPage.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand All @@ -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;
Expand Down Expand Up @@ -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));
Expand Down Expand Up @@ -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) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -504,13 +504,19 @@ protected Node createRightNode() {
i18n("settings.launcher.theme_color_type.background"),
ThemeColorType.BACKGROUND);

var copperColorChoice = new RadioChoiceList.Choice<ThemeColorType>(
i18n("settings.launcher.theme_color_type.copper"),
ThemeColorType.COPPER);
copperColorChoice.setSubtitle(i18n("settings.launcher.theme_color_type.copper.description"));

RadioChoiceList<ThemeColorType> themeColorChoiceList = new RadioChoiceList<>();
themeColorChoiceList.setFallbackValue(ThemeColorType.DEFAULT);
themeColorChoiceList.setChoices(Arrays.asList(
defaultColorChoice,
systemColorChoice,
customColorChoice,
backgroundColorChoice));
backgroundColorChoice,
copperColorChoice));

JFXButton themeColorOverrideButton = createThemeAppearanceOverrideButton();
themeColorSublist.setTitleRight(themeColorOverrideButton);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,28 +15,38 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
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.
Expand All @@ -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.
Expand All @@ -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<Boolean> shouldPromptSupplier;

Type(Supplier<Boolean> supportedLazy) {
this.shouldPromptSupplier = supportedLazy;
}
}
}
Loading
Loading