IDEA-299042 Run widget popup: inline actions have incorrect width
[idea/community.git] / platform / platform-impl / src / com / intellij / ui / popup / list / PopupInlineActionsSupportImpl.kt
1 // Copyright 2000-2022 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
2 package com.intellij.ui.popup.list
3
4 import com.intellij.icons.AllIcons
5 import com.intellij.openapi.actionSystem.AnAction
6 import com.intellij.ui.ExperimentalUI
7 import com.intellij.ui.components.panels.Wrapper
8 import com.intellij.ui.popup.ActionPopupStep
9 import com.intellij.ui.popup.PopupFactoryImpl.ActionItem
10 import com.intellij.ui.popup.PopupFactoryImpl.InlineActionItem
11 import com.intellij.ui.popup.list.ListPopupImpl.ListWithInlineButtons
12 import com.intellij.ui.scale.JBUIScale
13 import com.intellij.util.ui.JBInsets
14 import com.intellij.util.ui.JBUI
15 import java.awt.Point
16 import java.awt.event.InputEvent
17 import javax.swing.*
18
19 private const val INLINE_BUTTON_WIDTH = 16
20
21 class PopupInlineActionsSupportImpl(private val myListPopup: ListPopupImpl) : PopupInlineActionsSupport {
22
23   private val myStep = myListPopup.listStep as ActionPopupStep
24
25   override fun hasExtraButtons(element: Any): Boolean = calcExtraButtonsCount(element) > 0
26
27   override fun calcExtraButtonsCount(element: Any): Int {
28     if (!ExperimentalUI.isNewUI() || element !is ActionItem) return 0
29
30     var res = 0
31     res += myStep.getInlineActions(element).size
32     if (res != 0 && myStep.hasSubstep(element)) res++
33     return res
34   }
35
36   override fun calcButtonIndex(element: Any?, point: Point): Int? {
37     if (element == null) return null
38     val list = myListPopup.list
39     val index = list.selectedIndex
40     val bounds = list.getCellBounds(index, index) ?: return null
41     JBInsets.removeFrom(bounds, PopupListElementRenderer.getListCellPadding())
42
43     val buttonsCount: Int = calcExtraButtonsCount(element)
44     if (buttonsCount <= 0) return null
45
46     val distanceToRight = bounds.x + bounds.width - point.x
47     val buttonsToRight = distanceToRight / buttonWidth()
48     if (buttonsToRight >= buttonsCount) return null
49
50     return buttonsCount - buttonsToRight - 1
51   }
52
53   override fun runInlineAction(element: Any, index: Int, event: InputEvent?) = getExtraButtonsActions(element, event)[index].run()
54
55   private fun getExtraButtonsActions(element: Any, event: InputEvent?): List<Runnable> {
56     if (!ExperimentalUI.isNewUI() || element !is ActionItem) return emptyList()
57
58     val res: MutableList<Runnable> = ArrayList()
59
60     res.addAll(myStep.getInlineActions(element).map {
61       item: InlineActionItem -> createInlineActionRunnable(item.action, event)
62     })
63     if (!res.isEmpty() && myStep.hasSubstep(element)) res.add(createNextStepRunnable(element))
64     return res
65   }
66
67   override fun getExtraButtons(list: JList<*>, value: Any, isSelected: Boolean): List<JComponent> {
68     if (value !is ActionItem) return emptyList()
69     val inlineActions = myStep.getInlineActions(value)
70     if (inlineActions.isEmpty()) return emptyList()
71
72     val res: MutableList<JComponent> = java.util.ArrayList()
73     val activeIndex = getActiveButtonIndex(list)
74
75     for (i in 0 until inlineActions.size) res.add(createActionButton(inlineActions[i], i == activeIndex, isSelected))
76     res.add(createSubmenuButton(value, res.size == activeIndex))
77
78     return res
79   }
80
81   private fun getActiveButtonIndex(list: JList<*>): Int? = (list as? ListWithInlineButtons)?.selectedButtonIndex
82
83   private fun createSubmenuButton(value: ActionItem, active: Boolean): JComponent {
84     val icon = if (myStep.isFinal(value)) AllIcons.Actions.More else AllIcons.Icons.Ide.MenuArrow
85     return createExtraButton(icon, active)
86   }
87
88
89   private fun createActionButton(action: InlineActionItem, active: Boolean, isSelected: Boolean): JComponent =
90     createExtraButton(action.getIcon(isSelected), active)
91
92   private fun createExtraButton(icon: Icon, active: Boolean): JComponent {
93     val label = JLabel(icon)
94     val leftRightInsets = JBUI.CurrentTheme.List.buttonLeftRightInsets()
95     label.border = JBUI.Borders.empty(0, leftRightInsets)
96     val panel = Wrapper(label)
97     val size = panel.preferredSize
98     size.width = buttonWidth(leftRightInsets)
99     panel.preferredSize = size
100     panel.minimumSize = size
101     panel.isOpaque = active
102     panel.background = JBUI.CurrentTheme.Table.Hover.background(true)
103     return panel
104   }
105
106   private fun buttonWidth(leftRightInsets: Int = JBUI.CurrentTheme.List.buttonLeftRightInsets()) : Int = JBUIScale.scale(INLINE_BUTTON_WIDTH + leftRightInsets * 2)
107
108   private fun createNextStepRunnable(element: ActionItem) = Runnable { myListPopup.showNextStepPopup(myStep.onChosen(element, false), element) }
109
110   private fun createInlineActionRunnable(action: AnAction, inputEvent: InputEvent?) = Runnable { myStep.performAction(action, inputEvent) }
111 }