IDEA-325417 Add presentation assistant quick settings

GitOrigin-RevId: fc0fc4cfb129c6b872ff35e7b28b57d2ed3eec43
This commit is contained in:
Aydar Mukhametzyanov
2023-10-09 17:52:32 +02:00
committed by intellij-monorepo-bot
parent 810b05b32d
commit 8a5daeceda
9 changed files with 297 additions and 15 deletions

View File

@@ -0,0 +1,6 @@
<!-- Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. -->
<svg width="26" height="26" viewBox="0 0 26 26" fill="none" xmlns="http://www.w3.org/2000/svg">
<circle cx="13" cy="8" r="1" fill="#CED0D6"/>
<circle cx="13" cy="13" r="1" fill="#CED0D6"/>
<circle cx="13" cy="18" r="1" fill="#CED0D6"/>
</svg>

After

Width:  |  Height:  |  Size: 370 B

View File

@@ -1691,7 +1691,6 @@ intention.family.editor.notification.option=Editor notification option
intention.family.editor.notification=Editor notification
text.password.hidden=<hidden>
presentation.assistant.configurable.alternative.keymap=Alternative Keymap:
presentation.assistant.configurable.description=Description:
presentation.assistant.configurable.duration=Display for:
presentation.assistant.configurable.duration.seconds=seconds
@@ -1727,6 +1726,9 @@ presentation.assistant.notification.title=Shortcuts for macOS are not shown
presentation.assistant.notification=Presentation assistant
presentation.assistant.settings=Presentation Assistant
presentation.assistant.toggle.action.name=Presentation Assistant
presentation.assistant.quick.settings.size.group=Size
presentation.assistant.quick.settings.position.group=Position
presentation.assistant.quick.settings.settings=Settings...
packages.settings.version=Version
packages.settings.latest.version=Latest version

View File

@@ -5,6 +5,7 @@ import com.intellij.ide.ui.LafManagerListener
import com.intellij.openapi.Disposable
import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.application.ModalityState
import com.intellij.openapi.observable.util.addMouseHoverListener
import com.intellij.openapi.project.Project
import com.intellij.openapi.ui.popup.JBPopup
import com.intellij.openapi.ui.popup.JBPopupFactory
@@ -13,17 +14,15 @@ import com.intellij.openapi.ui.popup.LightweightWindowEvent
import com.intellij.openapi.util.Disposer
import com.intellij.openapi.wm.WindowManager
import com.intellij.ui.awt.RelativePoint
import com.intellij.ui.hover.HoverListener
import com.intellij.ui.scale.JBUIScale
import com.intellij.util.Alarm
import com.intellij.util.ui.Animator
import com.intellij.util.ui.JBInsets
import java.awt.Dimension
import java.awt.Point
import java.awt.Rectangle
import java.awt.Window
import java.awt.*
import javax.swing.SwingUtilities
internal class ActionInfoPopupGroup(project: Project, textFragments: List<TextData>, showAnimated: Boolean) : Disposable {
internal class ActionInfoPopupGroup(val project: Project, textFragments: List<TextData>, showAnimated: Boolean) : Disposable {
data class ActionBlock(val popup: JBPopup, val panel: ActionInfoPanel) {
val isDisposed: Boolean get() = popup.isDisposed
}
@@ -35,6 +34,38 @@ internal class ActionInfoPopupGroup(project: Project, textFragments: List<TextDa
val popup = createPopup(panel, showAnimated)
ActionBlock(popup, panel)
}
private val settingsButton = PresentationAssistantQuickSettingsButton(project, appearance) { isSettingsButtonForcedToBeShown = (it > 0) }
private var isPopupHovered: Boolean = false
set(value) {
val oldValue = field
field = value
if (oldValue != isPopupHovered) {
if (isPopupHovered) {
settingsButton.acquireShownStateRequest(computeLocation(project, actionBlocks.size))
}
else {
settingsButton.releaseShownStateRequest()
}
}
updateForcedToBeShown()
}
private var isSettingsButtonForcedToBeShown: Boolean = true
set(value) {
field = value
updateForcedToBeShown()
}
private var forcedToBeShown: Boolean = false
set(value) {
if (value != field) {
if (value) hideAlarm.cancelAllRequests()
else if (isShown) resetHideAlarm()
}
field = value
}
private val hideAlarm = Alarm(this)
private var animator: Animator
@@ -82,6 +113,12 @@ internal class ActionInfoPopupGroup(project: Project, textFragments: List<TextDa
}
})
popup.content.addMouseHoverListener(this, object: HoverListener() {
override fun mouseEntered(component: Component, x: Int, y: Int) { isPopupHovered = true }
override fun mouseMoved(component: Component, x: Int, y: Int) { isPopupHovered = true }
override fun mouseExited(component: Component) { isPopupHovered = false }
})
return popup
}
@@ -107,6 +144,7 @@ internal class ActionInfoPopupGroup(project: Project, textFragments: List<TextDa
actionBlock.popup.setBounds(newBounds)
}
settingsButton.hidePopup()
showFinalAnimationFrame()
}
@@ -122,6 +160,7 @@ internal class ActionInfoPopupGroup(project: Project, textFragments: List<TextDa
}
}
Disposer.dispose(animator)
Disposer.dispose(settingsButton)
}
fun canBeReused(size: Int): Boolean = size == actionBlocks.size && (phase == Phase.FADING_IN || phase == Phase.SHOWN)
@@ -148,6 +187,7 @@ internal class ActionInfoPopupGroup(project: Project, textFragments: List<TextDa
phase = Phase.SHOWN
setAlpha(0f)
}
private fun fadeOut() {
if (phase != Phase.SHOWN) return
phase = Phase.FADING_OUT
@@ -156,6 +196,10 @@ internal class ActionInfoPopupGroup(project: Project, textFragments: List<TextDa
animator.resume()
}
private fun updateForcedToBeShown() {
forcedToBeShown = isPopupHovered || isSettingsButtonForcedToBeShown
}
private fun computeLocation(project: Project, index: Int?): RelativePoint {
val preferredSizes = actionBlocks.map { it.panel.preferredSize }
val gap = JBUIScale.scale(appearance.spaceBetweenPopups)
@@ -174,7 +218,7 @@ internal class ActionInfoPopupGroup(project: Project, textFragments: List<TextDa
1 -> visibleRect.x + (visibleRect.width - popupGroupSize.width) / 2
else -> visibleRect.x + visibleRect.width - popupGroupSize.width - margin
} + (index?.takeIf {
0 < index && index < actionBlocks.size
0 < index && index <= actionBlocks.size
}?.let {
// Calculate X for particular popup
(0..<index).map { preferredSizes[it].width }.reduce { total, width ->
@@ -212,7 +256,8 @@ internal class ActionInfoPopupGroup(project: Project, textFragments: List<TextDa
val titleInsets: JBInsets,
val subtitleInsets: JBInsets,
val spaceBetweenPopups: Int,
val titleSubtitleGap: Int)
val titleSubtitleGap: Int,
val settingsButtonWidth: Int)
companion object {
private fun appearanceFromSize(popupSize: PresentationAssistantPopupSize): Appearance = when(popupSize) {
@@ -221,21 +266,24 @@ internal class ActionInfoPopupGroup(project: Project, textFragments: List<TextDa
JBInsets(6, 12, 0, 12),
JBInsets(0, 14, 6, 14),
8,
1)
1,
25)
PresentationAssistantPopupSize.MEDIUM -> Appearance(32f,
13f,
JBInsets(6, 16, 0, 16),
JBInsets(0, 18, 8, 18),
12,
-2)
-2,
30)
PresentationAssistantPopupSize.LARGE -> Appearance(40f,
14f,
JBInsets(6, 16, 0, 16),
JBInsets(0, 18, 8, 18),
12,
-2)
-2,
34)
}
}
}

View File

@@ -19,7 +19,7 @@ import com.intellij.openapi.project.Project
import com.intellij.util.xmlb.XmlSerializerUtil
import org.jetbrains.annotations.Nls
enum class PresentationAssistantPopupSize(val value: Int, @Nls val stringValue: String) {
enum class PresentationAssistantPopupSize(val value: Int, @Nls val displayName: String) {
SMALL(0, IdeBundle.message("presentation.assistant.configurable.size.small")),
MEDIUM(1, IdeBundle.message("presentation.assistant.configurable.size.medium")),
LARGE(2, IdeBundle.message("presentation.assistant.configurable.size.large"));
@@ -33,7 +33,7 @@ enum class PresentationAssistantPopupSize(val value: Int, @Nls val stringValue:
}
}
enum class PresentationAssistantPopupAlignment(val x: Int, val y: Int, @Nls val stringValue: String) {
enum class PresentationAssistantPopupAlignment(val x: Int, val y: Int, @Nls val displayName: String) {
TOP_LEFT(0, 0, IdeBundle.message("presentation.assistant.configurable.alignment.top.left")),
TOP_CENTER(1, 0, IdeBundle.message("presentation.assistant.configurable.alignment.top.center")),
TOP_RIGHT(2, 0, IdeBundle.message("presentation.assistant.configurable.alignment.top.right")),

View File

@@ -29,7 +29,7 @@ class PresentationAssistantConfigurable: DslConfigurableBase(), Configurable {
panel {
row(IdeBundle.message("presentation.assistant.configurable.popup.size")) {
comboBox(CollectionComboBoxModel(PresentationAssistantPopupSize.entries, PresentationAssistantPopupSize.from(configuration.popupSize)),
textListCellRenderer { it?.stringValue })
textListCellRenderer { it?.displayName })
.bindItem({ PresentationAssistantPopupSize.from(configuration.popupSize) }) {
configuration.popupSize = it?.value ?: PresentationAssistantPopupSize.MEDIUM.value
}
@@ -41,7 +41,7 @@ class PresentationAssistantConfigurable: DslConfigurableBase(), Configurable {
row(IdeBundle.message("presentation.assistant.configurable.popup.position")) {
comboBox(CollectionComboBoxModel(PresentationAssistantPopupAlignment.entries,
PresentationAssistantPopupAlignment.from(configuration.horizontalAlignment, configuration.verticalAlignment)),
textListCellRenderer { it?.stringValue })
textListCellRenderer { it?.displayName })
.bindItem({ PresentationAssistantPopupAlignment.from(configuration.horizontalAlignment, configuration.verticalAlignment) }) {
configuration.horizontalAlignment = it?.x ?: PresentationAssistantPopupAlignment.defaultAlignment.x
configuration.verticalAlignment = it?.y ?: PresentationAssistantPopupAlignment.defaultAlignment.y

View File

@@ -0,0 +1,147 @@
// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.platform.ide.impl.presentationAssistant
import com.intellij.icons.AllIcons
import com.intellij.openapi.Disposable
import com.intellij.openapi.actionSystem.CommonDataKeys
import com.intellij.openapi.actionSystem.DataContext
import com.intellij.openapi.actionSystem.PlatformDataKeys
import com.intellij.openapi.actionSystem.impl.SimpleDataContext
import com.intellij.openapi.observable.util.addMouseListener
import com.intellij.openapi.project.Project
import com.intellij.openapi.ui.popup.JBPopup
import com.intellij.openapi.ui.popup.JBPopupFactory
import com.intellij.ui.awt.RelativePoint
import com.intellij.ui.components.JBLabel
import com.intellij.ui.scale.JBUIScale
import com.intellij.util.Alarm
import java.awt.Dimension
import java.awt.Point
import java.awt.event.MouseEvent
import java.awt.event.MouseListener
internal class PresentationAssistantQuickSettingsButton(private val project: Project,
private val appearance: ActionInfoPopupGroup.Appearance,
private val shownStateRequestCountChanged: (Int) -> Unit):
JBLabel(AllIcons.Actions.PresentationAssistantSettings), Disposable, DataContext {
private var popup: JBPopup? = null
private var hideAlarm = Alarm()
private var shownStateRequestCount = 0
set(value) {
val oldValue = field
field = value
if (oldValue != shownStateRequestCount) {
if (shownStateRequestCount > 0) cancelHideAlarm()
else scheduleHide()
}
shownStateRequestCountChanged(shownStateRequestCount)
}
init {
background = ActionInfoPanel.BACKGROUND
isOpaque = true
updatePreferredSize()
addMouseListener(this, object : MouseListener {
override fun mouseClicked(e: MouseEvent?) {}
override fun mousePressed(e: MouseEvent?) {}
override fun mouseReleased(e: MouseEvent?) {
showSettingsPopup()
}
override fun mouseEntered(e: MouseEvent?) {
shownStateRequestCount++
}
override fun mouseExited(e: MouseEvent?) {
releaseShownStateRequest()
}
})
}
override fun updateUI() {
super.updateUI()
if (parent != null) updatePreferredSize()
}
private fun updatePreferredSize() {
val width = JBUIScale.scale(appearance.settingsButtonWidth)
preferredSize = Dimension(width, width)
}
private fun showSettingsPopup() {
shownStateRequestCount++
val popup = JBPopupFactory.getInstance().createActionGroupPopup("",
PresentationAssistantQuickSettingsGroup(),
SimpleDataContext.getSimpleContext(CommonDataKeys.PROJECT, project, null),
null,
false,
{ releaseShownStateRequest() },
Int.MAX_VALUE)
popup.showInBestPositionFor(SimpleDataContext
.builder()
.add(PlatformDataKeys.CONTEXT_COMPONENT, this)
.add(PlatformDataKeys.CONTEXT_MENU_POINT, Point(0, height + appearance.spaceBetweenPopups))
.build())
}
fun acquireShownStateRequest(point: RelativePoint) {
shownStateRequestCount++
if (shownStateRequestCount > 0 && popup == null) showPopup(point)
}
private fun showPopup(point: RelativePoint) {
hidePopup()
popup = createSettingsButtonPopup()
popup?.show(point)
}
fun releaseShownStateRequest() {
if (shownStateRequestCount > 0) shownStateRequestCount--
}
private fun scheduleHide() {
cancelHideAlarm()
hideAlarm.addRequest({
hidePopup()
}, 2000)
}
private fun cancelHideAlarm() {
hideAlarm.cancelAllRequests()
}
fun hidePopup() {
if (popup == null) return
popup?.cancel()
popup = null
cancelHideAlarm()
shownStateRequestCount = 0
}
override fun dispose() {
hidePopup()
}
private fun createSettingsButtonPopup(): JBPopup {
val popup = with(JBPopupFactory.getInstance().createComponentPopupBuilder(this, this)) {
setFocusable(true)
setBelongsToGlobalPopupStack(false)
setCancelKeyEnabled(false)
createPopup()
}
return popup
}
override fun getData(dataId: String): Any? {
if (dataId == CommonDataKeys.PROJECT.name) return project
return null
}
}

View File

@@ -0,0 +1,76 @@
// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.platform.ide.impl.presentationAssistant
import com.intellij.ide.IdeBundle
import com.intellij.openapi.actionSystem.*
import com.intellij.openapi.options.ShowSettingsUtil
import com.intellij.openapi.project.DumbAware
import com.intellij.openapi.project.DumbAwareAction
import com.intellij.openapi.project.DumbAwareToggleAction
import java.util.function.Supplier
class PresentationAssistantQuickSettingsGroup: DefaultActionGroup(), DumbAware {
override fun getActionUpdateThread(): ActionUpdateThread = ActionUpdateThread.EDT
override fun getChildren(e: AnActionEvent?): Array<AnAction> {
return arrayOf(PresentationAssistantQuickSettingsSizeGroup(),
PresentationAssistantQuickSettingsPositionGroup(),
Separator(),
PresentationAssistantQuickSettingsOpenSettings())
}
}
internal class PresentationAssistantQuickSettingsSizeGroup: DefaultActionGroup(IdeBundle.message("presentation.assistant.quick.settings.size.group"), PresentationAssistantPopupSize.entries.map {
PresentationAssistantQuickSettingsSize(it)
}), DumbAware {
override fun getActionUpdateThread(): ActionUpdateThread = ActionUpdateThread.EDT
override fun isPopup(): Boolean = true
}
internal class PresentationAssistantQuickSettingsSize(val size: PresentationAssistantPopupSize): DumbAwareToggleAction(size.displayName) {
private val configuration = PresentationAssistant.INSTANCE.configuration
override fun getActionUpdateThread(): ActionUpdateThread = ActionUpdateThread.EDT
override fun isSelected(e: AnActionEvent): Boolean = configuration.popupSize == size.value
override fun setSelected(e: AnActionEvent, state: Boolean) {
if (state) {
configuration.popupSize = size.value
PresentationAssistant.INSTANCE.updatePresenter()
}
}
}
internal class PresentationAssistantQuickSettingsPositionGroup: DefaultActionGroup(
Supplier { IdeBundle.message("presentation.assistant.quick.settings.position.group") },
PresentationAssistantPopupAlignment.entries.map { PresentationAssistantQuickSettingsPosition(it) }
), DumbAware {
override fun getActionUpdateThread(): ActionUpdateThread = ActionUpdateThread.EDT
override fun isPopup(): Boolean = true
}
internal class PresentationAssistantQuickSettingsPosition(val position: PresentationAssistantPopupAlignment): DumbAwareToggleAction(position.displayName) {
private val configuration = PresentationAssistant.INSTANCE.configuration
override fun getActionUpdateThread(): ActionUpdateThread = ActionUpdateThread.EDT
override fun isSelected(e: AnActionEvent): Boolean =
configuration.verticalAlignment == position.y &&
configuration.horizontalAlignment == position.x
override fun setSelected(e: AnActionEvent, state: Boolean) {
if (state) {
configuration.verticalAlignment = position.y
configuration.horizontalAlignment = position.x
PresentationAssistant.INSTANCE.updatePresenter()
}
}
}
internal class PresentationAssistantQuickSettingsOpenSettings: DumbAwareAction(IdeBundle.message("presentation.assistant.quick.settings.settings")) {
override fun actionPerformed(e: AnActionEvent) {
val project = e.project ?: return
ShowSettingsUtil.getInstance().showSettingsDialog(project, PresentationAssistantConfigurable::class.java)
}
}

View File

@@ -1637,6 +1637,8 @@
<group id="ChangeProjectColorActionGroup" class="com.intellij.openapi.wm.impl.welcomeScreen.projectActions.ChangeProjectColorActionGroup" searchable="false" popup="true"/>
<group id="PresentationAssistantQuickSettingsGroup" class="com.intellij.platform.ide.impl.presentationAssistant.PresentationAssistantQuickSettingsGroup" searchable="false" popup="true"/>
<group id="ToolbarPopupActions"/>
<group id="ToolbarPopupActions.MainToolbarNewUI">

View File

@@ -139,6 +139,7 @@ public class AllIcons {
/** 16x16 */ public static final @NotNull Icon Play_forward = load("actions/play_forward.svg", 84808551, 2);
/** 16x16 */ public static final @NotNull Icon Play_last = load("actions/play_last.svg", 1200003259, 2);
/** 16x16 */ public static final @NotNull Icon PopFrame = load("actions/popFrame.svg", -2061379356, 2);
/** 26x26 */ public static final @NotNull Icon PresentationAssistantSettings = load("actions/presentationAssistantSettings.svg", -1777013294, 0);
/** 16x16 */ public static final @NotNull Icon PreserveCase = load("actions/preserveCase.svg", 1309462994, 0);
/** 16x16 */ public static final @NotNull Icon PreserveCaseHover = load("actions/preserveCaseHover.svg", 819147333, 0);
/** 16x16 */ public static final @NotNull Icon PreserveCaseSelected = load("actions/preserveCaseSelected.svg", 456685590, 0);