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 {
@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;

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

View File

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

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.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";

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.\
Click the button below to begin!
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.\
Klicken Sie auf die Schaltfläche unten, um zu beginnen!<
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