[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;
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.gui.widget.ClickableWidget;
public interface WidgetFactory {
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.Environment;
import net.minecraft.client.MinecraftClient;
import net.minecraft.client.gui.ScreenRect;
import net.minecraft.client.gui.screen.Screen;
import net.minecraft.client.gui.tab.TabManager;
import net.minecraft.client.gui.widget.ButtonWidget;
@ -63,18 +64,17 @@ public class TinyConfigScreen extends Screen {
protected void 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) {
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()
&& config.getPresets().keySet().stream().allMatch(s -> s.equals(CategoryBuilder.CONFIG_PRESET_DEFAULT))
&& config.getReferencedConfigs().isEmpty()
@ -88,7 +88,7 @@ public class TinyConfigScreen extends Screen {
this.tabs = List.of();
}
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)
.tabs(tabs.toArray(TinyConfigTab[]::new))
@ -98,23 +98,24 @@ public class TinyConfigScreen extends Screen {
tabNavigation.selectTab(0, false);
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()) {
this.addDrawableChild(ButtonWidget.builder(Text.translatable("libjf-config-v1.presets"),
button -> Objects.requireNonNull(client).setScreen(new PresetsScreen(this, config)))
.position(4, 6)
.size(80, 20)
.dimensions(4, 6, 80, 20)
.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.addDrawableChild(done);
// Sizing is also done in TinyConfigTab. Keep these in sync!
tabManager.setTabArea(new ScreenRect(0, 32, width, height));
reload = true;
}

View File

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

View File

@ -100,8 +100,10 @@ public class EntryInfoWidgetBuilder {
final ButtonWidget button = ButtonWidget.builder(valueToText.apply(state.cachedValue), btn -> {
state.updateCache(increment.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 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) {
state.managedTemp = false;
state.tempValue = null;
return (screen, textRenderer) -> new WidgetFactory.Widget(
R::nop,
ButtonWidget.builder(Text.translatable("libjf-config-core-v1.edit"), button -> {
final String jsonified;
if (state.tempValue == null) {
try {
jsonified = GsonHolders.CONFIG.getGson().toJson(state.cachedValue);
} catch (Throwable e) {
LibJf.LOGGER.error("Could not stringify element", e);
SystemToast.add(
screen.getClient().getToastManager(),
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.description")
);
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;
}
return (screen, textRenderer) -> {
final ButtonWidget button = ButtonWidget.builder(Text.translatable("libjf-config-core-v1.edit"), $ -> {
final String jsonified;
if (state.tempValue == null) {
try {
jsonified = GsonHolders.CONFIG.getGson().toJson(state.cachedValue);
} catch (Throwable e) {
LibJf.LOGGER.error("Could not stringify element", e);
SystemToast.add(
screen.getClient().getToastManager(),
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.description")
);
return;
}
));
})
.dimensions(screen.width - 110, 0, info.getWidth(), 20)
.build()
);
} 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;
}
}
));
})
.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) {
@ -213,7 +219,7 @@ public class EntryInfoWidgetBuilder {
state.updateCache(d2t.apply(v));
}, 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.minecraft.client.MinecraftClient;
import net.minecraft.client.font.TextRenderer;
import net.minecraft.client.gui.Element;
import net.minecraft.client.gui.Selectable;
import net.minecraft.client.gui.*;
import net.minecraft.client.gui.screen.Screen;
import net.minecraft.client.gui.widget.*;
import net.minecraft.client.util.math.MatrixStack;
import net.minecraft.text.Text;
import org.jetbrains.annotations.Nullable;
import java.sql.Ref;
import java.util.List;
import java.util.Optional;
import java.util.function.BooleanSupplier;
@ -21,8 +21,8 @@ import java.util.function.Supplier;
public class EntryListWidget extends ElementListWidget<EntryListWidget.ConfigEntry> {
TextRenderer textRenderer;
public EntryListWidget(MinecraftClient client, TextRenderer tr, int i, int j, int k, int l, int m) {
super(client, i, j, k, l, m);
public EntryListWidget(MinecraftClient client, TextRenderer tr, int width, int height, int top, int bottom, int itemHeight) {
super(client, width, height, top, bottom, itemHeight);
this.centerListVertically = false;
textRenderer = tr;
setRenderBackground(client.world == null);
@ -33,16 +33,16 @@ public class EntryListWidget extends ElementListWidget<EntryListWidget.ConfigEnt
return this.width -7;
}
public void addUnknown(ClickableWidget resetButton, BooleanSupplier resetVisible, Text text) {
this.addEntry(new ConfigUnknownEntry(text, resetVisible, resetButton));
public void addUnknown(ClickableWidget resetButton, BooleanSupplier resetVisible, Text text, Reflowable reflow) {
this.addEntry(new ConfigUnknownEntry(text, resetVisible, resetButton, reflow));
}
public void addButton(@Nullable ClickableWidget button, ClickableWidget resetButton, BooleanSupplier resetVisible, Text text) {
this.addEntry(new ConfigScreenEntry(button, text, resetVisible, resetButton));
public void addButton(@Nullable ClickableWidget button, ClickableWidget resetButton, BooleanSupplier resetVisible, Text text, Reflowable reflow) {
this.addEntry(new ConfigScreenEntry(button, text, resetVisible, resetButton, reflow));
}
public void addReference(int centerX, Text text, Supplier<Screen> targetScreen) {
this.addEntry(new ConfigReferenceEntry(centerX, text, targetScreen));
public void addReference(Text text, Supplier<Screen> targetScreen) {
this.addEntry(new ConfigReferenceEntry(width, text, targetScreen));
}
@Override
@ -64,8 +64,16 @@ public class EntryListWidget extends ElementListWidget<EntryListWidget.ConfigEnt
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)
public abstract static class ConfigEntry extends Entry<ConfigEntry> {
public abstract static class ConfigEntry extends Entry<ConfigEntry> implements Reflowable {
@Override
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) {
@ -78,10 +86,9 @@ public class EntryListWidget extends ElementListWidget<EntryListWidget.ConfigEnt
public static class ConfigReferenceEntry extends ConfigEntry {
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()))
.position(centerX - 154, 0)
.size(308, 20)
.dimensions(width / 2 - 154, 0, 308, 20)
.build();
}
@ -101,6 +108,11 @@ public class EntryListWidget extends ElementListWidget<EntryListWidget.ConfigEnt
button.setY(y);
button.render(matrices, mouseX, mouseY, tickDelta);
}
@Override
public void reflow(int width, int height) {
button.setX(width / 2 - 154);
}
}
@Environment(EnvType.CLIENT)
@ -110,12 +122,14 @@ public class EntryListWidget extends ElementListWidget<EntryListWidget.ConfigEnt
private final BooleanSupplier resetVisible;
private final ClickableWidget resetButton;
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.resetVisible = resetVisible;
this.resetButton = resetButton;
this.text = text;
this.reflow = reflow;
}
@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
public List<? extends Element> children() {
return List.of(button, resetButton);
@ -151,11 +170,13 @@ public class EntryListWidget extends ElementListWidget<EntryListWidget.ConfigEnt
private final BooleanSupplier resetVisible;
private final ClickableWidget resetButton;
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.resetButton = resetButton;
this.text = text;
this.reflow = reflow;
}
@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
public List<? extends Element> children() {
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();
Objects.requireNonNull(client).setScreen(parent);
})
.position(width / 2 - 100, 0)
.size(200, 20)
.dimensions(width / 2 - 100, 0, 200, 20)
.build());
}
this.addSelectableChild(this.list);