High-performance Discord Message Component Based Kotlin UI Framework
Render Reactive Message and Manage States and Listeners
<dependency>
<groupId>io.github.sonmoosans</groupId>
<artifactId>dui</artifactId>
<version>1.4.0</version>
</dependency>
DUI provides high code quality, high performance, memory safe UI System
- Manage Message Component Listeners
- Rendering message like a reactive UI
- Functional Programming style usage
- Highly flexible: Rendering with everything such as
Graphics2d
- Memory-Safe: We use dynamic listener to reduce memory usage
Everything is Clean and Beautiful.
Create a message containing an Embed that displays a number
With a button that increases the number by clicking it
val counter = component<Unit> {
val count by useState("count", 0)
embed(title = "Counter", description = count.toString())
row {
button("Increase") {
count++
event.edit()
}
}
}
DUI provided some built-in hooks for rendering messages
All Hooks contain a unique ID field so it can be called in any orders
ID of Built-in Hooks can be anonymous, which is generated from lambda
val theme = useContext(ThemeContext)
val state by useState("id", "initial value")
val (count, setCount) = useState { "initial value" }
val sync = useSync()
val memo = useMemo(dependencies) { processString(state) }
val confirmModal = useModal {
title = "Do you sure?"
row {
input(id = "confirm", label = "Type 'I am gay' to confirm")
}
submit {
//do something
}
}
useChange(dependencies) {
println("Updated!")
}
useEffect(dependencies) {
println("Updated!")
}
useExport(data = "Export Something")
DUI also provides some built-in Components
rowLayout { //Split into multi Action Rows if overflow
button(label = "Test") {
event.ignore()
}
menu {
option("Label", "Value")
submit {
event.ignore()
}
}
}
pager { //a simple Pager implementation
page {
embed(title = "Page 1")
}
}
tabLayout { //Adds a SelectMenu to switch between Tabs
tab("User") {
text("Your Profile")
proflie()
}
tab("Settings") {
embed(title = "Settings Tab")
}
}
Component only needs a Data
instance for rendering.
val Test = component<Props> {
embed(title = props.value)
}
All Data entries will be stored in a Map
You can implement your own management system above it
Remember that You must destroy unused Data manually
val Test = dynamicComponent(YourGenerator) {
embed(title = props.value)
}
It reads props from event info using a Generator
and invoke dynamic listeners with parsed data
Therefore, it doesn't require to store any data
However, you cannot store too many things to a listener id since length of listener Ids has a limit
IDComponent
, OnceComponent
use it as the default listener type
val result = something()
button("Do something", dynamic = false) {
println(result)
event.edit()
}
Data Based Listeners are stored in each Data
object
Therefore, It will use some memory when there are a lot of data objects and listeners
SingleDataComponent
, DynamicComponent
use it as the default listener type
val ref = useRef { something() }
button("Do Something", dynamic = true) {
println(ref.current)
event.edit()
}
You can use Dynamic Listener instead to reduce memory usage
Since they are bundled with Component itself
Dynamic Listeners only needs to be created once, and can be used for unlimited times.
Since data is not synchronized, You should not access any data outside the Listener
You must wrap those variables inside a useRef
hook to access them
//set default value of 'dynamic'
dynamic = value
button("...") {
//do something
}
//set default value of 'dynamic' only in scope
dynamic(value) {
button("...") {
//do something
}
}
Data based Listeners can override dynamic listeners by using the same ID
Listener ID Structure: [Component ID]-[Data ID]-[Listener ID]
row {
button("Do Something") { //ID: 4343243243-3-432423432
println("Component Interaction Event")
}
button("Remove", id = "onRemove") { //ID: 4343243243-3-onRemove
println("Component Interaction Event")
}
menu(placeholder = "Select Item") {
option("...", "...")
submit("onSelect") { //ID: 4343243243-3-onSelect
}
}
}
To use external Listener ID, don't pass the event handler
Therefore, you can create your own Event handler
row {
//For Select Menu, just pass the ID to root function instead of 'submit' function
menu(id = "onRemove", placeholder = "Select Something") {
option("...", "...")
}
button("Remove", id = "onRemove") //ID: onRemove
}
Not only embed or text, DUI supports render everything. Including rendering UI with Graphics2D
DUI also has a small Utility for Rendering with Graphics2D
//You may wrap this in useMemo Hook
val image = BufferedImage(500, 600, BufferedImage.TYPE_INT_RGB)
with (image.createGraphics()) {
val (w, h) = 450 to 100
font = font.deriveFont(25f)
translate((500 - w) / 2, 50)
for (i in 0..3) {
paint(Color.DARK_GRAY) {
fillRoundRect(0, 0, w, h, 20, 20)
}
translate(0, h + 10)
}
}
files {
file("ui.png", image.toInputStream())
}
Create a Component
val example = component {
val count by useState("count", 0)
text(count.toString())
row {
button("Increase") {
count++
event.edit()
}
}
}
In above example, we create a count
state
When "Increase" Button is clicked, Increase count state and Reply to the event
Then, Register a Slash command (We use BJDA for this)
See their tutorial to learn how to use BJDA
fun TestCommand() = command("test", "Testing Command") {
execute {
val ui = example.create(event.user.idLong, Unit) {
//sync(event.hook)
//use with useSync hook to sync multi messages
}
event.reply(ui).queue()
}
}
- Dynamic Listeners unmounted after restarting bot
Give this repo a star!