mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
IDEA-317375 Horizontal menu on Windows
GitOrigin-RevId: a29edbee39c8af8999921aed896e7b44426a1036
This commit is contained in:
committed by
intellij-monorepo-bot
parent
d326d9200c
commit
ae7c631932
@@ -2763,6 +2763,8 @@ to.create.new.file.tooltip=To create a new file, press <b>{0}</b>. Alternatively
|
||||
general.module.type.description=A basic project that allows working with separate files and compiling Java and Kotlin classes.
|
||||
|
||||
main.toolbar.menu.button=Main Menu
|
||||
main.toolbar.expanded.menu.close=Collapse Menu
|
||||
|
||||
notification.group.system.clipboard=System clipboard unavailable
|
||||
notification.group.required.plugins=Required plugins not loaded
|
||||
notification.group.language.plugin=Recommended language plugin available
|
||||
|
||||
@@ -137,7 +137,7 @@ open class IdeRootPane internal constructor(frame: JFrame,
|
||||
MacToolbarFrameHeader(frame = frame, root = this)
|
||||
}
|
||||
else {
|
||||
ToolbarFrameHeader(frame = frame, ideMenu = IdeMenuBar.createMenuBar())
|
||||
ToolbarFrameHeader(frame = frame)
|
||||
}
|
||||
}
|
||||
else {
|
||||
@@ -148,7 +148,7 @@ open class IdeRootPane internal constructor(frame: JFrame,
|
||||
customFrameTitlePane = customFrameTitlePane,
|
||||
selectedEditorFilePath = selectedEditorFilePath,
|
||||
)
|
||||
layeredPane.add(customFrameTitlePane.getComponent(), (JLayeredPane.DEFAULT_LAYER - 2) as Any)
|
||||
layeredPane.add(customFrameTitlePane.getComponent(), (JLayeredPane.DEFAULT_LAYER - 3) as Any)
|
||||
}
|
||||
else {
|
||||
helper = UndecoratedHelper
|
||||
|
||||
+138
@@ -0,0 +1,138 @@
|
||||
// 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.openapi.wm.impl.customFrameDecorations.header.toolbar
|
||||
|
||||
import com.intellij.icons.ExpUiIcons
|
||||
import com.intellij.ide.IdeBundle
|
||||
import com.intellij.openapi.actionSystem.AnAction
|
||||
import com.intellij.openapi.actionSystem.AnActionEvent
|
||||
import com.intellij.openapi.project.DumbAwareAction
|
||||
import com.intellij.openapi.util.SystemInfoRt
|
||||
import com.intellij.openapi.util.registry.Registry
|
||||
import com.intellij.openapi.wm.impl.IdeMenuBar
|
||||
import com.intellij.openapi.wm.impl.customFrameDecorations.header.FrameHeader
|
||||
import com.intellij.ui.dsl.builder.Align
|
||||
import com.intellij.ui.dsl.builder.AlignY
|
||||
import com.intellij.ui.dsl.builder.EmptySpacingConfiguration
|
||||
import com.intellij.ui.dsl.builder.panel
|
||||
import java.awt.*
|
||||
import java.awt.event.ComponentAdapter
|
||||
import java.awt.event.ComponentEvent
|
||||
import javax.swing.*
|
||||
|
||||
private const val ALPHA = (255 * 0.8).toInt()
|
||||
|
||||
internal class ExpandableMenu(private val frameHeader: FrameHeader) {
|
||||
|
||||
val ideMenu = IdeMenuBar.createMenuBar()
|
||||
lateinit var headerContent: JComponent
|
||||
private var expandedMenuBar: JPanel? = null
|
||||
private val shadowComponent = ShadowComponent()
|
||||
private val rootPane: JRootPane?
|
||||
get() = SwingUtilities.getRootPane(headerContent)
|
||||
|
||||
init {
|
||||
frameHeader.addComponentListener(object : ComponentAdapter() {
|
||||
override fun componentResized(e: ComponentEvent?) {
|
||||
updateBounds()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fun isEnabled(): Boolean {
|
||||
return SystemInfoRt.isWindows && Registry.`is`("ide.windows.main.menu.expand.horizontal")
|
||||
}
|
||||
|
||||
// todo compact mode
|
||||
// todo hint under menu
|
||||
fun switchState(actionToShow: AnAction? = null) {
|
||||
if (expandedMenuBar != null && actionToShow == null) {
|
||||
removeExpandedMenuBar()
|
||||
return
|
||||
}
|
||||
|
||||
removeExpandedMenuBar()
|
||||
val layeredPane = rootPane?.layeredPane ?: return
|
||||
|
||||
expandedMenuBar = panel {
|
||||
customizeSpacingConfiguration(EmptySpacingConfiguration()) {
|
||||
row {
|
||||
cell(wrapComponent(createMenuButton(CloseExpandedMenuAction())))
|
||||
cell(wrapComponent(ideMenu)).align(AlignY.FILL)
|
||||
cell(shadowComponent).align(Align.FILL)
|
||||
}
|
||||
}
|
||||
}.apply { isOpaque = false }
|
||||
|
||||
updateBounds()
|
||||
updateColor()
|
||||
layeredPane.add(expandedMenuBar!!, (JLayeredPane.DEFAULT_LAYER - 2) as Any)
|
||||
|
||||
var menu = ideMenu.getMenu(0)
|
||||
if (actionToShow != null) {
|
||||
for (i in 0..ideMenu.menuCount - 1) {
|
||||
val m = ideMenu.getMenu(i)
|
||||
if (m.mnemonic == actionToShow.templatePresentation.mnemonic) {
|
||||
menu = m
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
val subElements = menu.popupMenu.subElements
|
||||
if (subElements.isNotEmpty()) {
|
||||
MenuSelectionManager.defaultManager().selectedPath = arrayOf(ideMenu, menu, menu.popupMenu, subElements[0])
|
||||
}
|
||||
}
|
||||
|
||||
fun updateColor() {
|
||||
val color = frameHeader.background
|
||||
expandedMenuBar?.background = color
|
||||
@Suppress("UseJBColor")
|
||||
shadowComponent.background = Color(color.red, color.green, color.blue, ALPHA)
|
||||
}
|
||||
|
||||
private fun wrapComponent(component: JComponent): JPanel {
|
||||
return JPanel(BorderLayout()).apply {
|
||||
add(component, BorderLayout.CENTER)
|
||||
background = null
|
||||
}
|
||||
}
|
||||
|
||||
private fun updateBounds() {
|
||||
expandedMenuBar?.let {
|
||||
val insets = headerContent.insets
|
||||
val location = SwingUtilities.convertPoint(headerContent, Point(insets.left, insets.top), rootPane ?: return)
|
||||
it.bounds = Rectangle(location.x, location.y,
|
||||
headerContent.width - insets.left - insets.right,
|
||||
headerContent.height - insets.top - insets.bottom)
|
||||
}
|
||||
}
|
||||
|
||||
private fun removeExpandedMenuBar() {
|
||||
if (expandedMenuBar != null) {
|
||||
rootPane?.layeredPane?.remove(expandedMenuBar)
|
||||
expandedMenuBar = null
|
||||
|
||||
rootPane?.repaint()
|
||||
}
|
||||
}
|
||||
|
||||
private inner class CloseExpandedMenuAction : DumbAwareAction(
|
||||
IdeBundle.messagePointer("main.toolbar.expanded.menu.close"), ExpUiIcons.General.Close) {
|
||||
|
||||
override fun actionPerformed(e: AnActionEvent) {
|
||||
removeExpandedMenuBar()
|
||||
}
|
||||
}
|
||||
|
||||
private class ShadowComponent : JComponent() {
|
||||
init {
|
||||
isOpaque = false
|
||||
}
|
||||
|
||||
override fun paint(g: Graphics?) {
|
||||
g ?: return
|
||||
g.color = background
|
||||
g.fillRect(0, 0, width, height)
|
||||
}
|
||||
}
|
||||
}
|
||||
+40
-28
@@ -4,7 +4,7 @@ package com.intellij.openapi.wm.impl.customFrameDecorations.header.toolbar
|
||||
import com.intellij.icons.ExpUiIcons
|
||||
import com.intellij.ide.DataManager
|
||||
import com.intellij.ide.IdeBundle
|
||||
import com.intellij.ide.ui.UISettings.Companion.getInstance
|
||||
import com.intellij.ide.ui.UISettings
|
||||
import com.intellij.ide.ui.customization.CustomActionsSchema
|
||||
import com.intellij.openapi.Disposable
|
||||
import com.intellij.openapi.actionSystem.*
|
||||
@@ -30,11 +30,12 @@ import java.awt.event.ActionListener
|
||||
import java.awt.event.HierarchyEvent
|
||||
import java.awt.event.KeyEvent
|
||||
import javax.swing.JComponent
|
||||
import javax.swing.JRootPane
|
||||
import javax.swing.KeyStroke
|
||||
import javax.swing.SwingUtilities
|
||||
|
||||
@ApiStatus.Internal
|
||||
internal class MainMenuButton {
|
||||
internal class MainMenuButton(private val expandableMenu: ExpandableMenu?) {
|
||||
|
||||
private val menuAction = ShowMenuAction()
|
||||
private var disposable: Disposable? = null
|
||||
@@ -42,7 +43,7 @@ internal class MainMenuButton {
|
||||
private var registeredKeyStrokes = mutableListOf<KeyStroke>()
|
||||
|
||||
val button: ActionButton = createMenuButton(menuAction)
|
||||
var rootPane: JComponent? = null
|
||||
var rootPane: JRootPane? = null
|
||||
set(value) {
|
||||
if (field !== value) {
|
||||
uninstall()
|
||||
@@ -132,26 +133,32 @@ internal class MainMenuButton {
|
||||
IdeBundle.messagePointer("main.toolbar.menu.button"),
|
||||
ExpUiIcons.General.WindowsMenu_20x20) {
|
||||
|
||||
override fun actionPerformed(e: AnActionEvent) = showPopup(e.dataContext)
|
||||
override fun actionPerformed(e: AnActionEvent) {
|
||||
if (expandableMenu?.isEnabled() == true) {
|
||||
expandableMenu.switchState()
|
||||
} else {
|
||||
showPopup(e.dataContext)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun showPopup(context: DataContext, actionToShow: AnAction? = null) {
|
||||
val mainMenu = getMainMenuGroup()
|
||||
val popup = JBPopupFactory.getInstance()
|
||||
.createActionGroupPopup(null, mainMenu, context, JBPopupFactory.ActionSelectionAid.SPEEDSEARCH, true,
|
||||
ActionPlaces.MAIN_MENU)
|
||||
.apply { isShowSubmenuOnHover = true }
|
||||
.apply { setMinimumSize(Dimension(JBUI.CurrentTheme.CustomFrameDecorations.menuPopupMinWidth(), 0)) }
|
||||
as ListPopupImpl
|
||||
popup.showUnderneathOf(button)
|
||||
fun showPopup(context: DataContext, actionToShow: AnAction? = null) {
|
||||
val mainMenu = getMainMenuGroup()
|
||||
val popup = JBPopupFactory.getInstance()
|
||||
.createActionGroupPopup(null, mainMenu, context, JBPopupFactory.ActionSelectionAid.SPEEDSEARCH, true,
|
||||
ActionPlaces.MAIN_MENU)
|
||||
.apply { isShowSubmenuOnHover = true }
|
||||
.apply { setMinimumSize(Dimension(JBUI.CurrentTheme.CustomFrameDecorations.menuPopupMinWidth(), 0)) }
|
||||
as ListPopupImpl
|
||||
popup.showUnderneathOf(button)
|
||||
|
||||
if (actionToShow != null) {
|
||||
for (listStep in popup.listStep.values) {
|
||||
listStep as PopupFactoryImpl.ActionItem
|
||||
if (listStep.action.unwrap() === actionToShow.unwrap()) {
|
||||
SwingUtilities.invokeLater {
|
||||
// Wait popup showing
|
||||
popup.selectAndExpandValue(listStep)
|
||||
}
|
||||
if (actionToShow != null) {
|
||||
for (listStep in popup.listStep.values) {
|
||||
listStep as PopupFactoryImpl.ActionItem
|
||||
if (listStep.action.unwrap() === actionToShow.unwrap()) {
|
||||
SwingUtilities.invokeLater {
|
||||
// Wait popup showing
|
||||
popup.selectAndExpandValue(listStep)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -161,18 +168,22 @@ internal class MainMenuButton {
|
||||
private inner class ShowSubMenuAction(private val actionToShow: AnAction) : ActionListener {
|
||||
|
||||
override fun actionPerformed(e: ActionEvent?) {
|
||||
if (!getInstance().disableMnemonics) {
|
||||
val component = IdeFocusManager.getGlobalInstance().focusOwner ?: button
|
||||
menuAction.showPopup(DataManager.getInstance().getDataContext(component), actionToShow)
|
||||
if (!UISettings.getInstance().disableMnemonics) {
|
||||
if (expandableMenu?.isEnabled() == true) {
|
||||
expandableMenu.switchState(actionToShow)
|
||||
} else {
|
||||
val component = IdeFocusManager.getGlobalInstance().focusOwner ?: button
|
||||
showPopup(DataManager.getInstance().getDataContext(component), actionToShow)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private fun createMenuButton(action: AnAction): ActionButton {
|
||||
internal fun createMenuButton(action: AnAction): ActionButton {
|
||||
val button = object : ActionButton(action, PresentationFactory().getPresentation(action),
|
||||
ActionPlaces.MAIN_MENU, {ActionToolbar.experimentalToolbarMinimumButtonSize() }) {
|
||||
ActionPlaces.MAIN_MENU, { ActionToolbar.experimentalToolbarMinimumButtonSize() }) {
|
||||
override fun getDataContext(): DataContext {
|
||||
return DataManager.getInstance().dataContextFromFocusAsync.blockingGet(200) ?: super.getDataContext()
|
||||
}
|
||||
@@ -182,7 +193,7 @@ private fun createMenuButton(action: AnAction): ActionButton {
|
||||
return button
|
||||
}
|
||||
|
||||
private fun getMainMenuGroup(): ActionGroup {
|
||||
internal fun getMainMenuGroup(): ActionGroup {
|
||||
val mainMenuGroup = CustomActionsSchema.getInstance().getCorrectedAction(IdeActions.GROUP_MAIN_MENU)
|
||||
mainMenuGroup as ActionGroup
|
||||
return DefaultActionGroup(
|
||||
@@ -191,7 +202,8 @@ private fun getMainMenuGroup(): ActionGroup {
|
||||
// Wrap action groups to force them to be popup groups,
|
||||
// otherwise they end up as separate items in the burger menu (IDEA-294669).
|
||||
ActionGroupPopupWrapper(child)
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
LOG.error("A top-level child of the main menu is not an action group: $child")
|
||||
null
|
||||
}
|
||||
|
||||
+15
-6
@@ -40,13 +40,14 @@ private enum class ShowMode {
|
||||
MENU, TOOLBAR
|
||||
}
|
||||
|
||||
internal class ToolbarFrameHeader(frame: JFrame, ideMenu: IdeMenuBar) : FrameHeader(frame), UISettingsListener, ToolbarHolder, MainFrameCustomHeader {
|
||||
private val myMenuBar = ideMenu
|
||||
internal class ToolbarFrameHeader(frame: JFrame) : FrameHeader(frame), UISettingsListener, ToolbarHolder, MainFrameCustomHeader {
|
||||
private val myMenuBar = IdeMenuBar.createMenuBar()
|
||||
private val menuBarHeaderTitle = SimpleCustomDecorationPath(frame, true).apply {
|
||||
isOpaque = false
|
||||
}
|
||||
private val menuBarContainer = createMenuBarContainer()
|
||||
private val mainMenuButton = MainMenuButton()
|
||||
private val expandableMenu = ExpandableMenu(this)
|
||||
private val mainMenuButton = MainMenuButton(expandableMenu)
|
||||
private var toolbar : MainToolbar? = null
|
||||
private val myToolbarPlaceholder = createToolbarPlaceholder()
|
||||
private val myHeaderContent = createHeaderContent()
|
||||
@@ -86,6 +87,7 @@ internal class ToolbarFrameHeader(frame: JFrame, ideMenu: IdeMenuBar) : FrameHea
|
||||
if (isCompact) "PATH" else "TOOLBAR"
|
||||
|
||||
init {
|
||||
expandableMenu.headerContent = myHeaderContent
|
||||
layout = GridBagLayout()
|
||||
val gb = GridBag().anchor(WEST)
|
||||
|
||||
@@ -168,6 +170,7 @@ internal class ToolbarFrameHeader(frame: JFrame, ideMenu: IdeMenuBar) : FrameHea
|
||||
|
||||
override fun updateMenuActions(forceRebuild: Boolean) {
|
||||
myMenuBar.updateMenuActions(forceRebuild)
|
||||
expandableMenu.ideMenu.updateMenuActions(forceRebuild)
|
||||
}
|
||||
|
||||
override fun getComponent(): JComponent = this
|
||||
@@ -222,6 +225,12 @@ internal class ToolbarFrameHeader(frame: JFrame, ideMenu: IdeMenuBar) : FrameHea
|
||||
|
||||
override fun getHeaderBackground(active: Boolean) = CustomFrameDecorations.mainToolbarBackground(active)
|
||||
|
||||
override fun updateActive() {
|
||||
super.updateActive()
|
||||
|
||||
expandableMenu.updateColor()
|
||||
}
|
||||
|
||||
private fun getElementRect(comp: Component, rectProcessor: ((Rectangle) -> Unit)? = null): RelativeRectangle {
|
||||
val rect = Rectangle(comp.size)
|
||||
rectProcessor?.invoke(rect)
|
||||
@@ -230,16 +239,16 @@ internal class ToolbarFrameHeader(frame: JFrame, ideMenu: IdeMenuBar) : FrameHea
|
||||
|
||||
private fun createHeaderContent(): JPanel {
|
||||
val res = NonOpaquePanel(CardLayout())
|
||||
res.border = JBUI.Borders.empty()
|
||||
res.border = JBUI.Borders.emptyLeft(JBUI.scale(16))
|
||||
|
||||
val menuPnl = NonOpaquePanel(GridBagLayout()).apply {
|
||||
val gb = GridBag().anchor(WEST).nextLine()
|
||||
add(menuBarContainer, gb.next().insetLeft(JBUI.scale(16)).fillCellVertically().weighty(1.0))
|
||||
add(menuBarContainer, gb.next().fillCellVertically().weighty(1.0))
|
||||
add(Box.createHorizontalGlue(), gb.next().weightx(1.0).fillCell())
|
||||
}
|
||||
val toolbarPnl = NonOpaquePanel(GridBagLayout()).apply {
|
||||
val gb = GridBag().anchor(WEST).nextLine()
|
||||
add(mainMenuButton.button, gb.next().insetLeft(JBUI.scale(16)))
|
||||
add(mainMenuButton.button, gb.next())
|
||||
add(myToolbarPlaceholder, gb.next().weightx(1.0).fillCell())
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -44,7 +44,7 @@ internal class MainToolbar: JPanel(HorizontalLayout(10)) {
|
||||
init {
|
||||
background = JBUI.CurrentTheme.CustomFrameDecorations.mainToolbarBackground(true)
|
||||
isOpaque = true
|
||||
mainMenuButton = if (IdeRootPane.isMenuButtonInToolbar) MainMenuButton() else null
|
||||
mainMenuButton = if (IdeRootPane.isMenuButtonInToolbar) MainMenuButton(null) else null
|
||||
ClientProperty.put(this, IdeBackgroundUtil.NO_BACKGROUND, true)
|
||||
}
|
||||
|
||||
|
||||
@@ -241,6 +241,9 @@ ide.tree.ui.assert.threads.description=Dynamically enables corresponding thread
|
||||
ide.tree.ui.async.batch.size=1000
|
||||
ide.tree.ui.async.batch.size.description=Number of virtual files to process in one read action
|
||||
|
||||
ide.windows.main.menu.expand.horizontal=false
|
||||
ide.windows.main.menu.expand.horizontal.description=Expands hamburger menu horizontally
|
||||
|
||||
ide.macos.main.menu.alignment.options=[Native*|Aligned|Aligned in group|No icons]
|
||||
ide.macos.main.menu.alignment.options.description=Native - align menu items as macOS does, Aligned - align menu items vertically inside a group, No icons - hide icons
|
||||
ide.splitter.mouseZone=6
|
||||
|
||||
Reference in New Issue
Block a user