From 3b489e8e1a16817de05a3ab38ab4ceb330b877a1 Mon Sep 17 00:00:00 2001 From: Karl Tauber Date: Mon, 22 Nov 2021 15:42:47 +0100 Subject: [PATCH] AnimatedPainter: support independent animation of multiple values --- .../flatlaf/icons/FlatAbstractIcon.java | 8 +- .../flatlaf/icons/FlatAnimatedIcon.java | 30 +++- .../formdev/flatlaf/util/AnimatedBorder.java | 19 +-- .../formdev/flatlaf/util/AnimatedIcon.java | 74 ++++++--- .../formdev/flatlaf/util/AnimatedPainter.java | 44 +++-- .../flatlaf/util/AnimatedPainterSupport.java | 78 +++++---- .../testing/FlatAnimatedBorderTest.java | 26 +-- .../flatlaf/testing/FlatAnimatedIconTest.java | 150 ++++++++++++++---- .../flatlaf/testing/FlatAnimatedIconTest.jfd | 18 ++- 9 files changed, 325 insertions(+), 122 deletions(-) diff --git a/flatlaf-core/src/main/java/com/formdev/flatlaf/icons/FlatAbstractIcon.java b/flatlaf-core/src/main/java/com/formdev/flatlaf/icons/FlatAbstractIcon.java index 8448f128..db19bcdd 100644 --- a/flatlaf-core/src/main/java/com/formdev/flatlaf/icons/FlatAbstractIcon.java +++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/icons/FlatAbstractIcon.java @@ -69,7 +69,13 @@ public abstract class FlatAbstractIcon } } - protected abstract void paintIcon( Component c, Graphics2D g2 ); + /** + * Paint the icon at {@code [0,0]} location. + *

+ * 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() { diff --git a/flatlaf-core/src/main/java/com/formdev/flatlaf/icons/FlatAnimatedIcon.java b/flatlaf-core/src/main/java/com/formdev/flatlaf/icons/FlatAnimatedIcon.java index 4d80f49b..feff6f66 100644 --- a/flatlaf-core/src/main/java/com/formdev/flatlaf/icons/FlatAnimatedIcon.java +++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/icons/FlatAnimatedIcon.java @@ -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; *

* 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. + *

+ * 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 ); } diff --git a/flatlaf-core/src/main/java/com/formdev/flatlaf/util/AnimatedBorder.java b/flatlaf-core/src/main/java/com/formdev/flatlaf/util/AnimatedBorder.java index b681e729..afb7a8e2 100644 --- a/flatlaf-core/src/main/java/com/formdev/flatlaf/util/AnimatedBorder.java +++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/util/AnimatedBorder.java @@ -25,26 +25,27 @@ import javax.swing.border.Border; /** * Border that automatically animates painting on component value changes. *

- * {@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. *

* Example for an animated border: *

- * 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() );
  * 
* * Animation works only if the component passed to {@link #paintBorder(Component, Graphics, int, int, int, int)} diff --git a/flatlaf-core/src/main/java/com/formdev/flatlaf/util/AnimatedIcon.java b/flatlaf-core/src/main/java/com/formdev/flatlaf/util/AnimatedIcon.java index 5c39d264..5f351bcc 100644 --- a/flatlaf-core/src/main/java/com/formdev/flatlaf/util/AnimatedIcon.java +++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/util/AnimatedIcon.java @@ -25,37 +25,35 @@ import javax.swing.JComponent; /** * Icon that automatically animates painting on component value changes. *

- * {@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. *

* Example for an animated icon: *

- * 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() );
  * 
* * 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. + *

+ * 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. + *

+ * 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 ); } } } diff --git a/flatlaf-core/src/main/java/com/formdev/flatlaf/util/AnimatedPainter.java b/flatlaf-core/src/main/java/com/formdev/flatlaf/util/AnimatedPainter.java index d85e76d2..269e13b9 100644 --- a/flatlaf-core/src/main/java/com/formdev/flatlaf/util/AnimatedPainter.java +++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/util/AnimatedPainter.java @@ -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. *

- * {@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. *

* See {@link AnimatedBorder} or {@link AnimatedIcon} for examples. *

@@ -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). *

* 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. *

* 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. *

* 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 ); + } } diff --git a/flatlaf-core/src/main/java/com/formdev/flatlaf/util/AnimatedPainterSupport.java b/flatlaf-core/src/main/java/com/formdev/flatlaf/util/AnimatedPainterSupport.java index e27ac2e4..0c4d1126 100644 --- a/flatlaf-core/src/main/java/com/formdev/flatlaf/util/AnimatedPainterSupport.java +++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/util/AnimatedPainterSupport.java @@ -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; + } } } } diff --git a/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatAnimatedBorderTest.java b/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatAnimatedBorderTest.java index 348e6343..b4d6501b 100644 --- a/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatAnimatedBorderTest.java +++ b/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatAnimatedBorderTest.java @@ -217,7 +217,8 @@ public class FlatAnimatedBorderTest } @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 ) { + float animatedValue = animatedValues[0]; FlatUIUtils.setRenderingHints( g ); // border width is 1 if not focused and 2 if focused @@ -242,8 +243,8 @@ public class FlatAnimatedBorderTest } @Override - public float getValue( Component c ) { - return FlatUIUtils.isPermanentFocusOwner( c ) ? 1 : 0; + public float[] getValues( Component c ) { + return new float[] { FlatUIUtils.isPermanentFocusOwner( c ) ? 1 : 0 }; } @Override @@ -271,7 +272,8 @@ public class FlatAnimatedBorderTest } @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 ) { + float animatedValue = animatedValues[0]; FlatUIUtils.setRenderingHints( g ); // use paintAtScale1x() for consistent line thickness when scaled @@ -312,8 +314,8 @@ public class FlatAnimatedBorderTest } @Override - public float getValue( Component c ) { - return FlatUIUtils.isPermanentFocusOwner( c ) ? 1 : 0; + public float[] getValues( Component c ) { + return new float[] { FlatUIUtils.isPermanentFocusOwner( c ) ? 1 : 0 }; } @Override @@ -338,9 +340,10 @@ public class FlatAnimatedBorderTest private static final float LABEL_FONT_SCALE = 0.75f; @Override - public void paintAnimated( Component c, Graphics2D g, int x, int y, int width, int height, float animatedValue ) { - super.paintAnimated( c, g, x, y, width, height, animatedValue ); + public void paintAnimated( Component c, Graphics2D g, int x, int y, int width, int height, float[] animatedValues ) { + super.paintAnimated( c, g, x, y, width, height, animatedValues ); + float animatedValue = animatedValues[0]; JComponent jc = (JComponent) c; String label = (String) jc.getClientProperty( LABEL_TEXT_KEY ); if( label == null ) @@ -398,7 +401,8 @@ public class FlatAnimatedBorderTest 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 ) { + float animatedValue = animatedValues[0]; int lh = UIScale.scale( 2 ); g.setColor( Color.blue ); @@ -411,8 +415,8 @@ public class FlatAnimatedBorderTest } @Override - public float getValue( Component c ) { - return FlatUIUtils.isPermanentFocusOwner( c ) ? 1 : 0; + public float[] getValues( Component c ) { + return new float[] { FlatUIUtils.isPermanentFocusOwner( c ) ? 1 : 0 }; } @Override diff --git a/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatAnimatedIconTest.java b/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatAnimatedIconTest.java index 2788711c..b196d97a 100644 --- a/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatAnimatedIconTest.java +++ b/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatAnimatedIconTest.java @@ -18,13 +18,14 @@ package com.formdev.flatlaf.testing; import java.awt.Color; import java.awt.Component; -import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.geom.Ellipse2D; +import java.awt.geom.Path2D; import javax.swing.*; import com.formdev.flatlaf.icons.FlatAnimatedIcon; import com.formdev.flatlaf.util.AnimatedIcon; import com.formdev.flatlaf.util.ColorFunctions; +import com.formdev.flatlaf.util.UIScale; import net.miginfocom.swing.*; /** @@ -38,6 +39,7 @@ public class FlatAnimatedIconTest private static final Color CHART_RADIO_BUTTON_3 = Color.green; private static final Color CHART_CHECK_BOX_1 = Color.magenta; private static final Color CHART_CHECK_BOX_2 = Color.orange; + private static final Color[] CHART_SWITCH_EX = new Color[] { Color.red, Color.green, Color.blue }; private static final String CHART_COLOR_KEY = "chartColor"; @@ -57,6 +59,7 @@ public class FlatAnimatedIconTest radioButton3.setIcon( radioIcon ); checkBox1.setIcon( new AnimatedSwitchIcon() ); + checkBox3.setIcon( new AnimatedSwitchIconEx() ); checkBox2.setIcon( new AnimatedMinimalTestIcon() ); radioButton1.putClientProperty( CHART_COLOR_KEY, CHART_RADIO_BUTTON_1 ); @@ -83,6 +86,7 @@ public class FlatAnimatedIconTest radioButton3ChartColor = new FlatAnimatorTest.JChartColor(); checkBox1 = new JCheckBox(); checkBox1ChartColor = new FlatAnimatorTest.JChartColor(); + checkBox3 = new JCheckBox(); checkBox2 = new JCheckBox(); checkBox2ChartColor = new FlatAnimatorTest.JChartColor(); durationLabel = new JLabel(); @@ -101,6 +105,7 @@ public class FlatAnimatedIconTest "[]para" + "[]" + "[]" + + "[]" + "[grow]" + "[]")); @@ -109,7 +114,7 @@ public class FlatAnimatedIconTest radioButton1.setSelected(true); add(radioButton1, "cell 0 0"); add(radioButton1ChartColor, "cell 1 0"); - add(lineChartPanel, "cell 2 0 1 6"); + add(lineChartPanel, "cell 2 0 1 7"); //---- radioButton2 ---- radioButton2.setText("radio 2"); @@ -126,18 +131,22 @@ public class FlatAnimatedIconTest add(checkBox1, "cell 0 3"); add(checkBox1ChartColor, "cell 1 3"); + //---- checkBox3 ---- + checkBox3.setText("switch ex"); + add(checkBox3, "cell 0 4"); + //---- checkBox2 ---- checkBox2.setText("minimal"); - add(checkBox2, "cell 0 4"); - add(checkBox2ChartColor, "cell 1 4"); + add(checkBox2, "cell 0 5"); + add(checkBox2ChartColor, "cell 1 5"); //---- durationLabel ---- durationLabel.setText("Duration:"); - add(durationLabel, "cell 0 6 3 1"); + add(durationLabel, "cell 0 7 3 1"); //---- durationField ---- durationField.setModel(new SpinnerNumberModel(200, 100, null, 50)); - add(durationField, "cell 0 6 3 1"); + add(durationField, "cell 0 7 3 1"); //---- buttonGroup1 ---- ButtonGroup buttonGroup1 = new ButtonGroup(); @@ -157,6 +166,7 @@ public class FlatAnimatedIconTest private FlatAnimatorTest.JChartColor radioButton3ChartColor; private JCheckBox checkBox1; private FlatAnimatorTest.JChartColor checkBox1ChartColor; + private JCheckBox checkBox3; private JCheckBox checkBox2; private FlatAnimatorTest.JChartColor checkBox2ChartColor; private JLabel durationLabel; @@ -185,7 +195,8 @@ public class FlatAnimatedIconTest } @Override - public void paintIconAnimated( Component c, Graphics g, int x, int y, float animatedValue ) { + public void paintIconAnimated( Component c, Graphics2D g, float[] animatedValues ) { + float animatedValue = animatedValues[0]; Color color = ColorFunctions.mix( onColor, offColor, animatedValue ); // border @@ -201,7 +212,7 @@ public class FlatAnimatedIconTest float dotDiameter = DOT_SIZE * animatedValue; float xy = (SIZE - dotDiameter) / 2f; g.setColor( color ); - ((Graphics2D)g).fill( new Ellipse2D.Float( xy, xy, dotDiameter, dotDiameter ) ); + g.fill( new Ellipse2D.Float( xy, xy, dotDiameter, dotDiameter ) ); if( animatedValue != 0 && animatedValue != 1 ) { Color chartColor = (Color) ((JComponent)c).getClientProperty( CHART_COLOR_KEY ); @@ -210,8 +221,8 @@ public class FlatAnimatedIconTest } @Override - public float getValue( Component c ) { - return ((JRadioButton)c).isSelected() ? 1 : 0; + public float[] getValues( Component c ) { + return new float[] { ((JRadioButton)c).isSelected() ? 1 : 0 }; } @Override @@ -222,7 +233,7 @@ public class FlatAnimatedIconTest //---- class AnimatedSwitchIcon ------------------------------------------- - public class AnimatedSwitchIcon + private class AnimatedSwitchIcon extends FlatAnimatedIcon { private final Color offColor = Color.lightGray; @@ -233,17 +244,20 @@ public class FlatAnimatedIconTest } @Override - public void paintIconAnimated( Component c, Graphics g, int x, int y, float animatedValue ) { + public void paintIconAnimated( Component c, Graphics2D g, float[] animatedValues ) { + float animatedValue = animatedValues[0]; Color color = ColorFunctions.mix( onColor, offColor, animatedValue ); + // paint track g.setColor( color ); - g.fillRoundRect( x, y, width, height, height, height ); + g.fillRoundRect( 0, 0, width, height, height, height ); + // paint thumb int thumbSize = height - 4; - float thumbX = x + 2 + ((width - 4 - thumbSize) * animatedValue); - int thumbY = y + 2; + float thumbX = 2 + ((width - 4 - thumbSize) * animatedValue); + int thumbY = 2; g.setColor( Color.white ); - ((Graphics2D)g).fill( new Ellipse2D.Float( thumbX, thumbY, thumbSize, thumbSize ) ); + g.fill( new Ellipse2D.Float( thumbX, thumbY, thumbSize, thumbSize ) ); if( animatedValue != 0 && animatedValue != 1 ) { Color chartColor = (Color) ((JComponent)c).getClientProperty( CHART_COLOR_KEY ); @@ -252,8 +266,91 @@ public class FlatAnimatedIconTest } @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 }; + } + + @Override + public int getAnimationDuration() { + return (Integer) durationField.getValue(); + } + } + + //---- class AnimatedSwitchIconEx ----------------------------------------- + + private static final int HW = 8; + + private class AnimatedSwitchIconEx + extends FlatAnimatedIcon + { + private final Color offColor = Color.lightGray; + private final Color onColor = Color.red; + private final Color hoverColor = new Color( 0x4400cc00, true ); + private final Color pressedColor = new Color( 0x440000cc, true ); + + public AnimatedSwitchIconEx() { + super( 28 + HW, 16 + HW, null ); + } + + @Override + public void paintIconAnimated( Component c, Graphics2D g, float[] animatedValues ) { + Color color = ColorFunctions.mix( onColor, offColor, animatedValues[0] ); + + int hw2 = HW / 2; + int x = hw2; + int y = hw2; + int width = this.width - HW; + int height = this.height - HW; + + // paint track + g.setColor( color ); + g.fillRoundRect( x, y, width, height, height, height ); + + // paint thumb + int thumbSize = height - 4; + float thumbX = x + 2 + ((width - 4 - thumbSize) * animatedValues[0]); + int thumbY = y + 2; + g.setColor( Color.white ); + g.fill( new Ellipse2D.Float( thumbX, thumbY, thumbSize, thumbSize ) ); + + // paint hover + if( animatedValues[1] > 0 ) { + g.setColor( hoverColor ); + paintHoverOrPressed( g, thumbX, thumbY, thumbSize, animatedValues[1] ); + } + + // paint pressed + if( animatedValues[2] > 0 ) { + g.setColor( pressedColor ); + paintHoverOrPressed( g, thumbX, thumbY, thumbSize, animatedValues[2] ); + } + + for( int i = 0; i < animatedValues.length; i++ ) { + float animatedValue = animatedValues[i]; + if( animatedValue != 0 && animatedValue != 1 ) + lineChartPanel.lineChart.addValue( animatedValue, CHART_SWITCH_EX[i] ); + } + } + + private void paintHoverOrPressed( Graphics2D g, float thumbX, int thumbY, int thumbSize, float animatedValue ) { + float hw = (HW + 4) * animatedValue; + Path2D path = new Path2D.Float( Path2D.WIND_EVEN_ODD ); + path.append( new Ellipse2D.Float( thumbX - (hw / 2), thumbY - (hw / 2), + thumbSize + hw, thumbSize + hw ), false ); + path.append( new Ellipse2D.Float( thumbX, thumbY, thumbSize, thumbSize ), false ); + g.fill( path ); + } + + @Override + public float[] getValues( Component c ) { + AbstractButton b = (AbstractButton) c; + ButtonModel bm = b.getModel(); + + return new float[] { + b.isSelected() ? 1 : 0, + bm.isRollover() ? 1 : 0, + bm.isPressed() ? 1 : 0, + }; } @Override @@ -272,22 +369,21 @@ public class FlatAnimatedIconTest { @Override public int getIconWidth() { - return 100; + return UIScale.scale( 50 ); } @Override public int getIconHeight() { - return 20; + return UIScale.scale( 16 ); } @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 ) { + float animatedValue = animatedValues[0]; 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 * animatedValue ), height ); if( animatedValue != 0 && animatedValue != 1 ) { Color chartColor = (Color) ((JComponent)c).getClientProperty( CHART_COLOR_KEY ); @@ -296,8 +392,8 @@ public class FlatAnimatedIconTest } @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 }; } @Override diff --git a/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatAnimatedIconTest.jfd b/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatAnimatedIconTest.jfd index 8e9b2097..dc62fa60 100644 --- a/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatAnimatedIconTest.jfd +++ b/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatAnimatedIconTest.jfd @@ -6,7 +6,7 @@ new FormModel { add( new FormContainer( "com.formdev.flatlaf.testing.FlatTestPanel", new FormLayoutManager( class net.miginfocom.swing.MigLayout ) { "$layoutConstraints": "insets dialog,hidemode 3" "$columnConstraints": "[][fill]para[grow,fill]" - "$rowConstraints": "[][][]para[][][grow][]" + "$rowConstraints": "[][][]para[][][][grow][]" } ) { name: "this" add( new FormComponent( "javax.swing.JRadioButton" ) { @@ -25,7 +25,7 @@ new FormModel { add( new FormComponent( "com.formdev.flatlaf.testing.FlatAnimatorTest$LineChartPanel" ) { name: "lineChartPanel" }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { - "value": "cell 2 0 1 6" + "value": "cell 2 0 1 7" } ) add( new FormComponent( "javax.swing.JRadioButton" ) { name: "radioButton2" @@ -62,22 +62,28 @@ new FormModel { }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { "value": "cell 1 3" } ) + add( new FormComponent( "javax.swing.JCheckBox" ) { + name: "checkBox3" + "text": "switch ex" + }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { + "value": "cell 0 4" + } ) add( new FormComponent( "javax.swing.JCheckBox" ) { name: "checkBox2" "text": "minimal" }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { - "value": "cell 0 4" + "value": "cell 0 5" } ) add( new FormComponent( "com.formdev.flatlaf.testing.FlatAnimatorTest$JChartColor" ) { name: "checkBox2ChartColor" }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { - "value": "cell 1 4" + "value": "cell 1 5" } ) add( new FormComponent( "javax.swing.JLabel" ) { name: "durationLabel" "text": "Duration:" }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { - "value": "cell 0 6 3 1" + "value": "cell 0 7 3 1" } ) add( new FormComponent( "javax.swing.JSpinner" ) { name: "durationField" @@ -87,7 +93,7 @@ new FormModel { value: 200 } }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { - "value": "cell 0 6 3 1" + "value": "cell 0 7 3 1" } ) }, new FormLayoutConstraints( null ) { "location": new java.awt.Point( 0, 0 )