Removed "Inventory" from the GUI API names
This commit is contained in:
182
src/main/kotlin/net/axay/kspigot/gui/GUI.kt
Normal file
182
src/main/kotlin/net/axay/kspigot/gui/GUI.kt
Normal file
@@ -0,0 +1,182 @@
|
||||
@file:Suppress("MemberVisibilityCanBePrivate")
|
||||
|
||||
package net.axay.kspigot.gui
|
||||
|
||||
import org.bukkit.inventory.Inventory
|
||||
import org.bukkit.inventory.ItemStack
|
||||
|
||||
private const val DEFAULT_PAGE = 1
|
||||
|
||||
class GUIData<T : ForInventory>(
|
||||
val guiType: GUIType<T>,
|
||||
val title: String?,
|
||||
internal val pages: Map<Int, GUIPage<T>>,
|
||||
val transitionTo: InventoryChangeEffect?,
|
||||
val transitionFrom: InventoryChangeEffect?,
|
||||
internal val generalOnClick: ((GUIClickEvent<T>) -> Unit)?
|
||||
)
|
||||
|
||||
abstract class GUI<T : ForInventory>(
|
||||
val data: GUIData<T>
|
||||
) {
|
||||
|
||||
var currentPageInt: Int = DEFAULT_PAGE; protected set
|
||||
val currentPage
|
||||
get() = getPage(currentPageInt)
|
||||
?: throw IllegalStateException("The currentPageInt has no associated page!")
|
||||
|
||||
internal abstract val bukkitInventory: Inventory
|
||||
|
||||
internal var isInMove: Boolean = false
|
||||
|
||||
internal val currentElements = HashSet<GUIElement<*>>()
|
||||
|
||||
internal abstract fun loadPageUnsafe(
|
||||
page: Int,
|
||||
offsetHorizontally: Int = 0,
|
||||
offsetVertically: Int = 0
|
||||
)
|
||||
|
||||
internal abstract fun loadPageUnsafe(
|
||||
page: GUIPage<*>,
|
||||
offsetHorizontally: Int = 0,
|
||||
offsetVertically: Int = 0
|
||||
)
|
||||
|
||||
internal abstract fun loadContent(
|
||||
content: Map<Int, GUISlot<*>>,
|
||||
offsetHorizontally: Int = 0,
|
||||
offsetVertically: Int = 0
|
||||
)
|
||||
|
||||
/**
|
||||
* @return True, if the [inventory] belongs to this GUI.
|
||||
*/
|
||||
abstract fun isThisInv(inventory: Inventory): Boolean
|
||||
|
||||
/**
|
||||
* Registers this GUI.
|
||||
* (KSpigot will listen for actions in the inventory.)
|
||||
*/
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
fun register() = GUIHolder.register(this as GUI<ForInventory>)
|
||||
|
||||
/**
|
||||
* Stops KSpigot from listening to actions in this
|
||||
* GUI anymore.
|
||||
*/
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
fun unregister() = GUIHolder.unregister(this as GUI<ForInventory>)
|
||||
|
||||
/**
|
||||
* Loads the specified page in order to display it in the GUI.
|
||||
*/
|
||||
fun loadPage(page: GUIPage<T>) = loadPageUnsafe(page)
|
||||
|
||||
/**
|
||||
* Temporarily sets the given item at the given slots.
|
||||
*/
|
||||
abstract operator fun set(slot: InventorySlotCompound<T>, value: ItemStack)
|
||||
|
||||
/**
|
||||
* Searches for a page associated to the given [page] index.
|
||||
*/
|
||||
fun getPage(page: Int?) = data.pages[page]
|
||||
|
||||
/**
|
||||
* Reloads the current page.
|
||||
*/
|
||||
fun reloadCurrentPage() {
|
||||
if (!isInMove)
|
||||
loadPage(currentPage)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Inventory GUI implementations
|
||||
|
||||
class GUIShared<T : ForInventory>(
|
||||
GUIData: GUIData<T>
|
||||
) : GUI<T>(GUIData) {
|
||||
|
||||
override val bukkitInventory = data.guiType.createBukkitInv(null, data.title)
|
||||
|
||||
init {
|
||||
loadPageUnsafe(DEFAULT_PAGE)
|
||||
}
|
||||
|
||||
override fun isThisInv(inventory: Inventory) = inventory == bukkitInventory
|
||||
|
||||
override fun loadPageUnsafe(page: Int, offsetHorizontally: Int, offsetVertically: Int) {
|
||||
data.pages[page]?.let { loadPageUnsafe(it, offsetHorizontally, offsetVertically) }
|
||||
}
|
||||
|
||||
override fun loadPageUnsafe(page: GUIPage<*>, offsetHorizontally: Int, offsetVertically: Int) {
|
||||
|
||||
val ifOffset = offsetHorizontally != 0 || offsetVertically != 0
|
||||
|
||||
if (!ifOffset) {
|
||||
|
||||
// unregister this inv from all elements on the previous page
|
||||
currentElements.forEach { it.stopUsing(this) }
|
||||
currentElements.clear()
|
||||
|
||||
// register this inv for all new elements
|
||||
HashSet(page.slots.values).forEach { if (it is GUIElement) {
|
||||
currentElements += it
|
||||
it.startUsing(this)
|
||||
} }
|
||||
|
||||
currentPageInt = page.number
|
||||
|
||||
}
|
||||
|
||||
loadContent(page.slots, offsetHorizontally, offsetVertically)
|
||||
|
||||
}
|
||||
|
||||
override fun loadContent(
|
||||
content: Map<Int, GUISlot<*>>,
|
||||
offsetHorizontally: Int,
|
||||
offsetVertically: Int
|
||||
) {
|
||||
|
||||
val ifOffset = offsetHorizontally != 0 || offsetVertically != 0
|
||||
|
||||
val dimensions = data.guiType.dimensions
|
||||
|
||||
// clear the space which will be redefined
|
||||
if (ifOffset) {
|
||||
dimensions.invSlots.forEach {
|
||||
val slotToClear = dimensions.invSlotsWithRealSlots[it.add(offsetHorizontally, offsetVertically)]
|
||||
if (slotToClear != null) bukkitInventory.clear(slotToClear)
|
||||
}
|
||||
} else bukkitInventory.clear()
|
||||
|
||||
// render the given content
|
||||
content.forEach {
|
||||
|
||||
val slot = it.value
|
||||
if (slot is GUIElement) {
|
||||
|
||||
if (ifOffset) {
|
||||
val invSlot = InventorySlot.fromRealSlot(it.key, dimensions)
|
||||
if (invSlot != null) {
|
||||
val offsetSlot = invSlot.add(offsetHorizontally, offsetVertically).realSlotIn(dimensions)
|
||||
if (offsetSlot != null) bukkitInventory.setItem(offsetSlot, slot.getItemStack(offsetSlot))
|
||||
}
|
||||
} else bukkitInventory.setItem(it.key, slot.getItemStack(it.key))
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
override operator fun set(slot: InventorySlotCompound<T>, value: ItemStack) {
|
||||
slot.realSlotsWithInvType(data.guiType).forEach {
|
||||
bukkitInventory.setItem(it, value)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
254
src/main/kotlin/net/axay/kspigot/gui/GUIBuilder.kt
Normal file
254
src/main/kotlin/net/axay/kspigot/gui/GUIBuilder.kt
Normal file
@@ -0,0 +1,254 @@
|
||||
@file:Suppress("MemberVisibilityCanBePrivate", "unused")
|
||||
|
||||
package net.axay.kspigot.gui
|
||||
|
||||
import net.axay.kspigot.gui.elements.*
|
||||
import org.bukkit.inventory.ItemStack
|
||||
import kotlin.math.absoluteValue
|
||||
|
||||
fun <T : ForInventory> kSpigotGUI(
|
||||
type: GUIType<T>,
|
||||
shared: Boolean = true,
|
||||
builder: GUIBuilder<T>.() -> Unit,
|
||||
) = GUIBuilder(type, shared).apply(builder).build()
|
||||
|
||||
class GUIBuilder<T : ForInventory>(
|
||||
val type: GUIType<T>,
|
||||
val shared: Boolean
|
||||
) {
|
||||
|
||||
var title: String = ""
|
||||
|
||||
var transitionTo: InventoryChangeEffect? = null
|
||||
var transitionFrom: InventoryChangeEffect? = null
|
||||
|
||||
private val guiSlots = HashMap<Int, GUIPage<T>>()
|
||||
|
||||
private var onClickElement: ((GUIClickEvent<T>) -> Unit)? = null
|
||||
|
||||
/**
|
||||
* Opens the builder for a new page and adds
|
||||
* the new page to the GUI.
|
||||
* @param page The index of the page.
|
||||
*/
|
||||
fun page(page: Int, builder: GUIPageBuilder<T>.() -> Unit) {
|
||||
guiSlots[page] = GUIPageBuilder(type, page).apply(builder).build()
|
||||
}
|
||||
|
||||
fun onClickElement(onClick: (GUIClickEvent<T>) -> Unit) {
|
||||
onClickElement = onClick
|
||||
}
|
||||
|
||||
internal fun build(): GUI<T> {
|
||||
val guiData = GUIData(type, title, guiSlots, transitionTo, transitionFrom, onClickElement)
|
||||
val gui =
|
||||
if (shared) GUIShared(guiData) else TODO("Currently, there is no non-shared GUI implementation available.")
|
||||
return gui.apply { register() }
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class GUIPageBuilder<T : ForInventory>(
|
||||
private val type: GUIType<T>,
|
||||
val page: Int
|
||||
) {
|
||||
|
||||
private val guiSlots = HashMap<Int, GUISlot<T>>()
|
||||
|
||||
var transitionTo: PageChangeEffect? = null
|
||||
var transitionFrom: PageChangeEffect? = null
|
||||
|
||||
internal fun build() = GUIPage(page, guiSlots, transitionTo, transitionFrom)
|
||||
|
||||
private fun defineSlots(slots: InventorySlotCompound<T>, element: GUISlot<T>) =
|
||||
slots.withInvType(type).forEach { curSlot ->
|
||||
curSlot.realSlotIn(type.dimensions)?.let { guiSlots[it] = element }
|
||||
}
|
||||
|
||||
/**
|
||||
* A button is an item protected from any player
|
||||
* actions. If clicked, the specified [onClick]
|
||||
* function is invoked.
|
||||
*/
|
||||
fun button(slots: InventorySlotCompound<T>, itemStack: ItemStack, onClick: (GUIClickEvent<T>) -> Unit) =
|
||||
defineSlots(slots, GUIButton(itemStack, onClick))
|
||||
|
||||
/**
|
||||
* An item protected from any player actions.
|
||||
* This is not a button.
|
||||
*/
|
||||
fun placeholder(slots: InventorySlotCompound<T>, itemStack: ItemStack) =
|
||||
defineSlots(slots, GUIPlaceholder(itemStack))
|
||||
|
||||
/**
|
||||
* A free slot does not block any player actions.
|
||||
* The player can put items in this slot or take
|
||||
* items out of it.
|
||||
*/
|
||||
fun freeSlot(slots: InventorySlotCompound<T>) = defineSlots(slots, GUIFreeSlot())
|
||||
|
||||
/**
|
||||
* This is a button which loads the specified
|
||||
* [toPage] if clicked.
|
||||
*/
|
||||
fun pageChanger(
|
||||
slots: InventorySlotCompound<T>,
|
||||
icon: ItemStack,
|
||||
toPage: Int,
|
||||
onChange: ((GUIClickEvent<T>) -> Unit)? = null
|
||||
) = defineSlots(
|
||||
slots, GUIButtonPageChange(
|
||||
icon,
|
||||
GUIPageChangeCalculator.GUIConsistentPageCalculator(toPage),
|
||||
onChange
|
||||
)
|
||||
)
|
||||
|
||||
/**
|
||||
* This button always tries to find the previous
|
||||
* page if clicked, and if a previous page
|
||||
* exists it is loaded.
|
||||
*/
|
||||
fun previousPage(
|
||||
slots: InventorySlotCompound<T>,
|
||||
icon: ItemStack,
|
||||
onChange: ((GUIClickEvent<T>) -> Unit)? = null
|
||||
) = defineSlots(
|
||||
slots, GUIButtonPageChange(
|
||||
icon,
|
||||
GUIPageChangeCalculator.GUIPreviousPageCalculator,
|
||||
onChange
|
||||
)
|
||||
)
|
||||
|
||||
/**
|
||||
* This button always tries to find the next
|
||||
* page if clicked, and if a next page
|
||||
* exists it is loaded.
|
||||
*/
|
||||
fun nextPage(
|
||||
slots: InventorySlotCompound<T>,
|
||||
icon: ItemStack,
|
||||
onChange: ((GUIClickEvent<T>) -> Unit)? = null
|
||||
) = defineSlots(
|
||||
slots, GUIButtonPageChange(
|
||||
icon,
|
||||
GUIPageChangeCalculator.GUINextPageCalculator,
|
||||
onChange
|
||||
)
|
||||
)
|
||||
|
||||
/**
|
||||
* By pressing this button, the player switches to another
|
||||
* GUI. The transition effect is applied.
|
||||
*/
|
||||
fun changeGUI(
|
||||
slots: InventorySlotCompound<T>,
|
||||
icon: ItemStack,
|
||||
newGUI: () -> GUI<*>,
|
||||
newPage: Int? = null,
|
||||
onChange: ((GUIClickEvent<T>) -> Unit)? = null
|
||||
) = defineSlots(
|
||||
slots, GUIButtonInventoryChange(
|
||||
icon,
|
||||
newGUI,
|
||||
newPage,
|
||||
onChange
|
||||
)
|
||||
)
|
||||
|
||||
/**
|
||||
* Creates a new compound, holding data which can be displayed
|
||||
* in any compound space.
|
||||
*/
|
||||
fun <E> createCompound(
|
||||
iconGenerator: (E) -> ItemStack,
|
||||
onClick: (clickEvent: GUIClickEvent<T>, element: E) -> Unit
|
||||
) = GUISpaceCompound(type, iconGenerator, onClick)
|
||||
|
||||
/**
|
||||
* Creates a new compound, holding data which can be displayed
|
||||
* in any compound space.
|
||||
* This compound is strictly a rectangle.
|
||||
* The space is automatically defined.
|
||||
*/
|
||||
fun <E> createCompound(
|
||||
fromSlot: SingleInventorySlot<out T>,
|
||||
toSlot: SingleInventorySlot<out T>,
|
||||
iconGenerator: (E) -> ItemStack,
|
||||
onClick: (clickEvent: GUIClickEvent<T>, element: E) -> Unit
|
||||
): GUIRectSpaceCompound<T, E> {
|
||||
val rectSlotCompound = fromSlot rectTo toSlot
|
||||
return GUIRectSpaceCompound(
|
||||
type,
|
||||
iconGenerator,
|
||||
onClick,
|
||||
(rectSlotCompound.endInclusive.slotInRow - rectSlotCompound.start.slotInRow) + 1
|
||||
).apply {
|
||||
addSlots(rectSlotCompound)
|
||||
defineSlots(
|
||||
rectSlotCompound,
|
||||
GUISpaceCompoundElement(this)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Defines an area where the content of the given compound
|
||||
* is displayed.
|
||||
*/
|
||||
fun <E> compoundSpace(
|
||||
slots: InventorySlotCompound<T>,
|
||||
compound: AbstractGUISpaceCompound<T, E>
|
||||
) {
|
||||
compound.addSlots(slots)
|
||||
defineSlots(
|
||||
slots,
|
||||
GUISpaceCompoundElement(compound)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* By pressing this button,
|
||||
* the user scrolls forwards or backwards in the compound.
|
||||
*/
|
||||
fun compoundScroll(
|
||||
slots: InventorySlotCompound<T>,
|
||||
icon: ItemStack,
|
||||
compound: GUISpaceCompound<T, *>,
|
||||
scrollDistance: Int = 1,
|
||||
scrollTimes: Int = 1,
|
||||
reverse: Boolean = false
|
||||
) = defineSlots(
|
||||
slots,
|
||||
GUISpaceCompoundScrollButton(
|
||||
icon,
|
||||
compound,
|
||||
scrollDistance.absoluteValue,
|
||||
scrollTimes,
|
||||
reverse
|
||||
)
|
||||
)
|
||||
|
||||
/**
|
||||
* By pressing this button,
|
||||
* the user scrolls forwards or backwards in the compound.
|
||||
*/
|
||||
fun compoundScroll(
|
||||
slots: InventorySlotCompound<T>,
|
||||
icon: ItemStack,
|
||||
compound: GUIRectSpaceCompound<T, *>,
|
||||
scrollTimes: Int = 1,
|
||||
reverse: Boolean = false
|
||||
) = defineSlots(
|
||||
slots,
|
||||
GUISpaceCompoundScrollButton(
|
||||
icon,
|
||||
compound,
|
||||
compound.compoundWidth,
|
||||
scrollTimes,
|
||||
reverse
|
||||
)
|
||||
)
|
||||
|
||||
}
|
8
src/main/kotlin/net/axay/kspigot/gui/GUIClickEvent.kt
Normal file
8
src/main/kotlin/net/axay/kspigot/gui/GUIClickEvent.kt
Normal file
@@ -0,0 +1,8 @@
|
||||
package net.axay.kspigot.gui
|
||||
|
||||
import org.bukkit.event.inventory.InventoryClickEvent
|
||||
|
||||
class GUIClickEvent<T : ForInventory>(
|
||||
val bukkitEvent: InventoryClickEvent,
|
||||
val gui: GUI<T>
|
||||
)
|
25
src/main/kotlin/net/axay/kspigot/gui/GUIElements.kt
Normal file
25
src/main/kotlin/net/axay/kspigot/gui/GUIElements.kt
Normal file
@@ -0,0 +1,25 @@
|
||||
package net.axay.kspigot.gui
|
||||
|
||||
import org.bukkit.inventory.ItemStack
|
||||
|
||||
abstract class GUISlot<T : ForInventory> {
|
||||
abstract fun onClick(clickEvent: GUIClickEvent<T>)
|
||||
}
|
||||
|
||||
// ELEMENT
|
||||
|
||||
abstract class GUIElement<T : ForInventory> : GUISlot<T>() {
|
||||
|
||||
abstract fun getItemStack(slot: Int): ItemStack
|
||||
|
||||
final override fun onClick(clickEvent: GUIClickEvent<T>) {
|
||||
clickEvent.gui.data.generalOnClick?.invoke(clickEvent)
|
||||
onClickElement(clickEvent)
|
||||
}
|
||||
|
||||
protected abstract fun onClickElement(clickEvent: GUIClickEvent<T>)
|
||||
|
||||
internal open fun startUsing(gui: GUI<*>) { }
|
||||
internal open fun stopUsing(gui: GUI<*>) { }
|
||||
|
||||
}
|
15
src/main/kotlin/net/axay/kspigot/gui/GUIExtensions.kt
Normal file
15
src/main/kotlin/net/axay/kspigot/gui/GUIExtensions.kt
Normal file
@@ -0,0 +1,15 @@
|
||||
package net.axay.kspigot.gui
|
||||
|
||||
import org.bukkit.entity.HumanEntity
|
||||
import org.bukkit.inventory.InventoryView
|
||||
|
||||
fun HumanEntity.openGUI(gui: GUI<*>, page: Int? = null): InventoryView? {
|
||||
|
||||
closeInventory()
|
||||
|
||||
if (page != null)
|
||||
gui.loadPageUnsafe(page)
|
||||
|
||||
return openInventory(gui.bukkitInventory)
|
||||
|
||||
}
|
50
src/main/kotlin/net/axay/kspigot/gui/GUIHolder.kt
Normal file
50
src/main/kotlin/net/axay/kspigot/gui/GUIHolder.kt
Normal file
@@ -0,0 +1,50 @@
|
||||
package net.axay.kspigot.gui
|
||||
|
||||
import net.axay.kspigot.event.listen
|
||||
import org.bukkit.event.inventory.InventoryAction
|
||||
import org.bukkit.event.inventory.InventoryClickEvent
|
||||
|
||||
object GUIHolder : AutoCloseable {
|
||||
|
||||
private val registered = HashSet<GUI<ForInventory>>()
|
||||
|
||||
fun register(GUI: GUI<ForInventory>) {
|
||||
registered.add(GUI)
|
||||
}
|
||||
|
||||
fun unregister(GUI: GUI<ForInventory>) {
|
||||
registered.remove(GUI)
|
||||
}
|
||||
|
||||
init {
|
||||
|
||||
listen<InventoryClickEvent> {
|
||||
|
||||
val clickedInv = it.clickedInventory ?: return@listen
|
||||
|
||||
val inv = registered.find { search -> search.isThisInv(clickedInv) } ?: return@listen
|
||||
|
||||
if (inv.isInMove) {
|
||||
it.isCancelled = true
|
||||
return@listen
|
||||
}
|
||||
|
||||
if (it.action.isGUIClick)
|
||||
inv.currentPage.slots[it.slot]?.onClick(GUIClickEvent(it, inv)) ?: kotlin.run {
|
||||
it.isCancelled = true
|
||||
}
|
||||
else
|
||||
it.isCancelled = true
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
override fun close() {
|
||||
registered.forEach { inv -> inv.bukkitInventory.viewers.forEach { it.closeInventory() } }
|
||||
registered.clear()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private val InventoryAction.isGUIClick
|
||||
get() = this == InventoryAction.PICKUP_ALL || this == InventoryAction.PICKUP_HALF
|
8
src/main/kotlin/net/axay/kspigot/gui/GUIPage.kt
Normal file
8
src/main/kotlin/net/axay/kspigot/gui/GUIPage.kt
Normal file
@@ -0,0 +1,8 @@
|
||||
package net.axay.kspigot.gui
|
||||
|
||||
class GUIPage<T : ForInventory>(
|
||||
val number: Int,
|
||||
internal val slots: Map<Int, GUISlot<T>>,
|
||||
val transitionTo: PageChangeEffect?,
|
||||
val transitionFrom: PageChangeEffect?
|
||||
)
|
143
src/main/kotlin/net/axay/kspigot/gui/GUIPageChange.kt
Normal file
143
src/main/kotlin/net/axay/kspigot/gui/GUIPageChange.kt
Normal file
@@ -0,0 +1,143 @@
|
||||
package net.axay.kspigot.gui
|
||||
|
||||
import net.axay.kspigot.runnables.task
|
||||
|
||||
abstract class GUIPageChangeCalculator {
|
||||
|
||||
abstract fun calculateNewPage(currentPage: Int, pages: Collection<Int>): Int?
|
||||
|
||||
object GUIPreviousPageCalculator : GUIPageChangeCalculator() {
|
||||
override fun calculateNewPage(currentPage: Int, pages: Collection<Int>) =
|
||||
pages.sortedDescending().find { it < currentPage }
|
||||
}
|
||||
|
||||
object GUINextPageCalculator : GUIPageChangeCalculator() {
|
||||
override fun calculateNewPage(currentPage: Int, pages: Collection<Int>) =
|
||||
pages.sorted().find { it > currentPage }
|
||||
}
|
||||
|
||||
class GUIConsistentPageCalculator(private val toPage: Int) : GUIPageChangeCalculator() {
|
||||
override fun calculateNewPage(currentPage: Int, pages: Collection<Int>) = toPage
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
enum class PageChangeEffect {
|
||||
INSTANT,
|
||||
SLIDE_HORIZONTALLY,
|
||||
SLIDE_VERTICALLY,
|
||||
SWIPE_HORIZONTALLY,
|
||||
SWIPE_VERTICALLY,
|
||||
}
|
||||
|
||||
enum class InventoryChangeEffect(
|
||||
val effect: PageChangeEffect
|
||||
) {
|
||||
INSTANT(PageChangeEffect.INSTANT)
|
||||
}
|
||||
|
||||
internal fun GUI<*>.changePage(
|
||||
effect: PageChangeEffect,
|
||||
fromPage: GUIPage<*>,
|
||||
toPage: GUIPage<*>
|
||||
) {
|
||||
|
||||
val fromPageInt = fromPage.number
|
||||
val toPageInt = toPage.number
|
||||
|
||||
when (effect) {
|
||||
|
||||
PageChangeEffect.INSTANT -> loadPageUnsafe(toPage)
|
||||
|
||||
PageChangeEffect.SLIDE_HORIZONTALLY -> {
|
||||
|
||||
val width = data.guiType.dimensions.width
|
||||
|
||||
changePageEffect(fromPageInt, toPageInt, width) { currentOffset, ifInverted ->
|
||||
if (ifInverted) {
|
||||
loadPageUnsafe(fromPage, offsetHorizontally = currentOffset)
|
||||
loadPageUnsafe(toPage, offsetHorizontally = -(width - currentOffset))
|
||||
} else {
|
||||
loadPageUnsafe(fromPage, offsetHorizontally = -currentOffset)
|
||||
loadPageUnsafe(toPage, offsetHorizontally = width - currentOffset)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
PageChangeEffect.SLIDE_VERTICALLY -> {
|
||||
|
||||
val height = data.guiType.dimensions.height
|
||||
|
||||
changePageEffect(fromPageInt, toPageInt, height) { currentOffset, ifInverted ->
|
||||
if (ifInverted) {
|
||||
loadPageUnsafe(fromPage, offsetVertically = currentOffset)
|
||||
loadPageUnsafe(toPage, offsetVertically = -(height - currentOffset))
|
||||
} else {
|
||||
loadPageUnsafe(fromPage, offsetVertically = -currentOffset)
|
||||
loadPageUnsafe(toPage, offsetVertically = height - currentOffset)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
PageChangeEffect.SWIPE_HORIZONTALLY -> {
|
||||
|
||||
val width = data.guiType.dimensions.width
|
||||
|
||||
changePageEffect(fromPageInt, toPageInt, width) { currentOffset, ifInverted ->
|
||||
if (ifInverted) {
|
||||
loadPageUnsafe(toPage, offsetHorizontally = -(width - currentOffset))
|
||||
} else {
|
||||
loadPageUnsafe(toPage, offsetHorizontally = width - currentOffset)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
PageChangeEffect.SWIPE_VERTICALLY -> {
|
||||
|
||||
val height = data.guiType.dimensions.height
|
||||
|
||||
changePageEffect(fromPageInt, toPageInt, height) { currentOffset, ifInverted ->
|
||||
if (ifInverted) {
|
||||
loadPageUnsafe(toPage, offsetVertically = -(height - currentOffset))
|
||||
} else {
|
||||
loadPageUnsafe(toPage, offsetVertically = height - currentOffset)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
internal fun GUI<*>.changeGUI(
|
||||
effect: InventoryChangeEffect,
|
||||
fromPage: GUIPage<*>,
|
||||
toPage: GUIPage<*>
|
||||
) = changePage(effect.effect, fromPage, toPage)
|
||||
|
||||
private inline fun changePageEffect(
|
||||
fromPage: Int,
|
||||
toPage: Int,
|
||||
doFor: Int,
|
||||
crossinline effect: (currentOffset: Int, ifInverted: Boolean) -> Unit,
|
||||
) {
|
||||
|
||||
val ifInverted = fromPage >= toPage
|
||||
|
||||
var currentOffset = 1
|
||||
task(
|
||||
sync = true,
|
||||
period = 1,
|
||||
howOften = doFor.toLong()
|
||||
) {
|
||||
|
||||
effect.invoke(currentOffset, ifInverted)
|
||||
|
||||
currentOffset++
|
||||
|
||||
}
|
||||
|
||||
}
|
377
src/main/kotlin/net/axay/kspigot/gui/GUISlots.kt
Normal file
377
src/main/kotlin/net/axay/kspigot/gui/GUISlots.kt
Normal file
@@ -0,0 +1,377 @@
|
||||
@file:Suppress("unused", "MemberVisibilityCanBePrivate")
|
||||
|
||||
package net.axay.kspigot.gui
|
||||
|
||||
import net.axay.kspigot.languageextensions.kotlinextensions.MinMaxPair
|
||||
|
||||
// INVENTORY
|
||||
|
||||
data class InventoryDimensions(val width: Int, val height: Int) {
|
||||
|
||||
val slotAmount = width * height
|
||||
|
||||
val invSlots by lazy {
|
||||
ArrayList<InventorySlot>().apply {
|
||||
(1..height).forEach { row ->
|
||||
(1..width).forEach { slotInRow ->
|
||||
this += InventorySlot(row, slotInRow)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val invSlotsWithRealSlots by lazy {
|
||||
HashMap<InventorySlot, Int>().apply {
|
||||
invSlots.forEach { curSlot ->
|
||||
curSlot.realSlotIn(this@InventoryDimensions)?.let { this[curSlot] = it }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val realSlots by lazy { invSlotsWithRealSlots.values }
|
||||
|
||||
}
|
||||
|
||||
// SLOTS
|
||||
|
||||
data class InventorySlot(val row: Int, val slotInRow: Int) : Comparable<InventorySlot> {
|
||||
|
||||
companion object {
|
||||
fun fromRealSlot(realSlot: Int, dimensions: InventoryDimensions) =
|
||||
dimensions.invSlotsWithRealSlots.toList().find { it.second == realSlot }?.first
|
||||
}
|
||||
|
||||
override fun compareTo(other: InventorySlot) = when {
|
||||
row > other.row -> 1
|
||||
row < other.row -> -1
|
||||
else -> when {
|
||||
slotInRow > other.slotInRow -> 1
|
||||
slotInRow < other.slotInRow -> -1
|
||||
else -> 0
|
||||
}
|
||||
}
|
||||
|
||||
fun realSlotIn(inventoryDimensions: InventoryDimensions): Int? {
|
||||
if (!isInDimension(inventoryDimensions)) return null
|
||||
val realRow = inventoryDimensions.height - (row - 1)
|
||||
val rowsUnder = if (realRow - 1 >= 0) realRow - 1 else 0
|
||||
return ((rowsUnder * inventoryDimensions.width) + slotInRow) - 1
|
||||
}
|
||||
|
||||
fun isInDimension(inventoryDimensions: InventoryDimensions) =
|
||||
(1..inventoryDimensions.width).contains(slotInRow) && (1..inventoryDimensions.height).contains(row)
|
||||
|
||||
fun add(offsetHorizontally: Int, offsetVertically: Int) = InventorySlot(
|
||||
row + offsetVertically,
|
||||
slotInRow + offsetHorizontally
|
||||
)
|
||||
|
||||
}
|
||||
|
||||
interface InventorySlotCompound<out T : ForInventory> {
|
||||
|
||||
fun withInvType(invType: GUIType<T>): Collection<InventorySlot>
|
||||
|
||||
fun realSlotsWithInvType(invType: GUIType<T>) =
|
||||
withInvType(invType).mapNotNull { it.realSlotIn(invType.dimensions) }
|
||||
|
||||
}
|
||||
|
||||
open class SingleInventorySlot<T : ForInventory> internal constructor(
|
||||
val inventorySlot: InventorySlot
|
||||
) : InventorySlotCompound<T> {
|
||||
|
||||
constructor(row: Int, slotInRow: Int) : this(InventorySlot(row, slotInRow))
|
||||
|
||||
private val slotAsList = listOf(inventorySlot)
|
||||
|
||||
override fun withInvType(invType: GUIType<T>) = slotAsList
|
||||
|
||||
}
|
||||
|
||||
internal enum class InventorySlotRangeType {
|
||||
LINEAR,
|
||||
RECTANGLE,
|
||||
}
|
||||
|
||||
class InventorySlotRange<out T : ForInventory> internal constructor(
|
||||
|
||||
startSlot: SingleInventorySlot<out T>,
|
||||
endSlot: SingleInventorySlot<out T>,
|
||||
|
||||
private val type: InventorySlotRangeType
|
||||
|
||||
) : InventorySlotCompound<T>, ClosedRange<InventorySlot> {
|
||||
|
||||
override val start: InventorySlot
|
||||
override val endInclusive: InventorySlot
|
||||
|
||||
init {
|
||||
val minMaxPair = MinMaxPair(startSlot.inventorySlot, endSlot.inventorySlot)
|
||||
start = minMaxPair.min
|
||||
endInclusive = minMaxPair.max
|
||||
}
|
||||
|
||||
override fun withInvType(invType: GUIType<T>) = LinkedHashSet<InventorySlot>().apply {
|
||||
when (type) {
|
||||
|
||||
InventorySlotRangeType.RECTANGLE -> {
|
||||
// all possible combinations between the two slots
|
||||
// -> form a rectangle
|
||||
for (row in start.row..endInclusive.row)
|
||||
for (slotInRow in start.slotInRow..endInclusive.slotInRow)
|
||||
this += InventorySlot(row, slotInRow)
|
||||
}
|
||||
|
||||
InventorySlotRangeType.LINEAR -> {
|
||||
if (endInclusive.row > start.row) {
|
||||
// from start --->| to end of row
|
||||
for (slotInRow in start.slotInRow..invType.dimensions.width)
|
||||
this += InventorySlot(start.row, slotInRow)
|
||||
// all rows in between
|
||||
if (endInclusive.row > start.row + 1)
|
||||
for (row in start.row + 1 until endInclusive.row)
|
||||
for (slotInRow in 1..invType.dimensions.width)
|
||||
this += InventorySlot(row, slotInRow)
|
||||
// from start of row |----> to endInclusive
|
||||
for (slotInRow in 1..endInclusive.slotInRow)
|
||||
this += InventorySlot(endInclusive.row, slotInRow)
|
||||
} else if (endInclusive.row == start.row) {
|
||||
// from start ---> to endInclusive in the same row
|
||||
for (slotInRow in start.slotInRow..endInclusive.slotInRow)
|
||||
this += InventorySlot(start.row, slotInRow)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* This range contains all slots having an index between
|
||||
* the indeces of the two given slots.
|
||||
*/
|
||||
infix fun <T : ForInventory> SingleInventorySlot<out T>.linTo(slot: SingleInventorySlot<out T>) =
|
||||
InventorySlotRange(this, slot, InventorySlotRangeType.LINEAR)
|
||||
|
||||
/**
|
||||
* This range contains all slots inside of a thought rectangle
|
||||
* with the two given slots as two opposite corners of the rectangle.
|
||||
*/
|
||||
infix fun <T : ForInventory> SingleInventorySlot<out T>.rectTo(slot: SingleInventorySlot<out T>) =
|
||||
InventorySlotRange(this, slot, InventorySlotRangeType.RECTANGLE)
|
||||
|
||||
class InventoryRowSlots<T : ForInventory> internal constructor(
|
||||
val row: Int
|
||||
) : InventorySlotCompound<T> {
|
||||
|
||||
override fun withInvType(invType: GUIType<T>) = HashSet<InventorySlot>().apply {
|
||||
for (slotInRow in 1..invType.dimensions.width)
|
||||
this += InventorySlot(row, slotInRow)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class InventoryColumnSlots<T : ForInventory> internal constructor(
|
||||
val column: Int
|
||||
) : InventorySlotCompound<T> {
|
||||
|
||||
override fun withInvType(invType: GUIType<T>) = HashSet<InventorySlot>().apply {
|
||||
for (row in 1..invType.dimensions.height)
|
||||
this += InventorySlot(row, column)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class InventoryBorderSlots<T : ForInventory> internal constructor(
|
||||
val padding: Int
|
||||
) : InventorySlotCompound<T> {
|
||||
|
||||
override fun withInvType(invType: GUIType<T>) = HashSet<InventorySlot>().apply {
|
||||
|
||||
val dimensions = invType.dimensions
|
||||
|
||||
for (currentPadding in 0 until padding) {
|
||||
for (slotInRow in 1 + currentPadding..dimensions.width - currentPadding) {
|
||||
this += InventorySlot(1, slotInRow)
|
||||
this += InventorySlot(dimensions.height, slotInRow)
|
||||
}
|
||||
for (row in 2 + currentPadding until dimensions.height - currentPadding) {
|
||||
this += InventorySlot(row, 1)
|
||||
this += InventorySlot(row, dimensions.width)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class InventoryCornerSlots<T : ForInventory> internal constructor(
|
||||
val ifBottomLeft: Boolean = false,
|
||||
val ifBottomRight: Boolean = false,
|
||||
val ifTopLeft: Boolean = false,
|
||||
val ifTopRight: Boolean = false
|
||||
) : InventorySlotCompound<T> {
|
||||
|
||||
override fun withInvType(invType: GUIType<T>) = HashSet<InventorySlot>().apply {
|
||||
|
||||
val dimensions = invType.dimensions
|
||||
|
||||
if (ifBottomLeft) this += InventorySlot(1, 1)
|
||||
if (ifBottomRight) this += InventorySlot(1, dimensions.width)
|
||||
if (ifTopLeft) this += InventorySlot(dimensions.height, 1)
|
||||
if (ifTopRight) this += InventorySlot(dimensions.height, dimensions.width)
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// SLOT TYPE SAFETY
|
||||
|
||||
// COLUMNS
|
||||
|
||||
interface ForColumnOne : ForInventoryWidthThree, ForInventoryWidthFive, ForInventoryWidthNine
|
||||
interface ForColumnTwo : ForInventoryWidthThree, ForInventoryWidthFive, ForInventoryWidthNine
|
||||
interface ForColumnThree : ForInventoryWidthThree, ForInventoryWidthFive, ForInventoryWidthNine
|
||||
interface ForColumnFour : ForInventoryWidthFive, ForInventoryWidthNine
|
||||
interface ForColumnFive : ForInventoryWidthFive, ForInventoryWidthNine
|
||||
interface ForColumnSix : ForInventoryWidthNine
|
||||
interface ForColumnSeven : ForInventoryWidthNine
|
||||
interface ForColumnEight : ForInventoryWidthNine
|
||||
interface ForColumnNine : ForInventoryWidthNine
|
||||
|
||||
// ROWS
|
||||
|
||||
interface ForRowOne : ForInventoryOneByNine, ForInventoryTwoByNine, ForInventoryThreeByNine, ForInventoryFourByNine,
|
||||
ForInventoryFiveByNine, ForInventorySixByNine
|
||||
|
||||
interface ForRowTwo : ForInventoryTwoByNine, ForInventoryThreeByNine, ForInventoryFourByNine, ForInventoryFiveByNine,
|
||||
ForInventorySixByNine
|
||||
|
||||
interface ForRowThree : ForInventoryThreeByNine, ForInventoryFourByNine, ForInventoryFiveByNine, ForInventorySixByNine
|
||||
interface ForRowFour : ForInventoryFourByNine, ForInventoryFiveByNine, ForInventorySixByNine
|
||||
interface ForRowFive : ForInventoryFiveByNine, ForInventorySixByNine
|
||||
interface ForRowSix : ForInventorySixByNine
|
||||
|
||||
// EDGE CASES:
|
||||
// ROW ONE
|
||||
interface ForRowOneSlotOneToThree : ForRowOne, ForInventoryOneByFive, ForInventoryThreeByThree
|
||||
interface ForRowOneSlotFourToFive : ForRowOne, ForInventoryOneByFive
|
||||
|
||||
// ROW TWO
|
||||
interface ForRowTwoSlotOneToThree : ForRowTwo, ForInventoryThreeByThree
|
||||
|
||||
// ROW THREE
|
||||
interface ForRowThreeSlotOneToThree : ForRowThree, ForInventoryThreeByThree
|
||||
|
||||
// COMPLETE ROWS (including the edge cases)
|
||||
interface ForCompleteRowOne : ForRowOne, ForRowOneSlotOneToThree, ForRowOneSlotFourToFive
|
||||
interface ForCompleteRowTwo : ForRowTwo, ForRowTwoSlotOneToThree
|
||||
interface ForCompleteRowThree : ForRowThree, ForRowThreeSlotOneToThree
|
||||
|
||||
object Slots {
|
||||
|
||||
// ROW ONE
|
||||
val RowOneSlotOne = SingleInventorySlot<ForRowOneSlotOneToThree>(1, 1)
|
||||
val RowOneSlotTwo = SingleInventorySlot<ForRowOneSlotOneToThree>(1, 2)
|
||||
val RowOneSlotThree = SingleInventorySlot<ForRowOneSlotOneToThree>(1, 3)
|
||||
val RowOneSlotFour = SingleInventorySlot<ForRowOneSlotFourToFive>(1, 4)
|
||||
val RowOneSlotFive = SingleInventorySlot<ForRowOneSlotFourToFive>(1, 5)
|
||||
val RowOneSlotSix = SingleInventorySlot<ForRowOne>(1, 6)
|
||||
val RowOneSlotSeven = SingleInventorySlot<ForRowOne>(1, 7)
|
||||
val RowOneSlotEight = SingleInventorySlot<ForRowOne>(1, 8)
|
||||
val RowOneSlotNine = SingleInventorySlot<ForRowOne>(1, 9)
|
||||
|
||||
// ROW TWO
|
||||
val RowTwoSlotOne = SingleInventorySlot<ForRowTwoSlotOneToThree>(2, 1)
|
||||
val RowTwoSlotTwo = SingleInventorySlot<ForRowTwoSlotOneToThree>(2, 2)
|
||||
val RowTwoSlotThree = SingleInventorySlot<ForRowTwoSlotOneToThree>(2, 3)
|
||||
val RowTwoSlotFour = SingleInventorySlot<ForRowTwo>(2, 4)
|
||||
val RowTwoSlotFive = SingleInventorySlot<ForRowTwo>(2, 5)
|
||||
val RowTwoSlotSix = SingleInventorySlot<ForRowTwo>(2, 6)
|
||||
val RowTwoSlotSeven = SingleInventorySlot<ForRowTwo>(2, 7)
|
||||
val RowTwoSlotEight = SingleInventorySlot<ForRowTwo>(2, 8)
|
||||
val RowTwoSlotNine = SingleInventorySlot<ForRowTwo>(2, 9)
|
||||
|
||||
// ROW THREE
|
||||
val RowThreeSlotOne = SingleInventorySlot<ForRowThreeSlotOneToThree>(3, 1)
|
||||
val RowThreeSlotTwo = SingleInventorySlot<ForRowThreeSlotOneToThree>(3, 2)
|
||||
val RowThreeSlotThree = SingleInventorySlot<ForRowThreeSlotOneToThree>(3, 3)
|
||||
val RowThreeSlotFour = SingleInventorySlot<ForRowThree>(3, 4)
|
||||
val RowThreeSlotFive = SingleInventorySlot<ForRowThree>(3, 5)
|
||||
val RowThreeSlotSix = SingleInventorySlot<ForRowThree>(3, 6)
|
||||
val RowThreeSlotSeven = SingleInventorySlot<ForRowThree>(3, 7)
|
||||
val RowThreeSlotEight = SingleInventorySlot<ForRowThree>(3, 8)
|
||||
val RowThreeSlotNine = SingleInventorySlot<ForRowThree>(3, 9)
|
||||
|
||||
// ROW FOUR
|
||||
val RowFourSlotOne = SingleInventorySlot<ForRowFour>(4, 1)
|
||||
val RowFourSlotTwo = SingleInventorySlot<ForRowFour>(4, 2)
|
||||
val RowFourSlotThree = SingleInventorySlot<ForRowFour>(4, 3)
|
||||
val RowFourSlotFour = SingleInventorySlot<ForRowFour>(4, 4)
|
||||
val RowFourSlotFive = SingleInventorySlot<ForRowFour>(4, 5)
|
||||
val RowFourSlotSix = SingleInventorySlot<ForRowFour>(4, 6)
|
||||
val RowFourSlotSeven = SingleInventorySlot<ForRowFour>(4, 7)
|
||||
val RowFourSlotEight = SingleInventorySlot<ForRowFour>(4, 8)
|
||||
val RowFourSlotNine = SingleInventorySlot<ForRowFour>(4, 9)
|
||||
|
||||
// ROW FIVE
|
||||
val RowFiveSlotOne = SingleInventorySlot<ForRowFive>(5, 1)
|
||||
val RowFiveSlotTwo = SingleInventorySlot<ForRowFive>(5, 2)
|
||||
val RowFiveSlotThree = SingleInventorySlot<ForRowFive>(5, 3)
|
||||
val RowFiveSlotFour = SingleInventorySlot<ForRowFive>(5, 4)
|
||||
val RowFiveSlotFive = SingleInventorySlot<ForRowFive>(5, 5)
|
||||
val RowFiveSlotSix = SingleInventorySlot<ForRowFive>(5, 6)
|
||||
val RowFiveSlotSeven = SingleInventorySlot<ForRowFive>(5, 7)
|
||||
val RowFiveSlotEight = SingleInventorySlot<ForRowFive>(5, 8)
|
||||
val RowFiveSlotNine = SingleInventorySlot<ForRowFive>(5, 9)
|
||||
|
||||
// ROW SIX
|
||||
val RowSixSlotOne = SingleInventorySlot<ForRowSix>(6, 1)
|
||||
val RowSixSlotTwo = SingleInventorySlot<ForRowSix>(6, 2)
|
||||
val RowSixSlotThree = SingleInventorySlot<ForRowSix>(6, 3)
|
||||
val RowSixSlotFour = SingleInventorySlot<ForRowSix>(6, 4)
|
||||
val RowSixSlotFive = SingleInventorySlot<ForRowSix>(6, 5)
|
||||
val RowSixSlotSix = SingleInventorySlot<ForRowSix>(6, 6)
|
||||
val RowSixSlotSeven = SingleInventorySlot<ForRowSix>(6, 7)
|
||||
val RowSixSlotEight = SingleInventorySlot<ForRowSix>(6, 8)
|
||||
val RowSixSlotNine = SingleInventorySlot<ForRowSix>(6, 9)
|
||||
|
||||
// ROW
|
||||
val RowOne = InventoryRowSlots<ForCompleteRowOne>(1)
|
||||
val RowTwo = InventoryRowSlots<ForCompleteRowTwo>(2)
|
||||
val RowThree = InventoryRowSlots<ForCompleteRowThree>(3)
|
||||
val RowFour = InventoryRowSlots<ForRowFour>(4)
|
||||
val RowFive = InventoryRowSlots<ForRowFive>(5)
|
||||
val RowSix = InventoryRowSlots<ForRowSix>(6)
|
||||
|
||||
// COLUMN
|
||||
val ColumnOne = InventoryColumnSlots<ForColumnOne>(1)
|
||||
val ColumnTwo = InventoryColumnSlots<ForColumnTwo>(2)
|
||||
val ColumnThree = InventoryColumnSlots<ForColumnThree>(3)
|
||||
val ColumnFour = InventoryColumnSlots<ForColumnFour>(4)
|
||||
val ColumnFive = InventoryColumnSlots<ForColumnFive>(5)
|
||||
val ColumnSix = InventoryColumnSlots<ForColumnSix>(6)
|
||||
val ColumnSeven = InventoryColumnSlots<ForColumnSeven>(7)
|
||||
val ColumnEight = InventoryColumnSlots<ForColumnEight>(8)
|
||||
val ColumnNine = InventoryColumnSlots<ForColumnNine>(9)
|
||||
|
||||
// BORDER
|
||||
val BorderPaddingOne = InventoryBorderSlots<ForEveryInventory>(1)
|
||||
val BorderPaddingTwo = InventoryBorderSlots<ForEveryInventory>(2)
|
||||
val BorderPaddingThree = InventoryBorderSlots<ForEveryInventory>(3)
|
||||
val Border = BorderPaddingOne
|
||||
|
||||
// CORNER
|
||||
val Corners = InventoryCornerSlots<ForEveryInventory>()
|
||||
val CornersLeft = InventoryCornerSlots<ForEveryInventory>(ifBottomLeft = true, ifTopLeft = true)
|
||||
val CornersRight = InventoryCornerSlots<ForEveryInventory>(ifBottomRight = true, ifTopRight = true)
|
||||
val CornersBottom = InventoryCornerSlots<ForEveryInventory>(ifBottomLeft = true, ifBottomRight = true)
|
||||
val CornersTop = InventoryCornerSlots<ForEveryInventory>(ifTopLeft = true, ifTopRight = true)
|
||||
val CornerBottomLeft = InventoryCornerSlots<ForEveryInventory>(ifBottomLeft = true)
|
||||
val CornerBottomRight = InventoryCornerSlots<ForEveryInventory>(ifBottomRight = true)
|
||||
val CornerTopLeft = InventoryCornerSlots<ForEveryInventory>(ifTopLeft = true)
|
||||
val CornerTopRight = InventoryCornerSlots<ForEveryInventory>(ifTopRight = true)
|
||||
|
||||
}
|
61
src/main/kotlin/net/axay/kspigot/gui/GUIType.kt
Normal file
61
src/main/kotlin/net/axay/kspigot/gui/GUIType.kt
Normal file
@@ -0,0 +1,61 @@
|
||||
@file:Suppress("MemberVisibilityCanBePrivate", "CanBeParameter")
|
||||
|
||||
package net.axay.kspigot.gui
|
||||
|
||||
import org.bukkit.Bukkit
|
||||
import org.bukkit.event.inventory.InventoryType
|
||||
import org.bukkit.inventory.Inventory
|
||||
import org.bukkit.inventory.InventoryHolder
|
||||
|
||||
class GUIType<in T : ForInventory>(
|
||||
val dimensions: InventoryDimensions,
|
||||
val bukkitType: InventoryType? = null
|
||||
) {
|
||||
|
||||
companion object {
|
||||
|
||||
val ONE_BY_NINE = GUIType<ForInventoryOneByNine>(InventoryDimensions(9, 1))
|
||||
val TWO_BY_NINE = GUIType<ForInventoryTwoByNine>(InventoryDimensions(9, 2))
|
||||
val THREE_BY_NINE = GUIType<ForInventoryThreeByNine>(InventoryDimensions(9, 3))
|
||||
val FOUR_BY_NINE = GUIType<ForInventoryFourByNine>(InventoryDimensions(9, 4))
|
||||
val FIVE_BY_NINE = GUIType<ForInventoryFiveByNine>(InventoryDimensions(9, 5))
|
||||
val SIX_BY_NINE = GUIType<ForInventorySixByNine>(InventoryDimensions(9, 6))
|
||||
val ONE_BY_FIVE =
|
||||
GUIType<ForInventoryOneByFive>(InventoryDimensions(5, 1), bukkitType = InventoryType.HOPPER)
|
||||
val THREE_BY_THREE =
|
||||
GUIType<ForInventoryThreeByThree>(InventoryDimensions(3, 3), bukkitType = InventoryType.DROPPER)
|
||||
|
||||
}
|
||||
|
||||
fun createBukkitInv(holder: InventoryHolder? = null, title: String? = null): Inventory {
|
||||
val realTitle = title ?: ""
|
||||
return when {
|
||||
bukkitType != null -> Bukkit.createInventory(holder, bukkitType, realTitle)
|
||||
else -> Bukkit.createInventory(holder, dimensions.slotAmount, realTitle)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// INVENTORY TYPE SAFETY
|
||||
|
||||
interface ForInventory
|
||||
|
||||
interface ForInventoryThreeByThree : ForInventoryThreeByNine
|
||||
interface ForInventoryOneByFive : ForInventoryOneByNine
|
||||
interface ForInventoryOneByNine : ForInventoryTwoByNine
|
||||
interface ForInventoryTwoByNine : ForInventoryThreeByNine
|
||||
interface ForInventoryThreeByNine : ForInventoryFourByNine
|
||||
interface ForInventoryFourByNine : ForInventoryFiveByNine
|
||||
interface ForInventoryFiveByNine : ForInventorySixByNine
|
||||
interface ForInventorySixByNine : ForInventory
|
||||
|
||||
interface ForEveryInventory
|
||||
: ForInventoryOneByNine, ForInventoryTwoByNine, ForInventoryThreeByNine,
|
||||
ForInventoryFourByNine, ForInventoryFiveByNine, ForInventorySixByNine,
|
||||
ForInventoryThreeByThree, ForInventoryOneByFive
|
||||
|
||||
interface ForInventoryWidthThree : ForInventoryThreeByThree
|
||||
interface ForInventoryWidthFive : ForInventoryOneByFive
|
||||
interface ForInventoryWidthNine : ForInventoryOneByNine, ForInventoryTwoByNine, ForInventoryThreeByNine,
|
||||
ForInventoryFourByNine, ForInventoryFiveByNine, ForInventorySixByNine
|
18
src/main/kotlin/net/axay/kspigot/gui/elements/GUIButton.kt
Normal file
18
src/main/kotlin/net/axay/kspigot/gui/elements/GUIButton.kt
Normal file
@@ -0,0 +1,18 @@
|
||||
package net.axay.kspigot.gui.elements
|
||||
|
||||
import net.axay.kspigot.gui.*
|
||||
import org.bukkit.inventory.ItemStack
|
||||
|
||||
open class GUIButton<T : ForInventory>(
|
||||
private val icon: ItemStack,
|
||||
private val action: (GUIClickEvent<T>) -> Unit,
|
||||
) : GUIElement<T>() {
|
||||
|
||||
final override fun getItemStack(slot: Int) = icon
|
||||
|
||||
override fun onClickElement(clickEvent: GUIClickEvent<T>) {
|
||||
clickEvent.bukkitEvent.isCancelled = true
|
||||
action(clickEvent)
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,26 @@
|
||||
package net.axay.kspigot.gui.elements
|
||||
|
||||
import net.axay.kspigot.gui.*
|
||||
import org.bukkit.inventory.ItemStack
|
||||
|
||||
class GUIButtonInventoryChange<T : ForInventory>(
|
||||
icon: ItemStack,
|
||||
changeToGUICallback: () -> GUI<*>,
|
||||
changeToPageInt: Int?,
|
||||
onChange: ((GUIClickEvent<T>) -> Unit)?
|
||||
) : GUIButton<T>(icon, {
|
||||
|
||||
val changeToGUI = changeToGUICallback.invoke()
|
||||
|
||||
val effect = (changeToGUI.data.transitionTo ?: it.gui.data.transitionFrom)
|
||||
?: InventoryChangeEffect.INSTANT
|
||||
|
||||
val changeToPage = changeToGUI.getPage(changeToPageInt) ?: changeToGUI.currentPage
|
||||
|
||||
changeToGUI.changeGUI(effect, it.gui.currentPage, changeToPage)
|
||||
|
||||
it.bukkitEvent.whoClicked.openGUI(changeToGUI)
|
||||
|
||||
onChange?.invoke(it)
|
||||
|
||||
})
|
@@ -0,0 +1,25 @@
|
||||
package net.axay.kspigot.gui.elements
|
||||
|
||||
import net.axay.kspigot.gui.*
|
||||
import org.bukkit.inventory.ItemStack
|
||||
|
||||
class GUIButtonPageChange<T : ForInventory>(
|
||||
icon: ItemStack,
|
||||
calculator: GUIPageChangeCalculator,
|
||||
onChange: ((GUIClickEvent<T>) -> Unit)?
|
||||
) : GUIButton<T>(icon, {
|
||||
|
||||
val currentPage = it.gui.currentPage
|
||||
val newPage = it.gui.getPage(calculator.calculateNewPage(it.gui.currentPageInt, it.gui.data.pages.keys))
|
||||
if (newPage != null) {
|
||||
|
||||
val effect = (newPage.transitionTo ?: currentPage.transitionFrom)
|
||||
?: PageChangeEffect.INSTANT
|
||||
|
||||
it.gui.changePage(effect, currentPage, newPage)
|
||||
|
||||
onChange?.invoke(it)
|
||||
|
||||
}
|
||||
|
||||
})
|
11
src/main/kotlin/net/axay/kspigot/gui/elements/GUIFreeSlot.kt
Normal file
11
src/main/kotlin/net/axay/kspigot/gui/elements/GUIFreeSlot.kt
Normal file
@@ -0,0 +1,11 @@
|
||||
package net.axay.kspigot.gui.elements
|
||||
|
||||
import net.axay.kspigot.gui.ForInventory
|
||||
import net.axay.kspigot.gui.GUIClickEvent
|
||||
import net.axay.kspigot.gui.GUISlot
|
||||
|
||||
class GUIFreeSlot<T : ForInventory> : GUISlot<T>() {
|
||||
override fun onClick(clickEvent: GUIClickEvent<T>) {
|
||||
/* do nothing */
|
||||
}
|
||||
}
|
@@ -0,0 +1,16 @@
|
||||
package net.axay.kspigot.gui.elements
|
||||
|
||||
import net.axay.kspigot.gui.*
|
||||
import org.bukkit.inventory.ItemStack
|
||||
|
||||
class GUIPlaceholder<T : ForInventory>(
|
||||
private val icon: ItemStack
|
||||
) : GUIElement<T>() {
|
||||
|
||||
override fun getItemStack(slot: Int) = icon
|
||||
|
||||
override fun onClickElement(clickEvent: GUIClickEvent<T>) {
|
||||
clickEvent.bukkitEvent.isCancelled = true
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,166 @@
|
||||
@file:Suppress("MemberVisibilityCanBePrivate")
|
||||
|
||||
package net.axay.kspigot.gui.elements
|
||||
|
||||
import net.axay.kspigot.gui.*
|
||||
import org.bukkit.Material
|
||||
import org.bukkit.inventory.ItemStack
|
||||
|
||||
class GUISpaceCompoundElement<T : ForInventory, E> internal constructor(
|
||||
private val compound: AbstractGUISpaceCompound<T, E>
|
||||
) : GUIElement<T>() {
|
||||
|
||||
override fun getItemStack(slot: Int) = compound.getItemStack(slot)
|
||||
|
||||
override fun onClickElement(clickEvent: GUIClickEvent<T>) {
|
||||
compound.onClickElement(clickEvent)
|
||||
}
|
||||
|
||||
override fun startUsing(gui: GUI<*>) = compound.registerGUI(gui)
|
||||
|
||||
override fun stopUsing(gui: GUI<*>) = compound.unregisterGUI(gui)
|
||||
|
||||
}
|
||||
|
||||
class GUIRectSpaceCompound<T : ForInventory, E>(
|
||||
invType: GUIType<T>,
|
||||
iconGenerator: (E) -> ItemStack,
|
||||
onClick: (GUIClickEvent<T>, E) -> Unit,
|
||||
internal val compoundWidth: Int
|
||||
) : AbstractGUISpaceCompound<T, E>(invType, iconGenerator, onClick) {
|
||||
|
||||
override fun handleScrollEndReached(newProgress: Int, internalSlotsSize: Int, contentSize: Int) =
|
||||
(internalSlotsSize + newProgress <= contentSize + (compoundWidth - (contentSize % compoundWidth)))
|
||||
|
||||
}
|
||||
|
||||
class GUISpaceCompound<T : ForInventory, E>(
|
||||
invType: GUIType<T>,
|
||||
iconGenerator: (E) -> ItemStack,
|
||||
onClick: (GUIClickEvent<T>, E) -> Unit
|
||||
) : AbstractGUISpaceCompound<T, E>(invType, iconGenerator, onClick) {
|
||||
|
||||
override fun handleScrollEndReached(newProgress: Int, internalSlotsSize: Int, contentSize: Int) = false
|
||||
|
||||
}
|
||||
|
||||
abstract class AbstractGUISpaceCompound<T : ForInventory, E> internal constructor(
|
||||
val guiType: GUIType<T>,
|
||||
private val iconGenerator: (E) -> ItemStack,
|
||||
private val onClick: (GUIClickEvent<T>, E) -> Unit
|
||||
) {
|
||||
|
||||
private val content = ArrayList<E>()
|
||||
private var currentContent: List<E> = emptyList()
|
||||
|
||||
private val internalSlots: MutableList<Int> = ArrayList()
|
||||
|
||||
private var scrollProgress: Int = 0
|
||||
|
||||
private var contentSort: () -> Unit = { }
|
||||
|
||||
private val registeredGUIs = HashSet<GUI<*>>()
|
||||
|
||||
private fun contentAtSlot(slot: Int) = currentContent.getOrNull(internalSlots.indexOf(slot))
|
||||
|
||||
private fun recalculateCurrentContent() {
|
||||
|
||||
if (scrollProgress >= content.size)
|
||||
throw IllegalStateException("The scrollProgress is greater than the content size.")
|
||||
|
||||
// avoid IndexOutOfBoundsException
|
||||
var sliceUntil = internalSlots.size + scrollProgress
|
||||
if (sliceUntil > content.lastIndex)
|
||||
sliceUntil = content.size
|
||||
|
||||
currentContent = content.slice(scrollProgress until sliceUntil)
|
||||
|
||||
}
|
||||
|
||||
private fun updateOpenGUIs() {
|
||||
registeredGUIs.forEach { it.reloadCurrentPage() }
|
||||
}
|
||||
|
||||
internal fun scroll(distance: Int): Boolean {
|
||||
val value = scrollProgress + distance
|
||||
return if (value >= 0) {
|
||||
|
||||
// always scroll if the end of the content is not reached
|
||||
val ifScroll = if (internalSlots.size + value <= content.size) true
|
||||
// scroll further if the width of the compound is defined and the last line can be filled up
|
||||
else handleScrollEndReached(value, internalSlots.size, content.size)
|
||||
|
||||
if (ifScroll) {
|
||||
scrollProgress = value
|
||||
recalculateCurrentContent()
|
||||
updateOpenGUIs()
|
||||
true
|
||||
} else false
|
||||
|
||||
} else false
|
||||
}
|
||||
|
||||
internal abstract fun handleScrollEndReached(newProgress: Int, internalSlotsSize: Int, contentSize: Int): Boolean
|
||||
|
||||
internal fun getItemStack(slot: Int): ItemStack {
|
||||
return contentAtSlot(slot)?.let { return@let iconGenerator.invoke(it) }
|
||||
?: ItemStack(Material.AIR)
|
||||
}
|
||||
|
||||
internal fun onClickElement(clickEvent: GUIClickEvent<T>) {
|
||||
val element = contentAtSlot(clickEvent.bukkitEvent.slot) ?: kotlin.run {
|
||||
clickEvent.bukkitEvent.isCancelled = true
|
||||
return
|
||||
}
|
||||
onClick.invoke(clickEvent, element)
|
||||
}
|
||||
|
||||
internal fun addSlots(slots: InventorySlotCompound<T>) {
|
||||
slots.realSlotsWithInvType(guiType).forEach {
|
||||
if (!internalSlots.contains(it))
|
||||
internalSlots.add(it)
|
||||
}
|
||||
internalSlots.sort()
|
||||
}
|
||||
|
||||
internal fun registerGUI(gui: GUI<*>) {
|
||||
registeredGUIs += gui
|
||||
}
|
||||
|
||||
internal fun unregisterGUI(gui: GUI<*>) {
|
||||
registeredGUIs -= gui
|
||||
}
|
||||
|
||||
/**
|
||||
* Defines the sort behaviour which gets applied to the content
|
||||
* automatically.
|
||||
*/
|
||||
fun <R : Comparable<R>> sortContentBy(reverse: Boolean = false, selector: (E) -> R?) {
|
||||
contentSort = {
|
||||
if (!reverse) content.sortBy(selector) else content.sortByDescending(selector)
|
||||
}
|
||||
contentSort.invoke()
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a new element to the compound.
|
||||
*/
|
||||
fun addContent(element: E) {
|
||||
addContent(listOf(element))
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds new elements to the compound.
|
||||
*/
|
||||
fun addContent(elements: Collection<E>) {
|
||||
|
||||
content += elements
|
||||
contentSort.invoke()
|
||||
|
||||
recalculateCurrentContent()
|
||||
|
||||
updateOpenGUIs()
|
||||
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,24 @@
|
||||
package net.axay.kspigot.gui.elements
|
||||
|
||||
import net.axay.kspigot.gui.ForInventory
|
||||
import net.axay.kspigot.runnables.task
|
||||
import org.bukkit.inventory.ItemStack
|
||||
|
||||
class GUISpaceCompoundScrollButton<T : ForInventory>(
|
||||
icon: ItemStack,
|
||||
private val compound: AbstractGUISpaceCompound<T, *>,
|
||||
private val scrollDistance: Int,
|
||||
private val scrollTimes: Int,
|
||||
private val reverse: Boolean = false
|
||||
) : GUIButton<T>(icon, {
|
||||
if (scrollTimes > 1) {
|
||||
task(
|
||||
period = 1,
|
||||
howOften = scrollTimes.toLong()
|
||||
) {
|
||||
val ifScrolled = if (reverse) compound.scroll(-scrollDistance) else compound.scroll(scrollDistance)
|
||||
if (!ifScrolled) it.cancel()
|
||||
}
|
||||
} else if (scrollTimes == 1)
|
||||
if (reverse) compound.scroll(-scrollDistance) else compound.scroll(scrollDistance)
|
||||
})
|
Reference in New Issue
Block a user