mirror of
https://github.com/ppy/osu.git
synced 2026-05-19 22:20:48 +08:00
Isolate footer behaviour to ScreenStackFooter, support subscreens
This commit is contained in:
+6
-63
@@ -189,19 +189,14 @@ namespace osu.Game
|
||||
/// </summary>
|
||||
public readonly IBindable<OverlayActivation> OverlayActivationMode = new Bindable<OverlayActivation>();
|
||||
|
||||
/// <summary>
|
||||
/// Whether the back button is currently displayed.
|
||||
/// </summary>
|
||||
private readonly IBindable<bool> backButtonVisibility = new BindableBool();
|
||||
|
||||
IBindable<LocalUserPlayingState> ILocalUserPlayInfo.PlayingState => UserPlayingState;
|
||||
|
||||
protected readonly Bindable<LocalUserPlayingState> UserPlayingState = new Bindable<LocalUserPlayingState>();
|
||||
|
||||
protected OsuScreenStack ScreenStack;
|
||||
|
||||
protected BackButton BackButton;
|
||||
protected ScreenFooter ScreenFooter;
|
||||
protected BackButton BackButton => screenStackFooter.BackButton;
|
||||
protected ScreenFooter ScreenFooter => screenStackFooter.Footer;
|
||||
|
||||
protected SettingsOverlay Settings;
|
||||
|
||||
@@ -233,6 +228,8 @@ namespace osu.Game
|
||||
|
||||
private RealmDetachedBeatmapStore detachedBeatmapStore;
|
||||
|
||||
private ScreenStackFooter screenStackFooter;
|
||||
|
||||
private readonly string[] args;
|
||||
|
||||
private readonly List<OsuFocusedOverlayContainer> focusedOverlays = new List<OsuFocusedOverlayContainer>();
|
||||
@@ -1132,12 +1129,6 @@ namespace osu.Game
|
||||
{
|
||||
backReceptor = new ScreenFooter.BackReceptor(),
|
||||
ScreenStack = new OsuScreenStack { RelativeSizeAxes = Axes.Both },
|
||||
BackButton = new BackButton(backReceptor)
|
||||
{
|
||||
Anchor = Anchor.BottomLeft,
|
||||
Origin = Anchor.BottomLeft,
|
||||
Action = handleBackButton,
|
||||
},
|
||||
logoContainer = new Container { RelativeSizeAxes = Axes.Both },
|
||||
// TODO: what is this? why is this?
|
||||
// TODO: this is being screen scaled even though it's probably AN OVERLAY.
|
||||
@@ -1150,7 +1141,7 @@ namespace osu.Game
|
||||
{
|
||||
Depth = -1,
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Child = ScreenFooter = new ScreenFooter(backReceptor)
|
||||
Child = screenStackFooter = new ScreenStackFooter(ScreenStack, backReceptor)
|
||||
{
|
||||
// TODO: this is really really weird and should not exist.
|
||||
RequestLogoInFront = inFront => ScreenContainer.ChangeChildDepth(logoContainer, inFront ? float.MinValue : 0),
|
||||
@@ -1324,14 +1315,6 @@ namespace osu.Game
|
||||
if (mode.NewValue != OverlayActivation.All) CloseAllOverlays();
|
||||
};
|
||||
|
||||
backButtonVisibility.ValueChanged += visible =>
|
||||
{
|
||||
if (visible.NewValue)
|
||||
BackButton.Show();
|
||||
else
|
||||
BackButton.Hide();
|
||||
};
|
||||
|
||||
// Importantly, this should be run after binding PostNotification to the import handlers so they can present the import after game startup.
|
||||
handleStartupImport();
|
||||
}
|
||||
@@ -1723,13 +1706,12 @@ namespace osu.Game
|
||||
|
||||
if (current != null)
|
||||
{
|
||||
backButtonVisibility.UnbindFrom(current.BackButtonVisibility);
|
||||
OverlayActivationMode.UnbindFrom(current.OverlayActivationMode);
|
||||
configUserActivity.UnbindFrom(current.Activity);
|
||||
}
|
||||
|
||||
// Bind to new screen.
|
||||
if (newScreen != null)
|
||||
if (newScreen is OsuScreen newOsuScreen)
|
||||
{
|
||||
OverlayActivationMode.BindTo(newScreen.OverlayActivationMode);
|
||||
configUserActivity.BindTo(newScreen.Activity);
|
||||
@@ -1742,45 +1724,6 @@ namespace osu.Game
|
||||
else
|
||||
Toolbar.Show();
|
||||
|
||||
var newOsuScreen = (OsuScreen)newScreen;
|
||||
|
||||
if (newScreen.ShowFooter)
|
||||
{
|
||||
// the legacy back button should never display while the new footer is in use, as it
|
||||
// contains its own local back button.
|
||||
((BindableBool)backButtonVisibility).Value = false;
|
||||
|
||||
BackButton.Hide();
|
||||
ScreenFooter.Show();
|
||||
|
||||
if (newOsuScreen.IsLoaded)
|
||||
updateFooterButtons();
|
||||
else
|
||||
{
|
||||
// ensure the current buttons are immediately disabled on screen change (so they can't be pressed).
|
||||
ScreenFooter.SetButtons(Array.Empty<ScreenFooterButton>());
|
||||
|
||||
newOsuScreen.OnLoadComplete += _ => updateFooterButtons();
|
||||
}
|
||||
|
||||
void updateFooterButtons()
|
||||
{
|
||||
var buttons = newScreen.CreateFooterButtons();
|
||||
|
||||
newOsuScreen.LoadComponentsAgainstScreenDependencies(buttons);
|
||||
|
||||
ScreenFooter.SetButtons(buttons);
|
||||
ScreenFooter.Show();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
backButtonVisibility.BindTo(newScreen.BackButtonVisibility);
|
||||
|
||||
ScreenFooter.SetButtons(Array.Empty<ScreenFooterButton>());
|
||||
ScreenFooter.Hide();
|
||||
}
|
||||
|
||||
skinEditor.SetTarget(newOsuScreen);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,220 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Screens;
|
||||
using osu.Game.Graphics.UserInterface;
|
||||
|
||||
namespace osu.Game.Screens.Footer
|
||||
{
|
||||
public partial class ScreenStackFooter : CompositeDrawable
|
||||
{
|
||||
/// <summary>
|
||||
/// Called when logo tracking begins, intended to bring the osu! logo to the frontmost visually.
|
||||
/// </summary>
|
||||
public Action<bool>? RequestLogoInFront { private get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// The back button was pressed.
|
||||
/// </summary>
|
||||
public Action? BackButtonPressed { private get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// The (legacy) back button.
|
||||
/// </summary>
|
||||
public readonly BackButton BackButton;
|
||||
|
||||
/// <summary>
|
||||
/// The footer.
|
||||
/// </summary>
|
||||
public readonly ScreenFooter Footer;
|
||||
|
||||
/// <summary>
|
||||
/// Whether the legacy back button is currently displayed.
|
||||
/// </summary>
|
||||
private readonly IBindable<bool> backButtonVisibility = new BindableBool();
|
||||
|
||||
private readonly ScreenStackTracker screenTracker;
|
||||
|
||||
public ScreenStackFooter(ScreenStack screenStack, ScreenFooter.BackReceptor? backReceptor = null)
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both;
|
||||
|
||||
InternalChildren = new Drawable[]
|
||||
{
|
||||
BackButton = new BackButton(backReceptor)
|
||||
{
|
||||
Anchor = Anchor.BottomLeft,
|
||||
Origin = Anchor.BottomLeft,
|
||||
Action = () => BackButtonPressed?.Invoke(),
|
||||
},
|
||||
Footer = new ScreenFooter(backReceptor)
|
||||
{
|
||||
RequestLogoInFront = v => RequestLogoInFront?.Invoke(v),
|
||||
BackButtonPressed = () => BackButtonPressed?.Invoke()
|
||||
}
|
||||
};
|
||||
|
||||
screenTracker = new ScreenStackTracker(screenStack);
|
||||
screenTracker.ScreenChanged += onScreenChanged;
|
||||
|
||||
backButtonVisibility.ValueChanged += onBackButtonVisibilityChanged;
|
||||
}
|
||||
|
||||
private void onScreenChanged(IScreen lastScreen, IScreen newScreen)
|
||||
{
|
||||
unbindScreen(lastScreen);
|
||||
bindScreen(newScreen);
|
||||
}
|
||||
|
||||
private void onBackButtonVisibilityChanged(ValueChangedEvent<bool> visible)
|
||||
{
|
||||
if (visible.NewValue)
|
||||
BackButton.Show();
|
||||
else
|
||||
BackButton.Hide();
|
||||
}
|
||||
|
||||
private void unbindScreen(IScreen screen)
|
||||
{
|
||||
if (screen is not OsuScreen osuScreen)
|
||||
return;
|
||||
|
||||
backButtonVisibility.UnbindFrom(osuScreen.BackButtonVisibility);
|
||||
}
|
||||
|
||||
private void bindScreen(IScreen screen)
|
||||
{
|
||||
if (screen is not OsuScreen osuScreen)
|
||||
{
|
||||
((BindableBool)backButtonVisibility).Value = true;
|
||||
|
||||
Footer.SetButtons([]);
|
||||
Footer.Hide();
|
||||
return;
|
||||
}
|
||||
|
||||
if (osuScreen.ShowFooter)
|
||||
{
|
||||
// the legacy back button should never display while the new footer is in use, as it
|
||||
// contains its own local back button.
|
||||
((BindableBool)backButtonVisibility).Value = false;
|
||||
|
||||
Footer.Show();
|
||||
|
||||
if (osuScreen.IsLoaded)
|
||||
updateFooterButtons();
|
||||
else
|
||||
{
|
||||
// ensure the current buttons are immediately disabled on screen change (so they can't be pressed).
|
||||
Footer.SetButtons([]);
|
||||
|
||||
osuScreen.OnLoadComplete += _ => updateFooterButtons();
|
||||
}
|
||||
|
||||
void updateFooterButtons()
|
||||
{
|
||||
var buttons = osuScreen.CreateFooterButtons();
|
||||
|
||||
osuScreen.LoadComponentsAgainstScreenDependencies(buttons);
|
||||
|
||||
Footer.SetButtons(buttons);
|
||||
Footer.Show();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
backButtonVisibility.BindTo(osuScreen.BackButtonVisibility);
|
||||
|
||||
Footer.SetButtons([]);
|
||||
Footer.Hide();
|
||||
}
|
||||
}
|
||||
|
||||
protected override void Dispose(bool isDisposing)
|
||||
{
|
||||
base.Dispose(isDisposing);
|
||||
|
||||
screenTracker.Dispose();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Recursively represents a single screen stack and any nested subscreen stack.
|
||||
/// </summary>
|
||||
private class ScreenStackTracker : IDisposable
|
||||
{
|
||||
/// <summary>
|
||||
/// Invoked when the leading screen changes.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This differs from <see cref="ScreenStack.ScreenPushed"/> and <see cref="ScreenStack.ScreenExited"/>
|
||||
/// because <c>lastScreen</c> and <c>newScreen</c> may be subscreens of the current screen stack.
|
||||
/// <br />
|
||||
/// As such, no assumptions may be made as to the relation of screens to this entry's <see cref="ScreenStack"/>.
|
||||
/// </remarks>
|
||||
public event ScreenChangedDelegate? ScreenChanged;
|
||||
|
||||
/// <summary>
|
||||
/// The screen stack tracked by this entry.
|
||||
/// </summary>
|
||||
private readonly ScreenStack stack;
|
||||
|
||||
/// <summary>
|
||||
/// An entry corresponding to the subscreen stack of the current screen, if any.
|
||||
/// </summary>
|
||||
private ScreenStackTracker? subScreenTracker;
|
||||
|
||||
/// <summary>
|
||||
/// The screen which should be bound to the screen footer - the most nested subscreen.
|
||||
/// </summary>
|
||||
private IScreen leadingScreen => subScreenTracker?.leadingScreen ?? stack.CurrentScreen;
|
||||
|
||||
public ScreenStackTracker(ScreenStack stack)
|
||||
{
|
||||
this.stack = stack;
|
||||
|
||||
stack.ScreenPushed += onParentScreenChanged;
|
||||
stack.ScreenExited += onParentScreenChanged;
|
||||
}
|
||||
|
||||
private void onParentScreenChanged(IScreen lastScreen, IScreen newScreen)
|
||||
{
|
||||
// The screen which we will be UNBINDING from the screen footer later on.
|
||||
IScreen lastLeadingScreen = subScreenTracker?.leadingScreen ?? lastScreen;
|
||||
|
||||
// Subscreens are attached to a parent screen, so when the parent changes the subscreen must also.
|
||||
subScreenTracker?.Dispose();
|
||||
subScreenTracker = null;
|
||||
|
||||
// Check if we've switched to a screen that has a subscreen.
|
||||
if (newScreen is IHasSubScreenStack newStack)
|
||||
{
|
||||
subScreenTracker = new ScreenStackTracker(newStack.SubScreenStack);
|
||||
subScreenTracker.ScreenChanged += onSubScreenScreenChanged;
|
||||
}
|
||||
|
||||
ScreenChanged?.Invoke(lastLeadingScreen, leadingScreen);
|
||||
}
|
||||
|
||||
private void onSubScreenScreenChanged(IScreen lastScreen, IScreen newScreen)
|
||||
{
|
||||
ScreenChanged?.Invoke(lastScreen, newScreen);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
stack.ScreenPushed -= onParentScreenChanged;
|
||||
stack.ScreenExited -= onParentScreenChanged;
|
||||
|
||||
if (subScreenTracker != null)
|
||||
{
|
||||
subScreenTracker.ScreenChanged -= onSubScreenScreenChanged;
|
||||
subScreenTracker.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user