diff --git a/core/src/main/java/com/github/weisj/darklaf/components/ArrowButton.java b/core/src/main/java/com/github/weisj/darklaf/components/ArrowButton.java
index a558ca91..c01dd1f8 100644
--- a/core/src/main/java/com/github/weisj/darklaf/components/ArrowButton.java
+++ b/core/src/main/java/com/github/weisj/darklaf/components/ArrowButton.java
@@ -27,7 +27,6 @@ import javax.swing.*;
import javax.swing.plaf.DimensionUIResource;
import javax.swing.plaf.basic.BasicArrowButton;
-import com.github.weisj.darklaf.icons.UIAwareIcon;
import com.github.weisj.darklaf.ui.button.DarkButtonUI;
/** @author Jannis Weis */
@@ -41,18 +40,18 @@ public final class ArrowButton implements SwingConstants {
public static JButton createUpDownArrow(final JComponent parent, final int orientation, final boolean center,
final boolean applyInsetsOnSize, final Insets insets) {
- UIAwareIcon icon;
+ Icon icon;
switch (orientation) {
case NORTH:
- icon = (UIAwareIcon) UIManager.getIcon("ArrowButton.up.icon");
+ icon = UIManager.getIcon("ArrowButton.up.icon");
break;
case SOUTH:
- icon = (UIAwareIcon) UIManager.getIcon("ArrowButton.down.icon");
+ icon = UIManager.getIcon("ArrowButton.down.icon");
break;
default:
throw new IllegalStateException("Invalid button orientation: " + orientation);
}
- return createUpDownArrow(parent, icon, icon.getDual(), orientation, center, applyInsetsOnSize, insets);
+ return createUpDownArrow(parent, icon, icon, orientation, center, applyInsetsOnSize, insets);
}
public static JButton createUpDownArrow(final JComponent parent, final Icon activeIcon, final Icon inactiveIcon,
@@ -61,15 +60,22 @@ public final class ArrowButton implements SwingConstants {
private final Insets ins = insets != null ? insets : new Insets(0, 0, 0, 0);
{
putClientProperty(DarkButtonUI.KEY_NO_BORDERLESS_OVERWRITE, true);
+ setMargin(new Insets(0, 0, 0, 0));
}
@Override
public void paint(final Graphics g) {
- int x = (getWidth() - getIcon().getIconWidth()) / 2;
- int y = direction == NORTH ? getHeight() - getIcon().getIconHeight() + 4 : -4;
+ Insets margin = getMargin();
+ int w = getWidth() - margin.left - margin.right;
+ int h = getHeight() - margin.top - margin.bottom;
+ int x = margin.left + (w - getIcon().getIconWidth()) / 2;
+ int y;
if (center) {
- y = (getHeight() - getIcon().getIconHeight()) / 2;
+ y = (h - getIcon().getIconHeight()) / 2;
+ } else {
+ y = direction == NORTH ? h - getIcon().getIconHeight() + 4 : -4;
}
+ y += margin.top;
paintTriangle(g, x, y, 0, direction, parent.isEnabled());
}
@@ -82,8 +88,9 @@ public final class ArrowButton implements SwingConstants {
if (!applyInsetsOnSize) {
return new DimensionUIResource(getIcon().getIconWidth(), getIcon().getIconHeight());
} else {
- return new DimensionUIResource(getIcon().getIconWidth() + ins.left + ins.right,
- getIcon().getIconHeight() + ins.top + ins.bottom);
+ Insets i = getInsets();
+ return new DimensionUIResource(getIcon().getIconWidth() + i.left + i.right,
+ getIcon().getIconHeight() + i.top + i.bottom);
}
}
diff --git a/core/src/main/java/com/github/weisj/darklaf/components/button/JSplitButton.java b/core/src/main/java/com/github/weisj/darklaf/components/button/JSplitButton.java
new file mode 100644
index 00000000..987245f9
--- /dev/null
+++ b/core/src/main/java/com/github/weisj/darklaf/components/button/JSplitButton.java
@@ -0,0 +1,120 @@
+/*
+ * 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.components.button;
+
+import java.awt.event.ActionListener;
+
+import javax.swing.*;
+
+public class JSplitButton extends JButton {
+
+ public static final String KEY_ACTION_ADDED = "addedAction";
+ public static final String KEY_ACTION_REMOVED = "removedAction";
+
+ private JPopupMenu actionMenu;
+
+ /**
+ * Creates a button with no set text or icon.
+ */
+ public JSplitButton() {
+ super();
+ }
+
+ /**
+ * Creates a button with an icon.
+ *
+ * @param icon the Icon image to display on the button
+ */
+ public JSplitButton(final Icon icon) {
+ super(icon);
+ }
+
+ /**
+ * Creates a button with text.
+ *
+ * @param text the text of the button
+ */
+ public JSplitButton(final String text) {
+ super(text);
+ }
+
+ /**
+ * Creates a button where properties are taken from the Action
supplied.
+ *
+ * @param a the Action
used to specify the new button
+ *
+ * @since 1.3
+ */
+ public JSplitButton(final Action a) {
+ super(a);
+ }
+
+ /**
+ * Creates a button with initial text and an icon.
+ *
+ * @param text the text of the button
+ * @param icon the Icon image to display on the button
+ */
+ public JSplitButton(final String text, final Icon icon) {
+ super(text, icon);
+ }
+
+ public int getActionCount() {
+ return listenerList.getListenerCount(ActionListener.class);
+ }
+
+ @Override
+ public String getUIClassID() {
+ return "SplitButtonUI";
+ }
+
+ @Override
+ public void addActionListener(final ActionListener l) {
+ super.addActionListener(l);
+ firePropertyChange(KEY_ACTION_ADDED, null, l);
+ }
+
+ @Override
+ public void removeActionListener(final ActionListener l) {
+ super.removeActionListener(l);
+ firePropertyChange(KEY_ACTION_REMOVED, l, null);
+ }
+
+ public JPopupMenu getActionMenu() {
+ if (actionMenu == null) {
+ actionMenu = new JPopupMenu();
+ }
+ return actionMenu;
+ }
+
+ @Override
+ public void updateUI() {
+ super.updateUI();
+ if (actionMenu != null) {
+ SwingUtilities.updateComponentTreeUI(actionMenu);
+ }
+ }
+
+ public void setActionMenu(final JPopupMenu actionMenu) {
+ this.actionMenu = actionMenu;
+ }
+}
diff --git a/core/src/main/java/com/github/weisj/darklaf/ui/button/DarkButtonBorder.java b/core/src/main/java/com/github/weisj/darklaf/ui/button/DarkButtonBorder.java
index e3184f90..dd11ff22 100644
--- a/core/src/main/java/com/github/weisj/darklaf/ui/button/DarkButtonBorder.java
+++ b/core/src/main/java/com/github/weisj/darklaf/ui/button/DarkButtonBorder.java
@@ -102,6 +102,7 @@ public class DarkButtonBorder implements Border, UIResource {
public void paintBorder(final Component c, final Graphics g, final int x, final int y, final int width,
final int height) {
if (ButtonConstants.isBorderlessVariant(c)) {
+ paintBorderlessBorder(c, g, x, y, width, height);
return;
}
Graphics2D g2 = (Graphics2D) g;
@@ -134,19 +135,35 @@ public class DarkButtonBorder implements Border, UIResource {
int fh = by + bh + borderSize - focusIns.top - focusIns.bottom;
if (paintFocus(c)) {
- g.translate(fx, fy);
- PaintUtil.paintFocusBorder(g2, fw, fh, focusArc, borderSize);
- g.translate(-fx, -fy);
+ paintFocusBorder(g2, focusArc, borderSize, fx, fy, fw, fh);
}
- g2.setColor(getBorderColor(c, focus));
- PaintUtil.paintLineBorder(g2, bx, by, bw, bh, arc);
+ paintLineBorder(c, g2, arc, focus, bx, by, bw, bh);
+
if (corner != null) {
paintNeighbourFocus(g2, c, width, height);
}
config.restore();
}
+ protected void paintBorderlessBorder(final Component c, final Graphics g, final int x, final int y, final int width,
+ final int height) {
+
+ }
+
+ protected void paintLineBorder(final Component c, final Graphics2D g2, final int arc, final boolean focus,
+ final int bx, final int by, final int bw, final int bh) {
+ g2.setColor(getBorderColor(c, focus));
+ PaintUtil.paintLineBorder(g2, bx, by, bw, bh, arc);
+ }
+
+ protected void paintFocusBorder(final Graphics2D g2, final int focusArc, final int borderSize, final int fx,
+ final int fy, final int fw, final int fh) {
+ g2.translate(fx, fy);
+ PaintUtil.paintFocusBorder(g2, fw, fh, focusArc, borderSize);
+ g2.translate(-fx, -fy);
+ }
+
protected void paintNeighbourFocus(final Graphics2D g2, final Component c, final int width, final int height) {
JComponent left = ButtonConstants.getNeighbour(DarkButtonUI.KEY_LEFT_NEIGHBOUR, c);
boolean paintLeft = DarkUIUtil.hasFocus(left);
@@ -156,9 +173,8 @@ public class DarkButtonBorder implements Border, UIResource {
if (corner != null) ins = corner.maskInsets(ins, borderSize);
int h = height - Math.max(0, getShadowSize(left) - borderSize);
- g2.translate(-3 * borderSize + 1, -ins.top);
- PaintUtil.paintFocusBorder(g2, 4 * borderSize, h + ins.top + ins.bottom, getFocusArc(left), borderSize);
- g2.translate(-(-3 * borderSize + 1), ins.bottom);
+ paintFocusBorder(g2, getFocusArc(left), borderSize, -3 * borderSize + 1, -ins.top, 4 * borderSize,
+ h + ins.top + ins.bottom);
}
JComponent right = ButtonConstants.getNeighbour(DarkButtonUI.KEY_RIGHT_NEIGHBOUR, c);
boolean paintRight = DarkUIUtil.hasFocus(right);
@@ -168,9 +184,8 @@ public class DarkButtonBorder implements Border, UIResource {
if (corner != null) ins = corner.maskInsets(ins, borderSize);
int h = height - Math.max(0, getShadowSize(right) - borderSize);
- g2.translate(width - borderSize - 1, -ins.top);
- PaintUtil.paintFocusBorder(g2, 4 * borderSize, h + ins.top + ins.bottom, getFocusArc(right), borderSize);
- g2.translate(-(width - borderSize - 1), ins.top);
+ paintFocusBorder(g2, getFocusArc(right), borderSize, width - borderSize - 1, -ins.top, 4 * borderSize,
+ h + ins.top + ins.bottom);
}
JComponent top = ButtonConstants.getNeighbour(DarkButtonUI.KEY_TOP_NEIGHBOUR, c);
@@ -180,25 +195,22 @@ public class DarkButtonBorder implements Border, UIResource {
Insets ins = new Insets(0, 0, 0, 0);
if (corner != null) ins = corner.maskInsets(ins, borderSize);
- g2.translate(-ins.left, -3 * borderSize + 1);
- PaintUtil.paintFocusBorder(g2, width + ins.right + ins.left, 4 * borderSize, getFocusArc(top), borderSize);
- g2.translate(ins.left, -(-3 * borderSize + 1));
+ paintFocusBorder(g2, getFocusArc(top), borderSize, -ins.left, -3 * borderSize + 1,
+ width + ins.right + ins.left, 4 * borderSize);
}
JComponent topLeft = ButtonConstants.getNeighbour(DarkButtonUI.KEY_TOP_LEFT_NEIGHBOUR, c);
boolean paintTopLeft = DarkUIUtil.hasFocus(topLeft);
if (paintTopLeft) {
- g2.translate(-3 * borderSize + 1, -3 * borderSize + 1);
- PaintUtil.paintFocusBorder(g2, 4 * borderSize, 4 * borderSize, getFocusArc(topLeft), borderSize);
- g2.translate(-(-3 * borderSize + 1), -(-3 * borderSize + 1));
+ paintFocusBorder(g2, getFocusArc(topLeft), borderSize, -3 * borderSize + 1, -3 * borderSize + 1,
+ 4 * borderSize, 4 * borderSize);
}
JComponent topRight = ButtonConstants.getNeighbour(DarkButtonUI.KEY_TOP_RIGHT_NEIGHBOUR, c);
boolean paintTopRight = DarkUIUtil.hasFocus(topRight);
if (paintTopRight) {
- g2.translate(width - borderSize - 1, -3 * borderSize + 1);
- PaintUtil.paintFocusBorder(g2, 4 * borderSize, 4 * borderSize, getFocusArc(topRight), borderSize);
- g2.translate(-(width - borderSize - 1), -(-3 * borderSize + 1));
+ paintFocusBorder(g2, getFocusArc(topRight), borderSize, width - borderSize - 1, -3 * borderSize + 1,
+ 4 * borderSize, 4 * borderSize);
}
JComponent bottom = ButtonConstants.getNeighbour(DarkButtonUI.KEY_BOTTOM_NEIGHBOUR, c);
@@ -208,26 +220,22 @@ public class DarkButtonBorder implements Border, UIResource {
Insets ins = new Insets(0, 0, 0, 0);
if (corner != null) ins = corner.maskInsets(ins, borderSize);
- g2.translate(-ins.left, height - borderSize - 1);
- PaintUtil.paintFocusBorder(g2, width + ins.left + ins.right, 4 * borderSize, getFocusArc(bottom),
- borderSize);
- g2.translate(ins.left, -(height - borderSize - 1));
+ paintFocusBorder(g2, getFocusArc(bottom), borderSize, -ins.left, height - borderSize - 1,
+ width + ins.left + ins.right, 4 * borderSize);
}
JComponent bottomLeft = ButtonConstants.getNeighbour(DarkButtonUI.KEY_BOTTOM_LEFT_NEIGHBOUR, c);
boolean paintBottomLeft = DarkUIUtil.hasFocus(bottomLeft);
if (paintBottomLeft) {
- g2.translate(-3 * borderSize + 1, height - borderSize - 1);
- PaintUtil.paintFocusBorder(g2, 4 * borderSize, 4 * borderSize, getFocusArc(bottomLeft), borderSize);
- g2.translate(-(-3 * borderSize + 1), -(height - borderSize - 1));
+ paintFocusBorder(g2, getFocusArc(bottomLeft), borderSize, -3 * borderSize + 1, height - borderSize - 1,
+ 4 * borderSize, 4 * borderSize);
}
JComponent bottomRight = ButtonConstants.getNeighbour(DarkButtonUI.KEY_BOTTOM_RIGHT_NEIGHBOUR, c);
boolean paintBottomRight = DarkUIUtil.hasFocus(bottomRight);
if (paintBottomRight) {
- g2.translate(width - borderSize - 1, height - borderSize - 1);
- PaintUtil.paintFocusBorder(g2, 4 * borderSize, 4 * borderSize, getFocusArc(bottomRight), borderSize);
- g2.translate(-(width - borderSize - 1), -(height - borderSize - 1));
+ paintFocusBorder(g2, getFocusArc(bottomRight), borderSize, width - borderSize - 1, height - borderSize - 1,
+ 4 * borderSize, 4 * borderSize);
}
}
diff --git a/core/src/main/java/com/github/weisj/darklaf/ui/splitbutton/DarkSplitButtonBorder.java b/core/src/main/java/com/github/weisj/darklaf/ui/splitbutton/DarkSplitButtonBorder.java
new file mode 100644
index 00000000..54cf6c34
--- /dev/null
+++ b/core/src/main/java/com/github/weisj/darklaf/ui/splitbutton/DarkSplitButtonBorder.java
@@ -0,0 +1,83 @@
+/*
+ * 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.splitbutton;
+
+import java.awt.*;
+
+import javax.swing.*;
+
+import com.github.weisj.darklaf.components.button.JSplitButton;
+import com.github.weisj.darklaf.ui.button.ButtonConstants;
+import com.github.weisj.darklaf.ui.button.DarkButtonBorder;
+import com.github.weisj.darklaf.util.DarkUIUtil;
+
+public class DarkSplitButtonBorder extends DarkButtonBorder {
+
+ @Override
+ protected void paintBorderlessBorder(final Component c, final Graphics g, final int x, final int y, final int width,
+ final int height) {
+ if (showSplit(c)) {
+ paintDivider(c, g, x, y, width, height);
+ }
+ super.paintBorderlessBorder(c, g, x, y, width, height);
+ }
+
+ @Override
+ protected void paintLineBorder(final Component c, final Graphics2D g2, final int arc, final boolean focus,
+ final int bx, final int by, final int bw, final int bh) {
+ if (showSplit(c)) {
+ paintDivider(c, g2, bx, by, bw, bh);
+ }
+ super.paintLineBorder(c, g2, arc, focus, bx, by, bw, bh);
+ }
+
+ protected void paintDivider(final Component c, final Graphics g, final int x, final int y, final int width,
+ final int height) {
+ if (!(c instanceof JSplitButton)) return;
+ Component arrowButton = ((JSplitButton) c).getComponent(0);
+ if (arrowButton == null) return;
+ boolean ltr = c.getComponentOrientation().isLeftToRight();
+ int splitPos = ltr ? arrowButton.getX() : arrowButton.getX() + arrowButton.getWidth() - 1;
+
+ DarkSplitButtonUI ui = DarkUIUtil.getUIOfType(((JSplitButton) c).getUI(), DarkSplitButtonUI.class);
+ if (ui != null && ui.getDrawOutline(c)) {
+ boolean armed = ui.isArmedBorderless(ui.splitButton)
+ || (ui.useArrowButton() && ui.isArmedBorderless(ui.arrowButton));
+ g.setColor(ui.getBorderlessOutline(armed));
+ } else {
+ g.setColor(getBorderColor(c, false));
+ }
+
+ g.fillRect(splitPos, y, 1, height);
+ }
+
+ protected boolean showSplit(final Component c) {
+ boolean hasDefaultAction = c instanceof JSplitButton && ((JSplitButton) c).getActionCount() > 1;
+ boolean borderless = ButtonConstants.isBorderlessVariant(c);
+ if (!borderless) return hasDefaultAction;
+ if (hasDefaultAction && ButtonConstants.isBorderlessVariant(c)) {
+ DarkSplitButtonUI ui = DarkUIUtil.getUIOfType(((JSplitButton) c).getUI(), DarkSplitButtonUI.class);
+ return ui != null && ui.isRolloverBorderless((AbstractButton) c);
+ }
+ return false;
+ }
+}
diff --git a/core/src/main/java/com/github/weisj/darklaf/ui/splitbutton/DarkSplitButtonListener.java b/core/src/main/java/com/github/weisj/darklaf/ui/splitbutton/DarkSplitButtonListener.java
new file mode 100644
index 00000000..653cf9e6
--- /dev/null
+++ b/core/src/main/java/com/github/weisj/darklaf/ui/splitbutton/DarkSplitButtonListener.java
@@ -0,0 +1,86 @@
+/*
+ * 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.splitbutton;
+
+import java.awt.*;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.beans.PropertyChangeEvent;
+import java.beans.PropertyChangeListener;
+
+import javax.swing.*;
+import javax.swing.event.ChangeEvent;
+import javax.swing.event.ChangeListener;
+
+import com.github.weisj.darklaf.components.button.JSplitButton;
+import com.github.weisj.darklaf.ui.WidgetPopupHelper;
+import com.github.weisj.darklaf.ui.button.ButtonConstants;
+
+public class DarkSplitButtonListener implements ActionListener, PropertyChangeListener, ChangeListener {
+
+
+ private final DarkSplitButtonUI ui;
+
+ public DarkSplitButtonListener(final DarkSplitButtonUI ui) {
+ this.ui = ui;
+ }
+
+ @Override
+ public void actionPerformed(final ActionEvent e) {
+ if (e.getSource() == ui.splitButton && ui.useArrowButton()) return;
+ JPopupMenu actionMenu = ui.splitButton.getActionMenu();
+ if (actionMenu.isVisible()) {
+ actionMenu.setVisible(false);
+ } else {
+ boolean splitButton = e.getSource() == ui.splitButton;
+ actionMenu.setPreferredSize(null);
+ Dimension size = actionMenu.getPreferredSize();
+ Rectangle popupBounds =
+ WidgetPopupHelper.getPopupBounds(ui.splitButton, actionMenu, size, splitButton, !splitButton);
+ if (splitButton) {
+ actionMenu.setPreferredSize(popupBounds.getSize());
+ }
+ actionMenu.show(ui.splitButton, popupBounds.x, popupBounds.y);
+ }
+ }
+
+
+ @Override
+ public void propertyChange(final PropertyChangeEvent evt) {
+ String key = evt.getPropertyName();
+ if (JSplitButton.KEY_ACTION_ADDED.equals(key) || JSplitButton.KEY_ACTION_REMOVED.equals(key)) {
+ ui.updateDefaultAction();
+ ui.splitButton.doLayout();
+ ui.splitButton.repaint();
+ } else if (ButtonConstants.KEY_THIN.equals(key)) {
+ ui.updateArrowMargin();
+ }
+ }
+
+ @Override
+ public void stateChanged(final ChangeEvent e) {
+ ui.splitButton.repaint();
+ if (!ui.splitButton.hasFocus() && ui.arrowButton.getModel().isPressed()) {
+ ui.splitButton.requestFocusInWindow();
+ }
+ }
+}
diff --git a/core/src/main/java/com/github/weisj/darklaf/ui/splitbutton/DarkSplitButtonUI.java b/core/src/main/java/com/github/weisj/darklaf/ui/splitbutton/DarkSplitButtonUI.java
new file mode 100644
index 00000000..f242aefc
--- /dev/null
+++ b/core/src/main/java/com/github/weisj/darklaf/ui/splitbutton/DarkSplitButtonUI.java
@@ -0,0 +1,305 @@
+/*
+ * 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.splitbutton;
+
+import java.awt.*;
+
+import javax.swing.*;
+import javax.swing.plaf.ComponentUI;
+
+import com.github.weisj.darklaf.components.ArrowButton;
+import com.github.weisj.darklaf.components.button.JSplitButton;
+import com.github.weisj.darklaf.icons.ToggleIcon;
+import com.github.weisj.darklaf.ui.button.ButtonConstants;
+import com.github.weisj.darklaf.ui.button.DarkButtonUI;
+import com.github.weisj.darklaf.ui.popupmenu.DarkPopupMenuUI;
+import com.github.weisj.darklaf.util.PropertyUtil;
+
+public class DarkSplitButtonUI extends DarkButtonUI {
+
+ protected JSplitButton splitButton;
+ protected AbstractButton arrowButton;
+ private DarkSplitButtonListener arrowButtonListener;
+ private Icon overlayIcon;
+ private Icon overlayDisabledIcon;
+
+ private ToggleIcon arrowToggleIcon;
+ private ToggleIcon arrowDisabledToggleIcon;
+
+ private Insets arrowInsets;
+ private Insets arrowInsetsThin;
+ private final Insets arrowButtonMargin = new Insets(0, 0, 0, 0);
+
+ public static ComponentUI createUI(final JComponent c) {
+ return new DarkSplitButtonUI();
+ }
+
+
+ @Override
+ public void installUI(final JComponent c) {
+ splitButton = (JSplitButton) c;
+ super.installUI(c);
+ }
+
+ @Override
+ public void uninstallUI(final JComponent c) {
+ super.uninstallUI(c);
+ splitButton = null;
+ }
+
+ @Override
+ protected void installDefaults(final AbstractButton b) {
+ super.installDefaults(b);
+ overlayIcon = UIManager.getIcon("SplitButton.overlayIcon");
+ overlayDisabledIcon = UIManager.getIcon("SplitButton.overlayDisabledIcon");
+ arrowInsets = UIManager.getInsets("SplitButton.arrowInsets");
+ arrowInsetsThin = UIManager.getInsets("SplitButton.arrowThinInsets");
+ Icon arrowIcon = UIManager.getIcon("SplitButton.arrowIcon");
+ Icon arrowIconDisabled = UIManager.getIcon("SplitButton.arrowIconDisabled");
+ Icon arrowIconThin = UIManager.getIcon("SplitButton.arrowThinIcon");
+ Icon arrowIconThinDisabled = UIManager.getIcon("SplitButton.arrowThinIconDisabled");
+
+ arrowToggleIcon = new ToggleIcon(arrowIcon, arrowIconThin);
+ arrowDisabledToggleIcon = new ToggleIcon(arrowIconDisabled, arrowIconThinDisabled);
+
+ PropertyUtil.installBorder(splitButton, new DarkSplitButtonBorder());
+ arrowButton = createArrowButton();
+ configureArrowButton(arrowButton);
+ splitButton.add(arrowButton);
+ updateArrowMargin();
+ updateDefaultAction();
+ }
+
+ protected void configureArrowButton(final AbstractButton button) {
+ button.setRequestFocusEnabled(false);
+ button.setInheritsPopupMenu(true);
+ button.resetKeyboardActions();
+ button.setEnabled(splitButton.isEnabled());
+ button.putClientProperty(DarkPopupMenuUI.KEY_CONSUME_EVENT_ON_CLOSE, true);
+ }
+
+ @Override
+ protected void installListeners(final AbstractButton b) {
+ super.installListeners(b);
+ arrowButtonListener = createArrowButtonListener();
+ arrowButton.addActionListener(arrowButtonListener);
+ splitButton.addActionListener(arrowButtonListener);
+ arrowButton.getModel().addChangeListener(arrowButtonListener);
+ splitButton.addPropertyChangeListener(arrowButtonListener);
+ }
+
+ @Override
+ protected void uninstallListeners(final AbstractButton b) {
+ super.uninstallListeners(b);
+ arrowButton.removeActionListener(arrowButtonListener);
+ splitButton.removeActionListener(arrowButtonListener);
+ arrowButton.getModel().removeActionListener(arrowButtonListener);
+ splitButton.removePropertyChangeListener(arrowButtonListener);
+ arrowButtonListener = null;
+ }
+
+ @Override
+ protected void uninstallDefaults(final AbstractButton b) {
+ super.uninstallDefaults(b);
+ splitButton.remove(arrowButton);
+ arrowButton = null;
+ }
+
+ protected void updateArrowMargin() {
+ if (ButtonConstants.isThin(splitButton)) {
+ arrowButtonMargin.set(arrowInsetsThin.top, arrowInsetsThin.left, arrowInsetsThin.bottom,
+ arrowInsetsThin.right);
+ arrowToggleIcon.setChooseAlternativeIcon(true);
+ } else {
+ arrowButtonMargin.set(arrowInsets.top, arrowInsets.left, arrowInsets.bottom, arrowInsets.right);
+ arrowToggleIcon.setChooseAlternativeIcon(false);
+ }
+ splitButton.doLayout();
+ }
+
+ protected DarkSplitButtonListener createArrowButtonListener() {
+ return new DarkSplitButtonListener(this);
+ }
+
+ protected AbstractButton createArrowButton() {
+ AbstractButton b = ArrowButton.createUpDownArrow(splitButton, arrowToggleIcon, arrowDisabledToggleIcon,
+ SwingConstants.SOUTH, true, true, arrowButtonMargin);
+ b.setRolloverEnabled(true);
+ return b;
+ }
+
+ @Override
+ public Dimension getPreferredSize(final JComponent c) {
+ Dimension dim = super.getPreferredSize(c);
+ if (useArrowButton()) {
+ Dimension arrowSize = arrowButton.getPreferredSize();
+ dim.width += arrowSize.width;
+ dim.height = Math.max(dim.height, arrowSize.height);
+ }
+ return dim;
+ }
+
+ @Override
+ protected LayoutManager createLayout() {
+ return new DarkSplitButtonLayout();
+ }
+
+ protected boolean useArrowButton() {
+ return arrowButton != null && splitButton.getActionCount() > 1;
+ }
+
+ @Override
+ protected void paintIcon(final Graphics g, final AbstractButton b, final JComponent c) {
+ super.paintIcon(g, b, c);
+ if (b.getIcon() != null && !useArrowButton()) {
+ Icon overlay = b.isEnabled() ? overlayIcon : overlayDisabledIcon;
+ overlay.paintIcon(c, g, iconRect.x + iconRect.width - overlay.getIconWidth(),
+ iconRect.y + iconRect.height - overlay.getIconHeight());
+ }
+ }
+
+ @Override
+ public boolean isRolloverBorderless(final AbstractButton b) {
+ return super.isRolloverBorderless(b) || (useArrowButton() && arrowButton.getModel().isRollover());
+ }
+
+ @Override
+ protected void paintDarklafBorderBgImpl(final AbstractButton c, final Graphics2D g, final boolean showShadow,
+ final int shadow, final int effectiveArc, final Rectangle bgRect) {
+ super.paintDarklafBorderBgImpl(c, g, showShadow, shadow, effectiveArc, bgRect);
+ if (useArrowButton()) {
+ boolean isDefault = splitButton.isDefaultButton();
+ boolean enabled = splitButton.isEnabled();
+ boolean rollover = c.isRolloverEnabled() && arrowButton.getModel().isRollover();
+ boolean clicked = arrowButton.getModel().isArmed();
+ g.setColor(getBackgroundColor(splitButton, isDefault, rollover, clicked, enabled));
+ Shape clip = g.getClip();
+ boolean ltr = c.getComponentOrientation().isLeftToRight();
+ if (ltr) {
+ g.clipRect(arrowButton.getX(), 0, button.getWidth(), button.getHeight());
+ } else {
+ g.clipRect(0, 0, arrowButton.getX() + arrowButton.getWidth(), button.getHeight());
+ }
+ paintBackgroundRect(g, effectiveArc, bgRect);
+ g.setClip(clip);
+ }
+ }
+
+ protected void setArmedClip(final AbstractButton c, final Graphics g) {
+ boolean ltr = c.getComponentOrientation().isLeftToRight();
+ boolean arrowArmed = arrowButton.getModel().isRollover();
+ if (ltr) {
+ if (arrowArmed) {
+ g.clipRect(arrowButton.getX(), 0, splitButton.getWidth(), splitButton.getHeight());
+ } else {
+ g.clipRect(0, 0, arrowButton.getX(), splitButton.getHeight());
+ }
+ } else {
+ if (arrowArmed) {
+ g.clipRect(0, 0, arrowButton.getX() + arrowButton.getWidth(), splitButton.getHeight());
+ } else {
+ g.clipRect(arrowButton.getX() + arrowButton.getWidth(), 0, splitButton.getWidth(),
+ splitButton.getHeight());
+ }
+ }
+ }
+
+ @Override
+ protected void paintBorderlessBackgroundImpl(final AbstractButton b, final Graphics2D g, final int arc, final int x,
+ final int y, final int w, final int h) {
+ boolean splitArmed = splitButton.getModel().isArmed();
+ boolean arrowArmed = arrowButton.getModel().isArmed();
+ Shape clip = g.getClip();
+ if (splitArmed) {
+ super.paintBorderlessBackgroundImpl(arrowButton, g, arc, x, y, w, h);
+ setArmedClip(splitButton, g);
+ super.paintBorderlessBackgroundImpl(splitButton, g, arc, x, y, w, h);
+ } else if (arrowArmed) {
+ super.paintBorderlessBackgroundImpl(splitButton, g, arc, x, y, w, h);
+ setArmedClip(splitButton, g);
+ super.paintBorderlessBackgroundImpl(arrowButton, g, arc, x, y, w, h);
+ } else {
+ super.paintBorderlessBackgroundImpl(b, g, arc, x, y, w, h);
+ }
+ g.setClip(clip);
+ }
+
+ @Override
+ protected void paintBorderlessRectangularBackgroundIml(final AbstractButton b, final Graphics2D g, final int x,
+ final int y, final int w, final int h) {
+ boolean splitArmed = splitButton.getModel().isArmed();
+ boolean arrowArmed = splitButton.getModel().isArmed();
+ Shape clip = g.getClip();
+ if (splitArmed) {
+ super.paintBorderlessRectangularBackgroundIml(arrowButton, g, x, y, w, h);
+ setArmedClip(splitButton, g);
+ super.paintBorderlessRectangularBackgroundIml(splitButton, g, x, y, w, h);
+ } else if (arrowArmed) {
+ super.paintBorderlessRectangularBackgroundIml(splitButton, g, x, y, w, h);
+ setArmedClip(splitButton, g);
+ super.paintBorderlessRectangularBackgroundIml(arrowButton, g, x, y, w, h);
+ } else {
+ super.paintBorderlessRectangularBackgroundIml(b, g, x, y, w, h);
+ }
+ g.setClip(clip);
+ }
+
+ public void updateDefaultAction() {
+ arrowButton.setVisible(useArrowButton());
+ splitButton.putClientProperty(DarkPopupMenuUI.KEY_CONSUME_EVENT_ON_CLOSE, !useArrowButton());
+ }
+
+ protected class DarkSplitButtonLayout extends DarkButtonLayout {
+
+ @Override
+ public void layoutContainer(final Container parent) {
+ super.layoutContainer(parent);
+ if (useArrowButton()) {
+ Insets ins = parent.getInsets();
+ Dimension arrowSize = arrowButton.getPreferredSize();
+ boolean ltr = splitButton.getComponentOrientation().isLeftToRight();
+ if (ltr) {
+ arrowButton.setBounds(parent.getWidth() - ins.right - arrowSize.width, 0,
+ arrowSize.width + ins.right, parent.getHeight());
+ arrowButton.setMargin(new Insets(ins.top, 0, ins.bottom, ins.right));
+ } else {
+ arrowButton.setBounds(ins.left, ins.top, arrowSize.width,
+ parent.getHeight() - ins.top - ins.bottom);
+ arrowButton.setMargin(new Insets(ins.top, ins.left, ins.bottom, 0));
+ }
+ }
+ }
+
+ @Override
+ protected void prepareContentRects(final AbstractButton b, final int width, final int height) {
+ super.prepareContentRects(b, width, height);
+ if (useArrowButton()) {
+ Dimension arrowSize = arrowButton.getPreferredSize();
+ boolean ltr = splitButton.getComponentOrientation().isLeftToRight();
+ viewRect.width -= arrowSize.width;
+ if (!ltr) {
+ viewRect.x += arrowSize.width;
+ }
+ }
+ }
+ }
+}
diff --git a/core/src/main/resources/com/github/weisj/darklaf/icons/indicator/dropdown.svg b/core/src/main/resources/com/github/weisj/darklaf/icons/indicator/dropdown.svg
new file mode 100644
index 00000000..12c05bc7
--- /dev/null
+++ b/core/src/main/resources/com/github/weisj/darklaf/icons/indicator/dropdown.svg
@@ -0,0 +1,9 @@
+
diff --git a/core/src/main/resources/com/github/weisj/darklaf/icons/indicator/dropdownDisabled.svg b/core/src/main/resources/com/github/weisj/darklaf/icons/indicator/dropdownDisabled.svg
new file mode 100644
index 00000000..45378f70
--- /dev/null
+++ b/core/src/main/resources/com/github/weisj/darklaf/icons/indicator/dropdownDisabled.svg
@@ -0,0 +1,9 @@
+
diff --git a/core/src/main/resources/com/github/weisj/darklaf/properties/icons/indicator.properties b/core/src/main/resources/com/github/weisj/darklaf/properties/icons/indicator.properties
index e58cf2c3..80b7a588 100644
--- a/core/src/main/resources/com/github/weisj/darklaf/properties/icons/indicator.properties
+++ b/core/src/main/resources/com/github/weisj/darklaf/properties/icons/indicator.properties
@@ -48,3 +48,6 @@ Icons.speaker4.color = %menuIconEnabled
Icons.speaker4.volumeColor = %menuIconEnabled
Icons.speaker4Disabled.color = %menuIconDisabled
Icons.speaker4Disabled.volumeColor = %menuIconDisabled
+
+Icons.dropdown.color = %menuIconEnabled
+Icons.dropdownDisabled.color = %menuIconDisabled
diff --git a/core/src/main/resources/com/github/weisj/darklaf/properties/ui/misc.properties b/core/src/main/resources/com/github/weisj/darklaf/properties/ui/misc.properties
index 89343fe4..716d370d 100644
--- a/core/src/main/resources/com/github/weisj/darklaf/properties/ui/misc.properties
+++ b/core/src/main/resources/com/github/weisj/darklaf/properties/ui/misc.properties
@@ -24,15 +24,25 @@
#
# suppress inspection "UnusedProperty" for whole file
#
-ThemeSettings.icon = menu/themeSettings.svg[themed]
+ThemeSettings.icon = menu/themeSettings.svg[themed]
-LoadIndicator.stepWorkingIcon = progress/stepWorking.svg[themed]
-LoadIndicator.stepPassiveIcon = progress/stepPassive.svg[themed]
+LoadIndicator.stepWorkingIcon = progress/stepWorking.svg[themed]
+LoadIndicator.stepPassiveIcon = progress/stepPassive.svg[themed]
-CloseButton.closeIcon = navigation/close.svg[themed]
-CloseButton.closeDisabledIcon = navigation/closeDisabled.svg[themed]
-CloseButton.closeHoverIcon = navigation/closeHovered.svg[themed]
+CloseButton.closeIcon = navigation/close.svg[themed]
+CloseButton.closeDisabledIcon = navigation/closeDisabled.svg[themed]
+CloseButton.closeHoverIcon = navigation/closeHovered.svg[themed]
-HelpButton.helpIcon = menu/help.svg[themed]
-HelpButton.helpHighlightIcon = menu/helpHighlight.svg[themed]
-HelpButton.helpDisabledIcon = menu/helpDisabled.svg[themed]
+HelpButton.helpIcon = menu/help.svg[themed]
+HelpButton.helpHighlightIcon = menu/helpHighlight.svg[themed]
+HelpButton.helpDisabledIcon = menu/helpDisabled.svg[themed]
+
+SplitButtonUI = com.github.weisj.darklaf.ui.splitbutton.DarkSplitButtonUI
+SplitButton.arrowInsets = 4,4,4,4
+SplitButton.arrowThinInsets = 2,0,2,0
+SplitButton.overlayIcon = indicator/dropdown.svg[themed]
+SplitButton.overlayDisabledIcon = indicator/dropdownDisabled.svg[themed]
+SplitButton.arrowIcon = navigation/arrow/thick/arrowDown.svg[themed]
+SplitButton.arrowDisabledIcon = navigation/arrow/thick/arrowDownDisabled.svg[themed]
+SplitButton.arrowThinIcon = navigation/arrow/thick/arrowDown.svg[themed](14,14)
+SplitButton.arrowThinDisabledIcon = navigation/arrow/thick/arrowDownDisabled.svg[themed](14,14)
diff --git a/core/src/test/java/ui/button/SplitButtonDemo.java b/core/src/test/java/ui/button/SplitButtonDemo.java
new file mode 100644
index 00000000..7438045d
--- /dev/null
+++ b/core/src/test/java/ui/button/SplitButtonDemo.java
@@ -0,0 +1,74 @@
+/*
+ * 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 ui.button;
+
+import java.awt.event.ActionListener;
+
+import javax.swing.*;
+
+import ui.ComponentDemo;
+import ui.DemoResources;
+
+import com.github.weisj.darklaf.components.button.JSplitButton;
+
+public class SplitButtonDemo extends ButtonDemo {
+
+ public static void main(final String[] args) {
+ ComponentDemo.showDemo(new SplitButtonDemo());
+ }
+
+ @Override
+ protected JButton createButton() {
+ Icon icon = DemoResources.FOLDER_ICON;
+ JSplitButton button = new JSplitButton("Split Button", icon);
+ JPopupMenu menu = button.getActionMenu();
+ for (int i = 0; i < 5; i++) {
+ menu.add("Item " + i);
+ }
+ return button;
+ }
+
+ @Override
+ protected void addCheckBoxControls(final JPanel controlPanel, final JButton button) {
+ super.addCheckBoxControls(controlPanel, button);
+ controlPanel.add(new JCheckBox("Default action set") {
+ private final ActionListener l = ee -> {
+ };
+
+ {
+ addActionListener(e -> {
+ if (isSelected()) {
+ button.addActionListener(l);
+ } else {
+ button.removeActionListener(l);
+ }
+ });
+ }
+ });
+
+ }
+
+ @Override
+ public String getTitle() {
+ return "Split Button Demo";
+ }
+}