mirror of
https://github.com/Kilo-Org/kilocode.git
synced 2026-09-24 16:02:55 +08:00
feat(jetbrains): add Align wrapper with TRACK mode, replace CenterShrinkPanel
This commit is contained in:
@@ -366,6 +366,44 @@ For common spacing lookups, prefer `JBUI.CurrentTheme` area-specific insets (e.g
|
||||
| Simple `BorderLayout` panels | `JBUI.Panels.simplePanel(...)`, `BorderLayoutPanel` |
|
||||
| Simple vertical custom Swing groups | `VerticalLayout` |
|
||||
| Fluent platform panels | `JBPanel.withBorder(...)`, `.andTransparent()`, `.andOpaque()`, `.withBackground(...)` |
|
||||
| Single-component alignment wrapper | `ai.kilocode.client.ui.Align` — see section below |
|
||||
|
||||
### Align — Single-Component Alignment Wrapper
|
||||
|
||||
Use `Align` (`ai.kilocode.client.ui.Align`) when a single Swing component must be positioned inside available space without adding visual chrome. It is a transparent, no-border, no-color `JPanel(null)` that lays out its one child according to independent horizontal (`HAlign`) and vertical (`VAlign`) modes. `CenterShrinkPanel` has been removed; use `child.alignCenter()` as a direct replacement.
|
||||
|
||||
**Alignment modes:**
|
||||
|
||||
| Mode | Axis | Layout behavior | Wrapper size contribution |
|
||||
|---|---|---|---|
|
||||
| `HAlign.TRACK` / `VAlign.TRACK` | either | Child always fills all available space; ignores child min/preferred/max | Zero (wrapper reports insets only on that axis) |
|
||||
| `HAlign.FIT` / `VAlign.FIT` | either | Child fills available space clamped to child's effective `[min, max]` range | Child min/preferred/max respected |
|
||||
| `HAlign.LEFT` / `VAlign.TOP` | H / V | Child placed at left/top edge at bounded preferred size; shrinks to available when necessary | Child min/preferred/max respected |
|
||||
| `HAlign.CENTER` / `VAlign.CENTER` | H / V | Child centered at bounded preferred size; shrinks to available when necessary | Child min/preferred/max respected |
|
||||
| `HAlign.RIGHT` / `VAlign.BOTTOM` | H / V | Child placed at right/bottom edge at bounded preferred size; shrinks to available when necessary | Child min/preferred/max respected |
|
||||
|
||||
"Bounded preferred" means the child's preferred size coerced into the effective `[min, max]` range. If available space is smaller than the effective minimum, the layout shrinks the child to available space to avoid overflow.
|
||||
|
||||
**Kotlin-style factory extensions** on `Component`:
|
||||
|
||||
```kotlin
|
||||
child.align(HAlign.LEFT, VAlign.TOP) // explicit modes
|
||||
child.alignCenter() // CENTER / CENTER (replaces CenterShrinkPanel)
|
||||
child.alignLeft(VAlign.CENTER) // LEFT + custom V
|
||||
child.alignRight(VAlign.CENTER) // RIGHT + custom V
|
||||
child.alignTop(HAlign.CENTER) // TOP + custom H
|
||||
child.alignBottom() // BOTTOM + FIT horizontal
|
||||
child.track() // TRACK / TRACK — always fills all space
|
||||
child.trackX(VAlign.TOP) // TRACK horizontal, TOP vertical
|
||||
child.trackY(HAlign.CENTER) // CENTER horizontal, TRACK vertical
|
||||
```
|
||||
|
||||
**Rules:**
|
||||
|
||||
- Prefer the factory extensions over creating one-off `JPanel(FlowLayout(...))` or `BorderLayoutPanel` wrappers just to control alignment.
|
||||
- Use `TRACK` when the child must occupy all available space on an axis and must not reserve any space in the parent's size negotiation on that axis. Use `FIT` when you want to fill available space but still respect child min/max constraints.
|
||||
- All non-TRACK modes include the child's min, preferred, and max sizes in the wrapper's own min/preferred/max size. This means parent layout managers see the child constraints through the wrapper.
|
||||
- Do not use `Align` for spacing, padding, borders, colors, or multi-child layout — use `JBUI.Borders.empty(...)`, `UiStyle.Gap`, or an appropriate layout manager for those concerns.
|
||||
|
||||
### IntelliJ UI Surfaces
|
||||
|
||||
|
||||
+5
-4
@@ -10,8 +10,9 @@ import ai.kilocode.client.session.ui.style.SessionEditorStyle
|
||||
import ai.kilocode.client.session.ui.style.SessionEditorStyleTarget
|
||||
import ai.kilocode.client.session.ui.style.SessionUiStyle
|
||||
import ai.kilocode.client.session.controller.SessionController
|
||||
import ai.kilocode.client.ui.CenterShrinkPanel
|
||||
import ai.kilocode.client.ui.Align
|
||||
import ai.kilocode.client.ui.UiStyle
|
||||
import ai.kilocode.client.ui.alignCenter
|
||||
import ai.kilocode.rpc.dto.SessionDto
|
||||
import com.intellij.icons.AllIcons
|
||||
import com.intellij.openapi.Disposable
|
||||
@@ -44,7 +45,7 @@ import javax.swing.ListSelectionModel
|
||||
* Empty-session panel.
|
||||
*
|
||||
* The content is a BorderLayout panel, wrapped in a
|
||||
* [CenterShrinkPanel] (exposed as [view]) so callers need not know about centering.
|
||||
* [Align] (exposed as [view]) so callers need not know about centering.
|
||||
*/
|
||||
class EmptySessionPanel(
|
||||
parent: Disposable,
|
||||
@@ -52,7 +53,7 @@ class EmptySessionPanel(
|
||||
recents: List<SessionDto>,
|
||||
private val history: () -> Unit = {},
|
||||
) : BorderLayoutPanel(), Disposable, SessionEditorStyleTarget {
|
||||
val view: CenterShrinkPanel = CenterShrinkPanel(this)
|
||||
val view: Align = alignCenter()
|
||||
|
||||
private val model = DefaultListModel<LocalHistoryItem>()
|
||||
private var hover = -1
|
||||
@@ -133,7 +134,7 @@ class EmptySessionPanel(
|
||||
val header = BorderLayoutPanel(0, gap).apply {
|
||||
isOpaque = false
|
||||
add(logo, BorderLayout.NORTH)
|
||||
add(CenterShrinkPanel(description), BorderLayout.CENTER)
|
||||
add(description.alignCenter(), BorderLayout.CENTER)
|
||||
}
|
||||
|
||||
val recent = BorderLayoutPanel().apply {
|
||||
|
||||
@@ -0,0 +1,152 @@
|
||||
package ai.kilocode.client.ui
|
||||
|
||||
import java.awt.Component
|
||||
import java.awt.Dimension
|
||||
import javax.swing.JPanel
|
||||
|
||||
enum class HAlign { TRACK, FIT, LEFT, CENTER, RIGHT }
|
||||
enum class VAlign { TRACK, FIT, TOP, CENTER, BOTTOM }
|
||||
|
||||
/**
|
||||
* A transparent wrapper panel that positions its single child according to independent
|
||||
* horizontal ([h]) and vertical ([v]) alignment modes.
|
||||
*
|
||||
* **TRACK**: child fills all available space on that axis, ignoring child min/preferred/max.
|
||||
* The wrapper reports zero contribution from the child on that axis for its own min/preferred/max.
|
||||
*
|
||||
* **FIT**: child fills available space clamped to child's effective [min, max] range.
|
||||
*
|
||||
* **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.
|
||||
*
|
||||
* Wrapper min/preferred/max sizes are computed by combining the per-axis child contribution
|
||||
* (zero for TRACK axes) with the panel insets.
|
||||
*
|
||||
* Use the Kotlin-style factory extensions for concise call sites:
|
||||
* ```
|
||||
* label.alignCenter()
|
||||
* button.alignRight(VAlign.CENTER)
|
||||
* panel.align(HAlign.LEFT, VAlign.TOP)
|
||||
* content.track()
|
||||
* scrollable.trackX(VAlign.TOP)
|
||||
* ```
|
||||
*/
|
||||
class Align(
|
||||
child: Component,
|
||||
private val h: HAlign = HAlign.FIT,
|
||||
private val v: VAlign = VAlign.FIT,
|
||||
) : JPanel(null) {
|
||||
|
||||
init {
|
||||
isOpaque = false
|
||||
add(child)
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// Layout
|
||||
// -----------------------------------------------------------------------
|
||||
|
||||
override fun doLayout() {
|
||||
if (componentCount == 0) return
|
||||
val child = getComponent(0)
|
||||
val ins = insets
|
||||
val availW = maxOf(0, width - ins.left - ins.right)
|
||||
val availH = maxOf(0, height - ins.top - ins.bottom)
|
||||
|
||||
val (w, cx) = placeAxis(h, availW, child.minimumSize.width, child.preferredSize.width, child.maximumSize.width)
|
||||
val (ht, cy) = placeAxis(v, availH, child.minimumSize.height, child.preferredSize.height, child.maximumSize.height)
|
||||
|
||||
child.setBounds(ins.left + cx, ins.top + cy, w, ht)
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// Wrapper size negotiation
|
||||
// -----------------------------------------------------------------------
|
||||
|
||||
override fun getMinimumSize(): Dimension {
|
||||
if (componentCount == 0) return super.getMinimumSize()
|
||||
val child = getComponent(0)
|
||||
val ins = insets
|
||||
val cw = if (h == HAlign.TRACK) 0 else child.minimumSize.width
|
||||
val ch = if (v == VAlign.TRACK) 0 else child.minimumSize.height
|
||||
return Dimension(cw + ins.left + ins.right, ch + ins.top + ins.bottom)
|
||||
}
|
||||
|
||||
override fun getPreferredSize(): Dimension {
|
||||
if (componentCount == 0) return super.getPreferredSize()
|
||||
val child = getComponent(0)
|
||||
val ins = insets
|
||||
val cw = if (h == HAlign.TRACK) 0 else bounded(child.preferredSize.width, child.minimumSize.width, child.maximumSize.width)
|
||||
val ch = if (v == VAlign.TRACK) 0 else bounded(child.preferredSize.height, child.minimumSize.height, child.maximumSize.height)
|
||||
return Dimension(cw + ins.left + ins.right, ch + ins.top + ins.bottom)
|
||||
}
|
||||
|
||||
override fun getMaximumSize(): Dimension {
|
||||
if (componentCount == 0) return super.getMaximumSize()
|
||||
val child = getComponent(0)
|
||||
val ins = insets
|
||||
val cw = if (h == HAlign.TRACK) super.getMaximumSize().width else maxOf(child.minimumSize.width, child.maximumSize.width) + ins.left + ins.right
|
||||
val ch = if (v == VAlign.TRACK) super.getMaximumSize().height else maxOf(child.minimumSize.height, child.maximumSize.height) + ins.top + ins.bottom
|
||||
return Dimension(cw, ch)
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Internal helpers
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Returns (size, offset) for a single axis. Offset is relative to the inner origin (after insets).
|
||||
* - TRACK: size = avail, offset = 0
|
||||
* - FIT: size = clamp(avail, min, max), offset = 0
|
||||
* - edge/center: size = clamp(boundedPref, 0, avail), offset positions according to alignment
|
||||
*/
|
||||
private fun placeAxis(mode: Any, avail: Int, min: Int, pref: Int, max: Int): Pair<Int, Int> {
|
||||
val effMax = maxOf(min, max)
|
||||
return when (mode) {
|
||||
HAlign.TRACK, VAlign.TRACK -> avail to 0
|
||||
HAlign.FIT, VAlign.FIT -> {
|
||||
// fill available, capped at effMax; if avail < min we still shrink to avail
|
||||
val size = minOf(avail, effMax)
|
||||
size to 0
|
||||
}
|
||||
HAlign.LEFT, VAlign.TOP -> {
|
||||
val size = minOf(bounded(pref, min, effMax), avail)
|
||||
size to 0
|
||||
}
|
||||
HAlign.CENTER, VAlign.CENTER -> {
|
||||
val size = minOf(bounded(pref, min, effMax), avail)
|
||||
size to (avail - size) / 2
|
||||
}
|
||||
HAlign.RIGHT, VAlign.BOTTOM -> {
|
||||
val size = minOf(bounded(pref, min, effMax), avail)
|
||||
size to (avail - size)
|
||||
}
|
||||
else -> avail to 0
|
||||
}
|
||||
}
|
||||
|
||||
private fun bounded(value: Int, min: Int, max: Int) = value.coerceIn(min, maxOf(min, max))
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Kotlin-style factory extensions
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
fun Component.align(h: HAlign = HAlign.FIT, v: VAlign = VAlign.FIT) = Align(this, h, v)
|
||||
|
||||
fun Component.alignCenter() = Align(this, HAlign.CENTER, VAlign.CENTER)
|
||||
|
||||
fun Component.alignLeft(v: VAlign = VAlign.FIT) = Align(this, HAlign.LEFT, v)
|
||||
|
||||
fun Component.alignRight(v: VAlign = VAlign.FIT) = Align(this, HAlign.RIGHT, v)
|
||||
|
||||
fun Component.alignTop(h: HAlign = HAlign.FIT) = Align(this, h, VAlign.TOP)
|
||||
|
||||
fun Component.alignBottom(h: HAlign = HAlign.FIT) = Align(this, h, VAlign.BOTTOM)
|
||||
|
||||
fun Component.track() = Align(this, HAlign.TRACK, VAlign.TRACK)
|
||||
|
||||
fun Component.trackX(v: VAlign = VAlign.FIT) = Align(this, HAlign.TRACK, v)
|
||||
|
||||
fun Component.trackY(h: HAlign = HAlign.FIT) = Align(this, h, VAlign.TRACK)
|
||||
-38
@@ -1,38 +0,0 @@
|
||||
package ai.kilocode.client.ui
|
||||
|
||||
import java.awt.Component
|
||||
import java.awt.Dimension
|
||||
import javax.swing.JPanel
|
||||
|
||||
/**
|
||||
* Centers its single child and shrinks it to available space when needed.
|
||||
* If available space is larger than the child's maximum size, the child is not expanded.
|
||||
*/
|
||||
class CenterShrinkPanel(child: Component) : JPanel(null) {
|
||||
init {
|
||||
isOpaque = false
|
||||
add(child)
|
||||
}
|
||||
|
||||
override fun doLayout() {
|
||||
if (componentCount == 0) return
|
||||
val child = getComponent(0)
|
||||
val insets = getInsets()
|
||||
val availW = width - insets.left - insets.right
|
||||
val availH = height - insets.top - insets.bottom
|
||||
val pref = child.preferredSize
|
||||
val max = child.maximumSize
|
||||
val w = minOf(pref.width, max.width, availW)
|
||||
val h = minOf(pref.height, max.height, availH)
|
||||
val x = insets.left + (availW - w) / 2
|
||||
val y = insets.top + (availH - h) / 2
|
||||
child.setBounds(x, y, w, h)
|
||||
}
|
||||
|
||||
override fun getPreferredSize(): Dimension {
|
||||
if (componentCount == 0) return super.getPreferredSize()
|
||||
val pref = getComponent(0).preferredSize
|
||||
val insets = getInsets()
|
||||
return Dimension(pref.width + insets.left + insets.right, pref.height + insets.top + insets.bottom)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,396 @@
|
||||
package ai.kilocode.client.ui
|
||||
|
||||
import com.intellij.testFramework.fixtures.BasePlatformTestCase
|
||||
import com.intellij.ui.components.JBLabel
|
||||
import com.intellij.util.ui.JBUI
|
||||
import java.awt.Dimension
|
||||
|
||||
@Suppress("UnstableApiUsage")
|
||||
class AlignTest : BasePlatformTestCase() {
|
||||
|
||||
// ------ structure ------
|
||||
|
||||
fun `test wrapper is non-opaque`() {
|
||||
assertFalse(Align(JBLabel("x"), HAlign.FIT, VAlign.FIT).isOpaque)
|
||||
}
|
||||
|
||||
fun `test wrapper contains exactly the wrapped child`() {
|
||||
val child = JBLabel("x")
|
||||
val wrap = Align(child, HAlign.FIT, VAlign.FIT)
|
||||
assertEquals(1, wrap.componentCount)
|
||||
assertSame(child, wrap.getComponent(0))
|
||||
}
|
||||
|
||||
// ------ FIT / FIT basic fill ------
|
||||
|
||||
fun `test FIT FIT fills assigned inner bounds`() {
|
||||
val child = child(pref = 40 x 20)
|
||||
val wrap = Align(child, HAlign.FIT, VAlign.FIT)
|
||||
wrap.setBounds(0, 0, 200, 100)
|
||||
wrap.doLayout()
|
||||
assertBounds(0, 0, 200, 100, child)
|
||||
}
|
||||
|
||||
fun `test FIT FIT respects insets`() {
|
||||
val child = child(pref = 40 x 20)
|
||||
val wrap = Align(child, HAlign.FIT, VAlign.FIT)
|
||||
wrap.border = JBUI.Borders.empty(5, 10, 5, 10)
|
||||
wrap.setBounds(0, 0, 200, 100)
|
||||
wrap.doLayout()
|
||||
assertBounds(10, 5, 180, 90, child)
|
||||
}
|
||||
|
||||
// ------ FIT respects max ------
|
||||
|
||||
fun `test FIT FIT caps at maximum size`() {
|
||||
val child = child(pref = 40 x 20, max = 60 x 30)
|
||||
val wrap = Align(child, HAlign.FIT, VAlign.FIT)
|
||||
wrap.setBounds(0, 0, 200, 100)
|
||||
wrap.doLayout()
|
||||
// available > max → capped at max, placed at top-left
|
||||
assertBounds(0, 0, 60, 30, child)
|
||||
}
|
||||
|
||||
fun `test FIT FIT expands to minimum when available between min and pref`() {
|
||||
val child = child(min = 30 x 15, pref = 80 x 40, max = 200 x 100)
|
||||
val wrap = Align(child, HAlign.FIT, VAlign.FIT)
|
||||
wrap.setBounds(0, 0, 50, 25)
|
||||
wrap.doLayout()
|
||||
// available (50x25) is within [min, max], so child gets exactly available
|
||||
assertBounds(0, 0, 50, 25, child)
|
||||
}
|
||||
|
||||
fun `test FIT FIT shrinks to available when available below minimum`() {
|
||||
val child = child(min = 80 x 40, pref = 80 x 40)
|
||||
val wrap = Align(child, HAlign.FIT, VAlign.FIT)
|
||||
wrap.setBounds(0, 0, 30, 10)
|
||||
wrap.doLayout()
|
||||
// cannot respect min when space is smaller
|
||||
assertBounds(0, 0, 30, 10, child)
|
||||
}
|
||||
|
||||
// ------ CENTER / CENTER ------
|
||||
|
||||
fun `test CENTER CENTER centers at preferred size when space sufficient`() {
|
||||
val child = child(pref = 40 x 20)
|
||||
val wrap = Align(child, HAlign.CENTER, VAlign.CENTER)
|
||||
wrap.setBounds(0, 0, 200, 100)
|
||||
wrap.doLayout()
|
||||
assertBounds(80, 40, 40, 20, child)
|
||||
}
|
||||
|
||||
fun `test CENTER CENTER coerces preferred up to minimum`() {
|
||||
val child = child(min = 60 x 30, pref = 40 x 20)
|
||||
val wrap = Align(child, HAlign.CENTER, VAlign.CENTER)
|
||||
wrap.setBounds(0, 0, 200, 100)
|
||||
wrap.doLayout()
|
||||
// preferred < min → use min (60x30), centered
|
||||
assertBounds(70, 35, 60, 30, child)
|
||||
}
|
||||
|
||||
fun `test CENTER CENTER caps preferred at maximum`() {
|
||||
val child = child(pref = 100 x 60, max = 40 x 20)
|
||||
val wrap = Align(child, HAlign.CENTER, VAlign.CENTER)
|
||||
wrap.setBounds(0, 0, 200, 100)
|
||||
wrap.doLayout()
|
||||
// preferred > max → use max (40x20), centered
|
||||
assertBounds(80, 40, 40, 20, child)
|
||||
}
|
||||
|
||||
fun `test CENTER CENTER fits when bounded preferred exceeds available`() {
|
||||
val child = child(pref = 300 x 200)
|
||||
val wrap = Align(child, HAlign.CENTER, VAlign.CENTER)
|
||||
wrap.setBounds(0, 0, 100, 80)
|
||||
wrap.doLayout()
|
||||
assertBounds(0, 0, 100, 80, child)
|
||||
}
|
||||
|
||||
fun `test CENTER CENTER shrinks to available when available below minimum`() {
|
||||
val child = child(min = 150 x 90, pref = 150 x 90)
|
||||
val wrap = Align(child, HAlign.CENTER, VAlign.CENTER)
|
||||
wrap.setBounds(0, 0, 100, 60)
|
||||
wrap.doLayout()
|
||||
assertBounds(0, 0, 100, 60, child)
|
||||
}
|
||||
|
||||
// ------ LEFT / TOP ------
|
||||
|
||||
fun `test LEFT TOP positions at top-left with bounded preferred`() {
|
||||
val child = child(pref = 40 x 20)
|
||||
val wrap = Align(child, HAlign.LEFT, VAlign.TOP)
|
||||
wrap.setBounds(0, 0, 200, 100)
|
||||
wrap.doLayout()
|
||||
assertBounds(0, 0, 40, 20, child)
|
||||
}
|
||||
|
||||
fun `test LEFT TOP respects max`() {
|
||||
val child = child(pref = 100 x 60, max = 40 x 20)
|
||||
val wrap = Align(child, HAlign.LEFT, VAlign.TOP)
|
||||
wrap.setBounds(0, 0, 200, 100)
|
||||
wrap.doLayout()
|
||||
assertBounds(0, 0, 40, 20, child)
|
||||
}
|
||||
|
||||
fun `test LEFT TOP shrinks to available`() {
|
||||
val child = child(pref = 300 x 200)
|
||||
val wrap = Align(child, HAlign.LEFT, VAlign.TOP)
|
||||
wrap.setBounds(0, 0, 100, 80)
|
||||
wrap.doLayout()
|
||||
assertBounds(0, 0, 100, 80, child)
|
||||
}
|
||||
|
||||
// ------ RIGHT / BOTTOM ------
|
||||
|
||||
fun `test RIGHT BOTTOM positions at bottom-right with bounded preferred`() {
|
||||
val child = child(pref = 40 x 20)
|
||||
val wrap = Align(child, HAlign.RIGHT, VAlign.BOTTOM)
|
||||
wrap.setBounds(0, 0, 200, 100)
|
||||
wrap.doLayout()
|
||||
assertBounds(160, 80, 40, 20, child)
|
||||
}
|
||||
|
||||
fun `test RIGHT BOTTOM respects max`() {
|
||||
val child = child(pref = 100 x 60, max = 40 x 20)
|
||||
val wrap = Align(child, HAlign.RIGHT, VAlign.BOTTOM)
|
||||
wrap.setBounds(0, 0, 200, 100)
|
||||
wrap.doLayout()
|
||||
assertBounds(160, 80, 40, 20, child)
|
||||
}
|
||||
|
||||
fun `test RIGHT BOTTOM shrinks to available`() {
|
||||
val child = child(pref = 300 x 200)
|
||||
val wrap = Align(child, HAlign.RIGHT, VAlign.BOTTOM)
|
||||
wrap.setBounds(0, 0, 100, 80)
|
||||
wrap.doLayout()
|
||||
assertBounds(0, 0, 100, 80, child)
|
||||
}
|
||||
|
||||
// ------ insets with edge modes ------
|
||||
|
||||
fun `test CENTER CENTER insets honored`() {
|
||||
val child = child(pref = 40 x 20)
|
||||
val wrap = Align(child, HAlign.CENTER, VAlign.CENTER)
|
||||
wrap.border = JBUI.Borders.empty(10, 20, 10, 20)
|
||||
wrap.setBounds(0, 0, 200, 100)
|
||||
wrap.doLayout()
|
||||
val ins = wrap.insets // 10,20,10,20
|
||||
// inner: 160x80; child 40x20
|
||||
assertBounds(ins.left + 60, ins.top + 30, 40, 20, child)
|
||||
}
|
||||
|
||||
fun `test RIGHT BOTTOM insets honored`() {
|
||||
val child = child(pref = 40 x 20)
|
||||
val wrap = Align(child, HAlign.RIGHT, VAlign.BOTTOM)
|
||||
wrap.border = JBUI.Borders.empty(5, 5, 5, 5)
|
||||
wrap.setBounds(0, 0, 100, 80)
|
||||
wrap.doLayout()
|
||||
val ins = wrap.insets
|
||||
// inner: 90x70; child 40x20
|
||||
assertBounds(ins.left + 50, ins.top + 50, 40, 20, child)
|
||||
}
|
||||
|
||||
// ------ wrapper preferred/min/max sizes (non-TRACK) ------
|
||||
|
||||
fun `test preferredSize equals bounded child pref plus insets`() {
|
||||
val child = child(min = 30 x 15, pref = 80 x 40, max = 60 x 30)
|
||||
val wrap = Align(child, HAlign.CENTER, VAlign.CENTER)
|
||||
wrap.border = JBUI.Borders.empty(4, 6, 4, 6)
|
||||
val ins = wrap.insets
|
||||
// pref(80) coerced into [30,60] = 60; pref(40) coerced into [15,30] = 30
|
||||
val ps = wrap.preferredSize
|
||||
assertEquals(60 + ins.left + ins.right, ps.width)
|
||||
assertEquals(30 + ins.top + ins.bottom, ps.height)
|
||||
}
|
||||
|
||||
fun `test minimumSize equals child min plus insets`() {
|
||||
val child = child(min = 30 x 15, pref = 80 x 40)
|
||||
val wrap = Align(child, HAlign.LEFT, VAlign.TOP)
|
||||
wrap.border = JBUI.Borders.empty(4, 6, 4, 6)
|
||||
val ins = wrap.insets
|
||||
val ms = wrap.minimumSize
|
||||
assertEquals(30 + ins.left + ins.right, ms.width)
|
||||
assertEquals(15 + ins.top + ins.bottom, ms.height)
|
||||
}
|
||||
|
||||
fun `test maximumSize equals effective child max plus insets`() {
|
||||
val child = child(min = 30 x 15, pref = 80 x 40, max = 60 x 30)
|
||||
val wrap = Align(child, HAlign.LEFT, VAlign.TOP)
|
||||
wrap.border = JBUI.Borders.empty(4, 6, 4, 6)
|
||||
val ins = wrap.insets
|
||||
val xs = wrap.maximumSize
|
||||
assertEquals(60 + ins.left + ins.right, xs.width)
|
||||
assertEquals(30 + ins.top + ins.bottom, xs.height)
|
||||
}
|
||||
|
||||
fun `test maximumSize uses min when max is smaller than min`() {
|
||||
// max < min → effective max should be at least min
|
||||
val child = child(min = 50 x 30, pref = 50 x 30, max = 10 x 5)
|
||||
val wrap = Align(child, HAlign.LEFT, VAlign.TOP)
|
||||
val ins = wrap.insets
|
||||
val xs = wrap.maximumSize
|
||||
assertEquals(50 + ins.left + ins.right, xs.width)
|
||||
assertEquals(30 + ins.top + ins.bottom, xs.height)
|
||||
}
|
||||
|
||||
// ------ CenterShrinkPanel parity ------
|
||||
|
||||
fun `test CENTER CENTER matches old CenterShrinkPanel center-and-shrink behavior`() {
|
||||
// child pref is larger than max → should center at max size, not overflow
|
||||
val child = child(pref = 100 x 60, max = 40 x 20)
|
||||
val wrap = Align(child, HAlign.CENTER, VAlign.CENTER)
|
||||
wrap.setBounds(0, 0, 200, 100)
|
||||
wrap.doLayout()
|
||||
// expected: max(40x20), centered → x=(200-40)/2=80, y=(100-20)/2=40
|
||||
assertBounds(80, 40, 40, 20, child)
|
||||
}
|
||||
|
||||
// ------ TRACK / TRACK ------
|
||||
|
||||
fun `test TRACK TRACK fills all available regardless of child constraints`() {
|
||||
val child = child(min = 10 x 5, pref = 40 x 20, max = 60 x 30)
|
||||
val wrap = Align(child, HAlign.TRACK, VAlign.TRACK)
|
||||
wrap.setBounds(0, 0, 200, 100)
|
||||
wrap.doLayout()
|
||||
assertBounds(0, 0, 200, 100, child)
|
||||
}
|
||||
|
||||
fun `test TRACK TRACK preferred and min size are just insets`() {
|
||||
val child = child(min = 50 x 30, pref = 80 x 40, max = 100 x 60)
|
||||
val wrap = Align(child, HAlign.TRACK, VAlign.TRACK)
|
||||
wrap.border = JBUI.Borders.empty(4, 6, 4, 6)
|
||||
val ins = wrap.insets
|
||||
val ps = wrap.preferredSize
|
||||
val ms = wrap.minimumSize
|
||||
assertEquals(ins.left + ins.right, ps.width)
|
||||
assertEquals(ins.top + ins.bottom, ps.height)
|
||||
assertEquals(ins.left + ins.right, ms.width)
|
||||
assertEquals(ins.top + ins.bottom, ms.height)
|
||||
}
|
||||
|
||||
fun `test TRACK TRACK max size is not capped by child max`() {
|
||||
val child = child(pref = 40 x 20, max = 60 x 30)
|
||||
val wrap = Align(child, HAlign.TRACK, VAlign.TRACK)
|
||||
val xs = wrap.maximumSize
|
||||
// wrapper max must be larger than child max since TRACK should allow any size
|
||||
assertTrue("wrapper maxW ${xs.width} should exceed child maxW 60", xs.width > 60)
|
||||
assertTrue("wrapper maxH ${xs.height} should exceed child maxH 30", xs.height > 30)
|
||||
}
|
||||
|
||||
// ------ mixed TRACK + non-TRACK ------
|
||||
|
||||
fun `test TRACK H FIT V fills width ignores child constraints on H only`() {
|
||||
val child = child(min = 30 x 15, pref = 40 x 20, max = 60 x 30)
|
||||
val wrap = Align(child, HAlign.TRACK, VAlign.FIT)
|
||||
wrap.setBounds(0, 0, 200, 100)
|
||||
wrap.doLayout()
|
||||
// H=TRACK → width=200; V=FIT → height clamped to [15,30]=30
|
||||
assertBounds(0, 0, 200, 30, child)
|
||||
}
|
||||
|
||||
fun `test TRACK H preferred is inset-only on H axis with child bounded pref on V axis`() {
|
||||
val child = child(min = 30 x 15, pref = 80 x 40, max = 60 x 30)
|
||||
val wrap = Align(child, HAlign.TRACK, VAlign.CENTER)
|
||||
val ins = wrap.insets
|
||||
val ps = wrap.preferredSize
|
||||
// H=TRACK → horizontal contribution = 0
|
||||
assertEquals(ins.left + ins.right, ps.width)
|
||||
// V=CENTER → bounded pref height = clamp(40,[15,30]) = 30
|
||||
assertEquals(30 + ins.top + ins.bottom, ps.height)
|
||||
}
|
||||
|
||||
// ------ factory helpers ------
|
||||
|
||||
fun `test align extension returns Align wrapping child`() {
|
||||
val child = JBLabel("x")
|
||||
assertSame(child, child.align(HAlign.LEFT, VAlign.TOP).getComponent(0))
|
||||
}
|
||||
|
||||
fun `test alignCenter produces CENTER CENTER`() {
|
||||
val child = child(pref = 40 x 20)
|
||||
val wrap = child.alignCenter()
|
||||
wrap.setBounds(0, 0, 200, 100)
|
||||
wrap.doLayout()
|
||||
assertBounds(80, 40, 40, 20, child)
|
||||
}
|
||||
|
||||
fun `test alignRight produces RIGHT with given VAlign`() {
|
||||
val child = child(pref = 40 x 20)
|
||||
val wrap = child.alignRight(VAlign.TOP)
|
||||
wrap.setBounds(0, 0, 200, 100)
|
||||
wrap.doLayout()
|
||||
assertBounds(160, 0, 40, 20, child)
|
||||
}
|
||||
|
||||
fun `test alignLeft with default FIT vertical fills height`() {
|
||||
val child = child(pref = 40 x 20)
|
||||
val wrap = child.alignLeft()
|
||||
wrap.setBounds(0, 0, 200, 100)
|
||||
wrap.doLayout()
|
||||
assertBounds(0, 0, 40, 100, child)
|
||||
}
|
||||
|
||||
fun `test alignTop with CENTER horizontal centers and pins to top`() {
|
||||
val child = child(pref = 40 x 20)
|
||||
val wrap = child.alignTop(HAlign.CENTER)
|
||||
wrap.setBounds(0, 0, 200, 100)
|
||||
wrap.doLayout()
|
||||
assertBounds(80, 0, 40, 20, child)
|
||||
}
|
||||
|
||||
fun `test alignBottom with default FIT horizontal fills width and pins to bottom`() {
|
||||
val child = child(pref = 40 x 20)
|
||||
val wrap = child.alignBottom()
|
||||
wrap.setBounds(0, 0, 200, 100)
|
||||
wrap.doLayout()
|
||||
assertBounds(0, 80, 200, 20, child)
|
||||
}
|
||||
|
||||
fun `test track fills all space and wrapper preferred is inset-only`() {
|
||||
val child = child(pref = 40 x 20, max = 60 x 30)
|
||||
val wrap = child.track()
|
||||
wrap.setBounds(0, 0, 200, 100)
|
||||
wrap.doLayout()
|
||||
assertBounds(0, 0, 200, 100, child)
|
||||
val ins = wrap.insets
|
||||
assertEquals(ins.left + ins.right, wrap.preferredSize.width)
|
||||
assertEquals(ins.top + ins.bottom, wrap.preferredSize.height)
|
||||
}
|
||||
|
||||
fun `test trackX fills width only, V respects preferred`() {
|
||||
val child = child(pref = 40 x 20)
|
||||
val wrap = child.trackX(VAlign.TOP)
|
||||
wrap.setBounds(0, 0, 200, 100)
|
||||
wrap.doLayout()
|
||||
assertBounds(0, 0, 200, 20, child)
|
||||
}
|
||||
|
||||
fun `test trackY fills height only, H respects preferred`() {
|
||||
val child = child(pref = 40 x 20)
|
||||
val wrap = child.trackY(HAlign.CENTER)
|
||||
wrap.setBounds(0, 0, 200, 100)
|
||||
wrap.doLayout()
|
||||
assertBounds(80, 0, 40, 100, child)
|
||||
}
|
||||
|
||||
// ------ helpers ------
|
||||
|
||||
private infix fun Int.x(h: Int) = Dimension(this, h)
|
||||
|
||||
private fun child(
|
||||
min: Dimension = Dimension(0, 0),
|
||||
pref: Dimension,
|
||||
max: Dimension = Dimension(Int.MAX_VALUE, Int.MAX_VALUE),
|
||||
) = object : JBLabel("x") {
|
||||
override fun getMinimumSize() = min
|
||||
override fun getPreferredSize() = pref
|
||||
override fun getMaximumSize() = max
|
||||
}
|
||||
|
||||
private fun assertBounds(x: Int, y: Int, w: Int, h: Int, c: java.awt.Component) {
|
||||
val b = c.bounds
|
||||
assertEquals("x", x, b.x)
|
||||
assertEquals("y", y, b.y)
|
||||
assertEquals("width", w, b.width)
|
||||
assertEquals("height", h, b.height)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user