diff --git a/src/main/java/com/cleanroommc/modularui/api/widget/IParentWidget.java b/src/main/java/com/cleanroommc/modularui/api/widget/IParentWidget.java new file mode 100644 index 000000000..bcc87bca8 --- /dev/null +++ b/src/main/java/com/cleanroommc/modularui/api/widget/IParentWidget.java @@ -0,0 +1,45 @@ +package com.cleanroommc.modularui.api.widget; + +import java.util.function.BooleanSupplier; +import java.util.function.Supplier; + +public interface IParentWidget> { + + W getThis(); + + boolean addChild(I child, int index); + + default W child(int index, I child) { + if (!addChild(child, index)) { + throw new IllegalStateException("Failed to add child"); + } + return getThis(); + } + + default W child(I child) { + if (!addChild(child, -1)) { + throw new IllegalStateException("Failed to add child"); + } + return getThis(); + } + + default W childIf(boolean condition, I child) { + if (condition) return child(child); + return getThis(); + } + + default W childIf(BooleanSupplier condition, I child) { + if (condition.getAsBoolean()) return child(child); + return getThis(); + } + + default W childIf(boolean condition, Supplier child) { + if (condition) return child(child.get()); + return getThis(); + } + + default W childIf(BooleanSupplier condition, Supplier child) { + if (condition.getAsBoolean()) return child(child.get()); + return getThis(); + } +} diff --git a/src/main/java/com/cleanroommc/modularui/api/widget/IValueWidget.java b/src/main/java/com/cleanroommc/modularui/api/widget/IValueWidget.java index 54bce8b45..38289ce72 100644 --- a/src/main/java/com/cleanroommc/modularui/api/widget/IValueWidget.java +++ b/src/main/java/com/cleanroommc/modularui/api/widget/IValueWidget.java @@ -5,7 +5,7 @@ * * @param */ -public interface IValueWidget { +public interface IValueWidget extends IWidget { /** * @return stored value diff --git a/src/main/java/com/cleanroommc/modularui/api/widget/IWidget.java b/src/main/java/com/cleanroommc/modularui/api/widget/IWidget.java index d48bb30f6..15615b3e6 100644 --- a/src/main/java/com/cleanroommc/modularui/api/widget/IWidget.java +++ b/src/main/java/com/cleanroommc/modularui/api/widget/IWidget.java @@ -171,6 +171,10 @@ default boolean canHover() { return true; } + default boolean canClickThrough() { + return true; + } + /** * Marks tooltip for this widget as dirty. */ diff --git a/src/main/java/com/cleanroommc/modularui/api/widget/Interactable.java b/src/main/java/com/cleanroommc/modularui/api/widget/Interactable.java index 23070eeb7..d7bb9f61d 100644 --- a/src/main/java/com/cleanroommc/modularui/api/widget/Interactable.java +++ b/src/main/java/com/cleanroommc/modularui/api/widget/Interactable.java @@ -90,11 +90,13 @@ default Result onKeyTapped(char typedChar, int keyCode) { } /** - * Called when this widget is focused or when the mouse is above this widget + * Called when this widget is focused or when the mouse is above this widget. + * This method should return true if it can scroll at all and not if it scrolled right now. + * If this scroll view scrolled to the end and this returns false, the scroll will get passed through another scroll view below this. * * @param scrollDirection up or down - * @param amount usually irrelevant - * @return if other widgets should get called too + * @param amount amount scrolled by (usually irrelevant) + * @return true if this widget can be scrolled at all */ default boolean onMouseScroll(ModularScreen.UpOrDown scrollDirection, int amount) { return false; diff --git a/src/main/java/com/cleanroommc/modularui/screen/ClientScreenHandler.java b/src/main/java/com/cleanroommc/modularui/screen/ClientScreenHandler.java index 0fb1148e1..403b52604 100644 --- a/src/main/java/com/cleanroommc/modularui/screen/ClientScreenHandler.java +++ b/src/main/java/com/cleanroommc/modularui/screen/ClientScreenHandler.java @@ -191,6 +191,7 @@ private static boolean handleMouseInput(int button, @Nullable ModularScreen muiS } acc.setEventButton(button); acc.setLastMouseEvent(Minecraft.getSystemTime()); + if (muiScreen != null && muiScreen.handleDraggableInput(button, true)) return true; return doAction(muiScreen, ms -> ms.onMousePressed(button)); } if (button != -1) { @@ -204,6 +205,7 @@ private static boolean handleMouseInput(int button, @Nullable ModularScreen muiS } } acc.setEventButton(-1); + if (muiScreen != null && muiScreen.handleDraggableInput(button, false)) return true; return doAction(muiScreen, ms -> ms.onMouseRelease(button)); } if (acc.getEventButton() != -1 && acc.getLastMouseEvent() > 0L) { @@ -499,7 +501,7 @@ public static void drawDebugScreen(@Nullable ModularScreen muiScreen, @Nullable boolean allowShiftTransfer = slotGroup != null && slotGroup.allowShiftTransfer(); GuiDraw.drawText("Shift-Click Priority: " + (allowShiftTransfer ? slotGroup.getShiftClickPriority() : "DISABLED"), 5, lineY, 1, color, false); } - } else if(hovered instanceof RichTextWidget richTextWidget) { + } else if (hovered instanceof RichTextWidget richTextWidget) { drawSegmentLine(lineY -= 4, color); lineY -= 10; Object hoveredElement = richTextWidget.getHoveredElement(); diff --git a/src/main/java/com/cleanroommc/modularui/screen/ModularPanel.java b/src/main/java/com/cleanroommc/modularui/screen/ModularPanel.java index 66bb5b66e..491a3cb8e 100644 --- a/src/main/java/com/cleanroommc/modularui/screen/ModularPanel.java +++ b/src/main/java/com/cleanroommc/modularui/screen/ModularPanel.java @@ -290,6 +290,7 @@ public boolean onMousePressed(int mouseButton) { boolean result = false; if (this.hovering.isEmpty()) { + // no element is hovered -> try close panel if (closeOnOutOfBoundsClick()) { animateClose(); result = true; @@ -298,6 +299,7 @@ public boolean onMousePressed(int mouseButton) { loop: for (LocatedWidget widget : this.hovering) { widget.applyMatrix(getContext()); + // click widget and see how it reacts if (widget.getElement() instanceof Interactable interactable) { switch (interactable.onMousePressed(mouseButton)) { case IGNORE: @@ -327,6 +329,7 @@ public boolean onMousePressed(int mouseButton) { } } } + // see if widget can be dragged if (getContext().onHoveredClick(mouseButton, widget)) { pressed = LocatedWidget.EMPTY; result = true; @@ -334,7 +337,8 @@ public boolean onMousePressed(int mouseButton) { break; } widget.unapplyMatrix(getContext()); - if (widget.getElement().canHover()) { + // see if widgets below this can be interacted with + if (!widget.getElement().canClickThrough()) { result = true; break; } @@ -440,7 +444,7 @@ public boolean onKeyPressed(char typedChar, int keyCode) { } widget.unapplyMatrix(getContext()); } - if (widget.getElement().canHover()) break; + if (!widget.getElement().canClickThrough()) break; } if (!this.keyboard.held) { this.keyboard.lastPressed = pressed; diff --git a/src/main/java/com/cleanroommc/modularui/screen/ModularScreen.java b/src/main/java/com/cleanroommc/modularui/screen/ModularScreen.java index dcff9eb5a..d105608ca 100644 --- a/src/main/java/com/cleanroommc/modularui/screen/ModularScreen.java +++ b/src/main/java/com/cleanroommc/modularui/screen/ModularScreen.java @@ -283,6 +283,18 @@ public void drawForeground(float partialTicks) { GlStateManager.enableAlpha(); } + public boolean handleDraggableInput(int button, boolean pressed) { + if (this.context.hasDraggable()) { + if (pressed) { + this.context.onMousePressed(button); + } else { + this.context.onMouseReleased(button); + } + return true; + } + return false; + } + public boolean onMousePressed(int mouseButton) { for (IGuiAction.MousePressed action : getGuiActionListeners(IGuiAction.MousePressed.class)) { action.press(mouseButton); diff --git a/src/main/java/com/cleanroommc/modularui/screen/SecondaryPanel.java b/src/main/java/com/cleanroommc/modularui/screen/SecondaryPanel.java index 585c27cba..165b23ee3 100644 --- a/src/main/java/com/cleanroommc/modularui/screen/SecondaryPanel.java +++ b/src/main/java/com/cleanroommc/modularui/screen/SecondaryPanel.java @@ -1,9 +1,9 @@ package com.cleanroommc.modularui.screen; import com.cleanroommc.modularui.api.IPanelHandler; +import com.cleanroommc.modularui.api.MCHelper; import com.cleanroommc.modularui.widget.WidgetTree; -import net.minecraft.client.Minecraft; import net.minecraft.entity.player.EntityPlayer; import org.jetbrains.annotations.ApiStatus; @@ -76,7 +76,7 @@ public void openPanel() { this.screen = this.parent.getScreen(); } if (this.panel == null) { - this.panel = Objects.requireNonNull(this.provider.build(this.screen.getMainPanel(), Minecraft.getMinecraft().player)); + this.panel = Objects.requireNonNull(this.provider.build(this.screen.getMainPanel(), MCHelper.getPlayer())); if (this.panel == this.screen.getMainPanel()) { throw new IllegalArgumentException("Must not return main panel!"); } diff --git a/src/main/java/com/cleanroommc/modularui/screen/viewport/ModularGuiContext.java b/src/main/java/com/cleanroommc/modularui/screen/viewport/ModularGuiContext.java index 70ebc5a73..72dd94da4 100644 --- a/src/main/java/com/cleanroommc/modularui/screen/viewport/ModularGuiContext.java +++ b/src/main/java/com/cleanroommc/modularui/screen/viewport/ModularGuiContext.java @@ -30,7 +30,7 @@ public class ModularGuiContext extends GuiContext { public final ModularScreen screen; private LocatedWidget focusedWidget = LocatedWidget.EMPTY; @Nullable - private IGuiElement hovered; + private IWidget hovered; private int timeHovered = 0; private final HoveredIterable hoveredWidgets; @@ -81,7 +81,7 @@ public boolean isHoveredFor(IGuiElement guiElement, int ticks) { * @return the hovered widget (widget directly below the mouse) */ @Nullable - public IGuiElement getHovered() { + public IWidget getHovered() { return this.hovered; } @@ -310,7 +310,7 @@ public void drawDraggable() { @ApiStatus.Internal public void onFrameUpdate() { - IGuiElement hovered = this.screen.getPanelManager().getTopWidget(); + IWidget hovered = this.screen.getPanelManager().getTopWidget(); if (hasDraggable() && (this.lastDragX != getAbsMouseX() || this.lastDragY != getAbsMouseY())) { this.lastDragX = getAbsMouseX(); this.lastDragY = getAbsMouseY(); diff --git a/src/main/java/com/cleanroommc/modularui/test/EventHandler.java b/src/main/java/com/cleanroommc/modularui/test/EventHandler.java index ebd287748..5f94a01a4 100644 --- a/src/main/java/com/cleanroommc/modularui/test/EventHandler.java +++ b/src/main/java/com/cleanroommc/modularui/test/EventHandler.java @@ -16,7 +16,8 @@ public static void onItemUse(PlayerInteractEvent.RightClickItem event) { .inFrontOf(Minecraft.getMinecraft().player, 5, false) .screenScale(0.5f) .open(new TestGui());*/ - ClientGUI.open(new ResizerTest()); + //ClientGUI.open(new ResizerTest()); + ClientGUI.open(new TestGui()); } } } diff --git a/src/main/java/com/cleanroommc/modularui/test/ResizerTest.java b/src/main/java/com/cleanroommc/modularui/test/ResizerTest.java index 0631a5b56..edef9cd36 100644 --- a/src/main/java/com/cleanroommc/modularui/test/ResizerTest.java +++ b/src/main/java/com/cleanroommc/modularui/test/ResizerTest.java @@ -3,30 +3,98 @@ import com.cleanroommc.modularui.ModularUI; import com.cleanroommc.modularui.api.drawable.IKey; import com.cleanroommc.modularui.drawable.ItemDrawable; +import com.cleanroommc.modularui.drawable.SpriteDrawable; import com.cleanroommc.modularui.screen.CustomModularScreen; import com.cleanroommc.modularui.screen.ModularPanel; import com.cleanroommc.modularui.screen.viewport.ModularGuiContext; +import com.cleanroommc.modularui.utils.GameObjectHelper; +import com.cleanroommc.modularui.utils.SpriteHelper; +import com.cleanroommc.modularui.utils.fakeworld.ArraySchema; +import com.cleanroommc.modularui.utils.fakeworld.ISchema; +import com.cleanroommc.modularui.widget.DraggableWidget; import com.cleanroommc.modularui.widgets.RichTextWidget; +import com.cleanroommc.modularui.widgets.SchemaWidget; +import com.cleanroommc.modularui.widgets.SortableListWidget; + +import net.minecraft.client.renderer.texture.TextureAtlasSprite; import net.minecraft.init.Blocks; import net.minecraft.init.Items; import net.minecraft.item.ItemStack; +import net.minecraft.util.EnumFacing; import net.minecraft.util.text.TextFormatting; import org.jetbrains.annotations.NotNull; +import java.util.ArrayList; +import java.util.List; +import java.util.function.Function; + public class ResizerTest extends CustomModularScreen { @Override public @NotNull ModularPanel buildUI(ModularGuiContext context) { - /*TextureAtlasSprite sprite = SpriteHelper.getSpriteOfBlockState(GameObjectHelper.getBlockState("minecraft", "command_block"), EnumFacing.UP); + return buildSortableListUI(context); + } + + public @NotNull ModularPanel buildSpriteUI(ModularGuiContext context) { + TextureAtlasSprite sprite = SpriteHelper.getSpriteOfBlockState(GameObjectHelper.getBlockState("minecraft", "command_block"), EnumFacing.UP); //SpriteHelper.getSpriteOfItem(new ItemStack(Items.DIAMOND)); return ModularPanel.defaultPanel("main") .size(150) .child(new DraggableWidget<>() .background(new SpriteDrawable(sprite)) .size(20) - .align(Alignment.Center));*/ + .center()); + } + + + public @NotNull ModularPanel buildSortableListUI(ModularGuiContext context) { + List things = new ArrayList<>(); + for (int i = 0; i < 15; i++) { + things.add("Thing " + i); + } + return ModularPanel.defaultPanel("main") + .padding(7) + .child(new SortableListWidget() + .children(things, thing -> new SortableListWidget.Item<>(thing) + .overlay(IKey.str(thing)))); + } + + public @NotNull ModularPanel buildRichTextUI(ModularGuiContext context) { + return new ModularPanel("main") + .size(176, 166) + .child(new RichTextWidget() + .sizeRel(1f).margin(7) + .autoUpdate(true) + .textBuilder(text -> text.add("Hello ") + .add(new ItemDrawable(new ItemStack(Blocks.GRASS)) + .asIcon() + .asHoverable() + .tooltip(richTooltip -> richTooltip.addFromItem(new ItemStack(Blocks.GRASS)))) + .add(", nice to ") + .add(new ItemDrawable(new ItemStack(Items.PORKCHOP)) + .asIcon() + .asInteractable() + .onMousePressed(button -> { + ModularUI.LOGGER.info("Pressed Pork"); + return true; + })) + .add(" you. ") + .add(TextFormatting.GREEN + "This is a long ") + .add(IKey.str("string").format(TextFormatting.DARK_PURPLE) + .asTextIcon() + .asHoverable() + .addTooltipLine("Text Tooltip")) + .add(" of characters" + TextFormatting.RESET) + .add(" and not numbers as some might think...") + .newLine() + .add("") + .textShadow(false) + )); + } + + public @NotNull ModularPanel buildWorldSchemeUI(ModularGuiContext context) { /*TrackedDummyWorld world = new TrackedDummyWorld(); world.addBlock(new BlockPos(0, 0, 0), new BlockInfo(Blocks.DIAMOND_BLOCK.getDefaultState())); world.addBlock(new BlockPos(0, 1, 0), new BlockInfo(Blocks.BEDROCK.getDefaultState())); @@ -43,7 +111,6 @@ public class ResizerTest extends CustomModularScreen { .isometric(true) .asIcon().size(140));*/ - /*MapSchema world = new MapSchema.Builder() .add(new BlockPos(0, 0, 0), Blocks.DIAMOND_BLOCK.getDefaultState()) .add(new BlockPos(0, 1, 0), Blocks.BEDROCK.getDefaultState()) @@ -52,7 +119,7 @@ public class ResizerTest extends CustomModularScreen { .add(new BlockPos(0, 3, 0), Blocks.BEACON.getDefaultState()) .build();*/ - /*ISchema schema = ArraySchema.builder() + ISchema schema = ArraySchema.builder() .layer("D D", " ", " ", " ") .layer(" DDD ", " E E ", " ", " ") .layer(" DDD ", " E ", " G ", " B ") @@ -70,37 +137,7 @@ public class ResizerTest extends CustomModularScreen { .child(new SchemaWidget.LayerButton(schema, 0, 3) .bottom(1) .left(1) - .size(16));*/ - - return new ModularPanel("main") - .size(176, 166) - .child(new RichTextWidget() - .sizeRel(1f).margin(7) - .autoUpdate(true) - .textBuilder(text -> text.add("Hello ") - .add(new ItemDrawable(new ItemStack(Blocks.GRASS)) - .asIcon() - .asHoverable() - .tooltip(richTooltip -> richTooltip.addFromItem(new ItemStack(Blocks.GRASS)))) - .add(", nice to ") - .add(new ItemDrawable(new ItemStack(Items.PORKCHOP)) - .asIcon() - .asInteractable() - .onMousePressed(button -> { - ModularUI.LOGGER.info("Pressed Pork"); - return true; - })) - .add(" you. ") - .add(TextFormatting.GREEN + "This is a long ") - .add(IKey.str("string").format(TextFormatting.DARK_PURPLE) - .asTextIcon() - .asHoverable() - .addTooltipLine("Text Tooltip")) - .add(" of characters" + TextFormatting.RESET) - .add(" and not numbers as some might think...") - .newLine() - .add("") - .textShadow(false) - )); + .size(16)); + return panel; } } diff --git a/src/main/java/com/cleanroommc/modularui/test/TestBlock.java b/src/main/java/com/cleanroommc/modularui/test/TestBlock.java index 9b9c742fe..21db78c0c 100644 --- a/src/main/java/com/cleanroommc/modularui/test/TestBlock.java +++ b/src/main/java/com/cleanroommc/modularui/test/TestBlock.java @@ -2,7 +2,6 @@ import com.cleanroommc.modularui.ModularUI; import com.cleanroommc.modularui.factory.GuiFactories; -import com.cleanroommc.modularui.factory.TileEntityGuiFactory; import net.minecraft.block.Block; import net.minecraft.block.ITileEntityProvider; @@ -28,17 +27,24 @@ import org.jetbrains.annotations.NotNull; import javax.annotation.Nullable; +import java.util.function.Supplier; public class TestBlock extends Block implements ITileEntityProvider { - public static final Block testBlock = new TestBlock(); + public static final Block testBlock = new TestBlock(TestTile::new); + public static final Block testBlock2 = new TestBlock(TestTile2::new); public static final ItemBlock testItemBlock = new ItemBlock(testBlock); + public static final ItemBlock testItemBlock2 = new ItemBlock(testBlock2); public static void preInit() { ResourceLocation rl = new ResourceLocation(ModularUI.ID, "test_block"); testBlock.setRegistryName(rl); testItemBlock.setRegistryName(rl); GameRegistry.registerTileEntity(TestTile.class, rl); + rl = new ResourceLocation(ModularUI.ID, "test_block_2"); + testBlock2.setRegistryName(rl); + testItemBlock2.setRegistryName(rl); + GameRegistry.registerTileEntity(TestTile2.class, rl); TestItem.testItem.setRegistryName(ModularUI.ID, "test_item"); } @@ -46,12 +52,14 @@ public static void preInit() { public static void registerBlocks(RegistryEvent.Register event) { IForgeRegistry registry = event.getRegistry(); registry.register(testBlock); + registry.register(testBlock2); } @SubscribeEvent public static void registerItems(RegistryEvent.Register event) { IForgeRegistry registry = event.getRegistry(); registry.register(testItemBlock); + registry.register(testItemBlock2); registry.register(TestItem.testItem); } @@ -61,14 +69,17 @@ public static void registerModel(ModelRegistryEvent event) { ModelLoader.setCustomModelResourceLocation(TestItem.testItem, 0, mrl); } - public TestBlock() { + private final Supplier tileEntitySupplier; + + public TestBlock(Supplier tileEntitySupplier) { super(Material.ROCK); + this.tileEntitySupplier = tileEntitySupplier; } @Nullable @Override public TileEntity createNewTileEntity(@NotNull World worldIn, int meta) { - return new TestTile(); + return this.tileEntitySupplier.get(); } @Override diff --git a/src/main/java/com/cleanroommc/modularui/test/TestGui.java b/src/main/java/com/cleanroommc/modularui/test/TestGui.java index b1aa6b658..1c5480775 100644 --- a/src/main/java/com/cleanroommc/modularui/test/TestGui.java +++ b/src/main/java/com/cleanroommc/modularui/test/TestGui.java @@ -10,8 +10,10 @@ import com.cleanroommc.modularui.screen.viewport.ModularGuiContext; import com.cleanroommc.modularui.widget.Widget; import com.cleanroommc.modularui.widgets.ButtonWidget; +import com.cleanroommc.modularui.widgets.Dialog; import com.cleanroommc.modularui.widgets.SortableListWidget; import com.cleanroommc.modularui.widgets.layout.Grid; +import com.cleanroommc.modularui.widgets.layout.Row; import net.minecraftforge.fml.relauncher.Side; import net.minecraftforge.fml.relauncher.SideOnly; @@ -21,7 +23,6 @@ import java.util.List; import java.util.Map; -import java.util.concurrent.atomic.AtomicReference; import java.util.stream.Collectors; import java.util.stream.IntStream; @@ -45,14 +46,29 @@ public void onClose() { this.configuredOptions = this.lines; this.availableElements = new Object2ObjectOpenHashMap<>(); } - AtomicReference>> ref = new AtomicReference<>(null); + final Map> items = new Object2ObjectOpenHashMap<>(); + for (String line : this.lines) { + items.put(line, new SortableListWidget.Item<>(line).child(item -> new Row() + .child(new Widget<>() + .addTooltipLine(line) + .background(GuiTextures.BUTTON_CLEAN) + .overlay(IKey.str(line)) + .expanded().heightRel(1f)) + .child(new ButtonWidget<>() + .onMousePressed(button -> item.removeSelfFromList()) + .overlay(GuiTextures.CROSS_TINY.asIcon().size(10)) + .width(10).heightRel(1f)))); + } + SortableListWidget sortableListWidget = new SortableListWidget() + .children(configuredOptions, items::get) + .debugName("sortable list"); List> availableMatrix = Grid.mapToMatrix(2, this.lines, (index, value) -> { AvailableElement availableElement = new AvailableElement().overlay(IKey.str(value)) .size(60, 14) .addTooltipLine(value) .onMousePressed(mouseButton1 -> { if (this.availableElements.get(value).available) { - ref.get().add(value, -1); + sortableListWidget.child(items.get(value)); this.availableElements.get(value).available = false; } return true; @@ -66,44 +82,13 @@ public void onClose() { ModularPanel panel = ModularPanel.defaultPanel("test"); - /*List> matrix = new ArrayList<>(); - for (int i = 0; i < 400; i++) { - int r = i / 20; - int c = i % 20; - List row; - if (matrix.size() <= r) { - row = new ArrayList<>(); - matrix.add(row); - } else { - row = matrix.get(r); - } - row.add(IKey.str(String.valueOf(i + 1)).asWidget()); - } - panel.child(new Grid() - .matrix(matrix) - .scrollable() - .pos(10, 10).right(10).bottom(10))*/ - SortableListWidget> sortableListWidget = SortableListWidget.sortableBuilder(this.lines, this.configuredOptions, - s -> new SortableListWidget.Item<>(s, new Widget<>() - .addTooltipLine(s) - .background(GuiTextures.BUTTON_CLEAN) - .overlay(IKey.str(s)) - .left(0).right(10)) - .removeable()).debugName("sortable list"); - ref.set(sortableListWidget); panel.child(sortableListWidget .onRemove(stringItem -> this.availableElements.get(stringItem.getWidgetValue()).available = true) - /*.onChange(list -> { - this.configuredOptions = list; - for (String value : this.lines) { - this.availableElements.get(value).available = !list.contains(value); - } - })*/ .pos(10, 10) .bottom(23) .width(100)); IPanelHandler otherPanel = IPanelHandler.simple(panel, (mainPanel, player) -> { - ModularPanel panel1 = ModularPanel.defaultPanel("Option Selection", 150, 120); + ModularPanel panel1 = new Dialog<>("Option Selection").setDisablePanelsBelow(false).setDraggable(false).size(150, 120); return panel1.child(ButtonWidget.panelCloseButton()) .child(new Grid() .matrix(availableMatrix) @@ -117,23 +102,6 @@ public void onClose() { otherPanel.openPanel(); return true; })); - /*IDrawable optionHoverEffect = new Rectangle().setColor(Color.withAlpha(Color.WHITE.dark(5), 50)); - panel.child(new PopupMenu<>(ListWidget.builder(lines, t -> new SimpleWidget() - .width(1f).height(12) - .background(IKey.str(t).color(Color.WHITE.normal)) - .hoverBackground(optionHoverEffect, IKey.str(t).color(Color.WHITE.normal))) - .width(0.8f).height(36).top(1f) - .background(new Rectangle().setColor(Color.BLACK.bright(2)))) - .left(10) - .right(10) - .height(20) - .top(10) - .background(GuiTextures.BUTTON, IKey.str("Button")))*/ - /*.child(SlotGroup.playerInventory() - .flex(flex -> flex - .left(0.5f) - .bottom(7)));*/ - return panel; } diff --git a/src/main/java/com/cleanroommc/modularui/test/TestTile2.java b/src/main/java/com/cleanroommc/modularui/test/TestTile2.java new file mode 100644 index 000000000..dab015d2e --- /dev/null +++ b/src/main/java/com/cleanroommc/modularui/test/TestTile2.java @@ -0,0 +1,56 @@ +package com.cleanroommc.modularui.test; + +import com.cleanroommc.modularui.api.IGuiHolder; +import com.cleanroommc.modularui.factory.PosGuiData; +import com.cleanroommc.modularui.screen.ModularPanel; +import com.cleanroommc.modularui.value.sync.PanelSyncManager; +import com.cleanroommc.modularui.widget.ScrollWidget; +import com.cleanroommc.modularui.widget.scroll.VerticalScrollData; +import com.cleanroommc.modularui.widgets.ItemSlot; +import com.cleanroommc.modularui.widgets.slot.ModularSlot; + +import net.minecraft.item.Item; +import net.minecraft.item.ItemStack; +import net.minecraft.tileentity.TileEntity; +import net.minecraft.util.ITickable; +import net.minecraftforge.fml.common.registry.ForgeRegistries; +import net.minecraftforge.items.IItemHandlerModifiable; +import net.minecraftforge.items.ItemStackHandler; + +import java.util.Iterator; + +public class TestTile2 extends TileEntity implements IGuiHolder, ITickable { + + private static final int SLOT_COUNT = 9999; + + private final IItemHandlerModifiable itemHandler = new ItemStackHandler(SLOT_COUNT); + + public TestTile2() { + Iterator it = ForgeRegistries.ITEMS.iterator(); + for (int i = 0; i < SLOT_COUNT; i++) { + if (!it.hasNext()) { + it = ForgeRegistries.ITEMS.iterator(); + } + Item item = it.next(); + this.itemHandler.setStackInSlot(i, new ItemStack(item)); + } + } + + @Override + public ModularPanel buildUI(PosGuiData data, PanelSyncManager syncManager) { + ScrollWidget sw = new ScrollWidget<>(new VerticalScrollData()).size(9 * 18).margin(7); + sw.getScrollArea().getScrollY().setScrollSize(18 * (SLOT_COUNT / 9)); + for (int i = 0; i < SLOT_COUNT; i++) { + int x = i % 9; + int y = i / 9; + sw.child(new ItemSlot().pos(x * 18, y * 18) + .slot(new ModularSlot(this.itemHandler, i))); + } + return ModularPanel.defaultPanel("test_tile_2", 176, 13 * 18 + 14 + 10) + .bindPlayerInventory() + .child(sw); + } + + @Override + public void update() {} +} diff --git a/src/main/java/com/cleanroommc/modularui/widget/AbstractParentWidget.java b/src/main/java/com/cleanroommc/modularui/widget/AbstractParentWidget.java new file mode 100644 index 000000000..ab8008995 --- /dev/null +++ b/src/main/java/com/cleanroommc/modularui/widget/AbstractParentWidget.java @@ -0,0 +1,99 @@ +package com.cleanroommc.modularui.widget; + +import com.cleanroommc.modularui.api.drawable.IDrawable; +import com.cleanroommc.modularui.api.widget.IWidget; +import com.cleanroommc.modularui.screen.ModularPanel; +import com.cleanroommc.modularui.theme.WidgetTheme; +import com.cleanroommc.modularui.widgets.VoidWidget; + +import org.jetbrains.annotations.NotNull; + +import java.util.ArrayList; +import java.util.List; +import java.util.function.BooleanSupplier; +import java.util.function.Supplier; + +/** + * A widget which can hold any amount of children. + * + * @param type of children (in most cases just {@link IWidget}). Use {@link VoidWidget} if no children should be added. + * @param type of this widget + */ +public class AbstractParentWidget> extends Widget { + + private final List children = new ArrayList<>(); + + @NotNull + @Override + public List getChildren() { + return (List) this.children; + } + + public List getTypeChildren() { + return children; + } + + @Override + public boolean canHover() { + if (IDrawable.isVisible(getBackground()) || + IDrawable.isVisible(getHoverBackground()) || + IDrawable.isVisible(getHoverOverlay()) || + getTooltip() != null) return true; + WidgetTheme widgetTheme = getWidgetTheme(getContext().getTheme()); + if (getBackground() == null && IDrawable.isVisible(widgetTheme.getBackground())) return true; + return getHoverBackground() == null && IDrawable.isVisible(widgetTheme.getHoverBackground()); + } + + @Override + public boolean canClickThrough() { + return !canHover(); + } + + protected boolean addChild(I child, int index) { + if (child == null || child == this || getChildren().contains(child)) { + return false; + } + if (child instanceof ModularPanel) { + throw new IllegalArgumentException("ModularPanel should not be added as child widget; Use ModularScreen#openPanel instead"); + } + if (!isChildValid(child)) { + throw new IllegalArgumentException("Child '" + child + "' is not valid for parent '" + this + "'!"); + } + if (index < 0) { + index += getChildren().size() + 1; + } + this.children.add(index, child); + if (isValid()) { + child.initialise(this); + } + onChildAdd(child); + return true; + } + + protected boolean remove(I child) { + if (this.children.remove(child)) { + child.dispose(); + onChildRemove(child); + return true; + } + return false; + } + + protected boolean remove(int index) { + if (index < 0) { + index = getChildren().size() + index + 1; + } + I child = this.children.remove(index); + child.dispose(); + onChildRemove(child); + return true; + } + + protected boolean isChildValid(I child) { + return true; + } + + protected void onChildAdd(I child) {} + + protected void onChildRemove(I child) {} +} diff --git a/src/main/java/com/cleanroommc/modularui/widget/AbstractScrollWidget.java b/src/main/java/com/cleanroommc/modularui/widget/AbstractScrollWidget.java new file mode 100644 index 000000000..cd07c9009 --- /dev/null +++ b/src/main/java/com/cleanroommc/modularui/widget/AbstractScrollWidget.java @@ -0,0 +1,131 @@ +package com.cleanroommc.modularui.widget; + +import com.cleanroommc.modularui.api.layout.IViewport; +import com.cleanroommc.modularui.api.layout.IViewportStack; +import com.cleanroommc.modularui.api.widget.IGuiAction; +import com.cleanroommc.modularui.api.widget.IWidget; +import com.cleanroommc.modularui.api.widget.Interactable; +import com.cleanroommc.modularui.drawable.Stencil; +import com.cleanroommc.modularui.screen.ModularScreen; +import com.cleanroommc.modularui.screen.viewport.ModularGuiContext; +import com.cleanroommc.modularui.utils.HoveredWidgetList; +import com.cleanroommc.modularui.widget.scroll.HorizontalScrollData; +import com.cleanroommc.modularui.widget.scroll.ScrollArea; +import com.cleanroommc.modularui.widget.scroll.VerticalScrollData; +import com.cleanroommc.modularui.widget.sizer.Area; + +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * A scrollable parent widget. Children can be added + * + * @param type of children (in most cases just {@link IWidget}) + * @param type of this widget + */ +public abstract class AbstractScrollWidget> extends AbstractParentWidget implements IViewport, Interactable { + + private final ScrollArea scroll = new ScrollArea(); + + public AbstractScrollWidget(@Nullable HorizontalScrollData x, @Nullable VerticalScrollData y) { + super(); + this.scroll.setScrollDataX(x); + this.scroll.setScrollDataY(y); + listenGuiAction((IGuiAction.MouseReleased) mouseButton -> { + this.scroll.mouseReleased(getContext()); + return false; + }); + } + + @Override + public Area getArea() { + return this.scroll; + } + + public ScrollArea getScrollArea() { + return this.scroll; + } + + @Override + public void transformChildren(IViewportStack stack) { + stack.translate(-getScrollX(), -getScrollY()); + } + + @Override + public void getSelfAt(IViewportStack stack, HoveredWidgetList widgets, int x, int y) { + if (isInside(stack, x, y)) { + widgets.add(this, stack.peek()); + } + } + + @Override + public void getWidgetsAt(IViewportStack stack, HoveredWidgetList widgets, int x, int y) { + if (getArea().isInside(x, y) && !getScrollArea().isInsideScrollbarArea(x, y) && hasChildren()) { + IViewport.getChildrenAt(this, stack, widgets, x, y); + } + } + + @Override + public void onResized() { + if (this.scroll.getScrollX() != null) { + this.scroll.getScrollX().clamp(this.scroll); + } + if (this.scroll.getScrollY() != null) { + this.scroll.getScrollY().clamp(this.scroll); + } + } + + @Override + public boolean canHover() { + return super.canHover() || this.scroll.isInsideScrollbarArea(getContext().getMouseX(), getContext().getMouseY()); + } + + @Override + public @NotNull Result onMousePressed(int mouseButton) { + ModularGuiContext context = getContext(); + if (this.scroll.mouseClicked(context)) { + return Result.STOP; + } + return Result.IGNORE; + } + + @Override + public boolean onMouseScroll(ModularScreen.UpOrDown scrollDirection, int amount) { + return this.scroll.mouseScroll(getContext()); + } + + @Override + public boolean onMouseRelease(int mouseButton) { + this.scroll.mouseReleased(getContext()); + return false; + } + + @Override + public void onUpdate() { + super.onUpdate(); + this.scroll.drag(getContext().getAbsMouseX(), getContext().getAbsMouseY()); + } + + @Override + public void preDraw(ModularGuiContext context, boolean transformed) { + if (!transformed) { + Stencil.applyAtZero(this.scroll, context); + } + } + + @Override + public void postDraw(ModularGuiContext context, boolean transformed) { + if (!transformed) { + Stencil.remove(); + this.scroll.drawScrollbar(); + } + } + + public int getScrollX() { + return this.scroll.getScrollX() != null ? this.scroll.getScrollX().getScroll() : 0; + } + + public int getScrollY() { + return this.scroll.getScrollY() != null ? this.scroll.getScrollY().getScroll() : 0; + } +} diff --git a/src/main/java/com/cleanroommc/modularui/widget/ParentWidget.java b/src/main/java/com/cleanroommc/modularui/widget/ParentWidget.java index ab3c70a41..f44e4870c 100644 --- a/src/main/java/com/cleanroommc/modularui/widget/ParentWidget.java +++ b/src/main/java/com/cleanroommc/modularui/widget/ParentWidget.java @@ -1,105 +1,27 @@ package com.cleanroommc.modularui.widget; -import com.cleanroommc.modularui.api.drawable.IDrawable; +import com.cleanroommc.modularui.api.widget.IParentWidget; import com.cleanroommc.modularui.api.widget.IWidget; -import com.cleanroommc.modularui.screen.ModularPanel; -import com.cleanroommc.modularui.theme.WidgetTheme; -import org.jetbrains.annotations.NotNull; - -import java.util.ArrayList; -import java.util.List; -import java.util.function.BooleanSupplier; -import java.util.function.Supplier; - -public class ParentWidget> extends Widget { - - private final List children = new ArrayList<>(); - - @NotNull - @Override - public List getChildren() { - return this.children; - } - - @Override - public boolean canHover() { - if (IDrawable.isVisible(getBackground()) || - IDrawable.isVisible(getHoverBackground()) || - IDrawable.isVisible(getHoverOverlay()) || - getTooltip() != null) return true; - WidgetTheme widgetTheme = getWidgetTheme(getContext().getTheme()); - if (getBackground() == null && IDrawable.isVisible(widgetTheme.getBackground())) return true; - return getHoverBackground() == null && IDrawable.isVisible(widgetTheme.getHoverBackground()); - } +/** + * A widget which can hold any amount of children. + * + * @param type of this widget + */ +public class ParentWidget> extends AbstractParentWidget implements IParentWidget { public boolean addChild(IWidget child, int index) { - if (child == null || child == this || getChildren().contains(child)) { - return false; - } - if (child instanceof ModularPanel) { - throw new IllegalStateException("ModularPanel should not be added as child widget; Use ModularScreen#openPanel instead"); - } - if (index < 0) { - index = getChildren().size() + index + 1; - } - this.children.add(index, child); - if (isValid()) { - child.initialise(this); - } - onChildAdd(child); - return true; + return super.addChild(child, index); } + @Override public boolean remove(IWidget child) { - if (this.children.remove(child)) { - child.dispose(); - onChildRemove(child); - return true; - } - return false; + return super.remove(child); } + @Override public boolean remove(int index) { - if (index < 0) { - index = getChildren().size() + index + 1; - } - IWidget child = this.children.remove(index); - child.dispose(); - onChildRemove(child); - return true; - } - - public void onChildAdd(IWidget child) { - } - - public void onChildRemove(IWidget child) { - } - - public W child(IWidget child) { - if (!addChild(child, -1)) { - throw new IllegalStateException("Failed to add child"); - } - return getThis(); - } - - public W childIf(boolean condition, IWidget child) { - if (condition) return child(child); - return getThis(); - } - - public W childIf(BooleanSupplier condition, IWidget child) { - if (condition.getAsBoolean()) return child(child); - return getThis(); + return super.remove(index); } - public W childIf(boolean condition, Supplier child) { - if (condition) return child(child.get()); - return getThis(); - } - - public W childIf(BooleanSupplier condition, Supplier child) { - if (condition.getAsBoolean()) return child(child.get()); - return getThis(); - } } diff --git a/src/main/java/com/cleanroommc/modularui/widget/ScrollWidget.java b/src/main/java/com/cleanroommc/modularui/widget/ScrollWidget.java index 2647c3a33..f24e737de 100644 --- a/src/main/java/com/cleanroommc/modularui/widget/ScrollWidget.java +++ b/src/main/java/com/cleanroommc/modularui/widget/ScrollWidget.java @@ -1,136 +1,26 @@ package com.cleanroommc.modularui.widget; -import com.cleanroommc.modularui.api.layout.IViewport; -import com.cleanroommc.modularui.api.layout.IViewportStack; -import com.cleanroommc.modularui.api.widget.IGuiAction; -import com.cleanroommc.modularui.api.widget.Interactable; -import com.cleanroommc.modularui.drawable.Stencil; -import com.cleanroommc.modularui.screen.ModularScreen; -import com.cleanroommc.modularui.screen.viewport.ModularGuiContext; -import com.cleanroommc.modularui.utils.HoveredWidgetList; +import com.cleanroommc.modularui.api.widget.IParentWidget; +import com.cleanroommc.modularui.api.widget.IWidget; import com.cleanroommc.modularui.widget.scroll.HorizontalScrollData; -import com.cleanroommc.modularui.widget.scroll.ScrollArea; import com.cleanroommc.modularui.widget.scroll.VerticalScrollData; -import com.cleanroommc.modularui.widget.sizer.Area; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -public class ScrollWidget> extends ParentWidget implements IViewport, Interactable { - - private final ScrollArea scroll = new ScrollArea(); +public class ScrollWidget> extends AbstractScrollWidget implements IParentWidget { public ScrollWidget() { - this(null, null); + super(null, null); } public ScrollWidget(VerticalScrollData data) { - this(null, data); + super(null, data); } public ScrollWidget(HorizontalScrollData data) { - this(data, null); - } - - public ScrollWidget(@Nullable HorizontalScrollData x, @Nullable VerticalScrollData y) { - super(); - this.scroll.setScrollDataX(x); - this.scroll.setScrollDataY(y); - listenGuiAction((IGuiAction.MouseReleased) mouseButton -> { - this.scroll.mouseReleased(getContext()); - return false; - }); - } - - @Override - public Area getArea() { - return this.scroll; - } - - public ScrollArea getScrollArea() { - return this.scroll; - } - - @Override - public void transformChildren(IViewportStack stack) { - stack.translate(-getScrollX(), -getScrollY()); - } - - @Override - public void getSelfAt(IViewportStack stack, HoveredWidgetList widgets, int x, int y) { - if (isInside(stack, x, y)) { - widgets.add(this, stack.peek()); - } + super(data, null); } @Override - public void getWidgetsAt(IViewportStack stack, HoveredWidgetList widgets, int x, int y) { - if (getArea().isInside(x, y) && !getScrollArea().isInsideScrollbarArea(x, y) && hasChildren()) { - IViewport.getChildrenAt(this, stack, widgets, x, y); - } - } - - @Override - public void onResized() { - if (this.scroll.getScrollX() != null) { - this.scroll.getScrollX().clamp(this.scroll); - } - if (this.scroll.getScrollY() != null) { - this.scroll.getScrollY().clamp(this.scroll); - } - } - - @Override - public boolean canHover() { - return super.canHover() || this.scroll.isInsideScrollbarArea(getContext().getMouseX(), getContext().getMouseY()); - } - - @Override - public @NotNull Result onMousePressed(int mouseButton) { - ModularGuiContext context = getContext(); - if (this.scroll.mouseClicked(context)) { - return Result.STOP; - } - return Result.IGNORE; - } - - @Override - public boolean onMouseScroll(ModularScreen.UpOrDown scrollDirection, int amount) { - return this.scroll.mouseScroll(getContext()); - } - - @Override - public boolean onMouseRelease(int mouseButton) { - this.scroll.mouseReleased(getContext()); - return false; - } - - @Override - public void onUpdate() { - super.onUpdate(); - this.scroll.drag(getContext().getAbsMouseX(), getContext().getAbsMouseY()); - } - - @Override - public void preDraw(ModularGuiContext context, boolean transformed) { - if (!transformed) { - Stencil.applyAtZero(this.scroll, context); - } - } - - @Override - public void postDraw(ModularGuiContext context, boolean transformed) { - if (!transformed) { - Stencil.remove(); - this.scroll.drawScrollbar(); - } - } - - public int getScrollX() { - return this.scroll.getScrollX() != null ? this.scroll.getScrollX().getScroll() : 0; - } - - public int getScrollY() { - return this.scroll.getScrollY() != null ? this.scroll.getScrollY().getScroll() : 0; + public boolean addChild(IWidget child, int index) { + return super.addChild(child, index); } } diff --git a/src/main/java/com/cleanroommc/modularui/widget/scroll/HorizontalScrollData.java b/src/main/java/com/cleanroommc/modularui/widget/scroll/HorizontalScrollData.java index 72f2fc708..f70962458 100644 --- a/src/main/java/com/cleanroommc/modularui/widget/scroll/HorizontalScrollData.java +++ b/src/main/java/com/cleanroommc/modularui/widget/scroll/HorizontalScrollData.java @@ -5,18 +5,39 @@ public class HorizontalScrollData extends ScrollData { + /** + * Creates horizontal scroll data which handles scrolling and scroll bar. + * Scrollbar is 4 pixel high and is placed at the bottom. + */ public HorizontalScrollData() { - this(false); + this(false, DEFAULT_THICKNESS); } + /** + * Creates horizontal scroll data which handles scrolling and scroll bar. + * Scrollbar is 4 pixel high. + * + * @param topAlignment if the scroll bar should be placed at the top + */ public HorizontalScrollData(boolean topAlignment) { - this(topAlignment, 4); + this(topAlignment, DEFAULT_THICKNESS); } + /** + * Creates horizontal scroll data which handles scrolling and scroll bar. + * + * @param topAlignment if the scroll bar should be placed at the top + * @param thickness height of the scroll bar in pixel + */ public HorizontalScrollData(boolean topAlignment, int thickness) { super(GuiAxis.X, topAlignment, thickness); } + public HorizontalScrollData cancelScrollEdge(boolean cancelScrollEdge) { + setCancelScrollEdge(cancelScrollEdge); + return this; + } + @Override public float getProgress(ScrollArea area, int x, int y) { return (x - area.x) / (float) getFullVisibleSize(area); diff --git a/src/main/java/com/cleanroommc/modularui/widget/scroll/ScrollArea.java b/src/main/java/com/cleanroommc/modularui/widget/scroll/ScrollArea.java index 3e4f23f5b..d507c7f0e 100644 --- a/src/main/java/com/cleanroommc/modularui/widget/scroll/ScrollArea.java +++ b/src/main/java/com/cleanroommc/modularui/widget/scroll/ScrollArea.java @@ -25,8 +25,7 @@ public ScrollArea(int x, int y, int w, int h) { super(x, y, w, h); } - public ScrollArea() { - } + public ScrollArea() {} public void setScrollData(ScrollData data) { if (data instanceof HorizontalScrollData scrollData) { @@ -92,7 +91,10 @@ public boolean mouseScroll(GuiContext context) { * This method should be invoked when mouse wheel is scrolling */ public boolean mouseScroll(int x, int y, int scroll, boolean shift) { - if (!isInside(x, y)) return false; + if (!isInside(x, y)) { + // not hovering TODO: this shouldnt be required + return false; + } ScrollData data; if (this.scrollX != null) { @@ -100,6 +102,7 @@ public boolean mouseScroll(int x, int y, int scroll, boolean shift) { } else if (this.scrollY != null) { data = this.scrollY; } else { + // no scroll data present -> cant be scrolled return false; } @@ -120,9 +123,7 @@ public boolean mouseScroll(int x, int y, int scroll, boolean shift) { data.animateTo(this, scrollTo); return true; } - - //return data.cancelScrollEdge; - return false; + return data.isCancelScrollEdge(); } @SideOnly(Side.CLIENT) diff --git a/src/main/java/com/cleanroommc/modularui/widget/scroll/ScrollData.java b/src/main/java/com/cleanroommc/modularui/widget/scroll/ScrollData.java index 00bad4cbd..9758d0b5f 100644 --- a/src/main/java/com/cleanroommc/modularui/widget/scroll/ScrollData.java +++ b/src/main/java/com/cleanroommc/modularui/widget/scroll/ScrollData.java @@ -13,10 +13,48 @@ public abstract class ScrollData { + /** + * Creates scroll data which handles scrolling and scroll bar. Scrollbar is 4 pixel thick + * and will be at the end of the cross axis (bottom/right). + * + * @param axis axis on which to scroll + * @return new scroll data + */ + public static ScrollData of(GuiAxis axis) { + return of(axis, false, DEFAULT_THICKNESS); + } + + /** + * Creates scroll data which handles scrolling and scroll bar. Scrollbar is 4 pixel thick. + * + * @param axis axis on which to scroll + * @param axisStart if the scroll bar should be at the start of the cross axis (left/top) + * @return new scroll data + */ + public static ScrollData of(GuiAxis axis, boolean axisStart) { + return of(axis, axisStart, DEFAULT_THICKNESS); + } + + /** + * Creates scroll data which handles scrolling and scroll bar. + * + * @param axis axis on which to scroll + * @param axisStart if the scroll bar should be at the start of the cross axis (left/top) + * @param thickness cross axis thickness of the scroll bar in pixel + * @return new scroll data + */ + public static ScrollData of(GuiAxis axis, boolean axisStart, int thickness) { + if (axis.isHorizontal()) return new HorizontalScrollData(axisStart, thickness); + return new VerticalScrollData(axisStart, thickness); + } + + public static final int DEFAULT_THICKNESS = 4; + private final GuiAxis axis; private final boolean axisStart; private final int thickness; private int scrollSpeed = 30; + private boolean cancelScrollEdge = true; private int scrollSize; private int scroll; @@ -75,6 +113,21 @@ public boolean isHorizontal() { return this.axis.isHorizontal(); } + /** + * Determines if scrolling of widgets below should still be canceled if this scroll view + * has hit the end and is currently not scrolling. + * Most of the time this should be true + * + * @return true if scrolling should be canceled even when this view hit an edge + */ + public boolean isCancelScrollEdge() { + return cancelScrollEdge; + } + + public void setCancelScrollEdge(boolean cancelScrollEdge) { + this.cancelScrollEdge = cancelScrollEdge; + } + protected final int getRawVisibleSize(ScrollArea area) { return Math.max(0, getRawFullVisibleSize(area) - area.getPadding().getTotal(this.axis)); } diff --git a/src/main/java/com/cleanroommc/modularui/widget/scroll/VerticalScrollData.java b/src/main/java/com/cleanroommc/modularui/widget/scroll/VerticalScrollData.java index 48aaee923..ae9fd5e38 100644 --- a/src/main/java/com/cleanroommc/modularui/widget/scroll/VerticalScrollData.java +++ b/src/main/java/com/cleanroommc/modularui/widget/scroll/VerticalScrollData.java @@ -5,18 +5,39 @@ public class VerticalScrollData extends ScrollData { + /** + * Creates vertical scroll data which handles scrolling and scroll bar. + * Scrollbar is 4 pixel wide and is placed on the right. + */ public VerticalScrollData() { - this(false); + this(false, DEFAULT_THICKNESS); } + /** + * Creates vertical scroll data which handles scrolling and scroll bar. + * Scrollbar is 4 pixel wide. + * + * @param leftAlignment if the scroll bar should be placed on the left + */ public VerticalScrollData(boolean leftAlignment) { - this(leftAlignment, 4); + this(leftAlignment, DEFAULT_THICKNESS); } + /** + * Creates vertical scroll data which handles scrolling and scroll bar. + * + * @param leftAlignment if the scroll bar should be placed on the left + * @param thickness width of the scroll bar in pixel + */ public VerticalScrollData(boolean leftAlignment, int thickness) { super(GuiAxis.Y, leftAlignment, thickness); } + public VerticalScrollData cancelScrollEdge(boolean cancelScrollEdge) { + setCancelScrollEdge(cancelScrollEdge); + return this; + } + @Override public float getProgress(ScrollArea area, int x, int y) { return (y - area.y) / (float) getFullVisibleSize(area); diff --git a/src/main/java/com/cleanroommc/modularui/widgets/CategoryList.java b/src/main/java/com/cleanroommc/modularui/widgets/CategoryList.java index 908c69428..906e94285 100644 --- a/src/main/java/com/cleanroommc/modularui/widgets/CategoryList.java +++ b/src/main/java/com/cleanroommc/modularui/widgets/CategoryList.java @@ -8,7 +8,7 @@ import com.cleanroommc.modularui.screen.viewport.ModularGuiContext; import com.cleanroommc.modularui.theme.WidgetTheme; import com.cleanroommc.modularui.utils.Alignment; -import com.cleanroommc.modularui.widget.ParentWidget; +import com.cleanroommc.modularui.widget.AbstractParentWidget; import com.cleanroommc.modularui.widget.WidgetTree; import org.jetbrains.annotations.NotNull; @@ -16,7 +16,7 @@ import java.util.ArrayList; import java.util.List; -public class CategoryList extends ParentWidget implements Interactable, ILayoutWidget { +public class CategoryList extends AbstractParentWidget implements Interactable, ILayoutWidget { private final List subCategories = new ArrayList<>(); private boolean expanded = false; @@ -120,7 +120,7 @@ public CategoryList setExpandedOverlay(IDrawable expandedOverlay) { return this; } - public static class Root extends ListWidget { + public static class Root extends ListWidget { private final List categories = new ArrayList<>(); diff --git a/src/main/java/com/cleanroommc/modularui/widgets/Dialog.java b/src/main/java/com/cleanroommc/modularui/widgets/Dialog.java index 33e77c1b9..a7849535a 100644 --- a/src/main/java/com/cleanroommc/modularui/widgets/Dialog.java +++ b/src/main/java/com/cleanroommc/modularui/widgets/Dialog.java @@ -11,6 +11,10 @@ public class Dialog extends ModularPanel { private boolean disablePanelsBelow = true; private boolean closeOnOutOfBoundsClick = false; + public Dialog(String name) { + this(name, null); + } + public Dialog(String name, Consumer resultConsumer) { super(name); this.resultConsumer = resultConsumer; diff --git a/src/main/java/com/cleanroommc/modularui/widgets/ListValueWidget.java b/src/main/java/com/cleanroommc/modularui/widgets/ListValueWidget.java new file mode 100644 index 000000000..0ebfc235a --- /dev/null +++ b/src/main/java/com/cleanroommc/modularui/widgets/ListValueWidget.java @@ -0,0 +1,31 @@ +package com.cleanroommc.modularui.widgets; + +import com.cleanroommc.modularui.api.widget.IWidget; + +import java.util.ArrayList; +import java.util.List; +import java.util.function.Function; + +public class ListValueWidget> extends ListWidget { + + private final Function widgetToValue; + + public ListValueWidget(Function widgetToValue) { + this.widgetToValue = widgetToValue; + } + + public List getValues() { + List list = new ArrayList<>(); + for (I widget : getTypeChildren()) { + list.add(this.widgetToValue.apply(widget)); + } + return list; + } + + public W children(Iterable values, Function widgetCreator) { + for (V value : values) { + child(widgetCreator.apply(value)); + } + return getThis(); + } +} diff --git a/src/main/java/com/cleanroommc/modularui/widgets/ListWidget.java b/src/main/java/com/cleanroommc/modularui/widgets/ListWidget.java index e75884cce..9aed35764 100644 --- a/src/main/java/com/cleanroommc/modularui/widgets/ListWidget.java +++ b/src/main/java/com/cleanroommc/modularui/widgets/ListWidget.java @@ -1,65 +1,45 @@ package com.cleanroommc.modularui.widgets; import com.cleanroommc.modularui.api.GuiAxis; +import com.cleanroommc.modularui.api.drawable.IIcon; import com.cleanroommc.modularui.api.layout.ILayoutWidget; -import com.cleanroommc.modularui.api.widget.IValueWidget; +import com.cleanroommc.modularui.api.widget.IParentWidget; import com.cleanroommc.modularui.api.widget.IWidget; -import com.cleanroommc.modularui.widget.ScrollWidget; -import com.cleanroommc.modularui.widget.WidgetTree; +import com.cleanroommc.modularui.screen.viewport.ModularGuiContext; +import com.cleanroommc.modularui.theme.WidgetTheme; +import com.cleanroommc.modularui.widget.AbstractScrollWidget; +import com.cleanroommc.modularui.widget.scroll.HorizontalScrollData; import com.cleanroommc.modularui.widget.scroll.ScrollData; import com.cleanroommc.modularui.widget.scroll.VerticalScrollData; -import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; +import it.unimi.dsi.fastutil.ints.IntArrayList; +import it.unimi.dsi.fastutil.ints.IntList; import org.jetbrains.annotations.Nullable; -import java.util.Collection; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.function.Function; -import java.util.stream.Collectors; +import java.util.function.IntFunction; -public class ListWidget> extends ScrollWidget implements ILayoutWidget { - - protected final Function valueToWidgetMapper; - protected final Function widgetToValueMapper; +/** + * A widget which can hold any amount of children. + * + * @param type of children (in most cases just {@link IWidget}) + * @param type of this widget + */ +public class ListWidget> extends AbstractScrollWidget implements ILayoutWidget, IParentWidget { private ScrollData scrollData; + private IIcon childSeparator; + private final IntList separatorPositions = new IntArrayList(); private boolean keepScrollBarInArea = false; public ListWidget() { - this(v -> null, w -> null); - } - - public ListWidget(Collection widgets) { - this(); - for (IWidget widget : widgets) { - child(widget); - } - } - - public ListWidget(Function valueToWidgetMapper, Function widgetToValueMapper) { - super(new VerticalScrollData()); - this.valueToWidgetMapper = Objects.requireNonNull(valueToWidgetMapper); - this.widgetToValueMapper = Objects.requireNonNull(widgetToValueMapper); - this.scrollData = getScrollArea().getScrollY(); + super(null, null); } - public static & IWidget, W extends ListWidget> ListWidget of(Function valueToWidgetMapper) { - return new ListWidget<>(valueToWidgetMapper, IValueWidget::getWidgetValue); - } - - public static > ListWidget builder(Iterable iterable, Function creator) { - Map map = new Object2ObjectOpenHashMap<>(); - Map map_reverse = new Object2ObjectOpenHashMap<>(); - ListWidget listWidget = new ListWidget<>(map::get, map_reverse::get); - for (T t : iterable) { - I widget = creator.apply(t); - map.put(t, widget); - map_reverse.put(widget, t); - listWidget.child(widget); + @Override + public void onInit() { + if (this.scrollData == null) { + scrollDirection(new VerticalScrollData()); } - return listWidget; } @Override @@ -72,38 +52,33 @@ public void onResized() { } } - public boolean add(T value, int index) { - if (addChild(this.valueToWidgetMapper.apply(value), index)) { - if (isValid()) { - WidgetTree.resize(this); - } - return true; + @Override + public void draw(ModularGuiContext context, WidgetTheme widgetTheme) { + if (this.childSeparator == null || this.separatorPositions.isEmpty()) return; + GuiAxis axis = this.scrollData.getAxis(); + int x = getArea().getPadding().left, y = getArea().getPadding().top, w, h; + if (axis.isHorizontal()) { + w = this.childSeparator.getWidth(); + h = getArea().h() - getArea().getPadding().vertical(); + } else { + w = getArea().w() - getArea().getPadding().horizontal(); + h = this.childSeparator.getHeight(); } - return false; - } - - public boolean add(T value) { - return add(value, getChildren().size()); - } - - @Nullable - public IWidget remove(T value) { - IWidget widget = this.valueToWidgetMapper.apply(value); - if (remove(widget)) { - return widget; + for (int p : this.separatorPositions) { + if (axis.isHorizontal()) { + x = p; + } else { + y = p; + } + this.childSeparator.draw(context, x, y, w, h, widgetTheme); } - return null; - } - - public List getValues() { - return getChildren().stream() - .map(widget -> this.widgetToValueMapper.apply((I) widget)) - .collect(Collectors.toList()); } @Override public void layoutWidgets() { + this.separatorPositions.clear(); GuiAxis axis = this.scrollData.getAxis(); + int separatorSize = getSeparatorSize(); int p = getArea().getPadding().getStart(axis); for (IWidget widget : getChildren()) { if (axis.isVertical() ? @@ -119,20 +94,50 @@ public void layoutWidgets() { } else { widget.resizer().setYResized(true); } + this.separatorPositions.add(p); + p += separatorSize; } getScrollData().setScrollSize(p + getArea().getPadding().getEnd(axis)); } + @Override + public boolean addChild(I child, int index) { + return super.addChild(child, index); + } + + @Override + public void onChildAdd(I child) { + super.onChildAdd(child); + if (isValid()) { + this.scrollData.clamp(getScrollArea()); + } + } + + @Override + public void onChildRemove(I child) { + super.onChildRemove(child); + if (isValid()) { + this.scrollData.clamp(getScrollArea()); + } + } + + public int getSeparatorSize() { + if (this.childSeparator == null) return 0; + return this.scrollData.getAxis().isHorizontal() ? this.childSeparator.getWidth() : this.childSeparator.getHeight(); + } + public ScrollData getScrollData() { return this.scrollData; } + public W scrollDirection(GuiAxis axis) { + return scrollDirection(ScrollData.of(axis)); + } + public W scrollDirection(ScrollData data) { - if (this.scrollData.getAxis() != data.getAxis()) { - this.scrollData = data; - getScrollArea().removeScrollData(); - getScrollArea().setScrollData(this.scrollData); - } + this.scrollData = data; + getScrollArea().removeScrollData(); + getScrollArea().setScrollData(this.scrollData); return getThis(); } @@ -140,4 +145,23 @@ public W keepScrollBarInArea() { this.keepScrollBarInArea = true; return getThis(); } + + public W childSeparator(IIcon separator) { + this.childSeparator = separator; + return getThis(); + } + + public W children(Iterable widgets) { + for (I widget : widgets) { + child(widget); + } + return getThis(); + } + + public W children(int amount, IntFunction widgetCreator) { + for (int i = 0; i < amount; i++) { + child(widgetCreator.apply(i)); + } + return getThis(); + } } diff --git a/src/main/java/com/cleanroommc/modularui/widgets/ListWidgetOld.java b/src/main/java/com/cleanroommc/modularui/widgets/ListWidgetOld.java new file mode 100644 index 000000000..5df3b1c9b --- /dev/null +++ b/src/main/java/com/cleanroommc/modularui/widgets/ListWidgetOld.java @@ -0,0 +1,142 @@ +package com.cleanroommc.modularui.widgets; +/* +import com.cleanroommc.modularui.api.GuiAxis; +import com.cleanroommc.modularui.api.layout.ILayoutWidget; +import com.cleanroommc.modularui.api.widget.IValueWidget; +import com.cleanroommc.modularui.api.widget.IWidget; +import com.cleanroommc.modularui.widget.ScrollWidget; +import com.cleanroommc.modularui.widget.WidgetTree; +import com.cleanroommc.modularui.widget.scroll.ScrollData; +import com.cleanroommc.modularui.widget.scroll.VerticalScrollData; +import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; +import org.jetbrains.annotations.Nullable; + +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.function.Function; +import java.util.stream.Collectors; + +public class ListWidgetOld> extends ScrollWidget implements ILayoutWidget { + + protected final Function valueToWidgetMapper; + protected final Function widgetToValueMapper; + + private ScrollData scrollData; + private boolean keepScrollBarInArea = false; + + public ListWidgetOld() { + this(v -> null, w -> null); + } + + public ListWidgetOld(Collection widgets) { + this(); + for (IWidget widget : widgets) { + child(widget); + } + } + + public ListWidgetOld(Function valueToWidgetMapper, Function widgetToValueMapper) { + super(new VerticalScrollData()); + this.valueToWidgetMapper = Objects.requireNonNull(valueToWidgetMapper); + this.widgetToValueMapper = Objects.requireNonNull(widgetToValueMapper); + this.scrollData = getScrollArea().getScrollY(); + } + + public static & IWidget, W extends ListWidgetOld> ListWidgetOld of(Function valueToWidgetMapper) { + return new ListWidgetOld<>(valueToWidgetMapper, IValueWidget::getWidgetValue); + } + + public static > ListWidgetOld builder(Iterable iterable, Function creator) { + Map map = new Object2ObjectOpenHashMap<>(); + Map map_reverse = new Object2ObjectOpenHashMap<>(); + ListWidgetOld listWidget = new ListWidgetOld<>(map::get, map_reverse::get); + for (T t : iterable) { + I widget = creator.apply(t); + map.put(t, widget); + map_reverse.put(widget, t); + listWidget.child(widget); + } + return listWidget; + } + + @Override + public void onResized() { + if (this.keepScrollBarInArea) return; + if (this.scrollData.isVertical()) { + getArea().width += this.scrollData.getThickness(); + } else { + getArea().height += this.scrollData.getThickness(); + } + } + + public boolean add(T value, int index) { + if (addChild(this.valueToWidgetMapper.apply(value), index)) { + if (isValid()) { + WidgetTree.resize(this); + } + return true; + } + return false; + } + + public boolean add(T value) { + return add(value, getChildren().size()); + } + + @Nullable + public IWidget remove(T value) { + IWidget widget = this.valueToWidgetMapper.apply(value); + if (remove(widget)) { + return widget; + } + return null; + } + + public List getValues() { + return getChildren().stream() + .map(widget -> this.widgetToValueMapper.apply((I) widget)) + .collect(Collectors.toList()); + } + + @Override + public void layoutWidgets() { + GuiAxis axis = this.scrollData.getAxis(); + int p = getArea().getPadding().getStart(axis); + for (IWidget widget : getChildren()) { + if (axis.isVertical() ? + widget.getFlex().hasYPos() || !widget.resizer().isHeightCalculated() : + widget.getFlex().hasXPos() || !widget.resizer().isWidthCalculated()) { + continue; + } + p += widget.getArea().getMargin().getStart(axis); + widget.getArea().setRelativePoint(axis, p); + p += widget.getArea().getSize(axis) + widget.getArea().getMargin().getEnd(axis); + if (axis.isHorizontal()) { + widget.resizer().setXResized(true); + } else { + widget.resizer().setYResized(true); + } + } + getScrollData().setScrollSize(p + getArea().getPadding().getEnd(axis)); + } + + public ScrollData getScrollData() { + return this.scrollData; + } + + public W scrollDirection(ScrollData data) { + if (this.scrollData.getAxis() != data.getAxis()) { + this.scrollData = data; + getScrollArea().removeScrollData(); + getScrollArea().setScrollData(this.scrollData); + } + return getThis(); + } + + public W keepScrollBarInArea() { + this.keepScrollBarInArea = true; + return getThis(); + } +}*/ diff --git a/src/main/java/com/cleanroommc/modularui/widgets/SortableListWidget.java b/src/main/java/com/cleanroommc/modularui/widgets/SortableListWidget.java index 7f0afe198..fb43bb16f 100644 --- a/src/main/java/com/cleanroommc/modularui/widgets/SortableListWidget.java +++ b/src/main/java/com/cleanroommc/modularui/widgets/SortableListWidget.java @@ -8,46 +8,29 @@ import com.cleanroommc.modularui.widget.DraggableWidget; import com.cleanroommc.modularui.widget.WidgetTree; -import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import java.util.*; +import java.util.Collections; +import java.util.List; import java.util.function.Consumer; import java.util.function.Function; import java.util.function.Predicate; -public class SortableListWidget> extends ListWidget> { - - public static > SortableListWidget sortableBuilder(Collection fullList, List list, Function builder) { - Objects.requireNonNull(list); - Objects.requireNonNull(builder); - Map map = new Object2ObjectOpenHashMap<>(); - SortableListWidget sortableListWidget = new SortableListWidget<>(map::get); - for (T t : fullList) { - I item = builder.apply(t); - map.put(t, item); - } - for (T t : list) { - if (!fullList.contains(t)) { - throw new IllegalArgumentException("Elements from list must also be inside the full list!"); - } - sortableListWidget.add(t, -1); - } - return sortableListWidget; - } +public class SortableListWidget extends ListValueWidget, SortableListWidget> { private Consumer> onChange; private Consumer> onRemove; private int timeSinceLastMove = 0; - public SortableListWidget(Function valueToWidgetMapper) { - super(valueToWidgetMapper, Item::getWidgetValue); - width(80); + public SortableListWidget() { + super(Item::getWidgetValue); + heightRel(1f); } @Override public void onInit() { + super.onInit(); assignIndexes(); } @@ -58,22 +41,8 @@ public void onUpdate() { } @Override - public boolean addChild(IWidget child, int index) { - T value = this.widgetToValueMapper.apply((I) child); - if (child != this.valueToWidgetMapper.apply(value)) { - throw new IllegalArgumentException(); - } - ((Item) child).listWidget = this; - if (super.addChild(child, index)) { - if (isValid()) { - assignIndexes(); - if (this.onChange != null) { - this.onChange.accept(getValues()); - } - } - return true; - } - return false; + public int getDefaultWidth() { + return 80; } public void moveTo(int from, int to) { @@ -82,7 +51,7 @@ public void moveTo(int from, int to) { ModularUI.LOGGER.error("Failed to move element from {} to {}", from, to); return; } - Item child = (Item) getChildren().remove(from); + SortableListWidget.Item child = getTypeChildren().remove(from); getChildren().add(to, child); assignIndexes(); if (isValid()) { @@ -94,9 +63,11 @@ public void moveTo(int from, int to) { this.timeSinceLastMove = 0; } + @Override public boolean remove(int index) { - IWidget widget = getChildren().remove(index); + Item widget = getTypeChildren().remove(index); if (widget != null) { + onChildRemove(widget); assignIndexes(); if (isValid()) { WidgetTree.resize(this); @@ -105,26 +76,35 @@ public boolean remove(int index) { this.onChange.accept(getValues()); } if (this.onRemove != null) { - this.onRemove.accept((Item) widget); + this.onRemove.accept(widget); } return true; } return false; } + @Override + public void onChildAdd(Item child) { + if (isValid()) { + assignIndexes(); + if (this.onChange != null) this.onChange.accept(getValues()); + WidgetTree.resize(this); + } + } + private void assignIndexes() { - for (int i = 0; i < getChildren().size(); i++) { - Item item = (Item) getChildren().get(i); - item.index = i; + List> children = getTypeChildren(); + for (int i = 0; i < children.size(); i++) { + children.get(i).index = i; } } - public SortableListWidget onChange(Consumer> onChange) { + public SortableListWidget onChange(Consumer> onChange) { this.onChange = onChange; return this; } - public SortableListWidget onRemove(Consumer> onRemove) { + public SortableListWidget onRemove(Consumer> onRemove) { this.onRemove = onRemove; return this; } @@ -132,18 +112,13 @@ public SortableListWidget onRemove(Consumer> onRemove) { public static class Item extends DraggableWidget> implements IValueWidget { private final T value; - private final IWidget content; - private ButtonWidget> removeButton; - private final List children = new ArrayList<>(); + private List children; private Predicate dropPredicate; - private SortableListWidget> listWidget; + private SortableListWidget listWidget; private int index = -1; - public Item(T value, IWidget content) { + public Item(T value) { this.value = value; - this.content = content; - this.children.add(content); - this.content.flex().heightRel(1f); flex().widthRel(1f).height(18); background(GuiTextures.BUTTON_CLEAN); } @@ -151,16 +126,15 @@ public Item(T value, IWidget content) { @Override public void onInit() { super.onInit(); - if (this.removeButton != null) { - - this.children.add(this.removeButton); + if (getParent() instanceof SortableListWidget sortableListWidget) { + this.listWidget = (SortableListWidget) sortableListWidget; } } @NotNull @Override public List getChildren() { - return this.children; + return this.children != null ? this.children : Collections.emptyList(); } @Override @@ -171,8 +145,8 @@ public boolean canDropHere(int x, int y, @Nullable IGuiElement widget) { @Override public void onDrag(int mouseButton, long timeSinceLastClick) { super.onDrag(mouseButton, timeSinceLastClick); - IGuiElement hovered = getContext().getHovered(); - Item item = (Item) WidgetTree.findParent(hovered, guiElement -> guiElement instanceof Item); + IWidget hovered = getContext().getHovered(); + SortableListWidget.Item item = WidgetTree.findParent(hovered, Item.class); if (item != null && item != this && item.listWidget == this.listWidget) { this.listWidget.moveTo(this.index, item.index); } @@ -191,12 +165,27 @@ public int getIndex() { return this.index; } + public boolean removeSelfFromList() { + this.listWidget.remove(this.index); + return true; + } + + public Item child(IWidget widget) { + this.children = Collections.singletonList(widget); + if (isValid()) widget.initialise(this); + return this; + } + + public Item child(Function, IWidget> widgetCreator) { + return child(widgetCreator.apply(this)); + } + public Item dropPredicate(Predicate dropPredicate) { this.dropPredicate = dropPredicate; return this; } - public Item removeable() { + /*public Item removeable() { this.removeButton = new ButtonWidget<>() .onMousePressed(mouseButton -> this.listWidget.remove(this.index)) .background(GuiTextures.CLOSE.asIcon()) @@ -209,6 +198,6 @@ public Item removeable(Consumer>> butt removeable(); buttonBuilder.accept(this.removeButton); return this; - } + }*/ } } diff --git a/src/main/java/com/cleanroommc/modularui/widgets/SortableListWidgetOld.java b/src/main/java/com/cleanroommc/modularui/widgets/SortableListWidgetOld.java new file mode 100644 index 000000000..cfdc2bf0a --- /dev/null +++ b/src/main/java/com/cleanroommc/modularui/widgets/SortableListWidgetOld.java @@ -0,0 +1,214 @@ +package com.cleanroommc.modularui.widgets; +/* +import com.cleanroommc.modularui.ModularUI; +import com.cleanroommc.modularui.api.widget.IGuiElement; +import com.cleanroommc.modularui.api.widget.IValueWidget; +import com.cleanroommc.modularui.api.widget.IWidget; +import com.cleanroommc.modularui.drawable.GuiTextures; +import com.cleanroommc.modularui.widget.DraggableWidget; +import com.cleanroommc.modularui.widget.WidgetTree; + +import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.*; +import java.util.function.Consumer; +import java.util.function.Function; +import java.util.function.Predicate; + +public class SortableListWidgetOld> extends ListValueWidget> { + + public static > SortableListWidgetOld sortableBuilder(Collection fullList, List list, Function builder) { + Objects.requireNonNull(list); + Objects.requireNonNull(builder); + Map map = new Object2ObjectOpenHashMap<>(); + SortableListWidgetOld sortableListWidget = new SortableListWidgetOld<>(map::get); + for (T t : fullList) { + I item = builder.apply(t); + map.put(t, item); + } + for (T t : list) { + if (!fullList.contains(t)) { + throw new IllegalArgumentException("Elements from list must also be inside the full list!"); + } + sortableListWidget.add(t, -1); + } + return sortableListWidget; + } + + private Consumer> onChange; + private Consumer> onRemove; + private int timeSinceLastMove = 0; + + public SortableListWidgetOld(Function valueToWidgetMapper) { + super(valueToWidgetMapper, Item::getWidgetValue); + width(80); + } + + @Override + public void onInit() { + assignIndexes(); + } + + @Override + public void onUpdate() { + super.onUpdate(); + this.timeSinceLastMove++; + } + + @Override + public boolean addChild(IWidget child, int index) { + T value = this.widgetToValueMapper.apply((I) child); + if (child != this.valueToWidgetMapper.apply(value)) { + throw new IllegalArgumentException(); + } + ((Item) child).listWidget = this; + if (super.addChild(child, index)) { + if (isValid()) { + assignIndexes(); + if (this.onChange != null) { + this.onChange.accept(getValues()); + } + } + return true; + } + return false; + } + + public void moveTo(int from, int to) { + if (this.timeSinceLastMove < 3) return; + if (from < 0 || to < 0 || from == to) { + ModularUI.LOGGER.error("Failed to move element from {} to {}", from, to); + return; + } + Item child = (Item) getChildren().remove(from); + getChildren().add(to, child); + assignIndexes(); + if (isValid()) { + WidgetTree.resize(this); + } + if (this.onChange != null) { + this.onChange.accept(getValues()); + } + this.timeSinceLastMove = 0; + } + + public boolean remove(int index) { + IWidget widget = getChildren().remove(index); + if (widget != null) { + assignIndexes(); + if (isValid()) { + WidgetTree.resize(this); + } + if (this.onChange != null) { + this.onChange.accept(getValues()); + } + if (this.onRemove != null) { + this.onRemove.accept((Item) widget); + } + return true; + } + return false; + } + + private void assignIndexes() { + for (int i = 0; i < getChildren().size(); i++) { + Item item = (Item) getChildren().get(i); + item.index = i; + } + } + + public SortableListWidgetOld onChange(Consumer> onChange) { + this.onChange = onChange; + return this; + } + + public SortableListWidgetOld onRemove(Consumer> onRemove) { + this.onRemove = onRemove; + return this; + } + + public static class Item extends DraggableWidget> implements IValueWidget { + + private final T value; + private final IWidget content; + private ButtonWidget> removeButton; + private final List children = new ArrayList<>(); + private Predicate dropPredicate; + private SortableListWidgetOld> listWidget; + private int index = -1; + + public Item(T value, IWidget content) { + this.value = value; + this.content = content; + this.children.add(content); + this.content.flex().heightRel(1f); + flex().widthRel(1f).height(18); + background(GuiTextures.BUTTON_CLEAN); + } + + @Override + public void onInit() { + super.onInit(); + if (this.removeButton != null) { + + this.children.add(this.removeButton); + } + } + + @NotNull + @Override + public List getChildren() { + return this.children; + } + + @Override + public boolean canDropHere(int x, int y, @Nullable IGuiElement widget) { + return this.dropPredicate == null || this.dropPredicate.test(widget); + } + + @Override + public void onDrag(int mouseButton, long timeSinceLastClick) { + super.onDrag(mouseButton, timeSinceLastClick); + IGuiElement hovered = getContext().getHovered(); + Item item = (Item) WidgetTree.findParent(hovered, guiElement -> guiElement instanceof Item); + if (item != null && item != this && item.listWidget == this.listWidget) { + this.listWidget.moveTo(this.index, item.index); + } + } + + @Override + public void onDragEnd(boolean successful) { + } + + @Override + public T getWidgetValue() { + return this.value; + } + + public int getIndex() { + return this.index; + } + + public Item dropPredicate(Predicate dropPredicate) { + this.dropPredicate = dropPredicate; + return this; + } + + public Item removeable() { + this.removeButton = new ButtonWidget<>() + .onMousePressed(mouseButton -> this.listWidget.remove(this.index)) + .background(GuiTextures.CLOSE.asIcon()) + .width(10).heightRel(1f) + .right(0); + return this; + } + + public Item removeable(Consumer>> buttonBuilder) { + removeable(); + buttonBuilder.accept(this.removeButton); + return this; + } + } +}*/ diff --git a/src/main/java/com/cleanroommc/modularui/widgets/ValueWidget.java b/src/main/java/com/cleanroommc/modularui/widgets/ValueWidget.java new file mode 100644 index 000000000..c93ada003 --- /dev/null +++ b/src/main/java/com/cleanroommc/modularui/widgets/ValueWidget.java @@ -0,0 +1,18 @@ +package com.cleanroommc.modularui.widgets; + +import com.cleanroommc.modularui.api.widget.IValueWidget; +import com.cleanroommc.modularui.widget.Widget; + +public class ValueWidget, T> extends Widget implements IValueWidget { + + private final T widgetValue; + + public ValueWidget(T widgetValue) { + this.widgetValue = widgetValue; + } + + @Override + public T getWidgetValue() { + return widgetValue; + } +} diff --git a/src/main/java/com/cleanroommc/modularui/widgets/VoidWidget.java b/src/main/java/com/cleanroommc/modularui/widgets/VoidWidget.java new file mode 100644 index 000000000..2da4bf8e1 --- /dev/null +++ b/src/main/java/com/cleanroommc/modularui/widgets/VoidWidget.java @@ -0,0 +1,10 @@ +package com.cleanroommc.modularui.widgets; + +import com.cleanroommc.modularui.widget.EmptyWidget; + +public class VoidWidget extends EmptyWidget { + + private VoidWidget() { + throw new UnsupportedOperationException(); + } +} diff --git a/src/main/java/com/cleanroommc/modularui/widgets/layout/Flow.java b/src/main/java/com/cleanroommc/modularui/widgets/layout/Flow.java index 23565a1a6..49b215e97 100644 --- a/src/main/java/com/cleanroommc/modularui/widgets/layout/Flow.java +++ b/src/main/java/com/cleanroommc/modularui/widgets/layout/Flow.java @@ -4,6 +4,7 @@ import com.cleanroommc.modularui.api.layout.ILayoutWidget; import com.cleanroommc.modularui.api.widget.IWidget; import com.cleanroommc.modularui.utils.Alignment; +import com.cleanroommc.modularui.widget.AbstractParentWidget; import com.cleanroommc.modularui.widget.ParentWidget; import com.cleanroommc.modularui.widget.sizer.Box; diff --git a/src/main/java/com/cleanroommc/modularui/widgets/layout/Grid.java b/src/main/java/com/cleanroommc/modularui/widgets/layout/Grid.java index b7ca2fd3d..fe07a783d 100644 --- a/src/main/java/com/cleanroommc/modularui/widgets/layout/Grid.java +++ b/src/main/java/com/cleanroommc/modularui/widgets/layout/Grid.java @@ -1,9 +1,10 @@ package com.cleanroommc.modularui.widgets.layout; import com.cleanroommc.modularui.api.layout.ILayoutWidget; +import com.cleanroommc.modularui.api.widget.IParentWidget; import com.cleanroommc.modularui.api.widget.IWidget; import com.cleanroommc.modularui.utils.Alignment; -import com.cleanroommc.modularui.widget.ScrollWidget; +import com.cleanroommc.modularui.widget.AbstractScrollWidget; import com.cleanroommc.modularui.widget.scroll.HorizontalScrollData; import com.cleanroommc.modularui.widget.scroll.ScrollData; import com.cleanroommc.modularui.widget.scroll.VerticalScrollData; @@ -19,7 +20,7 @@ import java.util.function.IntFunction; import java.util.stream.Collectors; -public class Grid extends ScrollWidget implements ILayoutWidget { +public class Grid extends AbstractScrollWidget implements ILayoutWidget, IParentWidget { private final List> matrix = new ArrayList<>(); private final Box minElementMargin = new Box(); @@ -27,7 +28,9 @@ public class Grid extends ScrollWidget implements ILayoutWidget { private Alignment alignment = Alignment.Center; private boolean dirty = false; - public Grid() {} + public Grid() { + super(null, null); + } @Override public void onInit() { diff --git a/src/main/java/com/cleanroommc/modularui/widgets/layout/OrganizedPanel.java b/src/main/java/com/cleanroommc/modularui/widgets/layout/OrganizedPanel.java deleted file mode 100644 index 9e0f5625b..000000000 --- a/src/main/java/com/cleanroommc/modularui/widgets/layout/OrganizedPanel.java +++ /dev/null @@ -1,146 +0,0 @@ -package com.cleanroommc.modularui.widgets.layout; - -import com.cleanroommc.modularui.api.widget.IWidget; -import com.cleanroommc.modularui.screen.ModularPanel; -import com.cleanroommc.modularui.widget.ParentWidget; - -import org.jetbrains.annotations.ApiStatus; - -/** - * A panel with a header, left sidebar, right sidebar and a footer - */ -@ApiStatus.Experimental -public class OrganizedPanel extends ModularPanel { - - private ParentWidget header; - private ParentWidget leftSide; - private ParentWidget rightSide; - private ParentWidget footer; - private final ParentWidget body = new ParentWidget<>(); - - private int headerHeight = 20; - private int footerHeight = 12; - private int leftSideWidth = 60; - private int rightSideWidth = 60; - - public OrganizedPanel(String name) { - super(name); - getChildren().add(this.body); - } - - @Override - public boolean addChild(IWidget child, int index) { - return this.body.addChild(child, index); - } - - private ParentWidget getHeader() { - if (this.header == null) { - this.header = new ParentWidget<>(); - getChildren().add(this.header); - } - return this.header; - } - - private ParentWidget getLeftSide() { - if (this.leftSide == null) { - this.leftSide = new ParentWidget<>(); - getChildren().add(this.leftSide); - } - return this.leftSide; - } - - private ParentWidget getRightSide() { - if (this.rightSide == null) { - this.rightSide = new ParentWidget<>(); - getChildren().add(this.rightSide); - } - return this.rightSide; - } - - private ParentWidget getFooter() { - if (this.footer == null) { - this.footer = new ParentWidget<>(); - getChildren().add(this.footer); - } - return this.footer; - } - - public ParentWidget getBody() { - return this.body; - } - - @Override - public void beforeResize() { - int top = 0; - int bot = 0; - this.body.flex().reset(); - this.body.left(0).right(0).top(0).bottom(0); - if (this.header != null) { - this.header.flex().reset(); - this.header.left(0).right(0).top(0).height(this.headerHeight); - this.body.top(this.headerHeight); - top = this.headerHeight; - } - if (this.footer != null) { - this.footer.flex().reset(); - this.footer.left(0).right(0).bottom(0).height(this.footerHeight); - this.body.bottom(this.footerHeight); - bot = this.footerHeight; - } - if (this.leftSide != null) { - this.leftSide.flex().reset(); - this.leftSide.left(0).width(this.leftSideWidth).top(top).bottom(bot); - this.body.left(this.leftSideWidth); - } - if (this.rightSide != null) { - this.rightSide.flex().reset(); - this.rightSide.right(0).width(this.rightSideWidth).top(top).bottom(bot); - this.body.right(this.rightSideWidth); - } - } - - public OrganizedPanel header(IWidget widget) { - getHeader().child(widget); - return this; - } - - public OrganizedPanel footer(IWidget widget) { - getFooter().child(widget); - return this; - } - - public OrganizedPanel leftSide(IWidget widget) { - getLeftSide().child(widget); - return this; - } - - public OrganizedPanel rightSide(IWidget widget) { - getRightSide().child(widget); - return this; - } - - public OrganizedPanel setHeaderHeight(int headerHeight) { - this.headerHeight = headerHeight; - return this; - } - - public OrganizedPanel setFooterHeight(int footerHeight) { - this.footerHeight = footerHeight; - return this; - } - - public OrganizedPanel setLeftSideWidth(int leftSideWidth) { - this.leftSideWidth = leftSideWidth; - return this; - } - - public OrganizedPanel setRightSideWidth(int rightSideWidth) { - this.rightSideWidth = rightSideWidth; - return this; - } - - @Override - public OrganizedPanel bindPlayerInventory() { - return (OrganizedPanel) super.bindPlayerInventory(); - } -} diff --git a/src/main/java/com/cleanroommc/modularui/widgets/textfield/BaseTextFieldWidget.java b/src/main/java/com/cleanroommc/modularui/widgets/textfield/BaseTextFieldWidget.java index a268ef88c..b46d5745f 100644 --- a/src/main/java/com/cleanroommc/modularui/widgets/textfield/BaseTextFieldWidget.java +++ b/src/main/java/com/cleanroommc/modularui/widgets/textfield/BaseTextFieldWidget.java @@ -9,10 +9,12 @@ import com.cleanroommc.modularui.screen.viewport.ModularGuiContext; import com.cleanroommc.modularui.theme.WidgetTextFieldTheme; import com.cleanroommc.modularui.utils.Alignment; -import com.cleanroommc.modularui.widget.ScrollWidget; +import com.cleanroommc.modularui.widget.AbstractScrollWidget; import com.cleanroommc.modularui.widget.scroll.HorizontalScrollData; import com.cleanroommc.modularui.widget.scroll.ScrollData; +import com.cleanroommc.modularui.widgets.VoidWidget; + import net.minecraft.client.gui.GuiScreen; import org.jetbrains.annotations.NotNull; @@ -27,7 +29,7 @@ /** * The base of a text input widget. Handles mouse/keyboard input and rendering. */ -public class BaseTextFieldWidget> extends ScrollWidget implements IFocusedWidget { +public class BaseTextFieldWidget> extends AbstractScrollWidget implements IFocusedWidget { public static final DecimalFormat format = new DecimalFormat("###.###"); @@ -54,7 +56,7 @@ public class BaseTextFieldWidget> extends Scrol protected boolean changedTextColor = false; public BaseTextFieldWidget() { - super(new HorizontalScrollData()); + super(new HorizontalScrollData(), null); this.handler.setRenderer(this.renderer); this.handler.setScrollArea(getScrollArea()); padding(4, 0); @@ -66,7 +68,7 @@ public BaseTextFieldWidget() { } @Override - public boolean addChild(IWidget child, int index) { + public boolean isChildValid(VoidWidget child) { return false; }