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 8448f1288..db19bcdd3 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 void paintIcon( Component c, Graphics g, int x, int y ) { } } - 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 4d80f49b0..feff6f66e 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.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 @@ *
* 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 FlatAnimatedIcon( int width, int height, Color color ) { @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 new file mode 100644 index 000000000..afb7a8e22 --- /dev/null +++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/util/AnimatedBorder.java @@ -0,0 +1,81 @@ +/* + * Copyright 2021 FormDev Software GmbH + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.formdev.flatlaf.util; + +import java.awt.Component; +import java.awt.Graphics; +import java.awt.Graphics2D; +import javax.swing.JComponent; +import javax.swing.border.Border; + +/** + * Border that automatically animates painting on component value changes. + *
+ * {@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 MyAnimatedBorder + * implements AnimatedBorder + * { + * @Override + * 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 * animatedValues[0] ), lh ); + * } + * + * @Override + * public float[] getValues( Component c ) { + * return new float[] { c.isFocusOwner() ? 1 : 0 }; + * } + * + * @Override + * public Insets getBorderInsets( Component c ) { + * return UIScale.scale( new Insets( 4, 4, 4, 4 ) ); + * } + * + * @Override public boolean isBorderOpaque() { return false; } + * } + * + * // sample usage + * JTextField textField = new JTextField(); + * textField.setBorder( new MyAnimatedBorder() ); + *+ * + * Animation works only if the component passed to {@link #paintBorder(Component, Graphics, int, int, int, int)} + * is a instance of {@link JComponent}. + * A client property is set on the component to store the animation state. + * + * @author Karl Tauber + * @since 2 + */ +public interface AnimatedBorder + extends Border, AnimatedPainter +{ + /** + * Invokes {@link #paintWithAnimation(Component, Graphics, int, int, int, int)}. + */ + @Override + default void paintBorder( Component c, Graphics g, int x, int y, int width, int height ) { + paintWithAnimation( c, g, x, y, width, height ); + } +} 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 da0b0a1ae..5f351bcc1 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 @@ -18,44 +18,42 @@ import java.awt.Component; import java.awt.Graphics; +import java.awt.Graphics2D; import javax.swing.Icon; import javax.swing.JComponent; -import com.formdev.flatlaf.util.Animator.Interpolator; /** * 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)} @@ -65,15 +63,28 @@ * @author Karl Tauber */ public interface AnimatedIcon - extends Icon + extends Icon, AnimatedPainter { + /** + * Invokes {@link #paintWithAnimation(Component, Graphics, int, int, int, int)}. + */ + @Override + default void paintIcon( Component c, Graphics g, int x, int y ) { + paintWithAnimation( c, g, x, y, getIconWidth(), getIconHeight() ); + } + + /** + * {@inheritDoc} + * + * @since 2 + */ @Override - public default void paintIcon( Component c, Graphics g, int x, int y ) { - AnimationSupport.paintIcon( this, c, g, x, y ); + default void paintAnimated( Component c, Graphics2D g, int x, int y, int width, int height, float[] animatedValues ) { + paintIconAnimated( c, g, x, y, animatedValues[0] ); } /** - * Paints the icon for the given animated value. + * Paints the icon for the given (animated) value. * * @param c the component that this icon belongs to * @param g the graphics context @@ -82,8 +93,22 @@ public default void paintIcon( Component c, Graphics g, int x, int y ) { * @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 + */ + @Deprecated + default void paintIconAnimated( Component c, Graphics g, int x, int y, float animatedValue ) { + } + + /** + * {@inheritDoc} + * + * @since 2 */ - void paintIconAnimated( Component c, Graphics g, int x, int y, float animatedValue ); + @Override + default float[] getValues( Component c ) { + return new float[] { getValue( c ) }; + } /** * Gets the value of the component. @@ -92,158 +117,36 @@ public default void paintIcon( Component c, Graphics g, int x, int y ) { * 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 */ - float getValue( Component c ); - - /** - * Returns whether animation is enabled for this icon (default is {@code true}). - */ - default boolean isAnimationEnabled() { - return true; - } - - /** - * Returns the duration of the animation in milliseconds (default is 150). - */ - default int getAnimationDuration() { - return 150; - } - - /** - * Returns the resolution of the animation in milliseconds (default is 10). - * Resolution is the amount of time between timing events. - */ - default int getAnimationResolution() { - return 10; - } - - /** - * Returns the interpolator for the animation. - * Default is {@link CubicBezierEasing#STANDARD_EASING}. - */ - default Interpolator getAnimationInterpolator() { - return CubicBezierEasing.STANDARD_EASING; - } - - /** - * Returns the client property key used to store the animation support. - */ - default Object getClientPropertyKey() { - return getClass(); + @Deprecated + default float getValue( Component c ) { + return 0; } //---- class AnimationSupport --------------------------------------------- /** - * Animation support class that stores the animation state and implements the animation. + * Animation support. */ + @Deprecated class AnimationSupport { - private float startValue; - private float targetValue; - private float animatedValue; - private float fraction; - - private Animator animator; - - // last x,y coordinates of the icon needed to repaint while animating - private int x; - private int y; - + /** + * @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 ) { - if( !isAnimationEnabled( icon, c ) ) { - // 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 - paintIconImpl( icon, c, g, x, y, null ); - return; - } - - JComponent jc = (JComponent) c; - Object key = icon.getClientPropertyKey(); - AnimationSupport as = (AnimationSupport) jc.getClientProperty( key ); - if( as == null ) { - // painted first time --> do not animate, but remember current component value - as = new AnimationSupport(); - as.startValue = as.targetValue = as.animatedValue = icon.getValue( c ); - as.x = x; - as.y = y; - jc.putClientProperty( key, as ); - } else { - // get component value - float value = icon.getValue( c ); - - if( value != as.targetValue ) { - // value changed --> (re)start animation - - if( as.animator == null ) { - // create animator - AnimationSupport as2 = as; - as.animator = new Animator( icon.getAnimationDuration(), fraction -> { - // check whether component was removed while animation is running - if( !c.isDisplayable() ) { - as2.animator.stop(); - return; - } - - // compute animated value - as2.animatedValue = as2.startValue + ((as2.targetValue - as2.startValue) * fraction); - as2.fraction = fraction; - - // repaint icon - c.repaint( as2.x, as2.y, icon.getIconWidth(), icon.getIconHeight() ); - }, () -> { - as2.startValue = as2.animatedValue = as2.targetValue; - as2.animator = null; - } ); - } - - if( as.animator.isRunning() ) { - // if animation is still running, restart it from the current - // animated value to the new target value with reduced duration - as.animator.cancel(); - int duration2 = (int) (icon.getAnimationDuration() * as.fraction); - if( duration2 > 0 ) - as.animator.setDuration( duration2 ); - as.startValue = as.animatedValue; - } else { - // new animation - as.animator.setDuration( icon.getAnimationDuration() ); - as.animator.setResolution( icon.getAnimationResolution() ); - as.animator.setInterpolator( icon.getAnimationInterpolator() ); - - as.animatedValue = as.startValue; - } - - as.targetValue = value; - as.animator.start(); - } - - as.x = x; - as.y = y; - } - - paintIconImpl( icon, c, g, x, y, as ); - } - - private static void paintIconImpl( AnimatedIcon icon, Component c, Graphics g, int x, int y, AnimationSupport as ) { - float value = (as != null) ? as.animatedValue : icon.getValue( c ); - icon.paintIconAnimated( c, g, x, y, value ); - } - - private static boolean isAnimationEnabled( AnimatedIcon icon, Component c ) { - return Animator.useAnimation() && icon.isAnimationEnabled() && c instanceof JComponent; + 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 ) { - if( !isAnimationEnabled( icon, c ) ) - return; - - AnimationSupport as = (AnimationSupport) ((JComponent)c).getClientProperty( icon.getClientPropertyKey() ); - if( as != null ) { - as.x = x; - as.y = 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 new file mode 100644 index 000000000..0ff83de70 --- /dev/null +++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/util/AnimatedPainter.java @@ -0,0 +1,177 @@ +/* + * Copyright 2021 FormDev Software GmbH + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.formdev.flatlaf.util; + +import java.awt.Component; +import java.awt.Graphics; +import java.awt.Graphics2D; +import javax.swing.JComponent; +import com.formdev.flatlaf.util.Animator.Interpolator; + +/** + * Painter that automatically animates painting on component value(s) changes. + *
+ * {@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. + *
+ * Animation works only if the component passed to {@link #paintWithAnimation(Component, Graphics, int, int, int, int)} + * is a instance of {@link JComponent}. + * A client property is set on the component to store the animation state. + * + * @author Karl Tauber + * @since 2 + */ +public interface AnimatedPainter +{ + /** + * Starts painting. + * 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(s) (from old value(s) to new value(s)). + * + * @param c the component that this painter belongs to + * @param g the graphics context + * @param x the x coordinate of the paint area + * @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 + */ + default void paintWithAnimation( Component c, Graphics g, int x, int y, int width, int height ) { + AnimatedPainterSupport.paint( this, c, g, x, y, width, height ); + } + + /** + * Paints the given (animated) value(s). + *
+ * Invoked from {@link #paintWithAnimation(Component, Graphics, int, int, int, int)}. + * + * @param c the component that this painter belongs to + * @param g the graphics context + * @param x the x coordinate of the paint area + * @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 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[] animatedValues ); + + /** + * Invoked from animator to repaint an area. + *
+ * Useful to limit the repaint region. E.g. if only the bottom border is animated. + * If more than one border side is animated (e.g. bottom and right side), then it + * makes no sense to do separate repaints because the Swing repaint manager unions + * the regions and the whole component is repainted. + *
+ * The default implementation repaints the whole given area. + */ + default void repaintDuringAnimation( Component c, int x, int y, int width, int height ) { + c.repaint( x, y, width, height ); + } + + /** + * Gets the value(s) of the component. + *
+ * This can be any value and depends on the component. + * 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[] getValues( Component c );
+
+ /**
+ * Returns whether animation is enabled for this painter (default is {@code true}).
+ */
+ default boolean isAnimationEnabled() {
+ return true;
+ }
+
+ /**
+ * Returns the duration of the animation in milliseconds (default is 150).
+ */
+ default int getAnimationDuration() {
+ return 150;
+ }
+
+ /**
+ * Returns the resolution of the animation in milliseconds (default is 10).
+ * Resolution is the amount of time between timing events.
+ */
+ default int getAnimationResolution() {
+ return 10;
+ }
+
+ /**
+ * Returns the interpolator for the animation.
+ * Default is {@link CubicBezierEasing#STANDARD_EASING}.
+ */
+ default Interpolator getAnimationInterpolator() {
+ return CubicBezierEasing.STANDARD_EASING;
+ }
+
+ /**
+ * Returns the duration of the animation in milliseconds (default is 150)
+ * for the given value index and value.
+ */
+ default int getAnimationDuration( int valueIndex, float value ) {
+ return getAnimationDuration();
+ }
+
+ /**
+ * Returns the resolution of the animation in milliseconds (default is 10)
+ * for the given value index and value.
+ * Resolution is the amount of time between timing events.
+ */
+ default int getAnimationResolution( int valueIndex, float value ) {
+ return getAnimationResolution();
+ }
+
+ /**
+ * Returns the interpolator for the animation
+ * for the given value index and value.
+ * Default is {@link CubicBezierEasing#STANDARD_EASING}.
+ */
+ default Interpolator getAnimationInterpolator( int valueIndex, float value ) {
+ return getAnimationInterpolator();
+ }
+
+ /**
+ * Returns the client property key used to store the animation support.
+ */
+ 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
new file mode 100644
index 000000000..1492c31fa
--- /dev/null
+++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/util/AnimatedPainterSupport.java
@@ -0,0 +1,182 @@
+/*
+ * Copyright 2021 FormDev Software GmbH
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.formdev.flatlaf.util;
+
+import java.awt.Component;
+import java.awt.Graphics;
+import java.awt.Graphics2D;
+import javax.swing.JComponent;
+
+/**
+ * Animation support class that stores the animation state and implements the animation.
+ *
+ * @author Karl Tauber
+ * @since 2
+ */
+class AnimatedPainterSupport
+{
+ private int valueIndex;
+ private float startValue;
+ private float targetValue;
+ private float animatedValue;
+ private float fraction;
+
+ private Animator animator;
+
+ // last bounds of the paint area needed to repaint while animating
+ private int x;
+ private int y;
+ private int width;
+ private int height;
+
+ static void paint( AnimatedPainter painter, Component c, Graphics g,
+ int x, int y, int width, int height )
+ {
+ if( !isAnimationEnabled( painter, c ) ) {
+ // 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
+ 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[] ass = (AnimatedPainterSupport[]) jc.getClientProperty( key );
+
+ // 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 );
+ }
+
+ 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.valueIndex = i;
+ as.startValue = as.targetValue = as.animatedValue = value;
+ ass[i] = as;
+ } else if( value != as.targetValue ) {
+ // value changed --> (re)start animation
+
+ int animationDuration = painter.getAnimationDuration( as.valueIndex, value );
+
+ // do not animate if animation duration (for current value) is zero
+ if( animationDuration <= 0 ) {
+ if( as.animator != null ) {
+ as.animator.cancel();
+ as.animator = null;
+ }
+ as.startValue = as.targetValue = as.animatedValue = value;
+ as.fraction = 0;
+ continue;
+ }
+
+ if( as.animator == null ) {
+ // create animator
+ AnimatedPainterSupport as2 = as;
+ as.animator = new Animator( 1, fraction -> {
+ // check whether component was removed while animation is running
+ if( !c.isDisplayable() ) {
+ as2.animator.stop();
+ return;
+ }
+
+ // compute animated value
+ as2.animatedValue = as2.startValue + ((as2.targetValue - as2.startValue) * fraction);
+ as2.fraction = fraction;
+
+ // repaint
+ painter.repaintDuringAnimation( c, as2.x, as2.y, as2.width, as2.height );
+ }, () -> {
+ as2.startValue = as2.animatedValue = as2.targetValue;
+ as2.animator = null;
+ } );
+ }
+
+ if( as.animator.isRunning() ) {
+ // if animation is still running, restart it from the current
+ // animated value to the new target value with reduced duration
+ as.animator.cancel();
+ int duration2 = (int) (animationDuration * as.fraction);
+ if( duration2 > 0 )
+ as.animator.setDuration( duration2 );
+ as.startValue = as.animatedValue;
+ } else {
+ // new animation
+ as.animator.setDuration( animationDuration );
+
+ as.animatedValue = as.startValue;
+ }
+
+ // update animator for new value
+ as.animator.setResolution( painter.getAnimationResolution( as.valueIndex, value ) );
+ as.animator.setInterpolator( painter.getAnimationInterpolator( as.valueIndex, value ) );
+
+ // start animation
+ as.targetValue = value;
+ as.animator.start();
+ }
+
+ as.x = x;
+ as.y = y;
+ as.width = width;
+ as.height = height;
+ }
+
+ float[] animatedValues = new float[ass.length];
+ for( int i = 0; i < ass.length; i++ )
+ animatedValues[i] = ass[i].animatedValue;
+
+ 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 saveRepaintLocation( AnimatedPainter painter, Component c, int x, int y ) {
+ if( !isAnimationEnabled( painter, c ) )
+ return;
+
+ 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
new file mode 100644
index 000000000..14f94ea71
--- /dev/null
+++ b/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatAnimatedBorderTest.java
@@ -0,0 +1,437 @@
+/*
+ * Copyright 2021 FormDev Software GmbH
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.formdev.flatlaf.testing;
+
+import java.awt.Color;
+import java.awt.Component;
+import java.awt.FontMetrics;
+import java.awt.Graphics;
+import java.awt.Graphics2D;
+import java.awt.Insets;
+import java.awt.geom.Rectangle2D;
+import javax.swing.*;
+import javax.swing.border.AbstractBorder;
+import com.formdev.flatlaf.ui.FlatUIUtils;
+import com.formdev.flatlaf.util.AnimatedBorder;
+import com.formdev.flatlaf.util.ColorFunctions;
+import com.formdev.flatlaf.util.HiDPIUtils;
+import com.formdev.flatlaf.util.UIScale;
+import net.miginfocom.swing.*;
+
+/**
+ * @author Karl Tauber
+ */
+public class FlatAnimatedBorderTest
+ extends FlatTestPanel
+{
+ private static final Color CHART_FADE_1 = Color.blue;
+ private static final Color CHART_FADE_2 = Color.red;
+ private static final Color CHART_MATERIAL_1 = Color.green;
+ private static final Color CHART_MATERIAL_2 = Color.magenta;
+ private static final Color CHART_MATERIAL_3 = Color.pink;
+ private static final Color CHART_MATERIAL_4 = Color.cyan;
+ private static final Color CHART_MINIMAL = Color.orange;
+
+ private static final String CHART_COLOR_KEY = "chartColor";
+
+ public static void main( String[] args ) {
+ SwingUtilities.invokeLater( () -> {
+ FlatTestFrame frame = FlatTestFrame.create( args, "FlatAnimatedBorderTest" );
+ frame.showFrame( FlatAnimatedBorderTest::new );
+ } );
+ }
+
+ FlatAnimatedBorderTest() {
+ initComponents();
+
+ fade1TextField.setBorder( new AnimatedFocusFadeBorder() );
+ fade2TextField.setBorder( new AnimatedFocusFadeBorder() );
+
+ material1TextField.setBorder( new AnimatedMaterialBorder() );
+ material2TextField.setBorder( new AnimatedMaterialBorder() );
+ material3TextField.setBorder( new AnimatedMaterialLabeledBorder() );
+ material4TextField.setBorder( new AnimatedMaterialLabeledBorder() );
+
+ minimalTextField.setBorder( new AnimatedMinimalTestBorder() );
+
+ fade1TextField.putClientProperty( CHART_COLOR_KEY, CHART_FADE_1 );
+ fade2TextField.putClientProperty( CHART_COLOR_KEY, CHART_FADE_2 );
+ material1TextField.putClientProperty( CHART_COLOR_KEY, CHART_MATERIAL_1 );
+ material2TextField.putClientProperty( CHART_COLOR_KEY, CHART_MATERIAL_2 );
+ material3TextField.putClientProperty( CHART_COLOR_KEY, CHART_MATERIAL_3 );
+ material4TextField.putClientProperty( CHART_COLOR_KEY, CHART_MATERIAL_4 );
+ minimalTextField.putClientProperty( CHART_COLOR_KEY, CHART_MINIMAL );
+
+ fade1ChartColor.setForeground( CHART_FADE_1 );
+ fade2ChartColor.setForeground( CHART_FADE_2 );
+ material1ChartColor.setForeground( CHART_MATERIAL_1 );
+ material2ChartColor.setForeground( CHART_MATERIAL_2 );
+ material3ChartColor.setForeground( CHART_MATERIAL_3 );
+ material4ChartColor.setForeground( CHART_MATERIAL_4 );
+ minimalChartColor.setForeground( CHART_MINIMAL );
+
+ material3TextField.putClientProperty( AnimatedMaterialLabeledBorder.LABEL_TEXT_KEY, "Label" );
+ material4TextField.putClientProperty( AnimatedMaterialLabeledBorder.LABEL_TEXT_KEY, "Label" );
+ material4TextField.setText( "Text" );
+ }
+
+ private void initComponents() {
+ // JFormDesigner - Component initialization - DO NOT MODIFY //GEN-BEGIN:initComponents
+ label3 = new JLabel();
+ lineChartPanel = new FlatAnimatorTest.LineChartPanel();
+ fade1TextField = new JTextField();
+ fade1ChartColor = new FlatAnimatorTest.JChartColor();
+ fade2TextField = new JTextField();
+ fade2ChartColor = new FlatAnimatorTest.JChartColor();
+ label2 = new JLabel();
+ material1TextField = new JTextField();
+ material1ChartColor = new FlatAnimatorTest.JChartColor();
+ material2TextField = new JTextField();
+ material2ChartColor = new FlatAnimatorTest.JChartColor();
+ material3TextField = new JTextField();
+ material3ChartColor = new FlatAnimatorTest.JChartColor();
+ material4TextField = new JTextField();
+ material4ChartColor = new FlatAnimatorTest.JChartColor();
+ label1 = new JLabel();
+ minimalTextField = new JTextField();
+ minimalChartColor = new FlatAnimatorTest.JChartColor();
+ durationLabel = new JLabel();
+ durationField = new JSpinner();
+
+ //======== this ========
+ setLayout(new MigLayout(
+ "insets dialog,hidemode 3",
+ // columns
+ "[fill]" +
+ "[fill]para" +
+ "[fill]",
+ // rows
+ "[]" +
+ "[]" +
+ "[]para" +
+ "[]" +
+ "[]" +
+ "[]" +
+ "[]" +
+ "[]para" +
+ "[]" +
+ "[]" +
+ "[grow]" +
+ "[]"));
+
+ //---- label3 ----
+ label3.setText("Fade:");
+ add(label3, "cell 0 0");
+ add(lineChartPanel, "cell 2 0 1 12");
+ add(fade1TextField, "cell 0 1");
+ add(fade1ChartColor, "cell 1 1");
+ add(fade2TextField, "cell 0 2");
+ add(fade2ChartColor, "cell 1 2");
+
+ //---- label2 ----
+ label2.setText("Material:");
+ add(label2, "cell 0 3");
+ add(material1TextField, "cell 0 4");
+ add(material1ChartColor, "cell 1 4");
+ add(material2TextField, "cell 0 5");
+ add(material2ChartColor, "cell 1 5");
+
+ //---- material3TextField ----
+ material3TextField.putClientProperty("FlatLaf.styleClass", "large");
+ add(material3TextField, "cell 0 6");
+ add(material3ChartColor, "cell 1 6");
+
+ //---- material4TextField ----
+ material4TextField.putClientProperty("FlatLaf.styleClass", "large");
+ add(material4TextField, "cell 0 7");
+ add(material4ChartColor, "cell 1 7");
+
+ //---- label1 ----
+ label1.setText("Minimal:");
+ add(label1, "cell 0 8");
+ add(minimalTextField, "cell 0 9");
+ add(minimalChartColor, "cell 1 9");
+
+ //---- durationLabel ----
+ durationLabel.setText("Duration:");
+ add(durationLabel, "cell 0 11");
+
+ //---- durationField ----
+ durationField.setModel(new SpinnerNumberModel(200, 0, null, 50));
+ add(durationField, "cell 0 11");
+ // JFormDesigner - End of component initialization //GEN-END:initComponents
+ }
+
+ // JFormDesigner - Variables declaration - DO NOT MODIFY //GEN-BEGIN:variables
+ private JLabel label3;
+ private FlatAnimatorTest.LineChartPanel lineChartPanel;
+ private JTextField fade1TextField;
+ private FlatAnimatorTest.JChartColor fade1ChartColor;
+ private JTextField fade2TextField;
+ private FlatAnimatorTest.JChartColor fade2ChartColor;
+ private JLabel label2;
+ private JTextField material1TextField;
+ private FlatAnimatorTest.JChartColor material1ChartColor;
+ private JTextField material2TextField;
+ private FlatAnimatorTest.JChartColor material2ChartColor;
+ private JTextField material3TextField;
+ private FlatAnimatorTest.JChartColor material3ChartColor;
+ private JTextField material4TextField;
+ private FlatAnimatorTest.JChartColor material4ChartColor;
+ private JLabel label1;
+ private JTextField minimalTextField;
+ private FlatAnimatorTest.JChartColor minimalChartColor;
+ private JLabel durationLabel;
+ private JSpinner durationField;
+ // JFormDesigner - End of variables declaration //GEN-END:variables
+
+ //---- class AnimatedMaterialBorder ---------------------------------------
+
+ /**
+ * Experimental text field border that:
+ * - animates focus indicator color and border width
+ */
+ private class AnimatedFocusFadeBorder
+ extends AbstractBorder
+ implements AnimatedBorder
+ {
+ // needed because otherwise the empty paint method in superclass
+ // javax.swing.border.AbstractBorder would be used
+ @Override
+ public void paintBorder( Component c, Graphics g, int x, int y, int width, int height ) {
+ paintWithAnimation( c, g, x, y, width, height );
+ }
+
+ @Override
+ 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
+ float lw = UIScale.scale( 1 + animatedValue );
+
+ // paint border
+ Color color = ColorFunctions.mix( Color.red, Color.lightGray, animatedValue );
+ FlatUIUtils.paintOutlinedComponent( g, x, y, width, height, 0, 0, 0, lw, 0,
+ null, color, null );
+
+ if( animatedValue != 0 && animatedValue != 1 ) {
+ Color chartColor = (Color) ((JComponent)c).getClientProperty( CHART_COLOR_KEY );
+ lineChartPanel.lineChart.addValue( animatedValue, chartColor );
+ }
+ }
+
+ @Override
+ public Insets getBorderInsets( Component c, Insets insets ) {
+ insets.top = insets.bottom = UIScale.scale( 3 );
+ insets.left = insets.right = UIScale.scale( 7 );
+ return insets;
+ }
+
+ @Override
+ public float[] getValues( Component c ) {
+ return new float[] { FlatUIUtils.isPermanentFocusOwner( c ) ? 1 : 0 };
+ }
+
+ @Override
+ public int getAnimationDuration() {
+ return (Integer) durationField.getValue();
+ }
+ }
+
+ //---- class AnimatedMaterialBorder ---------------------------------------
+
+ /**
+ * Experimental text field border that:
+ * - paint border only at bottom
+ * - animates focus indicator at bottom
+ */
+ private class AnimatedMaterialBorder
+ extends AbstractBorder
+ implements AnimatedBorder
+ {
+ // needed because otherwise the empty paint method in superclass
+ // javax.swing.border.AbstractBorder would be used
+ @Override
+ public void paintBorder( Component c, Graphics g, int x, int y, int width, int height ) {
+ paintWithAnimation( c, g, x, y, width, height );
+ }
+
+ @Override
+ 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
+ HiDPIUtils.paintAtScale1x( g, x, y, width, height,
+ (g2d, x2, y2, width2, height2, scaleFactor) -> {
+ float lh = (float) (UIScale.scale( 1f ) * scaleFactor);
+
+ g2d.setColor( Color.gray );
+ g2d.fill( new Rectangle2D.Float( x2, y2 + height2 - lh, width2, lh ) );
+
+ if( animatedValue > 0 ) {
+ lh = (float) (UIScale.scale( 2f ) * scaleFactor);
+ int lw = Math.round( width2 * animatedValue );
+
+ g2d.setColor( Color.red );
+ g2d.fill( new Rectangle2D.Float( x2 + (width2 - lw) / 2, y2 + height2 - lh, lw, lh ) );
+ }
+ } );
+
+ if( animatedValue != 0 && animatedValue != 1 ) {
+ Color chartColor = (Color) ((JComponent)c).getClientProperty( CHART_COLOR_KEY );
+ lineChartPanel.lineChart.addValue( animatedValue, chartColor );
+ }
+ }
+
+ @Override
+ public void repaintDuringAnimation( Component c, int x, int y, int width, int height ) {
+ // limit repaint to bottom border
+ int lh = UIScale.scale( 2 );
+ c.repaint( x, y + height - lh, width, lh );
+ }
+
+ @Override
+ public Insets getBorderInsets( Component c, Insets insets ) {
+ insets.top = insets.bottom = UIScale.scale( 3 );
+ insets.left = insets.right = UIScale.scale( 7 );
+ return insets;
+ }
+
+ @Override
+ public float[] getValues( Component c ) {
+ return new float[] { FlatUIUtils.isPermanentFocusOwner( c ) ? 1 : 0 };
+ }
+
+ @Override
+ public int getAnimationDuration() {
+ return (Integer) durationField.getValue();
+ }
+ }
+
+ //---- class AnimatedMaterialLabeledBorder --------------------------------
+
+ /**
+ * Experimental text field border that:
+ * - paints a label above the text, or in center if text field is empty
+ * - paint border only at bottom
+ * - animates focus indicator at bottom
+ */
+ private class AnimatedMaterialLabeledBorder
+ extends AnimatedMaterialBorder
+ {
+ static final String LABEL_TEXT_KEY = "JTextField.labelText";
+
+ 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[] 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 )
+ return;
+
+ FontMetrics fm = c.getFontMetrics( c.getFont() );
+ int labelFontHeight = Math.round( fm.getHeight() * LABEL_FONT_SCALE );
+
+ int tx = UIScale.scale( 7 );
+ int ty = y + labelFontHeight - UIScale.scale( 2 );
+ float sf = LABEL_FONT_SCALE;
+
+ if( ((JTextField)c).getDocument().getLength() == 0 ) {
+ // paint label in center of text field if it is empty
+ int ty2 = ((height - fm.getHeight()) / 2) + labelFontHeight;
+ ty += (ty2 - ty) * (1 - animatedValue);
+ sf += (1 - LABEL_FONT_SCALE) * (1 - animatedValue);
+ }
+
+ Graphics2D g2 = (Graphics2D) g.create();
+ try {
+ g2.translate( tx, ty );
+ g2.scale( sf, sf );
+ g2.setColor( ColorFunctions.mix( Color.red, Color.gray, animatedValue ) );
+
+ FlatUIUtils.drawString( jc, g2, label, 0, 0 );
+ } finally {
+ g2.dispose();
+ }
+ }
+
+ @Override
+ public void repaintDuringAnimation( Component c, int x, int y, int width, int height ) {
+ c.repaint( x, y, width, height );
+ }
+
+ @Override
+ public Insets getBorderInsets( Component c, Insets insets ) {
+ super.getBorderInsets( c, insets );
+
+ FontMetrics fm = c.getFontMetrics( c.getFont() );
+ int labelFontHeight = Math.round( fm.getHeight() * LABEL_FONT_SCALE );
+ insets.top = labelFontHeight;
+ insets.bottom = UIScale.scale( 5 );
+ return insets;
+ }
+ }
+
+ //---- class AnimatedMinimalTestBorder ------------------------------------
+
+ /**
+ * Minimal example for an animated border.
+ */
+ private class AnimatedMinimalTestBorder
+ implements AnimatedBorder
+ {
+ @Override
+ 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 );
+ g.fillRect( x, y + height - lh, Math.round( width * animatedValue ), lh );
+
+ if( animatedValue != 0 && animatedValue != 1 ) {
+ Color chartColor = (Color) ((JComponent)c).getClientProperty( CHART_COLOR_KEY );
+ lineChartPanel.lineChart.addValue( animatedValue, chartColor );
+ }
+ }
+
+ @Override
+ public float[] getValues( Component c ) {
+ return new float[] { FlatUIUtils.isPermanentFocusOwner( c ) ? 1 : 0 };
+ }
+
+ @Override
+ public int getAnimationDuration() {
+ return (Integer) durationField.getValue();
+ }
+
+ @Override
+ public Insets getBorderInsets( Component c ) {
+ return UIScale.scale( new Insets( 3, 7, 3, 7 ) );
+ }
+
+ @Override
+ public boolean isBorderOpaque() {
+ return false;
+ }
+ }
+}
diff --git a/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatAnimatedBorderTest.jfd b/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatAnimatedBorderTest.jfd
new file mode 100644
index 000000000..4c32e5edc
--- /dev/null
+++ b/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatAnimatedBorderTest.jfd
@@ -0,0 +1,128 @@
+JFDML JFormDesigner: "7.0.5.0.382" Java: "16" encoding: "UTF-8"
+
+new FormModel {
+ contentType: "form/swing"
+ root: new FormRoot {
+ add( new FormContainer( "com.formdev.flatlaf.testing.FlatTestPanel", new FormLayoutManager( class net.miginfocom.swing.MigLayout ) {
+ "$layoutConstraints": "insets dialog,hidemode 3"
+ "$columnConstraints": "[fill][fill]para[fill]"
+ "$rowConstraints": "[][][]para[][][][][]para[][][grow][]"
+ } ) {
+ name: "this"
+ add( new FormComponent( "javax.swing.JLabel" ) {
+ name: "label3"
+ "text": "Fade:"
+ }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
+ "value": "cell 0 0"
+ } )
+ add( new FormComponent( "com.formdev.flatlaf.testing.FlatAnimatorTest$LineChartPanel" ) {
+ name: "lineChartPanel"
+ }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
+ "value": "cell 2 0 1 12"
+ } )
+ add( new FormComponent( "javax.swing.JTextField" ) {
+ name: "fade1TextField"
+ }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
+ "value": "cell 0 1"
+ } )
+ add( new FormComponent( "com.formdev.flatlaf.testing.FlatAnimatorTest$JChartColor" ) {
+ name: "fade1ChartColor"
+ }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
+ "value": "cell 1 1"
+ } )
+ add( new FormComponent( "javax.swing.JTextField" ) {
+ name: "fade2TextField"
+ }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
+ "value": "cell 0 2"
+ } )
+ add( new FormComponent( "com.formdev.flatlaf.testing.FlatAnimatorTest$JChartColor" ) {
+ name: "fade2ChartColor"
+ }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
+ "value": "cell 1 2"
+ } )
+ add( new FormComponent( "javax.swing.JLabel" ) {
+ name: "label2"
+ "text": "Material:"
+ }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
+ "value": "cell 0 3"
+ } )
+ add( new FormComponent( "javax.swing.JTextField" ) {
+ name: "material1TextField"
+ }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
+ "value": "cell 0 4"
+ } )
+ add( new FormComponent( "com.formdev.flatlaf.testing.FlatAnimatorTest$JChartColor" ) {
+ name: "material1ChartColor"
+ }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
+ "value": "cell 1 4"
+ } )
+ add( new FormComponent( "javax.swing.JTextField" ) {
+ name: "material2TextField"
+ }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
+ "value": "cell 0 5"
+ } )
+ add( new FormComponent( "com.formdev.flatlaf.testing.FlatAnimatorTest$JChartColor" ) {
+ name: "material2ChartColor"
+ }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
+ "value": "cell 1 5"
+ } )
+ add( new FormComponent( "javax.swing.JTextField" ) {
+ name: "material3TextField"
+ "$client.FlatLaf.styleClass": "large"
+ }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
+ "value": "cell 0 6"
+ } )
+ add( new FormComponent( "com.formdev.flatlaf.testing.FlatAnimatorTest$JChartColor" ) {
+ name: "material3ChartColor"
+ }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
+ "value": "cell 1 6"
+ } )
+ add( new FormComponent( "javax.swing.JTextField" ) {
+ name: "material4TextField"
+ "$client.FlatLaf.styleClass": "large"
+ }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
+ "value": "cell 0 7"
+ } )
+ add( new FormComponent( "com.formdev.flatlaf.testing.FlatAnimatorTest$JChartColor" ) {
+ name: "material4ChartColor"
+ }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
+ "value": "cell 1 7"
+ } )
+ add( new FormComponent( "javax.swing.JLabel" ) {
+ name: "label1"
+ "text": "Minimal:"
+ }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
+ "value": "cell 0 8"
+ } )
+ add( new FormComponent( "javax.swing.JTextField" ) {
+ name: "minimalTextField"
+ }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
+ "value": "cell 0 9"
+ } )
+ add( new FormComponent( "com.formdev.flatlaf.testing.FlatAnimatorTest$JChartColor" ) {
+ name: "minimalChartColor"
+ }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
+ "value": "cell 1 9"
+ } )
+ add( new FormComponent( "javax.swing.JLabel" ) {
+ name: "durationLabel"
+ "text": "Duration:"
+ }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
+ "value": "cell 0 11"
+ } )
+ add( new FormComponent( "javax.swing.JSpinner" ) {
+ name: "durationField"
+ "model": new javax.swing.SpinnerNumberModel {
+ minimum: 0
+ stepSize: 50
+ value: 200
+ }
+ }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
+ "value": "cell 0 11"
+ } )
+ }, new FormLayoutConstraints( null ) {
+ "location": new java.awt.Point( 0, 0 )
+ "size": new java.awt.Dimension( 725, 325 )
+ } )
+ }
+}
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 160caca65..49e80740e 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 @@
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.*;
/**
@@ -33,6 +34,15 @@
public class FlatAnimatedIconTest
extends FlatTestPanel
{
+ private static final Color CHART_RADIO_BUTTON_1 = Color.blue;
+ private static final Color CHART_RADIO_BUTTON_2 = Color.red;
+ 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";
+
public static void main( String[] args ) {
SwingUtilities.invokeLater( () -> {
FlatTestFrame frame = FlatTestFrame.create( args, "FlatAnimatedIconTest" );
@@ -49,16 +59,36 @@ public static void main( String[] args ) {
radioButton3.setIcon( radioIcon );
checkBox1.setIcon( new AnimatedSwitchIcon() );
+ checkBox3.setIcon( new AnimatedSwitchIconEx() );
checkBox2.setIcon( new AnimatedMinimalTestIcon() );
+
+ radioButton1.putClientProperty( CHART_COLOR_KEY, CHART_RADIO_BUTTON_1 );
+ radioButton2.putClientProperty( CHART_COLOR_KEY, CHART_RADIO_BUTTON_2 );
+ radioButton3.putClientProperty( CHART_COLOR_KEY, CHART_RADIO_BUTTON_3 );
+ checkBox1.putClientProperty( CHART_COLOR_KEY, CHART_CHECK_BOX_1 );
+ checkBox2.putClientProperty( CHART_COLOR_KEY, CHART_CHECK_BOX_2 );
+
+ radioButton1ChartColor.setForeground( CHART_RADIO_BUTTON_1 );
+ radioButton2ChartColor.setForeground( CHART_RADIO_BUTTON_2 );
+ radioButton3ChartColor.setForeground( CHART_RADIO_BUTTON_3 );
+ checkBox1ChartColor.setForeground( CHART_CHECK_BOX_1 );
+ checkBox2ChartColor.setForeground( CHART_CHECK_BOX_2 );
}
private void initComponents() {
// JFormDesigner - Component initialization - DO NOT MODIFY //GEN-BEGIN:initComponents
radioButton1 = new JRadioButton();
+ radioButton1ChartColor = new FlatAnimatorTest.JChartColor();
+ lineChartPanel = new FlatAnimatorTest.LineChartPanel();
radioButton2 = new JRadioButton();
+ radioButton2ChartColor = new FlatAnimatorTest.JChartColor();
radioButton3 = new JRadioButton();
+ radioButton3ChartColor = new FlatAnimatorTest.JChartColor();
checkBox1 = new JCheckBox();
+ checkBox1ChartColor = new FlatAnimatorTest.JChartColor();
+ checkBox3 = new JCheckBox();
checkBox2 = new JCheckBox();
+ checkBox2ChartColor = new FlatAnimatorTest.JChartColor();
durationLabel = new JLabel();
durationField = new JSpinner();
@@ -66,14 +96,16 @@ private void initComponents() {
setLayout(new MigLayout(
"insets dialog,hidemode 3",
// columns
- "[]para" +
- "[fill]",
+ "[]" +
+ "[fill]para" +
+ "[grow,fill]",
// rows
"[]" +
"[]" +
"[]para" +
"[]" +
"[]" +
+ "[]" +
"[grow]" +
"[]"));
@@ -81,30 +113,40 @@ private void initComponents() {
radioButton1.setText("radio 1");
radioButton1.setSelected(true);
add(radioButton1, "cell 0 0");
+ add(radioButton1ChartColor, "cell 1 0");
+ add(lineChartPanel, "cell 2 0 1 7");
//---- radioButton2 ----
radioButton2.setText("radio 2");
add(radioButton2, "cell 0 1");
+ add(radioButton2ChartColor, "cell 1 1");
//---- radioButton3 ----
radioButton3.setText("radio 3");
add(radioButton3, "cell 0 2");
+ add(radioButton3ChartColor, "cell 1 2");
//---- checkBox1 ----
checkBox1.setText("switch");
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(checkBox2, "cell 0 5");
+ add(checkBox2ChartColor, "cell 1 5");
//---- durationLabel ----
durationLabel.setText("Duration:");
- add(durationLabel, "cell 0 6 2 1");
+ add(durationLabel, "cell 0 7 3 1");
//---- durationField ----
- durationField.setModel(new SpinnerNumberModel(200, 100, null, 50));
- add(durationField, "cell 0 6 2 1");
+ durationField.setModel(new SpinnerNumberModel(200, 0, null, 50));
+ add(durationField, "cell 0 7 3 1");
//---- buttonGroup1 ----
ButtonGroup buttonGroup1 = new ButtonGroup();
@@ -116,10 +158,17 @@ private void initComponents() {
// JFormDesigner - Variables declaration - DO NOT MODIFY //GEN-BEGIN:variables
private JRadioButton radioButton1;
+ private FlatAnimatorTest.JChartColor radioButton1ChartColor;
+ private FlatAnimatorTest.LineChartPanel lineChartPanel;
private JRadioButton radioButton2;
+ private FlatAnimatorTest.JChartColor radioButton2ChartColor;
private JRadioButton radioButton3;
+ private FlatAnimatorTest.JChartColor radioButton3ChartColor;
private JCheckBox checkBox1;
+ private FlatAnimatorTest.JChartColor checkBox1ChartColor;
+ private JCheckBox checkBox3;
private JCheckBox checkBox2;
+ private FlatAnimatorTest.JChartColor checkBox2ChartColor;
private JLabel durationLabel;
private JSpinner durationField;
// JFormDesigner - End of variables declaration //GEN-END:variables
@@ -146,7 +195,8 @@ public AnimatedRadioButtonIcon() {
}
@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
@@ -162,12 +212,17 @@ public void paintIconAnimated( Component c, Graphics g, int x, int y, float anim
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 );
+ lineChartPanel.lineChart.addValue( animatedValue, chartColor );
+ }
}
@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
@@ -178,7 +233,7 @@ public int getAnimationDuration() {
//---- class AnimatedSwitchIcon -------------------------------------------
- public class AnimatedSwitchIcon
+ private class AnimatedSwitchIcon
extends FlatAnimatedIcon
{
private final Color offColor = Color.lightGray;
@@ -189,22 +244,113 @@ public AnimatedSwitchIcon() {
}
@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( 0, 0, width, height, height, height );
+
+ // paint thumb
+ int thumbSize = height - 4;
+ float thumbX = 2 + ((width - 4 - thumbSize) * animatedValue);
+ int thumbY = 2;
+ g.setColor( Color.white );
+ g.fill( new Ellipse2D.Float( thumbX, thumbY, thumbSize, thumbSize ) );
+
+ if( animatedValue != 0 && animatedValue != 1 ) {
+ Color chartColor = (Color) ((JComponent)c).getClientProperty( CHART_COLOR_KEY );
+ lineChartPanel.lineChart.addValue( animatedValue, chartColor );
+ }
+ }
+
+ @Override
+ 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) * animatedValue);
+ float thumbX = x + 2 + ((width - 4 - thumbSize) * animatedValues[0]);
int thumbY = y + 2;
g.setColor( Color.white );
- ((Graphics2D)g).fill( new Ellipse2D.Float( thumbX, thumbY, thumbSize, thumbSize ) );
+ 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 getValue( Component c ) {
- return ((AbstractButton)c).isSelected() ? 1 : 0;
+ 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
@@ -223,27 +369,31 @@ private class AnimatedMinimalTestIcon
{
@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 );
+ lineChartPanel.lineChart.addValue( animatedValue, chartColor );
+ }
}
@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 11c55e811..62a2ae327 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
@@ -1,12 +1,12 @@
-JFDML JFormDesigner: "7.0.2.0.298" Java: "15" encoding: "UTF-8"
+JFDML JFormDesigner: "7.0.5.0.382" Java: "16" encoding: "UTF-8"
new FormModel {
contentType: "form/swing"
root: new FormRoot {
add( new FormContainer( "com.formdev.flatlaf.testing.FlatTestPanel", new FormLayoutManager( class net.miginfocom.swing.MigLayout ) {
"$layoutConstraints": "insets dialog,hidemode 3"
- "$columnConstraints": "[]para[fill]"
- "$rowConstraints": "[][][]para[][][grow][]"
+ "$columnConstraints": "[][fill]para[grow,fill]"
+ "$rowConstraints": "[][][]para[][][][grow][]"
} ) {
name: "this"
add( new FormComponent( "javax.swing.JRadioButton" ) {
@@ -17,6 +17,16 @@ new FormModel {
}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
"value": "cell 0 0"
} )
+ add( new FormComponent( "com.formdev.flatlaf.testing.FlatAnimatorTest$JChartColor" ) {
+ name: "radioButton1ChartColor"
+ }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
+ "value": "cell 1 0"
+ } )
+ add( new FormComponent( "com.formdev.flatlaf.testing.FlatAnimatorTest$LineChartPanel" ) {
+ name: "lineChartPanel"
+ }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
+ "value": "cell 2 0 1 7"
+ } )
add( new FormComponent( "javax.swing.JRadioButton" ) {
name: "radioButton2"
"text": "radio 2"
@@ -24,6 +34,11 @@ new FormModel {
}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
"value": "cell 0 1"
} )
+ add( new FormComponent( "com.formdev.flatlaf.testing.FlatAnimatorTest$JChartColor" ) {
+ name: "radioButton2ChartColor"
+ }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
+ "value": "cell 1 1"
+ } )
add( new FormComponent( "javax.swing.JRadioButton" ) {
name: "radioButton3"
"text": "radio 3"
@@ -31,37 +46,58 @@ new FormModel {
}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
"value": "cell 0 2"
} )
+ add( new FormComponent( "com.formdev.flatlaf.testing.FlatAnimatorTest$JChartColor" ) {
+ name: "radioButton3ChartColor"
+ }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
+ "value": "cell 1 2"
+ } )
add( new FormComponent( "javax.swing.JCheckBox" ) {
name: "checkBox1"
"text": "switch"
}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
"value": "cell 0 3"
} )
+ add( new FormComponent( "com.formdev.flatlaf.testing.FlatAnimatorTest$JChartColor" ) {
+ name: "checkBox1ChartColor"
+ }, 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 5"
} )
add( new FormComponent( "javax.swing.JLabel" ) {
name: "durationLabel"
"text": "Duration:"
}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
- "value": "cell 0 6 2 1"
+ "value": "cell 0 7 3 1"
} )
add( new FormComponent( "javax.swing.JSpinner" ) {
name: "durationField"
"model": new javax.swing.SpinnerNumberModel {
- minimum: 100
+ minimum: 0
stepSize: 50
value: 200
}
}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
- "value": "cell 0 6 2 1"
+ "value": "cell 0 7 3 1"
} )
}, new FormLayoutConstraints( null ) {
"location": new java.awt.Point( 0, 0 )
- "size": new java.awt.Dimension( 415, 350 )
+ "size": new java.awt.Dimension( 810, 350 )
} )
add( new FormNonVisual( "javax.swing.ButtonGroup" ) {
name: "buttonGroup1"
diff --git a/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatAnimatorTest.java b/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatAnimatorTest.java
index 831a132d7..11928d854 100644
--- a/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatAnimatorTest.java
+++ b/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatAnimatorTest.java
@@ -17,9 +17,19 @@
package com.formdev.flatlaf.testing;
import java.awt.*;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
import javax.swing.*;
+import com.formdev.flatlaf.FlatLaf;
+import com.formdev.flatlaf.ui.FlatUIUtils;
import com.formdev.flatlaf.util.Animator;
import com.formdev.flatlaf.util.CubicBezierEasing;
+import com.formdev.flatlaf.util.HSLColor;
+import com.formdev.flatlaf.util.HiDPIUtils;
+import com.formdev.flatlaf.util.UIScale;
+import com.formdev.flatlaf.util.Animator.Interpolator;
import net.miginfocom.swing.*;
/**
@@ -28,8 +38,13 @@
public class FlatAnimatorTest
extends FlatTestPanel
{
+ private static final Color CHART_LINEAR = Color.blue;
+ private static final Color CHART_EASE_IN_OUT = Color.magenta;
+ private static final Color CHART_STANDARD_EASING = Color.red;
+
private Animator linearAnimator;
private Animator easeInOutAnimator;
+ private Animator standardEasingAnimator;
public static void main( String[] args ) {
SwingUtilities.invokeLater( () -> {
@@ -40,85 +55,543 @@ public static void main( String[] args ) {
FlatAnimatorTest() {
initComponents();
- }
- private void start() {
- startLinear();
- startEaseInOut();
+ linearChartColor.setForeground( CHART_LINEAR );
+ easeInOutChartColor.setForeground( CHART_EASE_IN_OUT );
+ standardEasingChartColor.setForeground( CHART_STANDARD_EASING );
}
- private void startLinear() {
- if( linearAnimator != null ) {
- linearAnimator.stop();
- linearAnimator.start();
- } else {
- linearAnimator = new Animator( 1000, fraction -> {
- linearScrollBar.setValue( Math.round( fraction * linearScrollBar.getMaximum() ) );
- } );
- linearAnimator.start();
- }
+ private void start() {
+ linearAnimator = start( linearAnimator, null, linearScrollBar, CHART_LINEAR );
+ easeInOutAnimator = start( easeInOutAnimator, CubicBezierEasing.EASE_IN_OUT, easeInOutScrollBar, CHART_EASE_IN_OUT );
+ standardEasingAnimator = start( standardEasingAnimator, CubicBezierEasing.STANDARD_EASING, standardEasingScrollBar, CHART_STANDARD_EASING );
}
- private void startEaseInOut() {
- if( easeInOutAnimator != null ) {
- easeInOutAnimator.stop();
- easeInOutAnimator.start();
+ private Animator start( Animator animator, Interpolator interpolator, JScrollBar scrollBar, Color chartColor ) {
+ if( animator != null ) {
+ animator.stop();
+ animator.start();
} else {
- easeInOutAnimator = new Animator( 1000, fraction -> {
- easeInOutScrollBar.setValue( Math.round( fraction * easeInOutScrollBar.getMaximum() ) );
+ animator = new Animator( 1000, fraction -> {
+ scrollBar.setValue( Math.round( fraction * scrollBar.getMaximum() ) );
+ lineChartPanel.lineChart.addValue( fraction, chartColor );
} );
- easeInOutAnimator.setInterpolator( CubicBezierEasing.EASE_IN_OUT );
- easeInOutAnimator.start();
+ animator.setInterpolator( interpolator );
+ animator.start();
}
+ return animator;
}
private void initComponents() {
// JFormDesigner - Component initialization - DO NOT MODIFY //GEN-BEGIN:initComponents
- JLabel label1 = new JLabel();
+ linearLabel = new JLabel();
+ linearChartColor = new FlatAnimatorTest.JChartColor();
linearScrollBar = new JScrollBar();
- JLabel label2 = new JLabel();
+ easeInOutLabel = new JLabel();
+ easeInOutChartColor = new FlatAnimatorTest.JChartColor();
easeInOutScrollBar = new JScrollBar();
+ standardEasingLabel = new JLabel();
+ standardEasingChartColor = new FlatAnimatorTest.JChartColor();
+ standardEasingScrollBar = new JScrollBar();
startButton = new JButton();
+ lineChartPanel = new FlatAnimatorTest.LineChartPanel();
//======== this ========
setLayout(new MigLayout(
"ltr,insets dialog,hidemode 3",
// columns
"[fill]" +
+ "[fill]" +
"[grow,fill]",
// rows
"[]" +
"[]" +
- "[]"));
+ "[]" +
+ "[]para" +
+ "[400,grow,fill]"));
- //---- label1 ----
- label1.setText("Linear:");
- add(label1, "cell 0 0");
+ //---- linearLabel ----
+ linearLabel.setText("Linear:");
+ add(linearLabel, "cell 0 0");
+ add(linearChartColor, "cell 1 0");
//---- linearScrollBar ----
linearScrollBar.setOrientation(Adjustable.HORIZONTAL);
linearScrollBar.setBlockIncrement(1);
- add(linearScrollBar, "cell 1 0");
+ add(linearScrollBar, "cell 2 0");
- //---- label2 ----
- label2.setText("Ease in out:");
- add(label2, "cell 0 1");
+ //---- easeInOutLabel ----
+ easeInOutLabel.setText("Ease in out:");
+ add(easeInOutLabel, "cell 0 1");
+ add(easeInOutChartColor, "cell 1 1");
//---- easeInOutScrollBar ----
easeInOutScrollBar.setOrientation(Adjustable.HORIZONTAL);
easeInOutScrollBar.setBlockIncrement(1);
- add(easeInOutScrollBar, "cell 1 1");
+ add(easeInOutScrollBar, "cell 2 1");
+
+ //---- standardEasingLabel ----
+ standardEasingLabel.setText("Standard easing:");
+ add(standardEasingLabel, "cell 0 2");
+ add(standardEasingChartColor, "cell 1 2");
+
+ //---- standardEasingScrollBar ----
+ standardEasingScrollBar.setOrientation(Adjustable.HORIZONTAL);
+ standardEasingScrollBar.setBlockIncrement(1);
+ add(standardEasingScrollBar, "cell 2 2");
//---- startButton ----
startButton.setText("Start");
startButton.addActionListener(e -> start());
- add(startButton, "cell 0 2");
+ add(startButton, "cell 0 3");
+ add(lineChartPanel, "cell 0 4 3 1");
// JFormDesigner - End of component initialization //GEN-END:initComponents
}
// JFormDesigner - Variables declaration - DO NOT MODIFY //GEN-BEGIN:variables
+ private JLabel linearLabel;
+ private FlatAnimatorTest.JChartColor linearChartColor;
private JScrollBar linearScrollBar;
+ private JLabel easeInOutLabel;
+ private FlatAnimatorTest.JChartColor easeInOutChartColor;
private JScrollBar easeInOutScrollBar;
+ private JLabel standardEasingLabel;
+ private FlatAnimatorTest.JChartColor standardEasingChartColor;
+ private JScrollBar standardEasingScrollBar;
private JButton startButton;
+ private FlatAnimatorTest.LineChartPanel lineChartPanel;
// JFormDesigner - End of variables declaration //GEN-END:variables
+
+ //---- class LineChartPanel -----------------------------------------------
+
+ static class LineChartPanel
+ extends JPanel
+ {
+ LineChartPanel() {
+ initComponents();
+
+ secondsWidthSlider.setValue( lineChart.getSecondsWidth() );
+ updateChartDelayedChanged();
+ }
+
+ void setSecondsWidth( int secondsWidth ) {
+ lineChart.setSecondsWidth( secondsWidth );
+ secondsWidthSlider.setValue( secondsWidth );
+ }
+
+ private void secondsWidthChanged() {
+ lineChart.setSecondsWidth( secondsWidthSlider.getValue() );
+ }
+
+ private void updateChartDelayedChanged() {
+ lineChart.setUpdateDelayed( updateChartDelayedCheckBox.isSelected() );
+ }
+
+ private void clearChart() {
+ lineChart.clear();
+ }
+
+ private void initComponents() {
+ // JFormDesigner - Component initialization - DO NOT MODIFY //GEN-BEGIN:initComponents
+ JScrollPane lineChartScrollPane = new JScrollPane();
+ lineChart = new FlatAnimatorTest.LineChart();
+ JLabel lineChartInfoLabel = new JLabel();
+ secondsWidthSlider = new JSlider();
+ updateChartDelayedCheckBox = new JCheckBox();
+ JButton clearChartButton = new JButton();
+
+ //======== this ========
+ setLayout(new MigLayout(
+ "ltr,insets 0,hidemode 3",
+ // columns
+ "[fill]" +
+ "[grow,fill]",
+ // rows
+ "[400,grow,fill]" +
+ "[]"));
+
+ //======== lineChartScrollPane ========
+ {
+ lineChartScrollPane.putClientProperty("JScrollPane.smoothScrolling", false);
+ lineChartScrollPane.setViewportView(lineChart);
+ }
+ add(lineChartScrollPane, "cell 0 0 2 1");
+
+ //---- lineChartInfoLabel ----
+ lineChartInfoLabel.setText("X: time (500ms per line) / Y: value (10% per line)");
+ add(lineChartInfoLabel, "cell 0 1 2 1");
+
+ //---- secondsWidthSlider ----
+ secondsWidthSlider.setMinimum(100);
+ secondsWidthSlider.setMaximum(2000);
+ secondsWidthSlider.addChangeListener(e -> secondsWidthChanged());
+ add(secondsWidthSlider, "cell 0 1 2 1");
+
+ //---- updateChartDelayedCheckBox ----
+ updateChartDelayedCheckBox.setText("Update chart delayed");
+ updateChartDelayedCheckBox.setMnemonic('U');
+ updateChartDelayedCheckBox.addActionListener(e -> updateChartDelayedChanged());
+ add(updateChartDelayedCheckBox, "cell 0 1 2 1,alignx right,growx 0");
+
+ //---- clearChartButton ----
+ clearChartButton.setText("Clear Chart");
+ clearChartButton.setMnemonic('C');
+ clearChartButton.addActionListener(e -> clearChart());
+ add(clearChartButton, "cell 0 1 2 1,alignx right,growx 0");
+ // JFormDesigner - End of component initialization //GEN-END:initComponents
+ }
+
+ // JFormDesigner - Variables declaration - DO NOT MODIFY //GEN-BEGIN:variables
+ FlatAnimatorTest.LineChart lineChart;
+ private JSlider secondsWidthSlider;
+ private JCheckBox updateChartDelayedCheckBox;
+ // JFormDesigner - End of variables declaration //GEN-END:variables
+ }
+
+ //---- class LineChart ----------------------------------------------------
+
+ static class LineChart
+ extends JComponent
+ implements Scrollable
+ {
+ private static final int NEW_SEQUENCE_TIME_LAG = 500;
+ private static final int NEW_SEQUENCE_GAP = 100;
+
+ private boolean asynchron;
+ private int secondsWidth = 500;
+
+ private static class Data {
+ final double value;
+ final boolean dot;
+ final Color chartColor;
+ final long time; // in milliseconds
+
+ Data( double value, boolean dot, Color chartColor, long time ) {
+ this.value = value;
+ this.dot = dot;
+ this.chartColor = chartColor;
+ this.time = time;
+ }
+
+ @Override
+ public String toString() {
+ // for debugging
+ return String.valueOf( value );
+ }
+ }
+
+ private final List syncChartData = new ArrayList<>();
+ private final Map