2
* Copyright (c) 2005-2007 Substance Kirill Grouchnikov. All Rights Reserved.
4
* Redistribution and use in source and binary forms, with or without
5
* modification, are permitted provided that the following conditions are met:
7
* o Redistributions of source code must retain the above copyright notice,
8
* this list of conditions and the following disclaimer.
10
* o Redistributions in binary form must reproduce the above copyright notice,
11
* this list of conditions and the following disclaimer in the documentation
12
* and/or other materials provided with the distribution.
14
* o Neither the name of Substance Kirill Grouchnikov nor the names of
15
* its contributors may be used to endorse or promote products derived
16
* from this software without specific prior written permission.
18
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
19
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
20
* THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
21
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
22
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
23
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
24
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
25
* OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
26
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
27
* OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
28
* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30
package org.pushingpixels.lafwidget.animation.effects;
33
import java.awt.image.BufferedImage;
34
import java.util.LinkedHashMap;
39
import org.pushingpixels.lafwidget.LafWidgetUtilities;
40
import org.pushingpixels.lafwidget.animation.AnimationConfigurationManager;
41
import org.pushingpixels.lafwidget.animation.AnimationFacet;
42
import org.pushingpixels.trident.Timeline;
43
import org.pushingpixels.trident.Timeline.TimelineState;
46
* Utility class that implements the ghost effects.
48
* @author Kirill Grouchnikov
50
public class GhostPaintingUtils {
52
* Minimal starting opacity for icon ghosting. Change to a higher value for
53
* debugging / demoing purposes.
55
public static float MIN_ICON_GHOSTING_ALPHA = 0.15f;
58
* Maximal starting opacity for icon ghosting. Change to a higher value for
59
* debugging / demoing purposes.
61
public static float MAX_ICON_GHOSTING_ALPHA = 0.5f;
64
* Minimal starting opacity for press ghosting. Change to a higher value for
65
* debugging / demoing purposes.
67
public static float MIN_PRESS_GHOSTING_ALPHA = 0.15f;
70
* Maximal starting opacity for press ghosting. Change to a higher value for
71
* debugging / demoing purposes.
73
public static float MAX_PRESS_GHOSTING_ALPHA = 0.3f;
76
* Global decay factor.
78
public static float DECAY_FACTOR = 1.0f;
81
* Cache of component ghost images. Used to speed up the rendering of the
84
private static LinkedHashMap<String, BufferedImage> componentGhostCache = new LinkedHashMap<String, BufferedImage>() {
86
protected boolean removeEldestEntry(
87
java.util.Map.Entry<String, BufferedImage> eldest) {
88
return this.size() > 50;
93
* Cache of icon ghost images. Used to speed up the rendering of the ghost
96
private static LinkedHashMap<String, BufferedImage> iconGhostCache = new LinkedHashMap<String, BufferedImage>() {
98
protected boolean removeEldestEntry(
99
java.util.Map.Entry<String, BufferedImage> eldest) {
100
return this.size() > 50;
105
* Returns a scaled ghost image of the specified component.
111
* @return A scaled ghost image of the specified component.
113
protected static synchronized BufferedImage getComponentGhostImage(
114
JComponent comp, Timeline ghostPressTimeline, double scaleFactor) {
115
String key = ghostPressTimeline.getTimelinePosition() + ":"
116
+ comp.hashCode() + ":" + scaleFactor;
118
BufferedImage result = componentGhostCache.get(key);
119
if (result == null) {
120
Rectangle bounds = comp.getBounds();
122
double iWidth = bounds.width * scaleFactor;
123
double iHeight = bounds.height * scaleFactor;
124
result = LafWidgetUtilities.getBlankImage((int) iWidth,
126
Graphics2D iGraphics = result.createGraphics();
127
iGraphics.scale(scaleFactor, scaleFactor);
128
comp.paint(iGraphics);
131
componentGhostCache.put(key, result);
137
* Returns a scaled ghost image of the specified icon.
145
* @return A scaled ghost image of the specified icon.
147
protected static synchronized BufferedImage getIconGhostImage(
148
JComponent comp, Timeline ghostRolloverTimeline, Icon icon,
149
double scaleFactor) {
150
String key = ghostRolloverTimeline.getTimelinePosition() + ":"
151
+ comp.hashCode() + ":" + icon.hashCode() + ":" + scaleFactor;
153
BufferedImage result = iconGhostCache.get(key);
154
if (result == null) {
155
int oWidth = icon.getIconWidth();
156
int oHeight = icon.getIconHeight();
157
double iWidth = oWidth * scaleFactor;
158
double iHeight = oHeight * scaleFactor;
159
result = LafWidgetUtilities.getBlankImage((int) iWidth,
161
Graphics2D iGraphics = result.createGraphics();
162
iGraphics.scale(scaleFactor, scaleFactor);
163
icon.paintIcon(comp, iGraphics, 0, 0);
166
iconGhostCache.put(key, result);
172
* Paints ghost images on the specified component.
174
* @param mainComponent
179
public static void paintGhostImages(Component mainComponent, Graphics g) {
180
if (!mainComponent.isShowing())
182
if (!mainComponent.isVisible())
184
// The following check is for offscreen rendering. The component
185
// may be showing and visible, but have no peer (non displayable).
186
if (!mainComponent.isDisplayable())
188
if (SwingUtilities.getWindowAncestor(mainComponent) == null)
191
Graphics2D graphics = (Graphics2D) g.create();
193
Rectangle mainRect = mainComponent.getBounds();
194
mainRect.setLocation(mainComponent.getLocationOnScreen());
195
if (AnimationConfigurationManager.getInstance().isAnimationAllowed(
196
AnimationFacet.GHOSTING_BUTTON_PRESS, mainComponent)) {
197
Map<JComponent, Timeline> runningGhostPressTimelines = GhostingListener
198
.getRunningGhostPressTimelines();
199
for (Map.Entry<JComponent, Timeline> entry : runningGhostPressTimelines
201
JComponent comp = entry.getKey();
202
Timeline timeline = entry.getValue();
204
if (comp == mainComponent)
207
if (!comp.isShowing())
209
if (!comp.isVisible())
211
// The following check is for offscreen rendering. The component
212
// may be showing and visible, but have no peer (non
214
if (!comp.isDisplayable())
217
Rectangle compRect = comp.getBounds();
218
compRect.setLocation(comp.getLocationOnScreen());
220
int dx = compRect.x - mainRect.x;
221
int dy = compRect.y - mainRect.y;
223
compRect.x -= compRect.width / 2;
224
compRect.y -= compRect.height / 2;
226
compRect.height *= 2;
228
if (mainRect.intersects(compRect)) {
229
float fade = timeline.getTimelinePosition();
232
double start = MAX_PRESS_GHOSTING_ALPHA - 0.0015
233
* compRect.getWidth();
234
float coef = Math.max((float) start,
235
MIN_PRESS_GHOSTING_ALPHA);
236
float opFactor = coef * (1.0f - DECAY_FACTOR * fade);
237
double iFactor = 1.0 + fade;
239
graphics.setComposite(LafWidgetUtilities.getAlphaComposite(
240
mainComponent, opFactor));
242
Rectangle bounds = comp.getBounds();
244
BufferedImage ghost = getComponentGhostImage(comp,
246
dx -= ((ghost.getWidth() - bounds.width) / 2);
247
dy -= ((ghost.getHeight() - bounds.height) / 2);
248
graphics.drawImage(ghost, dx, dy, null);
253
if (AnimationConfigurationManager.getInstance().isAnimationAllowed(
254
AnimationFacet.GHOSTING_ICON_ROLLOVER, mainComponent)) {
255
Map<JComponent, Timeline> runningGhostRolloverTimelines = GhostingListener
256
.getRunningGhostRolloverTimelines();
257
for (Map.Entry<JComponent, Timeline> entry : runningGhostRolloverTimelines
259
JComponent comp = entry.getKey();
260
Timeline timeline = entry.getValue();
261
if (comp == mainComponent)
264
if (!(comp instanceof JComponent))
267
JComponent jc = (JComponent) comp;
274
Rectangle compRect = jc.getBounds();
275
compRect.setLocation(jc.getLocationOnScreen());
277
int dx = compRect.x - mainRect.x;
278
int dy = compRect.y - mainRect.y;
280
compRect.x -= compRect.width / 2;
281
compRect.y -= compRect.height / 2;
283
compRect.height *= 2;
285
if (mainRect.intersects(compRect)) {
286
float fade = timeline.getTimelinePosition();
287
// Rectangle bounds = comp.getBounds();
289
Rectangle iconRect = (Rectangle) jc
290
.getClientProperty("icon.bounds");
291
if (iconRect != null) {
292
if (jc instanceof AbstractButton) {
293
icon = LafWidgetUtilities
294
.getIcon((AbstractButton) jc);
296
icon = (Icon) jc.getClientProperty("icon");
300
if ((icon != null) && (iconRect != null)) {
301
double iFactor = 1.0 + fade;
302
// double iWidth = icon.getIconWidth() * iFactor;
303
// double iHeight = icon.getIconHeight() * iFactor;
304
// BufferedImage iImage = LafWidgetUtilities
305
// .getBlankImage((int) iWidth, (int) iHeight);
306
// Graphics2D iGraphics = (Graphics2D) iImage
307
// .createGraphics();
308
// iGraphics.scale(iFactor, iFactor);
309
// icon.paintIcon(comp, iGraphics, 0, 0);
310
// iGraphics.dispose();
312
BufferedImage iImage = getIconGhostImage(comp,
313
timeline, icon, iFactor);
315
// System.out.println(iconRect);
317
// BufferedImage bImage = SubstanceCoreUtilities.blur(
320
int iWidth = iImage.getWidth();
321
int iHeight = iImage.getHeight();
322
dx -= ((iWidth - icon.getIconWidth()) / 2);
323
dy -= ((iHeight - icon.getIconHeight()) / 2);
325
double start = MAX_ICON_GHOSTING_ALPHA
326
- (MAX_ICON_GHOSTING_ALPHA - MIN_ICON_GHOSTING_ALPHA)
327
* (iWidth - 16) / 48;
328
float coef = Math.max((float) start,
329
MIN_ICON_GHOSTING_ALPHA);
330
float opFactor = coef * (1.0f - DECAY_FACTOR * fade);
331
graphics.setComposite(LafWidgetUtilities
332
.getAlphaComposite(mainComponent, opFactor));
334
graphics.drawImage(iImage, dx + iconRect.x, dy
344
* Paints the ghost icon inside the bounds of the specified button.
353
public static void paintGhostIcon(Graphics2D graphics, AbstractButton b,
355
paintGhostIcon(graphics, b, icon, (Rectangle) b
356
.getClientProperty("icon.bounds"));
360
* Paints the ghost icon inside the bounds of the specified button.
366
* @param iconRectangle
367
* Rectangle of the button icon.
369
public static void paintGhostIcon(Graphics2D graphics, AbstractButton b,
370
Rectangle iconRectangle) {
371
paintGhostIcon(graphics, b, LafWidgetUtilities.getIcon(b),
376
* Paints the ghost icon inside the bounds of the specified button.
384
* @param iconRectangle
385
* Rectangle of the button icon.
387
public static void paintGhostIcon(Graphics2D graphics, Component b,
388
Icon icon, Rectangle iconRectangle) {
389
// System.out.println(b.getText() + ":" + icon + ":" + iconRectangle);
390
if (!AnimationConfigurationManager.getInstance().isAnimationAllowed(
391
AnimationFacet.GHOSTING_ICON_ROLLOVER, b)) {
395
if (!(b instanceof JComponent))
398
GhostingListener gl = (GhostingListener) ((JComponent) b)
399
.getClientProperty(GhostingListener.GHOST_LISTENER_KEY);
403
Timeline ghostRolloverTimeline = gl.getGhostIconRolloverTimeline();
405
if (ghostRolloverTimeline.getState() != TimelineState.IDLE) {
406
float fade = ghostRolloverTimeline.getTimelinePosition();
407
if ((icon != null) && (iconRectangle != null)) {
408
double iFactor = 1.0 + fade;
409
BufferedImage iImage = getIconGhostImage((JComponent) b,
410
ghostRolloverTimeline, icon, iFactor);
412
int iWidth = iImage.getWidth();
413
int iHeight = iImage.getHeight();
414
int dx = ((iWidth - icon.getIconWidth()) / 2);
415
int dy = ((iHeight - icon.getIconHeight()) / 2);
417
double start = MAX_ICON_GHOSTING_ALPHA
418
- (MAX_ICON_GHOSTING_ALPHA - MIN_ICON_GHOSTING_ALPHA)
419
* (iWidth - 16) / 48;
420
float coef = Math.max((float) start, MIN_ICON_GHOSTING_ALPHA);
421
float opFactor = coef * (1.0f - DECAY_FACTOR * fade);
422
graphics.setComposite(LafWidgetUtilities.getAlphaComposite(b,
425
graphics.drawImage(iImage, iconRectangle.x - dx,
426
iconRectangle.y - dy, null);