diff --git a/HMCL/src/main/java/com/jfoenix/controls/JFXProgressBar.java b/HMCL/src/main/java/com/jfoenix/controls/JFXProgressBar.java index 38ff0a2607..8560232d7a 100644 --- a/HMCL/src/main/java/com/jfoenix/controls/JFXProgressBar.java +++ b/HMCL/src/main/java/com/jfoenix/controls/JFXProgressBar.java @@ -49,6 +49,16 @@ protected Skin createDefaultSkin() { return new JFXProgressBarSkin(this); } + private boolean smoothProgress = true; + + public boolean isSmoothProgress() { + return smoothProgress; + } + + public void setSmoothProgress(boolean smoothProgress) { + this.smoothProgress = smoothProgress; + } + private void initialize() { setPrefWidth(200); getStyleClass().add(DEFAULT_STYLE_CLASS); diff --git a/HMCL/src/main/java/com/jfoenix/skins/JFXProgressBarSkin.java b/HMCL/src/main/java/com/jfoenix/skins/JFXProgressBarSkin.java index 348ddce971..fb95b90429 100644 --- a/HMCL/src/main/java/com/jfoenix/skins/JFXProgressBarSkin.java +++ b/HMCL/src/main/java/com/jfoenix/skins/JFXProgressBarSkin.java @@ -23,12 +23,11 @@ import com.jfoenix.utils.TreeShowingProperty; import javafx.animation.*; import javafx.scene.Node; -import javafx.scene.control.ProgressIndicator; import javafx.scene.control.SkinBase; import javafx.scene.layout.*; import javafx.scene.shape.Rectangle; import javafx.util.Duration; -import org.jackhuang.hmcl.util.Lang; +import org.jackhuang.hmcl.ui.animation.AnimationUtils; /// # Material Design ProgressBar Skin /// @@ -37,21 +36,22 @@ /// @since 2017-10-06 public class JFXProgressBarSkin extends SkinBase { - private static final double ARC_HEIGHT = 4; + private static final double DEFAULT_HEIGHT = 4; private final StackPane track; private final StackPane bar; - private Timeline indeterminateTransition; private final Rectangle clip; + private Animation transition; private final TreeShowingProperty treeShowingProperty; + private double fullWidth; public JFXProgressBarSkin(JFXProgressBar control) { super(control); this.treeShowingProperty = new TreeShowingProperty(control); - registerChangeListener(treeShowingProperty, obs -> updateAnimation()); - registerChangeListener(control.progressProperty(), obs -> updateProgress()); + registerChangeListener(treeShowingProperty, obs -> updateProgress(false)); + registerChangeListener(control.progressProperty(), obs -> updateProgress(true)); track = new StackPane(); track.getStyleClass().setAll("track"); @@ -60,9 +60,8 @@ public JFXProgressBarSkin(JFXProgressBar control) { bar.getStyleClass().setAll("bar"); clip = new Rectangle(); - clip.setManaged(false); - clip.setArcWidth(ARC_HEIGHT); - clip.setArcHeight(ARC_HEIGHT); + clip.setArcWidth(DEFAULT_HEIGHT); + clip.setArcHeight(DEFAULT_HEIGHT); bar.setClip(clip); getChildren().setAll(track, bar); @@ -82,7 +81,7 @@ protected double computePrefWidth(double height, double topInset, double rightIn @Override protected double computePrefHeight(double width, double topInset, double rightInset, double bottomInset, double leftInset) { - return topInset + bar.prefHeight(width) + bottomInset; + return topInset + DEFAULT_HEIGHT + bottomInset; } @Override @@ -97,98 +96,117 @@ protected double computeMaxHeight(double width, double topInset, double rightIns @Override protected void layoutChildren(double x, double y, double w, double h) { - boolean indeterminate = getSkinnable().isIndeterminate(); - track.resizeRelocate(x, y, w, h); bar.resizeRelocate(x, y, w, h); + clip.relocate(0, 0); - clip.setTranslateX(0); + clip.setWidth(0); clip.setHeight(h); + clip.setTranslateX(0); - if (indeterminate) { - if (treeShowingProperty.get()) { - createIndeterminateTimeline(); - indeterminateTransition.play(); - } else { - clearAnimation(); - } - } else { - clearAnimation(); - - double progress = Lang.clamp(0.0, getSkinnable().getProgress(), 1.0); - double barWidth = ((int) w * 2 * progress) / 2.0; - if (progress > 0) { - barWidth = Math.max(barWidth, ARC_HEIGHT); - } + fullWidth = w; - clip.setWidth(barWidth); - } + clearAnimation(); + updateProgress(false); } - boolean wasIndeterminate = false; + private boolean wasIndeterminate = false; + + private void updateProgress(boolean playProgressAnimation) { + double progress = Math.min(getSkinnable().getProgress(), 1.0); + boolean isIndeterminate = progress < 0.0; + boolean isTreeShowing = treeShowingProperty.get(); - protected void pauseTimeline(boolean pause) { - if (getSkinnable().isIndeterminate()) { - if (pause) { - if (indeterminateTransition != null) { - indeterminateTransition.pause(); + if (isIndeterminate != wasIndeterminate) { + wasIndeterminate = isIndeterminate; + clearAnimation(); + clip.setTranslateX(0); + } + + if (isIndeterminate) { // indeterminate + if (isTreeShowing) { + if (transition == null) { + transition = createIndeterminateTransition(); + transition.playFromStart(); + } else { + transition.play(); } + } else if (transition != null) { + transition.pause(); + } + } else { // determinate + clearAnimation(); + if (isTreeShowing && playProgressAnimation + && AnimationUtils.isAnimationEnabled() + && getSkinnable().isSmoothProgress()) { + transition = createDeterminateTransition(progress); + transition.playFromStart(); } else { - if (indeterminateTransition == null) { - createIndeterminateTimeline(); - } - indeterminateTransition.play(); + clip.setWidth(computeBarWidth(progress)); } } } - private void updateAnimation() { - final boolean isTreeShowing = treeShowingProperty.get(); - if (indeterminateTransition != null) { - pauseTimeline(!isTreeShowing); - } - } + private static final Duration INDETERMINATE_DURATION = Duration.seconds(1); - private void updateProgress() { - final ProgressIndicator control = getSkinnable(); - final boolean isIndeterminate = control.isIndeterminate(); - if (!(isIndeterminate && wasIndeterminate)) { - control.requestLayout(); - } - wasIndeterminate = isIndeterminate; - } + private Transition createIndeterminateTransition() { + double minWidth = 0; + double maxWidth = fullWidth * 0.4; + Transition transition = new Transition() { + { + setInterpolator(Interpolator.LINEAR); + setCycleDuration(INDETERMINATE_DURATION); + } - private static final Duration DURATION = Duration.seconds(1); + @Override + protected void interpolate(double frac) { + double currentWidth; - private void createIndeterminateTimeline() { - clearAnimation(); - ProgressIndicator control = getSkinnable(); - final double w = control.getWidth() - snappedLeftInset() - snappedRightInset(); - indeterminateTransition = new Timeline( - new KeyFrame( - Duration.ZERO, - new KeyValue(clip.widthProperty(), 0.0, Interpolator.EASE_IN), - new KeyValue(clip.translateXProperty(), 0, Interpolator.LINEAR) - ), - new KeyFrame( - DURATION.multiply(0.5), - new KeyValue(clip.widthProperty(), w * 0.4, Interpolator.LINEAR) - ), - new KeyFrame( - DURATION.multiply(0.9), - new KeyValue(clip.translateXProperty(), w, Interpolator.LINEAR) - ), - new KeyFrame( - DURATION, - new KeyValue(clip.widthProperty(), 0.0, Interpolator.EASE_OUT) - )); - indeterminateTransition.setCycleCount(Timeline.INDEFINITE); + if (frac <= 0.5) { + currentWidth = Interpolator.EASE_IN.interpolate(minWidth, maxWidth, frac / 0.5); + } else { + currentWidth = Interpolator.EASE_OUT.interpolate(maxWidth, minWidth, (frac - 0.5) / 0.5); + } + + double targetCenter; + if (frac <= 0.1) { + targetCenter = 0.0; + } else if (frac >= 0.9) { + targetCenter = fullWidth; + } else { + targetCenter = ((frac - 0.1) / 0.8) * fullWidth; + } + + clip.setWidth(currentWidth); + clip.setTranslateX(targetCenter - currentWidth / 2.0); + } + }; + + transition.setCycleCount(Timeline.INDEFINITE); + return transition; + } + + private static final Duration DETERMINATE_DURATION = Duration.seconds(0.2); + + private Timeline createDeterminateTransition(double targetProgress) { + Timeline timeline = new Timeline( + new KeyFrame(Duration.ZERO, + new KeyValue(clip.widthProperty(), clip.getWidth(), Interpolator.EASE_OUT)), + new KeyFrame(DETERMINATE_DURATION, + new KeyValue(clip.widthProperty(), computeBarWidth(targetProgress), Interpolator.EASE_OUT)) + ); + timeline.setOnFinished(e -> { + if (transition == timeline) { + transition = null; + } + }); + return timeline; } private void clearAnimation() { - if (indeterminateTransition != null) { - indeterminateTransition.stop(); - indeterminateTransition = null; + if (transition != null) { + transition.stop(); + transition = null; } } @@ -198,4 +216,10 @@ public void dispose() { treeShowingProperty.dispose(); clearAnimation(); } + + private double computeBarWidth(double progress) { + assert progress >= 0 && progress <= 1; + double barWidth = ((int) fullWidth * 2 * progress) / 2.0; + return progress > 0 ? Math.max(barWidth, DEFAULT_HEIGHT) : barWidth; + } } diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/TaskListPane.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/TaskListPane.java index 80cac8eb9f..08c390a95e 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/TaskListPane.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/TaskListPane.java @@ -327,8 +327,9 @@ protected void updateItem(Node item, boolean empty) { pane.paddingProperty().unbind(); title.textProperty().unbind(); message.textProperty().unbind(); - bar.progressProperty().unbind(); + bar.setSmoothProgress(false); + bar.progressProperty().unbind(); StageNode prevStageNode; if (prevStageNodeRef != null && (prevStageNode = prevStageNodeRef.get()) != null) prevStageNode.status.removeListener(statusChangeListener); @@ -366,6 +367,8 @@ protected void updateItem(Node item, boolean empty) { pane.setRight(null); pane.setBottom(null); } + + bar.setSmoothProgress(true); } } diff --git a/HMCL/src/main/resources/assets/css/root.css b/HMCL/src/main/resources/assets/css/root.css index c429f344cc..5b035f97f6 100644 --- a/HMCL/src/main/resources/assets/css/root.css +++ b/HMCL/src/main/resources/assets/css/root.css @@ -913,12 +913,12 @@ * * *******************************************************************************/ + .jfx-progress-bar > .track { -fx-background-color: -monet-secondary-container; } -.jfx-progress-bar > .bar, -.jfx-progress-bar:indeterminate > .bar { +.jfx-progress-bar > .bar { -fx-background-color: -monet-primary; -fx-padding: 2; } @@ -929,7 +929,6 @@ -fx-background-insets: 0; } - /******************************************************************************* * * * JFX Textfield *