Browse Source

Make slider variant of JToggleButton animated.

Add on/off label hints to slider variant for some themes.
pull/214/head
weisj 4 years ago
parent
commit
331c5504c3
  1. 14
      core/src/main/java/com/github/weisj/darklaf/graphics/Animator.java
  2. 7
      core/src/main/java/com/github/weisj/darklaf/ui/button/DarkButtonListener.java
  3. 45
      core/src/main/java/com/github/weisj/darklaf/ui/button/DarkButtonUI.java
  4. 74
      core/src/main/java/com/github/weisj/darklaf/ui/togglebutton/DarkToggleButtonListener.java
  5. 219
      core/src/main/java/com/github/weisj/darklaf/ui/togglebutton/DarkToggleButtonUI.java
  6. 1
      core/src/main/resources/com/github/weisj/darklaf/properties/ui/toggleButton.properties
  7. 1
      theme/src/main/resources/com/github/weisj/darklaf/theme/high_contrast_dark/high_contrast_dark_ui.properties
  8. 1
      theme/src/main/resources/com/github/weisj/darklaf/theme/high_contrast_light/high_contrast_light_ui.properties

14
core/src/main/java/com/github/weisj/darklaf/graphics/Animator.java

@ -35,10 +35,11 @@ public abstract class Animator {
private final int totalFrames;
private final int cycleDuration;
private final boolean forward;
private final boolean repeatable;
private final int delayFrames;
private boolean forward;
private final Interpolator interpolator;
private ScheduledFuture<?> ticker;
@ -78,13 +79,16 @@ public abstract class Animator {
reset();
}
private void resetTime() {
public void setForward(final boolean forward) {
this.forward = forward;
}
public void resetTime() {
startTime = -1;
}
public void reset() {
currentFrame %= totalFrames;
if (!forward) currentFrame = totalFrames - currentFrame;
}
private static ScheduledExecutorService createScheduler() {
@ -105,7 +109,7 @@ public abstract class Animator {
stopTicker();
}
private void stopTicker() {
public void stopTicker() {
if (ticker != null) {
ticker.cancel(false);
ticker = null;
@ -207,7 +211,7 @@ public abstract class Animator {
return currentFrame;
}
public float getTotalFrames() {
public int getTotalFrames() {
return totalFrames;
}
}

7
core/src/main/java/com/github/weisj/darklaf/ui/button/DarkButtonListener.java

@ -21,7 +21,6 @@
*/
package com.github.weisj.darklaf.ui.button;
import java.awt.*;
import java.awt.event.FocusEvent;
import java.awt.event.MouseEvent;
import java.beans.PropertyChangeEvent;
@ -29,11 +28,11 @@ import java.beans.PropertyChangeEvent;
import javax.swing.*;
import javax.swing.plaf.basic.BasicButtonListener;
public class DarkButtonListener extends BasicButtonListener {
public class DarkButtonListener<T extends DarkButtonUI> extends BasicButtonListener {
private final DarkButtonUI ui;
protected final T ui;
public DarkButtonListener(final AbstractButton b, final DarkButtonUI ui) {
public DarkButtonListener(final AbstractButton b, final T ui) {
super(b);
this.ui = ui;
}

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

@ -150,7 +150,7 @@ public class DarkButtonUI extends BasicButtonUI implements ButtonConstants {
@Override
protected BasicButtonListener createButtonListener(final AbstractButton b) {
return new DarkButtonListener(b, this);
return new DarkButtonListener<>(b, this);
}
@Override
@ -366,15 +366,19 @@ public class DarkButtonUI extends BasicButtonUI implements ButtonConstants {
return ButtonConstants.chooseArcWithBorder(c, arc, 0, altArc, borderSize);
}
protected Color getForeground(final AbstractButton button) {
Color fg = button.getForeground();
if (fg instanceof UIResource && isDefaultButton && !ButtonConstants.isBorderlessVariant(button)) {
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 (fg instanceof UIResource && !button.getModel().isEnabled()) {
if (!enabled) {
fg = inactiveForeground;
}
return fg;
return PropertyUtil.chooseColor(b.getForeground(), fg);
}
protected Color getBackgroundColor(final AbstractButton b) {
@ -450,14 +454,14 @@ public class DarkButtonUI extends BasicButtonUI implements ButtonConstants {
}
}
protected void updateMargins(final AbstractButton b) {
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));
}
private Insets getMargins(final AbstractButton b) {
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;
@ -524,13 +528,32 @@ public class DarkButtonUI extends BasicButtonUI implements ButtonConstants {
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(), bl.getVerticalAlignment(),
bl.getHorizontalAlignment(), bl.getVerticalTextPosition(), bl.getHorizontalTextPosition(), viewRect,
iconRect, textRect,
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());

74
core/src/main/java/com/github/weisj/darklaf/ui/togglebutton/DarkToggleButtonListener.java

@ -24,15 +24,27 @@ package com.github.weisj.darklaf.ui.togglebutton;
import java.beans.PropertyChangeEvent;
import javax.swing.*;
import javax.swing.event.ChangeEvent;
import com.github.weisj.darklaf.graphics.Animator;
import com.github.weisj.darklaf.ui.button.DarkButtonListener;
import com.github.weisj.darklaf.ui.button.DarkButtonUI;
import com.github.weisj.darklaf.util.PropertyKey;
public class DarkToggleButtonListener extends DarkButtonListener implements ToggleButtonConstants {
public class DarkToggleButtonListener extends DarkButtonListener<DarkToggleButtonUI> implements ToggleButtonConstants {
public DarkToggleButtonListener(final AbstractButton b, final DarkButtonUI ui) {
private final SliderAnimator animator;
private final AbstractButton button;
private boolean selected;
public DarkToggleButtonListener(final AbstractButton b, final DarkToggleButtonUI ui) {
super(b, ui);
this.selected = b.isSelected();
button = b;
animator = new SliderAnimator(b);
}
public float getAnimationState() {
return animator.getState();
}
@Override
@ -47,9 +59,65 @@ public class DarkToggleButtonListener extends DarkButtonListener implements Togg
return;
}
b.setBorderPainted(!VARIANT_SLIDER.equals(newVal));
ui.updateMargins(b);
} else if (PropertyKey.COMPONENT_ORIENTATION.equals(key)) {
b.doLayout();
b.repaint();
}
}
@Override
public void stateChanged(final ChangeEvent e) {
super.stateChanged(e);
boolean sel = button.isSelected();
if (sel != selected) {
selected = sel;
int startFrame = 0;
if (animator.isRunning()) {
startFrame = animator.getCurrentFrame();
}
animator.suspend();
animator.setForward(sel);
animator.setEndValue(sel ? 1 : 0);
animator.resume(startFrame);
}
}
protected static class SliderAnimator extends Animator {
private final JComponent c;
private float state;
private float endValue;
public SliderAnimator(final JComponent c) {
super(10, 100, 0);
this.c = c;
}
public float getState() {
return state;
}
@Override
public void paintNow(final float fraction) {
this.state = fraction;
repaint();
}
@Override
protected void paintCycleEnd() {
this.state = endValue;
repaint();
}
private void repaint() {
if (c != null) {
c.paintImmediately(c.getVisibleRect());
}
}
public void setEndValue(final float endValue) {
this.endValue = endValue;
}
}
}

219
core/src/main/java/com/github/weisj/darklaf/ui/togglebutton/DarkToggleButtonUI.java

@ -28,21 +28,18 @@ import javax.swing.plaf.ComponentUI;
import javax.swing.plaf.UIResource;
import javax.swing.plaf.basic.BasicButtonListener;
import sun.swing.SwingUtilities2;
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.ui.button.ButtonConstants;
import com.github.weisj.darklaf.ui.button.DarkButtonUI;
import com.github.weisj.darklaf.util.PropertyUtil;
/** @author Jannis Weis */
public class DarkToggleButtonUI extends DarkButtonUI implements ToggleButtonConstants {
private static final Rectangle rect = new Rectangle();
protected boolean showSliderHints;
protected Dimension sliderSize;
protected Color background;
protected Color selectedBackground;
protected Color backgroundInactive;
protected Color focusBorderColor;
protected Color borderColor;
@ -52,6 +49,7 @@ public class DarkToggleButtonUI extends DarkButtonUI implements ToggleButtonCons
protected Color sliderBorderColor;
protected Color inactiveSliderBorderColor;
protected Color selectedForeground;
protected DarkToggleButtonListener toggleButtonListener;
public static ComponentUI createUI(final JComponent c) {
return new DarkToggleButtonUI();
@ -59,39 +57,62 @@ public class DarkToggleButtonUI extends DarkButtonUI implements ToggleButtonCons
@Override
protected void installDefaults(final AbstractButton b) {
super.installDefaults(b);
sliderSize = UIManager.getDimension("ToggleButton.sliderSize");
background = UIManager.getColor("ToggleButton.activeFillColor");
selectedBackground = UIManager.getColor("ToggleButton.activeFillColor");
backgroundInactive = UIManager.getColor("ToggleButton.inactiveFillColor");
focusBorderColor = UIManager.getColor("ToggleButton.focusedSliderBorderColor");
borderColor = UIManager.getColor("ToggleButton.sliderBorderColor");
inactiveBorderColor = UIManager.getColor("ToggleButton.disabledSliderBorderColor");
sliderColor = UIManager.getColor("ToggleButton.sliderKnobFillColor");
inactiveSliderColor = UIManager.getColor("ToggleButton.disabledSliderKnobFillColor");
sliderBorderColor = UIManager.getColor("ToggleButton.sliderKnobBorderColor");
inactiveSliderBorderColor = UIManager.getColor("ToggleButton.disabledSliderKnobBorderColor");
selectedForeground = UIManager.getColor("ToggleButton.selectedForeground");
showSliderHints = UIManager.getBoolean("ToggleButton.showSliderHints");
super.installDefaults(b);
}
@Override
protected LayoutManager createLayout() {
return new DarkToggleButtonLayout();
}
@Override
protected BasicButtonListener createButtonListener(final AbstractButton b) {
return new DarkToggleButtonListener(b, this);
if (toggleButtonListener == null) {
toggleButtonListener = new DarkToggleButtonListener(b, this);
}
return toggleButtonListener;
}
@Override
public void paint(final Graphics g, final JComponent c) {
if (ToggleButtonConstants.isSlider(c)) {
GraphicsContext config = GraphicsUtil.setupStrokePainting(g);
AbstractButton b = (AbstractButton) c;
String text = layoutSlider(b, SwingUtilities2.getFontMetrics(b, g), b.getWidth(), b.getHeight());
paintSlider((Graphics2D) g, b);
paintIcon(g, b, c);
paintSlider((Graphics2D) g, (AbstractButton) c);
config.restoreClip();
paintText(g, b, text);
config.restore();
} else {
}
super.paint(g, c);
}
@Override
protected boolean shouldDrawBackground(final AbstractButton c) {
return super.shouldDrawBackground(c) && !ToggleButtonConstants.isSlider(c);
}
@Override
protected Insets getMargins(final AbstractButton b) {
if (ToggleButtonConstants.isSlider(b)) {
boolean ltr = b.getComponentOrientation().isLeftToRight();
int extra = 2 * borderSize + getSliderBounds(b).width;
int left = ltr ? extra : 0;
int right = !ltr ? extra : 0;
return new Insets(0, left, 0, right);
}
return super.getMargins(b);
}
private void paintSlider(final Graphics2D g, final AbstractButton c) {
@ -105,55 +126,92 @@ public class DarkToggleButtonUI extends DarkButtonUI implements ToggleButtonCons
g.translate(borderSize, borderSize);
}
g.setColor(c.getBackground());
PaintUtil.fillRoundRect(g, 0, 0, bounds.width, bounds.height, bounds.height);
int bw = 1;
int knobSize = bounds.height;
int arc = Math.min(bounds.width, bounds.height);
Rectangle knobBounds = new Rectangle(0, 0, knobSize, knobSize);
knobBounds.x = (int) ((bounds.width - knobBounds.width) * toggleButtonListener.getAnimationState());
boolean rollOver = c.isRolloverEnabled() && c.getModel().isRollover();
boolean clicked = c.getModel().isArmed();
boolean enabled = c.isEnabled();
Color selectedBg = getBackgroundColor(c, false, rollOver, clicked, enabled, true);
Color deselectedBg = getBackgroundColor(c, false, rollOver, clicked, enabled, false);
Shape clip = g.getClip();
g.clipRect(0, 0, knobBounds.x + knobBounds.width / 2, bounds.height);
g.setColor(selectedBg);
PaintUtil.fillRoundRect(g, 0, 0, bounds.width, bounds.height, arc);
g.setClip(clip);
g.clipRect(knobBounds.x + knobBounds.width / 2, 0, bounds.width - knobBounds.width / 2, bounds.height);
g.setColor(deselectedBg);
PaintUtil.fillRoundRect(g, 0, 0, bounds.width, bounds.height, arc);
g.setClip(clip);
g.setColor(getToggleBorderColor(c));
PaintUtil.paintLineBorder(g, 0, 0, bounds.width, bounds.height, bounds.height);
PaintUtil.paintLineBorder(g, 0, 0, bounds.width, bounds.height, arc);
int size = bounds.height - 2;
knobBounds.x += bw;
knobBounds.y += bw;
knobBounds.width -= 2 * bw;
knobBounds.height -= 2 * bw;
knobSize -= 2 * bw;
if (showSliderHints) {
paintSliderHints(g, c, bounds, bw, knobSize);
}
paintSliderKnob(g, c, knobBounds);
g.translate(-bounds.x, -bounds.y);
}
protected void paintSliderHints(final Graphics2D g, final AbstractButton c, final Rectangle bounds, final int bw,
final int knobSize) {
int pad = 5;
int w = bounds.width - knobSize - 2 * bw - 2 * pad;
int y = (bounds.height - w) / 2;
if (c.isSelected()) {
g.setColor(getSliderColor(c));
PaintUtil.fillRoundRect(g, bounds.width - size - 1, 1, size, size, size);
g.setColor(getSliderBorderColor(c));
PaintUtil.paintLineBorder(g, bounds.width - size - 1, 1, size, size, size);
int x = bw + pad;
g.setColor(selectedForeground);
g.fillRect(x + (w - 1) / 2, y, 1, w);
} else {
int x = bw + knobSize + pad;
g.setColor(getForegroundColor(c, false, false));
PaintUtil.paintLineBorder(g, x, y, w, w, w);
}
}
protected void paintSliderKnob(final Graphics2D g, final AbstractButton c, final Rectangle bound) {
g.setColor(getSliderColor(c));
PaintUtil.fillRoundRect(g, 1, 1, size, size, size);
PaintUtil.fillRoundRect(g, bound.x, bound.y, bound.width, bound.height, bound.height);
g.setColor(getSliderBorderColor(c));
PaintUtil.paintLineBorder(g, 1, 1, size, size, size);
}
g.translate(-bounds.x, -bounds.y);
PaintUtil.paintLineBorder(g, bound.x, bound.y, bound.width, bound.height, bound.height);
}
@Override
protected Color getForeground(final AbstractButton button) {
if (button.isSelected() && button.isEnabled() && button.getForeground() instanceof UIResource
&& !ToggleButtonConstants.isSlider(button) && !ButtonConstants.isBorderlessVariant(button)) {
protected Color getForegroundColor(final AbstractButton b, final boolean defaultButton, final boolean enabled) {
if (b.isSelected() && enabled && b.getForeground() instanceof UIResource && !ToggleButtonConstants.isSlider(b)
&& !ButtonConstants.isBorderlessVariant(b)) {
return selectedForeground;
}
return super.getForeground(button);
return super.getForegroundColor(b, defaultButton, enabled);
}
protected Color getBackgroundColor(final AbstractButton b) {
boolean rollOver = b.isRolloverEnabled() && b.getModel().isRollover();
boolean clicked = b.getModel().isArmed();
boolean isSelected = b.isSelected();
if (b.isEnabled()) {
if (isSelected) return PropertyUtil.chooseColor(b.getBackground(), background);
if (clicked) {
return clickBackground;
} else if (rollOver) {
return hoverBackground;
} else {
if (b instanceof JToggleButton) {
return backgroundInactive;
} else {
return super.getBackgroundColor(b);
}
}
} else {
return inactiveBackground;
@Override
protected Color getBackgroundColor(final AbstractButton b, final boolean defaultButton, final boolean rollOver,
final boolean clicked, final boolean enabled) {
return getBackgroundColor(b, defaultButton, rollOver, clicked, enabled, b.getModel().isSelected());
}
protected Color getBackgroundColor(final AbstractButton b, final boolean defaultButton, final boolean rollOver,
final boolean clicked, final boolean enabled, final boolean selected) {
if (!enabled) return backgroundInactive;
if (selected) return selectedBackground;
return super.getBackgroundColor(b, defaultButton, rollOver, clicked, true);
}
@Override
@ -182,63 +240,44 @@ public class DarkToggleButtonUI extends DarkButtonUI implements ToggleButtonCons
}
protected Rectangle getSliderBounds(final JComponent c) {
Rectangle r = new Rectangle();
Insets ins = c.getInsets();
int x = ins.left;
int height = c.getHeight() - ins.bottom - ins.top;
int y = ins.top + (height - sliderSize.height) / 2;
rect.x = x;
rect.y = y;
rect.width = sliderSize.width;
rect.height = sliderSize.height;
r.x = x;
r.y = y;
r.width = sliderSize.width;
r.height = sliderSize.height;
if (!c.getComponentOrientation().isLeftToRight()) {
rect.x = c.getWidth() - ins.right - rect.x - rect.width;
r.x = c.getWidth() - ins.right - r.x - r.width;
}
return rect;
return r;
}
private String layoutSlider(final AbstractButton b, final FontMetrics fm, final int width, final int height) {
Insets i = b.getInsets();
Rectangle bounds = getSliderBounds(b);
@Override
public boolean contains(final JComponent c, final int x, final int y) {
if (!ToggleButtonConstants.isSlider(c)) return super.contains(c, x, y);
if (c instanceof JToggleButton) {
Rectangle bounds = getSliderBounds(c);
int arc = Math.min(bounds.width, bounds.height);
hitArea.setRoundRect(bounds.x, bounds.y, bounds.width, bounds.height, arc, arc);
}
return hitArea.contains(x, y);
}
viewRect.x = bounds.x + bounds.width + 2 * borderSize;
viewRect.width = width - (i.right + viewRect.x);
viewRect.y = i.top;
viewRect.height = height - (i.bottom + viewRect.y);
protected class DarkToggleButtonLayout extends DarkButtonLayout {
@Override
protected int getHorizontalAlignment(final AbstractButton b) {
if (ToggleButtonConstants.isSlider(b)) {
int horizontalPos = SwingConstants.LEFT;
if (!b.getComponentOrientation().isLeftToRight()) {
viewRect.x = bounds.x - viewRect.width - borderSize;
horizontalPos = SwingConstants.RIGHT;
}
textRect.x = textRect.y = textRect.width = textRect.height = 0;
iconRect.x = iconRect.y = iconRect.width = iconRect.height = 0;
// layout the text and icon
return SwingUtilities.layoutCompoundLabel(b, fm, b.getText(), b.getIcon(), b.getVerticalAlignment(),
horizontalPos, b.getVerticalTextPosition(), b.getHorizontalTextPosition(), viewRect, iconRect, textRect,
b.getText() == null ? 0 : b.getIconTextGap());
}
public Dimension getPreferredSize(final JComponent c) {
Dimension d = super.getPreferredSize(c);
if (ToggleButtonConstants.isSlider(c)) {
d.width += sliderSize.width + 2 * borderSize;
return horizontalPos;
}
return d;
}
@Override
public boolean contains(final JComponent c, final int x, final int y) {
if (!ToggleButtonConstants.isSlider(c)) return super.contains(c, x, y);
if ((hitArea.isEmpty()) && c instanceof JToggleButton) {
JToggleButton b = (JToggleButton) c;
layoutSlider(b, b.getFontMetrics(layoutDelegate.getFont()), b.getWidth(), b.getHeight());
return super.getHorizontalAlignment(b);
}
return hitArea.contains(x, y);
}
}

1
core/src/main/resources/com/github/weisj/darklaf/properties/ui/toggleButton.properties

@ -39,3 +39,4 @@ ToggleButton.inactiveFillColor = %widgetFill
ToggleButton.activeFillColor = %textForegroundInactive
ToggleButton.sliderSize = 35,17
ToggleButton.showSliderHints = false

1
theme/src/main/resources/com/github/weisj/darklaf/theme/high_contrast_dark/high_contrast_dark_ui.properties

@ -30,3 +30,4 @@ TabbedPane.selectedHoverBackground = %backgroundHover
ToolTip.paintShadow = false
RootPane.borderInsets = 1,1,1,1
ScrollBar.thumbAlpha = 100
ToggleButton.showSliderHints = true

1
theme/src/main/resources/com/github/weisj/darklaf/theme/high_contrast_light/high_contrast_light_ui.properties

@ -33,3 +33,4 @@ TabFrameTab.selectedForeground = %textSelectionForeground
TabFrameTab.hoverForeground = %textSelectionForeground
RootPane.borderInsets = 1,1,1,1
ScrollBar.thumbAlpha = 100
ToggleButton.showSliderHints = true

Loading…
Cancel
Save