001: /*
002: * Copyright (c) 2001-2006 JGoodies Karsten Lentzsch. All Rights Reserved.
003: *
004: * Redistribution and use in source and binary forms, with or without
005: * modification, are permitted provided that the following conditions are met:
006: *
007: * o Redistributions of source code must retain the above copyright notice,
008: * this list of conditions and the following disclaimer.
009: *
010: * o Redistributions in binary form must reproduce the above copyright notice,
011: * this list of conditions and the following disclaimer in the documentation
012: * and/or other materials provided with the distribution.
013: *
014: * o Neither the name of JGoodies Karsten Lentzsch nor the names of
015: * its contributors may be used to endorse or promote products derived
016: * from this software without specific prior written permission.
017: *
018: * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
019: * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
020: * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
021: * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
022: * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
023: * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
024: * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
025: * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
026: * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
027: * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
028: * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
029: */
030:
031: package com.jgoodies.animation.components;
032:
033: import java.awt.*;
034:
035: import javax.swing.JLabel;
036: import javax.swing.JPanel;
037: import javax.swing.SwingConstants;
038: import javax.swing.SwingUtilities;
039: import javax.swing.plaf.FontUIResource;
040:
041: import com.jgoodies.animation.AbstractAnimation;
042: import com.jgoodies.animation.Animation;
043: import com.jgoodies.animation.AnimationAdapter;
044: import com.jgoodies.animation.AnimationEvent;
045: import com.jgoodies.animation.Animator;
046:
047: /**
048: * An anti-aliased text label that can animate text changes
049: * using a blend over effect.<p>
050: *
051: * <strong>Note: This is preview code that is not supported.
052: * It is more raw than other classes that you have downloaded
053: * from JGoodies.com in the past and contains known bugs.</strong>
054: *
055: * @author Karsten Lentzsch
056: * @version $Revision: 1.1 $
057: */
058:
059: public final class AnimatedLabel extends JPanel {
060:
061: // Names of Bound Properties **********************************************
062:
063: public static final String PROPERTYNAME_ANIMATED = "animated";
064: public static final String PROPERTYNAME_DURATION = "duration";
065: public static final String PROPERTYNAME_FOREGROUND = "foreground";
066: public static final String PROPERTYNAME_TEXT = "text";
067:
068: // Constants **************************************************************
069:
070: public static final int RIGHT = SwingConstants.RIGHT;
071: public static final int CENTER = SwingConstants.CENTER;
072: public static final int LEFT = SwingConstants.LEFT;
073:
074: // Default Values *********************************************************
075:
076: public static final Color DEFAULT_BASE_COLOR = new Color(64, 64, 64);
077: public static final int DEFAULT_FONT_EXTRA_SIZE = 8;
078:
079: private static final int DEFAULT_DURATION = 300;
080: private static final int DEFAULT_ANIMATION_FPS = 30;
081:
082: // Instance Variables *****************************************************
083:
084: private JLabel[] labels;
085: private int foreground = 0;
086: private int background = 1;
087:
088: private Color baseColor;
089: private boolean animated;
090: private int orientation;
091: private long duration;
092: private int fps;
093: private Animation animation;
094: private Animator animator;
095:
096: // Instance Creation ****************************************************
097:
098: /**
099: * Constructs an <code>AnimatedLabel</code> with default base color,
100: * default font extra size, and an empty text.
101: */
102: public AnimatedLabel() {
103: this (DEFAULT_BASE_COLOR, DEFAULT_FONT_EXTRA_SIZE, "");
104: }
105:
106: /**
107: * Constructs an <code>AnimatedLabel</code> with the given initial text
108: * using a left oriented label.
109: *
110: * @param baseColor the color used as a basis for the text color
111: * @param fontExtraSize pixels that are added to the dialog font size
112: * @param text the initial text to be displayed
113: */
114: public AnimatedLabel(Color baseColor, int fontExtraSize, String text) {
115: this (baseColor, fontExtraSize, text, LEFT);
116: }
117:
118: /**
119: * Constructs an <code>AnimatedLabel</code> with the given initial text
120: * and orientation.
121: *
122: * @param baseColor the color used as a basis for the text color
123: * @param fontExtraSize pixels that are added to the dialog font size
124: * @param text the initial text to be displayed
125: * @param orientation the label's orientation
126: */
127: public AnimatedLabel(Color baseColor, int fontExtraSize,
128: String text, int orientation) {
129: this (baseColor, fontExtraSize, text, orientation,
130: DEFAULT_DURATION, DEFAULT_ANIMATION_FPS);
131: }
132:
133: /**
134: * Constructs an <code>AnimatedLabel</code> with the given properties.
135: *
136: * @param baseColor the color used as a basis for the text color
137: * @param fontExtraSize pixels that are added to the dialog font size
138: * @param text the initial text to be displayed
139: * @param orientation the label's orientation
140: * @param duration the duration of the blend over animation
141: * @param frames_per_second the blend over animation's frame rate
142: */
143: public AnimatedLabel(Color baseColor, int fontExtraSize,
144: String text, int orientation, int duration,
145: int frames_per_second) {
146: super (null);
147: this .baseColor = baseColor;
148: this .orientation = orientation;
149: this .duration = duration;
150: this .fps = frames_per_second;
151: this .animated = true;
152: initComponents(fontExtraSize);
153: build();
154: setTextImmediately(text);
155: }
156:
157: // Public API ***********************************************************
158:
159: /**
160: * Answers whether the animation is currently enabled.
161: *
162: * @return true if the animation is enabled, false if disabled
163: */
164: public boolean isAnimated() {
165: return animated;
166: }
167:
168: /**
169: * Returns the duration of the blend over animation.
170: *
171: * @return the duration of the blend over animaton
172: */
173: public long getDuration() {
174: return duration;
175: }
176:
177: /**
178: * Returns the label's foreground base color.
179: *
180: * @return this label's foreground base color
181: */
182: public Color getForeground() {
183: return baseColor;
184: }
185:
186: /**
187: * Returns the text of the foreground label.
188: *
189: * @return the text of the foreground label
190: */
191: public synchronized String getText() {
192: return labels[foreground].getText();
193: }
194:
195: /**
196: * Enables or disables the blend over effect. This can be useful in
197: * environments with a poor rendering performance or if the user disables
198: * all kinds of animations. You can still use this class but enable and
199: * disable the animations.
200: *
201: * @param animated true to enable the blend over effect, false to disable it
202: */
203: public void setAnimated(boolean animated) {
204: boolean oldAnimated = animated;
205: this .animated = animated;
206: firePropertyChange(PROPERTYNAME_ANIMATED, oldAnimated, animated);
207: }
208:
209: /**
210: * Sets the animation's duration and invalidates the animation cache.
211: *
212: * @param newDuration the duration to be set
213: */
214: public void setDuration(long newDuration) {
215: long oldDuration = duration;
216: duration = newDuration;
217: animation = null;
218: firePropertyChange(PROPERTYNAME_DURATION, oldDuration,
219: newDuration);
220: }
221:
222: /**
223: * Sets a new foreground base color.
224: *
225: * @param newForeground the color to be set as new foreground base color
226: */
227: public void setForeground(Color newForeground) {
228: Color oldForeground = getForeground();
229: baseColor = newForeground;
230: firePropertyChange(PROPERTYNAME_FOREGROUND, oldForeground,
231: newForeground);
232: }
233:
234: /**
235: * Sets a new text. If the animation is disabled the text will
236: * be set immediately otherwise a blend over animation is used.
237: *
238: * @param newText the new text to be displayed
239: */
240: public synchronized void setText(String newText) {
241: String oldText = getText();
242: if (oldText.equals(newText))
243: return;
244:
245: if (!isAnimated()) {
246: setTextImmediately(newText);
247: return;
248: }
249:
250: labels[background].setText(newText);
251: foreground = 1 - foreground;
252: background = 1 - background;
253: // Ensure a previous animation is stopped before we start a new one.
254: if (animator != null) {
255: animator.stop();
256: }
257: animator = new Animator(animation(), fps);
258: animator.start();
259: firePropertyChange(PROPERTYNAME_TEXT, oldText, newText);
260: }
261:
262: /**
263: * Sets a new text without using the blend over animation.
264: *
265: * @param newText the text to be set
266: */
267: public void setTextImmediately(String newText) {
268: String oldText = getText();
269: labels[background].setText(newText);
270: foreground = 1 - foreground;
271: background = 1 - background;
272: setAlpha(255, 0);
273: firePropertyChange(PROPERTYNAME_TEXT, oldText, newText);
274: }
275:
276: // Animation Creation ***************************************************
277:
278: /**
279: * Lazily creates and returns the blend over animation.
280: *
281: * @return the lazily created blend over animation
282: */
283: private Animation animation() {
284: if (animation == null) {
285: animation = new BlendOverAnimation(duration);
286: animation.addAnimationListener(new AnimationAdapter() {
287: public void animationStopped(AnimationEvent e) {
288: setAlpha(255, 0);
289: }
290: });
291: }
292: return animation;
293: }
294:
295: // Building *************************************************************
296:
297: /**
298: * Creates and configures the UI components. The label's size is specified
299: * using an <code>fontExtraSize</code> that is a delta in pixel to the
300: * dialog font size.
301: *
302: * @param fontExtraSize the pixel size delta for the label sizes
303: */
304: private void initComponents(int fontExtraSize) {
305: labels = new JLabel[2];
306: labels[foreground] = createBoldLabel(fontExtraSize,
307: getTranslucentColor(255));
308: labels[background] = createBoldLabel(fontExtraSize,
309: getTranslucentColor(255));
310: }
311:
312: private void build() {
313: setOpaque(false);
314: setLayout(new GridBagLayout());
315: GridBagConstraints gbc = new GridBagConstraints();
316: gbc.anchor = anchor();
317: gbc.gridx = 0;
318: gbc.gridy = 0;
319: add(labels[foreground], gbc);
320: add(labels[background], gbc);
321: }
322:
323: private int anchor() {
324: if (orientation == RIGHT) {
325: return GridBagConstraints.EAST;
326: } else if (orientation == CENTER) {
327: return GridBagConstraints.CENTER;
328: } else {
329: return GridBagConstraints.WEST;
330: }
331: }
332:
333: /**
334: * Creates and returns an anti-aliased label with a bold font for the
335: * specified size increment and foreground color.
336: *
337: * @param sizeIncrement a size delta in pixels relative to
338: * the dialog font size
339: * @param aForeground the label's foreground base color
340: * @return a bold anti aliased label
341: */
342: private JLabel createBoldLabel(int sizeIncrement, Color aForeground) {
343: JLabel label = new AntiAliasedLabel("", Font.BOLD,
344: sizeIncrement);
345: label.setForeground(aForeground);
346: return label;
347: }
348:
349: // Helper Methods *******************************************************
350:
351: /**
352: * Creates and returns a translucent color with the label's base color.
353: *
354: * @param alpha the current alpha value
355: * @return a translucent color with the given alpha based on this label's
356: * foreground base color.
357: */
358: private Color getTranslucentColor(int alpha) {
359: return new Color(baseColor.getRed(), baseColor.getGreen(),
360: baseColor.getBlue(), alpha);
361: }
362:
363: /**
364: * Sets the foreground and background colors using the given alpha values.
365: *
366: * @param foregroundAlpha alpha value for the foreground label
367: * @param backgroundAlpha alpha value for the background label
368: */
369: private void setAlpha0(int foregroundAlpha, int backgroundAlpha) {
370: labels[foreground]
371: .setForeground(getTranslucentColor(foregroundAlpha));
372: labels[background]
373: .setForeground(getTranslucentColor(backgroundAlpha));
374: }
375:
376: /**
377: * Sets the foreground and background colors in the event dispatch thread.
378: *
379: * @param foregroundAlpha alpha value for the foreground label
380: * @param backgroundAlpha alpha value for the background label
381: */
382: private void setAlpha(final int foregroundAlpha,
383: final int backgroundAlpha) {
384: if (SwingUtilities.isEventDispatchThread()) {
385: setAlpha0(foregroundAlpha, backgroundAlpha);
386: return;
387: }
388: Runnable runnable = new Runnable() {
389: public void run() {
390: setAlpha0(foregroundAlpha, backgroundAlpha);
391: }
392: };
393: SwingUtilities.invokeLater(runnable);
394: }
395:
396: // Animation Class ******************************************************
397:
398: /**
399: * An animation that changes the colors of overlapping labels
400: * to implement a blend over effect.
401: */
402: private class BlendOverAnimation extends AbstractAnimation {
403:
404: /**
405: * Constructs an animation that changes the colors of the
406: * prefix and suffix labels over the duration.
407: *
408: * @param duration the animation's duration
409: */
410: public BlendOverAnimation(long duration) {
411: super (duration, true);
412: }
413:
414: /**
415: * Applies the effect: sets the text and time.
416: *
417: * @param time the time point used to apply the effect
418: */
419: protected void applyEffect(long time) {
420: int foregroundAlpha = (int) (255 * time / duration());
421: int backgroundAlpha = 255 - foregroundAlpha;
422: setAlpha(foregroundAlpha, backgroundAlpha);
423: }
424: }
425:
426: // Helper Class ***********************************************************
427:
428: private static final class AntiAliasedLabel extends JLabel {
429:
430: private final int fontExtraSize;
431: private final int fontStyle;
432:
433: /**
434: * Constructs an <code>AntiAliasedLabel</code> for the given text,
435: * font style and font extra size.
436: *
437: * @param text the label's initial text
438: * @param fontStyle the font style attribute
439: * @param fontExtraSize a size delta in pixel relative to the dialog
440: * font size in pixels
441: */
442: private AntiAliasedLabel(String text, int fontStyle,
443: int fontExtraSize) {
444: super (text);
445: this .fontStyle = fontStyle;
446: this .fontExtraSize = fontExtraSize;
447: updateUI();
448: }
449:
450: /**
451: * Enables text anti-aliasing on, paints, and restores the old state.
452: *
453: * @param g the Graphics object to render on
454: */
455: public void paint(Graphics g) {
456: Graphics2D g2 = (Graphics2D) g;
457: Object oldHint = g2
458: .getRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING);
459: g2.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING,
460: RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
461: super .paint(g2);
462: g2.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING,
463: oldHint);
464: }
465:
466: /**
467: * Restores the font after the UI has changed.
468: */
469: public void updateUI() {
470: super .updateUI();
471: Font font = getFont();
472: if (0 == fontExtraSize) {
473: if (font.getStyle() != fontStyle)
474: setFont(new FontUIResource(font
475: .deriveFont(fontStyle)));
476: } else
477: setFont(new FontUIResource(new Font(font.getName(),
478: fontStyle, font.getSize() + fontExtraSize)));
479: }
480: }
481:
482: }
|