Refactor tab animation handling for consistency and clarity (#7210)
This commit is contained in:
1 parent
88222af99a
commit
a262a95408
3 files changed
+58
-61
No files matched your search
@@ -178,34 +178,6 @@ public enum ContainerAnimations implements TransitionPane.AnimationProducer {
|
||||
}
|
||||
},
|
||||
|
||||
/// Preserves the original slide-up and fade-in transition.
|
||||
TAB_UP {
|
||||
/// Creates a 400 ms slide-and-fade transition along the selected vertical direction.
|
||||
@Override
|
||||
public Timeline animate(Pane container, Node previousNode, Node nextNode) {
|
||||
return animateTab(container, previousNode, nextNode, 1);
|
||||
}
|
||||
|
||||
/// Returns the reverse tab transition.
|
||||
@Override
|
||||
public TransitionPane.AnimationProducer opposite() {
|
||||
return TAB_DOWN;
|
||||
}
|
||||
},
|
||||
/// Mirrors the original slide-up and fade-in transition vertically.
|
||||
TAB_DOWN {
|
||||
/// Creates a 400 ms slide-and-fade transition along the selected vertical direction.
|
||||
@Override
|
||||
public Timeline animate(Pane container, Node previousNode, Node nextNode) {
|
||||
return animateTab(container, previousNode, nextNode, -1);
|
||||
}
|
||||
|
||||
/// Returns the reverse tab transition.
|
||||
@Override
|
||||
public TransitionPane.AnimationProducer opposite() {
|
||||
return TAB_UP;
|
||||
}
|
||||
},
|
||||
NAVIGATION {
|
||||
/// Also clears the horizontal offsets of the target page's navigation panels.
|
||||
@Override
|
||||
@@ -280,27 +252,4 @@ public enum ContainerAnimations implements TransitionPane.AnimationProducer {
|
||||
}
|
||||
},
|
||||
;
|
||||
|
||||
/// Creates the slide-and-fade transition with emphasized easing in the selected direction.
|
||||
/// The previous page stays still and fades out halfway through the incoming page's motion.
|
||||
private static Timeline animateTab(Pane container, Node previousNode, Node nextNode, double direction) {
|
||||
Duration duration = Motion.MEDIUM4;
|
||||
double extent = container.getHeight();
|
||||
double offset = (extent > 0 ? extent * 0.2 : 50) * direction;
|
||||
var previousTranslation = previousNode.translateYProperty();
|
||||
var nextTranslation = nextNode.translateYProperty();
|
||||
return new Timeline(
|
||||
new KeyFrame(Duration.ZERO,
|
||||
new KeyValue(nextNode.cacheProperty(), isCacheAllowed(nextNode), Interpolator.DISCRETE),
|
||||
new KeyValue(previousTranslation, 0, Motion.EASE_IN_OUT_CUBIC_EMPHASIZED),
|
||||
new KeyValue(previousNode.opacityProperty(), 1, Motion.EASE_IN_OUT_CUBIC_EMPHASIZED),
|
||||
new KeyValue(nextNode.opacityProperty(), 0, Motion.EASE_IN_OUT_CUBIC_EMPHASIZED),
|
||||
new KeyValue(nextTranslation, offset, Motion.EASE_IN_OUT_CUBIC_EMPHASIZED)),
|
||||
new KeyFrame(duration.multiply(0.5),
|
||||
new KeyValue(previousNode.opacityProperty(), 0, Motion.EASE_IN_OUT_CUBIC_EMPHASIZED)),
|
||||
new KeyFrame(duration,
|
||||
new KeyValue(nextNode.cacheProperty(), nextNode.isCache(), Interpolator.DISCRETE),
|
||||
new KeyValue(nextNode.opacityProperty(), 1, Motion.EASE_IN_OUT_CUBIC_EMPHASIZED),
|
||||
new KeyValue(nextTranslation, 0, Motion.EASE_IN_OUT_CUBIC_EMPHASIZED)));
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,7 @@
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later WITH AdditionRef-HMCL
|
||||
package org.jackhuang.hmcl.ui.construct;
|
||||
|
||||
import javafx.animation.*;
|
||||
import javafx.beans.property.ObjectProperty;
|
||||
import javafx.beans.property.ReadOnlyObjectProperty;
|
||||
import javafx.beans.property.SimpleObjectProperty;
|
||||
@@ -9,13 +10,18 @@ import javafx.collections.FXCollections;
|
||||
import javafx.collections.ListChangeListener;
|
||||
import javafx.collections.ObservableList;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.layout.Pane;
|
||||
import javafx.util.Duration;
|
||||
import org.jackhuang.hmcl.ui.animation.ContainerAnimations;
|
||||
import org.jackhuang.hmcl.ui.animation.Motion;
|
||||
import org.jackhuang.hmcl.ui.animation.TransitionPane;
|
||||
import org.jetbrains.annotations.NotNullByDefault;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
import static org.jackhuang.hmcl.ui.animation.TransitionPane.isCacheAllowed;
|
||||
|
||||
/// Manages tab selection, lazy content initialization, and page lifecycle callbacks.
|
||||
/// An optional transition pane displays content activated by [#selectTab(TabItem)].
|
||||
/// The first tab is selected when an unselected list gains tabs, without initializing
|
||||
@@ -31,15 +37,15 @@ public final class TabController implements PageAware {
|
||||
/// Creates a controller with the supplied tabs and no content destination.
|
||||
///
|
||||
/// @param tabs the initial tabs in display order, or null for an empty controller
|
||||
public TabController(TabItem<?> ... tabs) {
|
||||
public TabController(TabItem<?>... tabs) {
|
||||
this(null, tabs);
|
||||
}
|
||||
|
||||
/// Creates a controller with an optional destination for selected content.
|
||||
///
|
||||
/// @param contentPane the destination updated by [#selectTab(TabItem)], or null
|
||||
/// @param tabs the initial tabs in display order, or null for an empty controller
|
||||
public TabController(@Nullable TransitionPane contentPane, TabItem<?> ... tabs) {
|
||||
/// @param tabs the initial tabs in display order, or null for an empty controller
|
||||
public TabController(@Nullable TransitionPane contentPane, TabItem<?>... tabs) {
|
||||
this.contentPane = contentPane;
|
||||
this.tabs.addListener(this::tabsChanged);
|
||||
this.tabs.setAll(tabs);
|
||||
@@ -90,11 +96,11 @@ public final class TabController implements PageAware {
|
||||
/// Exceptions from subsequent callbacks or content replacement propagate without
|
||||
/// rolling back completed effects. A shown callback runs after selection is updated.
|
||||
///
|
||||
/// @param tab a tab belonging to [#getTabs()]
|
||||
/// @param tab a tab belonging to [#getTabs()]
|
||||
/// @param playAnimation whether to animate replacement of existing content
|
||||
/// @throws NullPointerException if the tab is null
|
||||
/// @throws NullPointerException if the tab is null
|
||||
/// @throws IllegalArgumentException if the tab does not belong to this controller
|
||||
/// @throws IllegalStateException if a content destination is configured but the
|
||||
/// @throws IllegalStateException if a content destination is configured but the
|
||||
/// tab has no content after initialization
|
||||
public void selectTab(TabItem<?> tab, boolean playAnimation) {
|
||||
Objects.requireNonNull(tab, "tab");
|
||||
@@ -112,7 +118,7 @@ public final class TabController implements PageAware {
|
||||
if (contentPane != null) {
|
||||
if (playAnimation && previous != null && previous != tab && contentPane.getCurrentNode() != null) {
|
||||
boolean forward = tabs.indexOf(tab) > tabs.indexOf(previous);
|
||||
ContainerAnimations animation = forward ? ContainerAnimations.TAB_UP : ContainerAnimations.TAB_DOWN;
|
||||
TabAnimation animation = forward ? TabAnimation.TAB_UP : TabAnimation.TAB_DOWN;
|
||||
contentPane.setContent(content, animation);
|
||||
} else {
|
||||
contentPane.setContent(content, ContainerAnimations.NONE);
|
||||
@@ -174,4 +180,47 @@ public final class TabController implements PageAware {
|
||||
if (tab != null && tab.isInitialized() && tab.getNode() instanceof PageAware pageAware)
|
||||
pageAware.onPageHidden();
|
||||
}
|
||||
|
||||
@NotNullByDefault
|
||||
public enum TabAnimation implements TransitionPane.AnimationProducer {
|
||||
/// Preserves the original slide-up and fade-in transition.
|
||||
TAB_UP(1.0),
|
||||
/// Mirrors the original slide-up and fade-in transition vertically.
|
||||
TAB_DOWN(-1.0);
|
||||
|
||||
private static final Duration DURATION = Motion.MEDIUM4;
|
||||
private static final Duration HALF_DURATION = DURATION.divide(2);
|
||||
|
||||
private final double direction;
|
||||
|
||||
TabAnimation(double direction) {
|
||||
this.direction = direction;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Animation animate(Pane container, Node previousNode, Node nextNode) {
|
||||
double extent = container.getHeight();
|
||||
double offset = (extent > 0 ? extent * 0.2 : 50) * direction;
|
||||
var previousTranslation = previousNode.translateYProperty();
|
||||
var nextTranslation = nextNode.translateYProperty();
|
||||
return new Timeline(
|
||||
new KeyFrame(Duration.ZERO,
|
||||
new KeyValue(nextNode.cacheProperty(), isCacheAllowed(nextNode), Interpolator.DISCRETE),
|
||||
new KeyValue(previousTranslation, 0, Motion.EASE_IN_OUT_CUBIC_EMPHASIZED),
|
||||
new KeyValue(previousNode.opacityProperty(), 1, Motion.EASE_IN_OUT_CUBIC_EMPHASIZED),
|
||||
new KeyValue(nextNode.opacityProperty(), 0, Motion.EASE_IN_OUT_CUBIC_EMPHASIZED),
|
||||
new KeyValue(nextTranslation, offset, Motion.EASE_IN_OUT_CUBIC_EMPHASIZED)),
|
||||
new KeyFrame(HALF_DURATION,
|
||||
new KeyValue(previousNode.opacityProperty(), 0, Motion.EASE_IN_OUT_CUBIC_EMPHASIZED)),
|
||||
new KeyFrame(DURATION,
|
||||
new KeyValue(nextNode.cacheProperty(), nextNode.isCache(), Interpolator.DISCRETE),
|
||||
new KeyValue(nextNode.opacityProperty(), 1, Motion.EASE_IN_OUT_CUBIC_EMPHASIZED),
|
||||
new KeyValue(nextTranslation, 0, Motion.EASE_IN_OUT_CUBIC_EMPHASIZED)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public TransitionPane.AnimationProducer opposite() {
|
||||
return this == TAB_UP ? TAB_DOWN : TAB_UP;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -34,7 +34,6 @@ import org.jackhuang.hmcl.ui.Controllers;
|
||||
import org.jackhuang.hmcl.ui.FXUtils;
|
||||
import org.jackhuang.hmcl.ui.SVG;
|
||||
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.construct.*;
|
||||
import org.jackhuang.hmcl.ui.instances.Instances;
|
||||
@@ -517,7 +516,7 @@ public class TerracottaControllerPage extends StackPane {
|
||||
}
|
||||
// Prevent the shadow of components from being clipped
|
||||
StackPane.setMargin(components, new Insets(0, 0, 5, 0));
|
||||
transition.setContent(components, ContainerAnimations.TAB_UP);
|
||||
transition.setContent(components, TabController.TabAnimation.TAB_UP);
|
||||
};
|
||||
listener.changed(UI_STATE, null, UI_STATE.get());
|
||||
holder.add(listener);
|
||||
@@ -605,7 +604,7 @@ public class TerracottaControllerPage extends StackPane {
|
||||
pane.getChildren().add(item);
|
||||
}
|
||||
|
||||
this.transition.setContent(pane, ContainerAnimations.TAB_UP);
|
||||
this.transition.setContent(pane, TabController.TabAnimation.TAB_UP);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user