reorganize

This commit is contained in:
downthecrop 2025-09-08 04:00:39 -07:00
parent 529f0c22b0
commit cf6bb51d2c
20 changed files with 815 additions and 411 deletions

View file

@ -0,0 +1,45 @@
package KondoKit.components
import KondoKit.plugin.Companion.TITLE_BAR_COLOR
import KondoKit.plugin.Companion.WIDGET_COLOR
import KondoKit.plugin.Companion.secondaryColor
import java.awt.*
import javax.swing.*
class ButtonPanel(
private val alignment: Int = FlowLayout.CENTER,
private val hgap: Int = 5,
private val vgap: Int = 0
) : JPanel() {
init {
layout = FlowLayout(alignment, hgap, vgap)
background = WIDGET_COLOR
}
fun addButton(text: String, action: () -> Unit): JButton {
val button = JButton(text).apply {
background = TITLE_BAR_COLOR
foreground = secondaryColor
font = Font("RuneScape Small", Font.PLAIN, 14)
addActionListener {
action()
}
}
add(button)
return button
}
fun addIcon(icon: Icon, action: () -> Unit): JButton {
val button = JButton(icon).apply {
background = TITLE_BAR_COLOR
foreground = secondaryColor
font = Font("RuneScape Small", Font.PLAIN, 14)
addActionListener {
action()
}
}
add(button)
return button
}
}

View file

@ -0,0 +1,60 @@
package KondoKit.components
import KondoKit.ImageCanvas
import KondoKit.SpriteToBufferedImage
import KondoKit.plugin.Companion.WIDGET_COLOR
import KondoKit.plugin.Companion.primaryColor
import KondoKit.plugin.Companion.secondaryColor
import plugin.api.API
import java.awt.BorderLayout
import java.awt.Color
import java.awt.Dimension
import javax.swing.BorderFactory
import javax.swing.JPanel
class IconComponent(
spriteId: Int,
private val iconWidth: Int = 25,
private val iconHeight: Int = 23,
private val useThemeColors: Boolean = true,
private val tint: Color? = null,
private val grayscale: Boolean = false,
private val brightnessBoost: Float = 1.0f
) : JPanel() {
private val imageCanvas: ImageCanvas
init {
layout = BorderLayout()
background = if (useThemeColors) WIDGET_COLOR else Color.WHITE
val bufferedImageSprite = SpriteToBufferedImage.getBufferedImageFromSprite(
API.GetSprite(spriteId),
tint,
grayscale,
brightnessBoost
)
imageCanvas = ImageCanvas(bufferedImageSprite).apply {
val size = Dimension(iconWidth, iconHeight)
preferredSize = size
maximumSize = size
minimumSize = size
background = if (useThemeColors) WIDGET_COLOR else Color.WHITE
}
add(imageCanvas, BorderLayout.CENTER)
border = BorderFactory.createEmptyBorder(2, 2, 2, 2)
}
fun updateFillColor(color: Color) {
imageCanvas.fillColor = color
background = color
imageCanvas.repaint()
repaint()
}
fun applyThemeColors() {
updateFillColor(WIDGET_COLOR)
}
}

View file

@ -0,0 +1,35 @@
package KondoKit.components
import KondoKit.Helpers.formatHtmlLabelText
import KondoKit.plugin.Companion.primaryColor
import KondoKit.plugin.Companion.secondaryColor
import java.awt.Font
import java.awt.Color
import javax.swing.JLabel
import javax.swing.SwingConstants
class LabelComponent(
text: String = "",
private val isHtml: Boolean = false,
private val alignment: Int = SwingConstants.LEFT
) : JLabel() {
init {
this.text = text
this.horizontalAlignment = alignment
this.font = Font("RuneScape Small", Font.TRUETYPE_FONT, 16)
}
fun updateText(plainText: String, color: Color = secondaryColor) {
this.text = plainText
this.foreground = color
}
fun updateHtmlText(text1: String, color1: Color = primaryColor, text2: String = "", color2: Color = secondaryColor) {
this.text = formatHtmlLabelText(text1, color1, text2, color2)
}
fun setAsHeader() {
font = Font("RuneScape Small", Font.BOLD, 16)
}
}

View file

@ -0,0 +1,27 @@
package KondoKit.components
import KondoKit.plugin.Companion.POPUP_BACKGROUND
import KondoKit.plugin.Companion.POPUP_FOREGROUND
import java.awt.Font
import javax.swing.JMenuItem
import javax.swing.JPopupMenu
class PopupMenuComponent : JPopupMenu() {
init {
background = POPUP_BACKGROUND
}
fun addMenuItem(text: String, action: () -> Unit): JMenuItem {
val menuItem = JMenuItem(text).apply {
font = Font("RuneScape Small", Font.TRUETYPE_FONT, 16)
background = POPUP_BACKGROUND
foreground = POPUP_FOREGROUND
addActionListener {
action()
}
}
add(menuItem)
return menuItem
}
}

View file

@ -0,0 +1,77 @@
package KondoKit.components
import KondoKit.plugin.Companion.PROGRESS_BAR_FILL
import KondoKit.plugin.Companion.secondaryColor
import java.awt.Canvas
import java.awt.Color
import java.awt.Dimension
import java.awt.Font
import java.awt.Graphics
class ProgressBar(
private var progress: Double,
private val barColor: Color,
private var currentLevel: Int = 0,
private var nextLevel: Int = 1
) : Canvas() {
init {
font = Font("RuneScape Small", Font.TRUETYPE_FONT, 16)
}
override fun paint(g: Graphics) {
super.paint(g)
// Draw the filled part of the progress bar
g.color = barColor
val width = (progress * this.width / 100).toInt()
g.fillRect(0, 0, width, this.height)
// Draw the unfilled part of the progress bar
g.color = PROGRESS_BAR_FILL
g.fillRect(width, 0, this.width - width, this.height)
// Variables for text position
val textY = this.height / 2 + 6
// Draw the current level on the far left
drawTextWithShadow(g, "Lvl. $currentLevel", 5, textY, secondaryColor)
// Draw the percentage in the middle
val percentageText = String.format("%.2f%%", progress)
val percentageWidth = g.fontMetrics.stringWidth(percentageText)
drawTextWithShadow(g, percentageText, (this.width - percentageWidth) / 2, textY, secondaryColor)
// Draw the next level on the far right
val nextLevelText = "Lvl. $nextLevel"
val nextLevelWidth = g.fontMetrics.stringWidth(nextLevelText)
drawTextWithShadow(g, nextLevelText, this.width - nextLevelWidth - 5, textY, secondaryColor)
}
override fun getPreferredSize(): Dimension {
return Dimension(220, 16) // Force the height to 16px, width can be anything appropriate
}
override fun getMinimumSize(): Dimension {
return Dimension(220, 16) // Force the minimum height to 16px, width can be smaller
}
fun updateProgress(newProgress: Double, currentLevel: Int, nextLevel: Int, isVisible : Boolean) {
this.progress = newProgress
this.currentLevel = currentLevel
this.nextLevel = nextLevel
if(isVisible)
repaint()
}
// Helper function to draw text with a shadow effect
private fun drawTextWithShadow(g: Graphics, text: String, x: Int, y: Int, textColor: Color) {
// Draw shadow (black text with -1 x and -1 y offset)
g.color = Color(0, 0, 0)
g.drawString(text, x + 1, y + 1)
// Draw actual text on top
g.color = textColor
g.drawString(text, x, y)
}
}

View file

@ -0,0 +1,156 @@
package KondoKit.components.ReflectiveEditorComponents
import KondoKit.ImageCanvas
import KondoKit.plugin
import KondoKit.plugin.Companion.WIDGET_COLOR
import KondoKit.plugin.Companion.secondaryColor
import plugin.api.API
import java.awt.*
import java.awt.datatransfer.DataFlavor
import java.awt.event.ActionEvent
import java.awt.event.ActionListener
import java.awt.event.KeyAdapter
import java.awt.event.KeyEvent
import java.awt.event.MouseAdapter
import java.awt.event.MouseEvent
import java.awt.image.BufferedImage
import javax.imageio.ImageIO
import javax.swing.*
class CustomSearchField(private val parentPanel: JPanel, private val onSearch: (String) -> Unit) : Canvas() {
private var cursorVisible: Boolean = true
private var text: String = ""
private val bufferedImageSprite = getBufferedImageFromSprite(API.GetSprite(1423)) // MAG_SPRITE
private val imageCanvas = bufferedImageSprite.let {
ImageCanvas(it).apply {
preferredSize = Dimension(12, 12) // ICON_DIMENSION_SMALL
size = preferredSize
minimumSize = preferredSize
maximumSize = preferredSize
fillColor = WIDGET_COLOR
}
}
init {
preferredSize = Dimension(230, 30) // SEARCH_FIELD_DIMENSION
background = WIDGET_COLOR // COLOR_BACKGROUND_DARK
foreground = secondaryColor // COLOR_FOREGROUND_LIGHT
font = Font("Arial", Font.PLAIN, 14) // FONT_ARIAL_PLAIN_14
minimumSize = preferredSize
maximumSize = preferredSize
addKeyListener(object : KeyAdapter() {
override fun keyTyped(e: KeyEvent) {
// Prevent null character from being typed on Ctrl+A & Ctrl+V
if (e.isControlDown && (e.keyChar == '\u0001' || e.keyChar == '\u0016')) {
e.consume()
return
}
if (e.keyChar == '\b') {
if (text.isNotEmpty()) {
text = text.dropLast(1)
}
} else if (e.keyChar == '\n') {
onSearch(text)
} else {
text += e.keyChar
}
SwingUtilities.invokeLater {
repaint()
}
}
override fun keyPressed(e: KeyEvent) {
if (e.isControlDown) {
when (e.keyCode) {
KeyEvent.VK_A -> {
text = ""
SwingUtilities.invokeLater {
repaint()
}
}
KeyEvent.VK_V -> {
val clipboard = Toolkit.getDefaultToolkit().systemClipboard
val pasteText = clipboard.getData(DataFlavor.stringFlavor) as String
text += pasteText
SwingUtilities.invokeLater {
repaint()
}
}
}
}
}
})
addMouseListener(object : MouseAdapter() {
override fun mouseClicked(e: MouseEvent) {
if (e.x > width - 20 && e.y < 20) {
text = ""
onSearch(text)
SwingUtilities.invokeLater {
repaint()
}
}
}
})
javax.swing.Timer(500, ActionListener { _ ->
cursorVisible = !cursorVisible
if (plugin.StateManager.focusedView == "REFLECTIVE_EDITOR_VIEW")
SwingUtilities.invokeLater {
repaint()
}
}).start()
}
override fun paint(g: Graphics) {
super.paint(g)
g.color = foreground
g.font = font
val fm = g.fontMetrics
val cursorX = fm.stringWidth(text) + 30
// Draw magnifying glass icon
imageCanvas.let { canvas ->
val imgG = g.create(5, 5, canvas.width, canvas.height)
canvas.paint(imgG)
imgG.dispose()
}
// Use a local copy of the text to avoid threading issues
val currentText = text
// Draw placeholder text if field is empty, otherwise draw actual text
if (currentText == "") {
g.color = Color.GRAY // Use a lighter color for placeholder text
g.drawString("Search plugins...", 30, 20)
} else {
g.color = foreground // Use normal color for actual text
g.drawString(currentText, 30, 20)
}
if (cursorVisible && hasFocus()) {
g.color = foreground
g.drawLine(cursorX, 5, cursorX, 25)
}
// Only draw the "x" button if there's text
if (currentText != "") {
g.color = Color.RED
g.drawString("x", width - 20, 20)
}
}
private fun getBufferedImageFromSprite(sprite: Any?): BufferedImage {
// This is a simplified version - you might need to adjust based on your actual implementation
// For now, let's just return a placeholder image
val image = BufferedImage(12, 12, BufferedImage.TYPE_INT_ARGB)
val g2d = image.createGraphics()
g2d.color = Color.GRAY
g2d.fillOval(2, 2, 8, 8)
g2d.drawLine(8, 8, 11, 11)
g2d.dispose()
return image
}
}

View file

@ -0,0 +1,14 @@
package KondoKit.components.ReflectiveEditorComponents
data class GitLabPlugin(
val id: String,
val path: String,
val pluginProperties: PluginProperties?,
val pluginError: String?
)
data class PluginProperties(
val author: String,
val version: String,
val description: String
)

View file

@ -0,0 +1,117 @@
package KondoKit.components.ReflectiveEditorComponents
import KondoKit.views.ReflectiveEditorView
import com.google.gson.Gson
import com.google.gson.JsonObject
import java.awt.EventQueue
import java.io.BufferedReader
import java.io.InputStreamReader
import java.net.HttpURLConnection
import java.net.URL
import javax.swing.SwingUtilities
object GitLabPluginFetcher {
private const val GITLAB_ACCESS_TOKEN = "glpat-dE2Cs2e4b32-H7c9oGuS"
private const val GITLAB_PROJECT_ID = "38297322"
private const val GITLAB_BRANCH = "master"
// Function to fetch plugins from GitLab
fun fetchGitLabPlugins(onComplete: (List<GitLabPlugin>) -> Unit) {
Thread {
try {
val plugins = mutableListOf<GitLabPlugin>()
val apiUrl = "https://gitlab.com/api/v4/projects/${GITLAB_PROJECT_ID}/repository/tree?ref=${GITLAB_BRANCH}"
// Create URL connection
val url = URL(apiUrl)
val connection = url.openConnection() as HttpURLConnection
connection.requestMethod = "GET"
connection.setRequestProperty("PRIVATE-TOKEN", GITLAB_ACCESS_TOKEN)
// Read response
val responseCode = connection.responseCode
if (responseCode == HttpURLConnection.HTTP_OK) {
val reader = BufferedReader(InputStreamReader(connection.inputStream))
val response = reader.use { it.readText() }
// Parse JSON response
val gson = Gson()
val treeItems = gson.fromJson(response, Array::class.java)
// Filter for directories (trees)
for (item in treeItems) {
val jsonObject = item as JsonObject
if (jsonObject["type"].asString == "tree") {
val folderId = jsonObject["id"].asString
val folderPath = jsonObject["path"].asString
try {
val pluginProperties = fetchPluginProperties(folderPath)
plugins.add(GitLabPlugin(folderId, folderPath, pluginProperties, null))
} catch (e: Exception) {
plugins.add(GitLabPlugin(folderId, folderPath, null, "Error fetching plugin.properties"))
}
}
}
}
// Update on UI thread
SwingUtilities.invokeLater {
onComplete(plugins)
}
} catch (e: Exception) {
e.printStackTrace()
SwingUtilities.invokeLater {
onComplete(emptyList())
}
}
}.start()
}
// Function to fetch plugin.properties from a specific folder
private fun fetchPluginProperties(folderPath: String): PluginProperties {
val pluginFilePath = "$folderPath/plugin.properties"
val pluginUrl = "https://gitlab.com/api/v4/projects/${GITLAB_PROJECT_ID}/repository/files/${pluginFilePath.replace("/", "%2F")}/raw?ref=${GITLAB_BRANCH}"
// Create URL connection
val url = URL(pluginUrl)
val connection = url.openConnection() as HttpURLConnection
connection.requestMethod = "GET"
connection.setRequestProperty("PRIVATE-TOKEN", GITLAB_ACCESS_TOKEN)
// Read response
val responseCode = connection.responseCode
if (responseCode == HttpURLConnection.HTTP_OK) {
val reader = BufferedReader(InputStreamReader(connection.inputStream))
val rawContent = reader.use { it.readText() }
return parseProperties(rawContent)
} else {
throw Exception("Plugin file not found for folder: $folderPath")
}
}
// Function to parse plugin.properties content
private fun parseProperties(content: String): PluginProperties {
val lines = content.split("\n")
var author = "Unknown"
var version = "Unknown"
var description = "No description available"
for (line in lines) {
val parts = line.split("=")
if (parts.size == 2) {
val key = parts[0].trim()
val value = parts[1].replace("\"", "").replace("'", "").trim()
when {
key.startsWith("AUTHOR", ignoreCase = true) -> author = value
key.startsWith("VERSION", ignoreCase = true) -> version = value
key.startsWith("DESCRIPTION", ignoreCase = true) -> description = value
}
}
}
return PluginProperties(author, version, description)
}
}

View file

@ -0,0 +1,162 @@
package KondoKit.components
import KondoKit.plugin.Companion.SCROLL_BAR_COLOR
import KondoKit.plugin.Companion.VIEW_BACKGROUND_COLOR
import rt4.GameShell.frame
import java.awt.Graphics
import java.awt.Graphics2D
import java.awt.Rectangle
import java.awt.event.*
import java.util.*
import javax.swing.JPanel
import javax.swing.SwingUtilities
class ScrollablePanel(private val content: JPanel) : JPanel() {
private var lastMouseY = 0
private var currentOffsetY = 0
private var scrollbarHeight = 0
private var scrollbarY = 0
private var showScrollbar = false
private var draggingScrollPill = false
private var lastSize = 0
// Define a buffer for the view height (extra space for smoother scrolling)
private val viewBuffer = -30
init {
layout = null
background = VIEW_BACKGROUND_COLOR // Color.red color can be set to debug
// Initial content bounds
content.bounds = Rectangle(0, 0, 242, content.preferredSize.height.coerceAtLeast(frame.height + viewBuffer))
add(content)
// Add listeners for scrolling interactions
addMouseListener(object : MouseAdapter() {
override fun mousePressed(e: MouseEvent) {
lastMouseY = e.y
if (showScrollbar && e.x in (242 - 10)..242 && e.y in scrollbarY..(scrollbarY + scrollbarHeight)) {
draggingScrollPill = true
}
}
override fun mouseReleased(e: MouseEvent) {
draggingScrollPill = false
}
})
addMouseMotionListener(object : MouseMotionAdapter() {
override fun mouseDragged(e: MouseEvent) {
val deltaY = e.y - lastMouseY
if (draggingScrollPill && showScrollbar) {
val viewHeight = frame.height
val contentHeight = content.height
val scrollRatio = contentHeight.toDouble() / viewHeight
scrollContent((deltaY * scrollRatio).toInt())
} else if (showScrollbar) {
scrollContent(deltaY)
}
lastMouseY = e.y
}
})
addMouseWheelListener { e ->
if (showScrollbar) {
scrollContent(-e.wheelRotation * 20)
}
}
// Timer to periodically check and update scrollbar status
Timer().schedule(object : TimerTask() {
override fun run() {
updateScrollbar()
if(lastSize != content.preferredSize.height.coerceAtLeast(frame.height + viewBuffer))
handleResize()
}
}, 0, 1000)
// Component listener for resizing the frame
frame.addComponentListener(object : ComponentAdapter() {
override fun componentResized(e: ComponentEvent) {
handleResize()
}
})
}
private fun handleResize() {
SwingUtilities.invokeLater{
// Ensure the ScrollablePanel resizes with the frame
bounds = Rectangle(0, 0, 242, frame.height)
// Dynamically update content bounds and scrollbar on frame resize with buffer
lastSize = content.preferredSize.height.coerceAtLeast(frame.height + viewBuffer)
content.bounds = Rectangle(0, 0, 242, lastSize)
showScrollbar = content.height > frame.height
currentOffsetY = 0
content.setLocation(0, currentOffsetY)
updateScrollbar()
revalidate()
repaint()
}
}
private fun scrollContent(deltaY: Int) {
if (!showScrollbar) {
currentOffsetY = 0
content.setLocation(0, currentOffsetY)
return
}
SwingUtilities.invokeLater {
currentOffsetY += deltaY
// Apply buffer to maxOffset
val maxOffset = (frame.height - content.height + viewBuffer).coerceAtMost(0)
currentOffsetY = currentOffsetY.coerceAtMost(0).coerceAtLeast(maxOffset)
content.setLocation(0, currentOffsetY)
val contentHeight = content.height
val viewHeight = frame.height + viewBuffer
val scrollableRatio = viewHeight.toDouble() / contentHeight
scrollbarY = ((-currentOffsetY / contentHeight.toDouble()) * viewHeight).toInt()
scrollbarHeight = (viewHeight * scrollableRatio).toInt().coerceAtLeast(20)
repaint()
}
}
private fun updateScrollbar() {
SwingUtilities.invokeLater {
showScrollbar = content.height > frame.height
val contentHeight = content.height
val viewHeight = frame.height + viewBuffer
if (showScrollbar) {
val scrollableRatio = viewHeight.toDouble() / contentHeight
scrollbarY = ((-currentOffsetY / contentHeight.toDouble()) * viewHeight).toInt()
scrollbarHeight = (viewHeight * scrollableRatio).toInt().coerceAtLeast(20)
} else {
scrollbarY = 0
scrollbarHeight = 0
}
repaint()
}
}
override fun paintComponent(g: Graphics) {
super.paintComponent(g)
}
override fun paintChildren(g: Graphics) {
super.paintChildren(g)
if (showScrollbar) {
val g2 = g as Graphics2D
val scrollbarX = 238
g2.color = SCROLL_BAR_COLOR
g2.fillRect(scrollbarX, scrollbarY, 2, scrollbarHeight)
}
}
}

View file

@ -0,0 +1,155 @@
package KondoKit.components
import KondoKit.ImageCanvas
import KondoKit.SpriteToBufferedImage
import KondoKit.plugin.Companion.WIDGET_COLOR
import KondoKit.plugin.Companion.secondaryColor
import plugin.api.API
import java.awt.*
import java.awt.datatransfer.DataFlavor
import java.awt.event.*
import javax.swing.*
open class SearchField(
private val onSearch: (String) -> Unit,
private val placeholderText: String = "Search...",
private val fieldWidth: Int = 230,
private val fieldHeight: Int = 30
) : Canvas() {
private var cursorVisible: Boolean = true
private var text: String = ""
private val bufferedImageSprite = SpriteToBufferedImage.getBufferedImageFromSprite(API.GetSprite(1423)) // MAG_SPRITE
private val imageCanvas = bufferedImageSprite.let {
ImageCanvas(it).apply {
preferredSize = Dimension(12, 12)
size = preferredSize
minimumSize = preferredSize
maximumSize = preferredSize
fillColor = WIDGET_COLOR
}
}
init {
val dimension = Dimension(fieldWidth, fieldHeight)
preferredSize = dimension
background = WIDGET_COLOR
foreground = secondaryColor
font = Font("Arial", Font.PLAIN, 14)
minimumSize = dimension
maximumSize = dimension
addKeyListener(object : KeyAdapter() {
override fun keyTyped(e: KeyEvent) {
// Prevent null character from being typed on Ctrl+A & Ctrl+V
if (e.isControlDown && (e.keyChar == '\u0001' || e.keyChar == '\u0016')) {
e.consume()
return
}
if (e.keyChar == '\b') {
if (text.isNotEmpty()) {
text = text.dropLast(1)
}
} else if (e.keyChar == '\n') {
onSearch(text)
} else {
text += e.keyChar
}
SwingUtilities.invokeLater {
repaint()
}
}
override fun keyPressed(e: KeyEvent) {
if (e.isControlDown) {
when (e.keyCode) {
KeyEvent.VK_A -> {
text = ""
SwingUtilities.invokeLater {
repaint()
}
}
KeyEvent.VK_V -> {
try {
val clipboard = Toolkit.getDefaultToolkit().systemClipboard
val pasteText = clipboard.getData(DataFlavor.stringFlavor) as String
text += pasteText
SwingUtilities.invokeLater {
repaint()
}
} catch (ex: Exception) {
// Ignore clipboard errors
}
}
}
}
}
})
addMouseListener(object : MouseAdapter() {
override fun mouseClicked(e: MouseEvent) {
if (e.x > width - 20 && e.y < 20) {
text = ""
onSearch(text)
SwingUtilities.invokeLater {
repaint()
}
}
}
})
Timer(500) { _ ->
cursorVisible = !cursorVisible
SwingUtilities.invokeLater {
repaint()
}
}.start()
}
override fun paint(g: Graphics) {
super.paint(g)
g.color = foreground
g.font = font
val fm = g.fontMetrics
val cursorX = fm.stringWidth(text) + 30
// Draw magnifying glass icon
imageCanvas.let { canvas ->
val imgG = g.create(5, 5, canvas.width, canvas.height)
canvas.paint(imgG)
imgG.dispose()
}
// Use a local copy of the text to avoid threading issues
val currentText = text
// Draw placeholder text if field is empty, otherwise draw actual text
if (currentText.isEmpty()) {
g.color = Color.GRAY // Use a lighter color for placeholder text
g.drawString(placeholderText, 30, 20)
} else {
g.color = foreground // Use normal color for actual text
g.drawString(currentText, 30, 20)
}
if (cursorVisible && hasFocus()) {
g.color = foreground
g.drawLine(cursorX, 5, cursorX, 25)
}
// Only draw the "x" button if there's text
if (currentText.isNotEmpty()) {
g.color = Color.RED
g.drawString("x", width - 20, 20)
}
}
fun setText(newText: String) {
text = newText
repaint()
}
fun getText(): String = text
}

View file

@ -0,0 +1,231 @@
package KondoKit.components
import KondoKit.Helpers
import KondoKit.Helpers.FieldNotifier
import KondoKit.plugin.Companion.WIDGET_COLOR
import KondoKit.plugin.Companion.primaryColor
import KondoKit.plugin.Companion.secondaryColor
import plugin.Plugin
import java.awt.*
import java.awt.event.MouseAdapter
import java.awt.event.MouseEvent
import java.util.*
import java.util.Timer
import javax.swing.*
class SettingsPanel(private val plugin: Plugin) : JPanel() {
init {
layout = BoxLayout(this, BoxLayout.Y_AXIS)
background = WIDGET_COLOR
border = BorderFactory.createEmptyBorder(10, 10, 10, 10)
}
fun addSettingsFromPlugin() {
val fieldNotifier = FieldNotifier(plugin)
val exposedFields = plugin.javaClass.declaredFields.filter { field ->
field.annotations.any { annotation ->
annotation.annotationClass.simpleName == "Exposed"
}
}
if (exposedFields.isNotEmpty()) {
for (field in exposedFields) {
field.isAccessible = true
// Get the "Exposed" annotation specifically and retrieve its description, if available
val exposedAnnotation = field.annotations.firstOrNull { annotation ->
annotation.annotationClass.simpleName == "Exposed"
}
val description = exposedAnnotation?.let { annotation ->
try {
val descriptionField = annotation.annotationClass.java.getMethod("description")
descriptionField.invoke(annotation) as String
} catch (e: NoSuchMethodException) {
"" // No description method, return empty string
}
} ?: ""
val fieldPanel = JPanel().apply {
layout = GridBagLayout()
background = WIDGET_COLOR
foreground = secondaryColor
border = BorderFactory.createEmptyBorder(5, 0, 5, 0)
maximumSize = Dimension(Int.MAX_VALUE, 50)
}
val gbc = GridBagConstraints().apply {
insets = Insets(0, 5, 0, 5)
}
val label = JLabel(field.name.capitalize()).apply {
foreground = secondaryColor
font = Font("RuneScape Small", Font.TRUETYPE_FONT, 16)
}
gbc.gridx = 0
gbc.gridy = 0
gbc.weightx = 0.0
gbc.anchor = GridBagConstraints.WEST
fieldPanel.add(label, gbc)
// Create appropriate input component based on field type
val inputComponent: JComponent = when {
field.type == Boolean::class.javaPrimitiveType || field.type == java.lang.Boolean::class.java ->
JCheckBox().apply {
isSelected = field.get(plugin) as Boolean
}
field.type.isEnum ->
JComboBox((field.type.enumConstants as Array<Enum<*>>)).apply {
selectedItem = field.get(plugin)
}
field.type == Int::class.javaPrimitiveType || field.type == Integer::class.java ->
JSpinner(SpinnerNumberModel(field.get(plugin) as Int, Int.MIN_VALUE, Int.MAX_VALUE, 1))
field.type == Float::class.javaPrimitiveType ||
field.type == Double::class.javaPrimitiveType ||
field.type == java.lang.Float::class.java ||
field.type == java.lang.Double::class.java ->
JSpinner(SpinnerNumberModel((field.get(plugin) as Number).toDouble(), -Double.MAX_VALUE, Double.MAX_VALUE, 0.1))
else ->
JTextField(field.get(plugin)?.toString() ?: "")
}
// Add mouse listener to the label only if a description is available
if (description.isNotBlank()) {
label.cursor = Cursor.getPredefinedCursor(Cursor.HAND_CURSOR)
label.addMouseListener(object : MouseAdapter() {
override fun mouseEntered(e: MouseEvent) {
showCustomToolTip(description, label)
}
override fun mouseExited(e: MouseEvent) {
customToolTipWindow?.isVisible = false
}
})
}
gbc.gridx = 1
gbc.gridy = 0
gbc.weightx = 1.0
gbc.fill = GridBagConstraints.HORIZONTAL
fieldPanel.add(inputComponent, gbc)
val applyButton = JButton("\u2714").apply {
maximumSize = Dimension(Int.MAX_VALUE, 8)
}
gbc.gridx = 2
gbc.gridy = 0
gbc.weightx = 0.0
gbc.fill = GridBagConstraints.NONE
applyButton.addActionListener {
try {
val newValue = when (inputComponent) {
is JCheckBox -> inputComponent.isSelected
is JComboBox<*> -> inputComponent.selectedItem
is JSpinner -> inputComponent.value
is JTextField -> Helpers.convertValue(field.type, field.genericType, inputComponent.text)
else -> throw IllegalArgumentException("Unsupported input component type")
}
fieldNotifier.setFieldValue(field, newValue)
Helpers.showToast(
this@SettingsPanel,
"${field.name} updated successfully!"
)
} catch (e: Exception) {
Helpers.showToast(
this@SettingsPanel,
"Failed to update ${field.name}: ${e.message}",
JOptionPane.ERROR_MESSAGE
)
}
}
fieldPanel.add(applyButton, gbc)
add(fieldPanel)
// Track field changes in real-time and update UI
var previousValue = field.get(plugin)?.toString()
val timer = Timer()
timer.schedule(object : TimerTask() {
override fun run() {
val currentValue = field.get(plugin)?.toString()
if (currentValue != previousValue) {
previousValue = currentValue
SwingUtilities.invokeLater {
// Update the inputComponent based on the new value
when (inputComponent) {
is JCheckBox -> inputComponent.isSelected = field.get(plugin) as Boolean
is JComboBox<*> -> inputComponent.selectedItem = field.get(plugin)
is JSpinner -> inputComponent.value = field.get(plugin)
is JTextField -> inputComponent.text = field.get(plugin)?.toString() ?: ""
}
}
}
}
}, 0, 1000) // Poll every 1000 milliseconds (1 second)
}
if (exposedFields.isNotEmpty()) {
add(Box.createVerticalStrut(5))
}
}
}
companion object {
var customToolTipWindow: JWindow? = null
fun showCustomToolTip(text: String, component: JComponent) {
val _font = Font("RuneScape Small", Font.PLAIN, 16)
val maxWidth = 150
val lineHeight = 16
// Create a dummy JLabel to get FontMetrics for the font used in the tooltip
val dummyLabel = JLabel()
dummyLabel.font = _font
val fontMetrics = dummyLabel.getFontMetrics(_font)
// Calculate the approximate width of the text
val textWidth = fontMetrics.stringWidth(text)
// Calculate the number of lines required based on the text width and max tooltip width
val numberOfLines = Math.ceil(textWidth.toDouble() / maxWidth).toInt()
// Calculate the required height of the tooltip
val requiredHeight = numberOfLines * lineHeight + 6 // Adding some padding
if (customToolTipWindow == null) {
customToolTipWindow = JWindow().apply {
val bgColor = Helpers.colorToHex(KondoKit.plugin.Companion.TOOLTIP_BACKGROUND)
val textColor = Helpers.colorToHex(KondoKit.plugin.Companion.secondaryColor)
contentPane = JLabel("<html><div style='color: $textColor; background-color: $bgColor; padding: 3px; word-break: break-all;'>$text</div></html>").apply {
border = BorderFactory.createLineBorder(Color.BLACK)
isOpaque = true
background = KondoKit.plugin.Companion.TOOLTIP_BACKGROUND
foreground = Color.WHITE
font = _font
maximumSize = Dimension(maxWidth, Int.MAX_VALUE)
preferredSize = Dimension(maxWidth, requiredHeight)
}
pack()
}
} else {
// Update the tooltip text
val label = customToolTipWindow!!.contentPane as JLabel
val bgColor = Helpers.colorToHex(KondoKit.plugin.Companion.TOOLTIP_BACKGROUND)
val textColor = Helpers.colorToHex(KondoKit.plugin.Companion.secondaryColor)
label.text = "<html><div style='color: $textColor; background-color: $bgColor; padding: 3px; word-break: break-all;'>$text</div></html>"
label.preferredSize = Dimension(maxWidth, requiredHeight)
customToolTipWindow!!.pack()
}
// Position the tooltip near the component
val locationOnScreen = component.locationOnScreen
customToolTipWindow!!.setLocation(locationOnScreen.x, locationOnScreen.y + 15)
customToolTipWindow!!.isVisible = true
}
}
}

View file

@ -0,0 +1,127 @@
package KondoKit.components
import java.awt.*
import java.awt.event.MouseAdapter
import java.awt.event.MouseEvent
import java.awt.image.BufferedImage
import javax.swing.JPanel
class ToggleSwitch : JPanel() {
private var activated = false
private var switchColor = Color(200, 200, 200)
private var buttonColor = Color(255, 255, 255)
private var borderColor = Color(50, 50, 50)
private var activeSwitch = Color(0, 125, 255)
private var puffer: BufferedImage? = null
private var g: Graphics2D? = null
var onToggleListener: ((Boolean) -> Unit)? = null
init {
isVisible = true
addMouseListener(object : MouseAdapter() {
override fun mouseReleased(arg0: MouseEvent) {
activated = !activated
onToggleListener?.invoke(activated)
repaint()
}
})
cursor = Cursor(Cursor.HAND_CURSOR)
preferredSize = Dimension(32, 20)
maximumSize = Dimension(32, 20)
minimumSize = Dimension(32, 20)
isOpaque = false // Make the panel background transparent
}
override fun paint(gr: Graphics) {
if (g == null || puffer?.width != width || puffer?.height != height) {
puffer = BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB)
g = puffer?.createGraphics()
val rh = RenderingHints(
RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON
)
g?.setRenderingHints(rh)
}
// Clear the buffer with transparent background
g?.color = Color(0, 0, 0, 0)
g?.fillRect(0, 0, width, height)
// Draw the track with circular ends (rounded rectangle)
val trackHeight = height - 2
val trackWidth = width - 2
val trackArc = trackHeight // Makes it fully rounded at the ends
g?.color = if (activated) activeSwitch else switchColor
g?.fillRoundRect(1, 1, trackWidth, trackHeight, trackArc, trackArc)
g?.color = borderColor
g?.drawRoundRect(1, 1, trackWidth, trackHeight, trackArc, trackArc)
// Draw the thumb (circular button)
val thumbDiameter = trackHeight - 4
val thumbX = if (activated) {
width - thumbDiameter - 3 // Right side when activated
} else {
3 // Left side when not activated
}
val thumbY = (height - thumbDiameter) / 2
g?.color = buttonColor
g?.fillOval(thumbX, thumbY, thumbDiameter, thumbDiameter)
g?.color = borderColor
g?.drawOval(thumbX, thumbY, thumbDiameter, thumbDiameter)
gr.drawImage(puffer, 0, 0, null)
}
fun isActivated(): Boolean {
return activated
}
fun setActivated(activated: Boolean) {
this.activated = activated
repaint()
}
fun getSwitchColor(): Color {
return switchColor
}
/**
* Unactivated Background Color of switch
*/
fun setSwitchColor(switchColor: Color) {
this.switchColor = switchColor
}
fun getButtonColor(): Color {
return buttonColor
}
/**
* Switch-Button color
*/
fun setButtonColor(buttonColor: Color) {
this.buttonColor = buttonColor
}
fun getBorderColor(): Color {
return borderColor
}
/**
* Border-color of whole switch and switch-button
*/
fun setBorderColor(borderColor: Color) {
this.borderColor = borderColor
}
fun getActiveSwitch(): Color {
return activeSwitch
}
fun setActiveSwitch(activeSwitch: Color) {
this.activeSwitch = activeSwitch
}
}

View file

@ -0,0 +1,31 @@
package KondoKit.components
import KondoKit.plugin.Companion.TITLE_BAR_COLOR
import KondoKit.plugin.Companion.secondaryColor
import java.awt.*
import javax.swing.*
class ViewHeader(
title: String,
private val headerHeight: Int = 40
) : JPanel() {
private val titleLabel = JLabel(title)
init {
background = TITLE_BAR_COLOR
preferredSize = Dimension(Int.MAX_VALUE, headerHeight)
border = BorderFactory.createEmptyBorder(5, 10, 5, 10)
layout = BorderLayout()
titleLabel.foreground = secondaryColor
titleLabel.font = Font("RuneScape Small", Font.PLAIN, 16)
titleLabel.horizontalAlignment = SwingConstants.CENTER
add(titleLabel, BorderLayout.CENTER)
}
fun setTitle(title: String) {
titleLabel.text = title
}
}

View file

@ -0,0 +1,35 @@
package KondoKit.components
import KondoKit.plugin.Companion.WIDGET_COLOR
import java.awt.BorderLayout
import java.awt.Dimension
import javax.swing.BorderFactory
import javax.swing.JPanel
class WidgetPanel(
private val widgetWidth: Int = 220,
private val widgetHeight: Int = 50,
private val addDefaultPadding: Boolean = true
) : JPanel() {
init {
layout = BorderLayout(5, 5)
background = WIDGET_COLOR
val size = Dimension(widgetWidth, widgetHeight)
preferredSize = size
maximumSize = size
minimumSize = size
if (addDefaultPadding) {
border = BorderFactory.createEmptyBorder(5, 5, 5, 5)
}
}
fun setFixedSize(width: Int, height: Int) {
val size = Dimension(width, height)
preferredSize = size
maximumSize = size
minimumSize = size
}
}