Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
65 commits
Select commit Hold shift + click to select a range
a02e44b
feat:优化世界管理界面和世界信息界面
Mine-diamond Nov 18, 2025
6c85f9d
fix style
Mine-diamond Nov 18, 2025
cc099b0
fix style
Mine-diamond Nov 18, 2025
85777e4
feat:优化代码
Mine-diamond Nov 19, 2025
eb07b8f
feat:添加世界图标显示项和修改图标功能
Mine-diamond Nov 19, 2025
6f7a53a
Merge branch 'main' into world-manage-enhance
Mine-diamond Nov 19, 2025
bec4e83
feat:优化修改世界图标功能
Mine-diamond Nov 19, 2025
a005ec7
feat:优化代码
Mine-diamond Nov 20, 2025
c6ecfc5
feat:修改图片之前进行弹窗提示
Mine-diamond Nov 20, 2025
aa1be7d
feat:更新文本
Mine-diamond Nov 20, 2025
34adf97
feat:优化代码,添加i18n
Mine-diamond Nov 20, 2025
32905b3
feat:更新i18n键
Mine-diamond Nov 20, 2025
f5d19d2
feat:优化代码
Mine-diamond Nov 20, 2025
34b43cd
feat:优化代码
Mine-diamond Nov 20, 2025
28c2a2e
feat:优化代码
Mine-diamond Nov 20, 2025
90235be
fix: 修复无法获取25w07a之后的玩家坐标的问题
Mine-diamond Nov 20, 2025
32e10c7
fix: 修复i18n键名错误,更改繁中翻译
Mine-diamond Nov 20, 2025
2d20e7d
fix style
Mine-diamond Nov 20, 2025
66ec94a
fix: 修复部分逻辑错误
Mine-diamond Nov 21, 2025
81fca70
feat: 添加i18n
Mine-diamond Nov 21, 2025
0ae56a6
feat: 调整代码格式
Mine-diamond Nov 21, 2025
836bbed
feat: 优化代码
Mine-diamond Nov 22, 2025
ae070c7
feat: 优化代码
Mine-diamond Nov 22, 2025
345f2d6
feat: 添加复制世界功能
Mine-diamond Nov 22, 2025
af00011
feat: 优化代码
Mine-diamond Nov 22, 2025
7f97f55
feat: 添加版权信息
Mine-diamond Nov 22, 2025
88a2c6c
feat: 优化代码
Mine-diamond Nov 22, 2025
e6ff6c1
fix: 恢复意外移除的空行
Mine-diamond Nov 23, 2025
48e76e4
feat: i18n,将copy改为duplicate
Mine-diamond Nov 23, 2025
765e537
feat: 优化样式
Mine-diamond Nov 28, 2025
797b412
Merge branch 'main' into world-manage-enhance
Mine-diamond Dec 7, 2025
fc3727a
fix:修复错误
Mine-diamond Dec 7, 2025
c6cb77c
Merge branch 'main' into world-manage-enhance
Mine-diamond Dec 11, 2025
2fc124a
feat: 添加图标加载失败时的弹窗和成功时的提示
Mine-diamond Dec 11, 2025
32c231b
Apply i18n suggestions from code review
Mine-diamond Dec 12, 2025
98d77b7
feat: 再次修复重生点可能无法被读取的问题
Mine-diamond Dec 12, 2025
933f8f3
fix style
Mine-diamond Dec 12, 2025
b69e4c7
fix: 小修复
Mine-diamond Dec 12, 2025
85008ab
feat: 优化代码
Mine-diamond Dec 13, 2025
987e032
feat: 小修改
Mine-diamond Dec 13, 2025
d34ca49
feat: 优化代码
Mine-diamond Dec 18, 2025
d1be359
Merge branch 'main' into world-manage-enhance
Mine-diamond Dec 20, 2025
98ee173
feat: 将获取/关闭SessionLockChannel的方法也统一在WorldManageUIUtils中
Mine-diamond Dec 20, 2025
9864b08
feat: 优化世界锁逻辑,在世界被使用时禁用更多项目
Mine-diamond Dec 21, 2025
742bc19
feat: 取消支持自动更改分辨率
Mine-diamond Dec 21, 2025
03a999b
fix: 修复一些错误
Mine-diamond Dec 21, 2025
d3efcc0
feat: 修复无法获取是否为放大化世界的问题,现在获取leveldat改为共享leveldat
Mine-diamond Dec 22, 2025
375ac4d
fix: 修复一些错误
Mine-diamond Dec 22, 2025
238a1cb
fix: 应用copilot的修改建议
Mine-diamond Dec 23, 2025
a1e5d62
fix: 修复种子显示图标和修改世界图标图标的一些问题
Mine-diamond Dec 23, 2025
e2eb62c
Merge branch 'main' into world-manage-enhance
Mine-diamond Dec 23, 2025
7641e2a
feat: 优化代码
Mine-diamond Dec 25, 2025
8578cfa
fix: 修复20w14infinite版本的世界无法被正确加载的问题。
Mine-diamond Dec 28, 2025
a99902c
feat: World中保存的部分字段将不再保存而是get时获取
Mine-diamond Jan 1, 2026
26d6163
fix some issue
Mine-diamond Jan 1, 2026
123dcd9
fix some issue
Mine-diamond Jan 1, 2026
afa7ad7
feat: update
Mine-diamond Jan 2, 2026
3da183a
feat: 优化代码,复制世界时跳过session.lock
Mine-diamond Jan 2, 2026
29daf82
feat: 优化代码
Mine-diamond Jan 2, 2026
9723e1a
feat: level.dat现在会在加载世界时重新加载
Mine-diamond Jan 2, 2026
effebb8
Merge branch 'main' into world-manage-enhance
Mine-diamond Jan 2, 2026
d85eb63
feat: 世界图标添加删除功能
Mine-diamond Jan 3, 2026
a9a9e57
Merge branch 'main' into world-manage-enhance
Mine-diamond Jan 3, 2026
8dbd78e
feat: 修改i18n
Mine-diamond Jan 3, 2026
23f879b
feat: 修改样式
Mine-diamond Jan 3, 2026
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
605 changes: 337 additions & 268 deletions HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/WorldInfoPage.java

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -19,22 +19,16 @@

import javafx.scene.control.Control;
import javafx.scene.control.Skin;
import javafx.stage.FileChooser;
import org.jackhuang.hmcl.game.World;
import org.jackhuang.hmcl.game.WorldLockedException;
import org.jackhuang.hmcl.setting.Profile;
import org.jackhuang.hmcl.task.Schedulers;
import org.jackhuang.hmcl.task.Task;
import org.jackhuang.hmcl.ui.Controllers;
import org.jackhuang.hmcl.ui.FXUtils;
import org.jackhuang.hmcl.ui.construct.MessageDialogPane.MessageType;
import org.jackhuang.hmcl.ui.wizard.SinglePageWizardProvider;
import org.jackhuang.hmcl.util.StringUtils;
import org.jackhuang.hmcl.util.io.FileUtils;

import java.io.IOException;
import java.nio.file.Path;

import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
import static org.jackhuang.hmcl.util.logging.Logger.LOG;

public final class WorldListItem extends Control {
private final World world;
Expand All @@ -61,42 +55,30 @@ public World getWorld() {
}

public void export() {
FileChooser fileChooser = new FileChooser();
fileChooser.setTitle(i18n("world.export.title"));
fileChooser.getExtensionFilters().add(new FileChooser.ExtensionFilter(i18n("world"), "*.zip"));
fileChooser.setInitialFileName(world.getWorldName());
Path file = FileUtils.toPath(fileChooser.showSaveDialog(Controllers.getStage()));
if (file == null) {
return;
}

Controllers.getDecorator().startWizard(new SinglePageWizardProvider(controller -> new WorldExportPage(world, file, controller::onFinish)));
WorldManageUIUtils.export(world);
}

public void delete() {
Controllers.confirm(
i18n("button.remove.confirm"),
i18n("world.delete"),
() -> Task.runAsync(world::delete)
.whenComplete(Schedulers.javafx(), (result, exception) -> {
if (exception == null) {
parent.remove(this);
} else if (exception instanceof WorldLockedException) {
Controllers.dialog(i18n("world.locked.failed"), null, MessageType.WARNING);
} else {
Controllers.dialog(i18n("world.delete.failed", StringUtils.getStackTrace(exception)), null, MessageType.WARNING);
}
}).start(),
null
);
WorldManageUIUtils.delete(world, () -> parent.remove(this));
}

public void copy() {
WorldManageUIUtils.copyWorld(world, parent::refresh);
}

public void reveal() {
FXUtils.openFolder(world.getFile());
}

public void showManagePage() {
Controllers.navigate(new WorldManagePage(world, backupsDir, profile, id));
try {
world.reloadLevelDat();
Controllers.navigate(new WorldManagePage(world, backupsDir, profile, id));
} catch (IOException e) {
LOG.warning("Failed to load level dat of world " + world.getFile(), e);
Controllers.showToast(i18n("world.load.fail"));
parent.refresh();
}
}

public void launch() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
import org.jackhuang.hmcl.util.i18n.I18n;

import java.time.Instant;
import java.util.stream.Stream;

import static org.jackhuang.hmcl.ui.FXUtils.determineOptimalPopupPosition;
import static org.jackhuang.hmcl.util.StringUtils.parseColorEscapes;
Expand Down Expand Up @@ -143,11 +144,24 @@ public void showPopupMenu(JFXPopup.PopupHPosition hPosition, double initOffsetX,
}
}

IconedMenuItem exportMenuItem = new IconedMenuItem(SVG.OUTPUT, i18n("world.export"), item::export, popup);
IconedMenuItem deleteMenuItem = new IconedMenuItem(SVG.DELETE, i18n("world.delete"), item::delete, popup);
IconedMenuItem duplicateMenuItem = new IconedMenuItem(SVG.CONTENT_COPY, i18n("world.duplicate"), item::copy, popup);
boolean worldLocked = world.isLocked();
Stream.of(exportMenuItem, deleteMenuItem, duplicateMenuItem)
.forEach(iconedMenuItem -> iconedMenuItem.setDisable(worldLocked));

popupMenu.getContent().addAll(
new MenuSeparator(),
exportMenuItem,
deleteMenuItem,
duplicateMenuItem
);

popupMenu.getContent().addAll(
new MenuSeparator(),
new IconedMenuItem(SVG.OUTPUT, i18n("world.export"), item::export, popup),
new IconedMenuItem(SVG.DELETE, i18n("world.delete"), item::delete, popup),
new IconedMenuItem(SVG.FOLDER_OPEN, i18n("folder.world"), item::reveal, popup));
new IconedMenuItem(SVG.FOLDER_OPEN, i18n("folder.world"), item::reveal, popup)
);

JFXPopup.PopupVPosition vPosition = determineOptimalPopupPosition(root, popup);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,13 +34,13 @@
import org.jackhuang.hmcl.ui.decorator.DecoratorAnimatedPage;
import org.jackhuang.hmcl.ui.decorator.DecoratorPage;
import org.jackhuang.hmcl.util.ChunkBaseApp;
import org.jackhuang.hmcl.util.StringUtils;

import java.io.IOException;
import java.nio.channels.FileChannel;
import java.nio.file.Path;

import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
import static org.jackhuang.hmcl.util.logging.Logger.LOG;

/**
* @author Glavo
Expand All @@ -65,24 +65,19 @@ public final class WorldManagePage extends DecoratorAnimatedPage implements Deco
public WorldManagePage(World world, Path backupsDir, Profile profile, String id) {
this.world = world;
this.backupsDir = backupsDir;

this.profile = profile;
this.id = id;

this.worldInfoTab.setNodeSupplier(() -> new WorldInfoPage(this));
this.worldBackupsTab.setNodeSupplier(() -> new WorldBackupsPage(this));
this.datapackTab.setNodeSupplier(() -> new DatapackListPage(this));

this.state = new SimpleObjectProperty<>(State.fromTitle(i18n("world.manage.title", world.getWorldName())));
this.state = new SimpleObjectProperty<>(State.fromTitle(i18n("world.manage.title", StringUtils.parseColorEscapes(world.getWorldName()))));
this.header = new TabHeader(transitionPane, worldInfoTab, worldBackupsTab);
header.select(worldInfoTab);

// Does it need to be done in the background?
try {
sessionLockChannel = world.lock();
LOG.info("Acquired lock on world " + world.getFileName());
} catch (IOException ignored) {
}
sessionLockChannel = WorldManageUIUtils.getSessionLockChannel(world);

setCenter(transitionPane);

Expand All @@ -106,37 +101,74 @@ public WorldManagePage(World world, Path backupsDir, Profile profile, String id)
AdvancedListBox toolbar = new AdvancedListBox();

if (world.getGameVersion() != null && world.getGameVersion().isAtLeast("1.20", "23w14a")) {
toolbar.addNavigationDrawerItem(i18n("version.launch"), SVG.ROCKET_LAUNCH, this::launch, null);
toolbar.addNavigationDrawerItem(i18n("version.launch"), SVG.ROCKET_LAUNCH, this::launch, advancedListItem -> advancedListItem.setDisable(isReadOnly()));
toolbar.addNavigationDrawerItem(i18n("version.launch_script"), SVG.SCRIPT, this::generateLaunchScript, null);
}

if (ChunkBaseApp.isSupported(world)) {
PopupMenu popupMenu = new PopupMenu();
JFXPopup popup = new JFXPopup(popupMenu);
PopupMenu chunkBasePopupMenu = new PopupMenu();
JFXPopup chunkBasePopup = new JFXPopup(chunkBasePopupMenu);

popupMenu.getContent().addAll(
new IconedMenuItem(SVG.EXPLORE, i18n("world.chunkbase.seed_map"), () -> ChunkBaseApp.openSeedMap(world), popup),
new IconedMenuItem(SVG.VISIBILITY, i18n("world.chunkbase.stronghold"), () -> ChunkBaseApp.openStrongholdFinder(world), popup),
new IconedMenuItem(SVG.FORT, i18n("world.chunkbase.nether_fortress"), () -> ChunkBaseApp.openNetherFortressFinder(world), popup)

chunkBasePopupMenu.getContent().addAll(
new IconedMenuItem(SVG.EXPLORE, i18n("world.chunkbase.seed_map"), () -> ChunkBaseApp.openSeedMap(world), chunkBasePopup),
new IconedMenuItem(SVG.VISIBILITY, i18n("world.chunkbase.stronghold"), () -> ChunkBaseApp.openStrongholdFinder(world), chunkBasePopup),
new IconedMenuItem(SVG.FORT, i18n("world.chunkbase.nether_fortress"), () -> ChunkBaseApp.openNetherFortressFinder(world), chunkBasePopup)
);

if (world.getGameVersion() != null && world.getGameVersion().compareTo("1.13") >= 0) {
popupMenu.getContent().add(
new IconedMenuItem(SVG.LOCATION_CITY, i18n("world.chunkbase.end_city"), () -> ChunkBaseApp.openEndCityFinder(world), popup));
chunkBasePopupMenu.getContent().add(
new IconedMenuItem(SVG.LOCATION_CITY, i18n("world.chunkbase.end_city"), () -> ChunkBaseApp.openEndCityFinder(world), chunkBasePopup));
}

toolbar.addNavigationDrawerItem(i18n("world.chunkbase"), SVG.EXPLORE, null, chunkBaseMenuItem ->
chunkBaseMenuItem.setOnAction(e ->
popup.show(chunkBaseMenuItem,
chunkBasePopup.show(chunkBaseMenuItem,
JFXPopup.PopupVPosition.BOTTOM, JFXPopup.PopupHPosition.LEFT,
chunkBaseMenuItem.getWidth(), 0)));
}

toolbar.addNavigationDrawerItem(i18n("settings.game.exploration"), SVG.FOLDER_OPEN, () -> FXUtils.openFolder(world.getFile()), null);

{
PopupMenu managePopupMenu = new PopupMenu();
JFXPopup managePopup = new JFXPopup(managePopupMenu);

managePopupMenu.getContent().addAll(
new IconedMenuItem(SVG.OUTPUT, i18n("world.export"), () -> WorldManageUIUtils.export(world, sessionLockChannel), managePopup),
new IconedMenuItem(SVG.DELETE, i18n("world.delete"), () -> WorldManageUIUtils.delete(world, () -> fireEvent(new PageCloseEvent()), sessionLockChannel), managePopup),
new IconedMenuItem(SVG.CONTENT_COPY, i18n("world.duplicate"), () -> WorldManageUIUtils.copyWorld(world, null), managePopup)
);

toolbar.addNavigationDrawerItem(i18n("settings.game.management"), SVG.MENU, null, managePopupMenuItem ->
{
managePopupMenuItem.setOnAction(e ->
managePopup.show(managePopupMenuItem,
JFXPopup.PopupVPosition.BOTTOM, JFXPopup.PopupHPosition.LEFT,
managePopupMenuItem.getWidth(), 0));
managePopupMenuItem.setDisable(isReadOnly());
});

}

BorderPane.setMargin(toolbar, new Insets(0, 0, 12, 0));
left.setBottom(toolbar);

this.addEventHandler(Navigator.NavigationEvent.EXITED, this::onExited);
this.addEventHandler(Navigator.NavigationEvent.NAVIGATED, this::onNavigated);
}

private void onNavigated(Navigator.NavigationEvent event) {
if (sessionLockChannel == null || !sessionLockChannel.isOpen()) {
sessionLockChannel = WorldManageUIUtils.getSessionLockChannel(world);
}
}

public void onExited(Navigator.NavigationEvent event) {
try {
WorldManageUIUtils.closeSessionLockChannel(world, sessionLockChannel);
} catch (IOException ignored) {
}
}

@Override
Expand All @@ -156,19 +188,6 @@ public boolean isReadOnly() {
return sessionLockChannel == null;
}

public void onExited(Navigator.NavigationEvent event) {
if (sessionLockChannel != null) {
try {
sessionLockChannel.close();
LOG.info("Releases the lock on world " + world.getFileName());
} catch (IOException e) {
LOG.warning("Failed to close session lock channel", e);
}

sessionLockChannel = null;
}
}

public void launch() {
fireEvent(new PageCloseEvent());
Versions.launchAndEnterWorld(profile, id, world.getFileName());
Expand Down
Loading