Start working on GTK port (far from finished)
This commit is contained in:
parent
92109d353e
commit
eb3fb83673
|
@ -14,7 +14,7 @@ public class GuiCommand extends Command {
|
|||
@Override
|
||||
public void invoke(CommandArgs args) {
|
||||
LauncherEnv.updateBackend(new GuiEnvBackend());
|
||||
GuiMain.main();
|
||||
GuiMain.showGui();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -0,0 +1,19 @@
|
|||
plugins {
|
||||
id("inceptum.application-conventions")
|
||||
id("com.github.johnrengelman.shadow")
|
||||
}
|
||||
|
||||
application {
|
||||
mainClass.set("io.gitlab.jfronny.inceptum.gtk.GtkMain")
|
||||
}
|
||||
|
||||
repositories {
|
||||
maven { url = uri("https://jitpack.io") }
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation("com.github.bailuk:java-gtk:0.2")
|
||||
implementation(project(":launcher"))
|
||||
}
|
||||
|
||||
tasks.runShadow.get().workingDir = rootProject.projectDir
|
|
@ -0,0 +1,95 @@
|
|||
package io.gitlab.jfronny.inceptum.gtk;
|
||||
|
||||
import ch.bailu.gtk.GTK;
|
||||
import ch.bailu.gtk.gtk.*;
|
||||
import ch.bailu.gtk.type.Str;
|
||||
import io.gitlab.jfronny.commons.StringFormatter;
|
||||
import io.gitlab.jfronny.inceptum.common.Utils;
|
||||
import io.gitlab.jfronny.inceptum.gtk.util.I18n;
|
||||
import io.gitlab.jfronny.inceptum.launcher.LauncherEnv;
|
||||
import io.gitlab.jfronny.inceptum.launcher.api.account.MicrosoftAccount;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
public enum GtkEnvBackend implements LauncherEnv.EnvBackend { //TODO test
|
||||
INSTANCE;
|
||||
|
||||
public Window dialogParent = null;
|
||||
|
||||
@Override
|
||||
public void showError(String message, String title) {
|
||||
Utils.LOGGER.error(message);
|
||||
MessageDialog dialog = new MessageDialog(dialogParent, DialogFlags.MODAL | DialogFlags.DESTROY_WITH_PARENT, MessageType.ERROR, ButtonsType.CLOSE, null);
|
||||
dialog.setTitle(new Str(title));
|
||||
dialog.setMarkup(new Str(message));
|
||||
dialog.show();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void showError(String message, Throwable t) {
|
||||
Utils.LOGGER.error(message, t);
|
||||
MessageDialog dialog = new MessageDialog(dialogParent, DialogFlags.MODAL | DialogFlags.DESTROY_WITH_PARENT, MessageType.ERROR, ButtonsType.CLOSE, null);
|
||||
dialog.setTitle(new Str(message));
|
||||
dialog.setMarkup(new Str(StringFormatter.toString(t)));
|
||||
dialog.show();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void showInfo(String message, String title) {
|
||||
Utils.LOGGER.info(message);
|
||||
MessageDialog dialog = new MessageDialog(dialogParent, DialogFlags.MODAL | DialogFlags.DESTROY_WITH_PARENT, MessageType.INFO, ButtonsType.CLOSE, null);
|
||||
dialog.setTitle(new Str(title));
|
||||
dialog.setMarkup(new Str(message));
|
||||
dialog.show();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void showOkCancel(String message, String title, Runnable ok, Runnable cancel, boolean defaultCancel) {
|
||||
Utils.LOGGER.info(message);
|
||||
MessageDialog dialog = new MessageDialog(dialogParent, DialogFlags.MODAL | DialogFlags.DESTROY_WITH_PARENT, MessageType.QUESTION, ButtonsType.OK_CANCEL, null);
|
||||
dialog.setTitle(new Str(title));
|
||||
dialog.setMarkup(new Str(message));
|
||||
dialog.onResponse(response_id -> {
|
||||
if (response_id == ResponseType.OK) ok.run();
|
||||
else if (response_id == ResponseType.CANCEL) cancel.run();
|
||||
else {
|
||||
Utils.LOGGER.error("Unexpected response type: " + response_id);
|
||||
cancel.run();
|
||||
}
|
||||
});
|
||||
dialog.show();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void getInput(String prompt, String details, String defaultValue, Consumer<String> ok, Runnable cancel) throws IOException {
|
||||
Dialog dialog = new Dialog();
|
||||
if (dialogParent != null) dialog.setParent(dialogParent);
|
||||
dialog.setModal(GTK.TRUE);
|
||||
dialog.setDestroyWithParent(GTK.TRUE);
|
||||
dialog.setTitle(new Str(prompt));
|
||||
Box box = new Box(Orientation.VERTICAL, 0);
|
||||
box.append(new Label(new Str(details)));
|
||||
Entry entry = new Entry();
|
||||
Editable entryEditable = new Editable(entry.cast());
|
||||
entryEditable.setText(new Str(defaultValue));
|
||||
box.append(entry);
|
||||
dialog.setChild(box);
|
||||
dialog.addButton(I18n.str("ok"), ResponseType.OK);
|
||||
dialog.addButton(I18n.str("cancel"), ResponseType.CANCEL);
|
||||
dialog.onResponse(response_id -> {
|
||||
if (response_id == ResponseType.OK) ok.accept(entryEditable.getText().toString());
|
||||
else if (response_id == ResponseType.CANCEL) cancel.run();
|
||||
else {
|
||||
Utils.LOGGER.error("Unexpected response type: " + response_id);
|
||||
cancel.run();
|
||||
}
|
||||
});
|
||||
dialog.show();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void showLoginRefreshPrompt(MicrosoftAccount account) {
|
||||
//TODO
|
||||
}
|
||||
}
|
|
@ -0,0 +1,144 @@
|
|||
package io.gitlab.jfronny.inceptum.gtk;
|
||||
|
||||
import ch.bailu.gtk.GTK;
|
||||
import ch.bailu.gtk.gio.ApplicationFlags;
|
||||
import ch.bailu.gtk.gtk.*;
|
||||
import ch.bailu.gtk.type.Str;
|
||||
import ch.bailu.gtk.type.Strs;
|
||||
import io.gitlab.jfronny.inceptum.common.*;
|
||||
import io.gitlab.jfronny.inceptum.gtk.menu.MenuBuilder;
|
||||
import io.gitlab.jfronny.inceptum.gtk.util.I18n;
|
||||
import io.gitlab.jfronny.inceptum.launcher.LauncherEnv;
|
||||
import io.gitlab.jfronny.inceptum.launcher.api.account.*;
|
||||
import io.gitlab.jfronny.inceptum.launcher.util.InstanceList;
|
||||
import io.gitlab.jfronny.inceptum.launcher.system.install.Steps;
|
||||
import io.gitlab.jfronny.inceptum.launcher.system.launch.InstanceLauncher;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.*;
|
||||
|
||||
public class GtkMain {
|
||||
public static final Str ID = new Str("io.gitlab.jfronny.inceptum");
|
||||
public static final Str TITLE = new Str("Inceptum");
|
||||
public static final int WIDTH = 720 / 2;
|
||||
public static final int HEIGHT = 1440 / 2;
|
||||
private static MenuBuilder launchMenu;
|
||||
private static MenuBuilder accountsMenu;
|
||||
|
||||
public static void main(String[] args) throws IOException {
|
||||
LauncherEnv.initialize(GtkEnvBackend.INSTANCE);
|
||||
Utils.LOGGER.info("Launching Inceptum v" + BuildMetadata.VERSION);
|
||||
Utils.LOGGER.info("Loading from " + MetaHolder.BASE_PATH);
|
||||
//TODO look into java-gtk samples for window architecture
|
||||
int statusCode = -1;
|
||||
try {
|
||||
statusCode = showGui(args);
|
||||
} finally {
|
||||
LauncherEnv.terminate();
|
||||
System.exit(statusCode);
|
||||
}
|
||||
}
|
||||
|
||||
public static int showGui(String[] args) throws IOException {
|
||||
var app = new Application(ID, ApplicationFlags.FLAGS_NONE);
|
||||
app.onActivate(() -> {
|
||||
var menu = new MenuBuilder(app);
|
||||
var file = menu.submenu("file");
|
||||
file.button("new", () -> {
|
||||
//TODO
|
||||
});
|
||||
file.button("redownload", () -> {
|
||||
//TODO
|
||||
});
|
||||
file.button("exit", () -> {
|
||||
//TODO
|
||||
});
|
||||
launchMenu = menu.submenu("launch");
|
||||
generateLaunchMenu();
|
||||
accountsMenu = menu.submenu("account");
|
||||
generateAccountsMenu();
|
||||
var help = menu.submenu("help");
|
||||
help.button("about", GtkMain::showAboutDialog);
|
||||
help.button("log", () -> {
|
||||
//TODO
|
||||
});
|
||||
|
||||
var header = new HeaderBar();
|
||||
var button = new MenuButton();
|
||||
button.setPopover(launchMenu.asPopover()); //TODO another menu makes more sense here
|
||||
header.packEnd(button);
|
||||
|
||||
var window = new ApplicationWindow(app);
|
||||
window.setDefaultSize(WIDTH, HEIGHT);
|
||||
window.setTitle(TITLE);
|
||||
window.setTitlebar(header);
|
||||
window.setChild(new TextView());
|
||||
|
||||
//TODO create UI based on gnome boxes?
|
||||
|
||||
window.show();
|
||||
GtkEnvBackend.INSTANCE.dialogParent = window;
|
||||
window.onCloseRequest(() -> {
|
||||
GtkEnvBackend.INSTANCE.dialogParent = null;
|
||||
return GTK.FALSE;
|
||||
});
|
||||
});
|
||||
return app.run(args.length, new Strs(args));
|
||||
}
|
||||
|
||||
private static void showAboutDialog() {
|
||||
AboutDialog dialog = new AboutDialog();
|
||||
dialog.setProgramName(new Str("Inceptum"));
|
||||
dialog.setCopyright(new Str("Copyright (C) 2021 JFronny"));
|
||||
dialog.setVersion(new Str(BuildMetadata.VERSION.toString()));
|
||||
dialog.setLicenseType(License.MIT_X11);
|
||||
dialog.setLicense(I18n.str("about.license"));
|
||||
dialog.setWebsiteLabel(I18n.str("about.contact"));
|
||||
dialog.setWebsite(new Str("https://jfronny.gitlab.io/contact.html"));
|
||||
if (!BuildMetadata.IS_PUBLIC) {
|
||||
dialog.setComments(I18n.str("about.unsupported-build"));
|
||||
}
|
||||
int vm = Runtime.version().feature();
|
||||
dialog.setSystemInformation(I18n.str(BuildMetadata.VM_VERSION == vm ? "about.jvm" : "about.jvm.unsupported", vm));
|
||||
//TODO setLogo
|
||||
dialog.show();
|
||||
}
|
||||
|
||||
private static void generateLaunchMenu() {
|
||||
Objects.requireNonNull(launchMenu);
|
||||
launchMenu.clear();
|
||||
try {
|
||||
InstanceList.forEachLaunchable(entry -> {
|
||||
Utils.LOGGER.info("Entry " + entry.toString());
|
||||
launchMenu.literalButton(entry.id(), entry.toString(), () -> {
|
||||
try {
|
||||
Steps.reDownload(entry.path(), Steps.createProcessState());
|
||||
} catch (IOException e) {
|
||||
Utils.LOGGER.error("Could not redownload instance, trying to start anyways", e);
|
||||
}
|
||||
InstanceLauncher.launchClient(entry.path(), entry.meta());
|
||||
//TODO figure out why this crashes
|
||||
});
|
||||
});
|
||||
} catch (IOException e) {
|
||||
Utils.LOGGER.error("Could not generate launch menu", e);
|
||||
}
|
||||
}
|
||||
|
||||
private static void generateAccountsMenu() {
|
||||
Objects.requireNonNull(accountsMenu);
|
||||
accountsMenu.clear();
|
||||
accountsMenu.button("new", () -> {
|
||||
//TODO
|
||||
});
|
||||
accountsMenu.button("manage", () -> {
|
||||
//TODO UI to add/remove accounts
|
||||
});
|
||||
List<MicrosoftAccount> accounts = new ArrayList<>(AccountManager.getAccounts());
|
||||
accounts.add(null);
|
||||
accountsMenu.literalRadio("account", accounts.get(AccountManager.getSelectedIndex()), accounts, (i, acc) -> {
|
||||
if (acc == null) return I18n.get("account.none");
|
||||
return acc.minecraftUsername;
|
||||
}, AccountManager::switchAccount);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
package io.gitlab.jfronny.inceptum.gtk.menu;
|
||||
|
||||
import ch.bailu.gtk.gio.SimpleAction;
|
||||
|
||||
public class ButtonItem extends MenuItem {
|
||||
public ButtonItem(SimpleAction action) {
|
||||
super(action);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,128 @@
|
|||
package io.gitlab.jfronny.inceptum.gtk.menu;
|
||||
|
||||
import ch.bailu.gtk.GTK;
|
||||
import ch.bailu.gtk.gio.MenuItem;
|
||||
import ch.bailu.gtk.gio.*;
|
||||
import ch.bailu.gtk.glib.Variant;
|
||||
import ch.bailu.gtk.glib.VariantType;
|
||||
import ch.bailu.gtk.gtk.Application;
|
||||
import ch.bailu.gtk.gtk.PopoverMenu;
|
||||
import ch.bailu.gtk.type.Pointer;
|
||||
import ch.bailu.gtk.type.Str;
|
||||
import io.gitlab.jfronny.inceptum.gtk.util.I18n;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.function.*;
|
||||
|
||||
public class MenuBuilder {
|
||||
private static final Object LOCK = new Object();
|
||||
|
||||
private static Menu getRootMenu(Application app) {
|
||||
synchronized (LOCK) {
|
||||
var currentMenu = app.getMenubar();
|
||||
if (currentMenu.equals(Pointer.NULL)) {
|
||||
var menu = new Menu();
|
||||
app.setMenubar(menu);
|
||||
return menu;
|
||||
} else {
|
||||
return new Menu(currentMenu.cast());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private final ActionMap map;
|
||||
private final Map<String, Action> refs = new LinkedHashMap<>();
|
||||
private final Menu menu;
|
||||
private final String prefix;
|
||||
|
||||
public MenuBuilder(Application app) {
|
||||
this(app, getRootMenu(app), "");
|
||||
}
|
||||
|
||||
public MenuBuilder(Application app, Menu menu, String prefix) {
|
||||
this(new ActionMap(app.cast()), menu, prefix);
|
||||
}
|
||||
|
||||
public MenuBuilder(ActionMap map, Menu menu, String prefix) {
|
||||
this.map = Objects.requireNonNull(map);
|
||||
this.menu = Objects.requireNonNull(menu);
|
||||
this.prefix = Objects.requireNonNull(prefix);
|
||||
}
|
||||
|
||||
public ButtonItem button(String name, Runnable onClick) {
|
||||
return literalButton(name, I18n.get("menu." + prefix + name), onClick);
|
||||
}
|
||||
|
||||
public ButtonItem literalButton(String internalName, String label, Runnable onClick) {
|
||||
internalName = prefix + internalName;
|
||||
SimpleAction sAct = new SimpleAction(new Str(internalName), null);
|
||||
addAction(internalName, sAct);
|
||||
sAct.onActivate(v -> onClick.run());
|
||||
menu.appendItem(new MenuItem(new Str(label), new Str("app." + internalName)));
|
||||
return new ButtonItem(sAct);
|
||||
}
|
||||
|
||||
public ToggleItem toggle(String name, boolean initial, Consumer<Boolean> action) {
|
||||
name = prefix + name;
|
||||
SimpleAction sAct = SimpleAction.newStatefulSimpleAction(new Str(name), null, Variant.newBooleanVariant(GTK.is(initial)));
|
||||
Action rAct = addAction(name, sAct);
|
||||
sAct.onActivate(parameter -> {
|
||||
boolean state = !GTK.is(rAct.getState().getBoolean());
|
||||
sAct.setState(Variant.newBooleanVariant(GTK.is(state)));
|
||||
action.accept(state);
|
||||
});
|
||||
menu.appendItem(new MenuItem(I18n.str("menu." + name), new Str("app." + name)));
|
||||
return new ToggleItem(sAct);
|
||||
}
|
||||
|
||||
public <T> RadioItem<T> radio(String name, T initial, List<T> options, Consumer<T> action) {
|
||||
return literalRadio(name, initial, options, (i, t) -> I18n.get("menu." + name, i), action);
|
||||
}
|
||||
|
||||
public <T> RadioItem<T> literalRadio(String name, T initial, List<T> options, BiFunction<Integer, T, String> stringifier, Consumer<T> action) {
|
||||
Objects.requireNonNull(options);
|
||||
name = prefix + name;
|
||||
SimpleAction sAct = SimpleAction.newStatefulSimpleAction(new Str(name), new VariantType(new Str("i")), Variant.newInt32Variant(options.indexOf(initial)));
|
||||
addAction(name, sAct);
|
||||
sAct.onActivate(parameter -> {
|
||||
sAct.setState(parameter);
|
||||
action.accept(options.get(parameter.getInt32()));
|
||||
});
|
||||
int i = 0;
|
||||
for (T option : options) {
|
||||
menu.appendItem(new MenuItem(new Str(stringifier.apply(i, option)), new Str("app." + name + "(" + i + ")")));
|
||||
i++;
|
||||
}
|
||||
return new RadioItem<>(sAct, options);
|
||||
}
|
||||
|
||||
public MenuBuilder submenu(String name) {
|
||||
name = prefix + name;
|
||||
Menu submenu = new Menu();
|
||||
menu.appendSubmenu(I18n.str("menu." + name), submenu);
|
||||
return new MenuBuilder(map, submenu, name + ".");
|
||||
}
|
||||
|
||||
public void clear() {
|
||||
menu.removeAll();
|
||||
refs.forEach((name, action) -> {
|
||||
map.removeAction(new Str(name));
|
||||
});
|
||||
refs.clear();
|
||||
}
|
||||
|
||||
private Action addAction(String name, SimpleAction action) {
|
||||
Action rAct = new Action(action.cast());
|
||||
map.addAction(rAct);
|
||||
refs.put(name, rAct);
|
||||
return rAct;
|
||||
}
|
||||
|
||||
public Menu getMenu() {
|
||||
return menu;
|
||||
}
|
||||
|
||||
public PopoverMenu asPopover() {
|
||||
return PopoverMenu.newFromModelPopoverMenu(menu);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
package io.gitlab.jfronny.inceptum.gtk.menu;
|
||||
|
||||
import ch.bailu.gtk.GTK;
|
||||
import ch.bailu.gtk.gio.Action;
|
||||
import ch.bailu.gtk.gio.SimpleAction;
|
||||
|
||||
public abstract class MenuItem {
|
||||
protected final SimpleAction sAction;
|
||||
protected final Action action;
|
||||
|
||||
public MenuItem(SimpleAction action) {
|
||||
this.sAction = action;
|
||||
this.action = new Action(action.cast());
|
||||
}
|
||||
|
||||
public boolean getEnabled() {
|
||||
return GTK.is(action.getEnabled());
|
||||
}
|
||||
|
||||
public void setEnabled(boolean enabled) {
|
||||
sAction.setEnabled(GTK.is(enabled));
|
||||
}
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
package io.gitlab.jfronny.inceptum.gtk.menu;
|
||||
|
||||
import ch.bailu.gtk.gio.SimpleAction;
|
||||
import ch.bailu.gtk.glib.Variant;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class RadioItem<T> extends MenuItem {
|
||||
private final List<T> options;
|
||||
|
||||
public RadioItem(SimpleAction action, List<T> options) {
|
||||
super(action);
|
||||
this.options = options;
|
||||
}
|
||||
|
||||
public void setSelected(T selected) {
|
||||
sAction.setState(Variant.newInt32Variant(options.indexOf(selected)));
|
||||
}
|
||||
|
||||
public T getSelected() {
|
||||
return options.get(action.getState().getInt32());
|
||||
}
|
||||
}
|
|
@ -0,0 +1,25 @@
|
|||
package io.gitlab.jfronny.inceptum.gtk.menu;
|
||||
|
||||
import ch.bailu.gtk.GTK;
|
||||
import ch.bailu.gtk.gio.SimpleAction;
|
||||
import ch.bailu.gtk.glib.Variant;
|
||||
|
||||
public class ToggleItem extends MenuItem {
|
||||
public ToggleItem(SimpleAction action) {
|
||||
super(action);
|
||||
}
|
||||
|
||||
public boolean getState() {
|
||||
return GTK.is(action.getState().getBoolean());
|
||||
}
|
||||
|
||||
public void setState(boolean state) {
|
||||
sAction.setState(Variant.newBooleanVariant(GTK.is(state)));
|
||||
}
|
||||
|
||||
public boolean toggle() {
|
||||
boolean toggled = !getState();
|
||||
setState(toggled);
|
||||
return toggled;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,25 @@
|
|||
package io.gitlab.jfronny.inceptum.gtk.util;
|
||||
|
||||
import ch.bailu.gtk.type.Str;
|
||||
|
||||
import java.util.ResourceBundle;
|
||||
|
||||
public class I18n {
|
||||
private static final ResourceBundle bundle = ResourceBundle.getBundle("inceptum");
|
||||
|
||||
public static String get(String key) {
|
||||
return bundle.getString(key);
|
||||
}
|
||||
|
||||
public static String get(String key, Object... args) {
|
||||
return String.format(bundle.getString(key), args);
|
||||
}
|
||||
|
||||
public static Str str(String key) {
|
||||
return new Str(get(key));
|
||||
}
|
||||
|
||||
public static Str str(String key, Object... args) {
|
||||
return new Str(get(key, args));
|
||||
}
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
account.none=None
|
||||
about.contact=Contact
|
||||
about.jvm=Java VM %d
|
||||
about.license=This program comes with ABSOLUTELY NO WARRANTY.\
|
||||
This is free software, and you are welcome to redistribute it under certain conditions.
|
||||
menu.account=Account
|
||||
menu.account.manage=Manage
|
||||
menu.account.new=New
|
||||
menu.file=File
|
||||
menu.file.exit=Exit
|
||||
menu.file.new=New Instance
|
||||
menu.file.redownload=Re-download resources
|
||||
menu.help=Help
|
||||
menu.help.about=About
|
||||
menu.help.log=Log
|
||||
menu.launch=Launch
|
||||
about.unsupported-build=This is a custom build. No support will be provided.
|
||||
about.jvm.unsupported=Java VM %d (unsupported)
|
||||
ok=Ok
|
||||
cancel=Cancel
|
|
@ -0,0 +1,19 @@
|
|||
account.none=Keiner
|
||||
about.contact=Kontakt
|
||||
about.license=Dieses Programm komt OHNE JEGLICHE GARANTIE.\
|
||||
Dies ist freie Software, die Sie unter bestimmten Bedingungen gerne weitergeben dürfen.
|
||||
menu.account=Konto
|
||||
menu.account.manage=Verwalten
|
||||
menu.account.new=Neu
|
||||
menu.file=Datei
|
||||
menu.file.exit=Beenden
|
||||
menu.file.new=Neue Instanz
|
||||
menu.file.redownload=Ressourcen neu herunterladen
|
||||
menu.help=Hilfe
|
||||
menu.help.about=Über
|
||||
menu.help.log=Protokoll
|
||||
menu.launch=Starte
|
||||
about.unsupported-build=Dies ist ein selbst-erstelltes Build. Es wird kein Support geleistet.
|
||||
about.jvm.unsupported=Java VM %d (nicht unterstützt)
|
||||
ok=OK
|
||||
cancel=Abbrechen
|
|
@ -2,6 +2,10 @@ plugins {
|
|||
id("inceptum.application-conventions")
|
||||
}
|
||||
|
||||
application {
|
||||
mainClass.set("io.gitlab.jfronny.inceptum.imgui.GuiMain")
|
||||
}
|
||||
|
||||
dependencies {
|
||||
val flavor: String by rootProject.extra
|
||||
val lwjglVersion: String by rootProject.extra
|
||||
|
|
|
@ -48,13 +48,13 @@ public class GuiMain {
|
|||
Utils.LOGGER.info("Launching Inceptum v" + BuildMetadata.VERSION);
|
||||
Utils.LOGGER.info("Loading from " + MetaHolder.BASE_PATH);
|
||||
try {
|
||||
main();
|
||||
showGui();
|
||||
} finally {
|
||||
LauncherEnv.terminate();
|
||||
}
|
||||
}
|
||||
|
||||
public static void main() {
|
||||
public static void showGui() {
|
||||
Logger.registerFactory(name -> new CompoundLogger(name, InceptumEnvironmentInitializer.defaultFactory(name), MEMLOG));
|
||||
UpdateMetadata update = BuildMetadata.IS_PUBLIC ? Updater.getUpdate() : null;
|
||||
AccountManager.loadAccounts();
|
||||
|
|
|
@ -8,6 +8,7 @@ import io.gitlab.jfronny.inceptum.imgui.GuiMain;
|
|||
import io.gitlab.jfronny.inceptum.imgui.window.edit.InstanceEditWindow;
|
||||
import io.gitlab.jfronny.inceptum.launcher.model.inceptum.InstanceMeta;
|
||||
import io.gitlab.jfronny.inceptum.launcher.system.launch.InstanceLauncher;
|
||||
import io.gitlab.jfronny.inceptum.launcher.util.InstanceList;
|
||||
import io.gitlab.jfronny.inceptum.launcher.util.InstanceLock;
|
||||
import io.gitlab.jfronny.inceptum.launcher.system.install.Steps;
|
||||
|
||||
|
@ -17,53 +18,42 @@ import java.nio.file.Path;
|
|||
import java.util.*;
|
||||
|
||||
public class InstanceView {
|
||||
private static final Map<Path, FileBackedRef<InstanceMeta>> metas = new HashMap<>();
|
||||
|
||||
public static void draw(List<Path> paths) {
|
||||
public static void draw() throws IOException {
|
||||
List<InstanceList.Entry> entries = new ArrayList<>();
|
||||
InstanceList.forEach(entries::add);
|
||||
if (entries.isEmpty()) {
|
||||
ImGui.text("You have not yet created an instance");
|
||||
ImGui.text("Use File->New Instance to do so");
|
||||
}
|
||||
if (ImGui.beginTable("Instances", 2, ImGuiTableFlags.SizingFixedFit | ImGuiTableFlags.Borders)) {
|
||||
for (Path path : paths) {
|
||||
if (InstanceLock.isSetupLocked(path)) {
|
||||
for (InstanceList.Entry entry : entries) {
|
||||
if (InstanceLock.isSetupLocked(entry.path())) {
|
||||
ImGui.tableNextColumn();
|
||||
ImGui.text("Setting up");
|
||||
ImGui.tableNextColumn();
|
||||
ImGui.text("This instance is currently being set up");
|
||||
continue;
|
||||
}
|
||||
if (!Files.exists(path.resolve("instance.json"))) {
|
||||
Utils.LOGGER.error("Invalid instance (doesn't contain instance.json): " + path);
|
||||
continue;
|
||||
}
|
||||
InstanceMeta instance;
|
||||
try {
|
||||
if (!metas.containsKey(path))
|
||||
metas.put(path, new FileBackedRef<>(path.resolve("instance.json"), InstanceMeta.class));
|
||||
instance = metas.get(path).get();
|
||||
} catch (IOException e) {
|
||||
Utils.LOGGER.error("Could not load instance.json", e);
|
||||
if (!Files.exists(entry.path().resolve("instance.json"))) {
|
||||
Utils.LOGGER.error("Invalid instance (doesn't contain instance.json): " + entry);
|
||||
continue;
|
||||
}
|
||||
ImGui.tableNextColumn();
|
||||
boolean runDisabled = false;
|
||||
try {
|
||||
if (InstanceLock.isRunningLocked(path))
|
||||
runDisabled = true;
|
||||
} catch (IOException e) {
|
||||
continue;
|
||||
}
|
||||
boolean runDisabled = InstanceLock.isRunningLocked(entry.path());
|
||||
if (runDisabled) ImGui.beginDisabled();
|
||||
if (ImGui.button(path.getFileName().toString())) {
|
||||
if (ImGui.button(entry.toString())) {
|
||||
try {
|
||||
Steps.reDownload(path, Steps.createProcessState());
|
||||
Steps.reDownload(entry.path(), Steps.createProcessState());
|
||||
} catch (IOException e) {
|
||||
Utils.LOGGER.error("Could not redownload instance, trying to start anyways", e);
|
||||
}
|
||||
InstanceLauncher.launchClient(path, instance);
|
||||
InstanceLauncher.launchClient(entry.path(), entry.meta());
|
||||
}
|
||||
if (runDisabled) ImGui.endDisabled();
|
||||
ImGui.tableNextColumn();
|
||||
if (ImGui.button("Edit##" + path)) {
|
||||
if (ImGui.button("Edit##" + entry.path())) {
|
||||
try {
|
||||
GuiMain.open(new InstanceEditWindow(path, instance));
|
||||
GuiMain.open(new InstanceEditWindow(entry.path(), entry.meta()));
|
||||
} catch (IOException e) {
|
||||
Utils.LOGGER.error("Could not open instance edit window", e);
|
||||
}
|
||||
|
|
|
@ -30,12 +30,6 @@ public class AboutWindow extends Window {
|
|||
try {
|
||||
ImGui.text("For support, you can head to my ");
|
||||
ImGui.sameLine();
|
||||
if (ImGui.button("Discord")) {
|
||||
Utils.openWebBrowser(new URI("https://discord.gg/UjhHBqt"));
|
||||
}
|
||||
ImGui.sameLine();
|
||||
ImGui.text("guild or");
|
||||
ImGui.sameLine();
|
||||
if (ImGui.button("Matrix")) {
|
||||
Utils.openWebBrowser(new URI("https://matrix.to/#/%23jfronny%3Amatrix.org"));
|
||||
}
|
||||
|
|
|
@ -88,17 +88,10 @@ public class MainWindow extends Window {
|
|||
ImGui.showDemoWindow();
|
||||
}
|
||||
|
||||
List<Path> paths;
|
||||
try {
|
||||
if (!Files.exists(MetaHolder.INSTANCE_DIR)) Files.createDirectories(MetaHolder.INSTANCE_DIR);
|
||||
paths = JFiles.list(MetaHolder.INSTANCE_DIR, Files::isDirectory);
|
||||
InstanceView.draw();
|
||||
} catch (IOException e) {
|
||||
Utils.LOGGER.error("Could not list instances");
|
||||
return;
|
||||
Utils.LOGGER.error("Could not show instance list view");
|
||||
}
|
||||
if (paths.isEmpty()) {
|
||||
ImGui.text("You have not yet created an instance");
|
||||
ImGui.text("Use File->New Instance to do so");
|
||||
} else InstanceView.draw(paths);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -44,13 +44,8 @@ public class InstanceEditWindow extends Window {
|
|||
ImGui.text("This instance is still being set up.");
|
||||
return;
|
||||
}
|
||||
try {
|
||||
if (InstanceLock.isRunningLocked(path)) {
|
||||
ImGui.text("This instance is running. Edits in this state will result in breakage.");
|
||||
}
|
||||
} catch (IOException e) {
|
||||
ImGui.text("Could not read lock state on this instance");
|
||||
Utils.LOGGER.error("Could not read lock state", e);
|
||||
if (InstanceLock.isRunningLocked(path)) {
|
||||
ImGui.text("This instance is running. Edits in this state will result in breakage.");
|
||||
}
|
||||
lastTabWasMods = false;
|
||||
if (ImGui.beginTabBar("InstanceEdit" + path)) {
|
||||
|
|
|
@ -12,6 +12,7 @@ public class InstanceMeta {
|
|||
public String java;
|
||||
public Long minMem;
|
||||
public Long maxMem;
|
||||
public Long lastLaunched;
|
||||
public Arguments arguments;
|
||||
|
||||
public boolean isFabric() {
|
||||
|
|
|
@ -142,6 +142,13 @@ public class InstanceLauncher {
|
|||
}
|
||||
}
|
||||
}
|
||||
// Write launch time
|
||||
instance.lastLaunched = System.currentTimeMillis() / 1000L;
|
||||
try {
|
||||
JFiles.writeObject(instancePath.resolve("instance.json"), instance);
|
||||
} catch (IOException e) {
|
||||
Utils.LOGGER.error("Could not write instance config", e);
|
||||
}
|
||||
// Log command used to start
|
||||
Utils.LOGGER.info(String.join(" ", args));
|
||||
// Create process
|
||||
|
|
|
@ -0,0 +1,94 @@
|
|||
package io.gitlab.jfronny.inceptum.launcher.util;
|
||||
|
||||
import io.gitlab.jfronny.commons.cache.FileBackedRef;
|
||||
import io.gitlab.jfronny.commons.io.JFiles;
|
||||
import io.gitlab.jfronny.inceptum.common.MetaHolder;
|
||||
import io.gitlab.jfronny.inceptum.launcher.model.inceptum.InstanceMeta;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.*;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
public class InstanceList {
|
||||
private static final Map<Path, IEntry> metas = new LinkedHashMap<>();
|
||||
|
||||
public static void forEach(Consumer<Entry> target) throws IOException {
|
||||
Objects.requireNonNull(target);
|
||||
if (!Files.exists(MetaHolder.INSTANCE_DIR)) Files.createDirectories(MetaHolder.INSTANCE_DIR);
|
||||
JFiles.listTo(MetaHolder.INSTANCE_DIR, path -> {
|
||||
if (!Files.isDirectory(path)) return;
|
||||
target.accept(read(path));
|
||||
});
|
||||
}
|
||||
|
||||
public static void forEachLaunchable(Consumer<Entry> target) throws IOException {
|
||||
forEach(entry -> {
|
||||
if (!InstanceLock.isLocked(entry.path)) target.accept(entry);
|
||||
});
|
||||
}
|
||||
|
||||
public static Entry read(Path instancePath) throws IOException {
|
||||
Objects.requireNonNull(instancePath);
|
||||
synchronized (metas) {
|
||||
if (!metas.containsKey(instancePath)) {
|
||||
metas.put(instancePath, new IEntry(
|
||||
toId(instancePath.getFileName().toString()),
|
||||
instancePath,
|
||||
new FileBackedRef<>(instancePath.resolve("instance.json"), InstanceMeta.class)
|
||||
));
|
||||
}
|
||||
return metas.get(instancePath).toPub();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts any string into a set of lowercase ascii chars
|
||||
* @param input string to convert
|
||||
* @return a string matching [a-p]*
|
||||
*/
|
||||
private static String toId(String input) {
|
||||
StringBuilder result = new StringBuilder();
|
||||
for (byte b : input.getBytes(StandardCharsets.UTF_8)) {
|
||||
int by = Byte.toUnsignedInt(b);
|
||||
int ch2 = by & 15; // right bits
|
||||
int ch1 = (by - ch2) / 16; // left bits
|
||||
result.append((char)('a' + ch1));
|
||||
result.append((char)('a' + ch2));
|
||||
}
|
||||
return result.toString();
|
||||
}
|
||||
|
||||
private record IEntry(String id, Path path, FileBackedRef<InstanceMeta> meta) {
|
||||
@Override
|
||||
public String toString() {
|
||||
return path.getFileName().toString();
|
||||
}
|
||||
|
||||
public Entry toPub() throws IOException {
|
||||
return new Entry(id, path, meta.get());
|
||||
}
|
||||
}
|
||||
|
||||
public record Entry(String id, Path path, InstanceMeta meta) implements Comparable<Entry> {
|
||||
@Override
|
||||
public int compareTo(@NotNull InstanceList.Entry entry) {
|
||||
long time1 = meta.lastLaunched == null ? 0 : meta.lastLaunched;
|
||||
long time2 = entry.meta.lastLaunched == null ? 0 : entry.meta.lastLaunched;
|
||||
if (time1 == 0) {
|
||||
if (time2 == 0) return path.getFileName().toString().compareTo(entry.path.getFileName().toString());
|
||||
return -1;
|
||||
}
|
||||
if (time2 == 0) return 1;
|
||||
return Long.compare(time1, time2);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return path.getFileName().toString();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,5 +1,7 @@
|
|||
package io.gitlab.jfronny.inceptum.launcher.util;
|
||||
|
||||
import io.gitlab.jfronny.inceptum.common.Utils;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
|
@ -24,15 +26,19 @@ public class InstanceLock {
|
|||
Files.writeString(instancePath.resolve(INCEPTUM_LOCK), Long.toString(pid));
|
||||
}
|
||||
|
||||
public static boolean isRunningLocked(Path instancePath) throws IOException {
|
||||
public static boolean isRunningLocked(Path instancePath) {
|
||||
if (!Files.exists(instancePath.resolve(INCEPTUM_LOCK))) return false;
|
||||
if (ProcessUtils.isProcessAlive(Files.readString(instancePath.resolve(INCEPTUM_LOCK))))
|
||||
return true;
|
||||
Files.delete(instancePath.resolve(INCEPTUM_LOCK));
|
||||
try {
|
||||
if (ProcessUtils.isProcessAlive(Files.readString(instancePath.resolve(INCEPTUM_LOCK))))
|
||||
return true;
|
||||
Files.delete(instancePath.resolve(INCEPTUM_LOCK));
|
||||
} catch (IOException e) {
|
||||
Utils.LOGGER.error("Could not read running lock of " + instancePath.getFileName().toString(), e);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public static boolean isLocked(Path instancePath) throws IOException {
|
||||
public static boolean isLocked(Path instancePath) {
|
||||
return isSetupLocked(instancePath) || isRunningLocked(instancePath);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,3 +7,4 @@ include("launcher-cli")
|
|||
include("launcher-imgui")
|
||||
include("launcher-dist")
|
||||
include("launchwrapper")
|
||||
include("launcher-gtk")
|
||||
|
|
Loading…
Reference in New Issue