diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/Launcher.java b/HMCL/src/main/java/org/jackhuang/hmcl/Launcher.java index a558c4b74..b19020415 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/Launcher.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/Launcher.java @@ -309,4 +309,37 @@ public final class Launcher extends Application { } public static final CrashReporter CRASH_REPORTER = new CrashReporter(true); +/** + * Reload the application + */ + public static void reloadApplication() { + LOG.info("Reloading application...\n"); + + try { + // Reload configuration + try { + ConfigHolder.reload(); + LOG.info("Configuration reloaded successfully\n"); + } catch (SambaException e) { + showAlert(AlertType.WARNING, i18n("fatal.samba")); + } + + // Update UI in FX thread + Platform.runLater(() -> { + try { + // Re-initialize controllers + Controllers.reload(); + + LOG.info("Application reloaded successfully\n"); + } catch (Exception e) { + LOG.error("Failed to reload UI", e); + showAlert(AlertType.ERROR, i18n("error.reload_failed")); + } + }); + } catch (Exception e) { + LOG.error("Error during application reload\n", e); + showAlert(AlertType.ERROR, i18n("error.reload_failed")); + } + } + } diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/setting/ConfigHolder.java b/HMCL/src/main/java/org/jackhuang/hmcl/setting/ConfigHolder.java index 2854207c3..c11d6be5d 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/setting/ConfigHolder.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/setting/ConfigHolder.java @@ -209,4 +209,36 @@ public final class ConfigHolder { return new GlobalConfig(); } + public static void reload() { + try { + // Save current configuration state + boolean wasNewlyCreated = newlyCreated; + boolean wasOwnerChanged = ownerChanged; + boolean wasUnsupportedVersion = unsupportedVersion; + + // Reload configuration + configInstance = loadConfig(); + if (!unsupportedVersion) + configInstance.addListener(source -> FileSaver.save(configLocation, configInstance.toJson())); + + globalConfigInstance = loadGlobalConfig(); + globalConfigInstance.addListener(source -> FileSaver.save(GLOBAL_CONFIG_PATH, globalConfigInstance.toJson())); + + // Force update language settings + Locale.setDefault(config().getLocalization().getLocale()); + I18n.setLocale(configInstance.getLocalization()); + + // Update log retention policy (do not reinitialize Settings to avoid duplicate initialization) + LOG.setLogRetention(globalConfig().getLogRetention()); + + // Restore state flags + newlyCreated = wasNewlyCreated; + ownerChanged = wasOwnerChanged; + unsupportedVersion = wasUnsupportedVersion; + + LOG.info("Configuration reloaded and UI refreshed"); + } catch (IOException e) { + LOG.warning("Failed to reload configuration", e); + } + } } 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 8a0b682a0..180f44a99 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/Controllers.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/Controllers.java @@ -480,6 +480,58 @@ public final class Controllers { return decorator == null; } + /** + * Reload UI controllers + */ + public static void reload() { + if (isStopped()) { + throw new IllegalStateException("Application has been stopped"); + } + + // Reinitialize all pages + rootPage = new Lazy<>(RootPage::new); + versionPage = new Lazy<>(VersionPage::new); + gameListPage = new Lazy<>(() -> { + GameListPage gameListPage = new GameListPage(); + gameListPage.selectedProfileProperty().bindBidirectional(Profiles.selectedProfileProperty()); + gameListPage.profilesProperty().bindContent(Profiles.profilesProperty()); + FXUtils.applyDragListener(gameListPage, ModpackHelper::isFileModpackByExtension, modpacks -> { + Path modpack = modpacks.get(0); + Controllers.getDecorator().startWizard(new ModpackInstallWizardProvider(Profiles.getSelectedProfile(), modpack), i18n("install.modpack")); + }); + return gameListPage; + }); + downloadPage = new Lazy<>(DownloadPage::new); + accountListPage = new Lazy<>(() -> { + AccountListPage accountListPage = new AccountListPage(); + accountListPage.selectedAccountProperty().bindBidirectional(Accounts.selectedAccountProperty()); + accountListPage.accountsProperty().bindContent(Accounts.getAccounts()); + accountListPage.authServersProperty().bindContentBidirectional(config().getAuthlibInjectorServers()); + return accountListPage; + }); + settingsPage = new Lazy<>(LauncherSettingsPage::new); + + // Update window title + if (stage != null) { + stage.setTitle(Metadata.FULL_TITLE); + } + + // Reset scene and navigate back to home page + if (scene != null && decorator != null) { + scene.setRoot(decorator.getDecorator()); + decorator.getDecorator().prefWidthProperty().bind(scene.widthProperty()); + decorator.getDecorator().prefHeightProperty().bind(scene.heightProperty()); + + // Clear navigation history and navigate back to home page + if (decorator.getNavigator() != null) { + decorator.getNavigator().clear(); + decorator.navigate(getRootPage()); + } + } + + LOG.info("UI controllers have been successfully reloaded"); + } + public static void shutdown() { rootPage = null; versionPage = null; diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/decorator/DecoratorController.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/decorator/DecoratorController.java index 27ff5bccf..eafdeb6c7 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/decorator/DecoratorController.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/decorator/DecoratorController.java @@ -188,6 +188,10 @@ public class DecoratorController { return decorator; } + public Navigator getNavigator() { + return navigator; + } + // ==== Background ==== //FXThread diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/decorator/DecoratorSkin.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/decorator/DecoratorSkin.java index a4a5be60f..47d63f263 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/decorator/DecoratorSkin.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/decorator/DecoratorSkin.java @@ -30,6 +30,7 @@ import javafx.scene.Cursor; import javafx.scene.Node; import javafx.scene.control.Label; import javafx.scene.control.SkinBase; +import javafx.scene.control.Tooltip; import javafx.scene.effect.BlurType; import javafx.scene.effect.DropShadow; import javafx.scene.input.MouseButton; @@ -39,16 +40,22 @@ import javafx.scene.paint.Color; import javafx.scene.shape.Rectangle; import javafx.stage.Stage; +import org.jackhuang.hmcl.Launcher; import org.jackhuang.hmcl.Metadata; import org.jackhuang.hmcl.setting.Theme; import org.jackhuang.hmcl.ui.FXUtils; import org.jackhuang.hmcl.ui.SVG; +import java.util.Objects; import org.jackhuang.hmcl.ui.animation.AnimationProducer; 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.platform.OperatingSystem; +import static org.jackhuang.hmcl.setting.ConfigHolder.config; +import static org.jackhuang.hmcl.setting.ConfigHolder.globalConfig; +import static org.jackhuang.hmcl.util.i18n.I18n.i18n; + public class DecoratorSkin extends SkinBase { private final StackPane root, parent; private final StackPane titleContainer; @@ -229,6 +236,27 @@ public class DecoratorSkin extends SkinBase { buttonsContainer.setAlignment(Pos.TOP_RIGHT); buttonsContainer.setMaxHeight(40); { + + JFXButton btnReload = new JFXButton(); + btnReload.setGraphic(SVG.REFRESH.createIcon(Theme.foregroundFillBinding(), 20)); + btnReload.getStyleClass().add("jfx-decorator-button"); + btnReload.setTooltip(new Tooltip(i18n("button.reload"))); + btnReload.setOnAction(e -> Launcher.reloadApplication()); + btnReload.setVisible(false); + btnReload.managedProperty().bind(btnReload.visibleProperty()); + + // Create a generic listener to show reload button when property value changes + javafx.beans.value.ChangeListener restartButtonListener = (observable, oldValue, newValue) -> { + if (!Objects.equals(oldValue, newValue)) { + btnReload.setVisible(true); + } + }; + + // Listen to settings that require reload to take effect + config().localizationProperty().addListener(restartButtonListener); + globalConfig().fontAntiAliasingProperty().addListener(restartButtonListener); + config().animationDisabledProperty().addListener(restartButtonListener); + JFXButton btnHelp = new JFXButton(); btnHelp.setFocusTraversable(false); btnHelp.setGraphic(SVG.HELP.createIcon(Theme.foregroundFillBinding(), -1)); @@ -247,7 +275,7 @@ public class DecoratorSkin extends SkinBase { btnClose.getStyleClass().add("jfx-decorator-button"); btnClose.setOnAction(e -> skinnable.close()); - buttonsContainer.getChildren().setAll(btnHelp, btnMin, btnClose); + buttonsContainer.getChildren().setAll(btnReload, btnHelp, btnMin, btnClose); } AnchorPane layer = new AnchorPane(); layer.setPickOnBounds(false); diff --git a/HMCL/src/main/resources/assets/lang/I18N.properties b/HMCL/src/main/resources/assets/lang/I18N.properties index 56064f3d5..f84509a06 100644 --- a/HMCL/src/main/resources/assets/lang/I18N.properties +++ b/HMCL/src/main/resources/assets/lang/I18N.properties @@ -195,6 +195,9 @@ button.save_as=Save As button.select_all=Select All button.view=View button.yes=Yes +button.reload=Reload +reload.failed=Failed to reload HMCL +reload.failed.title=Reload Failed chat=Join Group Chat diff --git a/HMCL/src/main/resources/assets/lang/I18N_es.properties b/HMCL/src/main/resources/assets/lang/I18N_es.properties index ee0700dc7..0c6726520 100644 --- a/HMCL/src/main/resources/assets/lang/I18N_es.properties +++ b/HMCL/src/main/resources/assets/lang/I18N_es.properties @@ -195,6 +195,9 @@ button.save_as=Guardar como button.select_all=Seleccionar todo button.view=Vista button.yes=Sí +button.reload=Recargar +reload.failed=Error al recargar HMCL +reload.failed.title=Error de recarga chat=Chat de grupo diff --git a/HMCL/src/main/resources/assets/lang/I18N_ja.properties b/HMCL/src/main/resources/assets/lang/I18N_ja.properties index e5c9e5e78..dea110466 100644 --- a/HMCL/src/main/resources/assets/lang/I18N_ja.properties +++ b/HMCL/src/main/resources/assets/lang/I18N_ja.properties @@ -156,6 +156,9 @@ button.save_as=名前を付けて保存 button.select_all=すべて選択 button.view=読む button.yes=はい +button.reload=再読み込み +reload.failed=HMCLの再読み込みに失敗しました +reload.failed.title=再読み込み失敗 chat=グループチャット diff --git a/HMCL/src/main/resources/assets/lang/I18N_lzh.properties b/HMCL/src/main/resources/assets/lang/I18N_lzh.properties index b117bf296..ca07c7bc4 100644 --- a/HMCL/src/main/resources/assets/lang/I18N_lzh.properties +++ b/HMCL/src/main/resources/assets/lang/I18N_lzh.properties @@ -205,6 +205,9 @@ button.save_as=另存 button.select_all=悉擇之 button.view=覽 button.yes=然 +button.reload=重載 +reload.failed=重載 HMCL 失敗 +reload.failed.title=重載失敗 chat=會集 diff --git a/HMCL/src/main/resources/assets/lang/I18N_ru.properties b/HMCL/src/main/resources/assets/lang/I18N_ru.properties index 310a52a46..ab05064ad 100644 --- a/HMCL/src/main/resources/assets/lang/I18N_ru.properties +++ b/HMCL/src/main/resources/assets/lang/I18N_ru.properties @@ -196,6 +196,9 @@ button.save_as=Сохранить как button.select_all=Выбрать все button.view=Просмотреть button.yes=Да +button.reload=Перезагрузить +reload.failed=Не удалось перезагрузить HMCL +reload.failed.title=Ошибка перезагрузки chat=Групповой чат diff --git a/HMCL/src/main/resources/assets/lang/I18N_uk.properties b/HMCL/src/main/resources/assets/lang/I18N_uk.properties index bf2a920e3..d7d91519d 100644 --- a/HMCL/src/main/resources/assets/lang/I18N_uk.properties +++ b/HMCL/src/main/resources/assets/lang/I18N_uk.properties @@ -193,6 +193,9 @@ button.save_as=Зберегти як button.select_all=Вибрати все button.view=Переглянути button.yes=Так +button.reload=Перезавантажити +reload.failed=Не вдалося перезавантажити HMCL +reload.failed.title=Помилка перезавантаження chat=Приєднатися до групового чату diff --git a/HMCL/src/main/resources/assets/lang/I18N_zh.properties b/HMCL/src/main/resources/assets/lang/I18N_zh.properties index d01764c5f..1c95b6e68 100644 --- a/HMCL/src/main/resources/assets/lang/I18N_zh.properties +++ b/HMCL/src/main/resources/assets/lang/I18N_zh.properties @@ -198,6 +198,9 @@ button.save_as=另存新檔 button.select_all=全選 button.view=查看 button.yes=是 +button.reload=重新加載 +reload.failed=HMCL 重新加載失敗 +reload.failed.title=重新加載失敗 chat=官方群組 @@ -1168,7 +1171,7 @@ settings.launcher.proxy.socks=SOCKS settings.launcher.proxy.username=帳戶 settings.launcher.theme=主題 settings.launcher.title_transparent=標題欄透明 -settings.launcher.turn_off_animations=關閉動畫 (重啟後生效) +settings.launcher.st=關閉動畫 (重啟後生效) settings.launcher.version_list_source=版本清單來源 settings.launcher.background.settings.opacity=不透明度 diff --git a/HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties b/HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties index 43603a3cb..0301905ba 100644 --- a/HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties +++ b/HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties @@ -206,6 +206,9 @@ button.save_as=另存为 button.select_all=全选 button.view=查看 button.yes=是 +button.reload=重新加载 +reload.failed=HMCL 重新加载失败 +reload.failed.title=重新加载失败 chat=官方群组