mirror of
https://github.com/JFormDesigner/FlatLaf.git
synced 2026-02-12 23:07:15 -06:00
AnimatedPainter: support independent animation of multiple values
This commit is contained in:
@@ -69,7 +69,13 @@ public abstract class FlatAbstractIcon
|
||||
}
|
||||
}
|
||||
|
||||
protected abstract void paintIcon( Component c, Graphics2D g2 );
|
||||
/**
|
||||
* Paint the icon at {@code [0,0]} location.
|
||||
* <p>
|
||||
* The given graphics context is scaled.
|
||||
* Use unscaled coordinates, width and height for painting.
|
||||
*/
|
||||
protected abstract void paintIcon( Component c, Graphics2D g );
|
||||
|
||||
@Override
|
||||
public int getIconWidth() {
|
||||
|
||||
@@ -21,6 +21,7 @@ import java.awt.Component;
|
||||
import java.awt.Graphics;
|
||||
import java.awt.Graphics2D;
|
||||
import com.formdev.flatlaf.util.AnimatedIcon;
|
||||
import com.formdev.flatlaf.util.AnimatedPainter;
|
||||
|
||||
/**
|
||||
* Base class for animated icons that scales width and height, creates and initializes
|
||||
@@ -30,7 +31,7 @@ import com.formdev.flatlaf.util.AnimatedIcon;
|
||||
* <p>
|
||||
* This class does not store any state information (needed for animation) in its instance.
|
||||
* Instead a client property is set on the painted component.
|
||||
* This makes it possible to use a share icon instance for multiple components.
|
||||
* This makes it possible to use a shared icon instance for multiple components.
|
||||
*
|
||||
* @author Karl Tauber
|
||||
*/
|
||||
@@ -45,11 +46,34 @@ public abstract class FlatAnimatedIcon
|
||||
@Override
|
||||
public void paintIcon( Component c, Graphics g, int x, int y ) {
|
||||
super.paintIcon( c, g, x, y );
|
||||
AnimatedIcon.AnimationSupport.saveIconLocation( this, c, x, y );
|
||||
AnimatedPainter.saveRepaintLocation( this, c, x, y );
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void paintIcon( Component c, Graphics2D g ) {
|
||||
AnimatedIcon.AnimationSupport.paintIcon( this, c, g, 0, 0 );
|
||||
paintWithAnimation( c, g, 0, 0, getIconWidth(), getIconHeight() );
|
||||
}
|
||||
|
||||
/**
|
||||
* Delegates painting to {@link #paintIconAnimated(Component, Graphics2D, float[])}.
|
||||
* Ignores the given bounds because {@code [x,y]} are always {@code [0,0]} and
|
||||
* {@code [width,height]} are scaled, but painting code should use unscaled width
|
||||
* and height because given graphics context is scaled.
|
||||
*
|
||||
* @since 2
|
||||
*/
|
||||
@Override
|
||||
public void paintAnimated( Component c, Graphics2D g, int x, int y, int width, int height, float[] animatedValues ) {
|
||||
paintIconAnimated( c, g, animatedValues );
|
||||
}
|
||||
|
||||
/**
|
||||
* Paint the icon at {@code 0,0} location.
|
||||
* <p>
|
||||
* The given graphics context is scaled.
|
||||
* Use unscaled coordinates, width and height for painting.
|
||||
*
|
||||
* @since 2
|
||||
*/
|
||||
protected abstract void paintIconAnimated( Component c, Graphics2D g, float[] animatedValues );
|
||||
}
|
||||
|
||||
@@ -25,26 +25,27 @@ import javax.swing.border.Border;
|
||||
/**
|
||||
* Border that automatically animates painting on component value changes.
|
||||
* <p>
|
||||
* {@link #getValue(Component)} returns the value of the component.
|
||||
* If the value changes, then {@link #paintAnimated(Component, Graphics2D, int, int, int, int, float)}
|
||||
* is invoked multiple times with animated value (from old value to new value).
|
||||
* {@link #getValues(Component)} returns the value(s) of the component.
|
||||
* If the value(s) have changed, then {@link #paintAnimated(Component, Graphics2D, int, int, int, int, float[])}
|
||||
* is invoked multiple times with animated value(s) (from old value(s) to new value(s)).
|
||||
* If {@link #getValues(Component)} returns multiple values, then each value gets its own independent animation.
|
||||
* <p>
|
||||
* Example for an animated border:
|
||||
* <pre>
|
||||
* private class AnimatedMinimalTestBorder
|
||||
* private class MyAnimatedBorder
|
||||
* implements AnimatedBorder
|
||||
* {
|
||||
* @Override
|
||||
* public void paintAnimated( Component c, Graphics2D g, int x, int y, int width, int height, float animatedValue ) {
|
||||
* public void paintAnimated( Component c, Graphics2D g, int x, int y, int width, int height, float[] animatedValues ) {
|
||||
* int lh = UIScale.scale( 2 );
|
||||
*
|
||||
* g.setColor( Color.blue );
|
||||
* g.fillRect( x, y + height - lh, Math.round( width * animatedValue ), lh );
|
||||
* g.fillRect( x, y + height - lh, Math.round( width * animatedValues[0] ), lh );
|
||||
* }
|
||||
*
|
||||
* @Override
|
||||
* public float getValue( Component c ) {
|
||||
* return c.isFocusOwner() ? 1 : 0;
|
||||
* public float[] getValues( Component c ) {
|
||||
* return new float[] { c.isFocusOwner() ? 1 : 0 };
|
||||
* }
|
||||
*
|
||||
* @Override
|
||||
@@ -57,7 +58,7 @@ import javax.swing.border.Border;
|
||||
*
|
||||
* // sample usage
|
||||
* JTextField textField = new JTextField();
|
||||
* textField.setBorder( new AnimatedMinimalTestBorder() );
|
||||
* textField.setBorder( new MyAnimatedBorder() );
|
||||
* </pre>
|
||||
*
|
||||
* Animation works only if the component passed to {@link #paintBorder(Component, Graphics, int, int, int, int)}
|
||||
|
||||
@@ -25,37 +25,35 @@ import javax.swing.JComponent;
|
||||
/**
|
||||
* Icon that automatically animates painting on component value changes.
|
||||
* <p>
|
||||
* {@link #getValue(Component)} returns the value of the component.
|
||||
* If the value changes, then {@link #paintIconAnimated(Component, Graphics, int, int, float)}
|
||||
* is invoked multiple times with animated value (from old value to new value).
|
||||
* {@link #getValues(Component)} returns the value(s) of the component.
|
||||
* If the value(s) have changed, then {@link #paintAnimated(Component, Graphics2D, int, int, int, int, float[])}
|
||||
* is invoked multiple times with animated value(s) (from old value(s) to new value(s)).
|
||||
* If {@link #getValues(Component)} returns multiple values, then each value gets its own independent animation.
|
||||
* <p>
|
||||
* Example for an animated icon:
|
||||
* <pre>
|
||||
* private class AnimatedMinimalTestIcon
|
||||
* private class MyAnimatedIcon
|
||||
* implements AnimatedIcon
|
||||
* {
|
||||
* @Override public int getIconWidth() { return 100; }
|
||||
* @Override public int getIconHeight() { return 20; }
|
||||
*
|
||||
* @Override
|
||||
* public void paintIconAnimated( Component c, Graphics g, int x, int y, float animatedValue ) {
|
||||
* int w = getIconWidth();
|
||||
* int h = getIconHeight();
|
||||
*
|
||||
* public void paintAnimated( Component c, Graphics2D g, int x, int y, int width, int height, float[] animatedValues ) {
|
||||
* g.setColor( Color.red );
|
||||
* g.drawRect( x, y, w - 1, h - 1 );
|
||||
* g.fillRect( x, y, Math.round( w * animatedValue ), h );
|
||||
* g.drawRect( x, y, width - 1, height - 1 );
|
||||
* g.fillRect( x, y, Math.round( width * animatedValues[0] ), height );
|
||||
* }
|
||||
*
|
||||
* @Override
|
||||
* public float getValue( Component c ) {
|
||||
* return ((AbstractButton)c).isSelected() ? 1 : 0;
|
||||
* public float[] getValues( Component c ) {
|
||||
* return new float[] { ((AbstractButton)c).isSelected() ? 1 : 0 };
|
||||
* }
|
||||
* }
|
||||
*
|
||||
* // sample usage
|
||||
* JCheckBox checkBox = new JCheckBox( "test" );
|
||||
* checkBox.setIcon( new AnimatedMinimalTestIcon() );
|
||||
* checkBox.setIcon( new MyAnimatedIcon() );
|
||||
* </pre>
|
||||
*
|
||||
* Animation works only if the component passed to {@link #paintIcon(Component, Graphics, int, int)}
|
||||
@@ -76,15 +74,13 @@ public interface AnimatedIcon
|
||||
}
|
||||
|
||||
/**
|
||||
* Bridge method that is called from (new) superclass and delegates to
|
||||
* {@link #paintIconAnimated(Component, Graphics, int, int, float)}.
|
||||
* Necessary for API compatibility.
|
||||
* {@inheritDoc}
|
||||
*
|
||||
* @since 2
|
||||
*/
|
||||
@Override
|
||||
default void paintAnimated( Component c, Graphics2D g, int x, int y, int width, int height, float animatedValue ) {
|
||||
paintIconAnimated( c, g, x, y, animatedValue );
|
||||
default void paintAnimated( Component c, Graphics2D g, int x, int y, int width, int height, float[] animatedValues ) {
|
||||
paintIconAnimated( c, g, x, y, animatedValues[0] );
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -97,22 +93,60 @@ public interface AnimatedIcon
|
||||
* @param animatedValue the animated value, which is either equal to what {@link #getValue(Component)}
|
||||
* returned, or somewhere between the previous value and the latest value
|
||||
* that {@link #getValue(Component)} returned
|
||||
*
|
||||
* @deprecated override {@link #paintAnimated(Component, Graphics2D, int, int, int, int, float[])} instead
|
||||
*/
|
||||
void paintIconAnimated( Component c, Graphics g, int x, int y, float animatedValue );
|
||||
@Deprecated
|
||||
default void paintIconAnimated( Component c, Graphics g, int x, int y, float animatedValue ) {
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*
|
||||
* @since 2
|
||||
*/
|
||||
@Override
|
||||
default float[] getValues( Component c ) {
|
||||
return new float[] { getValue( c ) };
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the value of the component.
|
||||
* <p>
|
||||
* This can be any value and depends on the component.
|
||||
* If the value changes, then this class animates from the old value to the new one.
|
||||
* <p>
|
||||
* For a toggle button this could be {@code 0} for off and {@code 1} for on.
|
||||
*
|
||||
* @deprecated override {@link #getValues(Component)} instead
|
||||
*/
|
||||
@Deprecated
|
||||
default float getValue( Component c ) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
//---- class AnimationSupport ---------------------------------------------
|
||||
|
||||
/**
|
||||
* Animation support.
|
||||
*/
|
||||
@Deprecated
|
||||
class AnimationSupport
|
||||
{
|
||||
/**
|
||||
* @deprecated use {@link AnimatedPainter#paintWithAnimation(Component, Graphics, int, int, int, int)} instead
|
||||
*/
|
||||
@Deprecated
|
||||
public static void paintIcon( AnimatedIcon icon, Component c, Graphics g, int x, int y ) {
|
||||
AnimatedPainterSupport.paint( icon, c, g, x, y, icon.getIconWidth(), icon.getIconHeight() );
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated use {@link AnimatedPainter#saveRepaintLocation(AnimatedPainter, Component, int, int)} instead
|
||||
*/
|
||||
@Deprecated
|
||||
public static void saveIconLocation( AnimatedIcon icon, Component c, int x, int y ) {
|
||||
AnimatedPainterSupport.saveLocation( icon, c, x, y );
|
||||
AnimatedPainterSupport.saveRepaintLocation( icon, c, x, y );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,11 +23,12 @@ import javax.swing.JComponent;
|
||||
import com.formdev.flatlaf.util.Animator.Interpolator;
|
||||
|
||||
/**
|
||||
* Painter that automatically animates painting on component value changes.
|
||||
* Painter that automatically animates painting on component value(s) changes.
|
||||
* <p>
|
||||
* {@link #getValue(Component)} returns the value of the component.
|
||||
* If the value changes, then {@link #paintAnimated(Component, Graphics2D, int, int, int, int, float)}
|
||||
* is invoked multiple times with animated value (from old value to new value).
|
||||
* {@link #getValues(Component)} returns the value(s) of the component.
|
||||
* If the value(s) have changed, then {@link #paintAnimated(Component, Graphics2D, int, int, int, int, float[])}
|
||||
* is invoked multiple times with animated value(s) (from old value(s) to new value(s)).
|
||||
* If {@link #getValues(Component)} returns multiple values, then each value gets its own independent animation.
|
||||
* <p>
|
||||
* See {@link AnimatedBorder} or {@link AnimatedIcon} for examples.
|
||||
* <p>
|
||||
@@ -42,11 +43,11 @@ public interface AnimatedPainter
|
||||
{
|
||||
/**
|
||||
* Starts painting.
|
||||
* Either invokes {@link #paintAnimated(Component, Graphics2D, int, int, int, int, float)}
|
||||
* once to paint current value (see {@link #getValue(Component)}. Or if value has
|
||||
* Either invokes {@link #paintAnimated(Component, Graphics2D, int, int, int, int, float[])}
|
||||
* once to paint current value(s) (see {@link #getValues(Component)}. Or if value(s) has
|
||||
* changed, compared to last painting, then it starts an animation and invokes
|
||||
* {@link #paintAnimated(Component, Graphics2D, int, int, int, int, float)}
|
||||
* multiple times with animated value (from old value to new value).
|
||||
* {@link #paintAnimated(Component, Graphics2D, int, int, int, int, float[])}
|
||||
* multiple times with animated value(s) (from old value(s) to new value(s)).
|
||||
*
|
||||
* @param c the component that this painter belongs to
|
||||
* @param g the graphics context
|
||||
@@ -60,7 +61,7 @@ public interface AnimatedPainter
|
||||
}
|
||||
|
||||
/**
|
||||
* Paints the given (animated) value.
|
||||
* Paints the given (animated) value(s).
|
||||
* <p>
|
||||
* Invoked from {@link #paintWithAnimation(Component, Graphics, int, int, int, int)}.
|
||||
*
|
||||
@@ -70,11 +71,11 @@ public interface AnimatedPainter
|
||||
* @param y the y coordinate of the paint area
|
||||
* @param width the width of the paint area
|
||||
* @param height the height of the paint area
|
||||
* @param animatedValue the animated value, which is either equal to what {@link #getValue(Component)}
|
||||
* returned, or somewhere between the previous value and the latest value
|
||||
* that {@link #getValue(Component)} returned
|
||||
* @param animatedValues the animated values, which are either equal to what {@link #getValues(Component)}
|
||||
* returned, or somewhere between the previous values and the latest values
|
||||
* that {@link #getValues(Component)} returned
|
||||
*/
|
||||
void paintAnimated( Component c, Graphics2D g, int x, int y, int width, int height, float animatedValue );
|
||||
void paintAnimated( Component c, Graphics2D g, int x, int y, int width, int height, float[] animatedValues );
|
||||
|
||||
/**
|
||||
* Invoked from animator to repaint an area.
|
||||
@@ -91,14 +92,15 @@ public interface AnimatedPainter
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the value of the component.
|
||||
* Gets the value(s) of the component.
|
||||
* <p>
|
||||
* This can be any value and depends on the component.
|
||||
* If the value changes, then this class animates from the old value to the new one.
|
||||
* If the value(s) changes, then this class animates from the old value(s) to the new ones.
|
||||
* If multiple values are returned, then each value gets its own independent animation.
|
||||
* <p>
|
||||
* For a toggle button this could be {@code 0} for off and {@code 1} for on.
|
||||
*/
|
||||
float getValue( Component c );
|
||||
float[] getValues( Component c );
|
||||
|
||||
/**
|
||||
* Returns whether animation is enabled for this painter (default is {@code true}).
|
||||
@@ -136,4 +138,14 @@ public interface AnimatedPainter
|
||||
default Object getClientPropertyKey() {
|
||||
return getClass();
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves location for repainting animated area with
|
||||
* {@link AnimatedPainter#repaintDuringAnimation(Component, int, int, int, int)}.
|
||||
* Only needed when graphics context passed to {@link #paintWithAnimation(Component, Graphics, int, int, int, int)}
|
||||
* uses transformed location.
|
||||
*/
|
||||
static void saveRepaintLocation( AnimatedPainter painter, Component c, int x, int y ) {
|
||||
AnimatedPainterSupport.saveRepaintLocation( painter, c, x, y );
|
||||
}
|
||||
}
|
||||
|
||||
@@ -49,23 +49,45 @@ class AnimatedPainterSupport
|
||||
// paint without animation if animation is disabled or
|
||||
// component is not a JComponent and therefore does not support
|
||||
// client properties, which are required to keep animation state
|
||||
paintImpl( painter, c, g, x, y, width, height, null );
|
||||
painter.paintAnimated( c, (Graphics2D) g, x, y, width, height, painter.getValues( c ) );
|
||||
return;
|
||||
}
|
||||
|
||||
// get component values
|
||||
float values[] = painter.getValues( c );
|
||||
|
||||
JComponent jc = (JComponent) c;
|
||||
Object key = painter.getClientPropertyKey();
|
||||
AnimatedPainterSupport as = (AnimatedPainterSupport) jc.getClientProperty( key );
|
||||
if( as == null ) {
|
||||
// painted first time --> do not animate, but remember current component value
|
||||
as = new AnimatedPainterSupport();
|
||||
as.startValue = as.targetValue = as.animatedValue = painter.getValue( c );
|
||||
jc.putClientProperty( key, as );
|
||||
} else {
|
||||
// get component value
|
||||
float value = painter.getValue( c );
|
||||
AnimatedPainterSupport[] ass = (AnimatedPainterSupport[]) jc.getClientProperty( key );
|
||||
|
||||
if( value != as.targetValue ) {
|
||||
// check whether length of values array has changed
|
||||
if( ass != null && ass.length != values.length ) {
|
||||
// cancel all running animations
|
||||
for( int i = 0; i < ass.length; i++ ) {
|
||||
AnimatedPainterSupport as = ass[i];
|
||||
if( as.animator != null )
|
||||
as.animator.cancel();
|
||||
}
|
||||
ass = null;
|
||||
}
|
||||
|
||||
if( ass == null ) {
|
||||
ass = new AnimatedPainterSupport[values.length];
|
||||
jc.putClientProperty( key, ass );
|
||||
}
|
||||
|
||||
float[] animatedValues = new float[ass.length];
|
||||
|
||||
for( int i = 0; i < ass.length; i++ ) {
|
||||
AnimatedPainterSupport as = ass[i];
|
||||
float value = values[i];
|
||||
|
||||
if( as == null ) {
|
||||
// painted first time --> do not animate, but remember current component value
|
||||
as = new AnimatedPainterSupport();
|
||||
as.startValue = as.targetValue = as.animatedValue = value;
|
||||
ass[i] = as;
|
||||
} else if( value != as.targetValue ) {
|
||||
// value changed --> (re)start animation
|
||||
|
||||
if( as.animator == null ) {
|
||||
@@ -110,35 +132,33 @@ class AnimatedPainterSupport
|
||||
as.targetValue = value;
|
||||
as.animator.start();
|
||||
}
|
||||
|
||||
as.x = x;
|
||||
as.y = y;
|
||||
as.width = width;
|
||||
as.height = height;
|
||||
|
||||
animatedValues[i] = as.animatedValue;
|
||||
}
|
||||
|
||||
as.x = x;
|
||||
as.y = y;
|
||||
as.width = width;
|
||||
as.height = height;
|
||||
|
||||
paintImpl( painter, c, g, x, y, width, height, as );
|
||||
}
|
||||
|
||||
private static void paintImpl( AnimatedPainter painter, Component c, Graphics g,
|
||||
int x, int y, int width, int height, AnimatedPainterSupport as )
|
||||
{
|
||||
float value = (as != null) ? as.animatedValue : painter.getValue( c );
|
||||
painter.paintAnimated( c, (Graphics2D) g, x, y, width, height, value );
|
||||
painter.paintAnimated( c, (Graphics2D) g, x, y, width, height, animatedValues );
|
||||
}
|
||||
|
||||
private static boolean isAnimationEnabled( AnimatedPainter painter, Component c ) {
|
||||
return Animator.useAnimation() && painter.isAnimationEnabled() && c instanceof JComponent;
|
||||
}
|
||||
|
||||
static void saveLocation( AnimatedPainter painter, Component c, int x, int y ) {
|
||||
static void saveRepaintLocation( AnimatedPainter painter, Component c, int x, int y ) {
|
||||
if( !isAnimationEnabled( painter, c ) )
|
||||
return;
|
||||
|
||||
AnimatedPainterSupport as = (AnimatedPainterSupport) ((JComponent)c).getClientProperty( painter.getClientPropertyKey() );
|
||||
if( as != null ) {
|
||||
as.x = x;
|
||||
as.y = y;
|
||||
AnimatedPainterSupport[] ass = (AnimatedPainterSupport[]) ((JComponent)c).getClientProperty( painter.getClientPropertyKey() );
|
||||
if( ass != null ) {
|
||||
for( int i = 0; i < ass.length; i++ ) {
|
||||
AnimatedPainterSupport as = ass[i];
|
||||
as.x = x;
|
||||
as.y = y;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user