consistent sizing

This commit is contained in:
downthecrop 2025-10-23 13:33:44 -07:00
parent 16546ef862
commit 14b107861e
11 changed files with 343 additions and 336 deletions

View file

@ -14,21 +14,24 @@ object ViewConstants {
val FONT_RUNESCAPE_SMALL_16 = Font("RuneScape Small", Font.TRUETYPE_FONT, 16)
val FONT_RUNESCAPE_SMALL_14 = Font("RuneScape Small", Font.PLAIN, 14)
val FONT_RUNESCAPE_SMALL_PLAIN_16 = Font("RuneScape Small", Font.PLAIN, 16)
val FONT_RUNESCAPE_SMALL_BOLD_16 = Font("RuneScape Small", Font.BOLD, 16)
val FONT_ARIAL_PLAIN_14 = Font("Arial", Font.PLAIN, 14)
val FONT_ARIAL_BOLD_12 = Font("Arial", Font.BOLD, 12)
// Common Dimensions
val DIMENSION_SMALL_ICON = Dimension(12, 12)
val DIMENSION_LARGE_ICON = Dimension(30, 30)
val DEFAULT_WIDGET_SIZE = Dimension(220, 50)
val DEFAULT_WIDGET_SIZE = Dimension(234, 50)
val PLUGIN_LIST_ITEM_SIZE = Dimension(DEFAULT_WIDGET_SIZE.width, 60)
val TOGGLE_PLACEHOLDER_SIZE = Dimension(60, 24)
val TOTAL_XP_WIDGET_SIZE = Dimension(220, 30)
val IMAGE_SIZE = Dimension(25, 23)
val SEARCH_FIELD_SIZE = Dimension(230, 30)
val DEFAULT_PANEL_SIZE = Dimension(230, 500)
val FILTER_PANEL_SIZE = Dimension(230, 30)
val SKILLS_PANEL_SIZE = Dimension(230, 290)
val TOTAL_COMBAT_PANEL_SIZE = Dimension(230, 30)
val SKILL_PANEL_SIZE = Dimension(76, 35)
val SEARCH_FIELD_SIZE = Dimension(DEFAULT_WIDGET_SIZE.width, 30)
val DEFAULT_PANEL_SIZE = Dimension(DEFAULT_WIDGET_SIZE.width, 500)
val FILTER_PANEL_SIZE = Dimension(DEFAULT_WIDGET_SIZE.width, 30)
val SKILLS_PANEL_SIZE = Dimension(DEFAULT_WIDGET_SIZE.width, 290)
val TOTAL_COMBAT_PANEL_SIZE = Dimension(DEFAULT_WIDGET_SIZE.width, 30)
val SKILL_PANEL_SIZE = Dimension(DEFAULT_WIDGET_SIZE.width / 3, 35)
val IMAGE_CANVAS_SIZE = Dimension(20, 20)
val SKILL_SPRITE_SIZE = Dimension(14, 14)
val NUMBER_LABEL_SIZE = Dimension(20, 20)
@ -42,19 +45,6 @@ object ViewConstants {
val COLOR_RED = Color.RED
val COLOR_GRAY = Color.GRAY
// UI Colors (already defined in plugin.kt companion object, but referenced here for consistency)
// These should match the ones in plugin.kt Companion object
val COLOR_WIDGET = Color(30, 30, 30)
val COLOR_TITLE_BAR = Color(21, 21, 21)
val COLOR_VIEW_BACKGROUND = Color(40, 40, 40)
val COLOR_PRIMARY = Color(165, 165, 165)
val COLOR_SECONDARY = Color(255, 255, 255)
val COLOR_POPUP_BACKGROUND = Color(45, 45, 45)
val COLOR_POPUP_FOREGROUND = Color(220, 220, 220)
val COLOR_TOOLTIP_BACKGROUND = Color(50, 50, 50)
val COLOR_SCROLL_BAR = Color(64, 64, 64)
val COLOR_PROGRESS_BAR_FILL = Color(61, 56, 49)
// Skill display order
val SKILL_DISPLAY_ORDER = arrayOf(0, 3, 14, 2, 16, 13, 1, 15, 10, 4, 17, 7, 5, 12, 11, 6, 9, 8, 20, 18, 19, 22, 21, 23)

View file

@ -16,8 +16,8 @@ class SearchField(
private val parentPanel: JPanel,
private val onSearch: (String) -> Unit,
private val placeholderText: String = "Search...",
private val fieldWidth: Int = 230,
private val fieldHeight: Int = 30,
private val fieldWidth: Int = ViewConstants.SEARCH_FIELD_SIZE.width,
private val fieldHeight: Int = ViewConstants.SEARCH_FIELD_SIZE.height,
private val viewName: String? = ""
) : Canvas() {
@ -40,10 +40,25 @@ class SearchField(
preferredSize = dimension
background = WIDGET_COLOR
foreground = secondaryColor
font = Font("Arial", Font.PLAIN, 14)
font = ViewConstants.FONT_ARIAL_PLAIN_14
minimumSize = dimension
maximumSize = dimension
isFocusable = true
focusTraversalKeysEnabled = false
addFocusListener(object : FocusAdapter() {
override fun focusGained(e: FocusEvent) {
cursorVisible = true
repaintAsync()
}
override fun focusLost(e: FocusEvent) {
cursorVisible = false
repaintAsync()
}
})
addKeyListener(object : KeyAdapter() {
override fun keyTyped(e: KeyEvent) {
// Prevent null character from being typed on Ctrl+A & Ctrl+V
@ -56,33 +71,33 @@ class SearchField(
text = text.dropLast(1)
}
} else if (e.keyChar == '\n') {
triggerSearch()
// Handled in keyPressed to avoid duplicate searches
} else {
text += e.keyChar
}
SwingUtilities.invokeLater {
repaint()
}
repaintAsync()
}
override fun keyPressed(e: KeyEvent) {
if (e.keyCode == KeyEvent.VK_ENTER) {
triggerSearch()
e.consume()
return
}
if (e.isControlDown) {
when (e.keyCode) {
KeyEvent.VK_A -> {
// They probably want to clear the search box
text = ""
SwingUtilities.invokeLater {
repaint()
}
repaintAsync()
}
KeyEvent.VK_V -> {
try {
val clipboard = Toolkit.getDefaultToolkit().systemClipboard
val pasteText = clipboard.getData(DataFlavor.stringFlavor) as String
text += pasteText
SwingUtilities.invokeLater {
repaint()
}
repaintAsync()
} catch (_: Exception) { }
}
}
@ -93,13 +108,16 @@ class SearchField(
addMouseListener(object : MouseAdapter() {
// Clicked the search field 'x' button
override fun mouseClicked(e: MouseEvent) {
requestFocusInWindow()
if (e.x > width - 20 && e.y < 20) {
text = ""
triggerSearch()
SwingUtilities.invokeLater {
repaint()
repaintAsync()
}
}
override fun mousePressed(e: MouseEvent) {
requestFocusInWindow()
}
})
@ -107,9 +125,7 @@ class SearchField(
cursorVisible = !cursorVisible
// Only repaint if the view is active or if viewName is not specified
if (viewName == null || focusedView == viewName) {
SwingUtilities.invokeLater {
repaint()
}
repaintAsync()
}
}.start()
}
@ -155,7 +171,7 @@ class SearchField(
fun setText(newText: String) {
text = newText
repaint()
repaintAsync()
}
fun getText(): String = text
@ -164,10 +180,14 @@ class SearchField(
val query = text.trim()
if (query.isNotEmpty()) {
text = query
repaint()
repaintAsync()
onSearch(query)
} else {
onSearch(query) // Call with empty string for clearing filters
}
}
private fun repaintAsync() {
SwingUtilities.invokeLater { repaint() }
}
}

View file

@ -2,6 +2,7 @@ package KondoKit.components
import KondoKit.Helpers
import KondoKit.Helpers.FieldNotifier
import KondoKit.ViewConstants
import KondoKit.plugin.Companion.WIDGET_COLOR
import KondoKit.plugin.Companion.primaryColor
import KondoKit.plugin.Companion.secondaryColor
@ -62,7 +63,7 @@ class SettingsPanel(private val plugin: Plugin) : JPanel() {
val label = JLabel(field.name.capitalize()).apply {
foreground = secondaryColor
font = Font("RuneScape Small", Font.TRUETYPE_FONT, 16)
font = ViewConstants.FONT_RUNESCAPE_SMALL_16
}
gbc.gridx = 0
gbc.gridy = 0
@ -229,7 +230,7 @@ class SettingsPanel(private val plugin: Plugin) : JPanel() {
// Create title label
val titleLabel = JLabel("${field.name} (Key-Value Pairs)").apply {
foreground = secondaryColor
font = Font("RuneScape Small", Font.TRUETYPE_FONT, 16)
font = ViewConstants.FONT_RUNESCAPE_SMALL_16
alignmentX = Component.LEFT_ALIGNMENT
// Add mouse listener to the label only if a description is available
@ -451,14 +452,14 @@ class SettingsPanel(private val plugin: Plugin) : JPanel() {
var customToolTipWindow: JWindow? = null
fun showCustomToolTip(text: String, component: JComponent) {
val _font = Font("RuneScape Small", Font.PLAIN, 16)
val tooltipFont = ViewConstants.FONT_RUNESCAPE_SMALL_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)
dummyLabel.font = tooltipFont
val fontMetrics = dummyLabel.getFontMetrics(tooltipFont)
val lineHeight = fontMetrics.height
// Calculate the approximate width of the text
val textWidth = fontMetrics.stringWidth(text)
@ -478,7 +479,7 @@ class SettingsPanel(private val plugin: Plugin) : JPanel() {
isOpaque = true
background = KondoKit.plugin.Companion.TOOLTIP_BACKGROUND
foreground = Color.WHITE
font = _font
font = tooltipFont
maximumSize = Dimension(maxWidth, Int.MAX_VALUE)
preferredSize = Dimension(maxWidth, requiredHeight)
}

View file

@ -1,5 +1,6 @@
package KondoKit.components
import KondoKit.ViewConstants
import KondoKit.plugin.Companion.TITLE_BAR_COLOR
import KondoKit.plugin.Companion.secondaryColor
import java.awt.*
@ -19,7 +20,7 @@ class ViewHeader(
layout = BorderLayout()
titleLabel.foreground = secondaryColor
titleLabel.font = Font("RuneScape Small", Font.PLAIN, 16)
titleLabel.font = ViewConstants.FONT_RUNESCAPE_SMALL_PLAIN_16
titleLabel.horizontalAlignment = SwingConstants.CENTER
add(titleLabel, BorderLayout.CENTER)

View file

@ -1,5 +1,6 @@
package KondoKit.components
import KondoKit.ViewConstants
import KondoKit.plugin.Companion.WIDGET_COLOR
import java.awt.BorderLayout
import java.awt.Dimension
@ -7,13 +8,16 @@ import javax.swing.BorderFactory
import javax.swing.JPanel
class WidgetPanel(
private val widgetWidth: Int = 220,
private val widgetHeight: Int = 50,
private val widgetWidth: Int = ViewConstants.DEFAULT_WIDGET_SIZE.width,
private val widgetHeight: Int = ViewConstants.DEFAULT_WIDGET_SIZE.height,
private val addDefaultPadding: Boolean = true
) : JPanel() {
init {
layout = BorderLayout(5, 5)
layout = BorderLayout(
if (addDefaultPadding) 5 else 0,
if (addDefaultPadding) 5 else 0
)
background = WIDGET_COLOR
val size = Dimension(widgetWidth, widgetHeight)
@ -23,6 +27,8 @@ class WidgetPanel(
if (addDefaultPadding) {
border = BorderFactory.createEmptyBorder(5, 5, 5, 5)
} else {
border = BorderFactory.createEmptyBorder(0, 0, 0, 0)
}
}

View file

@ -1,5 +1,6 @@
package KondoKit.views
import KondoKit.ViewConstants
import KondoKit.plugin.Companion.VIEW_BACKGROUND_COLOR
import java.awt.Dimension
import javax.swing.BorderFactory
@ -9,7 +10,7 @@ import javax.swing.JPanel
open class BaseView(
private val viewName: String,
private val preferredWidth: Int = 242,
private val preferredWidth: Int = ViewConstants.DEFAULT_PANEL_SIZE.width,
private val addDefaultSpacing: Boolean = true
) : JPanel() {

View file

@ -5,8 +5,10 @@ import KondoKit.Helpers.showToast
import KondoKit.ImageCanvas
import KondoKit.ViewConstants
import KondoKit.SpriteToBufferedImage.getBufferedImageFromSprite
import KondoKit.components.SearchField
import KondoKit.components.LabelComponent
import KondoKit.components.WidgetPanel
import KondoKit.components.SearchField
import KondoKit.views.ViewLayoutHelpers.createSearchFieldSection
import KondoKit.plugin.Companion.WIDGET_COLOR
import KondoKit.plugin.Companion.VIEW_BACKGROUND_COLOR
import KondoKit.plugin.Companion.POPUP_FOREGROUND
@ -51,65 +53,50 @@ object HiscoresView : View {
setViewSize(ViewConstants.DEFAULT_PANEL_SIZE.height)
}
customSearchField = SearchField(
hiscorePanel,
{ username ->
val searchSection = createSearchFieldSection(
parent = hiscorePanel,
placeholderText = "Search player...",
viewName = VIEW_NAME
) { username ->
searchPlayerForHiscores(username, hiscorePanel)
},
"Search player...",
242,
30,
VIEW_NAME
)
val searchField = customSearchField!!
val searchFieldWrapper = JPanel().apply {
layout = BoxLayout(this, BoxLayout.X_AXIS)
background = VIEW_BACKGROUND_COLOR
preferredSize = ViewConstants.SEARCH_FIELD_SIZE
maximumSize = preferredSize
minimumSize = preferredSize
alignmentX = Component.CENTER_ALIGNMENT
add(searchField)
}
val searchPanel = JPanel().apply {
layout = BoxLayout(this, BoxLayout.Y_AXIS)
background = VIEW_BACKGROUND_COLOR
add(searchFieldWrapper)
}
customSearchField = searchSection.searchField
hiscorePanel.add(Box.createVerticalStrut(5))
hiscorePanel.add(searchPanel)
hiscorePanel.add(searchSection.wrapper)
hiscorePanel.add(Box.createVerticalStrut(5))
val playerNamePanel = JPanel().apply {
val playerNamePanel = WidgetPanel(
widgetWidth = ViewConstants.FILTER_PANEL_SIZE.width,
widgetHeight = ViewConstants.FILTER_PANEL_SIZE.height,
addDefaultPadding = false
).apply {
layout = GridBagLayout() // This will center the JLabel both vertically and horizontally
background = TOOLTIP_BACKGROUND.darker()
preferredSize = ViewConstants.FILTER_PANEL_SIZE
maximumSize = preferredSize
minimumSize = preferredSize
name = "playerNameLabel"
}
hiscorePanel.add(playerNamePanel)
hiscorePanel.add(Box.createVerticalStrut(5))
val skillsPanel = JPanel(FlowLayout(FlowLayout.CENTER, 0, 0)).apply {
val skillsPanel = WidgetPanel(
widgetWidth = ViewConstants.SKILLS_PANEL_SIZE.width,
widgetHeight = ViewConstants.SKILLS_PANEL_SIZE.height,
addDefaultPadding = false
).apply {
layout = FlowLayout(FlowLayout.CENTER, 0, 0)
background = VIEW_BACKGROUND_COLOR
preferredSize = ViewConstants.SKILLS_PANEL_SIZE
maximumSize = preferredSize
minimumSize = preferredSize
}
for (i in ViewConstants.SKILL_DISPLAY_ORDER) {
val skillPanel = JPanel().apply {
val skillPanel = WidgetPanel(
widgetWidth = ViewConstants.SKILL_PANEL_SIZE.width,
widgetHeight = ViewConstants.SKILL_PANEL_SIZE.height,
addDefaultPadding = false
).apply {
layout = BorderLayout()
background = WIDGET_COLOR
preferredSize = ViewConstants.SKILL_PANEL_SIZE
maximumSize = preferredSize
minimumSize = preferredSize
border = MatteBorder(5, 0, 0, 0, WIDGET_COLOR)
}
@ -143,11 +130,13 @@ object HiscoresView : View {
hiscorePanel.add(skillsPanel)
val totalCombatPanel = JPanel(FlowLayout(FlowLayout.CENTER, 0, 0)).apply {
val totalCombatPanel = WidgetPanel(
widgetWidth = ViewConstants.TOTAL_COMBAT_PANEL_SIZE.width,
widgetHeight = ViewConstants.TOTAL_COMBAT_PANEL_SIZE.height,
addDefaultPadding = false
).apply {
layout = FlowLayout(FlowLayout.LEFT, 0, 0)
background = WIDGET_COLOR
preferredSize = ViewConstants.TOTAL_COMBAT_PANEL_SIZE
maximumSize = preferredSize
minimumSize = preferredSize
}
val bufferedImageSprite = getBufferedImageFromSprite(API.GetSprite(ViewConstants.LVL_BAR_SPRITE))
@ -166,7 +155,12 @@ object HiscoresView : View {
iconTextGap = 10
}
val totalLevelPanel = JPanel(FlowLayout(FlowLayout.LEFT)).apply {
val totalLevelPanel = WidgetPanel(
widgetWidth = ViewConstants.TOTAL_COMBAT_PANEL_SIZE.width / 2,
widgetHeight = ViewConstants.TOTAL_COMBAT_PANEL_SIZE.height,
addDefaultPadding = false
).apply {
layout = FlowLayout(FlowLayout.LEFT, 5, 0)
background = WIDGET_COLOR
add(totalLevelIcon)
add(totalLevelLabel)
@ -188,7 +182,12 @@ object HiscoresView : View {
iconTextGap = 10
}
val combatLevelPanel = JPanel(FlowLayout(FlowLayout.LEFT)).apply {
val combatLevelPanel = WidgetPanel(
widgetWidth = ViewConstants.TOTAL_COMBAT_PANEL_SIZE.width / 2,
widgetHeight = ViewConstants.TOTAL_COMBAT_PANEL_SIZE.height,
addDefaultPadding = false
).apply {
layout = FlowLayout(FlowLayout.LEFT, 5, 0)
background = WIDGET_COLOR
add(combatLevelIcon)
add(combatLevelLabel)
@ -401,4 +400,3 @@ object HiscoresView : View {
return Math.round((base + maxCombatType + summoningFactor) * 1000.0) / 1000.0
}
}

View file

@ -5,6 +5,7 @@ import KondoKit.Helpers.addMouseListenerToAll
import KondoKit.Helpers.formatHtmlLabelText
import KondoKit.ImageCanvas
import KondoKit.SpriteToBufferedImage.getBufferedImageFromSprite
import KondoKit.ViewConstants
import KondoKit.views.XPTrackerView.wrappedWidget
import KondoKit.components.PopupMenuComponent
import KondoKit.components.ProgressBar
@ -228,7 +229,7 @@ object LootTrackerView : View, OnPostClientTickCallback, OnKillingBlowNPCCallbac
private fun createLabel(text: String): JLabel {
return JLabel(text).apply {
font = Font("RuneScape Small", Font.TRUETYPE_FONT, 16)
font = ViewConstants.FONT_RUNESCAPE_SMALL_16
horizontalAlignment = JLabel.LEFT
}
}
@ -320,7 +321,7 @@ object LootTrackerView : View, OnPostClientTickCallback, OnKillingBlowNPCCallbac
"GE: ${formatValue(totalGePrice)} ${geText}<br>" +
"HA: ${formatValue(totalHaPrice)} ${haText}</div></html>"
val _font = Font("RuneScape Small", Font.TRUETYPE_FONT, 16)
val tooltipFont = ViewConstants.FONT_RUNESCAPE_SMALL_16
if (customToolTipWindow == null) {
customToolTipWindow = JWindow().apply {
contentPane = JLabel(text).apply {
@ -328,7 +329,7 @@ object LootTrackerView : View, OnPostClientTickCallback, OnKillingBlowNPCCallbac
isOpaque = true
background = TOOLTIP_BACKGROUND
foreground = Color.WHITE
font = _font
font = tooltipFont
}
pack()
}
@ -463,15 +464,15 @@ object LootTrackerView : View, OnPostClientTickCallback, OnKillingBlowNPCCallbac
val childFramePanel = JPanel().apply {
layout = BoxLayout(this, BoxLayout.Y_AXIS)
background = WIDGET_COLOR
minimumSize = Dimension(230, 0)
maximumSize = Dimension(230, 700)
minimumSize = Dimension(ViewConstants.DEFAULT_WIDGET_SIZE.width, 0)
maximumSize = Dimension(ViewConstants.DEFAULT_WIDGET_SIZE.width, 700)
name = "HELLO_WORLD"
}
val labelPanel = JPanel(BorderLayout()).apply {
background = TITLE_BAR_COLOR
border = BorderFactory.createEmptyBorder(5, 5, 5, 5)
maximumSize = Dimension(230, 24)
maximumSize = Dimension(ViewConstants.DEFAULT_WIDGET_SIZE.width, 24)
minimumSize = maximumSize
preferredSize = maximumSize
}
@ -479,14 +480,14 @@ object LootTrackerView : View, OnPostClientTickCallback, OnKillingBlowNPCCallbac
val killCount = npcKillCounts.getOrPut(npcName) { 0 }
val countLabel = JLabel(formatHtmlLabelText(npcName, secondaryColor, " x $killCount", primaryColor)).apply {
foreground = secondaryColor
font = Font("RuneScape Small", Font.TRUETYPE_FONT, 16)
font = ViewConstants.FONT_RUNESCAPE_SMALL_16
horizontalAlignment = JLabel.LEFT
name = "killCountLabel_$npcName"
}
val valueLabel = JLabel("0 gp").apply {
foreground = secondaryColor
font = Font("RuneScape Small", Font.TRUETYPE_FONT, 16)
font = ViewConstants.FONT_RUNESCAPE_SMALL_16
horizontalAlignment = JLabel.RIGHT
name = "valueLabel_$npcName"
}
@ -531,7 +532,7 @@ object LootTrackerView : View, OnPostClientTickCallback, OnKillingBlowNPCCallbac
private fun removeLootFrameMenu(toRemove: JPanel, npcName: String): JPopupMenu {
// Create a popup menu
val popupMenu = PopupMenuComponent()
val rFont = Font("RuneScape Small", Font.TRUETYPE_FONT, 16)
val rFont = ViewConstants.FONT_RUNESCAPE_SMALL_16
popupMenu.background = POPUP_BACKGROUND
@ -571,7 +572,7 @@ object LootTrackerView : View, OnPostClientTickCallback, OnKillingBlowNPCCallbac
// Create menu items with custom font and colors
val menuItem1 = JMenuItem("Reset Loot Tracker").apply {
font = Font("RuneScape Small", Font.TRUETYPE_FONT, 16)
font = ViewConstants.FONT_RUNESCAPE_SMALL_16
background = POPUP_BACKGROUND
foreground = POPUP_FOREGROUND
}

View file

@ -2,7 +2,8 @@ package KondoKit.views
import KondoKit.Helpers
import KondoKit.components.*
import KondoKit.components.SearchField
import KondoKit.ViewConstants
import KondoKit.views.ViewLayoutHelpers.createSearchFieldSection
import KondoKit.pluginmanager.GitLabPlugin
import KondoKit.pluginmanager.GitLabPluginFetcher
import KondoKit.pluginmanager.PluginDownloadManager
@ -22,11 +23,8 @@ import rt4.GlobalJsonConfig
import java.awt.*
import java.awt.event.MouseAdapter
import java.awt.event.MouseEvent
import java.awt.image.BufferedImage
import java.io.File
import java.util.*
import java.util.concurrent.atomic.AtomicInteger
import javax.imageio.ImageIO
import javax.swing.*
import kotlin.math.ceil
@ -49,8 +47,6 @@ object ReflectiveEditorView : View {
private var pluginStatuses: List<PluginInfoWithStatus> = listOf()
private var cogIcon: Icon? = null
private var searchField: SearchField? = null
// Flag for scheduled plugin reload to avoid crashes
@ -61,19 +57,6 @@ object ReflectiveEditorView : View {
return pluginName == "KondoKit"
}
private fun loadCogIcon() {
try {
val imageStream = this::class.java.getResourceAsStream("res/cog.png")
if (imageStream != null) {
val image: BufferedImage = ImageIO.read(imageStream)
val scaledImage = image.getScaledInstance(12, 12, Image.SCALE_SMOOTH)
cogIcon = ImageIcon(scaledImage)
}
} catch (e: Exception) {
e.printStackTrace()
}
}
// Card layout for switching between views within the reflective editor view
private lateinit var cardLayout: CardLayout
private lateinit var mainPanel: JPanel
@ -99,8 +82,6 @@ object ReflectiveEditorView : View {
}
fun createReflectiveEditorView() {
loadCogIcon()
cardLayout = CardLayout()
mainPanel = JPanel(cardLayout)
mainPanel.background = VIEW_BACKGROUND_COLOR
@ -122,7 +103,6 @@ object ReflectiveEditorView : View {
cardLayout.show(mainPanel, PLUGIN_LIST_VIEW)
GitLabPluginFetcher.fetchGitLabPlugins { plugins ->
System.out.println("GitLab plugins fetched: ${plugins.size}")
gitLabPlugins = plugins
// Update plugin statuses with comparison between installed and remote plugins
updatePluginStatuses()
@ -131,41 +111,26 @@ object ReflectiveEditorView : View {
}
private fun createPluginListView(): JPanel {
val panel = JPanel()
panel.layout = BoxLayout(panel, BoxLayout.Y_AXIS)
panel.background = VIEW_BACKGROUND_COLOR
val panel = BaseView(PLUGIN_LIST_VIEW, addDefaultSpacing = false).apply {
background = VIEW_BACKGROUND_COLOR
}
val searchField = SearchField(
parentPanel = panel,
onSearch = { searchText ->
val searchSection = createSearchFieldSection(
parent = panel,
placeholderText = "Search plugins...",
viewName = VIEW_NAME,
initialText = pluginSearchText
) { searchText ->
pluginSearchText = searchText
SwingUtilities.invokeLater {
addPlugins(reflectiveEditorView!!)
}
},
placeholderText = "Search plugins...",
fieldWidth = 230,
fieldHeight = 30,
viewName = "REFLECTIVE_EDITOR_VIEW"
)
this.searchField = searchField
if (pluginSearchText.isNotBlank()) {
searchField.setText(pluginSearchText)
}
val searchFieldWrapperPanel = JPanel().apply {
layout = BoxLayout(this, BoxLayout.X_AXIS)
background = VIEW_BACKGROUND_COLOR
preferredSize = Dimension(230, 30)
maximumSize = preferredSize
minimumSize = preferredSize
alignmentX = Component.CENTER_ALIGNMENT
add(searchField)
}
searchFieldWrapper = searchFieldWrapperPanel
this.searchField = searchSection.searchField
searchFieldWrapper = searchSection.wrapper
panel.add(Box.createVerticalStrut(10))
panel.add(searchFieldWrapperPanel)
panel.add(searchSection.wrapper)
panel.add(Box.createVerticalStrut(10))
pluginListContentPanel = JPanel().apply {
@ -276,59 +241,34 @@ object ReflectiveEditorView : View {
}
if (pluginSearchText.isNotBlank()) {
System.out.println("Filtering plugins for search term: '$pluginSearchText'")
System.out.println("Total plugin statuses: ${pluginStatuses.size}")
val matchingPluginStatuses = pluginStatuses.filter { pluginStatus ->
// Only show plugins that are not currently loaded and not KondoKit
val shouldShow = !pluginStatus.isInstalled &&
!pluginStatus.isInstalled &&
!isKondoKit(pluginStatus.name) &&
(pluginStatus.name.contains(pluginSearchText, ignoreCase = true) ||
(pluginStatus.description?.contains(pluginSearchText, ignoreCase = true) ?: false))
System.out.println("Plugin: ${pluginStatus.name}, isInstalled: ${pluginStatus.isInstalled}, shouldShow: $shouldShow")
shouldShow
}
System.out.println("Matching plugin statuses: ${matchingPluginStatuses.size}")
// Add a separator
val separator = JPanel()
separator.background = VIEW_BACKGROUND_COLOR
separator.preferredSize = Dimension(Int.MAX_VALUE, 1)
separator.maximumSize = Dimension(Int.MAX_VALUE, 1)
if (matchingPluginStatuses.isNotEmpty()) {
val separator = JPanel().apply {
background = VIEW_BACKGROUND_COLOR
preferredSize = Dimension(Int.MAX_VALUE, 1)
maximumSize = preferredSize
}
contentPanel.add(Box.createVerticalStrut(10))
contentPanel.add(separator)
contentPanel.add(Box.createVerticalStrut(5))
val headerPanel = JPanel(FlowLayout(FlowLayout.LEFT))
headerPanel.background = VIEW_BACKGROUND_COLOR
val headerLabel = JLabel(if (matchingPluginStatuses.isNotEmpty()) "Available Plugins" else "")
headerLabel.foreground = secondaryColor
headerLabel.font = Font("RuneScape Small", Font.BOLD, 16)
headerPanel.add(headerLabel)
val headerPanel = JPanel(FlowLayout(FlowLayout.LEFT)).apply {
background = VIEW_BACKGROUND_COLOR
add(JLabel("Available Plugins").apply {
foreground = secondaryColor
font = ViewConstants.FONT_RUNESCAPE_SMALL_BOLD_16
})
}
contentPanel.add(headerPanel)
contentPanel.add(Box.createVerticalStrut(5))
if (matchingPluginStatuses.isNotEmpty()) {
if (matchingPluginStatuses.size > 1) {
val downloadAllPanel = JPanel(FlowLayout(FlowLayout.LEFT))
downloadAllPanel.background = VIEW_BACKGROUND_COLOR
val downloadAllButton = JButton("Download All")
downloadAllButton.background = TITLE_BAR_COLOR
downloadAllButton.foreground = secondaryColor
downloadAllButton.font = Font("RuneScape Small", Font.PLAIN, 14)
downloadAllButton.addActionListener {
startMultiplePluginDownloads(matchingPluginStatuses)
}
downloadAllPanel.add(downloadAllButton)
contentPanel.add(downloadAllPanel)
contentPanel.add(Box.createVerticalStrut(5))
}
// Add matching plugin statuses
for (pluginStatus in matchingPluginStatuses) {
System.out.println("Adding plugin to UI: ${pluginStatus.name}")
matchingPluginStatuses.forEach { pluginStatus ->
val pluginPanel = createPluginStatusItemPanel(pluginStatus)
contentPanel.add(pluginPanel)
contentPanel.add(Box.createVerticalStrut(5))
@ -342,16 +282,19 @@ object ReflectiveEditorView : View {
}
private fun createPluginItemPanel(pluginInfo: PluginInfo, plugin: Plugin): JPanel {
val panel = JPanel(BorderLayout())
panel.background = WIDGET_COLOR
panel.border = BorderFactory.createEmptyBorder(10, 10, 10, 10)
panel.maximumSize = Dimension(220, 60)
val panel = WidgetPanel(
widgetWidth = ViewConstants.PLUGIN_LIST_ITEM_SIZE.width,
widgetHeight = ViewConstants.PLUGIN_LIST_ITEM_SIZE.height,
addDefaultPadding = false
).apply {
layout = BorderLayout()
}
// Plugin name and version (using package name as in original implementation)
val packageName = plugin.javaClass.`package`.name
val nameLabel = JLabel(packageName)
nameLabel.foreground = secondaryColor
nameLabel.font = Font("RuneScape Small", Font.PLAIN, 16)
nameLabel.font = ViewConstants.FONT_RUNESCAPE_SMALL_PLAIN_16
// Check if plugin has exposed attributes
val exposedFields = plugin.javaClass.declaredFields.filter { field ->
@ -362,15 +305,10 @@ object ReflectiveEditorView : View {
// Edit button (only show if plugin has exposed attributes)
val editButton = if (exposedFields.isNotEmpty()) {
val button = JButton()
if (cogIcon != null) {
button.icon = cogIcon
} else {
button.text = "Edit"
}
val button = JButton("Edit")
button.background = TITLE_BAR_COLOR
button.foreground = secondaryColor
button.font = Font("RuneScape Small", Font.PLAIN, 14)
button.font = ViewConstants.FONT_RUNESCAPE_SMALL_14
button.addActionListener {
showPluginDetails(pluginInfo, plugin)
}
@ -386,8 +324,8 @@ object ReflectiveEditorView : View {
// Create a placeholder component that takes the same space but is invisible
val placeholder = JPanel()
placeholder.background = WIDGET_COLOR
placeholder.preferredSize = Dimension(60, 24) // Same size as toggle switch
placeholder.maximumSize = Dimension(60, 24)
placeholder.preferredSize = ViewConstants.TOGGLE_PLACEHOLDER_SIZE
placeholder.maximumSize = ViewConstants.TOGGLE_PLACEHOLDER_SIZE
placeholder.isVisible = false
placeholder
}
@ -417,15 +355,18 @@ object ReflectiveEditorView : View {
}
private fun createDisabledPluginItemPanel(pluginName: String): JPanel {
val panel = JPanel(BorderLayout())
panel.background = WIDGET_COLOR
panel.border = BorderFactory.createEmptyBorder(10, 10, 10, 10)
panel.maximumSize = Dimension(220, 60)
val panel = WidgetPanel(
widgetWidth = ViewConstants.PLUGIN_LIST_ITEM_SIZE.width,
widgetHeight = ViewConstants.PLUGIN_LIST_ITEM_SIZE.height,
addDefaultPadding = false
).apply {
layout = BorderLayout()
}
// Plugin name
val nameLabel = JLabel(pluginName)
nameLabel.foreground = secondaryColor
nameLabel.font = Font("RuneScape Small", Font.PLAIN, 16)
nameLabel.font = ViewConstants.FONT_RUNESCAPE_SMALL_PLAIN_16
// Plugin toggle switch (iOS style) - initially off for disabled plugins
val toggleSwitch = ToggleSwitch()
@ -462,21 +403,24 @@ object ReflectiveEditorView : View {
}
private fun createPluginStatusItemPanel(pluginStatus: PluginInfoWithStatus): JPanel {
val panel = JPanel(BorderLayout())
panel.background = WIDGET_COLOR
panel.border = BorderFactory.createEmptyBorder(10, 10, 10, 10)
panel.maximumSize = Dimension(220, 60)
val panel = WidgetPanel(
widgetWidth = ViewConstants.PLUGIN_LIST_ITEM_SIZE.width,
widgetHeight = ViewConstants.PLUGIN_LIST_ITEM_SIZE.height,
addDefaultPadding = false
).apply {
layout = BorderLayout()
}
// Plugin name
val nameLabel = JLabel(pluginStatus.name)
nameLabel.foreground = secondaryColor
nameLabel.font = Font("RuneScape Small", Font.PLAIN, 16)
nameLabel.font = ViewConstants.FONT_RUNESCAPE_SMALL_PLAIN_16
// Action button based on plugin status
val actionButton = JButton()
actionButton.background = TITLE_BAR_COLOR
actionButton.foreground = secondaryColor
actionButton.font = Font("RuneScape Small", Font.PLAIN, 14)
actionButton.font = ViewConstants.FONT_RUNESCAPE_SMALL_14
// Progress bar for downloads
val progressBar = JProgressBar(0, 100)
@ -543,6 +487,17 @@ object ReflectiveEditorView : View {
controlsPanel.add(toggleSwitch)
}
if (!pluginStatus.isInstalled) {
val tooltipText = buildInstallableTooltip(pluginStatus)
panel.toolTipText = tooltipText
actionButton.toolTipText = tooltipText
progressBar.toolTipText = tooltipText
} else {
panel.toolTipText = null
actionButton.toolTipText = null
progressBar.toolTipText = null
}
panel.add(infoPanel, BorderLayout.CENTER)
panel.add(controlsPanel, BorderLayout.EAST)
@ -554,6 +509,32 @@ object ReflectiveEditorView : View {
return panel
}
private fun buildInstallableTooltip(pluginStatus: PluginInfoWithStatus): String {
val lines = mutableListOf<String>()
pluginStatus.remoteVersion?.takeIf { it.isNotBlank() }?.let {
lines += "<b>Version:</b> ${escapeHtml(it)}"
}
pluginStatus.author?.takeIf { it.isNotBlank() }?.let {
lines += "<b>Author:</b> ${escapeHtml(it)}"
}
pluginStatus.description?.takeIf { it.isNotBlank() }?.let {
lines += escapeHtml(it).replace("\n", "<br>")
}
if (lines.isEmpty()) {
lines += "Click Download to install this plugin."
}
return "<html>${lines.joinToString("<br>")}</html>"
}
private fun escapeHtml(value: String): String {
return value
.replace("&", "&amp;")
.replace("<", "&lt;")
.replace(">", "&gt;")
}
private fun enablePlugin(pluginName: String) {
try {
// Source and destination directories
@ -733,13 +714,13 @@ object ReflectiveEditorView : View {
""".trimIndent()
val infoFont = ViewConstants.FONT_RUNESCAPE_SMALL_14
val infoLabel = LabelComponent(infoText, isHtml = true).apply {
font = Font("RuneScape Small", Font.PLAIN, 14)
font = infoFont
}
infoPanel.add(infoLabel, BorderLayout.CENTER)
val font = Font("RuneScape Small", Font.PLAIN, 14)
val fm = infoLabel.getFontMetrics(font)
val fm = infoLabel.getFontMetrics(infoFont)
val avgCharWidth = fm.stringWidth("x")
val availableWidth = 200
@ -759,7 +740,7 @@ object ReflectiveEditorView : View {
val finalHeight = kotlin.math.max(60, kotlin.math.min(estimatedHeight, 150))
infoPanel.maximumSize = Dimension(Int.MAX_VALUE, finalHeight)
infoPanel.preferredSize = Dimension(220, finalHeight)
infoPanel.preferredSize = Dimension(ViewConstants.DEFAULT_WIDGET_SIZE.width, finalHeight)
@ -798,7 +779,6 @@ object ReflectiveEditorView : View {
}
fun addPlugins(reflectiveEditorView: JPanel) {
System.out.println("addPlugins called")
// Ensure we run on the EDT; if not, reschedule and return
if (!SwingUtilities.isEventDispatchThread()) {
SwingUtilities.invokeLater { addPlugins(reflectiveEditorView) }
@ -850,14 +830,14 @@ object ReflectiveEditorView : View {
var customToolTipWindow: JWindow? = null
fun showCustomToolTip(text: String, component: JComponent) {
val _font = Font("RuneScape Small", Font.PLAIN, 16)
val tooltipFont = ViewConstants.FONT_RUNESCAPE_SMALL_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)
dummyLabel.font = tooltipFont
val fontMetrics = dummyLabel.getFontMetrics(tooltipFont)
val lineHeight = fontMetrics.height
// Calculate the approximate width of the text
val textWidth = fontMetrics.stringWidth(text)
@ -877,7 +857,7 @@ object ReflectiveEditorView : View {
isOpaque = true
background = TOOLTIP_BACKGROUND
foreground = Color.WHITE
font = _font
font = tooltipFont
maximumSize = Dimension(maxWidth, Int.MAX_VALUE)
preferredSize = Dimension(maxWidth, requiredHeight)
}
@ -911,7 +891,6 @@ object ReflectiveEditorView : View {
return parsePluginProperties(content)
}
} catch (e: Exception) {
System.out.println("Error reading properties for disabled plugin $pluginName: ${e.message}")
}
return null
}
@ -967,18 +946,15 @@ object ReflectiveEditorView : View {
loadedPluginsField.isAccessible = true
val loadedPlugins = loadedPluginsField.get(null) as HashMap<*, *>
System.out.println("Found ${loadedPlugins.size} loaded plugins")
for ((_, plugin) in loadedPlugins) {
val pluginName = getPluginDirName(plugin as Plugin)
loadedPluginNames.add(pluginName)
//System.out.println("Loaded plugin: $pluginName")
}
} catch (e: Exception) {
e.printStackTrace()
}
System.out.println("Returning loaded plugin names: ${loadedPluginNames.joinToString(", ")}")
return loadedPluginNames
}
@ -992,7 +968,6 @@ object ReflectiveEditorView : View {
// Log the download URL for debugging
val downloadUrl = PluginDownloadManager.getDownloadUrlForLogging(gitLabPlugin)
System.out.println("Download URL for plugin ${gitLabPlugin.path}: $downloadUrl")
// Update plugin status to show downloading
val updatedStatuses = pluginStatuses.map {
@ -1056,80 +1031,11 @@ object ReflectiveEditorView : View {
})
}
// Method to start downloading multiple plugins
private fun startMultiplePluginDownloads(pluginStatuses: List<PluginInfoWithStatus>) {
val gitLabPlugins = pluginStatuses.mapNotNull { it.gitLabPlugin }
if (gitLabPlugins.isEmpty()) {
showToast(mainPanel, "No valid plugins to download", JOptionPane.ERROR_MESSAGE)
return
}
// Update all plugin statuses to show downloading
val pluginNames = pluginStatuses.map { it.name }.toSet()
val updatedStatuses = pluginStatuses.map {
if (pluginNames.contains(it.name)) {
it.copy(isDownloading = true, downloadProgress = 0)
} else {
it
}
}
this.pluginStatuses = updatedStatuses
// Refresh UI to show progress bars
SwingUtilities.invokeLater {
addPlugins(reflectiveEditorView!!)
}
// Counter to track completed downloads
val completedCount = AtomicInteger(0)
val totalCount = gitLabPlugins.size
val failedPlugins = mutableListOf<String>()
// Start the downloads
PluginDownloadManager.downloadPlugins(gitLabPlugins) { pluginName, success, errorMessage ->
// Update progress in plugin statuses
val updatedStatuses = this.pluginStatuses.map {
if (it.name == pluginName) {
it.copy(isDownloading = false, downloadProgress = if (success) 100 else 0)
} else {
it
}
}
this.pluginStatuses = updatedStatuses
// Track completion
if (!success) {
failedPlugins.add(pluginName)
}
val completed = completedCount.incrementAndGet()
// If all downloads are complete, show final message
if (completed == totalCount) {
SwingUtilities.invokeLater {
if (failedPlugins.isEmpty()) {
showToast(mainPanel, "All plugins downloaded successfully!", JOptionPane.INFORMATION_MESSAGE)
// Reload plugins to make the newly downloaded plugins available
PluginRepository.reloadPlugins()
} else {
val failedList = failedPlugins.joinToString(", ")
showToast(mainPanel, "Completed with errors. Failed plugins: $failedList", JOptionPane.WARNING_MESSAGE)
}
// Refresh UI
addPlugins(reflectiveEditorView!!)
}
}
}
}
// Update plugin statuses by comparing installed and remote plugins
private fun updatePluginStatuses() {
val loadedPluginNames = getLoadedPluginNames()
val statuses = mutableListOf<PluginInfoWithStatus>()
System.out.println("Updating plugin statuses. Loaded plugins: ${loadedPluginNames.joinToString(", ")}")
// Get disabled plugin names and their versions
val disabledPluginInfo = getDisabledPluginInfo()
@ -1152,7 +1058,6 @@ object ReflectiveEditorView : View {
val isDisabled = disabledPluginInfo.containsKey(pluginName)
val disabledVersion = disabledPluginInfo[pluginName]
//System.out.println("Processing plugin: $pluginName, isLoaded: $isLoaded, isDisabled: $isDisabled, disabledVersion: $disabledVersion")
// Check if this plugin is currently being downloaded
val existingStatus = pluginStatuses.find { it.name == pluginName }
@ -1209,7 +1114,6 @@ object ReflectiveEditorView : View {
}
}
System.out.println("Updated plugin statuses. Total statuses: ${statuses.size}")
pluginStatuses = statuses
}
@ -1218,20 +1122,16 @@ object ReflectiveEditorView : View {
var needsReload = false
for (pluginName in loadedPluginNames) {
if (disabledPluginInfo.containsKey(pluginName)) {
System.out.println("Found duplicate plugin: $pluginName. Deleting disabled version.")
try {
val disabledDir = File(pluginsDirectory, "disabled")
val pluginDir = File(disabledDir, pluginName)
if (pluginDir.exists() && pluginDir.isDirectory) {
if (deleteRecursively(pluginDir)) {
System.out.println("Successfully deleted disabled version of $pluginName")
needsReload = true
} else {
System.out.println("Failed to delete disabled version of $pluginName")
}
}
} catch (e: Exception) {
System.out.println("Error deleting disabled version of $pluginName: ${e.message}")
}
}
}
@ -1257,10 +1157,8 @@ object ReflectiveEditorView : View {
val content = propertiesFile.readText()
val properties = parsePluginProperties(content)
disabledPluginInfo[pluginDir.name] = properties.version
System.out.println("Found disabled plugin: ${pluginDir.name}, version: ${properties.version}")
}
} catch (e: Exception) {
System.out.println("Error reading properties for disabled plugin ${pluginDir.name}: ${e.message}")
}
}
}

View file

@ -0,0 +1,90 @@
package KondoKit.views
import KondoKit.ViewConstants
import KondoKit.components.SearchField
import KondoKit.components.WidgetPanel
import KondoKit.plugin.Companion.VIEW_BACKGROUND_COLOR
import java.awt.Color
import java.awt.Component
import java.awt.Container
import java.awt.Dimension
import javax.swing.Box
import javax.swing.BoxLayout
import javax.swing.JPanel
/**
* Utility helpers shared across view implementations to avoid duplicating
* common UI wiring such as search rows and vertical panel setup.
*/
object ViewLayoutHelpers {
data class SearchFieldSection(
val wrapper: JPanel,
val searchField: SearchField
)
/**
* Creates a standard search field row with consistent sizing and styling.
*/
fun createSearchFieldSection(
parent: JPanel,
placeholderText: String,
viewName: String,
initialText: String = "",
fieldWidth: Int = ViewConstants.SEARCH_FIELD_SIZE.width,
fieldHeight: Int = ViewConstants.SEARCH_FIELD_SIZE.height,
onSearch: (String) -> Unit
): SearchFieldSection {
val searchField = SearchField(
parentPanel = parent,
onSearch = onSearch,
placeholderText = placeholderText,
fieldWidth = fieldWidth,
fieldHeight = fieldHeight,
viewName = viewName
)
if (initialText.isNotBlank()) {
searchField.setText(initialText)
}
val preferredSize = Dimension(fieldWidth, fieldHeight)
val wrapper = WidgetPanel(
widgetWidth = preferredSize.width,
widgetHeight = preferredSize.height,
addDefaultPadding = false
).apply {
layout = BoxLayout(this, BoxLayout.X_AXIS)
background = VIEW_BACKGROUND_COLOR
alignmentX = Component.CENTER_ALIGNMENT
add(searchField)
}
return SearchFieldSection(wrapper = wrapper, searchField = searchField)
}
/**
* Provides a BoxLayout.Y_AXIS panel with the default view background.
*/
fun createVerticalPanel(background: Color = VIEW_BACKGROUND_COLOR): JPanel {
return JPanel().apply {
layout = BoxLayout(this, BoxLayout.Y_AXIS)
this.background = background
}
}
}
fun Container.addVerticalSpacing(pixels: Int) {
add(Box.createVerticalStrut(pixels))
}
fun <T : Component> T.setFixedSize(width: Int, height: Int): T {
return setFixedSize(Dimension(width, height))
}
fun <T : Component> T.setFixedSize(size: Dimension): T {
preferredSize = size
minimumSize = size
maximumSize = size
return this
}

View file

@ -7,6 +7,7 @@ import KondoKit.Helpers.formatNumber
import KondoKit.Helpers.getProgressBarColor
import KondoKit.Helpers.getSpriteId
import KondoKit.SpriteToBufferedImage.getBufferedImageFromSprite
import KondoKit.ViewConstants
import KondoKit.XPTable
import KondoKit.components.PopupMenuComponent
import KondoKit.components.ProgressBar
@ -64,7 +65,7 @@ object XPTrackerView : View, OnUpdateCallback, OnXPUpdateCallback {
emptyMap()
}
private val widgetFont = Font("RuneScape Small", Font.TRUETYPE_FONT, 16)
private val widgetFont = ViewConstants.FONT_RUNESCAPE_SMALL_16
private fun createPopupListener(popupMenu: JPopupMenu) = object : MouseAdapter() {
override fun mousePressed(e: MouseEvent) {