mirror of https://github.com/weisJ/darklaf.git
weisj
5 years ago
11 changed files with 404 additions and 372 deletions
@ -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()); |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
@ -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); |
|
||||||
} |
|
@ -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; |
||||||
|
} |
||||||
|
} |
Loading…
Reference in new issue