mirror of
https://github.com/Kilo-Org/kilocode.git
synced 2026-09-21 05:52:35 +08:00
feat(jetbrains): center session readable width
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"@kilocode/kilo-jetbrains": patch
|
||||
---
|
||||
|
||||
Center JetBrains session content at a 98-column readable width on wide panels.
|
||||
+11
-1
@@ -44,6 +44,9 @@ import ai.kilocode.client.session.ui.selection.SessionHoverCopyOverlay
|
||||
import ai.kilocode.client.session.ui.selection.SessionSelection
|
||||
import ai.kilocode.client.session.ui.style.SessionEditorStyle
|
||||
import ai.kilocode.client.session.ui.style.SessionEditorStyleTarget
|
||||
import ai.kilocode.client.ui.layout.HAlign
|
||||
import ai.kilocode.client.ui.layout.VAlign
|
||||
import ai.kilocode.client.ui.layout.align
|
||||
import ai.kilocode.client.session.controller.EVENT_FLUSH_MS
|
||||
import ai.kilocode.client.session.controller.PromptSelection
|
||||
import ai.kilocode.client.session.controller.SessionController
|
||||
@@ -452,7 +455,14 @@ class SessionUi(
|
||||
sessionContent.add(header, BorderLayout.NORTH)
|
||||
sessionContent.add(scroll.component, BorderLayout.CENTER)
|
||||
root.content.add(sessionContent, BorderLayout.CENTER)
|
||||
root.content.add(prompt, BorderLayout.SOUTH)
|
||||
root.content.add(
|
||||
prompt.align(
|
||||
HAlign.CENTER,
|
||||
VAlign.FIT,
|
||||
maxW = { SessionUiStyle.SessionLayout.readableWidth(prompt, style.transcriptFont) },
|
||||
),
|
||||
BorderLayout.SOUTH,
|
||||
)
|
||||
add(root, BorderLayout.CENTER)
|
||||
}
|
||||
|
||||
|
||||
+17
-8
@@ -36,6 +36,8 @@ class SessionLayout(
|
||||
|
||||
private val cache = IdentityHashMap<Component, Measured>()
|
||||
|
||||
var maxWidth: ((Container) -> Int)? = null
|
||||
|
||||
override fun addLayoutComponent(name: String, comp: Component) = Unit
|
||||
override fun removeLayoutComponent(comp: Component) {
|
||||
cache.remove(comp)
|
||||
@@ -50,7 +52,7 @@ class SessionLayout(
|
||||
if (!comp.isVisible) continue
|
||||
if (!first) h += gap(comp)
|
||||
first = false
|
||||
val child = bounds(ins, w, comp)
|
||||
val child = bounds(parent, ins, w, comp)
|
||||
h += measure(comp, child.width)
|
||||
}
|
||||
// w and h are already scaled px (child preferred heights + scaled gaps/insets) and
|
||||
@@ -70,7 +72,7 @@ class SessionLayout(
|
||||
if (!comp.isVisible) continue
|
||||
if (!first) y += gap(comp)
|
||||
first = false
|
||||
val child = bounds(ins, w, comp)
|
||||
val child = bounds(parent, ins, w, comp)
|
||||
val h = measure(comp, child.width)
|
||||
comp.setBounds(child.left, y, child.width, h)
|
||||
y += h
|
||||
@@ -106,13 +108,20 @@ class SessionLayout(
|
||||
return h
|
||||
}
|
||||
|
||||
private fun bounds(ins: Insets, width: Int, comp: Component): Bounds {
|
||||
val view = view(comp) ?: return Bounds(ins.left, width)
|
||||
if (view.sessionViewKind != SessionView.Kind.UserPrompt) return Bounds(ins.left, width)
|
||||
private fun bounds(parent: Container, ins: Insets, width: Int, comp: Component): Bounds {
|
||||
val lane = lane(parent, ins, width)
|
||||
val view = view(comp) ?: return lane
|
||||
if (view.sessionViewKind != SessionView.Kind.UserPrompt) return lane
|
||||
val shift = JBUI.scale(SessionUiStyle.SessionLayout.USER_PROMPT_INDENT)
|
||||
val next = width - shift
|
||||
if (next < JBUI.scale(SessionUiStyle.SessionLayout.USER_PROMPT_INDENT)) return Bounds(ins.left, width)
|
||||
return Bounds(ins.left + shift, next)
|
||||
val next = lane.width - shift
|
||||
if (next < JBUI.scale(SessionUiStyle.SessionLayout.USER_PROMPT_INDENT)) return lane
|
||||
return Bounds(lane.left + shift, next)
|
||||
}
|
||||
|
||||
private fun lane(parent: Container, ins: Insets, width: Int): Bounds {
|
||||
val cap = maxWidth?.invoke(parent) ?: width
|
||||
val w = minOf(width, cap.coerceAtLeast(0))
|
||||
return Bounds(ins.left + (width - w) / 2, w)
|
||||
}
|
||||
|
||||
private fun insets(parent: Container): Insets {
|
||||
|
||||
+3
@@ -97,6 +97,9 @@ class SessionMessageListPanel(
|
||||
|
||||
init {
|
||||
Disposer.register(parent, this)
|
||||
(layout as? SessionLayout)?.maxWidth = { view ->
|
||||
SessionUiStyle.SessionLayout.readableWidth(view, style.transcriptFont)
|
||||
}
|
||||
applyStyle(style)
|
||||
|
||||
model.addListener(parent) { event ->
|
||||
|
||||
+7
-1
@@ -55,7 +55,12 @@ class EmptySessionPanel(
|
||||
private val browse: (String) -> Unit = BrowserUtil::browse,
|
||||
private val timers: UiTimerSource = UiTimers,
|
||||
) : BorderLayoutPanel(), Disposable, SessionEditorStyleTarget {
|
||||
val view: Align = align(HAlign.CENTER, VAlign.CENTER)
|
||||
private var style = SessionEditorStyle.current()
|
||||
val view: Align = align(
|
||||
HAlign.CENTER,
|
||||
VAlign.CENTER,
|
||||
maxW = { SessionUiStyle.SessionLayout.readableWidth(this, style.transcriptFont) },
|
||||
)
|
||||
|
||||
private val timer = timers.timer(ACTIVITY_MS) { syncActivity() }
|
||||
internal val recent = RecentsList(recents, controller)
|
||||
@@ -248,6 +253,7 @@ class EmptySessionPanel(
|
||||
}
|
||||
|
||||
override fun applyStyle(style: SessionEditorStyle) {
|
||||
this.style = style
|
||||
welcomeLabel.font = style.regularFont
|
||||
recent.applyStyle(style)
|
||||
revalidate()
|
||||
|
||||
+7
@@ -5,6 +5,7 @@ import com.intellij.ui.JBColor
|
||||
import com.intellij.util.ui.JBUI
|
||||
import com.intellij.util.ui.UIUtil
|
||||
import java.awt.Color
|
||||
import java.awt.Component
|
||||
import java.awt.Font
|
||||
import javax.swing.UIManager
|
||||
|
||||
@@ -75,6 +76,7 @@ object SessionUiStyle {
|
||||
|
||||
/** Geometry for the transcript list and its scroll behavior. */
|
||||
object SessionLayout {
|
||||
const val READABLE_COLUMNS = 98
|
||||
const val GAP = 3
|
||||
const val USER_PROMPT_GAP = 10
|
||||
const val TRANSCRIPT_SCROLLBAR_PADDING = 10
|
||||
@@ -87,6 +89,11 @@ object SessionUiStyle {
|
||||
|
||||
const val USER_PROMPT_INDENT = 100
|
||||
const val SCROLL_INCREMENT = 48
|
||||
|
||||
fun readableWidth(component: Component, font: Font): Int {
|
||||
val width = component.getFontMetrics(font).charWidth('0').coerceAtLeast(1)
|
||||
return width * READABLE_COLUMNS
|
||||
}
|
||||
}
|
||||
|
||||
/** Shared tokens for individual transcript views and session views. */
|
||||
|
||||
+43
-21
@@ -21,6 +21,9 @@ enum class VAlign { TRACK, FIT, TOP, CENTER, BOTTOM }
|
||||
* **LEFT / CENTER / RIGHT** (horizontal) and **TOP / CENTER / BOTTOM** (vertical):
|
||||
* child uses its bounded preferred size (coerced into [min, max]) and is placed at the
|
||||
* corresponding edge or centered. Shrinks to available space when necessary.
|
||||
* When a dynamic max function is supplied for an axis, these modes fill available space
|
||||
* up to that max and use alignment only for the leftover space, matching CSS
|
||||
* `width: 100%; max-width: ...; margin-inline: auto` behavior.
|
||||
*
|
||||
* During layout, the child is first sized on TRACK/FIT axes before preferred size
|
||||
* is read. This mirrors Swing layouts such as [java.awt.BorderLayout] where the
|
||||
@@ -42,7 +45,9 @@ class Align(
|
||||
child: Component,
|
||||
h: HAlign = HAlign.FIT,
|
||||
v: VAlign = VAlign.FIT,
|
||||
) : JPanel(Layout(h, v)) {
|
||||
maxW: (() -> Int)? = null,
|
||||
maxH: (() -> Int)? = null,
|
||||
) : JPanel(Layout(h, v, maxW, maxH)) {
|
||||
|
||||
init {
|
||||
isOpaque = false
|
||||
@@ -52,6 +57,8 @@ class Align(
|
||||
private class Layout(
|
||||
private val h: HAlign,
|
||||
private val v: VAlign,
|
||||
private val maxW: (() -> Int)?,
|
||||
private val maxH: (() -> Int)?,
|
||||
) : LayoutManager2 {
|
||||
|
||||
override fun addLayoutComponent(comp: Component, constraints: Any?) = Unit
|
||||
@@ -66,17 +73,17 @@ class Align(
|
||||
val availH = maxOf(0, parent.height - ins.top - ins.bottom)
|
||||
|
||||
val min = child.minimumSize
|
||||
val max = child.maximumSize
|
||||
if (probes(h) || probes(v)) {
|
||||
val max = max(child)
|
||||
if (probes(h) || probes(v) || maxW != null || maxH != null) {
|
||||
child.setSize(
|
||||
if (probes(h)) probe(h, availW, min.width, max.width) else child.width,
|
||||
if (probes(v)) probe(v, availH, min.height, max.height) else child.height,
|
||||
if (probes(h) || maxW != null) probe(h, availW, min.width, max.width, maxW != null) else child.width,
|
||||
if (probes(v) || maxH != null) probe(v, availH, min.height, max.height, maxH != null) else child.height,
|
||||
)
|
||||
}
|
||||
val pref = child.preferredSize
|
||||
|
||||
val (w, cx) = place(h, availW, min.width, pref.width, max.width)
|
||||
val (ht, cy) = place(v, availH, min.height, pref.height, max.height)
|
||||
val (w, cx) = place(h, availW, min.width, pref.width, max.width, maxW != null)
|
||||
val (ht, cy) = place(v, availH, min.height, pref.height, max.height, maxH != null)
|
||||
|
||||
child.setBounds(ins.left + cx, ins.top + cy, w, ht)
|
||||
}
|
||||
@@ -95,18 +102,18 @@ class Align(
|
||||
val child = parent.getComponent(0)
|
||||
val ins = parent.insets
|
||||
val min = child.minimumSize
|
||||
val max = child.maximumSize
|
||||
val max = max(child)
|
||||
val availW = maxOf(0, parent.width - ins.left - ins.right)
|
||||
val availH = maxOf(0, parent.height - ins.top - ins.bottom)
|
||||
if ((availW > 0 && probes(h)) || (availH > 0 && probes(v))) {
|
||||
if ((availW > 0 && (probes(h) || maxW != null)) || (availH > 0 && (probes(v) || maxH != null))) {
|
||||
child.setSize(
|
||||
if (availW > 0 && probes(h)) probe(h, availW, min.width, max.width) else child.width,
|
||||
if (availH > 0 && probes(v)) probe(v, availH, min.height, max.height) else child.height,
|
||||
if (availW > 0 && (probes(h) || maxW != null)) probe(h, availW, min.width, max.width, maxW != null) else child.width,
|
||||
if (availH > 0 && (probes(v) || maxH != null)) probe(v, availH, min.height, max.height, maxH != null) else child.height,
|
||||
)
|
||||
}
|
||||
val pref = child.preferredSize
|
||||
val cw = if (h == HAlign.TRACK) 0 else bounded(pref.width, min.width, max.width)
|
||||
val ch = if (v == VAlign.TRACK) 0 else bounded(pref.height, min.height, max.height)
|
||||
val cw = if (h == HAlign.TRACK) 0 else size(h, pref.width, min.width, max.width, maxW != null)
|
||||
val ch = if (v == VAlign.TRACK) 0 else size(v, pref.height, min.height, max.height, maxH != null)
|
||||
return Dimension(cw + ins.left + ins.right, ch + ins.top + ins.bottom)
|
||||
}
|
||||
|
||||
@@ -114,19 +121,25 @@ class Align(
|
||||
if (target.componentCount == 0) return Dimension(Int.MAX_VALUE, Int.MAX_VALUE)
|
||||
val child = target.getComponent(0)
|
||||
val ins = target.insets
|
||||
val max = max(child)
|
||||
val cw = if (h == HAlign.TRACK) {
|
||||
Int.MAX_VALUE
|
||||
} else {
|
||||
maxOf(child.minimumSize.width, child.maximumSize.width) + ins.left + ins.right
|
||||
maxOf(child.minimumSize.width, max.width) + ins.left + ins.right
|
||||
}
|
||||
val ch = if (v == VAlign.TRACK) {
|
||||
Int.MAX_VALUE
|
||||
} else {
|
||||
maxOf(child.minimumSize.height, child.maximumSize.height) + ins.top + ins.bottom
|
||||
maxOf(child.minimumSize.height, max.height) + ins.top + ins.bottom
|
||||
}
|
||||
return Dimension(cw, ch)
|
||||
}
|
||||
|
||||
private fun max(child: Component): Dimension {
|
||||
val base = child.maximumSize
|
||||
return Dimension(maxW?.invoke() ?: base.width, maxH?.invoke() ?: base.height)
|
||||
}
|
||||
|
||||
override fun getLayoutAlignmentX(target: Container) = 0.5f
|
||||
override fun getLayoutAlignmentY(target: Container) = 0.5f
|
||||
override fun invalidateLayout(target: Container) = Unit
|
||||
@@ -143,7 +156,7 @@ class Align(
|
||||
* - FIT: size = clamp(avail, min, max), offset = 0
|
||||
* - edge/center: size = clamp(boundedPref, 0, avail), offset positions according to alignment
|
||||
*/
|
||||
private fun place(mode: Any, avail: Int, min: Int, pref: Int, max: Int): Pair<Int, Int> {
|
||||
private fun place(mode: Any, avail: Int, min: Int, pref: Int, max: Int, cap: Boolean): Pair<Int, Int> {
|
||||
val effMax = maxOf(min, max)
|
||||
return when (mode) {
|
||||
HAlign.TRACK, VAlign.TRACK -> avail to 0
|
||||
@@ -153,15 +166,15 @@ private fun place(mode: Any, avail: Int, min: Int, pref: Int, max: Int): Pair<In
|
||||
size to 0
|
||||
}
|
||||
HAlign.LEFT, VAlign.TOP -> {
|
||||
val size = minOf(bounded(pref, min, effMax), avail)
|
||||
val size = minOf(size(mode, pref, min, effMax, cap), avail)
|
||||
size to 0
|
||||
}
|
||||
HAlign.CENTER, VAlign.CENTER -> {
|
||||
val size = minOf(bounded(pref, min, effMax), avail)
|
||||
val size = minOf(size(mode, pref, min, effMax, cap), avail)
|
||||
size to (avail - size) / 2
|
||||
}
|
||||
HAlign.RIGHT, VAlign.BOTTOM -> {
|
||||
val size = minOf(bounded(pref, min, effMax), avail)
|
||||
val size = minOf(size(mode, pref, min, effMax, cap), avail)
|
||||
size to (avail - size)
|
||||
}
|
||||
else -> avail to 0
|
||||
@@ -170,9 +183,18 @@ private fun place(mode: Any, avail: Int, min: Int, pref: Int, max: Int): Pair<In
|
||||
|
||||
private fun bounded(value: Int, min: Int, max: Int) = value.coerceIn(min, maxOf(min, max))
|
||||
|
||||
private fun size(mode: Any, pref: Int, min: Int, max: Int, cap: Boolean): Int {
|
||||
if (!cap) return bounded(pref, min, max)
|
||||
if (mode == HAlign.LEFT || mode == HAlign.CENTER || mode == HAlign.RIGHT ||
|
||||
mode == VAlign.TOP || mode == VAlign.CENTER || mode == VAlign.BOTTOM
|
||||
) return maxOf(min, max)
|
||||
return bounded(pref, min, max)
|
||||
}
|
||||
|
||||
private fun probes(mode: Any) = mode == HAlign.TRACK || mode == HAlign.FIT || mode == VAlign.TRACK || mode == VAlign.FIT
|
||||
|
||||
private fun probe(mode: Any, avail: Int, min: Int, max: Int): Int {
|
||||
private fun probe(mode: Any, avail: Int, min: Int, max: Int, cap: Boolean): Int {
|
||||
if (cap && mode != HAlign.TRACK && mode != VAlign.TRACK) return minOf(avail, maxOf(min, max))
|
||||
if (mode == HAlign.FIT || mode == VAlign.FIT) return minOf(avail, maxOf(min, max))
|
||||
return avail
|
||||
}
|
||||
@@ -181,4 +203,4 @@ private fun probe(mode: Any, avail: Int, min: Int, max: Int): Int {
|
||||
// Factory extension
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
fun Component.align(h: HAlign, v: VAlign) = Align(this, h, v)
|
||||
fun Component.align(h: HAlign, v: VAlign, maxW: (() -> Int)? = null, maxH: (() -> Int)? = null) = Align(this, h, v, maxW, maxH)
|
||||
|
||||
+16
-6
@@ -22,6 +22,7 @@ import ai.kilocode.client.session.ui.style.SessionEditorStyle
|
||||
import ai.kilocode.client.session.ui.header.SessionHeaderPanel
|
||||
import ai.kilocode.client.session.ui.style.SessionUiStyle
|
||||
import ai.kilocode.client.session.controller.SessionControllerEvent
|
||||
import ai.kilocode.client.ui.layout.Align
|
||||
import ai.kilocode.rpc.dto.ChatEventDto
|
||||
import ai.kilocode.rpc.dto.ConfigDto
|
||||
import ai.kilocode.rpc.dto.KiloAppStateDto
|
||||
@@ -37,6 +38,7 @@ import com.intellij.ui.components.JBScrollPane
|
||||
import java.awt.Dimension
|
||||
import javax.swing.JLayeredPane
|
||||
import javax.swing.JPanel
|
||||
import javax.swing.SwingUtilities
|
||||
import kotlinx.coroutines.CompletableDeferred
|
||||
|
||||
@Suppress("UnstableApiUsage")
|
||||
@@ -80,7 +82,8 @@ class SessionUiLayoutTest : SessionUiTestBase() {
|
||||
val connection = find<ConnectionPanel>(ui)
|
||||
val prompt = find<PromptPanel>(ui)
|
||||
|
||||
assertSame(root.content, prompt.parent)
|
||||
assertTrue(prompt.parent is Align)
|
||||
assertSame(root.content, prompt.parent.parent)
|
||||
assertSame(root.overlay, connection.parent)
|
||||
assertTrue(root.overlay.components.any { it is SessionAccountOverlay })
|
||||
assertFalse(root.content.components.contains(connection))
|
||||
@@ -245,15 +248,17 @@ class SessionUiLayoutTest : SessionUiTestBase() {
|
||||
|
||||
showConnection()
|
||||
layout()
|
||||
val point = promptPoint(root, prompt)
|
||||
|
||||
assertTrue(connection.isVisible)
|
||||
assertSame(root.overlay, connection.parent)
|
||||
assertEquals(prompt.x, connection.x)
|
||||
assertEquals(point.x, connection.x)
|
||||
assertEquals(prompt.width, connection.width)
|
||||
assertEquals(prompt.y - SessionUiStyle.View.Outline.width(), connection.y + connection.height)
|
||||
assertEquals(point.y - SessionUiStyle.View.Outline.width(), connection.y + connection.height)
|
||||
}
|
||||
|
||||
fun `test expanded connection panel remains anchored above prompt`() {
|
||||
val root = find<SessionRootPanel>(ui)
|
||||
val connection = find<ConnectionPanel>(ui)
|
||||
val prompt = find<PromptPanel>(ui)
|
||||
|
||||
@@ -266,7 +271,7 @@ class SessionUiLayoutTest : SessionUiTestBase() {
|
||||
layout()
|
||||
|
||||
assertTrue(connection.detailsVisible())
|
||||
assertEquals(prompt.y - SessionUiStyle.View.Outline.width(), connection.y + connection.height)
|
||||
assertEquals(promptPoint(root, prompt).y - SessionUiStyle.View.Outline.width(), connection.y + connection.height)
|
||||
}
|
||||
|
||||
fun `test connection panel is unaffected by active question view`() {
|
||||
@@ -276,6 +281,7 @@ class SessionUiLayoutTest : SessionUiTestBase() {
|
||||
layout()
|
||||
val connection = find<ConnectionPanel>(ui)
|
||||
val prompt = find<PromptPanel>(ui)
|
||||
val root = find<SessionRootPanel>(ui)
|
||||
val top = connection.y
|
||||
|
||||
controller().model.setState(questionStateChanged())
|
||||
@@ -284,7 +290,7 @@ class SessionUiLayoutTest : SessionUiTestBase() {
|
||||
assertTrue(find<QuestionView>(ui).isVisible)
|
||||
assertSame(find<SessionMessageListPanel>(ui), find<QuestionView>(ui).parent)
|
||||
assertEquals(top, connection.y)
|
||||
assertEquals(prompt.y - SessionUiStyle.View.Outline.width(), connection.y + connection.height)
|
||||
assertEquals(promptPoint(root, prompt).y - SessionUiStyle.View.Outline.width(), connection.y + connection.height)
|
||||
assertSame(find<SessionMessageListPanel>(ui), scrollView())
|
||||
}
|
||||
|
||||
@@ -295,6 +301,7 @@ class SessionUiLayoutTest : SessionUiTestBase() {
|
||||
layout()
|
||||
val connection = find<ConnectionPanel>(ui)
|
||||
val prompt = find<PromptPanel>(ui)
|
||||
val root = find<SessionRootPanel>(ui)
|
||||
val top = connection.y
|
||||
|
||||
controller().model.setState(permissionStateChanged())
|
||||
@@ -303,7 +310,7 @@ class SessionUiLayoutTest : SessionUiTestBase() {
|
||||
assertTrue(find<PermissionView>(ui).isVisible)
|
||||
assertSame(find<SessionMessageListPanel>(ui), find<PermissionView>(ui).parent)
|
||||
assertEquals(top, connection.y)
|
||||
assertEquals(prompt.y - SessionUiStyle.View.Outline.width(), connection.y + connection.height)
|
||||
assertEquals(promptPoint(root, prompt).y - SessionUiStyle.View.Outline.width(), connection.y + connection.height)
|
||||
assertSame(find<SessionMessageListPanel>(ui), scrollView())
|
||||
}
|
||||
|
||||
@@ -764,6 +771,9 @@ class SessionUiLayoutTest : SessionUiTestBase() {
|
||||
.single()
|
||||
.let { it as javax.swing.JComponent }
|
||||
|
||||
private fun promptPoint(root: SessionRootPanel, prompt: PromptPanel) =
|
||||
SwingUtilities.convertPoint(prompt.parent, prompt.x, prompt.y, root.overlay)
|
||||
|
||||
private class Row(override val sessionViewKind: SessionView.Kind) : JPanel(), SessionView {
|
||||
override fun getPreferredSize() = Dimension(100, 10)
|
||||
}
|
||||
|
||||
+50
@@ -185,6 +185,56 @@ class SessionLayoutTest : BasePlatformTestCase() {
|
||||
assertEquals(20, child.height)
|
||||
}
|
||||
|
||||
fun `test max width centers default session view in wide container`() {
|
||||
val p = panel(width = 500)
|
||||
(p.layout as SessionLayout).maxWidth = { 300 }
|
||||
val child = view(height = 20, kind = SessionView.Kind.Default)
|
||||
p.add(child)
|
||||
p.doLayout()
|
||||
|
||||
assertEquals(100, child.x)
|
||||
assertEquals(300, child.width)
|
||||
assertEquals(20, child.height)
|
||||
}
|
||||
|
||||
fun `test max width fills narrow container`() {
|
||||
val p = panel(width = 250)
|
||||
(p.layout as SessionLayout).maxWidth = { 300 }
|
||||
val child = view(height = 20, kind = SessionView.Kind.Default)
|
||||
p.add(child)
|
||||
p.doLayout()
|
||||
|
||||
assertEquals(0, child.x)
|
||||
assertEquals(250, child.width)
|
||||
assertEquals(20, child.height)
|
||||
}
|
||||
|
||||
fun `test max width centers user prompt indent inside lane`() {
|
||||
val p = panel(width = 500)
|
||||
(p.layout as SessionLayout).maxWidth = { 300 }
|
||||
val child = view(height = 20, kind = SessionView.Kind.UserPrompt)
|
||||
p.add(child)
|
||||
p.doLayout()
|
||||
|
||||
assertEquals(100 + JBUI.scale(SessionUiStyle.SessionLayout.USER_PROMPT_INDENT), child.x)
|
||||
assertEquals(300 - JBUI.scale(SessionUiStyle.SessionLayout.USER_PROMPT_INDENT), child.width)
|
||||
assertEquals(20, child.height)
|
||||
}
|
||||
|
||||
fun `test max width measures child height at capped width`() {
|
||||
val p = panel(width = 500)
|
||||
(p.layout as SessionLayout).maxWidth = { 300 }
|
||||
val child = object : JPanel() {
|
||||
override fun getPreferredSize(): Dimension = Dimension(0, if (width == 300) 20 else 80)
|
||||
}
|
||||
p.add(child)
|
||||
p.doLayout()
|
||||
|
||||
assertEquals(100, child.x)
|
||||
assertEquals(300, child.width)
|
||||
assertEquals(20, child.height)
|
||||
}
|
||||
|
||||
// ---- invisible children ------
|
||||
|
||||
fun `test invisible child is skipped in layout`() {
|
||||
|
||||
+57
@@ -298,6 +298,63 @@ class AlignTest : BasePlatformTestCase() {
|
||||
assertEquals(30 + ins.top + ins.bottom, ps.height)
|
||||
}
|
||||
|
||||
// ------ function max ------
|
||||
|
||||
fun `test CENTER with max function fills cap and centers`() {
|
||||
val child = child(pref = 40 x 20)
|
||||
val wrap = Align(child, HAlign.CENTER, VAlign.TOP, maxW = { 120 })
|
||||
wrap.setBounds(0, 0, 300, 100)
|
||||
wrap.doLayout()
|
||||
|
||||
assertBounds(90, 0, 120, 20, child)
|
||||
}
|
||||
|
||||
fun `test CENTER with max function fills available below cap`() {
|
||||
val child = child(pref = 40 x 20)
|
||||
val wrap = Align(child, HAlign.CENTER, VAlign.TOP, maxW = { 120 })
|
||||
wrap.setBounds(0, 0, 80, 100)
|
||||
wrap.doLayout()
|
||||
|
||||
assertBounds(0, 0, 80, 20, child)
|
||||
}
|
||||
|
||||
fun `test RIGHT with max function pins capped width to right`() {
|
||||
val child = child(pref = 40 x 20)
|
||||
val wrap = Align(child, HAlign.RIGHT, VAlign.TOP, maxW = { 120 })
|
||||
wrap.setBounds(0, 0, 300, 100)
|
||||
wrap.doLayout()
|
||||
|
||||
assertBounds(180, 0, 120, 20, child)
|
||||
}
|
||||
|
||||
fun `test max function is evaluated on each layout`() {
|
||||
var cap = 120
|
||||
val child = child(pref = 40 x 20)
|
||||
val wrap = Align(child, HAlign.CENTER, VAlign.TOP, maxW = { cap })
|
||||
wrap.setBounds(0, 0, 300, 100)
|
||||
wrap.doLayout()
|
||||
assertBounds(90, 0, 120, 20, child)
|
||||
|
||||
cap = 60
|
||||
wrap.doLayout()
|
||||
|
||||
assertBounds(120, 0, 60, 20, child)
|
||||
}
|
||||
|
||||
fun `test max function probes capped width before measuring height`() {
|
||||
val child = object : JBLabel("x") {
|
||||
override fun getMinimumSize() = Dimension(0, 0)
|
||||
override fun getPreferredSize() = Dimension(20, if (width == 120) 12 else 60)
|
||||
override fun getMaximumSize() = Dimension(Int.MAX_VALUE, Int.MAX_VALUE)
|
||||
}
|
||||
val wrap = Align(child, HAlign.CENTER, VAlign.TOP, maxW = { 120 })
|
||||
|
||||
wrap.setBounds(0, 0, 300, 100)
|
||||
wrap.doLayout()
|
||||
|
||||
assertBounds(90, 0, 120, 12, child)
|
||||
}
|
||||
|
||||
// ------ align() factory ------
|
||||
|
||||
fun `test align extension returns Align wrapping child`() {
|
||||
|
||||
Reference in New Issue
Block a user