Skip to content

Commit b8642a2

Browse files
committed
feat: Allow consuming click events so they do not forward to parents
feat: Allow creating two inventory composables at once, so long as one is not inside the other, explicitly deny the latter
1 parent 45bb078 commit b8642a2

File tree

10 files changed

+112
-95
lines changed

10 files changed

+112
-95
lines changed

build.gradle.kts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,6 @@ dependencies {
3232
compileOnly(idofrontLibs.kotlinx.coroutines)
3333
compileOnly(idofrontLibs.minecraft.mccoroutine)
3434
compileOnly(idofrontLibs.kotlin.reflect)
35-
compileOnly(idofrontLibs.minecraft.anvilgui)
3635

3736
// Shaded
3837
api(compose.runtime) {

src/main/kotlin/com/mineinabyss/guiy/components/canvases/Anvil.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,6 @@ fun Anvil(
3737
val inventory: Inventory = remember(holder) {
3838
Bukkit.getServer().createInventory(holder, InventoryType.ANVIL, titleMM)
3939
}
40-
holder.setActiveInventory(GuiyInventory(inventory, onClose))
4140

4241
// Track updates to anvil text via packets
4342
var playerViewText by remember(inventory) { mutableStateOf("") }
@@ -54,6 +53,7 @@ fun Anvil(
5453

5554
Inventory(
5655
inventory,
56+
onClose = onClose,
5757
title = titleMM,
5858
modifier = constrainedModifier,
5959
gridToInventoryIndex = { it.x },

src/main/kotlin/com/mineinabyss/guiy/components/canvases/Chest.kt

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ package com.mineinabyss.guiy.components.canvases
33
import androidx.compose.runtime.*
44
import com.mineinabyss.guiy.components.rememberMiniMsg
55
import com.mineinabyss.guiy.components.state.IntCoordinates
6-
import com.mineinabyss.guiy.inventory.GuiyInventory
76
import com.mineinabyss.guiy.inventory.GuiyInventoryHolder
87
import com.mineinabyss.guiy.inventory.InventoryCloseScope
98
import com.mineinabyss.guiy.layout.Layout
@@ -68,13 +67,12 @@ fun Chest(
6867
}
6968

7069
val inventory: Inventory = remember(size) {
71-
Bukkit.createInventory(holder, CHEST_WIDTH * size.height, title).also {
72-
}
70+
Bukkit.createInventory(holder, CHEST_WIDTH * size.height, title)
7371
}
74-
holder.setActiveInventory(GuiyInventory(inventory, onClose))
7572

7673
Inventory(
7774
inventory = inventory,
75+
onClose = onClose,
7876
title = title,
7977
modifier = constrainedModifier,
8078
gridToInventoryIndex = { (x, y) ->

src/main/kotlin/com/mineinabyss/guiy/components/canvases/Inventory.kt

Lines changed: 17 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,18 @@
11
package com.mineinabyss.guiy.components.canvases
22

33
import androidx.compose.runtime.*
4-
import com.github.shynixn.mccoroutine.bukkit.minecraftDispatcher
54
import com.mineinabyss.guiy.components.state.IntCoordinates
65
import com.mineinabyss.guiy.guiyPlugin
7-
import com.mineinabyss.guiy.inventory.GuiyCanvas
8-
import com.mineinabyss.guiy.inventory.LocalCanvas
9-
import com.mineinabyss.guiy.inventory.LocalGuiyOwner
10-
import com.mineinabyss.guiy.inventory.MapBackedGuiyCanvas
6+
import com.mineinabyss.guiy.inventory.*
117
import com.mineinabyss.guiy.layout.Layout
128
import com.mineinabyss.guiy.layout.Renderer
139
import com.mineinabyss.guiy.layout.StaticMeasurePolicy
1410
import com.mineinabyss.guiy.modifiers.Modifier
11+
import com.mineinabyss.guiy.modifiers.click.clickable
1512
import com.mineinabyss.guiy.nodes.GuiyNode
1613
import com.mineinabyss.idofront.entities.title
17-
import kotlinx.coroutines.withContext
14+
import com.mineinabyss.idofront.messaging.injectedLogger
1815
import net.kyori.adventure.text.Component
19-
import org.bukkit.event.inventory.InventoryCloseEvent
2016
import org.bukkit.inventory.Inventory
2117

2218
val LocalInventory: ProvidableCompositionLocal<Inventory> =
@@ -32,39 +28,33 @@ val LocalInventory: ProvidableCompositionLocal<Inventory> =
3228
@Composable
3329
fun Inventory(
3430
inventory: Inventory,
31+
onClose: InventoryCloseScope.() -> Unit,
3532
title: Component? = null,
3633
modifier: Modifier = Modifier,
3734
gridToInventoryIndex: (IntCoordinates) -> Int?,
3835
inventoryIndexToGrid: (Int) -> IntCoordinates,
3936
content: @Composable () -> Unit,
4037
) {
41-
val viewers by LocalGuiyOwner.current.viewers.collectAsState()
38+
val holder: GuiyInventoryHolder = LocalInventoryHolder.current
4239

4340
// Update title
44-
LaunchedEffect(title, viewers) {
41+
LaunchedEffect(title) {
4542
if (title != null) inventory.viewers.forEach { it.openInventory.title(title) }
4643
}
4744

48-
// Manage opening inventory for new viewers or when inventory changes
49-
LaunchedEffect(viewers, inventory) {
50-
val oldViewers = inventory.viewers.toSet()
45+
val canvas = remember { MapBackedGuiyCanvas() }
5146

52-
withContext(guiyPlugin.minecraftDispatcher) {
53-
// Close inventory for removed viewers
54-
(oldViewers - viewers).forEach {
55-
it.closeInventory(InventoryCloseEvent.Reason.PLUGIN)
56-
}
47+
val existingInventory = runCatching { LocalInventory.current }.getOrNull()
5748

58-
// Open inventory for new viewers
59-
(viewers - oldViewers).forEach {
60-
it.closeInventory(InventoryCloseEvent.Reason.PLUGIN)
61-
it.openInventory(inventory)
49+
if (existingInventory != null) {
50+
SideEffect {
51+
guiyPlugin.injectedLogger().e {
52+
"Creating inventory $inventory inside other inventory ($existingInventory), Guiy does not support this yet."
6253
}
6354
}
55+
return
6456
}
6557

66-
val canvas = remember { MapBackedGuiyCanvas() }
67-
6858
CompositionLocalProvider(
6959
LocalCanvas provides canvas,
7060
LocalInventory provides inventory
@@ -73,6 +63,8 @@ fun Inventory(
7363
measurePolicy = StaticMeasurePolicy,
7464
renderer = object : Renderer {
7565
override fun GuiyCanvas.render(node: GuiyNode) {
66+
// The last inventory to render sets this state so holder can choose which inventory to open
67+
holder.setActiveInventory(GuiyInventory(inventory, onClose))
7668
canvas.startRender()
7769
}
7870

@@ -90,7 +82,8 @@ fun Inventory(
9082
}
9183
}
9284
},
93-
modifier = modifier,
85+
// Consume click so only the visible inventory can process clicks
86+
modifier = modifier.clickable(consumeClick = true) { },
9487
content = content,
9588
)
9689
}
Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package com.mineinabyss.guiy.components.canvases
22

33
import androidx.compose.runtime.*
44
import com.github.shynixn.mccoroutine.bukkit.launch
5+
import com.github.shynixn.mccoroutine.bukkit.minecraftDispatcher
56
import com.mineinabyss.guiy.guiyPlugin
67
import com.mineinabyss.guiy.inventory.GuiyInventoryHolder
78
import com.mineinabyss.guiy.inventory.InventoryCloseScope
@@ -11,21 +12,42 @@ import com.mineinabyss.guiy.modifiers.click.ClickScope
1112
import com.mineinabyss.guiy.modifiers.drag.DragScope
1213
import com.mineinabyss.idofront.time.ticks
1314
import kotlinx.coroutines.delay
15+
import kotlinx.coroutines.withContext
1416
import org.bukkit.entity.Player
1517
import org.bukkit.event.Cancellable
18+
import org.bukkit.event.inventory.InventoryCloseEvent
1619

1720
val LocalInventoryHolder = compositionLocalOf<GuiyInventoryHolder> { error("No local inventory holder defined") }
1821

1922
@Composable
20-
fun ProvideInventoryHolder(content: @Composable () -> Unit) {
23+
fun InventoryHolder(content: @Composable () -> Unit) {
2124
val holder = rememberInventoryHolder()
22-
2325
// Close inventory when disposing
2426
DisposableEffect(holder) {
2527
onDispose {
2628
guiyPlugin.launch { holder.inventory.close() }
2729
}
2830
}
31+
val viewers by LocalGuiyOwner.current.viewers.collectAsState()
32+
val inventory = holder.activeInventory.collectAsState().value
33+
34+
// Manage opening inventory for new viewers or when inventory changes
35+
LaunchedEffect(viewers, inventory) {
36+
val oldViewers = inventory?.inventory?.viewers?.toSet() ?: return@LaunchedEffect
37+
38+
withContext(guiyPlugin.minecraftDispatcher) {
39+
// Close inventory for removed viewers
40+
(oldViewers - viewers).forEach {
41+
it.closeInventory(InventoryCloseEvent.Reason.PLUGIN)
42+
}
43+
44+
// Open inventory for new viewers
45+
(viewers - oldViewers).forEach {
46+
it.closeInventory(InventoryCloseEvent.Reason.PLUGIN)
47+
it.openInventory(inventory.inventory)
48+
}
49+
}
50+
}
2951

3052
CompositionLocalProvider(LocalInventoryHolder provides holder) {
3153
content()

src/main/kotlin/com/mineinabyss/guiy/inventory/GuiyOwner.kt

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,7 @@ package com.mineinabyss.guiy.inventory
33
import androidx.compose.runtime.*
44
import androidx.compose.runtime.snapshots.Snapshot
55
import com.github.shynixn.mccoroutine.bukkit.minecraftDispatcher
6-
import com.mineinabyss.guiy.components.canvases.LocalInventoryHolder
7-
import com.mineinabyss.guiy.components.canvases.ProvideInventoryHolder
8-
import com.mineinabyss.guiy.components.canvases.rememberInventoryHolder
6+
import com.mineinabyss.guiy.components.canvases.InventoryHolder
97
import com.mineinabyss.guiy.guiyPlugin
108
import com.mineinabyss.guiy.layout.LayoutNode
119
import com.mineinabyss.guiy.modifiers.Constraints
@@ -22,11 +20,11 @@ import kotlin.coroutines.CoroutineContext
2220
import kotlin.reflect.KType
2321

2422
data class ClickResult(
25-
val cancelBukkitEvent: Boolean? = null,
23+
val clickConsumed: Boolean? = null,
2624
) {
2725
fun mergeWith(other: ClickResult) = ClickResult(
2826
// Prioritize true > false > null
29-
cancelBukkitEvent = (cancelBukkitEvent ?: other.cancelBukkitEvent)?.or(other.cancelBukkitEvent ?: false)
27+
clickConsumed = (clickConsumed ?: other.clickConsumed)?.or(other.clickConsumed == true)
3028
)
3129
}
3230

@@ -130,7 +128,9 @@ class GuiyOwner(
130128
val w = node.width
131129
val x = if (w == 0) 0 else slot % width
132130
val y = if (w == 0) 0 else slot / width
133-
acc.mergeWith(rootNode.processClick(scope, x, y))
131+
val processed = rootNode.processClick(scope, x, y)
132+
if (processed.clickConsumed == true) return processed
133+
acc.mergeWith(processed)
134134
}
135135
}
136136

@@ -139,7 +139,7 @@ class GuiyOwner(
139139
}
140140
}) {
141141
// A default inventory holder for most usecases
142-
ProvideInventoryHolder {
142+
InventoryHolder {
143143
content()
144144
}
145145
}

src/main/kotlin/com/mineinabyss/guiy/layout/LayoutNode.kt

Lines changed: 19 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,10 @@ import com.mineinabyss.guiy.components.state.ItemPositions
77
import com.mineinabyss.guiy.inventory.ClickResult
88
import com.mineinabyss.guiy.inventory.GuiyCanvas
99
import com.mineinabyss.guiy.inventory.OffsetCanvas
10-
import com.mineinabyss.guiy.modifiers.*
10+
import com.mineinabyss.guiy.modifiers.Constraints
11+
import com.mineinabyss.guiy.modifiers.LayoutChangingModifier
12+
import com.mineinabyss.guiy.modifiers.Modifier
13+
import com.mineinabyss.guiy.modifiers.OnSizeChangedModifier
1114
import com.mineinabyss.guiy.modifiers.click.ClickModifier
1215
import com.mineinabyss.guiy.modifiers.click.ClickScope
1316
import com.mineinabyss.guiy.modifiers.drag.DragModifier
@@ -100,21 +103,26 @@ internal class LayoutNode : Measurable, Placeable, GuiyNode {
100103
renderer.apply { offsetCanvas?.renderAfterChildren(this@LayoutNode) }
101104
}
102105

103-
/**
104-
* @return Whether no elements were clickable or any element requested the bukkit click event to be cancelled.
105-
*/
106106
fun processClick(scope: ClickScope, x: Int, y: Int): ClickResult {
107-
val cancelClickEvent = get<ClickModifier>()?.run {
108-
onClick.invoke(scope)
109-
cancelClickEvent
110-
}
111-
return children
107+
val childResult = children
112108
.filter { x in it.x until (it.x + it.width) && y in it.y until (it.y + it.height) }
113-
.fold(ClickResult(cancelClickEvent)) { acc, it ->
114-
acc.mergeWith(it.processClick(scope, x - it.x, y - it.y))
109+
.foldRight(ClickResult()) { it, acc ->
110+
val processed = it.processClick(scope, x - it.x, y - it.y)
111+
if (processed.clickConsumed == true) {
112+
return processed
113+
}
114+
acc.mergeWith(processed)
115115
}
116+
117+
if (childResult.clickConsumed != true) get<ClickModifier>()?.run {
118+
onClick.invoke(scope)
119+
if (consumeClick) return ClickResult(true)
120+
}
121+
122+
return childResult
116123
}
117124

125+
118126
data class DragInfo(
119127
val dragModifier: DragModifier,
120128
val itemMap: ItemPositions = ItemPositions(),

src/main/kotlin/com/mineinabyss/guiy/modifiers/click/ClickModifier.kt

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,13 @@ import com.mineinabyss.guiy.modifiers.Modifier
44

55
open class ClickModifier(
66
val merged: Boolean = false,
7-
val cancelClickEvent: Boolean,
7+
val consumeClick: Boolean,
88
val onClick: (ClickScope.() -> Unit),
99
// val allowClick: (ClickScope.() -> Boolean)
1010
) : Modifier.Element<ClickModifier> {
1111
override fun mergeWith(other: ClickModifier) = ClickModifier(
1212
merged = true,
13-
cancelClickEvent = cancelClickEvent || other.cancelClickEvent,
13+
consumeClick = consumeClick || other.consumeClick,
1414
onClick = {
1515
if (!other.merged)
1616
onClick()
@@ -25,13 +25,13 @@ open class ClickModifier(
2525
}
2626

2727
fun Modifier.clickable(
28-
cancelClickEvent: Boolean = true,
28+
consumeClick: Boolean = false,
2929
// allowClick: ClickScope.() -> Boolean = { true },
3030
onClick: ClickScope.() -> Unit
3131
) =
3232
then(
3333
ClickModifier(
34-
cancelClickEvent = cancelClickEvent,
34+
consumeClick = consumeClick,
3535
onClick = onClick, /*allowClick = allowClick*/
3636
)
3737
)

0 commit comments

Comments
 (0)