-
Notifications
You must be signed in to change notification settings - Fork 6
5. Reusability
A lot of the struggle with creating comprehensive UIs is the fact that many common actions need to be repeated between different UIs.
This includes rendering static GUI-Components that only need to be handled in a certain way or rendering more dynamic GUI-Components like components for a Pagination utility that may be often used within an application that should simply reuse rendering code for positioning the GUIComponent and rendering the GUIItem within it.
If we wanted to create a basic GUISlot that simply renders the players head in a certain way we would be able to achieve it like this.
fun GUI.playerSkullSlot(player: Player, slotX: Int, slotY: Int) {
slot(slotX, slotY) {
item = item(Material.PLAYER_SKULL) {
name = Component.text("${player.name} Head")
skullOwner = player
}
}
}Because we are simply extending our GUI class we have all the normal GUI class methods available to us, so we can simply just render a new slot at our desired position.
And to render it in our GUI would look something like this.
fun render(player: Player): GUI {
return gui(
plugin = instance,
title = Component.text("Rendered UI"),
type = GUIType.Chest(rows = 3)
) {
playerSkullSlot(player, 1, 1)
}
}If we already have the GUISlot a GUIComponent needs to be rendered into it and all we need to do now is actually create the GUIComponent we don't need to extend the GUI class like before, instead we can do something like this where we instead extend the GUI.Slot.
fun GUI.Slot.playerBalanceComponent(playerBalance: Int) {
// base GUIItem.
val balanceItem = item(Material.GOLD_INGOT) {
name = Component.text("Balance: ${playerBalance}")
}
fun renderRich() {
balanceItem.lore = listOf(
Component.text("You're rich!")
)
}
fun renderNormal() {
balanceItem.lore = listOf(
Component.text("You're ${100_000 - playerBalance} away from being rich!")
)
}
// conditionally change the lore or other properties of the item.
if(playerBalance > 100_000) {
renderRich()
} else {
renderNormal()
}
// assigning the item of the GUISlot.
item = balanceItem
}Then rendering would similarly be like this, but we place our component inside the slot block.
fun render(playerBalance: Int) {
return gui(
plugin = instance,
title = Component.text("Rendered UI"),
type = GUIType.Chest(rows = 1)
) {
slot(4, 1) {
playerBalanceComponent(playerBalance)
onClick = {
it.sendMessage("You have ${playerBalance} balance.")
}
}
}
} Sometimes we don't want to write an extension function that is used in many places and takes a constant parameter in order to render its GUIComponent, in order to solve that problem we can instead compose a GUISlot which will then return a render function that we can use in our GUI.
fun weirdlyNamedItemComponent(
coordinates: Pair<Int, Int>
): GUI.(String) -> Unit {
return fun GUI.(
name: String
) {
val (xCoordinate, yCoordinate) = coordinates
slot(xCoordinate, yCoordinate) {
item = item(Material.PLAYER_HEAD) {
name = Component.text("This item is called $name")
}
onClick = {
it.sendMessage("It's positioned at $xCoordinate, $yCoordinate!")
}
}
}
}We then have to initialize a variable outside the scope of our gui block to stand in as our render function.
fun render(player: Player): GUI {
val renderWeirdlyNamedItem = weirdlyNamedItemComponent(4 to 2)
return gui(
plugin = instance,
title = Component.text("Rendered UI"),
type = GUIType.Chest(rows = 3)
) {
if(player.hasPermission("gui.name.nero")) {
renderWeirdlyNamedItem("Nero")
} else if(player.hasPermission("gui.name.steve")) {
renderWeirdlyNamedItem("Steve")
} else {
renderWeirdlyNamedItem("Jeff")
}
}
}We then render accordingly and as seen in the weirdlyNamedItemComponent return type it returns a GUI.(String) -> Unit which means we can pass a String into it as a parameter, this is not limited to strings however, and can be used with any type.
With this example we also don't need to pass a String into our function return type, we can simply return a GUI.() -> Unit and use this composed GUISlot to handle its own state.
This same concept similarly works with returning a GUI.Slot.() -> Unit which can simply render a GUIComponent without having to specify what slot it should render in, in the component function.