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..aa0ec72bb --- /dev/null +++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/util/AnimatedBorder.java @@ -0,0 +1,83 @@ +/* + * 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 #getAnimatableValues(Component)} returns the animatable 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 #getAnimatableValues(Component)} returns multiple values, then each value + * gets its own independent animation, which may start/end at different points in time, + * may have different duration, resolution and interpolator. + *
+ * Example for an animated border: + *
+ * private class MyAnimatedBorder
+ * implements AnimatedBorder
+ * {
+ * @Override
+ * public float[] getAnimatableValues( Component c ) {
+ * return new float[] { c.isFocusOwner() ? 1 : 0 };
+ * }
+ *
+ * @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 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..2381bb913 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,62 +18,87 @@
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 #getAnimatableValues(Component)} returns the animatable 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 #getAnimatableValues(Component)} returns multiple values, then each value + * gets its own independent animation, which may start/end at different points in time, + * may have different duration, resolution and interpolator. *
* Example for an animated icon: *
- * private class AnimatedMinimalTestIcon
+ * private class MyAnimatedIcon
* implements AnimatedIcon
* {
+ * @Override
+ * public float[] getAnimatableValues( Component c ) {
+ * return new float[] { ((AbstractButton)c).isSelected() ? 1 : 0 };
+ * }
+ *
* @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 );
- * }
- *
- * @Override
- * public float getValue( Component c ) {
- * return ((AbstractButton)c).isSelected() ? 1 : 0;
+ * g.drawRect( x, y, width - 1, height - 1 );
+ * g.fillRect( x, y, Math.round( width * animatedValues[0] ), height );
* }
* }
*
* // 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)}
- * is a instance of {@link JComponent}.
+ * is an instance of {@link JComponent}.
* A client property is set on the component to store the animation state.
*
* @author Karl Tauber
*/
public interface AnimatedIcon
- extends Icon
+ extends Icon, AnimatedPainter
{
+ /**
+ * {@inheritDoc}
+ *
+ * @since 2
+ */
+ @Override
+ default float[] getAnimatableValues( Component c ) {
+ // for compatibility
+ return new float[] { getValue( c ) };
+ }
+
+ /**
+ * 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 ) {
+ // for compatibility
+ 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,52 +107,45 @@ 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
*/
- 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 ) {
+ }
/**
- * Gets the value of the component.
+ * Gets the animatable 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 #getAnimatableValues(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; + @Deprecated + default float getValue( Component c ) { + return 0; } /** - * Returns the interpolator for the animation. - * Default is {@link CubicBezierEasing#STANDARD_EASING}. + * {@inheritDoc} + * + * @since TODO */ - default Interpolator getAnimationInterpolator() { - return CubicBezierEasing.STANDARD_EASING; + @Override + default Object getAnimationClientPropertyKey() { + // for compatibility + return getClientPropertyKey(); } /** * Returns the client property key used to store the animation support. + * + * @deprecated override {@link #getAnimationClientPropertyKey()} instead */ + @Deprecated default Object getClientPropertyKey() { return getClass(); } @@ -135,115 +153,25 @@ default Object getClientPropertyKey() { //---- 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, (Graphics2D) 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..58270a064 --- /dev/null +++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/util/AnimatedPainter.java @@ -0,0 +1,181 @@ +/* + * 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 #getAnimatableValues(Component)} returns the animatable 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 #getAnimatableValues(Component)} returns multiple values, then each value + * gets its own independent animation, which may start/end at different points in time, + * may have different duration, resolution and interpolator. + *
+ * 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 an 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 +{ + /** + * Gets the animatable value(s) of the component. + *
+ * This can be any value(s) 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. + * A complex check box could return values for selected, hover, pressed and focused states. + * The painter then can show independent animations for those states. + */ + float[] getAnimatableValues( Component c ); + + /** + * Starts painting. + * Either invokes {@link #paintAnimated(Component, Graphics2D, int, int, int, int, float[])} + * once to paint current value(s) (see {@link #getAnimatableValues(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, (Graphics2D) 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 #getAnimatableValues(Component)} + * returned, or somewhere between the previous values and the latest values + * that {@link #getAnimatableValues(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 );
+ }
+
+ /**
+ * 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 getAnimationClientPropertyKey() {
+ 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..4a41fdbe6
--- /dev/null
+++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/util/AnimatedPainterSupport.java
@@ -0,0 +1,185 @@
+/*
+ * 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.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 final 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;
+
+ private AnimatedPainterSupport( int valueIndex ) {
+ this.valueIndex = valueIndex;
+ }
+
+ static void paint( AnimatedPainter painter, Component c, Graphics2D g,
+ int x, int y, int width, int height )
+ {
+ // get animatable component values
+ float[] values = painter.getAnimatableValues( c );
+
+ 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, g, x, y, width, height, values );
+ return;
+ }
+
+ JComponent jc = (JComponent) c;
+ Object key = painter.getAnimationClientPropertyKey();
+ 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( 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, 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.getAnimationClientPropertyKey() );
+ 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..fa84f7c3c
--- /dev/null
+++ b/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatAnimatedBorderTest.java
@@ -0,0 +1,438 @@
+/*
+ * 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.FlatClientProperties;
+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 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" +
+ "[grow,fill]",
+ // rows
+ "[]" +
+ "[]" +
+ "[]para" +
+ "[]" +
+ "[]" +
+ "[]" +
+ "[]" +
+ "[]para" +
+ "[]" +
+ "[]" +
+ "[grow]" +
+ "[]"));
+
+ //---- label3 ----
+ label3.setText("Fade:");
+ add(label3, "cell 0 0");
+ add(lineChartPanel, "cell 2 0 1 12,growy");
+ 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(FlatClientProperties.STYLE_CLASS, "large");
+ add(material3TextField, "cell 0 6");
+ add(material3ChartColor, "cell 1 6");
+
+ //---- material4TextField ----
+ material4TextField.putClientProperty(FlatClientProperties.STYLE_CLASS, "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 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.addValue( chartColor, animatedValue, Integer.MIN_VALUE, "fade" );
+ }
+ }
+
+ @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[] getAnimatableValues( 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.addValue( chartColor, animatedValue, Integer.MIN_VALUE, "material" );
+ }
+ }
+
+ @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[] getAnimatableValues( 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.addValue( chartColor, animatedValue, Integer.MIN_VALUE, "minimal" );
+ }
+ }
+
+ @Override
+ public float[] getAnimatableValues( 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..8c0230827
--- /dev/null
+++ b/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatAnimatedBorderTest.jfd
@@ -0,0 +1,128 @@
+JFDML JFormDesigner: "8.3" 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[grow,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.LineChartPanel" ) {
+ name: "lineChartPanel"
+ }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
+ "value": "cell 2 0 1 12,growy"
+ } )
+ 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, 465 )
+ } )
+ }
+}
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..3cf352c1c 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 = { 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 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 8,growy");
//---- 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 2 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 2 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 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.addValue( chartColor, animatedValue, Integer.MIN_VALUE, "radio" );
+ }
}
@Override
- public float getValue( Component c ) {
- return ((JRadioButton)c).isSelected() ? 1 : 0;
+ public float[] getAnimatableValues( 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.addValue( chartColor, animatedValue, Integer.MIN_VALUE, "switch" );
+ }
+ }
+
+ @Override
+ public float[] getAnimatableValues( 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.addValue( CHART_SWITCH_EX[i], animatedValue, Integer.MIN_VALUE, "switch ex" );
+ }
+ }
+
+ 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[] getAnimatableValues( 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.addValue( chartColor, animatedValue, Integer.MIN_VALUE, "minimal" );
+ }
}
@Override
- public float getValue( Component c ) {
- return ((AbstractButton)c).isSelected() ? 1 : 0;
+ public float[] getAnimatableValues( 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..dd7867fc6 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: "8.3" 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.LineChartPanel" ) {
+ name: "lineChartPanel"
+ }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
+ "value": "cell 2 0 1 8,growy"
+ } )
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 2 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 2 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..7cb6e6516 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
@@ -20,6 +20,8 @@
import javax.swing.*;
import com.formdev.flatlaf.util.Animator;
import com.formdev.flatlaf.util.CubicBezierEasing;
+import com.formdev.flatlaf.util.UIScale;
+import com.formdev.flatlaf.util.Animator.Interpolator;
import net.miginfocom.swing.*;
/**
@@ -28,8 +30,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 +47,132 @@ 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.addValue( chartColor, fraction, Integer.MIN_VALUE, "animator" );
} );
- 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 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 LineChartPanel lineChartPanel;
// JFormDesigner - End of variables declaration //GEN-END:variables
+
+ //---- class JChartColor --------------------------------------------------
+
+ static class JChartColor
+ extends JComponent
+ {
+ @Override
+ public Dimension getPreferredSize() {
+ return new Dimension( UIScale.scale( 24 ), UIScale.scale( 12 ) );
+ }
+
+ @Override
+ public Dimension getMinimumSize() {
+ return getPreferredSize();
+ }
+
+ @Override
+ protected void paintComponent( Graphics g ) {
+ g.setColor( getForeground() );
+ g.fillRect( 0, 0, UIScale.scale( 24 ), UIScale.scale( 12 ) );
+ }
+ }
}
diff --git a/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatAnimatorTest.jfd b/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatAnimatorTest.jfd
index bb92a1299..c3bf86cf3 100644
--- a/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatAnimatorTest.jfd
+++ b/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatAnimatorTest.jfd
@@ -1,4 +1,4 @@
-JFDML JFormDesigner: "7.0.2.0.298" Java: "14.0.2" encoding: "UTF-8"
+JFDML JFormDesigner: "8.3" encoding: "UTF-8"
new FormModel {
contentType: "form/swing"
@@ -8,16 +8,27 @@ new FormModel {
}
add( new FormContainer( "com.formdev.flatlaf.testing.FlatTestPanel", new FormLayoutManager( class net.miginfocom.swing.MigLayout ) {
"$layoutConstraints": "ltr,insets dialog,hidemode 3"
- "$columnConstraints": "[fill][grow,fill]"
- "$rowConstraints": "[][][]"
+ "$columnConstraints": "[fill][fill][grow,fill]"
+ "$rowConstraints": "[][][][]para[400,grow,fill]"
} ) {
name: "this"
add( new FormComponent( "javax.swing.JLabel" ) {
- name: "label1"
+ name: "linearLabel"
"text": "Linear:"
+ auxiliary() {
+ "JavaCodeGenerator.variableLocal": false
+ }
}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
"value": "cell 0 0"
} )
+ add( new FormComponent( "com.formdev.flatlaf.testing.FlatAnimatorTest$JChartColor" ) {
+ name: "linearChartColor"
+ auxiliary() {
+ "JavaCodeGenerator.variableLocal": false
+ }
+ }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
+ "value": "cell 1 0"
+ } )
add( new FormComponent( "javax.swing.JScrollBar" ) {
name: "linearScrollBar"
"orientation": 0
@@ -26,14 +37,25 @@ new FormModel {
"JavaCodeGenerator.variableLocal": false
}
}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
- "value": "cell 1 0"
+ "value": "cell 2 0"
} )
add( new FormComponent( "javax.swing.JLabel" ) {
- name: "label2"
+ name: "easeInOutLabel"
"text": "Ease in out:"
+ auxiliary() {
+ "JavaCodeGenerator.variableLocal": false
+ }
}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
"value": "cell 0 1"
} )
+ add( new FormComponent( "com.formdev.flatlaf.testing.FlatAnimatorTest$JChartColor" ) {
+ name: "easeInOutChartColor"
+ auxiliary() {
+ "JavaCodeGenerator.variableLocal": false
+ }
+ }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
+ "value": "cell 1 1"
+ } )
add( new FormComponent( "javax.swing.JScrollBar" ) {
name: "easeInOutScrollBar"
"orientation": 0
@@ -42,7 +64,34 @@ new FormModel {
"JavaCodeGenerator.variableLocal": false
}
}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
- "value": "cell 1 1"
+ "value": "cell 2 1"
+ } )
+ add( new FormComponent( "javax.swing.JLabel" ) {
+ name: "standardEasingLabel"
+ "text": "Standard easing:"
+ auxiliary() {
+ "JavaCodeGenerator.variableLocal": false
+ }
+ }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
+ "value": "cell 0 2"
+ } )
+ add( new FormComponent( "com.formdev.flatlaf.testing.FlatAnimatorTest$JChartColor" ) {
+ name: "standardEasingChartColor"
+ auxiliary() {
+ "JavaCodeGenerator.variableLocal": false
+ }
+ }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
+ "value": "cell 1 2"
+ } )
+ add( new FormComponent( "javax.swing.JScrollBar" ) {
+ name: "standardEasingScrollBar"
+ "orientation": 0
+ "blockIncrement": 1
+ auxiliary() {
+ "JavaCodeGenerator.variableLocal": false
+ }
+ }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
+ "value": "cell 2 2"
} )
add( new FormComponent( "javax.swing.JButton" ) {
name: "startButton"
@@ -52,11 +101,19 @@ new FormModel {
}
addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "start", false ) )
}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
- "value": "cell 0 2"
+ "value": "cell 0 3"
+ } )
+ add( new FormComponent( "com.formdev.flatlaf.testing.LineChartPanel" ) {
+ name: "lineChartPanel"
+ auxiliary() {
+ "JavaCodeGenerator.variableLocal": false
+ }
+ }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
+ "value": "cell 0 4 3 1"
} )
}, new FormLayoutConstraints( null ) {
"location": new java.awt.Point( 0, 0 )
- "size": new java.awt.Dimension( 415, 350 )
+ "size": new java.awt.Dimension( 625, 625 )
} )
}
}
diff --git a/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/LineChartPanel.java b/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/LineChartPanel.java
new file mode 100644
index 000000000..436ab871e
--- /dev/null
+++ b/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/LineChartPanel.java
@@ -0,0 +1,883 @@
+/*
+/*
+ * Copyright 2023 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.Dimension;
+import java.awt.EventQueue;
+import java.awt.Graphics;
+import java.awt.Graphics2D;
+import java.awt.Point;
+import java.awt.Rectangle;
+import java.awt.event.HierarchyEvent;
+import java.awt.event.MouseEvent;
+import java.text.MessageFormat;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import javax.swing.*;
+import com.formdev.flatlaf.FlatClientProperties;
+import com.formdev.flatlaf.FlatLaf;
+import com.formdev.flatlaf.ui.FlatUIUtils;
+import com.formdev.flatlaf.util.HSLColor;
+import com.formdev.flatlaf.util.HiDPIUtils;
+import com.formdev.flatlaf.util.UIScale;
+import net.miginfocom.swing.*;
+
+/**
+ * @author Karl Tauber
+ */
+class LineChartPanel
+ extends JPanel
+{
+ LineChartPanel() {
+ initComponents();
+
+ lineChartScrollPane.putClientProperty( FlatClientProperties.SCROLL_PANE_SMOOTH_SCROLLING, false );
+
+ oneSecondWidthChanged();
+ updateChartDelayedChanged();
+
+ // clear chart on startup
+ addHierarchyListener( e -> {
+ if( (e.getChangeFlags() & HierarchyEvent.SHOWING_CHANGED) != 0 && isShowing() )
+ EventQueue.invokeLater( this::clearChart );
+ } );
+
+ // show chart tooltips immediately and forever
+ ToolTipManager.sharedInstance().setInitialDelay( 0 );
+ ToolTipManager.sharedInstance().setDismissDelay( Integer.MAX_VALUE );
+ }
+
+ @Override
+ public void addNotify() {
+ super.addNotify();
+
+ // allow clearing chart with Alt+C without moving focus to button
+ getRootPane().registerKeyboardAction(
+ e -> clearChart(),
+ KeyStroke.getKeyStroke( "alt " + (char) clearChartButton.getMnemonic() ),
+ JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT );
+ }
+
+ public boolean isYZeroAtTop() {
+ return lineChart.yZeroAtTop;
+ }
+
+ public void setYZeroAtTop( boolean yZeroAtTop ) {
+ lineChart.yZeroAtTop = yZeroAtTop;
+ lineChart.repaint();
+ }
+
+ public boolean isAsynchron() {
+ return lineChart.asynchron;
+ }
+
+ public void setAsynchron( boolean asynchron ) {
+ lineChart.asynchron = asynchron;
+ lineChart.repaint();
+ }
+
+ public boolean isTemporaryValueDetection() {
+ return lineChart.temporaryValueDetection;
+ }
+
+ public void setTemporaryValueDetection( boolean temporaryValueDetection ) {
+ lineChart.temporaryValueDetection = temporaryValueDetection;
+ lineChart.repaint();
+ }
+
+ public String getLegendYValueText() {
+ return yValueLabel.getText();
+ }
+
+ public void setLegendYValueText( String s ) {
+ yValueLabel.setText( s );
+ }
+
+ public String getLegend1Text() {
+ return legend1Label.getText();
+ }
+
+ public void setLegend1Text( String s ) {
+ legend1Label.setText( s );
+ }
+
+ public String getLegend2Text() {
+ return legend2Label.getText();
+ }
+
+ public void setLegend2Text( String s ) {
+ legend2Label.setText( s );
+ }
+
+ public int getOneSecondWidth() {
+ return oneSecondWidthSlider.getValue();
+ }
+
+ public void setOneSecondWidth( int oneSecondWidth ) {
+ oneSecondWidthSlider.setValue( oneSecondWidth );
+ }
+
+ public boolean isUpdateChartDelayed() {
+ return updateChartDelayedCheckBox.isSelected();
+ }
+
+ public void setUpdateChartDelayed( boolean updateChartDelayed ) {
+ updateChartDelayedCheckBox.setSelected( updateChartDelayed );
+ updateChartDelayedChanged();
+ }
+
+ void addValue( Color chartColor, double value, int ivalue, String name ) {
+ lineChart.addValue( chartColor, value, ivalue, null, false, name );
+ }
+
+ void addValueWithDot( Color chartColor, double value, int ivalue, Color dotColor, String name ) {
+ if( dotColor == null )
+ dotColor = chartColor;
+ lineChart.addValue( chartColor, value, ivalue, dotColor, false, name );
+ }
+
+ void addDot( Color chartColor, double value, int ivalue, Color dotColor, String name ) {
+ if( dotColor == null )
+ dotColor = chartColor;
+ lineChart.addValue( chartColor, value, ivalue, dotColor, true, name );
+ }
+
+ void addMethodHighlight( String classAndMethod, String highlightColor ) {
+ lineChart.methodHighlightMap.put( classAndMethod, highlightColor );
+ }
+
+ private void oneSecondWidthChanged() {
+ int oneSecondWidth = oneSecondWidthSlider.getValue();
+ int msPerLineX =
+ oneSecondWidth <= 2000 ? 100 :
+ oneSecondWidth <= 4000 ? 50 :
+ oneSecondWidth <= 8000 ? 25 :
+ 10;
+
+ lineChart.oneSecondWidth = oneSecondWidth;
+ lineChart.msPerLineX = msPerLineX;
+ lineChart.revalidate();
+ lineChart.repaint();
+
+ if( xLabelText == null )
+ xLabelText = xLabel.getText();
+ xLabel.setText( MessageFormat.format( xLabelText, msPerLineX ) );
+ }
+ private String xLabelText;
+
+ private void updateChartDelayedChanged() {
+ lineChart.updateDelayed = updateChartDelayedCheckBox.isSelected();
+ }
+
+ private void clearChart() {
+ lineChart.clear();
+ }
+
+ private void initComponents() {
+ // JFormDesigner - Component initialization - DO NOT MODIFY //GEN-BEGIN:initComponents @formatter:off
+ lineChartScrollPane = new JScrollPane();
+ lineChart = new LineChartPanel.LineChart();
+ JPanel legendPanel = new JPanel();
+ xLabel = new JLabel();
+ legend1Label = new JLabel();
+ JLabel yLabel = new JLabel();
+ yValueLabel = new JLabel();
+ JLabel yLabel2 = new JLabel();
+ JPanel hSpacer1 = new JPanel(null);
+ legend2Label = new JLabel();
+ JLabel oneSecondWidthLabel = new JLabel();
+ oneSecondWidthSlider = new JSlider();
+ updateChartDelayedCheckBox = new JCheckBox();
+ clearChartButton = new JButton();
+
+ //======== this ========
+ setLayout(new MigLayout(
+ "hidemode 3",
+ // columns
+ "[grow,fill]",
+ // rows
+ "[100:300,grow,fill]" +
+ "[]"));
+
+ //======== lineChartScrollPane ========
+ {
+ lineChartScrollPane.setViewportView(lineChart);
+ }
+ add(lineChartScrollPane, "cell 0 0");
+
+ //======== legendPanel ========
+ {
+ legendPanel.setLayout(new MigLayout(
+ "insets 0,hidemode 3,gapy 0",
+ // columns
+ "[fill]para" +
+ "[fill]",
+ // rows
+ "[]" +
+ "[]"));
+
+ //---- xLabel ----
+ xLabel.setText("X: time ({0}ms per line)");
+ legendPanel.add(xLabel, "cell 0 0");
+ legendPanel.add(legend1Label, "cell 1 0");
+
+ //---- yLabel ----
+ yLabel.setText("Y: ");
+ legendPanel.add(yLabel, "cell 0 1,gapx 0 0");
+
+ //---- yValueLabel ----
+ yValueLabel.setText("value");
+ legendPanel.add(yValueLabel, "cell 0 1,gapx 0 0");
+
+ //---- yLabel2 ----
+ yLabel2.setText(" (10% per line)");
+ legendPanel.add(yLabel2, "cell 0 1,gapx 0 0");
+ legendPanel.add(hSpacer1, "cell 0 1,growx");
+ legendPanel.add(legend2Label, "cell 1 1");
+ }
+ add(legendPanel, "cell 0 1");
+
+ //---- oneSecondWidthLabel ----
+ oneSecondWidthLabel.setText("Scale X:");
+ oneSecondWidthLabel.setDisplayedMnemonic('A');
+ oneSecondWidthLabel.setLabelFor(oneSecondWidthSlider);
+ add(oneSecondWidthLabel, "cell 0 1,alignx right,growx 0");
+
+ //---- oneSecondWidthSlider ----
+ oneSecondWidthSlider.setMinimum(100);
+ oneSecondWidthSlider.setMaximum(10000);
+ oneSecondWidthSlider.setSnapToTicks(true);
+ oneSecondWidthSlider.setMajorTickSpacing(100);
+ oneSecondWidthSlider.setValue(500);
+ oneSecondWidthSlider.addChangeListener(e -> oneSecondWidthChanged());
+ add(oneSecondWidthSlider, "cell 0 1,alignx right,growx 0");
+
+ //---- updateChartDelayedCheckBox ----
+ updateChartDelayedCheckBox.setText("Update chart delayed");
+ updateChartDelayedCheckBox.setMnemonic('P');
+ updateChartDelayedCheckBox.addActionListener(e -> updateChartDelayedChanged());
+ add(updateChartDelayedCheckBox, "cell 0 1,alignx right,growx 0");
+
+ //---- clearChartButton ----
+ clearChartButton.setText("Clear Chart");
+ clearChartButton.setMnemonic('C');
+ clearChartButton.addActionListener(e -> clearChart());
+ add(clearChartButton, "cell 0 1,alignx right,growx 0");
+ // JFormDesigner - End of component initialization //GEN-END:initComponents @formatter:on
+ }
+
+ // JFormDesigner - Variables declaration - DO NOT MODIFY //GEN-BEGIN:variables @formatter:off
+ private JScrollPane lineChartScrollPane;
+ private LineChartPanel.LineChart lineChart;
+ private JLabel xLabel;
+ private JLabel legend1Label;
+ private JLabel yValueLabel;
+ private JLabel legend2Label;
+ private JSlider oneSecondWidthSlider;
+ private JCheckBox updateChartDelayedCheckBox;
+ private JButton clearChartButton;
+ // JFormDesigner - End of variables declaration //GEN-END:variables @formatter:on
+
+
+ //---- class LineChart ----------------------------------------------------
+
+ private static class LineChart
+ extends JComponent
+ implements Scrollable
+ {
+ private static final int UPDATE_DELAY_MS = 20;
+ private static final int NEW_SEQUENCE_TIME_LAG_MS = 500;
+ private static final int NEW_SEQUENCE_GAP_MS = 100;
+ private static final int HIT_OFFSET = 4;
+
+ private static final boolean TEST = false;
+
+ // asynchron means that chart for each color starts at x=0
+ private boolean asynchron;
+ private boolean temporaryValueDetection;
+ private boolean yZeroAtTop;
+ private int oneSecondWidth = 500;
+ private int msPerLineX = 100;
+ private final HashMap" );
+ if( data.dotOnly )
+ buf.append( "DOT: " );
+ buf.append( data.name );
+ if( data.ivalue != Integer.MIN_VALUE )
+ buf.append( ' ' ).append( data.ivalue );
+ buf.append( " (" ).append( String.format( "%.3f", data.value ) ).append( ')' );
+ buf.append( "
" );
+
+ StackTraceElement[] stackTrace = data.stack.getStackTrace();
+ for( int j = 0; j < stackTrace.length; j++ ) {
+ StackTraceElement stackElement = stackTrace[j];
+ String className = stackElement.getClassName();
+ String methodName = stackElement.getMethodName();
+ String classAndMethod = className + '.' + methodName;
+
+ // ignore methods from this class
+ if( className.startsWith( LineChartPanel.class.getName() ) )
+ continue;
+
+ int repeatCount = 0;
+ for( int k = j + 1; k < stackTrace.length; k++ ) {
+ if( !stackElement.equals( stackTrace[k] ) )
+ break;
+ repeatCount++;
+ }
+ j += repeatCount;
+
+ String highlight = methodHighlightMap.get( classAndMethod );
+ if( highlight == null )
+ highlight = methodHighlightMap.get( className );
+ if( highlight == null )
+ highlight = methodHighlightMap.get( methodName );
+ if( highlight != null )
+ buf.append( "" );
+
+ // append method
+ buf.append( className )
+ .append( "." )
+ .append( methodName )
+ .append( "" );
+ if( highlight != null )
+ buf.append( "" );
+
+ // append source
+ buf.append( " " );
+ if( stackElement.getFileName() != null ) {
+ buf.append( '(' );
+ buf.append( stackElement.getFileName() );
+ if( stackElement.getLineNumber() >= 0 )
+ buf.append( ':' ).append( stackElement.getLineNumber() );
+ buf.append( ')' );
+ } else
+ buf.append( "(Unknown Source)" );
+ buf.append( "" );
+
+ // append repeat count
+ if( repeatCount > 0 )
+ buf.append( " " ).append( repeatCount + 1 ).append( "x" );
+ buf.append( "
" );
+
+ // break at some methods to make stack smaller
+ if( classAndMethod.equals( "java.awt.event.InvocationEvent.dispatch" ) ||
+ classAndMethod.equals( "java.awt.Component.processMouseEvent" ) ||
+ classAndMethod.equals( "java.awt.Component.processMouseWheelEvent" ) ||
+ classAndMethod.equals( "java.awt.Component.processMouseMotionEvent" ) ||
+ classAndMethod.equals( "javax.swing.JComponent.processKeyBinding" ) ||
+ classAndMethod.equals( "javax.swing.JComponent.paintComponent" ) ||
+ classAndMethod.equals( "com.formdev.flatlaf.util.Animator.timingEvent" ) )
+ break;
+ }
+ buf.append( "..." );
+ }
+
+ if( buf == null )
+ return null;
+
+ buf.append( "" );
+ String toolTip = buf.toString();
+
+ // print to console
+ if( !Objects.equals( toolTip, lastToolTipPrinted ) ) {
+ lastToolTipPrinted = toolTip;
+
+ System.out.println( toolTip
+ .replace( "
", "\n" )
+ .replace( "", "\n---- " )
+ .replace( "
", " ----\n" )
+ .replaceAll( "<[^>]+>", "" ) );
+ }
+
+ return buf.toString();
+ }
+
+ private void initTestData() {
+// asynchron = true;
+
+ addTestSimpleLine( Color.red, 0.0, "red" );
+ addTestSimpleLine( Color.green, 0.1, "green" );
+ addTestSimpleLine( Color.blue, 0.2, "blue" );
+ addTestSimpleLine( Color.magenta, 0.3, "magenta" );
+
+ addTestMiddleDotOnly( Color.red, 0.0, "red" );
+ addTestMiddleDotOnly( Color.green, 0.1, "green" );
+ addTestMiddleDotOnly( Color.blue, 0.2, "blue" );
+ addTestMiddleDotOnly( Color.magenta, 0.3, "magenta" );
+
+ addTestLeadingDotOnly( Color.red, 0.0, "red" );
+ addTestLeadingDotOnly( Color.green, 0.1, "green" );
+ addTestLeadingDotOnly( Color.blue, 0.2, "blue" );
+ addTestLeadingDotOnly( Color.magenta, 0.3, "magenta" );
+
+ addTestTrailingDotOnly( Color.red, 0.0, "red" );
+ addTestTrailingDotOnly( Color.green, 0.1, "green" );
+ addTestTrailingDotOnly( Color.blue, 0.2, "blue" );
+ addTestTrailingDotOnly( Color.magenta, 0.3, "magenta" );
+
+ addTestSingleData( Color.red, 0.0, "red" );
+ addTestSingleData( Color.green, 0.1, "green" );
+ addTestSingleData( Color.blue, 0.2, "blue" );
+ addTestSingleData( Color.magenta, 0.3, "magenta" );
+
+ temporaryValueDetection = true;
+ addTestWithTemporaryValues( Color.red, 0.0, "red" );
+ addTestWithTemporaryValues( Color.green, 0.1, "green" );
+ addTestWithTemporaryValues( Color.blue, 0.2, "blue" );
+ addTestWithTemporaryValues( Color.magenta, 0.3, "magenta" );
+ }
+
+ private void addTestSimpleLine( Color chartColor, double baseValue, String name ) {
+ addTestValue( 0, chartColor, baseValue + 0.0, null, false, name );
+ addTestValue( 50, chartColor, baseValue + 0.1, null, false, name );
+ addTestValue( 50, chartColor, baseValue + 0.4, null, false, name );
+ testTime += 1000;
+ }
+
+ private void addTestMiddleDotOnly( Color chartColor, double baseValue, String name ) {
+ addTestValue( 0, chartColor, baseValue + 0.0, null, false, name );
+ addTestValue( 20, chartColor, baseValue + 0.3, chartColor, true, name );
+ addTestValue( 30, chartColor, baseValue + 0.1, null, false, name );
+ addTestValue( 20, chartColor, baseValue + 0.05, chartColor, true, name );
+ addTestValue( 30, chartColor, baseValue + 0.4, null, false, name );
+ testTime += 1000;
+ }
+
+ private void addTestLeadingDotOnly( Color chartColor, double baseValue, String name ) {
+ addTestValue( 0, chartColor, baseValue + 0.05, chartColor, true, name );
+ addTestValue( 20, chartColor, baseValue + 0.0, null, false, name );
+ addTestValue( 50, chartColor, baseValue + 0.1, null, false, name );
+ addTestValue( 30, chartColor, baseValue + 0.4, null, false, name );
+ testTime += 1000;
+ }
+
+ private void addTestTrailingDotOnly( Color chartColor, double baseValue, String name ) {
+ addTestValue( 0, chartColor, baseValue + 0.0, null, false, name );
+ addTestValue( 50, chartColor, baseValue + 0.1, null, false, name );
+ addTestValue( 30, chartColor, baseValue + 0.4, null, false, name );
+ addTestValue( 20, chartColor, baseValue + 0.05, chartColor, true, name );
+ testTime += 1000;
+ }
+
+ private void addTestSingleData( Color chartColor, double baseValue, String name ) {
+ addTestValue( 0, chartColor, baseValue + 0.15, chartColor, false, name );
+ testTime += 1000;
+ }
+
+ private void addTestWithTemporaryValues( Color chartColor, double baseValue, String name ) {
+ addTestValue( 0, chartColor, baseValue + 0.0, null, false, name );
+ addTestValue( 50, chartColor, baseValue + 0.1, null, false, name );
+ addTestValue( 5, chartColor, baseValue + 0.4, null, false, name );
+ addTestValue( 5, chartColor, baseValue + 0.1, null, false, name );
+ addTestValue( 40, chartColor, baseValue + 0.3, null, false, name );
+ testTime += 1000;
+ }
+
+ private void addTestValue( int timeDelta, Color chartColor, double value, Color dotColor, boolean dotOnly, String name ) {
+ testTime += timeDelta;
+
+ List chartData = asyncColor2dataMap.computeIfAbsent( chartColor, k -> new ArrayList<>() );
+ Data data = new Data( value, testIValue++, chartColor, dotColor, dotOnly, testTime, name, new Exception() );
+ if( asynchron )
+ chartData.add( data );
+ else
+ syncChartData.add( data );
+
+ lastUsedChartColor = chartColor;
+ }
+
+ private int testIValue;
+ private long testTime;
+
+ //TODO remove and use ColorFunctions.fade() when merging to main
+ private static Color fade( Color color, float amount ) {
+ int newAlpha = Math.round( 255 * amount );
+ return new Color( (color.getRGB() & 0xffffff) | (newAlpha << 24), true );
+ }
+ }
+}
diff --git a/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/LineChartPanel.jfd b/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/LineChartPanel.jfd
new file mode 100644
index 000000000..2f66cb4a2
--- /dev/null
+++ b/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/LineChartPanel.jfd
@@ -0,0 +1,123 @@
+JFDML JFormDesigner: "8.3" encoding: "UTF-8"
+
+new FormModel {
+ contentType: "form/swing"
+ root: new FormRoot {
+ add( new FormContainer( "javax.swing.JPanel", new FormLayoutManager( class net.miginfocom.swing.MigLayout ) {
+ "$layoutConstraints": "hidemode 3"
+ "$columnConstraints": "[grow,fill]"
+ "$rowConstraints": "[100:300,grow,fill][]"
+ } ) {
+ name: "this"
+ add( new FormContainer( "javax.swing.JScrollPane", new FormLayoutManager( class javax.swing.JScrollPane ) ) {
+ name: "lineChartScrollPane"
+ add( new FormComponent( "com.formdev.flatlaf.testing.LineChartPanel$LineChart" ) {
+ name: "lineChart"
+ } )
+ }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
+ "value": "cell 0 0"
+ } )
+ add( new FormContainer( "javax.swing.JPanel", new FormLayoutManager( class net.miginfocom.swing.MigLayout ) {
+ "$layoutConstraints": "insets 0,hidemode 3,gapy 0"
+ "$columnConstraints": "[fill]para[fill]"
+ "$rowConstraints": "[][]"
+ } ) {
+ name: "legendPanel"
+ auxiliary() {
+ "JavaCodeGenerator.variableLocal": true
+ }
+ add( new FormComponent( "javax.swing.JLabel" ) {
+ name: "xLabel"
+ "text": "X: time ({0}ms per line)"
+ }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
+ "value": "cell 0 0"
+ } )
+ add( new FormComponent( "javax.swing.JLabel" ) {
+ name: "legend1Label"
+ }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
+ "value": "cell 1 0"
+ } )
+ add( new FormComponent( "javax.swing.JLabel" ) {
+ name: "yLabel"
+ "text": "Y: "
+ auxiliary() {
+ "JavaCodeGenerator.variableLocal": true
+ }
+ }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
+ "value": "cell 0 1,gapx 0 0"
+ } )
+ add( new FormComponent( "javax.swing.JLabel" ) {
+ name: "yValueLabel"
+ "text": "value"
+ }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
+ "value": "cell 0 1,gapx 0 0"
+ } )
+ add( new FormComponent( "javax.swing.JLabel" ) {
+ name: "yLabel2"
+ "text": " (10% per line)"
+ auxiliary() {
+ "JavaCodeGenerator.variableLocal": true
+ }
+ }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
+ "value": "cell 0 1,gapx 0 0"
+ } )
+ add( new FormComponent( "com.jformdesigner.designer.wrapper.HSpacer" ) {
+ name: "hSpacer1"
+ auxiliary() {
+ "JavaCodeGenerator.variableLocal": true
+ }
+ }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
+ "value": "cell 0 1,growx"
+ } )
+ add( new FormComponent( "javax.swing.JLabel" ) {
+ name: "legend2Label"
+ }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
+ "value": "cell 1 1"
+ } )
+ }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
+ "value": "cell 0 1"
+ } )
+ add( new FormComponent( "javax.swing.JLabel" ) {
+ name: "oneSecondWidthLabel"
+ "text": "Scale X:"
+ "displayedMnemonic": 65
+ "labelFor": new FormReference( "oneSecondWidthSlider" )
+ auxiliary() {
+ "JavaCodeGenerator.variableLocal": true
+ }
+ }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
+ "value": "cell 0 1,alignx right,growx 0"
+ } )
+ add( new FormComponent( "javax.swing.JSlider" ) {
+ name: "oneSecondWidthSlider"
+ "minimum": 100
+ "maximum": 10000
+ "snapToTicks": true
+ "majorTickSpacing": 100
+ "value": 500
+ addEvent( new FormEvent( "javax.swing.event.ChangeListener", "stateChanged", "oneSecondWidthChanged", false ) )
+ }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
+ "value": "cell 0 1,alignx right,growx 0"
+ } )
+ add( new FormComponent( "javax.swing.JCheckBox" ) {
+ name: "updateChartDelayedCheckBox"
+ "text": "Update chart delayed"
+ "mnemonic": 80
+ addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "updateChartDelayedChanged", false ) )
+ }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
+ "value": "cell 0 1,alignx right,growx 0"
+ } )
+ add( new FormComponent( "javax.swing.JButton" ) {
+ name: "clearChartButton"
+ "text": "Clear Chart"
+ "mnemonic": 67
+ addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "clearChart", false ) )
+ }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
+ "value": "cell 0 1,alignx right,growx 0"
+ } )
+ }, new FormLayoutConstraints( null ) {
+ "location": new java.awt.Point( 0, 0 )
+ "size": new java.awt.Dimension( 880, 300 )
+ } )
+ }
+}