Merge 6cca22740fe2288cfc230fa79dfd1573fd1b206b into bd9ae189f83e33a6977bbe056774c851e96fe0a7

This commit is contained in:
rootwhois 2025-09-21 15:25:58 +08:00 committed by GitHub
commit c97b7c0f89
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
13 changed files with 175 additions and 2 deletions

View File

@ -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"));
}
}
}

View File

@ -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);
}
}
}

View File

@ -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;

View File

@ -188,6 +188,10 @@ public class DecoratorController {
return decorator;
}
public Navigator getNavigator() {
return navigator;
}
// ==== Background ====
//FXThread

View File

@ -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<Decorator> {
private final StackPane root, parent;
private final StackPane titleContainer;
@ -229,6 +236,27 @@ public class DecoratorSkin extends SkinBase<Decorator> {
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<Object> 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<Decorator> {
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);

View File

@ -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

View File

@ -195,6 +195,9 @@ button.save_as=Guardar como
button.select_all=Seleccionar todo
button.view=Vista
button.yes=
button.reload=Recargar
reload.failed=Error al recargar HMCL
reload.failed.title=Error de recarga
chat=Chat de grupo

View File

@ -156,6 +156,9 @@ button.save_as=名前を付けて保存
button.select_all=すべて選択
button.view=読む
button.yes=はい
button.reload=再読み込み
reload.failed=HMCLの再読み込みに失敗しました
reload.failed.title=再読み込み失敗
chat=グループチャット

View File

@ -205,6 +205,9 @@ button.save_as=另存
button.select_all=悉擇之
button.view=
button.yes=
button.reload=重載
reload.failed=重載 HMCL 失敗
reload.failed.title=重載失敗
chat=會集

View File

@ -196,6 +196,9 @@ button.save_as=Сохранить как
button.select_all=Выбрать все
button.view=Просмотреть
button.yes=Да
button.reload=Перезагрузить
reload.failed=Не удалось перезагрузить HMCL
reload.failed.title=Ошибка перезагрузки
chat=Групповой чат

View File

@ -193,6 +193,9 @@ button.save_as=Зберегти як
button.select_all=Вибрати все
button.view=Переглянути
button.yes=Так
button.reload=Перезавантажити
reload.failed=Не вдалося перезавантажити HMCL
reload.failed.title=Помилка перезавантаження
chat=Приєднатися до групового чату

View File

@ -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=不透明度

View File

@ -206,6 +206,9 @@ button.save_as=另存为
button.select_all=全选
button.view=查看
button.yes=
button.reload=重新加载
reload.failed=HMCL 重新加载失败
reload.failed.title=重新加载失败
chat=官方群组