diff --git a/common/src/main/java/io/gitlab/jfronny/inceptum/common/InceptumConfig.java b/common/src/main/java/io/gitlab/jfronny/inceptum/common/InceptumConfig.java index 3b5acc5..e46fa4b 100644 --- a/common/src/main/java/io/gitlab/jfronny/inceptum/common/InceptumConfig.java +++ b/common/src/main/java/io/gitlab/jfronny/inceptum/common/InceptumConfig.java @@ -13,7 +13,7 @@ import java.nio.file.Path; public class InceptumConfig { @GComment("Whether to show snapshots in the version selector for new instances") public static boolean snapshots = false; - @GComment("Whether to launch the GUI in dark mode\nConfigurable in Settings->Dark Theme") + @GComment("Whether to launch the ImGUI in dark mode\nConfigurable in Settings->Dark Theme") public static boolean darkTheme = false; @GComment("Whether the GTK UI should default to a list view instead of a grid") public static boolean listView = false; diff --git a/launcher-gtk/src/main/java/io/gitlab/jfronny/inceptum/gtk/GtkTest.java b/launcher-gtk/src/main/java/io/gitlab/jfronny/inceptum/gtk/GtkTest.java new file mode 100644 index 0000000..3e07f95 --- /dev/null +++ b/launcher-gtk/src/main/java/io/gitlab/jfronny/inceptum/gtk/GtkTest.java @@ -0,0 +1,69 @@ +package io.gitlab.jfronny.inceptum.gtk; + +import io.gitlab.jfronny.inceptum.gtk.control.Dropdown; +import io.gitlab.jfronny.inceptum.launcher.LauncherEnv; +import org.gtk.gio.ListStore; +import org.gtk.gobject.GObject; +import org.gtk.gtk.*; + +import java.io.IOException; +import java.util.function.Function; + +public class GtkTest extends ApplicationWindow { + private final String searchTextWidget; + private final String searchTextMethod; + private final ListStore modelWidget; + private final SortListModel sortModelWidget; + private final FilterListModel filterModelWidget; + private final CustomFilter filterWidget; + + public GtkTest(Application application) { + super(application); + this.searchTextWidget = ""; + this.searchTextMethod = ""; + + this.modelWidget = new ListStore(Widget.type); + this.sortModelWidget = new SortListModel(modelWidget, null); + this.filterModelWidget = new FilterListModel(sortModelWidget, null); + this.filterWidget = new CustomFilter(this::doFilterWidgetView, null); + this.filterModelWidget.setFilter(this.filterWidget); + +// modelWidget.append(); + } + + private boolean doFilterWidgetView(GObject item) { + var arg = filterModelWidget; + return false; + } + + public static void main(String[] args) throws IOException { + LauncherEnv.initialize(GtkEnvBackend.INSTANCE); + int statusCode = -1; + try { + statusCode = GtkMain.setupApplication(args, app -> { +// var wnd = new GtkTest(app); + var wnd = new ApplicationWindow(app); + var row = new Box(Orientation.VERTICAL, 0); + var btn = DropDown.newFromStrings(new String[]{"Ae", "Io", "U"}); + var innerBox = (Box) btn.lastChild.firstChild.firstChild; + var chkbx = CheckButton.newWithLabel("Joe Biden"); + chkbx.insertBefore(innerBox, innerBox.firstChild); + btn.enableSearch = true; + btn.selected = 1; + btn.onNotify("selected", pspec -> System.out.println(btn.selected)); + row.append(btn); + wnd.child = row; + wnd.show(); + GtkEnvBackend.INSTANCE.dialogParent = wnd; + wnd.onCloseRequest(() -> { + GtkEnvBackend.INSTANCE.dialogParent = null; + app.quit(); + return false; + }); + }); + } finally { + LauncherEnv.terminate(); + System.exit(statusCode); + } + } +} diff --git a/launcher-gtk/src/main/java/io/gitlab/jfronny/inceptum/gtk/control/Dropdown.java b/launcher-gtk/src/main/java/io/gitlab/jfronny/inceptum/gtk/control/Dropdown.java new file mode 100644 index 0000000..eca64ab --- /dev/null +++ b/launcher-gtk/src/main/java/io/gitlab/jfronny/inceptum/gtk/control/Dropdown.java @@ -0,0 +1,49 @@ +package io.gitlab.jfronny.inceptum.gtk.control; + +import io.gitlab.jfronny.commons.throwable.ThrowingRunnable; +import io.gitlab.jfronny.inceptum.gtk.menu.MenuBuilder; +import org.gtk.gtk.MenuButton; + +import java.util.LinkedList; +import java.util.List; +import java.util.function.*; + +public class Dropdown extends MenuButton { + private final T[] source; + private final List> onChange = new LinkedList<>(); + private int selected; + + public Dropdown(T[] source, int def, Function stringify) { + this.source = source; + MenuBuilder builder = MenuBuilder.create(this, "nil"); + this.selected = def; + for (int i = 0; i < source.length; i++) { + String n = stringify.apply(source[i]); + if (def == i) label = n; + builder.literalButton("" + i, n, new LB(i, n)); + } + } + + public void onChanged(Consumer action) { + onChange.add(action); + } + + private class LB implements ThrowingRunnable { + private final int i; + private final String n; + + public LB(int i, String n) { + this.i = i; + this.n = n; + } + + @Override + public void run() throws RuntimeException { + if (selected != i) { + selected = i; + for (Consumer action : onChange) action.accept(source[i]); + Dropdown.this.label = n; + } + } + } +} diff --git a/launcher-gtk/src/main/java/io/gitlab/jfronny/inceptum/gtk/control/IRow.java b/launcher-gtk/src/main/java/io/gitlab/jfronny/inceptum/gtk/control/IRow.java index ee806b5..f934547 100644 --- a/launcher-gtk/src/main/java/io/gitlab/jfronny/inceptum/gtk/control/IRow.java +++ b/launcher-gtk/src/main/java/io/gitlab/jfronny/inceptum/gtk/control/IRow.java @@ -5,6 +5,9 @@ import org.gtk.gtk.*; import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.PropertyKey; +import java.util.function.Consumer; +import java.util.function.IntConsumer; + public class IRow extends Box { public IRow(@PropertyKey(resourceBundle = I18n.BUNDLE) String title, @PropertyKey(resourceBundle = I18n.BUNDLE) @Nullable String subtitle, Object... args) { super(Orientation.HORIZONTAL, 40); @@ -35,4 +38,37 @@ public class IRow extends Box { btn.onClicked(action); append(btn); } + + public void setDropdown(String[] options, int defaultIndex, IntConsumer changed) { + firstChild.hexpand = true; + DropDown btn = DropDown.newFromStrings(options); + btn.valign = Align.CENTER; + btn.halign = Align.END; + btn.selected = defaultIndex; + btn.onNotify("selected", pspec -> { + changed.accept(btn.selected); + }); + append(btn); + } + + public void setCheckbox(@PropertyKey(resourceBundle = I18n.BUNDLE) String text, boolean value, Consumer changed) { + firstChild.hexpand = true; + CheckButton btn = CheckButton.newWithLabel(I18n.get(text)); + btn.valign = Align.CENTER; + btn.halign = Align.END; + btn.active = value; + btn.onToggled(() -> changed.accept(btn.active)); + append(btn); + } + + public Entry setEntry(String value, Consumer onChanged) { + Entry entry = new Entry(); + entry.text = value; + entry.hexpand = true; + entry.valign = Align.CENTER; + entry.halign = Align.FILL; + entry.onChanged(() -> onChanged.accept(entry.text)); + append(entry); + return entry; + } } diff --git a/launcher-gtk/src/main/java/io/gitlab/jfronny/inceptum/gtk/control/ISEntry.java b/launcher-gtk/src/main/java/io/gitlab/jfronny/inceptum/gtk/control/ISEntry.java deleted file mode 100644 index d40529c..0000000 --- a/launcher-gtk/src/main/java/io/gitlab/jfronny/inceptum/gtk/control/ISEntry.java +++ /dev/null @@ -1,18 +0,0 @@ -package io.gitlab.jfronny.inceptum.gtk.control; - -import org.gtk.gtk.*; - -import java.util.function.Consumer; - -public class ISEntry extends Entry { - public ISEntry(String content) { - this.text = content; - hexpand = true; - halign = Align.FILL; - valign = Align.CENTER; - } - - public void onChanged(Consumer s) { - super.onChanged(() -> s.accept(text)); - } -} diff --git a/launcher-gtk/src/main/java/io/gitlab/jfronny/inceptum/gtk/menu/MenuBuilder.java b/launcher-gtk/src/main/java/io/gitlab/jfronny/inceptum/gtk/menu/MenuBuilder.java index 6a5f59a..9f1c580 100644 --- a/launcher-gtk/src/main/java/io/gitlab/jfronny/inceptum/gtk/menu/MenuBuilder.java +++ b/launcher-gtk/src/main/java/io/gitlab/jfronny/inceptum/gtk/menu/MenuBuilder.java @@ -7,8 +7,8 @@ import org.gtk.gio.MenuItem; import org.gtk.gio.*; import org.gtk.glib.Variant; import org.gtk.glib.VariantType; +import org.gtk.gtk.*; import org.gtk.gtk.Application; -import org.gtk.gtk.PopoverMenu; import org.jetbrains.annotations.Nullable; import java.util.LinkedHashMap; @@ -44,6 +44,13 @@ public class MenuBuilder { this(app, getRootMenu(app), ""); } + public static MenuBuilder create(MenuButton target, String groupName) { + Menu menu = new Menu(); + PopoverMenu pm = PopoverMenu.newFromModel(menu); + target.popover = pm; + return new MenuBuilder(pm, groupName); + } + public MenuBuilder(PopoverMenu menu, String groupName) { this(insertMap(menu, groupName), (Menu) menu.menuModel, "", groupName); } diff --git a/launcher-gtk/src/main/java/io/gitlab/jfronny/inceptum/gtk/window/InstanceSettingsWindow.java b/launcher-gtk/src/main/java/io/gitlab/jfronny/inceptum/gtk/window/InstanceSettingsWindow.java index 003f131..ebe5f1c 100644 --- a/launcher-gtk/src/main/java/io/gitlab/jfronny/inceptum/gtk/window/InstanceSettingsWindow.java +++ b/launcher-gtk/src/main/java/io/gitlab/jfronny/inceptum/gtk/window/InstanceSettingsWindow.java @@ -1,41 +1,18 @@ package io.gitlab.jfronny.inceptum.gtk.window; -import io.gitlab.jfronny.commons.ArgumentsTokenizer; -import io.gitlab.jfronny.commons.StringFormatter; -import io.gitlab.jfronny.commons.io.JFiles; -import io.gitlab.jfronny.commons.ref.R; -import io.gitlab.jfronny.inceptum.common.MetaHolder; -import io.gitlab.jfronny.inceptum.common.Utils; -import io.gitlab.jfronny.inceptum.gtk.GtkEnvBackend; -import io.gitlab.jfronny.inceptum.gtk.GtkMain; -import io.gitlab.jfronny.inceptum.gtk.control.*; import io.gitlab.jfronny.inceptum.gtk.util.I18n; -import io.gitlab.jfronny.inceptum.gtk.window.dialog.ProcessStateWatcherDialog; -import io.gitlab.jfronny.inceptum.launcher.model.inceptum.InstanceMeta; -import io.gitlab.jfronny.inceptum.launcher.system.exporter.Exporter; -import io.gitlab.jfronny.inceptum.launcher.system.exporter.Exporters; -import io.gitlab.jfronny.inceptum.launcher.system.instance.*; -import io.gitlab.jfronny.inceptum.launcher.util.ProcessState; -import manifold.ext.props.rt.api.var; +import io.gitlab.jfronny.inceptum.gtk.window.edit.*; +import io.gitlab.jfronny.inceptum.launcher.system.instance.Instance; import org.gnome.adw.HeaderBar; import org.gnome.adw.*; import org.gtk.gobject.BindingFlags; import org.gtk.gtk.Application; -import org.gtk.gtk.MessageDialog; import org.gtk.gtk.Window; import org.gtk.gtk.*; -import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Path; -import java.util.List; - public class InstanceSettingsWindow extends Window { - private final Instance instance; - public InstanceSettingsWindow(Application app, Instance instance) { this.application = app; - this.instance = instance; ViewStack stack = new ViewStack(); @@ -59,245 +36,8 @@ public class InstanceSettingsWindow extends Window { child = view; - // General settings - { - var box = new Box(Orientation.VERTICAL, 8); - box.marginHorizontal = 24; - box.marginTop = 12; - { - Frame frame = new Frame(null); - ListBox lb = new ListBox(); - lb.selectionMode = SelectionMode.NONE; - frame.child = lb; - box.append(frame); - { - var row = new IRow("instance.settings.general.name", "instance.settings.general.name.placeholder"); - ISEntry entry = new ISEntry(instance.name); - entry.maxLength = 64; - entry.placeholderText = I18n.get("instance.settings.general.name.placeholder"); - row.append(entry); - Button apply = Button.newWithLabel(I18n.get("instance.settings.apply")); - apply.valign = Align.CENTER; - apply.onClicked(() -> { - try { - Path newPath = MetaHolder.INSTANCE_DIR.resolve(InstanceNameTool.getNextValid(entry.text)); - Files.move(instance.path, newPath); - close(); - new InstanceSettingsWindow(application, InstanceList.read(newPath)).show(); - } catch (IOException e) { - showError("Could not rename", e); - } - }); - entry.onChanged(s -> { - apply.sensitive = !s.equals(instance.name); - }); - apply.sensitive = false; - row.append(apply); - lb.append(row); - } - } - //TODO Version (dropdown) + checkbox: "show snapshots" - //TODO Fabric support (checkbox) + dropdown: Loader version - //TODO Custom Java (checkbox) + String: path - { - box.append(new ILabel("instance.settings.general.args", ILabel.Mode.HEADING)); - Frame frame = new Frame(null); - ListBox lb = new ListBox(); - lb.selectionMode = SelectionMode.NONE; - frame.child = lb; - box.append(frame); - if (instance.meta.arguments == null) instance.meta.arguments = new InstanceMeta.Arguments(List.of(), List.of(), List.of()); - if (instance.meta.arguments.jvm == null) instance.meta.arguments = instance.meta.arguments.withJvm(List.of()); - if (instance.meta.arguments.client == null) instance.meta.arguments = instance.meta.arguments.withClient(List.of()); - if (instance.meta.arguments.server == null) instance.meta.arguments = instance.meta.arguments.withServer(List.of()); - { - var row = new IRow("instance.settings.general.args.jvm", "instance.settings.general.args.jvm.subtitle"); - ISEntry entry = new ISEntry(ArgumentsTokenizer.join(instance.meta.arguments.jvm.toArray(String[]::new))); - entry.onChanged(s -> { - instance.meta.arguments = instance.meta.arguments.withJvm(List.of(ArgumentsTokenizer.tokenize(s))); - instance.writeMeta(); - }); - row.append(entry); - lb.append(row); - } - { - var row = new IRow("instance.settings.general.args.client", "instance.settings.general.args.client.subtitle"); - ISEntry entry = new ISEntry(ArgumentsTokenizer.join(instance.meta.arguments.client.toArray(String[]::new))); - entry.onChanged(s -> { - instance.meta.arguments = instance.meta.arguments.withClient(List.of(ArgumentsTokenizer.tokenize(s))); - instance.writeMeta(); - }); - row.append(entry); - lb.append(row); - } - { - var row = new IRow("instance.settings.general.args.server", "instance.settings.general.args.server.subtitle"); - ISEntry entry = new ISEntry(ArgumentsTokenizer.join(instance.meta.arguments.server.toArray(String[]::new))); - entry.onChanged(s -> { - instance.meta.arguments = instance.meta.arguments.withServer(List.of(ArgumentsTokenizer.tokenize(s))); - instance.writeMeta(); - }); - row.append(entry); - lb.append(row); - } - } - { - box.append(new ILabel("instance.settings.general.manage", ILabel.Mode.HEADING)); - Frame frame = new Frame(null); - ListBox lb = new ListBox(); - lb.selectionMode = SelectionMode.NONE; - frame.child = lb; - box.append(frame); - { - var row = new IRow("instance.delete", "instance.delete.subtitle"); - row.setButton("instance.delete", () -> { - MessageDialog dialog = new MessageDialog(this, DialogFlags.MODAL.or(DialogFlags.DESTROY_WITH_PARENT), MessageType.WARNING, ButtonsType.OK_CANCEL, null); - dialog.markup = I18n.get("instance.delete.confirm"); - dialog.title = I18n.get("instance.delete.confirm.title"); - dialog.onResponse(responseId -> { - switch (ResponseType.of(responseId)) { - case OK -> { - try { - JFiles.deleteRecursive(instance.path); - dialog.close(); - close(); - } catch (IOException e) { - showError("Could not delete the instance", e); - } - dialog.close(); - } - case CLOSE, CANCEL -> dialog.close(); - case DELETE_EVENT -> dialog.destroy(); - } - }); - dialog.show(); - }); - lb.append(row); - } - { - var row = new IRow("instance.directory", "instance.directory.subtitle"); - row.setButton("instance.directory", () -> Utils.openFile(instance.path.toFile())); - lb.append(row); - } - } - stack.addTitledWithIcon(box, null, I18n.get("instance.settings.general"), "preferences-other-symbolic"); - } - - // Mods - { - var box = new Box(Orientation.VERTICAL, 0); - box.marginHorizontal = 24; - box.marginTop = 12; - box.append(new ILabel("instance.settings.mods.unsupported")); - //TODO implement this, somehow - stack.addTitledWithIcon(box, null, I18n.get("instance.settings.mods"), "package-x-generic-symbolic"); - } - - // Export - { - var box = new Box(Orientation.VERTICAL, 0); - box.marginHorizontal = 24; - box.marginTop = 12; - { - Frame frame = new Frame(null); - ListBox lb = new ListBox(); - lb.selectionMode = SelectionMode.NONE; - frame.child = lb; - box.append(frame); - { - var row = new IRow("instance.settings.export.version", "instance.settings.export.version.subtitle"); - ISEntry entry = new ISEntry(instance.meta.instanceVersion); - entry.onChanged(s -> { - instance.meta.instanceVersion = s; - instance.writeMeta(); - }); - row.append(entry); - lb.append(row); - } - for (Exporter exporter : Exporters.EXPORTERS) { - var row = new IRow("instance.settings.export.title", "instance.settings.export.subtitle", exporter.name, exporter.fileExtension); - row.setButton("instance.settings.export", () -> { - FileChooserNative dialog = new FileChooserNative( - I18n.get("instance.settings.export.dialog.title", exporter.name), - this, - FileChooserAction.SAVE, - "_" + I18n.get("save"), - "_" + I18n.get("cancel") - ); - dialog.currentName = exporter.getDefaultFileName(instance); - dialog.onResponse(responseId -> { - if (responseId == ResponseType.ACCEPT.value) { - var file = dialog.getFile().path; - if (file == null) { - GtkEnvBackend.simpleDialog( - this, - "The path returned by the file dialog is null", - "Could not export", - MessageType.ERROR, - ButtonsType.CLOSE, - null, - null - ); - return; - } - export(exporter, Path.of(file)); - } - }); - dialog.show(); - }); - lb.append(row); - } - } - stack.addTitledWithIcon(box, null, I18n.get("instance.settings.export"), "send-to-symbolic"); - } - } - - private void export(Exporter exporter, Path path) { - ProcessState state = new ProcessState(Exporters.STEP_COUNT, "Initializing..."); - ProcessStateWatcherDialog.show( - this, - I18n.get("instance.settings.export.dialog.title", exporter.name), - I18n.get("instance.settings.export.dialog.error", instance.name), - state, - cancel -> { - exporter.generate(state, instance, path); - GtkMain.schedule(() -> { - MessageDialog success = new MessageDialog( - this, - DialogFlags.MODAL.or(DialogFlags.DESTROY_WITH_PARENT), - MessageType.INFO, - ButtonsType.NONE, - I18n.get("instance.settings.export.dialog.success", instance.name, path.toString()) - ); - success.title = I18n.get("instance.settings.export.dialog.success.title"); - success.addButton(I18n.get("show"), ResponseType.OK.value); - success.addButton(I18n.get("ok"), ResponseType.CANCEL.value); - success.onResponse(responseId1 -> { - switch (ResponseType.of(responseId1)) { - case OK -> { - success.close(); - Utils.openFile(path.toFile()); - } - case CLOSE, CANCEL -> success.close(); - case DELETE_EVENT -> success.destroy(); - } - }); - success.show(); - }); - }, - R::nop - ); - } - - private void showError(String message, Throwable t) { - GtkEnvBackend.simpleDialog( - this, - StringFormatter.toString(t), - message, - MessageType.ERROR, - ButtonsType.CLOSE, - null, - null - ); + stack.addTitledWithIcon(new GeneralTab(instance, this), null, I18n.get("instance.settings.general"), "preferences-other-symbolic"); + stack.addTitledWithIcon(new ModsTab(instance, this), null, I18n.get("instance.settings.mods"), "package-x-generic-symbolic"); + stack.addTitledWithIcon(new ExportTab(instance, this), null, I18n.get("instance.settings.export"), "send-to-symbolic"); } } diff --git a/launcher-gtk/src/main/java/io/gitlab/jfronny/inceptum/gtk/window/LauncherSettingsWindow.java b/launcher-gtk/src/main/java/io/gitlab/jfronny/inceptum/gtk/window/LauncherSettingsWindow.java new file mode 100644 index 0000000..737f5bf --- /dev/null +++ b/launcher-gtk/src/main/java/io/gitlab/jfronny/inceptum/gtk/window/LauncherSettingsWindow.java @@ -0,0 +1,49 @@ +package io.gitlab.jfronny.inceptum.gtk.window; + +import io.gitlab.jfronny.inceptum.common.InceptumConfig; +import io.gitlab.jfronny.inceptum.common.model.inceptum.UpdateChannel; +import io.gitlab.jfronny.inceptum.gtk.control.IRow; +import org.gtk.gtk.*; + +public class LauncherSettingsWindow extends Window { + public LauncherSettingsWindow(Application app) { + this.application = app; + + var box = new Box(Orientation.VERTICAL, 8); + box.marginHorizontal = 24; + box.marginTop = 12; + this.child = box; + + { + Frame frame = new Frame(null); + ListBox listBox = new ListBox(); + listBox.selectionMode = SelectionMode.NONE; + frame.child = listBox; + box.append(frame); + { + IRow row = new IRow("settings.snapshots", "settings.snapshots.subtitle"); + listBox.append(row); + row.setCheckbox("settings.snapshots", InceptumConfig.snapshots, b -> { + InceptumConfig.snapshots = b; + InceptumConfig.saveConfig(); + }); + } + { + IRow row = new IRow("settings.update-channel", "settings.update-channel.subtitle"); + listBox.append(row); + row.setDropdown(new String[] {"Stable", "CI"}, InceptumConfig.channel == UpdateChannel.CI ? 1 : 0, state -> { + InceptumConfig.channel = state == 1 ? UpdateChannel.CI : UpdateChannel.Stable; + InceptumConfig.saveConfig(); + }); + } + { + IRow row = new IRow("settings.author-name", "settings.author-name.subtitle"); + listBox.append(row); + row.setEntry(InceptumConfig.authorName, s -> { + InceptumConfig.authorName = s; + InceptumConfig.saveConfig(); + }); + } + } + } +} diff --git a/launcher-gtk/src/main/java/io/gitlab/jfronny/inceptum/gtk/window/MainWindow.java b/launcher-gtk/src/main/java/io/gitlab/jfronny/inceptum/gtk/window/MainWindow.java index 4befb68..15883f8 100644 --- a/launcher-gtk/src/main/java/io/gitlab/jfronny/inceptum/gtk/window/MainWindow.java +++ b/launcher-gtk/src/main/java/io/gitlab/jfronny/inceptum/gtk/window/MainWindow.java @@ -43,16 +43,14 @@ public class MainWindow extends ApplicationWindow { accountsButton.iconName = "avatar-default-symbolic"; accountsButton.menuModel = GtkMenubar.accountsMenu.menu; - listButton = new Button(); - listButton.iconName = "view-list-symbolic"; + listButton = Button.newFromIconName("view-list-symbolic"); listButton.onClicked(() -> { InceptumConfig.listView = true; InceptumConfig.saveConfig(); generateWindowBody(); }); - gridButton = new Button(); - gridButton.iconName = "view-grid-symbolic"; + gridButton = Button.newFromIconName("view-grid-symbolic"); gridButton.onClicked(() -> { InceptumConfig.listView = false; InceptumConfig.saveConfig(); @@ -63,7 +61,7 @@ public class MainWindow extends ApplicationWindow { MenuBuilder uiMenu = new MenuBuilder(app, new Menu(), "hamburger"); uiMenu.button("support", () -> Utils.openWebBrowser(new URI("https://git.frohnmeyer-wds.de/JfMods/Inceptum/issues"))); - uiMenu.button("preferences", () -> {}); //TODO preferences UI inspired by boxes + uiMenu.button("preferences", () -> new LauncherSettingsWindow(app).show()); uiMenu.button("about", AboutWindow::createAndShow); MenuButton menuButton = new MenuButton(); menuButton.iconName = "open-menu-symbolic"; diff --git a/launcher-gtk/src/main/java/io/gitlab/jfronny/inceptum/gtk/window/edit/ExportTab.java b/launcher-gtk/src/main/java/io/gitlab/jfronny/inceptum/gtk/window/edit/ExportTab.java new file mode 100644 index 0000000..c21e8e7 --- /dev/null +++ b/launcher-gtk/src/main/java/io/gitlab/jfronny/inceptum/gtk/window/edit/ExportTab.java @@ -0,0 +1,100 @@ +package io.gitlab.jfronny.inceptum.gtk.window.edit; + +import io.gitlab.jfronny.commons.ref.R; +import io.gitlab.jfronny.inceptum.common.Utils; +import io.gitlab.jfronny.inceptum.gtk.GtkEnvBackend; +import io.gitlab.jfronny.inceptum.gtk.GtkMain; +import io.gitlab.jfronny.inceptum.gtk.util.I18n; +import io.gitlab.jfronny.inceptum.gtk.window.InstanceSettingsWindow; +import io.gitlab.jfronny.inceptum.gtk.window.dialog.ProcessStateWatcherDialog; +import io.gitlab.jfronny.inceptum.launcher.system.exporter.Exporter; +import io.gitlab.jfronny.inceptum.launcher.system.exporter.Exporters; +import io.gitlab.jfronny.inceptum.launcher.system.instance.Instance; +import io.gitlab.jfronny.inceptum.launcher.util.ProcessState; +import org.gtk.gtk.*; + +import java.nio.file.Path; + +public class ExportTab extends SettingsTab { + public ExportTab(Instance instance, InstanceSettingsWindow window) { + super(instance, window); + section(null, section -> { + { + var row = section.row("instance.settings.export.version", "instance.settings.export.version.subtitle"); + row.setEntry(instance.meta.instanceVersion, s -> { + instance.meta.instanceVersion = s; + instance.writeMeta(); + }); + } + for (Exporter exporter : Exporters.EXPORTERS) { + var row = section.row("instance.settings.export.title", "instance.settings.export.subtitle", exporter.name, exporter.fileExtension); + row.setButton("instance.settings.export", () -> { + FileChooserNative dialog = new FileChooserNative( + I18n.get("instance.settings.export.dialog.title", exporter.name), + window, + FileChooserAction.SAVE, + "_" + I18n.get("save"), + "_" + I18n.get("cancel") + ); + dialog.currentName = exporter.getDefaultFileName(instance); + dialog.onResponse(responseId -> { + if (responseId == ResponseType.ACCEPT.value) { + var file = dialog.getFile().path; + if (file == null) { + GtkEnvBackend.simpleDialog( + window, + "The path returned by the file dialog is null", + "Could not export", + MessageType.ERROR, + ButtonsType.CLOSE, + null, + null + ); + return; + } + export(exporter, Path.of(file)); + } + }); + dialog.show(); + }); + } + }); + } + + private void export(Exporter exporter, Path path) { + ProcessState state = new ProcessState(Exporters.STEP_COUNT, "Initializing..."); + ProcessStateWatcherDialog.show( + window, + I18n.get("instance.settings.export.dialog.title", exporter.name), + I18n.get("instance.settings.export.dialog.error", instance.name), + state, + cancel -> { + exporter.generate(state, instance, path); + GtkMain.schedule(() -> { + MessageDialog success = new MessageDialog( + window, + DialogFlags.MODAL.or(DialogFlags.DESTROY_WITH_PARENT), + MessageType.INFO, + ButtonsType.NONE, + I18n.get("instance.settings.export.dialog.success", instance.name, path.toString()) + ); + success.title = I18n.get("instance.settings.export.dialog.success.title"); + success.addButton(I18n.get("show"), ResponseType.OK.value); + success.addButton(I18n.get("ok"), ResponseType.CANCEL.value); + success.onResponse(responseId1 -> { + switch (ResponseType.of(responseId1)) { + case OK -> { + success.close(); + Utils.openFile(path.toFile()); + } + case CLOSE, CANCEL -> success.close(); + case DELETE_EVENT -> success.destroy(); + } + }); + success.show(); + }); + }, + R::nop + ); + } +} diff --git a/launcher-gtk/src/main/java/io/gitlab/jfronny/inceptum/gtk/window/edit/GeneralTab.java b/launcher-gtk/src/main/java/io/gitlab/jfronny/inceptum/gtk/window/edit/GeneralTab.java new file mode 100644 index 0000000..d090853 --- /dev/null +++ b/launcher-gtk/src/main/java/io/gitlab/jfronny/inceptum/gtk/window/edit/GeneralTab.java @@ -0,0 +1,125 @@ +package io.gitlab.jfronny.inceptum.gtk.window.edit; + +import io.gitlab.jfronny.commons.ArgumentsTokenizer; +import io.gitlab.jfronny.commons.io.JFiles; +import io.gitlab.jfronny.inceptum.common.*; +import io.gitlab.jfronny.inceptum.gtk.control.*; +import io.gitlab.jfronny.inceptum.gtk.util.I18n; +import io.gitlab.jfronny.inceptum.gtk.window.InstanceSettingsWindow; +import io.gitlab.jfronny.inceptum.launcher.api.McApi; +import io.gitlab.jfronny.inceptum.launcher.model.inceptum.InstanceMeta; +import io.gitlab.jfronny.inceptum.launcher.model.mojang.VersionsList; +import io.gitlab.jfronny.inceptum.launcher.system.instance.*; +import org.gtk.gtk.*; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Date; +import java.util.List; + +public class GeneralTab extends SettingsTab { + private static final VersionsList VERSIONS = McApi.getVersions(); + + public GeneralTab(Instance instance, InstanceSettingsWindow window) { + super(instance, window); + section(null, section -> { + var row = section.row("instance.settings.general.name", "instance.settings.general.name.placeholder"); + Button apply = Button.newWithLabel(I18n.get("instance.settings.apply")); + Entry entry = row.setEntry(instance.name, s -> apply.sensitive = !s.equals(instance.name)); + entry.placeholderText = I18n.get("instance.settings.general.name.placeholder"); + apply.valign = Align.CENTER; + apply.onClicked(() -> { + try { + Path newPath = MetaHolder.INSTANCE_DIR.resolve(InstanceNameTool.getNextValid(entry.text)); + Files.move(instance.path, newPath); + window.close(); + new InstanceSettingsWindow(window.application, InstanceList.read(newPath)).show(); + } catch (IOException e) { + showError("Could not rename", e); + } + }); + apply.sensitive = false; + row.append(apply); + }); + section("instance.settings.general.game", section -> { + var row = section.row("instance.settings.general.game.version", "instance.settings.general.game.version.subtitle"); + String[] versions = VERSIONS.versions.stream() + .filter(s -> InceptumConfig.snapshots || s.type.equals("release")) + .map(s -> s.id) + .toArray(String[]::new); + int def = 0; + for (int i = 0; i < versions.length; i++) if (versions[i].equals(VERSIONS.latest.release)) def = i; + row.setDropdown( + versions, + def, + i -> { + instance.meta.gameVersion = versions[i]; + instance.writeMeta(); + }); + //TODO Fabric support (checkbox) + dropdown: Loader version + //TODO Custom Java (checkbox) + String: path + //TODO minMem/maxMem (slider?) + }); + section("instance.settings.general.args", section -> { + if (instance.meta.arguments == null) instance.meta.arguments = new InstanceMeta.Arguments(List.of(), List.of(), List.of()); + if (instance.meta.arguments.jvm == null) instance.meta.arguments = instance.meta.arguments.withJvm(List.of()); + if (instance.meta.arguments.client == null) instance.meta.arguments = instance.meta.arguments.withClient(List.of()); + if (instance.meta.arguments.server == null) instance.meta.arguments = instance.meta.arguments.withServer(List.of()); + { + var row = section.row("instance.settings.general.args.jvm", "instance.settings.general.args.jvm.subtitle"); + row.setEntry(ArgumentsTokenizer.join(instance.meta.arguments.jvm.toArray(String[]::new)), s -> { + instance.meta.arguments = instance.meta.arguments.withJvm(List.of(ArgumentsTokenizer.tokenize(s))); + instance.writeMeta(); + }); + } + { + var row = section.row("instance.settings.general.args.client", "instance.settings.general.args.client.subtitle"); + row.setEntry(ArgumentsTokenizer.join(instance.meta.arguments.client.toArray(String[]::new)), s -> { + instance.meta.arguments = instance.meta.arguments.withClient(List.of(ArgumentsTokenizer.tokenize(s))); + instance.writeMeta(); + }); + } + { + var row = section.row("instance.settings.general.args.server", "instance.settings.general.args.server.subtitle"); + row.setEntry(ArgumentsTokenizer.join(instance.meta.arguments.server.toArray(String[]::new)), s -> { + instance.meta.arguments = instance.meta.arguments.withServer(List.of(ArgumentsTokenizer.tokenize(s))); + instance.writeMeta(); + }); + } + }); + section("instance.settings.general.manage", section -> { + { + var row = section.row("instance.delete", "instance.delete.subtitle"); + row.setButton("instance.delete", () -> { + MessageDialog dialog = new MessageDialog(window, DialogFlags.MODAL.or(DialogFlags.DESTROY_WITH_PARENT), MessageType.WARNING, ButtonsType.OK_CANCEL, null); + dialog.markup = I18n.get("instance.delete.confirm"); + dialog.title = I18n.get("instance.delete.confirm.title"); + dialog.onResponse(responseId -> { + switch (ResponseType.of(responseId)) { + case OK -> { + try { + JFiles.deleteRecursive(instance.path); + dialog.close(); + window.close(); + } catch (IOException e) { + showError("Could not delete the instance", e); + } + dialog.close(); + } + case CLOSE, CANCEL -> dialog.close(); + case DELETE_EVENT -> dialog.destroy(); + } + }); + dialog.show(); + }); + } + { + var row = section.row("instance.directory", "instance.directory.subtitle"); + row.setButton("instance.directory", () -> Utils.openFile(instance.path.toFile())); + } + }); + long timestamp = instance.meta.lastLaunched == null ? 0 : instance.meta.lastLaunched; + append(new ILabel("instance.settings.general.last-launched", ILabel.Mode.SUBTITLE, new Date(timestamp * 1000).toString())); + } +} diff --git a/launcher-gtk/src/main/java/io/gitlab/jfronny/inceptum/gtk/window/edit/ModsTab.java b/launcher-gtk/src/main/java/io/gitlab/jfronny/inceptum/gtk/window/edit/ModsTab.java new file mode 100644 index 0000000..05e1f16 --- /dev/null +++ b/launcher-gtk/src/main/java/io/gitlab/jfronny/inceptum/gtk/window/edit/ModsTab.java @@ -0,0 +1,13 @@ +package io.gitlab.jfronny.inceptum.gtk.window.edit; + +import io.gitlab.jfronny.inceptum.gtk.control.ILabel; +import io.gitlab.jfronny.inceptum.gtk.window.InstanceSettingsWindow; +import io.gitlab.jfronny.inceptum.launcher.system.instance.Instance; + +public class ModsTab extends SettingsTab { + public ModsTab(Instance instance, InstanceSettingsWindow window) { + super(instance, window); + append(new ILabel("instance.settings.mods.unsupported")); + //TODO implement this, somehow + } +} diff --git a/launcher-gtk/src/main/java/io/gitlab/jfronny/inceptum/gtk/window/edit/SettingsTab.java b/launcher-gtk/src/main/java/io/gitlab/jfronny/inceptum/gtk/window/edit/SettingsTab.java new file mode 100644 index 0000000..9d910da --- /dev/null +++ b/launcher-gtk/src/main/java/io/gitlab/jfronny/inceptum/gtk/window/edit/SettingsTab.java @@ -0,0 +1,62 @@ +package io.gitlab.jfronny.inceptum.gtk.window.edit; + +import io.gitlab.jfronny.commons.StringFormatter; +import io.gitlab.jfronny.inceptum.gtk.GtkEnvBackend; +import io.gitlab.jfronny.inceptum.gtk.control.ILabel; +import io.gitlab.jfronny.inceptum.gtk.control.IRow; +import io.gitlab.jfronny.inceptum.gtk.util.I18n; +import io.gitlab.jfronny.inceptum.gtk.window.InstanceSettingsWindow; +import io.gitlab.jfronny.inceptum.launcher.system.instance.Instance; +import org.gtk.gtk.*; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.annotations.PropertyKey; + +public class SettingsTab extends Box { + protected final Instance instance; + protected final InstanceSettingsWindow window; + + public SettingsTab(Instance instance, InstanceSettingsWindow window) { + super(Orientation.VERTICAL, 8); + this.instance = instance; + this.marginHorizontal = 24; + this.marginTop = 12; + this.window = window; + } + + protected void section(@Nullable @PropertyKey(resourceBundle = I18n.BUNDLE) String title, SectionBuilder builder) { + if (title != null) append(new ILabel(title, ILabel.Mode.HEADING)); + Frame frame = new Frame(null); + ListBox listBox = new ListBox(); + listBox.selectionMode = SelectionMode.NONE; + frame.child = listBox; + builder.build(new SectionBuilder.Section() { + @Override + public IRow row(String title, @Nullable String subtitle, Object... args) { + IRow row = new IRow(title, subtitle, args); + listBox.append(row); + return row; + } + }); + append(frame); + } + + protected interface SectionBuilder { + void build(Section section); + + interface Section { + IRow row(@PropertyKey(resourceBundle = I18n.BUNDLE) String title, @PropertyKey(resourceBundle = I18n.BUNDLE) @Nullable String subtitle, Object... args); + } + } + + protected void showError(String message, Throwable t) { + GtkEnvBackend.simpleDialog( + window, + StringFormatter.toString(t), + message, + MessageType.ERROR, + ButtonsType.CLOSE, + null, + null + ); + } +} diff --git a/launcher-gtk/src/main/resources/inceptum.properties b/launcher-gtk/src/main/resources/inceptum.properties index 7383d2a..a695471 100644 --- a/launcher-gtk/src/main/resources/inceptum.properties +++ b/launcher-gtk/src/main/resources/inceptum.properties @@ -73,4 +73,14 @@ auth.title=Microsoft Login auth.description=This feature uses modified ATLauncher code, so the login prompt will ask you to log in to ATLauncher.\ Click the button below to begin! auth.open-browser=Open in Browser -instance.settings.general.manage=Manage \ No newline at end of file +instance.settings.general.manage=Manage +instance.settings.general.last-launched=Last launched: %1$s +instance.settings.general.game=Game +instance.settings.general.game.version=Version +instance.settings.general.game.version.subtitle=Minecraft version of this instance +settings.author-name=Author Name +settings.snapshots=Snapshots +settings.update-channel=Update Channel +settings.snapshots.subtitle=Whether to show snapshots in the version selector for new instances +settings.update-channel.subtitle=The update channel. I personnaly recommend the CI channel as it gest the latest features and fixes more quickly, but it might be more unstable +settings.author-name.subtitle=The author name to add to packs where the metadata format requires specifying one \ No newline at end of file diff --git a/launcher-gtk/src/main/resources/inceptum_de.properties b/launcher-gtk/src/main/resources/inceptum_de.properties index fc83264..aee271b 100644 --- a/launcher-gtk/src/main/resources/inceptum_de.properties +++ b/launcher-gtk/src/main/resources/inceptum_de.properties @@ -73,4 +73,14 @@ auth.title=Microsoft-Anmeldung auth.description=Diese Funktion nutzt Code des ATLauncher, weshalb der Anmeldebildschirm nach einer Anmeldung für ATLauncher fragen wird.\ Klicken Sie auf die Schaltfläche unten, um zu beginnen!< auth.open-browser=Im Browser öffnen -instance.settings.general.manage=Manage \ No newline at end of file +instance.settings.general.manage=Manage +instance.settings.general.last-launched=Zuletzt gestartet: %1$s +instance.settings.general.game=Game +instance.settings.general.game.version=Version +instance.settings.general.game.version.subtitle=Minecraft-Version dieser Instanz +settings.author-name=Name des Authors +settings.snapshots=Vorschauversionen +settings.update-channel=Updatekanal +settings.snapshots.subtitle=Ob Vorschauversionen im Versions-Auswahlmenü gezeigt werden sollen +settings.update-channel.subtitle=Der Update-Kanal. Ich empfehle den etwas instabileren, aber häufiger aktualisierten CI-Kanal +settings.author-name.subtitle=Der Name, der bei Modpack-Exporten, deren Metadaten einen Autor angeben, aufgelistet werden soll \ No newline at end of file