com.github.weisj.darklaf.ui.button.DarkButtonUI Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of darklaf-core Show documentation
Show all versions of darklaf-core Show documentation
A themeable Look and Feel for java swing
/*
* MIT License
*
* Copyright (c) 2020 Jannis Weis
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
* associated documentation files (the "Software"), to deal in the Software without restriction,
* including without limitation the rights to use, copy, modify, merge, publish, distribute,
* sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all copies or
* substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
* NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
* DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*
*/
package com.github.weisj.darklaf.ui.button;
import java.awt.*;
import java.awt.geom.RoundRectangle2D;
import javax.swing.*;
import javax.swing.border.Border;
import javax.swing.plaf.ComponentUI;
import javax.swing.plaf.InsetsUIResource;
import javax.swing.plaf.UIResource;
import javax.swing.plaf.basic.BasicButtonListener;
import javax.swing.plaf.basic.BasicButtonUI;
import javax.swing.plaf.basic.BasicGraphicsUtils;
import com.github.weisj.darklaf.components.tooltip.ToolTipStyle;
import com.github.weisj.darklaf.delegate.AbstractButtonLayoutDelegate;
import com.github.weisj.darklaf.graphics.GraphicsContext;
import com.github.weisj.darklaf.graphics.GraphicsUtil;
import com.github.weisj.darklaf.graphics.PaintUtil;
import com.github.weisj.darklaf.graphics.StringPainter;
import com.github.weisj.darklaf.ui.VisualPaddingListener;
import com.github.weisj.darklaf.ui.togglebutton.ToggleButtonFocusNavigationActions;
import com.github.weisj.darklaf.ui.tooltip.ToolTipConstants;
import com.github.weisj.darklaf.util.AlignmentExt;
import com.github.weisj.darklaf.util.DarkUIUtil;
import com.github.weisj.darklaf.util.PropertyKey;
import com.github.weisj.darklaf.util.PropertyUtil;
/** @author Jannis Weis */
public class DarkButtonUI extends BasicButtonUI implements ButtonConstants {
protected static final RoundRectangle2D hitArea = new RoundRectangle2D.Float();
protected static final AbstractButtonLayoutDelegate layoutDelegate = new ButtonLayoutDelegate();
protected boolean isDefaultButton = false;
protected final Rectangle viewRect = new Rectangle();
protected final Rectangle textRect = new Rectangle();
protected final Rectangle iconRect = new Rectangle();
protected String displayText;
protected int borderSize;
protected int shadowHeight;
protected boolean drawOutline;
protected Color inactiveForeground;
protected Color defaultForeground;
protected Color defaultBackground;
protected Color defaultHoverBackground;
protected Color defaultClickBackground;
protected Color background;
protected Color hoverBackground;
protected Color clickBackground;
protected Color inactiveBackground;
protected Color borderlessHover;
protected Color borderlessClick;
protected Color borderlessOutlineHover;
protected Color borderlessOutlineClick;
protected Color shadowColor;
protected Insets insets;
protected Insets thinInsets;
protected Insets squareInsets;
protected Insets squareThinInsets;
protected Insets borderlessRectangularInsets;
protected AbstractButton button;
protected int arc;
protected int altArc;
protected ToggleButtonFocusNavigationActions keyboardActions;
protected VisualPaddingListener visualPaddingListener;
public static ComponentUI createUI(final JComponent c) {
return new DarkButtonUI();
}
@Override
public void installUI(final JComponent c) {
button = (AbstractButton) c;
super.installUI(c);
}
@Override
protected void installDefaults(final AbstractButton b) {
super.installDefaults(b);
b.setLayout(createLayout());
PropertyUtil.installProperty(b, ToolTipConstants.KEY_STYLE,
ToolTipStyle.parse(UIManager.get("Button.toolTipStyle")));
LookAndFeel.installProperty(b, PropertyKey.OPAQUE, false);
borderSize = UIManager.getInt("Button.borderThickness");
shadowHeight = UIManager.getInt("Button.shadowHeight");
shadowColor = UIManager.getColor("Button.shadow");
inactiveForeground = UIManager.getColor("Button.disabledText");
defaultForeground = UIManager.getColor("Button.selectedButtonForeground");
defaultBackground = UIManager.getColor("Button.defaultFillColor");
defaultHoverBackground = UIManager.getColor("Button.defaultFillColorRollOver");
defaultClickBackground = UIManager.getColor("Button.defaultFillColorClick");
background = UIManager.getColor("Button.activeFillColor");
hoverBackground = UIManager.getColor("Button.activeFillColorRollOver");
clickBackground = UIManager.getColor("Button.activeFillColorClick");
inactiveBackground = UIManager.getColor("Button.inactiveFillColor");
borderlessHover = UIManager.getColor("Button.borderless.hover");
borderlessClick = UIManager.getColor("Button.borderless.click");
borderlessOutlineHover = UIManager.getColor("Button.borderless.outline.hover");
borderlessOutlineClick = UIManager.getColor("Button.borderless.outline.click");
arc = UIManager.getInt("Button.arc");
altArc = UIManager.getInt("Button.altArc");
drawOutline = UIManager.getBoolean("Button.borderless.drawOutline");
insets = UIManager.getInsets("Button.borderInsets");
thinInsets = UIManager.getInsets("Button.thinBorderInsets");
squareInsets = UIManager.getInsets("Button.squareBorderInsets");
squareThinInsets = UIManager.getInsets("Button.squareThinBorderInsets");
borderlessRectangularInsets = UIManager.getInsets("Button.borderlessRectangularInsets");
if (insets == null) insets = new Insets(0, 0, 0, 0);
if (thinInsets == null) thinInsets = new Insets(0, 0, 0, 0);
if (squareThinInsets == null) squareThinInsets = new Insets(0, 0, 0, 0);
if (squareInsets == null) squareInsets = new Insets(0, 0, 0, 0);
if (borderlessRectangularInsets == null) borderlessRectangularInsets = new Insets(0, 0, 0, 0);
updateMargins(b);
}
protected LayoutManager createLayout() {
return new DarkButtonLayout();
}
@Override
protected void installListeners(final AbstractButton b) {
super.installListeners(b);
keyboardActions = new ToggleButtonFocusNavigationActions(b);
keyboardActions.installActions();
visualPaddingListener = new VisualPaddingListener();
b.addPropertyChangeListener(visualPaddingListener);
}
@Override
protected BasicButtonListener createButtonListener(final AbstractButton b) {
return new DarkButtonListener<>(b, this);
}
@Override
protected void uninstallDefaults(final AbstractButton b) {
super.uninstallDefaults(b);
b.setLayout(null);
}
@Override
protected void uninstallListeners(final AbstractButton b) {
super.uninstallListeners(b);
keyboardActions.uninstallActions();
keyboardActions = null;
b.removePropertyChangeListener(visualPaddingListener);
visualPaddingListener = null;
}
protected void validateLayout() {
boolean defButton = ButtonConstants.isDefaultButton(button);
if (defButton != isDefaultButton) {
isDefaultButton = defButton;
button.doLayout();
}
}
@Override
public void paint(final Graphics g, final JComponent c) {
validateLayout();
GraphicsContext config = new GraphicsContext(g);
AbstractButton b = (AbstractButton) c;
prepareDelegate(b);
paintButtonBackground(g, c);
paintIcon(g, b, c);
config.restoreClip();
paintText(g, b, displayText);
}
protected void paintButtonBackground(final Graphics g, final JComponent c) {
Graphics2D g2 = (Graphics2D) g;
AbstractButton b = (AbstractButton) c;
if (shouldDrawBackground(b)) {
int arc = getArc(c);
int width = c.getWidth();
int height = c.getHeight();
Insets margin = b.getMargin();
if (margin instanceof UIResource) {
margin = null;
}
if (ButtonConstants.isBorderlessVariant(c)) {
paintBorderlessBackground(b, g2, arc, width, height, margin);
} else if (b.getBorder() instanceof DarkButtonBorder) {
paintDarklafBorderBackground(b, g2, arc, width, height);
} else {
paintDefaultBackground(b, g2, width, height);
}
}
}
protected void paintDefaultBackground(final AbstractButton b, final Graphics2D g, final int width,
final int height) {
Insets ins = b.getInsets();
g.setColor(getBackgroundColor(b));
PaintUtil.fillRect(g, ins.left, ins.top, width - ins.left - ins.right, height - ins.top - ins.bottom);
}
protected void paintDarklafBorderBackground(final AbstractButton c, final Graphics2D g, final int arc,
final int width, final int height) {
boolean showShadow = DarkButtonBorder.showDropShadow(c);
int shadow = showShadow ? shadowHeight : 0;
int effectiveArc = ButtonConstants.chooseArcWithBorder(c, arc, 0, 0, borderSize);
AlignmentExt corner = DarkButtonBorder.getCornerFlag(c);
Rectangle bgRect = getEffectiveRect(width, height, -(effectiveArc + 1), corner);
paintDarklafBorderBgImpl(c, g, showShadow, shadow, effectiveArc, bgRect);
}
protected void paintDarklafBorderBgImpl(final AbstractButton c, final Graphics2D g, final boolean showShadow,
final int shadow, final int effectiveArc, final Rectangle bgRect) {
if (c.isEnabled() && showShadow && PaintUtil.getShadowComposite().getAlpha() != 0) {
paintShadow(g, shadow, effectiveArc, bgRect);
}
g.setColor(getBackgroundColor(c));
paintBackgroundRect(g, effectiveArc, bgRect);
}
protected void paintShadow(final Graphics2D g, final int shadow, final int effectiveArc, final Rectangle bgRect) {
g.setColor(shadowColor);
Composite comp = g.getComposite();
g.setComposite(PaintUtil.getShadowComposite());
int stroke = (int) PaintUtil.getStrokeWidth(g);
paintBackgroundRect(g, effectiveArc * 2, bgRect.x, bgRect.y + shadow + stroke, bgRect.width, bgRect.height);
g.setComposite(comp);
}
protected void paintBackgroundRect(final Graphics2D g2, final int effectiveArc, final Rectangle bgRect) {
paintBackgroundRect(g2, effectiveArc, bgRect.x, bgRect.y, bgRect.width, bgRect.height);
}
protected void paintBackgroundRect(final Graphics2D g2, final int effectiveArc, final int x, final int y,
final int width, final int height) {
if (effectiveArc == 0) {
g2.fillRect(x, y, width, height);
} else {
PaintUtil.fillRoundRect(g2, x, y, width, height, effectiveArc, false);
}
}
protected Rectangle getEffectiveRect(final int width, final int height, final int adjustment,
final AlignmentExt corner) {
Insets insetMask = new Insets(borderSize, borderSize, Math.max(borderSize, shadowHeight), borderSize);
if (corner != null) {
insetMask = corner.maskInsets(insetMask, adjustment);
}
int bx = insetMask.left;
int by = insetMask.top;
int bw = width - insetMask.left - insetMask.right;
int bh = height - insetMask.top - insetMask.bottom;
return new Rectangle(bx, by, bw, bh);
}
protected void paintBorderlessBackground(final AbstractButton b, final Graphics2D g, final int arc, final int width,
final int height, final Insets m) {
if (isRolloverBorderless(b)) {
Border border = b.getBorder();
Insets ins = border != null ? border.getBorderInsets(b) : new Insets(0, 0, 0, 0);
Insets margin = m;
if (margin == null) {
margin = new Insets(0, 0, 0, 0);
} else {
// Ensure margins really only affect the size of the background around the content.
// If the button is larger than expected adjust the margin s.t. the shadow background is
// only painted in the area around the viewRect specified by the margin.
Rectangle r = iconRect.union(textRect);
margin.left = r.x - margin.left;
margin.right = width - (r.x + r.width + margin.right);
margin.top = r.y - margin.top;
margin.bottom = height - (r.y + r.height + margin.bottom);
}
int x = Math.max(ins.left, margin.left);
int y = Math.max(ins.top, margin.top);
int w = width - x - Math.max(ins.right, margin.right);
int h = height - y - Math.max(ins.bottom, margin.bottom);
GraphicsUtil.setupAAPainting(g);
if (ButtonConstants.isBorderlessRectangular(b)) {
paintBorderlessRectangularBackgroundIml(b, g, x, y, w, h);
} else if (ButtonConstants.isBorderless(b)) {
paintBorderlessBackgroundImpl(b, g, arc, x, y, w, h);
} else {
int size = Math.min(w, h);
paintBorderlessBackgroundImpl(b, g, arc, (width - size) / 2, (height - size) / 2, size, size);
}
}
}
protected void paintBorderlessRectangularBackgroundIml(final AbstractButton b, final Graphics2D g, final int x,
final int y, final int w, final int h) {
g.setColor(getBorderlessBackground(b));
g.fillRect(x, y, w, h);
}
protected void paintBorderlessBackgroundImpl(final AbstractButton b, final Graphics2D g, final int arc, final int x,
final int y, final int w, final int h) {
if (!drawOutline) {
g.setColor(getBorderlessBackground(b));
g.fillRoundRect(x, y, w, h, arc, arc);
} else {
g.setColor(getBorderlessOutline(b));
PaintUtil.paintLineBorder(g, x, y, w, h, arc);
}
}
public boolean isRolloverBorderless(final AbstractButton b) {
return b.isEnabled() && b.getModel().isRollover();
}
protected void paintText(final Graphics g, final AbstractButton b, final String text) {
ButtonModel model = b.getModel();
g.setColor(getForeground(b));
int mnemonicIndex = b.getDisplayedMnemonicIndex();
if (!model.isEnabled()) {
mnemonicIndex = -1;
}
StringPainter.drawStringUnderlineCharAt(g, b, text, mnemonicIndex, textRect, layoutDelegate.getFont());
}
protected void paintIcon(final Graphics g, final AbstractButton b, final JComponent c) {
if (b.getIcon() != null) {
g.clipRect(iconRect.x, iconRect.y, iconRect.width, iconRect.height);
paintIcon(g, c, iconRect);
}
}
protected void repaintNeighbours() {
DarkUIUtil.repaint(ButtonConstants.getNeighbour(KEY_LEFT_NEIGHBOUR, button));
DarkUIUtil.repaint(ButtonConstants.getNeighbour(KEY_TOP_NEIGHBOUR, button));
DarkUIUtil.repaint(ButtonConstants.getNeighbour(KEY_TOP_RIGHT_NEIGHBOUR, button));
DarkUIUtil.repaint(ButtonConstants.getNeighbour(KEY_TOP_LEFT_NEIGHBOUR, button));
DarkUIUtil.repaint(ButtonConstants.getNeighbour(KEY_RIGHT_NEIGHBOUR, button));
DarkUIUtil.repaint(ButtonConstants.getNeighbour(KEY_BOTTOM_NEIGHBOUR, button));
DarkUIUtil.repaint(ButtonConstants.getNeighbour(KEY_BOTTOM_RIGHT_NEIGHBOUR, button));
DarkUIUtil.repaint(ButtonConstants.getNeighbour(KEY_BOTTOM_LEFT_NEIGHBOUR, button));
}
protected boolean shouldDrawBackground(final AbstractButton c) {
return button.isContentAreaFilled();
}
protected int getArc(final Component c) {
return ButtonConstants.chooseArcWithBorder(c, arc, 0, altArc, borderSize);
}
protected Color getForeground(final AbstractButton b) {
return getForegroundColor(b, isDefaultButton, b.getModel().isEnabled());
}
protected Color getForegroundColor(final AbstractButton b, final boolean defaultButton, final boolean enabled) {
Color fg = b.getForeground();
if (defaultButton && !ButtonConstants.isBorderlessVariant(b)) {
fg = defaultForeground;
}
if (!enabled) {
fg = inactiveForeground;
}
return PropertyUtil.chooseColor(b.getForeground(), fg);
}
protected Color getBackgroundColor(final AbstractButton b) {
boolean defaultButton = isDefaultButton;
boolean rollOver = b.isRolloverEnabled() && b.getModel().isRollover();
boolean clicked = b.getModel().isArmed();
boolean enabled = b.isEnabled();
return getBackgroundColor(b, defaultButton, rollOver, clicked, enabled);
}
protected Color getBackgroundColor(final AbstractButton b, final boolean defaultButton, final boolean rollOver,
final boolean clicked, final boolean enabled) {
if (enabled) {
if (defaultButton) {
if (clicked) {
return defaultClickBackground;
} else if (rollOver) {
return defaultHoverBackground;
} else {
return defaultBackground;
}
} else {
if (clicked) {
return clickBackground;
} else if (rollOver) {
return hoverBackground;
} else {
return PropertyUtil.chooseColor(b.getBackground(), background);
}
}
} else {
return inactiveBackground;
}
}
protected Color getBorderlessBackground(final AbstractButton c) {
return isArmedBorderless(c) ? PropertyUtil.getColor(c, KEY_CLICK_COLOR, borderlessClick)
: PropertyUtil.getColor(c, KEY_HOVER_COLOR, borderlessHover);
}
public boolean isArmedBorderless(final AbstractButton b) {
return b.getModel().isArmed();
}
public Color getBorderlessOutline(final AbstractButton c) {
return getBorderlessOutline(isArmedBorderless(c));
}
public Color getBorderlessOutline(final boolean armed) {
return armed ? borderlessOutlineClick : borderlessOutlineHover;
}
@Override
public Dimension getPreferredSize(final JComponent c) {
AbstractButton b = (AbstractButton) c;
prepareDelegate(b);
Dimension dim = BasicGraphicsUtils.getPreferredButtonSize(layoutDelegate, b.getIconTextGap());
DarkUIUtil.addInsets(dim, b.getMargin());
if (ButtonConstants.isSquare(b)) {
int size = Math.max(dim.width, dim.height);
dim.setSize(size, size);
}
return dim;
}
protected void prepareDelegate(final AbstractButton b) {
layoutDelegate.setDelegate(b);
Font f = b.getFont();
if (ButtonConstants.isDefaultButton(b) && !f.isBold()) {
layoutDelegate.setFont(f.deriveFont(Font.BOLD));
} else {
layoutDelegate.setFont(f);
}
}
public void updateMargins(final AbstractButton b) {
Insets margin = b.getMargin();
if (margin != null && !(margin instanceof UIResource)) return;
Insets m = getMargins(b);
b.setMargin(new InsetsUIResource(m.top, m.left, m.bottom, m.right));
}
protected Insets getMargins(final AbstractButton b) {
if (ButtonConstants.isBorderlessRectangular(b)) return borderlessRectangularInsets;
boolean square = ButtonConstants.isSquare(b);
return ButtonConstants.isThin(b) ? square ? squareThinInsets : thinInsets : square ? squareInsets : insets;
}
@Override
public boolean contains(final JComponent c, final int x, final int y) {
if (ButtonConstants.isBorderlessRectangular(c)) {
return super.contains(c, x, y);
}
if (!(x >= 0 && x <= c.getWidth() && y >= 0 && y <= c.getHeight())) return false;
int bs = c.getBorder() instanceof DarkButtonBorder && !ButtonConstants.isBorderless(c) ? borderSize : 0;
int arc = getArc(c);
hitArea.setRoundRect(bs, bs, c.getWidth() - 2 * bs, c.getHeight() - 2 * bs, arc, arc);
return hitArea.contains(x, y);
}
public boolean getDrawOutline(final Component c) {
return drawOutline && ButtonConstants.isBorderless(c);
}
protected static class ButtonLayoutDelegate extends AbstractButtonLayoutDelegate {
protected Font font;
@Override
public void setFont(final Font font) {
this.font = font;
}
@Override
public Font getFont() {
return font;
}
}
public class DarkButtonLayout implements LayoutManager {
@Override
public void addLayoutComponent(final String name, final Component comp) {}
@Override
public void removeLayoutComponent(final Component comp) {}
@Override
public Dimension preferredLayoutSize(final Container parent) {
// Still managed by BasicButtonUI#getPreferredSize
return null;
}
@Override
public Dimension minimumLayoutSize(final Container parent) {
// Still managed by BasicButtonUI#getMinimumSize
return null;
}
@Override
public void layoutContainer(final Container parent) {
AbstractButton b = (AbstractButton) parent;
prepareDelegate(b);
FontMetrics fm = b.getFontMetrics(layoutDelegate.getFont());
displayText = layout(layoutDelegate, b, fm, b.getWidth(), b.getHeight());
}
protected String layout(final AbstractButtonLayoutDelegate bl, final AbstractButton b, final FontMetrics fm,
final int width, final int height) {
prepareContentRects(b, width, height);
int horizontalAlignment = getHorizontalAlignment(b);
int verticalAlignment = getHorizontalAlignment(b);
int verticalTextPosition = getVerticalTextPosition(b);
int horizontalTextPosition = getHorizontalTextPosition(b);
// layout the text and icon
return SwingUtilities.layoutCompoundLabel(bl, fm, bl.getText(), bl.getIcon(), verticalAlignment,
horizontalAlignment, verticalTextPosition, horizontalTextPosition, viewRect, iconRect, textRect,
bl.getText() == null || ButtonConstants.isIconOnly(b) ? 0 : bl.getIconTextGap());
}
protected int getHorizontalTextPosition(final AbstractButton b) {
return b.getHorizontalTextPosition();
}
protected int getHorizontalAlignment(final AbstractButton b) {
return b.getHorizontalAlignment();
}
protected int getVerticalTextPosition(final AbstractButton b) {
return b.getVerticalTextPosition();
}
protected int getVerticalAlignment(final AbstractButton b) {
return b.getVerticalAlignment();
}
protected void prepareContentRects(final AbstractButton b, final int width, final int height) {
Insets i = DarkUIUtil.addInsets(b.getInsets(), b.getMargin());
AlignmentExt corner = DarkButtonBorder.getCornerFlag(b);
if (corner != null) {
Insets insetMask = new Insets(borderSize, borderSize, borderSize, borderSize);
insetMask = corner.maskInsetsInverted(insetMask, 0);
i.left -= insetMask.left;
i.right -= insetMask.right;
i.top -= insetMask.top;
i.bottom -= insetMask.bottom;
}
viewRect.setRect(0, 0, width, height);
DarkUIUtil.applyInsets(viewRect, i);
textRect.x = textRect.y = textRect.width = textRect.height = 0;
iconRect.x = iconRect.y = iconRect.width = iconRect.height = 0;
}
}
}