Fix option button ui for different LaFs

This commit is contained in:
Konstantin Kolosovsky
2018-01-30 01:13:45 +03:00
parent c968462fc7
commit 2d7cbbe52f
16 changed files with 365 additions and 45 deletions
@@ -529,7 +529,9 @@ public final class LafManagerImpl extends LafManager implements PersistentStateC
* and default UI is created there directly.
*/
private static void fixOptionButton(UIDefaults uiDefaults) {
uiDefaults.put("OptionButtonUI", BasicOptionButtonUI.class.getCanonicalName());
if (!UIUtil.isUnderIntelliJLaF() && !UIUtil.isUnderDarcula()) {
uiDefaults.put("OptionButtonUI", BasicOptionButtonUI.class.getCanonicalName());
}
}
/**
@@ -0,0 +1,101 @@
/*
* Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
*/
package com.intellij.ide.ui.laf.darcula
import com.intellij.ide.ui.laf.darcula.DarculaUIUtil.lw
import com.intellij.ide.ui.laf.darcula.ui.DarculaButtonPainter
import com.intellij.ide.ui.laf.darcula.ui.DarculaButtonUI.getDisabledTextColor
import com.intellij.ide.ui.laf.darcula.ui.DarculaButtonUI.getTextColor
import com.intellij.ide.ui.laf.darcula.ui.DarculaComboBoxUI
import com.intellij.ui.components.BasicOptionButtonUI
import com.intellij.util.ui.JBUI
import com.intellij.util.ui.JBUI.scale
import java.awt.Dimension
import java.awt.Graphics
import java.awt.Graphics2D
import java.awt.RenderingHints
import java.awt.geom.Line2D
import javax.swing.AbstractButton
import javax.swing.JComponent
import javax.swing.border.Border
open class DarculaOptionButtonUI : BasicOptionButtonUI() {
protected open val clipXOffset = scale(7)
private var optionButtonBorder: Border? = null
override fun configureOptionButton() = super.configureOptionButton().also { optionButtonBorder = optionButton.border }
override fun unconfigureOptionButton() = super.unconfigureOptionButton().also {
optionButton.border = optionButtonBorder
optionButtonBorder = null
}
override fun createMainButton() = object : MainButton() {
override fun paintNotSimple(g: Graphics2D) {
g.clipRect(0, 0, width - clipXOffset, height)
paintBackground(g, this)
super.paintNotSimple(g)
}
}
override fun configureMainButton() = super.configureMainButton().also { mainButton.isOpaque = false }
override fun unconfigureMainButton() = super.unconfigureMainButton().also { mainButton.isOpaque = true }
override fun createArrowButton() = object : ArrowButton() {
override fun paintNotSimple(g: Graphics2D) {
g.clipRect(clipXOffset, 0, width - clipXOffset, height)
paintBackground(g, this)
super.paintNotSimple(g)
paintArrow(g, this)
}
}
protected open fun paintArrow(g: Graphics2D, b: AbstractButton) {
g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON)
g.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL, RenderingHints.VALUE_STROKE_NORMALIZE)
g.color = if (b.isEnabled) getTextColor(b) else getDisabledTextColor()
g.fill(DarculaComboBoxUI.getArrowShape(b.width, b.height))
}
override fun configureArrowButton() = super.configureArrowButton().also { arrowButton.isOpaque = false }
override fun unconfigureArrowButton() = super.unconfigureArrowButton().also { arrowButton.isOpaque = true }
override val arrowButtonPreferredSize get() = Dimension(scale(28), optionButton.preferredSize.height)
override val showPopupXOffset get() = JBUI.scale(5)
override fun paint(g: Graphics, c: JComponent) {
if (!isSimpleButton) paintSeparatorArea(g as Graphics2D, c)
}
protected open fun paintSeparatorArea(g: Graphics2D, c: JComponent) {
g.clipRect(mainButton.width - clipXOffset, 0, 2 * clipXOffset, c.height)
paintBackground(g, c)
mainButton.ui.paint(g, c)
paintSeparator(g, c)
}
protected open fun paintSeparator(g: Graphics2D, c: JComponent) {
val insets = mainButton.insets
val yOffset = (insets.top + insets.bottom) / 4 + lw(g)
val x = mainButton.width.toFloat()
g.color = (mainButton.border as DarculaButtonPainter).borderColor
g.draw(Line2D.Float(x, yOffset, x, mainButton.height - yOffset))
}
override fun updateOptions() = super.updateOptions().also {
optionButton.border = if (isSimpleButton) optionButtonBorder else mainButton.border
}
companion object {
@Suppress("UNUSED_PARAMETER")
@JvmStatic
fun createUI(c: JComponent) = DarculaOptionButtonUI()
}
}
@@ -177,3 +177,5 @@ Tree.leftChildIndent=7
Tree.rightChildIndent=11
FileView.fileIcon=AllIcons.FileTypes.Unknown
OptionButtonUI=com.intellij.ide.ui.laf.darcula.DarculaOptionButtonUI
@@ -97,7 +97,7 @@ public class DarculaButtonPainter implements Border, UIResource {
}
}
protected Color getBorderColor() {
public Color getBorderColor() {
return Gray._100.withAlpha(180);
}
@@ -25,6 +25,7 @@ import com.intellij.util.ui.JBInsets;
import com.intellij.util.ui.JBUI;
import com.intellij.util.ui.MacUIUtil;
import com.intellij.util.ui.UIUtil;
import org.jetbrains.annotations.NotNull;
import sun.swing.SwingUtilities2;
import javax.swing.*;
@@ -131,14 +132,7 @@ public class DarculaButtonUI extends BasicButtonUI {
AbstractButton button = (AbstractButton)c;
ButtonModel model = button.getModel();
Color fg = button.getForeground();
if (fg instanceof UIResource && isDefaultButton(button)) {
final Color selectedFg = UIManager.getColor("Button.darcula.selectedButtonForeground");
if (selectedFg != null) {
fg = selectedFg;
}
}
g.setColor(fg);
g.setColor(getTextColor(button));
//UISettings.setupAntialiasing(g);
@@ -155,12 +149,27 @@ public class DarculaButtonUI extends BasicButtonUI {
}
}
public static Color getTextColor(@NotNull AbstractButton button) {
Color fg = button.getForeground();
if (fg instanceof UIResource && isDefaultButton(button)) {
final Color selectedFg = UIManager.getColor("Button.darcula.selectedButtonForeground");
if (selectedFg != null) {
fg = selectedFg;
}
}
return fg;
}
public static Color getDisabledTextColor() {
return UIManager.getColor("Button.disabledText");
}
protected void paintDisabledText(Graphics g, String text, JComponent c, Rectangle textRect, FontMetrics metrics) {
g.setColor(UIManager.getColor("Button.darcula.disabledText.shadow"));
SwingUtilities2.drawStringUnderlineCharAt(c, g, text, -1,
textRect.x + getTextShiftOffset()+1,
textRect.y + metrics.getAscent() + getTextShiftOffset()+1);
g.setColor(UIManager.getColor("Button.disabledText"));
g.setColor(getDisabledTextColor());
SwingUtilities2.drawStringUnderlineCharAt(c, g, text, -1,
textRect.x + getTextShiftOffset(),
textRect.y + metrics.getAscent() + getTextShiftOffset());
@@ -126,27 +126,12 @@ public class DarculaComboBoxUI extends BasicComboBoxUI implements Border, ErrorB
g2.fill(new Rectangle2D.Float(0, bw + lw, lw(g2), getHeight() - (bw + lw) * 2));
g2.setColor(new JBColor(Gray._255, comboBox.isEnabled() ? getForeground() : getOutlineColor(comboBox.isEnabled())));
g2.fill(getArrowShape());
g2.fill(getArrowShape(getWidth(), getHeight()));
} finally {
g2.dispose();
}
}
private Shape getArrowShape() {
int tW = JBUI.scale(8);
int tH = JBUI.scale(6);
int xU = (getWidth() - tW) / 2 - JBUI.scale(1);
int yU = (getHeight() - tH) / 2 + JBUI.scale(1);
Path2D path = new Path2D.Float();
path.moveTo(xU, yU);
path.lineTo(xU + tW, yU);
path.lineTo(xU + tW/2, yU + tH);
path.lineTo(xU, yU);
path.closePath();
return path;
}
@Override
public Dimension getPreferredSize() {
Insets i = comboBox.getInsets();
@@ -167,6 +152,22 @@ public class DarculaComboBoxUI extends BasicComboBoxUI implements Border, ErrorB
return JBUI.insets(3, 8, 3, 3).asUIResource();
}
@NotNull
public static Shape getArrowShape(int width, int height) {
int tW = JBUI.scale(8);
int tH = JBUI.scale(6);
int xU = (width - tW) / 2 - JBUI.scale(1);
int yU = (height - tH) / 2 + JBUI.scale(1);
Path2D path = new Path2D.Float();
path.moveTo(xU, yU);
path.lineTo(xU + tW, yU);
path.lineTo(xU + tW / 2, yU + tH);
path.lineTo(xU, yU);
path.closePath();
return path;
}
@Override
public void paint(Graphics g, JComponent c) {
Container parent = c.getParent();
@@ -62,7 +62,7 @@ public class MacIntelliJButtonUI extends DarculaButtonUI {
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g2.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL, MacUIUtil.USE_QUARTZ ? RenderingHints.VALUE_STROKE_PURE : RenderingHints.VALUE_STROKE_NORMALIZE);
float lw = UIUtil.isRetina(g2) ? 0.5f : 1.0f;
float lw = getLineWidth(g2);
Insets i = c.getBorder().getBorderInsets(c); // ComboButton adds arrow width to the insets, so take the bare border.
// Draw background
@@ -90,16 +90,7 @@ public class MacIntelliJButtonUI extends DarculaButtonUI {
h - lw*2 - (i.top + i.bottom),
ARC_SIZE - lw, ARC_SIZE - lw), false);
if (!b.isEnabled()) {
p = new GradientPaint(w/2, i.top, Gray.xD2, w/2, h - (i.top + i.bottom), Gray.xC3);
} else if (isDefaultButton(c)) {
p = IntelliJLaf.isGraphite() ?
new GradientPaint(w/2, i.top, new Color(0xa5a5ab), w/2, h - (i.top + i.bottom), new Color(0x7d7d83)) :
new GradientPaint(w/2, i.top, new Color(0x4ba0f8), w/2, h - (i.top + i.bottom), new Color(0x095eff));
} else {
p = new GradientPaint(w/2, i.top, Gray.xC9, w/2, h - (i.top + i.bottom), Gray.xAC);
}
p = getBorderPaint(c);
g2.setPaint(p);
g2.fill(outline);
@@ -110,6 +101,30 @@ public class MacIntelliJButtonUI extends DarculaButtonUI {
}
}
public static float getLineWidth(Graphics2D g) {
return UIUtil.isRetina(g) ? 0.5f : 1.0f;
}
@SuppressWarnings("UseJBColor")
public static Paint getBorderPaint(JComponent c) {
int w = c.getWidth();
int h = c.getHeight();
Insets i = c.getBorder().getBorderInsets(c);
Paint p;
if (!c.isEnabled()) {
p = new GradientPaint(w / 2, i.top, Gray.xD2, w / 2, h - (i.top + i.bottom), Gray.xC3);
}
else if (isDefaultButton(c)) {
p = IntelliJLaf.isGraphite() ?
new GradientPaint(w / 2, i.top, new Color(0xa5a5ab), w / 2, h - (i.top + i.bottom), new Color(0x7d7d83)) :
new GradientPaint(w / 2, i.top, new Color(0x4ba0f8), w / 2, h - (i.top + i.bottom), new Color(0x095eff));
}
else {
p = new GradientPaint(w / 2, i.top, Gray.xC9, w / 2, h - (i.top + i.bottom), Gray.xAC);
}
return p;
}
@Override
public Dimension getPreferredSize(JComponent c) {
Dimension size = super.getPreferredSize(c);
@@ -0,0 +1,38 @@
/*
* Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
*/
package com.intellij.ide.ui.laf.intellij
import com.intellij.ide.ui.laf.darcula.DarculaOptionButtonUI
import com.intellij.util.ui.JBUI.scale
import java.awt.Dimension
import java.awt.Graphics2D
import java.awt.geom.Rectangle2D
import javax.swing.JComponent
// TODO replace arrow with specific icon when it's ready
class MacIntelliJOptionButtonUI : DarculaOptionButtonUI() {
override val arrowButtonPreferredSize get() = Dimension(scale(21), optionButton.preferredSize.height)
override val showPopupXOffset = scale(3)
override fun paintSeparatorArea(g: Graphics2D, c: JComponent) = super.paintSeparatorArea(g, c).also {
// clipXOffset is rather big for mac and cuts arrow - so we also paint arrow part here
g.translate(mainButton.width, 0)
paintArrow(g, arrowButton)
}
override fun paintSeparator(g: Graphics2D, c: JComponent) {
val insets = mainButton.insets
val lw = MacIntelliJButtonUI.getLineWidth(g)
g.paint = MacIntelliJButtonUI.getBorderPaint(c)
g.fill(Rectangle2D.Float(mainButton.width.toFloat(), insets.top + lw, lw, mainButton.height - (insets.top + insets.bottom + 2 * lw)))
}
companion object {
@Suppress("UNUSED_PARAMETER")
@JvmStatic
fun createUI(c: JComponent) = MacIntelliJOptionButtonUI()
}
}
@@ -20,6 +20,7 @@ import com.intellij.openapi.actionSystem.ActionToolbar;
import com.intellij.util.ui.JBInsets;
import com.intellij.util.ui.JBUI;
import com.intellij.util.ui.UIUtil;
import org.jetbrains.annotations.NotNull;
import javax.swing.*;
import javax.swing.border.Border;
@@ -43,7 +44,7 @@ public class WinIntelliJButtonBorder implements Border, UIResource {
ButtonModel bm = b.getModel();
Rectangle outerRect = new Rectangle(x, y, width, height);
try {
JBInsets.removeFrom(outerRect, JBUI.insets(1));
JBInsets.removeFrom(outerRect, getOuterInsets());
if (UIUtil.getParentOfType(ActionToolbar.class, c) != null) {
JBInsets.removeFrom(outerRect, JBUI.insetsRight(3));
}
@@ -52,7 +53,6 @@ public class WinIntelliJButtonBorder implements Border, UIResource {
g2.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL, RenderingHints.VALUE_STROKE_NORMALIZE);
Color color = UIManager.getColor("Button.intellij.native.borderColor");
int bw = 1;
if (!c.isEnabled()) {
g2.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, DISABLED_ALPHA_LEVEL));
} else if (bm.isPressed()) {
@@ -61,10 +61,10 @@ public class WinIntelliJButtonBorder implements Border, UIResource {
color = UIManager.getColor("Button.intellij.native.focusedBorderColor");
} else {
if (DarculaButtonUI.isDefaultButton(b)) {
bw = 2;
color = UIManager.getColor("Button.intellij.native.focusedBorderColor");
}
}
int bw = getBorderWidth(b);
Path2D border = new Path2D.Float(Path2D.WIND_EVEN_ODD);
border.append(outerRect, false);
@@ -80,6 +80,20 @@ public class WinIntelliJButtonBorder implements Border, UIResource {
}
}
@NotNull
public JBInsets getOuterInsets() {
return JBUI.insets(1);
}
public static boolean isWideBorder(@NotNull AbstractButton b) {
ButtonModel bm = b.getModel();
return b.isEnabled() && !bm.isPressed() && !b.hasFocus() && !bm.isRollover() && DarculaButtonUI.isDefaultButton(b);
}
public static int getBorderWidth(@NotNull AbstractButton b) {
return isWideBorder(b) ? 2 : 1;
}
@Override
public Insets getBorderInsets(Component c) {
if (isSquare(c)) {
@@ -26,6 +26,7 @@ import com.intellij.util.ui.JBUI;
import com.intellij.util.ui.UIUtil;
import com.intellij.util.ui.components.BorderLayoutPanel;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import javax.swing.*;
import javax.swing.border.Border;
@@ -246,7 +247,7 @@ public class WinIntelliJComboBoxUI extends DarculaComboBoxUI {
}
}
Icon icon = MacIntelliJIconCache.getIcon("comboDropTriangle", false, false, isEnabled());
Icon icon = getArrowIcon(this);
int x = JBUI.scale(5);
int y = (getHeight() - icon.getIconHeight()) / 2;
icon.paintIcon(this, g2, x, y);
@@ -276,6 +277,10 @@ public class WinIntelliJComboBoxUI extends DarculaComboBoxUI {
return button;
}
public static Icon getArrowIcon(@NotNull JComponent c) {
return MacIntelliJIconCache.getIcon("comboDropTriangle", false, false, c.isEnabled());
}
@Override public void unconfigureArrowButton() {
super.unconfigureArrowButton();
if (arrowButton != null) {
@@ -0,0 +1,102 @@
/*
* Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
*/
package com.intellij.ide.ui.laf.intellij
import com.intellij.ide.ui.laf.intellij.WinIntelliJButtonUI.DISABLED_ALPHA_LEVEL
import com.intellij.ui.components.BasicOptionButtonUI
import com.intellij.util.ui.JBUI.scale
import java.awt.*
import javax.swing.AbstractButton
import javax.swing.JComponent
import javax.swing.UIManager
class WinIntelliJOptionButtonUI : BasicOptionButtonUI() {
private val outerInsets get() = (mainButton.border as WinIntelliJButtonBorder).outerInsets
private val lw get() = scale(1)
override fun createMainButton() = object : MainButton() {
override fun paintNotSimple(g: Graphics2D) {
g.clipRect(0, 0, width - outerInsets.right, height)
// background is painted in button ui
super.paintNotSimple(g)
}
override fun paintBorderNotSimple(g: Graphics2D) = super.paintBorderNotSimple(g).also {
// we do not need any rendering hints set by border here - so we clone again
cloneAndPaint(g) { paintSeparatorArea(it, this) }
}
}
override fun configureMainButton() = super.configureMainButton().also { mainButton.isOpaque = false }
override fun unconfigureMainButton() = super.unconfigureMainButton().also { mainButton.isOpaque = true }
override fun createArrowButton() = object : ArrowButton() {
override fun paintNotSimple(g: Graphics2D) {
val bw = scale(2)
g.clipRect(outerInsets.left + bw, 0, width - (outerInsets.left + bw), height)
// background is painted in button ui
super.paintNotSimple(g)
paintArrow(g, this)
}
}
fun paintArrow(g: Graphics2D, b: AbstractButton) {
val icon = WinIntelliJComboBoxUI.getArrowIcon(b)
if (icon != null) {
val x = scale(7)
val y = (b.height - icon.iconHeight) / 2
icon.paintIcon(b, g, x, y)
}
}
override fun configureArrowButton() = super.configureArrowButton().also { arrowButton.isOpaque = false }
override fun unconfigureArrowButton() = super.unconfigureArrowButton().also { arrowButton.isOpaque = true }
override val arrowButtonPreferredSize get() = Dimension(scale(23), optionButton.preferredSize.height)
override val showPopupXOffset get() = scale(4)
override fun createLayoutManager() = object : OptionButtonLayout() {
override fun layoutContainer(parent: Container) {
val mainButtonWidth = optionButton.width - if (arrowButton.isVisible) arrowButton.preferredSize.width else 0
val offset = if (arrowButton.isVisible) scale(2) else 0
mainButton.bounds = Rectangle(offset, 0, mainButtonWidth, optionButton.height)
arrowButton.bounds = Rectangle(mainButtonWidth - offset, 0, arrowButton.preferredSize.width, optionButton.height)
}
}
fun paintSeparatorArea(g: Graphics2D, c: JComponent) {
val bw = scale(WinIntelliJButtonBorder.getBorderWidth(mainButton))
val x = mainButton.width - outerInsets.right - lw
val y = outerInsets.top + bw
val height = mainButton.height - (outerInsets.top + outerInsets.bottom + 2 * bw)
paintSeparator(g, c, x, y, height)
if (WinIntelliJButtonBorder.isWideBorder(mainButton)) {
paintMainButtonBackground(g, x - 2 * lw, y, height)
}
}
private fun paintSeparator(g: Graphics2D, c: JComponent, x: Int, y: Int, height: Int) {
g.color = UIManager.getColor("Button.intellij.native.borderColor")
if (!c.isEnabled) {
g.composite = AlphaComposite.getInstance(AlphaComposite.SRC_OVER, DISABLED_ALPHA_LEVEL)
}
g.fill(Rectangle(x, y, lw, height))
}
private fun paintMainButtonBackground(g: Graphics2D, x: Int, y: Int, height: Int) {
g.color = mainButton.background
g.fill(Rectangle(x, y, 2 * lw, height))
}
companion object {
@Suppress("UNUSED_PARAMETER")
@JvmStatic
fun createUI(c: JComponent) = WinIntelliJOptionButtonUI()
}
}
@@ -179,3 +179,5 @@ Tree.leftChildIndent=7
Tree.rightChildIndent=11
FileView.fileIcon=AllIcons.FileTypes.Unknown
OptionButtonUI=com.intellij.ide.ui.laf.darcula.DarculaOptionButtonUI
@@ -54,3 +54,5 @@ TabbedPane.selectedTabTitleNormalColor=dcdcdc
TableHeader.cellBorder=2,2,2,2
Tree.rowHeight=20
OptionButtonUI=com.intellij.ide.ui.laf.intellij.MacIntelliJOptionButtonUI
@@ -207,4 +207,6 @@ RootPaneUI=com.intellij.ide.ui.laf.darcula.ui.DarculaRootPaneUI
OnOffButtonUI=com.intellij.ide.ui.laf.intellij.WinOnOffButtonUI
HelpTooltip.xOffset=1
HelpTooltip.yOffset=1
HelpTooltip.yOffset=1
OptionButtonUI=com.intellij.ide.ui.laf.intellij.WinIntelliJOptionButtonUI
@@ -701,7 +701,7 @@ public class NotificationsManagerImpl extends NotificationsManager {
if (isDarcula) {
setBorder(new DarculaButtonPainter() {
@Override
protected Color getBorderColor() {
public Color getBorderColor() {
return new ColorUIResource(0x616263);
}
});
@@ -177,7 +177,7 @@ open class BasicOptionButtonUI : OptionButtonUI() {
closePopup()
updateExtraWidth()
updateTooltip()
arrowButton.isVisible = !isSimpleButton
updateOptions()
}
}
}
@@ -305,9 +305,19 @@ open class BasicOptionButtonUI : OptionButtonUI() {
arrowButton.toolTipText = toolTip
}
protected open fun updateOptions() {
arrowButton.isVisible = !isSimpleButton
}
open inner class BaseButton : JButton() {
override fun hasFocus() = optionButton.hasFocus()
override fun isDefaultButton() = optionButton.isDefaultButton
override fun paint(g: Graphics) = if (isSimpleButton) super.paint(g) else cloneAndPaint(g) { paintNotSimple(it) }
open fun paintNotSimple(g: Graphics2D) = super.paint(g)
override fun paintBorder(g: Graphics) = if (isSimpleButton) super.paintBorder(g) else cloneAndPaint(g) { paintBorderNotSimple(it) }
open fun paintBorderNotSimple(g: Graphics2D) = super.paintBorder(g)
}
open inner class MainButton : BaseButton()
@@ -377,5 +387,20 @@ open class BasicOptionButtonUI : OptionButtonUI() {
@Suppress("UNUSED_PARAMETER")
@JvmStatic
fun createUI(c: JComponent) = BasicOptionButtonUI()
fun paintBackground(g: Graphics, c: JComponent) {
g.color = c.background
g.fillRect(0, 0, c.width, c.height)
}
fun cloneAndPaint(g: Graphics, block: (Graphics2D) -> Unit) {
val g2 = g.create() as Graphics2D
try {
block(g2)
}
finally {
g2.dispose()
}
}
}
}