Skip to content

Instantly share code, notes, and snippets.

@downthecrop
Last active May 19, 2024 06:21
Show Gist options
  • Save downthecrop/a2553d93a78c5bfbd5bb0f9bb995d9f1 to your computer and use it in GitHub Desktop.
Save downthecrop/a2553d93a78c5bfbd5bb0f9bb995d9f1 to your computer and use it in GitHub Desktop.
basic menu system for settings
package UILib
import UILib.plugin.ParentFrame.isUIVisible
import plugin.Plugin
import plugin.annotations.PluginMeta
import plugin.api.API
import plugin.api.FontColor
import plugin.api.FontType
import plugin.api.TextModifier
import java.awt.Color
import java.awt.Toolkit
import java.awt.datatransfer.DataFlavor
import java.awt.event.*
@PluginMeta(
author = "downthecrop",
description = "Simple UI Framework",
version = 1.0
)
class plugin : Plugin() {
override fun Init() {
API.AddMouseListener(MouseCallbacks)
API.AddKeyboardListener(KeyboardCallbacks)
API.AddMouseWheelListener(MouseWheelCallbacks)
fun openOne () {
ParentFrame.contentFrame.clearElements()
ParentFrame.contentFrame.addElement(TextField("Option 1", 0, 300, 20,"Option 1 Content"))
ParentFrame.contentFrame.addElement(Option("Graphics", listOf("Low", "Medium", "High")))
ParentFrame.contentFrame.addElement(TextField("Name", 20, 150, 200, "Enter your name"))
ParentFrame.contentFrame.addElement(Option("Sound", listOf("Mute", "Low", "Medium", "High")))
ParentFrame.contentFrame.addElement(Button("Submit", 20, 300, 100) {
println("Submit button clicked!")
})
ParentFrame.contentFrame.addElement(Button("Say Hello", 20, 200, 100) {
API.SendMessage("Hello!!")
})
}
ParentFrame.leftNavFrame.addElement(Button("Menu 1", 0, 100, 30) {
openOne()
})
ParentFrame.leftNavFrame.addElement(Button("Menu 2", 0, 100, 30) {
ParentFrame.contentFrame.clearElements()
ParentFrame.contentFrame.addElement(TextField("Option 2", 0, 300, 20,"Option 2 Content"))
val buttonGroup = ButtonGroup(
listOf(
Button("Teleport home", 0, 200, 20) {
API.DispatchCommand("::home")
},
Button("Open Bank", 0, 200, 20) {
API.DispatchCommand("::bank")
},
)
)
ParentFrame.contentFrame.addElement( Button("Spawn AGS", 0, 200, 20) {
API.DispatchCommand("::item 11694")
})
ParentFrame.contentFrame.addElement(buttonGroup)
})
ParentFrame.leftNavFrame.addElement(Button("Menu 3", 0, 100, 30) {
ParentFrame.contentFrame.clearElements()
ParentFrame.contentFrame.addElement(TextField("Option 3", 0, 300, 20,"Option 3 Content"))
ParentFrame.contentFrame.addElement(Spacer(50))
val buttonGroup = ButtonGroup(
listOf(
Button("Option 1", 0, 100, 20) { println("Option 1 clicked") },
Button("Option 2", 0, 100, 20) { println("Option 2 clicked") },
Button("Option 3", 0, 100, 20) { println("Option 3 clicked") }
)
)
ParentFrame.contentFrame.addElement(buttonGroup)
})
ParentFrame.leftNavFrame.scrollRect = ScrollRect(ParentFrame.leftNavFrame, 0, 0, 150, 370, 600)
ParentFrame.contentFrame.scrollRect = ScrollRect(ParentFrame.contentFrame, 0, 0, 450, 370, 600)
// Default to Option 1
openOne()
}
override fun Draw(deltaTime: Long) {
ParentFrame.draw(deltaTime)
}
object MouseCallbacks : MouseAdapter() {
override fun mousePressed(e: MouseEvent?) {
e?.let {
ParentFrame.handleMousePress(it.x, it.y)
ParentFrame.handleMouseClick(it.x, it.y)
}
}
override fun mouseReleased(e: MouseEvent?) {
e?.let {
ParentFrame.handleMouseRelease()
}
}
override fun mouseDragged(e: MouseEvent?) {
e?.let {
ParentFrame.handleMouseDrag(it.x, it.y)
}
}
}
object MouseWheelCallbacks : MouseWheelListener {
override fun mouseWheelMoved(e: MouseWheelEvent?) {
e?.let {
if (ParentFrame.leftNavFrame.scrollRect?.isMouseInside(it.x, it.y) == true) {
ParentFrame.leftNavFrame.scrollRect?.handleMouseWheelMoved(it)
} else if (ParentFrame.contentFrame.scrollRect?.isMouseInside(it.x, it.y) == true) {
ParentFrame.contentFrame.scrollRect?.handleMouseWheelMoved(it)
} else {
}
}
}
}
object KeyboardCallbacks : KeyAdapter() {
override fun keyTyped(e: KeyEvent?) {
e?.let {
ParentFrame.handleKeyTyped(it)
}
}
override fun keyPressed(e: KeyEvent?) {
e?.let {
ParentFrame.handleKeyPressed(it)
}
}
}
object ParentFrame : Frame(0, 0, 600, 400) {
val leftNavFrame = Frame(0, 30, 150, 370)
val contentFrame = Frame(150, 30, 450, 370)
override fun draw(deltaTime: Long) {
if (!isUIVisible) return
val bgWidth = width
val bgHeight = height + 30
API.FillRect(windowX, windowY, bgWidth, bgHeight, Color.BLACK.rgb, 200)
API.DrawText(FontType.LARGE, FontColor.fromColor(Color.WHITE), TextModifier.CENTER, "Example UI", windowX + bgWidth / 2, windowY + 20)
val closeButtonX = windowX + bgWidth - 20
val closeButtonY = windowY
API.FillRect(closeButtonX, closeButtonY, 20, 20, Color.RED.rgb, 255)
API.DrawText(FontType.SMALL, FontColor.fromColor(Color.WHITE), TextModifier.CENTER, "X", closeButtonX + 10, closeButtonY + 15)
// Draw Left Navigation Frame
leftNavFrame.windowX = windowX
leftNavFrame.windowY = windowY + 30
leftNavFrame.scrollRect?.let {
val clipX = leftNavFrame.windowX + it.xOffset
val clipY = leftNavFrame.windowY + it.yOffset
val clipWidth = it.width
val clipHeight = it.height
API.ClipRect(clipX, clipY, clipX + clipWidth, clipY + clipHeight)
it.draw()
}
leftNavFrame.draw(deltaTime)
// Draw Content Frame
contentFrame.windowX = windowX + 150
contentFrame.windowY = windowY + 30
contentFrame.scrollRect?.let {
val clipX = contentFrame.windowX + it.xOffset
val clipY = contentFrame.windowY + it.yOffset
val clipWidth = it.width
val clipHeight = it.height
API.ClipRect(clipX, clipY, clipX + clipWidth, clipY + clipHeight)
it.draw()
}
contentFrame.draw(deltaTime)
// Reset clipping rectangle
API.ClipRect(0, 0, API.GetWindowDimensions().width, API.GetWindowDimensions().height)
}
override fun handleMouseClick(x: Int, y: Int): Boolean {
if (!isUIVisible) return false
val closeButtonX = windowX + width - 20
val closeButtonY = windowY
if (x in closeButtonX until closeButtonX + 20 && y in closeButtonY until closeButtonY + 20) {
isUIVisible = false
return true
}
if (leftNavFrame.handleMouseClick(x, y)) return true
if (contentFrame.handleMouseClick(x, y)) return true
return false
}
override fun handleKeyTyped(e: KeyEvent) {
if (!isUIVisible) return
leftNavFrame.handleKeyTyped(e)
contentFrame.handleKeyTyped(e)
}
override fun handleKeyPressed(e: KeyEvent) {
if (!isUIVisible) return
leftNavFrame.handleKeyPressed(e)
contentFrame.handleKeyPressed(e)
}
}
open class Frame(
var windowX: Int,
var windowY: Int,
val width: Int,
val height: Int
) {
private val elements = mutableListOf<UIElement>()
private val openDropdowns = mutableListOf<Pair<Option, Int>>()
var scrollRect: ScrollRect? = null
var isUIVisible = true
private var dragStartX = 0
private var dragStartY = 0
private var isDragging = false
fun addElement(element: UIElement) {
elements.add(element)
}
fun clearElements() {
elements.clear()
// Scroll to top
scrollRect?.scrollOffset = 0
}
open fun draw(deltaTime: Long) {
if (!isUIVisible) return
API.FillRect(windowX, windowY, width, height, Color.GRAY.rgb, 200)
var yPosition = windowY + 10
openDropdowns.clear()
for (element in elements) {
element.draw(deltaTime, windowX + 10, yPosition - (scrollRect?.scrollOffset ?: 0))
if (element is Option && element.isDropdownOpen) {
openDropdowns.add(element to yPosition)
}
yPosition += element.height + 10
}
for ((dropdown, yPos) in openDropdowns) {
dropdown.drawDropdown(windowX + 10, yPos - (scrollRect?.scrollOffset ?: 0))
}
}
fun handleMousePress(x: Int, y: Int) {
if (!isUIVisible) return
if (x in windowX until windowX + width && y in windowY until windowY + 30) {
isDragging = true
dragStartX = x - windowX
dragStartY = y - windowY
}
}
fun handleMouseDrag(x: Int, y: Int) {
if (!isUIVisible) return
if (isDragging) {
windowX = x - dragStartX
windowY = y - dragStartY
}
}
fun handleMouseRelease() {
if (!isUIVisible) return
isDragging = false
}
open fun handleMouseClick(x: Int, y: Int): Boolean {
if (!isUIVisible) return false
var yPosition = windowY + 10
for (element in elements) {
if (element.handleMouseClick(x, y, windowX + 10, yPosition - (scrollRect?.scrollOffset ?: 0))) {
return true
}
yPosition += element.height + 10
}
closeAllDropdowns()
return false
}
open fun handleKeyTyped(e: KeyEvent) {
if (!isUIVisible) return
for (element in elements) {
if (element is TextField && element.isFocused) {
element.handleKeyTyped(e)
}
}
}
open fun handleKeyPressed(e: KeyEvent) {
if (!isUIVisible) return
for (element in elements) {
if (element is TextField && element.isFocused) {
element.handleKeyPressed(e)
}
}
}
fun closeAllDropdowns() {
for (element in elements) {
if (element is Option) {
element.isDropdownOpen = false
}
}
}
}
class ScrollRect(
private val frame: Frame,
val xOffset: Int,
val yOffset: Int,
val width: Int,
val height: Int,
private val contentHeight: Int
) {
var scrollOffset = 0
private val scrollStep = 20
fun draw() {
val x = frame.windowX + xOffset
val y = frame.windowY + yOffset
val scrollbarHeight = (height.toDouble() / contentHeight * height).toInt().coerceAtLeast(20)
val scrollbarY = y + (scrollOffset.toDouble() / contentHeight * height).toInt()
API.FillRect(x + width - 10, scrollbarY, 10, scrollbarHeight, Color.ORANGE.rgb, 128)
}
fun handleMouseWheelMoved(e: MouseWheelEvent): Boolean {
val rotation = e.wheelRotation
scrollOffset = (scrollOffset + rotation * scrollStep).coerceIn(0, contentHeight - height)
return true
}
fun isMouseInside(mouseX: Int, mouseY: Int): Boolean {
val x = frame.windowX + xOffset
val y = frame.windowY + yOffset
return mouseX in x until x + width && mouseY in y until y + height
}
}
interface UIElement {
val height: Int
fun draw(deltaTime: Long, x: Int, y: Int)
fun handleMouseClick(mouseX: Int, mouseY: Int, x: Int, y: Int): Boolean
}
class Option(private val label: String, private val dropdownItems: List<String>) : UIElement {
var isDropdownOpen = false
private var selectedItem = dropdownItems[0]
override val height: Int
get() = 20
override fun draw(deltaTime: Long, x: Int, y: Int) {
val labelWidth = 150
val dropdownWidth = 200
API.DrawText(FontType.SMALL, FontColor.fromColor(Color.WHITE), TextModifier.LEFT, label, x, y + 15)
API.FillRect(x + labelWidth, y, dropdownWidth, height, Color.GRAY.rgb, 64)
API.DrawText(FontType.SMALL, FontColor.fromColor(Color.WHITE), TextModifier.LEFT, selectedItem, x + labelWidth + 5, y + 15)
}
fun drawDropdown(x: Int, y: Int) {
if (!isDropdownOpen) return
val labelWidth = 150
val dropdownWidth = 200
var dropdownY = y + height
for ((index, item) in dropdownItems.withIndex()) {
API.FillRect(x + labelWidth, dropdownY, dropdownWidth, 20, Color.DARK_GRAY.rgb, 0)
API.DrawText(FontType.SMALL, FontColor.fromColor(Color.WHITE), TextModifier.LEFT, item, x + labelWidth + 5, dropdownY + 15)
dropdownY += 20
if (index < dropdownItems.size - 1) {
API.FillRect(x + labelWidth, dropdownY - 1, dropdownWidth, 1, Color.BLACK.rgb, 255)
}
}
}
override fun handleMouseClick(mouseX: Int, mouseY: Int, x: Int, y: Int): Boolean {
val labelWidth = 150
val dropdownWidth = 200
if (mouseX in x + labelWidth until x + labelWidth + dropdownWidth && mouseY in y until y + height) {
if (!isDropdownOpen) {
ParentFrame.closeAllDropdowns()
isDropdownOpen = true
} else {
isDropdownOpen = false
}
return true
}
if (isDropdownOpen) {
var dropdownY = y + height
for (item in dropdownItems) {
if (mouseX in x + labelWidth until x + labelWidth + dropdownWidth && mouseY in dropdownY until dropdownY + 20) {
selectedItem = item
isDropdownOpen = false
API.SendMessage("Selected $item")
return true
}
dropdownY += 20
}
}
return false
}
}
class TextField(
private val label: String,
private val x: Int,
private val width: Int,
override val height: Int,
private val placeholder: String? = null
) : UIElement {
var isFocused = false
private var text = ""
private var showCursor = true
private var elapsedTime = 0L
private var selectAll = false
private val blinkInterval = 500L
override fun draw(deltaTime: Long, windowX: Int, yPosition: Int) {
elapsedTime += deltaTime
if (elapsedTime >= blinkInterval) {
showCursor = !showCursor
elapsedTime = 0L
}
val alpha = if (isFocused) 100 else 50
val labelWidth = 150
API.DrawText(FontType.SMALL, FontColor.fromColor(Color.WHITE), TextModifier.LEFT, label, windowX, yPosition + 15)
API.FillRect(windowX + labelWidth, yPosition, width, height, Color.BLUE.rgb, alpha)
val maxChars = (width - 10) / 8
val displayText = if (text.isEmpty() && !isFocused && placeholder != null) {
placeholder.take(maxChars)
} else if (isFocused && showCursor) {
if (text.length > maxChars) text.take(maxChars - 1) + "|" else "$text|"
} else {
text.take(maxChars)
}
val textColor = if (text.isEmpty() && !isFocused && placeholder != null) {
FontColor.fromColor(Color.GRAY)
} else {
FontColor.fromColor(Color.WHITE)
}
API.DrawText(FontType.SMALL, textColor, TextModifier.LEFT, displayText, windowX + labelWidth + 5, yPosition + 15)
}
override fun handleMouseClick(mouseX: Int, mouseY: Int, windowX: Int, yPosition: Int): Boolean {
val labelWidth = 150
isFocused = mouseX in windowX + labelWidth until windowX + labelWidth + width && mouseY in yPosition until yPosition + height
return isFocused
}
fun handleKeyTyped(e: KeyEvent) {
if (isFocused && !selectAll) {
val char = e.keyChar
if (char == '\b') {
if (text.isNotEmpty()) {
text = text.substring(0, text.length - 1)
}
} else {
text += char
}
}
}
fun handleKeyPressed(e: KeyEvent) {
if (isFocused) {
when {
e.isControlDown && e.keyCode == KeyEvent.VK_V -> {
val clipboard = Toolkit.getDefaultToolkit().systemClipboard
try {
val data = clipboard.getData(DataFlavor.stringFlavor) as String
text += data
} catch (ex: Exception) {
ex.printStackTrace()
}
}
e.isControlDown && e.keyCode == KeyEvent.VK_A -> {
selectAll = true
}
selectAll && (e.keyCode == KeyEvent.VK_DELETE || e.keyCode == KeyEvent.VK_BACK_SPACE) -> {
text = ""
selectAll = false
}
else -> {
selectAll = false
}
}
}
}
}
class Button(
private val label: String,
private val x: Int,
val width: Int,
override val height: Int,
private val onClick: () -> Unit
) : UIElement {
override fun draw(deltaTime: Long, windowX: Int, yPosition: Int) {
API.FillRect(windowX + x, yPosition, width, height, Color.GRAY.rgb, 64)
API.DrawText(FontType.SMALL, FontColor.fromColor(Color.WHITE), TextModifier.CENTER, label, windowX + x + width / 2, yPosition + height / 2 + 5)
}
override fun handleMouseClick(mouseX: Int, mouseY: Int, windowX: Int, yPosition: Int): Boolean {
if (mouseX in windowX + x until windowX + x + width && mouseY in yPosition until yPosition + height) {
onClick()
return true
}
return false
}
}
class ButtonGroup(private val buttons: List<Button>) : UIElement {
override val height: Int
get() = buttons.maxOf { it.height }
override fun draw(deltaTime: Long, x: Int, y: Int) {
var currentX = x
for (button in buttons) {
button.draw(deltaTime, currentX, y)
currentX += button.width + 10
}
}
override fun handleMouseClick(mouseX: Int, mouseY: Int, x: Int, y: Int): Boolean {
var currentX = x
for (button in buttons) {
if (button.handleMouseClick(mouseX, mouseY, currentX, y)) {
return true
}
currentX += button.width + 10
}
return false
}
}
class Spacer(
override val height: Int
) : UIElement {
override fun draw(deltaTime: Long, x: Int, y: Int) {
// Draw nothing
}
override fun handleMouseClick(mouseX: Int, mouseY: Int, x: Int, y: Int): Boolean {
return false
}
}
override fun ProcessCommand(commandStr: String?, args: Array<out String>?) {
super.ProcessCommand(commandStr, args)
when (commandStr) {
"::openmenu" -> {
isUIVisible = true
}
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment