You have no choice

This commit is contained in:
skippyall
2026-05-02 23:33:45 +02:00
parent f3c934c619
commit bd87a15cf2
7 changed files with 145 additions and 183 deletions
@@ -8,7 +8,9 @@ import net.minecraft.world.inventory.MenuType;
import net.minecraft.world.item.Items; import net.minecraft.world.item.Items;
import java.util.List; import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.function.BiFunction; import java.util.function.BiFunction;
import java.util.function.Function;
public class PaginatedList extends MinionsGui { public class PaginatedList extends MinionsGui {
private int page = 0; private int page = 0;
@@ -16,6 +18,7 @@ public class PaginatedList extends MinionsGui {
private final Component title; private final Component title;
private final int size; private final int size;
private final BiFunction<Integer, PaginatedList, GuiElementBuilder> display; private final BiFunction<Integer, PaginatedList, GuiElementBuilder> display;
private Runnable onClose = null;
public PaginatedList(MinionsGui parent, Component title, int size, BiFunction<Integer, PaginatedList, GuiElementBuilder> display) { public PaginatedList(MinionsGui parent, Component title, int size, BiFunction<Integer, PaginatedList, GuiElementBuilder> display) {
super(parent); super(parent);
@@ -25,12 +28,20 @@ public class PaginatedList extends MinionsGui {
open(); open();
} }
public PaginatedList(MinionsGui parent, Component title, int size, BiFunction<Integer, PaginatedList, GuiElementBuilder> display, Runnable onClose) {
this(parent, title, size, display);
this.onClose = onClose;
}
@Override @Override
protected void open() { protected void open() {
gui = new SimpleGui(MenuType.GENERIC_9x6, viewer, false) { gui = new SimpleGui(MenuType.GENERIC_9x6, viewer, false) {
@Override @Override
public void onPlayerClose(boolean success) { public void onPlayerClose(boolean success) {
onBackingClosed(); onBackingClosed();
if(onClose != null) {
onClose.run();
}
} }
}; };
gui.setTitle(title); gui.setTitle(title);
@@ -53,6 +64,17 @@ public class PaginatedList extends MinionsGui {
new PaginatedList(parent, title, list.size(), (i, gui) -> display.apply(list.byId(i), gui)); new PaginatedList(parent, title, list.size(), (i, gui) -> display.apply(list.byId(i), gui));
} }
public static <T> CompletableFuture<T> createListFuture(MinionsGui parent, Component title, List<T> list, Function<T, GuiElementBuilder> display) {
CompletableFuture<T> future = new CompletableFuture<>();
new PaginatedList(parent, title, list.size(), (i, me) -> display.apply(list.get(i))
.setCallback(() -> {
future.complete(list.get(i));
me.goBack();
})
);
return future;
}
private void addItems() { private void addItems() {
int slot = 9; int slot = 9;
for(int i = 36 * page; i < Math.min(36 * (page + 1), size); i++) { for(int i = 36 * page; i < Math.min(36 * (page + 1), size); i++) {
@@ -0,0 +1,71 @@
package io.github.skippyall.minions.gui.input
import eu.pb4.sgui.api.elements.GuiElementBuilder
import eu.pb4.sgui.api.gui.SimpleGui
import io.github.skippyall.minions.gui.MinionsGui
import io.github.skippyall.minions.gui.minion.SimpleMinionsGui
import kotlinx.coroutines.CompletableDeferred
import kotlinx.coroutines.future.asCompletableFuture
import net.minecraft.network.chat.Component
import net.minecraft.world.inventory.MenuType
import net.minecraft.world.item.Items
import java.util.concurrent.CompletableFuture
object BooleanInput {
fun confirm(
parent: MinionsGui,
title: Component,
falseText: Component = Component.translatable("minions.gui.abort"),
trueText: Component = Component.translatable("minions.gui.confirm")
): CompletableDeferred<Boolean> {
val deferred = CompletableDeferred<Boolean>()
SimpleMinionsGui(parent) { onClose: Runnable, me: SimpleMinionsGui ->
val gui: SimpleGui = object : SimpleGui(MenuType.GENERIC_3x3, parent.viewer, false) {
override fun onPlayerClose(success: Boolean) {
deferred.complete(false)
onClose.run()
}
}
gui.setTitle(title)
gui.setSlot(
3, GuiElementBuilder(Items.REDSTONE_BLOCK)
.setName(falseText)
.setCallback(Runnable {
deferred.complete(false)
me.goBack()
})
)
gui.setSlot(
5, GuiElementBuilder(Items.EMERALD_BLOCK)
.setName(trueText)
.setCallback(Runnable {
deferred.complete(true)
me.goBack()
})
)
gui.open()
gui
}
return deferred
}
@JvmStatic
@JvmOverloads
fun confirmFuture(
parent: MinionsGui,
title: Component,
falseText: Component = Component.translatable("minions.gui.abort"),
trueText: Component = Component.translatable("minions.gui.confirm")
): CompletableFuture<Boolean> {
return confirm(
parent,
title,
falseText,
trueText
).asCompletableFuture()
}
}
@@ -1,122 +0,0 @@
package io.github.skippyall.minions.gui.input;
import eu.pb4.sgui.api.elements.GuiElementBuilder;
import eu.pb4.sgui.api.gui.SimpleGui;
import io.github.skippyall.minions.gui.Displayable;
import io.github.skippyall.minions.gui.GuiDisplay;
import io.github.skippyall.minions.gui.MinionsGui;
import io.github.skippyall.minions.gui.minion.SimpleMinionsGui;
import net.minecraft.network.chat.Component;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.inventory.MenuType;
import net.minecraft.world.item.Items;
import org.jetbrains.annotations.Nullable;
import java.util.concurrent.CompletableFuture;
import java.util.function.BiFunction;
import java.util.function.Function;
public class ChoiceInput {
public static <T> BiFunction<ServerPlayer, T, CompletableFuture<T>> createDialogOpener(MenuType<?> screen, Component title, Function<T, GuiDisplay> displayFunction, T[] values, @Nullable T fallback) {
return (player, object) -> {
CompletableFuture<T> future = new CompletableFuture<>();
SimpleGui gui = new SimpleGui(screen, player, false) {
@Override
public void onPlayerClose(boolean success) {
if(fallback == null) {
future.cancel(false);
} else {
future.complete(fallback);
}
}
};
gui.setTitle(title);
for(T value : values) {
gui.addSlot(new GuiElementBuilder(displayFunction.apply(value).createItemStack())
.setCallback(() -> future.complete(value))
);
}
gui.open();
return future;
};
}
public static <T extends Displayable> BiFunction<ServerPlayer, T, CompletableFuture<T>> createDialogOpener(T[] values) {
return createDialogOpener(MenuType.GENERIC_9x3, Component.empty(), t -> t != null ? t.getDisplay() : null, values, null);
}
public static CompletableFuture<Void> confirm(ServerPlayer player, Component title) {
CompletableFuture<Void> future = new CompletableFuture<>();
SimpleGui gui = new SimpleGui(MenuType.GENERIC_3x3, player, false) {
@Override
public void onPlayerClose(boolean success) {
future.cancel(false);
}
};
gui.setTitle(title);
gui.setSlot(3, new GuiElementBuilder(Items.REDSTONE_BLOCK)
.setName(Component.translatable("minions.gui.abort"))
.setCallback(() -> future.cancel(false))
);
gui.setSlot(5, new GuiElementBuilder(Items.EMERALD_BLOCK)
.setName(Component.translatable("minions.gui.confirm"))
.setCallback(() -> future.complete(null))
);
gui.open();
return future;
}
public static CompletableFuture<Boolean> confirm(MinionsGui parent, Component title) {
CompletableFuture<Boolean> future = new CompletableFuture<>();
new SimpleMinionsGui(parent, (onClose, me) -> {
SimpleGui gui = new SimpleGui(MenuType.GENERIC_3x3, parent.viewer, false) {
@Override
public void onPlayerClose(boolean success) {
future.complete(false);
onClose.run();
}
};
gui.setTitle(title);
gui.setSlot(3, new GuiElementBuilder(Items.REDSTONE_BLOCK)
.setName(Component.translatable("minions.gui.abort"))
.setCallback(() -> {
future.complete(false);
me.goBack();
})
);
gui.setSlot(5, new GuiElementBuilder(Items.EMERALD_BLOCK)
.setName(Component.translatable("minions.gui.confirm"))
.setCallback(() -> {
future.complete(true);
me.goBack();
})
);
gui.open();
return gui;
});
return future;
}
public static BiFunction<ServerPlayer, Boolean, CompletableFuture<Boolean>> inputBoolean(Component title) {
return createDialogOpener(MenuType.GENERIC_3x3, title, value -> {
if(value) {
return new GuiDisplay.ItemBased(Items.EMERALD_BLOCK);
} else {
return new GuiDisplay.ItemBased(Items.REDSTONE_BLOCK);
}
}, new Boolean[]{false, true}, false);
}
}
@@ -3,8 +3,8 @@ package io.github.skippyall.minions.gui.input
import eu.pb4.sgui.api.elements.GuiElementBuilder import eu.pb4.sgui.api.elements.GuiElementBuilder
import eu.pb4.sgui.api.gui.AnvilInputGui import eu.pb4.sgui.api.gui.AnvilInputGui
import io.github.skippyall.minions.gui.MinionsGui import io.github.skippyall.minions.gui.MinionsGui
import kotlinx.coroutines.Job import kotlinx.coroutines.CompletableDeferred
import kotlinx.coroutines.async import kotlinx.coroutines.Deferred
import kotlinx.coroutines.future.asCompletableFuture import kotlinx.coroutines.future.asCompletableFuture
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import net.minecraft.network.chat.Component import net.minecraft.network.chat.Component
@@ -29,9 +29,7 @@ class TextInput<T>(
private lateinit var gui: AnvilInputGui private lateinit var gui: AnvilInputGui
private var result: Result<T, Component>? = null private var result: Result<T, Component>? = null
private var isConfirm = false val deferred = CompletableDeferred<T?>()
val job = Job()
init { init {
updateConfirmButton(defaultValue) updateConfirmButton(defaultValue)
@@ -46,8 +44,8 @@ class TextInput<T>(
override fun onPlayerClose(success: Boolean) { override fun onPlayerClose(success: Boolean) {
onBackingClosed() onBackingClosed()
if (job.isActive && !isConfirm) { if (deferred.isActive) {
job.cancel() deferred.complete(null)
} }
} }
} }
@@ -75,20 +73,19 @@ class TextInput<T>(
} }
fun onConfirm() { fun onConfirm() {
result?.ifSuccess { _: T? -> result?.ifSuccess { success: T ->
isConfirm = true deferred.complete(success)
} }
job.complete()
} }
companion object { companion object {
@JvmStatic @JvmStatic
suspend fun <T>input( fun <T>input(
gui: MinionsGui, gui: MinionsGui,
title: Component, title: Component,
defaultValue: String, defaultValue: String,
parser: suspend (String) -> Result<T, Component>, parser: suspend (String) -> Result<T, Component>,
): T? { ): Deferred<T?> {
val input = TextInput( val input = TextInput(
parent = gui, parent = gui,
title = title, title = title,
@@ -96,9 +93,7 @@ class TextInput<T>(
parser = parser, parser = parser,
) )
input.job.join() return input.deferred
return input.result?.getOrDefault(null)
} }
@JvmStatic @JvmStatic
@@ -108,26 +103,20 @@ class TextInput<T>(
defaultValue: String, defaultValue: String,
parser: (String) -> Result<T, Component>, parser: (String) -> Result<T, Component>,
): CompletableFuture<T?> { ): CompletableFuture<T?> {
return gui.scope.async { return input(
val input = TextInput( gui = gui,
parent = gui, title = title,
title = title, defaultValue = defaultValue,
defaultValue = defaultValue, parser = parser
parser = parser, ).asCompletableFuture()
)
input.job.join()
return@async input.result?.getOrDefault(null)
}.asCompletableFuture()
} }
@JvmStatic @JvmStatic
suspend fun inputString( fun inputString(
gui: MinionsGui, gui: MinionsGui,
title: Component, title: Component,
defaultValue: String, defaultValue: String,
): String? { ): Deferred<String?> {
return input<String>( return input<String>(
gui = gui, gui = gui,
title = title, title = title,
@@ -142,21 +131,19 @@ class TextInput<T>(
title: Component, title: Component,
defaultValue: String, defaultValue: String,
): CompletableFuture<String?> { ): CompletableFuture<String?> {
return gui.scope.async { return inputString(
inputString( gui = gui,
gui, title = title,
title, defaultValue = defaultValue,
defaultValue ).asCompletableFuture()
)
}.asCompletableFuture()
} }
@JvmStatic @JvmStatic
suspend fun inputLong( fun inputLong(
gui: MinionsGui, gui: MinionsGui,
title: Component, title: Component,
defaultValue: Long, defaultValue: Long,
): Long? { ): Deferred<Long?> {
return input<Long>( return input<Long>(
gui = gui, gui = gui,
title = title, title = title,
@@ -176,21 +163,19 @@ class TextInput<T>(
title: Component, title: Component,
defaultValue: Long, defaultValue: Long,
): CompletableFuture<Long?> { ): CompletableFuture<Long?> {
return gui.scope.async { return inputLong(
inputLong( gui = gui,
gui, title = title,
title, defaultValue = defaultValue,
defaultValue ).asCompletableFuture()
)
}.asCompletableFuture()
} }
@JvmStatic @JvmStatic
suspend fun inputDouble( fun inputDouble(
gui: MinionsGui, gui: MinionsGui,
title: Component, title: Component,
defaultValue: Double, defaultValue: Double,
): Double? { ): Deferred<Double?> {
return input<Double>( return input<Double>(
gui = gui, gui = gui,
title = title, title = title,
@@ -210,13 +195,11 @@ class TextInput<T>(
title: Component, title: Component,
defaultValue: Double, defaultValue: Double,
): CompletableFuture<Double?> { ): CompletableFuture<Double?> {
return gui.scope.async { return inputDouble(
inputDouble( gui = gui,
gui, title = title,
title, defaultValue = defaultValue,
defaultValue ).asCompletableFuture()
)
}.asCompletableFuture()
} }
} }
} }
@@ -4,7 +4,7 @@ import eu.pb4.sgui.api.elements.GuiElementBuilder;
import eu.pb4.sgui.api.gui.SimpleGui; import eu.pb4.sgui.api.gui.SimpleGui;
import io.github.skippyall.minions.clipboard.ClipboardItem; import io.github.skippyall.minions.clipboard.ClipboardItem;
import io.github.skippyall.minions.gui.MinionsGui; import io.github.skippyall.minions.gui.MinionsGui;
import io.github.skippyall.minions.gui.input.ChoiceInput; import io.github.skippyall.minions.gui.input.BooleanInput;
import io.github.skippyall.minions.gui.minion.GuiContext; import io.github.skippyall.minions.gui.minion.GuiContext;
import io.github.skippyall.minions.minion.MinionListener; import io.github.skippyall.minions.minion.MinionListener;
import io.github.skippyall.minions.minion.MinionRuntime; import io.github.skippyall.minions.minion.MinionRuntime;
@@ -60,7 +60,7 @@ public class ConfigureInstructionGui extends MinionsGui implements ConfiguredIns
gui.setSlot(7, new GuiElementBuilder(Items.LAVA_BUCKET) gui.setSlot(7, new GuiElementBuilder(Items.LAVA_BUCKET)
.setName(Component.translatable("minions.gui.instruction.configure.delete")) .setName(Component.translatable("minions.gui.instruction.configure.delete"))
.setCallback(() -> ChoiceInput.confirm(this, Component.translatable("minions.gui.instruction.configure.delete.confirm", name)) .setCallback(() -> BooleanInput.confirmFuture(this, Component.translatable("minions.gui.instruction.configure.delete.confirm", name))
.thenAccept((confirmed) -> { .thenAccept((confirmed) -> {
if(confirmed) { if(confirmed) {
minion.getInstructionManager().removeInstruction(name); minion.getInstructionManager().removeInstruction(name);
@@ -1,7 +1,10 @@
package io.github.skippyall.minions.registration; package io.github.skippyall.minions.registration;
import com.mojang.serialization.Codec; import com.mojang.serialization.Codec;
import eu.pb4.sgui.api.elements.GuiElementBuilder;
import io.github.skippyall.minions.Minions; import io.github.skippyall.minions.Minions;
import io.github.skippyall.minions.gui.PaginatedList;
import io.github.skippyall.minions.gui.input.BooleanInput;
import io.github.skippyall.minions.gui.input.TextInput; import io.github.skippyall.minions.gui.input.TextInput;
import io.github.skippyall.minions.minion.program.instruction.move.TurnDirection; import io.github.skippyall.minions.minion.program.instruction.move.TurnDirection;
import io.github.skippyall.minions.program.value.SimpleValueType; import io.github.skippyall.minions.program.value.SimpleValueType;
@@ -10,7 +13,7 @@ import net.minecraft.core.Registry;
import net.minecraft.network.chat.Component; import net.minecraft.network.chat.Component;
import net.minecraft.resources.Identifier; import net.minecraft.resources.Identifier;
import java.util.concurrent.CompletableFuture; import java.util.List;
public class ValueTypes { public class ValueTypes {
public static ValueType<Long> LONG = register( public static ValueType<Long> LONG = register(
@@ -49,8 +52,7 @@ public class ValueTypes {
Codec.BOOL, Codec.BOOL,
false, false,
o -> o instanceof Boolean b ? b : null, o -> o instanceof Boolean b ? b : null,
//TODO Properly implement ChoiceInput (parent, value) -> BooleanInput.confirmFuture(parent, Component.literal(""), Component.translatable("value_type.minions.boolean.false"), Component.translatable("value_type.minions.boolean.true")),
(gui, value) -> CompletableFuture.completedFuture(value),//ChoiceInput.inputBoolean(Text.literal("")),
value -> Component.literal(value.toString()) value -> Component.literal(value.toString())
) )
); );
@@ -76,8 +78,12 @@ public class ValueTypes {
TurnDirection.CODEC, TurnDirection.CODEC,
TurnDirection.RIGHT, TurnDirection.RIGHT,
o -> o instanceof TurnDirection d ? d : null, o -> o instanceof TurnDirection d ? d : null,
//TODO Properly implement ChoiceInput (parent, oldValue) -> PaginatedList.createListFuture(
(parent, oldValue) -> CompletableFuture.completedFuture(oldValue), // ChoiceInput.createDialogOpener(TurnDirection.values()), parent,
Component.translatable("value_type.minions.turn_direction"),
List.of(TurnDirection.values()),
value -> new GuiElementBuilder(value.getDisplay().createItemStack())
),
value -> Component.literal(value.name) value -> Component.literal(value.name)
) )
); );
@@ -90,6 +90,8 @@
"value_type.minions.long": "Integer", "value_type.minions.long": "Integer",
"value_type.minions.double": "Decimal", "value_type.minions.double": "Decimal",
"value_type.minions.boolean": "Boolean", "value_type.minions.boolean": "Boolean",
"value_type.minions.boolean.true": "True",
"value_type.minions.boolean.false": "False",
"value_type.minions.string": "Text", "value_type.minions.string": "Text",
"value_type.minions.turn_direction": "Turn Direction", "value_type.minions.turn_direction": "Turn Direction",