diff --git a/examples/paper/src/main/java/me/devnatan/inventoryframework/runtime/SamplePlugin.java b/examples/paper/src/main/java/me/devnatan/inventoryframework/runtime/SamplePlugin.java
index dc3b881c0..294d7b971 100644
--- a/examples/paper/src/main/java/me/devnatan/inventoryframework/runtime/SamplePlugin.java
+++ b/examples/paper/src/main/java/me/devnatan/inventoryframework/runtime/SamplePlugin.java
@@ -17,6 +17,7 @@ public void onEnable() {
ViewFrame viewFrame = ViewFrame.create(this)
.install(AnvilInputFeature.AnvilInput)
.with(new AnvilInputSample(), new Failing(), new SimplePagination(), new AutoUpdate())
+ .enableDebug()
.register();
getCommand("ifexample").setExecutor(new IFExampleCommandExecutor(viewFrame));
diff --git a/inventory-framework-api/src/main/java/me/devnatan/inventoryframework/Viewer.java b/inventory-framework-api/src/main/java/me/devnatan/inventoryframework/Viewer.java
index dccd17236..674733555 100644
--- a/inventory-framework-api/src/main/java/me/devnatan/inventoryframework/Viewer.java
+++ b/inventory-framework-api/src/main/java/me/devnatan/inventoryframework/Viewer.java
@@ -101,6 +101,20 @@ public interface Viewer {
@ApiStatus.Internal
void setSwitching(boolean switching);
+ /**
+ * This is an internal inventory-framework API that should not be used from outside of
+ * this library. No compatibility guarantees are provided.
+ */
+ @ApiStatus.Internal
+ boolean isInteractionsLocked();
+
+ /**
+ * This is an internal inventory-framework API that should not be used from outside of
+ * this library. No compatibility guarantees are provided.
+ */
+ @ApiStatus.Internal
+ void setInteractionsLocked(boolean interactionsLocked);
+
/**
* This is an internal inventory-framework API that should not be used from outside of
* this library. No compatibility guarantees are provided.
diff --git a/inventory-framework-api/src/main/java/me/devnatan/inventoryframework/pipeline/Pipeline.java b/inventory-framework-api/src/main/java/me/devnatan/inventoryframework/pipeline/Pipeline.java
index 40eef3776..01a2d4cbe 100644
--- a/inventory-framework-api/src/main/java/me/devnatan/inventoryframework/pipeline/Pipeline.java
+++ b/inventory-framework-api/src/main/java/me/devnatan/inventoryframework/pipeline/Pipeline.java
@@ -7,6 +7,7 @@
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
+import me.devnatan.inventoryframework.IFDebug;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.TestOnly;
@@ -115,6 +116,7 @@ public void execute(S subject) {
public void execute(PipelinePhase phase, S subject) {
final PipelineContext context =
new PipelineContext<>(phase, interceptors.getOrDefault(phase, Collections.emptyList()));
+ IFDebug.debug("Pipeline call: %s", phase.getName());
context.execute(subject);
}
}
diff --git a/inventory-framework-api/src/main/java/me/devnatan/inventoryframework/pipeline/PipelineContext.java b/inventory-framework-api/src/main/java/me/devnatan/inventoryframework/pipeline/PipelineContext.java
index 0a078a3e6..b3f085514 100644
--- a/inventory-framework-api/src/main/java/me/devnatan/inventoryframework/pipeline/PipelineContext.java
+++ b/inventory-framework-api/src/main/java/me/devnatan/inventoryframework/pipeline/PipelineContext.java
@@ -1,6 +1,7 @@
package me.devnatan.inventoryframework.pipeline;
import java.util.List;
+import me.devnatan.inventoryframework.IFDebug;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.annotations.VisibleForTesting;
@@ -40,6 +41,9 @@ private void loop() {
final PipelineInterceptor nextInterceptor = safeInterceptors.get(pointer);
index = pointer + 1;
+ IFDebug.debug(
+ "Interception loop #%d: %s",
+ index, nextInterceptor.getClass().getName());
nextInterceptor.intercept(this, subject);
} while (true);
}
diff --git a/inventory-framework-core/src/main/java/me/devnatan/inventoryframework/context/AbstractIFContext.java b/inventory-framework-core/src/main/java/me/devnatan/inventoryframework/context/AbstractIFContext.java
index 1d4117485..481b5ef54 100644
--- a/inventory-framework-core/src/main/java/me/devnatan/inventoryframework/context/AbstractIFContext.java
+++ b/inventory-framework-core/src/main/java/me/devnatan/inventoryframework/context/AbstractIFContext.java
@@ -109,7 +109,8 @@ public void removeComponent(@NotNull Component component) {
private IFSlotRenderContext createSlotRenderContext(@NotNull Component component, boolean force) {
if (!(this instanceof IFRenderContext))
- throw new InventoryFrameworkException("Slot render context cannot be created from non-render parent");
+ throw new InventoryFrameworkException("Slot render context cannot be created from non-render parent: "
+ + getClass().getName());
final IFRenderContext renderContext = (IFRenderContext) this;
final IFSlotRenderContext slotRender = getRoot()
diff --git a/inventory-framework-platform-bukkit/src/main/java/me/devnatan/inventoryframework/BukkitViewer.java b/inventory-framework-platform-bukkit/src/main/java/me/devnatan/inventoryframework/BukkitViewer.java
index 16e479198..a20939fd1 100644
--- a/inventory-framework-platform-bukkit/src/main/java/me/devnatan/inventoryframework/BukkitViewer.java
+++ b/inventory-framework-platform-bukkit/src/main/java/me/devnatan/inventoryframework/BukkitViewer.java
@@ -14,7 +14,7 @@ public final class BukkitViewer implements Viewer {
private IFRenderContext activeContext;
private Deque previousContexts = new LinkedList<>();
private long lastInteractionInMillis;
- private boolean switching;
+ private boolean switching, interactionsLocked;
public BukkitViewer(@NotNull Player player, IFRenderContext activeContext) {
this.player = player;
@@ -97,6 +97,16 @@ public void setSwitching(boolean switching) {
this.switching = switching;
}
+ @Override
+ public boolean isInteractionsLocked() {
+ return interactionsLocked || isBlockedByInteractionDelay();
+ }
+
+ @Override
+ public void setInteractionsLocked(boolean interactionsLocked) {
+ this.interactionsLocked = interactionsLocked;
+ }
+
@Override
public IFRenderContext getPreviousContext() {
return previousContexts.peekLast();
diff --git a/inventory-framework-platform-bukkit/src/main/java/me/devnatan/inventoryframework/IFInventoryListener.java b/inventory-framework-platform-bukkit/src/main/java/me/devnatan/inventoryframework/IFInventoryListener.java
index 71165c4b2..cfed98eb7 100644
--- a/inventory-framework-platform-bukkit/src/main/java/me/devnatan/inventoryframework/IFInventoryListener.java
+++ b/inventory-framework-platform-bukkit/src/main/java/me/devnatan/inventoryframework/IFInventoryListener.java
@@ -41,6 +41,14 @@ public void onInventoryClick(final InventoryClickEvent event) {
final Viewer viewer = viewFrame.getViewer(player);
if (viewer == null) return;
+ if (viewer.isInteractionsLocked()) {
+ IFDebug.debug("Interaction locked at listener level %s", player.getName());
+ event.setCancelled(true);
+ return;
+ }
+
+ viewer.setInteractionsLocked(true);
+
final IFRenderContext context = viewer.getActiveContext();
final Component clickedComponent = context.getComponentsAt(event.getRawSlot()).stream()
.filter(Component::isVisible)
@@ -55,6 +63,8 @@ public void onInventoryClick(final InventoryClickEvent event) {
.createSlotClickContext(event.getRawSlot(), viewer, clickedContainer, clickedComponent, event, false);
root.getPipeline().execute(StandardPipelinePhases.CLICK, clickContext);
+
+ viewer.setInteractionsLocked(false);
}
@SuppressWarnings("unused")
@@ -68,11 +78,15 @@ public void onInventoryClose(final InventoryCloseEvent event) {
final Viewer viewer = viewFrame.getViewer(player);
if (viewer == null) return;
+ viewer.setInteractionsLocked(true);
+
final IFRenderContext context = viewer.getCurrentContext();
final RootView root = context.getRoot();
final IFCloseContext closeContext = root.getElementFactory().createCloseContext(viewer, context);
root.getPipeline().execute(StandardPipelinePhases.CLOSE, closeContext);
+
+ viewer.setInteractionsLocked(false);
}
@SuppressWarnings("deprecation")
diff --git a/inventory-framework-platform-bukkit/src/main/java/me/devnatan/inventoryframework/pipeline/GlobalClickInterceptor.java b/inventory-framework-platform-bukkit/src/main/java/me/devnatan/inventoryframework/pipeline/GlobalClickInterceptor.java
index ec3bd5531..e8e216fca 100644
--- a/inventory-framework-platform-bukkit/src/main/java/me/devnatan/inventoryframework/pipeline/GlobalClickInterceptor.java
+++ b/inventory-framework-platform-bukkit/src/main/java/me/devnatan/inventoryframework/pipeline/GlobalClickInterceptor.java
@@ -2,6 +2,7 @@
import static me.devnatan.inventoryframework.ViewConfig.CANCEL_ON_CLICK;
+import me.devnatan.inventoryframework.IFDebug;
import me.devnatan.inventoryframework.VirtualView;
import me.devnatan.inventoryframework.context.SlotClickContext;
import org.bukkit.event.inventory.InventoryClickEvent;
@@ -15,7 +16,10 @@ public final class GlobalClickInterceptor implements PipelineInterceptor pipeline, @NotNull VirtualView subject) {
- if (!(subject instanceof SlotClickContext)) return;
+ if (!(subject instanceof SlotClickContext)) {
+ IFDebug.debug("Cannot handle global click: not a SlotClickContext");
+ return;
+ }
final SlotClickContext context = (SlotClickContext) subject;
final InventoryClickEvent event = context.getClickOrigin();
diff --git a/inventory-framework-platform-minestom/src/main/kotlin/me/devnatan/inventoryframework/MinestomViewer.kt b/inventory-framework-platform-minestom/src/main/kotlin/me/devnatan/inventoryframework/MinestomViewer.kt
index dc4d74fdb..e4b264af9 100644
--- a/inventory-framework-platform-minestom/src/main/kotlin/me/devnatan/inventoryframework/MinestomViewer.kt
+++ b/inventory-framework-platform-minestom/src/main/kotlin/me/devnatan/inventoryframework/MinestomViewer.kt
@@ -14,6 +14,7 @@ class MinestomViewer(
private val previousContexts: Deque = LinkedList()
private var lastInteractionInMillis: Long = 0
private var switching = false
+ private var interactionsLocked = false
override fun getCurrentContext(): IFRenderContext =
if (isSwitching()) {
@@ -71,6 +72,12 @@ class MinestomViewer(
this.switching = switching
}
+ override fun isInteractionsLocked(): Boolean = interactionsLocked || isBlockedByInteractionDelay
+
+ override fun setInteractionsLocked(interactionsLocked: Boolean) {
+ this.interactionsLocked = interactionsLocked
+ }
+
override fun getPreviousContext(): IFRenderContext? = previousContexts.peekLast()
override fun setPreviousContext(previousContext: IFRenderContext) {