IJPL-148995 Support speed search highlighting in UI DSL listCellRenderer

GitOrigin-RevId: 09cdeffefe6c289ccdeffce42614cb2e65980515
This commit is contained in:
Pavel Porvatov
2024-05-02 20:16:15 +02:00
committed by intellij-monorepo-bot
parent b04e206abc
commit 5fcb10fa31
13 changed files with 241 additions and 189 deletions

View File

@@ -9565,9 +9565,11 @@ f:com.intellij.ui.dsl.listCellRenderer.BuilderKt
- f:getFont():java.awt.Font
- f:getForeground():java.awt.Color
- f:getGreyForeground():java.awt.Color
- f:getSpeedSearchHighlighting():Z
- f:setAttributes(com.intellij.ui.SimpleTextAttributes):V
- f:setFont(java.awt.Font):V
- f:setForeground(java.awt.Color):V
- f:setSpeedSearchHighlighting(Z):V
f:com.intellij.ui.dsl.listCellRenderer.LcrUtilsKt
- sf:stripHorizontalInsets(javax.swing.JComponent):V
com.intellij.ui.dsl.listCellRenderer.UiDslRendererProvider

View File

@@ -15,7 +15,7 @@ sealed class LcrInitParams {
}
/**
* If specified then the cell occupies all available free space (so next cells will be near right edge) and the content of the cell
* If specified then the cell occupies all available free space (so next cells will be near right edge), and the content of the cell
* is placed according to the [align] value
*/
var align: Align? = null

View File

@@ -12,7 +12,7 @@ import javax.swing.UIManager
class LcrTextInitParams(foreground: Color) : LcrInitParams() {
/**
* A grey text, that is usually used for non-primary information in renderers
* A gray text, that is usually used for non-primary information in renderers
*/
val greyForeground: Color
get() = NamedColorUtil.getInactiveTextColor()
@@ -30,4 +30,9 @@ class LcrTextInitParams(foreground: Color) : LcrInitParams() {
var attributes: SimpleTextAttributes? = null
var font: Font? = UIManager.getFont("Label.font")
/**
* true if the text is used by speed search and therefore should be highlighted while searching
*/
var speedSearchHighlighting: Boolean = true
}

View File

@@ -15,7 +15,6 @@ internal sealed class LcrCellBaseImpl<T: LcrInitParams>(val initParams: T, val b
enum class Type(private val instanceFactory: () -> JComponent) {
ICON(::JLabel),
TEXT(::JLabel),
SIMPLE_COLORED_TEXT(::PatchedSimpleColoredComponent);
private val instance = lazy { instanceFactory() }

View File

@@ -11,6 +11,7 @@ import com.intellij.ui.dsl.gridLayout.builders.RowsGridBuilder
import com.intellij.ui.dsl.listCellRenderer.*
import com.intellij.ui.popup.list.SelectablePanel
import com.intellij.ui.render.RenderingUtil
import com.intellij.ui.speedSearch.SpeedSearchUtil
import com.intellij.util.ui.JBInsets
import com.intellij.util.ui.JBUI
import com.intellij.util.ui.UIUtil
@@ -72,12 +73,7 @@ open class LcrRowImpl<T>(private val renderer: LcrRow<T>.() -> Unit) : LcrRow<T>
initParams.init()
}
if (initParams.attributes == null) {
add(LcrTextImpl(initParams, true, gap, text, selected, foreground))
}
else {
add(LcrSimpleColoredTextImpl(initParams, true, gap, text, selected, foreground))
}
add(LcrSimpleColoredTextImpl(initParams, true, gap, text, selected, foreground))
}
override fun getListCellRendererComponent(list: JList<out T>,
@@ -138,6 +134,10 @@ open class LcrRowImpl<T>(private val renderer: LcrRow<T>.() -> Unit) : LcrRow<T>
else {
result.contentLayout.rootGrid.resizableColumns -= i
}
if (cell is LcrSimpleColoredTextImpl && cell.initParams.speedSearchHighlighting) {
SpeedSearchUtil.applySpeedSearchHighlighting(list, component as SimpleColoredComponent, true, isSelected)
}
}
result.accessibleContext.accessibleName = getAccessibleName(result.content.components)

View File

@@ -26,7 +26,8 @@ internal class LcrSimpleColoredTextImpl(initParams: LcrTextInitParams, baselineA
component as SimpleColoredComponent
component.clear()
component.font = initParams.font
val attributes = if (selected) SimpleTextAttributes(initParams.attributes!!.style, rowForeground) else initParams.attributes!!
val baseAttributes = initParams.attributes ?: SimpleTextAttributes(SimpleTextAttributes.STYLE_PLAIN, initParams.foreground)
val attributes = if (selected) SimpleTextAttributes(baseAttributes.style, rowForeground) else baseAttributes
component.append(text, attributes)
component.accessibleContext.accessibleName = initParams.accessibleName
}

View File

@@ -1,31 +0,0 @@
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.ui.dsl.listCellRenderer.impl
import com.intellij.ui.dsl.checkTrue
import com.intellij.ui.dsl.listCellRenderer.LcrRow
import com.intellij.ui.dsl.listCellRenderer.LcrTextInitParams
import org.jetbrains.annotations.ApiStatus
import org.jetbrains.annotations.Nls
import java.awt.Color
import javax.swing.JComponent
import javax.swing.JLabel
@ApiStatus.Internal
internal class LcrTextImpl(initParams: LcrTextInitParams, baselineAlign: Boolean, beforeGap: LcrRow.Gap,
private val text: @Nls String,
private val selected: Boolean,
private val rowForeground: Color) :
LcrCellBaseImpl<LcrTextInitParams>(initParams, baselineAlign, beforeGap) {
override val type = Type.TEXT
override fun apply(component: JComponent) {
checkTrue(type.isInstance(component))
component as JLabel
component.text = text
component.font = initParams.font
component.foreground = if (selected) rowForeground else initParams.foreground
component.accessibleContext.accessibleName = initParams.accessibleName
}
}

View File

@@ -8,6 +8,9 @@ import com.intellij.ide.util.treeView.NodeRenderer
import com.intellij.internal.showSources
import com.intellij.internal.ui.sandbox.components.*
import com.intellij.internal.ui.sandbox.dsl.*
import com.intellij.internal.ui.sandbox.dsl.listCellRenderer.LcrComboBoxPanel
import com.intellij.internal.ui.sandbox.dsl.listCellRenderer.LcrListPanel
import com.intellij.internal.ui.sandbox.dsl.listCellRenderer.LcrOthersPanel
import com.intellij.internal.ui.sandbox.tests.components.JBTextAreaTestPanel
import com.intellij.openapi.Disposable
import com.intellij.openapi.project.Project
@@ -62,13 +65,18 @@ internal class UISandboxDialog(private val project: Project?) : DialogWrapper(pr
JBTabsPanel())),
Group("Kotlin UI DSL", children = listOf(
Group("ListCellRenderer", children = listOf(
LcrListPanel(),
LcrComboBoxPanel(),
LcrOthersPanel()
)),
CellsWithSubPanelsPanel(),
CheckBoxRadioButtonPanel(),
CommentsPanel(),
DeprecatedApiPanel(),
GroupsPanel(),
LabelsPanel(),
ListCellRendererPanel(),
LongTextsPanel(),
OnChangePanel(),
OthersPanel(),

View File

@@ -1,140 +0,0 @@
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.internal.ui.sandbox.dsl
import com.intellij.icons.AllIcons
import com.intellij.internal.ui.sandbox.UISandboxPanel
import com.intellij.openapi.Disposable
import com.intellij.ui.SimpleListCellRenderer
import com.intellij.ui.SimpleTextAttributes
import com.intellij.ui.components.JBList
import com.intellij.ui.components.JBScrollPane
import com.intellij.ui.dsl.builder.*
import com.intellij.ui.dsl.listCellRenderer.LcrInitParams
import com.intellij.ui.dsl.listCellRenderer.listCellRenderer
import com.intellij.ui.dsl.listCellRenderer.textListCellRenderer
import com.intellij.ui.layout.selected
import com.intellij.util.ui.JBDimension
import com.intellij.util.ui.UIUtil
import java.awt.Color
import javax.swing.JCheckBox
import javax.swing.JComponent
import javax.swing.ListCellRenderer
@Suppress("UseJBColor")
internal class ListCellRendererPanel : UISandboxPanel {
override val title: String = "ListCellRenderer"
override fun createContent(disposable: Disposable): JComponent {
return panel {
group("JBList") {
row {
jbList(listOf("First", "Second", "Last"), textListCellRenderer { it })
jbList((1..99).map { "Item $it" }, textListCellRenderer { it })
jbList((1..99).toList(), listCellRenderer {
icon(if (index % 2 == 0) AllIcons.General.Add else AllIcons.General.Gear)
text("Item $value")
}).label("Icons", LabelPosition.TOP)
val colors = listOf(UIUtil.getLabelForeground(),
Color.GREEN,
Color.MAGENTA)
val styles = listOf(SimpleTextAttributes.STYLE_PLAIN,
SimpleTextAttributes.STYLE_BOLD,
SimpleTextAttributes.STYLE_ITALIC)
jbList((1..99).toList(), listCellRenderer {
val i = index % colors.size
text("Item $value") {
if (i > 0) {
foreground = colors[i]
}
}
}).label("Foreground", LabelPosition.TOP)
jbList((1..99).toList(), listCellRenderer {
val i = index % colors.size
text("Item $value") {
attributes = SimpleTextAttributes(styles[i], colors[i])
}
}).label("Attributes", LabelPosition.TOP)
jbList((1..99).toList(), listCellRenderer {
val i = index % colors.size
if (i > 0) {
background = colors[i]
}
text("Item $value")
}).label("Background", LabelPosition.TOP)
val aligns = listOf(LcrInitParams.Align.LEFT, LcrInitParams.Align.CENTER, LcrInitParams.Align.RIGHT)
jbList((1..99).toList(), listCellRenderer {
val customAlign = aligns.getOrNull(index % (aligns.size + 1))
text("$value: $customAlign") {
align = customAlign
}
}).label("Align", LabelPosition.TOP)
.align(Align.FILL)
}
}
group("ComboBox") {
lateinit var enabled: JCheckBox
row {
enabled = checkBox("Enabled")
.selected(true)
.component
}
indent {
row("Empty:") {
comboBox(emptyList<String>(), textListCellRenderer { it })
}
row("No selection:") {
comboBox(listOf("First", "Second", "Last"), textListCellRenderer { it })
.applyToComponent { selectedItem = null }
}
row("Few items:") {
comboBox(listOf("First", "Second", "Try with y", "Try with ()"), textListCellRenderer { it })
}
row("Items with icon:") {
comboBox((1..100).map { "Item $it" }, listCellRenderer {
icon(if (index % 2 == 0) AllIcons.General.Add else AllIcons.General.Gear)
text(value ?: "")
})
}
row("Long items:") {
comboBox((1..100).map { "$it " + "Item".repeat(10) }, textListCellRenderer { it }).component
}
}.enabledIf(enabled.selected)
}
group("Renderers") {
row("iconListCellRenderer:") {
val renderer = listCellRenderer {
icon(AllIcons.General.Gear)
text(value)
}
cell(renderer.getListCellRendererComponent(JBList(), "Some text", 0, false, false) as JComponent)
}
row("SimpleListCellRenderer:") {
val renderer = SimpleListCellRenderer.create<String> { label, value, _ ->
label.icon = AllIcons.General.Gear
label.text = value
}
cell(renderer.getListCellRendererComponent(JBList(), "Some text", 0, false, false) as JComponent)
}
}
}
}
}
private fun <T> Row.jbList(items: List<T>, renderer: ListCellRenderer<T>): Cell<JBScrollPane> {
val list = JBList(items)
list.setCellRenderer(renderer)
val scroll = JBScrollPane(list)
scroll.minimumSize = JBDimension(100, 200)
scroll.isOverlappingScrollBar = true
return cell(scroll)
}

View File

@@ -0,0 +1,54 @@
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
@file:ApiStatus.Internal
package com.intellij.internal.ui.sandbox.dsl.listCellRenderer
import com.intellij.icons.AllIcons
import com.intellij.internal.ui.sandbox.UISandboxPanel
import com.intellij.openapi.Disposable
import com.intellij.ui.dsl.builder.panel
import com.intellij.ui.dsl.builder.selected
import com.intellij.ui.dsl.listCellRenderer.listCellRenderer
import com.intellij.ui.dsl.listCellRenderer.textListCellRenderer
import com.intellij.ui.layout.selected
import org.jetbrains.annotations.ApiStatus
import javax.swing.JCheckBox
import javax.swing.JComponent
internal class LcrComboBoxPanel : UISandboxPanel {
override val title: String = "ComboBox"
override fun createContent(disposable: Disposable): JComponent {
return panel {
lateinit var enabled: JCheckBox
row {
enabled = checkBox("Enabled")
.selected(true)
.component
}
indent {
row("Empty:") {
comboBox(emptyList<String>(), textListCellRenderer { it })
}
row("No selection:") {
comboBox(listOf("First", "Second", "Last"), textListCellRenderer { it })
.applyToComponent { selectedItem = null }
}
row("Few items:") {
comboBox(listOf("First", "Second", "Try with y", "Try with ()"), textListCellRenderer { it })
}
row("Items with icon:") {
comboBox((1..100).map { "Item $it" }, listCellRenderer {
icon(if (index % 2 == 0) AllIcons.General.Add else AllIcons.General.Gear)
text(value ?: "")
})
}
row("Long items:") {
comboBox((1..100).map { "$it " + "Item".repeat(10) }, textListCellRenderer { it }).component
}
}.enabledIf(enabled.selected)
}
}
}

View File

@@ -0,0 +1,112 @@
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
@file:ApiStatus.Internal
package com.intellij.internal.ui.sandbox.dsl.listCellRenderer
import com.intellij.icons.AllIcons
import com.intellij.internal.ui.sandbox.UISandboxPanel
import com.intellij.openapi.Disposable
import com.intellij.ui.JBColor
import com.intellij.ui.SimpleTextAttributes
import com.intellij.ui.TreeUIHelper
import com.intellij.ui.components.JBList
import com.intellij.ui.components.JBScrollPane
import com.intellij.ui.dsl.builder.*
import com.intellij.ui.dsl.listCellRenderer.LcrInitParams
import com.intellij.ui.dsl.listCellRenderer.listCellRenderer
import com.intellij.ui.dsl.listCellRenderer.textListCellRenderer
import com.intellij.util.ui.JBDimension
import com.intellij.util.ui.UIUtil
import org.jetbrains.annotations.ApiStatus
import javax.swing.JComponent
import javax.swing.ListCellRenderer
internal class LcrListPanel : UISandboxPanel {
override val title: String = "List"
override fun createContent(disposable: Disposable): JComponent {
return panel {
row {
jbList(listOf("First", "Second", "Last"), textListCellRenderer { it })
jbList((1..99).map { "Item $it" }, textListCellRenderer { it })
jbList((1..99).toList(), listCellRenderer {
icon(if (index % 2 == 0) AllIcons.General.Add else AllIcons.General.Gear)
text("Item $value")
}).label("Icons", LabelPosition.TOP)
val aligns = listOf(LcrInitParams.Align.LEFT, LcrInitParams.Align.CENTER, LcrInitParams.Align.RIGHT)
jbList((1..99).toList(), listCellRenderer {
val customAlign = aligns.getOrNull(index % (aligns.size + 1))
text("$value: $customAlign") {
align = customAlign
}
}).label("Align", LabelPosition.TOP)
.align(Align.FILL)
}
row {
val colors = listOf(UIUtil.getLabelForeground(),
JBColor.GREEN,
JBColor.MAGENTA)
val styles = listOf(SimpleTextAttributes.STYLE_PLAIN,
SimpleTextAttributes.STYLE_BOLD,
SimpleTextAttributes.STYLE_ITALIC)
jbList((1..99).toList(), listCellRenderer {
val i = index % colors.size
text("Item $value") {
if (i > 0) {
foreground = colors[i]
}
}
}).label("Foreground", LabelPosition.TOP)
jbList((1..99).toList(), listCellRenderer {
val i = index % colors.size
text("Item $value") {
attributes = SimpleTextAttributes(styles[i], colors[i])
}
}).label("Attributes", LabelPosition.TOP)
jbList((1..99).toList(), listCellRenderer {
val i = index % colors.size
if (i > 0) {
background = colors[i]
}
text("Item $value")
}).label("Background", LabelPosition.TOP)
}
row {
@Suppress("UNCHECKED_CAST")
val list = jbList((1..99).toList(), listCellRenderer {
text("Item $value") {
align = LcrInitParams.Align.LEFT
}
text("Not searchable text") {
foreground = greyForeground
speedSearchHighlighting = false
}
}).label("Speed search:", LabelPosition.TOP)
.applyToComponent {
minimumSize = JBDimension(100, 300)
}
.component.viewport.view as JBList<Int>
TreeUIHelper.getInstance().installListSpeedSearch(list) {
"Item $it"
}
}
}
}
}
private fun <T> Row.jbList(items: List<T>, renderer: ListCellRenderer<T>): Cell<JBScrollPane> {
val list = JBList(items)
list.setCellRenderer(renderer)
val scroll = JBScrollPane(list)
scroll.minimumSize = JBDimension(100, 200)
scroll.isOverlappingScrollBar = true
return cell(scroll)
}

View File

@@ -0,0 +1,42 @@
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
@file:ApiStatus.Internal
package com.intellij.internal.ui.sandbox.dsl.listCellRenderer
import com.intellij.icons.AllIcons
import com.intellij.internal.ui.sandbox.UISandboxPanel
import com.intellij.openapi.Disposable
import com.intellij.ui.SimpleListCellRenderer
import com.intellij.ui.components.JBList
import com.intellij.ui.dsl.builder.panel
import com.intellij.ui.dsl.listCellRenderer.listCellRenderer
import org.jetbrains.annotations.ApiStatus
import javax.swing.JComponent
internal class LcrOthersPanel : UISandboxPanel {
override val title: String = "Others"
override fun createContent(disposable: Disposable): JComponent {
return panel {
group("Renderers") {
row("iconListCellRenderer:") {
val renderer = listCellRenderer {
icon(AllIcons.General.Gear)
text(value)
}
cell(renderer.getListCellRendererComponent(JBList(), "Some text", 0, false, false) as JComponent)
}
row("SimpleListCellRenderer:") {
val renderer = SimpleListCellRenderer.create<String> { label, value, _ ->
label.icon = AllIcons.General.Gear
label.text = value
}
cell(renderer.getListCellRendererComponent(JBList(), "Some text", 0, false, false) as JComponent)
}
}
}
}
}

View File

@@ -3,13 +3,13 @@ package com.intellij.ui.dsl.listCellRenderer
import com.intellij.icons.AllIcons
import com.intellij.testFramework.TestApplicationManager
import com.intellij.ui.SimpleColoredComponent
import com.intellij.ui.components.JBList
import com.intellij.ui.scale.JBUIScale
import com.intellij.util.ui.UIUtil
import org.junit.Before
import org.junit.Test
import java.awt.Component
import javax.swing.JLabel
import javax.swing.JList
import kotlin.test.assertEquals
import kotlin.test.assertTrue
@@ -50,9 +50,9 @@ class BuilderTest {
val component = renderer.getListCellRendererComponent(list, 1, 0, false, false)
setRendererSize(component, 100, 100)
val a = findLabel(component, "A")
val b = findLabel(component, "B")
val c = findLabel(component, "C")
val a = findTextComponent(component, "A")
val b = findTextComponent(component, "B")
val c = findTextComponent(component, "C")
assertTrue(a.x >= 0)
assertEquals(a.x + a.width + DEFAULT_GAP, b.x)
@@ -92,9 +92,9 @@ class BuilderTest {
UIUtil.uiTraverser(component).forEach { it.doLayout() }
}
private fun findLabel(root: Component, text: String): JLabel {
val result = UIUtil.uiTraverser(root).filter { it is JLabel && it.text == text }.toList()
private fun findTextComponent(root: Component, text: String): SimpleColoredComponent {
val result = UIUtil.uiTraverser(root).filter { it is SimpleColoredComponent && it.getCharSequence(false).toString() == text }.toList()
assertEquals(result.size, 1)
return result[0] as JLabel
return result[0] as SimpleColoredComponent
}
}