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