diff --git a/core/src/main/java/com/github/weisj/darklaf/components/tooltip/DarkToolTip.java b/core/src/main/java/com/github/weisj/darklaf/components/tooltip/DarkToolTip.java deleted file mode 100644 index da005360..00000000 --- a/core/src/main/java/com/github/weisj/darklaf/components/tooltip/DarkToolTip.java +++ /dev/null @@ -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()); - } - } -} diff --git a/core/src/main/java/com/github/weisj/darklaf/components/tooltip/ToolTipContext.java b/core/src/main/java/com/github/weisj/darklaf/components/tooltip/ToolTipContext.java index dc75fff8..d9faf3b8 100644 --- a/core/src/main/java/com/github/weisj/darklaf/components/tooltip/ToolTipContext.java +++ b/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 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 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 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)}. - *

- * 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. - *

- * Default will be the component bounding rectangle. - * - * @param toolTipRectSupplier rectangle supplier method. - * @return this - */ - public ToolTipContext setToolTipRectSupplier(final Function 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)}. + *

+ * 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. + *

+ * Default will be the component bounding rectangle. + * + * @param toolTipRectSupplier rectangle supplier method. + * @return this + */ + public ToolTipContext setToolTipRectSupplier(final Function 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); + } + } } diff --git a/core/src/main/java/com/github/weisj/darklaf/components/tooltip/ToolTipListener.java b/core/src/main/java/com/github/weisj/darklaf/components/tooltip/ToolTipListener.java deleted file mode 100644 index 14a937cc..00000000 --- a/core/src/main/java/com/github/weisj/darklaf/components/tooltip/ToolTipListener.java +++ /dev/null @@ -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); -} diff --git a/core/src/main/java/com/github/weisj/darklaf/components/tooltip/ToolTipStyle.java b/core/src/main/java/com/github/weisj/darklaf/components/tooltip/ToolTipStyle.java index f412a75d..1228c509 100644 --- a/core/src/main/java/com/github/weisj/darklaf/components/tooltip/ToolTipStyle.java +++ b/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, } diff --git a/core/src/main/java/com/github/weisj/darklaf/ui/BasicTransferable.java b/core/src/main/java/com/github/weisj/darklaf/ui/BasicTransferable.java index 513151af..fea89c2d 100644 --- a/core/src/main/java/com/github/weisj/darklaf/ui/BasicTransferable.java +++ b/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"); } } diff --git a/core/src/main/java/com/github/weisj/darklaf/ui/DarkPopupFactory.java b/core/src/main/java/com/github/weisj/darklaf/ui/DarkPopupFactory.java index 104175dd..8dbc0085 100644 --- a/core/src/main/java/com/github/weisj/darklaf/ui/DarkPopupFactory.java +++ b/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); diff --git a/core/src/main/java/com/github/weisj/darklaf/ui/tooltip/DarkTooltipBorder.java b/core/src/main/java/com/github/weisj/darklaf/ui/tooltip/DarkTooltipBorder.java index ac5b802a..4fbc2723 100644 --- a/core/src/main/java/com/github/weisj/darklaf/ui/tooltip/DarkTooltipBorder.java +++ b/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 diff --git a/core/src/main/java/com/github/weisj/darklaf/ui/tooltip/DarkTooltipUI.java b/core/src/main/java/com/github/weisj/darklaf/ui/tooltip/DarkTooltipUI.java index caa0d1a7..dff5ee01 100644 --- a/core/src/main/java/com/github/weisj/darklaf/ui/tooltip/DarkTooltipUI.java +++ b/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()); + } + } } diff --git a/core/src/main/java/com/github/weisj/darklaf/ui/tooltip/ToolTipUtil.java b/core/src/main/java/com/github/weisj/darklaf/ui/tooltip/ToolTipUtil.java new file mode 100644 index 00000000..247a9f6e --- /dev/null +++ b/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; + } +} diff --git a/core/src/test/java/ui/dialog/DialogDemo.java b/core/src/test/java/ui/dialog/DialogDemo.java index 96453cb4..87b27279 100644 --- a/core/src/test/java/ui/dialog/DialogDemo.java +++ b/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; } } diff --git a/core/src/test/java/ui/toolTip/ToolTipDemo.java b/core/src/test/java/ui/toolTip/ToolTipDemo.java index 7dfcd44b..362187a6 100644 --- a/core/src/test/java/ui/toolTip/ToolTipDemo.java +++ b/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();