Skip to content

Commit

Permalink
Merge pull request #28682 from frenzibyte/footer-v2-overlay-content
Browse files Browse the repository at this point in the history
Add implementation for `ScreenFooter` to house footer content of sheared overlays
  • Loading branch information
peppy authored Jul 10, 2024
2 parents cd9973b + f281019 commit 4fe3f39
Show file tree
Hide file tree
Showing 8 changed files with 389 additions and 81 deletions.
176 changes: 165 additions & 11 deletions osu.Game.Tests/Visual/UserInterface/TestSceneScreenFooter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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,
},
},
};
Expand Down Expand Up @@ -82,13 +94,155 @@ public void TestReplaceButtons()
}));
}

[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);
}
}
}
}
Expand Down
70 changes: 56 additions & 14 deletions osu.Game/Overlays/Mods/ShearedOverlayContainer.cs
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -11,45 +9,52 @@
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;

Expand All @@ -65,7 +70,7 @@ protected ShearedOverlayContainer(OverlayColourScheme colourScheme)
[BackgroundDependencyLoader]
private void load()
{
const float footer_height = 50;
const float footer_height = ScreenFooter.HEIGHT;

Child = TopLevelContent = new Container
{
Expand Down Expand Up @@ -113,6 +118,17 @@ private void load()
};
}

/// <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)
Expand All @@ -124,14 +140,28 @@ protected override bool OnClick(ClickEvent e)
return base.OnClick(e);
}

private bool hideFooterOnPopOut;

protected override void PopIn()
{
const double fade_in_duration = 400;

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()
Expand All @@ -142,7 +172,19 @@ protected override void PopOut()
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);
}
}
}
16 changes: 13 additions & 3 deletions osu.Game/Overlays/OverlayColourProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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}`.
Expand Down Expand Up @@ -47,7 +47,17 @@ public OverlayColourProvider(OverlayColourScheme colourScheme)
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)
Expand Down
Loading

0 comments on commit 4fe3f39

Please sign in to comment.