mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
IJPL-248623 Use platform dropdown link for prompt launch settings
The inline launch-settings selector previously painted its own trailing arrow, which split the visual state from platform hit testing. Reuse DropDownLink for the text and chevron, and keep the provider icon as a small sibling label that delegates to the same popup action. This preserves the compact composer tray layout while matching the existing combo-like link behavior. Cover the icon-click path in the palette structure test and update the behavior spec. GitOrigin-RevId: f3b37c5869f23e06c495d5b9f84e32205c5d0b08
This commit is contained in:
committed by
intellij-monorepo-bot
parent
202f9bf836
commit
f3e1850d2c
@@ -28,6 +28,7 @@ import com.intellij.openapi.project.DumbAwareToggleAction
|
||||
import com.intellij.ui.EditorTextField
|
||||
import com.intellij.ui.WindowMoveListener
|
||||
import com.intellij.ui.components.ActionLink
|
||||
import com.intellij.ui.components.DropDownLink
|
||||
import com.intellij.ui.components.JBLabel
|
||||
import com.intellij.ui.components.JBList
|
||||
import com.intellij.ui.components.JBScrollPane
|
||||
@@ -52,7 +53,6 @@ import java.awt.Container
|
||||
import java.awt.Cursor
|
||||
import java.awt.Dimension
|
||||
import java.awt.FlowLayout
|
||||
import java.awt.Graphics
|
||||
import java.awt.event.ContainerAdapter
|
||||
import java.awt.event.ContainerEvent
|
||||
import java.awt.event.MouseAdapter
|
||||
@@ -317,12 +317,6 @@ internal class AgentPromptToolbarIconToggleAction(
|
||||
internal class HeaderActionLink(text: @Nls String) : ActionLink(text) {
|
||||
var onVisibilityChanged: (() -> Unit)? = null
|
||||
var onPresentationChanged: (() -> Unit)? = null
|
||||
var trailingIcon: Icon? = null
|
||||
set(value) {
|
||||
field = value
|
||||
revalidate()
|
||||
repaint()
|
||||
}
|
||||
|
||||
override fun setVisible(aFlag: Boolean) {
|
||||
val visibilityChanged = isVisible != aFlag
|
||||
@@ -339,32 +333,14 @@ internal class HeaderActionLink(text: @Nls String) : ActionLink(text) {
|
||||
onPresentationChanged?.invoke()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun getPreferredSize(): Dimension {
|
||||
return super.getPreferredSize().withTrailingIconWidth()
|
||||
}
|
||||
|
||||
override fun getMinimumSize(): Dimension {
|
||||
return super.getMinimumSize().withTrailingIconWidth()
|
||||
}
|
||||
|
||||
override fun paintComponent(g: Graphics) {
|
||||
super.paintComponent(g)
|
||||
val icon = trailingIcon ?: return
|
||||
val x = if (componentOrientation.isLeftToRight) {
|
||||
width - insets.right - icon.iconWidth
|
||||
}
|
||||
else {
|
||||
insets.left
|
||||
}
|
||||
val y = (height - icon.iconHeight) / 2
|
||||
icon.paintIcon(this, g, x, y)
|
||||
}
|
||||
|
||||
private fun Dimension.withTrailingIconWidth(): Dimension {
|
||||
val icon = trailingIcon ?: return this
|
||||
width += icon.iconWidth + iconTextGap
|
||||
return this
|
||||
internal class HeaderDropDownLink(
|
||||
text: @Nls String,
|
||||
private val onPerform: (HeaderDropDownLink) -> Unit,
|
||||
) : DropDownLink<String>(text, { error("HeaderDropDownLink handles popup display in performAction") }) {
|
||||
override fun performAction() {
|
||||
onPerform(this)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -383,23 +359,42 @@ internal class AgentPromptToolbarProfileAction(
|
||||
var onPresentationChanged: (() -> Unit)? = null
|
||||
|
||||
@JvmField
|
||||
val link: HeaderActionLink = HeaderActionLink(initialText).apply {
|
||||
val link: HeaderDropDownLink = HeaderDropDownLink(initialText) { source ->
|
||||
popupHandler.invoke(DataManager.getInstance().getDataContext(source), source)
|
||||
}.apply {
|
||||
autoHideOnDisable = false
|
||||
withFont(JBUI.Fonts.smallFont().asPlain())
|
||||
foreground = UIUtil.getContextHelpForeground()
|
||||
border = JBUI.Borders.empty()
|
||||
setIcon(initialIcon, false)
|
||||
trailingIcon = AllIcons.General.LinkDropTriangle
|
||||
setToolTipText(HtmlChunk.text(initialDescription))
|
||||
accessibleContext.accessibleName = initialText
|
||||
accessibleContext.accessibleDescription = initialDescription
|
||||
addActionListener {
|
||||
popupHandler.invoke(DataManager.getInstance().getDataContext(this), this)
|
||||
}
|
||||
}
|
||||
|
||||
val customComponent: JComponent
|
||||
get() = link
|
||||
@JvmField
|
||||
val iconLabel: JLabel = JLabel(initialIcon).apply {
|
||||
cursor = Cursor.getPredefinedCursor(Cursor.HAND_CURSOR)
|
||||
border = JBUI.Borders.emptyRight(4)
|
||||
labelFor = link
|
||||
setToolTipText(HtmlChunk.text(initialDescription))
|
||||
accessibleContext.accessibleName = initialText
|
||||
accessibleContext.accessibleDescription = initialDescription
|
||||
addMouseListener(object : MouseAdapter() {
|
||||
override fun mousePressed(e: MouseEvent) {
|
||||
if (e.button == MouseEvent.BUTTON1 && link.isEnabled) {
|
||||
link.doClick()
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
private val component = JPanel(FlowLayout(FlowLayout.LEFT, 0, 0)).apply {
|
||||
isOpaque = false
|
||||
add(iconLabel)
|
||||
add(link)
|
||||
}
|
||||
|
||||
val customComponent: JComponent = component
|
||||
|
||||
val textForTest: @Nls String
|
||||
get() = profileText
|
||||
@@ -430,19 +425,28 @@ internal class AgentPromptToolbarProfileAction(
|
||||
templatePresentation.description = description
|
||||
templatePresentation.icon = icon
|
||||
link.text = text
|
||||
component.isVisible = visible
|
||||
component.isEnabled = enabled
|
||||
link.isVisible = visible
|
||||
link.isEnabled = enabled
|
||||
link.setIcon(icon, false)
|
||||
iconLabel.icon = icon
|
||||
iconLabel.isVisible = visible
|
||||
iconLabel.isEnabled = enabled
|
||||
link.setToolTipText(HtmlChunk.text(description))
|
||||
link.accessibleContext.accessibleName = text
|
||||
link.accessibleContext.accessibleDescription = description
|
||||
iconLabel.setToolTipText(HtmlChunk.text(description))
|
||||
iconLabel.accessibleContext.accessibleName = text
|
||||
iconLabel.accessibleContext.accessibleDescription = description
|
||||
component.revalidate()
|
||||
component.repaint()
|
||||
link.revalidate()
|
||||
link.repaint()
|
||||
onPresentationChanged?.invoke()
|
||||
}
|
||||
|
||||
override fun createCustomComponent(presentation: Presentation, place: String): JComponent {
|
||||
return link
|
||||
return component
|
||||
}
|
||||
|
||||
override fun update(e: AnActionEvent) {
|
||||
@@ -710,7 +714,7 @@ internal fun createAgentPromptPaletteView(
|
||||
}
|
||||
val generationSettingsActionsPanel = JPanel(FlowLayout(FlowLayout.RIGHT, 0, 0)).apply {
|
||||
isOpaque = false
|
||||
add(launchProfileLink)
|
||||
add(profileSelectorAction.customComponent)
|
||||
defaultProfileActionControl.component.border = JBUI.Borders.emptyLeft(if (isInlinePrompt) 6 else 8)
|
||||
add(defaultProfileActionControl.component)
|
||||
}
|
||||
|
||||
@@ -460,8 +460,10 @@ class AgentPromptPaletteViewStructureTest {
|
||||
val promptAreaInRoot = checkNotNull(findPromptArea(view.rootPanel, promptArea))
|
||||
|
||||
val profileActionComponent = view.profileAction.customComponent
|
||||
assertThat(profileActionComponent).isSameAs(view.launchProfileLink)
|
||||
assertThat(profileActionComponent).isNotSameAs(view.launchProfileLink)
|
||||
assertThat(SwingUtilities.isDescendingFrom(view.launchProfileLink, view.rootPanel)).isTrue()
|
||||
assertThat(SwingUtilities.isDescendingFrom(view.launchProfileLink, profileActionComponent)).isTrue()
|
||||
assertThat(SwingUtilities.isDescendingFrom(view.profileAction.iconLabel, profileActionComponent)).isTrue()
|
||||
assertThat(SwingUtilities.isDescendingFrom(profileActionComponent, view.rootPanel)).isTrue()
|
||||
assertThat(SwingUtilities.isDescendingFrom(profileActionComponent, view.rightHeaderPanel)).isFalse()
|
||||
assertThat(SwingUtilities.isDescendingFrom(profileActionComponent, view.headerControls.toolbarComponent)).isFalse()
|
||||
@@ -500,14 +502,14 @@ class AgentPromptPaletteViewStructureTest {
|
||||
assertThat(view.generationSettingsPanel.isVisible).isTrue()
|
||||
assertThat(view.launchProfileLink.text).isEqualTo("Default")
|
||||
assertThat(view.profileAction.textForTest).isEqualTo("Default")
|
||||
assertThat(view.launchProfileLink.icon).isNotNull()
|
||||
assertThat(view.profileAction.iconLabel.icon).isNotNull()
|
||||
assertThat(view.launchProfileLink.icon).isSameAs(view.addContextButton.icon)
|
||||
assertThat(view.launchProfileLink.font.isBold).isFalse()
|
||||
assertThat((view.launchProfileLink as HeaderActionLink).trailingIcon).isSameAs(view.addContextButton.icon)
|
||||
assertThat(view.launchTuningSummaryLink.isVisible).isFalse()
|
||||
val launchTuningSummaryCenter = SwingUtilities.convertPoint(
|
||||
view.launchProfileLink,
|
||||
view.launchProfileLink.width / 2,
|
||||
view.launchProfileLink.height / 2,
|
||||
profileActionComponent,
|
||||
profileActionComponent.width / 2,
|
||||
profileActionComponent.height / 2,
|
||||
view.rootPanel,
|
||||
)
|
||||
val topComponent = SwingUtilities.getDeepestComponentAt(view.rootPanel, launchTuningSummaryCenter.x, launchTuningSummaryCenter.y)
|
||||
@@ -515,6 +517,28 @@ class AgentPromptPaletteViewStructureTest {
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun launchSettingsProviderIconOpensSameControlAsDropdownLink() {
|
||||
runInEdtAndWait {
|
||||
val view = createAgentPromptPaletteView(
|
||||
promptArea = EditorTextField(),
|
||||
contextChipsPanel = JPanel(),
|
||||
onExistingTaskSelected = {},
|
||||
)
|
||||
var popupAnchor: JComponent? = null
|
||||
view.profileAction.setPopupHandler { _, anchor -> popupAnchor = anchor }
|
||||
|
||||
triggerMousePressed(view.profileAction.iconLabel)
|
||||
|
||||
assertThat(popupAnchor).isSameAs(view.launchProfileLink)
|
||||
|
||||
popupAnchor = null
|
||||
view.launchProfileLink.doClick()
|
||||
|
||||
assertThat(popupAnchor).isSameAs(view.launchProfileLink)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun productionPromptFieldDoesNotReserveOverlaySpaceForGenerationSettingsControls() {
|
||||
runInEdtAndWait {
|
||||
|
||||
@@ -36,7 +36,7 @@ Task-cost profiles make the global prompt ready to send while still letting user
|
||||
- This spec does not require renaming the existing persisted `activeLaunchProfileId` field; it may remain the stored default profile id for compatibility.
|
||||
|
||||
## Requirements
|
||||
- The prompt composer exposes the launch-settings affordance in the bottom tray location specified by `global-prompt-composer.spec.md`. The affordance must show the selected provider icon, including the red YOLO/Brave badge for YOLO profiles, plus the compact selected profile name or generated profile name, and open one popup for profile choice, per-task tuning, and profile management. Default/save/update actions stay visible inline next to the launch-settings affordance. Built-in provider-backed profiles use compact mode labels, such as `Default`, `Full Auto`, `Skip Permissions`, or `Brave Mode`, to avoid repeating provider names. User profiles show their saved profile name, and unmatched edited controls show a generated compact name, such as `High` or `GPT-5 High`.
|
||||
- The prompt composer exposes the launch-settings affordance in the bottom tray location specified by `global-prompt-composer.spec.md`. The affordance must show the selected provider icon, including the red YOLO/Brave badge for YOLO profiles, plus the compact selected profile name or generated profile name, and clicking either the icon or the text/dropdown link opens one popup for profile choice, per-task tuning, and profile management. Default/save/update actions stay visible inline next to the launch-settings affordance. Built-in provider-backed profiles use compact mode labels, such as `Default`, `Full Auto`, `Skip Permissions`, or `Brave Mode`, to avoid repeating provider names. User profiles show their saved profile name, and unmatched edited controls show a generated compact name, such as `High` or `GPT-5 High`.
|
||||
[@test] ../../prompt/ui/testSrc/AgentPromptPaletteViewStructureTest.kt
|
||||
|
||||
- The prompt must restore the stored default profile on open when that profile is still applicable. If the default is unavailable, the prompt may fall back to the provider-list default without writing a replacement default.
|
||||
|
||||
Reference in New Issue
Block a user