Browse Source

Improved rendering for DropShadowBorder.

Adjusted dropshadow parameters for tooltips.
Made tooltip border hide the shadow on macOS.
Fixed PopupMenu flickering.
pull/127/head
weisj 5 years ago
parent
commit
44e7399ad6
  1. 83
      core/src/main/java/com/github/weisj/darklaf/components/border/DropShadowBorder.java
  2. 16
      core/src/main/java/com/github/weisj/darklaf/components/tooltip/ToolTipContext.java
  3. 6
      core/src/main/java/com/github/weisj/darklaf/ui/DarkPopupFactory.java
  4. 11
      core/src/main/java/com/github/weisj/darklaf/ui/popupmenu/DarkPopupMenuUI.java
  5. 24
      core/src/main/java/com/github/weisj/darklaf/ui/tooltip/DarkTooltipBorder.java
  6. 4
      core/src/main/java/com/github/weisj/darklaf/ui/tooltip/ToolTipUtil.java
  7. 1
      core/src/main/resources/com/github/weisj/darklaf/properties/platform/mac.properties
  8. 1
      core/src/main/resources/com/github/weisj/darklaf/properties/ui/toolTip.properties
  9. 12
      core/src/test/java/ui/toolTip/ToolTipDemo.java

83
core/src/main/java/com/github/weisj/darklaf/components/border/DropShadowBorder.java

@ -53,7 +53,7 @@ import java.util.Map;
* @author Jannis Weis
*/
public class DropShadowBorder implements Border, Serializable {
private static final Map<Integer, Map<Position, BufferedImage>> CACHE = new HashMap<>();
private static final Map<Integer, BufferedImage[]> CACHE = new HashMap<>();
private Color shadowColor;
private int shadowSize;
private float shadowOpacity;
@ -89,21 +89,14 @@ public class DropShadowBorder implements Border, Serializable {
}
public DropShadowBorder(final boolean showLeftShadow) {
this(Color.BLACK, 5, .5f, 12,
false, showLeftShadow, true, true);
}
/**
* {@inheritDoc}
*/
@Override
public void paintBorder(final Component c, final Graphics graphics,
final int x, final int y, final int width, final int height) {
/*
* 1) Get images for this border
* 2) Paint the images for each side of the border that should be painted
*/
Map<Position, BufferedImage> images = getImages((Graphics2D) graphics);
BufferedImage[] images = getImages((Graphics2D) graphics);
Graphics2D g2 = (Graphics2D) graphics.create();
@ -166,7 +159,7 @@ public class DropShadowBorder implements Border, Serializable {
}
g2.setRenderingHint(RenderingHints.KEY_INTERPOLATION,
RenderingHints.VALUE_INTERPOLATION_BILINEAR);
RenderingHints.VALUE_INTERPOLATION_BICUBIC);
g2.setRenderingHint(RenderingHints.KEY_RENDERING,
RenderingHints.VALUE_RENDER_SPEED);
@ -175,7 +168,7 @@ public class DropShadowBorder implements Border, Serializable {
Rectangle leftShadowRect =
new Rectangle(x, topLeftShadowPoint.y + shadowSize, shadowSize,
bottomLeftShadowPoint.y - topLeftShadowPoint.y - shadowSize);
g2.drawImage(images.get(Position.LEFT),
g2.drawImage(images[Position.LEFT.ordinal()],
leftShadowRect.x, leftShadowRect.y,
leftShadowRect.width, leftShadowRect.height, null);
}
@ -186,7 +179,7 @@ public class DropShadowBorder implements Border, Serializable {
new Rectangle(bottomLeftShadowPoint.x + shadowSize, y + height - shadowSize,
bottomRightShadowPoint.x - bottomLeftShadowPoint.x - shadowSize,
shadowSize);
g2.drawImage(images.get(Position.BOTTOM),
g2.drawImage(images[Position.BOTTOM.ordinal()],
bottomShadowRect.x, bottomShadowRect.y,
bottomShadowRect.width, bottomShadowRect.height, null);
}
@ -196,7 +189,7 @@ public class DropShadowBorder implements Border, Serializable {
Rectangle rightShadowRect =
new Rectangle(x + width - shadowSize, topRightShadowPoint.y + shadowSize, shadowSize,
bottomRightShadowPoint.y - topRightShadowPoint.y - shadowSize);
g2.drawImage(images.get(Position.RIGHT),
g2.drawImage(images[Position.RIGHT.ordinal()],
rightShadowRect.x, rightShadowRect.y,
rightShadowRect.width, rightShadowRect.height, null);
}
@ -206,26 +199,26 @@ public class DropShadowBorder implements Border, Serializable {
Rectangle topShadowRect =
new Rectangle(topLeftShadowPoint.x + shadowSize, y,
topRightShadowPoint.x - topLeftShadowPoint.x - shadowSize, shadowSize);
g2.drawImage(images.get(Position.TOP),
g2.drawImage(images[Position.TOP.ordinal()],
topShadowRect.x, topShadowRect.y,
topShadowRect.width, topShadowRect.height, null);
}
if (showLeftShadow || showTopShadow) {
assert topLeftShadowPoint != null;
g2.drawImage(images.get(Position.TOP_LEFT), topLeftShadowPoint.x, topLeftShadowPoint.y, null);
g2.drawImage(images[Position.TOP_LEFT.ordinal()], topLeftShadowPoint.x, topLeftShadowPoint.y, null);
}
if (showLeftShadow || showBottomShadow) {
assert bottomLeftShadowPoint != null;
g2.drawImage(images.get(Position.BOTTOM_LEFT), bottomLeftShadowPoint.x, bottomLeftShadowPoint.y, null);
g2.drawImage(images[Position.BOTTOM_LEFT.ordinal()], bottomLeftShadowPoint.x, bottomLeftShadowPoint.y, null);
}
if (showRightShadow || showBottomShadow) {
assert bottomRightShadowPoint != null;
g2.drawImage(images.get(Position.BOTTOM_RIGHT), bottomRightShadowPoint.x, bottomRightShadowPoint.y, null);
g2.drawImage(images[Position.BOTTOM_RIGHT.ordinal()], bottomRightShadowPoint.x, bottomRightShadowPoint.y, null);
}
if (showRightShadow || showTopShadow) {
assert topRightShadowPoint != null;
g2.drawImage(images.get(Position.TOP_RIGHT), topRightShadowPoint.x, topRightShadowPoint.y, null);
g2.drawImage(images[Position.TOP_RIGHT.ordinal()], topRightShadowPoint.x, topRightShadowPoint.y, null);
}
} finally {
g2.dispose();
@ -234,12 +227,12 @@ public class DropShadowBorder implements Border, Serializable {
@SuppressWarnings("SuspiciousNameCombination")
private Map<Position, BufferedImage> getImages(final Graphics2D g2) {
private BufferedImage[] getImages(final Graphics2D g2) {
//first, check to see if an image for this size has already been rendered
//if so, use the cache. Else, draw and save
Map<Position, BufferedImage> images = CACHE.get(getBorderHash(shadowSize, shadowOpacity, shadowColor));
BufferedImage[] images = CACHE.get(getBorderHash(shadowSize, shadowOpacity, shadowColor));
if (images == null) {
images = new HashMap<>();
images = new BufferedImage[Position.count()];
/*
* To draw a drop shadow, I have to:
@ -281,42 +274,42 @@ public class DropShadowBorder implements Border, Serializable {
int y = 1;
int w = shadowSize;
int h = shadowSize;
images.put(Position.TOP_LEFT, getSubImage(targetImage, x, y, w, h));
images[Position.TOP_LEFT.ordinal()] = getSubImage(targetImage, x, y, w, h);
x = 1;
y = h;
w = shadowSize;
h = 1;
images.put(Position.LEFT, getSubImage(targetImage, x, y, w, h));
images[Position.LEFT.ordinal()] = getSubImage(targetImage, x, y, w, h);
x = 1;
y = rectWidth;
w = shadowSize;
h = shadowSize;
images.put(Position.BOTTOM_LEFT, getSubImage(targetImage, x, y, w, h));
images[Position.BOTTOM_LEFT.ordinal()] = getSubImage(targetImage, x, y, w, h);
x = cornerSize + 1;
y = rectWidth;
w = 1;
h = shadowSize;
images.put(Position.BOTTOM, getSubImage(targetImage, x, y, w, h));
images[Position.BOTTOM.ordinal()] = getSubImage(targetImage, x, y, w, h);
x = rectWidth;
y = x;
w = shadowSize;
h = shadowSize;
images.put(Position.BOTTOM_RIGHT, getSubImage(targetImage, x, y, w, h));
images[Position.BOTTOM_RIGHT.ordinal()] = getSubImage(targetImage, x, y, w, h);
x = rectWidth;
y = cornerSize + 1;
w = shadowSize;
h = 1;
images.put(Position.RIGHT, getSubImage(targetImage, x, y, w, h));
images[Position.RIGHT.ordinal()] = getSubImage(targetImage, x, y, w, h);
x = rectWidth;
y = 1;
w = shadowSize;
h = shadowSize;
images.put(Position.TOP_RIGHT, getSubImage(targetImage, x, y, w, h));
images[Position.TOP_RIGHT.ordinal()] = getSubImage(targetImage, x, y, w, h);
x = shadowSize;
y = 1;
w = 1;
h = shadowSize;
images.put(Position.TOP, getSubImage(targetImage, x, y, w, h));
images[Position.TOP.ordinal()] = getSubImage(targetImage, x, y, w, h);
image.flush();
CACHE.put(getBorderHash(shadowSize, shadowOpacity, shadowColor), images);
@ -371,69 +364,81 @@ public class DropShadowBorder implements Border, Serializable {
return showTopShadow;
}
public void setShowTopShadow(final boolean showTopShadow) {
public DropShadowBorder setShowTopShadow(final boolean showTopShadow) {
this.showTopShadow = showTopShadow;
return this;
}
public boolean isShowLeftShadow() {
return showLeftShadow;
}
public void setShowLeftShadow(final boolean showLeftShadow) {
public DropShadowBorder setShowLeftShadow(final boolean showLeftShadow) {
this.showLeftShadow = showLeftShadow;
return this;
}
public boolean isShowRightShadow() {
return showRightShadow;
}
public void setShowRightShadow(final boolean showRightShadow) {
public DropShadowBorder setShowRightShadow(final boolean showRightShadow) {
this.showRightShadow = showRightShadow;
return this;
}
public boolean isShowBottomShadow() {
return showBottomShadow;
}
public void setShowBottomShadow(final boolean showBottomShadow) {
public DropShadowBorder setShowBottomShadow(final boolean showBottomShadow) {
this.showBottomShadow = showBottomShadow;
return this;
}
public int getShadowSize() {
return shadowSize;
}
public void setShadowSize(final int shadowSize) {
public DropShadowBorder setShadowSize(final int shadowSize) {
this.shadowSize = shadowSize;
return this;
}
public Color getShadowColor() {
return shadowColor;
}
public void setShadowColor(final Color shadowColor) {
public DropShadowBorder setShadowColor(final Color shadowColor) {
this.shadowColor = shadowColor;
return this;
}
public float getShadowOpacity() {
return shadowOpacity;
}
public void setShadowOpacity(final float shadowOpacity) {
public DropShadowBorder setShadowOpacity(final float shadowOpacity) {
this.shadowOpacity = shadowOpacity;
return this;
}
public int getCornerSize() {
return cornerSize;
}
public void setCornerSize(final int cornerSize) {
public DropShadowBorder setCornerSize(final int cornerSize) {
this.cornerSize = cornerSize;
return this;
}
private enum Position {
TOP, TOP_LEFT, LEFT, BOTTOM_LEFT,
BOTTOM, BOTTOM_RIGHT, RIGHT, TOP_RIGHT
BOTTOM, BOTTOM_RIGHT, RIGHT, TOP_RIGHT;
static int count() {
return 8;
}
}
}

16
core/src/main/java/com/github/weisj/darklaf/components/tooltip/ToolTipContext.java

@ -79,6 +79,7 @@ public class ToolTipContext {
private Insets insets;
private ToolTipStyle style;
private boolean ignoreBorder;
private boolean bestFit;
/**
* Create a new tooltip context to ease the creation of custom tooltips.
@ -179,6 +180,10 @@ public class ToolTipContext {
return ignoreBorder;
}
public boolean isBestFit() {
return bestFit;
}
/**
* Sets the alignment with respect to the supplied alignment rectangle. When using {@link Alignment#CENTER} one can
* additionally supply an alignment using {@link #setCenterAlignment(Alignment)}. The tooltip will either be aligned
@ -424,6 +429,17 @@ public class ToolTipContext {
return this;
}
/**
* Sets whether the tooltip should try its best to fit inside the window/screen.
*
* @param bestFit true if best fit adjustments should be made.
* @return this.
*/
public ToolTipContext setUseBestFit(final boolean bestFit) {
this.bestFit = bestFit;
return this;
}
/**
* Calculates the tooltip location.
*

6
core/src/main/java/com/github/weisj/darklaf/ui/DarkPopupFactory.java

@ -24,6 +24,7 @@
package com.github.weisj.darklaf.ui;
import com.github.weisj.darklaf.platform.Decorations;
import com.github.weisj.darklaf.ui.popupmenu.DarkPopupMenuUI;
import com.github.weisj.darklaf.ui.rootpane.DarkRootPaneUI;
import com.github.weisj.darklaf.ui.tooltip.DarkTooltipBorder;
@ -40,6 +41,7 @@ public class DarkPopupFactory extends PopupFactory {
boolean isLightWeight = popup.getClass().getSimpleName().endsWith("LightWeightPopup");
boolean isBalloonTooltip = contents instanceof JToolTip
&& ((JToolTip) contents).getBorder() instanceof DarkTooltipBorder;
boolean isPopupMenu = contents instanceof JPopupMenu;
if (isMediumWeight || isLightWeight) {
if (isBalloonTooltip) {
// null owner forces a heavyweight popup.
@ -56,7 +58,6 @@ public class DarkPopupFactory extends PopupFactory {
// That is why we set window background explicitly.
Window window = SwingUtilities.getWindowAncestor(contents);
if (window != null) {
window.setBackground(UIManager.getColor("PopupMenu.translucentBackground"));
boolean install = true;
if (window instanceof RootPaneContainer) {
JRootPane rootPane = ((RootPaneContainer) window).getRootPane();
@ -68,7 +69,8 @@ public class DarkPopupFactory extends PopupFactory {
} else {
Decorations.uninstallPopupWindow(window);
}
if (isBalloonTooltip) {
if (isBalloonTooltip || isPopupMenu) {
if (isPopupMenu) ((JComponent) contents).putClientProperty(DarkPopupMenuUI.KEY_MAKE_VISIBLE, true);
window.setOpacity(0.0f);
}
}

11
core/src/main/java/com/github/weisj/darklaf/ui/popupmenu/DarkPopupMenuUI.java

@ -48,6 +48,7 @@ public class DarkPopupMenuUI extends BasicPopupMenuUI {
public static final String KEY_DO_NOT_CANCEL_POPUP = "doNotCancelPopup";
public static final String KEY_DO_NOT_CANCEL_ON_SCROLL = "doNotCancelOnScroll";
public static final String KEY_MAKE_VISIBLE = "PopupFactory.makeVisible";
public static final StringBufferWrapper HIDE_POPUP_VALUE = new StringBufferWrapper(new StringBuffer(
"doNotCancelPopup"));
public static final String KEY_DEFAULT_LIGHTWEIGHT_POPUPS = "PopupMenu.defaultLightWeightPopups";
@ -57,6 +58,16 @@ public class DarkPopupMenuUI extends BasicPopupMenuUI {
return new DarkPopupMenuUI();
}
@Override
public void paint(final Graphics g, final JComponent c) {
Window window = SwingUtilities.getWindowAncestor(c);
if (window != null
&& Boolean.TRUE.equals(popupMenu.getClientProperty(KEY_MAKE_VISIBLE))) {
popupMenu.putClientProperty(KEY_MAKE_VISIBLE, false);
window.setOpacity(1);
}
super.paint(g, c);
}
public static List<JPopupMenu> getPopups() {
MenuSelectionManager msm = MenuSelectionManager.defaultManager();

24
core/src/main/java/com/github/weisj/darklaf/ui/tooltip/DarkTooltipBorder.java

@ -41,10 +41,11 @@ import java.awt.geom.Rectangle2D;
*/
public class DarkTooltipBorder implements Border {
private final DropShadowBorder shadowBorder = new DropShadowBorder(Color.BLACK,
10, 0.2f, 10,
false, true,
true, true);
private static final int shadowSize = 12;
private static final int cornerSize = 2 * shadowSize;
private static final float opacity = 0.1f;
private final DropShadowBorder shadowBorder = new DropShadowBorder(Color.BLACK, shadowSize, opacity, cornerSize,
false, true, true, true);
private final BubbleBorder bubbleBorder;
public DarkTooltipBorder() {
@ -102,6 +103,15 @@ public class DarkTooltipBorder implements Border {
Area bubbleArea = bubbleBorder.getInnerArea(x + ins.left, y + ins.top,
width - ins.left - ins.right,
height - ins.top - ins.bottom);
if (UIManager.getBoolean("ToolTip.paintShadow")) {
paintShadow(c, g, x, y, width, height, bubbleArea);
}
bubbleBorder.paintBorder(g, bubbleArea);
context.restore();
}
public void paintShadow(final Component c, final Graphics g, final int x, final int y,
final int width, final int height, final Area bubbleArea) {
Shape oldClip = g.getClip();
Area clip = new Area(new Rectangle2D.Double(x, y, width, height));
clip.subtract(bubbleArea);
@ -110,14 +120,12 @@ public class DarkTooltipBorder implements Border {
int off = 0;
Alignment pointerSide = bubbleBorder.getPointerSide();
if (pointerSide == Alignment.NORTH
|| pointerSide == Alignment.NORTH_EAST
|| pointerSide == Alignment.NORTH_WEST) {
|| pointerSide == Alignment.NORTH_EAST
|| pointerSide == Alignment.NORTH_WEST) {
off = bubbleBorder.getPointerSize();
}
shadowBorder.paintBorder(c, g, x + bw, y + bw + off, width - 2 * bw, height - 2 * bw - off);
g.setClip(oldClip);
bubbleBorder.paintBorder(g, bubbleArea);
context.restore();
}
@Override

4
core/src/main/java/com/github/weisj/darklaf/ui/tooltip/ToolTipUtil.java

@ -58,7 +58,9 @@ public class ToolTipUtil {
}
protected static Point getBestPositionMatch(final ToolTipContext context, final Point p) {
// For now adjustments are only made when the alignment is in the center and no mouse coordinates are used.
if (!context.isBestFit()) {
return context.getToolTipLocation(p, null);
}
Rectangle screenBounds = getScreenBounds(context.getTarget(), p);
Rectangle windowBounds = DarkUIUtil.getWindow(context.getTarget()).getBounds();
Rectangle tooltipBounds = new Rectangle();

1
core/src/main/resources/com/github/weisj/darklaf/properties/platform/mac.properties

@ -21,3 +21,4 @@ FileChooser.listViewWindowsStyle = false
CheckBox.borderInsets = 2,2,2,2
RadioButton.borderInsets = 2,2,2,2
PopupMenu.defaultLightWeightPopups = false
ToolTip.paintShadow = false

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

@ -28,3 +28,4 @@ ToolTip.backgroundInactive = %backgroundToolTipInactive
ToolTip.borderColor = %borderTertiary
ToolTip.border = com.github.weisj.darklaf.ui.tooltip.DarkDefaultToolTipBorder
ToolTip.borderShadowColor = %shadow
ToolTip.paintShadow = true

12
core/src/test/java/ui/toolTip/ToolTipDemo.java

@ -49,12 +49,20 @@ public class ToolTipDemo implements ComponentDemo {
button.setToolTipText("This is the ToolTip demo text!");
button.putClientProperty(DarkTooltipUI.KEY_STYLE, DarkTooltipUI.VARIANT_BALLOON);
JPanel controlPanel = panel.addControls();
JPanel controlPanel = panel.addControls(3);
controlPanel.add(new JCheckBox("Align inside") {{
setSelected(context.isAlignInside());
addActionListener(e -> context.setAlignInside(isSelected()));
}}, "span");
}});
controlPanel.add(new JCheckBox("Ignore Border") {{
setSelected(context.isIgnoreBorder());
addActionListener(e -> context.setIgnoreBorder(isSelected()));
}});
controlPanel.add(new JCheckBox("Use best fit") {{
setSelected(context.isBestFit());
addActionListener(e -> context.setUseBestFit(isSelected()));
}});
controlPanel = panel.addControls();
controlPanel.add(new JLabel("Tooltip Style:", JLabel.RIGHT));

Loading…
Cancel
Save