修复 TabHeader 相关动画问题 (#4822)

This commit is contained in:
Glavo
2025-11-19 15:32:18 +08:00
committed by GitHub
parent da5bbd2b22
commit d6ad1cbdf7
7 changed files with 57 additions and 62 deletions

View File

@@ -26,7 +26,6 @@ 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; import org.jetbrains.annotations.Nullable;
public class TransitionPane extends StackPane { public class TransitionPane extends StackPane {
@@ -41,17 +40,6 @@ public class TransitionPane extends StackPane {
return currentNode; return currentNode;
} }
public void bindTabHeader(TabHeader tabHeader) {
this.setContent(tabHeader.getSelectionModel().getSelectedItem().getNode(), ContainerAnimations.NONE);
FXUtils.onChange(tabHeader.getSelectionModel().selectedItemProperty(), newValue -> {
this.setContent(newValue.getNode(),
ContainerAnimations.SLIDE_UP_FADE_IN,
Motion.MEDIUM4,
Motion.EASE_IN_OUT_CUBIC_EMPHASIZED
);
});
}
public final void setContent(Node newView, AnimationProducer transition) { public final void setContent(Node newView, AnimationProducer transition) {
setContent(newView, transition, Motion.SHORT4); setContent(newView, transition, Motion.SHORT4);
} }
@@ -66,6 +54,7 @@ public class TransitionPane extends StackPane {
currentNode = newView; currentNode = newView;
if (!AnimationUtils.isAnimationEnabled() || previousNode == null || transition == ContainerAnimations.NONE) { if (!AnimationUtils.isAnimationEnabled() || previousNode == null || transition == ContainerAnimations.NONE) {
AnimationUtils.reset(newView, true);
getChildren().setAll(newView); getChildren().setAll(newView);
return; return;
} }

View File

@@ -36,12 +36,24 @@ import javafx.scene.transform.Rotate;
import javafx.scene.transform.Scale; import javafx.scene.transform.Scale;
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.ContainerAnimations;
import org.jackhuang.hmcl.ui.animation.Motion;
import org.jackhuang.hmcl.ui.animation.TransitionPane;
import org.jackhuang.hmcl.util.javafx.MappedObservableList; import org.jackhuang.hmcl.util.javafx.MappedObservableList;
import org.jetbrains.annotations.Nullable;
@SuppressWarnings("deprecation") @SuppressWarnings("deprecation")
public class TabHeader extends Control implements TabControl, PageAware { public class TabHeader extends Control implements TabControl, PageAware {
private final TransitionPane contentPane;
public TabHeader(Tab<?>... tabs) { public TabHeader(Tab<?>... tabs) {
this(null, tabs);
}
public TabHeader(@Nullable TransitionPane contentPane, Tab<?>... tabs) {
this.contentPane = contentPane;
getStyleClass().setAll("tab-header"); getStyleClass().setAll("tab-header");
if (tabs != null) { if (tabs != null) {
getTabs().addAll(tabs); getTabs().addAll(tabs);
@@ -49,41 +61,48 @@ public class TabHeader extends Control implements TabControl, PageAware {
} }
private final ObservableList<Tab<?>> tabs = FXCollections.observableArrayList(); private final ObservableList<Tab<?>> tabs = FXCollections.observableArrayList();
private final ObjectProperty<Side> side = new SimpleObjectProperty<>(Side.TOP);
@Override @Override
public ObservableList<Tab<?>> getTabs() { public ObservableList<Tab<?>> getTabs() {
return tabs; return tabs;
} }
private final ObjectProperty<SingleSelectionModel<Tab<?>>> selectionModel = new SimpleObjectProperty<>(this, "selectionModel", new TabControlSelectionModel(this)); private final SingleSelectionModel<Tab<?>> selectionModel = new TabControlSelectionModel(this);
public SingleSelectionModel<Tab<?>> getSelectionModel() { public SingleSelectionModel<Tab<?>> getSelectionModel() {
return selectionModel.get();
}
public ObjectProperty<SingleSelectionModel<Tab<?>>> selectionModelProperty() {
return selectionModel; return selectionModel;
} }
public void setSelectionModel(SingleSelectionModel<Tab<?>> selectionModel) { public void select(Tab<?> tab) {
this.selectionModel.set(selectionModel); select(tab, true);
} }
public void select(Tab<?> tab) { public void select(Tab<?> tab, boolean playAnimation) {
Tab<?> oldTab = getSelectionModel().getSelectedItem(); Tab<?> oldTab = getSelectionModel().getSelectedItem();
if (oldTab != null) { if (oldTab != null) {
if (oldTab.getNode() instanceof PageAware) { if (oldTab.getNode() instanceof PageAware pageAware) {
((PageAware) oldTab.getNode()).onPageHidden(); pageAware.onPageHidden();
} }
} }
tab.initializeIfNeeded(); tab.initializeIfNeeded();
if (tab.getNode() instanceof PageAware) { if (tab.getNode() instanceof PageAware pageAware) {
((PageAware) tab.getNode()).onPageShown(); pageAware.onPageShown();
} }
getSelectionModel().select(tab); getSelectionModel().select(tab);
if (contentPane != null) {
if (playAnimation && contentPane.getCurrentNode() != null) {
contentPane.setContent(tab.getNode(),
ContainerAnimations.SLIDE_UP_FADE_IN,
Motion.MEDIUM4,
Motion.EASE_IN_OUT_CUBIC_EMPHASIZED
);
} else {
contentPane.setContent(tab.getNode(), ContainerAnimations.NONE);
}
}
} }
@Override @Override
@@ -106,12 +125,7 @@ public class TabHeader extends Control implements TabControl, PageAware {
} }
} }
/** private final ObjectProperty<Side> side = new SimpleObjectProperty<>(Side.TOP);
* The position to place the tabs.
*/
public Side getSide() {
return side.get();
}
/** /**
* The position of the tabs. * The position of the tabs.
@@ -120,6 +134,13 @@ public class TabHeader extends Control implements TabControl, PageAware {
return side; return side;
} }
/**
* The position to place the tabs.
*/
public Side getSide() {
return side.get();
}
/** /**
* The position the place the tabs in this TabHeader. * The position the place the tabs in this TabHeader.
*/ */

View File

@@ -39,7 +39,6 @@ import org.jackhuang.hmcl.ui.WeakListenerHolder;
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;
import org.jackhuang.hmcl.ui.construct.TabControl;
import org.jackhuang.hmcl.ui.construct.TabHeader; import org.jackhuang.hmcl.ui.construct.TabHeader;
import org.jackhuang.hmcl.ui.decorator.DecoratorAnimatedPage; import org.jackhuang.hmcl.ui.decorator.DecoratorAnimatedPage;
import org.jackhuang.hmcl.ui.decorator.DecoratorPage; import org.jackhuang.hmcl.ui.decorator.DecoratorPage;
@@ -99,12 +98,11 @@ public class DownloadPage extends DecoratorAnimatedPage implements DecoratorPage
resourcePackTab.setNodeSupplier(loadVersionFor(() -> HMCLLocalizedDownloadListPage.ofResourcePack((profile, version, file) -> download(profile, version, file, "resourcepacks"), true))); resourcePackTab.setNodeSupplier(loadVersionFor(() -> HMCLLocalizedDownloadListPage.ofResourcePack((profile, version, file) -> download(profile, version, file, "resourcepacks"), true)));
shaderTab.setNodeSupplier(loadVersionFor(() -> new DownloadListPage(ModrinthRemoteModRepository.SHADER_PACKS, (profile, version, file) -> download(profile, version, file, "shaderpacks"), true))); shaderTab.setNodeSupplier(loadVersionFor(() -> new DownloadListPage(ModrinthRemoteModRepository.SHADER_PACKS, (profile, version, file) -> download(profile, version, file, "shaderpacks"), true)));
worldTab.setNodeSupplier(loadVersionFor(() -> new DownloadListPage(CurseForgeRemoteModRepository.WORLDS))); worldTab.setNodeSupplier(loadVersionFor(() -> new DownloadListPage(CurseForgeRemoteModRepository.WORLDS)));
tab = new TabHeader(newGameTab, modpackTab, modTab, resourcePackTab, shaderTab, worldTab); tab = new TabHeader(transitionPane, newGameTab, modpackTab, modTab, resourcePackTab, shaderTab, worldTab);
Profiles.registerVersionsListener(this::loadVersions); Profiles.registerVersionsListener(this::loadVersions);
tab.select(newGameTab); tab.select(newGameTab);
transitionPane.bindTabHeader(tab);
AdvancedListBox sideBar = new AdvancedListBox() AdvancedListBox sideBar = new AdvancedListBox()
.startCategory(i18n("download.game").toUpperCase(Locale.ROOT)) .startCategory(i18n("download.game").toUpperCase(Locale.ROOT))
@@ -121,13 +119,6 @@ public class DownloadPage extends DecoratorAnimatedPage implements DecoratorPage
setCenter(transitionPane); setCenter(transitionPane);
} }
private void selectTabIfCurseForgeAvailable(TabControl.Tab<?> newTab) {
if (CurseForgeRemoteModRepository.isAvailable())
tab.select(newTab);
else
Controllers.dialog(i18n("download.curseforge.unavailable"));
}
private static <T extends Node> Supplier<T> loadVersionFor(Supplier<T> nodeSupplier) { private static <T extends Node> Supplier<T> loadVersionFor(Supplier<T> nodeSupplier) {
return () -> { return () -> {
T node = nodeSupplier.get(); T node = nodeSupplier.get();
@@ -202,20 +193,20 @@ public class DownloadPage extends DecoratorAnimatedPage implements DecoratorPage
} }
public void showGameDownloads() { public void showGameDownloads() {
tab.select(newGameTab); tab.select(newGameTab, false);
} }
public void showModpackDownloads() { public void showModpackDownloads() {
tab.select(modpackTab); tab.select(modpackTab, false);
} }
public DownloadListPage showModDownloads() { public DownloadListPage showModDownloads() {
tab.select(modTab); tab.select(modTab, false);
return modTab.getNode(); return modTab.getNode();
} }
public void showWorldDownloads() { public void showWorldDownloads() {
tab.select(worldTab); tab.select(worldTab, false);
} }
private static final class DownloadNavigator implements Navigation { private static final class DownloadNavigator implements Navigation {

View File

@@ -55,11 +55,10 @@ public class LauncherSettingsPage extends DecoratorAnimatedPage implements Decor
helpTab.setNodeSupplier(HelpPage::new); helpTab.setNodeSupplier(HelpPage::new);
feedbackTab.setNodeSupplier(FeedbackPage::new); feedbackTab.setNodeSupplier(FeedbackPage::new);
aboutTab.setNodeSupplier(AboutPage::new); aboutTab.setNodeSupplier(AboutPage::new);
tab = new TabHeader(gameTab, javaManagementTab, settingsTab, personalizationTab, downloadTab, helpTab, feedbackTab, aboutTab); tab = new TabHeader(transitionPane, gameTab, javaManagementTab, settingsTab, personalizationTab, downloadTab, helpTab, feedbackTab, aboutTab);
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.bindTabHeader(tab);
AdvancedListBox sideBar = new AdvancedListBox() AdvancedListBox sideBar = new AdvancedListBox()
.addNavigationDrawerTab(tab, gameTab, i18n("settings.type.global.manage"), SVG.STADIA_CONTROLLER, SVG.STADIA_CONTROLLER_FILL) .addNavigationDrawerTab(tab, gameTab, i18n("settings.type.global.manage"), SVG.STADIA_CONTROLLER, SVG.STADIA_CONTROLLER_FILL)
@@ -90,11 +89,11 @@ public class LauncherSettingsPage extends DecoratorAnimatedPage implements Decor
public void showGameSettings(Profile profile) { public void showGameSettings(Profile profile) {
gameTab.getNode().loadVersion(profile, null); gameTab.getNode().loadVersion(profile, null);
tab.select(gameTab); tab.select(gameTab, false);
} }
public void showFeedback() { public void showFeedback() {
tab.select(feedbackTab); tab.select(feedbackTab, false);
} }
@Override @Override

View File

@@ -56,9 +56,8 @@ public class TerracottaPage extends DecoratorAnimatedPage implements DecoratorPa
public TerracottaPage() { public TerracottaPage() {
statusPage.setNodeSupplier(TerracottaControllerPage::new); statusPage.setNodeSupplier(TerracottaControllerPage::new);
tab = new TabHeader(statusPage); tab = new TabHeader(transitionPane, statusPage);
tab.select(statusPage); tab.select(statusPage);
transitionPane.bindTabHeader(tab);
BorderPane left = new BorderPane(); BorderPane left = new BorderPane();
FXUtils.setLimitWidth(left, 200); FXUtils.setLimitWidth(left, 200);

View File

@@ -71,13 +71,11 @@ public class VersionPage extends DecoratorAnimatedPage implements DecoratorPage
worldListTab.setNodeSupplier(loadVersionFor(WorldListPage::new)); worldListTab.setNodeSupplier(loadVersionFor(WorldListPage::new));
schematicsTab.setNodeSupplier(loadVersionFor(SchematicsPage::new)); schematicsTab.setNodeSupplier(loadVersionFor(SchematicsPage::new));
tab = new TabHeader(versionSettingsTab, installerListTab, modListTab, worldListTab, schematicsTab); tab = new TabHeader(transitionPane, versionSettingsTab, installerListTab, modListTab, worldListTab, schematicsTab);
tab.select(versionSettingsTab);
addEventHandler(Navigator.NavigationEvent.NAVIGATED, this::onNavigated); addEventHandler(Navigator.NavigationEvent.NAVIGATED, this::onNavigated);
tab.select(versionSettingsTab);
transitionPane.bindTabHeader(tab);
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));
} }

View File

@@ -64,15 +64,13 @@ public final class WorldManagePage extends DecoratorAnimatedPage implements Deco
this.world = world; this.world = world;
this.backupsDir = backupsDir; this.backupsDir = backupsDir;
this.worldInfoTab.setNodeSupplier(() -> new WorldInfoPage(this));
this.worldBackupsTab.setNodeSupplier(() -> new WorldBackupsPage(this));
this.datapackTab.setNodeSupplier(() -> new DatapackListPage(this));
this.state = new SimpleObjectProperty<>(State.fromTitle(i18n("world.manage.title", world.getWorldName()))); this.state = new SimpleObjectProperty<>(State.fromTitle(i18n("world.manage.title", world.getWorldName())));
this.header = new TabHeader(worldInfoTab, worldBackupsTab); this.header = new TabHeader(transitionPane, worldInfoTab, worldBackupsTab);
worldInfoTab.setNodeSupplier(() -> new WorldInfoPage(this));
worldBackupsTab.setNodeSupplier(() -> new WorldBackupsPage(this));
datapackTab.setNodeSupplier(() -> new DatapackListPage(this));
header.select(worldInfoTab); header.select(worldInfoTab);
transitionPane.bindTabHeader(header);
setCenter(transitionPane); setCenter(transitionPane);