From b2245e2246a17348706fddb29be134e5d401b4aa Mon Sep 17 00:00:00 2001 From: Karl Tauber Date: Tue, 13 Jul 2021 19:43:07 +0200 Subject: [PATCH 1/8] AnimatedBorder added (for future animations) (issue #66) --- .../formdev/flatlaf/util/AnimatedBorder.java | 248 +++++++++++++++++ .../testing/FlatAnimatedBorderTest.java | 256 ++++++++++++++++++ .../testing/FlatAnimatedBorderTest.jfd | 76 ++++++ 3 files changed, 580 insertions(+) create mode 100644 flatlaf-core/src/main/java/com/formdev/flatlaf/util/AnimatedBorder.java create mode 100644 flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatAnimatedBorderTest.java create mode 100644 flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatAnimatedBorderTest.jfd 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..add515a24 --- /dev/null +++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/util/AnimatedBorder.java @@ -0,0 +1,248 @@ +/* + * 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 javax.swing.JComponent; +import javax.swing.border.Border; +import com.formdev.flatlaf.util.Animator.Interpolator; + +/** + * Border that automatically animates painting on component value changes. + *

+ * {@link #getValue(Component)} returns the value of the component. + * If the value changes, then {@link #paintBorderAnimated(Component, Graphics, int, int, int, int, float)} + * is invoked multiple times with animated value (from old value to new value). + *

+ * Example for an animated border: + *

+ * private class AnimatedMinimalTestBorder
+ *     implements AnimatedBorder
+ * {
+ *     @Override
+ *     public void paintBorderAnimated( Component c, Graphics g, int x, int y, int width, int height, float animatedValue ) {
+ *         int lh = UIScale.scale( 2 );
+ *
+ *         g.setColor( Color.blue );
+ *         g.fillRect( x, y + height - lh, Math.round( width * animatedValue ), lh );
+ *     }
+ *
+ *     @Override
+ *     public float getValue( Component c ) {
+ *         return c.isFocusOwner() ? 1 : 0;
+ *     }
+ *
+ *     @Override
+ *     public Insets getBorderInsets( Component c ) {
+ *         return UIScale.scale( new Insets( 4, 4, 4, 4 ) );
+ *     }
+ *
+ *     @Override public boolean isBorderOpaque() { return false; }
+ * }
+ *
+ * // sample usage
+ * JTextField textField = new JTextField();
+ * textField.setBorder( new AnimatedMinimalTestBorder() );
+ * 
+ * + * 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 + */ +public interface AnimatedBorder + extends Border +{ + @Override + default void paintBorder( Component c, Graphics g, int x, int y, int width, int height ) { + AnimationSupport.paintBorder( this, c, g, x, y, width, height ); + } + + /** + * Paints the border for the given animated value. + * + * @param c the component that this border belongs to + * @param g the graphics context + * @param x the x coordinate of the border + * @param y the y coordinate of the border + * @param width the width coordinate of the border + * @param height the height coordinate of the border + * @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 + */ + void paintBorderAnimated( Component c, Graphics g, int x, int y, int width, int height, float animatedValue ); + + /** + * Gets the value of the component. + *

+ * This can be any value and depends on the component. + * If the value changes, then this class animates from the old value to the new one. + *

+ * For a text field this could be {@code 0} for not focused and {@code 1} for focused. + */ + float getValue( Component c ); + + /** + * Returns whether animation is enabled for this border (default is {@code true}). + */ + default boolean isAnimationEnabled() { + return true; + } + + /** + * Returns the duration of the animation in milliseconds (default is 150). + */ + default int getAnimationDuration() { + return 150; + } + + /** + * Returns the resolution of the animation in milliseconds (default is 10). + * Resolution is the amount of time between timing events. + */ + default int getAnimationResolution() { + return 10; + } + + /** + * Returns the interpolator for the animation. + * Default is {@link CubicBezierEasing#STANDARD_EASING}. + */ + default Interpolator getAnimationInterpolator() { + return CubicBezierEasing.STANDARD_EASING; + } + + /** + * Returns the client property key used to store the animation support. + */ + default Object getClientPropertyKey() { + return getClass(); + } + + //---- class AnimationSupport --------------------------------------------- + + /** + * Animation support class that stores the animation state and implements the animation. + */ + class AnimationSupport + { + private float startValue; + private float targetValue; + private float animatedValue; + private float fraction; + + private Animator animator; + + // last bounds of the border needed to repaint while animating + private int x; + private int y; + private int width; + private int height; + + public static void paintBorder( AnimatedBorder border, Component c, Graphics g, + int x, int y, int width, int height ) + { + if( !isAnimationEnabled( border, 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 + paintBorderImpl( border, c, g, x, y, width, height, null ); + return; + } + + JComponent jc = (JComponent) c; + Object key = border.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 = border.getValue( c ); + jc.putClientProperty( key, as ); + } else { + // get component value + float value = border.getValue( c ); + + if( value != as.targetValue ) { + // value changed --> (re)start animation + + if( as.animator == null ) { + // create animator + AnimationSupport as2 = as; + as.animator = new Animator( border.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 border + c.repaint( 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) (border.getAnimationDuration() * as.fraction); + if( duration2 > 0 ) + as.animator.setDuration( duration2 ); + as.startValue = as.animatedValue; + } else { + // new animation + as.animator.setDuration( border.getAnimationDuration() ); + as.animator.setResolution( border.getAnimationResolution() ); + as.animator.setInterpolator( border.getAnimationInterpolator() ); + + as.animatedValue = as.startValue; + } + + as.targetValue = value; + as.animator.start(); + } + } + + as.x = x; + as.y = y; + as.width = width; + as.height = height; + + paintBorderImpl( border, c, g, x, y, width, height, as ); + } + + private static void paintBorderImpl( AnimatedBorder border, Component c, Graphics g, + int x, int y, int width, int height, AnimationSupport as ) + { + float value = (as != null) ? as.animatedValue : border.getValue( c ); + border.paintBorderAnimated( c, g, x, y, width, height, value ); + } + + private static boolean isAnimationEnabled( AnimatedBorder border, Component c ) { + return Animator.useAnimation() && border.isAnimationEnabled() && c instanceof JComponent; + } + } +} 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..abd8fcc5d --- /dev/null +++ b/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatAnimatedBorderTest.java @@ -0,0 +1,256 @@ +/* + * 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.Graphics; +import java.awt.Graphics2D; +import java.awt.Insets; +import java.awt.geom.Rectangle2D; +import javax.swing.*; +import com.formdev.flatlaf.ui.FlatMarginBorder; +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 +{ + public static void main( String[] args ) { + SwingUtilities.invokeLater( () -> { + FlatTestFrame frame = FlatTestFrame.create( args, "FlatAnimatedBorderTest" ); + frame.showFrame( FlatAnimatedBorderTest::new ); + } ); + } + + FlatAnimatedBorderTest() { + initComponents(); + + textField5.setBorder( new AnimatedFocusFadeBorder() ); + textField6.setBorder( new AnimatedFocusFadeBorder() ); + + textField1.setBorder( new AnimatedMaterialBorder() ); + textField2.setBorder( new AnimatedMaterialBorder() ); + + textField4.setBorder( new AnimatedMinimalTestBorder() ); + } + + private void initComponents() { + // JFormDesigner - Component initialization - DO NOT MODIFY //GEN-BEGIN:initComponents + label3 = new JLabel(); + textField5 = new JTextField(); + textField6 = new JTextField(); + label2 = new JLabel(); + textField1 = new JTextField(); + textField2 = new JTextField(); + label1 = new JLabel(); + textField4 = new JTextField(); + durationLabel = new JLabel(); + durationField = new JSpinner(); + + //======== this ======== + setLayout(new MigLayout( + "insets dialog,hidemode 3", + // columns + "[fill]", + // rows + "[]" + + "[]" + + "[]para" + + "[]" + + "[]" + + "[]para" + + "[]" + + "[]" + + "[grow]" + + "[]")); + + //---- label3 ---- + label3.setText("Fade:"); + add(label3, "cell 0 0"); + add(textField5, "cell 0 1"); + add(textField6, "cell 0 2"); + + //---- label2 ---- + label2.setText("Material:"); + add(label2, "cell 0 3"); + add(textField1, "cell 0 4"); + add(textField2, "cell 0 5"); + + //---- label1 ---- + label1.setText("Minimal:"); + add(label1, "cell 0 6"); + add(textField4, "cell 0 7"); + + //---- durationLabel ---- + durationLabel.setText("Duration:"); + add(durationLabel, "cell 0 9"); + + //---- durationField ---- + durationField.setModel(new SpinnerNumberModel(200, 100, null, 50)); + add(durationField, "cell 0 9"); + // JFormDesigner - End of component initialization //GEN-END:initComponents + } + + // JFormDesigner - Variables declaration - DO NOT MODIFY //GEN-BEGIN:variables + private JLabel label3; + private JTextField textField5; + private JTextField textField6; + private JLabel label2; + private JTextField textField1; + private JTextField textField2; + private JLabel label1; + private JTextField textField4; + 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 FlatMarginBorder + 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 ) { + AnimationSupport.paintBorder( this, c, g, x, y, width, height ); + } + + @Override + public void paintBorderAnimated( Component c, Graphics g, int x, int y, int width, int height, float animatedValue ) { + FlatUIUtils.setRenderingHints( g ); + + // border width is 1 if not focused and 2 if focused + float lw = UIScale.scale( 1 + animatedValue ); + + // paint border + g.setColor( ColorFunctions.mix( Color.red, Color.lightGray, animatedValue ) ); + FlatUIUtils.paintComponentBorder( (Graphics2D) g, x, y, width, height, 0, lw, 0 ); + } + + @Override + public float getValue( Component c ) { + return 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 FlatMarginBorder + 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 ) { + AnimationSupport.paintBorder( this, c, g, x, y, width, height ); + } + + @Override + public void paintBorderAnimated( Component c, Graphics g, int x, int y, int width, int height, float animatedValue ) { + FlatUIUtils.setRenderingHints( g ); + + // use paintAtScale1x() for consistent line thickness when scaled + HiDPIUtils.paintAtScale1x( (Graphics2D) 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 ) ); + } + } ); + } + + @Override + public float getValue( Component c ) { + return FlatUIUtils.isPermanentFocusOwner( c ) ? 1 : 0; + } + + @Override + public int getAnimationDuration() { + return (Integer) durationField.getValue(); + } + } + + //---- class AnimatedMinimalTestBorder ------------------------------------ + + /** + * Minimal example for an animated border. + */ + private class AnimatedMinimalTestBorder + implements AnimatedBorder + { + @Override + public void paintBorderAnimated( Component c, Graphics g, int x, int y, int width, int height, float animatedValue ) { + int lh = UIScale.scale( 2 ); + + g.setColor( Color.blue ); + g.fillRect( x, y + height - lh, Math.round( width * animatedValue ), lh ); + } + + @Override + public float getValue( Component c ) { + return FlatUIUtils.isPermanentFocusOwner( c ) ? 1 : 0; + } + + @Override + public int getAnimationDuration() { + return (Integer) durationField.getValue(); + } + + @Override + public Insets getBorderInsets( Component c ) { + return UIScale.scale( new Insets( 4, 4, 4, 4 ) ); + } + + @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..cae2e60df --- /dev/null +++ b/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatAnimatedBorderTest.jfd @@ -0,0 +1,76 @@ +JFDML JFormDesigner: "7.0.4.0.360" Java: "16" encoding: "UTF-8" + +new FormModel { + contentType: "form/swing" + root: new FormRoot { + add( new FormContainer( "com.formdev.flatlaf.testing.FlatTestPanel", new FormLayoutManager( class net.miginfocom.swing.MigLayout ) { + "$layoutConstraints": "insets dialog,hidemode 3" + "$columnConstraints": "[fill]" + "$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( "javax.swing.JTextField" ) { + name: "textField5" + }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { + "value": "cell 0 1" + } ) + add( new FormComponent( "javax.swing.JTextField" ) { + name: "textField6" + }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { + "value": "cell 0 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: "textField1" + }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { + "value": "cell 0 4" + } ) + add( new FormComponent( "javax.swing.JTextField" ) { + name: "textField2" + }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { + "value": "cell 0 5" + } ) + add( new FormComponent( "javax.swing.JLabel" ) { + name: "label1" + "text": "Minimal:" + }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { + "value": "cell 0 6" + } ) + add( new FormComponent( "javax.swing.JTextField" ) { + name: "textField4" + }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { + "value": "cell 0 7" + } ) + add( new FormComponent( "javax.swing.JLabel" ) { + name: "durationLabel" + "text": "Duration:" + }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { + "value": "cell 0 9" + } ) + add( new FormComponent( "javax.swing.JSpinner" ) { + name: "durationField" + "model": new javax.swing.SpinnerNumberModel { + minimum: 100 + stepSize: 50 + value: 200 + } + }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { + "value": "cell 0 9" + } ) + }, new FormLayoutConstraints( null ) { + "location": new java.awt.Point( 0, 0 ) + "size": new java.awt.Dimension( 405, 315 ) + } ) + } +} From e4fa2e28ea92addb80139bad1cd95656bd89bb99 Mon Sep 17 00:00:00 2001 From: Karl Tauber Date: Wed, 14 Jul 2021 14:41:35 +0200 Subject: [PATCH 2/8] AnimatedBorder: - support repainting only necessary region while animating - use AbstractBorder in test app and fixed insets --- .../formdev/flatlaf/util/AnimatedBorder.java | 17 ++++++++++- .../testing/FlatAnimatedBorderTest.java | 29 ++++++++++++++++--- 2 files changed, 41 insertions(+), 5 deletions(-) diff --git a/flatlaf-core/src/main/java/com/formdev/flatlaf/util/AnimatedBorder.java b/flatlaf-core/src/main/java/com/formdev/flatlaf/util/AnimatedBorder.java index add515a24..b47268e72 100644 --- a/flatlaf-core/src/main/java/com/formdev/flatlaf/util/AnimatedBorder.java +++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/util/AnimatedBorder.java @@ -65,6 +65,7 @@ * A client property is set on the component to store the animation state. * * @author Karl Tauber + * @since 1.5 */ public interface AnimatedBorder extends Border @@ -89,6 +90,20 @@ default void paintBorder( Component c, Graphics g, int x, int y, int width, int */ void paintBorderAnimated( Component c, Graphics g, int x, int y, int width, int height, float animatedValue ); + /** + * Repaint the animated part of the border. + *

+ * 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 component. + */ + default void repaintBorder( Component c, int x, int y, int width, int height ) { + c.repaint( x, y, width, height ); + } + /** * Gets the value of the component. *

@@ -197,7 +212,7 @@ public static void paintBorder( AnimatedBorder border, Component c, Graphics g, as2.fraction = fraction; // repaint border - c.repaint( as2.x, as2.y, as2.width, as2.height ); + border.repaintBorder( c, as2.x, as2.y, as2.width, as2.height ); }, () -> { as2.startValue = as2.animatedValue = as2.targetValue; as2.animator = null; diff --git a/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatAnimatedBorderTest.java b/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatAnimatedBorderTest.java index abd8fcc5d..c24acd688 100644 --- a/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatAnimatedBorderTest.java +++ b/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatAnimatedBorderTest.java @@ -23,7 +23,7 @@ import java.awt.Insets; import java.awt.geom.Rectangle2D; import javax.swing.*; -import com.formdev.flatlaf.ui.FlatMarginBorder; +import javax.swing.border.AbstractBorder; import com.formdev.flatlaf.ui.FlatUIUtils; import com.formdev.flatlaf.util.AnimatedBorder; import com.formdev.flatlaf.util.ColorFunctions; @@ -133,7 +133,7 @@ private void initComponents() { * - animates focus indicator color and border width */ private class AnimatedFocusFadeBorder - extends FlatMarginBorder + extends AbstractBorder implements AnimatedBorder { // needed because otherwise the empty paint method in superclass @@ -155,6 +155,13 @@ public void paintBorderAnimated( Component c, Graphics g, int x, int y, int widt FlatUIUtils.paintComponentBorder( (Graphics2D) g, x, y, width, height, 0, lw, 0 ); } + @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 getValue( Component c ) { return FlatUIUtils.isPermanentFocusOwner( c ) ? 1 : 0; @@ -174,7 +181,7 @@ public int getAnimationDuration() { * - animates focus indicator at bottom */ private class AnimatedMaterialBorder - extends FlatMarginBorder + extends AbstractBorder implements AnimatedBorder { // needed because otherwise the empty paint method in superclass @@ -206,6 +213,20 @@ public void paintBorderAnimated( Component c, Graphics g, int x, int y, int widt } ); } + @Override + public void repaintBorder( 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 getValue( Component c ) { return FlatUIUtils.isPermanentFocusOwner( c ) ? 1 : 0; @@ -245,7 +266,7 @@ public int getAnimationDuration() { @Override public Insets getBorderInsets( Component c ) { - return UIScale.scale( new Insets( 4, 4, 4, 4 ) ); + return UIScale.scale( new Insets( 3, 7, 3, 7 ) ); } @Override From aa6fb2fcce555e8f7afaac0338e94642ade6a1c1 Mon Sep 17 00:00:00 2001 From: Karl Tauber Date: Sat, 20 Nov 2021 11:01:51 +0100 Subject: [PATCH 3/8] AnimatedPainter added (refactored from AnimatedBorder and AnimatedIcon) (checked API compatibility of AnimatedIcon with gradle task sigtestCheck) --- .../formdev/flatlaf/util/AnimatedBorder.java | 201 +----------------- .../formdev/flatlaf/util/AnimatedIcon.java | 177 ++------------- .../formdev/flatlaf/util/AnimatedPainter.java | 139 ++++++++++++ .../flatlaf/util/AnimatedPainterSupport.java | 144 +++++++++++++ .../testing/FlatAnimatedBorderTest.java | 19 +- 5 files changed, 325 insertions(+), 355 deletions(-) create mode 100644 flatlaf-core/src/main/java/com/formdev/flatlaf/util/AnimatedPainter.java create mode 100644 flatlaf-core/src/main/java/com/formdev/flatlaf/util/AnimatedPainterSupport.java diff --git a/flatlaf-core/src/main/java/com/formdev/flatlaf/util/AnimatedBorder.java b/flatlaf-core/src/main/java/com/formdev/flatlaf/util/AnimatedBorder.java index b47268e72..b681e729b 100644 --- a/flatlaf-core/src/main/java/com/formdev/flatlaf/util/AnimatedBorder.java +++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/util/AnimatedBorder.java @@ -18,15 +18,15 @@ import java.awt.Component; import java.awt.Graphics; +import java.awt.Graphics2D; import javax.swing.JComponent; import javax.swing.border.Border; -import com.formdev.flatlaf.util.Animator.Interpolator; /** * Border that automatically animates painting on component value changes. *

* {@link #getValue(Component)} returns the value of the component. - * If the value changes, then {@link #paintBorderAnimated(Component, Graphics, int, int, int, int, float)} + * If the value changes, then {@link #paintAnimated(Component, Graphics2D, int, int, int, int, float)} * is invoked multiple times with animated value (from old value to new value). *

* Example for an animated border: @@ -35,7 +35,7 @@ * implements AnimatedBorder * { * @Override - * public void paintBorderAnimated( Component c, Graphics g, int x, int y, int width, int height, float animatedValue ) { + * public void paintAnimated( Component c, Graphics2D g, int x, int y, int width, int height, float animatedValue ) { * int lh = UIScale.scale( 2 ); * * g.setColor( Color.blue ); @@ -65,199 +65,16 @@ * A client property is set on the component to store the animation state. * * @author Karl Tauber - * @since 1.5 + * @since 2 */ public interface AnimatedBorder - extends Border + extends Border, AnimatedPainter { - @Override - default void paintBorder( Component c, Graphics g, int x, int y, int width, int height ) { - AnimationSupport.paintBorder( this, c, g, x, y, width, height ); - } - - /** - * Paints the border for the given animated value. - * - * @param c the component that this border belongs to - * @param g the graphics context - * @param x the x coordinate of the border - * @param y the y coordinate of the border - * @param width the width coordinate of the border - * @param height the height coordinate of the border - * @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 - */ - void paintBorderAnimated( Component c, Graphics g, int x, int y, int width, int height, float animatedValue ); - - /** - * Repaint the animated part of the border. - *

- * 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 component. - */ - default void repaintBorder( Component c, int x, int y, int width, int height ) { - c.repaint( x, y, width, height ); - } - - /** - * Gets the value of the component. - *

- * This can be any value and depends on the component. - * If the value changes, then this class animates from the old value to the new one. - *

- * For a text field this could be {@code 0} for not focused and {@code 1} for focused. - */ - float getValue( Component c ); - - /** - * Returns whether animation is enabled for this border (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}. + * Invokes {@link #paintWithAnimation(Component, Graphics, int, int, int, int)}. */ - default Interpolator getAnimationInterpolator() { - return CubicBezierEasing.STANDARD_EASING; - } - - /** - * Returns the client property key used to store the animation support. - */ - default Object getClientPropertyKey() { - return getClass(); - } - - //---- class AnimationSupport --------------------------------------------- - - /** - * Animation support class that stores the animation state and implements the animation. - */ - class AnimationSupport - { - private float startValue; - private float targetValue; - private float animatedValue; - private float fraction; - - private Animator animator; - - // last bounds of the border needed to repaint while animating - private int x; - private int y; - private int width; - private int height; - - public static void paintBorder( AnimatedBorder border, Component c, Graphics g, - int x, int y, int width, int height ) - { - if( !isAnimationEnabled( border, 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 - paintBorderImpl( border, c, g, x, y, width, height, null ); - return; - } - - JComponent jc = (JComponent) c; - Object key = border.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 = border.getValue( c ); - jc.putClientProperty( key, as ); - } else { - // get component value - float value = border.getValue( c ); - - if( value != as.targetValue ) { - // value changed --> (re)start animation - - if( as.animator == null ) { - // create animator - AnimationSupport as2 = as; - as.animator = new Animator( border.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 border - border.repaintBorder( 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) (border.getAnimationDuration() * as.fraction); - if( duration2 > 0 ) - as.animator.setDuration( duration2 ); - as.startValue = as.animatedValue; - } else { - // new animation - as.animator.setDuration( border.getAnimationDuration() ); - as.animator.setResolution( border.getAnimationResolution() ); - as.animator.setInterpolator( border.getAnimationInterpolator() ); - - as.animatedValue = as.startValue; - } - - as.targetValue = value; - as.animator.start(); - } - } - - as.x = x; - as.y = y; - as.width = width; - as.height = height; - - paintBorderImpl( border, c, g, x, y, width, height, as ); - } - - private static void paintBorderImpl( AnimatedBorder border, Component c, Graphics g, - int x, int y, int width, int height, AnimationSupport as ) - { - float value = (as != null) ? as.animatedValue : border.getValue( c ); - border.paintBorderAnimated( c, g, x, y, width, height, value ); - } - - private static boolean isAnimationEnabled( AnimatedBorder border, Component c ) { - return Animator.useAnimation() && border.isAnimationEnabled() && c instanceof JComponent; - } + @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..5c39d2649 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,9 +18,9 @@ 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. @@ -65,15 +65,30 @@ * @author Karl Tauber */ public interface AnimatedIcon - extends Icon + extends Icon, AnimatedPainter { + /** + * Invokes {@link #paintWithAnimation(Component, Graphics, int, int, int, int)}. + */ @Override - public default void paintIcon( Component c, Graphics g, int x, int y ) { - AnimationSupport.paintIcon( this, c, g, x, y ); + default void paintIcon( Component c, Graphics g, int x, int y ) { + paintWithAnimation( c, g, x, y, getIconWidth(), getIconHeight() ); } /** - * Paints the icon for the given animated value. + * Bridge method that is called from (new) superclass and delegates to + * {@link #paintIconAnimated(Component, Graphics, int, int, float)}. + * Necessary for API compatibility. + * + * @since 2 + */ + @Override + default void paintAnimated( Component c, Graphics2D g, int x, int y, int width, int height, float animatedValue ) { + paintIconAnimated( c, g, x, y, animatedValue ); + } + + /** + * Paints the icon for the given (animated) value. * * @param c the component that this icon belongs to * @param g the graphics context @@ -85,165 +100,19 @@ public default void paintIcon( Component c, Graphics g, int x, int y ) { */ void paintIconAnimated( Component c, Graphics g, int x, int y, float animatedValue ); - /** - * Gets the value of the component. - *

- * This can be any value and depends on the component. - * If the value changes, then this class animates from the old value to the new one. - *

- * For a toggle button this could be {@code 0} for off and {@code 1} for on. - */ - float getValue( Component c ); - - /** - * Returns whether animation is enabled for this icon (default is {@code true}). - */ - default boolean isAnimationEnabled() { - return true; - } - - /** - * Returns the duration of the animation in milliseconds (default is 150). - */ - default int getAnimationDuration() { - return 150; - } - - /** - * Returns the resolution of the animation in milliseconds (default is 10). - * Resolution is the amount of time between timing events. - */ - default int getAnimationResolution() { - return 10; - } - - /** - * Returns the interpolator for the animation. - * Default is {@link CubicBezierEasing#STANDARD_EASING}. - */ - default Interpolator getAnimationInterpolator() { - return CubicBezierEasing.STANDARD_EASING; - } - - /** - * Returns the client property key used to store the animation support. - */ - default Object getClientPropertyKey() { - return getClass(); - } - //---- class AnimationSupport --------------------------------------------- /** - * Animation support class that stores the animation state and implements the animation. + * Animation support. */ 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; - public static void paintIcon( AnimatedIcon icon, Component c, Graphics g, int x, int y ) { - if( !isAnimationEnabled( icon, c ) ) { - // paint without animation if animation is disabled or - // component is not a JComponent and therefore does not support - // client properties, which are required to keep animation state - paintIconImpl( icon, c, g, x, y, null ); - return; - } - - JComponent jc = (JComponent) c; - Object key = icon.getClientPropertyKey(); - AnimationSupport as = (AnimationSupport) jc.getClientProperty( key ); - if( as == null ) { - // painted first time --> do not animate, but remember current component value - as = new AnimationSupport(); - as.startValue = as.targetValue = as.animatedValue = icon.getValue( c ); - as.x = x; - as.y = y; - jc.putClientProperty( key, as ); - } else { - // get component value - float value = icon.getValue( c ); - - if( value != as.targetValue ) { - // value changed --> (re)start animation - - if( as.animator == null ) { - // create animator - AnimationSupport as2 = as; - as.animator = new Animator( icon.getAnimationDuration(), fraction -> { - // check whether component was removed while animation is running - if( !c.isDisplayable() ) { - as2.animator.stop(); - return; - } - - // compute animated value - as2.animatedValue = as2.startValue + ((as2.targetValue - as2.startValue) * fraction); - as2.fraction = fraction; - - // repaint icon - c.repaint( as2.x, as2.y, icon.getIconWidth(), icon.getIconHeight() ); - }, () -> { - as2.startValue = as2.animatedValue = as2.targetValue; - as2.animator = null; - } ); - } - - if( as.animator.isRunning() ) { - // if animation is still running, restart it from the current - // animated value to the new target value with reduced duration - as.animator.cancel(); - int duration2 = (int) (icon.getAnimationDuration() * as.fraction); - if( duration2 > 0 ) - as.animator.setDuration( duration2 ); - as.startValue = as.animatedValue; - } else { - // new animation - as.animator.setDuration( icon.getAnimationDuration() ); - as.animator.setResolution( icon.getAnimationResolution() ); - as.animator.setInterpolator( icon.getAnimationInterpolator() ); - - as.animatedValue = as.startValue; - } - - as.targetValue = value; - as.animator.start(); - } - - as.x = x; - as.y = y; - } - - paintIconImpl( icon, c, g, x, y, as ); - } - - private static void paintIconImpl( AnimatedIcon icon, Component c, Graphics g, int x, int y, AnimationSupport as ) { - float value = (as != null) ? as.animatedValue : icon.getValue( c ); - icon.paintIconAnimated( c, g, x, y, value ); - } - - private static boolean isAnimationEnabled( AnimatedIcon icon, Component c ) { - return Animator.useAnimation() && icon.isAnimationEnabled() && c instanceof JComponent; + AnimatedPainterSupport.paint( icon, c, g, x, y, icon.getIconWidth(), icon.getIconHeight() ); } 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.saveLocation( 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..d85e76d22 --- /dev/null +++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/util/AnimatedPainter.java @@ -0,0 +1,139 @@ +/* + * 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 changes. + *

+ * {@link #getValue(Component)} returns the value of the component. + * If the value changes, then {@link #paintAnimated(Component, Graphics2D, int, int, int, int, float)} + * is invoked multiple times with animated value (from old value to new value). + *

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

+ * Animation works only if the component passed to {@link #paintWithAnimation(Component, Graphics, int, int, int, int)} + * is a instance of {@link JComponent}. + * A client property is set on the component to store the animation state. + * + * @author Karl Tauber + * @since 2 + */ +public interface AnimatedPainter +{ + /** + * Starts painting. + * Either invokes {@link #paintAnimated(Component, Graphics2D, int, int, int, int, float)} + * once to paint current value (see {@link #getValue(Component)}. Or if value has + * changed, compared to last painting, then it starts an animation and invokes + * {@link #paintAnimated(Component, Graphics2D, int, int, int, int, float)} + * multiple times with animated value (from old value to new value). + * + * @param c the component that this painter belongs to + * @param g the graphics context + * @param x the x coordinate of the paint area + * @param y the y coordinate of the paint area + * @param width the width of the paint area + * @param height the height of the paint area + */ + default void paintWithAnimation( Component c, Graphics g, int x, int y, int width, int height ) { + AnimatedPainterSupport.paint( this, c, g, x, y, width, height ); + } + + /** + * Paints the given (animated) value. + *

+ * 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 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 + */ + void paintAnimated( Component c, Graphics2D g, int x, int y, int width, int height, float animatedValue ); + + /** + * Invoked from animator to repaint an area. + *

+ * Useful to limit the repaint region. E.g. if only the bottom border is animated. + * If more than one border side is animated (e.g. bottom and right side), then it + * makes no sense to do separate repaints because the Swing repaint manager unions + * the regions and the whole component is repainted. + *

+ * The default implementation repaints the whole given area. + */ + default void repaintDuringAnimation( Component c, int x, int y, int width, int height ) { + c.repaint( x, y, width, height ); + } + + /** + * Gets the value 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. + */ + float getValue( Component c ); + + /** + * Returns whether animation is enabled for this painter (default is {@code true}). + */ + default boolean isAnimationEnabled() { + return true; + } + + /** + * Returns the duration of the animation in milliseconds (default is 150). + */ + default int getAnimationDuration() { + return 150; + } + + /** + * Returns the resolution of the animation in milliseconds (default is 10). + * Resolution is the amount of time between timing events. + */ + default int getAnimationResolution() { + return 10; + } + + /** + * Returns the interpolator for the animation. + * Default is {@link CubicBezierEasing#STANDARD_EASING}. + */ + default Interpolator getAnimationInterpolator() { + return CubicBezierEasing.STANDARD_EASING; + } + + /** + * Returns the client property key used to store the animation support. + */ + default Object getClientPropertyKey() { + return getClass(); + } +} 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..e27ac2e46 --- /dev/null +++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/util/AnimatedPainterSupport.java @@ -0,0 +1,144 @@ +/* + * Copyright 2021 FormDev Software GmbH + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.formdev.flatlaf.util; + +import java.awt.Component; +import java.awt.Graphics; +import java.awt.Graphics2D; +import javax.swing.JComponent; + +/** + * Animation support class that stores the animation state and implements the animation. + * + * @author Karl Tauber + * @since 2 + */ +class AnimatedPainterSupport +{ + private float startValue; + private float targetValue; + private float animatedValue; + private float fraction; + + private Animator animator; + + // last bounds of the paint area needed to repaint while animating + private int x; + private int y; + private int width; + private int height; + + static void paint( AnimatedPainter painter, Component c, Graphics g, + int x, int y, int width, int height ) + { + if( !isAnimationEnabled( painter, c ) ) { + // paint without animation if animation is disabled or + // component is not a JComponent and therefore does not support + // client properties, which are required to keep animation state + paintImpl( painter, c, g, x, y, width, height, null ); + return; + } + + JComponent jc = (JComponent) c; + Object key = painter.getClientPropertyKey(); + AnimatedPainterSupport as = (AnimatedPainterSupport) jc.getClientProperty( key ); + if( as == null ) { + // painted first time --> do not animate, but remember current component value + as = new AnimatedPainterSupport(); + as.startValue = as.targetValue = as.animatedValue = painter.getValue( c ); + jc.putClientProperty( key, as ); + } else { + // get component value + float value = painter.getValue( c ); + + if( value != as.targetValue ) { + // value changed --> (re)start animation + + if( as.animator == null ) { + // create animator + AnimatedPainterSupport as2 = as; + as.animator = new Animator( painter.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 + 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) (painter.getAnimationDuration() * as.fraction); + if( duration2 > 0 ) + as.animator.setDuration( duration2 ); + as.startValue = as.animatedValue; + } else { + // new animation + as.animator.setDuration( painter.getAnimationDuration() ); + as.animator.setResolution( painter.getAnimationResolution() ); + as.animator.setInterpolator( painter.getAnimationInterpolator() ); + + as.animatedValue = as.startValue; + } + + as.targetValue = value; + as.animator.start(); + } + } + + as.x = x; + as.y = y; + as.width = width; + as.height = height; + + paintImpl( painter, c, g, x, y, width, height, as ); + } + + private static void paintImpl( AnimatedPainter painter, Component c, Graphics g, + int x, int y, int width, int height, AnimatedPainterSupport as ) + { + float value = (as != null) ? as.animatedValue : painter.getValue( c ); + painter.paintAnimated( c, (Graphics2D) g, x, y, width, height, value ); + } + + private static boolean isAnimationEnabled( AnimatedPainter painter, Component c ) { + return Animator.useAnimation() && painter.isAnimationEnabled() && c instanceof JComponent; + } + + static void saveLocation( AnimatedPainter painter, Component c, int x, int y ) { + if( !isAnimationEnabled( painter, c ) ) + return; + + AnimatedPainterSupport as = (AnimatedPainterSupport) ((JComponent)c).getClientProperty( painter.getClientPropertyKey() ); + if( as != null ) { + as.x = x; + as.y = y; + } + } +} diff --git a/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatAnimatedBorderTest.java b/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatAnimatedBorderTest.java index c24acd688..2169ce39c 100644 --- a/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatAnimatedBorderTest.java +++ b/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatAnimatedBorderTest.java @@ -140,19 +140,20 @@ private class AnimatedFocusFadeBorder // javax.swing.border.AbstractBorder would be used @Override public void paintBorder( Component c, Graphics g, int x, int y, int width, int height ) { - AnimationSupport.paintBorder( this, c, g, x, y, width, height ); + paintWithAnimation( c, g, x, y, width, height ); } @Override - public void paintBorderAnimated( Component c, Graphics g, int x, int y, int width, int height, float animatedValue ) { + public void paintAnimated( Component c, Graphics2D g, int x, int y, int width, int height, float animatedValue ) { FlatUIUtils.setRenderingHints( g ); // border width is 1 if not focused and 2 if focused float lw = UIScale.scale( 1 + animatedValue ); // paint border - g.setColor( ColorFunctions.mix( Color.red, Color.lightGray, animatedValue ) ); - FlatUIUtils.paintComponentBorder( (Graphics2D) g, x, y, width, height, 0, lw, 0 ); + Color color = ColorFunctions.mix( Color.red, Color.lightGray, animatedValue ); + FlatUIUtils.paintOutlinedComponent( g, x, y, width, height, 0, 0, 0, lw, 0, + null, color, null ); } @Override @@ -188,15 +189,15 @@ private class AnimatedMaterialBorder // javax.swing.border.AbstractBorder would be used @Override public void paintBorder( Component c, Graphics g, int x, int y, int width, int height ) { - AnimationSupport.paintBorder( this, c, g, x, y, width, height ); + paintWithAnimation( c, g, x, y, width, height ); } @Override - public void paintBorderAnimated( Component c, Graphics g, int x, int y, int width, int height, float animatedValue ) { + public void paintAnimated( Component c, Graphics2D g, int x, int y, int width, int height, float animatedValue ) { FlatUIUtils.setRenderingHints( g ); // use paintAtScale1x() for consistent line thickness when scaled - HiDPIUtils.paintAtScale1x( (Graphics2D) g, x, y, width, height, + HiDPIUtils.paintAtScale1x( g, x, y, width, height, (g2d, x2, y2, width2, height2, scaleFactor) -> { float lh = (float) (UIScale.scale( 1f ) * scaleFactor); @@ -214,7 +215,7 @@ public void paintBorderAnimated( Component c, Graphics g, int x, int y, int widt } @Override - public void repaintBorder( Component c, int x, int y, int width, int height ) { + 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 ); @@ -247,7 +248,7 @@ private class AnimatedMinimalTestBorder implements AnimatedBorder { @Override - public void paintBorderAnimated( Component c, Graphics g, int x, int y, int width, int height, float animatedValue ) { + public void paintAnimated( Component c, Graphics2D g, int x, int y, int width, int height, float animatedValue ) { int lh = UIScale.scale( 2 ); g.setColor( Color.blue ); From 9523c89c5187f7ff99b8f9474f6cc156c05f9170 Mon Sep 17 00:00:00 2001 From: Karl Tauber Date: Sat, 20 Nov 2021 14:30:27 +0100 Subject: [PATCH 4/8] FlatAnimatorTest: added standard-easing and line chart (line chart copied from `FlatSmoothScrollingTest.LineChartPanel` in branch `smooth-scrolling` commit 331ab06b0087d3a70f7c131368b88e5e7c92102f) --- .../flatlaf/testing/FlatAnimatorTest.java | 481 ++++++++++++++++-- .../flatlaf/testing/FlatAnimatorTest.jfd | 126 ++++- 2 files changed, 564 insertions(+), 43 deletions(-) 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..bf83cc34b 100644 --- a/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatAnimatorTest.java +++ b/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatAnimatorTest.java @@ -17,9 +17,19 @@ package com.formdev.flatlaf.testing; import java.awt.*; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; import javax.swing.*; +import com.formdev.flatlaf.FlatLaf; +import com.formdev.flatlaf.ui.FlatUIUtils; import com.formdev.flatlaf.util.Animator; import com.formdev.flatlaf.util.CubicBezierEasing; +import com.formdev.flatlaf.util.HSLColor; +import com.formdev.flatlaf.util.HiDPIUtils; +import com.formdev.flatlaf.util.UIScale; +import com.formdev.flatlaf.util.Animator.Interpolator; import net.miginfocom.swing.*; /** @@ -28,8 +38,13 @@ public class FlatAnimatorTest extends FlatTestPanel { + private static final Color CHART_LINEAR = Color.blue; + private static final Color CHART_EASE_IN_OUT = Color.magenta; + private static final Color CHART_STANDARD_EASING = Color.red; + private Animator linearAnimator; private Animator easeInOutAnimator; + private Animator standardEasingAnimator; public static void main( String[] args ) { SwingUtilities.invokeLater( () -> { @@ -40,85 +55,483 @@ public static void main( String[] args ) { FlatAnimatorTest() { initComponents(); - } - private void start() { - startLinear(); - startEaseInOut(); + linearChartColor.setForeground( CHART_LINEAR ); + easeInOutChartColor.setForeground( CHART_EASE_IN_OUT ); + standardEasingChartColor.setForeground( CHART_STANDARD_EASING ); } - private void startLinear() { - if( linearAnimator != null ) { - linearAnimator.stop(); - linearAnimator.start(); - } else { - linearAnimator = new Animator( 1000, fraction -> { - linearScrollBar.setValue( Math.round( fraction * linearScrollBar.getMaximum() ) ); - } ); - linearAnimator.start(); - } + private void start() { + linearAnimator = start( linearAnimator, null, linearScrollBar, CHART_LINEAR ); + easeInOutAnimator = start( easeInOutAnimator, CubicBezierEasing.EASE_IN_OUT, easeInOutScrollBar, CHART_EASE_IN_OUT ); + standardEasingAnimator = start( standardEasingAnimator, CubicBezierEasing.STANDARD_EASING, standardEasingScrollBar, CHART_STANDARD_EASING ); } - private void startEaseInOut() { - if( easeInOutAnimator != null ) { - easeInOutAnimator.stop(); - easeInOutAnimator.start(); + private Animator start( Animator animator, Interpolator interpolator, JScrollBar scrollBar, Color chartColor ) { + if( animator != null ) { + animator.stop(); + animator.start(); } else { - easeInOutAnimator = new Animator( 1000, fraction -> { - easeInOutScrollBar.setValue( Math.round( fraction * easeInOutScrollBar.getMaximum() ) ); + animator = new Animator( 1000, fraction -> { + scrollBar.setValue( Math.round( fraction * scrollBar.getMaximum() ) ); + lineChartPanel.lineChart.addValue( fraction, chartColor ); } ); - easeInOutAnimator.setInterpolator( CubicBezierEasing.EASE_IN_OUT ); - easeInOutAnimator.start(); + animator.setInterpolator( interpolator ); + animator.start(); } + return animator; } private void initComponents() { // JFormDesigner - Component initialization - DO NOT MODIFY //GEN-BEGIN:initComponents - JLabel label1 = new JLabel(); + linearLabel = new JLabel(); + linearChartColor = new FlatAnimatorTest.JChartColor(); linearScrollBar = new JScrollBar(); - JLabel label2 = new JLabel(); + easeInOutLabel = new JLabel(); + easeInOutChartColor = new FlatAnimatorTest.JChartColor(); easeInOutScrollBar = new JScrollBar(); + standardEasingLabel = new JLabel(); + standardEasingChartColor = new FlatAnimatorTest.JChartColor(); + standardEasingScrollBar = new JScrollBar(); startButton = new JButton(); + lineChartPanel = new FlatAnimatorTest.LineChartPanel(); //======== this ======== setLayout(new MigLayout( "ltr,insets dialog,hidemode 3", // columns "[fill]" + + "[fill]" + "[grow,fill]", // rows "[]" + "[]" + - "[]")); + "[]" + + "[]para" + + "[400,grow,fill]")); - //---- label1 ---- - label1.setText("Linear:"); - add(label1, "cell 0 0"); + //---- linearLabel ---- + linearLabel.setText("Linear:"); + add(linearLabel, "cell 0 0"); + add(linearChartColor, "cell 1 0"); //---- linearScrollBar ---- linearScrollBar.setOrientation(Adjustable.HORIZONTAL); linearScrollBar.setBlockIncrement(1); - add(linearScrollBar, "cell 1 0"); + add(linearScrollBar, "cell 2 0"); - //---- label2 ---- - label2.setText("Ease in out:"); - add(label2, "cell 0 1"); + //---- easeInOutLabel ---- + easeInOutLabel.setText("Ease in out:"); + add(easeInOutLabel, "cell 0 1"); + add(easeInOutChartColor, "cell 1 1"); //---- easeInOutScrollBar ---- easeInOutScrollBar.setOrientation(Adjustable.HORIZONTAL); easeInOutScrollBar.setBlockIncrement(1); - add(easeInOutScrollBar, "cell 1 1"); + add(easeInOutScrollBar, "cell 2 1"); + + //---- standardEasingLabel ---- + standardEasingLabel.setText("Standard easing:"); + add(standardEasingLabel, "cell 0 2"); + add(standardEasingChartColor, "cell 1 2"); + + //---- standardEasingScrollBar ---- + standardEasingScrollBar.setOrientation(Adjustable.HORIZONTAL); + standardEasingScrollBar.setBlockIncrement(1); + add(standardEasingScrollBar, "cell 2 2"); //---- startButton ---- startButton.setText("Start"); startButton.addActionListener(e -> start()); - add(startButton, "cell 0 2"); + add(startButton, "cell 0 3"); + add(lineChartPanel, "cell 0 4 3 1"); // JFormDesigner - End of component initialization //GEN-END:initComponents } // JFormDesigner - Variables declaration - DO NOT MODIFY //GEN-BEGIN:variables + private JLabel linearLabel; + private FlatAnimatorTest.JChartColor linearChartColor; private JScrollBar linearScrollBar; + private JLabel easeInOutLabel; + private FlatAnimatorTest.JChartColor easeInOutChartColor; private JScrollBar easeInOutScrollBar; + private JLabel standardEasingLabel; + private FlatAnimatorTest.JChartColor standardEasingChartColor; + private JScrollBar standardEasingScrollBar; private JButton startButton; + private FlatAnimatorTest.LineChartPanel lineChartPanel; // JFormDesigner - End of variables declaration //GEN-END:variables + + //---- class LineChartPanel ----------------------------------------------- + + static class LineChartPanel + extends JPanel + { + LineChartPanel() { + initComponents(); + + updateChartDelayedChanged(); + lineChart.setSecondWidth( 500 ); + } + + private void updateChartDelayedChanged() { + lineChart.setUpdateDelayed( updateChartDelayedCheckBox.isSelected() ); + } + + private void clearChart() { + lineChart.clear(); + } + + private void initComponents() { + // JFormDesigner - Component initialization - DO NOT MODIFY //GEN-BEGIN:initComponents + JScrollPane lineChartScrollPane = new JScrollPane(); + lineChart = new FlatAnimatorTest.LineChart(); + JLabel lineChartInfoLabel = new JLabel(); + updateChartDelayedCheckBox = new JCheckBox(); + JButton clearChartButton = new JButton(); + + //======== this ======== + setLayout(new MigLayout( + "ltr,insets 0,hidemode 3", + // columns + "[fill]" + + "[grow,fill]", + // rows + "[400,grow,fill]" + + "[]")); + + //======== lineChartScrollPane ======== + { + lineChartScrollPane.putClientProperty("JScrollPane.smoothScrolling", false); + lineChartScrollPane.setViewportView(lineChart); + } + add(lineChartScrollPane, "cell 0 0 2 1"); + + //---- lineChartInfoLabel ---- + lineChartInfoLabel.setText("X: time (500ms per line) / Y: value (10% per line)"); + add(lineChartInfoLabel, "cell 0 1 2 1"); + + //---- updateChartDelayedCheckBox ---- + updateChartDelayedCheckBox.setText("Update chart delayed"); + updateChartDelayedCheckBox.setMnemonic('U'); + updateChartDelayedCheckBox.addActionListener(e -> updateChartDelayedChanged()); + add(updateChartDelayedCheckBox, "cell 0 1 2 1,alignx right,growx 0"); + + //---- clearChartButton ---- + clearChartButton.setText("Clear Chart"); + clearChartButton.setMnemonic('C'); + clearChartButton.addActionListener(e -> clearChart()); + add(clearChartButton, "cell 0 1 2 1,alignx right,growx 0"); + // JFormDesigner - End of component initialization //GEN-END:initComponents + } + + // JFormDesigner - Variables declaration - DO NOT MODIFY //GEN-BEGIN:variables + FlatAnimatorTest.LineChart lineChart; + private JCheckBox updateChartDelayedCheckBox; + // JFormDesigner - End of variables declaration //GEN-END:variables + } + + //---- class LineChart ---------------------------------------------------- + + static class LineChart + extends JComponent + implements Scrollable + { + private static final int NEW_SEQUENCE_TIME_LAG = 500; + private static final int NEW_SEQUENCE_GAP = 50; + + private int secondWidth = 1000; + + private static class Data { + final double value; + final boolean dot; + final long time; // in milliseconds + + Data( double value, boolean dot, long time ) { + this.value = value; + this.dot = dot; + this.time = time; + } + + @Override + public String toString() { + // for debugging + return String.valueOf( value ); + } + } + + private final Map> color2dataMap = new HashMap<>(); + private final Timer repaintTime; + private Color lastUsedChartColor; + private boolean updateDelayed; + + LineChart() { + repaintTime = new Timer( 20, e -> repaintAndRevalidate() ); + repaintTime.setRepeats( false ); + } + + void addValue( double value, Color chartColor ) { + addValue( value, false, chartColor ); + } + + void addValue( double value, boolean dot, Color chartColor ) { + List chartData = color2dataMap.computeIfAbsent( chartColor, k -> new ArrayList<>() ); + chartData.add( new Data( value, dot, System.nanoTime() / 1000000) ); + + lastUsedChartColor = chartColor; + + if( updateDelayed ) { + repaintTime.stop(); + repaintTime.start(); + } else + repaintAndRevalidate(); + } + + void clear() { + color2dataMap.clear(); + lastUsedChartColor = null; + + repaint(); + revalidate(); + } + + void setUpdateDelayed( boolean updateDelayed ) { + this.updateDelayed = updateDelayed; + } + + void setSecondWidth( int secondWidth ) { + this.secondWidth = secondWidth; + } + + private void repaintAndRevalidate() { + repaint(); + revalidate(); + + // scroll horizontally + if( lastUsedChartColor != null ) { + // compute chart width of last used color and start of last sequence + int[] lastSeqX = new int[1]; + int cw = chartWidth( color2dataMap.get( lastUsedChartColor ), lastSeqX ); + + // scroll to end of last sequence (of last used color) + int lastSeqWidth = cw - lastSeqX[0]; + int width = Math.min( lastSeqWidth, getParent().getWidth() ); + int x = cw - width; + scrollRectToVisible( new Rectangle( x, 0, width, getHeight() ) ); + } + } + + @Override + protected void paintComponent( Graphics g ) { + Graphics g2 = g.create(); + try { + HiDPIUtils.paintAtScale1x( (Graphics2D) g2, this, this::paintImpl ); + } finally { + g2.dispose(); + } + } + + private void paintImpl( Graphics2D g, int x, int y, int width, int height, double scaleFactor ) { + FlatUIUtils.setRenderingHints( g ); + + int secondWidth = (int) (this.secondWidth * scaleFactor); + int seqGapWidth = (int) (NEW_SEQUENCE_GAP * scaleFactor); + + Color lineColor = FlatUIUtils.getUIColor( "Component.borderColor", Color.lightGray ); + Color lineColor2 = FlatLaf.isLafDark() + ? new HSLColor( lineColor ).adjustTone( 30 ) + : new HSLColor( lineColor ).adjustShade( 30 ); + + g.translate( x, y ); + + // fill background + g.setColor( UIManager.getColor( "Table.background" ) ); + g.fillRect( x, y, width, height ); + + // paint horizontal lines + for( int i = 1; i < 10; i++ ) { + int hy = (height * i) / 10; + g.setColor( (i != 5) ? lineColor : lineColor2 ); + g.drawLine( 0, hy, width, hy ); + } + + // paint vertical lines + int twoHundredMillisWidth = secondWidth / 5; + for( int i = twoHundredMillisWidth; i < width; i += twoHundredMillisWidth ) { + g.setColor( (i % secondWidth != 0) ? lineColor : lineColor2 ); + g.drawLine( i, 0, i, height ); + } + + // paint lines + for( Map.Entry> e : color2dataMap.entrySet() ) { + List chartData = e.getValue(); + Color chartColor = e.getKey(); + if( FlatLaf.isLafDark() ) + chartColor = new HSLColor( chartColor ).adjustTone( 50 ); + Color temporaryValueColor = new Color( (chartColor.getRGB() & 0xffffff) | 0x40000000, true ); + + long seqTime = 0; + int seqX = 0; + long ptime = 0; + int px = 0; + int py = 0; + int pcount = 0; + + g.setColor( chartColor ); + + boolean first = true; + int size = chartData.size(); + for( int i = 0; i < size; i++ ) { + Data data = chartData.get( i ); + int dy = (int) ((height - 1) * data.value); + + if( data.dot ) { + int dotx = px; + if( i > 0 && data.time > ptime + NEW_SEQUENCE_TIME_LAG ) + dotx += seqGapWidth; + int o = UIScale.scale( 1 ); + int s = UIScale.scale( 3 ); + g.fillRect( dotx - o, dy - o, s, s ); + continue; + } + + if( data.time > ptime + NEW_SEQUENCE_TIME_LAG ) { + if( !first && pcount == 0 ) + g.drawLine( px, py, px + (int) (4 * scaleFactor), py ); + + // start new sequence + seqTime = data.time; + seqX = !first ? px + seqGapWidth : 0; + px = seqX; + pcount = 0; + first = false; + } else { + boolean isTemporaryValue = isTemporaryValue( chartData, i ) || isTemporaryValue( chartData, i - 1 ); + if( isTemporaryValue ) + g.setColor( temporaryValueColor ); + + // line in sequence + int dx = (int) (seqX + (((data.time - seqTime) / 1000.) * secondWidth)); + g.drawLine( px, py, dx, dy ); + px = dx; + pcount++; + + if( isTemporaryValue ) + g.setColor( chartColor ); + } + + py = dy; + ptime = data.time; + } + } + } + + /** + * One or two values between two equal values are considered "temporary", + * which means that they are the target value for the following scroll animation. + */ + private boolean isTemporaryValue( List chartData, int i ) { + if( i == 0 || i == chartData.size() - 1 ) + return false; + + Data dataBefore = chartData.get( i - 1 ); + Data dataAfter = chartData.get( i + 1 ); + + if( dataBefore.dot || dataAfter.dot ) + return false; + + double valueBefore = dataBefore.value; + double valueAfter = dataAfter.value; + + return valueBefore == valueAfter || + (i < chartData.size() - 2 && valueBefore == chartData.get( i + 2 ).value) || + (i > 1 && chartData.get( i - 2 ).value == valueAfter); + } + + private int chartWidth() { + int width = 0; + for( List chartData : color2dataMap.values() ) + width = Math.max( width, chartWidth( chartData, null ) ); + return width; + } + + private int chartWidth( List chartData, int[] lastSeqX ) { + long seqTime = 0; + int seqX = 0; + long ptime = 0; + int px = 0; + + int size = chartData.size(); + for( int i = 0; i < size; i++ ) { + Data data = chartData.get( i ); + + if( data.time > ptime + NEW_SEQUENCE_TIME_LAG ) { + // start new sequence + seqTime = data.time; + seqX = (i > 0) ? px + NEW_SEQUENCE_GAP : 0; + px = seqX; + } else { + // line in sequence + int dx = (int) (seqX + (((data.time - seqTime) / 1000.) * secondWidth)); + px = dx; + } + + ptime = data.time; + } + + if( lastSeqX != null ) + lastSeqX[0] = seqX; + + return px; + } + + @Override + public Dimension getPreferredSize() { + return new Dimension( chartWidth(), 200 ); + } + + @Override + public Dimension getPreferredScrollableViewportSize() { + return new Dimension( chartWidth(), 200 ); + } + + @Override + public int getScrollableUnitIncrement( Rectangle visibleRect, int orientation, int direction ) { + return secondWidth; + } + + @Override + public int getScrollableBlockIncrement( Rectangle visibleRect, int orientation, int direction ) { + JViewport viewport = (JViewport) SwingUtilities.getAncestorOfClass( JViewport.class, this ); + return (viewport != null) ? viewport.getWidth() : 200; + } + + @Override + public boolean getScrollableTracksViewportWidth() { + JViewport viewport = (JViewport) SwingUtilities.getAncestorOfClass( JViewport.class, this ); + return (viewport != null) ? viewport.getWidth() > chartWidth() : true; + } + + @Override + public boolean getScrollableTracksViewportHeight() { + return true; + } + } + + //---- class JChartColor -------------------------------------------------- + + static class JChartColor + extends JComponent + { + @Override + public Dimension getPreferredSize() { + return new Dimension( UIScale.scale( 24 ), UIScale.scale( 12 ) ); + } + + @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..a3ec5caa4 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: "7.0.5.0.382" Java: "16" 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,70 @@ 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.FlatAnimatorTest$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 ) + } ) + add( new FormContainer( "javax.swing.JPanel", new FormLayoutManager( class net.miginfocom.swing.MigLayout ) { + "$columnConstraints": "[fill][grow,fill]" + "$rowConstraints": "[400,grow,fill][]" + "$layoutConstraints": "ltr,insets 0,hidemode 3" + } ) { + name: "lineChartPanelNested" + auxiliary() { + "JavaCodeGenerator.className": "LineChartPanel" + } + add( new FormContainer( "javax.swing.JScrollPane", new FormLayoutManager( class javax.swing.JScrollPane ) ) { + name: "lineChartScrollPane" + "$client.JScrollPane.smoothScrolling": false + add( new FormComponent( "com.formdev.flatlaf.testing.FlatAnimatorTest$LineChart" ) { + name: "lineChart" + auxiliary() { + "JavaCodeGenerator.variableLocal": false + "JavaCodeGenerator.variableModifiers": 0 + } + } ) + }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { + "value": "cell 0 0 2 1" + } ) + add( new FormComponent( "javax.swing.JLabel" ) { + name: "lineChartInfoLabel" + "text": "X: time (500ms per line) / Y: value (10% per line)" + }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { + "value": "cell 0 1 2 1" + } ) + add( new FormComponent( "javax.swing.JCheckBox" ) { + name: "updateChartDelayedCheckBox" + "text": "Update chart delayed" + "mnemonic": 85 + auxiliary() { + "JavaCodeGenerator.variableLocal": false + } + addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "updateChartDelayedChanged", false ) ) + }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { + "value": "cell 0 1 2 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 2 1,alignx right,growx 0" + } ) + }, new FormLayoutConstraints( null ) { + "location": new java.awt.Point( 0, 650 ) + "size": new java.awt.Dimension( 603, 325 ) } ) } } From b903f181307af1be0dc97fc437ff40855f116fda Mon Sep 17 00:00:00 2001 From: Karl Tauber Date: Sun, 21 Nov 2021 12:27:31 +0100 Subject: [PATCH 5/8] FlatAnimatorTest: - support synchronized line chart - LineChartPanel: added slider to change horizontal scaling - FlatAnimatedIconTest: added line chart panel - FlatAnimatedBorderTest: added line chart panel --- .../testing/FlatAnimatedBorderTest.java | 95 ++++++-- .../testing/FlatAnimatedBorderTest.jfd | 46 +++- .../flatlaf/testing/FlatAnimatedIconTest.java | 62 ++++- .../flatlaf/testing/FlatAnimatedIconTest.jfd | 40 +++- .../flatlaf/testing/FlatAnimatorTest.java | 214 +++++++++++------- .../flatlaf/testing/FlatAnimatorTest.jfd | 11 + 6 files changed, 354 insertions(+), 114 deletions(-) diff --git a/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatAnimatedBorderTest.java b/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatAnimatedBorderTest.java index 2169ce39c..30d864145 100644 --- a/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatAnimatedBorderTest.java +++ b/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatAnimatedBorderTest.java @@ -37,6 +37,14 @@ 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_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" ); @@ -47,25 +55,43 @@ public static void main( String[] args ) { FlatAnimatedBorderTest() { initComponents(); - textField5.setBorder( new AnimatedFocusFadeBorder() ); - textField6.setBorder( new AnimatedFocusFadeBorder() ); + fade1TextField.setBorder( new AnimatedFocusFadeBorder() ); + fade2TextField.setBorder( new AnimatedFocusFadeBorder() ); + + material1TextField.setBorder( new AnimatedMaterialBorder() ); + material2TextField.setBorder( new AnimatedMaterialBorder() ); + + minimalTextField.setBorder( new AnimatedMinimalTestBorder() ); - textField1.setBorder( new AnimatedMaterialBorder() ); - textField2.setBorder( new AnimatedMaterialBorder() ); + 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 ); + minimalTextField.putClientProperty( CHART_COLOR_KEY, CHART_MINIMAL ); - textField4.setBorder( new AnimatedMinimalTestBorder() ); + fade1ChartColor.setForeground( CHART_FADE_1 ); + fade2ChartColor.setForeground( CHART_FADE_2 ); + material1ChartColor.setForeground( CHART_MATERIAL_1 ); + material2ChartColor.setForeground( CHART_MATERIAL_2 ); + minimalChartColor.setForeground( CHART_MINIMAL ); } private void initComponents() { // JFormDesigner - Component initialization - DO NOT MODIFY //GEN-BEGIN:initComponents label3 = new JLabel(); - textField5 = new JTextField(); - textField6 = new JTextField(); + lineChartPanel = new FlatAnimatorTest.LineChartPanel(); + fade1TextField = new JTextField(); + fade1ChartColor = new FlatAnimatorTest.JChartColor(); + fade2TextField = new JTextField(); + fade2ChartColor = new FlatAnimatorTest.JChartColor(); label2 = new JLabel(); - textField1 = new JTextField(); - textField2 = new JTextField(); + material1TextField = new JTextField(); + material1ChartColor = new FlatAnimatorTest.JChartColor(); + material2TextField = new JTextField(); + material2ChartColor = new FlatAnimatorTest.JChartColor(); label1 = new JLabel(); - textField4 = new JTextField(); + minimalTextField = new JTextField(); + minimalChartColor = new FlatAnimatorTest.JChartColor(); durationLabel = new JLabel(); durationField = new JSpinner(); @@ -73,6 +99,8 @@ private void initComponents() { setLayout(new MigLayout( "insets dialog,hidemode 3", // columns + "[fill]" + + "[fill]para" + "[fill]", // rows "[]" + @@ -89,19 +117,25 @@ private void initComponents() { //---- label3 ---- label3.setText("Fade:"); add(label3, "cell 0 0"); - add(textField5, "cell 0 1"); - add(textField6, "cell 0 2"); + add(lineChartPanel, "cell 2 0 1 10"); + 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(textField1, "cell 0 4"); - add(textField2, "cell 0 5"); + add(material1TextField, "cell 0 4"); + add(material1ChartColor, "cell 1 4"); + add(material2TextField, "cell 0 5"); + add(material2ChartColor, "cell 1 5"); //---- label1 ---- label1.setText("Minimal:"); add(label1, "cell 0 6"); - add(textField4, "cell 0 7"); + add(minimalTextField, "cell 0 7"); + add(minimalChartColor, "cell 1 7"); //---- durationLabel ---- durationLabel.setText("Duration:"); @@ -115,13 +149,19 @@ private void initComponents() { // JFormDesigner - Variables declaration - DO NOT MODIFY //GEN-BEGIN:variables private JLabel label3; - private JTextField textField5; - private JTextField textField6; + private FlatAnimatorTest.LineChartPanel lineChartPanel; + private JTextField fade1TextField; + private FlatAnimatorTest.JChartColor fade1ChartColor; + private JTextField fade2TextField; + private FlatAnimatorTest.JChartColor fade2ChartColor; private JLabel label2; - private JTextField textField1; - private JTextField textField2; + private JTextField material1TextField; + private FlatAnimatorTest.JChartColor material1ChartColor; + private JTextField material2TextField; + private FlatAnimatorTest.JChartColor material2ChartColor; private JLabel label1; - private JTextField textField4; + private JTextField minimalTextField; + private FlatAnimatorTest.JChartColor minimalChartColor; private JLabel durationLabel; private JSpinner durationField; // JFormDesigner - End of variables declaration //GEN-END:variables @@ -154,6 +194,11 @@ public void paintAnimated( Component c, Graphics2D g, int x, int y, int width, i Color color = ColorFunctions.mix( Color.red, Color.lightGray, animatedValue ); FlatUIUtils.paintOutlinedComponent( g, x, y, width, height, 0, 0, 0, lw, 0, null, color, null ); + + if( animatedValue != 0 && animatedValue != 1 ) { + Color chartColor = (Color) ((JComponent)c).getClientProperty( CHART_COLOR_KEY ); + lineChartPanel.lineChart.addValue( animatedValue, chartColor ); + } } @Override @@ -212,6 +257,11 @@ public void paintAnimated( Component c, Graphics2D g, int x, int y, int width, i g2d.fill( new Rectangle2D.Float( x2 + (width2 - lw) / 2, y2 + height2 - lh, lw, lh ) ); } } ); + + if( animatedValue != 0 && animatedValue != 1 ) { + Color chartColor = (Color) ((JComponent)c).getClientProperty( CHART_COLOR_KEY ); + lineChartPanel.lineChart.addValue( animatedValue, chartColor ); + } } @Override @@ -253,6 +303,11 @@ public void paintAnimated( Component c, Graphics2D g, int x, int y, int width, i g.setColor( Color.blue ); g.fillRect( x, y + height - lh, Math.round( width * animatedValue ), lh ); + + if( animatedValue != 0 && animatedValue != 1 ) { + Color chartColor = (Color) ((JComponent)c).getClientProperty( CHART_COLOR_KEY ); + lineChartPanel.lineChart.addValue( animatedValue, chartColor ); + } } @Override 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 index cae2e60df..cf32c3188 100644 --- a/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatAnimatedBorderTest.jfd +++ b/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatAnimatedBorderTest.jfd @@ -1,11 +1,11 @@ -JFDML JFormDesigner: "7.0.4.0.360" Java: "16" encoding: "UTF-8" +JFDML JFormDesigner: "7.0.5.0.382" Java: "16" encoding: "UTF-8" new FormModel { contentType: "form/swing" root: new FormRoot { add( new FormContainer( "com.formdev.flatlaf.testing.FlatTestPanel", new FormLayoutManager( class net.miginfocom.swing.MigLayout ) { "$layoutConstraints": "insets dialog,hidemode 3" - "$columnConstraints": "[fill]" + "$columnConstraints": "[fill][fill]para[fill]" "$rowConstraints": "[][][]para[][][]para[][][grow][]" } ) { name: "this" @@ -15,16 +15,31 @@ new FormModel { }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { "value": "cell 0 0" } ) + add( new FormComponent( "com.formdev.flatlaf.testing.FlatAnimatorTest$LineChartPanel" ) { + name: "lineChartPanel" + }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { + "value": "cell 2 0 1 10" + } ) add( new FormComponent( "javax.swing.JTextField" ) { - name: "textField5" + 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: "textField6" + 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:" @@ -32,15 +47,25 @@ new FormModel { "value": "cell 0 3" } ) add( new FormComponent( "javax.swing.JTextField" ) { - name: "textField1" + 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: "textField2" + 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.JLabel" ) { name: "label1" "text": "Minimal:" @@ -48,10 +73,15 @@ new FormModel { "value": "cell 0 6" } ) add( new FormComponent( "javax.swing.JTextField" ) { - name: "textField4" + name: "minimalTextField" }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { "value": "cell 0 7" } ) + add( new FormComponent( "com.formdev.flatlaf.testing.FlatAnimatorTest$JChartColor" ) { + name: "minimalChartColor" + }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { + "value": "cell 1 7" + } ) add( new FormComponent( "javax.swing.JLabel" ) { name: "durationLabel" "text": "Duration:" @@ -70,7 +100,7 @@ new FormModel { } ) }, new FormLayoutConstraints( null ) { "location": new java.awt.Point( 0, 0 ) - "size": new java.awt.Dimension( 405, 315 ) + "size": new java.awt.Dimension( 725, 325 ) } ) } } diff --git a/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatAnimatedIconTest.java b/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatAnimatedIconTest.java index 160caca65..2788711ca 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 @@ -33,6 +33,14 @@ 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 String CHART_COLOR_KEY = "chartColor"; + public static void main( String[] args ) { SwingUtilities.invokeLater( () -> { FlatTestFrame frame = FlatTestFrame.create( args, "FlatAnimatedIconTest" ); @@ -50,15 +58,33 @@ public static void main( String[] args ) { checkBox1.setIcon( new AnimatedSwitchIcon() ); checkBox2.setIcon( new AnimatedMinimalTestIcon() ); + + radioButton1.putClientProperty( CHART_COLOR_KEY, CHART_RADIO_BUTTON_1 ); + radioButton2.putClientProperty( CHART_COLOR_KEY, CHART_RADIO_BUTTON_2 ); + radioButton3.putClientProperty( CHART_COLOR_KEY, CHART_RADIO_BUTTON_3 ); + checkBox1.putClientProperty( CHART_COLOR_KEY, CHART_CHECK_BOX_1 ); + checkBox2.putClientProperty( CHART_COLOR_KEY, CHART_CHECK_BOX_2 ); + + radioButton1ChartColor.setForeground( CHART_RADIO_BUTTON_1 ); + radioButton2ChartColor.setForeground( CHART_RADIO_BUTTON_2 ); + radioButton3ChartColor.setForeground( CHART_RADIO_BUTTON_3 ); + checkBox1ChartColor.setForeground( CHART_CHECK_BOX_1 ); + checkBox2ChartColor.setForeground( CHART_CHECK_BOX_2 ); } private void initComponents() { // JFormDesigner - Component initialization - DO NOT MODIFY //GEN-BEGIN:initComponents radioButton1 = new JRadioButton(); + radioButton1ChartColor = new FlatAnimatorTest.JChartColor(); + lineChartPanel = new FlatAnimatorTest.LineChartPanel(); radioButton2 = new JRadioButton(); + radioButton2ChartColor = new FlatAnimatorTest.JChartColor(); radioButton3 = new JRadioButton(); + radioButton3ChartColor = new FlatAnimatorTest.JChartColor(); checkBox1 = new JCheckBox(); + checkBox1ChartColor = new FlatAnimatorTest.JChartColor(); checkBox2 = new JCheckBox(); + checkBox2ChartColor = new FlatAnimatorTest.JChartColor(); durationLabel = new JLabel(); durationField = new JSpinner(); @@ -66,8 +92,9 @@ private void initComponents() { setLayout(new MigLayout( "insets dialog,hidemode 3", // columns - "[]para" + - "[fill]", + "[]" + + "[fill]para" + + "[grow,fill]", // rows "[]" + "[]" + @@ -81,30 +108,36 @@ 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 6"); //---- 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"); //---- checkBox2 ---- checkBox2.setText("minimal"); add(checkBox2, "cell 0 4"); + add(checkBox2ChartColor, "cell 1 4"); //---- durationLabel ---- durationLabel.setText("Duration:"); - add(durationLabel, "cell 0 6 2 1"); + add(durationLabel, "cell 0 6 3 1"); //---- durationField ---- durationField.setModel(new SpinnerNumberModel(200, 100, null, 50)); - add(durationField, "cell 0 6 2 1"); + add(durationField, "cell 0 6 3 1"); //---- buttonGroup1 ---- ButtonGroup buttonGroup1 = new ButtonGroup(); @@ -116,10 +149,16 @@ private void initComponents() { // JFormDesigner - Variables declaration - DO NOT MODIFY //GEN-BEGIN:variables private JRadioButton radioButton1; + private FlatAnimatorTest.JChartColor radioButton1ChartColor; + private FlatAnimatorTest.LineChartPanel lineChartPanel; private JRadioButton radioButton2; + private FlatAnimatorTest.JChartColor radioButton2ChartColor; private JRadioButton radioButton3; + private FlatAnimatorTest.JChartColor radioButton3ChartColor; private JCheckBox checkBox1; + private FlatAnimatorTest.JChartColor checkBox1ChartColor; private JCheckBox checkBox2; + private FlatAnimatorTest.JChartColor checkBox2ChartColor; private JLabel durationLabel; private JSpinner durationField; // JFormDesigner - End of variables declaration //GEN-END:variables @@ -163,6 +202,11 @@ public void paintIconAnimated( Component c, Graphics g, int x, int y, float anim float xy = (SIZE - dotDiameter) / 2f; g.setColor( color ); ((Graphics2D)g).fill( new Ellipse2D.Float( xy, xy, dotDiameter, dotDiameter ) ); + + if( animatedValue != 0 && animatedValue != 1 ) { + Color chartColor = (Color) ((JComponent)c).getClientProperty( CHART_COLOR_KEY ); + lineChartPanel.lineChart.addValue( animatedValue, chartColor ); + } } @Override @@ -200,6 +244,11 @@ public void paintIconAnimated( Component c, Graphics g, int x, int y, float anim int thumbY = y + 2; g.setColor( Color.white ); ((Graphics2D)g).fill( new Ellipse2D.Float( thumbX, thumbY, thumbSize, thumbSize ) ); + + if( animatedValue != 0 && animatedValue != 1 ) { + Color chartColor = (Color) ((JComponent)c).getClientProperty( CHART_COLOR_KEY ); + lineChartPanel.lineChart.addValue( animatedValue, chartColor ); + } } @Override @@ -239,6 +288,11 @@ public void paintIconAnimated( Component c, Graphics g, int x, int y, float anim g.setColor( Color.red ); g.drawRect( x, y, w - 1, h - 1 ); g.fillRect( x, y, Math.round( w * animatedValue ), h ); + + if( animatedValue != 0 && animatedValue != 1 ) { + Color chartColor = (Color) ((JComponent)c).getClientProperty( CHART_COLOR_KEY ); + lineChartPanel.lineChart.addValue( animatedValue, chartColor ); + } } @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..8e9b20974 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,11 +1,11 @@ -JFDML JFormDesigner: "7.0.2.0.298" Java: "15" encoding: "UTF-8" +JFDML JFormDesigner: "7.0.5.0.382" Java: "16" encoding: "UTF-8" new FormModel { contentType: "form/swing" root: new FormRoot { add( new FormContainer( "com.formdev.flatlaf.testing.FlatTestPanel", new FormLayoutManager( class net.miginfocom.swing.MigLayout ) { "$layoutConstraints": "insets dialog,hidemode 3" - "$columnConstraints": "[]para[fill]" + "$columnConstraints": "[][fill]para[grow,fill]" "$rowConstraints": "[][][]para[][][grow][]" } ) { name: "this" @@ -17,6 +17,16 @@ new FormModel { }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { "value": "cell 0 0" } ) + add( new FormComponent( "com.formdev.flatlaf.testing.FlatAnimatorTest$JChartColor" ) { + name: "radioButton1ChartColor" + }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { + "value": "cell 1 0" + } ) + add( new FormComponent( "com.formdev.flatlaf.testing.FlatAnimatorTest$LineChartPanel" ) { + name: "lineChartPanel" + }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { + "value": "cell 2 0 1 6" + } ) 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,23 +46,38 @@ 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: "checkBox2" "text": "minimal" }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { "value": "cell 0 4" } ) + add( new FormComponent( "com.formdev.flatlaf.testing.FlatAnimatorTest$JChartColor" ) { + name: "checkBox2ChartColor" + }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { + "value": "cell 1 4" + } ) 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 6 3 1" } ) add( new FormComponent( "javax.swing.JSpinner" ) { name: "durationField" @@ -57,11 +87,11 @@ new FormModel { value: 200 } }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { - "value": "cell 0 6 2 1" + "value": "cell 0 6 3 1" } ) }, new FormLayoutConstraints( null ) { "location": new java.awt.Point( 0, 0 ) - "size": new java.awt.Dimension( 415, 350 ) + "size": new java.awt.Dimension( 810, 350 ) } ) add( new FormNonVisual( "javax.swing.ButtonGroup" ) { name: "buttonGroup1" diff --git a/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatAnimatorTest.java b/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatAnimatorTest.java index bf83cc34b..11928d854 100644 --- a/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatAnimatorTest.java +++ b/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatAnimatorTest.java @@ -170,8 +170,17 @@ static class LineChartPanel LineChartPanel() { initComponents(); + secondsWidthSlider.setValue( lineChart.getSecondsWidth() ); updateChartDelayedChanged(); - lineChart.setSecondWidth( 500 ); + } + + void setSecondsWidth( int secondsWidth ) { + lineChart.setSecondsWidth( secondsWidth ); + secondsWidthSlider.setValue( secondsWidth ); + } + + private void secondsWidthChanged() { + lineChart.setSecondsWidth( secondsWidthSlider.getValue() ); } private void updateChartDelayedChanged() { @@ -187,6 +196,7 @@ private void initComponents() { JScrollPane lineChartScrollPane = new JScrollPane(); lineChart = new FlatAnimatorTest.LineChart(); JLabel lineChartInfoLabel = new JLabel(); + secondsWidthSlider = new JSlider(); updateChartDelayedCheckBox = new JCheckBox(); JButton clearChartButton = new JButton(); @@ -211,6 +221,12 @@ private void initComponents() { lineChartInfoLabel.setText("X: time (500ms per line) / Y: value (10% per line)"); add(lineChartInfoLabel, "cell 0 1 2 1"); + //---- secondsWidthSlider ---- + secondsWidthSlider.setMinimum(100); + secondsWidthSlider.setMaximum(2000); + secondsWidthSlider.addChangeListener(e -> secondsWidthChanged()); + add(secondsWidthSlider, "cell 0 1 2 1"); + //---- updateChartDelayedCheckBox ---- updateChartDelayedCheckBox.setText("Update chart delayed"); updateChartDelayedCheckBox.setMnemonic('U'); @@ -227,6 +243,7 @@ private void initComponents() { // JFormDesigner - Variables declaration - DO NOT MODIFY //GEN-BEGIN:variables FlatAnimatorTest.LineChart lineChart; + private JSlider secondsWidthSlider; private JCheckBox updateChartDelayedCheckBox; // JFormDesigner - End of variables declaration //GEN-END:variables } @@ -238,18 +255,21 @@ static class LineChart implements Scrollable { private static final int NEW_SEQUENCE_TIME_LAG = 500; - private static final int NEW_SEQUENCE_GAP = 50; + private static final int NEW_SEQUENCE_GAP = 100; - private int secondWidth = 1000; + private boolean asynchron; + private int secondsWidth = 500; private static class Data { final double value; final boolean dot; + final Color chartColor; final long time; // in milliseconds - Data( double value, boolean dot, long time ) { + Data( double value, boolean dot, Color chartColor, long time ) { this.value = value; this.dot = dot; + this.chartColor = chartColor; this.time = time; } @@ -260,7 +280,8 @@ public String toString() { } } - private final Map> color2dataMap = new HashMap<>(); + private final List syncChartData = new ArrayList<>(); + private final Map> asyncColor2dataMap = new HashMap<>(); private final Timer repaintTime; private Color lastUsedChartColor; private boolean updateDelayed; @@ -270,13 +291,24 @@ public String toString() { repaintTime.setRepeats( false ); } + void enableAsynchron() { + if( !syncChartData.isEmpty() ) + throw new IllegalStateException(); + + asynchron = true; + } + void addValue( double value, Color chartColor ) { addValue( value, false, chartColor ); } void addValue( double value, boolean dot, Color chartColor ) { - List chartData = color2dataMap.computeIfAbsent( chartColor, k -> new ArrayList<>() ); - chartData.add( new Data( value, dot, System.nanoTime() / 1000000) ); + List chartData = asyncColor2dataMap.computeIfAbsent( chartColor, k -> new ArrayList<>() ); + Data data = new Data( value, dot, chartColor, System.nanoTime() / 1000000 ); + if( asynchron ) + chartData.add( data ); + else + syncChartData.add( data ); lastUsedChartColor = chartColor; @@ -288,7 +320,8 @@ void addValue( double value, boolean dot, Color chartColor ) { } void clear() { - color2dataMap.clear(); + syncChartData.clear(); + asyncColor2dataMap.clear(); lastUsedChartColor = null; repaint(); @@ -299,8 +332,14 @@ void setUpdateDelayed( boolean updateDelayed ) { this.updateDelayed = updateDelayed; } - void setSecondWidth( int secondWidth ) { - this.secondWidth = secondWidth; + int getSecondsWidth() { + return secondsWidth; + } + + void setSecondsWidth( int secondsWidth ) { + this.secondsWidth = secondsWidth; + repaint(); + revalidate(); } private void repaintAndRevalidate() { @@ -311,7 +350,7 @@ private void repaintAndRevalidate() { if( lastUsedChartColor != null ) { // compute chart width of last used color and start of last sequence int[] lastSeqX = new int[1]; - int cw = chartWidth( color2dataMap.get( lastUsedChartColor ), lastSeqX ); + int cw = chartWidth( asynchron ? asyncColor2dataMap.get( lastUsedChartColor ) : syncChartData, lastSeqX ); // scroll to end of last sequence (of last used color) int lastSeqWidth = cw - lastSeqX[0]; @@ -334,8 +373,7 @@ protected void paintComponent( Graphics g ) { private void paintImpl( Graphics2D g, int x, int y, int width, int height, double scaleFactor ) { FlatUIUtils.setRenderingHints( g ); - int secondWidth = (int) (this.secondWidth * scaleFactor); - int seqGapWidth = (int) (NEW_SEQUENCE_GAP * scaleFactor); + int secondsWidth = (int) (this.secondsWidth * scaleFactor); Color lineColor = FlatUIUtils.getUIColor( "Component.borderColor", Color.lightGray ); Color lineColor2 = FlatLaf.isLafDark() @@ -356,72 +394,86 @@ private void paintImpl( Graphics2D g, int x, int y, int width, int height, doubl } // paint vertical lines - int twoHundredMillisWidth = secondWidth / 5; + int twoHundredMillisWidth = secondsWidth / 5; for( int i = twoHundredMillisWidth; i < width; i += twoHundredMillisWidth ) { - g.setColor( (i % secondWidth != 0) ? lineColor : lineColor2 ); + g.setColor( (i % secondsWidth != 0) ? lineColor : lineColor2 ); g.drawLine( i, 0, i, height ); } // paint lines - for( Map.Entry> e : color2dataMap.entrySet() ) { - List chartData = e.getValue(); + for( Map.Entry> e : asyncColor2dataMap.entrySet() ) { + List chartData = asynchron ? e.getValue() : syncChartData; Color chartColor = e.getKey(); - if( FlatLaf.isLafDark() ) - chartColor = new HSLColor( chartColor ).adjustTone( 50 ); - Color temporaryValueColor = new Color( (chartColor.getRGB() & 0xffffff) | 0x40000000, true ); - - long seqTime = 0; - int seqX = 0; - long ptime = 0; - int px = 0; - int py = 0; - int pcount = 0; - - g.setColor( chartColor ); - - boolean first = true; - int size = chartData.size(); - for( int i = 0; i < size; i++ ) { - Data data = chartData.get( i ); - int dy = (int) ((height - 1) * data.value); - - if( data.dot ) { - int dotx = px; - if( i > 0 && data.time > ptime + NEW_SEQUENCE_TIME_LAG ) - dotx += seqGapWidth; - int o = UIScale.scale( 1 ); - int s = UIScale.scale( 3 ); - g.fillRect( dotx - o, dy - o, s, s ); - continue; - } - - if( data.time > ptime + NEW_SEQUENCE_TIME_LAG ) { - if( !first && pcount == 0 ) - g.drawLine( px, py, px + (int) (4 * scaleFactor), py ); - - // start new sequence - seqTime = data.time; - seqX = !first ? px + seqGapWidth : 0; - px = seqX; - pcount = 0; - first = false; - } else { - boolean isTemporaryValue = isTemporaryValue( chartData, i ) || isTemporaryValue( chartData, i - 1 ); - if( isTemporaryValue ) - g.setColor( temporaryValueColor ); - - // line in sequence - int dx = (int) (seqX + (((data.time - seqTime) / 1000.) * secondWidth)); - g.drawLine( px, py, dx, dy ); - px = dx; - pcount++; - - if( isTemporaryValue ) - g.setColor( chartColor ); - } - - py = dy; - ptime = data.time; + paintChartData( g, chartData, chartColor, height, scaleFactor ); + } + } + + private void paintChartData( Graphics2D g, List chartData, Color chartColor, int height, double scaleFactor ) { + if( FlatLaf.isLafDark() ) + chartColor = new HSLColor( chartColor ).adjustTone( 50 ); + Color temporaryValueColor = new Color( (chartColor.getRGB() & 0xffffff) | 0x40000000, true ); + + int seqGapWidth = (int) (NEW_SEQUENCE_GAP * scaleFactor); + long seqTime = 0; + int seqX = 0; + long ptime = 0; + int px = 0; + int py = height - 1; + int cx = px; + int cy = py; + int pcount = 0; + + g.setColor( chartColor ); + + boolean first = true; + int size = chartData.size(); + for( int i = 0; i < size; i++ ) { + Data data = chartData.get( i ); + boolean useData = (data.chartColor == chartColor); + int dy = height - 1 - (int) ((height - 1) * data.value); + + if( data.dot ) { + int dotx = px; + if( i > 0 && data.time > ptime + NEW_SEQUENCE_TIME_LAG ) + dotx += seqGapWidth; + int o = UIScale.scale( 1 ); + int s = UIScale.scale( 3 ); + g.fillRect( dotx - o, dy - o, s, s ); + continue; + } + + if( data.time > ptime + NEW_SEQUENCE_TIME_LAG ) { + if( !first && pcount == 0 ) + g.drawLine( px, py, px + (int) (4 * scaleFactor), py ); + + // start new sequence + seqTime = data.time; + seqX = !first ? px + seqGapWidth : 0; + px = seqX; + pcount = 0; + first = false; + } else { + boolean isTemporaryValue = isTemporaryValue( chartData, i ) || isTemporaryValue( chartData, i - 1 ); + if( isTemporaryValue ) + g.setColor( temporaryValueColor ); + + // line in sequence + int dx = (int) (seqX + (((data.time - seqTime) / 1000.) * secondsWidth)); + if( useData ) + g.drawLine( cx, cy, dx, dy ); + px = dx; + pcount++; + + if( isTemporaryValue ) + g.setColor( chartColor ); + } + + py = dy; + ptime = data.time; + + if( useData ) { + cx = px; + cy = py; } } } @@ -450,8 +502,11 @@ private boolean isTemporaryValue( List chartData, int i ) { private int chartWidth() { int width = 0; - for( List chartData : color2dataMap.values() ) - width = Math.max( width, chartWidth( chartData, null ) ); + if( asynchron ) { + for( List chartData : asyncColor2dataMap.values() ) + width = Math.max( width, chartWidth( chartData, null ) ); + } else + width = Math.max( width, chartWidth( syncChartData, null ) ); return width; } @@ -472,7 +527,7 @@ private int chartWidth( List chartData, int[] lastSeqX ) { px = seqX; } else { // line in sequence - int dx = (int) (seqX + (((data.time - seqTime) / 1000.) * secondWidth)); + int dx = (int) (seqX + (((data.time - seqTime) / 1000.) * secondsWidth)); px = dx; } @@ -497,7 +552,7 @@ public Dimension getPreferredScrollableViewportSize() { @Override public int getScrollableUnitIncrement( Rectangle visibleRect, int orientation, int direction ) { - return secondWidth; + return secondsWidth; } @Override @@ -528,6 +583,11 @@ 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() ); 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 a3ec5caa4..f8a7f3c1f 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 @@ -143,6 +143,17 @@ new FormModel { }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { "value": "cell 0 1 2 1" } ) + add( new FormComponent( "javax.swing.JSlider" ) { + name: "secondsWidthSlider" + "minimum": 100 + "maximum": 2000 + auxiliary() { + "JavaCodeGenerator.variableLocal": false + } + addEvent( new FormEvent( "javax.swing.event.ChangeListener", "stateChanged", "secondsWidthChanged", false ) ) + }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { + "value": "cell 0 1 2 1" + } ) add( new FormComponent( "javax.swing.JCheckBox" ) { name: "updateChartDelayedCheckBox" "text": "Update chart delayed" From ccbf577f46659aa8e9b70920ff92bcf57137efb1 Mon Sep 17 00:00:00 2001 From: Karl Tauber Date: Sun, 21 Nov 2021 23:20:47 +0100 Subject: [PATCH 6/8] AnimatedBorder: demo for labeled material border --- .../testing/FlatAnimatedBorderTest.java | 112 +++++++++++++++++- .../testing/FlatAnimatedBorderTest.jfd | 36 ++++-- 2 files changed, 135 insertions(+), 13 deletions(-) diff --git a/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatAnimatedBorderTest.java b/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatAnimatedBorderTest.java index 30d864145..348e63439 100644 --- a/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatAnimatedBorderTest.java +++ b/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatAnimatedBorderTest.java @@ -18,6 +18,7 @@ import java.awt.Color; import java.awt.Component; +import java.awt.FontMetrics; import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.Insets; @@ -41,6 +42,8 @@ public class FlatAnimatedBorderTest 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"; @@ -60,6 +63,8 @@ public static void main( String[] args ) { material1TextField.setBorder( new AnimatedMaterialBorder() ); material2TextField.setBorder( new AnimatedMaterialBorder() ); + material3TextField.setBorder( new AnimatedMaterialLabeledBorder() ); + material4TextField.setBorder( new AnimatedMaterialLabeledBorder() ); minimalTextField.setBorder( new AnimatedMinimalTestBorder() ); @@ -67,13 +72,21 @@ public static void main( String[] args ) { 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() { @@ -89,6 +102,10 @@ private void initComponents() { 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(); @@ -108,6 +125,8 @@ private void initComponents() { "[]para" + "[]" + "[]" + + "[]" + + "[]" + "[]para" + "[]" + "[]" + @@ -117,7 +136,7 @@ private void initComponents() { //---- label3 ---- label3.setText("Fade:"); add(label3, "cell 0 0"); - add(lineChartPanel, "cell 2 0 1 10"); + add(lineChartPanel, "cell 2 0 1 12"); add(fade1TextField, "cell 0 1"); add(fade1ChartColor, "cell 1 1"); add(fade2TextField, "cell 0 2"); @@ -131,19 +150,29 @@ private void initComponents() { add(material2TextField, "cell 0 5"); add(material2ChartColor, "cell 1 5"); + //---- material3TextField ---- + material3TextField.putClientProperty("FlatLaf.styleClass", "large"); + add(material3TextField, "cell 0 6"); + add(material3ChartColor, "cell 1 6"); + + //---- material4TextField ---- + material4TextField.putClientProperty("FlatLaf.styleClass", "large"); + add(material4TextField, "cell 0 7"); + add(material4ChartColor, "cell 1 7"); + //---- label1 ---- label1.setText("Minimal:"); - add(label1, "cell 0 6"); - add(minimalTextField, "cell 0 7"); - add(minimalChartColor, "cell 1 7"); + add(label1, "cell 0 8"); + add(minimalTextField, "cell 0 9"); + add(minimalChartColor, "cell 1 9"); //---- durationLabel ---- durationLabel.setText("Duration:"); - add(durationLabel, "cell 0 9"); + add(durationLabel, "cell 0 11"); //---- durationField ---- durationField.setModel(new SpinnerNumberModel(200, 100, null, 50)); - add(durationField, "cell 0 9"); + add(durationField, "cell 0 11"); // JFormDesigner - End of component initialization //GEN-END:initComponents } @@ -159,6 +188,10 @@ private void initComponents() { 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; @@ -289,6 +322,73 @@ public int getAnimationDuration() { } } + //---- 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 animatedValue ) { + super.paintAnimated( c, g, x, y, width, height, animatedValue ); + + 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 ------------------------------------ /** 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 index cf32c3188..3a3e527e8 100644 --- a/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatAnimatedBorderTest.jfd +++ b/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatAnimatedBorderTest.jfd @@ -6,7 +6,7 @@ new FormModel { add( new FormContainer( "com.formdev.flatlaf.testing.FlatTestPanel", new FormLayoutManager( class net.miginfocom.swing.MigLayout ) { "$layoutConstraints": "insets dialog,hidemode 3" "$columnConstraints": "[fill][fill]para[fill]" - "$rowConstraints": "[][][]para[][][]para[][][grow][]" + "$rowConstraints": "[][][]para[][][][][]para[][][grow][]" } ) { name: "this" add( new FormComponent( "javax.swing.JLabel" ) { @@ -18,7 +18,7 @@ new FormModel { add( new FormComponent( "com.formdev.flatlaf.testing.FlatAnimatorTest$LineChartPanel" ) { name: "lineChartPanel" }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { - "value": "cell 2 0 1 10" + "value": "cell 2 0 1 12" } ) add( new FormComponent( "javax.swing.JTextField" ) { name: "fade1TextField" @@ -66,27 +66,49 @@ new FormModel { }, 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 6" + "value": "cell 0 8" } ) add( new FormComponent( "javax.swing.JTextField" ) { name: "minimalTextField" }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { - "value": "cell 0 7" + "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 7" + "value": "cell 1 9" } ) add( new FormComponent( "javax.swing.JLabel" ) { name: "durationLabel" "text": "Duration:" }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { - "value": "cell 0 9" + "value": "cell 0 11" } ) add( new FormComponent( "javax.swing.JSpinner" ) { name: "durationField" @@ -96,7 +118,7 @@ new FormModel { value: 200 } }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { - "value": "cell 0 9" + "value": "cell 0 11" } ) }, new FormLayoutConstraints( null ) { "location": new java.awt.Point( 0, 0 ) From 3b489e8e1a16817de05a3ab38ab4ceb330b877a1 Mon Sep 17 00:00:00 2001 From: Karl Tauber Date: Mon, 22 Nov 2021 15:42:47 +0100 Subject: [PATCH 7/8] AnimatedPainter: support independent animation of multiple values --- .../flatlaf/icons/FlatAbstractIcon.java | 8 +- .../flatlaf/icons/FlatAnimatedIcon.java | 30 +++- .../formdev/flatlaf/util/AnimatedBorder.java | 19 +-- .../formdev/flatlaf/util/AnimatedIcon.java | 74 ++++++--- .../formdev/flatlaf/util/AnimatedPainter.java | 44 +++-- .../flatlaf/util/AnimatedPainterSupport.java | 78 +++++---- .../testing/FlatAnimatedBorderTest.java | 26 +-- .../flatlaf/testing/FlatAnimatedIconTest.java | 150 ++++++++++++++---- .../flatlaf/testing/FlatAnimatedIconTest.jfd | 18 ++- 9 files changed, 325 insertions(+), 122 deletions(-) diff --git a/flatlaf-core/src/main/java/com/formdev/flatlaf/icons/FlatAbstractIcon.java b/flatlaf-core/src/main/java/com/formdev/flatlaf/icons/FlatAbstractIcon.java index 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 index b681e729b..afb7a8e22 100644 --- a/flatlaf-core/src/main/java/com/formdev/flatlaf/util/AnimatedBorder.java +++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/util/AnimatedBorder.java @@ -25,26 +25,27 @@ /** * Border that automatically animates painting on component value changes. *

- * {@link #getValue(Component)} returns the value of the component. - * If the value changes, then {@link #paintAnimated(Component, Graphics2D, int, int, int, int, float)} - * is invoked multiple times with animated value (from old value to new value). + * {@link #getValues(Component)} returns the value(s) of the component. + * If the value(s) have changed, then {@link #paintAnimated(Component, Graphics2D, int, int, int, int, float[])} + * is invoked multiple times with animated value(s) (from old value(s) to new value(s)). + * If {@link #getValues(Component)} returns multiple values, then each value gets its own independent animation. *

* Example for an animated border: *

- * private class AnimatedMinimalTestBorder
+ * private class MyAnimatedBorder
  *     implements AnimatedBorder
  * {
  *     @Override
- *     public void paintAnimated( Component c, Graphics2D g, int x, int y, int width, int height, float animatedValue ) {
+ *     public void paintAnimated( Component c, Graphics2D g, int x, int y, int width, int height, float[] animatedValues ) {
  *         int lh = UIScale.scale( 2 );
  *
  *         g.setColor( Color.blue );
- *         g.fillRect( x, y + height - lh, Math.round( width * animatedValue ), lh );
+ *         g.fillRect( x, y + height - lh, Math.round( width * animatedValues[0] ), lh );
  *     }
  *
  *     @Override
- *     public float getValue( Component c ) {
- *         return c.isFocusOwner() ? 1 : 0;
+ *     public float[] getValues( Component c ) {
+ *         return new float[] { c.isFocusOwner() ? 1 : 0 };
  *     }
  *
  *     @Override
@@ -57,7 +58,7 @@
  *
  * // sample usage
  * JTextField textField = new JTextField();
- * textField.setBorder( new AnimatedMinimalTestBorder() );
+ * textField.setBorder( new MyAnimatedBorder() );
  * 
* * Animation works only if the component passed to {@link #paintBorder(Component, Graphics, int, int, int, int)} diff --git a/flatlaf-core/src/main/java/com/formdev/flatlaf/util/AnimatedIcon.java b/flatlaf-core/src/main/java/com/formdev/flatlaf/util/AnimatedIcon.java index 5c39d2649..5f351bcc1 100644 --- a/flatlaf-core/src/main/java/com/formdev/flatlaf/util/AnimatedIcon.java +++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/util/AnimatedIcon.java @@ -25,37 +25,35 @@ /** * Icon that automatically animates painting on component value changes. *

- * {@link #getValue(Component)} returns the value of the component. - * If the value changes, then {@link #paintIconAnimated(Component, Graphics, int, int, float)} - * is invoked multiple times with animated value (from old value to new value). + * {@link #getValues(Component)} returns the value(s) of the component. + * If the value(s) have changed, then {@link #paintAnimated(Component, Graphics2D, int, int, int, int, float[])} + * is invoked multiple times with animated value(s) (from old value(s) to new value(s)). + * If {@link #getValues(Component)} returns multiple values, then each value gets its own independent animation. *

* Example for an animated icon: *

- * private class AnimatedMinimalTestIcon
+ * private class MyAnimatedIcon
  *     implements AnimatedIcon
  * {
  *     @Override public int getIconWidth() { return 100; }
  *     @Override public int getIconHeight() { return 20; }
  *
  *     @Override
- *     public void paintIconAnimated( Component c, Graphics g, int x, int y, float animatedValue ) {
- *         int w = getIconWidth();
- *         int h = getIconHeight();
- *
+ *     public void paintAnimated( Component c, Graphics2D g, int x, int y, int width, int height, float[] animatedValues ) {
  *         g.setColor( Color.red );
- *         g.drawRect( x, y, w - 1, h - 1 );
- *         g.fillRect( x, y, Math.round( w * animatedValue ), h );
+ *         g.drawRect( x, y, width - 1, height - 1 );
+ *         g.fillRect( x, y, Math.round( width * animatedValues[0] ), height );
  *     }
  *
  *     @Override
- *     public float getValue( Component c ) {
- *         return ((AbstractButton)c).isSelected() ? 1 : 0;
+ *     public float[] getValues( Component c ) {
+ *         return new float[] { ((AbstractButton)c).isSelected() ? 1 : 0 };
  *     }
  * }
  *
  * // sample usage
  * JCheckBox checkBox = new JCheckBox( "test" );
- * checkBox.setIcon( new AnimatedMinimalTestIcon() );
+ * checkBox.setIcon( new MyAnimatedIcon() );
  * 
* * Animation works only if the component passed to {@link #paintIcon(Component, Graphics, int, int)} @@ -76,15 +74,13 @@ default void paintIcon( Component c, Graphics g, int x, int y ) { } /** - * Bridge method that is called from (new) superclass and delegates to - * {@link #paintIconAnimated(Component, Graphics, int, int, float)}. - * Necessary for API compatibility. + * {@inheritDoc} * * @since 2 */ @Override - default void paintAnimated( Component c, Graphics2D g, int x, int y, int width, int height, float animatedValue ) { - paintIconAnimated( c, g, x, y, animatedValue ); + default void paintAnimated( Component c, Graphics2D g, int x, int y, int width, int height, float[] animatedValues ) { + paintIconAnimated( c, g, x, y, animatedValues[0] ); } /** @@ -97,22 +93,60 @@ default void paintAnimated( Component c, Graphics2D g, int x, int y, int width, * @param animatedValue the animated value, which is either equal to what {@link #getValue(Component)} * returned, or somewhere between the previous value and the latest value * that {@link #getValue(Component)} returned + * + * @deprecated override {@link #paintAnimated(Component, Graphics2D, int, int, int, int, float[])} instead + */ + @Deprecated + default void paintIconAnimated( Component c, Graphics g, int x, int y, float animatedValue ) { + } + + /** + * {@inheritDoc} + * + * @since 2 */ - void paintIconAnimated( Component c, Graphics g, int x, int y, float animatedValue ); + @Override + default float[] getValues( Component c ) { + return new float[] { getValue( c ) }; + } + + /** + * Gets the value of the component. + *

+ * This can be any value and depends on the component. + * If the value changes, then this class animates from the old value to the new one. + *

+ * For a toggle button this could be {@code 0} for off and {@code 1} for on. + * + * @deprecated override {@link #getValues(Component)} instead + */ + @Deprecated + default float getValue( Component c ) { + return 0; + } //---- class AnimationSupport --------------------------------------------- /** * Animation support. */ + @Deprecated class AnimationSupport { + /** + * @deprecated use {@link AnimatedPainter#paintWithAnimation(Component, Graphics, int, int, int, int)} instead + */ + @Deprecated public static void paintIcon( AnimatedIcon icon, Component c, Graphics g, int x, int y ) { AnimatedPainterSupport.paint( icon, c, g, x, y, icon.getIconWidth(), icon.getIconHeight() ); } + /** + * @deprecated use {@link AnimatedPainter#saveRepaintLocation(AnimatedPainter, Component, int, int)} instead + */ + @Deprecated public static void saveIconLocation( AnimatedIcon icon, Component c, int x, int y ) { - AnimatedPainterSupport.saveLocation( icon, c, x, y ); + AnimatedPainterSupport.saveRepaintLocation( icon, c, x, y ); } } } diff --git a/flatlaf-core/src/main/java/com/formdev/flatlaf/util/AnimatedPainter.java b/flatlaf-core/src/main/java/com/formdev/flatlaf/util/AnimatedPainter.java index d85e76d22..269e13b92 100644 --- a/flatlaf-core/src/main/java/com/formdev/flatlaf/util/AnimatedPainter.java +++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/util/AnimatedPainter.java @@ -23,11 +23,12 @@ import com.formdev.flatlaf.util.Animator.Interpolator; /** - * Painter that automatically animates painting on component value changes. + * Painter that automatically animates painting on component value(s) changes. *

- * {@link #getValue(Component)} returns the value of the component. - * If the value changes, then {@link #paintAnimated(Component, Graphics2D, int, int, int, int, float)} - * is invoked multiple times with animated value (from old value to new value). + * {@link #getValues(Component)} returns the value(s) of the component. + * If the value(s) have changed, then {@link #paintAnimated(Component, Graphics2D, int, int, int, int, float[])} + * is invoked multiple times with animated value(s) (from old value(s) to new value(s)). + * If {@link #getValues(Component)} returns multiple values, then each value gets its own independent animation. *

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

@@ -42,11 +43,11 @@ public interface AnimatedPainter { /** * Starts painting. - * Either invokes {@link #paintAnimated(Component, Graphics2D, int, int, int, int, float)} - * once to paint current value (see {@link #getValue(Component)}. Or if value has + * Either invokes {@link #paintAnimated(Component, Graphics2D, int, int, int, int, float[])} + * once to paint current value(s) (see {@link #getValues(Component)}. Or if value(s) has * changed, compared to last painting, then it starts an animation and invokes - * {@link #paintAnimated(Component, Graphics2D, int, int, int, int, float)} - * multiple times with animated value (from old value to new value). + * {@link #paintAnimated(Component, Graphics2D, int, int, int, int, float[])} + * multiple times with animated value(s) (from old value(s) to new value(s)). * * @param c the component that this painter belongs to * @param g the graphics context @@ -60,7 +61,7 @@ default void paintWithAnimation( Component c, Graphics g, int x, int y, int widt } /** - * Paints the given (animated) value. + * Paints the given (animated) value(s). *

* Invoked from {@link #paintWithAnimation(Component, Graphics, int, int, int, int)}. * @@ -70,11 +71,11 @@ default void paintWithAnimation( Component c, Graphics g, int x, int y, int widt * @param y the y coordinate of the paint area * @param width the width of the paint area * @param height the height of the paint area - * @param animatedValue the animated value, which is either equal to what {@link #getValue(Component)} - * returned, or somewhere between the previous value and the latest value - * that {@link #getValue(Component)} returned + * @param animatedValues the animated values, which are either equal to what {@link #getValues(Component)} + * returned, or somewhere between the previous values and the latest values + * that {@link #getValues(Component)} returned */ - void paintAnimated( Component c, Graphics2D g, int x, int y, int width, int height, float animatedValue ); + void paintAnimated( Component c, Graphics2D g, int x, int y, int width, int height, float[] animatedValues ); /** * Invoked from animator to repaint an area. @@ -91,14 +92,15 @@ default void repaintDuringAnimation( Component c, int x, int y, int width, int h } /** - * Gets the value of the component. + * Gets the value(s) of the component. *

* This can be any value and depends on the component. - * If the value changes, then this class animates from the old value to the new one. + * If the value(s) changes, then this class animates from the old value(s) to the new ones. + * If multiple values are returned, then each value gets its own independent animation. *

* For a toggle button this could be {@code 0} for off and {@code 1} for on. */ - float getValue( Component c ); + float[] getValues( Component c ); /** * Returns whether animation is enabled for this painter (default is {@code true}). @@ -136,4 +138,14 @@ default Interpolator getAnimationInterpolator() { default Object getClientPropertyKey() { return getClass(); } + + /** + * Saves location for repainting animated area with + * {@link AnimatedPainter#repaintDuringAnimation(Component, int, int, int, int)}. + * Only needed when graphics context passed to {@link #paintWithAnimation(Component, Graphics, int, int, int, int)} + * uses transformed location. + */ + static void saveRepaintLocation( AnimatedPainter painter, Component c, int x, int y ) { + AnimatedPainterSupport.saveRepaintLocation( painter, c, x, y ); + } } diff --git a/flatlaf-core/src/main/java/com/formdev/flatlaf/util/AnimatedPainterSupport.java b/flatlaf-core/src/main/java/com/formdev/flatlaf/util/AnimatedPainterSupport.java index e27ac2e46..0c4d1126f 100644 --- a/flatlaf-core/src/main/java/com/formdev/flatlaf/util/AnimatedPainterSupport.java +++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/util/AnimatedPainterSupport.java @@ -49,23 +49,45 @@ static void paint( AnimatedPainter painter, Component c, Graphics g, // paint without animation if animation is disabled or // component is not a JComponent and therefore does not support // client properties, which are required to keep animation state - paintImpl( painter, c, g, x, y, width, height, null ); + painter.paintAnimated( c, (Graphics2D) g, x, y, width, height, painter.getValues( c ) ); return; } + // get component values + float values[] = painter.getValues( c ); + JComponent jc = (JComponent) c; Object key = painter.getClientPropertyKey(); - AnimatedPainterSupport as = (AnimatedPainterSupport) jc.getClientProperty( key ); - if( as == null ) { - // painted first time --> do not animate, but remember current component value - as = new AnimatedPainterSupport(); - as.startValue = as.targetValue = as.animatedValue = painter.getValue( c ); - jc.putClientProperty( key, as ); - } else { - // get component value - float value = painter.getValue( c ); - - if( value != as.targetValue ) { + 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 ); + } + + float[] animatedValues = new float[ass.length]; + + for( int i = 0; i < ass.length; i++ ) { + AnimatedPainterSupport as = ass[i]; + float value = values[i]; + + if( as == null ) { + // painted first time --> do not animate, but remember current component value + as = new AnimatedPainterSupport(); + as.startValue = as.targetValue = as.animatedValue = value; + ass[i] = as; + } else if( value != as.targetValue ) { // value changed --> (re)start animation if( as.animator == null ) { @@ -110,35 +132,33 @@ static void paint( AnimatedPainter painter, Component c, Graphics g, as.targetValue = value; as.animator.start(); } - } - as.x = x; - as.y = y; - as.width = width; - as.height = height; + as.x = x; + as.y = y; + as.width = width; + as.height = height; - paintImpl( painter, c, g, x, y, width, height, as ); - } + animatedValues[i] = as.animatedValue; + } - private static void paintImpl( AnimatedPainter painter, Component c, Graphics g, - int x, int y, int width, int height, AnimatedPainterSupport as ) - { - float value = (as != null) ? as.animatedValue : painter.getValue( c ); - painter.paintAnimated( c, (Graphics2D) g, x, y, width, height, value ); + painter.paintAnimated( c, (Graphics2D) g, x, y, width, height, animatedValues ); } private static boolean isAnimationEnabled( AnimatedPainter painter, Component c ) { return Animator.useAnimation() && painter.isAnimationEnabled() && c instanceof JComponent; } - static void saveLocation( AnimatedPainter painter, Component c, int x, int y ) { + static void saveRepaintLocation( AnimatedPainter painter, Component c, int x, int y ) { if( !isAnimationEnabled( painter, c ) ) return; - AnimatedPainterSupport as = (AnimatedPainterSupport) ((JComponent)c).getClientProperty( painter.getClientPropertyKey() ); - if( as != null ) { - as.x = x; - as.y = y; + AnimatedPainterSupport[] ass = (AnimatedPainterSupport[]) ((JComponent)c).getClientProperty( painter.getClientPropertyKey() ); + if( ass != null ) { + for( int i = 0; i < ass.length; i++ ) { + AnimatedPainterSupport as = ass[i]; + as.x = x; + as.y = y; + } } } } diff --git a/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatAnimatedBorderTest.java b/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatAnimatedBorderTest.java index 348e63439..b4d6501bd 100644 --- a/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatAnimatedBorderTest.java +++ b/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatAnimatedBorderTest.java @@ -217,7 +217,8 @@ public void paintBorder( Component c, Graphics g, int x, int y, int width, int h } @Override - public void paintAnimated( Component c, Graphics2D g, int x, int y, int width, int height, float animatedValue ) { + public void paintAnimated( Component c, Graphics2D g, int x, int y, int width, int height, float[] animatedValues ) { + float animatedValue = animatedValues[0]; FlatUIUtils.setRenderingHints( g ); // border width is 1 if not focused and 2 if focused @@ -242,8 +243,8 @@ public Insets getBorderInsets( Component c, Insets insets ) { } @Override - public float getValue( Component c ) { - return FlatUIUtils.isPermanentFocusOwner( c ) ? 1 : 0; + public float[] getValues( Component c ) { + return new float[] { FlatUIUtils.isPermanentFocusOwner( c ) ? 1 : 0 }; } @Override @@ -271,7 +272,8 @@ public void paintBorder( Component c, Graphics g, int x, int y, int width, int h } @Override - public void paintAnimated( Component c, Graphics2D g, int x, int y, int width, int height, float animatedValue ) { + public void paintAnimated( Component c, Graphics2D g, int x, int y, int width, int height, float[] animatedValues ) { + float animatedValue = animatedValues[0]; FlatUIUtils.setRenderingHints( g ); // use paintAtScale1x() for consistent line thickness when scaled @@ -312,8 +314,8 @@ public Insets getBorderInsets( Component c, Insets insets ) { } @Override - public float getValue( Component c ) { - return FlatUIUtils.isPermanentFocusOwner( c ) ? 1 : 0; + public float[] getValues( Component c ) { + return new float[] { FlatUIUtils.isPermanentFocusOwner( c ) ? 1 : 0 }; } @Override @@ -338,9 +340,10 @@ private class AnimatedMaterialLabeledBorder private static final float LABEL_FONT_SCALE = 0.75f; @Override - public void paintAnimated( Component c, Graphics2D g, int x, int y, int width, int height, float animatedValue ) { - super.paintAnimated( c, g, x, y, width, height, animatedValue ); + public void paintAnimated( Component c, Graphics2D g, int x, int y, int width, int height, float[] animatedValues ) { + super.paintAnimated( c, g, x, y, width, height, animatedValues ); + float animatedValue = animatedValues[0]; JComponent jc = (JComponent) c; String label = (String) jc.getClientProperty( LABEL_TEXT_KEY ); if( label == null ) @@ -398,7 +401,8 @@ private class AnimatedMinimalTestBorder implements AnimatedBorder { @Override - public void paintAnimated( Component c, Graphics2D g, int x, int y, int width, int height, float animatedValue ) { + public void paintAnimated( Component c, Graphics2D g, int x, int y, int width, int height, float[] animatedValues ) { + float animatedValue = animatedValues[0]; int lh = UIScale.scale( 2 ); g.setColor( Color.blue ); @@ -411,8 +415,8 @@ public void paintAnimated( Component c, Graphics2D g, int x, int y, int width, i } @Override - public float getValue( Component c ) { - return FlatUIUtils.isPermanentFocusOwner( c ) ? 1 : 0; + public float[] getValues( Component c ) { + return new float[] { FlatUIUtils.isPermanentFocusOwner( c ) ? 1 : 0 }; } @Override diff --git a/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatAnimatedIconTest.java b/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatAnimatedIconTest.java index 2788711ca..b196d97a8 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.*; /** @@ -38,6 +39,7 @@ public class FlatAnimatedIconTest private static final Color CHART_RADIO_BUTTON_3 = Color.green; private static final Color CHART_CHECK_BOX_1 = Color.magenta; private static final Color CHART_CHECK_BOX_2 = Color.orange; + private static final Color[] CHART_SWITCH_EX = new Color[] { Color.red, Color.green, Color.blue }; private static final String CHART_COLOR_KEY = "chartColor"; @@ -57,6 +59,7 @@ public 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 ); @@ -83,6 +86,7 @@ private void initComponents() { radioButton3ChartColor = new FlatAnimatorTest.JChartColor(); checkBox1 = new JCheckBox(); checkBox1ChartColor = new FlatAnimatorTest.JChartColor(); + checkBox3 = new JCheckBox(); checkBox2 = new JCheckBox(); checkBox2ChartColor = new FlatAnimatorTest.JChartColor(); durationLabel = new JLabel(); @@ -101,6 +105,7 @@ private void initComponents() { "[]para" + "[]" + "[]" + + "[]" + "[grow]" + "[]")); @@ -109,7 +114,7 @@ private void initComponents() { radioButton1.setSelected(true); add(radioButton1, "cell 0 0"); add(radioButton1ChartColor, "cell 1 0"); - add(lineChartPanel, "cell 2 0 1 6"); + add(lineChartPanel, "cell 2 0 1 7"); //---- radioButton2 ---- radioButton2.setText("radio 2"); @@ -126,18 +131,22 @@ private void initComponents() { add(checkBox1, "cell 0 3"); add(checkBox1ChartColor, "cell 1 3"); + //---- checkBox3 ---- + checkBox3.setText("switch ex"); + add(checkBox3, "cell 0 4"); + //---- checkBox2 ---- checkBox2.setText("minimal"); - add(checkBox2, "cell 0 4"); - add(checkBox2ChartColor, "cell 1 4"); + add(checkBox2, "cell 0 5"); + add(checkBox2ChartColor, "cell 1 5"); //---- durationLabel ---- durationLabel.setText("Duration:"); - add(durationLabel, "cell 0 6 3 1"); + add(durationLabel, "cell 0 7 3 1"); //---- durationField ---- durationField.setModel(new SpinnerNumberModel(200, 100, null, 50)); - add(durationField, "cell 0 6 3 1"); + add(durationField, "cell 0 7 3 1"); //---- buttonGroup1 ---- ButtonGroup buttonGroup1 = new ButtonGroup(); @@ -157,6 +166,7 @@ private void initComponents() { private FlatAnimatorTest.JChartColor radioButton3ChartColor; private JCheckBox checkBox1; private FlatAnimatorTest.JChartColor checkBox1ChartColor; + private JCheckBox checkBox3; private JCheckBox checkBox2; private FlatAnimatorTest.JChartColor checkBox2ChartColor; private JLabel durationLabel; @@ -185,7 +195,8 @@ public 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 @@ -201,7 +212,7 @@ 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 ); @@ -210,8 +221,8 @@ public void paintIconAnimated( Component c, Graphics g, int x, int y, float anim } @Override - public float getValue( Component c ) { - return ((JRadioButton)c).isSelected() ? 1 : 0; + public float[] getValues( Component c ) { + return new float[] { ((JRadioButton)c).isSelected() ? 1 : 0 }; } @Override @@ -222,7 +233,7 @@ public int getAnimationDuration() { //---- class AnimatedSwitchIcon ------------------------------------------- - public class AnimatedSwitchIcon + private class AnimatedSwitchIcon extends FlatAnimatedIcon { private final Color offColor = Color.lightGray; @@ -233,17 +244,20 @@ 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( x, y, width, height, height, height ); + g.fillRoundRect( 0, 0, width, height, height, height ); + // paint thumb int thumbSize = height - 4; - float thumbX = x + 2 + ((width - 4 - thumbSize) * animatedValue); - int thumbY = y + 2; + float thumbX = 2 + ((width - 4 - thumbSize) * animatedValue); + int thumbY = 2; g.setColor( Color.white ); - ((Graphics2D)g).fill( new Ellipse2D.Float( thumbX, thumbY, thumbSize, thumbSize ) ); + g.fill( new Ellipse2D.Float( thumbX, thumbY, thumbSize, thumbSize ) ); if( animatedValue != 0 && animatedValue != 1 ) { Color chartColor = (Color) ((JComponent)c).getClientProperty( CHART_COLOR_KEY ); @@ -252,8 +266,91 @@ public void paintIconAnimated( Component c, Graphics g, int x, int y, float anim } @Override - public float getValue( Component c ) { - return ((AbstractButton)c).isSelected() ? 1 : 0; + public float[] getValues( Component c ) { + return new float[] { ((AbstractButton)c).isSelected() ? 1 : 0 }; + } + + @Override + public int getAnimationDuration() { + return (Integer) durationField.getValue(); + } + } + + //---- class AnimatedSwitchIconEx ----------------------------------------- + + private static final int HW = 8; + + private class AnimatedSwitchIconEx + extends FlatAnimatedIcon + { + private final Color offColor = Color.lightGray; + private final Color onColor = Color.red; + private final Color hoverColor = new Color( 0x4400cc00, true ); + private final Color pressedColor = new Color( 0x440000cc, true ); + + public AnimatedSwitchIconEx() { + super( 28 + HW, 16 + HW, null ); + } + + @Override + public void paintIconAnimated( Component c, Graphics2D g, float[] animatedValues ) { + Color color = ColorFunctions.mix( onColor, offColor, animatedValues[0] ); + + int hw2 = HW / 2; + int x = hw2; + int y = hw2; + int width = this.width - HW; + int height = this.height - HW; + + // paint track + g.setColor( color ); + g.fillRoundRect( x, y, width, height, height, height ); + + // paint thumb + int thumbSize = height - 4; + float thumbX = x + 2 + ((width - 4 - thumbSize) * animatedValues[0]); + int thumbY = y + 2; + g.setColor( Color.white ); + g.fill( new Ellipse2D.Float( thumbX, thumbY, thumbSize, thumbSize ) ); + + // paint hover + if( animatedValues[1] > 0 ) { + g.setColor( hoverColor ); + paintHoverOrPressed( g, thumbX, thumbY, thumbSize, animatedValues[1] ); + } + + // paint pressed + if( animatedValues[2] > 0 ) { + g.setColor( pressedColor ); + paintHoverOrPressed( g, thumbX, thumbY, thumbSize, animatedValues[2] ); + } + + for( int i = 0; i < animatedValues.length; i++ ) { + float animatedValue = animatedValues[i]; + if( animatedValue != 0 && animatedValue != 1 ) + lineChartPanel.lineChart.addValue( animatedValue, CHART_SWITCH_EX[i] ); + } + } + + private void paintHoverOrPressed( Graphics2D g, float thumbX, int thumbY, int thumbSize, float animatedValue ) { + float hw = (HW + 4) * animatedValue; + Path2D path = new Path2D.Float( Path2D.WIND_EVEN_ODD ); + path.append( new Ellipse2D.Float( thumbX - (hw / 2), thumbY - (hw / 2), + thumbSize + hw, thumbSize + hw ), false ); + path.append( new Ellipse2D.Float( thumbX, thumbY, thumbSize, thumbSize ), false ); + g.fill( path ); + } + + @Override + public float[] getValues( Component c ) { + AbstractButton b = (AbstractButton) c; + ButtonModel bm = b.getModel(); + + return new float[] { + b.isSelected() ? 1 : 0, + bm.isRollover() ? 1 : 0, + bm.isPressed() ? 1 : 0, + }; } @Override @@ -272,22 +369,21 @@ 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 ); @@ -296,8 +392,8 @@ public void paintIconAnimated( Component c, Graphics g, int x, int y, float anim } @Override - public float getValue( Component c ) { - return ((AbstractButton)c).isSelected() ? 1 : 0; + public float[] getValues( Component c ) { + return new float[] { ((AbstractButton)c).isSelected() ? 1 : 0 }; } @Override diff --git a/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatAnimatedIconTest.jfd b/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatAnimatedIconTest.jfd index 8e9b20974..dc62fa608 100644 --- a/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatAnimatedIconTest.jfd +++ b/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatAnimatedIconTest.jfd @@ -6,7 +6,7 @@ new FormModel { add( new FormContainer( "com.formdev.flatlaf.testing.FlatTestPanel", new FormLayoutManager( class net.miginfocom.swing.MigLayout ) { "$layoutConstraints": "insets dialog,hidemode 3" "$columnConstraints": "[][fill]para[grow,fill]" - "$rowConstraints": "[][][]para[][][grow][]" + "$rowConstraints": "[][][]para[][][][grow][]" } ) { name: "this" add( new FormComponent( "javax.swing.JRadioButton" ) { @@ -25,7 +25,7 @@ new FormModel { add( new FormComponent( "com.formdev.flatlaf.testing.FlatAnimatorTest$LineChartPanel" ) { name: "lineChartPanel" }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { - "value": "cell 2 0 1 6" + "value": "cell 2 0 1 7" } ) add( new FormComponent( "javax.swing.JRadioButton" ) { name: "radioButton2" @@ -62,22 +62,28 @@ new FormModel { }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { "value": "cell 1 3" } ) + add( new FormComponent( "javax.swing.JCheckBox" ) { + name: "checkBox3" + "text": "switch ex" + }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { + "value": "cell 0 4" + } ) add( new FormComponent( "javax.swing.JCheckBox" ) { name: "checkBox2" "text": "minimal" }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { - "value": "cell 0 4" + "value": "cell 0 5" } ) add( new FormComponent( "com.formdev.flatlaf.testing.FlatAnimatorTest$JChartColor" ) { name: "checkBox2ChartColor" }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { - "value": "cell 1 4" + "value": "cell 1 5" } ) add( new FormComponent( "javax.swing.JLabel" ) { name: "durationLabel" "text": "Duration:" }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { - "value": "cell 0 6 3 1" + "value": "cell 0 7 3 1" } ) add( new FormComponent( "javax.swing.JSpinner" ) { name: "durationField" @@ -87,7 +93,7 @@ new FormModel { value: 200 } }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { - "value": "cell 0 6 3 1" + "value": "cell 0 7 3 1" } ) }, new FormLayoutConstraints( null ) { "location": new java.awt.Point( 0, 0 ) From 26d7008c045e65bf7f33bd19e5ab250dc372111b Mon Sep 17 00:00:00 2001 From: Karl Tauber Date: Mon, 22 Nov 2021 22:07:45 +0100 Subject: [PATCH 8/8] AnimatedPainter: support individual animation duration, resolution and interpolator depending on value(s) --- .../formdev/flatlaf/util/AnimatedPainter.java | 26 ++++++++++++++ .../flatlaf/util/AnimatedPainterSupport.java | 36 ++++++++++++++----- .../testing/FlatAnimatedBorderTest.java | 2 +- .../testing/FlatAnimatedBorderTest.jfd | 2 +- .../flatlaf/testing/FlatAnimatedIconTest.java | 2 +- .../flatlaf/testing/FlatAnimatedIconTest.jfd | 2 +- 6 files changed, 57 insertions(+), 13 deletions(-) diff --git a/flatlaf-core/src/main/java/com/formdev/flatlaf/util/AnimatedPainter.java b/flatlaf-core/src/main/java/com/formdev/flatlaf/util/AnimatedPainter.java index 269e13b92..0ff83de70 100644 --- a/flatlaf-core/src/main/java/com/formdev/flatlaf/util/AnimatedPainter.java +++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/util/AnimatedPainter.java @@ -132,6 +132,32 @@ 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. */ diff --git a/flatlaf-core/src/main/java/com/formdev/flatlaf/util/AnimatedPainterSupport.java b/flatlaf-core/src/main/java/com/formdev/flatlaf/util/AnimatedPainterSupport.java index 0c4d1126f..1492c31fa 100644 --- a/flatlaf-core/src/main/java/com/formdev/flatlaf/util/AnimatedPainterSupport.java +++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/util/AnimatedPainterSupport.java @@ -29,6 +29,7 @@ */ class AnimatedPainterSupport { + private int valueIndex; private float startValue; private float targetValue; private float animatedValue; @@ -76,8 +77,6 @@ static void paint( AnimatedPainter painter, Component c, Graphics g, jc.putClientProperty( key, ass ); } - float[] animatedValues = new float[ass.length]; - for( int i = 0; i < ass.length; i++ ) { AnimatedPainterSupport as = ass[i]; float value = values[i]; @@ -85,15 +84,29 @@ static void paint( AnimatedPainter painter, Component c, Graphics g, if( as == null ) { // painted first time --> do not animate, but remember current component value as = new AnimatedPainterSupport(); + as.valueIndex = i; as.startValue = as.targetValue = as.animatedValue = value; ass[i] = as; } else if( value != as.targetValue ) { // value changed --> (re)start animation + int animationDuration = painter.getAnimationDuration( as.valueIndex, value ); + + // do not animate if animation duration (for current value) is zero + if( animationDuration <= 0 ) { + if( as.animator != null ) { + as.animator.cancel(); + as.animator = null; + } + as.startValue = as.targetValue = as.animatedValue = value; + as.fraction = 0; + continue; + } + if( as.animator == null ) { // create animator AnimatedPainterSupport as2 = as; - as.animator = new Animator( painter.getAnimationDuration(), fraction -> { + as.animator = new Animator( 1, fraction -> { // check whether component was removed while animation is running if( !c.isDisplayable() ) { as2.animator.stop(); @@ -116,19 +129,22 @@ static void paint( AnimatedPainter painter, Component c, Graphics g, // 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) (painter.getAnimationDuration() * as.fraction); + int duration2 = (int) (animationDuration * as.fraction); if( duration2 > 0 ) as.animator.setDuration( duration2 ); as.startValue = as.animatedValue; } else { // new animation - as.animator.setDuration( painter.getAnimationDuration() ); - as.animator.setResolution( painter.getAnimationResolution() ); - as.animator.setInterpolator( painter.getAnimationInterpolator() ); + 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(); } @@ -137,10 +153,12 @@ static void paint( AnimatedPainter painter, Component c, Graphics g, as.y = y; as.width = width; as.height = height; - - animatedValues[i] = as.animatedValue; } + float[] animatedValues = new float[ass.length]; + for( int i = 0; i < ass.length; i++ ) + animatedValues[i] = ass[i].animatedValue; + painter.paintAnimated( c, (Graphics2D) g, x, y, width, height, animatedValues ); } diff --git a/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatAnimatedBorderTest.java b/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatAnimatedBorderTest.java index b4d6501bd..14f94ea71 100644 --- a/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatAnimatedBorderTest.java +++ b/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatAnimatedBorderTest.java @@ -171,7 +171,7 @@ private void initComponents() { add(durationLabel, "cell 0 11"); //---- durationField ---- - durationField.setModel(new SpinnerNumberModel(200, 100, null, 50)); + durationField.setModel(new SpinnerNumberModel(200, 0, null, 50)); add(durationField, "cell 0 11"); // JFormDesigner - End of component initialization //GEN-END:initComponents } 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 index 3a3e527e8..4c32e5edc 100644 --- a/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatAnimatedBorderTest.jfd +++ b/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatAnimatedBorderTest.jfd @@ -113,7 +113,7 @@ new FormModel { add( new FormComponent( "javax.swing.JSpinner" ) { name: "durationField" "model": new javax.swing.SpinnerNumberModel { - minimum: 100 + minimum: 0 stepSize: 50 value: 200 } 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 b196d97a8..49e80740e 100644 --- a/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatAnimatedIconTest.java +++ b/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatAnimatedIconTest.java @@ -145,7 +145,7 @@ private void initComponents() { add(durationLabel, "cell 0 7 3 1"); //---- durationField ---- - durationField.setModel(new SpinnerNumberModel(200, 100, null, 50)); + durationField.setModel(new SpinnerNumberModel(200, 0, null, 50)); add(durationField, "cell 0 7 3 1"); //---- buttonGroup1 ---- 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 dc62fa608..62a2ae327 100644 --- a/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatAnimatedIconTest.jfd +++ b/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatAnimatedIconTest.jfd @@ -88,7 +88,7 @@ new FormModel { add( new FormComponent( "javax.swing.JSpinner" ) { name: "durationField" "model": new javax.swing.SpinnerNumberModel { - minimum: 100 + minimum: 0 stepSize: 50 value: 200 }