更新界面动画效果 (#4780)
This commit is contained in:
562
HMCL/src/main/java/com/jfoenix/controls/JFXDialog.java
Normal file
562
HMCL/src/main/java/com/jfoenix/controls/JFXDialog.java
Normal file
@@ -0,0 +1,562 @@
|
|||||||
|
/*
|
||||||
|
* Licensed to the Apache Software Foundation (ASF) under one
|
||||||
|
* or more contributor license agreements. See the NOTICE file
|
||||||
|
* distributed with this work for additional information
|
||||||
|
* regarding copyright ownership. The ASF licenses this file
|
||||||
|
* to you under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing,
|
||||||
|
* software distributed under the License is distributed on an
|
||||||
|
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||||
|
* KIND, either express or implied. See the License for the
|
||||||
|
* specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.jfoenix.controls;
|
||||||
|
|
||||||
|
import com.jfoenix.controls.events.JFXDialogEvent;
|
||||||
|
import com.jfoenix.converters.DialogTransitionConverter;
|
||||||
|
import com.jfoenix.effects.JFXDepthManager;
|
||||||
|
import com.jfoenix.transitions.CachedTransition;
|
||||||
|
import javafx.animation.*;
|
||||||
|
import javafx.beans.DefaultProperty;
|
||||||
|
import javafx.beans.property.BooleanProperty;
|
||||||
|
import javafx.beans.property.ObjectProperty;
|
||||||
|
import javafx.beans.property.ObjectPropertyBase;
|
||||||
|
import javafx.beans.property.SimpleBooleanProperty;
|
||||||
|
import javafx.css.*;
|
||||||
|
import javafx.event.Event;
|
||||||
|
import javafx.event.EventHandler;
|
||||||
|
import javafx.geometry.Pos;
|
||||||
|
import javafx.scene.CacheHint;
|
||||||
|
import javafx.scene.Node;
|
||||||
|
import javafx.scene.SnapshotParameters;
|
||||||
|
import javafx.scene.image.ImageView;
|
||||||
|
import javafx.scene.image.WritableImage;
|
||||||
|
import javafx.scene.input.MouseEvent;
|
||||||
|
import javafx.scene.layout.*;
|
||||||
|
import javafx.scene.paint.Color;
|
||||||
|
import javafx.util.Duration;
|
||||||
|
import org.jackhuang.hmcl.ui.animation.Motion;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/// Note: for JFXDialog to work properly, the root node **MUST**
|
||||||
|
/// be of type [StackPane]
|
||||||
|
///
|
||||||
|
/// @author Shadi Shaheen
|
||||||
|
/// @version 1.0
|
||||||
|
/// @since 2016-03-09
|
||||||
|
@DefaultProperty(value = "content")
|
||||||
|
public class JFXDialog extends StackPane {
|
||||||
|
|
||||||
|
private static final double INITIAL_SCALE = 0.8;
|
||||||
|
|
||||||
|
// public static enum JFXDialogLayout{PLAIN, HEADING, ACTIONS, BACKDROP};
|
||||||
|
public enum DialogTransition {
|
||||||
|
CENTER, NONE
|
||||||
|
}
|
||||||
|
|
||||||
|
private StackPane contentHolder;
|
||||||
|
|
||||||
|
private double offsetX = 0;
|
||||||
|
private double offsetY = 0;
|
||||||
|
|
||||||
|
private StackPane dialogContainer;
|
||||||
|
private Region content;
|
||||||
|
private Transition showAnimation;
|
||||||
|
private Transition hideAnimation;
|
||||||
|
|
||||||
|
private final EventHandler<? super MouseEvent> closeHandler = e -> close();
|
||||||
|
|
||||||
|
/// creates empty JFXDialog control with CENTER animation type
|
||||||
|
public JFXDialog() {
|
||||||
|
this(null, null, DialogTransition.CENTER);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// creates empty JFXDialog control with a specified animation type
|
||||||
|
public JFXDialog(DialogTransition transition) {
|
||||||
|
this(null, null, transition);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// creates JFXDialog control with a specified animation type, the animation type
|
||||||
|
/// can be one of the following:
|
||||||
|
///
|
||||||
|
/// - CENTER
|
||||||
|
/// - TOP
|
||||||
|
/// - RIGHT
|
||||||
|
/// - BOTTOM
|
||||||
|
/// - LEFT
|
||||||
|
///
|
||||||
|
/// @param dialogContainer is the parent of the dialog, it
|
||||||
|
/// @param content the content of dialog
|
||||||
|
/// @param transitionType the animation type
|
||||||
|
public JFXDialog(StackPane dialogContainer, Region content, DialogTransition transitionType) {
|
||||||
|
initialize();
|
||||||
|
setContent(content);
|
||||||
|
setDialogContainer(dialogContainer);
|
||||||
|
this.transitionType.set(transitionType);
|
||||||
|
// init change listeners
|
||||||
|
initChangeListeners();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// creates JFXDialog control with a specified animation type that
|
||||||
|
/// is closed when clicking on the overlay, the animation type
|
||||||
|
/// can be one of the following:
|
||||||
|
///
|
||||||
|
/// - CENTER
|
||||||
|
/// - TOP
|
||||||
|
/// - RIGHT
|
||||||
|
/// - BOTTOM
|
||||||
|
/// - LEFT
|
||||||
|
///
|
||||||
|
public JFXDialog(StackPane dialogContainer, Region content, DialogTransition transitionType, boolean overlayClose) {
|
||||||
|
setOverlayClose(overlayClose);
|
||||||
|
initialize();
|
||||||
|
setContent(content);
|
||||||
|
setDialogContainer(dialogContainer);
|
||||||
|
this.transitionType.set(transitionType);
|
||||||
|
// init change listeners
|
||||||
|
initChangeListeners();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void initChangeListeners() {
|
||||||
|
overlayCloseProperty().addListener((o, oldVal, newVal) -> {
|
||||||
|
if (newVal) {
|
||||||
|
this.addEventHandler(MouseEvent.MOUSE_PRESSED, closeHandler);
|
||||||
|
} else {
|
||||||
|
this.removeEventHandler(MouseEvent.MOUSE_PRESSED, closeHandler);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void initialize() {
|
||||||
|
this.setVisible(false);
|
||||||
|
this.getStyleClass().add(DEFAULT_STYLE_CLASS);
|
||||||
|
this.transitionType.addListener((o, oldVal, newVal) -> {
|
||||||
|
showAnimation = getShowAnimation(transitionType.get());
|
||||||
|
hideAnimation = getHideAnimation(transitionType.get());
|
||||||
|
});
|
||||||
|
|
||||||
|
contentHolder = new StackPane();
|
||||||
|
contentHolder.setBackground(new Background(new BackgroundFill(Color.WHITE, new CornerRadii(2), null)));
|
||||||
|
JFXDepthManager.setDepth(contentHolder, 4);
|
||||||
|
contentHolder.setPickOnBounds(false);
|
||||||
|
// ensure stackpane is never resized beyond it's preferred size
|
||||||
|
contentHolder.setMaxSize(Region.USE_PREF_SIZE, Region.USE_PREF_SIZE);
|
||||||
|
this.getChildren().add(contentHolder);
|
||||||
|
this.getStyleClass().add("jfx-dialog-overlay-pane");
|
||||||
|
StackPane.setAlignment(contentHolder, Pos.CENTER);
|
||||||
|
this.setBackground(new Background(new BackgroundFill(Color.rgb(0, 0, 0, 0.1), null, null)));
|
||||||
|
// close the dialog if clicked on the overlay pane
|
||||||
|
if (overlayClose.get()) {
|
||||||
|
this.addEventHandler(MouseEvent.MOUSE_PRESSED, closeHandler);
|
||||||
|
}
|
||||||
|
// prevent propagating the events to overlay pane
|
||||||
|
contentHolder.addEventHandler(MouseEvent.ANY, Event::consume);
|
||||||
|
}
|
||||||
|
|
||||||
|
/***************************************************************************
|
||||||
|
* *
|
||||||
|
* Setters / Getters *
|
||||||
|
* *
|
||||||
|
**************************************************************************/
|
||||||
|
|
||||||
|
/// @return the dialog container
|
||||||
|
public StackPane getDialogContainer() {
|
||||||
|
return dialogContainer;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// set the dialog container
|
||||||
|
/// Note: the dialog container must be StackPane, its the container for the dialog to be shown in.
|
||||||
|
public void setDialogContainer(StackPane dialogContainer) {
|
||||||
|
if (dialogContainer != null) {
|
||||||
|
this.dialogContainer = dialogContainer;
|
||||||
|
// FIXME: need to be improved to consider only the parent boundary
|
||||||
|
offsetX = dialogContainer.getBoundsInLocal().getWidth();
|
||||||
|
offsetY = dialogContainer.getBoundsInLocal().getHeight();
|
||||||
|
showAnimation = getShowAnimation(transitionType.get());
|
||||||
|
hideAnimation = getHideAnimation(transitionType.get());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @return dialog content node
|
||||||
|
public Region getContent() {
|
||||||
|
return content;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// set the content of the dialog
|
||||||
|
public void setContent(Region content) {
|
||||||
|
if (content != null) {
|
||||||
|
this.content = content;
|
||||||
|
this.content.setPickOnBounds(false);
|
||||||
|
contentHolder.getChildren().setAll(content);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// indicates whether the dialog will close when clicking on the overlay or not
|
||||||
|
private final BooleanProperty overlayClose = new SimpleBooleanProperty(true);
|
||||||
|
|
||||||
|
public final BooleanProperty overlayCloseProperty() {
|
||||||
|
return this.overlayClose;
|
||||||
|
}
|
||||||
|
|
||||||
|
public final boolean isOverlayClose() {
|
||||||
|
return this.overlayCloseProperty().get();
|
||||||
|
}
|
||||||
|
|
||||||
|
public final void setOverlayClose(final boolean overlayClose) {
|
||||||
|
this.overlayCloseProperty().set(overlayClose);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// if sets to true, the content of dialog container will be cached and replaced with an image
|
||||||
|
/// when displaying the dialog (better performance).
|
||||||
|
/// this is recommended if the content behind the dialog will not change during the showing
|
||||||
|
/// period
|
||||||
|
private final BooleanProperty cacheContainer = new SimpleBooleanProperty(false);
|
||||||
|
|
||||||
|
public boolean isCacheContainer() {
|
||||||
|
return cacheContainer.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
public BooleanProperty cacheContainerProperty() {
|
||||||
|
return cacheContainer;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCacheContainer(boolean cacheContainer) {
|
||||||
|
this.cacheContainer.set(cacheContainer);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// it will show the dialog in the specified container
|
||||||
|
public void show(StackPane dialogContainer) {
|
||||||
|
this.setDialogContainer(dialogContainer);
|
||||||
|
showDialog();
|
||||||
|
}
|
||||||
|
|
||||||
|
private ArrayList<Node> tempContent;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* show the dialog inside its parent container
|
||||||
|
*/
|
||||||
|
public void show() {
|
||||||
|
this.setDialogContainer(dialogContainer);
|
||||||
|
showDialog();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void showDialog() {
|
||||||
|
if (dialogContainer == null) {
|
||||||
|
throw new RuntimeException("ERROR: JFXDialog container is not set!");
|
||||||
|
}
|
||||||
|
if (isCacheContainer()) {
|
||||||
|
tempContent = new ArrayList<>(dialogContainer.getChildren());
|
||||||
|
|
||||||
|
SnapshotParameters snapShotparams = new SnapshotParameters();
|
||||||
|
snapShotparams.setFill(Color.TRANSPARENT);
|
||||||
|
WritableImage temp = dialogContainer.snapshot(snapShotparams,
|
||||||
|
new WritableImage((int) dialogContainer.getWidth(),
|
||||||
|
(int) dialogContainer.getHeight()));
|
||||||
|
ImageView tempImage = new ImageView(temp);
|
||||||
|
tempImage.setCache(true);
|
||||||
|
tempImage.setCacheHint(CacheHint.SPEED);
|
||||||
|
dialogContainer.getChildren().setAll(tempImage, this);
|
||||||
|
} else {
|
||||||
|
//prevent error if opening an already opened dialog
|
||||||
|
dialogContainer.getChildren().remove(this);
|
||||||
|
tempContent = null;
|
||||||
|
dialogContainer.getChildren().add(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (showAnimation != null) {
|
||||||
|
showAnimation.play();
|
||||||
|
} else {
|
||||||
|
setVisible(true);
|
||||||
|
setOpacity(1);
|
||||||
|
Event.fireEvent(JFXDialog.this, new JFXDialogEvent(JFXDialogEvent.OPENED));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* close the dialog
|
||||||
|
*/
|
||||||
|
public void close() {
|
||||||
|
if (hideAnimation != null) {
|
||||||
|
hideAnimation.play();
|
||||||
|
} else {
|
||||||
|
setOpacity(0);
|
||||||
|
setVisible(false);
|
||||||
|
closeDialog();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void closeDialog() {
|
||||||
|
resetProperties();
|
||||||
|
Event.fireEvent(JFXDialog.this, new JFXDialogEvent(JFXDialogEvent.CLOSED));
|
||||||
|
if (tempContent == null) {
|
||||||
|
dialogContainer.getChildren().remove(this);
|
||||||
|
} else {
|
||||||
|
dialogContainer.getChildren().setAll(tempContent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/***************************************************************************
|
||||||
|
* *
|
||||||
|
* Transitions *
|
||||||
|
* *
|
||||||
|
**************************************************************************/
|
||||||
|
|
||||||
|
private Transition getShowAnimation(DialogTransition transitionType) {
|
||||||
|
Transition animation = null;
|
||||||
|
if (contentHolder != null) {
|
||||||
|
animation = switch (transitionType) {
|
||||||
|
case CENTER -> {
|
||||||
|
contentHolder.setScaleX(INITIAL_SCALE);
|
||||||
|
contentHolder.setScaleY(INITIAL_SCALE);
|
||||||
|
yield new CenterTransition();
|
||||||
|
}
|
||||||
|
case NONE -> {
|
||||||
|
contentHolder.setScaleX(1);
|
||||||
|
contentHolder.setScaleY(1);
|
||||||
|
contentHolder.setTranslateX(0);
|
||||||
|
contentHolder.setTranslateY(0);
|
||||||
|
yield null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if (animation != null) {
|
||||||
|
animation.setOnFinished(finish ->
|
||||||
|
Event.fireEvent(JFXDialog.this, new JFXDialogEvent(JFXDialogEvent.OPENED)));
|
||||||
|
}
|
||||||
|
return animation;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Transition getHideAnimation(DialogTransition transitionType) {
|
||||||
|
Transition animation = null;
|
||||||
|
if (contentHolder != null) {
|
||||||
|
animation = switch (transitionType) {
|
||||||
|
case CENTER -> new HideTransition();
|
||||||
|
case NONE -> null;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if (animation != null) {
|
||||||
|
animation.setOnFinished(finish -> closeDialog());
|
||||||
|
}
|
||||||
|
return animation;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void resetProperties() {
|
||||||
|
this.setVisible(false);
|
||||||
|
contentHolder.setTranslateX(0);
|
||||||
|
contentHolder.setTranslateY(0);
|
||||||
|
contentHolder.setScaleX(1);
|
||||||
|
contentHolder.setScaleY(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
private final class HideTransition extends CachedTransition {
|
||||||
|
private static final Interpolator INTERPOLATOR = Motion.EMPHASIZED_ACCELERATE;
|
||||||
|
|
||||||
|
public HideTransition() {
|
||||||
|
super(contentHolder, new Timeline(
|
||||||
|
new KeyFrame(Duration.ZERO,
|
||||||
|
new KeyValue(contentHolder.scaleXProperty(), 1, INTERPOLATOR),
|
||||||
|
new KeyValue(contentHolder.scaleYProperty(), 1, INTERPOLATOR),
|
||||||
|
new KeyValue(JFXDialog.this.opacityProperty(), 1, INTERPOLATOR),
|
||||||
|
new KeyValue(JFXDialog.this.visibleProperty(), true, Motion.LINEAR)
|
||||||
|
),
|
||||||
|
new KeyFrame(Motion.LONG2.subtract(Duration.millis(10)),
|
||||||
|
new KeyValue(JFXDialog.this.visibleProperty(), false, Motion.LINEAR),
|
||||||
|
new KeyValue(JFXDialog.this.opacityProperty(), 0, INTERPOLATOR)
|
||||||
|
),
|
||||||
|
new KeyFrame(Motion.LONG2,
|
||||||
|
new KeyValue(contentHolder.scaleXProperty(), INITIAL_SCALE, INTERPOLATOR),
|
||||||
|
new KeyValue(contentHolder.scaleYProperty(), INITIAL_SCALE, INTERPOLATOR)
|
||||||
|
))
|
||||||
|
);
|
||||||
|
// reduce the number to increase the shifting , increase number to reduce shifting
|
||||||
|
setCycleDuration(Duration.seconds(0.4));
|
||||||
|
setDelay(Duration.ZERO);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private final class CenterTransition extends CachedTransition {
|
||||||
|
private static final Interpolator INTERPOLATOR = Motion.EMPHASIZED_DECELERATE;
|
||||||
|
|
||||||
|
CenterTransition() {
|
||||||
|
super(contentHolder, new Timeline(
|
||||||
|
new KeyFrame(Duration.ZERO,
|
||||||
|
new KeyValue(contentHolder.scaleXProperty(), INITIAL_SCALE, INTERPOLATOR),
|
||||||
|
new KeyValue(contentHolder.scaleYProperty(), INITIAL_SCALE, INTERPOLATOR),
|
||||||
|
new KeyValue(JFXDialog.this.visibleProperty(), false, Motion.LINEAR)
|
||||||
|
),
|
||||||
|
new KeyFrame(Duration.millis(10),
|
||||||
|
new KeyValue(JFXDialog.this.visibleProperty(), true, Motion.LINEAR),
|
||||||
|
new KeyValue(JFXDialog.this.opacityProperty(), 0, INTERPOLATOR)
|
||||||
|
),
|
||||||
|
new KeyFrame(Motion.EXTRA_LONG4,
|
||||||
|
new KeyValue(contentHolder.scaleXProperty(), 1, INTERPOLATOR),
|
||||||
|
new KeyValue(contentHolder.scaleYProperty(), 1, INTERPOLATOR),
|
||||||
|
new KeyValue(JFXDialog.this.opacityProperty(), 1, INTERPOLATOR)
|
||||||
|
))
|
||||||
|
);
|
||||||
|
// reduce the number to increase the shifting , increase number to reduce shifting
|
||||||
|
setCycleDuration(Duration.seconds(0.4));
|
||||||
|
setDelay(Duration.ZERO);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/***************************************************************************
|
||||||
|
* *
|
||||||
|
* Stylesheet Handling *
|
||||||
|
* *
|
||||||
|
**************************************************************************/
|
||||||
|
|
||||||
|
/// Initialize the style class to 'jfx-dialog'.
|
||||||
|
///
|
||||||
|
/// This is the selector class from which CSS can be used to style
|
||||||
|
/// this control.
|
||||||
|
private static final String DEFAULT_STYLE_CLASS = "jfx-dialog";
|
||||||
|
|
||||||
|
/// dialog transition type property, it can be one of the following:
|
||||||
|
///
|
||||||
|
/// - CENTER
|
||||||
|
/// - TOP
|
||||||
|
/// - RIGHT
|
||||||
|
/// - BOTTOM
|
||||||
|
/// - LEFT
|
||||||
|
/// - NONE
|
||||||
|
///
|
||||||
|
private final StyleableObjectProperty<DialogTransition> transitionType = new SimpleStyleableObjectProperty<>(
|
||||||
|
StyleableProperties.DIALOG_TRANSITION,
|
||||||
|
JFXDialog.this,
|
||||||
|
"dialogTransition",
|
||||||
|
DialogTransition.CENTER);
|
||||||
|
|
||||||
|
public DialogTransition getTransitionType() {
|
||||||
|
return transitionType == null ? DialogTransition.CENTER : transitionType.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
public StyleableObjectProperty<DialogTransition> transitionTypeProperty() {
|
||||||
|
return this.transitionType;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setTransitionType(DialogTransition transition) {
|
||||||
|
this.transitionType.set(transition);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final class StyleableProperties {
|
||||||
|
private static final CssMetaData<JFXDialog, DialogTransition> DIALOG_TRANSITION =
|
||||||
|
new CssMetaData<JFXDialog, DialogTransition>("-jfx-dialog-transition",
|
||||||
|
DialogTransitionConverter.getInstance(),
|
||||||
|
DialogTransition.CENTER) {
|
||||||
|
@Override
|
||||||
|
public boolean isSettable(JFXDialog control) {
|
||||||
|
return control.transitionType == null || !control.transitionType.isBound();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public StyleableProperty<DialogTransition> getStyleableProperty(JFXDialog control) {
|
||||||
|
return control.transitionTypeProperty();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
private static final List<CssMetaData<? extends Styleable, ?>> CHILD_STYLEABLES;
|
||||||
|
|
||||||
|
static {
|
||||||
|
final List<CssMetaData<? extends Styleable, ?>> styleables =
|
||||||
|
new ArrayList<>(StackPane.getClassCssMetaData());
|
||||||
|
Collections.addAll(styleables,
|
||||||
|
DIALOG_TRANSITION
|
||||||
|
);
|
||||||
|
CHILD_STYLEABLES = Collections.unmodifiableList(styleables);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<CssMetaData<? extends Styleable, ?>> getCssMetaData() {
|
||||||
|
return getClassCssMetaData();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static List<CssMetaData<? extends Styleable, ?>> getClassCssMetaData() {
|
||||||
|
return StyleableProperties.CHILD_STYLEABLES;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/***************************************************************************
|
||||||
|
* *
|
||||||
|
* Custom Events *
|
||||||
|
* *
|
||||||
|
**************************************************************************/
|
||||||
|
|
||||||
|
private final ObjectProperty<EventHandler<? super JFXDialogEvent>> onDialogClosedProperty = new ObjectPropertyBase<EventHandler<? super JFXDialogEvent>>() {
|
||||||
|
@Override
|
||||||
|
protected void invalidated() {
|
||||||
|
setEventHandler(JFXDialogEvent.CLOSED, get());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object getBean() {
|
||||||
|
return JFXDialog.this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getName() {
|
||||||
|
return "onClosed";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Defines a function to be called when the dialog is closed.
|
||||||
|
* Note: it will be triggered after the close animation is finished.
|
||||||
|
*/
|
||||||
|
public ObjectProperty<EventHandler<? super JFXDialogEvent>> onDialogClosedProperty() {
|
||||||
|
return onDialogClosedProperty;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setOnDialogClosed(EventHandler<? super JFXDialogEvent> handler) {
|
||||||
|
onDialogClosedProperty().set(handler);
|
||||||
|
}
|
||||||
|
|
||||||
|
public EventHandler<? super JFXDialogEvent> getOnDialogClosed() {
|
||||||
|
return onDialogClosedProperty().get();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private final ObjectProperty<EventHandler<? super JFXDialogEvent>> onDialogOpenedProperty = new ObjectPropertyBase<EventHandler<? super JFXDialogEvent>>() {
|
||||||
|
@Override
|
||||||
|
protected void invalidated() {
|
||||||
|
setEventHandler(JFXDialogEvent.OPENED, get());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object getBean() {
|
||||||
|
return JFXDialog.this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getName() {
|
||||||
|
return "onOpened";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Defines a function to be called when the dialog is opened.
|
||||||
|
* Note: it will be triggered after the show animation is finished.
|
||||||
|
*/
|
||||||
|
public ObjectProperty<EventHandler<? super JFXDialogEvent>> onDialogOpenedProperty() {
|
||||||
|
return onDialogOpenedProperty;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setOnDialogOpened(EventHandler<? super JFXDialogEvent> handler) {
|
||||||
|
onDialogOpenedProperty().set(handler);
|
||||||
|
}
|
||||||
|
|
||||||
|
public EventHandler<? super JFXDialogEvent> getOnDialogOpened() {
|
||||||
|
return onDialogOpenedProperty().get();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -49,6 +49,7 @@ import org.jackhuang.hmcl.task.Task;
|
|||||||
import org.jackhuang.hmcl.task.TaskExecutor;
|
import org.jackhuang.hmcl.task.TaskExecutor;
|
||||||
import org.jackhuang.hmcl.ui.account.AccountListPage;
|
import org.jackhuang.hmcl.ui.account.AccountListPage;
|
||||||
import org.jackhuang.hmcl.ui.animation.AnimationUtils;
|
import org.jackhuang.hmcl.ui.animation.AnimationUtils;
|
||||||
|
import org.jackhuang.hmcl.ui.animation.Motion;
|
||||||
import org.jackhuang.hmcl.ui.construct.*;
|
import org.jackhuang.hmcl.ui.construct.*;
|
||||||
import org.jackhuang.hmcl.ui.construct.MessageDialogPane.MessageType;
|
import org.jackhuang.hmcl.ui.construct.MessageDialogPane.MessageType;
|
||||||
import org.jackhuang.hmcl.ui.decorator.DecoratorController;
|
import org.jackhuang.hmcl.ui.decorator.DecoratorController;
|
||||||
@@ -328,16 +329,16 @@ public final class Controllers {
|
|||||||
if (AnimationUtils.playWindowAnimation()) {
|
if (AnimationUtils.playWindowAnimation()) {
|
||||||
Timeline timeline = new Timeline(
|
Timeline timeline = new Timeline(
|
||||||
new KeyFrame(Duration.millis(0),
|
new KeyFrame(Duration.millis(0),
|
||||||
new KeyValue(decorator.getDecorator().opacityProperty(), 0, FXUtils.EASE),
|
new KeyValue(decorator.getDecorator().opacityProperty(), 0, Motion.EASE),
|
||||||
new KeyValue(decorator.getDecorator().scaleXProperty(), 0.8, FXUtils.EASE),
|
new KeyValue(decorator.getDecorator().scaleXProperty(), 0.8, Motion.EASE),
|
||||||
new KeyValue(decorator.getDecorator().scaleYProperty(), 0.8, FXUtils.EASE),
|
new KeyValue(decorator.getDecorator().scaleYProperty(), 0.8, Motion.EASE),
|
||||||
new KeyValue(decorator.getDecorator().scaleZProperty(), 0.8, FXUtils.EASE)
|
new KeyValue(decorator.getDecorator().scaleZProperty(), 0.8, Motion.EASE)
|
||||||
),
|
),
|
||||||
new KeyFrame(Duration.millis(600),
|
new KeyFrame(Duration.millis(600),
|
||||||
new KeyValue(decorator.getDecorator().opacityProperty(), 1, FXUtils.EASE),
|
new KeyValue(decorator.getDecorator().opacityProperty(), 1, Motion.EASE),
|
||||||
new KeyValue(decorator.getDecorator().scaleXProperty(), 1, FXUtils.EASE),
|
new KeyValue(decorator.getDecorator().scaleXProperty(), 1, Motion.EASE),
|
||||||
new KeyValue(decorator.getDecorator().scaleYProperty(), 1, FXUtils.EASE),
|
new KeyValue(decorator.getDecorator().scaleYProperty(), 1, Motion.EASE),
|
||||||
new KeyValue(decorator.getDecorator().scaleZProperty(), 1, FXUtils.EASE)
|
new KeyValue(decorator.getDecorator().scaleZProperty(), 1, Motion.EASE)
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
timeline.play();
|
timeline.play();
|
||||||
|
|||||||
@@ -437,28 +437,12 @@ public final class FXUtils {
|
|||||||
installSlowTooltip(node, new Tooltip(tooltip));
|
installSlowTooltip(node, new Tooltip(tooltip));
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void playAnimation(Node node, String animationKey, Timeline timeline) {
|
public static void playAnimation(Node node, String animationKey, Animation animation) {
|
||||||
animationKey = "FXUTILS.ANIMATION." + animationKey;
|
animationKey = "hmcl.animations." + animationKey;
|
||||||
Object oldTimeline = node.getProperties().get(animationKey);
|
if (node.getProperties().get(animationKey) instanceof Animation oldAnimation)
|
||||||
// if (oldTimeline instanceof Timeline) ((Timeline) oldTimeline).stop();
|
oldAnimation.stop();
|
||||||
if (timeline != null) timeline.play();
|
animation.play();
|
||||||
node.getProperties().put(animationKey, timeline);
|
node.getProperties().put(animationKey, animation);
|
||||||
}
|
|
||||||
|
|
||||||
public static <T> Animation playAnimation(Node node, String animationKey, Duration duration, WritableValue<T> property, T from, T to, Interpolator interpolator) {
|
|
||||||
if (from == null) from = property.getValue();
|
|
||||||
if (duration == null || Objects.equals(duration, Duration.ZERO) || Objects.equals(from, to)) {
|
|
||||||
playAnimation(node, animationKey, null);
|
|
||||||
property.setValue(to);
|
|
||||||
return null;
|
|
||||||
} else {
|
|
||||||
Timeline timeline = new Timeline(
|
|
||||||
new KeyFrame(Duration.ZERO, new KeyValue(property, from, interpolator)),
|
|
||||||
new KeyFrame(duration, new KeyValue(property, to, interpolator))
|
|
||||||
);
|
|
||||||
playAnimation(node, animationKey, timeline);
|
|
||||||
return timeline;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void openFolder(Path file) {
|
public static void openFolder(Path file) {
|
||||||
@@ -1349,8 +1333,6 @@ 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) {
|
public static void onEscPressed(Node node, Runnable action) {
|
||||||
node.addEventHandler(KeyEvent.KEY_PRESSED, e -> {
|
node.addEventHandler(KeyEvent.KEY_PRESSED, e -> {
|
||||||
if (e.getCode() == KeyCode.ESCAPE) {
|
if (e.getCode() == KeyCode.ESCAPE) {
|
||||||
|
|||||||
@@ -1,32 +0,0 @@
|
|||||||
/*
|
|
||||||
* Hello Minecraft! Launcher
|
|
||||||
* Copyright (C) 2020 huangyuhui <huanghongxun2008@126.com> 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 <https://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
package org.jackhuang.hmcl.ui.animation;
|
|
||||||
|
|
||||||
import javafx.scene.Node;
|
|
||||||
import javafx.scene.layout.Pane;
|
|
||||||
import javafx.util.Duration;
|
|
||||||
|
|
||||||
public interface AnimationHandler {
|
|
||||||
Duration getDuration();
|
|
||||||
|
|
||||||
Pane getCurrentRoot();
|
|
||||||
|
|
||||||
Node getPreviousNode();
|
|
||||||
|
|
||||||
Node getCurrentNode();
|
|
||||||
}
|
|
||||||
@@ -1,31 +0,0 @@
|
|||||||
/*
|
|
||||||
* Hello Minecraft! Launcher
|
|
||||||
* Copyright (C) 2020 huangyuhui <huanghongxun2008@126.com> 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 <https://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
package org.jackhuang.hmcl.ui.animation;
|
|
||||||
|
|
||||||
import javafx.animation.KeyFrame;
|
|
||||||
import org.jetbrains.annotations.Nullable;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
public interface AnimationProducer {
|
|
||||||
void init(AnimationHandler handler);
|
|
||||||
|
|
||||||
List<KeyFrame> animate(AnimationHandler handler);
|
|
||||||
|
|
||||||
@Nullable AnimationProducer opposite();
|
|
||||||
}
|
|
||||||
@@ -20,33 +20,23 @@ package org.jackhuang.hmcl.ui.animation;
|
|||||||
import javafx.animation.Interpolator;
|
import javafx.animation.Interpolator;
|
||||||
import javafx.animation.KeyFrame;
|
import javafx.animation.KeyFrame;
|
||||||
import javafx.animation.KeyValue;
|
import javafx.animation.KeyValue;
|
||||||
|
import javafx.animation.Timeline;
|
||||||
|
import javafx.scene.Node;
|
||||||
|
import javafx.scene.layout.Pane;
|
||||||
import javafx.util.Duration;
|
import javafx.util.Duration;
|
||||||
import org.jackhuang.hmcl.ui.FXUtils;
|
|
||||||
import org.jetbrains.annotations.Nullable;
|
|
||||||
|
|
||||||
import java.util.Arrays;
|
public enum ContainerAnimations implements TransitionPane.AnimationProducer {
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
public enum ContainerAnimations implements AnimationProducer {
|
|
||||||
NONE {
|
NONE {
|
||||||
@Override
|
@Override
|
||||||
public void init(AnimationHandler c) {
|
public Timeline animate(
|
||||||
c.getPreviousNode().setTranslateX(0);
|
Pane container, Node previousNode, Node nextNode,
|
||||||
c.getPreviousNode().setTranslateY(0);
|
Duration duration, Interpolator interpolator) {
|
||||||
c.getPreviousNode().setScaleX(1);
|
return new Timeline();
|
||||||
c.getPreviousNode().setScaleY(1);
|
|
||||||
c.getPreviousNode().setOpacity(1);
|
|
||||||
c.getCurrentNode().setTranslateX(0);
|
|
||||||
c.getCurrentNode().setTranslateY(0);
|
|
||||||
c.getCurrentNode().setScaleX(1);
|
|
||||||
c.getCurrentNode().setScaleY(1);
|
|
||||||
c.getCurrentNode().setOpacity(1);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<KeyFrame> animate(AnimationHandler c) {
|
public TransitionPane.AnimationProducer opposite() {
|
||||||
return Collections.emptyList();
|
return this;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -55,151 +45,48 @@ public enum ContainerAnimations implements AnimationProducer {
|
|||||||
*/
|
*/
|
||||||
FADE {
|
FADE {
|
||||||
@Override
|
@Override
|
||||||
public void init(AnimationHandler c) {
|
public Timeline animate(
|
||||||
c.getPreviousNode().setTranslateX(0);
|
Pane container, Node previousNode, Node nextNode,
|
||||||
c.getPreviousNode().setTranslateY(0);
|
Duration duration, Interpolator interpolator) {
|
||||||
c.getPreviousNode().setScaleX(1);
|
return new Timeline(new KeyFrame(Duration.ZERO,
|
||||||
c.getPreviousNode().setScaleY(1);
|
new KeyValue(previousNode.opacityProperty(), 1, interpolator),
|
||||||
c.getPreviousNode().setOpacity(1);
|
new KeyValue(nextNode.opacityProperty(), 0, interpolator)),
|
||||||
c.getCurrentNode().setTranslateX(0);
|
new KeyFrame(duration,
|
||||||
c.getCurrentNode().setTranslateY(0);
|
new KeyValue(previousNode.opacityProperty(), 0, interpolator),
|
||||||
c.getCurrentNode().setScaleX(1);
|
new KeyValue(nextNode.opacityProperty(), 1, interpolator)));
|
||||||
c.getCurrentNode().setScaleY(1);
|
|
||||||
c.getCurrentNode().setOpacity(0);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<KeyFrame> animate(AnimationHandler c) {
|
public TransitionPane.AnimationProducer opposite() {
|
||||||
return Arrays.asList(new KeyFrame(Duration.ZERO,
|
return this;
|
||||||
new KeyValue(c.getPreviousNode().opacityProperty(), 1, Interpolator.EASE_BOTH),
|
|
||||||
new KeyValue(c.getCurrentNode().opacityProperty(), 0, Interpolator.EASE_BOTH)),
|
|
||||||
new KeyFrame(c.getDuration(),
|
|
||||||
new KeyValue(c.getPreviousNode().opacityProperty(), 0, Interpolator.EASE_BOTH),
|
|
||||||
new KeyValue(c.getCurrentNode().opacityProperty(), 1, Interpolator.EASE_BOTH)));
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
|
||||||
* A fade between the old and new view
|
|
||||||
*/
|
|
||||||
FADE_IN {
|
|
||||||
@Override
|
|
||||||
public void init(AnimationHandler c) {
|
|
||||||
c.getCurrentNode().setTranslateX(0);
|
|
||||||
c.getCurrentNode().setTranslateY(0);
|
|
||||||
c.getCurrentNode().setScaleX(1);
|
|
||||||
c.getCurrentNode().setScaleY(1);
|
|
||||||
c.getCurrentNode().setOpacity(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public List<KeyFrame> animate(AnimationHandler c) {
|
|
||||||
return Arrays.asList(new KeyFrame(Duration.ZERO,
|
|
||||||
new KeyValue(c.getCurrentNode().opacityProperty(), 0, FXUtils.SINE)),
|
|
||||||
new KeyFrame(c.getDuration(),
|
|
||||||
new KeyValue(c.getCurrentNode().opacityProperty(), 1, FXUtils.SINE)));
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A fade between the old and new view
|
|
||||||
*/
|
|
||||||
FADE_OUT {
|
|
||||||
@Override
|
|
||||||
public void init(AnimationHandler c) {
|
|
||||||
c.getCurrentNode().setTranslateX(0);
|
|
||||||
c.getCurrentNode().setTranslateY(0);
|
|
||||||
c.getCurrentNode().setScaleX(1);
|
|
||||||
c.getCurrentNode().setScaleY(1);
|
|
||||||
c.getCurrentNode().setOpacity(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public List<KeyFrame> animate(AnimationHandler c) {
|
|
||||||
return Arrays.asList(new KeyFrame(Duration.ZERO,
|
|
||||||
new KeyValue(c.getCurrentNode().opacityProperty(), 1, FXUtils.SINE)),
|
|
||||||
new KeyFrame(c.getDuration(),
|
|
||||||
new KeyValue(c.getCurrentNode().opacityProperty(), 0, FXUtils.SINE)));
|
|
||||||
}
|
|
||||||
},
|
|
||||||
/**
|
|
||||||
* A zoom effect
|
|
||||||
*/
|
|
||||||
ZOOM_IN {
|
|
||||||
@Override
|
|
||||||
public void init(AnimationHandler c) {
|
|
||||||
c.getPreviousNode().setTranslateX(0);
|
|
||||||
c.getPreviousNode().setTranslateY(0);
|
|
||||||
c.getPreviousNode().setScaleX(1);
|
|
||||||
c.getPreviousNode().setScaleY(1);
|
|
||||||
c.getPreviousNode().setOpacity(1);
|
|
||||||
c.getCurrentNode().setTranslateX(0);
|
|
||||||
c.getCurrentNode().setTranslateY(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public List<KeyFrame> animate(AnimationHandler c) {
|
|
||||||
return Arrays.asList(new KeyFrame(Duration.ZERO,
|
|
||||||
new KeyValue(c.getPreviousNode().scaleXProperty(), 1, Interpolator.EASE_BOTH),
|
|
||||||
new KeyValue(c.getPreviousNode().scaleYProperty(), 1, Interpolator.EASE_BOTH),
|
|
||||||
new KeyValue(c.getPreviousNode().opacityProperty(), 1, Interpolator.EASE_BOTH)),
|
|
||||||
new KeyFrame(c.getDuration(),
|
|
||||||
new KeyValue(c.getPreviousNode().scaleXProperty(), 4, Interpolator.EASE_BOTH),
|
|
||||||
new KeyValue(c.getPreviousNode().scaleYProperty(), 4, Interpolator.EASE_BOTH),
|
|
||||||
new KeyValue(c.getPreviousNode().opacityProperty(), 0, Interpolator.EASE_BOTH)));
|
|
||||||
}
|
|
||||||
},
|
|
||||||
/**
|
|
||||||
* A zoom effect
|
|
||||||
*/
|
|
||||||
ZOOM_OUT {
|
|
||||||
@Override
|
|
||||||
public void init(AnimationHandler c) {
|
|
||||||
c.getPreviousNode().setTranslateX(0);
|
|
||||||
c.getPreviousNode().setTranslateY(0);
|
|
||||||
c.getPreviousNode().setScaleX(1);
|
|
||||||
c.getPreviousNode().setScaleY(1);
|
|
||||||
c.getPreviousNode().setOpacity(1);
|
|
||||||
c.getCurrentNode().setTranslateX(0);
|
|
||||||
c.getCurrentNode().setTranslateY(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public List<KeyFrame> animate(AnimationHandler c) {
|
|
||||||
return Arrays.asList(new KeyFrame(Duration.ZERO,
|
|
||||||
new KeyValue(c.getPreviousNode().scaleXProperty(), 1, Interpolator.EASE_BOTH),
|
|
||||||
new KeyValue(c.getPreviousNode().scaleYProperty(), 1, Interpolator.EASE_BOTH),
|
|
||||||
new KeyValue(c.getPreviousNode().opacityProperty(), 1, Interpolator.EASE_BOTH)),
|
|
||||||
new KeyFrame(c.getDuration(),
|
|
||||||
new KeyValue(c.getPreviousNode().scaleXProperty(), 0, Interpolator.EASE_BOTH),
|
|
||||||
new KeyValue(c.getPreviousNode().scaleYProperty(), 0, Interpolator.EASE_BOTH),
|
|
||||||
new KeyValue(c.getPreviousNode().opacityProperty(), 0, Interpolator.EASE_BOTH)));
|
|
||||||
}
|
|
||||||
},
|
|
||||||
/**
|
/**
|
||||||
* A swipe effect
|
* A swipe effect
|
||||||
*/
|
*/
|
||||||
SWIPE_LEFT {
|
SWIPE_LEFT {
|
||||||
@Override
|
@Override
|
||||||
public void init(AnimationHandler c) {
|
public void init(TransitionPane container, Node previousNode, Node nextNode) {
|
||||||
c.getPreviousNode().setScaleX(1);
|
super.init(container, previousNode, nextNode);
|
||||||
c.getPreviousNode().setScaleY(1);
|
nextNode.setTranslateX(container.getWidth());
|
||||||
c.getPreviousNode().setOpacity(0);
|
|
||||||
c.getPreviousNode().setTranslateX(0);
|
|
||||||
c.getCurrentNode().setScaleX(1);
|
|
||||||
c.getCurrentNode().setScaleY(1);
|
|
||||||
c.getCurrentNode().setOpacity(1);
|
|
||||||
c.getCurrentNode().setTranslateX(c.getCurrentRoot().getWidth());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<KeyFrame> animate(AnimationHandler c) {
|
public Timeline animate(
|
||||||
return Arrays.asList(new KeyFrame(Duration.ZERO,
|
Pane container, Node previousNode, Node nextNode,
|
||||||
new KeyValue(c.getCurrentNode().translateXProperty(), c.getCurrentRoot().getWidth(), Interpolator.EASE_BOTH),
|
Duration duration, Interpolator interpolator) {
|
||||||
new KeyValue(c.getPreviousNode().translateXProperty(), 0, Interpolator.EASE_BOTH)),
|
return new Timeline(new KeyFrame(Duration.ZERO,
|
||||||
new KeyFrame(c.getDuration(),
|
new KeyValue(nextNode.translateXProperty(), container.getWidth(), interpolator),
|
||||||
new KeyValue(c.getCurrentNode().translateXProperty(), 0, Interpolator.EASE_BOTH),
|
new KeyValue(previousNode.translateXProperty(), 0, interpolator)),
|
||||||
new KeyValue(c.getPreviousNode().translateXProperty(), -c.getCurrentRoot().getWidth(), Interpolator.EASE_BOTH)));
|
new KeyFrame(duration,
|
||||||
|
new KeyValue(nextNode.translateXProperty(), 0, interpolator),
|
||||||
|
new KeyValue(previousNode.translateXProperty(), -container.getWidth(), interpolator)));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public TransitionPane.AnimationProducer opposite() {
|
||||||
|
return SWIPE_RIGHT;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -208,105 +95,123 @@ public enum ContainerAnimations implements AnimationProducer {
|
|||||||
*/
|
*/
|
||||||
SWIPE_RIGHT {
|
SWIPE_RIGHT {
|
||||||
@Override
|
@Override
|
||||||
public void init(AnimationHandler c) {
|
public void init(TransitionPane container, Node previousNode, Node nextNode) {
|
||||||
c.getPreviousNode().setScaleX(1);
|
super.init(container, previousNode, nextNode);
|
||||||
c.getPreviousNode().setScaleY(1);
|
nextNode.setTranslateX(-container.getWidth());
|
||||||
c.getPreviousNode().setOpacity(0);
|
|
||||||
c.getPreviousNode().setTranslateX(0);
|
|
||||||
c.getCurrentNode().setScaleX(1);
|
|
||||||
c.getCurrentNode().setScaleY(1);
|
|
||||||
c.getCurrentNode().setOpacity(1);
|
|
||||||
c.getCurrentNode().setTranslateX(-c.getCurrentRoot().getWidth());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<KeyFrame> animate(AnimationHandler c) {
|
public Timeline animate(
|
||||||
return Arrays.asList(new KeyFrame(Duration.ZERO,
|
Pane container, Node previousNode, Node nextNode,
|
||||||
new KeyValue(c.getCurrentNode().translateXProperty(), -c.getCurrentRoot().getWidth(), Interpolator.EASE_BOTH),
|
Duration duration, Interpolator interpolator) {
|
||||||
new KeyValue(c.getPreviousNode().translateXProperty(), 0, Interpolator.EASE_BOTH)),
|
return new Timeline(new KeyFrame(Duration.ZERO,
|
||||||
new KeyFrame(c.getDuration(),
|
new KeyValue(nextNode.translateXProperty(), -container.getWidth(), interpolator),
|
||||||
new KeyValue(c.getCurrentNode().translateXProperty(), 0, Interpolator.EASE_BOTH),
|
new KeyValue(previousNode.translateXProperty(), 0, interpolator)),
|
||||||
new KeyValue(c.getPreviousNode().translateXProperty(), c.getCurrentRoot().getWidth(), Interpolator.EASE_BOTH)));
|
new KeyFrame(duration,
|
||||||
|
new KeyValue(nextNode.translateXProperty(), 0, interpolator),
|
||||||
|
new KeyValue(previousNode.translateXProperty(), container.getWidth(), interpolator)));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public TransitionPane.AnimationProducer opposite() {
|
||||||
|
return SWIPE_LEFT;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
SWIPE_LEFT_FADE_SHORT {
|
/// @see <a href="https://m3.material.io/styles/motion/transitions/transition-patterns">Transitions - Material Design 3</a>
|
||||||
|
FORWARD {
|
||||||
@Override
|
@Override
|
||||||
public void init(AnimationHandler c) {
|
public Timeline animate(
|
||||||
c.getPreviousNode().setScaleX(1);
|
Pane container, Node previousNode, Node nextNode,
|
||||||
c.getPreviousNode().setScaleY(1);
|
Duration duration, Interpolator interpolator) {
|
||||||
c.getPreviousNode().setOpacity(0);
|
double offset = container.getWidth() > 0 ? container.getWidth() * 0.2 : 50;
|
||||||
c.getPreviousNode().setTranslateX(0);
|
return new Timeline(
|
||||||
c.getCurrentNode().setScaleX(1);
|
new KeyFrame(Duration.ZERO,
|
||||||
c.getCurrentNode().setScaleY(1);
|
new KeyValue(previousNode.translateXProperty(), 0, interpolator),
|
||||||
c.getCurrentNode().setOpacity(1);
|
new KeyValue(previousNode.opacityProperty(), 1, interpolator),
|
||||||
c.getCurrentNode().setTranslateX(c.getCurrentRoot().getWidth());
|
new KeyValue(nextNode.opacityProperty(), 0, interpolator)),
|
||||||
|
new KeyFrame(duration.multiply(0.5),
|
||||||
|
new KeyValue(previousNode.translateXProperty(), -offset, interpolator),
|
||||||
|
new KeyValue(previousNode.opacityProperty(), 0, interpolator),
|
||||||
|
|
||||||
|
new KeyValue(nextNode.opacityProperty(), 0, interpolator),
|
||||||
|
new KeyValue(nextNode.translateXProperty(), offset, interpolator)),
|
||||||
|
new KeyFrame(duration,
|
||||||
|
new KeyValue(nextNode.opacityProperty(), 1, interpolator),
|
||||||
|
new KeyValue(nextNode.translateXProperty(), 0, interpolator))
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<KeyFrame> animate(AnimationHandler c) {
|
public TransitionPane.AnimationProducer opposite() {
|
||||||
return Arrays.asList(new KeyFrame(Duration.ZERO,
|
return BACKWARD;
|
||||||
new KeyValue(c.getCurrentNode().translateXProperty(), 50, Interpolator.EASE_BOTH),
|
|
||||||
new KeyValue(c.getPreviousNode().translateXProperty(), 0, Interpolator.EASE_BOTH),
|
|
||||||
new KeyValue(c.getCurrentNode().opacityProperty(), 0, Interpolator.EASE_BOTH),
|
|
||||||
new KeyValue(c.getPreviousNode().opacityProperty(), 1, Interpolator.EASE_BOTH)),
|
|
||||||
new KeyFrame(c.getDuration(),
|
|
||||||
new KeyValue(c.getCurrentNode().translateXProperty(), 0, Interpolator.EASE_BOTH),
|
|
||||||
new KeyValue(c.getPreviousNode().translateXProperty(), -50, Interpolator.EASE_BOTH),
|
|
||||||
new KeyValue(c.getCurrentNode().opacityProperty(), 1, Interpolator.EASE_BOTH),
|
|
||||||
new KeyValue(c.getPreviousNode().opacityProperty(), 0, Interpolator.EASE_BOTH)));
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
SWIPE_RIGHT_FADE_SHORT {
|
/// @see <a href="https://m3.material.io/styles/motion/transitions/transition-patterns">Transitions - Material Design 3</a>
|
||||||
|
BACKWARD {
|
||||||
@Override
|
@Override
|
||||||
public void init(AnimationHandler c) {
|
public Timeline animate(
|
||||||
c.getPreviousNode().setScaleX(1);
|
Pane container, Node previousNode, Node nextNode,
|
||||||
c.getPreviousNode().setScaleY(1);
|
Duration duration, Interpolator interpolator) {
|
||||||
c.getPreviousNode().setOpacity(0);
|
double offset = container.getWidth() > 0 ? container.getWidth() * 0.2 : 50;
|
||||||
c.getPreviousNode().setTranslateX(0);
|
return new Timeline(
|
||||||
c.getCurrentNode().setScaleX(1);
|
new KeyFrame(Duration.ZERO,
|
||||||
c.getCurrentNode().setScaleY(1);
|
new KeyValue(previousNode.translateXProperty(), 0, interpolator),
|
||||||
c.getCurrentNode().setOpacity(1);
|
new KeyValue(previousNode.opacityProperty(), 1, interpolator),
|
||||||
c.getCurrentNode().setTranslateX(c.getCurrentRoot().getWidth());
|
new KeyValue(nextNode.opacityProperty(), 0, interpolator)),
|
||||||
|
new KeyFrame(duration.multiply(0.5),
|
||||||
|
new KeyValue(previousNode.translateXProperty(), offset, interpolator),
|
||||||
|
new KeyValue(previousNode.opacityProperty(), 0, interpolator),
|
||||||
|
|
||||||
|
new KeyValue(nextNode.opacityProperty(), 0, interpolator),
|
||||||
|
new KeyValue(nextNode.translateXProperty(), -offset, interpolator)),
|
||||||
|
new KeyFrame(duration,
|
||||||
|
new KeyValue(nextNode.opacityProperty(), 1, interpolator),
|
||||||
|
new KeyValue(nextNode.translateXProperty(), 0, interpolator))
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<KeyFrame> animate(AnimationHandler c) {
|
public TransitionPane.AnimationProducer opposite() {
|
||||||
return Arrays.asList(new KeyFrame(Duration.ZERO,
|
return FORWARD;
|
||||||
new KeyValue(c.getCurrentNode().translateXProperty(), -50, Interpolator.EASE_BOTH),
|
|
||||||
new KeyValue(c.getPreviousNode().translateXProperty(), 0, Interpolator.EASE_BOTH),
|
|
||||||
new KeyValue(c.getCurrentNode().opacityProperty(), 0, Interpolator.EASE_BOTH),
|
|
||||||
new KeyValue(c.getPreviousNode().opacityProperty(), 1, Interpolator.EASE_BOTH)),
|
|
||||||
new KeyFrame(c.getDuration(),
|
|
||||||
new KeyValue(c.getCurrentNode().translateXProperty(), 0, Interpolator.EASE_BOTH),
|
|
||||||
new KeyValue(c.getPreviousNode().translateXProperty(), 50, Interpolator.EASE_BOTH),
|
|
||||||
new KeyValue(c.getCurrentNode().opacityProperty(), 1, Interpolator.EASE_BOTH),
|
|
||||||
new KeyValue(c.getPreviousNode().opacityProperty(), 0, Interpolator.EASE_BOTH)));
|
|
||||||
}
|
}
|
||||||
};
|
},
|
||||||
|
|
||||||
private ContainerAnimations opposite;
|
/// Imitates the animation when switching tabs in the Windows 11 Settings interface
|
||||||
|
SLIDE_UP_FADE_IN {
|
||||||
|
@Override
|
||||||
|
public Timeline animate(
|
||||||
|
Pane container, Node previousNode, Node nextNode,
|
||||||
|
Duration duration, Interpolator interpolator) {
|
||||||
|
double offset = container.getHeight() > 0 ? container.getHeight() * 0.2 : 50;
|
||||||
|
return new Timeline(
|
||||||
|
new KeyFrame(Duration.ZERO,
|
||||||
|
new KeyValue(previousNode.translateYProperty(), 0, interpolator),
|
||||||
|
new KeyValue(previousNode.opacityProperty(), 1, interpolator),
|
||||||
|
new KeyValue(nextNode.opacityProperty(), 0, interpolator),
|
||||||
|
new KeyValue(nextNode.translateYProperty(), offset, interpolator)),
|
||||||
|
new KeyFrame(duration.multiply(0.5),
|
||||||
|
new KeyValue(previousNode.opacityProperty(), 0, interpolator)),
|
||||||
|
new KeyFrame(duration,
|
||||||
|
new KeyValue(nextNode.opacityProperty(), 1, interpolator),
|
||||||
|
new KeyValue(nextNode.translateYProperty(), 0, interpolator))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
;
|
||||||
|
|
||||||
static {
|
protected static void reset(Node node) {
|
||||||
NONE.opposite = NONE;
|
node.setTranslateX(0);
|
||||||
FADE.opposite = FADE;
|
node.setTranslateY(0);
|
||||||
SWIPE_LEFT.opposite = SWIPE_RIGHT;
|
node.setScaleX(1);
|
||||||
SWIPE_RIGHT.opposite = SWIPE_LEFT;
|
node.setScaleY(1);
|
||||||
FADE_IN.opposite = FADE_OUT;
|
node.setOpacity(1);
|
||||||
FADE_OUT.opposite = FADE_IN;
|
|
||||||
ZOOM_IN.opposite = ZOOM_OUT;
|
|
||||||
ZOOM_OUT.opposite = ZOOM_IN;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public abstract void init(AnimationHandler handler);
|
public void init(TransitionPane container, Node previousNode, Node nextNode) {
|
||||||
|
reset(previousNode);
|
||||||
@Override
|
reset(nextNode);
|
||||||
public abstract List<KeyFrame> animate(AnimationHandler handler);
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public @Nullable ContainerAnimations opposite() {
|
|
||||||
return opposite;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
781
HMCL/src/main/java/org/jackhuang/hmcl/ui/animation/Motion.java
Normal file
781
HMCL/src/main/java/org/jackhuang/hmcl/ui/animation/Motion.java
Normal file
@@ -0,0 +1,781 @@
|
|||||||
|
/*
|
||||||
|
* Hello Minecraft! Launcher
|
||||||
|
* Copyright (C) 2025 huangyuhui <huanghongxun2008@126.com> 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 <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
package org.jackhuang.hmcl.ui.animation;
|
||||||
|
|
||||||
|
import javafx.animation.Interpolator;
|
||||||
|
import javafx.util.Duration;
|
||||||
|
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
/// @author Glavo
|
||||||
|
/// @see <a href="https://api.flutter.dev/flutter/animation/Curves-class.html">Flutter Curves</a>
|
||||||
|
public final class Motion {
|
||||||
|
|
||||||
|
//region Curves
|
||||||
|
|
||||||
|
/// A linear animation curve.
|
||||||
|
///
|
||||||
|
/// This is the identity map over the unit interval: its [Interpolator#curve(double)]
|
||||||
|
/// method returns its input unmodified. This is useful as a default curve for
|
||||||
|
/// cases where a [Interpolator] is required but no actual curve is desired.
|
||||||
|
///
|
||||||
|
/// @see <a href="https://flutter.github.io/assets-for-api-docs/assets/animation/curve_linear.mp4">curve_linear.mp4</a>
|
||||||
|
public static final Interpolator LINEAR = Interpolator.LINEAR;
|
||||||
|
|
||||||
|
/// The emphasizedAccelerate easing curve in the Material specification.
|
||||||
|
///
|
||||||
|
/// See also:
|
||||||
|
///
|
||||||
|
/// * [M3 guidelines: Easing tokens](https://m3.material.io/styles/motion/easing-and-duration/tokens-specs#433b1153-2ea3-4fe2-9748-803a47bc97ee)
|
||||||
|
/// * [M3 guidelines: Applying easing and duration](https://m3.material.io/styles/motion/easing-and-duration/applying-easing-and-duration)
|
||||||
|
public static final Interpolator EMPHASIZED_ACCELERATE = new Cubic(0.3, 0.0, 0.8, 0.15);
|
||||||
|
|
||||||
|
/// The emphasizedDecelerate easing curve in the Material specification.
|
||||||
|
///
|
||||||
|
/// See also:
|
||||||
|
///
|
||||||
|
/// * [M3 guidelines: Easing tokens](https://m3.material.io/styles/motion/easing-and-duration/tokens-specs#433b1153-2ea3-4fe2-9748-803a47bc97ee)
|
||||||
|
/// * [M3 guidelines: Applying easing and duration](https://m3.material.io/styles/motion/easing-and-duration/applying-easing-and-duration)
|
||||||
|
public static final Interpolator EMPHASIZED_DECELERATE = new Cubic(0.05, 0.7, 0.1, 1.0);
|
||||||
|
|
||||||
|
/// The standard easing curve in the Material specification.
|
||||||
|
///
|
||||||
|
/// See also:
|
||||||
|
///
|
||||||
|
/// * [M3 guidelines: Easing tokens](https://m3.material.io/styles/motion/easing-and-duration/tokens-specs#433b1153-2ea3-4fe2-9748-803a47bc97ee)
|
||||||
|
/// * [M3 guidelines: Applying easing and duration](https://m3.material.io/styles/motion/easing-and-duration/applying-easing-and-duration)
|
||||||
|
public static final Interpolator STANDARD = new Cubic(0.2, 0.0, 0.0, 1.0);
|
||||||
|
|
||||||
|
/// The standardAccelerate easing curve in the Material specification.
|
||||||
|
///
|
||||||
|
/// See also:
|
||||||
|
///
|
||||||
|
/// * [M3 guidelines: Easing tokens](https://m3.material.io/styles/motion/easing-and-duration/tokens-specs#433b1153-2ea3-4fe2-9748-803a47bc97ee)
|
||||||
|
/// * [M3 guidelines: Applying easing and duration](https://m3.material.io/styles/motion/easing-and-duration/applying-easing-and-duration)
|
||||||
|
public static final Interpolator STANDARD_ACCELERATE = new Cubic(0.3, 0.0, 1.0, 1.0);
|
||||||
|
|
||||||
|
/// The standardDecelerate easing curve in the Material specification.
|
||||||
|
///
|
||||||
|
/// See also:
|
||||||
|
///
|
||||||
|
/// * [M3 guidelines: Easing tokens](https://m3.material.io/styles/motion/easing-and-duration/tokens-specs#433b1153-2ea3-4fe2-9748-803a47bc97ee)
|
||||||
|
/// * [M3 guidelines: Applying easing and duration](https://m3.material.io/styles/motion/easing-and-duration/applying-easing-and-duration)
|
||||||
|
public static Interpolator STANDARD_DECELERATE = new Cubic(0.0, 0.0, 0.0, 1.0);
|
||||||
|
|
||||||
|
/// The legacyDecelerate easing curve in the Material specification.
|
||||||
|
///
|
||||||
|
/// See also:
|
||||||
|
///
|
||||||
|
/// * [M3 guidelines: Easing tokens](https://m3.material.io/styles/motion/easing-and-duration/tokens-specs#433b1153-2ea3-4fe2-9748-803a47bc97ee)
|
||||||
|
/// * [M3 guidelines: Applying easing and duration](https://m3.material.io/styles/motion/easing-and-duration/applying-easing-and-duration)
|
||||||
|
public static Interpolator LEGACY_DECELERATE = new Cubic(0.0, 0.0, 0.2, 1.0);
|
||||||
|
|
||||||
|
/// The legacyAccelerate easing curve in the Material specification.
|
||||||
|
///
|
||||||
|
/// See also:
|
||||||
|
///
|
||||||
|
/// * [M3 guidelines: Easing tokens](https://m3.material.io/styles/motion/easing-and-duration/tokens-specs#433b1153-2ea3-4fe2-9748-803a47bc97ee)
|
||||||
|
/// * [M3 guidelines: Applying easing and duration](https://m3.material.io/styles/motion/easing-and-duration/applying-easing-and-duration)
|
||||||
|
public static Interpolator LEGACY_ACCELERATE = new Cubic(0.4, 0.0, 1.0, 1.0);
|
||||||
|
|
||||||
|
/// The legacy easing curve in the Material specification.
|
||||||
|
///
|
||||||
|
/// See also:
|
||||||
|
///
|
||||||
|
/// * [M3 guidelines: Easing tokens](https://m3.material.io/styles/motion/easing-and-duration/tokens-specs#433b1153-2ea3-4fe2-9748-803a47bc97ee)
|
||||||
|
/// * [M3 guidelines: Applying easing and duration](https://m3.material.io/styles/motion/easing-and-duration/applying-easing-and-duration)
|
||||||
|
public static Interpolator LEGACY = new Cubic(0.4, 0.0, 0.2, 1.0);
|
||||||
|
|
||||||
|
/// A cubic animation curve that speeds up quickly and ends slowly.
|
||||||
|
///
|
||||||
|
/// This is the same as the CSS easing function `ease`.
|
||||||
|
///
|
||||||
|
/// @see <a href="https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease.mp4">curve_ease.mp4</a>
|
||||||
|
public static final Interpolator EASE = new Cubic(0.25, 0.1, 0.25, 1.0);
|
||||||
|
|
||||||
|
/// A cubic animation curve that starts slowly and ends quickly.
|
||||||
|
///
|
||||||
|
/// This is the same as the CSS easing function `ease-in`.
|
||||||
|
///
|
||||||
|
/// @see <a href="https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_in.mp4">curve_ease_in.mp4</a>
|
||||||
|
public static final Interpolator EASE_IN = new Cubic(0.42, 0.0, 1.0, 1.0);
|
||||||
|
|
||||||
|
/// A cubic animation curve that starts slowly and ends linearly.
|
||||||
|
///
|
||||||
|
/// The symmetric animation to [#LINEAR_TO_EASE_OUT].
|
||||||
|
///
|
||||||
|
/// @see <a href="https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_in_to_linear.mp4">curve_ease_in_to_linear.mp4</a>
|
||||||
|
public static final Interpolator EASE_IN_TO_LINEAR = new Cubic(0.67, 0.03, 0.65, 0.09);
|
||||||
|
|
||||||
|
/// A cubic animation curve that starts slowly and ends quickly. This is
|
||||||
|
/// similar to [#EASE_IN], but with sinusoidal easing for a slightly less
|
||||||
|
/// abrupt beginning and end. Nonetheless, the result is quite gentle and is
|
||||||
|
/// hard to distinguish from [#linear] at a glance.
|
||||||
|
///
|
||||||
|
/// Derived from Robert Penner’s easing functions.
|
||||||
|
///
|
||||||
|
/// @see <a href="https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_in_sine.mp4">curve_ease_in_sine.mp4</a>
|
||||||
|
public static final Interpolator EASE_IN_SINE = new Cubic(0.47, 0.0, 0.745, 0.715);
|
||||||
|
|
||||||
|
/// A cubic animation curve that starts slowly and ends quickly. Based on a
|
||||||
|
/// quadratic equation where `f(t) = t²`, this is effectively the inverse of
|
||||||
|
/// [#decelerate].
|
||||||
|
///
|
||||||
|
/// Compared to [#EASE_IN_SINE], this curve is slightly steeper.
|
||||||
|
///
|
||||||
|
/// Derived from Robert Penner’s easing functions.
|
||||||
|
///
|
||||||
|
/// @see <a href="https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_in_quad.mp4">curve_ease_in_quad.mp4</a>
|
||||||
|
public static final Interpolator EASE_IN_QUAD = new Cubic(0.55, 0.085, 0.68, 0.53);
|
||||||
|
|
||||||
|
/// A cubic animation curve that starts slowly and ends quickly. This curve is
|
||||||
|
/// based on a cubic equation where `f(t) = t³`. The result is a safe sweet
|
||||||
|
/// spot when choosing a curve for widgets animating off the viewport.
|
||||||
|
///
|
||||||
|
/// Compared to [#EASE_IN_QUAD], this curve is slightly steeper.
|
||||||
|
///
|
||||||
|
/// Derived from Robert Penner’s easing functions.
|
||||||
|
///
|
||||||
|
/// @see <a href="https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_in_cubic.mp4">curve_ease_in_cubic.mp4</a>
|
||||||
|
public static final Interpolator EASE_IN_CUBIC = new Cubic(0.55, 0.055, 0.675, 0.19);
|
||||||
|
|
||||||
|
/// A cubic animation curve that starts slowly and ends quickly. This curve is
|
||||||
|
/// based on a quartic equation where `f(t) = t⁴`.
|
||||||
|
///
|
||||||
|
/// Animations using this curve or steeper curves will benefit from a longer
|
||||||
|
/// duration to avoid motion feeling unnatural.
|
||||||
|
///
|
||||||
|
/// Compared to [#EASE_IN_CUBIC], this curve is slightly steeper.
|
||||||
|
///
|
||||||
|
/// Derived from Robert Penner’s easing functions.
|
||||||
|
///
|
||||||
|
/// @see <a href="https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_in_quart.mp4">curve_ease_in_quart.mp4</a>
|
||||||
|
public static final Interpolator EASE_IN_QUART = new Cubic(0.895, 0.03, 0.685, 0.22);
|
||||||
|
|
||||||
|
/// A cubic animation curve that starts slowly and ends quickly. This curve is
|
||||||
|
/// based on a quintic equation where `f(t) = t⁵`.
|
||||||
|
///
|
||||||
|
/// Compared to [#EASE_IN_QUART], this curve is slightly steeper.
|
||||||
|
///
|
||||||
|
/// Derived from Robert Penner’s easing functions.
|
||||||
|
///
|
||||||
|
/// @see <a href="https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_in_quint.mp4">curve_ease_in_quint.mp4</a>
|
||||||
|
public static final Interpolator EASE_IN_QUINT = new Cubic(0.755, 0.05, 0.855, 0.06);
|
||||||
|
|
||||||
|
/// A cubic animation curve that starts slowly and ends quickly. This curve is
|
||||||
|
/// based on an exponential equation where `f(t) = 2¹⁰⁽ᵗ⁻¹⁾`.
|
||||||
|
///
|
||||||
|
/// Using this curve can give your animations extra flare, but a longer
|
||||||
|
/// duration may need to be used to compensate for the steepness of the curve.
|
||||||
|
///
|
||||||
|
/// Compared to [#EASE_IN_QUINT], this curve is slightly steeper.
|
||||||
|
///
|
||||||
|
/// Derived from Robert Penner’s easing functions.
|
||||||
|
///
|
||||||
|
/// @see <a href="https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_in_expo.mp4">curve_ease_in_expo.mp4</a>
|
||||||
|
public static final Interpolator EASE_IN_EXPO = new Cubic(0.95, 0.05, 0.795, 0.035);
|
||||||
|
|
||||||
|
/// A cubic animation curve that starts slowly and ends quickly. This curve is
|
||||||
|
/// effectively the bottom-right quarter of a circle.
|
||||||
|
///
|
||||||
|
/// Like [#EASE_IN_EXPO], this curve is fairly dramatic and will reduce
|
||||||
|
/// the clarity of an animation if not given a longer duration.
|
||||||
|
///
|
||||||
|
/// Derived from Robert Penner’s easing functions.
|
||||||
|
///
|
||||||
|
/// @see <a href="https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_in_circ.mp4">curve_ease_in_circ.mp4</a>
|
||||||
|
public static final Interpolator EASE_IN_CIRC = new Cubic(0.6, 0.04, 0.98, 0.335);
|
||||||
|
|
||||||
|
/// A cubic animation curve that starts slowly and ends quickly. This curve
|
||||||
|
/// is similar to [#elasticIn] in that it overshoots its bounds before
|
||||||
|
/// reaching its end. Instead of repeated swinging motions before ascending,
|
||||||
|
/// though, this curve overshoots once, then continues to ascend.
|
||||||
|
///
|
||||||
|
/// Derived from Robert Penner’s easing functions.
|
||||||
|
///
|
||||||
|
/// @see <a href="https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_in_back.mp4">curve_ease_in_back.mp4</a>
|
||||||
|
public static final Interpolator EASE_IN_BACK = new Cubic(0.6, -0.28, 0.735, 0.045);
|
||||||
|
|
||||||
|
/// A cubic animation curve that starts quickly and ends slowly.
|
||||||
|
///
|
||||||
|
/// This is the same as the CSS easing function `ease-out`.
|
||||||
|
///
|
||||||
|
/// @see <a href="https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_out.mp4">curve_ease_out.mp4</a>
|
||||||
|
public static final Interpolator EASE_OUT = new Cubic(0.0, 0.0, 0.58, 1.0);
|
||||||
|
|
||||||
|
/// A cubic animation curve that starts linearly and ends slowly.
|
||||||
|
///
|
||||||
|
/// A symmetric animation to [#EASE_IN_TO_LINEAR].
|
||||||
|
///
|
||||||
|
/// @see <a href="https://flutter.github.io/assets-for-api-docs/assets/animation/curve_linear_to_ease_out.mp4">curve_linear_to_ease_out.mp4</a>
|
||||||
|
public static final Interpolator LINEAR_TO_EASE_OUT = new Cubic(0.35, 0.91, 0.33, 0.97);
|
||||||
|
|
||||||
|
/// A cubic animation curve that starts quickly and ends slowly. This is
|
||||||
|
/// similar to [#EASE_OUT], but with sinusoidal easing for a slightly
|
||||||
|
/// less abrupt beginning and end. Nonetheless, the result is quite gentle and
|
||||||
|
/// is hard to distinguish from [#linear] at a glance.
|
||||||
|
///
|
||||||
|
/// Derived from Robert Penner’s easing functions.
|
||||||
|
///
|
||||||
|
/// @see <a href="https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_out_sine.mp4">curve_ease_out_sine.mp4</a>
|
||||||
|
public static final Interpolator EASE_OUT_SINE = new Cubic(0.39, 0.575, 0.565, 1.0);
|
||||||
|
|
||||||
|
/// A cubic animation curve that starts quickly and ends slowly. This is
|
||||||
|
/// effectively the same as [#decelerate], only simulated using a cubic
|
||||||
|
/// bezier function.
|
||||||
|
///
|
||||||
|
/// Compared to [#EASE_OUT_SINE], this curve is slightly steeper.
|
||||||
|
///
|
||||||
|
/// Derived from Robert Penner’s easing functions.
|
||||||
|
///
|
||||||
|
/// @see <a href="https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_out_quad.mp4">curve_ease_out_quad.mp4</a>
|
||||||
|
public static final Interpolator EASE_OUT_QUAD = new Cubic(0.25, 0.46, 0.45, 0.94);
|
||||||
|
|
||||||
|
/// A cubic animation curve that starts quickly and ends slowly. This curve is
|
||||||
|
/// a flipped version of [#EASE_IN_CUBIC].
|
||||||
|
///
|
||||||
|
/// The result is a safe sweet spot when choosing a curve for animating a
|
||||||
|
/// widget's position entering or already inside the viewport.
|
||||||
|
///
|
||||||
|
/// Compared to [#EASE_OUT_QUAD], this curve is slightly steeper.
|
||||||
|
///
|
||||||
|
/// Derived from Robert Penner’s easing functions.
|
||||||
|
///
|
||||||
|
/// @see <a href="https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_out_cubic.mp4">curve_ease_out_cubic.mp4</a>
|
||||||
|
public static final Interpolator EASE_OUT_CUBIC = new Cubic(0.215, 0.61, 0.355, 1.0);
|
||||||
|
|
||||||
|
/// A cubic animation curve that starts quickly and ends slowly. This curve is
|
||||||
|
/// a flipped version of [#EASE_IN_QUART].
|
||||||
|
///
|
||||||
|
/// Animations using this curve or steeper curves will benefit from a longer
|
||||||
|
/// duration to avoid motion feeling unnatural.
|
||||||
|
///
|
||||||
|
/// Compared to [#EASE_OUT_CUBIC], this curve is slightly steeper.
|
||||||
|
///
|
||||||
|
/// Derived from Robert Penner’s easing functions.
|
||||||
|
///
|
||||||
|
/// @see <a href="https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_out_quart.mp4">curve_ease_out_quart.mp4</a>
|
||||||
|
public static final Interpolator EASE_OUT_QUART = new Cubic(0.165, 0.84, 0.44, 1.0);
|
||||||
|
|
||||||
|
/// A cubic animation curve that starts quickly and ends slowly. This curve is
|
||||||
|
/// a flipped version of [#EASE_IN_QUINT].
|
||||||
|
///
|
||||||
|
/// Compared to [#EASE_OUT_QUART], this curve is slightly steeper.
|
||||||
|
///
|
||||||
|
/// Derived from Robert Penner’s easing functions.
|
||||||
|
///
|
||||||
|
/// @see <a href="https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_out_quint.mp4">curve_ease_out_quint.mp4</a>
|
||||||
|
public static final Interpolator EASE_OUT_QUINT = new Cubic(0.23, 1.0, 0.32, 1.0);
|
||||||
|
|
||||||
|
/// A cubic animation curve that starts quickly and ends slowly. This curve is
|
||||||
|
/// a flipped version of [#EASE_IN_EXPO]. Using this curve can give your
|
||||||
|
/// animations extra flare, but a longer duration may need to be used to
|
||||||
|
/// compensate for the steepness of the curve.
|
||||||
|
///
|
||||||
|
/// Derived from Robert Penner’s easing functions.
|
||||||
|
///
|
||||||
|
/// @see <a href="https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_out_expo.mp4">curve_ease_out_expo.mp4</a>
|
||||||
|
public static final Interpolator EASE_OUT_EXPO = new Cubic(0.19, 1.0, 0.22, 1.0);
|
||||||
|
|
||||||
|
/// A cubic animation curve that starts quickly and ends slowly. This curve is
|
||||||
|
/// effectively the top-left quarter of a circle.
|
||||||
|
///
|
||||||
|
/// Like [#EASE_OUT_EXPO], this curve is fairly dramatic and will reduce
|
||||||
|
/// the clarity of an animation if not given a longer duration.
|
||||||
|
///
|
||||||
|
/// Derived from Robert Penner’s easing functions.
|
||||||
|
///
|
||||||
|
/// @see <a href="https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_out_circ.mp4">curve_ease_out_circ.mp4</a>
|
||||||
|
public static final Interpolator EASE_OUT_CIRC = new Cubic(0.075, 0.82, 0.165, 1.0);
|
||||||
|
|
||||||
|
/// A cubic animation curve that starts quickly and ends slowly. This curve is
|
||||||
|
/// similar to [#elasticOut] in that it overshoots its bounds before
|
||||||
|
/// reaching its end. Instead of repeated swinging motions after ascending,
|
||||||
|
/// though, this curve only overshoots once.
|
||||||
|
///
|
||||||
|
/// Derived from Robert Penner’s easing functions.
|
||||||
|
///
|
||||||
|
/// @see <a href="https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_out_back.mp4">curve_ease_out_back.mp4</a>
|
||||||
|
public static final Interpolator EASE_OUT_BACK = new Cubic(0.175, 0.885, 0.32, 1.275);
|
||||||
|
|
||||||
|
/// A cubic animation curve that starts slowly, speeds up, and then ends
|
||||||
|
/// slowly.
|
||||||
|
///
|
||||||
|
/// This is the same as the CSS easing function `ease-in-out`.
|
||||||
|
///
|
||||||
|
/// @see <a href="https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_in_out.mp4">curve_ease_in_out.mp4</a>
|
||||||
|
public static final Interpolator EASE_IN_OUT = new Cubic(0.42, 0.0, 0.58, 1.0);
|
||||||
|
|
||||||
|
/// A cubic animation curve that starts slowly, speeds up, and then ends
|
||||||
|
/// slowly. This is similar to [#EASE_IN_OUT], but with sinusoidal easing
|
||||||
|
/// for a slightly less abrupt beginning and end.
|
||||||
|
///
|
||||||
|
/// Derived from Robert Penner’s easing functions.
|
||||||
|
///
|
||||||
|
/// @see <a href="https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_in_out_sine.mp4">curve_ease_in_out_sine.mp4</a>
|
||||||
|
public static final Interpolator EASE_IN_OUT_SINE = new Cubic(0.445, 0.05, 0.55, 0.95);
|
||||||
|
|
||||||
|
/// A cubic animation curve that starts slowly, speeds up, and then ends
|
||||||
|
/// slowly. This curve can be imagined as [#EASE_IN_QUAD] as the first
|
||||||
|
/// half, and [#EASE_OUT_QUAD] as the second.
|
||||||
|
///
|
||||||
|
/// Compared to [#EASE_IN_OUT_SINE], this curve is slightly steeper.
|
||||||
|
///
|
||||||
|
/// Derived from Robert Penner’s easing functions.
|
||||||
|
///
|
||||||
|
/// @see <a href="https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_in_out_quad.mp4">curve_ease_in_out_quad.mp4</a>
|
||||||
|
public static final Interpolator EASE_IN_OUT_QUAD = new Cubic(0.455, 0.03, 0.515, 0.955);
|
||||||
|
|
||||||
|
/// A cubic animation curve that starts slowly, speeds up, and then ends
|
||||||
|
/// slowly. This curve can be imagined as [#EASE_IN_CUBIC] as the first
|
||||||
|
/// half, and [#EASE_OUT_CUBIC] as the second.
|
||||||
|
///
|
||||||
|
/// The result is a safe sweet spot when choosing a curve for a widget whose
|
||||||
|
/// initial and final positions are both within the viewport.
|
||||||
|
///
|
||||||
|
/// Compared to [#EASE_IN_OUT_QUAD], this curve is slightly steeper.
|
||||||
|
///
|
||||||
|
/// Derived from Robert Penner’s easing functions.
|
||||||
|
///
|
||||||
|
/// @see <a href="https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_in_out_cubic.mp4">curve_ease_in_out_cubic.mp4</a>
|
||||||
|
public static final Interpolator EASE_IN_OUT_CUBIC = new Cubic(0.645, 0.045, 0.355, 1.0);
|
||||||
|
|
||||||
|
/// A cubic animation curve that starts slowly, speeds up, and then ends
|
||||||
|
/// slowly. This curve can be imagined as [#EASE_IN_QUART] as the first
|
||||||
|
/// half, and [#EASE_OUT_QUART] as the second.
|
||||||
|
///
|
||||||
|
/// Animations using this curve or steeper curves will benefit from a longer
|
||||||
|
/// duration to avoid motion feeling unnatural.
|
||||||
|
///
|
||||||
|
/// Compared to [#EASE_IN_OUT_CUBIC], this curve is slightly steeper.
|
||||||
|
///
|
||||||
|
/// Derived from Robert Penner’s easing functions.
|
||||||
|
///
|
||||||
|
/// @see <a href="https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_in_out_quart.mp4">curve_ease_in_out_quart.mp4</a>
|
||||||
|
public static final Interpolator EASE_IN_OUT_QUART = new Cubic(0.77, 0.0, 0.175, 1.0);
|
||||||
|
|
||||||
|
/// A cubic animation curve that starts slowly, speeds up, and then ends
|
||||||
|
/// slowly. This curve can be imagined as [#EASE_IN_QUINT] as the first
|
||||||
|
/// half, and [#EASE_OUT_QUINT] as the second.
|
||||||
|
///
|
||||||
|
/// Compared to [#EASE_IN_OUT_QUART], this curve is slightly steeper.
|
||||||
|
///
|
||||||
|
/// Derived from Robert Penner’s easing functions.
|
||||||
|
///
|
||||||
|
/// @see <a href="https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_in_out_quint.mp4">curve_ease_in_out_quint.mp4</a>
|
||||||
|
public static final Interpolator EASE_IN_OUT_QUINT = new Cubic(0.86, 0.0, 0.07, 1.0);
|
||||||
|
|
||||||
|
/// A cubic animation curve that starts slowly, speeds up, and then ends
|
||||||
|
/// slowly.
|
||||||
|
///
|
||||||
|
/// Since this curve is arrived at with an exponential function, the midpoint
|
||||||
|
/// is exceptionally steep. Extra consideration should be taken when designing
|
||||||
|
/// an animation using this.
|
||||||
|
///
|
||||||
|
/// Compared to [#EASE_IN_OUT_QUINT], this curve is slightly steeper.
|
||||||
|
///
|
||||||
|
/// Derived from Robert Penner’s easing functions.
|
||||||
|
///
|
||||||
|
/// @see <a href="https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_in_out_expo.mp4">curve_ease_in_out_expo.mp4</a>
|
||||||
|
public static final Interpolator EASE_IN_OUT_EXPO = new Cubic(1.0, 0.0, 0.0, 1.0);
|
||||||
|
|
||||||
|
/// A cubic animation curve that starts slowly, speeds up, and then ends
|
||||||
|
/// slowly. This curve can be imagined as [#EASE_IN_CIRC] as the first
|
||||||
|
/// half, and [#EASE_OUT_CIRC] as the second.
|
||||||
|
///
|
||||||
|
/// Like [#EASE_IN_OUT_EXPO], this curve is fairly dramatic and will reduce
|
||||||
|
/// the clarity of an animation if not given a longer duration.
|
||||||
|
///
|
||||||
|
/// Compared to [#EASE_IN_OUT_EXPO], this curve is slightly steeper.
|
||||||
|
///
|
||||||
|
/// Derived from Robert Penner’s easing functions.
|
||||||
|
///
|
||||||
|
/// @see <a href="https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_in_out_circ.mp4">curve_ease_in_out_circ.mp4</a>
|
||||||
|
public static final Interpolator EASE_IN_OUT_CIRC = new Cubic(0.785, 0.135, 0.15, 0.86);
|
||||||
|
|
||||||
|
/// A cubic animation curve that starts slowly, speeds up shortly thereafter,
|
||||||
|
/// and then ends slowly. This curve can be imagined as a steeper version of
|
||||||
|
/// [#EASE_IN_OUT_CUBIC].
|
||||||
|
///
|
||||||
|
/// The result is a more emphasized eased curve when choosing a curve for a
|
||||||
|
/// widget whose initial and final positions are both within the viewport.
|
||||||
|
///
|
||||||
|
/// Compared to [#EASE_IN_OUT_CUBIC], this curve is slightly steeper.
|
||||||
|
///
|
||||||
|
/// @see <a href="https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_in_out_cubic_emphasized.mp4">curve_ease_in_out_cubic_emphasized.mp4</a>
|
||||||
|
public static final Interpolator EASE_IN_OUT_CUBIC_EMPHASIZED = new ThreePointCubic(
|
||||||
|
new Offset(0.05, 0),
|
||||||
|
new Offset(0.133333, 0.06),
|
||||||
|
new Offset(0.166666, 0.4),
|
||||||
|
new Offset(0.208333, 0.82),
|
||||||
|
new Offset(0.25, 1)
|
||||||
|
);
|
||||||
|
|
||||||
|
/// A cubic animation curve that starts slowly, speeds up, and then ends
|
||||||
|
/// slowly. This curve can be imagined as [#EASE_IN_BACK] as the first
|
||||||
|
/// half, and [#EASE_OUT_BACK] as the second.
|
||||||
|
///
|
||||||
|
/// Since two curves are used as a basis for this curve, the resulting
|
||||||
|
/// animation will overshoot its bounds twice before reaching its end - first
|
||||||
|
/// by exceeding its lower bound, then exceeding its upper bound and finally
|
||||||
|
/// descending to its final position.
|
||||||
|
///
|
||||||
|
/// Derived from Robert Penner’s easing functions.
|
||||||
|
///
|
||||||
|
/// @see <a href="https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_in_out_back.mp4">curve_ease_in_out_back.mp4</a>
|
||||||
|
public static final Interpolator EASE_IN_OUT_BACK = new Cubic(0.68, -0.55, 0.265, 1.55);
|
||||||
|
|
||||||
|
/// A curve that starts quickly and eases into its final position.
|
||||||
|
///
|
||||||
|
/// Over the course of the animation, the object spends more time near its
|
||||||
|
/// final destination. As a result, the user isn’t left waiting for the
|
||||||
|
/// animation to finish, and the negative effects of motion are minimized.
|
||||||
|
///
|
||||||
|
/// @see <a href="https://flutter.github.io/assets-for-api-docs/assets/animation/curve_fast_out_slow_in.mp4">curve_fast_out_slow_in.mp4</a>
|
||||||
|
public static final Interpolator FAST_OUT_SLOW_IN = new Cubic(0.4, 0.0, 0.2, 1.0);
|
||||||
|
|
||||||
|
/// A cubic animation curve that starts quickly, slows down, and then ends
|
||||||
|
/// quickly.
|
||||||
|
///
|
||||||
|
/// @see <a href="https://flutter.github.io/assets-for-api-docs/assets/animation/curve_slow_middle.mp4">curve_slow_middle.mp4</a>
|
||||||
|
public static final Interpolator SLOW_MIDDLE = new Cubic(0.15, 0.85, 0.85, 0.15);
|
||||||
|
|
||||||
|
private static double bounce(double t) {
|
||||||
|
if (t < 1.0 / 2.75) {
|
||||||
|
return 7.5625 * t * t;
|
||||||
|
} else if (t < 2 / 2.75) {
|
||||||
|
t -= 1.5 / 2.75;
|
||||||
|
return 7.5625 * t * t + 0.75;
|
||||||
|
} else if (t < 2.5 / 2.75) {
|
||||||
|
t -= 2.25 / 2.75;
|
||||||
|
return 7.5625 * t * t + 0.9375;
|
||||||
|
}
|
||||||
|
t -= 2.625 / 2.75;
|
||||||
|
return 7.5625 * t * t + 0.984375;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// An oscillating curve that grows in magnitude.
|
||||||
|
///
|
||||||
|
/// @see <a href="https://flutter.github.io/assets-for-api-docs/assets/animation/curve_bounce_in.mp4">curve_bounce_in.mp4</a>
|
||||||
|
|
||||||
|
public static final Interpolator BOUNCE_IN = new Interpolator() {
|
||||||
|
@Override
|
||||||
|
protected double curve(double t) {
|
||||||
|
return 1.0 - bounce(1.0 - t);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/// An oscillating curve that first grows and then shrink in magnitude.
|
||||||
|
///
|
||||||
|
/// @see <a href="https://flutter.github.io/assets-for-api-docs/assets/animation/curve_bounce_out.mp4">curve_bounce_out.mp4</a>
|
||||||
|
public static final Interpolator BOUNCE_OUT = new Interpolator() {
|
||||||
|
@Override
|
||||||
|
protected double curve(double t) {
|
||||||
|
return bounce(t);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/// An oscillating curve that first grows and then shrink in magnitude.
|
||||||
|
///
|
||||||
|
/// @see <a href="https://flutter.github.io/assets-for-api-docs/assets/animation/curve_bounce_in_out.mp4">curve_bounce_in_out.mp4</a>
|
||||||
|
public static final Interpolator BOUNCE_IN_OUT = new Interpolator() {
|
||||||
|
@Override
|
||||||
|
protected double curve(double t) {
|
||||||
|
if (t < 0.5) {
|
||||||
|
return (1.0 - bounce(1.0 - t * 2.0)) * 0.5;
|
||||||
|
} else {
|
||||||
|
return bounce(t * 2.0 - 1.0) * 0.5 + 0.5;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
private static final double PERIOD = 0.4;
|
||||||
|
|
||||||
|
/// An oscillating curve that grows in magnitude while overshooting its bounds.
|
||||||
|
///
|
||||||
|
/// @see <a href="https://flutter.github.io/assets-for-api-docs/assets/animation/curve_elastic_in.mp4">curve_elastic_in.mp4</a>
|
||||||
|
public static final Interpolator ELASTIC_IN = new Interpolator() {
|
||||||
|
@Override
|
||||||
|
protected double curve(double t) {
|
||||||
|
final double s = PERIOD / 4.0;
|
||||||
|
t = t - 1.0;
|
||||||
|
return -Math.pow(2.0, 10.0 * t) * Math.sin((t - s) * (Math.PI * 2.0) / PERIOD);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/// An oscillating curve that shrinks in magnitude while overshooting its bounds.
|
||||||
|
///
|
||||||
|
/// @see <a href="https://flutter.github.io/assets-for-api-docs/assets/animation/curve_elastic_out.mp4">curve_elastic_out.mp4</a>
|
||||||
|
public static Interpolator ELASTIC_OUT = new Interpolator() {
|
||||||
|
@Override
|
||||||
|
protected double curve(double t) {
|
||||||
|
final double s = PERIOD / 4.0;
|
||||||
|
return Math.pow(2.0, -10 * t) * Math.sin((t - s) * (Math.PI * 2.0) / PERIOD) + 1.0;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/// An oscillating curve that grows and then shrinks in magnitude while overshooting its bounds.
|
||||||
|
///
|
||||||
|
/// @see <a href="https://flutter.github.io/assets-for-api-docs/assets/animation/curve_elastic_in_out.mp4">curve_elastic_in_out.mp4</a>
|
||||||
|
public static Interpolator ELASTIC_IN_OUT = new Interpolator() {
|
||||||
|
@Override
|
||||||
|
@SuppressWarnings("DuplicateExpressions")
|
||||||
|
protected double curve(double t) {
|
||||||
|
final double s = PERIOD / 4.0;
|
||||||
|
t = 2.0 * t - 1.0;
|
||||||
|
if (t < 0.0) {
|
||||||
|
return -0.5 * Math.pow(2.0, 10.0 * t) * Math.sin((t - s) * (Math.PI * 2.0) / PERIOD);
|
||||||
|
} else {
|
||||||
|
return Math.pow(2.0, -10.0 * t) * Math.sin((t - s) * (Math.PI * 2.0) / PERIOD) * 0.5 + 1.0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/// A cubic polynomial mapping of the unit interval.
|
||||||
|
private static final class Cubic extends Interpolator {
|
||||||
|
private static final double CUBIC_ERROR_BOUND = 0.001;
|
||||||
|
|
||||||
|
/// The x coordinate of the first control point.
|
||||||
|
///
|
||||||
|
/// The line through the point (0, 0) and the first control point is tangent
|
||||||
|
/// to the curve at the point (0, 0).
|
||||||
|
private final double a;
|
||||||
|
|
||||||
|
/// The y coordinate of the first control point.
|
||||||
|
///
|
||||||
|
/// The line through the point (0, 0) and the first control point is tangent
|
||||||
|
/// to the curve at the point (0, 0).
|
||||||
|
private final double b;
|
||||||
|
|
||||||
|
/// The x coordinate of the second control point.
|
||||||
|
///
|
||||||
|
/// The line through the point (1, 1) and the second control point is tangent
|
||||||
|
/// to the curve at the point (1, 1).
|
||||||
|
private final double c;
|
||||||
|
|
||||||
|
/// The y coordinate of the second control point.
|
||||||
|
///
|
||||||
|
/// The line through the point (1, 1) and the second control point is tangent
|
||||||
|
/// to the curve at the point (1, 1).
|
||||||
|
private final double d;
|
||||||
|
|
||||||
|
private Cubic(double a, double b, double c, double d) {
|
||||||
|
this.a = a;
|
||||||
|
this.b = b;
|
||||||
|
this.c = c;
|
||||||
|
this.d = d;
|
||||||
|
}
|
||||||
|
|
||||||
|
double _evaluateCubic(double a, double b, double m) {
|
||||||
|
return 3 * a * (1 - m) * (1 - m) * m + 3 * b * (1 - m) * m * m + m * m * m;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected double curve(double t) {
|
||||||
|
double start = 0.0;
|
||||||
|
double end = 1.0;
|
||||||
|
while (true) {
|
||||||
|
final double midpoint = (start + end) / 2;
|
||||||
|
final double estimate = _evaluateCubic(a, c, midpoint);
|
||||||
|
if (Math.abs(t - estimate) < CUBIC_ERROR_BOUND) {
|
||||||
|
return _evaluateCubic(b, d, midpoint);
|
||||||
|
}
|
||||||
|
if (estimate < t) {
|
||||||
|
start = midpoint;
|
||||||
|
} else {
|
||||||
|
end = midpoint;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o) {
|
||||||
|
return o instanceof Cubic cubic
|
||||||
|
&& this.a == cubic.a
|
||||||
|
&& this.b == cubic.b
|
||||||
|
&& this.c == cubic.c
|
||||||
|
&& this.d == cubic.d;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return Objects.hash(a, b, c, d);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "Cubic[a=%s, b=%s, c=%s, d=%s]".formatted(a, b, c, d);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private record Offset(double dx, double dy) {
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final class ThreePointCubic extends Interpolator {
|
||||||
|
|
||||||
|
/// The coordinates of the first control point of the first curve.
|
||||||
|
///
|
||||||
|
/// The line through the point (0, 0) and this control point is tangent to the
|
||||||
|
/// curve at the point (0, 0).
|
||||||
|
private final Offset a1;
|
||||||
|
|
||||||
|
/// The coordinates of the second control point of the first curve.
|
||||||
|
///
|
||||||
|
/// The line through the [#midpoint] and this control point is tangent to the
|
||||||
|
/// curve approaching the [#midpoint].
|
||||||
|
private final Offset b1;
|
||||||
|
|
||||||
|
/// The coordinates of the middle shared point.
|
||||||
|
///
|
||||||
|
/// The curve will go through this point. If the control points surrounding
|
||||||
|
/// this middle point ([#b1], and [#a2]) are not colinear with this point, then
|
||||||
|
/// the curve's derivative will have a discontinuity (a cusp) at this point.
|
||||||
|
private final Offset midpoint;
|
||||||
|
|
||||||
|
/// The coordinates of the first control point of the second curve.
|
||||||
|
///
|
||||||
|
/// The line through the [#midpoint] and this control point is tangent to the
|
||||||
|
/// curve approaching the [#midpoint].
|
||||||
|
private final Offset a2;
|
||||||
|
|
||||||
|
/// The coordinates of the second control point of the second curve.
|
||||||
|
///
|
||||||
|
/// The line through the point (1, 1) and this control point is tangent to the
|
||||||
|
/// curve at (1, 1).
|
||||||
|
private final Offset b2;
|
||||||
|
|
||||||
|
/// Creates two cubic curves that share a common control point.
|
||||||
|
///
|
||||||
|
/// Rather than creating a new instance, consider using one of the common
|
||||||
|
/// three-point cubic curves in [Interpolator].
|
||||||
|
///
|
||||||
|
/// The arguments correspond to the control points for the two curves,
|
||||||
|
/// including the [#midpoint], but do not include the two implied end points at
|
||||||
|
/// (0,0) and (1,1), which are fixed.
|
||||||
|
private ThreePointCubic(Offset a1, Offset b1, Offset midpoint, Offset a2, Offset b2) {
|
||||||
|
this.a1 = a1;
|
||||||
|
this.b1 = b1;
|
||||||
|
this.midpoint = midpoint;
|
||||||
|
this.a2 = a2;
|
||||||
|
this.b2 = b2;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected double curve(double t) {
|
||||||
|
final boolean firstCurve = t < midpoint.dx;
|
||||||
|
final double scaleX = firstCurve ? midpoint.dx : 1.0 - midpoint.dx;
|
||||||
|
final double scaleY = firstCurve ? midpoint.dy : 1.0 - midpoint.dy;
|
||||||
|
final double scaledT = (t - (firstCurve ? 0.0 : midpoint.dx)) / scaleX;
|
||||||
|
if (firstCurve) {
|
||||||
|
return new Cubic(
|
||||||
|
a1.dx / scaleX,
|
||||||
|
a1.dy / scaleY,
|
||||||
|
b1.dx / scaleX,
|
||||||
|
b1.dy / scaleY
|
||||||
|
).curve(scaledT) *
|
||||||
|
scaleY;
|
||||||
|
} else {
|
||||||
|
return new Cubic(
|
||||||
|
(a2.dx - midpoint.dx) / scaleX,
|
||||||
|
(a2.dy - midpoint.dy) / scaleY,
|
||||||
|
(b2.dx - midpoint.dx) / scaleX,
|
||||||
|
(b2.dy - midpoint.dy) / scaleY
|
||||||
|
).curve(scaledT) *
|
||||||
|
scaleY +
|
||||||
|
midpoint.dy;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o) {
|
||||||
|
return o instanceof ThreePointCubic that
|
||||||
|
&& a1.equals(that.a1)
|
||||||
|
&& b1.equals(that.b1)
|
||||||
|
&& midpoint.equals(that.midpoint)
|
||||||
|
&& a2.equals(that.a2)
|
||||||
|
&& b2.equals(that.b2);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return Objects.hash(a1, b1, midpoint, a2, b2);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "ThreePointCubic[a1=%s, b1=%s, midpoint=%s, a2=%s, b2=%s]".formatted(a1, b1, midpoint, a2, b2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//endregion Curves
|
||||||
|
|
||||||
|
// region Durations
|
||||||
|
|
||||||
|
/// The short1 duration (50ms) in the Material specification.
|
||||||
|
public static final Duration SHORT1 = Duration.millis(50);
|
||||||
|
|
||||||
|
/// The short2 duration (100ms) in the Material specification.
|
||||||
|
public static final Duration SHORT2 = Duration.millis(100);
|
||||||
|
|
||||||
|
/// The short3 duration (150ms) in the Material specification.
|
||||||
|
public static final Duration SHORT3 = Duration.millis(150);
|
||||||
|
|
||||||
|
/// The short4 duration (200ms) in the Material specification.
|
||||||
|
public static final Duration SHORT4 = Duration.millis(200);
|
||||||
|
|
||||||
|
/// The medium1 duration (250ms) in the Material specification.
|
||||||
|
public static final Duration MEDIUM1 = Duration.millis(250);
|
||||||
|
|
||||||
|
/// The medium2 duration (300ms) in the Material specification.
|
||||||
|
public static final Duration MEDIUM2 = Duration.millis(300);
|
||||||
|
|
||||||
|
/// The medium3 duration (350ms) in the Material specification.
|
||||||
|
public static final Duration MEDIUM3 = Duration.millis(350);
|
||||||
|
|
||||||
|
/// The medium4 duration (400ms) in the Material specification.
|
||||||
|
public static final Duration MEDIUM4 = Duration.millis(400);
|
||||||
|
|
||||||
|
/// The long1 duration (450ms) in the Material specification.
|
||||||
|
public static final Duration LONG1 = Duration.millis(450);
|
||||||
|
|
||||||
|
/// The long2 duration (500ms) in the Material specification.
|
||||||
|
public static final Duration LONG2 = Duration.millis(500);
|
||||||
|
|
||||||
|
/// The long3 duration (550ms) in the Material specification.
|
||||||
|
public static final Duration LONG3 = Duration.millis(550);
|
||||||
|
|
||||||
|
/// The long4 duration (600ms) in the Material specification.
|
||||||
|
public static final Duration LONG4 = Duration.millis(600);
|
||||||
|
|
||||||
|
/// The extralong1 duration (700ms) in the Material specification.
|
||||||
|
public static final Duration EXTRA_LONG1 = Duration.millis(700);
|
||||||
|
|
||||||
|
/// The extralong2 duration (800ms) in the Material specification.
|
||||||
|
public static final Duration EXTRA_LONG2 = Duration.millis(800);
|
||||||
|
|
||||||
|
/// The extralong3 duration (900ms) in the Material specification.
|
||||||
|
public static final Duration EXTRA_LONG3 = Duration.millis(900);
|
||||||
|
|
||||||
|
/// The extralong4 duration (1000ms) in the Material specification.
|
||||||
|
public static final Duration EXTRA_LONG4 = Duration.millis(1000);
|
||||||
|
|
||||||
|
// endregion Durations
|
||||||
|
|
||||||
|
private Motion() {
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -17,77 +17,51 @@
|
|||||||
*/
|
*/
|
||||||
package org.jackhuang.hmcl.ui.animation;
|
package org.jackhuang.hmcl.ui.animation;
|
||||||
|
|
||||||
import javafx.animation.Timeline;
|
import javafx.animation.Animation;
|
||||||
|
import javafx.animation.Interpolator;
|
||||||
import javafx.application.Platform;
|
import javafx.application.Platform;
|
||||||
import javafx.scene.Node;
|
import javafx.scene.Node;
|
||||||
|
import javafx.scene.layout.Pane;
|
||||||
import javafx.scene.layout.StackPane;
|
import javafx.scene.layout.StackPane;
|
||||||
import javafx.util.Duration;
|
import javafx.util.Duration;
|
||||||
import org.jackhuang.hmcl.ui.FXUtils;
|
import org.jackhuang.hmcl.ui.FXUtils;
|
||||||
|
import org.jackhuang.hmcl.ui.construct.TabHeader;
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
public class TransitionPane extends StackPane implements AnimationHandler {
|
public class TransitionPane extends StackPane {
|
||||||
private static final Duration DEFAULT_DURATION = Duration.millis(200);
|
|
||||||
|
|
||||||
private Duration duration;
|
private Node currentNode;
|
||||||
private Node previousNode, currentNode;
|
|
||||||
|
|
||||||
public TransitionPane() {
|
public TransitionPane() {
|
||||||
FXUtils.setOverflowHidden(this);
|
FXUtils.setOverflowHidden(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public Node getPreviousNode() {
|
|
||||||
return previousNode;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Node getCurrentNode() {
|
public Node getCurrentNode() {
|
||||||
return currentNode;
|
return currentNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
public void bindTabHeader(TabHeader tabHeader) {
|
||||||
public StackPane getCurrentRoot() {
|
this.setContent(tabHeader.getSelectionModel().getSelectedItem().getNode(), ContainerAnimations.NONE);
|
||||||
return this;
|
FXUtils.onChange(tabHeader.getSelectionModel().selectedItemProperty(), newValue -> {
|
||||||
}
|
this.setContent(newValue.getNode(),
|
||||||
|
ContainerAnimations.SLIDE_UP_FADE_IN,
|
||||||
@Override
|
Motion.MEDIUM4,
|
||||||
public Duration getDuration() {
|
Motion.EASE_IN_OUT_CUBIC_EMPHASIZED
|
||||||
return duration;
|
);
|
||||||
}
|
|
||||||
|
|
||||||
public void setContent(Node newView, AnimationProducer transition) {
|
|
||||||
setContent(newView, transition, DEFAULT_DURATION);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setContent(Node newView, AnimationProducer transition, Duration duration) {
|
|
||||||
this.duration = duration;
|
|
||||||
|
|
||||||
updateContent(newView);
|
|
||||||
|
|
||||||
if (previousNode == EMPTY_PANE) {
|
|
||||||
getChildren().setAll(newView);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (AnimationUtils.isAnimationEnabled() && transition != ContainerAnimations.NONE) {
|
|
||||||
setMouseTransparent(true);
|
|
||||||
transition.init(this);
|
|
||||||
|
|
||||||
// runLater or "init" will not work
|
|
||||||
Platform.runLater(() -> {
|
|
||||||
Timeline newAnimation = new Timeline();
|
|
||||||
newAnimation.getKeyFrames().setAll(transition.animate(this));
|
|
||||||
newAnimation.setOnFinished(e -> {
|
|
||||||
setMouseTransparent(false);
|
|
||||||
getChildren().remove(previousNode);
|
|
||||||
});
|
});
|
||||||
FXUtils.playAnimation(this, "transition_pane", newAnimation);
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
getChildren().remove(previousNode);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateContent(Node newView) {
|
public final void setContent(Node newView, AnimationProducer transition) {
|
||||||
|
setContent(newView, transition, Motion.SHORT4);
|
||||||
|
}
|
||||||
|
|
||||||
|
public final void setContent(Node newView, AnimationProducer transition, Duration duration) {
|
||||||
|
setContent(newView, transition, duration, Motion.EASE);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setContent(Node newView, AnimationProducer transition,
|
||||||
|
Duration duration, Interpolator interpolator) {
|
||||||
|
Node previousNode;
|
||||||
if (getWidth() > 0 && getHeight() > 0) {
|
if (getWidth() > 0 && getHeight() > 0) {
|
||||||
previousNode = currentNode;
|
previousNode = currentNode;
|
||||||
if (previousNode == null) {
|
if (previousNode == null) {
|
||||||
@@ -105,10 +79,49 @@ public class TransitionPane extends StackPane implements AnimationHandler {
|
|||||||
currentNode = newView;
|
currentNode = newView;
|
||||||
|
|
||||||
getChildren().setAll(previousNode, currentNode);
|
getChildren().setAll(previousNode, currentNode);
|
||||||
|
|
||||||
|
if (previousNode == EMPTY_PANE) {
|
||||||
|
getChildren().setAll(newView);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (AnimationUtils.isAnimationEnabled() && transition != ContainerAnimations.NONE) {
|
||||||
|
setMouseTransparent(true);
|
||||||
|
transition.init(this, previousNode, getCurrentNode());
|
||||||
|
|
||||||
|
Node finalPreviousNode = previousNode;
|
||||||
|
// runLater or "init" will not work
|
||||||
|
Platform.runLater(() -> {
|
||||||
|
Animation newAnimation = transition.animate(
|
||||||
|
this,
|
||||||
|
finalPreviousNode,
|
||||||
|
getCurrentNode(),
|
||||||
|
duration, interpolator);
|
||||||
|
newAnimation.setOnFinished(e -> {
|
||||||
|
setMouseTransparent(false);
|
||||||
|
getChildren().remove(finalPreviousNode);
|
||||||
|
});
|
||||||
|
FXUtils.playAnimation(this, "transition_pane", newAnimation);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
getChildren().remove(previousNode);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private final EmptyPane EMPTY_PANE = new EmptyPane();
|
private final EmptyPane EMPTY_PANE = new EmptyPane();
|
||||||
|
|
||||||
public static class EmptyPane extends StackPane {
|
public static class EmptyPane extends StackPane {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public interface AnimationProducer {
|
||||||
|
default void init(TransitionPane container, Node previousNode, Node nextNode) {
|
||||||
|
}
|
||||||
|
|
||||||
|
Animation animate(Pane container, Node previousNode, Node nextNode,
|
||||||
|
Duration duration, Interpolator interpolator);
|
||||||
|
|
||||||
|
default @Nullable TransitionPane.AnimationProducer opposite() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,6 +17,7 @@
|
|||||||
*/
|
*/
|
||||||
package org.jackhuang.hmcl.ui.construct;
|
package org.jackhuang.hmcl.ui.construct;
|
||||||
|
|
||||||
|
import javafx.animation.Interpolator;
|
||||||
import javafx.beans.property.BooleanProperty;
|
import javafx.beans.property.BooleanProperty;
|
||||||
import javafx.beans.property.ObjectProperty;
|
import javafx.beans.property.ObjectProperty;
|
||||||
import javafx.beans.property.SimpleBooleanProperty;
|
import javafx.beans.property.SimpleBooleanProperty;
|
||||||
@@ -26,12 +27,14 @@ import javafx.event.EventHandler;
|
|||||||
import javafx.event.EventType;
|
import javafx.event.EventType;
|
||||||
import javafx.scene.Node;
|
import javafx.scene.Node;
|
||||||
import javafx.scene.layout.Region;
|
import javafx.scene.layout.Region;
|
||||||
|
import javafx.util.Duration;
|
||||||
import org.jackhuang.hmcl.ui.FXUtils;
|
import org.jackhuang.hmcl.ui.FXUtils;
|
||||||
import org.jackhuang.hmcl.ui.animation.AnimationProducer;
|
|
||||||
import org.jackhuang.hmcl.ui.animation.ContainerAnimations;
|
import org.jackhuang.hmcl.ui.animation.ContainerAnimations;
|
||||||
|
import org.jackhuang.hmcl.ui.animation.Motion;
|
||||||
import org.jackhuang.hmcl.ui.animation.TransitionPane;
|
import org.jackhuang.hmcl.ui.animation.TransitionPane;
|
||||||
import org.jackhuang.hmcl.ui.wizard.Navigation;
|
import org.jackhuang.hmcl.ui.wizard.Navigation;
|
||||||
|
|
||||||
|
import java.util.Objects;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.Stack;
|
import java.util.Stack;
|
||||||
|
|
||||||
@@ -56,6 +59,10 @@ public class Navigator extends TransitionPane {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void navigate(Node node, AnimationProducer animationProducer) {
|
public void navigate(Node node, AnimationProducer animationProducer) {
|
||||||
|
navigate(node, animationProducer, Motion.SHORT4, Motion.EASE);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void navigate(Node node, AnimationProducer animationProducer, Duration duration, Interpolator interpolator) {
|
||||||
FXUtils.checkFxUserThread();
|
FXUtils.checkFxUserThread();
|
||||||
|
|
||||||
if (!initialized)
|
if (!initialized)
|
||||||
@@ -75,7 +82,7 @@ public class Navigator extends TransitionPane {
|
|||||||
node.fireEvent(navigating);
|
node.fireEvent(navigating);
|
||||||
|
|
||||||
node.getProperties().put("hmcl.navigator.animation", animationProducer);
|
node.getProperties().put("hmcl.navigator.animation", animationProducer);
|
||||||
setContent(node, animationProducer);
|
setContent(node, animationProducer, duration, interpolator);
|
||||||
|
|
||||||
NavigationEvent navigated = new NavigationEvent(this, node, Navigation.NavigationDirection.NEXT, NavigationEvent.NAVIGATED);
|
NavigationEvent navigated = new NavigationEvent(this, node, Navigation.NavigationDirection.NEXT, NavigationEvent.NAVIGATED);
|
||||||
node.fireEvent(navigated);
|
node.fireEvent(navigated);
|
||||||
@@ -113,7 +120,7 @@ public class Navigator extends TransitionPane {
|
|||||||
Node poppedNode = stack.pop();
|
Node poppedNode = stack.pop();
|
||||||
NavigationEvent exited = new NavigationEvent(this, poppedNode, Navigation.NavigationDirection.PREVIOUS, NavigationEvent.EXITED);
|
NavigationEvent exited = new NavigationEvent(this, poppedNode, Navigation.NavigationDirection.PREVIOUS, NavigationEvent.EXITED);
|
||||||
poppedNode.fireEvent(exited);
|
poppedNode.fireEvent(exited);
|
||||||
if (poppedNode instanceof PageAware) ((PageAware) poppedNode).onPageHidden();
|
if (poppedNode instanceof PageAware pageAware) pageAware.onPageHidden();
|
||||||
|
|
||||||
backable.set(canGoBack());
|
backable.set(canGoBack());
|
||||||
Node node = stack.peek();
|
Node node = stack.peek();
|
||||||
@@ -123,8 +130,8 @@ public class Navigator extends TransitionPane {
|
|||||||
node.fireEvent(navigating);
|
node.fireEvent(navigating);
|
||||||
|
|
||||||
Object obj = from.getProperties().get("hmcl.navigator.animation");
|
Object obj = from.getProperties().get("hmcl.navigator.animation");
|
||||||
if (obj instanceof AnimationProducer) {
|
if (obj instanceof AnimationProducer animationProducer) {
|
||||||
setContent(node, (AnimationProducer) obj);
|
setContent(node, Objects.requireNonNullElse(animationProducer.opposite(), animationProducer));
|
||||||
} else {
|
} else {
|
||||||
setContent(node, ContainerAnimations.NONE);
|
setContent(node, ContainerAnimations.NONE);
|
||||||
}
|
}
|
||||||
@@ -160,12 +167,13 @@ public class Navigator extends TransitionPane {
|
|||||||
return stack.size();
|
return stack.size();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setContent(Node content, AnimationProducer animationProducer) {
|
@Override
|
||||||
super.setContent(content, animationProducer);
|
public void setContent(Node newView, AnimationProducer transition, Duration duration, Interpolator interpolator) {
|
||||||
|
super.setContent(newView, transition, duration, interpolator);
|
||||||
|
|
||||||
if (content instanceof Region) {
|
if (newView instanceof Region region) {
|
||||||
((Region) content).setMinSize(0, 0);
|
region.setMinSize(0, 0);
|
||||||
FXUtils.setOverflowHidden((Region) content);
|
FXUtils.setOverflowHidden(region);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -181,7 +189,7 @@ public class Navigator extends TransitionPane {
|
|||||||
this.onNavigated.set(onNavigated);
|
this.onNavigated.set(onNavigated);
|
||||||
}
|
}
|
||||||
|
|
||||||
private ObjectProperty<EventHandler<NavigationEvent>> onNavigated = new SimpleObjectProperty<EventHandler<NavigationEvent>>(this, "onNavigated") {
|
private final ObjectProperty<EventHandler<NavigationEvent>> onNavigated = new SimpleObjectProperty<EventHandler<NavigationEvent>>(this, "onNavigated") {
|
||||||
@Override
|
@Override
|
||||||
protected void invalidated() {
|
protected void invalidated() {
|
||||||
setEventHandler(NavigationEvent.NAVIGATED, get());
|
setEventHandler(NavigationEvent.NAVIGATED, get());
|
||||||
@@ -200,14 +208,14 @@ public class Navigator extends TransitionPane {
|
|||||||
this.onNavigating.set(onNavigating);
|
this.onNavigating.set(onNavigating);
|
||||||
}
|
}
|
||||||
|
|
||||||
private ObjectProperty<EventHandler<NavigationEvent>> onNavigating = new SimpleObjectProperty<EventHandler<NavigationEvent>>(this, "onNavigating") {
|
private final ObjectProperty<EventHandler<NavigationEvent>> onNavigating = new SimpleObjectProperty<EventHandler<NavigationEvent>>(this, "onNavigating") {
|
||||||
@Override
|
@Override
|
||||||
protected void invalidated() {
|
protected void invalidated() {
|
||||||
setEventHandler(NavigationEvent.NAVIGATING, get());
|
setEventHandler(NavigationEvent.NAVIGATING, get());
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
public static class NavigationEvent extends Event {
|
public static final class NavigationEvent extends Event {
|
||||||
public static final EventType<NavigationEvent> EXITED = new EventType<>("EXITED");
|
public static final EventType<NavigationEvent> EXITED = new EventType<>("EXITED");
|
||||||
public static final EventType<NavigationEvent> NAVIGATED = new EventType<>("NAVIGATED");
|
public static final EventType<NavigationEvent> NAVIGATED = new EventType<>("NAVIGATED");
|
||||||
public static final EventType<NavigationEvent> NAVIGATING = new EventType<>("NAVIGATING");
|
public static final EventType<NavigationEvent> NAVIGATING = new EventType<>("NAVIGATING");
|
||||||
|
|||||||
@@ -18,7 +18,6 @@
|
|||||||
package org.jackhuang.hmcl.ui.construct;
|
package org.jackhuang.hmcl.ui.construct;
|
||||||
|
|
||||||
import com.jfoenix.controls.JFXRippler;
|
import com.jfoenix.controls.JFXRippler;
|
||||||
import javafx.animation.Interpolator;
|
|
||||||
import javafx.animation.Transition;
|
import javafx.animation.Transition;
|
||||||
import javafx.beans.DefaultProperty;
|
import javafx.beans.DefaultProperty;
|
||||||
import javafx.beans.InvalidationListener;
|
import javafx.beans.InvalidationListener;
|
||||||
@@ -40,6 +39,7 @@ import javafx.scene.paint.Paint;
|
|||||||
import javafx.scene.shape.Rectangle;
|
import javafx.scene.shape.Rectangle;
|
||||||
import javafx.util.Duration;
|
import javafx.util.Duration;
|
||||||
import org.jackhuang.hmcl.ui.animation.AnimationUtils;
|
import org.jackhuang.hmcl.ui.animation.AnimationUtils;
|
||||||
|
import org.jackhuang.hmcl.ui.animation.Motion;
|
||||||
import org.jackhuang.hmcl.util.Lang;
|
import org.jackhuang.hmcl.util.Lang;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@@ -109,7 +109,7 @@ public class RipplerContainer extends StackPane {
|
|||||||
setOnMouseEntered(e -> new Transition() {
|
setOnMouseEntered(e -> new Transition() {
|
||||||
{
|
{
|
||||||
setCycleDuration(DURATION);
|
setCycleDuration(DURATION);
|
||||||
setInterpolator(Interpolator.EASE_IN);
|
setInterpolator(Motion.EASE_IN);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -121,7 +121,7 @@ public class RipplerContainer extends StackPane {
|
|||||||
setOnMouseExited(e -> new Transition() {
|
setOnMouseExited(e -> new Transition() {
|
||||||
{
|
{
|
||||||
setCycleDuration(DURATION);
|
setCycleDuration(DURATION);
|
||||||
setInterpolator(Interpolator.EASE_OUT);
|
setInterpolator(Motion.EASE_OUT);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -48,8 +48,8 @@ public class TabHeader extends Control implements TabControl, PageAware {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private ObservableList<Tab<?>> tabs = FXCollections.observableArrayList();
|
private final ObservableList<Tab<?>> tabs = FXCollections.observableArrayList();
|
||||||
private ObjectProperty<Side> side = new SimpleObjectProperty<>(Side.TOP);
|
private final ObjectProperty<Side> side = new SimpleObjectProperty<>(Side.TOP);
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ObservableList<Tab<?>> getTabs() {
|
public ObservableList<Tab<?>> getTabs() {
|
||||||
@@ -170,14 +170,14 @@ public class TabHeader extends Control implements TabControl, PageAware {
|
|||||||
this.selectedTab = control.getSelectionModel().getSelectedItem();
|
this.selectedTab = control.getSelectionModel().getSelectedItem();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected class HeaderContainer extends StackPane {
|
protected final class HeaderContainer extends StackPane {
|
||||||
private Timeline timeline;
|
private Timeline timeline;
|
||||||
private StackPane selectedTabLine;
|
private final StackPane selectedTabLine;
|
||||||
private HeadersRegion headersRegion;
|
private final HeadersRegion headersRegion;
|
||||||
private Scale scale = new Scale(1, 1, 0, 0);
|
private final Scale scale = new Scale(1, 1, 0, 0);
|
||||||
private Rotate rotate = new Rotate(0, 0, 1);
|
private final Rotate rotate = new Rotate(0, 0, 1);
|
||||||
private double selectedTabLineOffset;
|
@SuppressWarnings({"FieldCanBeLocal", "unused"})
|
||||||
private ObservableList<Node> binding;
|
private final ObservableList<Node> binding;
|
||||||
|
|
||||||
public HeaderContainer() {
|
public HeaderContainer() {
|
||||||
getStyleClass().add("tab-header-area");
|
getStyleClass().add("tab-header-area");
|
||||||
@@ -232,13 +232,12 @@ public class TabHeader extends Control implements TabControl, PageAware {
|
|||||||
protected void invalidated() {
|
protected void invalidated() {
|
||||||
super.invalidated();
|
super.invalidated();
|
||||||
|
|
||||||
switch (get()) {
|
action = switch (get()) {
|
||||||
case TOP: action = new Top(); break;
|
case TOP -> new Top();
|
||||||
case BOTTOM: action = new Bottom(); break;
|
case BOTTOM -> new Bottom();
|
||||||
case LEFT: action = new Left(); break;
|
case LEFT -> new Left();
|
||||||
case RIGHT: action = new Right(); break;
|
case RIGHT -> new Right();
|
||||||
default: throw new InternalError();
|
};
|
||||||
}
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -269,7 +268,7 @@ public class TabHeader extends Control implements TabControl, PageAware {
|
|||||||
action.layoutChildren();
|
action.layoutChildren();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void animateSelectionLine() {
|
private void animateSelectionLine() {
|
||||||
action.animateSelectionLine();
|
action.animateSelectionLine();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -325,7 +324,6 @@ public class TabHeader extends Control implements TabControl, PageAware {
|
|||||||
double oldWidth = lineWidth * oldScaleX;
|
double oldWidth = lineWidth * oldScaleX;
|
||||||
double oldTransX = selectedTabLine.getTranslateX();
|
double oldTransX = selectedTabLine.getTranslateX();
|
||||||
double newScaleX = newWidth * oldScaleX / oldWidth;
|
double newScaleX = newWidth * oldScaleX / oldWidth;
|
||||||
selectedTabLineOffset = newTransX;
|
|
||||||
// newTransX += offsetStart * (double)this.direction;
|
// newTransX += offsetStart * (double)this.direction;
|
||||||
double transDiff = newTransX - oldTransX;
|
double transDiff = newTransX - oldTransX;
|
||||||
if (transDiff < 0.0D) {
|
if (transDiff < 0.0D) {
|
||||||
@@ -465,7 +463,6 @@ public class TabHeader extends Control implements TabControl, PageAware {
|
|||||||
double oldHeight = lineHeight * oldScaleY;
|
double oldHeight = lineHeight * oldScaleY;
|
||||||
double oldTransY = selectedTabLine.getTranslateY();
|
double oldTransY = selectedTabLine.getTranslateY();
|
||||||
double newScaleY = newHeight * oldScaleY / oldHeight;
|
double newScaleY = newHeight * oldScaleY / oldHeight;
|
||||||
selectedTabLineOffset = newTransY;
|
|
||||||
// newTransY += offsetStart * (double)this.direction;
|
// newTransY += offsetStart * (double)this.direction;
|
||||||
double transDiff = newTransY - oldTransY;
|
double transDiff = newTransY - oldTransY;
|
||||||
if (transDiff < 0.0D) {
|
if (transDiff < 0.0D) {
|
||||||
|
|||||||
@@ -41,6 +41,7 @@ import javafx.stage.StageStyle;
|
|||||||
import javafx.util.Duration;
|
import javafx.util.Duration;
|
||||||
import org.jackhuang.hmcl.ui.FXUtils;
|
import org.jackhuang.hmcl.ui.FXUtils;
|
||||||
import org.jackhuang.hmcl.ui.animation.AnimationUtils;
|
import org.jackhuang.hmcl.ui.animation.AnimationUtils;
|
||||||
|
import org.jackhuang.hmcl.ui.animation.Motion;
|
||||||
import org.jackhuang.hmcl.ui.wizard.Navigation;
|
import org.jackhuang.hmcl.ui.wizard.Navigation;
|
||||||
import org.jackhuang.hmcl.util.platform.OperatingSystem;
|
import org.jackhuang.hmcl.util.platform.OperatingSystem;
|
||||||
|
|
||||||
@@ -82,19 +83,19 @@ public class Decorator extends Control {
|
|||||||
if (playRestoreMinimizeAnimation && !iconified) {
|
if (playRestoreMinimizeAnimation && !iconified) {
|
||||||
playRestoreMinimizeAnimation = false;
|
playRestoreMinimizeAnimation = false;
|
||||||
Timeline timeline = new Timeline(
|
Timeline timeline = new Timeline(
|
||||||
new KeyFrame(Duration.millis(0),
|
new KeyFrame(Duration.ZERO,
|
||||||
new KeyValue(this.opacityProperty(), 0, FXUtils.EASE),
|
new KeyValue(this.opacityProperty(), 0, Motion.EASE),
|
||||||
new KeyValue(this.translateYProperty(), 200, FXUtils.EASE),
|
new KeyValue(this.translateYProperty(), 200, Motion.EASE),
|
||||||
new KeyValue(this.scaleXProperty(), 0.4, FXUtils.EASE),
|
new KeyValue(this.scaleXProperty(), 0.4, Motion.EASE),
|
||||||
new KeyValue(this.scaleYProperty(), 0.4, FXUtils.EASE),
|
new KeyValue(this.scaleYProperty(), 0.4, Motion.EASE),
|
||||||
new KeyValue(this.scaleZProperty(), 0.4, FXUtils.EASE)
|
new KeyValue(this.scaleZProperty(), 0.4, Motion.EASE)
|
||||||
),
|
),
|
||||||
new KeyFrame(Duration.millis(200),
|
new KeyFrame(Motion.SHORT4,
|
||||||
new KeyValue(this.opacityProperty(), 1, FXUtils.EASE),
|
new KeyValue(this.opacityProperty(), 1, Motion.EASE),
|
||||||
new KeyValue(this.translateYProperty(), 0, FXUtils.EASE),
|
new KeyValue(this.translateYProperty(), 0, Motion.EASE),
|
||||||
new KeyValue(this.scaleXProperty(), 1, FXUtils.EASE),
|
new KeyValue(this.scaleXProperty(), 1, Motion.EASE),
|
||||||
new KeyValue(this.scaleYProperty(), 1, FXUtils.EASE),
|
new KeyValue(this.scaleYProperty(), 1, Motion.EASE),
|
||||||
new KeyValue(this.scaleZProperty(), 1, FXUtils.EASE)
|
new KeyValue(this.scaleZProperty(), 1, Motion.EASE)
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
timeline.play();
|
timeline.play();
|
||||||
@@ -277,19 +278,19 @@ public class Decorator extends Control {
|
|||||||
if (AnimationUtils.playWindowAnimation() && OperatingSystem.CURRENT_OS != OperatingSystem.MACOS) {
|
if (AnimationUtils.playWindowAnimation() && OperatingSystem.CURRENT_OS != OperatingSystem.MACOS) {
|
||||||
playRestoreMinimizeAnimation = true;
|
playRestoreMinimizeAnimation = true;
|
||||||
Timeline timeline = new Timeline(
|
Timeline timeline = new Timeline(
|
||||||
new KeyFrame(Duration.millis(0),
|
new KeyFrame(Duration.ZERO,
|
||||||
new KeyValue(this.opacityProperty(), 1, FXUtils.EASE),
|
new KeyValue(this.opacityProperty(), 1, Motion.EASE),
|
||||||
new KeyValue(this.translateYProperty(), 0, FXUtils.EASE),
|
new KeyValue(this.translateYProperty(), 0, Motion.EASE),
|
||||||
new KeyValue(this.scaleXProperty(), 1, FXUtils.EASE),
|
new KeyValue(this.scaleXProperty(), 1, Motion.EASE),
|
||||||
new KeyValue(this.scaleYProperty(), 1, FXUtils.EASE),
|
new KeyValue(this.scaleYProperty(), 1, Motion.EASE),
|
||||||
new KeyValue(this.scaleZProperty(), 1, FXUtils.EASE)
|
new KeyValue(this.scaleZProperty(), 1, Motion.EASE)
|
||||||
),
|
),
|
||||||
new KeyFrame(Duration.millis(200),
|
new KeyFrame(Motion.SHORT4,
|
||||||
new KeyValue(this.opacityProperty(), 0, FXUtils.EASE),
|
new KeyValue(this.opacityProperty(), 0, Motion.EASE),
|
||||||
new KeyValue(this.translateYProperty(), 200, FXUtils.EASE),
|
new KeyValue(this.translateYProperty(), 200, Motion.EASE),
|
||||||
new KeyValue(this.scaleXProperty(), 0.4, FXUtils.EASE),
|
new KeyValue(this.scaleXProperty(), 0.4, Motion.EASE),
|
||||||
new KeyValue(this.scaleYProperty(), 0.4, FXUtils.EASE),
|
new KeyValue(this.scaleYProperty(), 0.4, Motion.EASE),
|
||||||
new KeyValue(this.scaleZProperty(), 0.4, FXUtils.EASE)
|
new KeyValue(this.scaleZProperty(), 0.4, Motion.EASE)
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
timeline.setOnFinished(event -> primaryStage.setIconified(true));
|
timeline.setOnFinished(event -> primaryStage.setIconified(true));
|
||||||
|
|||||||
@@ -1,90 +0,0 @@
|
|||||||
/*
|
|
||||||
* Hello Minecraft! Launcher
|
|
||||||
* Copyright (C) 2021 huangyuhui <huanghongxun2008@126.com> 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 <https://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
package org.jackhuang.hmcl.ui.decorator;
|
|
||||||
|
|
||||||
import javafx.animation.Interpolator;
|
|
||||||
import javafx.animation.KeyFrame;
|
|
||||||
import javafx.animation.KeyValue;
|
|
||||||
import javafx.scene.Node;
|
|
||||||
import javafx.util.Duration;
|
|
||||||
import org.jackhuang.hmcl.ui.animation.AnimationHandler;
|
|
||||||
import org.jackhuang.hmcl.ui.animation.AnimationProducer;
|
|
||||||
import org.jackhuang.hmcl.ui.animation.TransitionPane;
|
|
||||||
import org.jetbrains.annotations.Nullable;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
public final class DecoratorAnimationProducer implements AnimationProducer {
|
|
||||||
@Override
|
|
||||||
public void init(AnimationHandler handler) {
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public List<KeyFrame> animate(AnimationHandler handler) {
|
|
||||||
Node prev = handler.getPreviousNode();
|
|
||||||
Node next = handler.getCurrentNode();
|
|
||||||
if (prev instanceof TransitionPane.EmptyPane) {
|
|
||||||
return Collections.emptyList();
|
|
||||||
}
|
|
||||||
|
|
||||||
Duration halfDuration = handler.getDuration().divide(2);
|
|
||||||
|
|
||||||
List<KeyFrame> keyFrames = new ArrayList<>();
|
|
||||||
|
|
||||||
keyFrames.add(new KeyFrame(Duration.ZERO,
|
|
||||||
new KeyValue(prev.opacityProperty(), 1, Interpolator.EASE_BOTH)));
|
|
||||||
keyFrames.add(new KeyFrame(halfDuration,
|
|
||||||
new KeyValue(prev.opacityProperty(), 0, Interpolator.EASE_BOTH)));
|
|
||||||
if (prev instanceof DecoratorAnimatedPage) {
|
|
||||||
Node left = ((DecoratorAnimatedPage) prev).getLeft();
|
|
||||||
Node center = ((DecoratorAnimatedPage) prev).getCenter();
|
|
||||||
|
|
||||||
keyFrames.add(new KeyFrame(Duration.ZERO,
|
|
||||||
new KeyValue(left.translateXProperty(), 0, Interpolator.EASE_BOTH),
|
|
||||||
new KeyValue(center.translateXProperty(), 0, Interpolator.EASE_BOTH)));
|
|
||||||
keyFrames.add(new KeyFrame(halfDuration,
|
|
||||||
new KeyValue(left.translateXProperty(), -30, Interpolator.EASE_BOTH),
|
|
||||||
new KeyValue(center.translateXProperty(), 30, Interpolator.EASE_BOTH)));
|
|
||||||
}
|
|
||||||
|
|
||||||
keyFrames.add(new KeyFrame(halfDuration,
|
|
||||||
new KeyValue(next.opacityProperty(), 0, Interpolator.EASE_BOTH)));
|
|
||||||
keyFrames.add(new KeyFrame(handler.getDuration(),
|
|
||||||
new KeyValue(next.opacityProperty(), 1, Interpolator.EASE_BOTH)));
|
|
||||||
if (next instanceof DecoratorAnimatedPage) {
|
|
||||||
Node left = ((DecoratorAnimatedPage) next).getLeft();
|
|
||||||
Node center = ((DecoratorAnimatedPage) next).getCenter();
|
|
||||||
|
|
||||||
keyFrames.add(new KeyFrame(halfDuration,
|
|
||||||
new KeyValue(left.translateXProperty(), -30, Interpolator.EASE_BOTH),
|
|
||||||
new KeyValue(center.translateXProperty(), 30, Interpolator.EASE_BOTH)));
|
|
||||||
keyFrames.add(new KeyFrame(handler.getDuration(),
|
|
||||||
new KeyValue(left.translateXProperty(), 0, Interpolator.EASE_BOTH),
|
|
||||||
new KeyValue(center.translateXProperty(), 0, Interpolator.EASE_BOTH)));
|
|
||||||
}
|
|
||||||
|
|
||||||
return keyFrames;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public @Nullable AnimationProducer opposite() {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -19,6 +19,7 @@ package org.jackhuang.hmcl.ui.decorator;
|
|||||||
|
|
||||||
import com.jfoenix.controls.JFXDialog;
|
import com.jfoenix.controls.JFXDialog;
|
||||||
import com.jfoenix.controls.JFXSnackbar;
|
import com.jfoenix.controls.JFXSnackbar;
|
||||||
|
import javafx.animation.Interpolator;
|
||||||
import javafx.animation.KeyFrame;
|
import javafx.animation.KeyFrame;
|
||||||
import javafx.animation.KeyValue;
|
import javafx.animation.KeyValue;
|
||||||
import javafx.animation.Timeline;
|
import javafx.animation.Timeline;
|
||||||
@@ -49,8 +50,7 @@ import org.jackhuang.hmcl.task.Task;
|
|||||||
import org.jackhuang.hmcl.ui.Controllers;
|
import org.jackhuang.hmcl.ui.Controllers;
|
||||||
import org.jackhuang.hmcl.ui.FXUtils;
|
import org.jackhuang.hmcl.ui.FXUtils;
|
||||||
import org.jackhuang.hmcl.ui.account.AddAuthlibInjectorServerPane;
|
import org.jackhuang.hmcl.ui.account.AddAuthlibInjectorServerPane;
|
||||||
import org.jackhuang.hmcl.ui.animation.AnimationUtils;
|
import org.jackhuang.hmcl.ui.animation.*;
|
||||||
import org.jackhuang.hmcl.ui.animation.ContainerAnimations;
|
|
||||||
import org.jackhuang.hmcl.ui.construct.DialogAware;
|
import org.jackhuang.hmcl.ui.construct.DialogAware;
|
||||||
import org.jackhuang.hmcl.ui.construct.DialogCloseEvent;
|
import org.jackhuang.hmcl.ui.construct.DialogCloseEvent;
|
||||||
import org.jackhuang.hmcl.ui.construct.Navigator;
|
import org.jackhuang.hmcl.ui.construct.Navigator;
|
||||||
@@ -90,16 +90,16 @@ public class DecoratorController {
|
|||||||
if (AnimationUtils.playWindowAnimation()) {
|
if (AnimationUtils.playWindowAnimation()) {
|
||||||
Timeline timeline = new Timeline(
|
Timeline timeline = new Timeline(
|
||||||
new KeyFrame(Duration.millis(0),
|
new KeyFrame(Duration.millis(0),
|
||||||
new KeyValue(decorator.opacityProperty(), 1, FXUtils.EASE),
|
new KeyValue(decorator.opacityProperty(), 1, Motion.EASE),
|
||||||
new KeyValue(decorator.scaleXProperty(), 1, FXUtils.EASE),
|
new KeyValue(decorator.scaleXProperty(), 1, Motion.EASE),
|
||||||
new KeyValue(decorator.scaleYProperty(), 1, FXUtils.EASE),
|
new KeyValue(decorator.scaleYProperty(), 1, Motion.EASE),
|
||||||
new KeyValue(decorator.scaleZProperty(), 0.3, FXUtils.EASE)
|
new KeyValue(decorator.scaleZProperty(), 0.3, Motion.EASE)
|
||||||
),
|
),
|
||||||
new KeyFrame(Duration.millis(200),
|
new KeyFrame(Duration.millis(200),
|
||||||
new KeyValue(decorator.opacityProperty(), 0, FXUtils.EASE),
|
new KeyValue(decorator.opacityProperty(), 0, Motion.EASE),
|
||||||
new KeyValue(decorator.scaleXProperty(), 0.8, FXUtils.EASE),
|
new KeyValue(decorator.scaleXProperty(), 0.8, Motion.EASE),
|
||||||
new KeyValue(decorator.scaleYProperty(), 0.8, FXUtils.EASE),
|
new KeyValue(decorator.scaleYProperty(), 0.8, Motion.EASE),
|
||||||
new KeyValue(decorator.scaleZProperty(), 0.8, FXUtils.EASE)
|
new KeyValue(decorator.scaleZProperty(), 0.8, Motion.EASE)
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
timeline.setOnFinished(event -> Launcher.stopApplication());
|
timeline.setOnFinished(event -> Launcher.stopApplication());
|
||||||
@@ -364,10 +364,57 @@ public class DecoratorController {
|
|||||||
|
|
||||||
// ==== Navigation ====
|
// ==== Navigation ====
|
||||||
|
|
||||||
private static final DecoratorAnimationProducer animation = new DecoratorAnimationProducer();
|
private static final TransitionPane.AnimationProducer ANIMATION = (Pane container,
|
||||||
|
Node previousNode, Node nextNode,
|
||||||
|
Duration duration,
|
||||||
|
Interpolator interpolator) -> {
|
||||||
|
Timeline timeline = new Timeline();
|
||||||
|
if (previousNode instanceof TransitionPane.EmptyPane) {
|
||||||
|
return timeline;
|
||||||
|
}
|
||||||
|
|
||||||
|
Duration halfDuration = duration.divide(2);
|
||||||
|
|
||||||
|
List<KeyFrame> keyFrames = new ArrayList<>();
|
||||||
|
|
||||||
|
keyFrames.add(new KeyFrame(Duration.ZERO,
|
||||||
|
new KeyValue(previousNode.opacityProperty(), 1, interpolator)));
|
||||||
|
keyFrames.add(new KeyFrame(halfDuration,
|
||||||
|
new KeyValue(previousNode.opacityProperty(), 0, interpolator)));
|
||||||
|
if (previousNode instanceof DecoratorAnimatedPage prevPage) {
|
||||||
|
Node left = prevPage.getLeft();
|
||||||
|
Node center = prevPage.getCenter();
|
||||||
|
|
||||||
|
keyFrames.add(new KeyFrame(Duration.ZERO,
|
||||||
|
new KeyValue(left.translateXProperty(), 0, interpolator),
|
||||||
|
new KeyValue(center.translateXProperty(), 0, interpolator)));
|
||||||
|
keyFrames.add(new KeyFrame(halfDuration,
|
||||||
|
new KeyValue(left.translateXProperty(), -30, interpolator),
|
||||||
|
new KeyValue(center.translateXProperty(), 30, interpolator)));
|
||||||
|
}
|
||||||
|
|
||||||
|
keyFrames.add(new KeyFrame(halfDuration,
|
||||||
|
new KeyValue(nextNode.opacityProperty(), 0, interpolator)));
|
||||||
|
keyFrames.add(new KeyFrame(duration,
|
||||||
|
new KeyValue(nextNode.opacityProperty(), 1, interpolator)));
|
||||||
|
if (nextNode instanceof DecoratorAnimatedPage nextPage) {
|
||||||
|
Node left = nextPage.getLeft();
|
||||||
|
Node center = nextPage.getCenter();
|
||||||
|
|
||||||
|
keyFrames.add(new KeyFrame(halfDuration,
|
||||||
|
new KeyValue(left.translateXProperty(), -30, interpolator),
|
||||||
|
new KeyValue(center.translateXProperty(), 30, interpolator)));
|
||||||
|
keyFrames.add(new KeyFrame(duration,
|
||||||
|
new KeyValue(left.translateXProperty(), 0, interpolator),
|
||||||
|
new KeyValue(center.translateXProperty(), 0, interpolator)));
|
||||||
|
}
|
||||||
|
|
||||||
|
timeline.getKeyFrames().setAll(keyFrames);
|
||||||
|
return timeline;
|
||||||
|
};
|
||||||
|
|
||||||
public void navigate(Node node) {
|
public void navigate(Node node) {
|
||||||
navigator.navigate(node, animation);
|
navigator.navigate(node, ANIMATION);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void close() {
|
private void close() {
|
||||||
@@ -458,7 +505,9 @@ public class DecoratorController {
|
|||||||
Platform.runLater(() -> showDialog(node));
|
Platform.runLater(() -> showDialog(node));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
dialog = new JFXDialog();
|
dialog = new JFXDialog(AnimationUtils.isAnimationEnabled()
|
||||||
|
? JFXDialog.DialogTransition.CENTER
|
||||||
|
: JFXDialog.DialogTransition.NONE);
|
||||||
dialogPane = new JFXDialogPane();
|
dialogPane = new JFXDialogPane();
|
||||||
|
|
||||||
dialog.setContent(dialogPane);
|
dialog.setContent(dialogPane);
|
||||||
|
|||||||
@@ -1,57 +0,0 @@
|
|||||||
/*
|
|
||||||
* Hello Minecraft! Launcher
|
|
||||||
* Copyright (C) 2020 huangyuhui <huanghongxun2008@126.com> 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 <https://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
package org.jackhuang.hmcl.ui.decorator;
|
|
||||||
|
|
||||||
import javafx.scene.Node;
|
|
||||||
import org.jackhuang.hmcl.ui.animation.AnimationProducer;
|
|
||||||
import org.jackhuang.hmcl.ui.construct.Navigator;
|
|
||||||
|
|
||||||
public abstract class DecoratorNavigatorPage extends DecoratorTransitionPage {
|
|
||||||
protected final Navigator navigator = new Navigator();
|
|
||||||
|
|
||||||
{
|
|
||||||
this.navigator.setOnNavigating(this::onNavigating);
|
|
||||||
this.navigator.setOnNavigated(this::onNavigated);
|
|
||||||
backableProperty().bind(navigator.backableProperty());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void navigate(Node page, AnimationProducer animationProducer) {
|
|
||||||
navigator.navigate(page, animationProducer);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean back() {
|
|
||||||
if (navigator.canGoBack()) {
|
|
||||||
navigator.close();
|
|
||||||
return false;
|
|
||||||
} else {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void onNavigating(Navigator.NavigationEvent event) {
|
|
||||||
if (event.getSource() != this.navigator) return;
|
|
||||||
onNavigating(event.getNode());
|
|
||||||
}
|
|
||||||
|
|
||||||
private void onNavigated(Navigator.NavigationEvent event) {
|
|
||||||
if (event.getSource() != this.navigator) return;
|
|
||||||
onNavigated(event.getNode());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -18,6 +18,10 @@
|
|||||||
package org.jackhuang.hmcl.ui.decorator;
|
package org.jackhuang.hmcl.ui.decorator;
|
||||||
|
|
||||||
import com.jfoenix.controls.JFXButton;
|
import com.jfoenix.controls.JFXButton;
|
||||||
|
import javafx.animation.Interpolator;
|
||||||
|
import javafx.animation.KeyFrame;
|
||||||
|
import javafx.animation.KeyValue;
|
||||||
|
import javafx.animation.Timeline;
|
||||||
import javafx.beans.InvalidationListener;
|
import javafx.beans.InvalidationListener;
|
||||||
import javafx.beans.WeakInvalidationListener;
|
import javafx.beans.WeakInvalidationListener;
|
||||||
import javafx.beans.binding.Bindings;
|
import javafx.beans.binding.Bindings;
|
||||||
@@ -39,12 +43,13 @@ import javafx.scene.paint.Color;
|
|||||||
import javafx.scene.shape.Rectangle;
|
import javafx.scene.shape.Rectangle;
|
||||||
import javafx.stage.Stage;
|
import javafx.stage.Stage;
|
||||||
|
|
||||||
|
import javafx.util.Duration;
|
||||||
import org.jackhuang.hmcl.Metadata;
|
import org.jackhuang.hmcl.Metadata;
|
||||||
import org.jackhuang.hmcl.setting.Theme;
|
import org.jackhuang.hmcl.setting.Theme;
|
||||||
import org.jackhuang.hmcl.ui.FXUtils;
|
import org.jackhuang.hmcl.ui.FXUtils;
|
||||||
import org.jackhuang.hmcl.ui.SVG;
|
import org.jackhuang.hmcl.ui.SVG;
|
||||||
import org.jackhuang.hmcl.ui.animation.AnimationProducer;
|
|
||||||
import org.jackhuang.hmcl.ui.animation.ContainerAnimations;
|
import org.jackhuang.hmcl.ui.animation.ContainerAnimations;
|
||||||
|
import org.jackhuang.hmcl.ui.animation.Motion;
|
||||||
import org.jackhuang.hmcl.ui.animation.TransitionPane;
|
import org.jackhuang.hmcl.ui.animation.TransitionPane;
|
||||||
import org.jackhuang.hmcl.ui.wizard.Navigation;
|
import org.jackhuang.hmcl.ui.wizard.Navigation;
|
||||||
import org.jackhuang.hmcl.util.platform.OperatingSystem;
|
import org.jackhuang.hmcl.util.platform.OperatingSystem;
|
||||||
@@ -205,16 +210,13 @@ public class DecoratorSkin extends SkinBase<Decorator> {
|
|||||||
if (s == null) return;
|
if (s == null) return;
|
||||||
Node node = createNavBar(skinnable, s.getLeftPaneWidth(), s.isBackable(), skinnable.canCloseProperty().get(), skinnable.showCloseAsHomeProperty().get(), s.isRefreshable(), s.getTitle(), s.getTitleNode());
|
Node node = createNavBar(skinnable, s.getLeftPaneWidth(), s.isBackable(), skinnable.canCloseProperty().get(), skinnable.showCloseAsHomeProperty().get(), s.isRefreshable(), s.getTitle(), s.getTitleNode());
|
||||||
if (s.isAnimate()) {
|
if (s.isAnimate()) {
|
||||||
AnimationProducer animation;
|
TransitionPane.AnimationProducer animation = switch (skinnable.getNavigationDirection()) {
|
||||||
if (skinnable.getNavigationDirection() == Navigation.NavigationDirection.NEXT) {
|
case NEXT -> NavBarAnimations.NEXT;
|
||||||
animation = ContainerAnimations.SWIPE_LEFT_FADE_SHORT;
|
case PREVIOUS -> NavBarAnimations.PREVIOUS;
|
||||||
} else if (skinnable.getNavigationDirection() == Navigation.NavigationDirection.PREVIOUS) {
|
default -> ContainerAnimations.FADE;
|
||||||
animation = ContainerAnimations.SWIPE_RIGHT_FADE_SHORT;
|
};
|
||||||
} else {
|
|
||||||
animation = ContainerAnimations.FADE;
|
|
||||||
}
|
|
||||||
skinnable.setNavigationDirection(Navigation.NavigationDirection.START);
|
skinnable.setNavigationDirection(Navigation.NavigationDirection.START);
|
||||||
navBarPane.setContent(node, animation);
|
navBarPane.setContent(node, animation, Motion.SHORT4);
|
||||||
} else {
|
} else {
|
||||||
navBarPane.getChildren().setAll(node);
|
navBarPane.getChildren().setAll(node);
|
||||||
}
|
}
|
||||||
@@ -500,4 +502,78 @@ public class DecoratorSkin extends SkinBase<Decorator> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enum NavBarAnimations implements TransitionPane.AnimationProducer {
|
||||||
|
NEXT {
|
||||||
|
@Override
|
||||||
|
public void init(TransitionPane container, Node previousNode, Node nextNode) {
|
||||||
|
previousNode.setScaleX(1);
|
||||||
|
previousNode.setScaleY(1);
|
||||||
|
previousNode.setOpacity(0);
|
||||||
|
previousNode.setTranslateX(0);
|
||||||
|
nextNode.setScaleX(1);
|
||||||
|
nextNode.setScaleY(1);
|
||||||
|
nextNode.setOpacity(1);
|
||||||
|
nextNode.setTranslateX(container.getWidth());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Timeline animate(
|
||||||
|
Pane container, Node previousNode, Node nextNode,
|
||||||
|
Duration duration, Interpolator interpolator) {
|
||||||
|
return new Timeline(
|
||||||
|
new KeyFrame(Duration.ZERO,
|
||||||
|
new KeyValue(nextNode.translateXProperty(), 50, interpolator),
|
||||||
|
new KeyValue(previousNode.translateXProperty(), 0, interpolator),
|
||||||
|
new KeyValue(nextNode.opacityProperty(), 0, interpolator),
|
||||||
|
new KeyValue(previousNode.opacityProperty(), 1, interpolator)),
|
||||||
|
new KeyFrame(duration,
|
||||||
|
new KeyValue(nextNode.translateXProperty(), 0, interpolator),
|
||||||
|
new KeyValue(previousNode.translateXProperty(), -50, interpolator),
|
||||||
|
new KeyValue(nextNode.opacityProperty(), 1, interpolator),
|
||||||
|
new KeyValue(previousNode.opacityProperty(), 0, interpolator))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public TransitionPane.AnimationProducer opposite() {
|
||||||
|
return NEXT;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
PREVIOUS {
|
||||||
|
@Override
|
||||||
|
public void init(TransitionPane container, Node previousNode, Node nextNode) {
|
||||||
|
previousNode.setScaleX(1);
|
||||||
|
previousNode.setScaleY(1);
|
||||||
|
previousNode.setOpacity(1);
|
||||||
|
previousNode.setTranslateX(0);
|
||||||
|
nextNode.setScaleX(1);
|
||||||
|
nextNode.setScaleY(1);
|
||||||
|
nextNode.setOpacity(0);
|
||||||
|
nextNode.setTranslateX(container.getWidth());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Timeline animate(Pane container, Node previousNode, Node nextNode, Duration duration, Interpolator interpolator) {
|
||||||
|
return new Timeline(
|
||||||
|
new KeyFrame(Duration.ZERO,
|
||||||
|
new KeyValue(nextNode.translateXProperty(), -50, interpolator),
|
||||||
|
new KeyValue(previousNode.translateXProperty(), 0, interpolator),
|
||||||
|
new KeyValue(nextNode.opacityProperty(), 0, interpolator),
|
||||||
|
new KeyValue(previousNode.opacityProperty(), 1, interpolator)),
|
||||||
|
new KeyFrame(duration,
|
||||||
|
new KeyValue(nextNode.translateXProperty(), 0, interpolator),
|
||||||
|
new KeyValue(previousNode.translateXProperty(), 50, interpolator),
|
||||||
|
new KeyValue(nextNode.opacityProperty(), 1, interpolator),
|
||||||
|
new KeyValue(previousNode.opacityProperty(), 0, interpolator))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public TransitionPane.AnimationProducer opposite() {
|
||||||
|
return PREVIOUS;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,73 +0,0 @@
|
|||||||
/*
|
|
||||||
* Hello Minecraft! Launcher
|
|
||||||
* Copyright (C) 2020 huangyuhui <huanghongxun2008@126.com> 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 <https://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
package org.jackhuang.hmcl.ui.decorator;
|
|
||||||
|
|
||||||
import javafx.beans.property.ObjectProperty;
|
|
||||||
import javafx.beans.property.SimpleObjectProperty;
|
|
||||||
import javafx.collections.FXCollections;
|
|
||||||
import javafx.collections.ObservableList;
|
|
||||||
import javafx.scene.control.SingleSelectionModel;
|
|
||||||
import org.jackhuang.hmcl.ui.animation.ContainerAnimations;
|
|
||||||
import org.jackhuang.hmcl.ui.construct.Navigator;
|
|
||||||
import org.jackhuang.hmcl.ui.construct.TabControl;
|
|
||||||
import org.jackhuang.hmcl.ui.construct.TabHeader;
|
|
||||||
import org.jackhuang.hmcl.ui.wizard.Navigation;
|
|
||||||
|
|
||||||
public abstract class DecoratorTabPage extends DecoratorTransitionPage implements TabControl {
|
|
||||||
|
|
||||||
public DecoratorTabPage() {
|
|
||||||
getSelectionModel().selectedItemProperty().addListener((a, b, newValue) -> {
|
|
||||||
newValue.initializeIfNeeded();
|
|
||||||
if (newValue.getNode() != null) {
|
|
||||||
onNavigating(getCurrentPage());
|
|
||||||
if (getCurrentPage() != null) getCurrentPage().fireEvent(new Navigator.NavigationEvent(null, getCurrentPage(), Navigation.NavigationDirection.NEXT, Navigator.NavigationEvent.NAVIGATING));
|
|
||||||
navigate(newValue.getNode(), ContainerAnimations.FADE);
|
|
||||||
onNavigated(getCurrentPage());
|
|
||||||
if (getCurrentPage() != null) getCurrentPage().fireEvent(new Navigator.NavigationEvent(null, getCurrentPage(), Navigation.NavigationDirection.NEXT, Navigator.NavigationEvent.NAVIGATED));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public DecoratorTabPage(TabHeader.Tab<?>... tabs) {
|
|
||||||
this();
|
|
||||||
if (tabs != null) {
|
|
||||||
getTabs().addAll(tabs);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private ObservableList<TabHeader.Tab<?>> tabs = FXCollections.observableArrayList();
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public ObservableList<TabHeader.Tab<?>> getTabs() {
|
|
||||||
return tabs;
|
|
||||||
}
|
|
||||||
|
|
||||||
private final ObjectProperty<SingleSelectionModel<TabHeader.Tab<?>>> selectionModel = new SimpleObjectProperty<>(this, "selectionModel", new TabControl.TabControlSelectionModel(this));
|
|
||||||
|
|
||||||
public SingleSelectionModel<TabHeader.Tab<?>> getSelectionModel() {
|
|
||||||
return selectionModel.get();
|
|
||||||
}
|
|
||||||
|
|
||||||
public ObjectProperty<SingleSelectionModel<TabHeader.Tab<?>>> selectionModelProperty() {
|
|
||||||
return selectionModel;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setSelectionModel(SingleSelectionModel<TabHeader.Tab<?>> selectionModel) {
|
|
||||||
this.selectionModel.set(selectionModel);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -17,6 +17,7 @@
|
|||||||
*/
|
*/
|
||||||
package org.jackhuang.hmcl.ui.decorator;
|
package org.jackhuang.hmcl.ui.decorator;
|
||||||
|
|
||||||
|
import javafx.animation.Interpolator;
|
||||||
import javafx.beans.binding.Bindings;
|
import javafx.beans.binding.Bindings;
|
||||||
import javafx.beans.property.*;
|
import javafx.beans.property.*;
|
||||||
import javafx.scene.Node;
|
import javafx.scene.Node;
|
||||||
@@ -24,7 +25,7 @@ import javafx.scene.control.Control;
|
|||||||
import javafx.scene.control.Skin;
|
import javafx.scene.control.Skin;
|
||||||
import javafx.scene.layout.Region;
|
import javafx.scene.layout.Region;
|
||||||
import javafx.scene.layout.StackPane;
|
import javafx.scene.layout.StackPane;
|
||||||
import org.jackhuang.hmcl.ui.animation.AnimationProducer;
|
import javafx.util.Duration;
|
||||||
import org.jackhuang.hmcl.ui.animation.TransitionPane;
|
import org.jackhuang.hmcl.ui.animation.TransitionPane;
|
||||||
import org.jackhuang.hmcl.ui.wizard.Refreshable;
|
import org.jackhuang.hmcl.ui.wizard.Refreshable;
|
||||||
|
|
||||||
@@ -36,8 +37,8 @@ public abstract class DecoratorTransitionPage extends Control implements Decorat
|
|||||||
private Node currentPage;
|
private Node currentPage;
|
||||||
protected final TransitionPane transitionPane = new TransitionPane();
|
protected final TransitionPane transitionPane = new TransitionPane();
|
||||||
|
|
||||||
protected void navigate(Node page, AnimationProducer animation) {
|
protected void navigate(Node page, TransitionPane.AnimationProducer animation, Duration duration, Interpolator interpolator) {
|
||||||
transitionPane.setContent(currentPage = page, animation);
|
transitionPane.setContent(currentPage = page, animation, duration, interpolator);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void onNavigating(Node from) {
|
protected void onNavigating(Node from) {
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ package org.jackhuang.hmcl.ui.decorator;
|
|||||||
import javafx.scene.Node;
|
import javafx.scene.Node;
|
||||||
import javafx.scene.control.SkinBase;
|
import javafx.scene.control.SkinBase;
|
||||||
import org.jackhuang.hmcl.task.Task;
|
import org.jackhuang.hmcl.task.Task;
|
||||||
|
import org.jackhuang.hmcl.ui.animation.Motion;
|
||||||
import org.jackhuang.hmcl.ui.construct.Navigator;
|
import org.jackhuang.hmcl.ui.construct.Navigator;
|
||||||
import org.jackhuang.hmcl.ui.construct.PageCloseEvent;
|
import org.jackhuang.hmcl.ui.construct.PageCloseEvent;
|
||||||
import org.jackhuang.hmcl.ui.wizard.*;
|
import org.jackhuang.hmcl.ui.wizard.*;
|
||||||
@@ -74,7 +75,7 @@ public class DecoratorWizardDisplayer extends DecoratorTransitionPage implements
|
|||||||
@Override
|
@Override
|
||||||
public void navigateTo(Node page, Navigation.NavigationDirection nav) {
|
public void navigateTo(Node page, Navigation.NavigationDirection nav) {
|
||||||
displayer.navigateTo(page, nav);
|
displayer.navigateTo(page, nav);
|
||||||
navigate(page, nav.getAnimation());
|
navigate(page, nav.getAnimation(), Motion.SHORT4, Motion.EASE);
|
||||||
|
|
||||||
String prefix = category == null ? "" : category + " - ";
|
String prefix = category == null ? "" : category + " - ";
|
||||||
|
|
||||||
|
|||||||
@@ -33,6 +33,7 @@ import javafx.scene.layout.BorderPane;
|
|||||||
import org.jackhuang.hmcl.ui.FXUtils;
|
import org.jackhuang.hmcl.ui.FXUtils;
|
||||||
import org.jackhuang.hmcl.ui.InstallerItem;
|
import org.jackhuang.hmcl.ui.InstallerItem;
|
||||||
import org.jackhuang.hmcl.ui.construct.MessageDialogPane;
|
import org.jackhuang.hmcl.ui.construct.MessageDialogPane;
|
||||||
|
import org.jackhuang.hmcl.ui.wizard.Navigation;
|
||||||
import org.jackhuang.hmcl.ui.wizard.WizardController;
|
import org.jackhuang.hmcl.ui.wizard.WizardController;
|
||||||
import org.jackhuang.hmcl.ui.wizard.WizardPage;
|
import org.jackhuang.hmcl.ui.wizard.WizardPage;
|
||||||
import org.jackhuang.hmcl.util.SettingsMap;
|
import org.jackhuang.hmcl.util.SettingsMap;
|
||||||
@@ -60,7 +61,16 @@ public abstract class AbstractInstallersPage extends Control implements WizardPa
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!(library.resolvedStateProperty().get() instanceof InstallerItem.IncompatibleState))
|
if (!(library.resolvedStateProperty().get() instanceof InstallerItem.IncompatibleState))
|
||||||
controller.onNext(new VersionsPage(controller, i18n("install.installer.choose", i18n("install.installer." + libraryId)), gameVersion, downloadProvider, libraryId, () -> controller.onPrev(false)));
|
controller.onNext(
|
||||||
|
new VersionsPage(
|
||||||
|
controller,
|
||||||
|
i18n("install.installer.choose", i18n("install.installer." + libraryId)),
|
||||||
|
gameVersion,
|
||||||
|
downloadProvider,
|
||||||
|
libraryId,
|
||||||
|
() -> controller.onPrev(false, Navigation.NavigationDirection.NEXT)
|
||||||
|
), Navigation.NavigationDirection.NEXT
|
||||||
|
);
|
||||||
});
|
});
|
||||||
library.setOnRemove(() -> {
|
library.setOnRemove(() -> {
|
||||||
controller.getSettings().remove(libraryId);
|
controller.getSettings().remove(libraryId);
|
||||||
|
|||||||
@@ -36,7 +36,6 @@ import org.jackhuang.hmcl.ui.Controllers;
|
|||||||
import org.jackhuang.hmcl.ui.FXUtils;
|
import org.jackhuang.hmcl.ui.FXUtils;
|
||||||
import org.jackhuang.hmcl.ui.SVG;
|
import org.jackhuang.hmcl.ui.SVG;
|
||||||
import org.jackhuang.hmcl.ui.WeakListenerHolder;
|
import org.jackhuang.hmcl.ui.WeakListenerHolder;
|
||||||
import org.jackhuang.hmcl.ui.animation.ContainerAnimations;
|
|
||||||
import org.jackhuang.hmcl.ui.animation.TransitionPane;
|
import org.jackhuang.hmcl.ui.animation.TransitionPane;
|
||||||
import org.jackhuang.hmcl.ui.construct.AdvancedListBox;
|
import org.jackhuang.hmcl.ui.construct.AdvancedListBox;
|
||||||
import org.jackhuang.hmcl.ui.construct.MessageDialogPane;
|
import org.jackhuang.hmcl.ui.construct.MessageDialogPane;
|
||||||
@@ -105,9 +104,7 @@ public class DownloadPage extends DecoratorAnimatedPage implements DecoratorPage
|
|||||||
Profiles.registerVersionsListener(this::loadVersions);
|
Profiles.registerVersionsListener(this::loadVersions);
|
||||||
|
|
||||||
tab.select(newGameTab);
|
tab.select(newGameTab);
|
||||||
FXUtils.onChangeAndOperate(tab.getSelectionModel().selectedItemProperty(), newValue -> {
|
transitionPane.bindTabHeader(tab);
|
||||||
transitionPane.setContent(newValue.getNode(), ContainerAnimations.FADE);
|
|
||||||
});
|
|
||||||
|
|
||||||
AdvancedListBox sideBar = new AdvancedListBox()
|
AdvancedListBox sideBar = new AdvancedListBox()
|
||||||
.startCategory(i18n("download.game").toUpperCase(Locale.ROOT))
|
.startCategory(i18n("download.game").toUpperCase(Locale.ROOT))
|
||||||
|
|||||||
@@ -23,7 +23,6 @@ import org.jackhuang.hmcl.setting.Profile;
|
|||||||
import org.jackhuang.hmcl.setting.Profiles;
|
import org.jackhuang.hmcl.setting.Profiles;
|
||||||
import org.jackhuang.hmcl.ui.FXUtils;
|
import org.jackhuang.hmcl.ui.FXUtils;
|
||||||
import org.jackhuang.hmcl.ui.SVG;
|
import org.jackhuang.hmcl.ui.SVG;
|
||||||
import org.jackhuang.hmcl.ui.animation.ContainerAnimations;
|
|
||||||
import org.jackhuang.hmcl.ui.animation.TransitionPane;
|
import org.jackhuang.hmcl.ui.animation.TransitionPane;
|
||||||
import org.jackhuang.hmcl.ui.construct.*;
|
import org.jackhuang.hmcl.ui.construct.*;
|
||||||
import org.jackhuang.hmcl.ui.decorator.DecoratorAnimatedPage;
|
import org.jackhuang.hmcl.ui.decorator.DecoratorAnimatedPage;
|
||||||
@@ -60,10 +59,7 @@ public class LauncherSettingsPage extends DecoratorAnimatedPage implements Decor
|
|||||||
|
|
||||||
tab.select(gameTab);
|
tab.select(gameTab);
|
||||||
addEventHandler(Navigator.NavigationEvent.NAVIGATED, event -> gameTab.getNode().loadVersion(Profiles.getSelectedProfile(), null));
|
addEventHandler(Navigator.NavigationEvent.NAVIGATED, event -> gameTab.getNode().loadVersion(Profiles.getSelectedProfile(), null));
|
||||||
transitionPane.setContent(gameTab.getNode(), ContainerAnimations.NONE);
|
transitionPane.bindTabHeader(tab);
|
||||||
FXUtils.onChange(tab.getSelectionModel().selectedItemProperty(), newValue -> {
|
|
||||||
transitionPane.setContent(newValue.getNode(), ContainerAnimations.FADE);
|
|
||||||
});
|
|
||||||
|
|
||||||
AdvancedListBox sideBar = new AdvancedListBox()
|
AdvancedListBox sideBar = new AdvancedListBox()
|
||||||
.addNavigationDrawerTab(tab, gameTab, i18n("settings.type.global.manage"), SVG.STADIA_CONTROLLER)
|
.addNavigationDrawerTab(tab, gameTab, i18n("settings.type.global.manage"), SVG.STADIA_CONTROLLER)
|
||||||
|
|||||||
@@ -535,7 +535,7 @@ public class TerracottaControllerPage extends StackPane {
|
|||||||
children.addAll(nodesProperty);
|
children.addAll(nodesProperty);
|
||||||
}
|
}
|
||||||
|
|
||||||
transition.setContent(components, ContainerAnimations.SWIPE_LEFT_FADE_SHORT);
|
transition.setContent(components, ContainerAnimations.SLIDE_UP_FADE_IN);
|
||||||
};
|
};
|
||||||
listener.changed(UI_STATE, null, UI_STATE.get());
|
listener.changed(UI_STATE, null, UI_STATE.get());
|
||||||
holder.add(listener);
|
holder.add(listener);
|
||||||
@@ -718,7 +718,7 @@ public class TerracottaControllerPage extends StackPane {
|
|||||||
pane.getChildren().add(item);
|
pane.getChildren().add(item);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.transition.setContent(pane, ContainerAnimations.SWIPE_LEFT_FADE_SHORT);
|
this.transition.setContent(pane, ContainerAnimations.SLIDE_UP_FADE_IN);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -31,7 +31,6 @@ import org.jackhuang.hmcl.terracotta.TerracottaMetadata;
|
|||||||
import org.jackhuang.hmcl.ui.Controllers;
|
import org.jackhuang.hmcl.ui.Controllers;
|
||||||
import org.jackhuang.hmcl.ui.FXUtils;
|
import org.jackhuang.hmcl.ui.FXUtils;
|
||||||
import org.jackhuang.hmcl.ui.SVG;
|
import org.jackhuang.hmcl.ui.SVG;
|
||||||
import org.jackhuang.hmcl.ui.animation.ContainerAnimations;
|
|
||||||
import org.jackhuang.hmcl.ui.animation.TransitionPane;
|
import org.jackhuang.hmcl.ui.animation.TransitionPane;
|
||||||
import org.jackhuang.hmcl.ui.construct.AdvancedListBox;
|
import org.jackhuang.hmcl.ui.construct.AdvancedListBox;
|
||||||
import org.jackhuang.hmcl.ui.construct.PageAware;
|
import org.jackhuang.hmcl.ui.construct.PageAware;
|
||||||
@@ -58,11 +57,7 @@ public class TerracottaPage extends DecoratorAnimatedPage implements DecoratorPa
|
|||||||
statusPage.setNodeSupplier(TerracottaControllerPage::new);
|
statusPage.setNodeSupplier(TerracottaControllerPage::new);
|
||||||
tab = new TabHeader(statusPage);
|
tab = new TabHeader(statusPage);
|
||||||
tab.select(statusPage);
|
tab.select(statusPage);
|
||||||
|
transitionPane.bindTabHeader(tab);
|
||||||
transitionPane.setContent(statusPage.getNode(), ContainerAnimations.NONE);
|
|
||||||
FXUtils.onChange(tab.getSelectionModel().selectedItemProperty(), newValue -> {
|
|
||||||
transitionPane.setContent(newValue.getNode(), ContainerAnimations.FADE);
|
|
||||||
});
|
|
||||||
|
|
||||||
BorderPane left = new BorderPane();
|
BorderPane left = new BorderPane();
|
||||||
FXUtils.setLimitWidth(left, 200);
|
FXUtils.setLimitWidth(left, 200);
|
||||||
|
|||||||
@@ -37,7 +37,6 @@ import org.jackhuang.hmcl.setting.Profile;
|
|||||||
import org.jackhuang.hmcl.ui.FXUtils;
|
import org.jackhuang.hmcl.ui.FXUtils;
|
||||||
import org.jackhuang.hmcl.ui.SVG;
|
import org.jackhuang.hmcl.ui.SVG;
|
||||||
import org.jackhuang.hmcl.ui.WeakListenerHolder;
|
import org.jackhuang.hmcl.ui.WeakListenerHolder;
|
||||||
import org.jackhuang.hmcl.ui.animation.ContainerAnimations;
|
|
||||||
import org.jackhuang.hmcl.ui.animation.TransitionPane;
|
import org.jackhuang.hmcl.ui.animation.TransitionPane;
|
||||||
import org.jackhuang.hmcl.ui.construct.*;
|
import org.jackhuang.hmcl.ui.construct.*;
|
||||||
import org.jackhuang.hmcl.ui.decorator.DecoratorAnimatedPage;
|
import org.jackhuang.hmcl.ui.decorator.DecoratorAnimatedPage;
|
||||||
@@ -77,10 +76,7 @@ public class VersionPage extends DecoratorAnimatedPage implements DecoratorPage
|
|||||||
addEventHandler(Navigator.NavigationEvent.NAVIGATED, this::onNavigated);
|
addEventHandler(Navigator.NavigationEvent.NAVIGATED, this::onNavigated);
|
||||||
|
|
||||||
tab.select(versionSettingsTab);
|
tab.select(versionSettingsTab);
|
||||||
transitionPane.setContent(versionSettingsTab.getNode(), ContainerAnimations.NONE);
|
transitionPane.bindTabHeader(tab);
|
||||||
FXUtils.onChange(tab.getSelectionModel().selectedItemProperty(), newValue -> {
|
|
||||||
transitionPane.setContent(newValue.getNode(), ContainerAnimations.FADE);
|
|
||||||
});
|
|
||||||
|
|
||||||
listenerHolder.add(EventBus.EVENT_BUS.channel(RefreshedVersionsEvent.class).registerWeak(event -> checkSelectedVersion(), EventPriority.HIGHEST));
|
listenerHolder.add(EventBus.EVENT_BUS.channel(RefreshedVersionsEvent.class).registerWeak(event -> checkSelectedVersion(), EventPriority.HIGHEST));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,7 +28,6 @@ import javafx.scene.layout.VBox;
|
|||||||
import org.jackhuang.hmcl.game.World;
|
import org.jackhuang.hmcl.game.World;
|
||||||
import org.jackhuang.hmcl.ui.FXUtils;
|
import org.jackhuang.hmcl.ui.FXUtils;
|
||||||
import org.jackhuang.hmcl.ui.SVG;
|
import org.jackhuang.hmcl.ui.SVG;
|
||||||
import org.jackhuang.hmcl.ui.animation.ContainerAnimations;
|
|
||||||
import org.jackhuang.hmcl.ui.animation.TransitionPane;
|
import org.jackhuang.hmcl.ui.animation.TransitionPane;
|
||||||
import org.jackhuang.hmcl.ui.construct.*;
|
import org.jackhuang.hmcl.ui.construct.*;
|
||||||
import org.jackhuang.hmcl.ui.decorator.DecoratorAnimatedPage;
|
import org.jackhuang.hmcl.ui.decorator.DecoratorAnimatedPage;
|
||||||
@@ -73,9 +72,8 @@ public final class WorldManagePage extends DecoratorAnimatedPage implements Deco
|
|||||||
datapackTab.setNodeSupplier(() -> new DatapackListPage(this));
|
datapackTab.setNodeSupplier(() -> new DatapackListPage(this));
|
||||||
|
|
||||||
header.select(worldInfoTab);
|
header.select(worldInfoTab);
|
||||||
transitionPane.setContent(worldInfoTab.getNode(), ContainerAnimations.NONE);
|
transitionPane.bindTabHeader(header);
|
||||||
FXUtils.onChange(header.getSelectionModel().selectedItemProperty(), newValue ->
|
|
||||||
transitionPane.setContent(newValue.getNode(), ContainerAnimations.FADE));
|
|
||||||
setCenter(transitionPane);
|
setCenter(transitionPane);
|
||||||
|
|
||||||
BorderPane left = new BorderPane();
|
BorderPane left = new BorderPane();
|
||||||
|
|||||||
@@ -40,11 +40,9 @@ public interface Navigation {
|
|||||||
|
|
||||||
enum NavigationDirection {
|
enum NavigationDirection {
|
||||||
START(ContainerAnimations.NONE),
|
START(ContainerAnimations.NONE),
|
||||||
PREVIOUS(ContainerAnimations.SWIPE_RIGHT),
|
PREVIOUS(ContainerAnimations.BACKWARD),
|
||||||
NEXT(ContainerAnimations.SWIPE_LEFT),
|
NEXT(ContainerAnimations.FORWARD),
|
||||||
FINISH(ContainerAnimations.SWIPE_LEFT),
|
FINISH(ContainerAnimations.FORWARD);
|
||||||
IN(ContainerAnimations.ZOOM_IN),
|
|
||||||
OUT(ContainerAnimations.ZOOM_OUT);
|
|
||||||
|
|
||||||
private final ContainerAnimations animation;
|
private final ContainerAnimations animation;
|
||||||
|
|
||||||
|
|||||||
@@ -83,6 +83,10 @@ public class WizardController implements Navigation {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void onNext(Node page) {
|
public void onNext(Node page) {
|
||||||
|
onNext(page, NavigationDirection.NEXT);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onNext(Node page, NavigationDirection direction) {
|
||||||
pages.push(page);
|
pages.push(page);
|
||||||
|
|
||||||
if (stopped) { // navigatingTo may stop this wizard.
|
if (stopped) { // navigatingTo may stop this wizard.
|
||||||
@@ -93,11 +97,15 @@ public class WizardController implements Navigation {
|
|||||||
((WizardPage) page).onNavigate(settings);
|
((WizardPage) page).onNavigate(settings);
|
||||||
|
|
||||||
LOG.info("Navigating to " + page + ", pages: " + pages);
|
LOG.info("Navigating to " + page + ", pages: " + pages);
|
||||||
displayer.navigateTo(page, NavigationDirection.NEXT);
|
displayer.navigateTo(page, direction);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onPrev(boolean cleanUp) {
|
public void onPrev(boolean cleanUp) {
|
||||||
|
onPrev(cleanUp, NavigationDirection.PREVIOUS);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onPrev(boolean cleanUp, NavigationDirection direction) {
|
||||||
if (!canPrev()) {
|
if (!canPrev()) {
|
||||||
if (provider.cancelIfCannotGoBack()) {
|
if (provider.cancelIfCannotGoBack()) {
|
||||||
onCancel();
|
onCancel();
|
||||||
@@ -116,7 +124,7 @@ public class WizardController implements Navigation {
|
|||||||
((WizardPage) prevPage).onNavigate(settings);
|
((WizardPage) prevPage).onNavigate(settings);
|
||||||
|
|
||||||
LOG.info("Navigating to " + prevPage + ", pages: " + pages);
|
LOG.info("Navigating to " + prevPage + ", pages: " + pages);
|
||||||
displayer.navigateTo(prevPage, NavigationDirection.PREVIOUS);
|
displayer.navigateTo(prevPage, direction);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
Reference in New Issue
Block a user