Last active
May 19, 2024 06:21
-
-
Save downthecrop/a2553d93a78c5bfbd5bb0f9bb995d9f1 to your computer and use it in GitHub Desktop.
basic menu system for settings
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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