Skip to content

Signals & Refresh Block

MattMX edited this page Jun 28, 2024 · 4 revisions

In KtGUI we added Signals, which are an easy way to update specific parts of your GUI without having to call any methods, allowing you to take an even more declarative approach to your interfaces.

Signals (Primatives/Strings)

Let's start by creating a simple GUI.

val signalsExample = gui(!"Signals", InventoryType.HOPPER) {

}

And let's create our first signal.

val signalsExample = gui(!"Signals", InventoryType.HOPPER) {
    var counter by signal(0)
}

Signals can only be created if a SignalOwner is provided. GuiScreen already implements it, and provides us with a utility function to make a signal even quicker. Remember, for primatives and Strings, use the by keyword, lists and complex Objects are little different, we will get to that later.

To use the Signal we need to use a SignalButton, since not all GuiButtons require the implementation of Signals.

val signalsExample = gui(!"Signals", InventoryType.HOPPER) {
    var counter by signal(0)
    
    effect {
        button(Material.GUNPOWDER) {

        } slot 2
    }
}

Now let's make it change when we click the button.

val signalsExample = gui(!"Signals", InventoryType.HOPPER) {
    var counter by signal(0)

    effect {
        button(Material.GUNPOWDER) {
            named(!"&d&lYou have clicked $counter times")
            material(if (counter == 0) Material.GUNPOWDER else Material.GLOWSTONE)

            click {
                ClickType.LEFT {
                    counter++
                }
            }
        } slot 2
    }
}

Now when a player opens this GUI, the will be met with a GUNPOWDER item, when clicked will update counter and any SignalButtons that refer to it.

This means we can create many different signals, each being used in different components. These components will only update if something they mention is changed.

Signals (Objects)

When using Signals with lists or any objects with fields we want to change, you should not use the by keyword, and instead assign the Signal directly with =.

For example:

val signalsListExample = gui(!"Signals (List)", InventoryType.HOPPER) {
    val list = signal<Nothing, ArrayList<String>>(arrayListOf("MattMX", "GabbySimon"))

    effect {
        button(Material.BOOK) {
            named(!"${list().size}")
            lore {
                list().forEach { li -> add(!"&f- &7$li") }
            }
            click {
                ClickType.LEFT {
                    list.mut { add("MattMX") }
                }
                ClickType.RIGHT {
                    list.mut { remove("MattMX") }
                }
                ClickType.DROP {
                    list setTo arrayListOf()
                    // Alternatively:
                    list(arrayListOf())
                }
            }
        } slot 2
    }
}

We now need to use Signal#invoke() to access the value stored within.

To modify the Signal internally, we can call Signal#mut { /* code */ } which will run the code block and update the value.

If we want to completely change the value of the Signal, we can use Signal#setTo(newValue) or Signal#invoke(newValue).

Refresh Block

We can't always keep track of our variables and where they change.

If you find yourself in this situation, the simplest solution to update your button components is to use a RefreshBlock.

The refresh block will update any code inside of it via an async scheduled loop using the Bukkit scheduling API.

KtGui will automatically remove the task if the gui is destroyed (player quits, closes gui, no more gui audience)

val test = gui(!"Counter Button", 1) {
  var start = System.currentTimeMillis()

  // Will refresh once every 20 ticks (1 second)
  refresh(20) {
    val time = Duration.ofMillis(System.currentTimeMillis() - start).pretty()    

    button(Material.CLOCK) {
      named(!"&f${time}")
      lore(!"&aClick to reset")
      click.left {
        start = System.currentTimeMillis()
      }
    } slot 4
  }
}
Clone this wiki locally