修复 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.util.Duration;
import org.jackhuang.hmcl.ui.FXUtils;
import org.jackhuang.hmcl.ui.construct.TabHeader;
import org.jetbrains.annotations.Nullable;
public class TransitionPane extends StackPane {
@@ -41,17 +40,6 @@ public class TransitionPane extends StackPane {
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) {
setContent(newView, transition, Motion.SHORT4);
}
@@ -66,6 +54,7 @@ public class TransitionPane extends StackPane {
currentNode = newView;
if (!AnimationUtils.isAnimationEnabled() || previousNode == null || transition == ContainerAnimations.NONE) {
AnimationUtils.reset(newView, true);
getChildren().setAll(newView);
return;
}

View File

@@ -36,12 +36,24 @@ import javafx.scene.transform.Rotate;
import javafx.scene.transform.Scale;
import javafx.util.Duration;
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.jetbrains.annotations.Nullable;
@SuppressWarnings("deprecation")
public class TabHeader extends Control implements TabControl, PageAware {
private final TransitionPane contentPane;
public TabHeader(Tab<?>... tabs) {
this(null, tabs);
}
public TabHeader(@Nullable TransitionPane contentPane, Tab<?>... tabs) {
this.contentPane = contentPane;
getStyleClass().setAll("tab-header");
if (tabs != null) {
getTabs().addAll(tabs);
@@ -49,41 +61,48 @@ public class TabHeader extends Control implements TabControl, PageAware {
}
private final ObservableList<Tab<?>> tabs = FXCollections.observableArrayList();
private final ObjectProperty<Side> side = new SimpleObjectProperty<>(Side.TOP);
@Override
public ObservableList<Tab<?>> getTabs() {
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() {
return selectionModel.get();
}
public ObjectProperty<SingleSelectionModel<Tab<?>>> selectionModelProperty() {
return selectionModel;
}
public void setSelectionModel(SingleSelectionModel<Tab<?>> selectionModel) {
this.selectionModel.set(selectionModel);
public void select(Tab<?> tab) {
select(tab, true);
}
public void select(Tab<?> tab) {
public void select(Tab<?> tab, boolean playAnimation) {
Tab<?> oldTab = getSelectionModel().getSelectedItem();
if (oldTab != null) {
if (oldTab.getNode() instanceof PageAware) {
((PageAware) oldTab.getNode()).onPageHidden();
if (oldTab.getNode() instanceof PageAware pageAware) {
pageAware.onPageHidden();
}
}
tab.initializeIfNeeded();
if (tab.getNode() instanceof PageAware) {
((PageAware) tab.getNode()).onPageShown();
if (tab.getNode() instanceof PageAware pageAware) {
pageAware.onPageShown();
}
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
@@ -106,12 +125,7 @@ public class TabHeader extends Control implements TabControl, PageAware {
}
}
/**
* The position to place the tabs.
*/
public Side getSide() {
return side.get();
}
private final ObjectProperty<Side> side = new SimpleObjectProperty<>(Side.TOP);
/**
* The position of the tabs.
@@ -120,6 +134,13 @@ public class TabHeader extends Control implements TabControl, PageAware {
return side;
}
/**
* The position to place the tabs.
*/
public Side getSide() {
return side.get();
}
/**
* 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.construct.AdvancedListBox;
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.decorator.DecoratorAnimatedPage;
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)));
shaderTab.setNodeSupplier(loadVersionFor(() -> new DownloadListPage(ModrinthRemoteModRepository.SHADER_PACKS, (profile, version, file) -> download(profile, version, file, "shaderpacks"), true)));
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);
tab.select(newGameTab);
transitionPane.bindTabHeader(tab);
AdvancedListBox sideBar = new AdvancedListBox()
.startCategory(i18n("download.game").toUpperCase(Locale.ROOT))
@@ -121,13 +119,6 @@ public class DownloadPage extends DecoratorAnimatedPage implements DecoratorPage
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) {
return () -> {
T node = nodeSupplier.get();
@@ -202,20 +193,20 @@ public class DownloadPage extends DecoratorAnimatedPage implements DecoratorPage
}
public void showGameDownloads() {
tab.select(newGameTab);
tab.select(newGameTab, false);
}
public void showModpackDownloads() {
tab.select(modpackTab);
tab.select(modpackTab, false);
}
public DownloadListPage showModDownloads() {
tab.select(modTab);
tab.select(modTab, false);
return modTab.getNode();
}
public void showWorldDownloads() {
tab.select(worldTab);
tab.select(worldTab, false);
}
private static final class DownloadNavigator implements Navigation {

View File

@@ -55,11 +55,10 @@ public class LauncherSettingsPage extends DecoratorAnimatedPage implements Decor
helpTab.setNodeSupplier(HelpPage::new);
feedbackTab.setNodeSupplier(FeedbackPage::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);
addEventHandler(Navigator.NavigationEvent.NAVIGATED, event -> gameTab.getNode().loadVersion(Profiles.getSelectedProfile(), null));
transitionPane.bindTabHeader(tab);
AdvancedListBox sideBar = new AdvancedListBox()
.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) {
gameTab.getNode().loadVersion(profile, null);
tab.select(gameTab);
tab.select(gameTab, false);
}
public void showFeedback() {
tab.select(feedbackTab);
tab.select(feedbackTab, false);
}
@Override

View File

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

View File

@@ -71,13 +71,11 @@ public class VersionPage extends DecoratorAnimatedPage implements DecoratorPage
worldListTab.setNodeSupplier(loadVersionFor(WorldListPage::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);
tab.select(versionSettingsTab);
transitionPane.bindTabHeader(tab);
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.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.header = new TabHeader(worldInfoTab, worldBackupsTab);
worldInfoTab.setNodeSupplier(() -> new WorldInfoPage(this));
worldBackupsTab.setNodeSupplier(() -> new WorldBackupsPage(this));
datapackTab.setNodeSupplier(() -> new DatapackListPage(this));
this.header = new TabHeader(transitionPane, worldInfoTab, worldBackupsTab);
header.select(worldInfoTab);
transitionPane.bindTabHeader(header);
setCenter(transitionPane);