|
@@ -1,28 +1,15 @@
|
|
|
-/*
|
|
|
- * Copyright (C) 2021 Parisi Alessandro
|
|
|
- * This file is part of MaterialFX (https://github.com/palexdev/MaterialFX).
|
|
|
- *
|
|
|
- * MaterialFX is free software: you can redistribute it and/or modify
|
|
|
- * it under the terms of the GNU General Public License as published by
|
|
|
- * the Free Software Foundation, either version 3 of the License, or
|
|
|
- * (at your option) any later version.
|
|
|
- *
|
|
|
- * MaterialFX is distributed in the hope that it will be useful,
|
|
|
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
- * GNU General Public License for more details.
|
|
|
- *
|
|
|
- * You should have received a copy of the GNU General Public License
|
|
|
- * along with MaterialFX. If not, see <http://www.gnu.org/licenses/>.
|
|
|
- */
|
|
|
-
|
|
|
package io.github.palexdev.materialfx.skins;
|
|
|
|
|
|
import io.github.palexdev.materialfx.controls.MFXProgressBar;
|
|
|
import javafx.animation.*;
|
|
|
+import javafx.scene.Group;
|
|
|
import javafx.scene.control.SkinBase;
|
|
|
import javafx.scene.layout.StackPane;
|
|
|
+import javafx.scene.paint.Color;
|
|
|
import javafx.scene.shape.Rectangle;
|
|
|
+import javafx.scene.shape.StrokeLineCap;
|
|
|
+import javafx.scene.shape.StrokeLineJoin;
|
|
|
+import javafx.scene.shape.StrokeType;
|
|
|
import javafx.util.Duration;
|
|
|
|
|
|
/**
|
|
@@ -32,13 +19,12 @@ public class MFXProgressBarSkin extends SkinBase<MFXProgressBar> {
|
|
|
//================================================================================
|
|
|
// Properties
|
|
|
//================================================================================
|
|
|
- private final StackPane track;
|
|
|
- private final StackPane bar1;
|
|
|
- private final StackPane bar2;
|
|
|
+ private final StackPane container;
|
|
|
+ private final Rectangle track;
|
|
|
+ private final Rectangle bar1;
|
|
|
+ private final Rectangle bar2;
|
|
|
|
|
|
- private boolean wasIndeterminate = false;
|
|
|
- private double barWidth = 0;
|
|
|
- private ParallelTransition indeterminateTransition;
|
|
|
+ private ParallelTransition indeterminateAnimation;
|
|
|
|
|
|
//================================================================================
|
|
|
// Constructors
|
|
@@ -46,146 +32,179 @@ public class MFXProgressBarSkin extends SkinBase<MFXProgressBar> {
|
|
|
public MFXProgressBarSkin(MFXProgressBar progressBar) {
|
|
|
super(progressBar);
|
|
|
|
|
|
- track = new StackPane();
|
|
|
- track.getStyleClass().add("track");
|
|
|
+ track = buildRectangle("track");
|
|
|
+ track.heightProperty().bind(progressBar.heightProperty());
|
|
|
+ track.widthProperty().bind(progressBar.widthProperty());
|
|
|
+
|
|
|
+ bar1 = buildRectangle("bar1");
|
|
|
+ bar1.heightProperty().bind(progressBar.heightProperty());
|
|
|
+
|
|
|
+ bar2 = buildRectangle("bar2");
|
|
|
+ bar2.heightProperty().bind(progressBar.heightProperty());
|
|
|
+ bar2.visibleProperty().bind(progressBar.indeterminateProperty());
|
|
|
+
|
|
|
+ Rectangle clip = new Rectangle();
|
|
|
+ clip.heightProperty().bind(progressBar.heightProperty());
|
|
|
+ clip.widthProperty().bind(progressBar.widthProperty());
|
|
|
+ clip.arcHeightProperty().bind(track.arcHeightProperty());
|
|
|
+ clip.arcWidthProperty().bind(track.arcWidthProperty());
|
|
|
+
|
|
|
+ Group group = new Group(track, bar1, bar2);
|
|
|
+ group.setClip(clip);
|
|
|
+ group.setManaged(false);
|
|
|
|
|
|
- bar1 = new StackPane();
|
|
|
- bar1.getStyleClass().add("bar");
|
|
|
- bar2 = new StackPane();
|
|
|
- bar2.getStyleClass().add("bar");
|
|
|
+ container = new StackPane(group);
|
|
|
+ getChildren().setAll(container);
|
|
|
|
|
|
setListeners();
|
|
|
- getChildren().setAll(track, bar1, bar2);
|
|
|
}
|
|
|
|
|
|
//================================================================================
|
|
|
// Methods
|
|
|
//================================================================================
|
|
|
- private Rectangle buildClip() {
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Adds listeners for: progress, width, visible, parent,scene and animation speed properties.
|
|
|
+ */
|
|
|
+ private void setListeners() {
|
|
|
MFXProgressBar progressBar = getSkinnable();
|
|
|
|
|
|
- Rectangle clip = new Rectangle();
|
|
|
- clip.widthProperty().bind(progressBar.widthProperty());
|
|
|
- clip.heightProperty().bind(progressBar.heightProperty());
|
|
|
- return clip;
|
|
|
+ progressBar.progressProperty().addListener((observable, oldValue, newValue) -> updateBars());
|
|
|
+ progressBar.widthProperty().addListener((observable, oldValue, newValue) -> {
|
|
|
+ resetBars();
|
|
|
+ updateBars();
|
|
|
+ });
|
|
|
+ progressBar.visibleProperty().addListener((observable, oldValue, newValue) -> {
|
|
|
+ resetBars();
|
|
|
+ updateBars();
|
|
|
+ });
|
|
|
+ progressBar.parentProperty().addListener((observable, oldValue, newValue) -> {
|
|
|
+ resetBars();
|
|
|
+ updateBars();
|
|
|
+ });
|
|
|
+ progressBar.sceneProperty().addListener((observable, oldValue, newValue) -> {
|
|
|
+ resetBars();
|
|
|
+ updateBars();
|
|
|
+ });
|
|
|
+ progressBar.animationSpeedProperty().addListener((observable, oldValue, newValue) -> {
|
|
|
+ resetBars();
|
|
|
+ updateBars();
|
|
|
+ });
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
- * Adds listeners for: width, visible, parent and scene properties.
|
|
|
+ * Responsible for updating the progress bar state.
|
|
|
+ * <p></p>
|
|
|
+ * If it is indeterminate calls {@link #playIndeterminateAnimation()}, otherwise calls
|
|
|
+ * {@link #resetBars()} and {@link #updateProgress()}.
|
|
|
*/
|
|
|
- private void setListeners() {
|
|
|
+ protected void updateBars() {
|
|
|
MFXProgressBar progressBar = getSkinnable();
|
|
|
|
|
|
- progressBar.progressProperty().addListener(invalidated -> {
|
|
|
- progressBar.requestLayout();
|
|
|
+ if (progressBar.isIndeterminate()) {
|
|
|
+ playIndeterminateAnimation();
|
|
|
+ } else {
|
|
|
+ resetBars();
|
|
|
updateProgress();
|
|
|
- });
|
|
|
- progressBar.widthProperty().addListener((observable, oldValue, newValue) -> updateProgress());
|
|
|
- progressBar.visibleProperty().addListener((observable, oldValue, newValue) -> updateAnimation());
|
|
|
- progressBar.parentProperty().addListener((observable, oldValue, newValue) -> updateAnimation());
|
|
|
- progressBar.sceneProperty().addListener((observable, oldValue, newValue) -> updateAnimation());
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
- * Resets the animation.
|
|
|
+ * Responsible for clearing the indeterminate animation (stop, clear children and set to null), and
|
|
|
+ * resetting the bars layout, scale and width properties.
|
|
|
*/
|
|
|
- private void clearAnimation() {
|
|
|
- if (indeterminateTransition != null) {
|
|
|
- indeterminateTransition.stop();
|
|
|
- indeterminateTransition.getChildren().clear();
|
|
|
- indeterminateTransition = null;
|
|
|
+ protected void resetBars() {
|
|
|
+ if (indeterminateAnimation != null) {
|
|
|
+ indeterminateAnimation.stop();
|
|
|
+ indeterminateAnimation.getChildren().clear();
|
|
|
+ indeterminateAnimation = null;
|
|
|
}
|
|
|
+
|
|
|
+ bar1.setLayoutX(0);
|
|
|
+ bar1.setScaleX(1.0);
|
|
|
+ bar1.setWidth(0);
|
|
|
+ bar2.setLayoutX(0);
|
|
|
+ bar2.setScaleX(1.0);
|
|
|
+ bar2.setWidth(0);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Responsible for calculating the bar width according to the current progress
|
|
|
+ * (so when the progress bar is not indeterminate).
|
|
|
+ */
|
|
|
+ protected void updateProgress() {
|
|
|
+ MFXProgressBar progressBar = getSkinnable();
|
|
|
+
|
|
|
+ double width = ((progressBar.getWidth()) * (progressBar.getProgress() * 100)) / 100;
|
|
|
+ bar1.setWidth(width);
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
- * Creates the animation for the indeterminate bar.
|
|
|
+ * If the indeterminate animation is already playing returns.
|
|
|
+ * <p></p>
|
|
|
+ * Responsible for building the indeterminate animation.
|
|
|
*/
|
|
|
- private void createIndeterminateTimeline() {
|
|
|
+ protected void playIndeterminateAnimation() {
|
|
|
MFXProgressBar progressBar = getSkinnable();
|
|
|
|
|
|
- if (indeterminateTransition != null) {
|
|
|
- clearAnimation();
|
|
|
+ if (indeterminateAnimation != null) {
|
|
|
+ return;
|
|
|
}
|
|
|
|
|
|
final double width = progressBar.getWidth() - (snappedLeftInset() + snappedRightInset());
|
|
|
-
|
|
|
- KeyFrame kf0 = new KeyFrame(Duration.ZERO,
|
|
|
+ KeyFrame kf0 = new KeyFrame(Duration.ONE,
|
|
|
new KeyValue(bar1.scaleXProperty(), 0.7),
|
|
|
- new KeyValue(bar1.translateXProperty(), -width),
|
|
|
- new KeyValue(bar2.translateXProperty(), -width)
|
|
|
+ new KeyValue(bar1.layoutXProperty(), -width),
|
|
|
+ new KeyValue(bar1.widthProperty(), width / 2),
|
|
|
+ new KeyValue(bar2.layoutXProperty(), -width),
|
|
|
+ new KeyValue(bar2.widthProperty(), width / 2)
|
|
|
);
|
|
|
KeyFrame kf1 = new KeyFrame(Duration.millis(700),
|
|
|
new KeyValue(bar1.scaleXProperty(), 1.25, Interpolator.EASE_BOTH)
|
|
|
);
|
|
|
KeyFrame kf2 = new KeyFrame(Duration.millis(1300),
|
|
|
- new KeyValue(bar1.translateXProperty(), width, Interpolator.LINEAR)
|
|
|
+ new KeyValue(bar1.layoutXProperty(), width, Interpolator.LINEAR)
|
|
|
);
|
|
|
- KeyFrame kf3 = new KeyFrame(Duration.millis(900),
|
|
|
+ KeyFrame kf3 = new KeyFrame(Duration.millis(1100),
|
|
|
new KeyValue(bar1.scaleXProperty(), 1.0, Interpolator.EASE_OUT)
|
|
|
);
|
|
|
KeyFrame kf4 = new KeyFrame(Duration.millis(1100),
|
|
|
- new KeyValue(bar2.translateXProperty(), width * 2, Interpolator.LINEAR),
|
|
|
- new KeyValue(bar2.scaleXProperty(), 2.25, Interpolator.EASE_BOTH)
|
|
|
+ new KeyValue(bar2.layoutXProperty(), width * 2, Interpolator.LINEAR),
|
|
|
+ new KeyValue(bar2.scaleXProperty(), 2, Interpolator.EASE_BOTH)
|
|
|
);
|
|
|
|
|
|
Timeline bar1Animation = new Timeline(kf0, kf1, kf2, kf3);
|
|
|
Timeline bar2Animation = new Timeline(kf4);
|
|
|
bar2Animation.setDelay(Duration.millis(1100));
|
|
|
|
|
|
- indeterminateTransition = new ParallelTransition(bar1Animation, bar2Animation);
|
|
|
- indeterminateTransition.setCycleCount(Timeline.INDEFINITE);
|
|
|
+ indeterminateAnimation = new ParallelTransition(bar1Animation, bar2Animation);
|
|
|
+ indeterminateAnimation.setCycleCount(Timeline.INDEFINITE);
|
|
|
+ indeterminateAnimation.setRate(progressBar.getAnimationSpeed());
|
|
|
+ indeterminateAnimation.play();
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
- * Pauses/Resumes the animation.
|
|
|
+ * Responsible for building the track and the bars for the progress bar.
|
|
|
*/
|
|
|
- private void updateTimeline(boolean pause) {
|
|
|
+ protected Rectangle buildRectangle(String styleClass) {
|
|
|
MFXProgressBar progressBar = getSkinnable();
|
|
|
|
|
|
- if (progressBar.isIndeterminate()) {
|
|
|
- if (indeterminateTransition == null) {
|
|
|
- createIndeterminateTimeline();
|
|
|
- }
|
|
|
- if (pause) {
|
|
|
- indeterminateTransition.pause();
|
|
|
- } else {
|
|
|
- indeterminateTransition.play();
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- private void updateAnimation() {
|
|
|
- final boolean isTreeVisible = isTreeVisible();
|
|
|
- if (indeterminateTransition != null) {
|
|
|
- updateTimeline(!isTreeVisible);
|
|
|
- } else if (isTreeVisible) {
|
|
|
- createIndeterminateTimeline();
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- /**
|
|
|
- * Updates the bar progress.
|
|
|
- */
|
|
|
- private void updateProgress() {
|
|
|
- MFXProgressBar progressBar = getSkinnable();
|
|
|
-
|
|
|
- final boolean isIndeterminate = progressBar.isIndeterminate();
|
|
|
- if (!(isIndeterminate && wasIndeterminate)) {
|
|
|
- barWidth = ((int) (progressBar.getWidth() - snappedLeftInset() - snappedRightInset()) * 2
|
|
|
- * Math.min(1, Math.max(0, progressBar.getProgress()))) / 2.0F;
|
|
|
- progressBar.requestLayout();
|
|
|
- }
|
|
|
- wasIndeterminate = isIndeterminate;
|
|
|
- }
|
|
|
-
|
|
|
- private boolean isTreeVisible() {
|
|
|
- MFXProgressBar progressBar = getSkinnable();
|
|
|
- return progressBar.isVisible() && progressBar.getParent() != null && progressBar.getScene() != null;
|
|
|
+ Rectangle rectangle = new Rectangle();
|
|
|
+ rectangle.getStyleClass().setAll(styleClass);
|
|
|
+ rectangle.setStroke(Color.TRANSPARENT);
|
|
|
+ rectangle.setStrokeLineCap(StrokeLineCap.ROUND);
|
|
|
+ rectangle.setStrokeLineJoin(StrokeLineJoin.ROUND);
|
|
|
+ rectangle.setStrokeType(StrokeType.INSIDE);
|
|
|
+ rectangle.setStrokeWidth(0);
|
|
|
+/* rectangle.arcHeightProperty().bind(progressBar.bordersRadiusProperty());
|
|
|
+ rectangle.arcWidthProperty().bind(progressBar.bordersRadiusProperty());*/
|
|
|
+ return rectangle;
|
|
|
}
|
|
|
|
|
|
//================================================================================
|
|
|
- // Override Methods
|
|
|
+ // OverrideMethods
|
|
|
//================================================================================
|
|
|
+
|
|
|
@Override
|
|
|
protected double computePrefWidth(double height, double topInset, double rightInset, double bottomInset, double leftInset) {
|
|
|
return Math.max(100, leftInset + bar1.prefWidth(getSkinnable().getWidth()) + rightInset);
|
|
@@ -193,12 +212,7 @@ public class MFXProgressBarSkin extends SkinBase<MFXProgressBar> {
|
|
|
|
|
|
@Override
|
|
|
protected double computePrefHeight(double width, double topInset, double rightInset, double bottomInset, double leftInset) {
|
|
|
- return topInset + bar1.prefHeight(width) + bottomInset;
|
|
|
- }
|
|
|
-
|
|
|
- @Override
|
|
|
- protected double computeMaxWidth(double height, double topInset, double rightInset, double bottomInset, double leftInset) {
|
|
|
- return getSkinnable().prefWidth(height);
|
|
|
+ return Math.max(5, bar1.prefHeight(width)) + topInset + bottomInset;
|
|
|
}
|
|
|
|
|
|
@Override
|
|
@@ -207,33 +221,17 @@ public class MFXProgressBarSkin extends SkinBase<MFXProgressBar> {
|
|
|
}
|
|
|
|
|
|
@Override
|
|
|
- public void dispose() {
|
|
|
- super.dispose();
|
|
|
-
|
|
|
- if (indeterminateTransition != null) {
|
|
|
- clearAnimation();
|
|
|
- }
|
|
|
+ protected double computeMaxWidth(double height, double topInset, double rightInset, double bottomInset, double leftInset) {
|
|
|
+ return getSkinnable().prefWidth(height);
|
|
|
}
|
|
|
|
|
|
@Override
|
|
|
- protected void layoutChildren(double x, double y, double w, double h) {
|
|
|
- MFXProgressBar progressBar = getSkinnable();
|
|
|
-
|
|
|
- track.resizeRelocate(x, y, w, h);
|
|
|
- bar1.resizeRelocate(x, y, progressBar.isIndeterminate() ? w / 2 : barWidth, h);
|
|
|
- bar2.resizeRelocate(x, y, progressBar.isIndeterminate() ? w / 2 : 0, h);
|
|
|
-
|
|
|
- if (progressBar.isIndeterminate()) {
|
|
|
- bar1.setTranslateX(-w);
|
|
|
- bar2.setTranslateX(-w);
|
|
|
- createIndeterminateTimeline();
|
|
|
- indeterminateTransition.play();
|
|
|
- progressBar.setClip(buildClip());
|
|
|
- } else {
|
|
|
- bar1.setTranslateX(0);
|
|
|
- bar2.setTranslateX(0);
|
|
|
- clearAnimation();
|
|
|
- progressBar.setClip(null);
|
|
|
+ public void dispose() {
|
|
|
+ super.dispose();
|
|
|
+ if (indeterminateAnimation != null) {
|
|
|
+ indeterminateAnimation.stop();
|
|
|
+ indeterminateAnimation.getChildren().clear();
|
|
|
+ indeterminateAnimation = null;
|
|
|
}
|
|
|
}
|
|
|
}
|