Skip to content

Commit

Permalink
💥 Huge Update Part 2/3 [effects, resources]
Browse files Browse the repository at this point in the history
[Effects Module]
- Animations Package
:recycle: AnimationFactory: remade animations using the later-introduced Animations class
:recycle: AnimationFactory: do not reset anymore Node translate properties before animating, it's better/more flexible leaving it to the user
 :recycle: AnimationFactory: slide transitions have been reviewed/fixed the translation is not calculated by using the node's bounds anymore. Rather, the distance between the Node and the choosen side of its parent is computed, so that the Node will alway go outside. Additionally, a global variable allows to set an extra offset that is added to this distance
 :sparkles: Animations: added methods to perform a given action when a certain animation's status changes. It can be very useful to do something when an animation is stopped (make sure to read docs on the why, and the difference between finished/stopped)
 :sparkles: Animations: added addConditional(...) method to AbstractBuilder too

[Resources Module]
- Builders Package
:recycle: IconBuilder: added some new methods
:recycle: IconWrapperBuilder: added new methods to reflect changes in MFXIconWrapper

- Fonts Package
:truck: Moved generate random icon methods from IconProviders to enumeration classes
:sparkles: MFXFontIcon: added constructors with IconDescriptor args
:sparkles: MFXFontIcon: implement cloneable interface. Now calling clone() on an icon will produce a new MFXFontIcon object with the same properties (size, colors, icon...)
:boom: MFXIconWrapper: introduced animations functionality. Now icons can be switched through an animation that is either predefined (see AnimationPresets) or custom. Make sure to read the docs to understand how they work!
:recycle: MFXIconWrapper: do not use listeners on the icon and size properties, rather override the set/invalidated methods inline for a little boost in performance
:recycle: MFXIconWrapper: children are not maintained in the desired order in the wrapper's list, rather it now uses the viewOrder property to achieve the same, this too should give a little performance boost
:bug: MFXIconWrapper: set the ripple generator to not be managed automatically and make sure to position and resize it in the layoutChildren method. This caused the ripple to not show in some cases

Signed-off-by: palexdev <alessandro.parisi406@gmail.com>
  • Loading branch information
palexdev authored and palexdev committed Sep 24, 2023
1 parent 37458e0 commit 5dd024c
Show file tree
Hide file tree
Showing 14 changed files with 1,087 additions and 203 deletions.
21 changes: 21 additions & 0 deletions .idea/runConfigurations/ResourcesLauncher.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -18,173 +18,218 @@

package io.github.palexdev.mfxeffects.animations;

import io.github.palexdev.mfxeffects.animations.Animations.KeyFrames;
import io.github.palexdev.mfxeffects.animations.Animations.TimelineBuilder;
import javafx.animation.Interpolator;
import javafx.animation.KeyFrame;
import javafx.animation.KeyValue;
import javafx.animation.Timeline;
import javafx.scene.Node;
import javafx.scene.Parent;
import javafx.util.Duration;

/**
* Convenience factory for various animations applied to {@code Nodes}.
*
* @see #extraOffset
* @see Timeline
*/
public enum AnimationFactory {
FADE_IN {
@Override
public Timeline build(Node node, double durationMillis, Interpolator i) {
AnimationFactory.resetNode(node);
KeyValue keyValue1 = new KeyValue(node.opacityProperty(), 0, i);
KeyFrame keyFrame1 = new KeyFrame(Duration.ZERO, keyValue1);

KeyValue keyValue2 = new KeyValue(node.opacityProperty(), 1.0, i);
KeyFrame keyFrame2 = new KeyFrame(Duration.millis(durationMillis), keyValue2);

return new Timeline(keyFrame1, keyFrame2);
public Timeline build(Node node, double millis, Interpolator i) {
return TimelineBuilder.build()
.add(KeyFrames.of(0, node.opacityProperty(), 0.0))
.add(KeyFrames.of(millis, node.opacityProperty(), 1.0, i))
.getAnimation();
}
},
FADE_OUT {
@Override
public Timeline build(Node node, double durationMillis, Interpolator i) {
AnimationFactory.resetNode(node);
KeyValue keyValue1 = new KeyValue(node.opacityProperty(), 1.0, i);
KeyFrame keyFrame1 = new KeyFrame(Duration.ZERO, keyValue1);

KeyValue keyValue2 = new KeyValue(node.opacityProperty(), 0, i);
KeyFrame keyFrame2 = new KeyFrame(Duration.millis(durationMillis), keyValue2);

return new Timeline(keyFrame1, keyFrame2);
public Timeline build(Node node, double millis, Interpolator i) {
return TimelineBuilder.build()
.add(KeyFrames.of(0, node.opacityProperty(), 1.0))
.add(KeyFrames.of(millis, node.opacityProperty(), 0.0, i))
.getAnimation();
}
},
SLIDE_IN_BOTTOM {
@Override
public Timeline build(Node node, double durationMillis, Interpolator i) {
AnimationFactory.resetNode(node);
KeyValue keyValue1 = new KeyValue(node.translateYProperty(), -node.getBoundsInParent().getHeight() * 2, i);
KeyFrame keyFrame1 = new KeyFrame(Duration.ZERO, keyValue1);

KeyValue keyValue2 = new KeyValue(node.translateYProperty(), 0, i);
KeyFrame keyFrame2 = new KeyFrame(Duration.millis(durationMillis), keyValue2);

return new Timeline(keyFrame1, keyFrame2);
public Timeline build(Node node, double millis, Interpolator i) {
double distance = computeDistanceBottom(node);
return TimelineBuilder.build()
.add(KeyFrames.of(0, node.translateYProperty(), distance))
.add(KeyFrames.of(millis, node.translateYProperty(), 0, i))
.getAnimation();
}
},
SLIDE_OUT_BOTTOM {
@Override
public Timeline build(Node node, double durationMillis, Interpolator i) {
AnimationFactory.resetNode(node);
KeyValue keyValue1 = new KeyValue(node.translateYProperty(), 0, i);
KeyFrame keyFrame1 = new KeyFrame(Duration.ZERO, keyValue1);

KeyValue keyValue2 = new KeyValue(node.translateYProperty(), node.getBoundsInParent().getHeight() * 2, i);
KeyFrame keyFrame2 = new KeyFrame(Duration.millis(durationMillis), keyValue2);

return new Timeline(keyFrame1, keyFrame2);
public Timeline build(Node node, double millis, Interpolator i) {
double distance = computeDistanceBottom(node);
return TimelineBuilder.build()
.add(KeyFrames.of(0, node.translateYProperty(), 0))
.add(KeyFrames.of(millis, node.translateYProperty(), distance, i))
.getAnimation();
}
},
SLIDE_IN_LEFT {
@Override
public Timeline build(Node node, double durationMillis, Interpolator i) {
AnimationFactory.resetNode(node);
KeyValue keyValue1 = new KeyValue(node.translateXProperty(), -node.getBoundsInParent().getWidth() * 2, i);
KeyFrame keyFrame1 = new KeyFrame(Duration.ZERO, keyValue1);

KeyValue keyValue2 = new KeyValue(node.translateXProperty(), 0, i);
KeyFrame keyFrame2 = new KeyFrame(Duration.millis(durationMillis), keyValue2);

return new Timeline(keyFrame1, keyFrame2);
public Timeline build(Node node, double millis, Interpolator i) {
double distance = computeDistanceLeft(node);
return TimelineBuilder.build()
.add(KeyFrames.of(0, node.translateXProperty(), -distance))
.add(KeyFrames.of(millis, node.translateXProperty(), 0, i))
.getAnimation();
}
},
SLIDE_OUT_LEFT {
@Override
public Timeline build(Node node, double durationMillis, Interpolator i) {
AnimationFactory.resetNode(node);
KeyValue keyValue1 = new KeyValue(node.translateXProperty(), 0, i);
KeyFrame keyFrame1 = new KeyFrame(Duration.ZERO, keyValue1);

KeyValue keyValue2 = new KeyValue(node.translateXProperty(), -node.getBoundsInParent().getWidth() * 2, i);
KeyFrame keyFrame2 = new KeyFrame(Duration.millis(durationMillis), keyValue2);

return new Timeline(keyFrame1, keyFrame2);
public Timeline build(Node node, double millis, Interpolator i) {
double distance = computeDistanceLeft(node);
return TimelineBuilder.build()
.add(KeyFrames.of(0, node.translateXProperty(), 0))
.add(KeyFrames.of(millis, node.translateXProperty(), -distance, i))
.getAnimation();
}
},
SLIDE_IN_RIGHT {
@Override
public Timeline build(Node node, double durationMillis, Interpolator i) {
AnimationFactory.resetNode(node);
KeyValue keyValue1 = new KeyValue(node.translateXProperty(), node.getBoundsInParent().getWidth() * 2, i);
KeyFrame keyFrame1 = new KeyFrame(Duration.ZERO, keyValue1);

KeyValue keyValue2 = new KeyValue(node.translateXProperty(), 0, i);
KeyFrame keyFrame2 = new KeyFrame(Duration.millis(durationMillis), keyValue2);

return new Timeline(keyFrame1, keyFrame2);
public Timeline build(Node node, double millis, Interpolator i) {
double distance = computeDistanceRight(node);
return TimelineBuilder.build()
.add(KeyFrames.of(0, node.translateXProperty(), distance))
.add(KeyFrames.of(millis, node.translateXProperty(), 0, i))
.getAnimation();
}
},
SLIDE_OUT_RIGHT {
@Override
public Timeline build(Node node, double durationMillis, Interpolator i) {
AnimationFactory.resetNode(node);
KeyValue keyValue1 = new KeyValue(node.translateXProperty(), 0, i);
KeyFrame keyFrame1 = new KeyFrame(Duration.ZERO, keyValue1);

KeyValue keyValue2 = new KeyValue(node.translateXProperty(), node.getBoundsInParent().getWidth() * 2, i);
KeyFrame keyFrame2 = new KeyFrame(Duration.millis(durationMillis), keyValue2);

return new Timeline(keyFrame1, keyFrame2);
public Timeline build(Node node, double millis, Interpolator i) {
double distance = computeDistanceRight(node);
return TimelineBuilder.build()
.add(KeyFrames.of(0, node.translateXProperty(), 0))
.add(KeyFrames.of(millis, node.translateXProperty(), distance, i))
.getAnimation();
}
},
SLIDE_IN_TOP {
@Override
public Timeline build(Node node, double durationMillis, Interpolator i) {
AnimationFactory.resetNode(node);
KeyValue keyValue1 = new KeyValue(node.translateYProperty(), node.getBoundsInParent().getHeight() * 2, i);
KeyFrame keyFrame1 = new KeyFrame(Duration.ZERO, keyValue1);

KeyValue keyValue2 = new KeyValue(node.translateYProperty(), 0, i);
KeyFrame keyFrame2 = new KeyFrame(Duration.millis(durationMillis), keyValue2);

return new Timeline(keyFrame1, keyFrame2);
public Timeline build(Node node, double millis, Interpolator i) {
double distance = computeDistanceTop(node);
return TimelineBuilder.build()
.add(KeyFrames.of(0, node.translateYProperty(), distance))
.add(KeyFrames.of(millis, node.translateYProperty(), 0, i))
.getAnimation();
}
},
SLIDE_OUT_TOP {
@Override
public Timeline build(Node node, double durationMillis, Interpolator i) {
AnimationFactory.resetNode(node);
KeyValue keyValue1 = new KeyValue(node.translateYProperty(), 0, i);
KeyFrame keyFrame1 = new KeyFrame(Duration.ZERO, keyValue1);

KeyValue keyValue2 = new KeyValue(node.translateYProperty(), -node.getBoundsInParent().getHeight() * 2, i);
KeyFrame keyFrame2 = new KeyFrame(Duration.millis(durationMillis), keyValue2);

return new Timeline(keyFrame1, keyFrame2);
public Timeline build(Node node, double millis, Interpolator i) {
double distance = computeDistanceTop(node);
return TimelineBuilder.build()
.add(KeyFrames.of(0, node.translateYProperty(), 0))
.add(KeyFrames.of(millis, node.translateYProperty(), distance, i))
.getAnimation();
}
};

public static final Interpolator INTERPOLATOR_V1 = Interpolator.SPLINE(0.25, 0.1, 0.25, 1);
public static final Interpolator INTERPOLATOR_V2 = Interpolator.SPLINE(0.0825D, 0.3025D, 0.0875D, 0.9975D);

private static void resetNode(Node node) {
if (node != null) {
node.setTranslateX(0);
node.setTranslateY(0);
}
/**
* This special variable is used in slide animations when the "travel distance" is computed.
* This extra offset is added to the computed value to ensure the node is outside the parent, for a smooth animation.
*/
@SuppressWarnings("NonFinalFieldInEnum")
public static double extraOffset = 5.0;

/**
* Computes the distance between the node and the left side of its parent by using its
* {@link Node#boundsInParentProperty()}. This distance ensures the node is going to be outside/inside the parent
* towards the animation's ending.
*
* @see #extraOffset
*/
public double computeDistanceLeft(Node node) {
double w = node.getBoundsInParent().getWidth();
return node.getBoundsInParent().getMinX() + w + extraOffset;
}

/**
* Computes the distance between the node and the right side of its parent. For this computation the
* {@link Node#parentProperty()} must not return a {@code null} value (the node must be child of some other node).
* <p>
* If the parent is {@code null} a 'fallback' value is returned, that is the width of the node plus the extra offset.
* <p>
* Otherwise, the value is computed like this:
* {@code parent.getLayoutBounds().getWidth() - node.getBoundsInParent().getMaxX() + node.getBoundsInParent().getWidth() + extraOffset}
*
* @see #extraOffset
*/
public double computeDistanceRight(Node node) {
double w = node.getBoundsInParent().getWidth();
Parent parent = node.getParent();
if (parent == null) return w + extraOffset;
return parent.getLayoutBounds().getWidth() - node.getBoundsInParent().getMaxX() + w + extraOffset;
}

/**
* Computes the distance between the node and the top side of its parent by using its
* {@link Node#boundsInParentProperty()}. This distance ensures the node is going to be outside/inside the parent
* towards the animation's ending.
*
* @see #extraOffset
*/
public double computeDistanceTop(Node node) {
double h = node.getBoundsInParent().getHeight();
return node.getBoundsInParent().getMinY() + h + extraOffset;
}

/**
* Computes the distance between the node and the bottom side of its parent. For this computation the
* {@link Node#parentProperty()} must not return a {@code null} value (the node must be child of some other node).
* <p>
* If the parent is {@code null} a 'fallback' value is returned, that is the height of the node plus the extra offset.
* <p>
* Otherwise, the value is computed like this:
* {@code parent.getLayoutBounds().getHeight() - node.getBoundsInParent().getMaxY() + node.getBoundsInParent().getHeight() + extraOffset}
*
* @see #extraOffset
*/
public double computeDistanceBottom(Node node) {
double h = node.getBoundsInParent().getHeight();
Parent parent = node.getParent();
if (parent == null) return h + extraOffset;
return parent.getLayoutBounds().getHeight() - node.getBoundsInParent().getMaxY() + h + extraOffset;
}

/**
* Calls {@link #build(Node, double, Interpolator)} with {@link #INTERPOLATOR_V1} as the default interpolator.
*/
public Timeline build(Node node, double durationMillis) {
return build(node, durationMillis, INTERPOLATOR_V1);
public Timeline build(Node node, double millis) {
return build(node, millis, INTERPOLATOR_V1);
}

/**
* Calls {@link #build(Node, double)} with the given duration converted to milliseconds.
*/
public Timeline build(Node node, Duration duration) {
return build(node, duration.toMillis());
}

/**
* Calls {@link #build(Node, double, Interpolator)} with the given duration converted to milliseconds and the given
* interpolator.
*/
public Timeline build(Node node, Duration duration, Interpolator i) {
return build(node, duration.toMillis(), i);
}

/**
* Each enum constant will produce a {@link Timeline} with the given parameters.
*
* @param node the {@link Node} on which perform the animation
* @param durationMillis the duration of the animation in milliseconds
* @param i the {@link Interpolator} used by the animations
* @param node the {@link Node} on which perform the animation
* @param millis the duration of the animation in milliseconds
* @param i the {@link Interpolator} used by the animations
*/
public abstract Timeline build(Node node, double durationMillis, Interpolator i);
public abstract Timeline build(Node node, double millis, Interpolator i);
}
Loading

0 comments on commit 5dd024c

Please sign in to comment.