重构 WorldListPage 以使用 ListCell 展示元素 (#5224)
This commit is contained in:
@@ -1,80 +0,0 @@
|
|||||||
/*
|
|
||||||
* Hello Minecraft! Launcher
|
|
||||||
* Copyright (C) 2020 huangyuhui <huanghongxun2008@126.com> 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 <https://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
package org.jackhuang.hmcl.ui.versions;
|
|
||||||
|
|
||||||
import javafx.scene.control.Control;
|
|
||||||
import javafx.scene.control.Skin;
|
|
||||||
import org.jackhuang.hmcl.game.World;
|
|
||||||
import org.jackhuang.hmcl.setting.Profile;
|
|
||||||
import org.jackhuang.hmcl.ui.Controllers;
|
|
||||||
import org.jackhuang.hmcl.ui.FXUtils;
|
|
||||||
|
|
||||||
import java.nio.file.Path;
|
|
||||||
|
|
||||||
public final class WorldListItem extends Control {
|
|
||||||
private final World world;
|
|
||||||
private final Path backupsDir;
|
|
||||||
private final WorldListPage parent;
|
|
||||||
private final Profile profile;
|
|
||||||
private final String id;
|
|
||||||
|
|
||||||
public WorldListItem(WorldListPage parent, World world, Path backupsDir, Profile profile, String id) {
|
|
||||||
this.world = world;
|
|
||||||
this.backupsDir = backupsDir;
|
|
||||||
this.parent = parent;
|
|
||||||
this.profile = profile;
|
|
||||||
this.id = id;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected Skin<?> createDefaultSkin() {
|
|
||||||
return new WorldListItemSkin(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
public World getWorld() {
|
|
||||||
return world;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void export() {
|
|
||||||
WorldManageUIUtils.export(world);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void delete() {
|
|
||||||
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));
|
|
||||||
}
|
|
||||||
|
|
||||||
public void launch() {
|
|
||||||
Versions.launchAndEnterWorld(profile, id, world.getFileName());
|
|
||||||
}
|
|
||||||
|
|
||||||
public void generateLaunchScript() {
|
|
||||||
Versions.generateLaunchScriptForQuickEnterWorld(profile, id, world.getFileName());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,170 +0,0 @@
|
|||||||
/*
|
|
||||||
* Hello Minecraft! Launcher
|
|
||||||
* Copyright (C) 2020 huangyuhui <huanghongxun2008@126.com> 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 <https://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
package org.jackhuang.hmcl.ui.versions;
|
|
||||||
|
|
||||||
import com.jfoenix.controls.JFXButton;
|
|
||||||
import com.jfoenix.controls.JFXPopup;
|
|
||||||
import javafx.geometry.Insets;
|
|
||||||
import javafx.geometry.Pos;
|
|
||||||
import javafx.scene.control.SkinBase;
|
|
||||||
import javafx.scene.image.ImageView;
|
|
||||||
import javafx.scene.input.MouseButton;
|
|
||||||
import javafx.scene.layout.BorderPane;
|
|
||||||
import javafx.scene.layout.HBox;
|
|
||||||
import javafx.scene.layout.StackPane;
|
|
||||||
import org.jackhuang.hmcl.game.World;
|
|
||||||
import org.jackhuang.hmcl.ui.FXUtils;
|
|
||||||
import org.jackhuang.hmcl.ui.SVG;
|
|
||||||
import org.jackhuang.hmcl.ui.construct.*;
|
|
||||||
import org.jackhuang.hmcl.util.ChunkBaseApp;
|
|
||||||
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;
|
|
||||||
import static org.jackhuang.hmcl.util.i18n.I18n.formatDateTime;
|
|
||||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
|
||||||
|
|
||||||
public final class WorldListItemSkin extends SkinBase<WorldListItem> {
|
|
||||||
|
|
||||||
private final BorderPane root;
|
|
||||||
|
|
||||||
public WorldListItemSkin(WorldListItem skinnable) {
|
|
||||||
super(skinnable);
|
|
||||||
|
|
||||||
World world = skinnable.getWorld();
|
|
||||||
|
|
||||||
root = new BorderPane();
|
|
||||||
root.getStyleClass().add("md-list-cell");
|
|
||||||
root.setPadding(new Insets(8));
|
|
||||||
|
|
||||||
{
|
|
||||||
StackPane left = new StackPane();
|
|
||||||
FXUtils.installSlowTooltip(left, world.getFile().toString());
|
|
||||||
root.setLeft(left);
|
|
||||||
left.setPadding(new Insets(0, 8, 0, 0));
|
|
||||||
|
|
||||||
ImageView imageView = new ImageView();
|
|
||||||
left.getChildren().add(imageView);
|
|
||||||
FXUtils.limitSize(imageView, 32, 32);
|
|
||||||
imageView.setImage(world.getIcon() == null ? FXUtils.newBuiltinImage("/assets/img/unknown_server.png") : world.getIcon());
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
TwoLineListItem item = new TwoLineListItem();
|
|
||||||
root.setCenter(item);
|
|
||||||
item.setMouseTransparent(true);
|
|
||||||
if (world.getWorldName() != null)
|
|
||||||
item.setTitle(parseColorEscapes(world.getWorldName()));
|
|
||||||
item.setSubtitle(i18n("world.datetime", formatDateTime(Instant.ofEpochMilli(world.getLastPlayed()))));
|
|
||||||
|
|
||||||
if (world.getGameVersion() != null)
|
|
||||||
item.addTag(I18n.getDisplayVersion(world.getGameVersion()));
|
|
||||||
if (world.isLocked())
|
|
||||||
item.addTag(i18n("world.locked"));
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
HBox right = new HBox(8);
|
|
||||||
root.setRight(right);
|
|
||||||
right.setAlignment(Pos.CENTER_RIGHT);
|
|
||||||
|
|
||||||
JFXButton btnMore = new JFXButton();
|
|
||||||
right.getChildren().add(btnMore);
|
|
||||||
btnMore.getStyleClass().add("toggle-icon4");
|
|
||||||
btnMore.setGraphic(SVG.MORE_VERT.createIcon());
|
|
||||||
btnMore.setOnAction(event -> showPopupMenu(JFXPopup.PopupHPosition.RIGHT, 0, root.getHeight()));
|
|
||||||
}
|
|
||||||
|
|
||||||
RipplerContainer container = new RipplerContainer(root);
|
|
||||||
container.setOnMouseClicked(event -> {
|
|
||||||
if (event.getClickCount() != 1)
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (event.getButton() == MouseButton.PRIMARY)
|
|
||||||
skinnable.showManagePage();
|
|
||||||
else if (event.getButton() == MouseButton.SECONDARY)
|
|
||||||
showPopupMenu(JFXPopup.PopupHPosition.LEFT, event.getX(), event.getY());
|
|
||||||
});
|
|
||||||
|
|
||||||
getChildren().setAll(container);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Popup Menu
|
|
||||||
|
|
||||||
public void showPopupMenu(JFXPopup.PopupHPosition hPosition, double initOffsetX, double initOffsetY) {
|
|
||||||
PopupMenu popupMenu = new PopupMenu();
|
|
||||||
JFXPopup popup = new JFXPopup(popupMenu);
|
|
||||||
|
|
||||||
WorldListItem item = getSkinnable();
|
|
||||||
World world = item.getWorld();
|
|
||||||
|
|
||||||
if (world.getGameVersion() != null && world.getGameVersion().isAtLeast("1.20", "23w14a")) {
|
|
||||||
|
|
||||||
IconedMenuItem launchItem = new IconedMenuItem(SVG.ROCKET_LAUNCH, i18n("version.launch_and_enter_world"), item::launch, popup);
|
|
||||||
launchItem.setDisable(world.isLocked());
|
|
||||||
popupMenu.getContent().add(launchItem);
|
|
||||||
|
|
||||||
popupMenu.getContent().addAll(
|
|
||||||
new IconedMenuItem(SVG.SCRIPT, i18n("version.launch_script"), item::generateLaunchScript, popup),
|
|
||||||
new MenuSeparator()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
popupMenu.getContent().add(new IconedMenuItem(SVG.SETTINGS, i18n("world.manage.button"), item::showManagePage, popup));
|
|
||||||
|
|
||||||
if (ChunkBaseApp.isSupported(world)) {
|
|
||||||
popupMenu.getContent().addAll(
|
|
||||||
new MenuSeparator(),
|
|
||||||
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)
|
|
||||||
);
|
|
||||||
|
|
||||||
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));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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.FOLDER_OPEN, i18n("folder.world"), item::reveal, popup)
|
|
||||||
);
|
|
||||||
|
|
||||||
JFXPopup.PopupVPosition vPosition = determineOptimalPopupPosition(root, popup);
|
|
||||||
|
|
||||||
popup.show(root, vPosition, hPosition, initOffsetX, vPosition == JFXPopup.PopupVPosition.TOP ? initOffsetY : -initOffsetY);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -17,18 +17,32 @@
|
|||||||
*/
|
*/
|
||||||
package org.jackhuang.hmcl.ui.versions;
|
package org.jackhuang.hmcl.ui.versions;
|
||||||
|
|
||||||
|
import com.jfoenix.controls.JFXButton;
|
||||||
import com.jfoenix.controls.JFXCheckBox;
|
import com.jfoenix.controls.JFXCheckBox;
|
||||||
|
import com.jfoenix.controls.JFXListView;
|
||||||
|
import com.jfoenix.controls.JFXPopup;
|
||||||
import javafx.beans.property.BooleanProperty;
|
import javafx.beans.property.BooleanProperty;
|
||||||
import javafx.beans.property.SimpleBooleanProperty;
|
import javafx.beans.property.SimpleBooleanProperty;
|
||||||
|
import javafx.geometry.Insets;
|
||||||
|
import javafx.geometry.Pos;
|
||||||
import javafx.scene.Node;
|
import javafx.scene.Node;
|
||||||
|
import javafx.scene.control.ListCell;
|
||||||
import javafx.scene.control.Skin;
|
import javafx.scene.control.Skin;
|
||||||
|
import javafx.scene.control.Tooltip;
|
||||||
|
import javafx.scene.image.ImageView;
|
||||||
|
import javafx.scene.input.MouseButton;
|
||||||
|
import javafx.scene.layout.BorderPane;
|
||||||
|
import javafx.scene.layout.HBox;
|
||||||
|
import javafx.scene.layout.StackPane;
|
||||||
import javafx.stage.FileChooser;
|
import javafx.stage.FileChooser;
|
||||||
import org.jackhuang.hmcl.game.World;
|
import org.jackhuang.hmcl.game.World;
|
||||||
import org.jackhuang.hmcl.setting.Profile;
|
import org.jackhuang.hmcl.setting.Profile;
|
||||||
import org.jackhuang.hmcl.task.Schedulers;
|
import org.jackhuang.hmcl.task.Schedulers;
|
||||||
import org.jackhuang.hmcl.task.Task;
|
import org.jackhuang.hmcl.task.Task;
|
||||||
import org.jackhuang.hmcl.ui.*;
|
import org.jackhuang.hmcl.ui.*;
|
||||||
import org.jackhuang.hmcl.ui.construct.Validator;
|
import org.jackhuang.hmcl.ui.construct.*;
|
||||||
|
import org.jackhuang.hmcl.util.ChunkBaseApp;
|
||||||
|
import org.jackhuang.hmcl.util.i18n.I18n;
|
||||||
import org.jackhuang.hmcl.util.io.FileUtils;
|
import org.jackhuang.hmcl.util.io.FileUtils;
|
||||||
import org.jackhuang.hmcl.util.versioning.GameVersionNumber;
|
import org.jackhuang.hmcl.util.versioning.GameVersionNumber;
|
||||||
|
|
||||||
@@ -36,15 +50,18 @@ import java.io.IOException;
|
|||||||
import java.nio.file.FileAlreadyExistsException;
|
import java.nio.file.FileAlreadyExistsException;
|
||||||
import java.nio.file.InvalidPathException;
|
import java.nio.file.InvalidPathException;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
|
import java.time.Instant;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.stream.Collectors;
|
|
||||||
import java.util.stream.Stream;
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
|
import static org.jackhuang.hmcl.ui.FXUtils.determineOptimalPopupPosition;
|
||||||
|
import static org.jackhuang.hmcl.util.StringUtils.parseColorEscapes;
|
||||||
|
import static org.jackhuang.hmcl.util.i18n.I18n.formatDateTime;
|
||||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||||
import static org.jackhuang.hmcl.util.logging.Logger.LOG;
|
import static org.jackhuang.hmcl.util.logging.Logger.LOG;
|
||||||
|
|
||||||
public final class WorldListPage extends ListPageBase<WorldListItem> implements VersionPage.VersionLoadable {
|
public final class WorldListPage extends ListPageBase<World> implements VersionPage.VersionLoadable {
|
||||||
private final BooleanProperty showAll = new SimpleBooleanProperty(this, "showAll", false);
|
private final BooleanProperty showAll = new SimpleBooleanProperty(this, "showAll", false);
|
||||||
|
|
||||||
private Path savesDir;
|
private Path savesDir;
|
||||||
@@ -52,19 +69,15 @@ public final class WorldListPage extends ListPageBase<WorldListItem> implements
|
|||||||
private List<World> worlds;
|
private List<World> worlds;
|
||||||
private Profile profile;
|
private Profile profile;
|
||||||
private String id;
|
private String id;
|
||||||
private GameVersionNumber gameVersion;
|
|
||||||
|
private int refreshCount = 0;
|
||||||
|
|
||||||
public WorldListPage() {
|
public WorldListPage() {
|
||||||
FXUtils.applyDragListener(this, it -> "zip".equals(FileUtils.getExtension(it)), modpacks -> {
|
FXUtils.applyDragListener(this, it -> "zip".equals(FileUtils.getExtension(it)), modpacks -> {
|
||||||
installWorld(modpacks.get(0));
|
installWorld(modpacks.get(0));
|
||||||
});
|
});
|
||||||
|
|
||||||
showAll.addListener(e -> {
|
showAll.addListener(e -> updateWorldList());
|
||||||
if (worlds != null)
|
|
||||||
itemsProperty().setAll(worlds.stream()
|
|
||||||
.filter(world -> isShowAll() || world.getGameVersion() == null || world.getGameVersion().equals(gameVersion))
|
|
||||||
.map(world -> new WorldListItem(this, world, backupsDir, profile, id)).toList());
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -81,33 +94,46 @@ public final class WorldListPage extends ListPageBase<WorldListItem> implements
|
|||||||
refresh();
|
refresh();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void remove(WorldListItem item) {
|
private void updateWorldList() {
|
||||||
itemsProperty().remove(item);
|
if (worlds == null) {
|
||||||
|
getItems().clear();
|
||||||
|
} else if (showAll.get()) {
|
||||||
|
getItems().setAll(worlds);
|
||||||
|
} else {
|
||||||
|
GameVersionNumber gameVersion = profile.getRepository().getGameVersion(id).map(GameVersionNumber::asGameVersion).orElse(null);
|
||||||
|
getItems().setAll(worlds.stream()
|
||||||
|
.filter(world -> world.getGameVersion() == null || world.getGameVersion().equals(gameVersion))
|
||||||
|
.toList());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void refresh() {
|
public void refresh() {
|
||||||
if (profile == null || id == null)
|
if (profile == null || id == null)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
int currentRefresh = ++refreshCount;
|
||||||
|
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
Task.runAsync(() -> gameVersion = profile.getRepository().getGameVersion(id).map(GameVersionNumber::asGameVersion).orElse(null))
|
Task.supplyAsync(Schedulers.io(), () -> {
|
||||||
.thenApplyAsync(unused -> {
|
// Ensure the game version number is parsed
|
||||||
try (Stream<World> stream = World.getWorlds(savesDir)) {
|
profile.getRepository().getGameVersion(id);
|
||||||
return stream.parallel().collect(Collectors.toList());
|
try (Stream<World> stream = World.getWorlds(savesDir)) {
|
||||||
}
|
return stream.toList();
|
||||||
})
|
}
|
||||||
.whenComplete(Schedulers.javafx(), (result, exception) -> {
|
}).whenComplete(Schedulers.javafx(), (result, exception) -> {
|
||||||
worlds = result;
|
if (refreshCount != currentRefresh) {
|
||||||
setLoading(false);
|
// A newer refresh task is running, discard this result
|
||||||
if (exception == null) {
|
return;
|
||||||
itemsProperty().setAll(result.stream()
|
}
|
||||||
.filter(world -> isShowAll() || world.getGameVersion() == null || world.getGameVersion().equals(gameVersion))
|
|
||||||
.map(world -> new WorldListItem(this, world, backupsDir, profile, id))
|
worlds = result;
|
||||||
.collect(Collectors.toList()));
|
updateWorldList();
|
||||||
} else {
|
|
||||||
LOG.warning("Failed to load world list page", exception);
|
if (exception != null)
|
||||||
}
|
LOG.warning("Failed to load world list page", exception);
|
||||||
}).start();
|
|
||||||
|
setLoading(false);
|
||||||
|
}).start();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void add() {
|
public void add() {
|
||||||
@@ -133,8 +159,8 @@ public final class WorldListPage extends ListPageBase<WorldListItem> implements
|
|||||||
Controllers.prompt(i18n("world.name.enter"), (name, resolve, reject) -> {
|
Controllers.prompt(i18n("world.name.enter"), (name, resolve, reject) -> {
|
||||||
Task.runAsync(() -> world.install(savesDir, name))
|
Task.runAsync(() -> world.install(savesDir, name))
|
||||||
.whenComplete(Schedulers.javafx(), () -> {
|
.whenComplete(Schedulers.javafx(), () -> {
|
||||||
itemsProperty().add(new WorldListItem(this, new World(savesDir.resolve(name)), backupsDir, profile, id));
|
|
||||||
resolve.run();
|
resolve.run();
|
||||||
|
refresh();
|
||||||
}, e -> {
|
}, e -> {
|
||||||
if (e instanceof FileAlreadyExistsException)
|
if (e instanceof FileAlreadyExistsException)
|
||||||
reject.accept(i18n("world.import.failed", i18n("world.import.already_exists")));
|
reject.accept(i18n("world.import.failed", i18n("world.import.already_exists")));
|
||||||
@@ -150,19 +176,39 @@ public final class WorldListPage extends ListPageBase<WorldListItem> implements
|
|||||||
}).start();
|
}).start();
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isShowAll() {
|
private void showManagePage(World world) {
|
||||||
return showAll.get();
|
Controllers.navigate(new WorldManagePage(world, backupsDir, profile, id));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void export(World world) {
|
||||||
|
WorldManageUIUtils.export(world);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void delete(World world) {
|
||||||
|
WorldManageUIUtils.delete(world, this::refresh);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void copy(World world) {
|
||||||
|
WorldManageUIUtils.copyWorld(world, this::refresh);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void reveal(World world) {
|
||||||
|
FXUtils.openFolder(world.getFile());
|
||||||
|
}
|
||||||
|
|
||||||
|
public void launch(World world) {
|
||||||
|
Versions.launchAndEnterWorld(profile, id, world.getFileName());
|
||||||
|
}
|
||||||
|
|
||||||
|
public void generateLaunchScript(World world) {
|
||||||
|
Versions.generateLaunchScriptForQuickEnterWorld(profile, id, world.getFileName());
|
||||||
}
|
}
|
||||||
|
|
||||||
public BooleanProperty showAllProperty() {
|
public BooleanProperty showAllProperty() {
|
||||||
return showAll;
|
return showAll;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setShowAll(boolean showAll) {
|
private final class WorldListPageSkin extends ToolbarListPageSkin<World, WorldListPage> {
|
||||||
this.showAll.set(showAll);
|
|
||||||
}
|
|
||||||
|
|
||||||
private final class WorldListPageSkin extends ToolbarListPageSkin<WorldListItem, WorldListPage> {
|
|
||||||
|
|
||||||
WorldListPageSkin() {
|
WorldListPageSkin() {
|
||||||
super(WorldListPage.this);
|
super(WorldListPage.this);
|
||||||
@@ -179,5 +225,162 @@ public final class WorldListPage extends ListPageBase<WorldListItem> implements
|
|||||||
createToolbarButton2(i18n("world.add"), SVG.ADD, skinnable::add),
|
createToolbarButton2(i18n("world.add"), SVG.ADD, skinnable::add),
|
||||||
createToolbarButton2(i18n("world.download"), SVG.DOWNLOAD, skinnable::download));
|
createToolbarButton2(i18n("world.download"), SVG.DOWNLOAD, skinnable::download));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected ListCell<World> createListCell(JFXListView<World> listView) {
|
||||||
|
return new WorldListCell(getSkinnable());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final class WorldListCell extends ListCell<World> {
|
||||||
|
|
||||||
|
private final WorldListPage page;
|
||||||
|
|
||||||
|
private final RipplerContainer graphic;
|
||||||
|
private final ImageView imageView;
|
||||||
|
private final Tooltip leftTooltip;
|
||||||
|
private final TwoLineListItem content;
|
||||||
|
|
||||||
|
public WorldListCell(WorldListPage page) {
|
||||||
|
this.page = page;
|
||||||
|
|
||||||
|
var root = new BorderPane();
|
||||||
|
root.getStyleClass().add("md-list-cell");
|
||||||
|
root.setPadding(new Insets(8));
|
||||||
|
|
||||||
|
{
|
||||||
|
StackPane left = new StackPane();
|
||||||
|
this.leftTooltip = new Tooltip();
|
||||||
|
FXUtils.installSlowTooltip(left, leftTooltip);
|
||||||
|
root.setLeft(left);
|
||||||
|
left.setPadding(new Insets(0, 8, 0, 0));
|
||||||
|
|
||||||
|
this.imageView = new ImageView();
|
||||||
|
left.getChildren().add(imageView);
|
||||||
|
FXUtils.limitSize(imageView, 32, 32);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
this.content = new TwoLineListItem();
|
||||||
|
root.setCenter(content);
|
||||||
|
content.setMouseTransparent(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
HBox right = new HBox(8);
|
||||||
|
root.setRight(right);
|
||||||
|
right.setAlignment(Pos.CENTER_RIGHT);
|
||||||
|
|
||||||
|
JFXButton btnMore = new JFXButton();
|
||||||
|
right.getChildren().add(btnMore);
|
||||||
|
btnMore.getStyleClass().add("toggle-icon4");
|
||||||
|
btnMore.setGraphic(SVG.MORE_VERT.createIcon());
|
||||||
|
btnMore.setOnAction(event -> {
|
||||||
|
World world = getItem();
|
||||||
|
if (world != null)
|
||||||
|
showPopupMenu(world, JFXPopup.PopupHPosition.RIGHT, 0, root.getHeight());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
this.graphic = new RipplerContainer(root);
|
||||||
|
graphic.setOnMouseClicked(event -> {
|
||||||
|
if (event.getClickCount() != 1)
|
||||||
|
return;
|
||||||
|
|
||||||
|
World world = getItem();
|
||||||
|
if (world == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (event.getButton() == MouseButton.PRIMARY)
|
||||||
|
page.showManagePage(world);
|
||||||
|
else if (event.getButton() == MouseButton.SECONDARY)
|
||||||
|
showPopupMenu(world, JFXPopup.PopupHPosition.LEFT, event.getX(), event.getY());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void updateItem(World world, boolean empty) {
|
||||||
|
super.updateItem(world, empty);
|
||||||
|
|
||||||
|
this.content.getTags().clear();
|
||||||
|
|
||||||
|
if (empty || world == null) {
|
||||||
|
setGraphic(null);
|
||||||
|
imageView.setImage(null);
|
||||||
|
leftTooltip.setText("");
|
||||||
|
content.setTitle("");
|
||||||
|
content.setSubtitle("");
|
||||||
|
} else {
|
||||||
|
imageView.setImage(world.getIcon() == null ? FXUtils.newBuiltinImage("/assets/img/unknown_server.png") : world.getIcon());
|
||||||
|
leftTooltip.setText(world.getFile().toString());
|
||||||
|
content.setTitle(world.getWorldName() != null ? parseColorEscapes(world.getWorldName()) : "");
|
||||||
|
|
||||||
|
if (world.getGameVersion() != null)
|
||||||
|
content.addTag(I18n.getDisplayVersion(world.getGameVersion()));
|
||||||
|
if (world.isLocked())
|
||||||
|
content.addTag(i18n("world.locked"));
|
||||||
|
|
||||||
|
content.setSubtitle(i18n("world.datetime", formatDateTime(Instant.ofEpochMilli(world.getLastPlayed()))));
|
||||||
|
|
||||||
|
setGraphic(graphic);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Popup Menu
|
||||||
|
|
||||||
|
public void showPopupMenu(World world, JFXPopup.PopupHPosition hPosition, double initOffsetX, double initOffsetY) {
|
||||||
|
PopupMenu popupMenu = new PopupMenu();
|
||||||
|
JFXPopup popup = new JFXPopup(popupMenu);
|
||||||
|
|
||||||
|
if (world.getGameVersion() != null && world.getGameVersion().isAtLeast("1.20", "23w14a")) {
|
||||||
|
|
||||||
|
IconedMenuItem launchItem = new IconedMenuItem(SVG.ROCKET_LAUNCH, i18n("version.launch_and_enter_world"), () -> page.launch(world), popup);
|
||||||
|
launchItem.setDisable(world.isLocked());
|
||||||
|
popupMenu.getContent().add(launchItem);
|
||||||
|
|
||||||
|
popupMenu.getContent().addAll(
|
||||||
|
new IconedMenuItem(SVG.SCRIPT, i18n("version.launch_script"), () -> page.generateLaunchScript(world), popup),
|
||||||
|
new MenuSeparator()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
popupMenu.getContent().add(new IconedMenuItem(SVG.SETTINGS, i18n("world.manage.button"), () -> page.showManagePage(world), popup));
|
||||||
|
|
||||||
|
if (ChunkBaseApp.isSupported(world)) {
|
||||||
|
popupMenu.getContent().addAll(
|
||||||
|
new MenuSeparator(),
|
||||||
|
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)
|
||||||
|
);
|
||||||
|
|
||||||
|
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));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
IconedMenuItem exportMenuItem = new IconedMenuItem(SVG.OUTPUT, i18n("world.export"), () -> page.export(world), popup);
|
||||||
|
IconedMenuItem deleteMenuItem = new IconedMenuItem(SVG.DELETE, i18n("world.delete"), () -> page.delete(world), popup);
|
||||||
|
IconedMenuItem duplicateMenuItem = new IconedMenuItem(SVG.CONTENT_COPY, i18n("world.duplicate"), () -> page.copy(world), 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.FOLDER_OPEN, i18n("folder.world"), () -> page.reveal(world), popup)
|
||||||
|
);
|
||||||
|
|
||||||
|
JFXPopup.PopupVPosition vPosition = determineOptimalPopupPosition(this, popup);
|
||||||
|
popup.show(this, vPosition, hPosition, initOffsetX, vPosition == JFXPopup.PopupVPosition.TOP ? initOffsetY : -initOffsetY);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user