Browse Source

Added option to disable border on specific side of buttons. This allows for button groups that visually are one long buttons (e.g. they act as radio buttons).

Extended ButtonDemo to show new border mechanic.
Fixed ToggleButtons not showing the pressed background.
pull/105/head
weisj 5 years ago
parent
commit
24323293f1
  1. 137
      core/src/main/java/com/github/weisj/darklaf/ui/button/DarkButtonBorder.java
  2. 60
      core/src/main/java/com/github/weisj/darklaf/ui/button/DarkButtonUI.java
  3. 16
      core/src/main/java/com/github/weisj/darklaf/ui/button/DarkToggleButtonUI.java
  4. 7
      core/src/main/java/com/github/weisj/darklaf/ui/filechooser/DarkFileChooserUI.java
  5. 16
      core/src/test/java/ui/button/ButtonDemo.java
  6. 45
      utils/src/main/java/com/github/weisj/darklaf/util/Alignment.java
  7. 105
      utils/src/main/java/com/github/weisj/darklaf/util/AlignmentExt.java

137
core/src/main/java/com/github/weisj/darklaf/ui/button/DarkButtonBorder.java

@ -23,6 +23,7 @@
*/
package com.github.weisj.darklaf.ui.button;
import com.github.weisj.darklaf.util.AlignmentExt;
import com.github.weisj.darklaf.util.DarkUIUtil;
import com.github.weisj.darklaf.util.GraphicsContext;
@ -87,6 +88,51 @@ public class DarkButtonBorder implements Border, UIResource {
if (labelInsets == null) labelInsets = new Insets(0, 0, 0, 0);
}
public static boolean showDropShadow(final JComponent c) {
return showDropShadow(getCornerFlag(c));
}
public static boolean showDropShadow(final AlignmentExt a) {
return a == null
|| a == AlignmentExt.SOUTH
|| a == AlignmentExt.SOUTH_EAST
|| a == AlignmentExt.SOUTH_WEST
|| a == AlignmentExt.LEFT
|| a == AlignmentExt.RIGHT
|| a == AlignmentExt.BOTTOM
|| a == AlignmentExt.MIDDLE_HORIZONTAL;
}
protected int getArc(final Component c) {
if (DarkButtonUI.isNoArc(c)) return 0;
boolean square = DarkButtonUI.isSquare(c);
boolean alt = DarkButtonUI.chooseAlternativeArc(c);
return square ? alt ? arc : squareArc : alt ? squareArc : arc;
}
protected int getFocusArc(final Component c) {
if (DarkButtonUI.isNoArc(c)) return minimumArc;
boolean square = DarkButtonUI.isSquare(c);
boolean alt = DarkButtonUI.chooseAlternativeArc(c);
return square ? alt ? focusArc : squareFocusArc : alt ? squareFocusArc : focusArc;
}
public static AlignmentExt getCornerFlag(final Component component) {
if (component instanceof JComponent) {
Object align = ((JComponent) component).getClientProperty(DarkButtonUI.KEY_CORNER);
return align instanceof AlignmentExt ? (AlignmentExt) align : null;
}
return null;
}
protected int getShadowSize() {
return shadowSize;
}
protected int getBorderSize() {
return borderSize;
}
@Override
public void paintBorder(final Component c, final Graphics g,
final int x, final int y, final int width, final int height) {
@ -99,50 +145,56 @@ public class DarkButtonBorder implements Border, UIResource {
int arc = getArc(c);
int focusArc = getFocusArc(c);
GraphicsContext config = new GraphicsContext(g);
AlignmentExt corner = getCornerFlag(c);
if (c.isEnabled()) {
paintShadow(g2, width, height, arc);
boolean paintShadow = showDropShadow(corner);
int shadowHeight = paintShadow ? getShadowSize() : 0;
int borderSize = getBorderSize();
Insets insetMask = new Insets(borderSize, borderSize, borderSize, borderSize);
Insets focusIns = new Insets(0, 0, 0, 0);
if (corner != null) {
focusIns = corner.maskInsets(focusIns, -borderSize - focusArc);
insetMask = corner.maskInsets(insetMask, -arc);
}
int shadowHeight = getShadowSize();
int borderSize = getBorderSize();
int bx = insetMask.left;
int by = insetMask.top;
int bw = width - insetMask.left - insetMask.right;
int bh = height - insetMask.top - insetMask.bottom;
int fx = focusIns.left;
int fy = focusIns.top;
int fw = width - focusIns.left - focusIns.right;
int fh = height - focusIns.top - focusIns.bottom;
if (c.isEnabled() && paintShadow) {
paintShadow((Graphics2D) g, bx, by, bw, bh, arc);
}
if (c.hasFocus()) {
DarkUIUtil.paintFocusBorder(g2, width, height - shadowHeight, focusArc, borderSize);
if (paintFocus(c)) {
g.translate(fx, fy);
DarkUIUtil.paintFocusBorder(g2, fw, fh - shadowHeight, focusArc, borderSize);
g.translate(-fx, -fy);
}
g2.setColor(getBorderColor(c));
DarkUIUtil.paintLineBorder(g2, borderSize, borderSize, width - 2 * borderSize,
height - 2 * borderSize - shadowHeight, arc);
DarkUIUtil.paintLineBorder(g2, bx, by, bw, bh - shadowHeight, arc);
config.restore();
}
protected int getArc(final Component c) {
if (DarkButtonUI.isNoArc(c)) return 0;
boolean square = DarkButtonUI.isSquare(c);
boolean alt = DarkButtonUI.chooseAlternativeArc(c);
return square ? alt ? arc : squareArc : alt ? squareArc : arc;
protected boolean paintFocus(final Component c) {
if (c instanceof AbstractButton) {
return ((AbstractButton) c).isFocusPainted() && c.hasFocus();
}
protected int getFocusArc(final Component c) {
if (DarkButtonUI.isNoArc(c)) return minimumArc;
boolean square = DarkButtonUI.isSquare(c);
boolean alt = DarkButtonUI.chooseAlternativeArc(c);
return square ? alt ? focusArc : squareFocusArc : alt ? squareFocusArc : focusArc;
return c.hasFocus();
}
private void paintShadow(final Graphics2D g2, final int width, final int height, final int arc) {
private void paintShadow(final Graphics2D g2, final int x, final int y,
final int width, final int height, final int arc) {
GraphicsContext context = new GraphicsContext(g2);
int borderSize = getBorderSize();
int shadowSize = getShadowSize();
Area shadowShape = new Area(new RoundRectangle2D.Double(borderSize, borderSize,
width - 2 * borderSize, height - 2 * borderSize,
arc, arc));
Area innerArea = new Area(new RoundRectangle2D.Double(borderSize, borderSize,
width - 2 * borderSize,
height - 2 * borderSize - shadowSize,
arc, arc));
Area shadowShape = new Area(new RoundRectangle2D.Double(x, y, width, height, arc, arc));
Area innerArea = new Area(new RoundRectangle2D.Double(x, y, width, height - shadowSize, arc, arc));
shadowShape.subtract(innerArea);
g2.setComposite(DarkUIUtil.SHADOW_COMPOSITE);
g2.setColor(shadowColor);
@ -150,16 +202,12 @@ public class DarkButtonBorder implements Border, UIResource {
context.restore();
}
protected int getShadowSize() {
return shadowSize;
}
protected int getBorderSize() {
return borderSize;
public boolean isBorderOpaque() {
return false;
}
protected Color getBorderColor(final Component c) {
if (c.hasFocus()) {
if (paintFocus(c)) {
return focusBorderColor;
} else if (c instanceof JButton && ((JButton) c).isDefaultButton() && c.isEnabled()) {
return defaultBorderColor;
@ -184,10 +232,19 @@ public class DarkButtonBorder implements Border, UIResource {
: thinInsets
: square ? squareInsets
: insets;
return new InsetsUIResource(pad.top, pad.left, pad.bottom + shadow, pad.right);
}
public boolean isBorderOpaque() {
return false;
return maskInsets(new InsetsUIResource(pad.top, pad.left, pad.bottom, pad.right), c, shadow);
}
protected Insets maskInsets(final Insets ins, final Component c, final int shadow) {
ins.bottom += shadow;
AlignmentExt alignment = getCornerFlag(c);
if (alignment == null) return ins;
Insets insetMask = new Insets(borderSize, borderSize, borderSize + shadow, borderSize);
insetMask = alignment.maskInsetsInverted(insetMask);
ins.top -= insetMask.top;
ins.bottom -= insetMask.bottom;
ins.left -= insetMask.left;
ins.right -= insetMask.right;
return ins;
}
}

60
core/src/main/java/com/github/weisj/darklaf/ui/button/DarkButtonUI.java

@ -55,6 +55,7 @@ public class DarkButtonUI extends BasicButtonUI implements PropertyChangeListene
public static final String KEY_SQUARE = KEY_PREFIX + "square";
public static final String KEY_THIN = KEY_PREFIX + "thin";
public static final String KEY_NO_SHADOW_OVERWRITE = KEY_PREFIX + "noShadowOverwrite";
public static final String KEY_CORNER = KEY_PREFIX + "cornerFlag";
public static final String VARIANT_ONLY_LABEL = "onlyLabel";
public static final String VARIANT_FULL_SHADOW = "fullShadow";
public static final String VARIANT_SHADOW = "shadow";
@ -198,6 +199,17 @@ public class DarkButtonUI extends BasicButtonUI implements PropertyChangeListene
if (!isShadowVariant(b)) {
i = new Insets(i.top, borderSize, i.bottom, borderSize);
}
AlignmentExt corner = DarkButtonBorder.getCornerFlag(c);
Insets insetMask = new Insets(borderSize, borderSize, borderSize, borderSize);
if (corner != null) {
insetMask = corner.maskInsetsInverted(insetMask, 0);
}
i.left -= insetMask.left;
i.right -= insetMask.right;
i.top -= insetMask.top;
i.bottom -= insetMask.bottom;
viewRect.x = i.left;
viewRect.y = i.top;
viewRect.width = width - (i.right + i.left);
@ -348,6 +360,43 @@ public class DarkButtonUI extends BasicButtonUI implements PropertyChangeListene
Insets margin = b.getMargin();
if (margin instanceof UIResource) margin = new Insets(0, 0, 0, 0);
if (isShadowVariant(c)) {
paintShadowBackground(g, c, g2, b, arc, width, height, margin);
} else {
paintDefaultBackground((Graphics2D) g, c, g2, arc, width, height);
}
}
}
protected void paintDefaultBackground(final Graphics2D g, final JComponent c, final Graphics2D g2,
final int arc, final int width, final int height) {
g2.setColor(getBackgroundColor(c));
int shadow = DarkButtonBorder.showDropShadow(c) ? shadowHeight : 0;
int effectiveArc = isSquare(c) && !chooseAlternativeArc(c) ? 0 : arc;
Rectangle bgRect = getEffectiveRect(width, height, c, -(effectiveArc + 1));
if (effectiveArc == 0) {
g2.fillRect(bgRect.x, bgRect.y, bgRect.width, bgRect.height - shadow);
} else {
DarkUIUtil.fillRoundRect(g, bgRect.x, bgRect.y, bgRect.width, bgRect.height - shadow, effectiveArc);
}
}
protected Rectangle getEffectiveRect(final int width, final int height, final JComponent c, final int adjustment) {
AlignmentExt corner = DarkButtonBorder.getCornerFlag(c);
Insets insetMask = new Insets(borderSize, borderSize, borderSize, 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 paintShadowBackground(final Graphics g, final JComponent c, final Graphics2D g2,
final AbstractButton b, final int arc,
final int width, final int height, final Insets margin) {
if (b.isEnabled() && b.getModel().isRollover()) {
GraphicsUtil.setupAAPainting(g2);
g.setColor(getShadowColor(b));
@ -366,17 +415,6 @@ public class DarkButtonUI extends BasicButtonUI implements PropertyChangeListene
arc, arc);
}
}
} else {
g2.setColor(getBackgroundColor(c));
if (isSquare(c) && !chooseAlternativeArc(c)) {
g2.fillRect(borderSize, borderSize, width - 2 * borderSize,
height - 2 * borderSize - shadowHeight);
} else {
DarkUIUtil.fillRoundRect((Graphics2D) g, borderSize, borderSize, width - 2 * borderSize,
height - 2 * borderSize - shadowHeight, arc);
}
}
}
}
@Override

16
core/src/main/java/com/github/weisj/darklaf/ui/button/DarkToggleButtonUI.java

@ -116,15 +116,29 @@ public class DarkToggleButtonUI extends DarkButtonUI {
}
protected Color getBackgroundColor(final JComponent c) {
AbstractButton b = (AbstractButton) c;
boolean rollOver = (b.isRolloverEnabled() || doConvertToShadow(b)) && (((JButton) c).getModel().isRollover());
boolean clicked = b.getModel().isArmed();
if (c.isEnabled()) {
if (clicked) {
return clickBackground;
} else if (rollOver) {
return hoverBackground;
} else {
if (c instanceof JToggleButton && c.isEnabled()) {
if (((JToggleButton) c).isSelected()) {
return background;
} else {
return backgroundInactive;
}
}
} else {
return super.getBackgroundColor(c);
}
}
} else {
return inactiveBackground;
}
}
@Override
protected String layout(final AbstractButton b, final JComponent c,

7
core/src/main/java/com/github/weisj/darklaf/ui/filechooser/DarkFileChooserUI.java

@ -26,6 +26,7 @@ package com.github.weisj.darklaf.ui.filechooser;
import com.github.weisj.darklaf.components.tooltip.TooltipAwareButton;
import com.github.weisj.darklaf.components.tooltip.TooltipAwareToggleButton;
import com.github.weisj.darklaf.ui.button.DarkButtonUI;
import com.github.weisj.darklaf.util.AlignmentExt;
import sun.swing.FilePane;
import javax.accessibility.AccessibleContext;
@ -107,6 +108,7 @@ public class DarkFileChooserUI extends DarkFileChooserUIBridge {
upFolderButton.putClientProperty(DarkButtonUI.KEY_SQUARE, true);
upFolderButton.putClientProperty(DarkButtonUI.KEY_ALT_ARC, true);
upFolderButton.setText(null);
upFolderButton.setFocusPainted(false);
upFolderButton.setIcon(upFolderIcon);
upFolderButton.setToolTipText(upFolderToolTipText);
upFolderButton.putClientProperty(AccessibleContext.ACCESSIBLE_NAME_PROPERTY,
@ -125,6 +127,7 @@ public class DarkFileChooserUI extends DarkFileChooserUIBridge {
JButton b = new TooltipAwareButton(homeFolderIcon);
b.putClientProperty(DarkButtonUI.KEY_NO_SHADOW_OVERWRITE, true);
b.setToolTipText(toolTipText);
b.setFocusPainted(false);
b.putClientProperty(AccessibleContext.ACCESSIBLE_NAME_PROPERTY,
homeFolderAccessibleName);
b.setAlignmentX(JComponent.LEFT_ALIGNMENT);
@ -160,10 +163,12 @@ public class DarkFileChooserUI extends DarkFileChooserUIBridge {
// List Button
listViewButton = new TooltipAwareToggleButton(listViewIcon);
listViewButton.setFocusPainted(false);
listViewButton.putClientProperty(DarkButtonUI.KEY_NO_SHADOW_OVERWRITE, true);
listViewButton.putClientProperty(DarkButtonUI.KEY_SQUARE, true);
listViewButton.putClientProperty(DarkButtonUI.KEY_ALT_ARC, true);
listViewButton.putClientProperty(DarkButtonUI.KEY_SQUARE, Boolean.TRUE);
listViewButton.putClientProperty(DarkButtonUI.KEY_CORNER, AlignmentExt.LEFT);
listViewButton.setToolTipText(listViewButtonToolTipText);
listViewButton.putClientProperty(AccessibleContext.ACCESSIBLE_NAME_PROPERTY, listViewButtonAccessibleName);
@ -177,9 +182,11 @@ public class DarkFileChooserUI extends DarkFileChooserUIBridge {
// Details Button
detailsViewButton = new TooltipAwareToggleButton(detailsViewIcon);
detailsViewButton.setFocusPainted(false);
detailsViewButton.putClientProperty(DarkButtonUI.KEY_NO_SHADOW_OVERWRITE, true);
detailsViewButton.putClientProperty(DarkButtonUI.KEY_SQUARE, true);
detailsViewButton.putClientProperty(DarkButtonUI.KEY_ALT_ARC, true);
detailsViewButton.putClientProperty(DarkButtonUI.KEY_CORNER, AlignmentExt.RIGHT);
detailsViewButton.setToolTipText(detailsViewButtonToolTipText);
detailsViewButton.putClientProperty(AccessibleContext.ACCESSIBLE_NAME_PROPERTY, detailsViewButtonAccessibleName);
detailsViewButton.setAlignmentX(JComponent.LEFT_ALIGNMENT);

16
core/src/test/java/ui/button/ButtonDemo.java

@ -25,6 +25,7 @@ package ui.button;
import com.github.weisj.darklaf.icons.IconLoader;
import com.github.weisj.darklaf.ui.button.DarkButtonUI;
import com.github.weisj.darklaf.util.AlignmentExt;
import ui.ComponentDemo;
import ui.DemoPanel;
import ui.QuickColorChooser;
@ -95,6 +96,21 @@ public class ButtonDemo implements ComponentDemo {
setSelectedItem(DarkButtonUI.VARIANT_NONE);
addItemListener(e -> button.putClientProperty(DarkButtonUI.KEY_VARIANT, e.getItem()));
}});
controlPanel.add(new JLabel(DarkButtonUI.KEY_CORNER + ":"));
controlPanel.add(new JComboBox<String>() {{
addItem("None");
for (AlignmentExt a : AlignmentExt.values()) {
addItem(a.name());
}
setSelectedItem("None");
addItemListener(e -> {
if ("None".equals(e.getItem())) {
button.putClientProperty(DarkButtonUI.KEY_CORNER, null);
} else {
button.putClientProperty(DarkButtonUI.KEY_CORNER, AlignmentExt.valueOf(e.getItem().toString()));
}
});
}});
controlPanel.add(new JCheckBox("Icon Only") {{
setSelected(false);
addActionListener(e -> button.setText(isSelected() ? null : "Test Button"));

45
utils/src/main/java/com/github/weisj/darklaf/util/Alignment.java

@ -235,32 +235,57 @@ public enum Alignment {
}
}
public Insets maskInsets(final Insets insets) {
return maskInsets(insets, 0);
}
public Insets maskInsets(final Insets insets, final int maskValue) {
return maskInsets(insets.top, insets.left, insets.bottom, insets.right, maskValue);
}
public Insets maskInsets(final int top, final int left, final int bottom, final int right, final int mask) {
switch (this) {
case NORTH:
return new Insets(insets.top, 0, 0, 0);
return new Insets(top, mask, mask, mask);
case NORTH_EAST:
return new Insets(insets.top, 0, 0, insets.right);
return new Insets(top, mask, mask, right);
case EAST:
return new Insets(0, 0, 0, insets.right);
return new Insets(mask, mask, mask, right);
case SOUTH_EAST:
return new Insets(0, 0, insets.bottom, insets.right);
return new Insets(mask, mask, bottom, right);
case SOUTH:
return new Insets(0, 0, insets.bottom, 0);
return new Insets(mask, mask, bottom, mask);
case SOUTH_WEST:
return new Insets(0, insets.left, insets.bottom, 0);
return new Insets(mask, left, bottom, mask);
case WEST:
return new Insets(0, insets.left, 0, 0);
return new Insets(mask, left, mask, mask);
case NORTH_WEST:
return new Insets(insets.top, insets.left, 0, 0);
return new Insets(top, left, mask, mask);
case CENTER:
return new Insets(0, 0, 0, 0);
return new Insets(mask, mask, mask, mask);
default:
throw new IllegalArgumentException();
}
}
public Insets maskInsetsInverted(final Insets insets) {
return maskInsetsInverted(insets, 0);
}
public Insets maskInsetsInverted(final Insets insets, final int mask) {
return maskInsetsInverted(insets.top, insets.left, insets.bottom, insets.right, mask);
}
public Insets maskInsetsInverted(final int top, final int left, final int bottom, final int right, final int mask) {
Insets masking = maskInsets(0, 0, 0, 0, 1);
Insets maskVal = maskInsets(mask, mask, mask, mask, 0);
return new Insets(top * masking.top + maskVal.top,
left * masking.left + maskVal.left,
bottom * masking.bottom + maskVal.bottom,
right * masking.right + maskVal.right);
}
/**
* Align Rectangle inside other rectangle with respect to the alignment.
*

105
utils/src/main/java/com/github/weisj/darklaf/util/AlignmentExt.java

@ -0,0 +1,105 @@
/*
* 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.util;
import java.awt.*;
public enum AlignmentExt {
NORTH(Alignment.NORTH),
SOUTH(Alignment.SOUTH_WEST),
EAST(Alignment.EAST),
WEST(Alignment.WEST),
NORTH_EAST(Alignment.NORTH_EAST),
NORTH_WEST(Alignment.NORTH_WEST),
SOUTH_EAST(Alignment.SOUTH_EAST),
SOUTH_WEST(Alignment.SOUTH_WEST),
CENTER(Alignment.CENTER),
LEFT(null),
MIDDLE_HORIZONTAL(null),
RIGHT(null),
TOP(null),
MIDDLE_VERTICAL(null),
BOTTOM(null);
private Alignment parent;
AlignmentExt(final Alignment parent) {
this.parent = parent;
}
public Insets maskInsets(final Insets insets) {
return maskInsets(insets, 0);
}
public Insets maskInsets(final Insets insets, final int maskValue) {
return maskInsets(insets.top, insets.left, insets.bottom, insets.right, maskValue);
}
public Insets maskInsets(final int top, final int left, final int bottom, final int right, final int mask) {
switch (this) {
case NORTH:
case NORTH_EAST:
case EAST:
case SOUTH_EAST:
case SOUTH:
case SOUTH_WEST:
case WEST:
case NORTH_WEST:
case CENTER:
return parent.maskInsets(top, left, bottom, right, mask);
case LEFT:
return new Insets(top, left, bottom, mask);
case MIDDLE_HORIZONTAL:
return new Insets(top, mask, bottom, mask);
case RIGHT:
return new Insets(top, mask, bottom, right);
case TOP:
return new Insets(top, left, mask, right);
case MIDDLE_VERTICAL:
return new Insets(mask, left, mask, right);
case BOTTOM:
return new Insets(mask, left, bottom, right);
default:
throw new IllegalArgumentException();
}
}
public Insets maskInsetsInverted(final Insets insets) {
return maskInsetsInverted(insets, 0);
}
public Insets maskInsetsInverted(final Insets insets, final int mask) {
return maskInsetsInverted(insets.top, insets.left, insets.bottom, insets.right, mask);
}
public Insets maskInsetsInverted(final int top, final int left, final int bottom, final int right, final int mask) {
Insets masking = maskInsets(0, 0, 0, 0, 1);
Insets maskVal = maskInsets(mask, mask, mask, mask, 0);
return new Insets(top * masking.top + maskVal.top,
left * masking.left + maskVal.left,
bottom * masking.bottom + maskVal.bottom,
right * masking.right + maskVal.right);
}
}
Loading…
Cancel
Save