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

@ -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 searchField = SearchField(
parentPanel = panel,
onSearch = { 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)
val panel = BaseView(PLUGIN_LIST_VIEW, addDefaultSpacing = false).apply {
background = VIEW_BACKGROUND_COLOR
preferredSize = Dimension(230, 30)
maximumSize = preferredSize
minimumSize = preferredSize
alignmentX = Component.CENTER_ALIGNMENT
add(searchField)
}
searchFieldWrapper = searchFieldWrapperPanel
val searchSection = createSearchFieldSection(
parent = panel,
placeholderText = "Search plugins...",
viewName = VIEW_NAME,
initialText = pluginSearchText
) { searchText ->
pluginSearchText = searchText
SwingUtilities.invokeLater {
addPlugins(reflectiveEditorView!!)
}
}
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
(pluginStatus.description?.contains(pluginSearchText, ignoreCase = true) ?: false))
}
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)
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)
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))
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))
// Add matching plugin statuses
for (pluginStatus in matchingPluginStatuses) {
System.out.println("Adding plugin to UI: ${pluginStatus.name}")
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))
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)
@ -553,6 +508,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 {
@ -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}")
}
}
}