diff --git a/platform/collaboration-tools/src/com/intellij/collaboration/ui/codereview/list/ReviewListCellRenderer.kt b/platform/collaboration-tools/src/com/intellij/collaboration/ui/codereview/list/ReviewListCellRenderer.kt index c961a74ec98da..09d95c08cc573 100644 --- a/platform/collaboration-tools/src/com/intellij/collaboration/ui/codereview/list/ReviewListCellRenderer.kt +++ b/platform/collaboration-tools/src/com/intellij/collaboration/ui/codereview/list/ReviewListCellRenderer.kt @@ -140,7 +140,7 @@ class ReviewListCellRenderer(private val presenter: (T) -> ReviewListItemPres val color = tag.color if (color != null) { //TODO: need a separate untinted icon to color properly - label.icon = IconUtil.colorize(DvcsImplIcons.BranchLabel, color) + label.icon = IconUtil.colorizeTint(DvcsImplIcons.BranchLabel, color) } else { label.icon = DvcsImplIcons.BranchLabel diff --git a/platform/core-ui/src/util/IconUtil.kt b/platform/core-ui/src/util/IconUtil.kt index 927d20a14ac76..43f2084ffbb3f 100644 --- a/platform/core-ui/src/util/IconUtil.kt +++ b/platform/core-ui/src/util/IconUtil.kt @@ -440,16 +440,28 @@ object IconUtil { @JvmOverloads @JvmStatic - fun colorize(source: Icon, color: Color, keepGray: Boolean = false): Icon { + fun colorizeTint(source: Icon, color: Color, keepGray: Boolean = false): Icon { return filterIcon(icon = source, filterSupplier = object : RgbImageFilterSupplier { - override fun getFilter() = ColorFilter(color = color, keepGray = keepGray) + override fun getFilter() = ColorMultiplyFilter(color = color, keepGray = keepGray) }) } @JvmOverloads @JvmStatic - fun colorize(g: Graphics2D?, source: Icon, color: Color, keepGray: Boolean = false): Icon { - return filterIcon(g = g, source = source, filter = ColorFilter(color = color, keepGray = keepGray)) + fun colorizeTint(g: Graphics2D?, source: Icon, color: Color, keepGray: Boolean = false): Icon { + return filterIcon(g = g, source = source, filter = ColorMultiplyFilter(color = color, keepGray = keepGray)) + } + + @JvmStatic + fun colorizeReplace(source: Icon, color: Color): Icon { + return filterIcon(icon = source, filterSupplier = object : RgbImageFilterSupplier { + override fun getFilter() = ColorReplaceFilter(color = color) + }) + } + + @JvmStatic + fun colorizeReplace(g: Graphics2D?, source: Icon, color: Color): Icon { + return filterIcon(g = g, source = source, filter = ColorReplaceFilter(color = color)) } @JvmStatic @@ -641,7 +653,7 @@ class CropIcon internal constructor(val sourceIcon: Icon, val crop: Rectangle) : override fun hashCode(): Int = Objects.hash(sourceIcon, crop) } -private class ColorFilter(color: Color, private val keepGray: Boolean) : RGBImageFilter() { +private class ColorMultiplyFilter(color: Color, private val keepGray: Boolean) : RGBImageFilter() { private val base = Color.RGBtoHSB(color.red, color.green, color.blue, null) override fun filterRGB(x: Int, y: Int, rgba: Int): Int { @@ -655,6 +667,20 @@ private class ColorFilter(color: Color, private val keepGray: Boolean) : RGBImag } } +private class ColorReplaceFilter(color: Color) : RGBImageFilter() { + private val base = Color.RGBtoHSB(color.red, color.green, color.blue, null) + + override fun filterRGB(x: Int, y: Int, rgba: Int): Int { + val r = rgba shr 16 and 0xff + val g = rgba shr 8 and 0xff + val b = rgba and 0xff + val hsb = FloatArray(3) + Color.RGBtoHSB(r, g, b, hsb) + val rgb = Color.HSBtoRGB(base[0], base[1], base[2]) + return rgba and -0x1000000 or (rgb and 0xffffff) + } +} + private class DesaturationFilter : RGBImageFilter() { override fun filterRGB(x: Int, y: Int, rgba: Int): Int { val r = rgba shr 16 and 0xff @@ -762,7 +788,7 @@ private fun scaleByIcon(icon: Icon?, ancestor: Component?, defaultIcon: Icon, si } } -private fun filterIcon(g: Graphics2D?, source: Icon, filter: ColorFilter): Icon { +private fun filterIcon(g: Graphics2D?, source: Icon, filter: RGBImageFilter): Icon { val src = if (g == null) { ImageUtil.createImage(source.iconWidth, source.iconHeight, BufferedImage.TYPE_INT_ARGB) } diff --git a/platform/platform-impl/src/com/intellij/openapi/wm/impl/customFrameDecorations/CustomFrameTitleButtons.kt b/platform/platform-impl/src/com/intellij/openapi/wm/impl/customFrameDecorations/CustomFrameTitleButtons.kt index 2d0c3634837e4..44404d5c5d42f 100644 --- a/platform/platform-impl/src/com/intellij/openapi/wm/impl/customFrameDecorations/CustomFrameTitleButtons.kt +++ b/platform/platform-impl/src/com/intellij/openapi/wm/impl/customFrameDecorations/CustomFrameTitleButtons.kt @@ -1,33 +1,55 @@ // 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 -import com.intellij.icons.AllIcons import com.intellij.ide.ui.UISettings +import com.intellij.openapi.util.SystemInfo +import com.intellij.openapi.util.registry.Registry +import com.intellij.openapi.wm.impl.X11UiUtil +import com.intellij.openapi.wm.impl.customFrameDecorations.frameTitleButtons.FrameTitleButtons +import com.intellij.openapi.wm.impl.customFrameDecorations.frameTitleButtons.LinuxFrameTitleButtons +import com.intellij.openapi.wm.impl.customFrameDecorations.frameTitleButtons.WindowsFrameTitleButtons import com.intellij.openapi.wm.impl.customFrameDecorations.style.ComponentStyle import com.intellij.openapi.wm.impl.customFrameDecorations.style.ComponentStyleState -import com.intellij.openapi.wm.impl.customFrameDecorations.style.HOVER_KEY import com.intellij.openapi.wm.impl.customFrameDecorations.style.StyleManager import com.intellij.ui.scale.JBUIScale import com.intellij.util.ui.JBUI.Borders import com.intellij.util.ui.JBUI.CurrentTheme -import java.awt.Color import java.awt.Dimension import java.awt.FlowLayout -import java.awt.Graphics -import javax.accessibility.AccessibleContext -import javax.swing.* -import javax.swing.plaf.ButtonUI -import javax.swing.plaf.basic.BasicButtonUI +import javax.swing.Action +import javax.swing.Icon +import javax.swing.JComponent +import javax.swing.JPanel -internal open class CustomFrameTitleButtons(myCloseAction: Action) { + +internal open class CustomFrameTitleButtons(private val myCloseAction: Action, + private val myRestoreAction: Action? = null, + private val myIconifyAction: Action? = null, + private val myMaximizeAction: Action? = null +) { companion object { - fun create(closeAction: Action): CustomFrameTitleButtons { - val darculaTitleButtons = CustomFrameTitleButtons(closeAction) + fun create(myCloseAction: Action, + myRestoreAction: Action? = null, + myIconifyAction: Action? = null, + myMaximizeAction: Action? = null): CustomFrameTitleButtons { + val darculaTitleButtons = CustomFrameTitleButtons(myCloseAction, myRestoreAction, myIconifyAction, myMaximizeAction) darculaTitleButtons.createChildren() return darculaTitleButtons } } + private val isLinuxThemingEnabled = Registry.`is`("ide.linux.mimic.system.theme", false) + + private val buttons: FrameTitleButtons = if ( + SystemInfo.isLinux && + (SystemInfo.isGNOME || SystemInfo.isKDE) && + isLinuxThemingEnabled && + !X11UiUtil.isWSL() + ) + LinuxFrameTitleButtons(myCloseAction, myRestoreAction, myIconifyAction, myMaximizeAction) + else + WindowsFrameTitleButtons(myCloseAction, myRestoreAction, myIconifyAction, myMaximizeAction) + private val baseStyle = ComponentStyle.ComponentStyleBuilder { isOpaque = false border = Borders.empty() @@ -41,32 +63,11 @@ internal open class CustomFrameTitleButtons(myCloseAction: Action) { } } - private val closeStyleBuilder: ComponentStyle.ComponentStyleBuilder = ComponentStyle.ComponentStyleBuilder { - isOpaque = false - border = Borders.empty() - icon = AllIcons.Windows.CloseActive - }.apply { - style(ComponentStyleState.HOVERED) { - isOpaque = true - background = Color(0xe81123) - icon = AllIcons.Windows.CloseHover - } - style(ComponentStyleState.PRESSED) { - isOpaque = true - background = Color(0xf1707a) - icon = AllIcons.Windows.CloseHover - } - } - private val activeCloseStyle = closeStyleBuilder.build() - private val inactiveCloseStyle = closeStyleBuilder - .updateDefault { - icon = AllIcons.Windows.CloseInactive - }.build() + private val panel = TitleButtonsPanel(buttons) - private val panel = TitleButtonsPanel() + fun getView(): JComponent = panel - val closeButton: JButton = createButton("Close", myCloseAction) internal var isCompactMode: Boolean set(value) { @@ -78,40 +79,62 @@ internal open class CustomFrameTitleButtons(myCloseAction: Action) { var isSelected: Boolean = false set(value) { - if(field != value) { + if (field != value) { field = value updateStyles() } } protected open fun updateStyles() { - StyleManager.applyStyle(closeButton, if(isSelected) activeCloseStyle else inactiveCloseStyle) + StyleManager.applyStyle( + buttons.closeButton, + getStyle( + if (isSelected) buttons.closeIcon else buttons.closeInactiveIcon, + buttons.closeHoverIcon + ) + ) + buttons.restoreButton?.let { + StyleManager.applyStyle( + it, + getStyle( + if (isSelected) buttons.restoreIcon else buttons.restoreInactiveIcon, + buttons.restoreIcon + ) + ) + } + buttons.maximizeButton?.let { + StyleManager.applyStyle( + it, + getStyle( + if (isSelected) buttons.maximizeIcon else buttons.maximizeInactiveIcon, + buttons.maximizeIcon + ) + ) + } + buttons.minimizeButton?.let { + StyleManager.applyStyle( + it, + getStyle( + if (isSelected) buttons.minimizeIcon else buttons.minimizeInactiveIcon, + buttons.minimizeIcon + ) + ) + } } protected fun createChildren() { - fillButtonPane() - addCloseButton() + buttons.fillButtonPane(panel) updateVisibility() updateStyles() } - fun getView(): JComponent = panel - - protected open fun fillButtonPane() { - } - open fun updateVisibility() { + buttons.minimizeButton?.isVisible = myIconifyAction?.isEnabled ?: false + buttons.restoreButton?.isVisible = myRestoreAction?.isEnabled ?: false + buttons.maximizeButton?.isVisible = myMaximizeAction?.isEnabled ?: false } - private fun addCloseButton() { - addComponent(closeButton) - } - - protected fun addComponent(component: JComponent) { - panel.addComponent(component) - } - - protected fun getStyle(icon: Icon, hoverIcon : Icon): ComponentStyle { + protected fun getStyle(icon: Icon, hoverIcon: Icon): ComponentStyle { val clone = baseStyle.clone() clone.updateDefault { this.icon = icon @@ -127,35 +150,9 @@ internal open class CustomFrameTitleButtons(myCloseAction: Action) { return clone.build() } - protected fun createButton(accessibleName: String, action: Action): JButton { - val button = object : JButton(){ - init { - super.setUI(HoveredButtonUI()) - } - - override fun setUI(ui: ButtonUI?) { - } - } - button.action = action - button.isFocusable = false - button.putClientProperty(AccessibleContext.ACCESSIBLE_NAME_PROPERTY, accessibleName) - button.text = null - return button - } -} -private class HoveredButtonUI : BasicButtonUI() { - override fun paint(g: Graphics, c: JComponent) { - getHoverColor(c)?.let { - g.color = it - g.fillRect(0, 0, c.width, c.height) - } - super.paint(g, c) - } - - private fun getHoverColor(c: JComponent): Color? = c.getClientProperty(HOVER_KEY) as? Color } -private class TitleButtonsPanel : JPanel(FlowLayout(FlowLayout.LEADING, 0, 0)) { +class TitleButtonsPanel(val buttons: FrameTitleButtons) : JPanel(FlowLayout(FlowLayout.LEADING, 0, 0)) { var isCompactMode = false set(value) { field = value @@ -177,10 +174,11 @@ private class TitleButtonsPanel : JPanel(FlowLayout(FlowLayout.LEADING, 0, 0)) { private fun JComponent.setScaledPreferredSize() { val size = CurrentTheme.TitlePane.buttonPreferredSize(UISettings.defFontScale).clone() as Dimension + // TODO isCompactMode is always false if (isCompactMode) { size.height = JBUIScale.scale(30) } - preferredSize = Dimension(size.width, size.height) + preferredSize = buttons.setScaledPreferredSize(size) } override fun updateUI() { diff --git a/platform/platform-impl/src/com/intellij/openapi/wm/impl/customFrameDecorations/LinuxLookAndFeel.kt b/platform/platform-impl/src/com/intellij/openapi/wm/impl/customFrameDecorations/LinuxLookAndFeel.kt new file mode 100644 index 0000000000000..7380d4fdf3bba --- /dev/null +++ b/platform/platform-impl/src/com/intellij/openapi/wm/impl/customFrameDecorations/LinuxLookAndFeel.kt @@ -0,0 +1,161 @@ +// 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 + +import com.intellij.icons.AllIcons +import com.intellij.openapi.util.SystemInfo +import com.intellij.ui.IconManager +import com.intellij.ui.JBColor +import com.intellij.util.IconUtil +import java.io.BufferedReader +import java.io.File +import java.io.InputStreamReader +import javax.swing.Icon +import kotlin.concurrent.thread + + +class LinuxLookAndFeel { + companion object { + val linuxIconPath = "/usr/share/icons" + val linuxHomePath = System.getenv("HOME") + fun getLinuxIcon(name: WindowToolbarIcons): Icon? { + val iconName: String + var iconPath: String? = null + if (SystemInfo.isGNOME) { + iconName = when (name) { + WindowToolbarIcons.CLOSE -> "window-close-symbolic.svg" + WindowToolbarIcons.MAXIMIZE -> "window-maximize-symbolic.svg" + WindowToolbarIcons.RESTORE -> "window-restore-symbolic.svg" + WindowToolbarIcons.MINIMIZE -> "window-minimize-symbolic.svg" + } + iconPath = findIconAbsolutePath(iconName) + } + else if (SystemInfo.isKDE) { + iconName = when (name) { + WindowToolbarIcons.CLOSE -> "close-normal.svg" + WindowToolbarIcons.MAXIMIZE -> "maximize-normal.svg" + WindowToolbarIcons.RESTORE -> "maximized-normal.svg" + WindowToolbarIcons.MINIMIZE -> "minimize-normal.svg" + } + iconPath = "$linuxHomePath/.config/gtk-3.0/assets/$iconName" + } + + + if (iconPath.isNullOrEmpty()) return null + var icon = IconManager.getInstance().getIcon("file:$iconPath", + AllIcons::class.java.classLoader) + icon = IconUtil.colorizeReplace(icon, JBColor(0x6c7080, 0xcfd1d8)) + return icon + } + + fun findIconAbsolutePath(iconName: String, useOtherTheme: String? = null, recursiveDepth: Int = 0): String? { + val themeName = useOtherTheme ?: getCurrentIconTheme() ?: return null + + val themePath = "$linuxIconPath/$themeName" + + // Prefer first find in 16x16 subfolder + var iconPath = execute("find $themePath/16x16 -type f -name $iconName") + if (isValidIconPath(iconPath)) + return iconPath + + // If no icons found, found anywhere + iconPath = execute("find $themePath -type f -name $iconName") + if (isValidIconPath(iconPath)) + return iconPath + + + if (recursiveDepth > 10) return null + + // If no icon found, then find icons in theme inheritance + for (inheritIconTheme in getInheritedIconThemes(themeName)) { + iconPath = findIconAbsolutePath(iconName, inheritIconTheme, recursiveDepth + 1) + if (isValidIconPath(iconPath)) + return iconPath + } + + return null + } + + private fun isValidIconPath(iconPath: String?): Boolean { + return !iconPath.isNullOrEmpty() && iconPath.startsWith(linuxIconPath) + } + + fun getInheritedIconThemes(themeName: String): List { + try { + val themePath = "$linuxIconPath/$themeName" + val themeConfigFile = File("$themePath/index.theme") + val inheritanceString = "Inherits=" + if (themeConfigFile.exists()) { + val lines = themeConfigFile.readLines() + for (line in lines) { + if (line.startsWith(inheritanceString)) { + return line.substringAfter(inheritanceString).split(",").map { it.trim() } + } + } + } + return listOf() + } + catch (exception: Exception) { + return listOf() + } + } + + fun getCurrentIconTheme(): String? { + return getDconfEntry("/org/gnome/desktop/interface/icon-theme")?.drop(1)?.dropLast(1) // Remove double quotes + } + + fun getHeaderLayout(): List { + try { + if (SystemInfo.isGNOME) { + // Next line returns something like appmenu:minimize,maximize,close + var elementsString = getDconfEntry("/org/gnome/desktop/wm/preferences/button-layout") + elementsString = elementsString?.drop(1)?.dropLast(1) // Remove double quotes + return elementsString?.split(":", ",") ?: emptyList() + } + else if (SystemInfo.isKDE) { + val gtk3ConfigFile = File("$linuxHomePath/.config/gtk-3.0/settings.ini") + val paramName = "gtk-decoration-layout=" + if (gtk3ConfigFile.exists()) { + val lines = gtk3ConfigFile.readLines() + for (line in lines) { + if (line.startsWith(paramName)) { + // Next line returns something like icon:minimize,maximize,close + return line.substringAfter(paramName).split(":", ",").map { it.trim() } + } + } + } + } + } + catch (exception: Exception) { + exception.printStackTrace() + } + return emptyList() + } + + private fun getDconfEntry(key: String): String? { + return execute("dconf read $key") + } + + private fun execute(command: String): String? { + try { + val processBuilder = ProcessBuilder(command.split(" ")) + processBuilder.redirectErrorStream(true) + + val process = processBuilder.start() + val reader = BufferedReader(InputStreamReader(process.inputStream)) + + val line: String? = reader.readLine() + process.waitFor() + + return line + } + catch (exception: Exception) { + exception.printStackTrace() + } + return null + } + } +} + +enum class WindowToolbarIcons { + MAXIMIZE, MINIMIZE, RESTORE, CLOSE +} \ No newline at end of file diff --git a/platform/platform-impl/src/com/intellij/openapi/wm/impl/customFrameDecorations/ResizableCustomFrameTitleButtons.kt b/platform/platform-impl/src/com/intellij/openapi/wm/impl/customFrameDecorations/ResizableCustomFrameTitleButtons.kt deleted file mode 100644 index 5dd3606a582ee..0000000000000 --- a/platform/platform-impl/src/com/intellij/openapi/wm/impl/customFrameDecorations/ResizableCustomFrameTitleButtons.kt +++ /dev/null @@ -1,57 +0,0 @@ -// 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 - -import com.intellij.icons.AllIcons -import com.intellij.openapi.wm.impl.customFrameDecorations.style.StyleManager -import javax.swing.Action - -internal class ResizableCustomFrameTitleButtons(closeAction: Action, - private val myRestoreAction: Action, - private val myIconifyAction: Action, - private val myMaximizeAction: Action -) : CustomFrameTitleButtons(closeAction) { - companion object { - private val restoreIcon = AllIcons.Windows.Restore - private val restoreInactiveIcon = AllIcons.Windows.RestoreInactive - - private val maximizeIcon = AllIcons.Windows.Maximize - private val maximizeInactiveIcon = AllIcons.Windows.MaximizeInactive - - private val minimizeIcon = AllIcons.Windows.Minimize - private val minimizeInactiveIcon = AllIcons.Windows.MinimizeInactive - - fun create(myCloseAction: Action, - myRestoreAction: Action, - myIconifyAction: Action, - myMaximizeAction: Action): ResizableCustomFrameTitleButtons { - val darculaTitleButtons = ResizableCustomFrameTitleButtons(myCloseAction, myRestoreAction, myIconifyAction, myMaximizeAction) - darculaTitleButtons.createChildren() - return darculaTitleButtons - } - } - - private val restoreButton = createButton("Restore", myRestoreAction) - private val maximizeButton = createButton("Maximize", myMaximizeAction) - private val minimizeButton = createButton("Iconify", myIconifyAction) - - override fun fillButtonPane() { - super.fillButtonPane() - addComponent(minimizeButton) - addComponent(maximizeButton) - addComponent(restoreButton) - } - - override fun updateVisibility() { - super.updateVisibility() - minimizeButton.isVisible = myIconifyAction.isEnabled - restoreButton.isVisible = myRestoreAction.isEnabled - maximizeButton.isVisible = myMaximizeAction.isEnabled - } - - override fun updateStyles() { - super.updateStyles() - StyleManager.applyStyle(restoreButton, getStyle(if (isSelected) restoreIcon else restoreInactiveIcon, restoreIcon)) - StyleManager.applyStyle(maximizeButton, getStyle(if (isSelected) maximizeIcon else maximizeInactiveIcon, maximizeIcon)) - StyleManager.applyStyle(minimizeButton, getStyle(if (isSelected) minimizeIcon else minimizeInactiveIcon, minimizeIcon)) - } -} \ No newline at end of file diff --git a/platform/platform-impl/src/com/intellij/openapi/wm/impl/customFrameDecorations/frameTitleButtons/FrameTitleButtons.kt b/platform/platform-impl/src/com/intellij/openapi/wm/impl/customFrameDecorations/frameTitleButtons/FrameTitleButtons.kt new file mode 100644 index 0000000000000..bb57c504ec47e --- /dev/null +++ b/platform/platform-impl/src/com/intellij/openapi/wm/impl/customFrameDecorations/frameTitleButtons/FrameTitleButtons.kt @@ -0,0 +1,32 @@ +// 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.frameTitleButtons + +import com.intellij.openapi.wm.impl.customFrameDecorations.TitleButtonsPanel +import java.awt.Dimension +import javax.swing.Action +import javax.swing.Icon +import javax.swing.JButton + +interface FrameTitleButtons { + val closeButton: JButton + val restoreButton: JButton? + val maximizeButton: JButton? + val minimizeButton: JButton? + + val restoreIcon: Icon + val restoreInactiveIcon: Icon + + val maximizeIcon: Icon + val maximizeInactiveIcon: Icon + + val minimizeIcon: Icon + val minimizeInactiveIcon: Icon + + val closeIcon: Icon + val closeInactiveIcon: Icon + val closeHoverIcon: Icon + + fun createButton(accessibleName: String, action: Action): JButton + fun fillButtonPane(panel: TitleButtonsPanel) + fun setScaledPreferredSize(size: Dimension): Dimension +} \ No newline at end of file diff --git a/platform/platform-impl/src/com/intellij/openapi/wm/impl/customFrameDecorations/frameTitleButtons/LinuxFrameTitleButtons.kt b/platform/platform-impl/src/com/intellij/openapi/wm/impl/customFrameDecorations/frameTitleButtons/LinuxFrameTitleButtons.kt new file mode 100644 index 0000000000000..4c24eb144abbc --- /dev/null +++ b/platform/platform-impl/src/com/intellij/openapi/wm/impl/customFrameDecorations/frameTitleButtons/LinuxFrameTitleButtons.kt @@ -0,0 +1,151 @@ +// 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.frameTitleButtons + +import com.intellij.icons.AllIcons +import com.intellij.ide.ui.LafManager +import com.intellij.openapi.util.SystemInfo +import com.intellij.openapi.wm.impl.customFrameDecorations.LinuxLookAndFeel +import com.intellij.openapi.wm.impl.customFrameDecorations.TitleButtonsPanel +import com.intellij.openapi.wm.impl.customFrameDecorations.WindowToolbarIcons +import com.intellij.openapi.wm.impl.customFrameDecorations.style.HOVER_KEY +import com.intellij.util.ui.JBUI +import java.awt.* +import javax.accessibility.AccessibleContext +import javax.swing.Action +import javax.swing.JButton +import javax.swing.JComponent +import javax.swing.plaf.ButtonUI +import javax.swing.plaf.basic.BasicButtonUI + + +class LinuxFrameTitleButtons( + myCloseAction: Action, + myRestoreAction: Action? = null, + myIconifyAction: Action? = null, + myMaximizeAction: Action? = null) : FrameTitleButtons { + override val closeButton: JButton = createButton("Close", myCloseAction) + override val restoreButton: JButton? = myRestoreAction?.let { createButton("Restore", it) } + override val maximizeButton: JButton? = myMaximizeAction?.let { createButton("Maximize", it) } + override val minimizeButton: JButton? = myIconifyAction?.let { createButton("Iconify", it) } + + + val restore = LinuxLookAndFeel.getLinuxIcon(WindowToolbarIcons.RESTORE) ?: AllIcons.Windows.Restore + override val restoreIcon = restore + override val restoreInactiveIcon = restore + + val maximize = LinuxLookAndFeel.getLinuxIcon(WindowToolbarIcons.MAXIMIZE) ?: AllIcons.Windows.Maximize + override val maximizeIcon = maximize + override val maximizeInactiveIcon = maximize + + val minimize = LinuxLookAndFeel.getLinuxIcon(WindowToolbarIcons.MINIMIZE) ?: AllIcons.Windows.Minimize + override val minimizeIcon = minimize + override val minimizeInactiveIcon = minimize + + val close = LinuxLookAndFeel.getLinuxIcon(WindowToolbarIcons.CLOSE) ?: AllIcons.Windows.CloseActive + override val closeIcon = close + override val closeInactiveIcon = close + override val closeHoverIcon = close + + override fun createButton(accessibleName: String, action: Action): JButton { + val button = object : JButton() { + init { + super.setUI(HoveredCircleButtonUI(accessibleName)) + } + + override fun setUI(ui: ButtonUI?) { + } + } + button.action = action + button.isFocusable = false + button.putClientProperty(AccessibleContext.ACCESSIBLE_NAME_PROPERTY, accessibleName) + button.text = null + return button + } + + override fun fillButtonPane(panel: TitleButtonsPanel) { + var linuxButtonsLayout = LinuxLookAndFeel.getHeaderLayout() + if (linuxButtonsLayout.isEmpty()) { + linuxButtonsLayout = listOf("minimize", "maximize", "close") + } + if (!linuxButtonsLayout.contains("close")) { + linuxButtonsLayout = linuxButtonsLayout.plus("close") + } + for (item in linuxButtonsLayout) { + when (item) { + "minimize" -> this.minimizeButton?.let { panel.addComponent(it) } + "maximize" -> { + this.maximizeButton?.let { panel.addComponent(it) } + this.restoreButton?.let { panel.addComponent(it) } + } + "close" -> panel.addComponent(this.closeButton) + } + } + panel.border = JBUI.Borders.emptyRight(1) + } + + + override fun setScaledPreferredSize(size: Dimension): Dimension { + if (SystemInfo.isKDE) { + return Dimension(24, size.height) + } + return Dimension(38, size.height) + } +} + + +// Can not use JBColor because title frame can use a dark background in Light theme +@Suppress("UseJBColor") +private class HoveredCircleButtonUI(val accessibleName: String) : BasicButtonUI() { + private val circleDiameter = 24 + + // Adwaita GTK background circle: + // - light theme: rgba(0, 0, 0, 0.08) -> 20.40 + // - dark theme: rgba(255, 255, 255, 0.1) -> 25.5 + private val circleLightBackground = Color(1f, 1f, 1f, 0.08f) + private val circleDarkBackground = Color(0f, 0f, 0f, 0.1f) + + // Breeze KDE background circle + private val circleTransparent = Color(0f, 0f, 0f, 0f) + private val circleRedHover = Color(1f, 0.572f, 0.638f, 1f) + private val circleDarkHover = Color(0.114f, 0.129f, 0.144f, 1f) + private val circleLightHover = Color(0.989f, 0.989f, 0.989f, 1f) + + override fun paint(g: Graphics, c: JComponent) { + val isLightBackground = with(LafManager.getInstance().currentUIThemeLookAndFeel.name) { + this == "Light with Light Header" || this == "IntelliJ Light" + } + if (SystemInfo.isKDE) { + g.color = circleTransparent + getHoverColor(c)?.let { + g.color = if (accessibleName == "Close") { + circleRedHover + } else { + if (isLightBackground) { + circleDarkHover + } else { + circleLightHover + } + } + } + } else { + val backgroundColor = if (isLightBackground) circleDarkBackground else circleLightBackground + + g.color = backgroundColor + getHoverColor(c)?.let { + g.color = alterAlpha(backgroundColor, 0.05f) + } + } + if (g is Graphics2D) { + g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON) + g.fillRoundRect((c.width / 2) - (circleDiameter / 2), (c.height / 2) - (circleDiameter / 2), circleDiameter, circleDiameter, c.width, c.height) + } + super.paint(g, c) + } + + private fun getHoverColor(c: JComponent): Color? = c.getClientProperty(HOVER_KEY) as? Color + + private fun alterAlpha(color: Color, amount: Float): Color { + return Color(color.red, color.green, color.blue, (((color.alpha.toFloat() / 256f) + amount) * 256).toInt()) + } +} + diff --git a/platform/platform-impl/src/com/intellij/openapi/wm/impl/customFrameDecorations/frameTitleButtons/WindowsFrameTitleButtons.kt b/platform/platform-impl/src/com/intellij/openapi/wm/impl/customFrameDecorations/frameTitleButtons/WindowsFrameTitleButtons.kt new file mode 100644 index 0000000000000..94e3120984761 --- /dev/null +++ b/platform/platform-impl/src/com/intellij/openapi/wm/impl/customFrameDecorations/frameTitleButtons/WindowsFrameTitleButtons.kt @@ -0,0 +1,82 @@ +// 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.frameTitleButtons + +import com.intellij.icons.AllIcons +import com.intellij.openapi.wm.impl.customFrameDecorations.TitleButtonsPanel +import com.intellij.openapi.wm.impl.customFrameDecorations.style.HOVER_KEY +import com.intellij.ui.JBColor +import com.intellij.util.IconUtil +import java.awt.Color +import java.awt.Dimension +import java.awt.Graphics +import javax.accessibility.AccessibleContext +import javax.swing.Action +import javax.swing.JButton +import javax.swing.JComponent +import javax.swing.plaf.ButtonUI +import javax.swing.plaf.basic.BasicButtonUI + +class WindowsFrameTitleButtons( + myCloseAction: Action, + myRestoreAction: Action? = null, + myIconifyAction: Action? = null, + myMaximizeAction: Action? = null) : FrameTitleButtons { + override val closeButton: JButton = createButton("Close", myCloseAction) + override val restoreButton: JButton? = myRestoreAction?.let { createButton("Restore", it) } + override val maximizeButton: JButton? = myMaximizeAction?.let { createButton("Maximize", it) } + override val minimizeButton: JButton? = myIconifyAction?.let { createButton("Iconify", it) } + + override val restoreIcon = AllIcons.Windows.Restore + override val restoreInactiveIcon = AllIcons.Windows.RestoreInactive + + override val maximizeIcon = AllIcons.Windows.Maximize + override val maximizeInactiveIcon = AllIcons.Windows.MaximizeInactive + + override val minimizeIcon = AllIcons.Windows.Minimize + override val minimizeInactiveIcon = AllIcons.Windows.MinimizeInactive + + + override val closeIcon = AllIcons.Windows.CloseActive + override val closeInactiveIcon = AllIcons.Windows.CloseInactive + override val closeHoverIcon = IconUtil.colorizeReplace(AllIcons.Windows.CloseActive, JBColor(0xffffff, 0xffffff)) + + override fun createButton(accessibleName: String, action: Action): JButton { + val button = object : JButton() { + init { + super.setUI(if (accessibleName == "Close") HoveredButtonUI(Color(0xe81123)) else HoveredButtonUI()) + } + + override fun setUI(ui: ButtonUI?) { + } + } + button.action = action + button.isFocusable = false + button.putClientProperty(AccessibleContext.ACCESSIBLE_NAME_PROPERTY, accessibleName) + button.text = null + return button + } + + override fun fillButtonPane(panel: TitleButtonsPanel) { + this.minimizeButton?.let { panel.addComponent(it) } + this.maximizeButton?.let { panel.addComponent(it) } + this.restoreButton?.let { panel.addComponent(it) } + panel.addComponent(this.closeButton) + } + + override fun setScaledPreferredSize(size: Dimension): Dimension { + return Dimension(size.width, size.height) + } +} + + +private class HoveredButtonUI(private val customBackgroundColor: Color? = null) : BasicButtonUI() { + override fun paint(g: Graphics, c: JComponent) { + getHoverColor(c)?.let { + g.color = customBackgroundColor ?: it + g.fillRect(0, 0, c.width, c.height) + } + super.paint(g, c) + } + + private fun getHoverColor(c: JComponent): Color? = c.getClientProperty(HOVER_KEY) as? Color +} \ No newline at end of file diff --git a/platform/platform-impl/src/com/intellij/openapi/wm/impl/customFrameDecorations/header/FrameHeader.kt b/platform/platform-impl/src/com/intellij/openapi/wm/impl/customFrameDecorations/header/FrameHeader.kt index a899a042c0ed8..b8621ccf1a470 100644 --- a/platform/platform-impl/src/com/intellij/openapi/wm/impl/customFrameDecorations/header/FrameHeader.kt +++ b/platform/platform-impl/src/com/intellij/openapi/wm/impl/customFrameDecorations/header/FrameHeader.kt @@ -7,7 +7,6 @@ import com.intellij.ide.IdeBundle import com.intellij.idea.ActionsBundle import com.intellij.openapi.wm.impl.IdeRootPane import com.intellij.openapi.wm.impl.customFrameDecorations.CustomFrameTitleButtons -import com.intellij.openapi.wm.impl.customFrameDecorations.ResizableCustomFrameTitleButtons import com.intellij.util.ui.JBFont import java.awt.Font import java.awt.Frame @@ -98,7 +97,7 @@ internal open class FrameHeader(protected val frame: JFrame) : CustomHeader(fram private fun createButtonsPane(): CustomFrameTitleButtons? { if (IdeRootPane.hideNativeLinuxTitle) { - return ResizableCustomFrameTitleButtons.create(closeAction, restoreAction, iconifyAction, maximizeAction) + return CustomFrameTitleButtons.create(closeAction, restoreAction, iconifyAction, maximizeAction) } return null } diff --git a/platform/platform-impl/src/com/intellij/openapi/wm/impl/welcomeScreen/FlatWelcomeFrame.kt b/platform/platform-impl/src/com/intellij/openapi/wm/impl/welcomeScreen/FlatWelcomeFrame.kt index d315b53227f75..288506b8eff6f 100644 --- a/platform/platform-impl/src/com/intellij/openapi/wm/impl/welcomeScreen/FlatWelcomeFrame.kt +++ b/platform/platform-impl/src/com/intellij/openapi/wm/impl/welcomeScreen/FlatWelcomeFrame.kt @@ -474,7 +474,7 @@ open class FlatWelcomeFrame @JvmOverloads constructor( var icon = presentation.icon if (icon == null || icon.iconHeight != JBUIScale.scale(16) || icon.iconWidth != JBUIScale.scale(16)) { icon = if (icon == null) JBUIScale.scaleIcon(EmptyIcon.create(16)) else IconUtil.scale(icon, null, 16f / icon.iconWidth) - icon = IconUtil.colorize(icon, JBColor(0x6e6e6e, 0xafb1b3)) + icon = IconUtil.colorizeTint(icon, JBColor(0x6e6e6e, 0xafb1b3)) } action = ActionGroupPanelWrapper.wrapGroups(action, this) val link = ActionLink(text, icon, action, null, ActionPlaces.WELCOME_SCREEN) diff --git a/platform/platform-impl/src/com/intellij/platform/ide/impl/presentationAssistant/PresentationAssistantQuickSettingsButton.kt b/platform/platform-impl/src/com/intellij/platform/ide/impl/presentationAssistant/PresentationAssistantQuickSettingsButton.kt index 1a391aa59d37f..f6f19128fd59a 100644 --- a/platform/platform-impl/src/com/intellij/platform/ide/impl/presentationAssistant/PresentationAssistantQuickSettingsButton.kt +++ b/platform/platform-impl/src/com/intellij/platform/ide/impl/presentationAssistant/PresentationAssistantQuickSettingsButton.kt @@ -27,7 +27,7 @@ import javax.swing.SwingConstants internal class PresentationAssistantQuickSettingsButton(private val project: Project, private val appearance: ActionInfoPopupGroup.Appearance, private val shownStateRequestCountChanged: (Int) -> Unit): - JBLabel(IconUtil.colorize(AllIcons.Actions.PresentationAssistantSettings, appearance.theme.keymapLabel)), Disposable, DataContext { + JBLabel(IconUtil.colorizeTint(AllIcons.Actions.PresentationAssistantSettings, appearance.theme.keymapLabel)), Disposable, DataContext { private var popup: JBPopup? = null private var hideAlarm = Alarm() diff --git a/platform/platform-impl/src/com/intellij/ui/icons/CoreIconManager.kt b/platform/platform-impl/src/com/intellij/ui/icons/CoreIconManager.kt index 21142fff582f6..6cf038ca21767 100644 --- a/platform/platform-impl/src/com/intellij/ui/icons/CoreIconManager.kt +++ b/platform/platform-impl/src/com/intellij/ui/icons/CoreIconManager.kt @@ -174,7 +174,7 @@ class CoreIconManager : IconManager, CoreAwareIconManager { override fun createOffsetIcon(icon: Icon): Icon = OffsetIcon(icon) - override fun colorize(g: Graphics2D, source: Icon, color: Color): Icon = IconUtil.colorize(g, source, color) + override fun colorize(g: Graphics2D, source: Icon, color: Color): Icon = IconUtil.colorizeTint(g, source, color) override fun createLayered(vararg icons: Icon): Icon = LayeredIcon.layeredIcon(icons) diff --git a/platform/util/resources/misc/registry.properties b/platform/util/resources/misc/registry.properties index b065d3d4109a3..8ad8d82088109 100644 --- a/platform/util/resources/misc/registry.properties +++ b/platform/util/resources/misc/registry.properties @@ -1925,6 +1925,10 @@ ide.linux.use.undecorated.border=true ide.linux.use.undecorated.border.description=Enables undecorated border for windows, see Window.undecorated.border theme property ide.linux.use.undecorated.border.restartRequired=true +ide.linux.mimic.system.theme=false +ide.linux.mimic.system.theme.description=Mimics linux look and feel +ide.linux.mimic.system.theme.restartRequired=true + ide.tree.large.model.allowed=false ide.tree.large.model.allowed.description=Allows to use FixedHeightLayoutCache if setLargeModel(true)