diff --git a/README.md b/README.md index bdd0373..8273ec8 100644 --- a/README.md +++ b/README.md @@ -33,9 +33,10 @@ guiy { ```kotlin @Composable -fun GuiyOwner.ExampleMenu(player: Player) { +fun ExampleMenu(player: Player) { + val owner = LocalGuiyOwner.current // Guiy will dynamically update players, title, or height if you use a state. - Chest(setOf(player), title = "Example", height = 4, onClose = { exit() /*reopen()*/ }) { + Chest(setOf(player), title = "Example", height = 4, onClose = { owner.exit() /*owner.reopen()*/ }) { ToggleButton() } } @@ -125,7 +126,7 @@ fun TimedToggle() { ### Real world use We are using this project internally, so you should be able to find up-to-date usage in -our [main project](https://github.com/MineInAbyss/MineInAbyss/tree/master/mineinabyss-features/src/main/kotlin/com/mineinabyss/guilds/menus). +our [main project](https://github.com/MineInAbyss/MineInAbyss/tree/master/mineinabyss-features/src/main/kotlin/com/mineinabyss/features/guilds/menus). ## Usage diff --git a/build.gradle.kts b/build.gradle.kts index 86fdb86..f00e2da 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -10,24 +10,26 @@ plugins { alias(idofrontLibs.plugins.mia.publication) alias(idofrontLibs.plugins.mia.autoversion) alias(idofrontLibs.plugins.mia.testing) - alias(idofrontLibs.plugins.compose) + alias(idofrontLibs.plugins.jetbrainsCompose) + alias(idofrontLibs.plugins.compose.compiler) } - -tasks.withType { - kotlinOptions { - freeCompilerArgs = listOf( +kotlin { + compilerOptions { + freeCompilerArgs.addAll( "-opt-in=kotlinx.serialization.ExperimentalSerializationApi" ) - jvmTarget = "17" } } -repositories { - mavenCentral() - google() - maven("https://maven.pkg.jetbrains.space/public/p/compose/dev") - maven("https://repo.codemc.org/repository/maven-public/") +allprojects { + repositories { + mavenCentral() + google() + maven("https://repo.mineinabyss.com/snapshots") + maven("https://maven.pkg.jetbrains.space/public/p/compose/dev") + maven("https://repo.codemc.org/repository/maven-public/") + } } dependencies { diff --git a/gradle.properties b/gradle.properties index d7806f3..12c8a03 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,4 +1,4 @@ kotlin.code.style=official group=com.mineinabyss -version=0.9 -idofrontVersion=0.23.0 +version=0.10 +idofrontVersion=0.24.0 diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index a595206..48c0a02 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.5-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/guiy-example/build.gradle.kts b/guiy-example/build.gradle.kts index ac673aa..361e3d3 100644 --- a/guiy-example/build.gradle.kts +++ b/guiy-example/build.gradle.kts @@ -4,7 +4,8 @@ plugins { alias(idofrontLibs.plugins.mia.kotlin.jvm) alias(idofrontLibs.plugins.mia.papermc) alias(idofrontLibs.plugins.mia.copyjar) - alias(idofrontLibs.plugins.compose) + alias(idofrontLibs.plugins.jetbrainsCompose) + alias(idofrontLibs.plugins.compose.compiler) } dependencies { diff --git a/guiy-example/src/main/kotlin/com/mineinabyss/guiy/example/gui/MainMenu.kt b/guiy-example/src/main/kotlin/com/mineinabyss/guiy/example/gui/MainMenu.kt index d27bdef..c220068 100644 --- a/guiy-example/src/main/kotlin/com/mineinabyss/guiy/example/gui/MainMenu.kt +++ b/guiy-example/src/main/kotlin/com/mineinabyss/guiy/example/gui/MainMenu.kt @@ -1,34 +1,34 @@ package com.mineinabyss.guiy.example.gui import androidx.compose.runtime.* +import com.mineinabyss.guiy.components.CreativeItem import com.mineinabyss.guiy.components.Item -import com.mineinabyss.guiy.components.ItemGrid import com.mineinabyss.guiy.components.canvases.Chest -import com.mineinabyss.guiy.components.rememberItemGridState -import com.mineinabyss.guiy.inventory.GuiyOwner +import com.mineinabyss.guiy.components.state.ItemPositions +import com.mineinabyss.guiy.inventory.LocalGuiyOwner +import com.mineinabyss.guiy.layout.Row import com.mineinabyss.guiy.modifiers.Modifier -import com.mineinabyss.guiy.modifiers.clickable +import com.mineinabyss.guiy.modifiers.fillMaxWidth import com.mineinabyss.guiy.modifiers.size -import kotlinx.coroutines.delay import org.bukkit.Material import org.bukkit.entity.Player -import org.bukkit.event.inventory.ClickType import org.bukkit.inventory.ItemStack @Composable -fun GuiyOwner.MainMenu(player: Player) { +fun MainMenu(player: Player) { + val owner = LocalGuiyOwner.current val title = "Hello world" - val state = rememberItemGridState() Chest( setOf(player), title, - onClose = { exit() }, - modifier = Modifier.clickable { - if(clickType == ClickType.SHIFT_LEFT) { - cursor = cursor?.let { state.add(it, 4, 1) } - } - } + onClose = { owner.exit() }, ) { - ItemGrid(state, Modifier.size(4, 1)) + Row { + listOf(Material.DIAMOND, Material.EMERALD, Material.GOLD_INGOT, Material.IRON_INGOT) + .forEach { + CreativeItem(ItemStack(it)) + } + Item(Material.BARRIER, "No interaction here", modifier = Modifier.fillMaxWidth()) + } } } diff --git a/settings.gradle.kts b/settings.gradle.kts index 327e469..ad228c3 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -3,6 +3,7 @@ pluginManagement { google() gradlePluginPortal() maven("https://repo.mineinabyss.com/releases") + maven("https://repo.mineinabyss.com/snapshots") maven("https://maven.pkg.jetbrains.space/public/p/compose/dev") maven("https://repo.papermc.io/repository/maven-public/") } @@ -22,6 +23,7 @@ dependencyResolutionManagement { repositories { maven("https://repo.mineinabyss.com/releases") + maven("https://repo.mineinabyss.com/snapshots") } versionCatalogs.create("idofrontLibs").from("com.mineinabyss:catalog:$idofrontVersion") diff --git a/src/main/kotlin/com/mineinabyss/guiy/components/CreativeItem.kt b/src/main/kotlin/com/mineinabyss/guiy/components/CreativeItem.kt new file mode 100644 index 0000000..c6baa08 --- /dev/null +++ b/src/main/kotlin/com/mineinabyss/guiy/components/CreativeItem.kt @@ -0,0 +1,38 @@ +package com.mineinabyss.guiy.components + +import androidx.compose.runtime.Composable +import com.mineinabyss.guiy.modifiers.Modifier +import com.mineinabyss.guiy.modifiers.click.clickable +import org.bukkit.event.inventory.ClickType +import org.bukkit.inventory.ItemStack + +/** + * An item that acts like a creative inventory item that can be copied on click. + */ +@Composable +fun CreativeItem( + itemStack: ItemStack?, modifier: Modifier = Modifier +) { + Item(itemStack, modifier.clickable { + // Mimic all vanilla interactions + val shiftClick = clickType == ClickType.SHIFT_LEFT || clickType == ClickType.SHIFT_RIGHT + val result: ItemStack? = when { + (shiftClick || clickType == ClickType.MIDDLE) && cursor == null -> itemStack?.clone() + ?.apply { amount = maxStackSize } + + clickType == ClickType.MIDDLE -> return@clickable + + (clickType == ClickType.SHIFT_LEFT && cursor != null && cursor.isSimilar(itemStack)) -> + cursor.clone().apply { amount = maxStackSize } + + cursor == null -> itemStack?.clone()?.apply { amount = 1 } + + clickType == ClickType.RIGHT || clickType == ClickType.SHIFT_RIGHT -> cursor.clone().subtract() + + (clickType == ClickType.LEFT || clickType == ClickType.SHIFT_LEFT) && !cursor.isSimilar(itemStack) -> null + + else -> cursor.clone().add() + } + whoClicked.setItemOnCursor(result) + }) +} diff --git a/src/main/kotlin/com/mineinabyss/guiy/components/Grid.kt b/src/main/kotlin/com/mineinabyss/guiy/components/Grid.kt index 4f65d14..4528428 100644 --- a/src/main/kotlin/com/mineinabyss/guiy/components/Grid.kt +++ b/src/main/kotlin/com/mineinabyss/guiy/components/Grid.kt @@ -6,6 +6,10 @@ import com.mineinabyss.guiy.layout.MeasurePolicy import com.mineinabyss.guiy.layout.MeasureResult import com.mineinabyss.guiy.modifiers.Modifier +/** + * A grid layout composable that arranges its children in a grid, left-to-right, top-to-bottom, wrapped + * to the next row when its width is exceeded. + */ @Composable fun Grid(modifier: Modifier = Modifier, content: @Composable () -> Unit) { Layout( diff --git a/src/main/kotlin/com/mineinabyss/guiy/components/InteractableItemGrid.kt b/src/main/kotlin/com/mineinabyss/guiy/components/InteractableItemGrid.kt new file mode 100644 index 0000000..01332d2 --- /dev/null +++ b/src/main/kotlin/com/mineinabyss/guiy/components/InteractableItemGrid.kt @@ -0,0 +1,82 @@ +package com.mineinabyss.guiy.components + +import androidx.compose.runtime.* + +// TODO implement a component where inventory events are NOT cancelled and state is propagated both ways +// The current system makes it hard to do so safely (without allowing for dupes) +/** + * A grid of items that can be interacted with in an inventory. + * + * @param grid Positions of items in the grid. + * @param onItemsChanged Executes when an item is changed (added, removed, or moved.) + * @param modifier The modifier for the grid. + */ +//@Composable +//fun InteractableItemGrid( +// grid: ItemPositions, +// onItemsChanged: (pos: ItemPositions) -> Unit, +// modifier: Modifier = Modifier, +//) { +// var size by remember { mutableStateOf(Size(0, 0)) } +// Grid(modifier.onSizeChanged { size = it }.draggable { +// //TODO +//// grid.items + updatedItems.map { (i, item) -> IntCoordinates(i) to item } +//// updatedItems.forEach { (i, item) -> +//// cursor!!.amount -= abs(item.amount - (state.items[i]?.amount ?: 0)) +//// state.items[i] = item +//// } +// }) { +// for (x in 0 until size.width) { +// for (y in 0 until size.height) { +// val item = grid[x, y] +// Item(item, Modifier.clickable(cancelClickEvent = true) { +// onItemsChanged(grid.with(x, y, resultItem)) +// val newItem = when (clickType) { +// ClickType.LEFT -> { +// if (item != null && cursor?.isSimilar(item) == true) { +// val total = (item.amount + (cursor?.amount ?: 0)) +// item.amount = total.coerceAtMost(item.maxStackSize) +// cursor?.amount = total - item.amount +// item +// } else { +// val temp = cursor +// cursor = item +// temp +// } +// } +// +// ClickType.RIGHT -> { +// when { +// cursor != null && item != null && cursor!!.type != item.type -> { +// val temp = cursor +// cursor = item +// temp +// } +// +// cursor == null && item != null -> { +// val c = item.clone() +// item.amount /= 2 +// cursor = c.apply { +// amount -= item.amount +// } +// item +// } +// +// cursor != null -> { +// val newItem = cursor!!.clone().apply { amount = (item?.amount ?: 0) + 1 } +// cursor!!.amount -= 1 +// newItem +// } +// +// else -> return@clickable +// } +// } +// +// else -> return@clickable +// } +// onItemsChanged(grid.with(x, y, newItem)) +// }) +// } +// } +// } +//} diff --git a/src/main/kotlin/com/mineinabyss/guiy/components/Item.kt b/src/main/kotlin/com/mineinabyss/guiy/components/Item.kt index f8cae83..f950ccd 100644 --- a/src/main/kotlin/com/mineinabyss/guiy/components/Item.kt +++ b/src/main/kotlin/com/mineinabyss/guiy/components/Item.kt @@ -1,18 +1,27 @@ package com.mineinabyss.guiy.components import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember import com.mineinabyss.guiy.components.canvases.LocalInventory import com.mineinabyss.guiy.layout.Layout import com.mineinabyss.guiy.layout.MeasureResult import com.mineinabyss.guiy.modifiers.Modifier import com.mineinabyss.guiy.modifiers.sizeIn +import com.mineinabyss.idofront.items.editItemMeta +import com.mineinabyss.idofront.textcomponents.miniMsg +import org.bukkit.Material import org.bukkit.inventory.ItemStack +/** + * An item to display in an inventory layout. + * + * @param itemStack The [ItemStack] to display. + */ @Composable fun Item(itemStack: ItemStack?, modifier: Modifier = Modifier) { val canvas = LocalInventory.current Layout( - measurePolicy = { measurables, constraints -> + measurePolicy = { _, constraints -> MeasureResult(constraints.minWidth, constraints.minHeight) {} }, renderer = { node -> @@ -24,3 +33,28 @@ fun Item(itemStack: ItemStack?, modifier: Modifier = Modifier) { modifier = Modifier.sizeIn(minWidth = 1, minHeight = 1).then(modifier) ) } + +/** + * An item to display in an inventory layout. + * + * @param material The [Material] of the item. + * @param title The item's display name (formatted by MiniMesssage). + * @param amount The amount of the item. + * @param lore The item's lore (formatted by MiniMessage). + */ +@Composable +fun Item( + material: Material, + title: String? = null, + amount: Int = 1, + lore: List = listOf(), + modifier: Modifier = Modifier, +) { + val titleMM = remember(title) { title?.miniMsg() } + val loreMM = remember(lore) { lore.map { it.miniMsg() } } + + Item(ItemStack(material, amount).editItemMeta { + displayName(titleMM) + lore(loreMM) + }, modifier) +} diff --git a/src/main/kotlin/com/mineinabyss/guiy/components/PlaceableArea.kt b/src/main/kotlin/com/mineinabyss/guiy/components/PlaceableArea.kt deleted file mode 100644 index d01af8a..0000000 --- a/src/main/kotlin/com/mineinabyss/guiy/components/PlaceableArea.kt +++ /dev/null @@ -1,115 +0,0 @@ -package com.mineinabyss.guiy.components - -import androidx.compose.runtime.* -import com.mineinabyss.guiy.components.canvases.CHEST_WIDTH -import com.mineinabyss.guiy.components.canvases.MAX_CHEST_HEIGHT -import com.mineinabyss.guiy.layout.Size -import com.mineinabyss.guiy.modifiers.Modifier -import com.mineinabyss.guiy.modifiers.clickable -import com.mineinabyss.guiy.modifiers.draggable -import com.mineinabyss.guiy.modifiers.onSizeChanged -import org.bukkit.event.inventory.ClickType -import org.bukkit.inventory.ItemStack -import kotlin.math.abs - -class ItemGridState( - val resize: Boolean = false -) { - val items = mutableStateListOf().apply { - repeat(MAX_CHEST_HEIGHT * CHEST_WIDTH) { add(null) } - } - - fun add(item: ItemStack, width: Int, height: Int): ItemStack? { - val remaining = item.clone() - items.forEachIndexed { i, gridItem -> - if (i % CHEST_WIDTH >= width || i / CHEST_WIDTH >= height) return@forEachIndexed - if (gridItem == null) { - items[i] = remaining - return null - } - if (gridItem.isSimilar(item)) { - val add = (gridItem.amount + remaining.amount).coerceAtMost(item.maxStackSize) - gridItem.amount - item.amount += add - remaining.amount -= add - items[i] = null - items[i] = item - if (remaining.amount == 0) return null - } - } - return remaining - } - - fun get(x: Int, y: Int) = items[x + y * CHEST_WIDTH] - - fun set(x: Int, y: Int, item: ItemStack?) { - items[x + y * CHEST_WIDTH] = item - } -} - -@Composable -fun rememberItemGridState(): ItemGridState = remember { - ItemGridState() -} - -@Composable -fun ItemGrid( - state: ItemGridState, - modifier: Modifier = Modifier, -) { - var size by remember { mutableStateOf(Size(0, 0)) } - Grid(modifier.onSizeChanged { size = it }.draggable { - updatedItems.forEach { (i, item) -> - cursor!!.amount -= abs(item.amount - (state.items[i]?.amount ?: 0)) - state.items[i] = item - } - }) { - for (x in 0 until size.width) { - for (y in 0 until size.height) { - val index = x + y * CHEST_WIDTH - val item = state.items[index] - Item(item, Modifier.clickable { - val newItem = when (clickType) { - ClickType.LEFT -> { - if (item != null && cursor?.isSimilar(item) == true) { - val total = (item.amount + (cursor?.amount ?: 0)) - item.amount = total.coerceAtMost(item.maxStackSize) - cursor?.amount = total - item.amount - item - } else { - val temp = cursor - cursor = item - temp - } - } - ClickType.RIGHT -> { - when { - cursor != null && item != null && cursor!!.type != item.type -> { - val temp = cursor - cursor = item - temp - } - cursor == null && item != null -> { - val c = item.clone() - item.amount /= 2 - cursor = c.apply { - amount -= item.amount - } - item - } - cursor != null -> { - val newItem = cursor!!.clone().apply { amount = (item?.amount ?: 0) + 1 } - cursor!!.amount -= 1 - newItem - } - else -> return@clickable - } - } - else -> return@clickable - } - state.items[index] = null - state.items[index] = newItem - }) - } - } - } -} diff --git a/src/main/kotlin/com/mineinabyss/guiy/components/Spacer.kt b/src/main/kotlin/com/mineinabyss/guiy/components/Spacer.kt index 8ddc3ff..77da50c 100644 --- a/src/main/kotlin/com/mineinabyss/guiy/components/Spacer.kt +++ b/src/main/kotlin/com/mineinabyss/guiy/components/Spacer.kt @@ -7,6 +7,9 @@ import com.mineinabyss.guiy.modifiers.Modifier import com.mineinabyss.guiy.modifiers.height import com.mineinabyss.guiy.modifiers.width +/** + * A layout element that takes up space without drawing anything. + */ @Composable fun Spacer(modifier: Modifier = Modifier) { Layout( @@ -17,6 +20,12 @@ fun Spacer(modifier: Modifier = Modifier) { ) } +/** + * A layout element that takes up space without drawing anything. + * + * @param width The width of the spacer. + * @param height The height of the spacer. + */ @Composable fun Spacer(width: Int? = null, height: Int? = null, modifier: Modifier = Modifier) { Spacer(modifier diff --git a/src/main/kotlin/com/mineinabyss/guiy/components/canvases/Chest.kt b/src/main/kotlin/com/mineinabyss/guiy/components/canvases/Chest.kt index 315f4c4..44381d1 100644 --- a/src/main/kotlin/com/mineinabyss/guiy/components/canvases/Chest.kt +++ b/src/main/kotlin/com/mineinabyss/guiy/components/canvases/Chest.kt @@ -25,8 +25,13 @@ const val CHEST_WIDTH = 9 const val MIN_CHEST_HEIGHT = 1 const val MAX_CHEST_HEIGHT = 6 +/** + * A Chest GUI [Inventory] composable overload. + * + * @param title The title of the Chest inventory, formatted with MiniMessage. + */ @Composable -fun GuiyOwner.Chest( +fun Chest( viewers: Set, title: String, modifier: Modifier = Modifier, @@ -36,8 +41,17 @@ fun GuiyOwner.Chest( Chest(viewers, title.miniMsg(), modifier, onClose, content) } +/** + * A Chest GUI [Inventory] composable. + * + * @param viewers The set of players who will view the inventory. + * @param title The title of the Chest inventory. + * @param modifier The modifier for the Chest GUI, default is Modifier. + * @param onClose The function to be executed when the Chest GUI is closed, default is an empty function. + * @param content The content of the Chest GUI, defined as a Composable function. + */ @Composable -fun GuiyOwner.Chest( +fun Chest( viewers: Set, title: Component, modifier: Modifier = Modifier, diff --git a/src/main/kotlin/com/mineinabyss/guiy/components/canvases/Inventory.kt b/src/main/kotlin/com/mineinabyss/guiy/components/canvases/Inventory.kt index ee4de2f..3885250 100644 --- a/src/main/kotlin/com/mineinabyss/guiy/components/canvases/Inventory.kt +++ b/src/main/kotlin/com/mineinabyss/guiy/components/canvases/Inventory.kt @@ -4,71 +4,32 @@ import androidx.compose.runtime.* import com.github.shynixn.mccoroutine.bukkit.launch import com.mineinabyss.guiy.guiyPlugin import com.mineinabyss.guiy.inventory.GuiyInventoryHolder -import com.mineinabyss.guiy.inventory.GuiyOwner import com.mineinabyss.guiy.inventory.LocalClickHandler import com.mineinabyss.guiy.layout.Layout -import com.mineinabyss.guiy.modifiers.ClickScope -import com.mineinabyss.guiy.modifiers.DragScope +import com.mineinabyss.guiy.modifiers.click.ClickScope +import com.mineinabyss.guiy.modifiers.drag.DragScope import com.mineinabyss.guiy.modifiers.Modifier import com.mineinabyss.guiy.nodes.InventoryCloseScope import com.mineinabyss.guiy.nodes.StaticMeasurePolicy import com.mineinabyss.idofront.time.ticks import kotlinx.coroutines.delay -import org.bukkit.Material import org.bukkit.entity.Player -import org.bukkit.event.inventory.InventoryClickEvent +import org.bukkit.event.Cancellable import org.bukkit.event.inventory.InventoryCloseEvent import org.bukkit.inventory.Inventory -//val LocalHolder: ProvidableCompositionLocal = -// staticCompositionLocalOf { error("No local holder defined") } - val LocalInventory: ProvidableCompositionLocal = compositionLocalOf { error("No local inventory defined") } +/** + * A layout composable that handles opening and closing an inventory for a set of players. + * + * @param inventory The bukkit inventory to be displayed. + * @param viewers The set of players who will view the inventory. + * @param modifier The modifier to be applied to the layout. + */ @Composable -inline fun rememberInventoryHolder( - viewers: Set, - crossinline onClose: InventoryCloseScope.(Player) -> Unit = {}, -): GuiyInventoryHolder { - val clickHandler = LocalClickHandler.current - return remember(clickHandler) { - object : GuiyInventoryHolder() { - override fun processClick(clickEvent: InventoryClickEvent) { - val scope = ClickScope( - clickEvent.click, - clickEvent.slot, - clickEvent.cursor.takeIf { it.type != Material.AIR } - ) - clickHandler.processClick(scope, clickEvent) - clickEvent.isCancelled = true - - clickEvent.setCursor(scope.cursor) - } - - override fun processDrag(scope: DragScope) { - clickHandler.processDrag(scope) - } - - override fun onClose(player: Player) { - val scope = object : InventoryCloseScope { - override fun reopen() { - - viewers.filter { it.openInventory.topInventory != inventory } - .forEach { it.openInventory(inventory) } - } - } - guiyPlugin.launch { - delay(1.ticks) - onClose.invoke(scope, player) - } - } - } - } -} - -@Composable -fun GuiyOwner.Inventory( +fun Inventory( inventory: Inventory, viewers: Set, modifier: Modifier = Modifier, @@ -109,3 +70,36 @@ fun GuiyOwner.Inventory( ) } } + +@Composable +inline fun rememberInventoryHolder( + viewers: Set, + crossinline onClose: InventoryCloseScope.(Player) -> Unit = {}, +): GuiyInventoryHolder { + val clickHandler = LocalClickHandler.current + return remember(clickHandler) { + object : GuiyInventoryHolder() { + override fun processClick(scope: ClickScope, event: Cancellable) { + val clickResult = clickHandler.processClick(scope) + } + + override fun processDrag(scope: DragScope) { + clickHandler.processDrag(scope) + } + + override fun onClose(player: Player) { + val scope = object : InventoryCloseScope { + override fun reopen() { + //TODO don't think this reference updates properly in the remember block + viewers.filter { it.openInventory.topInventory != inventory } + .forEach { it.openInventory(inventory) } + } + } + guiyPlugin.launch { + delay(1.ticks) + onClose.invoke(scope, player) + } + } + } + } +} diff --git a/src/main/kotlin/com/mineinabyss/guiy/components/state/IntCoordinates.kt b/src/main/kotlin/com/mineinabyss/guiy/components/state/IntCoordinates.kt new file mode 100644 index 0000000..2e3f78b --- /dev/null +++ b/src/main/kotlin/com/mineinabyss/guiy/components/state/IntCoordinates.kt @@ -0,0 +1,12 @@ +package com.mineinabyss.guiy.components.state + +@JvmInline +value class IntCoordinates(private val pair: Long) { + val x get() = (pair shr 32).toInt() + val y get() = pair.toInt() + + operator fun component1() = x + operator fun component2() = y + + constructor(x: Int, y: Int) : this((x.toLong() shl 32) or y.toLong()) +} diff --git a/src/main/kotlin/com/mineinabyss/guiy/components/state/ItemPositions.kt b/src/main/kotlin/com/mineinabyss/guiy/components/state/ItemPositions.kt new file mode 100644 index 0000000..518d610 --- /dev/null +++ b/src/main/kotlin/com/mineinabyss/guiy/components/state/ItemPositions.kt @@ -0,0 +1,18 @@ +package com.mineinabyss.guiy.components.state + +import androidx.compose.runtime.Immutable +import org.bukkit.inventory.ItemStack + +@Immutable +class ItemPositions( + val items: Map = emptyMap() +) { + fun plus(x: Int, y: Int, item: ItemStack) = + ItemPositions(items + (IntCoordinates(x, y) to item)) + + fun minus(x: Int, y: Int) = ItemPositions(items - IntCoordinates(x, y)) + + fun with(x: Int, y: Int, item: ItemStack?) = if (item == null) minus(x, y) else plus(x, y, item) + + operator fun get(x: Int, y: Int) = items[IntCoordinates(x, y)] +} diff --git a/src/main/kotlin/com/mineinabyss/guiy/inventory/GuiyEventListener.kt b/src/main/kotlin/com/mineinabyss/guiy/inventory/GuiyEventListener.kt index 96286b0..0ddaeb8 100644 --- a/src/main/kotlin/com/mineinabyss/guiy/inventory/GuiyEventListener.kt +++ b/src/main/kotlin/com/mineinabyss/guiy/inventory/GuiyEventListener.kt @@ -1,32 +1,31 @@ package com.mineinabyss.guiy.inventory -import com.github.shynixn.mccoroutine.bukkit.launch -import com.mineinabyss.guiy.guiyPlugin -import com.mineinabyss.guiy.modifiers.DragScope -import com.mineinabyss.idofront.nms.aliases.NMSItemStack -import com.mineinabyss.idofront.nms.aliases.NMSPlayer -import com.mineinabyss.idofront.nms.aliases.toNMS -import kotlinx.coroutines.delay +import com.mineinabyss.guiy.modifiers.click.ClickScope import org.bukkit.Material import org.bukkit.entity.Player -import org.bukkit.event.Event import org.bukkit.event.EventHandler import org.bukkit.event.Listener import org.bukkit.event.inventory.ClickType.* -import org.bukkit.event.inventory.DragType import org.bukkit.event.inventory.InventoryClickEvent import org.bukkit.event.inventory.InventoryCloseEvent import org.bukkit.event.inventory.InventoryDragEvent -import org.bukkit.inventory.ItemStack -import kotlin.math.abs class GuiyEventListener : Listener { @EventHandler fun InventoryClickEvent.onClick() { val guiyHolder = inventory.holder as? GuiyInventoryHolder ?: return + + // Avoid any exploits shift clicking or double-clicking into/from the GUI if (click !in setOf(LEFT, RIGHT, MIDDLE)) isCancelled = true - if (clickedInventory?.holder === guiyHolder) - guiyHolder.processClick(this) + + val clickedInventory = clickedInventory ?: return + if (clickedInventory.holder !== guiyHolder) return + isCancelled = true + + val scope = ClickScope( + click, slot, cursor.takeIf { it.type != Material.AIR }, whoClicked + ) + guiyHolder.processClick(scope, this) } @EventHandler @@ -42,32 +41,16 @@ class GuiyEventListener : Listener { @EventHandler fun InventoryDragEvent.onInventoryDrag() { val guiyHolder = inventory.holder as? GuiyInventoryHolder ?: return - val inPlayerInv = newItems.filter { it.key >= view.topInventory.size } +// val inPlayerInv = newItems.filter { it.key >= view.topInventory.size } val inGuiy = newItems.filter { it.key < view.topInventory.size } - if (inGuiy.isNotEmpty()) { + // Handle single slot drag events as clicks for better responsiveness + if (newItems.size == 1 && inGuiy.size == 1) { + isCancelled = true + val clicked = inGuiy.entries.first() + val scope = ClickScope(LEFT, clicked.key, cursor?.takeIf { it.type != Material.AIR }, whoClicked) + guiyHolder.processClick(scope, this) + } else if (inGuiy.isNotEmpty()) { isCancelled = true - result = Event.Result.DEFAULT - val newCursor = oldCursor.apply { inGuiy.map { inventory.getItem(it.key)?.amount ?: 0 }.sum() } - val event = InventoryDragEvent( - view, newCursor, oldCursor, type == DragType.SINGLE, inPlayerInv - ) - event.callEvent() - if (!event.isCancelled) { - for ((slot, item) in inPlayerInv) { - newCursor.amount -= abs((view.getItem(slot)?.amount ?: 0) - item.amount) - view.setItem(slot, item) - } - - val scope = DragScope(type, inGuiy, newCursor) - guiyHolder.processDrag(scope) - guiyPlugin.launch { - delay(1) - val nmsView = whoClicked.toNMS().containerMenu - nmsView.carried = NMSItemStack.fromBukkitCopy(scope.cursor ?: ItemStack(Material.AIR)) - nmsView.sendAllDataToRemote() - } - } } - } } diff --git a/src/main/kotlin/com/mineinabyss/guiy/inventory/GuiyInventoryHolder.kt b/src/main/kotlin/com/mineinabyss/guiy/inventory/GuiyInventoryHolder.kt index 91b43e8..d995da6 100644 --- a/src/main/kotlin/com/mineinabyss/guiy/inventory/GuiyInventoryHolder.kt +++ b/src/main/kotlin/com/mineinabyss/guiy/inventory/GuiyInventoryHolder.kt @@ -3,11 +3,11 @@ package com.mineinabyss.guiy.inventory import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.setValue -import com.mineinabyss.guiy.modifiers.DragScope +import com.mineinabyss.guiy.modifiers.click.ClickScope +import com.mineinabyss.guiy.modifiers.drag.DragScope import org.bukkit.entity.Player -import org.bukkit.event.inventory.InventoryClickEvent +import org.bukkit.event.Cancellable import org.bukkit.event.inventory.InventoryCloseEvent -import org.bukkit.event.inventory.InventoryDragEvent import org.bukkit.inventory.Inventory import org.bukkit.inventory.InventoryHolder @@ -17,7 +17,7 @@ abstract class GuiyInventoryHolder : InventoryHolder { override fun getInventory(): Inventory = activeInventory ?: error("Guiy inventory is used in bukkit but has not been rendered yet.") - abstract fun processClick(clickEvent: InventoryClickEvent) + abstract fun processClick(scope: ClickScope, event: Cancellable) abstract fun processDrag(scope: DragScope) abstract fun onClose(player: Player) diff --git a/src/main/kotlin/com/mineinabyss/guiy/inventory/GuiyOwner.kt b/src/main/kotlin/com/mineinabyss/guiy/inventory/GuiyOwner.kt index 205b85d..ef9f15b 100644 --- a/src/main/kotlin/com/mineinabyss/guiy/inventory/GuiyOwner.kt +++ b/src/main/kotlin/com/mineinabyss/guiy/inventory/GuiyOwner.kt @@ -3,22 +3,31 @@ package com.mineinabyss.guiy.inventory import androidx.compose.runtime.* import androidx.compose.runtime.snapshots.Snapshot import com.mineinabyss.guiy.layout.LayoutNode -import com.mineinabyss.guiy.modifiers.ClickScope +import com.mineinabyss.guiy.modifiers.click.ClickScope import com.mineinabyss.guiy.modifiers.Constraints -import com.mineinabyss.guiy.modifiers.DragScope +import com.mineinabyss.guiy.modifiers.drag.DragScope import com.mineinabyss.guiy.nodes.GuiyNodeApplier import kotlinx.coroutines.* -import org.bukkit.event.inventory.InventoryClickEvent -import org.bukkit.event.inventory.InventoryDragEvent import kotlin.coroutines.CoroutineContext val LocalClickHandler: ProvidableCompositionLocal = staticCompositionLocalOf { error("No provider for local click handler") } val LocalCanvas: ProvidableCompositionLocal = staticCompositionLocalOf { null } +val LocalGuiyOwner: ProvidableCompositionLocal = + staticCompositionLocalOf { error("No provider for GuiyOwner") } + +data class ClickResult( + val cancelBukkitEvent: Boolean? = null, +) { + fun mergeWith(other: ClickResult) = ClickResult( + // Prioritize true > false > null + cancelBukkitEvent = (cancelBukkitEvent ?: other.cancelBukkitEvent)?.or(other.cancelBukkitEvent ?: false) + ) +} interface ClickHandler { - fun processClick(scope: ClickScope, clickEvent: InventoryClickEvent) + fun processClick(scope: ClickScope): ClickResult fun processDrag(scope: DragScope) } @@ -52,7 +61,7 @@ class GuiyOwner : CoroutineScope { exitScheduled = true } - fun start(content: @Composable GuiyOwner.() -> Unit) { + fun start(content: @Composable () -> Unit) { !running || return running = true @@ -81,18 +90,18 @@ class GuiyOwner : CoroutineScope { } } - private fun setContent(content: @Composable GuiyOwner.() -> Unit) { + private fun setContent(content: @Composable () -> Unit) { hasFrameWaiters = true composition.setContent { CompositionLocalProvider(LocalClickHandler provides object : ClickHandler { - override fun processClick(scope: ClickScope, clickEvent: InventoryClickEvent) { - val slot = clickEvent.slot + override fun processClick(scope: ClickScope): ClickResult { + val slot = scope.slot val width = rootNode.width - rootNode.children.forEach { node -> + return rootNode.children.fold(ClickResult()) { acc, node -> val w = node.width val x = if (w == 0) 0 else slot % width val y = if (w == 0) 0 else slot / width - rootNode.processClick(scope, x, y) + acc.mergeWith(rootNode.processClick(scope, x, y)) } } @@ -107,9 +116,22 @@ class GuiyOwner : CoroutineScope { } fun guiy( - content: @Composable GuiyOwner.() -> Unit + content: @Composable () -> Unit ): GuiyOwner { return GuiyOwner().apply { - start(content) + start { + GuiyCompositionLocal(this) { + content() + } + } + } +} + +@Composable +fun GuiyCompositionLocal(owner: GuiyOwner, content: @Composable () -> Unit) { + CompositionLocalProvider( + LocalGuiyOwner provides owner + ) { + content() } } diff --git a/src/main/kotlin/com/mineinabyss/guiy/layout/Box.kt b/src/main/kotlin/com/mineinabyss/guiy/layout/Box.kt index 6dd62e2..66c3415 100644 --- a/src/main/kotlin/com/mineinabyss/guiy/layout/Box.kt +++ b/src/main/kotlin/com/mineinabyss/guiy/layout/Box.kt @@ -5,6 +5,9 @@ import com.mineinabyss.guiy.modifiers.Modifier import com.mineinabyss.guiy.nodes.ColumnMeasurePolicy import com.mineinabyss.guiy.nodes.RowMeasurePolicy +/** + * A layout component that places contents in a row left-to-right. + */ @Composable fun Row(modifier: Modifier = Modifier, content: @Composable () -> Unit) { Layout( @@ -14,6 +17,9 @@ fun Row(modifier: Modifier = Modifier, content: @Composable () -> Unit) { ) } +/** + * A layout component that places contents in a column top-to-bottom. + */ @Composable fun Column(modifier: Modifier = Modifier, content: @Composable () -> Unit) { Layout( diff --git a/src/main/kotlin/com/mineinabyss/guiy/layout/Layout.kt b/src/main/kotlin/com/mineinabyss/guiy/layout/Layout.kt index 2955a97..04842a4 100644 --- a/src/main/kotlin/com/mineinabyss/guiy/layout/Layout.kt +++ b/src/main/kotlin/com/mineinabyss/guiy/layout/Layout.kt @@ -7,6 +7,9 @@ import com.mineinabyss.guiy.modifiers.Modifier import com.mineinabyss.guiy.nodes.GuiyNode import com.mineinabyss.guiy.nodes.GuiyNodeApplier +/** + * The main component for layout, it measures and positions zero or more children. + */ @Composable inline fun Layout( measurePolicy: MeasurePolicy, diff --git a/src/main/kotlin/com/mineinabyss/guiy/layout/LayoutNode.kt b/src/main/kotlin/com/mineinabyss/guiy/layout/LayoutNode.kt index 04244c5..7cf49dc 100644 --- a/src/main/kotlin/com/mineinabyss/guiy/layout/LayoutNode.kt +++ b/src/main/kotlin/com/mineinabyss/guiy/layout/LayoutNode.kt @@ -1,9 +1,15 @@ package com.mineinabyss.guiy.layout -import com.mineinabyss.guiy.components.canvases.CHEST_WIDTH +import com.mineinabyss.guiy.components.state.IntCoordinates +import com.mineinabyss.guiy.components.state.ItemPositions +import com.mineinabyss.guiy.inventory.ClickResult import com.mineinabyss.guiy.inventory.GuiyCanvas import com.mineinabyss.guiy.inventory.OffsetCanvas import com.mineinabyss.guiy.modifiers.* +import com.mineinabyss.guiy.modifiers.click.ClickModifier +import com.mineinabyss.guiy.modifiers.click.ClickScope +import com.mineinabyss.guiy.modifiers.drag.DragModifier +import com.mineinabyss.guiy.modifiers.drag.DragScope import com.mineinabyss.guiy.nodes.GuiyNode import org.bukkit.inventory.ItemStack import kotlin.reflect.KClass @@ -88,39 +94,46 @@ internal class LayoutNode : Measurable, Placeable, GuiyNode { for (child in children) child.renderTo(offsetCanvas) } - fun processClick(scope: ClickScope, x: Int, y: Int) { - get()?.onClick?.invoke(scope) - children.filter { x in it.x until (it.x + it.width) && y in it.y until (it.y + it.height) } - .forEach { it.processClick(scope, x - it.x, y - it.y) } + /** + * @return Whether no elements were clickable or any element requested the bukkit click event to be cancelled. + */ + fun processClick(scope: ClickScope, x: Int, y: Int): ClickResult { + val cancelClickEvent = get()?.run { + onClick.invoke(scope) + cancelClickEvent + } + return children + .filter { x in it.x until (it.x + it.width) && y in it.y until (it.y + it.height) } + .fold(ClickResult(cancelClickEvent)) { acc, it -> + acc.mergeWith(it.processClick(scope, x - it.x, y - it.y)) + } } - fun indexIntersects(child: GuiyNode): Boolean { - return true - } - class DragInfo( + data class DragInfo( val dragModifier: DragModifier, - val itemMap: MutableMap = mutableMapOf(), + val itemMap: ItemPositions = ItemPositions(), ) - fun buildDragMap(index: Int, item: ItemStack, dragMap: MutableMap): Boolean { - val iX = index % CHEST_WIDTH - val iY = index / CHEST_WIDTH + fun buildDragMap(coords: IntCoordinates, item: ItemStack, dragMap: MutableMap): Boolean { + val (iX, iY) = coords if (iX !in 0 until width || iY !in 0 until height) return false val dragModifier = get() - if(dragModifier != null) { - dragMap.getOrPut(this) { DragInfo(dragModifier) }.itemMap[index] = item + if (dragModifier != null) { + val drag = dragMap.getOrPut(this) { DragInfo(dragModifier) } + dragMap[this] = drag.copy(itemMap = drag.itemMap.plus(iX, iY, item)) return true } return children.any { child -> - val newIndex = (iX - x + child.x) + (iY - x + child.x) * CHEST_WIDTH - child.buildDragMap(newIndex, item, dragMap) + //TODO ensure this offset is still correct in x,y + val newCoords = IntCoordinates(iX - x + child.x, iY - x + child.x) + child.buildDragMap(newCoords, item, dragMap) } } fun processDrag(scope: DragScope) { val dragMap = mutableMapOf() - scope.updatedItems.forEach { (i, item) -> - buildDragMap(i, item, dragMap) + scope.updatedItems.items.forEach { (coords, item) -> + buildDragMap(coords, item, dragMap) } dragMap.forEach { (_, info) -> info.dragModifier.onDrag.invoke(scope.copy(updatedItems = info.itemMap)) diff --git a/src/main/kotlin/com/mineinabyss/guiy/layout/Size.kt b/src/main/kotlin/com/mineinabyss/guiy/layout/Size.kt index 862224c..ef028b0 100644 --- a/src/main/kotlin/com/mineinabyss/guiy/layout/Size.kt +++ b/src/main/kotlin/com/mineinabyss/guiy/layout/Size.kt @@ -1,5 +1,8 @@ package com.mineinabyss.guiy.layout +import androidx.compose.runtime.Immutable + +@Immutable data class Size( val width: Int = 0, val height: Int = 0 diff --git a/src/main/kotlin/com/mineinabyss/guiy/modifiers/ClickModifier.kt b/src/main/kotlin/com/mineinabyss/guiy/modifiers/ClickModifier.kt deleted file mode 100644 index ed5d2e5..0000000 --- a/src/main/kotlin/com/mineinabyss/guiy/modifiers/ClickModifier.kt +++ /dev/null @@ -1,42 +0,0 @@ -package com.mineinabyss.guiy.modifiers - -import org.bukkit.event.inventory.ClickType -import org.bukkit.event.inventory.DragType -import org.bukkit.inventory.ItemStack - -data class ClickScope( - val clickType: ClickType, - val slot: Int, - var cursor: ItemStack? -) - -data class DragScope( - val dragType: DragType, - val updatedItems: Map, - var cursor: ItemStack? -) - -open class ClickModifier( - val merged: Boolean = false, - val onClick: (ClickScope.() -> Unit), -) : Modifier.Element { - override fun mergeWith(other: ClickModifier) = ClickModifier(merged = true) { - if (!other.merged) - onClick() - other.onClick(this) - } -} - -open class DragModifier( - val merged: Boolean = false, - val onDrag: (DragScope.() -> Unit), -) : Modifier.Element { - override fun mergeWith(other: DragModifier) = DragModifier(merged = true) { - if (!other.merged) - onDrag() - other.onDrag(this) - } -} - -fun Modifier.clickable(onClick: ClickScope.() -> Unit) = then(ClickModifier(onClick = onClick)) -fun Modifier.draggable(onDrag: DragScope.() -> Unit) = then(DragModifier(onDrag = onDrag)) diff --git a/src/main/kotlin/com/mineinabyss/guiy/modifiers/Constraints.kt b/src/main/kotlin/com/mineinabyss/guiy/modifiers/Constraints.kt index 953a6b3..3ff36b2 100644 --- a/src/main/kotlin/com/mineinabyss/guiy/modifiers/Constraints.kt +++ b/src/main/kotlin/com/mineinabyss/guiy/modifiers/Constraints.kt @@ -1,5 +1,8 @@ package com.mineinabyss.guiy.modifiers +import androidx.compose.runtime.Immutable + +@Immutable data class Constraints( val minWidth: Int = 0, val maxWidth: Int = Int.MAX_VALUE, diff --git a/src/main/kotlin/com/mineinabyss/guiy/modifiers/OnSizeChangedModifier.kt b/src/main/kotlin/com/mineinabyss/guiy/modifiers/OnSizeChangedModifier.kt index 033cda8..c5711a6 100644 --- a/src/main/kotlin/com/mineinabyss/guiy/modifiers/OnSizeChangedModifier.kt +++ b/src/main/kotlin/com/mineinabyss/guiy/modifiers/OnSizeChangedModifier.kt @@ -14,6 +14,7 @@ class OnSizeChangedModifier( } } +/** Notifies callback of any size changes to element. */ fun Modifier.onSizeChanged(onSizeChanged: (Size) -> Unit) = then( OnSizeChangedModifier(onSizeChanged = onSizeChanged) ) diff --git a/src/main/kotlin/com/mineinabyss/guiy/modifiers/PositionModifier.kt b/src/main/kotlin/com/mineinabyss/guiy/modifiers/PositionModifier.kt index a51fc6a..cd6dc3d 100644 --- a/src/main/kotlin/com/mineinabyss/guiy/modifiers/PositionModifier.kt +++ b/src/main/kotlin/com/mineinabyss/guiy/modifiers/PositionModifier.kt @@ -7,4 +7,5 @@ class PositionModifier( override fun mergeWith(other: PositionModifier) = other } +/** Places an element at an absolute offset in the inventory. */ fun Modifier.at(x: Int = 0, y: Int = 0) = then(PositionModifier(x, y)) diff --git a/src/main/kotlin/com/mineinabyss/guiy/modifiers/SizeModifier.kt b/src/main/kotlin/com/mineinabyss/guiy/modifiers/SizeModifier.kt index e56ab65..00ba042 100644 --- a/src/main/kotlin/com/mineinabyss/guiy/modifiers/SizeModifier.kt +++ b/src/main/kotlin/com/mineinabyss/guiy/modifiers/SizeModifier.kt @@ -45,6 +45,18 @@ fun Constraints.applyFill(horizontal: HorizontalFillModifier?, vertical: Vertica ) } +/** Forces element width to a percentage between min and max width constraints */ +fun Modifier.fillMaxWidth(percent: Double = 1.0) = then(HorizontalFillModifier(percent)) + +/** Forces element height to a percentage between min and max height constraints */ +fun Modifier.fillMaxHeight(percent: Double = 1.0) = then(VerticalFillModifier(percent)) + +/** Forces element width and height to a percentage between min and max width and height constraints */ +fun Modifier.fillMaxSize(percent: Double = 1.0) = then(HorizontalFillModifier(percent)).then(VerticalFillModifier(percent)) + +/** + * Sets min and max, width and height constraints for this element. + */ fun Modifier.sizeIn( minWidth: Int = 0, maxWidth: Int = Integer.MAX_VALUE, @@ -52,8 +64,11 @@ fun Modifier.sizeIn( maxHeight: Int = Integer.MAX_VALUE, ) = then(SizeModifier(Constraints(minWidth, maxWidth, minHeight, maxHeight))) +/** Sets identical min/max width and height constraints for this element. */ fun Modifier.size(width: Int, height: Int) = then(sizeIn(width, width, height, height)) +/** Sets identical min/max width constraints for this element. */ fun Modifier.width(width: Int) = then(sizeIn(width, width, 0, Integer.MAX_VALUE)) +/** Sets identical min/max height constraints for this element. */ fun Modifier.height(height: Int) = then(sizeIn(0, Integer.MAX_VALUE, height, height)) diff --git a/src/main/kotlin/com/mineinabyss/guiy/modifiers/click/ClickModifier.kt b/src/main/kotlin/com/mineinabyss/guiy/modifiers/click/ClickModifier.kt new file mode 100644 index 0000000..fb8b118 --- /dev/null +++ b/src/main/kotlin/com/mineinabyss/guiy/modifiers/click/ClickModifier.kt @@ -0,0 +1,37 @@ +package com.mineinabyss.guiy.modifiers.click + +import com.mineinabyss.guiy.modifiers.Modifier + +open class ClickModifier( + val merged: Boolean = false, + val cancelClickEvent: Boolean, + val onClick: (ClickScope.() -> Unit), +// val allowClick: (ClickScope.() -> Boolean) +) : Modifier.Element { + override fun mergeWith(other: ClickModifier) = ClickModifier( + merged = true, + cancelClickEvent = cancelClickEvent || other.cancelClickEvent, + onClick = { + if (!other.merged) + onClick() + other.onClick(this) + }, + /*allowClick = { + if (!other.merged) + allowClick() + other.allowClick(this) + }*/ + ) +} + +fun Modifier.clickable( + cancelClickEvent: Boolean = true, +// allowClick: ClickScope.() -> Boolean = { true }, + onClick: ClickScope.() -> Unit +) = + then( + ClickModifier( + cancelClickEvent = cancelClickEvent, + onClick = onClick, /*allowClick = allowClick*/ + ) + ) diff --git a/src/main/kotlin/com/mineinabyss/guiy/modifiers/click/ClickScope.kt b/src/main/kotlin/com/mineinabyss/guiy/modifiers/click/ClickScope.kt new file mode 100644 index 0000000..27eecc6 --- /dev/null +++ b/src/main/kotlin/com/mineinabyss/guiy/modifiers/click/ClickScope.kt @@ -0,0 +1,12 @@ +package com.mineinabyss.guiy.modifiers.click + +import org.bukkit.entity.HumanEntity +import org.bukkit.event.inventory.ClickType +import org.bukkit.inventory.ItemStack + +data class ClickScope( + val clickType: ClickType, + val slot: Int, + val cursor: ItemStack?, + val whoClicked: HumanEntity +) diff --git a/src/main/kotlin/com/mineinabyss/guiy/modifiers/drag/DragModifier.kt b/src/main/kotlin/com/mineinabyss/guiy/modifiers/drag/DragModifier.kt new file mode 100644 index 0000000..6c05b18 --- /dev/null +++ b/src/main/kotlin/com/mineinabyss/guiy/modifiers/drag/DragModifier.kt @@ -0,0 +1,17 @@ +package com.mineinabyss.guiy.modifiers.drag + +import com.mineinabyss.guiy.modifiers.Modifier + +open class DragModifier( + val merged: Boolean = false, + val onDrag: (DragScope.() -> Unit), +) : Modifier.Element { + override fun mergeWith(other: DragModifier) = DragModifier(merged = true) { + if (!other.merged) + onDrag() + other.onDrag(this) + } +} + +//TODO reimplement properly +//fun Modifier.draggable(onDrag: DragScope.() -> Unit) = then(DragModifier(onDrag = onDrag)) diff --git a/src/main/kotlin/com/mineinabyss/guiy/modifiers/drag/DragScope.kt b/src/main/kotlin/com/mineinabyss/guiy/modifiers/drag/DragScope.kt new file mode 100644 index 0000000..0c2dba8 --- /dev/null +++ b/src/main/kotlin/com/mineinabyss/guiy/modifiers/drag/DragScope.kt @@ -0,0 +1,13 @@ +package com.mineinabyss.guiy.modifiers.drag + +import androidx.compose.runtime.Immutable +import com.mineinabyss.guiy.components.state.ItemPositions +import org.bukkit.event.inventory.DragType +import org.bukkit.inventory.ItemStack + +@Immutable +data class DragScope( + val dragType: DragType, + val updatedItems: ItemPositions, + var cursor: ItemStack? +)