diff --git a/src/main/java/work/msdnicrosoft/commandbuttons/data/CommandDestination.java b/src/main/java/work/msdnicrosoft/commandbuttons/data/CommandDestination.java index 74dee8e..3019d66 100644 --- a/src/main/java/work/msdnicrosoft/commandbuttons/data/CommandDestination.java +++ b/src/main/java/work/msdnicrosoft/commandbuttons/data/CommandDestination.java @@ -1,12 +1,12 @@ package work.msdnicrosoft.commandbuttons.data; -import io.github.cottonmc.cotton.gui.widget.WButton; import io.github.cottonmc.cotton.gui.widget.WPlainPanel; import lombok.Getter; +import work.msdnicrosoft.commandbuttons.gui.DraggableButton; public class CommandDestination extends WPlainPanel { @Getter - WButton button = new WButton(); + DraggableButton button = new DraggableButton(); public CommandDestination() { //#if MC > 11605 diff --git a/src/main/java/work/msdnicrosoft/commandbuttons/gui/CommandGUI.java b/src/main/java/work/msdnicrosoft/commandbuttons/gui/CommandGUI.java index 5e21f11..28274a8 100644 --- a/src/main/java/work/msdnicrosoft/commandbuttons/gui/CommandGUI.java +++ b/src/main/java/work/msdnicrosoft/commandbuttons/gui/CommandGUI.java @@ -65,6 +65,15 @@ private void createCommandPanel() { private void defBtnBehavior(@NotNull CommandItem item, @NotNull CommandDestination destination) { destination.getButton().setLabel(ComponentUtil.literal(item.getDisplayName())); + destination.getButton().setOnDrag((x, y) -> this.commandPanel.move( + item, + destination.getX() + destination.getButton().getX() + x, + destination.getY() + destination.getButton().getY() + y + )); + destination.getButton().setOnDragEnd(() -> { + ConfigManager.save(); + this.commandPanel.layout(); + }); destination.getButton().setOnClick(() -> { if (this.editBtn.getToggle()) { CommandEditGUI gui = new CommandEditGUI(item, true); diff --git a/src/main/java/work/msdnicrosoft/commandbuttons/gui/CommandListPanel.java b/src/main/java/work/msdnicrosoft/commandbuttons/gui/CommandListPanel.java index 93ae91d..be7fd1e 100644 --- a/src/main/java/work/msdnicrosoft/commandbuttons/gui/CommandListPanel.java +++ b/src/main/java/work/msdnicrosoft/commandbuttons/gui/CommandListPanel.java @@ -21,6 +21,44 @@ public CommandListPanel(List data, Supplier supplier, BiConsumer con this.search = search; } + private List getVisibleData() { + List visibleData = Lists.newArrayList(this.data); + if (!this.search.getText().isEmpty()) { + visibleData = visibleData.stream() + .filter(d -> ((CommandItem) d).getDisplayName().contains(this.search.getText().trim())) + .collect(Collectors.toList()); + } + return visibleData; + } + + /** + * Moves an item to the grid cell currently underneath the mouse pointer. + * The backing list is the same list ConfigManager serializes, so its order is persistent. + */ + public void move(D dragged, int mouseX, int mouseY) { + List visibleData = this.getVisibleData(); + if (visibleData.size() < 2 || !visibleData.contains(dragged)) { + return; + } + + int column = Math.max(0, Math.min(mouseX / 83, 3)); + int row = Math.max(mouseY / 22, 0); + int visibleIndex = this.scrollBar.getValue() + row * 4 + column; + visibleIndex = Math.min(visibleIndex, visibleData.size() - 1); + + D target = visibleData.get(visibleIndex); + if (dragged == target) { + return; + } + + int targetIndex = this.data.indexOf(target); + if (targetIndex < 0 || !this.data.remove(dragged)) { + return; + } + this.data.add(Math.min(targetIndex, this.data.size()), dragged); + this.layout(); + } + /** * Modified from LibGui */ @@ -52,12 +90,7 @@ public void layout() { int layoutHeight = this.getHeight() - 4; int cellsHigh = Math.max((layoutHeight + 2) / (cellHeight + 2), 1); - List data = Lists.newArrayList(this.data); - if (!this.search.getText().isEmpty()) { - data = data.stream() - .filter(d -> ((CommandItem) d).getDisplayName().contains(this.search.getText().trim())) - .collect(Collectors.toList()); - } + List data = this.getVisibleData(); scrollBar.setWindow(cellsHigh); scrollBar.setMaxValue(data.size() > 32 ? data.size() - 8 : 8); diff --git a/src/main/java/work/msdnicrosoft/commandbuttons/gui/DraggableButton.java b/src/main/java/work/msdnicrosoft/commandbuttons/gui/DraggableButton.java new file mode 100644 index 0000000..1a678d6 --- /dev/null +++ b/src/main/java/work/msdnicrosoft/commandbuttons/gui/DraggableButton.java @@ -0,0 +1,147 @@ +package work.msdnicrosoft.commandbuttons.gui; + +import io.github.cottonmc.cotton.gui.widget.WButton; +//#if MC >= 12109 +import net.minecraft.client.input.MouseButtonEvent; +//#endif +//#if MC > 11605 +import io.github.cottonmc.cotton.gui.widget.data.InputResult; +//#else +//$$ import io.github.cottonmc.cotton.gui.widget.WWidget; +//#endif + +import java.util.function.BiConsumer; + +/** + * A regular LibGui button that also reports intentional drag gestures. + * Small mouse movements are kept as normal button clicks. + */ +public class DraggableButton extends WButton { + private static final double DRAG_THRESHOLD = 3.0D; + + private BiConsumer onDrag; + private Runnable onDragEnd; + private double draggedX; + private double draggedY; + private boolean dragging; + private boolean suppressNextClick; + + public DraggableButton setOnDrag(BiConsumer onDrag) { + this.onDrag = onDrag; + return this; + } + + public DraggableButton setOnDragEnd(Runnable onDragEnd) { + this.onDragEnd = onDragEnd; + return this; + } + + private void beginPointerInteraction() { + this.draggedX = 0.0D; + this.draggedY = 0.0D; + this.dragging = false; + this.suppressNextClick = false; + } + + private void drag(int x, int y, double deltaX, double deltaY) { + this.draggedX += deltaX; + this.draggedY += deltaY; + if (!this.dragging && this.draggedX * this.draggedX + this.draggedY * this.draggedY + >= DRAG_THRESHOLD * DRAG_THRESHOLD) { + this.dragging = true; + } + if (this.dragging && this.onDrag != null) { + this.onDrag.accept(x, y); + } + } + + private void endPointerInteraction() { + this.suppressNextClick = this.dragging; + if (this.dragging && this.onDragEnd != null) { + this.onDragEnd.run(); + } + this.dragging = false; + } + + //#if MC >= 12109 + @Override + public InputResult onMouseDown(MouseButtonEvent click, boolean doubled) { + this.beginPointerInteraction(); + return super.onMouseDown(click, doubled); + } + + @Override + public InputResult onMouseDrag(MouseButtonEvent click, double offsetX, double offsetY) { + this.drag((int) click.x(), (int) click.y(), offsetX, offsetY); + return this.dragging ? InputResult.PROCESSED : super.onMouseDrag(click, offsetX, offsetY); + } + + @Override + public InputResult onMouseUp(MouseButtonEvent click) { + this.endPointerInteraction(); + return super.onMouseUp(click); + } + + @Override + public InputResult onClick(MouseButtonEvent click, boolean doubled) { + if (this.suppressNextClick) { + this.suppressNextClick = false; + return InputResult.PROCESSED; + } + return super.onClick(click, doubled); + } + //#elseif MC > 11605 + //$$ @Override + //$$ public InputResult onMouseDown(int x, int y, int button) { + //$$ this.beginPointerInteraction(); + //$$ return super.onMouseDown(x, y, button); + //$$ } + //$$ + //$$ @Override + //$$ public InputResult onMouseDrag(int x, int y, int button, double deltaX, double deltaY) { + //$$ this.drag(x, y, deltaX, deltaY); + //$$ return this.dragging ? InputResult.PROCESSED : super.onMouseDrag(x, y, button, deltaX, deltaY); + //$$ } + //$$ + //$$ @Override + //$$ public InputResult onMouseUp(int x, int y, int button) { + //$$ this.endPointerInteraction(); + //$$ return super.onMouseUp(x, y, button); + //$$ } + //$$ + //$$ @Override + //$$ public InputResult onClick(int x, int y, int button) { + //$$ if (this.suppressNextClick) { + //$$ this.suppressNextClick = false; + //$$ return InputResult.PROCESSED; + //$$ } + //$$ return super.onClick(x, y, button); + //$$ } + //#else + //$$ @Override + //$$ public WWidget onMouseDown(int x, int y, int button) { + //$$ this.beginPointerInteraction(); + //$$ return super.onMouseDown(x, y, button); + //$$ } + //$$ + //$$ @Override + //$$ public void onMouseDrag(int x, int y, int button, double deltaX, double deltaY) { + //$$ this.drag(x, y, deltaX, deltaY); + //$$ } + //$$ + //$$ @Override + //$$ public WWidget onMouseUp(int x, int y, int button) { + //$$ this.endPointerInteraction(); + //$$ return super.onMouseUp(x, y, button); + //$$ } + //$$ + //$$ @Override + //$$ public void onClick(int x, int y, int button) { + //$$ if (this.suppressNextClick) { + //$$ this.suppressNextClick = false; + //$$ return; + //$$ } + //$$ super.onClick(x, y, button); + //$$ } + //#endif +}