[config-ui-tiny] reflow config screen entries on size change
ci/woodpecker/push/docs Pipeline was successful Details
ci/woodpecker/push/jfmod Pipeline failed Details
ci/woodpecker/tag/docs Pipeline was successful Details
ci/woodpecker/tag/jfmod Pipeline was successful Details

This commit is contained in:
Johannes Frohnmeyer 2023-03-18 14:58:44 +01:00
parent aafc14e3fc
commit 0821ce57c2
Signed by: Johannes
GPG Key ID: E76429612C2929F4
7 changed files with 144 additions and 100 deletions

View File

@ -1,12 +1,17 @@
package io.gitlab.jfronny.libjf.config.api.v1.ui.tiny; package io.gitlab.jfronny.libjf.config.api.v1.ui.tiny;
import io.gitlab.jfronny.libjf.config.impl.ui.tiny.TinyConfigScreen; import io.gitlab.jfronny.libjf.config.impl.ui.tiny.TinyConfigScreen;
import io.gitlab.jfronny.libjf.config.impl.ui.tiny.entry.Reflowable;
import net.minecraft.client.font.TextRenderer; import net.minecraft.client.font.TextRenderer;
import net.minecraft.client.gui.widget.ClickableWidget; import net.minecraft.client.gui.widget.ClickableWidget;
public interface WidgetFactory { public interface WidgetFactory {
Widget build(TinyConfigScreen screen, TextRenderer textRenderer); Widget build(TinyConfigScreen screen, TextRenderer textRenderer);
record Widget(Runnable updateControls, ClickableWidget control) { record Widget(Runnable updateControls, ClickableWidget control, Reflowable reflow) implements Reflowable {
@Override
public void reflow(int width, int height) {
reflow.reflow(width, height);
}
} }
} }

View File

@ -11,6 +11,7 @@ import io.gitlab.jfronny.libjf.config.impl.ui.tiny.presets.PresetsScreen;
import net.fabricmc.api.EnvType; import net.fabricmc.api.EnvType;
import net.fabricmc.api.Environment; import net.fabricmc.api.Environment;
import net.minecraft.client.MinecraftClient; import net.minecraft.client.MinecraftClient;
import net.minecraft.client.gui.ScreenRect;
import net.minecraft.client.gui.screen.Screen; import net.minecraft.client.gui.screen.Screen;
import net.minecraft.client.gui.tab.TabManager; import net.minecraft.client.gui.tab.TabManager;
import net.minecraft.client.gui.widget.ButtonWidget; import net.minecraft.client.gui.widget.ButtonWidget;
@ -63,18 +64,17 @@ public class TinyConfigScreen extends Screen {
protected void init() { protected void init() {
super.init(); super.init();
this.done = ButtonWidget.builder(ScreenTexts.DONE, button -> {
for (WidgetState<?> state : widgets) {
Try.orElse(state::writeToEntry, e -> LibJf.LOGGER.error("Could not write config data to class", e));
}
config.getRoot().write();
Objects.requireNonNull(client).setScreen(parent);
})
.position(this.width / 2 + 4, this.height - 28)
.size(150, 20)
.build();
if (!reload) { if (!reload) {
this.done = ButtonWidget.builder(ScreenTexts.DONE, button -> {
for (WidgetState<?> state : widgets) {
Try.orElse(state::writeToEntry, e -> LibJf.LOGGER.error("Could not write config data to class", e));
}
config.getRoot().write();
Objects.requireNonNull(client).setScreen(parent);
})
.dimensions(this.width / 2 + 4, this.height - 28, 150, 20)
.build();
if (config.getEntries().isEmpty() if (config.getEntries().isEmpty()
&& config.getPresets().keySet().stream().allMatch(s -> s.equals(CategoryBuilder.CONFIG_PRESET_DEFAULT)) && config.getPresets().keySet().stream().allMatch(s -> s.equals(CategoryBuilder.CONFIG_PRESET_DEFAULT))
&& config.getReferencedConfigs().isEmpty() && config.getReferencedConfigs().isEmpty()
@ -88,7 +88,7 @@ public class TinyConfigScreen extends Screen {
this.tabs = List.of(); this.tabs = List.of();
} }
if (this.tabs.isEmpty()) this.tabs = List.of(new TinyConfigTab(this, config, textRenderer, true)); if (this.tabs.isEmpty()) this.tabs = List.of(new TinyConfigTab(this, config, textRenderer, true));
} } else done.setPosition(width / 2 + 4, height - 28);
TabNavigationWidget tabNavigation = TabNavigationWidget.builder(tabManager, this.width) TabNavigationWidget tabNavigation = TabNavigationWidget.builder(tabManager, this.width)
.tabs(tabs.toArray(TinyConfigTab[]::new)) .tabs(tabs.toArray(TinyConfigTab[]::new))
@ -98,23 +98,24 @@ public class TinyConfigScreen extends Screen {
tabNavigation.selectTab(0, false); tabNavigation.selectTab(0, false);
tabNavigation.init(); tabNavigation.init();
this.addDrawableChild(ButtonWidget.builder(ScreenTexts.CANCEL,
button -> Objects.requireNonNull(client).setScreen(parent))
.dimensions(this.width / 2 - 154, this.height - 28, 150, 20)
.build());
this.addDrawableChild(done);
if (tabs.size() == 1 && !config.getPresets().isEmpty()) { if (tabs.size() == 1 && !config.getPresets().isEmpty()) {
this.addDrawableChild(ButtonWidget.builder(Text.translatable("libjf-config-v1.presets"), this.addDrawableChild(ButtonWidget.builder(Text.translatable("libjf-config-v1.presets"),
button -> Objects.requireNonNull(client).setScreen(new PresetsScreen(this, config))) button -> Objects.requireNonNull(client).setScreen(new PresetsScreen(this, config)))
.position(4, 6) .dimensions(4, 6, 80, 20)
.size(80, 20)
.build()); .build());
} }
this.addDrawableChild(ButtonWidget.builder(ScreenTexts.CANCEL,
button -> Objects.requireNonNull(client).setScreen(parent))
.position(this.width / 2 - 154, this.height - 28)
.size(150, 20)
.build());
this.addSelectableChild(this.placeholder); this.addSelectableChild(this.placeholder);
this.addDrawableChild(done);
// Sizing is also done in TinyConfigTab. Keep these in sync!
tabManager.setTabArea(new ScreenRect(0, 32, width, height));
reload = true; reload = true;
} }

View File

@ -30,18 +30,17 @@ public class TinyConfigTab implements Tab {
widget.updateCache(); widget.updateCache();
} }
// Sizing is also done in TinyConfigScreen. Keep these in sync!
this.list = new EntryListWidget(screen.getClient(), textRenderer, screen.width, screen.height, 32, screen.height - 36, 25); this.list = new EntryListWidget(screen.getClient(), textRenderer, screen.width, screen.height, 32, screen.height - 36, 25);
if (isRoot) { if (isRoot) {
for (Map.Entry<String, ConfigCategory> entry : config.getCategories().entrySet()) { for (Map.Entry<String, ConfigCategory> entry : config.getCategories().entrySet()) {
this.list.addReference(screen.width / 2, this.list.addReference(TinyConfigScreen.getTitle(entry.getValue().getTranslationPrefix()),
TinyConfigScreen.getTitle(entry.getValue().getTranslationPrefix()),
() -> new TinyConfigScreen(entry.getValue(), screen)); () -> new TinyConfigScreen(entry.getValue(), screen));
} }
} else { } else {
if (!config.getPresets().isEmpty()) { if (!config.getPresets().isEmpty()) {
this.list.addReference(screen.width / 2, this.list.addReference(Text.translatable("libjf-config-v1.presets"),
Text.translatable("libjf-config-v1.presets"),
() -> new PresetsScreen(screen, config)); () -> new PresetsScreen(screen, config));
} }
} }
@ -52,21 +51,23 @@ public class TinyConfigTab implements Tab {
info.reset(); info.reset();
if (control != null) control.updateControls().run(); if (control != null) control.updateControls().run();
})) }))
.position(screen.width - 155, 0) .dimensions(screen.width - 155, 0, 40, 20)
.size(40, 20)
.build(); .build();
BooleanSupplier resetVisible = () -> { BooleanSupplier resetVisible = () -> {
boolean visible = !Objects.equals(info.entry.getDefault(), info.cachedValue); boolean visible = !Objects.equals(info.entry.getDefault(), info.cachedValue);
resetButton.active = visible; resetButton.active = visible;
return visible; return visible;
}; };
if (control == null) this.list.addUnknown(resetButton, resetVisible, name); Reflowable reflow = (width, height) -> {
else this.list.addButton(control.control(), resetButton, resetVisible, name); resetButton.setX(width - 155);
if (control != null) control.reflow(width, height);
};
if (control == null) this.list.addUnknown(resetButton, resetVisible, name, reflow);
else this.list.addButton(control.control(), resetButton, resetVisible, name, reflow);
} }
for (ConfigInstance ci : config.getReferencedConfigs()) { for (ConfigInstance ci : config.getReferencedConfigs()) {
if (ci != null) { if (ci != null) {
this.list.addReference(screen.width / 2, this.list.addReference(Text.translatable("libjf-config-v1.see-also", TinyConfigScreen.getTitle(ci.getTranslationPrefix())),
Text.translatable("libjf-config-v1.see-also", TinyConfigScreen.getTitle(ci.getTranslationPrefix())),
() -> new TinyConfigScreen(ci, screen)); () -> new TinyConfigScreen(ci, screen));
} }
} }
@ -84,6 +85,7 @@ public class TinyConfigTab implements Tab {
@Override @Override
public void refreshGrid(ScreenRect tabArea) { public void refreshGrid(ScreenRect tabArea) {
list.refreshGrid(tabArea);
} }
@Override @Override

View File

@ -100,8 +100,10 @@ public class EntryInfoWidgetBuilder {
final ButtonWidget button = ButtonWidget.builder(valueToText.apply(state.cachedValue), btn -> { final ButtonWidget button = ButtonWidget.builder(valueToText.apply(state.cachedValue), btn -> {
state.updateCache(increment.apply(state.cachedValue)); state.updateCache(increment.apply(state.cachedValue));
btn.setMessage(valueToText.apply(state.cachedValue)); btn.setMessage(valueToText.apply(state.cachedValue));
}).position(screen.width - 110, 0).size(info.getWidth(), 20).build(); })
return new WidgetFactory.Widget(() -> button.setMessage(valueToText.apply(state.cachedValue)), button); .dimensions(screen.width - 110, 0, info.getWidth(), 20)
.build();
return new WidgetFactory.Widget(() -> button.setMessage(valueToText.apply(state.cachedValue)), button, (width, height) -> button.setX(width - 110));
}; };
} }
@ -148,59 +150,63 @@ public class EntryInfoWidgetBuilder {
return true; return true;
}); });
return new WidgetFactory.Widget(() -> widget.setText(state.cachedValue == null ? "" : state.cachedValue.toString()), widget); return new WidgetFactory.Widget(() -> widget.setText(state.cachedValue == null ? "" : state.cachedValue.toString()), widget, (width, height) -> widget.setX(width - 110));
}; };
} }
private static <T> WidgetFactory jsonScreen(ConfigCategory config, EntryInfo<T> info, WidgetState<T> state) { private static <T> WidgetFactory jsonScreen(ConfigCategory config, EntryInfo<T> info, WidgetState<T> state) {
state.managedTemp = false; state.managedTemp = false;
state.tempValue = null; state.tempValue = null;
return (screen, textRenderer) -> new WidgetFactory.Widget( return (screen, textRenderer) -> {
R::nop, final ButtonWidget button = ButtonWidget.builder(Text.translatable("libjf-config-core-v1.edit"), $ -> {
ButtonWidget.builder(Text.translatable("libjf-config-core-v1.edit"), button -> { final String jsonified;
final String jsonified; if (state.tempValue == null) {
if (state.tempValue == null) { try {
try { jsonified = GsonHolders.CONFIG.getGson().toJson(state.cachedValue);
jsonified = GsonHolders.CONFIG.getGson().toJson(state.cachedValue); } catch (Throwable e) {
} catch (Throwable e) { LibJf.LOGGER.error("Could not stringify element", e);
LibJf.LOGGER.error("Could not stringify element", e); SystemToast.add(
SystemToast.add( screen.getClient().getToastManager(),
screen.getClient().getToastManager(), SystemToast.Type.PACK_LOAD_FAILURE,
SystemToast.Type.PACK_LOAD_FAILURE, Text.translatable("libjf-config-ui-tiny-v1.entry.json.read.fail.title"),
Text.translatable("libjf-config-ui-tiny-v1.entry.json.read.fail.title"), Text.translatable("libjf-config-ui-tiny-v1.entry.json.read.fail.description")
Text.translatable("libjf-config-ui-tiny-v1.entry.json.read.fail.description") );
); return;
return;
}
} else {
jsonified = state.tempValue;
}
String key = config.getTranslationPrefix() + info.getName();
screen.getClient().setScreen(new EditorScreen(
Text.translatable(key),
I18n.hasTranslation(key + ".tooltip") ? Text.translatable(key + ".tooltip") : null,
screen,
jsonified,
json -> {
try {
state.updateCache(GsonHolders.CONFIG.getGson().fromJson(json, info.getValueType().asClass()));
state.tempValue = null;
} catch (Throwable e) {
LibJf.LOGGER.error("Could not write element", e);
SystemToast.add(
screen.getClient().getToastManager(),
SystemToast.Type.PACK_LOAD_FAILURE,
Text.translatable("libjf-config-ui-tiny-v1.entry.json.write.fail.title"),
Text.translatable("libjf-config-ui-tiny-v1.entry.json.write.fail.description")
);
state.tempValue = json;
}
} }
)); } else {
}) jsonified = state.tempValue;
.dimensions(screen.width - 110, 0, info.getWidth(), 20) }
.build() String key = config.getTranslationPrefix() + info.getName();
); screen.getClient().setScreen(new EditorScreen(
Text.translatable(key),
I18n.hasTranslation(key + ".tooltip") ? Text.translatable(key + ".tooltip") : null,
screen,
jsonified,
json -> {
try {
state.updateCache(GsonHolders.CONFIG.getGson().fromJson(json, info.getValueType().asClass()));
state.tempValue = null;
} catch (Throwable e) {
LibJf.LOGGER.error("Could not write element", e);
SystemToast.add(
screen.getClient().getToastManager(),
SystemToast.Type.PACK_LOAD_FAILURE,
Text.translatable("libjf-config-ui-tiny-v1.entry.json.write.fail.title"),
Text.translatable("libjf-config-ui-tiny-v1.entry.json.write.fail.description")
);
state.tempValue = json;
}
}
));
})
.dimensions(screen.width - 110, 0, info.getWidth(), 20)
.build();
return new WidgetFactory.Widget(
R::nop,
button,
(width, height) -> button.setX(width - 110)
);
};
} }
private static <T extends Number> WidgetFactory slider(EntryInfo info, WidgetState state, Function<T, Double> t2d, Function<Double, T> d2t, boolean wholeNumber) { private static <T extends Number> WidgetFactory slider(EntryInfo info, WidgetState state, Function<T, Double> t2d, Function<Double, T> d2t, boolean wholeNumber) {
@ -213,7 +219,7 @@ public class EntryInfoWidgetBuilder {
state.updateCache(d2t.apply(v)); state.updateCache(d2t.apply(v));
}, wholeNumber); }, wholeNumber);
return new WidgetFactory.Widget(() -> slider.setValue(t2d.apply((T) state.cachedValue)), slider); return new WidgetFactory.Widget(() -> slider.setValue(t2d.apply((T) state.cachedValue)), slider, (width, height) -> slider.setX(width - 110));
}; };
} }

View File

@ -4,14 +4,14 @@ import net.fabricmc.api.EnvType;
import net.fabricmc.api.Environment; import net.fabricmc.api.Environment;
import net.minecraft.client.MinecraftClient; import net.minecraft.client.MinecraftClient;
import net.minecraft.client.font.TextRenderer; import net.minecraft.client.font.TextRenderer;
import net.minecraft.client.gui.Element; import net.minecraft.client.gui.*;
import net.minecraft.client.gui.Selectable;
import net.minecraft.client.gui.screen.Screen; import net.minecraft.client.gui.screen.Screen;
import net.minecraft.client.gui.widget.*; import net.minecraft.client.gui.widget.*;
import net.minecraft.client.util.math.MatrixStack; import net.minecraft.client.util.math.MatrixStack;
import net.minecraft.text.Text; import net.minecraft.text.Text;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import java.sql.Ref;
import java.util.List; import java.util.List;
import java.util.Optional; import java.util.Optional;
import java.util.function.BooleanSupplier; import java.util.function.BooleanSupplier;
@ -21,8 +21,8 @@ import java.util.function.Supplier;
public class EntryListWidget extends ElementListWidget<EntryListWidget.ConfigEntry> { public class EntryListWidget extends ElementListWidget<EntryListWidget.ConfigEntry> {
TextRenderer textRenderer; TextRenderer textRenderer;
public EntryListWidget(MinecraftClient client, TextRenderer tr, int i, int j, int k, int l, int m) { public EntryListWidget(MinecraftClient client, TextRenderer tr, int width, int height, int top, int bottom, int itemHeight) {
super(client, i, j, k, l, m); super(client, width, height, top, bottom, itemHeight);
this.centerListVertically = false; this.centerListVertically = false;
textRenderer = tr; textRenderer = tr;
setRenderBackground(client.world == null); setRenderBackground(client.world == null);
@ -33,16 +33,16 @@ public class EntryListWidget extends ElementListWidget<EntryListWidget.ConfigEnt
return this.width -7; return this.width -7;
} }
public void addUnknown(ClickableWidget resetButton, BooleanSupplier resetVisible, Text text) { public void addUnknown(ClickableWidget resetButton, BooleanSupplier resetVisible, Text text, Reflowable reflow) {
this.addEntry(new ConfigUnknownEntry(text, resetVisible, resetButton)); this.addEntry(new ConfigUnknownEntry(text, resetVisible, resetButton, reflow));
} }
public void addButton(@Nullable ClickableWidget button, ClickableWidget resetButton, BooleanSupplier resetVisible, Text text) { public void addButton(@Nullable ClickableWidget button, ClickableWidget resetButton, BooleanSupplier resetVisible, Text text, Reflowable reflow) {
this.addEntry(new ConfigScreenEntry(button, text, resetVisible, resetButton)); this.addEntry(new ConfigScreenEntry(button, text, resetVisible, resetButton, reflow));
} }
public void addReference(int centerX, Text text, Supplier<Screen> targetScreen) { public void addReference(Text text, Supplier<Screen> targetScreen) {
this.addEntry(new ConfigReferenceEntry(centerX, text, targetScreen)); this.addEntry(new ConfigReferenceEntry(width, text, targetScreen));
} }
@Override @Override
@ -64,8 +64,16 @@ public class EntryListWidget extends ElementListWidget<EntryListWidget.ConfigEnt
return Optional.empty(); return Optional.empty();
} }
public void refreshGrid(ScreenRect tabArea) {
updateSize(tabArea.width(), tabArea.height(), tabArea.getTop(), tabArea.getBottom());
setLeftPos(tabArea.getLeft());
for (int i = 0, len = getEntryCount(); i < len; i++) {
getEntry(i).reflow(width, height);
}
}
@Environment(EnvType.CLIENT) @Environment(EnvType.CLIENT)
public abstract static class ConfigEntry extends Entry<ConfigEntry> { public abstract static class ConfigEntry extends Entry<ConfigEntry> implements Reflowable {
@Override @Override
public void render(MatrixStack matrices, int index, int y, int x, int entryWidth, int entryHeight, int mouseX, int mouseY, boolean hovered, float tickDelta) { public void render(MatrixStack matrices, int index, int y, int x, int entryWidth, int entryHeight, int mouseX, int mouseY, boolean hovered, float tickDelta) {
if (hovered) { if (hovered) {
@ -78,10 +86,9 @@ public class EntryListWidget extends ElementListWidget<EntryListWidget.ConfigEnt
public static class ConfigReferenceEntry extends ConfigEntry { public static class ConfigReferenceEntry extends ConfigEntry {
private final ClickableWidget button; private final ClickableWidget button;
public ConfigReferenceEntry(int centerX, Text text, Supplier<Screen> targetScreen) { public ConfigReferenceEntry(int width, Text text, Supplier<Screen> targetScreen) {
this.button = ButtonWidget.builder(text, btn -> MinecraftClient.getInstance().setScreen(targetScreen.get())) this.button = ButtonWidget.builder(text, btn -> MinecraftClient.getInstance().setScreen(targetScreen.get()))
.position(centerX - 154, 0) .dimensions(width / 2 - 154, 0, 308, 20)
.size(308, 20)
.build(); .build();
} }
@ -101,6 +108,11 @@ public class EntryListWidget extends ElementListWidget<EntryListWidget.ConfigEnt
button.setY(y); button.setY(y);
button.render(matrices, mouseX, mouseY, tickDelta); button.render(matrices, mouseX, mouseY, tickDelta);
} }
@Override
public void reflow(int width, int height) {
button.setX(width / 2 - 154);
}
} }
@Environment(EnvType.CLIENT) @Environment(EnvType.CLIENT)
@ -110,12 +122,14 @@ public class EntryListWidget extends ElementListWidget<EntryListWidget.ConfigEnt
private final BooleanSupplier resetVisible; private final BooleanSupplier resetVisible;
private final ClickableWidget resetButton; private final ClickableWidget resetButton;
private final Text text; private final Text text;
private final Reflowable reflow;
public ConfigScreenEntry(ClickableWidget button, Text text, BooleanSupplier resetVisible, ClickableWidget resetButton) { public ConfigScreenEntry(ClickableWidget button, Text text, BooleanSupplier resetVisible, ClickableWidget resetButton, Reflowable reflow) {
this.button = button; this.button = button;
this.resetVisible = resetVisible; this.resetVisible = resetVisible;
this.resetButton = resetButton; this.resetButton = resetButton;
this.text = text; this.text = text;
this.reflow = reflow;
} }
@Override @Override
@ -130,6 +144,11 @@ public class EntryListWidget extends ElementListWidget<EntryListWidget.ConfigEnt
} }
} }
@Override
public void reflow(int width, int height) {
reflow.reflow(width, height);
}
@Override @Override
public List<? extends Element> children() { public List<? extends Element> children() {
return List.of(button, resetButton); return List.of(button, resetButton);
@ -151,11 +170,13 @@ public class EntryListWidget extends ElementListWidget<EntryListWidget.ConfigEnt
private final BooleanSupplier resetVisible; private final BooleanSupplier resetVisible;
private final ClickableWidget resetButton; private final ClickableWidget resetButton;
private final Text text; private final Text text;
private final Reflowable reflow;
public ConfigUnknownEntry(Text text, BooleanSupplier resetVisible, ClickableWidget resetButton) { public ConfigUnknownEntry(Text text, BooleanSupplier resetVisible, ClickableWidget resetButton, Reflowable reflow) {
this.resetVisible = resetVisible; this.resetVisible = resetVisible;
this.resetButton = resetButton; this.resetButton = resetButton;
this.text = text; this.text = text;
this.reflow = reflow;
} }
@Override @Override
@ -168,6 +189,11 @@ public class EntryListWidget extends ElementListWidget<EntryListWidget.ConfigEnt
} }
} }
@Override
public void reflow(int width, int height) {
reflow.reflow(width, height);
}
@Override @Override
public List<? extends Element> children() { public List<? extends Element> children() {
return List.of(resetButton); return List.of(resetButton);

View File

@ -0,0 +1,5 @@
package io.gitlab.jfronny.libjf.config.impl.ui.tiny.entry;
public interface Reflowable {
void reflow(int width, int height);
}

View File

@ -36,8 +36,7 @@ public class PresetsScreen extends Screen {
config.fix(); config.fix();
Objects.requireNonNull(client).setScreen(parent); Objects.requireNonNull(client).setScreen(parent);
}) })
.position(width / 2 - 100, 0) .dimensions(width / 2 - 100, 0, 200, 20)
.size(200, 20)
.build()); .build());
} }
this.addSelectableChild(this.list); this.addSelectableChild(this.list);