mirror of
https://github.com/Kilo-Org/kilocode.git
synced 2026-09-19 01:51:21 +08:00
fix(jetbrains): bound session status overlays
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"@kilocode/kilo-jetbrains": patch
|
||||
---
|
||||
|
||||
Keep JetBrains session status and copy controls visually bounded to the transcript area.
|
||||
+4
-5
@@ -333,11 +333,6 @@ class SessionUi(
|
||||
)
|
||||
}
|
||||
|
||||
overlay = SessionHoverCopyOverlay(root, this)
|
||||
root.addOverlay(overlay) { pane, child ->
|
||||
overlay.bounds(pane, child)
|
||||
}
|
||||
|
||||
sessionContent = JPanel(BorderLayout()).apply { isOpaque = false }
|
||||
|
||||
blankBody = JPanel(BorderLayout()).apply { isOpaque = false }
|
||||
@@ -388,6 +383,10 @@ class SessionUi(
|
||||
header = SessionHeaderPanel(controller, this) { openBranchChanges() }
|
||||
|
||||
scroll = SessionScroll(root, sessionContent, messageBody, blankBody)
|
||||
overlay = SessionHoverCopyOverlay(root, scroll.component, this)
|
||||
root.addOverlay(overlay) { pane, child ->
|
||||
overlay.bounds(pane, child)
|
||||
}
|
||||
messageBody.onReflow = { on -> if (on && !opening) scroll.followTail() }
|
||||
scroll.onScroll = {
|
||||
overlay.clear()
|
||||
|
||||
+4
-1
@@ -18,6 +18,7 @@ import com.intellij.ui.AnimatedIcon
|
||||
import com.intellij.ui.components.JBLabel
|
||||
import com.intellij.util.ui.JBUI
|
||||
import com.intellij.util.ui.components.BorderLayoutPanel
|
||||
import java.awt.Color
|
||||
|
||||
/**
|
||||
* Progress footer rendered at the bottom of the session transcript while the
|
||||
@@ -51,7 +52,7 @@ class ProgressPanel(
|
||||
private val tick = clock.timer(1000) { syncElapsed() }
|
||||
|
||||
init {
|
||||
isOpaque = false
|
||||
isOpaque = true
|
||||
isVisible = false
|
||||
border = JBUI.Borders.empty(
|
||||
UiStyle.Gap.sm(),
|
||||
@@ -83,6 +84,8 @@ class ProgressPanel(
|
||||
/** Exposed for test assertions. */
|
||||
fun labelForeground() = label.foreground
|
||||
|
||||
override fun getBackground(): Color = SessionUiStyle.Colors.sessionBackground()
|
||||
|
||||
private fun onState(state: SessionState) {
|
||||
this.state = state
|
||||
when (state) {
|
||||
|
||||
+18
-4
@@ -18,6 +18,7 @@ import javax.swing.SwingUtilities
|
||||
|
||||
internal class SessionHoverCopyOverlay(
|
||||
private val root: JComponent,
|
||||
private val area: JComponent,
|
||||
parent: Disposable,
|
||||
) : JPanel(null), Disposable {
|
||||
private var target: SessionCopyTarget? = null
|
||||
@@ -57,18 +58,31 @@ internal class SessionHoverCopyOverlay(
|
||||
if (visible.isEmpty) return Rectangle()
|
||||
val size = child.preferredSize
|
||||
val gap = JBUI.scale(4)
|
||||
val limit = limit(pane)
|
||||
if (limit.isEmpty) return Rectangle()
|
||||
if (item.copyToolbar != null) {
|
||||
val pt = SwingUtilities.convertPoint(anchor, Point(visible.x, visible.y), pane)
|
||||
val x = (pt.x + visible.width - size.width).coerceIn(0, (pane.width - size.width).coerceAtLeast(0))
|
||||
val y = (pt.y + visible.height - size.height).coerceIn(0, (pane.height - size.height).coerceAtLeast(0))
|
||||
val x = clamp(pt.x + visible.width - size.width, limit.x, limit.x + limit.width - size.width)
|
||||
val y = clamp(pt.y + visible.height - size.height, limit.y, limit.y + limit.height - size.height)
|
||||
return Rectangle(x, y, size.width, size.height)
|
||||
}
|
||||
val pt = SwingUtilities.convertPoint(anchor, Point(visible.x + visible.width, visible.y), pane)
|
||||
val x = (pt.x - size.width - gap).coerceIn(0, (pane.width - size.width).coerceAtLeast(0))
|
||||
val y = (pt.y + gap).coerceIn(0, (pane.height - size.height).coerceAtLeast(0))
|
||||
val x = clamp(pt.x - size.width - gap, limit.x, limit.x + limit.width - size.width)
|
||||
val y = clamp(pt.y + gap, limit.y, limit.y + limit.height - size.height)
|
||||
return Rectangle(x, y, size.width, size.height)
|
||||
}
|
||||
|
||||
private fun limit(pane: JPanel): Rectangle {
|
||||
if (!area.isShowing || area.parent == null) return Rectangle()
|
||||
val pt = SwingUtilities.convertPoint(area.parent, area.location, pane)
|
||||
return Rectangle(pt.x, pt.y, area.width, area.height)
|
||||
}
|
||||
|
||||
private fun clamp(value: Int, min: Int, max: Int): Int {
|
||||
if (max < min) return min
|
||||
return value.coerceIn(min, max)
|
||||
}
|
||||
|
||||
override fun doLayout() {
|
||||
child.setBounds(0, 0, width, height)
|
||||
layout(child)
|
||||
|
||||
+6
@@ -5,6 +5,7 @@ import ai.kilocode.client.session.model.Permission
|
||||
import ai.kilocode.client.session.model.PermissionMeta
|
||||
import ai.kilocode.client.session.model.SessionModel
|
||||
import ai.kilocode.client.session.model.SessionState
|
||||
import ai.kilocode.client.session.ui.style.SessionUiStyle
|
||||
import ai.kilocode.client.ui.UiStyle
|
||||
import ai.kilocode.client.util.UiTimer
|
||||
import ai.kilocode.client.util.UiTimerSource
|
||||
@@ -45,6 +46,11 @@ class ProgressPanelTest : BasePlatformTestCase() {
|
||||
assertFalse(panel.isVisible)
|
||||
}
|
||||
|
||||
fun `test panel paints the session background`() {
|
||||
assertTrue(panel.isOpaque)
|
||||
assertEquals(SessionUiStyle.Colors.sessionBackground().rgb, panel.background.rgb)
|
||||
}
|
||||
|
||||
fun `test panel shows on Busy with text`() {
|
||||
model.setState(SessionState.Busy("Thinking\u2026"))
|
||||
|
||||
|
||||
+24
-1
@@ -235,7 +235,7 @@ class SessionSelectionCopyTest : SessionUiTestBase() {
|
||||
val root = ShowingPanel()
|
||||
val parent = Disposer.newDisposable("overlay-test")
|
||||
val target = TargetPanel("alpha")
|
||||
val overlay = SessionHoverCopyOverlay(root, parent)
|
||||
val overlay = SessionHoverCopyOverlay(root, root, parent)
|
||||
root.setBounds(0, 0, 100, 100)
|
||||
target.setBounds(10, 10, 80, 80)
|
||||
root.add(target)
|
||||
@@ -247,6 +247,29 @@ class SessionSelectionCopyTest : SessionUiTestBase() {
|
||||
assertFalse(overlay.isVisible)
|
||||
}
|
||||
|
||||
fun `test hover copy overlay bounds stay inside scroll area`() {
|
||||
val root = ShowingPanel()
|
||||
val area = ShowingPanel()
|
||||
val parent = Disposer.newDisposable("overlay-test")
|
||||
val target = TargetPanel("alpha")
|
||||
val overlay = SessionHoverCopyOverlay(root, area, parent)
|
||||
root.setBounds(0, 0, 200, 200)
|
||||
area.setBounds(0, 0, 200, 100)
|
||||
target.setBounds(170, 90, 20, 20)
|
||||
area.add(target)
|
||||
root.add(area)
|
||||
root.add(overlay)
|
||||
|
||||
try {
|
||||
show(overlay, target)
|
||||
val bounds = overlay.bounds(root, overlay.components.single() as JComponent)
|
||||
|
||||
assertTrue("copy overlay should stay above the prompt/status area", bounds.y + bounds.height <= area.y + area.height)
|
||||
} finally {
|
||||
Disposer.dispose(parent)
|
||||
}
|
||||
}
|
||||
|
||||
fun `test session context menu can reinstall after parent disposal`() {
|
||||
val root = JPanel(null)
|
||||
val one = Disposer.newDisposable("context-one")
|
||||
|
||||
Reference in New Issue
Block a user