mirror of
https://github.com/ppy/osu.git
synced 2026-06-03 01:00:10 +08:00
Merge pull request #28682 from frenzibyte/footer-v2-overlay-content
Add implementation for `ScreenFooter` to house footer content of sheared overlays
This commit is contained in:
@@ -2,10 +2,16 @@
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Cursor;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Graphics.UserInterface;
|
||||
using osu.Game.Overlays;
|
||||
using osu.Game.Overlays.Mods;
|
||||
using osu.Game.Screens.Footer;
|
||||
@@ -15,25 +21,31 @@ namespace osu.Game.Tests.Visual.UserInterface
|
||||
{
|
||||
public partial class TestSceneScreenFooter : OsuManualInputManagerTestScene
|
||||
{
|
||||
private DependencyProvidingContainer contentContainer = null!;
|
||||
private ScreenFooter screenFooter = null!;
|
||||
private TestModSelectOverlay overlay = null!;
|
||||
|
||||
[SetUp]
|
||||
public void SetUp() => Schedule(() =>
|
||||
{
|
||||
Children = new Drawable[]
|
||||
screenFooter = new ScreenFooter();
|
||||
|
||||
Child = contentContainer = new DependencyProvidingContainer
|
||||
{
|
||||
overlay = new TestModSelectOverlay
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
CachedDependencies = new (Type, object)[]
|
||||
{
|
||||
Padding = new MarginPadding
|
||||
{
|
||||
Bottom = ScreenFooter.HEIGHT
|
||||
}
|
||||
(typeof(ScreenFooter), screenFooter)
|
||||
},
|
||||
new PopoverContainer
|
||||
Children = new Drawable[]
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Child = screenFooter = new ScreenFooter(),
|
||||
overlay = new TestModSelectOverlay(),
|
||||
new PopoverContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Depth = float.MinValue,
|
||||
Child = screenFooter,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
@@ -82,14 +94,156 @@ namespace osu.Game.Tests.Visual.UserInterface
|
||||
}));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestExternalOverlayContent()
|
||||
{
|
||||
TestShearedOverlayContainer externalOverlay = null!;
|
||||
|
||||
AddStep("add overlay", () => contentContainer.Add(externalOverlay = new TestShearedOverlayContainer()));
|
||||
AddStep("set buttons", () => screenFooter.SetButtons(new[]
|
||||
{
|
||||
new ScreenFooterButton(externalOverlay)
|
||||
{
|
||||
AccentColour = Dependencies.Get<OsuColour>().Orange1,
|
||||
Icon = FontAwesome.Solid.Toolbox,
|
||||
Text = "One",
|
||||
},
|
||||
new ScreenFooterButton { Text = "Two", Action = () => { } },
|
||||
new ScreenFooterButton { Text = "Three", Action = () => { } },
|
||||
}));
|
||||
AddWaitStep("wait for transition", 3);
|
||||
|
||||
AddStep("show overlay", () => externalOverlay.Show());
|
||||
AddAssert("content displayed in footer", () => screenFooter.ChildrenOfType<TestShearedOverlayContainer.TestFooterContent>().Single().IsPresent);
|
||||
AddUntilStep("other buttons hidden", () => screenFooter.ChildrenOfType<ScreenFooterButton>().Skip(1).All(b => b.Child.Parent!.Y > 0));
|
||||
|
||||
AddStep("hide overlay", () => externalOverlay.Hide());
|
||||
AddUntilStep("content hidden from footer", () => screenFooter.ChildrenOfType<TestShearedOverlayContainer.TestFooterContent>().SingleOrDefault()?.IsPresent != true);
|
||||
AddUntilStep("other buttons returned", () => screenFooter.ChildrenOfType<ScreenFooterButton>().Skip(1).All(b => b.ChildrenOfType<Container>().First().Y == 0));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestTemporarilyShowFooter()
|
||||
{
|
||||
TestShearedOverlayContainer externalOverlay = null!;
|
||||
|
||||
AddStep("hide footer", () => screenFooter.Hide());
|
||||
AddStep("remove buttons", () => screenFooter.SetButtons(Array.Empty<ScreenFooterButton>()));
|
||||
|
||||
AddStep("add external overlay", () => contentContainer.Add(externalOverlay = new TestShearedOverlayContainer()));
|
||||
AddStep("show external overlay", () => externalOverlay.Show());
|
||||
AddAssert("footer shown", () => screenFooter.State.Value == Visibility.Visible);
|
||||
AddAssert("content displayed in footer", () => screenFooter.ChildrenOfType<TestShearedOverlayContainer.TestFooterContent>().Single().IsPresent);
|
||||
|
||||
AddStep("hide external overlay", () => externalOverlay.Hide());
|
||||
AddAssert("footer hidden", () => screenFooter.State.Value == Visibility.Hidden);
|
||||
AddUntilStep("content hidden from footer", () => screenFooter.ChildrenOfType<TestShearedOverlayContainer.TestFooterContent>().SingleOrDefault()?.IsPresent != true);
|
||||
|
||||
AddStep("show footer", () => screenFooter.Show());
|
||||
AddAssert("content still hidden from footer", () => screenFooter.ChildrenOfType<TestShearedOverlayContainer.TestFooterContent>().SingleOrDefault()?.IsPresent != true);
|
||||
|
||||
AddStep("show external overlay", () => externalOverlay.Show());
|
||||
AddAssert("footer still visible", () => screenFooter.State.Value == Visibility.Visible);
|
||||
|
||||
AddStep("hide external overlay", () => externalOverlay.Hide());
|
||||
AddAssert("footer still visible", () => screenFooter.State.Value == Visibility.Visible);
|
||||
|
||||
AddStep("hide footer", () => screenFooter.Hide());
|
||||
AddStep("show external overlay", () => externalOverlay.Show());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestBackButton()
|
||||
{
|
||||
TestShearedOverlayContainer externalOverlay = null!;
|
||||
|
||||
AddStep("hide footer", () => screenFooter.Hide());
|
||||
AddStep("remove buttons", () => screenFooter.SetButtons(Array.Empty<ScreenFooterButton>()));
|
||||
|
||||
AddStep("add external overlay", () => contentContainer.Add(externalOverlay = new TestShearedOverlayContainer()));
|
||||
AddStep("show external overlay", () => externalOverlay.Show());
|
||||
AddAssert("footer shown", () => screenFooter.State.Value == Visibility.Visible);
|
||||
|
||||
AddStep("press back", () => this.ChildrenOfType<ScreenBackButton>().Single().TriggerClick());
|
||||
AddAssert("overlay hidden", () => externalOverlay.State.Value == Visibility.Hidden);
|
||||
AddAssert("footer hidden", () => screenFooter.State.Value == Visibility.Hidden);
|
||||
|
||||
AddStep("show external overlay", () => externalOverlay.Show());
|
||||
AddStep("set block count", () => externalOverlay.BackButtonCount = 1);
|
||||
AddStep("press back", () => this.ChildrenOfType<ScreenBackButton>().Single().TriggerClick());
|
||||
AddAssert("overlay still visible", () => externalOverlay.State.Value == Visibility.Visible);
|
||||
AddAssert("footer still shown", () => screenFooter.State.Value == Visibility.Visible);
|
||||
AddStep("press back again", () => this.ChildrenOfType<ScreenBackButton>().Single().TriggerClick());
|
||||
AddAssert("overlay hidden", () => externalOverlay.State.Value == Visibility.Hidden);
|
||||
AddAssert("footer hidden", () => screenFooter.State.Value == Visibility.Hidden);
|
||||
}
|
||||
|
||||
private partial class TestModSelectOverlay : UserModSelectOverlay
|
||||
{
|
||||
protected override bool ShowPresets => true;
|
||||
}
|
||||
|
||||
public TestModSelectOverlay()
|
||||
: base(OverlayColourScheme.Aquamarine)
|
||||
private partial class TestShearedOverlayContainer : ShearedOverlayContainer
|
||||
{
|
||||
public override bool UseNewFooter => true;
|
||||
|
||||
public TestShearedOverlayContainer()
|
||||
: base(OverlayColourScheme.Orange)
|
||||
{
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
Header.Title = "Test overlay";
|
||||
Header.Description = "An overlay that is made purely for testing purposes.";
|
||||
}
|
||||
|
||||
public int BackButtonCount;
|
||||
|
||||
public override bool OnBackButton()
|
||||
{
|
||||
if (BackButtonCount > 0)
|
||||
{
|
||||
BackButtonCount--;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public override Drawable CreateFooterContent() => new TestFooterContent();
|
||||
|
||||
public partial class TestFooterContent : VisibilityContainer
|
||||
{
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both;
|
||||
|
||||
InternalChild = new FillFlowContainer
|
||||
{
|
||||
AutoSizeAxes = Axes.Both,
|
||||
Children = new[]
|
||||
{
|
||||
new ShearedButton(200) { Text = "Action #1", Action = () => { } },
|
||||
new ShearedButton(140) { Text = "Action #2", Action = () => { } },
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
protected override void PopIn()
|
||||
{
|
||||
this.MoveToY(0, 400, Easing.OutQuint)
|
||||
.FadeIn(400, Easing.OutQuint);
|
||||
}
|
||||
|
||||
protected override void PopOut()
|
||||
{
|
||||
this.MoveToY(-20f, 200, Easing.OutQuint)
|
||||
.FadeOut(200, Easing.OutQuint);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
// 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.
|
||||
|
||||
#nullable disable
|
||||
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
@@ -11,45 +9,52 @@ using osu.Framework.Input.Events;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Graphics.Containers;
|
||||
using osu.Game.Graphics.UserInterface;
|
||||
using osu.Game.Screens.Footer;
|
||||
|
||||
namespace osu.Game.Overlays.Mods
|
||||
{
|
||||
/// <summary>
|
||||
/// A sheared overlay which provides a header and footer and basic animations.
|
||||
/// Exposes <see cref="TopLevelContent"/>, <see cref="MainAreaContent"/> and <see cref="Footer"/> as valid targets for content.
|
||||
/// A sheared overlay which provides a header and basic animations.
|
||||
/// Exposes <see cref="TopLevelContent"/> and <see cref="MainAreaContent"/> as valid targets for content.
|
||||
/// </summary>
|
||||
public abstract partial class ShearedOverlayContainer : OsuFocusedOverlayContainer
|
||||
{
|
||||
protected const float PADDING = 14;
|
||||
public const float PADDING = 14;
|
||||
|
||||
[Cached]
|
||||
protected readonly OverlayColourProvider ColourProvider;
|
||||
public readonly OverlayColourProvider ColourProvider;
|
||||
|
||||
/// <summary>
|
||||
/// The overlay's header.
|
||||
/// </summary>
|
||||
protected ShearedOverlayHeader Header { get; private set; }
|
||||
protected ShearedOverlayHeader Header { get; private set; } = null!;
|
||||
|
||||
/// <summary>
|
||||
/// The overlay's footer.
|
||||
/// </summary>
|
||||
protected Container Footer { get; private set; }
|
||||
protected Container Footer { get; private set; } = null!;
|
||||
|
||||
[Resolved]
|
||||
private ScreenFooter? footer { get; set; }
|
||||
|
||||
// todo: very temporary property that will be removed once ModSelectOverlay and FirstRunSetupOverlay are updated to use new footer.
|
||||
public virtual bool UseNewFooter => false;
|
||||
|
||||
/// <summary>
|
||||
/// A container containing all content, including the header and footer.
|
||||
/// May be used for overlay-wide animations.
|
||||
/// </summary>
|
||||
protected Container TopLevelContent { get; private set; }
|
||||
protected Container TopLevelContent { get; private set; } = null!;
|
||||
|
||||
/// <summary>
|
||||
/// A container for content that is to be displayed between the header and footer.
|
||||
/// </summary>
|
||||
protected Container MainAreaContent { get; private set; }
|
||||
protected Container MainAreaContent { get; private set; } = null!;
|
||||
|
||||
/// <summary>
|
||||
/// A container for content that is to be displayed inside the footer.
|
||||
/// </summary>
|
||||
protected Container FooterContent { get; private set; }
|
||||
protected Container FooterContent { get; private set; } = null!;
|
||||
|
||||
protected override bool StartHidden => true;
|
||||
|
||||
@@ -65,7 +70,7 @@ namespace osu.Game.Overlays.Mods
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
const float footer_height = 50;
|
||||
const float footer_height = ScreenFooter.HEIGHT;
|
||||
|
||||
Child = TopLevelContent = new Container
|
||||
{
|
||||
@@ -113,6 +118,17 @@ namespace osu.Game.Overlays.Mods
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates content to be displayed on the game-wide footer.
|
||||
/// </summary>
|
||||
public virtual Drawable CreateFooterContent() => Empty();
|
||||
|
||||
/// <summary>
|
||||
/// Invoked when the back button in the footer is pressed.
|
||||
/// </summary>
|
||||
/// <returns>Whether the back button should not close the overlay.</returns>
|
||||
public virtual bool OnBackButton() => false;
|
||||
|
||||
protected override bool OnClick(ClickEvent e)
|
||||
{
|
||||
if (State.Value == Visibility.Visible)
|
||||
@@ -124,6 +140,8 @@ namespace osu.Game.Overlays.Mods
|
||||
return base.OnClick(e);
|
||||
}
|
||||
|
||||
private bool hideFooterOnPopOut;
|
||||
|
||||
protected override void PopIn()
|
||||
{
|
||||
const double fade_in_duration = 400;
|
||||
@@ -131,7 +149,19 @@ namespace osu.Game.Overlays.Mods
|
||||
this.FadeIn(fade_in_duration, Easing.OutQuint);
|
||||
|
||||
Header.MoveToY(0, fade_in_duration, Easing.OutQuint);
|
||||
Footer.MoveToY(0, fade_in_duration, Easing.OutQuint);
|
||||
|
||||
if (UseNewFooter && footer != null)
|
||||
{
|
||||
footer.SetActiveOverlayContainer(this);
|
||||
|
||||
if (footer.State.Value == Visibility.Hidden)
|
||||
{
|
||||
footer.Show();
|
||||
hideFooterOnPopOut = true;
|
||||
}
|
||||
}
|
||||
else
|
||||
Footer.MoveToY(0, fade_in_duration, Easing.OutQuint);
|
||||
}
|
||||
|
||||
protected override void PopOut()
|
||||
@@ -142,7 +172,19 @@ namespace osu.Game.Overlays.Mods
|
||||
this.FadeOut(fade_out_duration, Easing.OutQuint);
|
||||
|
||||
Header.MoveToY(-Header.DrawHeight, fade_out_duration, Easing.OutQuint);
|
||||
Footer.MoveToY(Footer.DrawHeight, fade_out_duration, Easing.OutQuint);
|
||||
|
||||
if (UseNewFooter && footer != null)
|
||||
{
|
||||
footer.ClearActiveOverlayContainer();
|
||||
|
||||
if (hideFooterOnPopOut)
|
||||
{
|
||||
footer.Hide();
|
||||
hideFooterOnPopOut = false;
|
||||
}
|
||||
}
|
||||
else
|
||||
Footer.MoveToY(Footer.DrawHeight, fade_out_duration, Easing.OutQuint);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,11 +9,11 @@ namespace osu.Game.Overlays
|
||||
{
|
||||
public class OverlayColourProvider
|
||||
{
|
||||
private readonly OverlayColourScheme colourScheme;
|
||||
public OverlayColourScheme ColourScheme { get; private set; }
|
||||
|
||||
public OverlayColourProvider(OverlayColourScheme colourScheme)
|
||||
{
|
||||
this.colourScheme = colourScheme;
|
||||
ColourScheme = colourScheme;
|
||||
}
|
||||
|
||||
// Note that the following five colours are also defined in `OsuColour` as `{colourScheme}{0,1,2,3,4}`.
|
||||
@@ -47,7 +47,17 @@ namespace osu.Game.Overlays
|
||||
public Color4 Background5 => getColour(0.1f, 0.15f);
|
||||
public Color4 Background6 => getColour(0.1f, 0.1f);
|
||||
|
||||
private Color4 getColour(float saturation, float lightness) => Color4.FromHsl(new Vector4(getBaseHue(colourScheme), saturation, lightness, 1));
|
||||
/// <summary>
|
||||
/// Changes the value of <see cref="ColourScheme"/> to a different colour scheme.
|
||||
/// Note that this does not trigger any kind of signal to any drawable that received colours from here, all drawables need to be updated manually.
|
||||
/// </summary>
|
||||
/// <param name="colourScheme">The proposed colour scheme.</param>
|
||||
public void ChangeColourScheme(OverlayColourScheme colourScheme)
|
||||
{
|
||||
ColourScheme = colourScheme;
|
||||
}
|
||||
|
||||
private Color4 getColour(float saturation, float lightness) => Color4.FromHsl(new Vector4(getBaseHue(ColourScheme), saturation, lightness, 1));
|
||||
|
||||
// See https://github.com/ppy/osu-web/blob/5a536d217a21582aad999db50a981003d3ad5659/app/helpers.php#L1620-L1628
|
||||
private static float getBaseHue(OverlayColourScheme colourScheme)
|
||||
|
||||
@@ -17,13 +17,10 @@ namespace osu.Game.Screens.Footer
|
||||
{
|
||||
public partial class ScreenBackButton : ShearedButton
|
||||
{
|
||||
// todo: see https://github.com/ppy/osu-framework/issues/3271
|
||||
private const float torus_scale_factor = 1.2f;
|
||||
|
||||
public const float BUTTON_WIDTH = 240;
|
||||
|
||||
public ScreenBackButton()
|
||||
: base(BUTTON_WIDTH, 70)
|
||||
: base(BUTTON_WIDTH)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -42,14 +39,14 @@ namespace osu.Game.Screens.Footer
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Size = new Vector2(20f),
|
||||
Size = new Vector2(17f),
|
||||
Icon = FontAwesome.Solid.ChevronLeft,
|
||||
},
|
||||
new OsuSpriteText
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Font = OsuFont.TorusAlternate.With(size: 20 * torus_scale_factor),
|
||||
Font = OsuFont.TorusAlternate.With(size: 17),
|
||||
Text = CommonStrings.Back,
|
||||
UseFullGlyphHeight = false,
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@ using osu.Framework.Input.Events;
|
||||
using osu.Game.Graphics.Containers;
|
||||
using osu.Game.Input.Bindings;
|
||||
using osu.Game.Overlays;
|
||||
using osu.Game.Overlays.Mods;
|
||||
using osu.Game.Screens.Menu;
|
||||
using osuTK;
|
||||
|
||||
@@ -24,11 +25,11 @@ namespace osu.Game.Screens.Footer
|
||||
private const int padding = 60;
|
||||
private const float delay_per_button = 30;
|
||||
|
||||
public const int HEIGHT = 60;
|
||||
public const int HEIGHT = 50;
|
||||
|
||||
private readonly List<OverlayContainer> overlays = new List<OverlayContainer>();
|
||||
|
||||
private ScreenBackButton backButton = null!;
|
||||
private Box background = null!;
|
||||
private FillFlowContainer<ScreenFooterButton> buttonsFlow = null!;
|
||||
private Container<ScreenFooterButton> removedButtonsContainer = null!;
|
||||
private LogoTrackingContainer logoTrackingContainer = null!;
|
||||
@@ -36,6 +37,8 @@ namespace osu.Game.Screens.Footer
|
||||
[Cached]
|
||||
private readonly OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Aquamarine);
|
||||
|
||||
public ScreenBackButton BackButton { get; private set; } = null!;
|
||||
|
||||
public Action? OnBack;
|
||||
|
||||
public ScreenFooter(BackReceptor? receptor = null)
|
||||
@@ -48,7 +51,7 @@ namespace osu.Game.Screens.Footer
|
||||
if (receptor == null)
|
||||
Add(receptor = new BackReceptor());
|
||||
|
||||
receptor.OnBackPressed = () => backButton.TriggerClick();
|
||||
receptor.OnBackPressed = () => BackButton.TriggerClick();
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
@@ -56,7 +59,7 @@ namespace osu.Game.Screens.Footer
|
||||
{
|
||||
InternalChildren = new Drawable[]
|
||||
{
|
||||
new Box
|
||||
background = new Box
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Colour = colourProvider.Background5
|
||||
@@ -71,12 +74,12 @@ namespace osu.Game.Screens.Footer
|
||||
Spacing = new Vector2(7, 0),
|
||||
AutoSizeAxes = Axes.Both
|
||||
},
|
||||
backButton = new ScreenBackButton
|
||||
BackButton = new ScreenBackButton
|
||||
{
|
||||
Margin = new MarginPadding { Bottom = 10f, Left = 12f },
|
||||
Margin = new MarginPadding { Bottom = 15f, Left = 12f },
|
||||
Anchor = Anchor.BottomLeft,
|
||||
Origin = Anchor.BottomLeft,
|
||||
Action = () => OnBack?.Invoke(),
|
||||
Action = onBackPressed,
|
||||
},
|
||||
removedButtonsContainer = new Container<ScreenFooterButton>
|
||||
{
|
||||
@@ -115,8 +118,11 @@ namespace osu.Game.Screens.Footer
|
||||
|
||||
public void SetButtons(IReadOnlyList<ScreenFooterButton> buttons)
|
||||
{
|
||||
temporarilyHiddenButtons.Clear();
|
||||
overlays.Clear();
|
||||
|
||||
ClearActiveOverlayContainer();
|
||||
|
||||
var oldButtons = buttonsFlow.ToArray();
|
||||
|
||||
for (int i = 0; i < oldButtons.Length; i++)
|
||||
@@ -127,9 +133,9 @@ namespace osu.Game.Screens.Footer
|
||||
removedButtonsContainer.Add(oldButton);
|
||||
|
||||
if (buttons.Count > 0)
|
||||
makeButtonDisappearToRightAndExpire(oldButton, i, oldButtons.Length);
|
||||
makeButtonDisappearToRight(oldButton, i, oldButtons.Length, true);
|
||||
else
|
||||
makeButtonDisappearToBottomAndExpire(oldButton, i, oldButtons.Length);
|
||||
makeButtonDisappearToBottom(oldButton, i, oldButtons.Length, true);
|
||||
}
|
||||
|
||||
for (int i = 0; i < buttons.Count; i++)
|
||||
@@ -158,27 +164,120 @@ namespace osu.Game.Screens.Footer
|
||||
}
|
||||
}
|
||||
|
||||
private ShearedOverlayContainer? activeOverlay;
|
||||
private Container? contentContainer;
|
||||
private readonly List<ScreenFooterButton> temporarilyHiddenButtons = new List<ScreenFooterButton>();
|
||||
|
||||
public void SetActiveOverlayContainer(ShearedOverlayContainer overlay)
|
||||
{
|
||||
if (contentContainer != null)
|
||||
{
|
||||
throw new InvalidOperationException(@"Cannot set overlay content while one is already present. " +
|
||||
$@"The previous overlay whose content is {contentContainer.Child.GetType().Name} should be hidden first.");
|
||||
}
|
||||
|
||||
activeOverlay = overlay;
|
||||
|
||||
Debug.Assert(temporarilyHiddenButtons.Count == 0);
|
||||
|
||||
var targetButton = buttonsFlow.SingleOrDefault(b => b.Overlay == overlay);
|
||||
|
||||
temporarilyHiddenButtons.AddRange(targetButton != null
|
||||
? buttonsFlow.SkipWhile(b => b != targetButton).Skip(1)
|
||||
: buttonsFlow);
|
||||
|
||||
for (int i = 0; i < temporarilyHiddenButtons.Count; i++)
|
||||
makeButtonDisappearToBottom(temporarilyHiddenButtons[i], 0, 0, false);
|
||||
|
||||
var fallbackPosition = buttonsFlow.Any()
|
||||
? buttonsFlow.ToSpaceOfOtherDrawable(Vector2.Zero, this)
|
||||
: BackButton.ToSpaceOfOtherDrawable(BackButton.LayoutRectangle.TopRight + new Vector2(5f, 0f), this);
|
||||
|
||||
var targetPosition = targetButton?.ToSpaceOfOtherDrawable(targetButton.LayoutRectangle.TopRight, this) ?? fallbackPosition;
|
||||
|
||||
updateColourScheme(overlay.ColourProvider.ColourScheme);
|
||||
|
||||
var content = overlay.CreateFooterContent();
|
||||
|
||||
Add(contentContainer = new Container
|
||||
{
|
||||
Y = -15f,
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Padding = new MarginPadding { Left = targetPosition.X },
|
||||
Child = content,
|
||||
});
|
||||
|
||||
if (temporarilyHiddenButtons.Count > 0)
|
||||
this.Delay(60).Schedule(() => content.Show());
|
||||
else
|
||||
content.Show();
|
||||
}
|
||||
|
||||
public void ClearActiveOverlayContainer()
|
||||
{
|
||||
if (contentContainer == null)
|
||||
return;
|
||||
|
||||
contentContainer.Child.Hide();
|
||||
|
||||
double timeUntilRun = contentContainer.Child.LatestTransformEndTime - Time.Current;
|
||||
|
||||
Container expireTarget = contentContainer;
|
||||
contentContainer = null;
|
||||
activeOverlay = null;
|
||||
|
||||
for (int i = 0; i < temporarilyHiddenButtons.Count; i++)
|
||||
makeButtonAppearFromBottom(temporarilyHiddenButtons[i], 0);
|
||||
|
||||
temporarilyHiddenButtons.Clear();
|
||||
|
||||
expireTarget.Delay(timeUntilRun).Expire();
|
||||
|
||||
updateColourScheme(OverlayColourScheme.Aquamarine);
|
||||
}
|
||||
|
||||
private void updateColourScheme(OverlayColourScheme colourScheme)
|
||||
{
|
||||
colourProvider.ChangeColourScheme(colourScheme);
|
||||
|
||||
background.FadeColour(colourProvider.Background5, 150, Easing.OutQuint);
|
||||
|
||||
foreach (var button in buttonsFlow)
|
||||
button.UpdateDisplay();
|
||||
}
|
||||
|
||||
private void makeButtonAppearFromLeft(ScreenFooterButton button, int index, int count, float startDelay)
|
||||
=> button.AppearFromLeft(startDelay + (count - index) * delay_per_button);
|
||||
|
||||
private void makeButtonAppearFromBottom(ScreenFooterButton button, int index)
|
||||
=> button.AppearFromBottom(index * delay_per_button);
|
||||
|
||||
private void makeButtonDisappearToRightAndExpire(ScreenFooterButton button, int index, int count)
|
||||
=> button.DisappearToRightAndExpire((count - index) * delay_per_button);
|
||||
private void makeButtonDisappearToRight(ScreenFooterButton button, int index, int count, bool expire)
|
||||
=> button.DisappearToRight((count - index) * delay_per_button, expire);
|
||||
|
||||
private void makeButtonDisappearToBottomAndExpire(ScreenFooterButton button, int index, int count)
|
||||
=> button.DisappearToBottomAndExpire((count - index) * delay_per_button);
|
||||
private void makeButtonDisappearToBottom(ScreenFooterButton button, int index, int count, bool expire)
|
||||
=> button.DisappearToBottom((count - index) * delay_per_button, expire);
|
||||
|
||||
private void showOverlay(OverlayContainer overlay)
|
||||
{
|
||||
foreach (var o in overlays)
|
||||
foreach (var o in overlays.Where(o => o != overlay))
|
||||
o.Hide();
|
||||
|
||||
overlay.ToggleVisibility();
|
||||
}
|
||||
|
||||
private void onBackPressed()
|
||||
{
|
||||
if (activeOverlay != null)
|
||||
{
|
||||
if (o == overlay)
|
||||
o.ToggleVisibility();
|
||||
else
|
||||
o.Hide();
|
||||
if (activeOverlay.OnBackButton())
|
||||
return;
|
||||
|
||||
activeOverlay.Hide();
|
||||
return;
|
||||
}
|
||||
|
||||
OnBack?.Invoke();
|
||||
}
|
||||
|
||||
public partial class BackReceptor : Drawable, IKeyBindingHandler<GlobalAction>
|
||||
|
||||
@@ -28,8 +28,8 @@ namespace osu.Game.Screens.Footer
|
||||
private const float shear = OsuGame.SHEAR;
|
||||
|
||||
protected const int CORNER_RADIUS = 10;
|
||||
protected const int BUTTON_HEIGHT = 90;
|
||||
protected const int BUTTON_WIDTH = 140;
|
||||
protected const int BUTTON_HEIGHT = 75;
|
||||
protected const int BUTTON_WIDTH = 116;
|
||||
|
||||
public Bindable<Visibility> OverlayState = new Bindable<Visibility>();
|
||||
|
||||
@@ -40,7 +40,7 @@ namespace osu.Game.Screens.Footer
|
||||
|
||||
private Colour4 buttonAccentColour;
|
||||
|
||||
protected Colour4 AccentColour
|
||||
public Colour4 AccentColour
|
||||
{
|
||||
set
|
||||
{
|
||||
@@ -50,7 +50,7 @@ namespace osu.Game.Screens.Footer
|
||||
}
|
||||
}
|
||||
|
||||
protected IconUsage Icon
|
||||
public IconUsage Icon
|
||||
{
|
||||
set => icon.Icon = value;
|
||||
}
|
||||
@@ -116,19 +116,18 @@ namespace osu.Game.Screens.Footer
|
||||
{
|
||||
Anchor = Anchor.TopCentre,
|
||||
Origin = Anchor.TopCentre,
|
||||
Y = 42,
|
||||
Y = 35,
|
||||
AutoSizeAxes = Axes.Both,
|
||||
Child = text = new OsuSpriteText
|
||||
{
|
||||
// figma design says the size is 16, but due to the issues with font sizes 19 matches better
|
||||
Font = OsuFont.TorusAlternate.With(size: 19),
|
||||
Font = OsuFont.TorusAlternate.With(size: 16),
|
||||
AlwaysPresent = true
|
||||
}
|
||||
},
|
||||
icon = new SpriteIcon
|
||||
{
|
||||
Y = 12,
|
||||
Size = new Vector2(20),
|
||||
Y = 10,
|
||||
Size = new Vector2(16),
|
||||
Anchor = Anchor.TopCentre,
|
||||
Origin = Anchor.TopCentre
|
||||
},
|
||||
@@ -140,7 +139,7 @@ namespace osu.Game.Screens.Footer
|
||||
Anchor = Anchor.BottomCentre,
|
||||
Origin = Anchor.Centre,
|
||||
Y = -CORNER_RADIUS,
|
||||
Size = new Vector2(120, 6),
|
||||
Size = new Vector2(100, 5),
|
||||
Masking = true,
|
||||
CornerRadius = 3,
|
||||
Child = bar = new Box
|
||||
@@ -167,12 +166,15 @@ namespace osu.Game.Screens.Footer
|
||||
if (Overlay != null)
|
||||
OverlayState.BindTo(Overlay.State);
|
||||
|
||||
OverlayState.BindValueChanged(_ => updateDisplay());
|
||||
Enabled.BindValueChanged(_ => updateDisplay(), true);
|
||||
OverlayState.BindValueChanged(_ => UpdateDisplay());
|
||||
Enabled.BindValueChanged(_ => UpdateDisplay(), true);
|
||||
|
||||
FinishTransforms(true);
|
||||
}
|
||||
|
||||
// use Content for tracking input as some buttons might be temporarily hidden with DisappearToBottom, and they become hidden by moving Content away from screen.
|
||||
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => Content.ReceivePositionalInputAt(screenSpacePos);
|
||||
|
||||
public GlobalAction? Hotkey;
|
||||
|
||||
protected override bool OnClick(ClickEvent e)
|
||||
@@ -187,11 +189,11 @@ namespace osu.Game.Screens.Footer
|
||||
|
||||
protected override bool OnHover(HoverEvent e)
|
||||
{
|
||||
updateDisplay();
|
||||
UpdateDisplay();
|
||||
return true;
|
||||
}
|
||||
|
||||
protected override void OnHoverLost(HoverLostEvent e) => updateDisplay();
|
||||
protected override void OnHoverLost(HoverLostEvent e) => UpdateDisplay();
|
||||
|
||||
public virtual bool OnPressed(KeyBindingPressEvent<GlobalAction> e)
|
||||
{
|
||||
@@ -203,7 +205,7 @@ namespace osu.Game.Screens.Footer
|
||||
|
||||
public virtual void OnReleased(KeyBindingReleaseEvent<GlobalAction> e) { }
|
||||
|
||||
private void updateDisplay()
|
||||
public void UpdateDisplay()
|
||||
{
|
||||
Color4 backgroundColour = OverlayState.Value == Visibility.Visible ? buttonAccentColour : colourProvider.Background3;
|
||||
Color4 textColour = OverlayState.Value == Visibility.Visible ? colourProvider.Background6 : colourProvider.Content1;
|
||||
@@ -228,6 +230,7 @@ namespace osu.Game.Screens.Footer
|
||||
|
||||
public void AppearFromLeft(double delay)
|
||||
{
|
||||
Content.FinishTransforms();
|
||||
Content.MoveToX(-300f)
|
||||
.FadeOut()
|
||||
.Delay(delay)
|
||||
@@ -237,6 +240,7 @@ namespace osu.Game.Screens.Footer
|
||||
|
||||
public void AppearFromBottom(double delay)
|
||||
{
|
||||
Content.FinishTransforms();
|
||||
Content.MoveToY(100f)
|
||||
.FadeOut()
|
||||
.Delay(delay)
|
||||
@@ -244,22 +248,26 @@ namespace osu.Game.Screens.Footer
|
||||
.FadeIn(240, Easing.OutCubic);
|
||||
}
|
||||
|
||||
public void DisappearToRightAndExpire(double delay)
|
||||
public void DisappearToRight(double delay, bool expire)
|
||||
{
|
||||
Content.FinishTransforms();
|
||||
Content.Delay(delay)
|
||||
.FadeOut(240, Easing.InOutCubic)
|
||||
.MoveToX(300f, 360, Easing.InOutCubic);
|
||||
|
||||
this.Delay(Content.LatestTransformEndTime - Time.Current).Expire();
|
||||
if (expire)
|
||||
this.Delay(Content.LatestTransformEndTime - Time.Current).Expire();
|
||||
}
|
||||
|
||||
public void DisappearToBottomAndExpire(double delay)
|
||||
public void DisappearToBottom(double delay, bool expire)
|
||||
{
|
||||
Content.FinishTransforms();
|
||||
Content.Delay(delay)
|
||||
.FadeOut(240, Easing.InOutCubic)
|
||||
.MoveToY(100f, 240, Easing.InOutCubic);
|
||||
|
||||
this.Delay(Content.LatestTransformEndTime - Time.Current).Expire();
|
||||
if (expire)
|
||||
this.Delay(Content.LatestTransformEndTime - Time.Current).Expire();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,9 +32,7 @@ namespace osu.Game.Screens.SelectV2.Footer
|
||||
{
|
||||
public partial class ScreenFooterButtonMods : ScreenFooterButton, IHasCurrentValue<IReadOnlyList<Mod>>
|
||||
{
|
||||
// todo: see https://github.com/ppy/osu-framework/issues/3271
|
||||
private const float torus_scale_factor = 1.2f;
|
||||
private const float bar_height = 37f;
|
||||
private const float bar_height = 30f;
|
||||
private const float mod_display_portion = 0.65f;
|
||||
|
||||
private readonly BindableWithCurrent<IReadOnlyList<Mod>> current = new BindableWithCurrent<IReadOnlyList<Mod>>(Array.Empty<Mod>());
|
||||
@@ -112,7 +110,7 @@ namespace osu.Game.Screens.SelectV2.Footer
|
||||
Origin = Anchor.Centre,
|
||||
Shear = -BUTTON_SHEAR,
|
||||
UseFullGlyphHeight = false,
|
||||
Font = OsuFont.Torus.With(size: 14 * torus_scale_factor, weight: FontWeight.Bold)
|
||||
Font = OsuFont.Torus.With(size: 14f, weight: FontWeight.Bold)
|
||||
}
|
||||
},
|
||||
new Container
|
||||
@@ -133,7 +131,7 @@ namespace osu.Game.Screens.SelectV2.Footer
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Shear = -BUTTON_SHEAR,
|
||||
Scale = new Vector2(0.6f),
|
||||
Scale = new Vector2(0.5f),
|
||||
Current = { BindTarget = Current },
|
||||
ExpansionMode = ExpansionMode.AlwaysContracted,
|
||||
},
|
||||
@@ -142,7 +140,7 @@ namespace osu.Game.Screens.SelectV2.Footer
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Shear = -BUTTON_SHEAR,
|
||||
Font = OsuFont.Torus.With(size: 14 * torus_scale_factor, weight: FontWeight.Bold),
|
||||
Font = OsuFont.Torus.With(size: 14f, weight: FontWeight.Bold),
|
||||
Mods = { BindTarget = Current },
|
||||
}
|
||||
}
|
||||
@@ -335,7 +333,7 @@ namespace osu.Game.Screens.SelectV2.Footer
|
||||
Text = ModSelectOverlayStrings.Unranked.ToUpper(),
|
||||
Margin = new MarginPadding { Horizontal = 15 },
|
||||
UseFullGlyphHeight = false,
|
||||
Font = OsuFont.Torus.With(size: 14 * torus_scale_factor, weight: FontWeight.Bold),
|
||||
Font = OsuFont.Torus.With(size: 14f, weight: FontWeight.Bold),
|
||||
Colour = Color4.Black,
|
||||
}
|
||||
};
|
||||
|
||||
@@ -42,7 +42,7 @@ namespace osu.Game.Screens.SelectV2.Footer
|
||||
{
|
||||
randomSpriteText = new OsuSpriteText
|
||||
{
|
||||
Font = OsuFont.TorusAlternate.With(size: 19),
|
||||
Font = OsuFont.TorusAlternate.With(size: 16),
|
||||
AlwaysPresent = true,
|
||||
Anchor = Anchor.TopCentre,
|
||||
Origin = Anchor.TopCentre,
|
||||
@@ -50,7 +50,7 @@ namespace osu.Game.Screens.SelectV2.Footer
|
||||
},
|
||||
rewindSpriteText = new OsuSpriteText
|
||||
{
|
||||
Font = OsuFont.TorusAlternate.With(size: 19),
|
||||
Font = OsuFont.TorusAlternate.With(size: 16),
|
||||
AlwaysPresent = true,
|
||||
Anchor = Anchor.TopCentre,
|
||||
Origin = Anchor.TopCentre,
|
||||
@@ -75,7 +75,7 @@ namespace osu.Game.Screens.SelectV2.Footer
|
||||
AlwaysPresent = true, // make sure the button is sized large enough to always show this
|
||||
Anchor = Anchor.BottomCentre,
|
||||
Origin = Anchor.BottomCentre,
|
||||
Font = OsuFont.TorusAlternate.With(size: 19),
|
||||
Font = OsuFont.TorusAlternate.With(size: 16),
|
||||
});
|
||||
|
||||
fallingRewind.FadeOutFromOne(fade_time, Easing.In);
|
||||
|
||||
Reference in New Issue
Block a user