Browse Source

Added option to use ToolTipContext via client properties. If non is set and the style of the tooltip is set to BALLOON then the a default context is used. The default style is PLAIN_BALLOON which behaves as the previous BALLOON style.

pull/97/head
weisj 5 years ago
parent
commit
494bd7dcdc
  1. 171
      core/src/main/java/com/github/weisj/darklaf/components/tooltip/DarkToolTip.java
  2. 279
      core/src/main/java/com/github/weisj/darklaf/components/tooltip/ToolTipContext.java
  3. 36
      core/src/main/java/com/github/weisj/darklaf/components/tooltip/ToolTipListener.java
  4. 1
      core/src/main/java/com/github/weisj/darklaf/components/tooltip/ToolTipStyle.java
  5. 9
      core/src/main/java/com/github/weisj/darklaf/ui/BasicTransferable.java
  6. 6
      core/src/main/java/com/github/weisj/darklaf/ui/DarkPopupFactory.java
  7. 19
      core/src/main/java/com/github/weisj/darklaf/ui/tooltip/DarkTooltipBorder.java
  8. 65
      core/src/main/java/com/github/weisj/darklaf/ui/tooltip/DarkTooltipUI.java
  9. 179
      core/src/main/java/com/github/weisj/darklaf/ui/tooltip/ToolTipUtil.java
  10. 1
      core/src/test/java/ui/dialog/DialogDemo.java
  11. 10
      core/src/test/java/ui/toolTip/ToolTipDemo.java

171
core/src/main/java/com/github/weisj/darklaf/components/tooltip/DarkToolTip.java

@ -1,171 +0,0 @@
/*
* 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.tooltip;
import com.github.weisj.darklaf.ui.tooltip.DarkTooltipBorder;
import com.github.weisj.darklaf.ui.tooltip.DarkTooltipUI;
import com.github.weisj.darklaf.util.Alignment;
import com.github.weisj.darklaf.util.Animator;
import com.github.weisj.darklaf.util.GraphicsContext;
import javax.swing.*;
import javax.swing.border.Border;
import java.awt.*;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.Objects;
public class DarkToolTip extends JToolTip implements PropertyChangeListener {
private static final AlphaComposite COMPOSITE = AlphaComposite.getInstance(AlphaComposite.SRC_OVER);
private static final float MAX_ALPHA = 1.0f;
private final Animator fadeAnimator;
private float alpha = 0;
public DarkToolTip(final Alignment alignment) {
setAlignment(alignment);
setOpaque(false);
fadeAnimator = new FadeInAnimator();
addPropertyChangeListener(this);
}
@Override
public void addNotify() {
alpha = 0;
setVisible(true);
notifyToolTipListeners(ToolTipEvent.SHOWN);
fadeAnimator.reset();
fadeAnimator.resume();
super.addNotify();
}
public void setAlignment(final Alignment alignment) {
putClientProperty(DarkTooltipUI.KEY_POINTER_LOCATION, alignment);
}
public void notifyToolTipListeners(final ToolTipEvent event) {
for (ToolTipListener listener : listenerList.getListeners(ToolTipListener.class)) {
if (listener != null) {
switch (event) {
case TEXT:
listener.textChanged(this);
break;
case SHOWN:
listener.toolTipShown(this);
break;
case HIDDEN:
listener.toolTipHidden(this);
break;
}
}
}
}
public void setInsets(final Insets insets) {
putClientProperty(DarkTooltipUI.KEY_INSETS, insets);
}
public void addToolTipListener(final ToolTipListener listener) {
listenerList.add(ToolTipListener.class, listener);
}
public void removeToolTipListener(final ToolTipListener listener) {
listenerList.remove(ToolTipListener.class, listener);
}
@Override
public void paint(final Graphics g) {
if (alpha == 0) return;
GraphicsContext config = new GraphicsContext(g);
if (alpha != MAX_ALPHA) {
((Graphics2D) g).setComposite(COMPOSITE.derive(alpha));
}
super.paint(g);
config.restore();
}
@Override
public void setBorder(final Border border) {
if (!(border instanceof DarkTooltipBorder)) return;
super.setBorder(border);
}
@Override
public void removeNotify() {
super.removeNotify();
notifyToolTipListeners(ToolTipEvent.HIDDEN);
alpha = 0;
}
@Override
public String getTipText() {
String text = super.getTipText();
if (text == null && getComponent() != null) {
return getComponent().getToolTipText();
}
return text;
}
@Override
public void propertyChange(final PropertyChangeEvent evt) {
if (DarkTooltipUI.TIP_TEXT_PROPERTY.equals(evt.getPropertyName())) {
setPreferredSize(getUI().getPreferredSize(this));
if (!Objects.equals(evt.getNewValue(), evt.getOldValue())) {
notifyToolTipListeners(ToolTipEvent.TEXT);
}
}
}
public void setStyle(final ToolTipStyle style) {
putClientProperty(DarkTooltipUI.KEY_STYLE, style);
}
private enum ToolTipEvent {
TEXT,
SHOWN,
HIDDEN
}
protected class FadeInAnimator extends Animator {
private static final int DELAY_FRAMES = 6;
private static final int FADEIN_FRAMES_COUNT = DELAY_FRAMES + 10;
public FadeInAnimator() {
super("Tooltip fadein", FADEIN_FRAMES_COUNT, FADEIN_FRAMES_COUNT * 15, false);
}
@Override
public void paintNow(final int frame, final int totalFrames, final int cycle) {
alpha = ((float) frame * MAX_ALPHA) / totalFrames;
paintImmediately(0, 0, getWidth(), getHeight());
}
@Override
protected void paintCycleEnd() {
alpha = MAX_ALPHA;
paintImmediately(0, 0, getWidth(), getHeight());
}
}
}

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

@ -25,6 +25,7 @@ package com.github.weisj.darklaf.components.tooltip;
import com.github.weisj.darklaf.components.alignment.AlignmentStrategy;
import com.github.weisj.darklaf.ui.tooltip.DarkTooltipBorder;
import com.github.weisj.darklaf.ui.tooltip.DarkTooltipUI;
import com.github.weisj.darklaf.util.Alignment;
import com.github.weisj.darklaf.util.DarkUIUtil;
@ -34,26 +35,12 @@ import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.geom.Area;
import java.util.Objects;
import java.util.function.Function;
public class ToolTipContext implements ToolTipListener {
public class ToolTipContext {
private final Insets calcInsets = new Insets(0, 0, 0, 0);
private final JComponent c;
private DarkToolTip toolTip;
private Alignment alignment;
private Alignment centerAlignment;
private boolean alignInside;
private boolean updatePosition;
private AlignmentStrategy alignmentStrategy;
private Function<MouseEvent, Rectangle> toolTipRectSupplier;
private boolean applyInsetsToRect;
private Point lastPos;
private Rectangle lastRect;
private boolean valid;
private Area hotSpotArea;
private boolean hideOnExit;
private JComponent target;
private final MouseListener mouseListener = new MouseAdapter() {
@Override
public void mouseExited(final MouseEvent e) {
@ -73,44 +60,55 @@ public class ToolTipContext implements ToolTipListener {
ToolTipManager.sharedInstance().mousePressed(null);
}
} else {
if (!c.contains(e.getPoint())) {
if (target != null && !target.contains(e.getPoint())) {
ToolTipManager.sharedInstance().mousePressed(null);
}
}
}
};
private Alignment alignment;
private Alignment centerAlignment;
private boolean alignInside;
private AlignmentStrategy alignmentStrategy;
private Function<MouseEvent, Rectangle> toolTipRectSupplier;
private boolean applyInsetsToRect;
private Area hotSpotArea;
private boolean hideOnExit;
private JToolTip toolTip;
private Insets insets;
private ToolTipStyle style;
/**
* Create a new tooltip context to ease the creation of custom tooltips.
*
* @param c the component which the tooltip belongs to.
*/
public ToolTipContext() {
this(null, null, null, null, true, null);
}
public ToolTipContext(final JComponent c) {
this(c, null, null, null, true, null);
/**
* Create a new tooltip context to ease the creation of custom tooltips.
*
* @param target the component which the tooltip belongs to.
*/
public ToolTipContext(final JComponent target) {
this(target, null, null, null, true, null);
}
/**
* Create a new tooltip context to ease the creation of custom tooltips.
*
* @param c the component which the tooltip belongs to.
* @param target the component which the tooltip belongs to.
* @param alignment {@link #setAlignment(Alignment)}
* @param centerAlignment {@link #setCenterAlignment(Alignment)}
* @param alignmentStrategy {@link #setAlignmentStrategy(AlignmentStrategy)}
* @param alignInside {@link #setAlignInside(boolean)}
* @param toolTipRectSupplier {@link #setToolTipRectSupplier(Function)}
*/
public ToolTipContext(final JComponent c, final Alignment alignment, final Alignment centerAlignment,
public ToolTipContext(final JComponent target, final Alignment alignment, final Alignment centerAlignment,
final AlignmentStrategy alignmentStrategy,
final boolean alignInside, final Function<MouseEvent, Rectangle> toolTipRectSupplier) {
if (c == null) {
throw new IllegalArgumentException("Component is null");
}
this.c = c;
valid = false;
this.target = target;
setToolTipStyle(ToolTipStyle.BALLOON);
setUpdatePosition(false);
setHideOnExit(false);
@ -133,7 +131,6 @@ public class ToolTipContext implements ToolTipListener {
if (style == null) {
this.style = ToolTipStyle.BALLOON;
}
updateToolTip();
return this;
}
@ -147,26 +144,18 @@ public class ToolTipContext implements ToolTipListener {
* @return this
*/
public ToolTipContext setUpdatePosition(final boolean updatePosition) {
this.updatePosition = updatePosition;
return this;
}
/**
* Sets whether the tooltip should be closed if the mouse has left the area set by {@link #setInsideArea(Area)}.
* <p>
* Default is false.
* Create a new tooltip context to ease the creation of custom tooltips.
*
* @param hideOnExit true if tooltip should hide.
* @return this.
* @param target the component which the tooltip belongs to.
* @param alignment {@link #setAlignment(Alignment)}
*/
public ToolTipContext setHideOnExit(final boolean hideOnExit) {
this.hideOnExit = hideOnExit;
if (hideOnExit) {
c.addMouseListener(mouseListener);
} else {
c.removeMouseListener(mouseListener);
}
return this;
public ToolTipContext(final JComponent target, final Alignment alignment) {
this(target, alignment, null, null, true, null);
}
/**
@ -198,7 +187,6 @@ public class ToolTipContext implements ToolTipListener {
if (alignment == null) {
this.alignment = Alignment.CENTER;
}
updateToolTip();
return this;
}
@ -216,7 +204,6 @@ public class ToolTipContext implements ToolTipListener {
if (centerAlignment == null) {
this.centerAlignment = Alignment.NORTH;
}
updateToolTip();
return this;
}
@ -238,120 +225,131 @@ public class ToolTipContext implements ToolTipListener {
return this;
}
/**
* Set the supplier for the rectangle which is used to calculate the location of the tooltip. The coordinates should
* be relative to the components origin.
* <p>
* Default will be the component bounding rectangle.
*
* @param toolTipRectSupplier rectangle supplier method.
* @return this
*/
public ToolTipContext setToolTipRectSupplier(final Function<MouseEvent, Rectangle> toolTipRectSupplier) {
this.toolTipRectSupplier = toolTipRectSupplier;
if (toolTipRectSupplier == null) {
this.toolTipRectSupplier = e -> new Rectangle(0, 0, c.getWidth(), c.getHeight());
}
return this;
}
private void updateToolTip() {
if (toolTip != null) {
toolTip.setAlignment(alignment == Alignment.CENTER
? centerAlignment.opposite()
: alignInside ? alignment : alignment.opposite());
toolTip.setInsets(insets);
toolTip.setStyle(style);
}
}
/**
* Create a new tooltip context to ease the creation of custom tooltips.
*
* @param c the component which the tooltip belongs to.
* @param alignment {@link #setAlignment(Alignment)}
*/
public ToolTipContext(final JComponent c, final Alignment alignment) {
this(c, alignment, null, null, true, null);
}
/**
* Create a new tooltip context to ease the creation of custom tooltips.
*
* @param c the component which the tooltip belongs to.
* @param target the component which the tooltip belongs to.
* @param alignment {@link #setAlignment(Alignment)}
* @param centerAlignment {@link #setCenterAlignment(Alignment)}
*/
public ToolTipContext(final JComponent c, final Alignment alignment, final Alignment centerAlignment) {
this(c, alignment, centerAlignment, null, true, null);
public ToolTipContext(final JComponent target, final Alignment alignment, final Alignment centerAlignment) {
this(target, alignment, centerAlignment, null, true, null);
}
/**
* Create a new tooltip context to ease the creation of custom tooltips.
*
* @param c the component which the tooltip belongs to.
* @param target the component which the tooltip belongs to.
* @param alignment {@link #setAlignment(Alignment)}
* @param alignInside {@link #setAlignInside(boolean)}
*/
public ToolTipContext(final JComponent c, final Alignment alignment, final boolean alignInside) {
this(c, alignment, null, null, alignInside, null);
public ToolTipContext(final JComponent target, final Alignment alignment, final boolean alignInside) {
this(target, alignment, null, null, alignInside, null);
}
/**
* Create a new tooltip context to ease the creation of custom tooltips.
*
* @param c the component which the tooltip belongs to.
* @param target the component which the tooltip belongs to.
* @param alignment {@link #setAlignment(Alignment)}
* @param centerAlignment {@link #setCenterAlignment(Alignment)}
* @param alignInside {@link #setAlignInside(boolean)}
*/
public ToolTipContext(final JComponent c, final Alignment alignment, final Alignment centerAlignment,
public ToolTipContext(final JComponent target, final Alignment alignment, final Alignment centerAlignment,
final boolean alignInside) {
this(c, alignment, centerAlignment, null, alignInside, null);
this(target, alignment, centerAlignment, null, alignInside, null);
}
/**
* Create a new tooltip context to ease the creation of custom tooltips.
*
* @param c the component which the tooltip belongs to.
* @param target the component which the tooltip belongs to.
* @param alignment {@link #setAlignment(Alignment)}
* @param alignmentStrategy {@link #setAlignmentStrategy(AlignmentStrategy)}
*/
public ToolTipContext(final JComponent c, final Alignment alignment, final AlignmentStrategy alignmentStrategy) {
this(c, alignment, null, alignmentStrategy, true, null);
public ToolTipContext(final JComponent target, final Alignment alignment, final AlignmentStrategy alignmentStrategy) {
this(target, alignment, null, alignmentStrategy, true, null);
}
/**
* Create a new tooltip context to ease the creation of custom tooltips.
*
* @param c the component which the tooltip belongs to.
* @param target the component which the tooltip belongs to.
* @param alignment {@link #setAlignment(Alignment)}
* @param centerAlignment {@link #setCenterAlignment(Alignment)}
* @param alignmentStrategy {@link #setAlignmentStrategy(AlignmentStrategy)}
*/
public ToolTipContext(final JComponent c, final Alignment alignment, final Alignment centerAlignment,
public ToolTipContext(final JComponent target, final Alignment alignment, final Alignment centerAlignment,
final AlignmentStrategy alignmentStrategy) {
this(c, alignment, centerAlignment, alignmentStrategy, true, null);
this(target, alignment, centerAlignment, alignmentStrategy, true, null);
}
/**
* Create a new tooltip context to ease the creation of custom tooltips.
*
* @param c the component which the tooltip belongs to.
* @param target the component which the tooltip belongs to.
* @param alignment {@link #setAlignment(Alignment)}
* @param alignmentStrategy {@link #setAlignmentStrategy(AlignmentStrategy)}
* @param alignInside {@link #setAlignInside(boolean)}
*/
public ToolTipContext(final JComponent c, final Alignment alignment, final AlignmentStrategy alignmentStrategy,
public ToolTipContext(final JComponent target, final Alignment alignment, final AlignmentStrategy alignmentStrategy,
final boolean alignInside) {
this(c, alignment, null, alignmentStrategy, alignInside, null);
this(target, alignment, null, alignmentStrategy, alignInside, null);
}
/**
* Sets whether the tooltip should be closed if the mouse has left the area set by {@link #setInsideArea(Area)}.
* <p>
* Default is false.
*
* @param hideOnExit true if tooltip should hide.
* @return this.
*/
public ToolTipContext setHideOnExit(final boolean hideOnExit) {
this.hideOnExit = hideOnExit;
if (target != null) {
if (hideOnExit) {
target.addMouseListener(mouseListener);
} else {
target.removeMouseListener(mouseListener);
}
}
return this;
}
/**
* Set the supplier for the rectangle which is used to calculate the location of the tooltip. The coordinates should
* be relative to the components origin.
* <p>
* Default will be the component bounding rectangle.
*
* @param toolTipRectSupplier rectangle supplier method.
* @return this
*/
public ToolTipContext setToolTipRectSupplier(final Function<MouseEvent, Rectangle> toolTipRectSupplier) {
this.toolTipRectSupplier = toolTipRectSupplier;
if (toolTipRectSupplier == null) {
this.toolTipRectSupplier = e -> new Rectangle(0, 0, target.getWidth(), target.getHeight());
}
return this;
}
public void updateToolTip() {
if (toolTip != null) {
toolTip.putClientProperty(DarkTooltipUI.KEY_POINTER_LOCATION,
alignment == Alignment.CENTER
? centerAlignment.opposite()
: alignInside ? alignment : alignment.opposite());
toolTip.putClientProperty(DarkTooltipUI.KEY_INSETS, insets);
toolTip.putClientProperty(DarkTooltipUI.KEY_STYLE, style);
toolTip.doLayout();
}
}
/**
@ -400,30 +398,32 @@ public class ToolTipContext implements ToolTipListener {
*/
public ToolTipContext setToolTipInsets(final Insets insets) {
this.insets = insets;
updateToolTip();
return this;
}
public Point getToolTipLocation(final MouseEvent event) {
Point mp = SwingUtilities.convertPoint((Component) event.getSource(), event.getPoint(), target);
return getToolTipLocation(mp, event);
}
/**
* Calculates the tooltip location.
*
* @param event the mouse event.
* @param mp the mouse position in the target component coordinate space.
* @param mouseEvent the mouse event.
* @return the tooltip location.
* @see JComponent#getToolTipLocation(MouseEvent)
*/
public Point getToolTipLocation(final MouseEvent event) {
public Point getToolTipLocation(final Point mp, final MouseEvent mouseEvent) {
if (target == null) return null;
updateToolTip();
MouseEvent event = processEvent(mouseEvent, mp);
Rectangle rect = toolTipRectSupplier.apply(event);
if (applyInsetsToRect) {
DarkUIUtil.applyInsets(rect, c.getInsets(calcInsets));
}
if (valid && !updatePosition
&& lastPos != null
&& !Objects.equals(rect, lastRect)) {
return lastPos;
DarkUIUtil.applyInsets(rect, target.getInsets(calcInsets));
}
getToolTip().setTipText(c.getToolTipText(event));
getToolTip().setTipText(target.getToolTipText(event));
Dimension dim = getContentSize();
Point mp = SwingUtilities.convertPoint((Component) event.getSource(), event.getPoint(), c);
Rectangle mRect = new Rectangle(mp.x, mp.y, 1, 1);
Point compPoint;
Point mousePoint;
@ -437,31 +437,38 @@ public class ToolTipContext implements ToolTipListener {
: alignOutside(dim, mRect);
}
lastPos = alignmentStrategy.align(compPoint, mousePoint);
lastRect = rect;
valid = true;
return lastPos;
return alignmentStrategy.align(compPoint, mousePoint);
}
private MouseEvent processEvent(final MouseEvent mouseEvent, final Point mp) {
if (mouseEvent != null) return mouseEvent;
return new MouseEvent(target, MouseEvent.MOUSE_MOVED, System.currentTimeMillis(), 0, mp.x, mp.y,
0, false, 0);
}
public JComponent getTarget() {
return target;
}
public void setTarget(final JComponent target) {
this.target = target;
}
/**
* Get the tooltip.
*
* @see JComponent#createToolTip()
* @return the tooltip.
* @see JComponent#createToolTip()
*/
public JToolTip getToolTip() {
if (toolTip == null) {
toolTip = new DarkToolTip(alignment);
toolTip.addToolTipListener(this);
toolTip.setComponent(this.c);
updateToolTip();
setToolTip(new JToolTip());
}
return toolTip;
}
public void updateToolTipUI() {
if (toolTip != null) toolTip.updateUI();
updateToolTip();
}
private Dimension getContentSize() {
@ -509,24 +516,6 @@ public class ToolTipContext implements ToolTipListener {
setHideOnExit(false);
}
@Override
public void toolTipShown(final JToolTip toolTip) {
}
@Override
public void toolTipHidden(final JToolTip toolTip) {
if (toolTip == this.toolTip) {
valid = false;
}
}
@Override
public void textChanged(final JToolTip toolTip) {
if (toolTip == this.toolTip) {
valid = false;
}
}
public Alignment getAlignment() {
return alignment;
}
@ -542,4 +531,12 @@ public class ToolTipContext implements ToolTipListener {
public boolean isAlignInside() {
return alignInside;
}
public void setToolTip(final JToolTip toolTip) {
if (toolTip == null) return;
this.toolTip = toolTip;
if (this.target != toolTip.getComponent()) {
this.toolTip.setComponent(this.target);
}
}
}

36
core/src/main/java/com/github/weisj/darklaf/components/tooltip/ToolTipListener.java

@ -1,36 +0,0 @@
/*
* 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.tooltip;
import javax.swing.*;
import java.util.EventListener;
public interface ToolTipListener extends EventListener {
void toolTipShown(JToolTip toolTip);
void toolTipHidden(JToolTip toolTip);
void textChanged(JToolTip toolTip);
}

1
core/src/main/java/com/github/weisj/darklaf/components/tooltip/ToolTipStyle.java

@ -25,5 +25,6 @@ package com.github.weisj.darklaf.components.tooltip;
public enum ToolTipStyle {
BALLOON,
PLAIN_BALLOON,
PLAIN,
}

9
core/src/main/java/com/github/weisj/darklaf/ui/BasicTransferable.java

@ -28,11 +28,8 @@ import javax.swing.plaf.UIResource;
import java.awt.datatransfer.DataFlavor;
import java.awt.datatransfer.Transferable;
import java.awt.datatransfer.UnsupportedFlavorException;
import java.io.IOException;
import java.io.InputStream;
import java.io.Reader;
import java.io.StringBufferInputStream;
import java.io.StringReader;
import java.io.*;
import java.util.logging.Logger;
/**
* A transferable implementation for the default data transfer of some Swing components.
@ -63,7 +60,7 @@ public class BasicTransferable implements Transferable, UIResource {
stringFlavors[1] = DataFlavor.stringFlavor;
} catch (ClassNotFoundException cle) {
System.err.println("error initializing javax.swing.plaf.basic.BasicTranserable");
Logger.getGlobal().severe("error initializing javax.swing.plaf.basic.BasicTranserable");
}
}

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

@ -36,12 +36,14 @@ public class DarkPopupFactory extends PopupFactory {
public Popup getPopup(final Component owner, final Component contents,
final int x, final int y) throws IllegalArgumentException {
Popup popup = super.getPopup(owner, contents, x, y);
if (popup.getClass().getSimpleName().endsWith("MediumWeightPopup")) {
boolean isMediumWeight = popup.getClass().getSimpleName().endsWith("MediumWeightPopup");
boolean isLightWeight = popup.getClass().getSimpleName().endsWith("LightWeightPopup");
if (isMediumWeight || isLightWeight) {
if (contents instanceof JToolTip
&& ((JToolTip) contents).getBorder() instanceof DarkTooltipBorder) {
// null owner forces a heavyweight popup.
popup = super.getPopup(null, contents, x, y);
} else {
} else if (isMediumWeight) {
JRootPane rootPane = SwingUtilities.getRootPane(contents);
// Prevents decorations from being reinstalled.
if (rootPane != null) rootPane.putClientProperty(DarkRootPaneUI.KEY_IS_MEDIUM_WEIGHT_POPUP_ROOT, true);

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

@ -28,6 +28,7 @@ import com.github.weisj.darklaf.components.border.DropShadowBorder;
import com.github.weisj.darklaf.components.tooltip.ToolTipStyle;
import com.github.weisj.darklaf.util.Alignment;
import com.github.weisj.darklaf.util.DarkUIUtil;
import com.github.weisj.darklaf.util.GraphicsContext;
import javax.swing.*;
import javax.swing.border.Border;
@ -45,6 +46,7 @@ public class DarkTooltipBorder implements Border {
false, true,
true, true);
private final BubbleBorder bubbleBorder;
private static final AlphaComposite COMPOSITE = AlphaComposite.getInstance(AlphaComposite.SRC_OVER);
public DarkTooltipBorder() {
bubbleBorder = new BubbleBorder(UIManager.getColor("ToolTip.borderColor"));
@ -86,9 +88,25 @@ public class DarkTooltipBorder implements Border {
}
}
protected float getAlpha(final Component c) {
if (c instanceof JComponent) {
Object alpha = ((JComponent) c).getClientProperty(DarkTooltipUI.KEY_PAINT_ALPHA);
if (alpha instanceof Float) {
return (float) alpha;
}
}
return DarkTooltipUI.MAX_ALPHA;
}
@Override
public void paintBorder(final Component c, final Graphics g,
final int x, final int y, final int width, final int height) {
float alpha = getAlpha(c);
if (alpha == 0) return;
GraphicsContext context = new GraphicsContext(g);
if (alpha != DarkTooltipUI.MAX_ALPHA) {
((Graphics2D) g).setComposite(COMPOSITE.derive(alpha));
}
if (isPlain(c)) {
g.setColor(bubbleBorder.getColor());
DarkUIUtil.drawRect(g, x, y, width, height, 1);
@ -115,6 +133,7 @@ public class DarkTooltipBorder implements Border {
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

65
core/src/main/java/com/github/weisj/darklaf/ui/tooltip/DarkTooltipUI.java

@ -25,9 +25,7 @@ package com.github.weisj.darklaf.ui.tooltip;
import com.github.weisj.darklaf.components.tooltip.ToolTipStyle;
import com.github.weisj.darklaf.ui.rootpane.DarkRootPaneUI;
import com.github.weisj.darklaf.util.Alignment;
import com.github.weisj.darklaf.util.DarkUIUtil;
import com.github.weisj.darklaf.util.PropertyKey;
import com.github.weisj.darklaf.util.*;
import javax.swing.*;
import javax.swing.border.Border;
@ -55,6 +53,12 @@ public class DarkTooltipUI extends BasicToolTipUI implements PropertyChangeListe
public static final String VARIANT_PLAIN = "plain";
public static final String VARIANT_BALLOON = "balloon";
public static final String TIP_TEXT_PROPERTY = "tiptext";
public static final String KEY_PAINT_ALPHA = KEY_PREFIX + "paintAlpha";
public static final String KEY_CONTEXT = KEY_PREFIX + "toolTipContext";
static final float MAX_ALPHA = 1.0f;
private static final AlphaComposite COMPOSITE = AlphaComposite.getInstance(AlphaComposite.SRC_OVER);
private Animator fadeAnimator;
private float alpha = 0;
protected JToolTip toolTip;
protected JRootPane lastRootPane;
@ -103,6 +107,7 @@ public class DarkTooltipUI extends BasicToolTipUI implements PropertyChangeListe
updateStyle();
}
};
private boolean added;
public static ComponentUI createUI(final JComponent c) {
@ -151,6 +156,7 @@ public class DarkTooltipUI extends BasicToolTipUI implements PropertyChangeListe
@Override
protected void installDefaults(final JComponent c) {
super.installDefaults(c);
fadeAnimator = new FadeInAnimator();
c.setOpaque(false);
DarkTooltipBorder border = new DarkTooltipBorder();
Alignment align = (Alignment) c.getClientProperty(KEY_POINTER_LOCATION);
@ -186,12 +192,25 @@ public class DarkTooltipUI extends BasicToolTipUI implements PropertyChangeListe
@Override
public void paint(final Graphics g, final JComponent c) {
if (((JToolTip) c).getTipText() == null) return;
if (added) {
added = false;
alpha = 0;
fadeAnimator.reset();
fadeAnimator.resume();
}
toolTip.putClientProperty(KEY_PAINT_ALPHA, alpha);
if (alpha == 0) return;
GraphicsContext config = new GraphicsContext(g);
if (alpha != MAX_ALPHA) {
((Graphics2D) g).setComposite(COMPOSITE.derive(alpha));
}
g.setColor(c.getBackground());
if (c.getBorder() instanceof DarkTooltipBorder) {
Area area = ((DarkTooltipBorder) c.getBorder()).getBackgroundArea(c, c.getWidth(), c.getHeight());
((Graphics2D) g).fill(area);
}
super.paint(g, c);
config.restore();
}
public Dimension getPreferredSize(final JComponent c) {
@ -301,6 +320,15 @@ public class DarkTooltipUI extends BasicToolTipUI implements PropertyChangeListe
updateSize();
isTipTextChanging = false;
}
} else if (PropertyKey.ANCESTOR.equals(key)) {
if (evt.getOldValue() == null) {
//Added to hierarchy. Schedule animation start.
added = true;
ToolTipUtil.applyContext(toolTip);
}
if (evt.getNewValue() == null) {
alpha = 0;
}
}
}
if (KEY_STYLE.equals(key)) {
@ -316,19 +344,34 @@ public class DarkTooltipUI extends BasicToolTipUI implements PropertyChangeListe
Object tooltipStyle = toolTip.getClientProperty(KEY_STYLE);
toolTip.putClientProperty(KEY_STYLE, style instanceof ToolTipStyle ? style :
tooltipStyle instanceof ToolTipStyle ? tooltipStyle
: ToolTipStyle.BALLOON);
}
: ToolTipStyle.PLAIN_BALLOON);
}
protected ToolTipStyle getStyle() {
Object prop = toolTip.getClientProperty(KEY_STYLE);
String propValue = prop != null ? prop.toString() : null;
if (ToolTipStyle.BALLOON.name().equals(propValue)) return ToolTipStyle.BALLOON;
return ToolTipStyle.PLAIN;
}
protected void updateSize() {
toolTip.setTipText(toolTip.getTipText());
toolTip.setPreferredSize(getPreferredSize(toolTip));
}
protected class FadeInAnimator extends Animator {
private static final int DELAY_FRAMES = 6;
private static final int FADEIN_FRAMES_COUNT = DELAY_FRAMES + 10;
public FadeInAnimator() {
super("Tooltip fadein", FADEIN_FRAMES_COUNT, FADEIN_FRAMES_COUNT * 20, false);
}
@Override
public void paintNow(final int frame, final int totalFrames, final int cycle) {
alpha = ((float) frame * MAX_ALPHA) / totalFrames;
toolTip.paintImmediately(0, 0, toolTip.getWidth(), toolTip.getHeight());
}
@Override
protected void paintCycleEnd() {
alpha = MAX_ALPHA;
toolTip.paintImmediately(0, 0, toolTip.getWidth(), toolTip.getHeight());
}
}
}

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

@ -0,0 +1,179 @@
/*
* 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.tooltip;
import com.github.weisj.darklaf.components.alignment.AlignmentStrategy;
import com.github.weisj.darklaf.components.tooltip.ToolTipContext;
import com.github.weisj.darklaf.components.tooltip.ToolTipStyle;
import com.github.weisj.darklaf.util.Alignment;
import com.github.weisj.darklaf.util.DarkUIUtil;
import javax.swing.*;
import java.awt.*;
public class ToolTipUtil {
private final static ToolTipContext DEFAULT_CONTEXT = new ToolTipContext().setAlignment(Alignment.CENTER)
.setCenterAlignment(Alignment.SOUTH);
public static void applyContext(final JToolTip toolTip) {
JComponent target = toolTip.getComponent();
if (target == null) return;
ToolTipContext context = getToolTipContext(target);
if (context == null) return;
context.setTarget(target);
context.setToolTip(toolTip);
Point p = MouseInfo.getPointerInfo().getLocation();
SwingUtilities.convertPointFromScreen(p, target);
Point pos = getBestPositionMatch(context, p);
if (pos != null) {
moveToolTip(toolTip, pos.x, pos.y, target);
}
}
protected static Point getBestPositionMatch(final ToolTipContext context, final Point p) {
if (context.getAlignment() != Alignment.CENTER
|| context.getAlignmentStrategy() != AlignmentStrategy.COMPONENT_BOTH) {
return context.getToolTipLocation(p, null);
}
// For now adjustments are only made when the alignment is in the center and no mouse coordinates are used.
Rectangle screenBounds = getScreenBounds(context.getTarget(), p);
Rectangle windowBounds = DarkUIUtil.getWindow(context.getTarget()).getBounds();
Rectangle tooltipBounds = new Rectangle();
tooltipBounds.setSize(context.getToolTip().getPreferredSize());
Alignment original = context.getCenterAlignment();
Alignment[] alignments = getAlignments(original);
Point pos = null;
// Check if a position keeps the tooltip inside the window.
for (Alignment a : alignments) {
pos = tryPosition(a, context, p, tooltipBounds, windowBounds, screenBounds);
if (pos != null) break;
}
if (pos == null) {
//Try again with screen bounds instead.
for (Alignment a : alignments) {
pos = tryPosition(a, context, p, tooltipBounds, screenBounds, screenBounds);
if (pos != null) break;
}
}
/*
* At this point if the tooltip is still extending outside the screen boundary
* we surrender and leave the tooltip as it was.
*/
if (pos == null) {
context.setCenterAlignment(Alignment.CENTER);
}
context.updateToolTip();
context.setCenterAlignment(original);
return pos;
}
protected static Alignment[] getAlignments(final Alignment start) {
//Example with NORTH:
return new Alignment[]{
start, //NORTH
start.opposite(), //SOUTH
start.clockwise().clockwise(),//EAST
start.anticlockwise().anticlockwise(), //WEST
start.clockwise(), //NORTH_EAST
start.clockwise().opposite(), //SOUTH_WEST
start.anticlockwise(), //NORTH_WEST
start.anticlockwise().opposite() //SOUTH_EAST
};
}
protected static Point tryPosition(final Alignment a, final ToolTipContext context, final Point p,
final Rectangle tooltipBounds, final Rectangle boundary,
final Rectangle screenBoundary) {
context.setCenterAlignment(a);
Point pos = context.getToolTipLocation(p, null);
Point screenPos = new Point(pos.x, pos.y);
SwingUtilities.convertPointToScreen(screenPos, context.getTarget());
tooltipBounds.setLocation(screenPos);
if (!fits(tooltipBounds, boundary, screenBoundary)) pos = null;
return pos;
}
protected static boolean fits(final Rectangle toolTipBounds, final Rectangle boundary,
final Rectangle screenBoundary) {
if (boundary == screenBoundary) {
return SwingUtilities.isRectangleContainingRectangle(boundary, toolTipBounds);
}
return SwingUtilities.isRectangleContainingRectangle(boundary, toolTipBounds)
&& SwingUtilities.isRectangleContainingRectangle(screenBoundary, toolTipBounds);
}
protected static ToolTipContext getToolTipContext(final JComponent comp) {
Object context = comp.getClientProperty(DarkTooltipUI.KEY_CONTEXT);
if (context instanceof ToolTipContext) {
return (ToolTipContext) context;
}
if (ToolTipStyle.BALLOON.equals(comp.getClientProperty(DarkTooltipUI.KEY_STYLE))) {
return DEFAULT_CONTEXT;
}
return null;
}
public static void moveToolTip(final JToolTip toolTip, final int x, final int y, final JComponent target) {
Window window = DarkUIUtil.getWindow(toolTip);
if (window == null) return;
Point targetPos = target.getLocationOnScreen();
window.setLocation(targetPos.x + x, targetPos.y + y);
}
protected static Rectangle getScreenBounds(final JComponent target, final Point p) {
GraphicsConfiguration gc = getDrawingGC(p);
if (gc == null) {
gc = target.getGraphicsConfiguration();
}
Rectangle sBounds = gc.getBounds();
Insets screenInsets = Toolkit.getDefaultToolkit()
.getScreenInsets(gc);
// Take into account screen insets, decrease viewport
sBounds.x += screenInsets.left;
sBounds.y += screenInsets.top;
sBounds.width -= (screenInsets.left + screenInsets.right);
sBounds.height -= (screenInsets.top + screenInsets.bottom);
return sBounds;
}
private static GraphicsConfiguration getDrawingGC(final Point location) {
GraphicsEnvironment env = GraphicsEnvironment.getLocalGraphicsEnvironment();
GraphicsDevice[] devices = env.getScreenDevices();
for (GraphicsDevice device : devices) {
GraphicsConfiguration config = device.getDefaultConfiguration();
Rectangle rect = config.getBounds();
if (rect.contains(location)) {
return config;
}
}
return null;
}
}

1
core/src/test/java/ui/dialog/DialogDemo.java

@ -441,7 +441,6 @@ public class DialogDemo extends JPanel {
if (imgURL != null) {
return new ImageIcon(imgURL);
} else {
System.err.println("Couldn't find file: " + path);
return null;
}
}

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

@ -26,7 +26,7 @@ package ui.toolTip;
import com.github.weisj.darklaf.components.alignment.AlignmentStrategy;
import com.github.weisj.darklaf.components.tooltip.ToolTipContext;
import com.github.weisj.darklaf.components.tooltip.ToolTipStyle;
import com.github.weisj.darklaf.components.tooltip.TooltipAwareButton;
import com.github.weisj.darklaf.ui.tooltip.DarkTooltipUI;
import com.github.weisj.darklaf.util.Alignment;
import ui.ComponentDemo;
import ui.DemoPanel;
@ -41,10 +41,12 @@ public class ToolTipDemo implements ComponentDemo {
@Override
public JComponent createComponent() {
TooltipAwareButton button = new TooltipAwareButton("Demo Button");
JButton button = new JButton("Demo Button");
DemoPanel panel = new DemoPanel(button);
ToolTipContext context = button.getToolTipContext();
button.setToolTipText("ToolTip demo text!");
ToolTipContext context = new ToolTipContext(button).setAlignment(Alignment.CENTER)
.setCenterAlignment(Alignment.SOUTH);
button.putClientProperty(DarkTooltipUI.KEY_CONTEXT, context);
button.setToolTipText("This is the ToolTip demo text!");
JPanel controlPanel = panel.addControls();

Loading…
Cancel
Save