mirror of
https://github.com/JFormDesigner/FlatLaf.git
synced 2025-12-07 14:30:56 +03:00
AnimatedPainter added (refactored from AnimatedBorder and AnimatedIcon)
(checked API compatibility of AnimatedIcon with gradle task sigtestCheck)
This commit is contained in:
@@ -18,15 +18,15 @@ package com.formdev.flatlaf.util;
|
||||
|
||||
import java.awt.Component;
|
||||
import java.awt.Graphics;
|
||||
import java.awt.Graphics2D;
|
||||
import javax.swing.JComponent;
|
||||
import javax.swing.border.Border;
|
||||
import com.formdev.flatlaf.util.Animator.Interpolator;
|
||||
|
||||
/**
|
||||
* Border that automatically animates painting on component value changes.
|
||||
* <p>
|
||||
* {@link #getValue(Component)} returns the value of the component.
|
||||
* If the value changes, then {@link #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).
|
||||
* <p>
|
||||
* Example for an animated border:
|
||||
@@ -35,7 +35,7 @@ import com.formdev.flatlaf.util.Animator.Interpolator;
|
||||
* 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 @@ import com.formdev.flatlaf.util.Animator.Interpolator;
|
||||
* 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
|
||||
{
|
||||
/**
|
||||
* Invokes {@link #paintWithAnimation(Component, Graphics, int, int, int, int)}.
|
||||
*/
|
||||
@Override
|
||||
default void paintBorder( Component c, Graphics g, int x, int y, int width, int height ) {
|
||||
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.
|
||||
* <p>
|
||||
* 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.
|
||||
* <p>
|
||||
* 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.
|
||||
* <p>
|
||||
* This can be any value and depends on the component.
|
||||
* If the value changes, then this class animates from the old value to the new one.
|
||||
* <p>
|
||||
* For a 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
|
||||
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;
|
||||
}
|
||||
paintWithAnimation( c, g, x, y, width, height );
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,9 +18,9 @@ package com.formdev.flatlaf.util;
|
||||
|
||||
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 @@ import com.formdev.flatlaf.util.Animator.Interpolator;
|
||||
* @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 interface AnimatedIcon
|
||||
*/
|
||||
void paintIconAnimated( Component c, Graphics g, int x, int y, float animatedValue );
|
||||
|
||||
/**
|
||||
* Gets the value of the component.
|
||||
* <p>
|
||||
* This can be any value and depends on the component.
|
||||
* If the value changes, then this class animates from the old value to the new one.
|
||||
* <p>
|
||||
* For a toggle button this could be {@code 0} for off and {@code 1} for on.
|
||||
*/
|
||||
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 );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
* <p>
|
||||
* {@link #getValue(Component)} returns the value of the component.
|
||||
* If the value changes, then {@link #paintAnimated(Component, Graphics2D, int, int, int, int, float)}
|
||||
* is invoked multiple times with animated value (from old value to new value).
|
||||
* <p>
|
||||
* See {@link AnimatedBorder} or {@link AnimatedIcon} for examples.
|
||||
* <p>
|
||||
* 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.
|
||||
* <p>
|
||||
* 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.
|
||||
* <p>
|
||||
* 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.
|
||||
* <p>
|
||||
* 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.
|
||||
* <p>
|
||||
* This can be any value and depends on the component.
|
||||
* If the value changes, then this class animates from the old value to the new one.
|
||||
* <p>
|
||||
* For a toggle button this could be {@code 0} for off and {@code 1} for on.
|
||||
*/
|
||||
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();
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -140,19 +140,20 @@ public class FlatAnimatedBorderTest
|
||||
// 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 @@ public class FlatAnimatedBorderTest
|
||||
// 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 class FlatAnimatedBorderTest
|
||||
}
|
||||
|
||||
@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 @@ public class FlatAnimatedBorderTest
|
||||
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 );
|
||||
|
||||
Reference in New Issue
Block a user