diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/Controllers.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/Controllers.java index 8f0c2f275..cecea31b6 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/Controllers.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/Controllers.java @@ -19,7 +19,6 @@ package org.jackhuang.hmcl.ui; import com.jfoenix.controls.JFXButton; import com.jfoenix.controls.JFXDialogLayout; -import javafx.animation.Interpolator; import javafx.animation.KeyFrame; import javafx.animation.KeyValue; import javafx.animation.Timeline; @@ -50,7 +49,6 @@ import org.jackhuang.hmcl.ui.account.AccountListPage; import org.jackhuang.hmcl.ui.animation.AnimationUtils; import org.jackhuang.hmcl.ui.construct.*; import org.jackhuang.hmcl.ui.construct.MessageDialogPane.MessageType; -import org.jackhuang.hmcl.ui.decorator.Decorator; import org.jackhuang.hmcl.ui.decorator.DecoratorController; import org.jackhuang.hmcl.ui.download.DownloadPage; import org.jackhuang.hmcl.ui.download.ModpackInstallWizardProvider; @@ -274,19 +272,19 @@ public final class Controllers { stage.initStyle(StageStyle.TRANSPARENT); stage.setScene(scene); - if (AnimationUtils.isAnimationEnabled() && !OperatingSystem.CURRENT_OS.isLinuxOrBSD()) { - Decorator node = decorator.getDecorator(); - Interpolator ease = Interpolator.SPLINE(0.25, 0.1, 0.25, 1); + if (AnimationUtils.playWindowAnimation()) { Timeline timeline = new Timeline( new KeyFrame(Duration.millis(0), - new KeyValue(node.opacityProperty(), 0, ease), - new KeyValue(node.scaleXProperty(), 0.3, ease), - new KeyValue(node.scaleYProperty(), 0.3, ease) + new KeyValue(decorator.getDecorator().opacityProperty(), 0, FXUtils.EASE), + new KeyValue(decorator.getDecorator().scaleXProperty(), 0.8, FXUtils.EASE), + new KeyValue(decorator.getDecorator().scaleYProperty(), 0.8, FXUtils.EASE), + new KeyValue(decorator.getDecorator().scaleZProperty(), 0.8, FXUtils.EASE) ), - new KeyFrame(Duration.millis(800), - new KeyValue(node.opacityProperty(), 1, ease), - new KeyValue(node.scaleXProperty(), 1, ease), - new KeyValue(node.scaleYProperty(), 1, ease) + new KeyFrame(Duration.millis(600), + new KeyValue(decorator.getDecorator().opacityProperty(), 1, FXUtils.EASE), + new KeyValue(decorator.getDecorator().scaleXProperty(), 1, FXUtils.EASE), + new KeyValue(decorator.getDecorator().scaleYProperty(), 1, FXUtils.EASE), + new KeyValue(decorator.getDecorator().scaleZProperty(), 1, FXUtils.EASE) ) ); timeline.play(); diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/FXUtils.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/FXUtils.java index f32ebf865..1bfde9976 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/FXUtils.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/FXUtils.java @@ -1085,6 +1085,8 @@ public final class FXUtils { } }; + public static final Interpolator EASE = Interpolator.SPLINE(0.25, 0.1, 0.25, 1); + public static void onEscPressed(Node node, Runnable action) { node.addEventHandler(KeyEvent.KEY_PRESSED, e -> { if (e.getCode() == KeyCode.ESCAPE) { diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/animation/AnimationUtils.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/animation/AnimationUtils.java index 4a3a725ed..93c3a6c84 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/animation/AnimationUtils.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/animation/AnimationUtils.java @@ -1,7 +1,28 @@ +/* + * Hello Minecraft! Launcher + * Copyright (C) 2025 huangyuhui and contributors + * + * This program 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. + * + * This program 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 this program. If not, see . + */ package org.jackhuang.hmcl.ui.animation; import org.jackhuang.hmcl.setting.ConfigHolder; +import org.jackhuang.hmcl.util.platform.OperatingSystem; +/** + * @author Glavo + */ public final class AnimationUtils { private AnimationUtils() { @@ -20,4 +41,8 @@ public final class AnimationUtils { public static boolean isAnimationEnabled() { return enabled; } + + public static boolean playWindowAnimation() { + return isAnimationEnabled() && !OperatingSystem.CURRENT_OS.isLinuxOrBSD(); + } } diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/decorator/Decorator.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/decorator/Decorator.java index 8b90bbbb2..154116548 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/decorator/Decorator.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/decorator/Decorator.java @@ -18,6 +18,9 @@ package org.jackhuang.hmcl.ui.decorator; import com.jfoenix.controls.JFXSnackbar; +import javafx.animation.KeyFrame; +import javafx.animation.KeyValue; +import javafx.animation.Timeline; import javafx.beans.property.*; import javafx.collections.FXCollections; import javafx.collections.ObservableList; @@ -35,6 +38,9 @@ import javafx.scene.layout.StackPane; import javafx.scene.paint.Color; import javafx.stage.Stage; import javafx.stage.StageStyle; +import javafx.util.Duration; +import org.jackhuang.hmcl.ui.FXUtils; +import org.jackhuang.hmcl.ui.animation.AnimationUtils; import org.jackhuang.hmcl.ui.wizard.Navigation; public class Decorator extends Control { @@ -61,12 +67,40 @@ public class Decorator extends Control { private final ReadOnlyBooleanWrapper allowMove = new ReadOnlyBooleanWrapper(); private final ReadOnlyBooleanWrapper dragging = new ReadOnlyBooleanWrapper(); + private boolean playRestoreMinimizeAnimation = false; + public Decorator(Stage primaryStage) { this.primaryStage = primaryStage; setBackground(new Background(new BackgroundFill(Color.TRANSPARENT, CornerRadii.EMPTY, Insets.EMPTY))); primaryStage.initStyle(StageStyle.UNDECORATED); + + if (AnimationUtils.playWindowAnimation()) { + FXUtils.onChange(primaryStage.iconifiedProperty(), iconified -> { + if (playRestoreMinimizeAnimation && !iconified) { + playRestoreMinimizeAnimation = false; + Timeline timeline = new Timeline( + new KeyFrame(Duration.millis(0), + new KeyValue(this.opacityProperty(), 0, FXUtils.EASE), + new KeyValue(this.translateYProperty(), 200, FXUtils.EASE), + new KeyValue(this.scaleXProperty(), 0.4, FXUtils.EASE), + new KeyValue(this.scaleYProperty(), 0.4, FXUtils.EASE), + new KeyValue(this.scaleZProperty(), 0.4, FXUtils.EASE) + ), + new KeyFrame(Duration.millis(200), + new KeyValue(this.opacityProperty(), 1, FXUtils.EASE), + new KeyValue(this.translateYProperty(), 0, FXUtils.EASE), + new KeyValue(this.scaleXProperty(), 1, FXUtils.EASE), + new KeyValue(this.scaleYProperty(), 1, FXUtils.EASE), + new KeyValue(this.scaleZProperty(), 1, FXUtils.EASE) + ) + ); + timeline.play(); + } + }); + } + } public Stage getPrimaryStage() { @@ -239,7 +273,29 @@ public class Decorator extends Control { } public void minimize() { - primaryStage.setIconified(true); + if (AnimationUtils.playWindowAnimation()) { + playRestoreMinimizeAnimation = true; + Timeline timeline = new Timeline( + new KeyFrame(Duration.millis(0), + new KeyValue(this.opacityProperty(), 1, FXUtils.EASE), + new KeyValue(this.translateYProperty(), 0, FXUtils.EASE), + new KeyValue(this.scaleXProperty(), 1, FXUtils.EASE), + new KeyValue(this.scaleYProperty(), 1, FXUtils.EASE), + new KeyValue(this.scaleZProperty(), 1, FXUtils.EASE) + ), + new KeyFrame(Duration.millis(200), + new KeyValue(this.opacityProperty(), 0, FXUtils.EASE), + new KeyValue(this.translateYProperty(), 200, FXUtils.EASE), + new KeyValue(this.scaleXProperty(), 0.4, FXUtils.EASE), + new KeyValue(this.scaleYProperty(), 0.4, FXUtils.EASE), + new KeyValue(this.scaleZProperty(), 0.4, FXUtils.EASE) + ) + ); + timeline.setOnFinished(event -> primaryStage.setIconified(true)); + timeline.play(); + } else { + primaryStage.setIconified(true); + } } public void close() { diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/decorator/DecoratorController.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/decorator/DecoratorController.java index 002db232e..ec12c9f25 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/decorator/DecoratorController.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/decorator/DecoratorController.java @@ -19,7 +19,6 @@ package org.jackhuang.hmcl.ui.decorator; import com.jfoenix.controls.JFXDialog; import com.jfoenix.controls.JFXSnackbar; -import javafx.animation.Interpolator; import javafx.animation.KeyFrame; import javafx.animation.KeyValue; import javafx.animation.Timeline; @@ -55,7 +54,6 @@ import org.jackhuang.hmcl.ui.construct.Navigator; import org.jackhuang.hmcl.ui.construct.StackContainerPane; import org.jackhuang.hmcl.ui.wizard.Refreshable; import org.jackhuang.hmcl.ui.wizard.WizardProvider; -import org.jackhuang.hmcl.util.platform.OperatingSystem; import org.jetbrains.annotations.Nullable; import java.io.IOException; @@ -89,25 +87,21 @@ public class DecoratorController { public DecoratorController(Stage stage, Node mainPage) { decorator = new Decorator(stage); decorator.setOnCloseButtonAction(() -> { - if (AnimationUtils.isAnimationEnabled() && !OperatingSystem.CURRENT_OS.isLinuxOrBSD()) { - Interpolator ease = Interpolator.SPLINE(0.25, 0.1, 0.25, 1); - - Timeline timeline = (new Timeline( + if (AnimationUtils.playWindowAnimation()) { + Timeline timeline = new Timeline( new KeyFrame(Duration.millis(0), - new KeyValue(decorator.opacityProperty(), 1, ease), - new KeyValue(decorator.translateYProperty(), 0, ease), - new KeyValue(decorator.scaleXProperty(), 1, ease), - new KeyValue(decorator.scaleYProperty(), 1, ease), - new KeyValue(decorator.scaleZProperty(), 0.3, ease) + new KeyValue(decorator.opacityProperty(), 1, FXUtils.EASE), + new KeyValue(decorator.scaleXProperty(), 1, FXUtils.EASE), + new KeyValue(decorator.scaleYProperty(), 1, FXUtils.EASE), + new KeyValue(decorator.scaleZProperty(), 0.3, FXUtils.EASE) ), - new KeyFrame(Duration.millis(400), - new KeyValue(decorator.opacityProperty(), 0, ease), - new KeyValue(decorator.translateYProperty(), 200, ease), - new KeyValue(decorator.scaleXProperty(), 0.3, ease), - new KeyValue(decorator.scaleYProperty(), 0.3, ease), - new KeyValue(decorator.scaleZProperty(), 0.3, ease) + new KeyFrame(Duration.millis(200), + new KeyValue(decorator.opacityProperty(), 0, FXUtils.EASE), + new KeyValue(decorator.scaleXProperty(), 0.8, FXUtils.EASE), + new KeyValue(decorator.scaleYProperty(), 0.8, FXUtils.EASE), + new KeyValue(decorator.scaleZProperty(), 0.8, FXUtils.EASE) ) - )); + ); timeline.setOnFinished(event -> Launcher.stopApplication()); timeline.play(); } else {