GTK: More work on settings screens
ci/woodpecker/push/docs Pipeline was successful Details
ci/woodpecker/push/woodpecker Pipeline was successful Details

This commit is contained in:
Johannes Frohnmeyer 2023-01-28 19:25:52 +01:00
parent 18c4953b36
commit cd3c8a1852
Signed by: Johannes
GPG Key ID: E76429612C2929F4
15 changed files with 542 additions and 292 deletions

View File

@ -13,7 +13,7 @@ import java.nio.file.Path;
public class InceptumConfig { public class InceptumConfig {
@GComment("Whether to show snapshots in the version selector for new instances") @GComment("Whether to show snapshots in the version selector for new instances")
public static boolean snapshots = false; 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; public static boolean darkTheme = false;
@GComment("Whether the GTK UI should default to a list view instead of a grid") @GComment("Whether the GTK UI should default to a list view instead of a grid")
public static boolean listView = false; public static boolean listView = false;

View File

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

View File

@ -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<T> extends MenuButton {
private final T[] source;
private final List<Consumer<T>> onChange = new LinkedList<>();
private int selected;
public Dropdown(T[] source, int def, Function<T, String> 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<T> action) {
onChange.add(action);
}
private class LB implements ThrowingRunnable<RuntimeException> {
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<T> action : onChange) action.accept(source[i]);
Dropdown.this.label = n;
}
}
}
}

View File

@ -5,6 +5,9 @@ import org.gtk.gtk.*;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import org.jetbrains.annotations.PropertyKey; import org.jetbrains.annotations.PropertyKey;
import java.util.function.Consumer;
import java.util.function.IntConsumer;
public class IRow extends Box { public class IRow extends Box {
public IRow(@PropertyKey(resourceBundle = I18n.BUNDLE) String title, @PropertyKey(resourceBundle = I18n.BUNDLE) @Nullable String subtitle, Object... args) { public IRow(@PropertyKey(resourceBundle = I18n.BUNDLE) String title, @PropertyKey(resourceBundle = I18n.BUNDLE) @Nullable String subtitle, Object... args) {
super(Orientation.HORIZONTAL, 40); super(Orientation.HORIZONTAL, 40);
@ -35,4 +38,37 @@ public class IRow extends Box {
btn.onClicked(action); btn.onClicked(action);
append(btn); 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<Boolean> 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<String> 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;
}
} }

View File

@ -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<String> s) {
super.onChanged(() -> s.accept(text));
}
}

View File

@ -7,8 +7,8 @@ import org.gtk.gio.MenuItem;
import org.gtk.gio.*; import org.gtk.gio.*;
import org.gtk.glib.Variant; import org.gtk.glib.Variant;
import org.gtk.glib.VariantType; import org.gtk.glib.VariantType;
import org.gtk.gtk.*;
import org.gtk.gtk.Application; import org.gtk.gtk.Application;
import org.gtk.gtk.PopoverMenu;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import java.util.LinkedHashMap; import java.util.LinkedHashMap;
@ -44,6 +44,13 @@ public class MenuBuilder {
this(app, getRootMenu(app), ""); 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) { public MenuBuilder(PopoverMenu menu, String groupName) {
this(insertMap(menu, groupName), (Menu) menu.menuModel, "", groupName); this(insertMap(menu, groupName), (Menu) menu.menuModel, "", groupName);
} }

View File

@ -1,41 +1,18 @@
package io.gitlab.jfronny.inceptum.gtk.window; 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.util.I18n;
import io.gitlab.jfronny.inceptum.gtk.window.dialog.ProcessStateWatcherDialog; import io.gitlab.jfronny.inceptum.gtk.window.edit.*;
import io.gitlab.jfronny.inceptum.launcher.model.inceptum.InstanceMeta; import io.gitlab.jfronny.inceptum.launcher.system.instance.Instance;
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 org.gnome.adw.HeaderBar; import org.gnome.adw.HeaderBar;
import org.gnome.adw.*; import org.gnome.adw.*;
import org.gtk.gobject.BindingFlags; import org.gtk.gobject.BindingFlags;
import org.gtk.gtk.Application; import org.gtk.gtk.Application;
import org.gtk.gtk.MessageDialog;
import org.gtk.gtk.Window; import org.gtk.gtk.Window;
import org.gtk.gtk.*; 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 { public class InstanceSettingsWindow extends Window {
private final Instance instance;
public InstanceSettingsWindow(Application app, Instance instance) { public InstanceSettingsWindow(Application app, Instance instance) {
this.application = app; this.application = app;
this.instance = instance;
ViewStack stack = new ViewStack(); ViewStack stack = new ViewStack();
@ -59,245 +36,8 @@ public class InstanceSettingsWindow extends Window {
child = view; child = view;
// General settings 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");
var box = new Box(Orientation.VERTICAL, 8); stack.addTitledWithIcon(new ExportTab(instance, this), null, I18n.get("instance.settings.export"), "send-to-symbolic");
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
);
} }
} }

View File

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

View File

@ -43,16 +43,14 @@ public class MainWindow extends ApplicationWindow {
accountsButton.iconName = "avatar-default-symbolic"; accountsButton.iconName = "avatar-default-symbolic";
accountsButton.menuModel = GtkMenubar.accountsMenu.menu; accountsButton.menuModel = GtkMenubar.accountsMenu.menu;
listButton = new Button(); listButton = Button.newFromIconName("view-list-symbolic");
listButton.iconName = "view-list-symbolic";
listButton.onClicked(() -> { listButton.onClicked(() -> {
InceptumConfig.listView = true; InceptumConfig.listView = true;
InceptumConfig.saveConfig(); InceptumConfig.saveConfig();
generateWindowBody(); generateWindowBody();
}); });
gridButton = new Button(); gridButton = Button.newFromIconName("view-grid-symbolic");
gridButton.iconName = "view-grid-symbolic";
gridButton.onClicked(() -> { gridButton.onClicked(() -> {
InceptumConfig.listView = false; InceptumConfig.listView = false;
InceptumConfig.saveConfig(); InceptumConfig.saveConfig();
@ -63,7 +61,7 @@ public class MainWindow extends ApplicationWindow {
MenuBuilder uiMenu = new MenuBuilder(app, new Menu(), "hamburger"); 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("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); uiMenu.button("about", AboutWindow::createAndShow);
MenuButton menuButton = new MenuButton(); MenuButton menuButton = new MenuButton();
menuButton.iconName = "open-menu-symbolic"; menuButton.iconName = "open-menu-symbolic";

View File

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

View File

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

View File

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

View File

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

View File

@ -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.\ 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! Click the button below to begin!
auth.open-browser=Open in Browser auth.open-browser=Open in Browser
instance.settings.general.manage=Manage 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

View File

@ -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.\ 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!< Klicken Sie auf die Schaltfläche unten, um zu beginnen!<
auth.open-browser=Im Browser öffnen auth.open-browser=Im Browser öffnen
instance.settings.general.manage=Manage 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