├── .gitignore ├── LICENSE ├── README.md ├── pom.xml └── src ├── main ├── java │ └── eu │ │ └── iamgio │ │ └── animated │ │ ├── binding │ │ ├── Animated.java │ │ ├── AnimationSettings.java │ │ ├── CustomizableAnimation.java │ │ ├── SingleChildParent.java │ │ ├── event │ │ │ ├── AnimationEvent.java │ │ │ └── ListenableAnimation.java │ │ ├── misc │ │ │ └── AnimatedSlider.java │ │ ├── presets │ │ │ ├── AnimatedBlur.java │ │ │ ├── AnimatedColor.java │ │ │ ├── AnimatedDropShadow.java │ │ │ ├── AnimatedLayout.java │ │ │ ├── AnimatedOpacity.java │ │ │ ├── AnimatedPrefSize.java │ │ │ ├── AnimatedRotation.java │ │ │ ├── AnimatedScale.java │ │ │ └── AnimatedTranslatePosition.java │ │ ├── property │ │ │ ├── animation │ │ │ │ ├── AnimationProperty.java │ │ │ │ ├── BindableContextNode.java │ │ │ │ ├── OnDemandAnimationProperty.java │ │ │ │ ├── OnDemandAnimationPropertyGroup.java │ │ │ │ └── SimpleAnimationProperty.java │ │ │ └── wrapper │ │ │ │ ├── DoublePropertyWrapper.java │ │ │ │ ├── IntegerPropertyWrapper.java │ │ │ │ ├── ObjectPropertyWrapper.java │ │ │ │ └── PropertyWrapper.java │ │ └── value │ │ │ ├── AnimatedDoubleValueLabel.java │ │ │ ├── AnimatedIntValueLabel.java │ │ │ ├── AnimatedValue.java │ │ │ ├── AnimatedValueImpl.java │ │ │ └── AnimatedValueLabel.java │ │ ├── common │ │ ├── Curve.java │ │ └── Pausable.java │ │ ├── transition │ │ ├── AnimatedLabel.java │ │ ├── AnimatedSwitcher.java │ │ ├── AnimatedThemeSwitcher.java │ │ ├── Animation.java │ │ ├── AnimationPair.java │ │ ├── EntranceAndExitAnimationCompatible.java │ │ ├── EntranceAnimationCompatible.java │ │ ├── ExitAnimationCompatible.java │ │ ├── animations │ │ │ ├── None.java │ │ │ ├── RequiresScene.java │ │ │ └── clip │ │ │ │ ├── CircleClip.java │ │ │ │ ├── CircleClipIn.java │ │ │ │ ├── CircleClipOut.java │ │ │ │ ├── ClipAnimation.java │ │ │ │ ├── RectangleClip.java │ │ │ │ ├── RectangleClipIn.java │ │ │ │ └── RectangleClipOut.java │ │ └── container │ │ │ ├── AnimatedContainer.java │ │ │ ├── AnimatedContainerHandler.java │ │ │ ├── AnimatedHBox.java │ │ │ └── AnimatedVBox.java │ │ └── util │ │ ├── PositionUtils.java │ │ └── ReflectionUtils.java └── kotlin │ └── eu │ └── iamgio │ └── animated │ └── AnimatedExtensions.kt └── test ├── java └── eu │ └── iamgio │ └── animatedtest │ ├── AnimatedButtonTest.java │ ├── AnimatedContainerTest.java │ ├── AnimatedCurrencyTest.java │ ├── AnimatedLayoutTest.java │ ├── AnimatedRootSwitchTest.java │ ├── AnimatedShadowTest.java │ ├── AnimatedSwitcherTest.java │ ├── AnimatedTest.java │ ├── AnimatedThemeTest.java │ ├── TestUtil.java │ └── fxml │ ├── FxmlAnimatedContainerTest.java │ ├── FxmlAnimatedSwitcherTest.java │ ├── FxmlAnimatedTest.java │ └── FxmlAnimatedValueLabelTest.java └── resources ├── fxml ├── Animated.fxml ├── AnimatedContainer.fxml ├── AnimatedSwitcher.fxml └── AnimatedValueLabel.fxml └── themes ├── button.css ├── dark.css ├── global.css └── light.css /.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | .idea/ 3 | *.iml -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2021-2023 Giorgio Garofalo 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # animated 2 | 3 | **animated** introduces **implicit animations**, a completely new concept in JavaFX strongly inspired by [Flutter's animations and motion widgets](https://flutter.dev/docs/development/ui/widgets/animation). 4 | 5 | ### Index 6 | 1. [Getting started](#getting-started) 7 | 2. [Implicit animations](#implicit-animations) 8 | 3. [Animated containers](#animated-containers) 9 | 4. [Animated switchers](#animated-switchers) 10 | 5. [Animated theme switch](#animated-theme-switch) 11 | 6. [Animated values](#animated-values) 12 | 7. [Other examples](#other-examples) 13 | 8. [FXML](#fxml) 14 | 9. [Kotlin extensions](#kotlin-extensions) 15 | 16 | ## Getting started 17 | 18 | Maven: 19 | ```xml 20 | 21 | eu.iamgio 22 | animated 23 | 1.3.0 24 | 25 | ``` 26 | 27 | Gradle: 28 | ```gradle 29 | allprojects { 30 | repositories { 31 | mavenCentral() 32 | } 33 | } 34 | dependencies { 35 | implementation 'eu.iamgio:animated:1.3.0' 36 | } 37 | ``` 38 | 39 | > **Note**: v1.0.0 brought several important changes, including a different project structure, 40 | > that may cause compilation errors when upgrading from 0.x versions. 41 | > Please check the [migration guide](https://github.com/iamgio/animated/releases/tag/v1.0.0) to see what changed and quickly learn how to fix those errors. 42 | 43 |
44 | 45 | --- 46 | 47 |
48 | 49 | ## Implicit animations 50 | 51 | Forget about timelines, explicit animations and other stuff that pollutes your code. This animation system will provide versatility to your code and interface. 52 | 53 | ![Demo](https://i.imgur.com/TKXA8de.gif) 54 | **[Code](src/test/java/eu/iamgio/animatedtest/AnimatedTest.java)** 55 | 56 | ```java 57 | Animated animated = new Animated(child, AnimationProperty.of(child.opacityProperty())); 58 | root.getChildren().add(animated); 59 | 60 | // Later... 61 | child.setOpacity(0.5); // Plays the transition 62 | ``` 63 | 64 | This approach instantiates an `Animated` node that contains one child and is bound to a property. 65 | Now that we have set an animated bound, we can see that `child.setOpacity(someValue)` creates a transition between the initial and final value. 66 | 67 | A single `Animated` object can take multiple properties at once, 68 | and they can also be added later by accessing `Animated#getTargetProperties`. 69 | 70 | ### Presets 71 | [Pre-made animation properties](src/main/java/eu/iamgio/animated/binding/presets) represent a concise and efficient 72 | way to create animated bindings, rather than manually referencing to the 73 | raw JavaFX property as we previously did. 74 | 75 | - `AnimatedBlur` 76 | - `AnimatedColor` 77 | - `AnimatedDropShadow.Color` 78 | - `AnimatedDropShadow.Radius` 79 | - `AnimatedLayout` 80 | - `AnimatedOpacity` 81 | - `AnimatedPrefSize` 82 | - `AnimatedRotation` 83 | - `AnimatedScale` 84 | - `AnimatedTranslatePosition` 85 | 86 | When these properties are instantiated via their zero-arguments constructor, 87 | the target node references to the `Animated`'s child. 88 | 89 | ```java 90 | // Before 91 | new Animated(child, 92 | AnimationProperty.of(child.prefWidthProperty()), 93 | AnimationProperty.of(child.prefHeightProperty()) 94 | ); 95 | 96 | // After: better! 97 | new Animated(child, new AnimatedPrefSize()); 98 | ``` 99 | 100 | ### Independent animations 101 | 102 | `Animated` is a node that has to be added to the scene in order to work. 103 | Here is a different approach that is independent from the scene: 104 | 105 | ```java 106 | AnimationProperty.of(node.opacityProperty()).register(); 107 | // or 108 | new AnimatedOpacity(node).register(); 109 | 110 | // Later... 111 | node.setOpacity(0.5); // Plays the transition 112 | ``` 113 | 114 | > Animations are not only visual: 115 | > you can also animate other changes such as audio volume! 116 | 117 | ### Custom animations 118 | 119 | The default animation is linear and lasts 1 second. 120 | It can be customized by calling `withSettings(AnimationSettings settings)` or `custom(Function settings)`, 121 | both methods available on animated nodes and animation properties. 122 | 123 | Examples: 124 | ```java 125 | Animated animated = new Animated(child, AnimationProperty.of(child.opacityProperty())); 126 | .custom(settings -> settings.withDuration(Duration.seconds(.5)).withCurve(Curve.EASE_IN_OUT)); 127 | ``` 128 | 129 | ```java 130 | Animated animated = new Animated(child, 131 | new AnimatedOpacity() 132 | .custom(settings -> settings.withDuration(Duration.seconds(.8))), 133 | new AnimatedRotation() 134 | .custom(settings -> settings.withDuration(Duration.seconds(.5))), 135 | ).custom(settings -> settings.withCurve(Curve.EASE_OUT)); 136 | ``` 137 | 138 |
139 | 140 | --- 141 | 142 |
143 | 144 | ## Animated containers 145 | 146 | **animated** provides custom implementations of `VBox` and `HBox` that animate their content whenever their children are affected by a change. 147 | This feature is based on animations from [AnimateFX](https://github.com/Typhon0/AnimateFX). 148 | 149 | ![Demo](https://i.imgur.com/jqm9KDA.gif) 150 | **[Code](src/test/java/eu/iamgio/animatedtest/AnimatedContainerTest.java)** 151 | 152 | **Constructors**: 153 | - `Animation in, Animation out` wraps two `AnimateFX` objects into customizable `animated` objects; 154 | - `AnimationFX in, AnimationFX out` takes two raw AnimateFX animations that cannot be customized; 155 | - `AnimationPair animation` takes a pair of animations, mostly used with pre-made pairs (e.g. `AnimationPair.fade()`). 156 | 157 | `pause()` and `resume()` allow disabling/enabling animations so that you can switch back to the regular implementation and forth. 158 | 159 | Example: 160 | ```java 161 | AnimatedVBox vBox = new AnimatedVBox(AnimationPair.fade()); 162 | 163 | // Later... 164 | vBox.getChildren().add(someNode); // someNode fades in 165 | vBox.getChildren().remove(someNode); // someNode fades out 166 | ``` 167 | 168 |
169 | 170 | --- 171 | 172 |
173 | 174 | ## Animated switchers 175 | 176 | The library also provides an `AnimatedSwitcher` node that creates a transition whenever its child changes. 177 | As for animated containers, this feature relies on AnimateFX. 178 | 179 | ![Demo](https://i.imgur.com/8v2Wn0a.gif) 180 | **[Code](src/test/java/eu/iamgio/animatedtest/AnimatedSwitcherTest.java)** 181 | 182 | See [animated containers](#animated-containers) for information about constructors. 183 | Right after the instantiation, calling `of(Node child)` will set the initial child without any animation played. 184 | 185 | Example: 186 | ```java 187 | AnimatedSwitcher switcher = new AnimatedSwitcher( 188 | new Animation(new FadeInDown()).setSpeed(2), 189 | new Animation(new FadeOutDown()).setSpeed(1.5) 190 | ).of(firstChild); 191 | root.getChildren().add(switcher); 192 | 193 | // Later... 194 | switcher.setChild(secondChild); // Plays the transition 195 | ``` 196 | 197 | ### Related: animated text 198 | 199 | `AnimatedLabel` uses a switcher to animate text. 200 | 201 | Example: 202 | ```java 203 | AnimatedLabel label = new AnimatedLabel("Text", AnimationPair.fade()); 204 | 205 | // Later... 206 | label.setText("New text"); // Plays the transition 207 | ``` 208 | 209 |
210 | 211 | --- 212 | 213 |
214 | 215 | ## Animated theme switch 216 | 217 | It is possible to create a transition whenever the stylesheets of the scene change via `AnimatedThemeSwitcher`, based on AnimateFX. 218 | 219 | 220 | ![Theme](https://i.imgur.com/Wwma43y.gif) 221 | **[Code](src/test/java/eu/iamgio/animatedtest/AnimatedThemeTest.java)** 222 | 223 | ```java 224 | scene.getStylesheets().setAll("/light.css"); // Initial theme 225 | AnimatedThemeSwitcher themeSwitcher = new AnimatedThemeSwitcher(scene, new CircleClipOut()); 226 | themeSwitcher.init(); // Required! 227 | 228 | // Later... 229 | scene.getStylesheets().setAll("/dark.css"); // Plays the transition 230 | 231 | // This also works with add, set, remove and other List methods. 232 | ``` 233 | 234 | > **Note** that not every type of root can be animated properly, such as `VBox` and `HBox`. 235 | > Parents that allow overlapping children, i.e. `Pane`, are suggested. 236 | 237 |
238 | 239 | --- 240 | 241 |
242 | 243 | ## Animated values 244 | 245 | The animated binding API provides a way to animate the content of a `Label` 246 | every time its associated value changes. 247 | 248 | 249 | ![Currency](https://i.imgur.com/9TZmEzl.gif) 250 | **[Code](src/test/java/eu/iamgio/animatedtest/AnimatedCurrencyTest.java)** 251 | 252 | ```java 253 | AnimatedValueLabel label = new AnimatedValueLabel<>(0) 254 | .custom(settings -> settings.withCurve(Curve.EASE_IN_OUT)); 255 | 256 | // We can also customize the displayed text 257 | label.setTextMapper(value -> "The value is " + value); 258 | 259 | // Later... 260 | label.setValue(10); // Plays the transition 261 | ``` 262 | 263 |
264 | 265 | --- 266 | 267 |
268 | 269 | ## Other examples 270 | 271 | ![Button](https://i.imgur.com/mVGkKcx.gif) 272 | **[Button color and border](src/test/java/eu/iamgio/animatedtest/AnimatedButtonTest.java)** 273 | 274 | ![Shadow](https://i.imgur.com/jd8Bbr4.gif) 275 | **[Drop shadows + label](src/test/java/eu/iamgio/animatedtest/AnimatedShadowTest.java)** 276 | 277 | ![Root switch](https://i.imgur.com/cYkSu9z.gif) 278 | **[Root switch](src/test/java/eu/iamgio/animatedtest/AnimatedRootSwitchTest.java)** 279 | 280 | ![Layout alignment](https://i.imgur.com/xNRltwq.gif) 281 | **[Layout alignment](src/test/java/eu/iamgio/animatedtest/AnimatedLayoutTest.java)** (inspired by the Edge home page) 282 | 283 |
284 | 285 | --- 286 | 287 |
288 | 289 | ## FXML 290 | 291 | - **Animated** 292 | ```xml 293 | 294 | 295 | 296 | 297 | 298 | 299 | 300 | 301 | 302 | 303 | 304 | 305 | 306 | 307 | 308 | 309 | 310 | 311 | ``` 312 | 313 | - **AnimatedContainer** 314 | ```xml 315 | 316 | 317 | 318 | 319 | 320 | 321 | 322 | 323 | 324 | 325 | 326 | 327 | 328 | 329 | 330 | 331 | 332 | ``` 333 | 334 | - **AnimatedSwitcher** 335 | ```xml 336 | 337 | 338 | 339 | 340 | 341 | 342 | 343 | 344 | 345 | 346 | 347 | 348 | 349 | 350 | 351 | 352 | 353 | ``` 354 | 355 | - **AnimatedLabel** 356 | ```xml 357 | 358 | 359 | 360 | 361 | 362 | 363 | 364 | 365 | 366 | 367 | 368 | 369 | 370 | ``` 371 | 372 | - **AnimatedValueLabel** 373 | FXML has issues with generic types, so you will need to instantiate an 374 | `AnimatedIntValueLabel` or `AnimatedDoubleValueLabel`. 375 | ```xml 376 | 377 | 378 | 379 | 380 | 381 | 382 | 383 | 384 | 385 | ``` 386 | 387 |
388 | 389 | > When instantiating an ``, 390 | > the class name (case sensitive) is searched in the following packages: 391 | > - [`animatefx.animation`](https://github.com/Typhon0/AnimateFX/tree/master/animatefx/src/main/java/animatefx/animation) 392 | > - [`eu.iamgio.animated.transition.animations`](src/main/java/eu/iamgio/animated/transition/animations) and sub-packages 393 | 394 | 395 |
396 | 397 | --- 398 | 399 |
400 | 401 | ## Kotlin extensions 402 | 403 | [Extension functions](src/main/kotlin/eu/iamgio/animated/AnimatedExtensions.kt) make the library less verbose with Kotlin. 404 | Example: 405 | ```kotlin 406 | val animated: Animated = Animated(child, child.someProperty().animated()) 407 | val pair: AnimationPair = FadeIn().options(speed = 1.5) outTo FadeOut() 408 | ``` -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | eu.iamgio 8 | animated 9 | 1.3.0 10 | 11 | animated 12 | Modern animation library for JavaFX. 13 | https://github.com/iamgio/animated 14 | 15 | 16 | 17 | MIT License 18 | https://www.opensource.org/licenses/mit-license.php 19 | 20 | 21 | 22 | 23 | 24 | Giorgio Garofalo 25 | giorgiogarofalo02@gmail.com 26 | com.github.iamgio 27 | https://iamgio.eu 28 | 29 | 30 | 31 | 32 | scm:git:git://github.com/iamgio/animated.git 33 | scm:git:ssh://github.com:iamgio/animated.git 34 | https://github.com/iamgio/animated 35 | 36 | 37 | 38 | 1.4.31 39 | 8 40 | 8 41 | 42 | 43 | 44 | 45 | ossrh 46 | https://s01.oss.sonatype.org/content/repositories/snapshots 47 | 48 | 49 | ossrh 50 | https://s01.oss.sonatype.org/service/local/staging/deploy/maven2/ 51 | 52 | 53 | 54 | 55 | 56 | 57 | org.sonatype.plugins 58 | nexus-staging-maven-plugin 59 | 1.6.8 60 | true 61 | 62 | ossrh 63 | https://s01.oss.sonatype.org/ 64 | true 65 | 66 | 67 | 68 | org.apache.maven.plugins 69 | maven-source-plugin 70 | 2.2.1 71 | 72 | 73 | attach-sources 74 | 75 | jar-no-fork 76 | 77 | 78 | 79 | 80 | 81 | org.apache.maven.plugins 82 | maven-jar-plugin 83 | 84 | 85 | 86 | eu.iamgio.animated 87 | 88 | 89 | 90 | 91 | 92 | org.apache.maven.plugins 93 | maven-javadoc-plugin 94 | 2.9.1 95 | 96 | 97 | attach-javadocs 98 | 99 | jar 100 | 101 | 102 | 103 | 104 | 105 | org.apache.maven.plugins 106 | maven-gpg-plugin 107 | 1.5 108 | 109 | 110 | sign-artifacts 111 | verify 112 | 113 | sign 114 | 115 | 116 | 117 | 118 | 119 | org.jetbrains.kotlin 120 | kotlin-maven-plugin 121 | ${kotlin.version} 122 | 123 | 124 | compile 125 | compile 126 | 127 | compile 128 | 129 | 130 | 131 | ${project.basedir}/src/main/kotlin 132 | 133 | 134 | 135 | 136 | test-compile 137 | test-compile 138 | 139 | test-compile 140 | 141 | 142 | 143 | 144 | 1.8 145 | 146 | 147 | 148 | org.apache.maven.plugins 149 | maven-compiler-plugin 150 | 3.8.1 151 | 152 | 153 | compile 154 | compile 155 | 156 | compile 157 | 158 | 159 | 160 | testCompile 161 | test-compile 162 | 163 | testCompile 164 | 165 | 166 | 167 | 168 | 8 169 | 8 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | org.openjfx 178 | javafx-controls 179 | 11 180 | provided 181 | 182 | 183 | io.github.typhon0 184 | AnimateFX 185 | 1.2.4 186 | 187 | 188 | org.jetbrains.kotlin 189 | kotlin-stdlib-jdk8 190 | ${kotlin.version} 191 | provided 192 | 193 | 194 | org.jetbrains.kotlin 195 | kotlin-test 196 | ${kotlin.version} 197 | test 198 | 199 | 200 | -------------------------------------------------------------------------------- /src/main/java/eu/iamgio/animated/binding/Animated.java: -------------------------------------------------------------------------------- 1 | package eu.iamgio.animated.binding; 2 | 3 | import eu.iamgio.animated.binding.property.animation.AnimationProperty; 4 | import eu.iamgio.animated.common.Pausable; 5 | import javafx.beans.property.BooleanProperty; 6 | import javafx.beans.property.DoubleProperty; 7 | import javafx.beans.property.SimpleBooleanProperty; 8 | import javafx.collections.FXCollections; 9 | import javafx.collections.ListChangeListener; 10 | import javafx.collections.ObservableList; 11 | import javafx.scene.Node; 12 | 13 | import java.util.function.Function; 14 | 15 | /** 16 | * A node that automatically animates multiple properties related to its child. 17 | * @author Giorgio Garofalo 18 | */ 19 | public class Animated extends SingleChildParent implements CustomizableAnimation, Pausable { 20 | 21 | private final ObservableList> properties = FXCollections.observableArrayList(); 22 | private final BooleanProperty paused = new SimpleBooleanProperty(false); 23 | 24 | /** 25 | * Instantiates a new {@link Animated} node without a child and with no target properties. 26 | */ 27 | public Animated() { 28 | registerPropertyListeners(); 29 | } 30 | 31 | /** 32 | * Instantiates a new {@link Animated} node. 33 | * @param child the target node that should be animated, to be wrapped by this {@link Animated} node 34 | * @param properties target properties that should be animated. 35 | * It is good practice to target properties that are related to this node's child 36 | * @see AnimationProperty#of(DoubleProperty) 37 | */ 38 | public Animated(Node child, AnimationProperty... properties) { 39 | super(child); 40 | registerPropertyListeners(); 41 | this.properties.addAll(properties); 42 | } 43 | 44 | /** 45 | * Instantiates a new {@link Animated} node with no target properties. 46 | * @param child the target node that should be animated, to be wrapped by this {@link Animated} node 47 | */ 48 | public Animated(Node child) { 49 | super(child); 50 | registerPropertyListeners(); 51 | } 52 | 53 | /** 54 | * Instantiates a new {@link Animated} node without a child. 55 | * @param properties target properties that should be animated. 56 | * It is good practice to target properties that are related to this node's child 57 | * @see AnimationProperty#of(DoubleProperty) 58 | */ 59 | public Animated(AnimationProperty... properties) { 60 | registerPropertyListeners(); 61 | this.properties.addAll(properties); 62 | } 63 | 64 | private void registerPropertyListeners() { 65 | properties.addListener((ListChangeListener>) change -> { 66 | while (change.next()) { 67 | change.getAddedSubList().forEach(property -> { 68 | property.attachTo(this); 69 | property.pausedProperty().bindBidirectional(this.paused); 70 | }); 71 | // TODO unbind on remove 72 | } 73 | }); 74 | } 75 | 76 | /** 77 | * {@inheritDoc} 78 | */ 79 | @Override 80 | public void setSettings(AnimationSettings settings) { 81 | this.properties.forEach(property -> property.setSettings(settings)); 82 | } 83 | 84 | /** 85 | * {@inheritDoc} 86 | */ 87 | @Override 88 | public Animated custom(Function settings) { 89 | this.properties.forEach(property -> property.custom(settings)); 90 | return this; 91 | } 92 | 93 | /** 94 | * {@inheritDoc} 95 | */ 96 | @Override 97 | public BooleanProperty pausedProperty() { 98 | return this.paused; 99 | } 100 | 101 | /** 102 | * @return a mutable list of the target properties that should be animated. 103 | * It is good practice to target properties that are related to this node's child 104 | */ 105 | public ObservableList> getTargetProperties() { 106 | return this.properties; 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /src/main/java/eu/iamgio/animated/binding/AnimationSettings.java: -------------------------------------------------------------------------------- 1 | package eu.iamgio.animated.binding; 2 | 3 | import eu.iamgio.animated.common.Curve; 4 | import javafx.util.Duration; 5 | 6 | /** 7 | * Data that affects the way an animation is played. 8 | */ 9 | public class AnimationSettings { 10 | 11 | private Duration duration = Duration.seconds(1); 12 | private Curve curve = Curve.LINEAR; 13 | 14 | /** 15 | * @return duration of the animation 16 | */ 17 | public Duration getDuration() { 18 | return duration; 19 | } 20 | 21 | /** 22 | * Note: using the fluent setter {@link #withDuration(Duration)} is suggested. 23 | * This method is kept only for FXML compatibility. 24 | * @param duration duration of the animation to set 25 | */ 26 | public void setDuration(Duration duration) { 27 | this.duration = duration; 28 | } 29 | 30 | /** 31 | * @param duration duration of the animation to set 32 | * @return this for concatenation 33 | */ 34 | public AnimationSettings withDuration(Duration duration) { 35 | this.duration = duration; 36 | return this; 37 | } 38 | 39 | /** 40 | * @return curve of the animation 41 | */ 42 | public Curve getCurve() { 43 | return curve; 44 | } 45 | 46 | /** 47 | * Note: using the fluent setter {@link #withCurve(Curve)} is suggested. 48 | * This method is kept only for FXML compatibility. 49 | * @param curve curve of the animation to set 50 | * 51 | */ 52 | public void setCurve(Curve curve) { 53 | this.curve = curve; 54 | } 55 | 56 | /** 57 | * @param curve curve of the animation to set 58 | * @return this for concatenation 59 | */ 60 | public AnimationSettings withCurve(Curve curve) { 61 | this.curve = curve; 62 | return this; 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/main/java/eu/iamgio/animated/binding/CustomizableAnimation.java: -------------------------------------------------------------------------------- 1 | package eu.iamgio.animated.binding; 2 | 3 | import java.util.function.Function; 4 | 5 | /** 6 | * Interface that defines a class with customizable {@link AnimationSettings}. 7 | * @param settings owner 8 | * @author Giorgio Garofalo 9 | */ 10 | public interface CustomizableAnimation { 11 | 12 | /** 13 | * Applies custom animation settings. 14 | * @param settings animation settings to set 15 | */ 16 | void setSettings(AnimationSettings settings); 17 | 18 | /** 19 | * Applies custom animation settings. 20 | * @param settings animation settings to set 21 | * @return this for concatenation 22 | */ 23 | @SuppressWarnings("unchecked") 24 | default T withSettings(AnimationSettings settings) { 25 | setSettings(settings); 26 | return (T) this; 27 | } 28 | 29 | /** 30 | * Applies custom animation settings 31 | * @param settings settings to update. Example:
custom(settings{@literal ->} settings.withDuration(...))
32 | * @return this for concatenation 33 | */ 34 | T custom(Function settings); 35 | } 36 | -------------------------------------------------------------------------------- /src/main/java/eu/iamgio/animated/binding/SingleChildParent.java: -------------------------------------------------------------------------------- 1 | package eu.iamgio.animated.binding; 2 | 3 | import javafx.beans.property.ObjectProperty; 4 | import javafx.beans.property.SimpleObjectProperty; 5 | import javafx.scene.Node; 6 | import javafx.scene.Parent; 7 | 8 | /** 9 | * Parent node that has only one child 10 | * @author Giorgio Garofalo 11 | */ 12 | public class SingleChildParent extends Parent { 13 | 14 | protected final ObjectProperty child = new SimpleObjectProperty<>(); 15 | 16 | protected SingleChildParent() { 17 | // Registers child listener 18 | this.child.addListener((observable, oldChild, newChild) -> { 19 | if (newChild != null) { 20 | getChildren().setAll(newChild); 21 | } else { 22 | getChildren().clear(); 23 | } 24 | }); 25 | } 26 | 27 | protected SingleChildParent(Node child) { 28 | this(); 29 | this.child.set(child); 30 | } 31 | 32 | /** 33 | * @return the current child 34 | */ 35 | public ObjectProperty childProperty() { 36 | return this.child; 37 | } 38 | 39 | /** 40 | * @return the current child 41 | */ 42 | public Node getChild() { 43 | return this.child.get(); 44 | } 45 | 46 | /** 47 | * Sets the new child. 48 | * @param child new child. If null, the current child is removed and nothing is added. 49 | */ 50 | public void setChild(Node child) { 51 | this.childProperty().set(child); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/main/java/eu/iamgio/animated/binding/event/AnimationEvent.java: -------------------------------------------------------------------------------- 1 | package eu.iamgio.animated.binding.event; 2 | 3 | import javafx.event.Event; 4 | import javafx.event.EventType; 5 | 6 | /** 7 | * An animation-related event. 8 | * @see ListenableAnimation 9 | */ 10 | public final class AnimationEvent extends Event { 11 | 12 | private static final EventType ANIMATION = new EventType<>("ANIMATION"); 13 | 14 | private final boolean interrupted; 15 | 16 | public AnimationEvent(boolean interrupted) { 17 | super(ANIMATION); 18 | this.interrupted = interrupted; 19 | } 20 | 21 | /** 22 | * @return whether this animation was interrupted. 23 | * Note: 24 | *
    25 | *
  • 26 | * If this event represents the end of an animation, the interrupted status 27 | * means the animation stopped before the expected time because the wrapped value 28 | * was externally changed and a new animation has to be played. 29 | *
  • 30 | *
  • 31 | * If this event represents the start of an animation, the interrupted status 32 | * means the animation started right after an interrupted end animation. 33 | *
  • 34 | *
35 | */ 36 | public boolean isInterrupted() { 37 | return this.interrupted; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/main/java/eu/iamgio/animated/binding/event/ListenableAnimation.java: -------------------------------------------------------------------------------- 1 | package eu.iamgio.animated.binding.event; 2 | 3 | import javafx.beans.property.ObjectProperty; 4 | import javafx.event.EventHandler; 5 | 6 | /** 7 | * A provider of listeners for animation-related events. 8 | * @see AnimationEvent 9 | */ 10 | public interface ListenableAnimation { 11 | 12 | /** 13 | * @return the action to run when an animation begins 14 | */ 15 | ObjectProperty> onAnimationStartedProperty(); 16 | 17 | /** 18 | * @param handler the action to run when an animation begins 19 | */ 20 | default void setOnAnimationStarted(EventHandler handler) { 21 | onAnimationStartedProperty().set(handler); 22 | } 23 | 24 | /** 25 | * @return the action to run when an animation finishes 26 | */ 27 | ObjectProperty> onAnimationEndedProperty(); 28 | 29 | /** 30 | * @param handler the action to run when an animation finishes 31 | */ 32 | default void setOnAnimationEnded(EventHandler handler) { 33 | onAnimationEndedProperty().set(handler); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/main/java/eu/iamgio/animated/binding/misc/AnimatedSlider.java: -------------------------------------------------------------------------------- 1 | package eu.iamgio.animated.binding.misc; 2 | 3 | import eu.iamgio.animated.binding.AnimationSettings; 4 | import eu.iamgio.animated.binding.CustomizableAnimation; 5 | import eu.iamgio.animated.binding.event.AnimationEvent; 6 | import eu.iamgio.animated.binding.event.ListenableAnimation; 7 | import eu.iamgio.animated.binding.property.animation.AnimationProperty; 8 | import javafx.beans.property.ObjectProperty; 9 | import javafx.event.EventHandler; 10 | import javafx.scene.control.Slider; 11 | 12 | import java.util.function.Function; 13 | 14 | /** 15 | * A {@link Slider} implementation that animates its value when an external change occurs. 16 | * The animation does not play when the value is changed by mouse-dragging. 17 | * 18 | * @see Slider 19 | */ 20 | public final class AnimatedSlider extends Slider implements CustomizableAnimation, ListenableAnimation { 21 | 22 | private final AnimationProperty property = AnimationProperty.of(valueProperty()); 23 | 24 | /** 25 | * Instantiates an {@link AnimatedSlider}. 26 | */ 27 | public AnimatedSlider() { 28 | super(); 29 | this.init(); 30 | } 31 | 32 | /** 33 | * Instantiates an {@link AnimatedSlider}. 34 | * @param min minimum value 35 | * @param max maximum value 36 | * @param value initial value 37 | */ 38 | public AnimatedSlider(double min, double max, double value) { 39 | super(min, max, value); 40 | this.init(); 41 | } 42 | 43 | private void init() { 44 | property.register(this); 45 | 46 | // Animation is paused when mouse-dragging 47 | setOnMouseDragged(e -> property.pause()); 48 | setOnMouseReleased(e -> property.resume()); 49 | } 50 | 51 | /** 52 | * {@inheritDoc} 53 | */ 54 | @Override 55 | public void setSettings(AnimationSettings settings) { 56 | property.setSettings(settings); 57 | } 58 | 59 | /** 60 | * {@inheritDoc} 61 | */ 62 | @Override 63 | public AnimatedSlider custom(Function settings) { 64 | property.custom(settings); 65 | return this; 66 | } 67 | 68 | /** 69 | * {@inheritDoc} 70 | */ 71 | @Override 72 | public ObjectProperty> onAnimationStartedProperty() { 73 | return this.property.onAnimationStartedProperty(); 74 | } 75 | 76 | /** 77 | * {@inheritDoc} 78 | */ 79 | @Override 80 | public ObjectProperty> onAnimationEndedProperty() { 81 | return this.property.onAnimationEndedProperty(); 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /src/main/java/eu/iamgio/animated/binding/presets/AnimatedBlur.java: -------------------------------------------------------------------------------- 1 | package eu.iamgio.animated.binding.presets; 2 | 3 | import eu.iamgio.animated.binding.property.animation.OnDemandAnimationProperty; 4 | import eu.iamgio.animated.binding.property.wrapper.PropertyWrapper; 5 | import javafx.scene.Node; 6 | import javafx.scene.effect.GaussianBlur; 7 | 8 | /** 9 | * Property that animates its child's {@link GaussianBlur} radius. 10 | * If the target node does not have an {@link javafx.scene.effect.Effect} 11 | * or if its effect is not a {@link GaussianBlur} at initialization time, 12 | * a new {@link GaussianBlur} with default radius will be set as its new effect. 13 | */ 14 | public class AnimatedBlur extends OnDemandAnimationProperty { 15 | 16 | public AnimatedBlur() { 17 | super(node -> PropertyWrapper.of(getEffectOrCreate(node).radiusProperty())); 18 | } 19 | 20 | public AnimatedBlur(Node child) { 21 | this(); 22 | targetNodeProperty().set(child); 23 | } 24 | 25 | private static GaussianBlur getEffectOrCreate(Node node) { 26 | if (node.getEffect() == null || !(node.getEffect() instanceof GaussianBlur)) { 27 | GaussianBlur blur = new GaussianBlur(); 28 | node.setEffect(blur); 29 | return blur; 30 | } else { 31 | return (GaussianBlur) node.getEffect(); 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/main/java/eu/iamgio/animated/binding/presets/AnimatedColor.java: -------------------------------------------------------------------------------- 1 | package eu.iamgio.animated.binding.presets; 2 | 3 | import eu.iamgio.animated.binding.property.animation.OnDemandAnimationProperty; 4 | import eu.iamgio.animated.binding.property.wrapper.PropertyWrapper; 5 | import javafx.scene.paint.Paint; 6 | import javafx.scene.shape.Shape; 7 | 8 | /** 9 | * Property that animates its child's color, for {@link Shape} nodes. 10 | * @author Giorgio Garofalo 11 | */ 12 | public class AnimatedColor extends OnDemandAnimationProperty { 13 | 14 | public AnimatedColor() { 15 | super(node -> PropertyWrapper.of(node.fillProperty())); 16 | } 17 | 18 | public AnimatedColor(Shape child) { 19 | this(); 20 | targetNodeProperty().set(child); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/main/java/eu/iamgio/animated/binding/presets/AnimatedDropShadow.java: -------------------------------------------------------------------------------- 1 | package eu.iamgio.animated.binding.presets; 2 | 3 | import eu.iamgio.animated.binding.property.animation.OnDemandAnimationProperty; 4 | import eu.iamgio.animated.binding.property.wrapper.PropertyWrapper; 5 | import javafx.scene.Node; 6 | import javafx.scene.effect.DropShadow; 7 | 8 | /** 9 | * Container of different drop shadow properties. 10 | * If the target node does not have an {@link javafx.scene.effect.Effect} 11 | * or if its effect is not a {@link DropShadow} at initialization time, 12 | * a new {@link DropShadow} with default color and radius will be set as its new effect. 13 | */ 14 | public class AnimatedDropShadow { 15 | 16 | /** 17 | * Property that animates its child's {@link DropShadow} color. 18 | * If the target node does not have an {@link javafx.scene.effect.Effect} 19 | * or if its effect is not a {@link DropShadow} at initialization time, 20 | * a new {@link DropShadow} with default color and radius will be set as its new effect. 21 | */ 22 | public static class Color extends OnDemandAnimationProperty { 23 | 24 | public Color() { 25 | super(node -> PropertyWrapper.of(getEffectOrCreate(node).colorProperty())); 26 | } 27 | 28 | public Color(Node child) { 29 | this(); 30 | targetNodeProperty().set(child); 31 | } 32 | } 33 | 34 | /** 35 | * Property that animates its child's {@link DropShadow} radius. 36 | * If the target node does not have an {@link javafx.scene.effect.Effect} 37 | * or if its effect is not a {@link DropShadow} at initialization time, 38 | * a new {@link DropShadow} with default color and radius will be set as its new effect. 39 | */ 40 | public static class Radius extends OnDemandAnimationProperty { 41 | 42 | public Radius() { 43 | super(node -> PropertyWrapper.of(getEffectOrCreate(node).radiusProperty())); 44 | } 45 | 46 | public Radius(Node child) { 47 | this(); 48 | targetNodeProperty().set(child); 49 | } 50 | } 51 | 52 | private static DropShadow getEffectOrCreate(Node node) { 53 | if (node.getEffect() == null || !(node.getEffect() instanceof DropShadow)) { 54 | final DropShadow shadow = new DropShadow(); 55 | node.setEffect(shadow); 56 | return shadow; 57 | } else { 58 | return (DropShadow) node.getEffect(); 59 | } 60 | } 61 | } 62 | 63 | -------------------------------------------------------------------------------- /src/main/java/eu/iamgio/animated/binding/presets/AnimatedLayout.java: -------------------------------------------------------------------------------- 1 | package eu.iamgio.animated.binding.presets; 2 | 3 | import eu.iamgio.animated.binding.property.animation.OnDemandAnimationPropertyGroup; 4 | import eu.iamgio.animated.binding.property.wrapper.PropertyWrapper; 5 | import javafx.beans.property.BooleanProperty; 6 | import javafx.beans.property.SimpleBooleanProperty; 7 | import javafx.geometry.Bounds; 8 | import javafx.geometry.HPos; 9 | import javafx.geometry.Pos; 10 | import javafx.geometry.VPos; 11 | import javafx.scene.Node; 12 | import javafx.scene.layout.Region; 13 | 14 | import java.util.Arrays; 15 | 16 | /** 17 | * Node that animates its child's position based on an alignment relative to an anchor root. 18 | * 19 | * @author Giorgio Garofalo 20 | */ 21 | public class AnimatedLayout extends OnDemandAnimationPropertyGroup { 22 | 23 | private final Node child; 24 | private final Region root; 25 | private final Pos alignment; 26 | 27 | private final BooleanProperty animateShrinking = new SimpleBooleanProperty(); 28 | 29 | // The latest registered child size 30 | private Bounds bounds; 31 | 32 | /** 33 | * Instantiates an {@link AnimatedLayout} node. 34 | * @param child the node to wrap, whose layout should be animated 35 | * @param root root to rely relayouts on 36 | * @param alignment position of the node relative to its root 37 | * @param animateShrinking whether the animation should be played when the root is shrunk 38 | */ 39 | public AnimatedLayout(Node child, Region root, Pos alignment, boolean animateShrinking) { 40 | super(Arrays.asList( 41 | node -> PropertyWrapper.of(node.layoutXProperty()), 42 | node -> PropertyWrapper.of(node.layoutYProperty()) 43 | )); 44 | 45 | targetNodeProperty().set(child); 46 | 47 | this.child = child; 48 | this.root = root; 49 | this.alignment = alignment; 50 | this.bounds = child.getLayoutBounds(); 51 | this.animateShrinking.set(animateShrinking); 52 | 53 | HPos hPos = alignment.getHpos(); 54 | VPos vPos = alignment.getVpos(); 55 | 56 | // Update the coordinates whenever the root gets resized 57 | bindX(hPos); 58 | bindY(vPos); 59 | 60 | // Register child size 61 | registerBoundsListener(hPos, vPos); 62 | } 63 | 64 | /** 65 | * Instantiates an {@link AnimatedLayout} node. 66 | * @param child the node to wrap, whose layout should be animated 67 | * @param root root to rely relayouts on 68 | * @param alignment position of the node relative to its root 69 | */ 70 | public AnimatedLayout(Node child, Region root, Pos alignment) { 71 | this(child, root, alignment, false); 72 | } 73 | 74 | /** 75 | * @return alignment of the child relative to the root 76 | */ 77 | public Pos getAlignment() { 78 | return alignment; 79 | } 80 | 81 | /** 82 | * @return whether the animation should be played when the root is shrunk 83 | */ 84 | public BooleanProperty animateShrinkingProperty() { 85 | return animateShrinking; 86 | } 87 | 88 | /** 89 | * @return whether the animation should be played when the root is shrunk. Does not affect centered alignments 90 | */ 91 | public boolean isAnimateShrinking() { 92 | return animateShrinking.get(); 93 | } 94 | 95 | /** 96 | * Enables or disables shrinking animation. 97 | * @param animateShrinking whether the animation should be played when the root is shrunk 98 | */ 99 | public void setAnimateShrinking(boolean animateShrinking) { 100 | this.animateShrinking.set(animateShrinking); 101 | } 102 | 103 | /** 104 | * Whenever the size of the node changes, it gets saved to a local variable 105 | * @param hPos horizontal position of the alignment 106 | * @param vPos vertical position of the alignment 107 | */ 108 | private void registerBoundsListener(HPos hPos, VPos vPos) { 109 | child.layoutBoundsProperty().addListener((o, oldValue, newValue) -> { 110 | if (oldValue != newValue || newValue != bounds) { 111 | bounds = newValue; 112 | 113 | // Don't animate if this is the first update 114 | boolean wasEmpty = oldValue.getWidth() == 0 && oldValue.getHeight() == 0; 115 | if (wasEmpty) { 116 | pause(); 117 | } 118 | 119 | // Update coordinates 120 | if (requiresBinding(hPos)) { 121 | updateX(isCenter(hPos)); 122 | } 123 | if (requiresBinding(vPos)) { 124 | updateY(isCenter(vPos)); 125 | } 126 | 127 | if (wasEmpty) { 128 | resume(); 129 | } 130 | } 131 | }); 132 | } 133 | 134 | /** 135 | * Updates the layout X coordinate of the node: either to the end of the root or to its center. 136 | * @param center whether the node should be centered to the root 137 | */ 138 | private void updateX(boolean center) { 139 | if (bounds == null) { 140 | return; 141 | } 142 | 143 | double x = root.getPrefWidth() - bounds.getWidth(); 144 | child.setLayoutX(center ? x / 2 : x); 145 | } 146 | 147 | /** 148 | * Updates the layout Y coordinate of the node: either to the end of the root or to its center. 149 | * @param center whether the node should be centered to the root 150 | */ 151 | private void updateY(boolean center) { 152 | if (bounds == null) { 153 | return; 154 | } 155 | 156 | double y = root.getPrefHeight() - bounds.getHeight(); 157 | child.setLayoutY(center ? y / 2 : y); 158 | } 159 | 160 | /** 161 | * Updates the layout X coordinate of the node whenever the width of the root changes. 162 | * @param hPos horizontal position of the alignment 163 | */ 164 | private void bindX(HPos hPos) { 165 | if (requiresBinding(hPos)) { 166 | root.prefWidthProperty().addListener((observable, oldValue, newValue) -> { 167 | boolean isShrunk = !isAnimateShrinking() && (double) newValue < (double) oldValue && !isCenter(hPos); 168 | 169 | if (isShrunk) { 170 | pause(); 171 | } 172 | 173 | updateX(isCenter(hPos)); 174 | 175 | if (isShrunk) { 176 | resume(); 177 | } 178 | }); 179 | } 180 | } 181 | 182 | /** 183 | * Updates the layout Y coordinate of the node whenever the height of the root changes. 184 | * @param vPos vertical position of the alignment 185 | */ 186 | private void bindY(VPos vPos) { 187 | if (requiresBinding(vPos)) { 188 | boolean center = vPos == VPos.CENTER; 189 | root.prefHeightProperty().addListener((observable, oldValue, newValue) -> { 190 | boolean isShrunk = !isAnimateShrinking() && (double) newValue < (double) oldValue && !isCenter(vPos); 191 | if (isShrunk) { 192 | pause(); 193 | } 194 | 195 | updateY(isCenter(vPos)); 196 | 197 | if (isShrunk) { 198 | resume(); 199 | } 200 | }); 201 | } 202 | } 203 | 204 | private boolean requiresBinding(HPos hPos) { 205 | return child != null && root != null && hPos != HPos.LEFT; 206 | } 207 | 208 | private boolean requiresBinding(VPos vPos) { 209 | return child != null && root != null && vPos != VPos.TOP; 210 | } 211 | 212 | private boolean isCenter(HPos hPos) { 213 | return hPos == HPos.CENTER; 214 | } 215 | 216 | private boolean isCenter(VPos vPos) { 217 | return vPos == VPos.CENTER; 218 | } 219 | } 220 | -------------------------------------------------------------------------------- /src/main/java/eu/iamgio/animated/binding/presets/AnimatedOpacity.java: -------------------------------------------------------------------------------- 1 | package eu.iamgio.animated.binding.presets; 2 | 3 | import eu.iamgio.animated.binding.property.animation.OnDemandAnimationProperty; 4 | import eu.iamgio.animated.binding.property.wrapper.PropertyWrapper; 5 | import javafx.scene.Node; 6 | 7 | /** 8 | * Property that animates its child's opacity. 9 | */ 10 | public class AnimatedOpacity extends OnDemandAnimationProperty { 11 | 12 | public AnimatedOpacity() { 13 | super(node -> PropertyWrapper.of(node.opacityProperty())); 14 | } 15 | 16 | public AnimatedOpacity(Node child) { 17 | this(); 18 | targetNodeProperty().set(child); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/main/java/eu/iamgio/animated/binding/presets/AnimatedPrefSize.java: -------------------------------------------------------------------------------- 1 | package eu.iamgio.animated.binding.presets; 2 | 3 | import eu.iamgio.animated.binding.property.animation.OnDemandAnimationPropertyGroup; 4 | import eu.iamgio.animated.binding.property.wrapper.PropertyWrapper; 5 | import javafx.scene.layout.Region; 6 | 7 | import java.util.Arrays; 8 | 9 | /** 10 | * Property that animates its child's preferred size. 11 | */ 12 | public class AnimatedPrefSize extends OnDemandAnimationPropertyGroup { 13 | 14 | public AnimatedPrefSize() { 15 | super(Arrays.asList( 16 | node -> PropertyWrapper.of(node.prefWidthProperty()), 17 | node -> PropertyWrapper.of(node.prefHeightProperty()) 18 | )); 19 | } 20 | 21 | public AnimatedPrefSize(Region child) { 22 | this(); 23 | targetNodeProperty().set(child); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/main/java/eu/iamgio/animated/binding/presets/AnimatedRotation.java: -------------------------------------------------------------------------------- 1 | package eu.iamgio.animated.binding.presets; 2 | 3 | import eu.iamgio.animated.binding.property.animation.OnDemandAnimationProperty; 4 | import eu.iamgio.animated.binding.property.wrapper.PropertyWrapper; 5 | import javafx.scene.Node; 6 | 7 | /** 8 | * Property that animates its child's rotation. 9 | */ 10 | public class AnimatedRotation extends OnDemandAnimationProperty { 11 | 12 | public AnimatedRotation() { 13 | super(node -> PropertyWrapper.of(node.opacityProperty())); 14 | } 15 | 16 | public AnimatedRotation(Node child) { 17 | this(); 18 | targetNodeProperty().set(child); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/main/java/eu/iamgio/animated/binding/presets/AnimatedScale.java: -------------------------------------------------------------------------------- 1 | package eu.iamgio.animated.binding.presets; 2 | 3 | import eu.iamgio.animated.binding.property.animation.OnDemandAnimationPropertyGroup; 4 | import eu.iamgio.animated.binding.property.wrapper.PropertyWrapper; 5 | import javafx.scene.Node; 6 | import javafx.scene.layout.Region; 7 | 8 | import java.util.Arrays; 9 | 10 | /** 11 | * Property that animates its child's scale X/Y. 12 | */ 13 | public class AnimatedScale extends OnDemandAnimationPropertyGroup { 14 | 15 | public AnimatedScale() { 16 | super(Arrays.asList( 17 | node -> PropertyWrapper.of(node.scaleXProperty()), 18 | node -> PropertyWrapper.of(node.scaleYProperty()) 19 | )); 20 | } 21 | 22 | public AnimatedScale(Region child) { 23 | this(); 24 | targetNodeProperty().set(child); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/main/java/eu/iamgio/animated/binding/presets/AnimatedTranslatePosition.java: -------------------------------------------------------------------------------- 1 | package eu.iamgio.animated.binding.presets; 2 | 3 | import eu.iamgio.animated.binding.property.animation.OnDemandAnimationPropertyGroup; 4 | import eu.iamgio.animated.binding.property.wrapper.PropertyWrapper; 5 | import javafx.scene.Node; 6 | import javafx.scene.layout.Region; 7 | 8 | import java.util.Arrays; 9 | 10 | /** 11 | * Property that animates its child's translate X/Y coordinates. 12 | */ 13 | public class AnimatedTranslatePosition extends OnDemandAnimationPropertyGroup { 14 | 15 | public AnimatedTranslatePosition() { 16 | super(Arrays.asList( 17 | node -> PropertyWrapper.of(node.translateXProperty()), 18 | node -> PropertyWrapper.of(node.translateYProperty()) 19 | )); 20 | } 21 | 22 | public AnimatedTranslatePosition(Region child) { 23 | this(); 24 | targetNodeProperty().set(child); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/main/java/eu/iamgio/animated/binding/property/animation/AnimationProperty.java: -------------------------------------------------------------------------------- 1 | package eu.iamgio.animated.binding.property.animation; 2 | 3 | import eu.iamgio.animated.binding.Animated; 4 | import eu.iamgio.animated.binding.AnimationSettings; 5 | import eu.iamgio.animated.binding.CustomizableAnimation; 6 | import eu.iamgio.animated.binding.event.AnimationEvent; 7 | import eu.iamgio.animated.binding.event.ListenableAnimation; 8 | import eu.iamgio.animated.binding.property.wrapper.PropertyWrapper; 9 | import eu.iamgio.animated.common.Pausable; 10 | import javafx.beans.property.*; 11 | import javafx.event.EventHandler; 12 | import javafx.scene.Node; 13 | 14 | import java.util.function.Function; 15 | 16 | /** 17 | * An animation property wraps a JavaFX property and describes its behavior when it is affected by a change. 18 | * The property has to be wrapped inside a {@link PropertyWrapper}. 19 | * @param type of the wrapped value 20 | * @author Giorgio Garofalo 21 | */ 22 | public abstract class AnimationProperty implements CustomizableAnimation>, ListenableAnimation, Pausable { 23 | 24 | // The target property 25 | private final PropertyWrapper property; 26 | 27 | // Whether the property should be animated 28 | private final BooleanProperty paused = new SimpleBooleanProperty(false); 29 | 30 | // Animation settings 31 | private final ObjectProperty settings; 32 | 33 | // Event handlers 34 | private final ObjectProperty> onAnimationStarted = new SimpleObjectProperty<>(); 35 | private final ObjectProperty> onAnimationEnded = new SimpleObjectProperty<>(); 36 | 37 | /** 38 | * Instantiates an implicitly animated property 39 | * @param property target property 40 | * @param settings animation settings 41 | */ 42 | public AnimationProperty(PropertyWrapper property, AnimationSettings settings) { 43 | this.property = property; 44 | this.settings = new SimpleObjectProperty<>(settings); 45 | } 46 | 47 | /** 48 | * Instantiates an implicitly animated property with default settings. 49 | * @param property target property 50 | */ 51 | public AnimationProperty(PropertyWrapper property) { 52 | this(property, new AnimationSettings()); 53 | } 54 | 55 | /** 56 | * @return the current animation settings 57 | */ 58 | public ObjectProperty settingsProperty() { 59 | return this.settings; 60 | } 61 | 62 | /** 63 | * @return the current animation settings 64 | */ 65 | public AnimationSettings getSettings() { 66 | return this.settings.get(); 67 | } 68 | 69 | /** 70 | * {@inheritDoc} 71 | */ 72 | @Override 73 | public void setSettings(AnimationSettings settings) { 74 | this.settings.set(settings); 75 | } 76 | 77 | /** 78 | * {@inheritDoc} 79 | */ 80 | @Override 81 | public AnimationProperty custom(Function settings) { 82 | return withSettings(settings.apply(getSettings())); 83 | } 84 | 85 | /** 86 | * Registers the listener 87 | * @param target nullable target {@link Node}. If present, the animation is not played when it is not in scene. 88 | */ 89 | public abstract void register(Node target); 90 | 91 | /** 92 | * Registers the listener 93 | */ 94 | public void register() { 95 | register(null); 96 | } 97 | 98 | /** 99 | * Attaches this property to an {@link Animated} node. 100 | * @param animated animated node to link this property to 101 | */ 102 | public abstract void attachTo(Animated animated); 103 | 104 | /** 105 | * Adds a binding to a target property: when the value of the wrapped property changes, 106 | * the target property is updated too, based on a mapper function. 107 | * The following example binds a label text to this property, converted to a string: 108 | *
109 |      *     property.addBinding(label.textProperty(), String::valueOf);
110 |      * 
111 | * 112 | * @param targetProperty property to bind 113 | * @param mapper function that takes the value of the wrapped property as input, 114 | * and returns the value the target property should get 115 | * @return this for concatenation 116 | * @param type of the target property 117 | */ 118 | public abstract AnimationProperty addBinding(Property targetProperty, Function mapper); 119 | 120 | /** 121 | * Adds a binding to a target property: when the value of the wrapped property changes, 122 | * the target property is updated too. 123 | * @param targetProperty property to bind 124 | * @return this for concatenation 125 | */ 126 | public AnimationProperty addBinding(Property targetProperty) { 127 | return this.addBinding(targetProperty, Function.identity()); 128 | } 129 | 130 | /** 131 | * @return target property 132 | */ 133 | public PropertyWrapper getProperty() { 134 | return this.property; 135 | } 136 | 137 | /** 138 | * Fluent setter for {@link ListenableAnimation#setOnAnimationStarted(EventHandler)} 139 | * @param handler the action to run when an animation begins 140 | * @return this for concatenation 141 | */ 142 | public AnimationProperty onAnimationStarted(EventHandler handler) { 143 | setOnAnimationStarted(handler); 144 | return this; 145 | } 146 | 147 | /** 148 | * Fluent setter for {@link ListenableAnimation#setOnAnimationEnded(EventHandler)} 149 | * @param handler the action to run when an animation finishes 150 | * @return this for concatenation 151 | */ 152 | public AnimationProperty onAnimationEnded(EventHandler handler) { 153 | setOnAnimationEnded(handler); 154 | return this; 155 | } 156 | 157 | /** 158 | * {@inheritDoc} 159 | */ 160 | @Override 161 | public BooleanProperty pausedProperty() { 162 | return this.paused; 163 | } 164 | 165 | /** 166 | * {@inheritDoc} 167 | */ 168 | @Override 169 | public ObjectProperty> onAnimationStartedProperty() { 170 | return this.onAnimationStarted; 171 | } 172 | 173 | /** 174 | * {@inheritDoc} 175 | */ 176 | @Override 177 | public ObjectProperty> onAnimationEndedProperty() { 178 | return this.onAnimationEnded; 179 | } 180 | 181 | /** 182 | * Copies settings and other attributes (pause status) from this property to another (and overrides existing values). 183 | * @param to property to copy attributes to 184 | */ 185 | void copyAttributesTo(AnimationProperty to) { 186 | to.pausedProperty().bindBidirectional(pausedProperty()); 187 | 188 | to.onAnimationStartedProperty().bindBidirectional(onAnimationStartedProperty()); 189 | to.onAnimationEndedProperty().bindBidirectional(onAnimationEndedProperty()); 190 | 191 | to.withSettings(this.getSettings()); 192 | } 193 | 194 | /** 195 | * Creates an {@link AnimationProperty} that wraps the given {@link ObjectProperty}. 196 | * @see eu.iamgio.animated.binding.property.wrapper.ObjectPropertyWrapper 197 | * @param property JavaFX property to wrap 198 | * @param property type 199 | * @return instance of a new animation property that wraps property. 200 | */ 201 | public static AnimationProperty of(ObjectProperty property) { 202 | return new SimpleAnimationProperty<>(PropertyWrapper.of(property)); 203 | } 204 | 205 | /** 206 | * Creates an {@link AnimationProperty} that wraps the given {@link DoubleProperty}. 207 | * @see eu.iamgio.animated.binding.property.wrapper.DoublePropertyWrapper 208 | * @param property JavaFX property to wrap 209 | * @return instance of a new animation property that wraps property. 210 | */ 211 | public static AnimationProperty of(DoubleProperty property) { 212 | return new SimpleAnimationProperty<>(PropertyWrapper.of(property)); 213 | } 214 | 215 | /** 216 | * Creates an {@link AnimationProperty} that wraps the given {@link IntegerProperty}. 217 | * @see eu.iamgio.animated.binding.property.wrapper.IntegerPropertyWrapper 218 | * @param property JavaFX property to wrap 219 | * @return instance of a new animation property that wraps property. 220 | */ 221 | public static AnimationProperty of(IntegerProperty property) { 222 | return new SimpleAnimationProperty<>(PropertyWrapper.of(property)); 223 | } 224 | } 225 | -------------------------------------------------------------------------------- /src/main/java/eu/iamgio/animated/binding/property/animation/BindableContextNode.java: -------------------------------------------------------------------------------- 1 | package eu.iamgio.animated.binding.property.animation; 2 | 3 | import javafx.beans.property.ObjectProperty; 4 | import javafx.scene.Node; 5 | 6 | /** 7 | * A container for a writable node property. 8 | */ 9 | public interface BindableContextNode { 10 | 11 | /** 12 | * @return the target node for the context 13 | */ 14 | ObjectProperty targetNodeProperty(); 15 | } 16 | -------------------------------------------------------------------------------- /src/main/java/eu/iamgio/animated/binding/property/animation/OnDemandAnimationProperty.java: -------------------------------------------------------------------------------- 1 | package eu.iamgio.animated.binding.property.animation; 2 | 3 | import eu.iamgio.animated.binding.Animated; 4 | import eu.iamgio.animated.binding.property.wrapper.PropertyWrapper; 5 | import javafx.beans.InvalidationListener; 6 | import javafx.beans.property.ObjectProperty; 7 | import javafx.beans.property.Property; 8 | import javafx.beans.property.SimpleObjectProperty; 9 | import javafx.scene.Node; 10 | 11 | import java.util.function.Function; 12 | 13 | /** 14 | * An on-demand animation property is a 'lazy evaluation' of an {@link AnimationProperty}: 15 | * its wrapped property is defined by a {@link Function} that is applied to the child 16 | * of the attached {@link Animated} node only when it changes. 17 | * Optionally, a constant target node can be specified instead of using the bound one from the animated node. 18 | * @param type of the JavaFX node to extract the property from 19 | * @param type of the wrapped value 20 | */ 21 | public class OnDemandAnimationProperty extends AnimationProperty implements BindableContextNode { 22 | 23 | private final Function> propertyRetriever; 24 | private final ObjectProperty targetNode; 25 | 26 | /** 27 | * Instantiates a new on-demand animation property. 28 | * @param propertyRetriever function that generates a wrapped JavaFX property when applied to a node. 29 | */ 30 | public OnDemandAnimationProperty(Function> propertyRetriever) { 31 | super(null); 32 | this.propertyRetriever = propertyRetriever; 33 | this.targetNode = new SimpleObjectProperty<>(); 34 | } 35 | 36 | /** 37 | * {@inheritDoc} 38 | */ 39 | @Override 40 | public ObjectProperty targetNodeProperty() { 41 | return this.targetNode; 42 | } 43 | 44 | /** 45 | * @return a new {@link AnimationProperty} that wraps the output of the function applied to the current target node. 46 | * @throws IllegalStateException if the target node is not set 47 | */ 48 | private AnimationProperty requestProperty() { 49 | if (targetNode.get() == null) { 50 | throw new IllegalStateException("The on-demand property was trying to access its status, " + 51 | "but its target node is not set."); 52 | } 53 | 54 | final AnimationProperty property = new SimpleAnimationProperty<>(propertyRetriever.apply(targetNode.get())); 55 | super.copyAttributesTo(property); 56 | return property; 57 | } 58 | 59 | /** 60 | * {@inheritDoc} 61 | */ 62 | @Override 63 | public void register(Node target) { 64 | final AnimationProperty requested = requestProperty(); 65 | requested.register(target != null ? target : targetNode.get()); 66 | } 67 | 68 | /** 69 | * {@inheritDoc} 70 | * A hook is set up so that the child of the animated node generates 71 | * and registers a new {@link SimpleAnimationProperty} when it changes. 72 | */ 73 | @SuppressWarnings("unchecked") 74 | @Override 75 | public void attachTo(Animated animated) { 76 | if (targetNode.get() == null) { 77 | // Not a beautiful way to achieve this. 78 | // Casting is a workaround and should be handled better in the future. 79 | targetNode.bind((Property) animated.childProperty()); 80 | } 81 | 82 | // Whenever the wrapped child of an animated node changes, 83 | // the animation property is evaluated and registered. 84 | 85 | final InvalidationListener listener = o -> this.register(targetNode.get()); 86 | 87 | // Calling the listener if a child is already present. 88 | if (animated.getChild() != null) { 89 | listener.invalidated(null); 90 | } 91 | 92 | animated.childProperty().addListener(listener); 93 | } 94 | 95 | /** 96 | * {@inheritDoc} 97 | */ 98 | @Override 99 | public AnimationProperty addBinding(Property targetProperty, Function mapper) { 100 | requestProperty().addBinding(targetProperty, mapper); 101 | return this; 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /src/main/java/eu/iamgio/animated/binding/property/animation/OnDemandAnimationPropertyGroup.java: -------------------------------------------------------------------------------- 1 | package eu.iamgio.animated.binding.property.animation; 2 | 3 | import eu.iamgio.animated.binding.Animated; 4 | import eu.iamgio.animated.binding.property.wrapper.PropertyWrapper; 5 | import javafx.beans.property.Property; 6 | import javafx.scene.Node; 7 | 8 | import java.util.List; 9 | import java.util.function.Function; 10 | import java.util.stream.Stream; 11 | 12 | /** 13 | * A group of multiple {@link OnDemandAnimationProperty}. 14 | * @param type of the JavaFX node to extract the properties from 15 | * @param type of the wrapped values 16 | */ 17 | public class OnDemandAnimationPropertyGroup extends OnDemandAnimationProperty { 18 | 19 | private final List>> propertyRetrievers; 20 | 21 | /** 22 | * Instantiates a new group of on-demand animation property. 23 | * @param propertyRetrievers list of function that generate wrapped JavaFX properties when applied to a node. 24 | */ 25 | public OnDemandAnimationPropertyGroup(List>> propertyRetrievers) { 26 | super(null); 27 | this.propertyRetrievers = propertyRetrievers; 28 | } 29 | 30 | private Stream> retrieveOnDemandProperties() { 31 | return this.propertyRetrievers.stream() 32 | .map(OnDemandAnimationProperty::new) 33 | .peek(property -> property.targetNodeProperty().bind(targetNodeProperty())) 34 | .peek(super::copyAttributesTo); 35 | } 36 | 37 | /** 38 | * {@inheritDoc} 39 | * All the sub-properties are registered. 40 | */ 41 | @Override 42 | public void register(Node target) { 43 | this.retrieveOnDemandProperties().forEach(property -> property.register(target)); 44 | } 45 | 46 | /** 47 | * {@inheritDoc} 48 | * All the sub-properties are applied to the target animated node. 49 | */ 50 | @Override 51 | public void attachTo(Animated animated) { 52 | this.retrieveOnDemandProperties().forEach(property -> property.attachTo(animated)); 53 | } 54 | 55 | /** 56 | * @throws UnsupportedOperationException an on-demand property group cannot be bound 57 | */ 58 | @Override 59 | public AnimationProperty addBinding(Property targetProperty, Function mapper) { 60 | throw new UnsupportedOperationException("An on-demand property group cannot be bound."); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/main/java/eu/iamgio/animated/binding/property/animation/SimpleAnimationProperty.java: -------------------------------------------------------------------------------- 1 | package eu.iamgio.animated.binding.property.animation; 2 | 3 | import eu.iamgio.animated.binding.Animated; 4 | import eu.iamgio.animated.binding.AnimationSettings; 5 | import eu.iamgio.animated.binding.event.AnimationEvent; 6 | import eu.iamgio.animated.binding.property.wrapper.PropertyWrapper; 7 | import javafx.animation.*; 8 | import javafx.beans.property.ObjectProperty; 9 | import javafx.beans.property.Property; 10 | import javafx.event.EventHandler; 11 | import javafx.scene.Node; 12 | 13 | import java.util.function.Function; 14 | 15 | /** 16 | * The base implementation of {@link AnimationProperty} that plays a timeline-based animation 17 | * whenever the value of the wrapped property changes. 18 | * @param type of the wrapped value 19 | * @author Giorgio Garofalo 20 | */ 21 | public class SimpleAnimationProperty extends AnimationProperty { 22 | 23 | // Animation timeline 24 | private final Timeline timeline; 25 | 26 | // Last time an animation frame was played (in millis) 27 | private double lastUpdate; 28 | 29 | // Last value the timeline changed 30 | private T lastValue; 31 | 32 | // Whether the changes should be handled (internally handled) 33 | private boolean handleChanges = false; 34 | 35 | /** 36 | * Instantiates an implicitly animated property 37 | * @param property target property 38 | * @param settings animation settings 39 | */ 40 | public SimpleAnimationProperty(PropertyWrapper property, AnimationSettings settings) { 41 | super(property, settings); 42 | this.timeline = new Timeline(); 43 | 44 | timeline.currentTimeProperty().addListener(o -> { 45 | lastUpdate = timeline.getCurrentTime().toMillis(); 46 | lastValue = property.getValue(); 47 | }); 48 | 49 | timeline.setOnFinished(e -> fireEvent(onAnimationEndedProperty(), new AnimationEvent(false))); 50 | } 51 | 52 | /** 53 | * Instantiates an implicitly animated property 54 | * @param property target property 55 | */ 56 | public SimpleAnimationProperty(PropertyWrapper property) { 57 | this(property, new AnimationSettings()); 58 | } 59 | 60 | /** 61 | * Plays the animation 62 | * @param value new property value 63 | * @param interrupted whether the animation was interrupted before it could finish as expected 64 | */ 65 | private void handleChanges(T value, boolean interrupted) { 66 | // Temporarily stop the timeline in case it is currently running 67 | if (interrupted) { 68 | timeline.stop(); 69 | this.fireEvent(onAnimationEndedProperty(), new AnimationEvent(true)); 70 | } 71 | 72 | final AnimationSettings settings = getSettings(); 73 | Interpolator interpolator = settings.getCurve().toInterpolator(); 74 | 75 | // Set keyframes 76 | timeline.getKeyFrames().setAll( 77 | new KeyFrame(settings.getDuration(), new KeyValue(getProperty().getProperty(), value, interpolator)) 78 | ); 79 | 80 | // Play the animation 81 | timeline.play(); 82 | 83 | this.fireEvent(onAnimationStartedProperty(), new AnimationEvent(interrupted)); 84 | } 85 | 86 | private boolean isRunning() { 87 | return timeline.getStatus() == Animation.Status.RUNNING; 88 | } 89 | 90 | /** 91 | * @return whether the current property change was fired by the animation or by an external source 92 | */ 93 | private boolean isAnimationFrame(T oldPropertyValue, T newPropertyValue) { 94 | return timeline.getCurrentTime().toMillis() == lastUpdate && (newPropertyValue.equals(lastValue) || oldPropertyValue.equals(lastValue)); 95 | } 96 | 97 | /** 98 | * {@inheritDoc} 99 | */ 100 | public void register(Node target) { 101 | getProperty().addListener(((observable, oldValue, newValue) -> { 102 | if (isPaused() || (target != null && target.getScene() == null)) { 103 | return; 104 | } 105 | 106 | boolean running = isRunning(); 107 | if (!running || !isAnimationFrame(oldValue, newValue)) { 108 | if (handleChanges ^= true) { 109 | getProperty().set(oldValue); 110 | handleChanges(newValue, running); 111 | } 112 | } 113 | })); 114 | } 115 | 116 | /** 117 | * {@inheritDoc} 118 | */ 119 | @Override 120 | public void attachTo(Animated animated) { 121 | this.register(animated.getChild()); 122 | } 123 | 124 | /** 125 | * {@inheritDoc} 126 | */ 127 | @Override 128 | public AnimationProperty addBinding(Property targetProperty, Function mapper) { 129 | getProperty().bindMapped(targetProperty, mapper); 130 | return this; 131 | } 132 | 133 | private void fireEvent(ObjectProperty> handlerProperty, AnimationEvent event) { 134 | EventHandler handler = handlerProperty.get(); 135 | if (handler != null) { 136 | handler.handle(event); 137 | } 138 | } 139 | } 140 | -------------------------------------------------------------------------------- /src/main/java/eu/iamgio/animated/binding/property/wrapper/DoublePropertyWrapper.java: -------------------------------------------------------------------------------- 1 | package eu.iamgio.animated.binding.property.wrapper; 2 | 3 | import javafx.beans.property.DoubleProperty; 4 | import javafx.beans.property.Property; 5 | import javafx.beans.value.ChangeListener; 6 | 7 | /** 8 | * Wrapper of a {@link Double} property. 9 | * @author Giorgio Garofalo 10 | */ 11 | public class DoublePropertyWrapper implements PropertyWrapper { 12 | 13 | private final DoubleProperty property; 14 | 15 | /** 16 | * Instantiates a wrapper of a {@link Double} property 17 | * @param property property to wrap 18 | */ 19 | public DoublePropertyWrapper(DoubleProperty property) { 20 | this.property = property; 21 | } 22 | 23 | /** 24 | * {@inheritDoc} 25 | */ 26 | @Override 27 | public Property getProperty() { 28 | return property.asObject(); 29 | } 30 | 31 | /** 32 | * {@inheritDoc} 33 | */ 34 | @Override 35 | public Double getValue() { 36 | return property.doubleValue(); 37 | } 38 | 39 | /** 40 | * {@inheritDoc} 41 | */ 42 | @Override 43 | public void set(Double value) { 44 | property.set(value); 45 | } 46 | 47 | /** 48 | * {@inheritDoc} 49 | */ 50 | @SuppressWarnings("unchecked") 51 | @Override 52 | public void addListener(ChangeListener listener) { 53 | property.addListener((ChangeListener) listener); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/main/java/eu/iamgio/animated/binding/property/wrapper/IntegerPropertyWrapper.java: -------------------------------------------------------------------------------- 1 | package eu.iamgio.animated.binding.property.wrapper; 2 | 3 | import javafx.beans.property.IntegerProperty; 4 | import javafx.beans.property.Property; 5 | import javafx.beans.value.ChangeListener; 6 | 7 | /** 8 | * Wrapper of an {@link Integer} property. 9 | * @author Giorgio Garofalo 10 | */ 11 | public class IntegerPropertyWrapper implements PropertyWrapper { 12 | 13 | private final IntegerProperty property; 14 | 15 | /** 16 | * Instantiates a wrapper of an {@link Integer} property 17 | * @param property property to wrap 18 | */ 19 | public IntegerPropertyWrapper(IntegerProperty property) { 20 | this.property = property; 21 | } 22 | 23 | /** 24 | * {@inheritDoc} 25 | */ 26 | @Override 27 | public Property getProperty() { 28 | return property.asObject(); 29 | } 30 | 31 | /** 32 | * {@inheritDoc} 33 | */ 34 | @Override 35 | public Integer getValue() { 36 | return property.intValue(); 37 | } 38 | 39 | /** 40 | * {@inheritDoc} 41 | */ 42 | @Override 43 | public void set(Integer value) { 44 | property.set(value); 45 | } 46 | 47 | /** 48 | * {@inheritDoc} 49 | */ 50 | @SuppressWarnings("unchecked") 51 | @Override 52 | public void addListener(ChangeListener listener) { 53 | property.addListener((ChangeListener) listener); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/main/java/eu/iamgio/animated/binding/property/wrapper/ObjectPropertyWrapper.java: -------------------------------------------------------------------------------- 1 | package eu.iamgio.animated.binding.property.wrapper; 2 | 3 | import javafx.beans.property.ObjectProperty; 4 | import javafx.beans.property.Property; 5 | import javafx.beans.value.ChangeListener; 6 | 7 | /** 8 | * Wrapper of a non-primitive property. 9 | * @author Giorgio Garofalo 10 | */ 11 | public class ObjectPropertyWrapper implements PropertyWrapper { 12 | 13 | private final ObjectProperty property; 14 | 15 | /** 16 | * Instantiates a wrapper of a non-primitive property 17 | * @param property property to wrap 18 | */ 19 | public ObjectPropertyWrapper(ObjectProperty property) { 20 | this.property = property; 21 | } 22 | 23 | /** 24 | * {@inheritDoc} 25 | */ 26 | @Override 27 | public Property getProperty() { 28 | return property; 29 | } 30 | 31 | /** 32 | * {@inheritDoc} 33 | */ 34 | @Override 35 | public T getValue() { 36 | return property.getValue(); 37 | } 38 | 39 | /** 40 | * {@inheritDoc} 41 | */ 42 | @Override 43 | public void set(T value) { 44 | property.set(value); 45 | } 46 | 47 | /** 48 | * {@inheritDoc} 49 | */ 50 | @Override 51 | public void addListener(ChangeListener listener) { 52 | property.addListener(listener); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/main/java/eu/iamgio/animated/binding/property/wrapper/PropertyWrapper.java: -------------------------------------------------------------------------------- 1 | package eu.iamgio.animated.binding.property.wrapper; 2 | 3 | import javafx.beans.binding.Bindings; 4 | import javafx.beans.property.DoubleProperty; 5 | import javafx.beans.property.IntegerProperty; 6 | import javafx.beans.property.ObjectProperty; 7 | import javafx.beans.property.Property; 8 | import javafx.beans.value.ChangeListener; 9 | 10 | import java.util.function.Function; 11 | 12 | /** 13 | * Wrapper of object and primitive JavaFX properties. 14 | * @param wrapped property type 15 | */ 16 | public interface PropertyWrapper { 17 | 18 | /** 19 | * @return the wrapped JavaFX property 20 | */ 21 | Property getProperty(); 22 | 23 | /** 24 | * @return the wrapped value 25 | */ 26 | T getValue(); 27 | 28 | /** 29 | * Changes the value of the wrapped property. 30 | * @param value new value to set 31 | */ 32 | void set(T value); 33 | 34 | /** 35 | * Registers a listener. 36 | * @param listener listener to register 37 | */ 38 | void addListener(ChangeListener listener); 39 | 40 | /** 41 | * Adds a binding to a target property: when the value of the wrapped property changes, 42 | * the target property is updated too, based on a mapper function. 43 | * @param targetProperty property to bind 44 | * @param mapper function that takes the value of the wrapped property as input, 45 | * and returns the value the target property should get 46 | * @param type of the target property 47 | */ 48 | default void bindMapped(Property targetProperty, Function mapper) { 49 | targetProperty.bind(Bindings.createObjectBinding(() -> mapper.apply(getValue()), getProperty())); 50 | } 51 | 52 | /** 53 | * Creates an {@link ObjectPropertyWrapper} for the given {@link ObjectProperty}. 54 | * @see eu.iamgio.animated.binding.property 55 | * @param property JavaFX property to wrap 56 | * @param property type 57 | * @return an instance of the proper subclass of {@link PropertyWrapper} that wraps property. 58 | */ 59 | static PropertyWrapper of(ObjectProperty property) { 60 | return new ObjectPropertyWrapper<>(property); 61 | } 62 | 63 | /** 64 | * Creates a {@link DoublePropertyWrapper} for the given {@link DoubleProperty}. 65 | * @see eu.iamgio.animated.binding.property 66 | * @param property JavaFX property to wrap 67 | * @return an instance of the proper subclass of {@link PropertyWrapper} that wraps property. 68 | */ 69 | static PropertyWrapper of(DoubleProperty property) { 70 | return new DoublePropertyWrapper(property); 71 | } 72 | 73 | /** 74 | * Creates an {@link IntegerPropertyWrapper} for the given {@link IntegerProperty}. 75 | * @see eu.iamgio.animated.binding.property 76 | * @param property JavaFX property to wrap 77 | * @return an instance of the proper subclass of {@link PropertyWrapper} that wraps property. 78 | */ 79 | static PropertyWrapper of(IntegerProperty property) { 80 | return new IntegerPropertyWrapper(property); 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /src/main/java/eu/iamgio/animated/binding/value/AnimatedDoubleValueLabel.java: -------------------------------------------------------------------------------- 1 | package eu.iamgio.animated.binding.value; 2 | 3 | import javafx.beans.NamedArg; 4 | 5 | /** 6 | * An {@link AnimatedValueLabel} that wraps a double value. 7 | */ 8 | public class AnimatedDoubleValueLabel extends AnimatedValueLabel { 9 | 10 | /** 11 | * Instantiates an {@link AnimatedDoubleValueLabel}. 12 | * @param value initial wrapped value 13 | */ 14 | public AnimatedDoubleValueLabel(@NamedArg("value") double value) { 15 | super(value); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/main/java/eu/iamgio/animated/binding/value/AnimatedIntValueLabel.java: -------------------------------------------------------------------------------- 1 | package eu.iamgio.animated.binding.value; 2 | 3 | import javafx.beans.NamedArg; 4 | 5 | /** 6 | * An {@link AnimatedValueLabel} that wraps an integer value. 7 | */ 8 | public class AnimatedIntValueLabel extends AnimatedValueLabel { 9 | 10 | /** 11 | * Instantiates an {@link AnimatedIntValueLabel}. 12 | * @param value initial wrapped value 13 | */ 14 | public AnimatedIntValueLabel(@NamedArg("value") int value) { 15 | super(value); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/main/java/eu/iamgio/animated/binding/value/AnimatedValue.java: -------------------------------------------------------------------------------- 1 | package eu.iamgio.animated.binding.value; 2 | 3 | import eu.iamgio.animated.binding.AnimationSettings; 4 | import eu.iamgio.animated.binding.CustomizableAnimation; 5 | import eu.iamgio.animated.binding.event.ListenableAnimation; 6 | import eu.iamgio.animated.common.Pausable; 7 | import javafx.beans.property.Property; 8 | import javafx.beans.property.ReadOnlyProperty; 9 | 10 | /** 11 | * An object that wraps a value, and exposes a parallel value that is updated with an animated transition 12 | * whenever the wrapped value is updated via {@link #setValue(Object)}. 13 | * {@link #animationValueProperty()} can be bound to any other external property to provide 14 | * an animated effect without affecting the wrapped value. 15 | */ 16 | public interface AnimatedValue extends CustomizableAnimation>, ListenableAnimation, Pausable { 17 | 18 | /** 19 | * @return the wrapped value 20 | */ 21 | Property valueProperty(); 22 | 23 | /** 24 | * @return the wrapped value. Note that it only depends on the user-supplied value via {@link #setValue(Object)}, 25 | * and not on the current animation frame, which is given by {@link #getAnimationValue()} 26 | */ 27 | T getValue(); 28 | 29 | /** 30 | * Sets a new wrapped value and starts the transition. 31 | * @param value new wrapped value 32 | */ 33 | void setValue(T value); 34 | 35 | /** 36 | * @return the current value of the wrapped property. While {@link #valueProperty()} only wraps the last user-supplied value, 37 | * this method takes into account the latest value produced by the animation, if it is playing. 38 | */ 39 | ReadOnlyProperty animationValueProperty(); 40 | 41 | /** 42 | * @return the current value of the wrapped property. While {@link #getValue()} only returns the last user-supplied value, 43 | * this method takes into account the latest value produced by the animation, if it is playing. 44 | */ 45 | T getAnimationValue(); 46 | 47 | /** 48 | * @return the current animation settings 49 | */ 50 | AnimationSettings getSettings(); 51 | } 52 | -------------------------------------------------------------------------------- /src/main/java/eu/iamgio/animated/binding/value/AnimatedValueImpl.java: -------------------------------------------------------------------------------- 1 | package eu.iamgio.animated.binding.value; 2 | 3 | import eu.iamgio.animated.binding.AnimationSettings; 4 | import eu.iamgio.animated.binding.event.AnimationEvent; 5 | import eu.iamgio.animated.binding.property.animation.AnimationProperty; 6 | import javafx.beans.property.*; 7 | import javafx.event.EventHandler; 8 | import javafx.scene.Node; 9 | 10 | import java.util.function.Function; 11 | 12 | /** 13 | * A value that animates its wrapped content whenever its wrapped value changes via {@link #setValue(Object)}. 14 | * Note that the animated value ({@link #animationValueProperty()}) must be bound to an external property to be effective. 15 | * 16 | * @param type of the wrapped value. 17 | */ 18 | class AnimatedValueImpl implements AnimatedValue { 19 | 20 | private final Property value; 21 | private final AnimationProperty animationProperty; 22 | private final BooleanProperty paused = new SimpleBooleanProperty(false); 23 | 24 | /** 25 | * Instantiates an {@link AnimatedValueImpl}. 26 | * @param value initial wrapped value 27 | */ 28 | AnimatedValueImpl(T value, Node target) { 29 | this.value = new SimpleObjectProperty<>(value); 30 | 31 | // This is a parallel property to the value property, 32 | // which is updated when the other one is updated, but with an animation. 33 | final ObjectProperty animatedValue = new SimpleObjectProperty<>(value); 34 | this.value.addListener((observable, oldValue, newValue) -> animatedValue.set(newValue)); 35 | 36 | this.animationProperty = AnimationProperty.of(animatedValue); 37 | 38 | animationProperty.pausedProperty().bind(this.paused); 39 | animationProperty.register(target); 40 | } 41 | 42 | /** 43 | * {@inheritDoc} 44 | */ 45 | @Override 46 | public Property valueProperty() { 47 | return this.value; 48 | } 49 | 50 | /** 51 | * {@inheritDoc} 52 | */ 53 | @Override 54 | public T getValue() { 55 | return this.value.getValue(); 56 | } 57 | 58 | /** 59 | * {@inheritDoc} 60 | */ 61 | @Override 62 | public void setValue(T value) { 63 | this.value.setValue(value); 64 | } 65 | 66 | /** 67 | * {@inheritDoc} 68 | */ 69 | @Override 70 | public ReadOnlyProperty animationValueProperty() { 71 | return this.animationProperty.getProperty().getProperty(); 72 | } 73 | 74 | /** 75 | * {@inheritDoc} 76 | */ 77 | @Override 78 | public T getAnimationValue() { 79 | return this.animationProperty.getProperty().getValue(); 80 | } 81 | 82 | /** 83 | * {@inheritDoc} 84 | */ 85 | @Override 86 | public AnimationSettings getSettings() { 87 | return this.animationProperty.getSettings(); 88 | } 89 | 90 | /** 91 | * {@inheritDoc} 92 | */ 93 | @Override 94 | public void setSettings(AnimationSettings settings) { 95 | this.animationProperty.setSettings(settings); 96 | } 97 | 98 | /** 99 | * {@inheritDoc} 100 | */ 101 | @Override 102 | public AnimatedValue custom(Function settings) { 103 | this.animationProperty.custom(settings); 104 | return this; 105 | } 106 | 107 | /** 108 | * {@inheritDoc} 109 | */ 110 | @Override 111 | public BooleanProperty pausedProperty() { 112 | return this.paused; 113 | } 114 | 115 | /** 116 | * {@inheritDoc} 117 | */ 118 | @Override 119 | public ObjectProperty> onAnimationStartedProperty() { 120 | return this.animationProperty.onAnimationStartedProperty(); 121 | } 122 | 123 | /** 124 | * {@inheritDoc} 125 | */ 126 | @Override 127 | public ObjectProperty> onAnimationEndedProperty() { 128 | return this.animationProperty.onAnimationEndedProperty(); 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /src/main/java/eu/iamgio/animated/binding/value/AnimatedValueLabel.java: -------------------------------------------------------------------------------- 1 | package eu.iamgio.animated.binding.value; 2 | 3 | import eu.iamgio.animated.binding.AnimationSettings; 4 | import eu.iamgio.animated.binding.event.AnimationEvent; 5 | import javafx.beans.NamedArg; 6 | import javafx.beans.binding.Bindings; 7 | import javafx.beans.property.*; 8 | import javafx.event.EventHandler; 9 | import javafx.scene.control.Label; 10 | 11 | import java.util.Objects; 12 | import java.util.function.Function; 13 | 14 | /** 15 | * A {@link Label} that animates its text content whenever its wrapped value changes via {@link #setValue(Object)}. 16 | * 17 | * @param type of the wrapped value. 18 | */ 19 | public class AnimatedValueLabel extends Label implements AnimatedValue { 20 | 21 | private final AnimatedValue value; 22 | private final Property> textMapper; 23 | 24 | /** 25 | * Instantiates an {@link AnimatedValueLabel}. 26 | * @param value initial wrapped value 27 | */ 28 | public AnimatedValueLabel(@NamedArg("value") T value) { 29 | this.value = new AnimatedValueImpl<>(value, this); 30 | this.textMapper = new SimpleObjectProperty<>(Objects::toString); 31 | 32 | this.textProperty().bind( 33 | Bindings.createStringBinding( 34 | () -> this.textMapper.getValue().apply(getAnimationValue()), 35 | animationValueProperty(), this.textMapper 36 | ) 37 | ); 38 | } 39 | 40 | /** 41 | * {@inheritDoc} 42 | */ 43 | @Override 44 | public void setSettings(AnimationSettings settings) { 45 | this.value.setSettings(settings); 46 | } 47 | 48 | /** 49 | * {@inheritDoc} 50 | */ 51 | @Override 52 | public AnimatedValueLabel custom(Function settings) { 53 | this.value.custom(settings); 54 | return this; 55 | } 56 | 57 | /** 58 | * {@inheritDoc} 59 | */ 60 | @Override 61 | public Property valueProperty() { 62 | return this.value.valueProperty(); 63 | } 64 | 65 | /** 66 | * {@inheritDoc} 67 | */ 68 | @Override 69 | public T getValue() { 70 | return this.value.getValue(); 71 | } 72 | 73 | /** 74 | * {@inheritDoc} 75 | */ 76 | @Override 77 | public void setValue(T value) { 78 | this.value.setValue(value); 79 | } 80 | 81 | /** 82 | * {@inheritDoc} 83 | */ 84 | @Override 85 | public ReadOnlyProperty animationValueProperty() { 86 | return this.value.animationValueProperty(); 87 | } 88 | 89 | /** 90 | * {@inheritDoc} 91 | */ 92 | @Override 93 | public T getAnimationValue() { 94 | return this.value.getAnimationValue(); 95 | } 96 | 97 | /** 98 | * {@inheritDoc} 99 | */ 100 | @Override 101 | public AnimationSettings getSettings() { 102 | return this.value.getSettings(); 103 | } 104 | 105 | /** 106 | * {@inheritDoc} 107 | */ 108 | @Override 109 | public BooleanProperty pausedProperty() { 110 | return this.value.pausedProperty(); 111 | } 112 | 113 | /** 114 | * {@inheritDoc} 115 | */ 116 | @Override 117 | public ObjectProperty> onAnimationStartedProperty() { 118 | return this.value.onAnimationStartedProperty(); 119 | } 120 | 121 | /** 122 | * {@inheritDoc} 123 | */ 124 | @Override 125 | public ObjectProperty> onAnimationEndedProperty() { 126 | return this.value.onAnimationEndedProperty(); 127 | } 128 | 129 | /** 130 | * @return the function that maps the current wrapped value to the displayed text. 131 | * It defaults to {@link Objects#toString(Object)} 132 | */ 133 | public Function getTextMapper() { 134 | return this.textMapper.getValue(); 135 | } 136 | 137 | /** 138 | * Sets the function that maps the current wrapped value to the displayed text. 139 | * @param textMapper new text mapper 140 | */ 141 | public void setTextMapper(Function textMapper) { 142 | this.textMapper.setValue(textMapper); 143 | } 144 | } 145 | -------------------------------------------------------------------------------- /src/main/java/eu/iamgio/animated/common/Curve.java: -------------------------------------------------------------------------------- 1 | package eu.iamgio.animated.common; 2 | 3 | import eu.iamgio.animated.binding.AnimationSettings; 4 | import javafx.animation.Interpolator; 5 | 6 | import java.util.function.Function; 7 | 8 | import static java.lang.Math.*; 9 | 10 | /** 11 | * A group of curves that affect the interpolation of an animation. 12 | * @author Giorgio Garofalo 13 | * @see AnimationSettings#withCurve(Curve) 14 | */ 15 | public enum Curve { 16 | 17 | /** 18 | * A curve that keeps the same value throughout its execution. 19 | */ 20 | LINEAR(t -> t), 21 | 22 | 23 | // 24 | // EASE OUT CURVES 25 | // 26 | 27 | 28 | /** 29 | * @see on easings.net 30 | */ 31 | EASE_OUT(t -> 1 - (1 - t) * (1 - t) * (1 - t)), 32 | 33 | /** 34 | * @see on easings.net 35 | */ 36 | EASE_OUT_SINE(t -> sin((t * PI) / 2)), 37 | 38 | /** 39 | * @see on easings.net 40 | */ 41 | EASE_OUT_EXPO(t -> t == 1 ? 1 : 1 - pow(2, -10 * t)), 42 | 43 | /** 44 | * @see on easings.net 45 | */ 46 | EASE_OUT_BACK(t -> { 47 | double c1 = 1.70158; 48 | double c3 = c1 + 1; 49 | 50 | return 1 + c3 * pow(t - 1, 3) + c1 * pow(t - 1, 2); 51 | }), 52 | 53 | /** 54 | * @see on easings.net 55 | */ 56 | EASE_OUT_BOUNCE(t -> { 57 | double n = 7.5625; 58 | double d = 2.75; 59 | if (t < 1 / d) { 60 | return n * t * t; 61 | } else if (t < 2 / d) { 62 | return n * (t -= 1.5 / d) * t + 0.75; 63 | } else if (t < 2.5 / d) { 64 | return n * (t -= 2.25 / d) * t + 0.9375; 65 | } else { 66 | return n * (t -= 2.625 / d) * t + 0.984375; 67 | } 68 | }), 69 | 70 | /** 71 | * @see on easings.net 72 | */ 73 | EASE_OUT_ELASTIC(t -> { 74 | double c = (2 * Math.PI) / 3; 75 | 76 | return t == 0 77 | ? 0 78 | : t == 1 79 | ? 1 80 | : pow(2, -10 * t) * sin((t * 10 - 0.75) * c) + 1; 81 | }), 82 | 83 | 84 | // 85 | // EASE IN CURVES 86 | // 87 | 88 | 89 | /** 90 | * @see on easings.net 91 | */ 92 | EASE_IN(t -> t * t * t), 93 | 94 | /** 95 | * @see on easings.net 96 | */ 97 | EASE_IN_SINE(t -> 1 - cos((t * PI) / 2)), 98 | 99 | /** 100 | * @see on easings.net 101 | */ 102 | EASE_IN_EXPO(t -> t == 0 ? 0 : pow(2, 10 * t - 10)), 103 | 104 | /** 105 | * @see on easings.net 106 | */ 107 | EASE_IN_BACK(t -> { 108 | double c1 = 1.70158; 109 | double c3 = c1 + 1; 110 | 111 | return c3 * t * t * t - c1 * t * t; 112 | }), 113 | 114 | /** 115 | * @see on easings.net 116 | */ 117 | EASE_IN_BOUNCE(t -> 1 - EASE_OUT_BOUNCE.curve.apply(1 - t)), 118 | 119 | /** 120 | * @see on easings.net 121 | */ 122 | EASE_IN_ELASTIC(t -> { 123 | double c = (2 * PI) / 3; 124 | 125 | return t == 0 126 | ? 0 127 | : t == 1 128 | ? 1 129 | : -pow(2, 10 * t - 10) * sin((t * 10 - 10.75) * c); 130 | }), 131 | 132 | 133 | // 134 | // EASE IN OUT CURVES 135 | // 136 | 137 | 138 | /** 139 | * @see on easings.net 140 | */ 141 | EASE_IN_OUT(t -> t < 0.5 ? 4 * t * t * t : 1 - pow(-2 * t + 2, 3) / 2), 142 | 143 | /** 144 | * @see on easings.net 145 | */ 146 | EASE_IN_OUT_SINE(t -> -(cos(PI * t) - 1) / 2), 147 | 148 | /** 149 | * @see on easings.net 150 | */ 151 | EASE_IN_OUT_EXPO(t -> t == 0 ? 0 : t == 1 ? 1 : t < 0.5 ? pow(2, 20 * t - 10) / 2 : (2 - pow(2, -20 * t + 10)) / 2), 152 | 153 | /** 154 | * @see on easings.net 155 | */ 156 | EASE_IN_OUT_BACK(t -> { 157 | double c1 = 1.70158; 158 | double c2 = c1 * 1.525; 159 | 160 | return t < 0.5 ? (pow(2 * t, 2) * ((c2 + 1) * 2 * t - c2)) / 2 : (pow(2 * t - 2, 161 | 2) * ((c2 + 1) * (t * 2 - 2) + c2) + 2) / 2; 162 | }), 163 | 164 | /** 165 | * @see on easings.net 166 | */ 167 | EASE_IN_OUT_BOUNCE(t -> t < 0.5 168 | ? (1 - EASE_OUT_BOUNCE.curve.apply(1 - 2 * t)) / 2 169 | : (1 + EASE_IN_BOUNCE.curve.apply(2 * t - 1)) / 2), 170 | 171 | /** 172 | * @see on easings.net 173 | */ 174 | EASE_IN_OUT_ELASTIC(t -> { 175 | double c = (2 * PI) / 4.5; 176 | double sin = sin((20 * t - 11.125) * c); 177 | 178 | return t == 0 179 | ? 0 180 | : t == 1 181 | ? 1 182 | : t < 0.5 183 | ? -(pow(2, 20 * t - 10) * sin) / 2 184 | : (pow(2, -20 * t + 10) * sin) / 2 + 1; 185 | }); 186 | 187 | 188 | // 189 | // END OF CURVES 190 | // 191 | 192 | private final Function curve; 193 | 194 | Curve(Function curve) { 195 | this.curve = curve; 196 | } 197 | 198 | /** 199 | * @return the function that describes this curve 200 | */ 201 | public Function getCurveFunction() { 202 | return curve; 203 | } 204 | 205 | /** 206 | * @return this curve to a JavaFX timeline interpolator 207 | */ 208 | public Interpolator toInterpolator() { 209 | return new Interpolator() { 210 | @Override 211 | protected double curve(double t) { 212 | return curve.apply(t); 213 | } 214 | }; 215 | } 216 | } 217 | -------------------------------------------------------------------------------- /src/main/java/eu/iamgio/animated/common/Pausable.java: -------------------------------------------------------------------------------- 1 | package eu.iamgio.animated.common; 2 | 3 | import javafx.beans.property.BooleanProperty; 4 | 5 | /** 6 | * A {@link Pausable} object exposes a boolean value that represents whether animations on that object should be skipped, 7 | * meaning the animated implementation should be ignored and the standard one should be used. 8 | * @author Giorgio Garofalo 9 | */ 10 | public interface Pausable { 11 | 12 | /** 13 | * @return whether animations should be skipped 14 | */ 15 | BooleanProperty pausedProperty(); 16 | 17 | /** 18 | * @return whether animations should be skipped 19 | */ 20 | default boolean isPaused() { 21 | return pausedProperty().get(); 22 | } 23 | 24 | /** 25 | * Prevents any animation from playing until {@link #resume()} is called 26 | * (only effective if {@link #isPaused()} is false). 27 | */ 28 | default void pause() { 29 | pausedProperty().set(true); 30 | } 31 | 32 | /** 33 | * Lets animations play (only effective if {@link #isPaused()} is true). 34 | */ 35 | default void resume() { 36 | pausedProperty().set(false); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/main/java/eu/iamgio/animated/transition/AnimatedLabel.java: -------------------------------------------------------------------------------- 1 | package eu.iamgio.animated.transition; 2 | 3 | import animatefx.animation.AnimationFX; 4 | import eu.iamgio.animated.common.Pausable; 5 | import javafx.beans.property.*; 6 | import javafx.scene.Parent; 7 | import javafx.scene.control.Label; 8 | 9 | /** 10 | * A node that wraps a text string that, whenever changed, plays an animation between the old and current state. 11 | * 12 | * @author Giorgio Garofalo 13 | */ 14 | public class AnimatedLabel extends Parent implements Pausable, EntranceAndExitAnimationCompatible { 15 | 16 | private final ObjectProperty in; 17 | private final ObjectProperty out; 18 | 19 | private final StringProperty textProperty = new SimpleStringProperty(); 20 | 21 | private final ObjectProperty labelFactory = new SimpleObjectProperty<>(LabelFactory.DEFAULT); 22 | private final ObjectProperty