Added GUIInstance structure; GUIIndividual now available

This commit is contained in:
bluefireoly
2020-10-25 10:54:52 +01:00
parent 9ff49ad624
commit 6db78d08f4
13 changed files with 192 additions and 153 deletions

View File

@@ -2,116 +2,81 @@
package net.axay.kspigot.gui
import org.bukkit.entity.Player
import org.bukkit.inventory.Inventory
import org.bukkit.inventory.ItemStack
import java.util.*
import kotlin.collections.HashSet
// TODO unregister in all layers
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)?
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>
) {
abstract fun getInstance(player: Player): GUIInstance<T>
}
var currentPageInt: Int = DEFAULT_PAGE; protected set
val currentPage
get() = getPage(currentPageInt)
?: throw IllegalStateException("The currentPageInt has no associated page!")
class GUIShared<T : ForInventory>(
guiData: GUIData<T>
) : GUI<T>(guiData) {
internal abstract val bukkitInventory: Inventory
val singleInstance by lazy { GUIInstance(this, null).apply { register() } }
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)
}
override fun getInstance(player: Player) = singleInstance
}
// Inventory GUI implementations
class GUIIndividual<T : ForInventory>(
guiData: GUIData<T>,
val resetOnClose: Boolean
) : GUI<T>(guiData) {
class GUIShared<T : ForInventory>(
GUIData: GUIData<T>
) : GUI<T>(GUIData) {
private val playerInstances = HashMap<Player, GUIInstance<T>>()
override val bukkitInventory = data.guiType.createBukkitInv(null, data.title)
override fun getInstance(player: Player) =
playerInstances[player] ?: createInstance(player)
private fun createInstance(player: Player) =
GUIInstance(this, player).apply { playerInstances[player] = this }
}
class GUIInstance<T : ForInventory>(
val gui: GUI<T>,
holder: Player?
) {
internal val bukkitInventory = gui.data.guiType.createBukkitInv(holder, gui.data.title)
private val currentElements = HashSet<GUIElement<*>>()
internal var isInMove: Boolean = false
var currentPageInt: Int = DEFAULT_PAGE; private set
val currentPage
get() = getPage(currentPageInt)
?: throw IllegalStateException("The currentPageInt has no associated page!")
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) }
internal fun loadPageUnsafe(page: Int, offsetHorizontally: Int = 0, offsetVertically: Int = 0) {
gui.data.pages[page]?.let { loadPageUnsafe(it, offsetHorizontally, offsetVertically) }
}
override fun loadPageUnsafe(page: GUIPage<*>, offsetHorizontally: Int, offsetVertically: Int) {
internal fun loadPageUnsafe(page: GUIPage<*>, offsetHorizontally: Int = 0, offsetVertically: Int = 0) {
val ifOffset = offsetHorizontally != 0 || offsetVertically != 0
@@ -122,10 +87,12 @@ class GUIShared<T : ForInventory>(
currentElements.clear()
// register this inv for all new elements
HashSet(page.slots.values).forEach { if (it is GUIElement) {
currentElements += it
it.startUsing(this)
} }
HashSet(page.slots.values).forEach {
if (it is GUIElement) {
currentElements += it
it.startUsing(this)
}
}
currentPageInt = page.number
@@ -135,15 +102,15 @@ class GUIShared<T : ForInventory>(
}
override fun loadContent(
internal fun loadContent(
content: Map<Int, GUISlot<*>>,
offsetHorizontally: Int,
offsetVertically: Int
offsetHorizontally: Int = 0,
offsetVertically: Int = 0
) {
val ifOffset = offsetHorizontally != 0 || offsetVertically != 0
val dimensions = data.guiType.dimensions
val dimensions = gui.data.guiType.dimensions
// clear the space which will be redefined
if (ifOffset) {
@@ -173,10 +140,50 @@ class GUIShared<T : ForInventory>(
}
override operator fun set(slot: InventorySlotCompound<T>, value: ItemStack) {
slot.realSlotsWithInvType(data.guiType).forEach {
/**
* Registers this GUI.
* (KSpigot will listen for actions in the inventory.)
*/
@Suppress("UNCHECKED_CAST")
fun register() = GUIHolder.register(this as GUIInstance<ForInventory>)
/**
* Stops KSpigot from listening to actions in this
* GUI anymore.
*/
@Suppress("UNCHECKED_CAST")
fun unregister() = GUIHolder.unregister(this as GUIInstance<ForInventory>)
/**
* @return True, if the [inventory] belongs to this GUI.
*/
fun isThisInv(inventory: Inventory) = inventory == bukkitInventory
/**
* 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.
*/
operator fun set(slot: InventorySlotCompound<T>, value: ItemStack) {
slot.realSlotsWithInvType(gui.data.guiType).forEach {
bukkitInventory.setItem(it, value)
}
}
/**
* Searches for a page associated to the given [page] index.
*/
fun getPage(page: Int?) = gui.data.pages[page]
/**
* Reloads the current page.
*/
fun reloadCurrentPage() {
if (!isInMove)
loadPage(currentPage)
}
}

View File

@@ -7,14 +7,14 @@ 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()
type: GUIType<T>,
guiCreator: GUICreator<T>,
builder: GUIBuilder<T>.() -> Unit,
) = GUIBuilder(type, guiCreator).apply(builder).build()
class GUIBuilder<T : ForInventory>(
val type: GUIType<T>,
val shared: Boolean
val type: GUIType<T>,
private val guiCreator: GUICreator<T>
) {
var title: String = ""
@@ -39,18 +39,15 @@ class GUIBuilder<T : ForInventory>(
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() }
}
internal fun build() = guiCreator.createInstance(
GUIData(type, title, guiSlots, transitionTo, transitionFrom, onClickElement)
)
}
class GUIPageBuilder<T : ForInventory>(
private val type: GUIType<T>,
val page: Int
private val type: GUIType<T>,
val page: Int
) {
private val guiSlots = HashMap<Int, GUISlot<T>>()

View File

@@ -1,8 +1,10 @@
package net.axay.kspigot.gui
import org.bukkit.entity.Player
import org.bukkit.event.inventory.InventoryClickEvent
class GUIClickEvent<T : ForInventory>(
val bukkitEvent: InventoryClickEvent,
val gui: GUI<T>
val guiInstance: GUIInstance<T>,
val player: Player
)

View File

@@ -0,0 +1,15 @@
package net.axay.kspigot.gui
abstract class GUICreator<T : ForInventory> {
abstract fun createInstance(guiData: GUIData<T>): GUI<T>
}
class SharedGUICreator<T : ForInventory> : GUICreator<T>() {
override fun createInstance(guiData: GUIData<T>) = GUIShared(guiData)
}
class IndividualGUICreator<T : ForInventory>(
private val resetOnClose: Boolean
) : GUICreator<T>() {
override fun createInstance(guiData: GUIData<T>) = GUIIndividual(guiData, resetOnClose)
}

View File

@@ -13,13 +13,13 @@ 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)
clickEvent.guiInstance.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<*>) { }
internal open fun startUsing(gui: GUIInstance<*>) {}
internal open fun stopUsing(gui: GUIInstance<*>) {}
}

View File

@@ -1,15 +1,18 @@
package net.axay.kspigot.gui
import org.bukkit.entity.HumanEntity
import org.bukkit.entity.Player
import org.bukkit.inventory.InventoryView
fun HumanEntity.openGUI(gui: GUI<*>, page: Int? = null): InventoryView? {
fun Player.openGUI(gui: GUI<*>, page: Int? = null): InventoryView? {
closeInventory()
return openGUIInstance(gui.getInstance(this), page)
}
internal fun Player.openGUIInstance(guiInstance: GUIInstance<*>, page: Int? = null): InventoryView? {
if (page != null)
gui.loadPageUnsafe(page)
guiInstance.loadPageUnsafe(page)
return openInventory(gui.bukkitInventory)
return openInventory(guiInstance.bukkitInventory)
}

View File

@@ -1,19 +1,20 @@
package net.axay.kspigot.gui
import net.axay.kspigot.event.listen
import org.bukkit.entity.Player
import org.bukkit.event.inventory.InventoryAction
import org.bukkit.event.inventory.InventoryClickEvent
object GUIHolder : AutoCloseable {
private val registered = HashSet<GUI<ForInventory>>()
private val registered = HashSet<GUIInstance<ForInventory>>()
fun register(GUI: GUI<ForInventory>) {
registered.add(GUI)
fun register(guiInstance: GUIInstance<ForInventory>) {
registered.add(guiInstance)
}
fun unregister(GUI: GUI<ForInventory>) {
registered.remove(GUI)
fun unregister(guiInstance: GUIInstance<ForInventory>) {
registered.remove(guiInstance)
}
init {
@@ -24,13 +25,18 @@ object GUIHolder : AutoCloseable {
val inv = registered.find { search -> search.isThisInv(clickedInv) } ?: return@listen
val player = it.whoClicked as? Player ?: kotlin.run {
it.isCancelled = true
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 {
inv.currentPage.slots[it.slot]?.onClick(GUIClickEvent(it, inv, player)) ?: kotlin.run {
it.isCancelled = true
}
else

View File

@@ -36,7 +36,7 @@ enum class InventoryChangeEffect(
INSTANT(PageChangeEffect.INSTANT)
}
internal fun GUI<*>.changePage(
internal fun GUIInstance<*>.changePage(
effect: PageChangeEffect,
fromPage: GUIPage<*>,
toPage: GUIPage<*>
@@ -51,7 +51,7 @@ internal fun GUI<*>.changePage(
PageChangeEffect.SLIDE_HORIZONTALLY -> {
val width = data.guiType.dimensions.width
val width = gui.data.guiType.dimensions.width
changePageEffect(fromPageInt, toPageInt, width) { currentOffset, ifInverted ->
if (ifInverted) {
@@ -67,7 +67,7 @@ internal fun GUI<*>.changePage(
PageChangeEffect.SLIDE_VERTICALLY -> {
val height = data.guiType.dimensions.height
val height = gui.data.guiType.dimensions.height
changePageEffect(fromPageInt, toPageInt, height) { currentOffset, ifInverted ->
if (ifInverted) {
@@ -83,7 +83,7 @@ internal fun GUI<*>.changePage(
PageChangeEffect.SWIPE_HORIZONTALLY -> {
val width = data.guiType.dimensions.width
val width = gui.data.guiType.dimensions.width
changePageEffect(fromPageInt, toPageInt, width) { currentOffset, ifInverted ->
if (ifInverted) {
@@ -97,7 +97,7 @@ internal fun GUI<*>.changePage(
PageChangeEffect.SWIPE_VERTICALLY -> {
val height = data.guiType.dimensions.height
val height = gui.data.guiType.dimensions.height
changePageEffect(fromPageInt, toPageInt, height) { currentOffset, ifInverted ->
if (ifInverted) {
@@ -112,7 +112,7 @@ internal fun GUI<*>.changePage(
}
}
internal fun GUI<*>.changeGUI(
internal fun GUIInstance<*>.changeGUI(
effect: InventoryChangeEffect,
fromPage: GUIPage<*>,
toPage: GUIPage<*>

View File

@@ -1,6 +1,8 @@
package net.axay.kspigot.gui.elements
import net.axay.kspigot.gui.*
import net.axay.kspigot.gui.ForInventory
import net.axay.kspigot.gui.GUIClickEvent
import net.axay.kspigot.gui.GUIElement
import org.bukkit.inventory.ItemStack
open class GUIButton<T : ForInventory>(

View File

@@ -10,16 +10,16 @@ class GUIButtonInventoryChange<T : ForInventory>(
onChange: ((GUIClickEvent<T>) -> Unit)?
) : GUIButton<T>(icon, {
val changeToGUI = changeToGUICallback.invoke()
val changeToGUI = changeToGUICallback.invoke().getInstance(it.player)
val effect = (changeToGUI.data.transitionTo ?: it.gui.data.transitionFrom)
val effect = (changeToGUI.gui.data.transitionTo ?: it.guiInstance.gui.data.transitionFrom)
?: InventoryChangeEffect.INSTANT
val changeToPage = changeToGUI.getPage(changeToPageInt) ?: changeToGUI.currentPage
changeToGUI.changeGUI(effect, it.gui.currentPage, changeToPage)
changeToGUI.changeGUI(effect, it.guiInstance.currentPage, changeToPage)
it.bukkitEvent.whoClicked.openGUI(changeToGUI)
it.player.openGUIInstance(changeToGUI)
onChange?.invoke(it)

View File

@@ -9,14 +9,19 @@ class GUIButtonPageChange<T : ForInventory>(
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))
val currentPage = it.guiInstance.currentPage
val newPage = it.guiInstance.getPage(
calculator.calculateNewPage(
it.guiInstance.currentPageInt,
it.guiInstance.gui.data.pages.keys
)
)
if (newPage != null) {
val effect = (newPage.transitionTo ?: currentPage.transitionFrom)
?: PageChangeEffect.INSTANT
it.gui.changePage(effect, currentPage, newPage)
it.guiInstance.changePage(effect, currentPage, newPage)
onChange?.invoke(it)

View File

@@ -1,6 +1,8 @@
package net.axay.kspigot.gui.elements
import net.axay.kspigot.gui.*
import net.axay.kspigot.gui.ForInventory
import net.axay.kspigot.gui.GUIClickEvent
import net.axay.kspigot.gui.GUIElement
import org.bukkit.inventory.ItemStack
class GUIPlaceholder<T : ForInventory>(

View File

@@ -16,17 +16,17 @@ class GUISpaceCompoundElement<T : ForInventory, E> internal constructor(
compound.onClickElement(clickEvent)
}
override fun startUsing(gui: GUI<*>) = compound.registerGUI(gui)
override fun startUsing(gui: GUIInstance<*>) = compound.registerGUI(gui)
override fun stopUsing(gui: GUI<*>) = compound.unregisterGUI(gui)
override fun stopUsing(gui: GUIInstance<*>) = compound.unregisterGUI(gui)
}
class GUIRectSpaceCompound<T : ForInventory, E>(
invType: GUIType<T>,
iconGenerator: (E) -> ItemStack,
onClick: (GUIClickEvent<T>, E) -> Unit,
internal val compoundWidth: Int
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) =
@@ -35,9 +35,9 @@ class GUIRectSpaceCompound<T : ForInventory, E>(
}
class GUISpaceCompound<T : ForInventory, E>(
invType: GUIType<T>,
iconGenerator: (E) -> ItemStack,
onClick: (GUIClickEvent<T>, E) -> Unit
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
@@ -45,9 +45,9 @@ class GUISpaceCompound<T : ForInventory, E>(
}
abstract class AbstractGUISpaceCompound<T : ForInventory, E> internal constructor(
val guiType: GUIType<T>,
private val iconGenerator: (E) -> ItemStack,
private val onClick: (GUIClickEvent<T>, E) -> Unit
val guiType: GUIType<T>,
private val iconGenerator: (E) -> ItemStack,
private val onClick: (GUIClickEvent<T>, E) -> Unit
) {
private val content = ArrayList<E>()
@@ -59,7 +59,7 @@ abstract class AbstractGUISpaceCompound<T : ForInventory, E> internal constructo
private var contentSort: () -> Unit = { }
private val registeredGUIs = HashSet<GUI<*>>()
private val registeredGUIs = HashSet<GUIInstance<*>>()
private fun contentAtSlot(slot: Int) = currentContent.getOrNull(internalSlots.indexOf(slot))
@@ -123,11 +123,11 @@ abstract class AbstractGUISpaceCompound<T : ForInventory, E> internal constructo
internalSlots.sort()
}
internal fun registerGUI(gui: GUI<*>) {
internal fun registerGUI(gui: GUIInstance<*>) {
registeredGUIs += gui
}
internal fun unregisterGUI(gui: GUI<*>) {
internal fun unregisterGUI(gui: GUIInstance<*>) {
registeredGUIs -= gui
}