1
0
mirror of https://github.com/ppy/osu.git synced 2025-01-13 07:22:54 +08:00

Merge pull request #28683 from frenzibyte/footer-v2-integration

Replace local footer in existing sheared overlays (e.g. mod select & first-run setup) with `ScreenFooter`
This commit is contained in:
Dean Herbert 2024-07-12 22:45:06 +09:00 committed by GitHub
commit eb3f480a2a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
19 changed files with 568 additions and 396 deletions

View File

@ -3,6 +3,7 @@
#nullable disable #nullable disable
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using NUnit.Framework; using NUnit.Framework;
@ -16,6 +17,7 @@ using osu.Framework.Testing;
using osu.Game.Overlays.Mods; using osu.Game.Overlays.Mods;
using osu.Game.Rulesets.Osu.Mods; using osu.Game.Rulesets.Osu.Mods;
using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Mods;
using osu.Game.Screens.Footer;
using osu.Game.Screens.OnlinePlay; using osu.Game.Screens.OnlinePlay;
using osu.Game.Utils; using osu.Game.Utils;
using osuTK.Input; using osuTK.Input;
@ -26,6 +28,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
{ {
private FreeModSelectOverlay freeModSelectOverlay; private FreeModSelectOverlay freeModSelectOverlay;
private FooterButtonFreeMods footerButtonFreeMods; private FooterButtonFreeMods footerButtonFreeMods;
private ScreenFooter footer;
private readonly Bindable<Dictionary<ModType, IReadOnlyList<Mod>>> availableMods = new Bindable<Dictionary<ModType, IReadOnlyList<Mod>>>(); private readonly Bindable<Dictionary<ModType, IReadOnlyList<Mod>>> availableMods = new Bindable<Dictionary<ModType, IReadOnlyList<Mod>>>();
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
@ -127,7 +130,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
{ {
createFreeModSelect(); createFreeModSelect();
AddAssert("overlay select all button enabled", () => freeModSelectOverlay.ChildrenOfType<SelectAllModsButton>().Single().Enabled.Value); AddAssert("overlay select all button enabled", () => this.ChildrenOfType<SelectAllModsButton>().Single().Enabled.Value);
AddAssert("footer button displays off", () => footerButtonFreeMods.ChildrenOfType<IHasText>().Any(t => t.Text == "off")); AddAssert("footer button displays off", () => footerButtonFreeMods.ChildrenOfType<IHasText>().Any(t => t.Text == "off"));
AddStep("click footer select all button", () => AddStep("click footer select all button", () =>
@ -150,7 +153,10 @@ namespace osu.Game.Tests.Visual.Multiplayer
private void createFreeModSelect() private void createFreeModSelect()
{ {
AddStep("create free mod select screen", () => Children = new Drawable[] AddStep("create free mod select screen", () => Child = new DependencyProvidingContainer
{
RelativeSizeAxes = Axes.Both,
Children = new Drawable[]
{ {
freeModSelectOverlay = new FreeModSelectOverlay freeModSelectOverlay = new FreeModSelectOverlay
{ {
@ -160,9 +166,14 @@ namespace osu.Game.Tests.Visual.Multiplayer
{ {
Anchor = Anchor.BottomRight, Anchor = Anchor.BottomRight,
Origin = Anchor.BottomRight, Origin = Anchor.BottomRight,
Y = -ScreenFooter.HEIGHT,
Current = { BindTarget = freeModSelectOverlay.SelectedMods }, Current = { BindTarget = freeModSelectOverlay.SelectedMods },
}, },
footer = new ScreenFooter(),
},
CachedDependencies = new (Type, object)[] { (typeof(ScreenFooter), footer) },
}); });
AddUntilStep("all column content loaded", AddUntilStep("all column content loaded",
() => freeModSelectOverlay.ChildrenOfType<ModColumn>().Any() () => freeModSelectOverlay.ChildrenOfType<ModColumn>().Any()
&& freeModSelectOverlay.ChildrenOfType<ModColumn>().All(column => column.IsLoaded && column.ItemsLoaded)); && freeModSelectOverlay.ChildrenOfType<ModColumn>().All(column => column.IsLoaded && column.ItemsLoaded));

View File

@ -312,14 +312,14 @@ namespace osu.Game.Tests.Visual.Multiplayer
AddUntilStep("wait for join", () => RoomJoined); AddUntilStep("wait for join", () => RoomJoined);
ClickButtonWhenEnabled<UserModSelectButton>(); ClickButtonWhenEnabled<UserModSelectButton>();
AddAssert("mod select shows unranked", () => screen.UserModsSelectOverlay.ChildrenOfType<RankingInformationDisplay>().Single().Ranked.Value == false); AddAssert("mod select shows unranked", () => this.ChildrenOfType<RankingInformationDisplay>().Single().Ranked.Value == false);
AddAssert("score multiplier = 1.20", () => screen.UserModsSelectOverlay.ChildrenOfType<RankingInformationDisplay>().Single().ModMultiplier.Value, () => Is.EqualTo(1.2).Within(0.01)); AddAssert("score multiplier = 1.20", () => this.ChildrenOfType<RankingInformationDisplay>().Single().ModMultiplier.Value, () => Is.EqualTo(1.2).Within(0.01));
AddStep("select flashlight", () => screen.UserModsSelectOverlay.ChildrenOfType<ModPanel>().Single(m => m.Mod is ModFlashlight).TriggerClick()); AddStep("select flashlight", () => screen.UserModsSelectOverlay.ChildrenOfType<ModPanel>().Single(m => m.Mod is ModFlashlight).TriggerClick());
AddAssert("score multiplier = 1.35", () => screen.UserModsSelectOverlay.ChildrenOfType<RankingInformationDisplay>().Single().ModMultiplier.Value, () => Is.EqualTo(1.35).Within(0.01)); AddAssert("score multiplier = 1.35", () => this.ChildrenOfType<RankingInformationDisplay>().Single().ModMultiplier.Value, () => Is.EqualTo(1.35).Within(0.01));
AddStep("change flashlight setting", () => ((OsuModFlashlight)screen.UserModsSelectOverlay.SelectedMods.Value.Single()).FollowDelay.Value = 1200); AddStep("change flashlight setting", () => ((OsuModFlashlight)screen.UserModsSelectOverlay.SelectedMods.Value.Single()).FollowDelay.Value = 1200);
AddAssert("score multiplier = 1.20", () => screen.UserModsSelectOverlay.ChildrenOfType<RankingInformationDisplay>().Single().ModMultiplier.Value, () => Is.EqualTo(1.2).Within(0.01)); AddAssert("score multiplier = 1.20", () => this.ChildrenOfType<RankingInformationDisplay>().Single().ModMultiplier.Value, () => Is.EqualTo(1.2).Within(0.01));
} }
private partial class TestMultiplayerMatchSubScreen : MultiplayerMatchSubScreen private partial class TestMultiplayerMatchSubScreen : MultiplayerMatchSubScreen

View File

@ -181,12 +181,6 @@ namespace osu.Game.Tests.Visual.SongSelect
#endregion #endregion
protected override void Update()
{
base.Update();
Stack.Padding = new MarginPadding { Bottom = screenScreenFooter.DrawHeight - screenScreenFooter.Y };
}
private void updateFooter(IScreen? _, IScreen? newScreen) private void updateFooter(IScreen? _, IScreen? newScreen)
{ {
if (newScreen is IOsuScreen osuScreen && osuScreen.ShowFooter) if (newScreen is IOsuScreen osuScreen && osuScreen.ShowFooter)

View File

@ -11,6 +11,8 @@ using Moq;
using NUnit.Framework; using NUnit.Framework;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Bindables; using osu.Framework.Bindables;
using osu.Framework.Extensions.ObjectExtensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Framework.Screens; using osu.Framework.Screens;
using osu.Framework.Testing; using osu.Framework.Testing;
@ -20,6 +22,7 @@ using osu.Game.Overlays;
using osu.Game.Overlays.FirstRunSetup; using osu.Game.Overlays.FirstRunSetup;
using osu.Game.Overlays.Notifications; using osu.Game.Overlays.Notifications;
using osu.Game.Screens; using osu.Game.Screens;
using osu.Game.Screens.Footer;
using osuTK; using osuTK;
using osuTK.Input; using osuTK.Input;
@ -28,6 +31,7 @@ namespace osu.Game.Tests.Visual.UserInterface
public partial class TestSceneFirstRunSetupOverlay : OsuManualInputManagerTestScene public partial class TestSceneFirstRunSetupOverlay : OsuManualInputManagerTestScene
{ {
private FirstRunSetupOverlay overlay; private FirstRunSetupOverlay overlay;
private ScreenFooter footer;
private readonly Mock<TestPerformerFromScreenRunner> performer = new Mock<TestPerformerFromScreenRunner>(); private readonly Mock<TestPerformerFromScreenRunner> performer = new Mock<TestPerformerFromScreenRunner>();
@ -60,19 +64,16 @@ namespace osu.Game.Tests.Visual.UserInterface
.Callback((Notification n) => lastNotification = n); .Callback((Notification n) => lastNotification = n);
}); });
AddStep("add overlay", () => createOverlay();
{
Child = overlay = new FirstRunSetupOverlay AddStep("show overlay", () => overlay.Show());
{
State = { Value = Visibility.Visible }
};
});
} }
[Test] [Test]
public void TestBasic() public void TestBasic()
{ {
AddAssert("overlay visible", () => overlay.State.Value == Visibility.Visible); AddAssert("overlay visible", () => overlay.State.Value == Visibility.Visible);
AddAssert("footer visible", () => footer.State.Value == Visibility.Visible);
} }
[Test] [Test]
@ -82,16 +83,13 @@ namespace osu.Game.Tests.Visual.UserInterface
AddUntilStep("step through", () => AddUntilStep("step through", () =>
{ {
if (overlay.CurrentScreen?.IsLoaded != false) overlay.NextButton.TriggerClick(); if (overlay.CurrentScreen?.IsLoaded != false) overlay.NextButton.AsNonNull().TriggerClick();
return overlay.State.Value == Visibility.Hidden; return overlay.State.Value == Visibility.Hidden;
}); });
AddAssert("first run false", () => !LocalConfig.Get<bool>(OsuSetting.ShowFirstRunSetup)); AddAssert("first run false", () => !LocalConfig.Get<bool>(OsuSetting.ShowFirstRunSetup));
AddStep("add overlay", () => createOverlay();
{
Child = overlay = new FirstRunSetupOverlay();
});
AddWaitStep("wait some", 5); AddWaitStep("wait some", 5);
@ -109,7 +107,7 @@ namespace osu.Game.Tests.Visual.UserInterface
if (keyboard) if (keyboard)
InputManager.Key(Key.Enter); InputManager.Key(Key.Enter);
else else
overlay.NextButton.TriggerClick(); overlay.NextButton.AsNonNull().TriggerClick();
} }
return overlay.State.Value == Visibility.Hidden; return overlay.State.Value == Visibility.Hidden;
@ -128,11 +126,9 @@ namespace osu.Game.Tests.Visual.UserInterface
[TestCase(true)] [TestCase(true)]
public void TestBackButton(bool keyboard) public void TestBackButton(bool keyboard)
{ {
AddAssert("back button disabled", () => !overlay.BackButton.Enabled.Value);
AddUntilStep("step to last", () => AddUntilStep("step to last", () =>
{ {
var nextButton = overlay.NextButton; var nextButton = overlay.NextButton.AsNonNull();
if (overlay.CurrentScreen?.IsLoaded != false) if (overlay.CurrentScreen?.IsLoaded != false)
nextButton.TriggerClick(); nextButton.TriggerClick();
@ -142,24 +138,29 @@ namespace osu.Game.Tests.Visual.UserInterface
AddUntilStep("step back to start", () => AddUntilStep("step back to start", () =>
{ {
if (overlay.CurrentScreen?.IsLoaded != false) if (overlay.CurrentScreen?.IsLoaded != false && !(overlay.CurrentScreen is ScreenWelcome))
{ {
if (keyboard) if (keyboard)
InputManager.Key(Key.Escape); InputManager.Key(Key.Escape);
else else
overlay.BackButton.TriggerClick(); footer.BackButton.TriggerClick();
} }
return overlay.CurrentScreen is ScreenWelcome; return overlay.CurrentScreen is ScreenWelcome;
}); });
AddAssert("back button disabled", () => !overlay.BackButton.Enabled.Value); AddAssert("overlay not dismissed", () => overlay.State.Value == Visibility.Visible);
if (keyboard) if (keyboard)
{ {
AddStep("exit via keyboard", () => InputManager.Key(Key.Escape)); AddStep("exit via keyboard", () => InputManager.Key(Key.Escape));
AddAssert("overlay dismissed", () => overlay.State.Value == Visibility.Hidden); AddAssert("overlay dismissed", () => overlay.State.Value == Visibility.Hidden);
} }
else
{
AddStep("press back button", () => footer.BackButton.TriggerClick());
AddAssert("overlay dismissed", () => overlay.State.Value == Visibility.Hidden);
}
} }
[Test] [Test]
@ -185,7 +186,7 @@ namespace osu.Game.Tests.Visual.UserInterface
[Test] [Test]
public void TestResumeViaNotification() public void TestResumeViaNotification()
{ {
AddStep("step to next", () => overlay.NextButton.TriggerClick()); AddStep("step to next", () => overlay.NextButton.AsNonNull().TriggerClick());
AddAssert("is at known screen", () => overlay.CurrentScreen is ScreenUIScale); AddAssert("is at known screen", () => overlay.CurrentScreen is ScreenUIScale);
@ -200,6 +201,27 @@ namespace osu.Game.Tests.Visual.UserInterface
AddAssert("is resumed", () => overlay.CurrentScreen is ScreenUIScale); AddAssert("is resumed", () => overlay.CurrentScreen is ScreenUIScale);
} }
private void createOverlay()
{
AddStep("add overlay", () =>
{
var receptor = new ScreenFooter.BackReceptor();
footer = new ScreenFooter(receptor);
Child = new DependencyProvidingContainer
{
RelativeSizeAxes = Axes.Both,
CachedDependencies = new[] { (typeof(ScreenFooter), (object)footer) },
Children = new Drawable[]
{
receptor,
overlay = new FirstRunSetupOverlay(),
footer,
}
};
});
}
// interface mocks break hot reload, mocking this stub implementation instead works around it. // interface mocks break hot reload, mocking this stub implementation instead works around it.
// see: https://github.com/moq/moq4/issues/1252 // see: https://github.com/moq/moq4/issues/1252
[UsedImplicitly] [UsedImplicitly]

View File

@ -24,6 +24,7 @@ using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Osu;
using osu.Game.Rulesets.Osu.Mods; using osu.Game.Rulesets.Osu.Mods;
using osu.Game.Rulesets.Taiko.Mods; using osu.Game.Rulesets.Taiko.Mods;
using osu.Game.Screens.Footer;
using osu.Game.Tests.Mods; using osu.Game.Tests.Mods;
using osuTK; using osuTK;
using osuTK.Input; using osuTK.Input;
@ -94,12 +95,28 @@ namespace osu.Game.Tests.Visual.UserInterface
private void createScreen() private void createScreen()
{ {
AddStep("create screen", () => Child = modSelectOverlay = new TestModSelectOverlay AddStep("create screen", () =>
{
var receptor = new ScreenFooter.BackReceptor();
var footer = new ScreenFooter(receptor);
Child = new DependencyProvidingContainer
{
RelativeSizeAxes = Axes.Both,
CachedDependencies = new[] { (typeof(ScreenFooter), (object)footer) },
Children = new Drawable[]
{
receptor,
modSelectOverlay = new TestModSelectOverlay
{ {
RelativeSizeAxes = Axes.Both, RelativeSizeAxes = Axes.Both,
State = { Value = Visibility.Visible }, State = { Value = Visibility.Visible },
Beatmap = Beatmap.Value, Beatmap = { Value = Beatmap.Value },
SelectedMods = { BindTarget = SelectedMods } SelectedMods = { BindTarget = SelectedMods },
},
footer,
}
};
}); });
waitForColumnLoad(); waitForColumnLoad();
} }
@ -120,7 +137,7 @@ namespace osu.Game.Tests.Visual.UserInterface
AddAssert("mod multiplier correct", () => AddAssert("mod multiplier correct", () =>
{ {
double multiplier = SelectedMods.Value.Aggregate(1d, (m, mod) => m * mod.ScoreMultiplier); double multiplier = SelectedMods.Value.Aggregate(1d, (m, mod) => m * mod.ScoreMultiplier);
return Precision.AlmostEquals(multiplier, modSelectOverlay.ChildrenOfType<RankingInformationDisplay>().Single().ModMultiplier.Value); return Precision.AlmostEquals(multiplier, this.ChildrenOfType<RankingInformationDisplay>().Single().ModMultiplier.Value);
}); });
assertCustomisationToggleState(disabled: false, active: false); assertCustomisationToggleState(disabled: false, active: false);
AddAssert("setting items created", () => modSelectOverlay.ChildrenOfType<ISettingsItem>().Any()); AddAssert("setting items created", () => modSelectOverlay.ChildrenOfType<ISettingsItem>().Any());
@ -135,7 +152,7 @@ namespace osu.Game.Tests.Visual.UserInterface
AddAssert("mod multiplier correct", () => AddAssert("mod multiplier correct", () =>
{ {
double multiplier = SelectedMods.Value.Aggregate(1d, (m, mod) => m * mod.ScoreMultiplier); double multiplier = SelectedMods.Value.Aggregate(1d, (m, mod) => m * mod.ScoreMultiplier);
return Precision.AlmostEquals(multiplier, modSelectOverlay.ChildrenOfType<RankingInformationDisplay>().Single().ModMultiplier.Value); return Precision.AlmostEquals(multiplier, this.ChildrenOfType<RankingInformationDisplay>().Single().ModMultiplier.Value);
}); });
assertCustomisationToggleState(disabled: false, active: false); assertCustomisationToggleState(disabled: false, active: false);
AddAssert("setting items created", () => modSelectOverlay.ChildrenOfType<ISettingsItem>().Any()); AddAssert("setting items created", () => modSelectOverlay.ChildrenOfType<ISettingsItem>().Any());
@ -757,7 +774,7 @@ namespace osu.Game.Tests.Visual.UserInterface
AddStep("click back button", () => AddStep("click back button", () =>
{ {
InputManager.MoveMouseTo(modSelectOverlay.BackButton); InputManager.MoveMouseTo(this.ChildrenOfType<ScreenBackButton>().Single());
InputManager.Click(MouseButton.Left); InputManager.Click(MouseButton.Left);
}); });
AddAssert("mod select hidden", () => modSelectOverlay.State.Value == Visibility.Hidden); AddAssert("mod select hidden", () => modSelectOverlay.State.Value == Visibility.Hidden);
@ -885,7 +902,7 @@ namespace osu.Game.Tests.Visual.UserInterface
InputManager.Click(MouseButton.Left); InputManager.Click(MouseButton.Left);
}); });
AddAssert("difficulty multiplier display shows correct value", AddAssert("difficulty multiplier display shows correct value",
() => modSelectOverlay.ChildrenOfType<RankingInformationDisplay>().Single().ModMultiplier.Value, () => Is.EqualTo(0.1).Within(Precision.DOUBLE_EPSILON)); () => this.ChildrenOfType<RankingInformationDisplay>().Single().ModMultiplier.Value, () => Is.EqualTo(0.1).Within(Precision.DOUBLE_EPSILON));
// this is highly unorthodox in a test, but because the `ModSettingChangeTracker` machinery heavily leans on events and object disposal and re-creation, // this is highly unorthodox in a test, but because the `ModSettingChangeTracker` machinery heavily leans on events and object disposal and re-creation,
// it is instrumental in the reproduction of the failure scenario that this test is supposed to cover. // it is instrumental in the reproduction of the failure scenario that this test is supposed to cover.
@ -895,7 +912,7 @@ namespace osu.Game.Tests.Visual.UserInterface
AddStep("reset half time speed to default", () => modSelectOverlay.ChildrenOfType<ModCustomisationPanel>().Single() AddStep("reset half time speed to default", () => modSelectOverlay.ChildrenOfType<ModCustomisationPanel>().Single()
.ChildrenOfType<RevertToDefaultButton<double>>().Single().TriggerClick()); .ChildrenOfType<RevertToDefaultButton<double>>().Single().TriggerClick());
AddUntilStep("difficulty multiplier display shows correct value", AddUntilStep("difficulty multiplier display shows correct value",
() => modSelectOverlay.ChildrenOfType<RankingInformationDisplay>().Single().ModMultiplier.Value, () => Is.EqualTo(0.3).Within(Precision.DOUBLE_EPSILON)); () => this.ChildrenOfType<RankingInformationDisplay>().Single().ModMultiplier.Value, () => Is.EqualTo(0.3).Within(Precision.DOUBLE_EPSILON));
} }
[Test] [Test]
@ -1015,8 +1032,6 @@ namespace osu.Game.Tests.Visual.UserInterface
private partial class TestModSelectOverlay : UserModSelectOverlay private partial class TestModSelectOverlay : UserModSelectOverlay
{ {
protected override bool ShowPresets => true; protected override bool ShowPresets => true;
public new ShearedButton BackButton => base.BackButton;
} }
private class TestUnimplementedMod : Mod private class TestUnimplementedMod : Mod

View File

@ -23,7 +23,7 @@ namespace osu.Game.Tests.Visual.UserInterface
{ {
private DependencyProvidingContainer contentContainer = null!; private DependencyProvidingContainer contentContainer = null!;
private ScreenFooter screenFooter = null!; private ScreenFooter screenFooter = null!;
private TestModSelectOverlay overlay = null!; private TestModSelectOverlay modOverlay = null!;
[SetUp] [SetUp]
public void SetUp() => Schedule(() => public void SetUp() => Schedule(() =>
@ -39,7 +39,7 @@ namespace osu.Game.Tests.Visual.UserInterface
}, },
Children = new Drawable[] Children = new Drawable[]
{ {
overlay = new TestModSelectOverlay(), modOverlay = new TestModSelectOverlay(),
new PopoverContainer new PopoverContainer
{ {
RelativeSizeAxes = Axes.Both, RelativeSizeAxes = Axes.Both,
@ -51,7 +51,7 @@ namespace osu.Game.Tests.Visual.UserInterface
screenFooter.SetButtons(new ScreenFooterButton[] screenFooter.SetButtons(new ScreenFooterButton[]
{ {
new ScreenFooterButtonMods(overlay) { Current = SelectedMods }, new ScreenFooterButtonMods(modOverlay) { Current = SelectedMods },
new ScreenFooterButtonRandom(), new ScreenFooterButtonRandom(),
new ScreenFooterButtonOptions(), new ScreenFooterButtonOptions(),
}); });
@ -178,6 +178,24 @@ namespace osu.Game.Tests.Visual.UserInterface
AddAssert("footer hidden", () => screenFooter.State.Value == Visibility.Hidden); AddAssert("footer hidden", () => screenFooter.State.Value == Visibility.Hidden);
} }
[Test]
public void TestLoadOverlayAfterFooterIsDisplayed()
{
TestShearedOverlayContainer externalOverlay = null!;
AddStep("show mod overlay", () => modOverlay.Show());
AddUntilStep("mod footer content shown", () => this.ChildrenOfType<ModSelectFooterContent>().SingleOrDefault()?.IsPresent, () => Is.True);
AddStep("add external overlay", () => contentContainer.Add(externalOverlay = new TestShearedOverlayContainer()));
AddUntilStep("wait for load", () => externalOverlay.IsLoaded);
AddAssert("mod footer content still shown", () => this.ChildrenOfType<ModSelectFooterContent>().SingleOrDefault()?.IsPresent, () => Is.True);
AddAssert("external overlay content not shown", () => this.ChildrenOfType<TestShearedOverlayContainer.TestFooterContent>().SingleOrDefault()?.IsPresent, () => Is.Not.True);
AddStep("hide mod overlay", () => modOverlay.Hide());
AddUntilStep("mod footer content hidden", () => this.ChildrenOfType<ModSelectFooterContent>().SingleOrDefault()?.IsPresent, () => Is.Not.True);
AddAssert("external overlay content still not shown", () => this.ChildrenOfType<TestShearedOverlayContainer.TestFooterContent>().SingleOrDefault()?.IsPresent, () => Is.Not.True);
}
private partial class TestModSelectOverlay : UserModSelectOverlay private partial class TestModSelectOverlay : UserModSelectOverlay
{ {
protected override bool ShowPresets => true; protected override bool ShowPresets => true;
@ -185,8 +203,6 @@ namespace osu.Game.Tests.Visual.UserInterface
private partial class TestShearedOverlayContainer : ShearedOverlayContainer private partial class TestShearedOverlayContainer : ShearedOverlayContainer
{ {
public override bool UseNewFooter => true;
public TestShearedOverlayContainer() public TestShearedOverlayContainer()
: base(OverlayColourScheme.Orange) : base(OverlayColourScheme.Orange)
{ {
@ -212,7 +228,7 @@ namespace osu.Game.Tests.Visual.UserInterface
return false; return false;
} }
public override Drawable CreateFooterContent() => new TestFooterContent(); public override VisibilityContainer CreateFooterContent() => new TestFooterContent();
public partial class TestFooterContent : VisibilityContainer public partial class TestFooterContent : VisibilityContainer
{ {

View File

@ -51,6 +51,7 @@ using osu.Game.Online.Chat;
using osu.Game.Online.Rooms; using osu.Game.Online.Rooms;
using osu.Game.Overlays; using osu.Game.Overlays;
using osu.Game.Overlays.BeatmapListing; using osu.Game.Overlays.BeatmapListing;
using osu.Game.Overlays.Mods;
using osu.Game.Overlays.Music; using osu.Game.Overlays.Music;
using osu.Game.Overlays.Notifications; using osu.Game.Overlays.Notifications;
using osu.Game.Overlays.SkinEditor; using osu.Game.Overlays.SkinEditor;
@ -83,7 +84,7 @@ namespace osu.Game
public partial class OsuGame : OsuGameBase, IKeyBindingHandler<GlobalAction>, ILocalUserPlayInfo, IPerformFromScreenRunner, IOverlayManager, ILinkHandler public partial class OsuGame : OsuGameBase, IKeyBindingHandler<GlobalAction>, ILocalUserPlayInfo, IPerformFromScreenRunner, IOverlayManager, ILinkHandler
{ {
#if DEBUG #if DEBUG
// Different port allows runnning release and debug builds alongside each other. // Different port allows running release and debug builds alongside each other.
public const int IPC_PORT = 44824; public const int IPC_PORT = 44824;
#else #else
public const int IPC_PORT = 44823; public const int IPC_PORT = 44823;
@ -132,6 +133,8 @@ namespace osu.Game
private Container topMostOverlayContent; private Container topMostOverlayContent;
private Container footerBasedOverlayContent;
protected ScalingContainer ScreenContainer { get; private set; } protected ScalingContainer ScreenContainer { get; private set; }
protected Container ScreenOffsetContainer { get; private set; } protected Container ScreenOffsetContainer { get; private set; }
@ -156,8 +159,6 @@ namespace osu.Game
private float toolbarOffset => (Toolbar?.Position.Y ?? 0) + (Toolbar?.DrawHeight ?? 0); private float toolbarOffset => (Toolbar?.Position.Y ?? 0) + (Toolbar?.DrawHeight ?? 0);
private float screenFooterOffset => (ScreenFooter?.DrawHeight ?? 0) - (ScreenFooter?.Position.Y ?? 0);
private IdleTracker idleTracker; private IdleTracker idleTracker;
/// <summary> /// <summary>
@ -242,6 +243,10 @@ namespace osu.Game
throw new ArgumentException($@"{overlayContainer} has already been registered via {nameof(IOverlayManager.RegisterBlockingOverlay)} once."); throw new ArgumentException($@"{overlayContainer} has already been registered via {nameof(IOverlayManager.RegisterBlockingOverlay)} once.");
externalOverlays.Add(overlayContainer); externalOverlays.Add(overlayContainer);
if (overlayContainer is ShearedOverlayContainer)
footerBasedOverlayContent.Add(overlayContainer);
else
overlayContent.Add(overlayContainer); overlayContent.Add(overlayContainer);
if (overlayContainer is OsuFocusedOverlayContainer focusedOverlayContainer) if (overlayContainer is OsuFocusedOverlayContainer focusedOverlayContainer)
@ -934,7 +939,6 @@ namespace osu.Game
return string.Join(" / ", combinations); return string.Join(" / ", combinations);
}; };
Container logoContainer;
ScreenFooter.BackReceptor backReceptor; ScreenFooter.BackReceptor backReceptor;
dependencies.CacheAs(idleTracker = new GameIdleTracker(6000)); dependencies.CacheAs(idleTracker = new GameIdleTracker(6000));
@ -948,6 +952,8 @@ namespace osu.Game
Add(sessionIdleTracker); Add(sessionIdleTracker);
Container logoContainer;
AddRange(new Drawable[] AddRange(new Drawable[]
{ {
new VolumeControlReceptor new VolumeControlReceptor
@ -976,11 +982,19 @@ namespace osu.Game
Origin = Anchor.BottomLeft, Origin = Anchor.BottomLeft,
Action = () => ScreenFooter.OnBack?.Invoke(), Action = () => ScreenFooter.OnBack?.Invoke(),
}, },
logoContainer = new Container { RelativeSizeAxes = Axes.Both },
footerBasedOverlayContent = new Container
{
Depth = -1,
RelativeSizeAxes = Axes.Both,
},
new PopoverContainer new PopoverContainer
{ {
Depth = -1,
RelativeSizeAxes = Axes.Both, RelativeSizeAxes = Axes.Both,
Child = ScreenFooter = new ScreenFooter(backReceptor) Child = ScreenFooter = new ScreenFooter(backReceptor)
{ {
RequestLogoInFront = inFront => ScreenContainer.ChangeChildDepth(logoContainer, inFront ? float.MinValue : 0),
OnBack = () => OnBack = () =>
{ {
if (!(ScreenStack.CurrentScreen is IOsuScreen currentScreen)) if (!(ScreenStack.CurrentScreen is IOsuScreen currentScreen))
@ -991,7 +1005,6 @@ namespace osu.Game
} }
}, },
}, },
logoContainer = new Container { RelativeSizeAxes = Axes.Both },
} }
}, },
} }
@ -1025,7 +1038,7 @@ namespace osu.Game
if (!IsDeployedBuild) if (!IsDeployedBuild)
{ {
dependencies.Cache(versionManager = new VersionManager { Depth = int.MinValue }); dependencies.Cache(versionManager = new VersionManager());
loadComponentSingleFile(versionManager, ScreenContainer.Add); loadComponentSingleFile(versionManager, ScreenContainer.Add);
} }
@ -1072,7 +1085,7 @@ namespace osu.Game
loadComponentSingleFile(CreateUpdateManager(), Add, true); loadComponentSingleFile(CreateUpdateManager(), Add, true);
// overlay elements // overlay elements
loadComponentSingleFile(FirstRunOverlay = new FirstRunSetupOverlay(), overlayContent.Add, true); loadComponentSingleFile(FirstRunOverlay = new FirstRunSetupOverlay(), footerBasedOverlayContent.Add, true);
loadComponentSingleFile(new ManageCollectionsDialog(), overlayContent.Add, true); loadComponentSingleFile(new ManageCollectionsDialog(), overlayContent.Add, true);
loadComponentSingleFile(beatmapListing = new BeatmapListingOverlay(), overlayContent.Add, true); loadComponentSingleFile(beatmapListing = new BeatmapListingOverlay(), overlayContent.Add, true);
loadComponentSingleFile(dashboard = new DashboardOverlay(), overlayContent.Add, true); loadComponentSingleFile(dashboard = new DashboardOverlay(), overlayContent.Add, true);
@ -1137,7 +1150,7 @@ namespace osu.Game
} }
// ensure only one of these overlays are open at once. // ensure only one of these overlays are open at once.
var singleDisplayOverlays = new OverlayContainer[] { FirstRunOverlay, chatOverlay, news, dashboard, beatmapListing, changelogOverlay, rankingsOverlay, wikiOverlay }; var singleDisplayOverlays = new OverlayContainer[] { chatOverlay, news, dashboard, beatmapListing, changelogOverlay, rankingsOverlay, wikiOverlay };
foreach (var overlay in singleDisplayOverlays) foreach (var overlay in singleDisplayOverlays)
{ {
@ -1485,7 +1498,6 @@ namespace osu.Game
ScreenOffsetContainer.Padding = new MarginPadding { Top = toolbarOffset }; ScreenOffsetContainer.Padding = new MarginPadding { Top = toolbarOffset };
overlayOffsetContainer.Padding = new MarginPadding { Top = toolbarOffset }; overlayOffsetContainer.Padding = new MarginPadding { Top = toolbarOffset };
ScreenStack.Padding = new MarginPadding { Bottom = screenFooterOffset };
float horizontalOffset = 0f; float horizontalOffset = 0f;

View File

@ -23,6 +23,7 @@ using osu.Game.Overlays.Settings;
using osu.Game.Rulesets; using osu.Game.Rulesets;
using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Mods;
using osu.Game.Screens; using osu.Game.Screens;
using osu.Game.Screens.Footer;
using osu.Game.Screens.Menu; using osu.Game.Screens.Menu;
using osu.Game.Screens.Select; using osu.Game.Screens.Select;
using osu.Game.Tests.Visual; using osu.Game.Tests.Visual;
@ -153,6 +154,7 @@ namespace osu.Game.Overlays.FirstRunSetup
OsuScreenStack stack; OsuScreenStack stack;
OsuLogo logo; OsuLogo logo;
ScreenFooter footer;
Padding = new MarginPadding(5); Padding = new MarginPadding(5);
@ -166,7 +168,8 @@ namespace osu.Game.Overlays.FirstRunSetup
{ {
RelativePositionAxes = Axes.Both, RelativePositionAxes = Axes.Both,
Position = new Vector2(0.5f), Position = new Vector2(0.5f),
}) }),
(typeof(ScreenFooter), footer = new ScreenFooter()),
}, },
RelativeSizeAxes = Axes.Both, RelativeSizeAxes = Axes.Both,
Children = new Drawable[] Children = new Drawable[]
@ -178,7 +181,8 @@ namespace osu.Game.Overlays.FirstRunSetup
Children = new Drawable[] Children = new Drawable[]
{ {
stack = new OsuScreenStack(), stack = new OsuScreenStack(),
logo footer,
logo,
}, },
}, },
} }

View File

@ -26,6 +26,7 @@ using osu.Game.Overlays.FirstRunSetup;
using osu.Game.Overlays.Mods; using osu.Game.Overlays.Mods;
using osu.Game.Overlays.Notifications; using osu.Game.Overlays.Notifications;
using osu.Game.Screens; using osu.Game.Screens;
using osu.Game.Screens.Footer;
using osu.Game.Screens.Menu; using osu.Game.Screens.Menu;
namespace osu.Game.Overlays namespace osu.Game.Overlays
@ -44,8 +45,7 @@ namespace osu.Game.Overlays
private ScreenStack? stack; private ScreenStack? stack;
public ShearedButton NextButton = null!; public ShearedButton? NextButton => DisplayedFooterContent?.NextButton;
public ShearedButton BackButton = null!;
private readonly Bindable<bool> showFirstRunSetup = new Bindable<bool>(); private readonly Bindable<bool> showFirstRunSetup = new Bindable<bool>();
@ -90,7 +90,7 @@ namespace osu.Game.Overlays
Anchor = Anchor.Centre, Anchor = Anchor.Centre,
Origin = Anchor.Centre, Origin = Anchor.Centre,
RelativeSizeAxes = Axes.Both, RelativeSizeAxes = Axes.Both,
Padding = new MarginPadding { Bottom = 20, }, Padding = new MarginPadding { Bottom = 20 },
Child = new GridContainer Child = new GridContainer
{ {
Anchor = Anchor.Centre, Anchor = Anchor.Centre,
@ -134,51 +134,6 @@ namespace osu.Game.Overlays
} }
}, },
}); });
FooterContent.Add(new GridContainer
{
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Margin = new MarginPadding { Vertical = PADDING },
Anchor = Anchor.BottomLeft,
Origin = Anchor.BottomLeft,
ColumnDimensions = new[]
{
new Dimension(GridSizeMode.Absolute, 10),
new Dimension(GridSizeMode.AutoSize),
new Dimension(),
new Dimension(GridSizeMode.Absolute, 10),
},
RowDimensions = new[]
{
new Dimension(GridSizeMode.AutoSize),
},
Content = new[]
{
new[]
{
Empty(),
BackButton = new ShearedButton(300)
{
Text = CommonStrings.Back,
Action = showPreviousStep,
Enabled = { Value = false },
DarkerColour = colours.Pink2,
LighterColour = colours.Pink1,
},
NextButton = new ShearedButton(0)
{
RelativeSizeAxes = Axes.X,
Width = 1,
Text = FirstRunSetupOverlayStrings.GetStarted,
DarkerColour = ColourProvider.Colour2,
LighterColour = ColourProvider.Colour1,
Action = showNextStep
},
Empty(),
},
}
});
} }
protected override void LoadComplete() protected override void LoadComplete()
@ -190,6 +145,36 @@ namespace osu.Game.Overlays
if (showFirstRunSetup.Value) Show(); if (showFirstRunSetup.Value) Show();
} }
[Resolved]
private ScreenFooter footer { get; set; } = null!;
public new FirstRunSetupFooterContent? DisplayedFooterContent => base.DisplayedFooterContent as FirstRunSetupFooterContent;
public override VisibilityContainer CreateFooterContent()
{
var footerContent = new FirstRunSetupFooterContent
{
ShowNextStep = showNextStep,
};
footerContent.OnLoadComplete += _ => updateButtons();
return footerContent;
}
public override bool OnBackButton()
{
if (currentStepIndex == 0)
return false;
Debug.Assert(stack != null);
stack.CurrentScreen.Exit();
currentStepIndex--;
updateButtons();
return true;
}
public override bool OnPressed(KeyBindingPressEvent<GlobalAction> e) public override bool OnPressed(KeyBindingPressEvent<GlobalAction> e)
{ {
if (!e.Repeat) if (!e.Repeat)
@ -197,19 +182,12 @@ namespace osu.Game.Overlays
switch (e.Action) switch (e.Action)
{ {
case GlobalAction.Select: case GlobalAction.Select:
NextButton.TriggerClick(); DisplayedFooterContent?.NextButton.TriggerClick();
return true; return true;
case GlobalAction.Back: case GlobalAction.Back:
if (BackButton.Enabled.Value) footer.BackButton.TriggerClick();
{ return false;
BackButton.TriggerClick();
return true;
}
// If back button is disabled, we are at the first step.
// The base call will handle dismissal of the overlay.
break;
} }
} }
@ -279,19 +257,6 @@ namespace osu.Game.Overlays
showNextStep(); showNextStep();
} }
private void showPreviousStep()
{
if (currentStepIndex == 0)
return;
Debug.Assert(stack != null);
stack.CurrentScreen.Exit();
currentStepIndex--;
updateButtons();
}
private void showNextStep() private void showNextStep()
{ {
Debug.Assert(currentStepIndex != null); Debug.Assert(currentStepIndex != null);
@ -322,29 +287,61 @@ namespace osu.Game.Overlays
updateButtons(); updateButtons();
} }
private void updateButtons() private void updateButtons() => DisplayedFooterContent?.UpdateButtons(currentStepIndex, steps);
{
BackButton.Enabled.Value = currentStepIndex > 0;
NextButton.Enabled.Value = currentStepIndex != null;
if (currentStepIndex == null) public partial class FirstRunSetupFooterContent : VisibilityContainer
{
public ShearedButton NextButton { get; private set; } = null!;
public Action? ShowNextStep;
[BackgroundDependencyLoader]
private void load(OverlayColourProvider colourProvider)
{
RelativeSizeAxes = Axes.Both;
InternalChild = NextButton = new ShearedButton(0)
{
Anchor = Anchor.BottomLeft,
Origin = Anchor.BottomLeft,
Margin = new MarginPadding { Right = 12f },
RelativeSizeAxes = Axes.X,
Width = 1,
Text = FirstRunSetupOverlayStrings.GetStarted,
DarkerColour = colourProvider.Colour2,
LighterColour = colourProvider.Colour1,
Action = () => ShowNextStep?.Invoke(),
};
}
public void UpdateButtons(int? currentStep, IReadOnlyList<Type> steps)
{
NextButton.Enabled.Value = currentStep != null;
if (currentStep == null)
return; return;
bool isFirstStep = currentStepIndex == 0; bool isFirstStep = currentStep == 0;
bool isLastStep = currentStepIndex == steps.Count - 1; bool isLastStep = currentStep == steps.Count - 1;
if (isFirstStep) if (isFirstStep)
{
BackButton.Text = CommonStrings.Back;
NextButton.Text = FirstRunSetupOverlayStrings.GetStarted; NextButton.Text = FirstRunSetupOverlayStrings.GetStarted;
}
else else
{ {
BackButton.Text = LocalisableString.Interpolate($@"{CommonStrings.Back} ({steps[currentStepIndex.Value - 1].GetLocalisableDescription()})");
NextButton.Text = isLastStep NextButton.Text = isLastStep
? CommonStrings.Finish ? CommonStrings.Finish
: LocalisableString.Interpolate($@"{CommonStrings.Next} ({steps[currentStepIndex.Value + 1].GetLocalisableDescription()})"); : LocalisableString.Interpolate($@"{CommonStrings.Next} ({steps[currentStep.Value + 1].GetLocalisableDescription()})");
}
}
protected override void PopIn()
{
this.FadeIn();
}
protected override void PopOut()
{
this.Delay(400).FadeOut();
} }
} }
} }

View File

@ -0,0 +1,177 @@
// 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.Collections.Generic;
using System.Linq;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Game.Beatmaps;
using osu.Game.Configuration;
using osu.Game.Graphics.UserInterface;
using osu.Game.Rulesets.Mods;
using osuTK;
namespace osu.Game.Overlays.Mods
{
public partial class ModSelectFooterContent : VisibilityContainer
{
private readonly ModSelectOverlay overlay;
private RankingInformationDisplay? rankingInformationDisplay;
private BeatmapAttributesDisplay? beatmapAttributesDisplay;
private FillFlowContainer<ShearedButton> buttonFlow = null!;
private FillFlowContainer contentFlow = null!;
public DeselectAllModsButton? DeselectAllModsButton { get; set; }
public readonly IBindable<WorkingBeatmap?> Beatmap = new Bindable<WorkingBeatmap?>();
public readonly IBindable<IReadOnlyList<Mod>> ActiveMods = new Bindable<IReadOnlyList<Mod>>();
/// <summary>
/// Whether the effects (on score multiplier, on or beatmap difficulty) of the current selected set of mods should be shown.
/// </summary>
protected virtual bool ShowModEffects => true;
/// <summary>
/// Whether the ranking information and beatmap attributes displays are stacked vertically due to small space.
/// </summary>
public bool DisplaysStackedVertically { get; private set; }
public ModSelectFooterContent(ModSelectOverlay overlay)
{
this.overlay = overlay;
}
[BackgroundDependencyLoader]
private void load()
{
RelativeSizeAxes = Axes.Both;
InternalChild = buttonFlow = new FillFlowContainer<ShearedButton>
{
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Direction = FillDirection.Horizontal,
Anchor = Anchor.BottomLeft,
Origin = Anchor.BottomLeft,
Padding = new MarginPadding { Horizontal = 20 },
Spacing = new Vector2(10),
ChildrenEnumerable = CreateButtons(),
};
if (ShowModEffects)
{
AddInternal(contentFlow = new FillFlowContainer
{
AutoSizeAxes = Axes.Both,
Direction = FillDirection.Vertical,
Spacing = new Vector2(30, 10),
Anchor = Anchor.BottomRight,
Origin = Anchor.BottomRight,
Margin = new MarginPadding { Horizontal = 20 },
Children = new Drawable[]
{
rankingInformationDisplay = new RankingInformationDisplay
{
Anchor = Anchor.BottomRight,
Origin = Anchor.BottomRight
},
beatmapAttributesDisplay = new BeatmapAttributesDisplay
{
Anchor = Anchor.BottomRight,
Origin = Anchor.BottomRight,
BeatmapInfo = { Value = Beatmap.Value?.BeatmapInfo },
},
}
});
}
}
private ModSettingChangeTracker? modSettingChangeTracker;
protected override void LoadComplete()
{
base.LoadComplete();
Beatmap.BindValueChanged(b =>
{
if (beatmapAttributesDisplay != null)
beatmapAttributesDisplay.BeatmapInfo.Value = b.NewValue?.BeatmapInfo;
}, true);
ActiveMods.BindValueChanged(m =>
{
updateInformation();
modSettingChangeTracker?.Dispose();
// Importantly, use ActiveMods.Value here (and not the ValueChanged NewValue) as the latter can
// potentially be stale, due to complexities in the way change trackers work.
//
// See https://github.com/ppy/osu/pull/23284#issuecomment-1529056988
modSettingChangeTracker = new ModSettingChangeTracker(ActiveMods.Value);
modSettingChangeTracker.SettingChanged += _ => updateInformation();
}, true);
}
private void updateInformation()
{
if (rankingInformationDisplay != null)
{
double multiplier = 1.0;
foreach (var mod in ActiveMods.Value)
multiplier *= mod.ScoreMultiplier;
rankingInformationDisplay.ModMultiplier.Value = multiplier;
rankingInformationDisplay.Ranked.Value = ActiveMods.Value.All(m => m.Ranked);
}
if (beatmapAttributesDisplay != null)
beatmapAttributesDisplay.Mods.Value = ActiveMods.Value;
}
protected override void Update()
{
base.Update();
if (beatmapAttributesDisplay != null)
{
float rightEdgeOfLastButton = buttonFlow[^1].ScreenSpaceDrawQuad.TopRight.X;
// this is cheating a bit; the 640 value is hardcoded based on how wide the expanded panel _generally_ is.
// due to the transition applied, the raw screenspace quad of the panel cannot be used, as it will trigger an ugly feedback cycle of expanding and collapsing.
float projectedLeftEdgeOfExpandedBeatmapAttributesDisplay = buttonFlow.ToScreenSpace(buttonFlow.DrawSize - new Vector2(640, 0)).X;
DisplaysStackedVertically = rightEdgeOfLastButton > projectedLeftEdgeOfExpandedBeatmapAttributesDisplay;
// only update preview panel's collapsed state after we are fully visible, to ensure all the buttons are where we expect them to be.
if (Alpha == 1)
beatmapAttributesDisplay.Collapsed.Value = DisplaysStackedVertically;
contentFlow.LayoutDuration = 200;
contentFlow.LayoutEasing = Easing.OutQuint;
contentFlow.Direction = DisplaysStackedVertically ? FillDirection.Vertical : FillDirection.Horizontal;
}
}
protected virtual IEnumerable<ShearedButton> CreateButtons() => new[]
{
DeselectAllModsButton = new DeselectAllModsButton(overlay)
};
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);
}
}
}

View File

@ -27,6 +27,7 @@ using osu.Game.Graphics.UserInterface;
using osu.Game.Input.Bindings; using osu.Game.Input.Bindings;
using osu.Game.Localisation; using osu.Game.Localisation;
using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Mods;
using osu.Game.Screens.Footer;
using osu.Game.Utils; using osu.Game.Utils;
using osuTK; using osuTK;
using osuTK.Graphics; using osuTK.Graphics;
@ -87,11 +88,6 @@ namespace osu.Game.Overlays.Mods
public ShearedSearchTextBox SearchTextBox { get; private set; } = null!; public ShearedSearchTextBox SearchTextBox { get; private set; } = null!;
/// <summary>
/// Whether the effects (on score multiplier, on or beatmap difficulty) of the current selected set of mods should be shown.
/// </summary>
protected virtual bool ShowModEffects => true;
/// <summary> /// <summary>
/// Whether per-mod customisation controls are visible. /// Whether per-mod customisation controls are visible.
/// </summary> /// </summary>
@ -108,11 +104,6 @@ namespace osu.Game.Overlays.Mods
protected virtual IReadOnlyList<Mod> ComputeActiveMods() => SelectedMods.Value; protected virtual IReadOnlyList<Mod> ComputeActiveMods() => SelectedMods.Value;
protected virtual IEnumerable<ShearedButton> CreateFooterButtons()
{
yield return deselectAllModsButton = new DeselectAllModsButton(this);
}
private readonly Bindable<Dictionary<ModType, IReadOnlyList<Mod>>> globalAvailableMods = new Bindable<Dictionary<ModType, IReadOnlyList<Mod>>>(); private readonly Bindable<Dictionary<ModType, IReadOnlyList<Mod>>> globalAvailableMods = new Bindable<Dictionary<ModType, IReadOnlyList<Mod>>>();
public IEnumerable<ModState> AllAvailableMods => AvailableMods.Value.SelectMany(pair => pair.Value); public IEnumerable<ModState> AllAvailableMods => AvailableMods.Value.SelectMany(pair => pair.Value);
@ -121,34 +112,18 @@ namespace osu.Game.Overlays.Mods
private ColumnScrollContainer columnScroll = null!; private ColumnScrollContainer columnScroll = null!;
private ColumnFlowContainer columnFlow = null!; private ColumnFlowContainer columnFlow = null!;
private FillFlowContainer<ShearedButton> footerButtonFlow = null!;
private FillFlowContainer footerContentFlow = null!;
private DeselectAllModsButton deselectAllModsButton = null!;
private Container aboveColumnsContent = null!; private Container aboveColumnsContent = null!;
private RankingInformationDisplay? rankingInformationDisplay;
private BeatmapAttributesDisplay? beatmapAttributesDisplay;
private ModCustomisationPanel customisationPanel = null!; private ModCustomisationPanel customisationPanel = null!;
protected ShearedButton BackButton { get; private set; } = null!; protected virtual SelectAllModsButton? SelectAllModsButton => null;
protected SelectAllModsButton? SelectAllModsButton { get; set; }
private Sample? columnAppearSample; private Sample? columnAppearSample;
private WorkingBeatmap? beatmap; public readonly Bindable<WorkingBeatmap?> Beatmap = new Bindable<WorkingBeatmap?>();
public WorkingBeatmap? Beatmap [Resolved]
{ private ScreenFooter? footer { get; set; }
get => beatmap;
set
{
if (beatmap == value) return;
beatmap = value;
if (IsLoaded && beatmapAttributesDisplay != null)
beatmapAttributesDisplay.BeatmapInfo.Value = beatmap?.BeatmapInfo;
}
}
protected ModSelectOverlay(OverlayColourScheme colourScheme = OverlayColourScheme.Green) protected ModSelectOverlay(OverlayColourScheme colourScheme = OverlayColourScheme.Green)
: base(colourScheme) : base(colourScheme)
@ -227,59 +202,6 @@ namespace osu.Game.Overlays.Mods
} }
}); });
FooterContent.Add(footerButtonFlow = new FillFlowContainer<ShearedButton>
{
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Direction = FillDirection.Horizontal,
Anchor = Anchor.BottomLeft,
Origin = Anchor.BottomLeft,
Padding = new MarginPadding
{
Vertical = PADDING,
Horizontal = 70
},
Spacing = new Vector2(10),
ChildrenEnumerable = CreateFooterButtons().Prepend(BackButton = new ShearedButton(BUTTON_WIDTH)
{
Text = CommonStrings.Back,
Action = Hide,
DarkerColour = colours.Pink2,
LighterColour = colours.Pink1
})
});
if (ShowModEffects)
{
FooterContent.Add(footerContentFlow = new FillFlowContainer
{
AutoSizeAxes = Axes.Both,
Direction = FillDirection.Vertical,
Spacing = new Vector2(30, 10),
Anchor = Anchor.BottomRight,
Origin = Anchor.BottomRight,
Margin = new MarginPadding
{
Vertical = PADDING,
Horizontal = 20
},
Children = new Drawable[]
{
rankingInformationDisplay = new RankingInformationDisplay
{
Anchor = Anchor.BottomRight,
Origin = Anchor.BottomRight
},
beatmapAttributesDisplay = new BeatmapAttributesDisplay
{
Anchor = Anchor.BottomRight,
Origin = Anchor.BottomRight,
BeatmapInfo = { Value = Beatmap?.BeatmapInfo },
},
}
});
}
globalAvailableMods.BindTo(game.AvailableMods); globalAvailableMods.BindTo(game.AvailableMods);
textSearchStartsActive = configManager.GetBindable<bool>(OsuSetting.ModSelectTextSearchStartsActive); textSearchStartsActive = configManager.GetBindable<bool>(OsuSetting.ModSelectTextSearchStartsActive);
@ -293,8 +215,6 @@ namespace osu.Game.Overlays.Mods
SearchTextBox.Current.Value = string.Empty; SearchTextBox.Current.Value = string.Empty;
} }
private ModSettingChangeTracker? modSettingChangeTracker;
protected override void LoadComplete() protected override void LoadComplete()
{ {
// this is called before base call so that the mod state is populated early, and the transition in `PopIn()` can play out properly. // this is called before base call so that the mod state is populated early, and the transition in `PopIn()` can play out properly.
@ -317,23 +237,6 @@ namespace osu.Game.Overlays.Mods
ActiveMods.Value = ComputeActiveMods(); ActiveMods.Value = ComputeActiveMods();
}, true); }, true);
ActiveMods.BindValueChanged(_ =>
{
updateOverlayInformation();
modSettingChangeTracker?.Dispose();
if (AllowCustomisation)
{
// Importantly, use ActiveMods.Value here (and not the ValueChanged NewValue) as the latter can
// potentially be stale, due to complexities in the way change trackers work.
//
// See https://github.com/ppy/osu/pull/23284#issuecomment-1529056988
modSettingChangeTracker = new ModSettingChangeTracker(ActiveMods.Value);
modSettingChangeTracker.SettingChanged += _ => updateOverlayInformation();
}
}, true);
customisationPanel.Expanded.BindValueChanged(_ => updateCustomisationVisualState(), true); customisationPanel.Expanded.BindValueChanged(_ => updateCustomisationVisualState(), true);
SearchTextBox.Current.BindValueChanged(query => SearchTextBox.Current.BindValueChanged(query =>
@ -351,6 +254,14 @@ namespace osu.Game.Overlays.Mods
}); });
} }
public new ModSelectFooterContent? DisplayedFooterContent => base.DisplayedFooterContent as ModSelectFooterContent;
public override VisibilityContainer CreateFooterContent() => new ModSelectFooterContent(this)
{
Beatmap = { BindTarget = Beatmap },
ActiveMods = { BindTarget = ActiveMods },
};
private static readonly LocalisableString input_search_placeholder = Resources.Localisation.Web.CommonStrings.InputSearch; private static readonly LocalisableString input_search_placeholder = Resources.Localisation.Web.CommonStrings.InputSearch;
private static readonly LocalisableString tab_to_search_placeholder = ModSelectOverlayStrings.TabToSearch; private static readonly LocalisableString tab_to_search_placeholder = ModSelectOverlayStrings.TabToSearch;
@ -359,26 +270,7 @@ namespace osu.Game.Overlays.Mods
base.Update(); base.Update();
SearchTextBox.PlaceholderText = SearchTextBox.HasFocus ? input_search_placeholder : tab_to_search_placeholder; SearchTextBox.PlaceholderText = SearchTextBox.HasFocus ? input_search_placeholder : tab_to_search_placeholder;
aboveColumnsContent.Padding = aboveColumnsContent.Padding with { Bottom = DisplayedFooterContent?.DisplaysStackedVertically == true ? 75f : 15f };
if (beatmapAttributesDisplay != null)
{
float rightEdgeOfLastButton = footerButtonFlow[^1].ScreenSpaceDrawQuad.TopRight.X;
// this is cheating a bit; the 640 value is hardcoded based on how wide the expanded panel _generally_ is.
// due to the transition applied, the raw screenspace quad of the panel cannot be used, as it will trigger an ugly feedback cycle of expanding and collapsing.
float projectedLeftEdgeOfExpandedBeatmapAttributesDisplay = footerButtonFlow.ToScreenSpace(footerButtonFlow.DrawSize - new Vector2(640, 0)).X;
bool screenIsntWideEnough = rightEdgeOfLastButton > projectedLeftEdgeOfExpandedBeatmapAttributesDisplay;
// only update preview panel's collapsed state after we are fully visible, to ensure all the buttons are where we expect them to be.
if (Alpha == 1)
beatmapAttributesDisplay.Collapsed.Value = screenIsntWideEnough;
footerContentFlow.LayoutDuration = 200;
footerContentFlow.LayoutEasing = Easing.OutQuint;
footerContentFlow.Direction = screenIsntWideEnough ? FillDirection.Vertical : FillDirection.Horizontal;
aboveColumnsContent.Padding = aboveColumnsContent.Padding with { Bottom = screenIsntWideEnough ? 70f : 15f };
}
} }
/// <summary> /// <summary>
@ -456,27 +348,6 @@ namespace osu.Game.Overlays.Mods
modState.ValidForSelection.Value = modState.Mod.Type != ModType.System && modState.Mod.HasImplementation && IsValidMod.Invoke(modState.Mod); modState.ValidForSelection.Value = modState.Mod.Type != ModType.System && modState.Mod.HasImplementation && IsValidMod.Invoke(modState.Mod);
} }
/// <summary>
/// Updates any information displayed on the overlay regarding the effects of the active mods.
/// This reads from <see cref="ActiveMods"/> instead of <see cref="SelectedMods"/>.
/// </summary>
private void updateOverlayInformation()
{
if (rankingInformationDisplay != null)
{
double multiplier = 1.0;
foreach (var mod in ActiveMods.Value)
multiplier *= mod.ScoreMultiplier;
rankingInformationDisplay.ModMultiplier.Value = multiplier;
rankingInformationDisplay.Ranked.Value = ActiveMods.Value.All(m => m.Ranked);
}
if (beatmapAttributesDisplay != null)
beatmapAttributesDisplay.Mods.Value = ActiveMods.Value;
}
private void updateCustomisation() private void updateCustomisation()
{ {
if (!AllowCustomisation) if (!AllowCustomisation)
@ -702,7 +573,7 @@ namespace osu.Game.Overlays.Mods
{ {
if (!SearchTextBox.HasFocus && !customisationPanel.Expanded.Value) if (!SearchTextBox.HasFocus && !customisationPanel.Expanded.Value)
{ {
deselectAllModsButton.TriggerClick(); DisplayedFooterContent?.DeselectAllModsButton?.TriggerClick();
return true; return true;
} }
@ -733,7 +604,13 @@ namespace osu.Game.Overlays.Mods
return base.OnPressed(e); return base.OnPressed(e);
void hideOverlay() => BackButton.TriggerClick(); void hideOverlay()
{
if (footer != null)
footer.BackButton.TriggerClick();
else
Hide();
}
} }
/// <inheritdoc cref="IKeyBindingHandler{PlatformAction}"/> /// <inheritdoc cref="IKeyBindingHandler{PlatformAction}"/>
@ -741,7 +618,7 @@ namespace osu.Game.Overlays.Mods
/// This is handled locally here due to conflicts in input handling between the search text box and the select all mods button. /// This is handled locally here due to conflicts in input handling between the search text box and the select all mods button.
/// Attempting to handle this action locally in both places leads to a possible scenario /// Attempting to handle this action locally in both places leads to a possible scenario
/// wherein activating the "select all" platform binding will both select all text in the search box and select all mods. /// wherein activating the "select all" platform binding will both select all text in the search box and select all mods.
/// </remarks>> /// </remarks>
public bool OnPressed(KeyBindingPressEvent<PlatformAction> e) public bool OnPressed(KeyBindingPressEvent<PlatformAction> e)
{ {
if (e.Repeat || e.Action != PlatformAction.SelectAll || SelectAllModsButton == null) if (e.Repeat || e.Action != PlatformAction.SelectAll || SelectAllModsButton == null)

View File

@ -1,12 +1,13 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. // 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. // See the LICENCE file in the repository root for full licence text.
using System;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Shapes;
using osu.Framework.Input.Events; using osu.Framework.Input.Events;
using osu.Game.Graphics;
using osu.Game.Graphics.Containers; using osu.Game.Graphics.Containers;
using osu.Game.Graphics.UserInterface; using osu.Game.Graphics.UserInterface;
using osu.Game.Screens.Footer; using osu.Game.Screens.Footer;
@ -37,9 +38,6 @@ namespace osu.Game.Overlays.Mods
[Resolved] [Resolved]
private ScreenFooter? footer { get; set; } 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> /// <summary>
/// A container containing all content, including the header and footer. /// A container containing all content, including the header and footer.
/// May be used for overlay-wide animations. /// May be used for overlay-wide animations.
@ -60,6 +58,10 @@ namespace osu.Game.Overlays.Mods
protected override bool BlockNonPositionalInput => true; protected override bool BlockNonPositionalInput => true;
// ShearedOverlayContainers are placed at a layer within the screen container as they rely on ScreenFooter which must be placed there.
// Therefore, dimming must be managed locally, since DimMainContent dims the entire screen layer.
protected sealed override bool DimMainContent => false;
protected ShearedOverlayContainer(OverlayColourScheme colourScheme) protected ShearedOverlayContainer(OverlayColourScheme colourScheme)
{ {
RelativeSizeAxes = Axes.Both; RelativeSizeAxes = Axes.Both;
@ -70,13 +72,16 @@ namespace osu.Game.Overlays.Mods
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load() private void load()
{ {
const float footer_height = ScreenFooter.HEIGHT;
Child = TopLevelContent = new Container Child = TopLevelContent = new Container
{ {
RelativeSizeAxes = Axes.Both, RelativeSizeAxes = Axes.Both,
Children = new Drawable[] Children = new Drawable[]
{ {
new Box
{
RelativeSizeAxes = Axes.Both,
Colour = ColourProvider.Background6.Opacity(0.75f),
},
Header = new ShearedOverlayHeader Header = new ShearedOverlayHeader
{ {
Anchor = Anchor.TopCentre, Anchor = Anchor.TopCentre,
@ -90,38 +95,19 @@ namespace osu.Game.Overlays.Mods
Padding = new MarginPadding Padding = new MarginPadding
{ {
Top = ShearedOverlayHeader.HEIGHT, Top = ShearedOverlayHeader.HEIGHT,
Bottom = footer_height + PADDING, Bottom = ScreenFooter.HEIGHT + PADDING,
} }
}, },
Footer = new InputBlockingContainer
{
RelativeSizeAxes = Axes.X,
Depth = float.MinValue,
Height = footer_height,
Margin = new MarginPadding { Top = PADDING },
Anchor = Anchor.BottomCentre,
Origin = Anchor.BottomCentre,
Children = new Drawable[]
{
new Box
{
RelativeSizeAxes = Axes.Both,
Colour = ColourProvider.Background5
},
FooterContent = new Container
{
RelativeSizeAxes = Axes.Both,
},
}
}
} }
}; };
} }
public VisibilityContainer? DisplayedFooterContent { get; private set; }
/// <summary> /// <summary>
/// Creates content to be displayed on the game-wide footer. /// Creates content to be displayed on the game-wide footer.
/// </summary> /// </summary>
public virtual Drawable CreateFooterContent() => Empty(); public virtual VisibilityContainer? CreateFooterContent() => null;
/// <summary> /// <summary>
/// Invoked when the back button in the footer is pressed. /// Invoked when the back button in the footer is pressed.
@ -140,6 +126,7 @@ namespace osu.Game.Overlays.Mods
return base.OnClick(e); return base.OnClick(e);
} }
private IDisposable? activeOverlayRegistration;
private bool hideFooterOnPopOut; private bool hideFooterOnPopOut;
protected override void PopIn() protected override void PopIn()
@ -150,9 +137,10 @@ namespace osu.Game.Overlays.Mods
Header.MoveToY(0, fade_in_duration, Easing.OutQuint); Header.MoveToY(0, fade_in_duration, Easing.OutQuint);
if (UseNewFooter && footer != null) if (footer != null)
{ {
footer.SetActiveOverlayContainer(this); activeOverlayRegistration = footer.RegisterActiveOverlayContainer(this, out var footerContent);
DisplayedFooterContent = footerContent;
if (footer.State.Value == Visibility.Hidden) if (footer.State.Value == Visibility.Hidden)
{ {
@ -160,8 +148,6 @@ namespace osu.Game.Overlays.Mods
hideFooterOnPopOut = true; hideFooterOnPopOut = true;
} }
} }
else
Footer.MoveToY(0, fade_in_duration, Easing.OutQuint);
} }
protected override void PopOut() protected override void PopOut()
@ -173,9 +159,11 @@ namespace osu.Game.Overlays.Mods
Header.MoveToY(-Header.DrawHeight, fade_out_duration, Easing.OutQuint); Header.MoveToY(-Header.DrawHeight, fade_out_duration, Easing.OutQuint);
if (UseNewFooter && footer != null) if (footer != null)
{ {
footer.ClearActiveOverlayContainer(); activeOverlayRegistration?.Dispose();
activeOverlayRegistration = null;
DisplayedFooterContent = null;
if (hideFooterOnPopOut) if (hideFooterOnPopOut)
{ {
@ -183,8 +171,6 @@ namespace osu.Game.Overlays.Mods
hideFooterOnPopOut = false; hideFooterOnPopOut = false;
} }
} }
else
Footer.MoveToY(Footer.DrawHeight, fade_out_duration, Easing.OutQuint);
} }
} }
} }

View File

@ -11,6 +11,7 @@ using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Shapes;
using osu.Framework.Input.Bindings; using osu.Framework.Input.Bindings;
using osu.Framework.Input.Events; using osu.Framework.Input.Events;
using osu.Framework.Threading;
using osu.Game.Graphics.Containers; using osu.Game.Graphics.Containers;
using osu.Game.Input.Bindings; using osu.Game.Input.Bindings;
using osu.Game.Overlays; using osu.Game.Overlays;
@ -24,6 +25,7 @@ namespace osu.Game.Screens.Footer
{ {
private const int padding = 60; private const int padding = 60;
private const float delay_per_button = 30; private const float delay_per_button = 30;
private const double transition_duration = 400;
public const int HEIGHT = 50; public const int HEIGHT = 50;
@ -37,8 +39,13 @@ namespace osu.Game.Screens.Footer
[Cached] [Cached]
private readonly OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Aquamarine); private readonly OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Aquamarine);
[Resolved]
private OsuGame? game { get; set; }
public ScreenBackButton BackButton { get; private set; } = null!; public ScreenBackButton BackButton { get; private set; } = null!;
public Action<bool>? RequestLogoInFront { get; set; }
public Action? OnBack; public Action? OnBack;
public ScreenFooter(BackReceptor? receptor = null) public ScreenFooter(BackReceptor? receptor = null)
@ -101,19 +108,35 @@ namespace osu.Game.Screens.Footer
}; };
} }
public void StartTrackingLogo(OsuLogo logo, float duration = 0, Easing easing = Easing.None) => logoTrackingContainer.StartTracking(logo, duration, easing); private ScheduledDelegate? changeLogoDepthDelegate;
public void StopTrackingLogo() => logoTrackingContainer.StopTracking();
public void StartTrackingLogo(OsuLogo logo, float duration = 0, Easing easing = Easing.None)
{
changeLogoDepthDelegate?.Cancel();
changeLogoDepthDelegate = null;
logoTrackingContainer.StartTracking(logo, duration, easing);
RequestLogoInFront?.Invoke(true);
}
public void StopTrackingLogo()
{
logoTrackingContainer.StopTracking();
if (game != null)
changeLogoDepthDelegate = Scheduler.AddDelayed(() => RequestLogoInFront?.Invoke(false), transition_duration);
}
protected override void PopIn() protected override void PopIn()
{ {
this.MoveToY(0, 400, Easing.OutQuint) this.MoveToY(0, transition_duration, Easing.OutQuint)
.FadeIn(400, Easing.OutQuint); .FadeIn(transition_duration, Easing.OutQuint);
} }
protected override void PopOut() protected override void PopOut()
{ {
this.MoveToY(HEIGHT, 400, Easing.OutQuint) this.MoveToY(HEIGHT, transition_duration, Easing.OutQuint)
.FadeOut(400, Easing.OutQuint); .FadeOut(transition_duration, Easing.OutQuint);
} }
public void SetButtons(IReadOnlyList<ScreenFooterButton> buttons) public void SetButtons(IReadOnlyList<ScreenFooterButton> buttons)
@ -121,7 +144,7 @@ namespace osu.Game.Screens.Footer
temporarilyHiddenButtons.Clear(); temporarilyHiddenButtons.Clear();
overlays.Clear(); overlays.Clear();
ClearActiveOverlayContainer(); clearActiveOverlayContainer();
var oldButtons = buttonsFlow.ToArray(); var oldButtons = buttonsFlow.ToArray();
@ -166,14 +189,15 @@ namespace osu.Game.Screens.Footer
private ShearedOverlayContainer? activeOverlay; private ShearedOverlayContainer? activeOverlay;
private Container? contentContainer; private Container? contentContainer;
private readonly List<ScreenFooterButton> temporarilyHiddenButtons = new List<ScreenFooterButton>(); private readonly List<ScreenFooterButton> temporarilyHiddenButtons = new List<ScreenFooterButton>();
public void SetActiveOverlayContainer(ShearedOverlayContainer overlay) public IDisposable RegisterActiveOverlayContainer(ShearedOverlayContainer overlay, out VisibilityContainer? footerContent)
{ {
if (contentContainer != null) if (activeOverlay != null)
{ {
throw new InvalidOperationException(@"Cannot set overlay content while one is already present. " + 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."); $@"The previous overlay ({activeOverlay.GetType().Name}) should be hidden first.");
} }
activeOverlay = overlay; activeOverlay = overlay;
@ -197,7 +221,9 @@ namespace osu.Game.Screens.Footer
updateColourScheme(overlay.ColourProvider.ColourScheme); updateColourScheme(overlay.ColourProvider.ColourScheme);
var content = overlay.CreateFooterContent(); footerContent = overlay.CreateFooterContent();
var content = footerContent ?? Empty();
Add(contentContainer = new Container Add(contentContainer = new Container
{ {
@ -211,29 +237,30 @@ namespace osu.Game.Screens.Footer
this.Delay(60).Schedule(() => content.Show()); this.Delay(60).Schedule(() => content.Show());
else else
content.Show(); content.Show();
return new InvokeOnDisposal(clearActiveOverlayContainer);
} }
public void ClearActiveOverlayContainer() private void clearActiveOverlayContainer()
{ {
if (contentContainer == null) if (activeOverlay == null)
return; return;
Debug.Assert(contentContainer != null);
contentContainer.Child.Hide(); contentContainer.Child.Hide();
double timeUntilRun = contentContainer.Child.LatestTransformEndTime - Time.Current; double timeUntilRun = contentContainer.Child.LatestTransformEndTime - Time.Current;
Container expireTarget = contentContainer;
contentContainer = null;
activeOverlay = null;
for (int i = 0; i < temporarilyHiddenButtons.Count; i++) for (int i = 0; i < temporarilyHiddenButtons.Count; i++)
makeButtonAppearFromBottom(temporarilyHiddenButtons[i], 0); makeButtonAppearFromBottom(temporarilyHiddenButtons[i], 0);
temporarilyHiddenButtons.Clear(); temporarilyHiddenButtons.Clear();
expireTarget.Delay(timeUntilRun).Expire();
updateColourScheme(OverlayColourScheme.Aquamarine); updateColourScheme(OverlayColourScheme.Aquamarine);
contentContainer.Delay(timeUntilRun).Expire();
contentContainer = null;
activeOverlay = null;
} }
private void updateColourScheme(OverlayColourScheme colourScheme) private void updateColourScheme(OverlayColourScheme colourScheme)

View File

@ -6,6 +6,7 @@ using osu.Game.Overlays;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Game.Graphics.UserInterface; using osu.Game.Graphics.UserInterface;
using osu.Game.Overlays.Mods; using osu.Game.Overlays.Mods;
using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Mods;
@ -14,8 +15,6 @@ namespace osu.Game.Screens.OnlinePlay
{ {
public partial class FreeModSelectOverlay : ModSelectOverlay public partial class FreeModSelectOverlay : ModSelectOverlay
{ {
protected override bool ShowModEffects => false;
protected override bool AllowCustomisation => false; protected override bool AllowCustomisation => false;
public new Func<Mod, bool> IsValidMod public new Func<Mod, bool> IsValidMod
@ -24,6 +23,8 @@ namespace osu.Game.Screens.OnlinePlay
set => base.IsValidMod = m => m.UserPlayable && value.Invoke(m); set => base.IsValidMod = m => m.UserPlayable && value.Invoke(m);
} }
protected override SelectAllModsButton? SelectAllModsButton => DisplayedFooterContent?.SelectAllModsButton;
public FreeModSelectOverlay() public FreeModSelectOverlay()
: base(OverlayColourScheme.Plum) : base(OverlayColourScheme.Plum)
{ {
@ -32,12 +33,35 @@ namespace osu.Game.Screens.OnlinePlay
protected override ModColumn CreateModColumn(ModType modType) => new ModColumn(modType, true); protected override ModColumn CreateModColumn(ModType modType) => new ModColumn(modType, true);
protected override IEnumerable<ShearedButton> CreateFooterButtons() public new FreeModSelectFooterContent? DisplayedFooterContent => base.DisplayedFooterContent as FreeModSelectFooterContent;
=> base.CreateFooterButtons()
.Prepend(SelectAllModsButton = new SelectAllModsButton(this) public override VisibilityContainer CreateFooterContent() => new FreeModSelectFooterContent(this)
{
Beatmap = { BindTarget = Beatmap },
ActiveMods = { BindTarget = ActiveMods },
};
public partial class FreeModSelectFooterContent : ModSelectFooterContent
{
private readonly FreeModSelectOverlay overlay;
protected override bool ShowModEffects => false;
public SelectAllModsButton? SelectAllModsButton;
public FreeModSelectFooterContent(FreeModSelectOverlay overlay)
: base(overlay)
{
this.overlay = overlay;
}
protected override IEnumerable<ShearedButton> CreateButtons()
=> base.CreateButtons()
.Prepend(SelectAllModsButton = new SelectAllModsButton(overlay)
{ {
Anchor = Anchor.BottomLeft, Anchor = Anchor.BottomLeft,
Origin = Anchor.BottomLeft, Origin = Anchor.BottomLeft,
}); });
} }
}
} }

View File

@ -456,7 +456,7 @@ namespace osu.Game.Screens.OnlinePlay.Match
// Retrieve the corresponding local beatmap, since we can't directly use the playlist's beatmap info // Retrieve the corresponding local beatmap, since we can't directly use the playlist's beatmap info
var localBeatmap = beatmap == null ? null : beatmapManager.QueryBeatmap(b => b.OnlineID == beatmap.OnlineID); var localBeatmap = beatmap == null ? null : beatmapManager.QueryBeatmap(b => b.OnlineID == beatmap.OnlineID);
UserModsSelectOverlay.Beatmap = Beatmap.Value = beatmapManager.GetWorkingBeatmap(localBeatmap); UserModsSelectOverlay.Beatmap.Value = Beatmap.Value = beatmapManager.GetWorkingBeatmap(localBeatmap);
} }
protected virtual void UpdateMods() protected virtual void UpdateMods()

View File

@ -851,7 +851,7 @@ namespace osu.Game.Screens.Select
BeatmapDetails.Beatmap = beatmap; BeatmapDetails.Beatmap = beatmap;
ModSelect.Beatmap = beatmap; ModSelect.Beatmap.Value = beatmap;
advancedStats.BeatmapInfo = beatmap.BeatmapInfo; advancedStats.BeatmapInfo = beatmap.BeatmapInfo;

View File

@ -6,7 +6,6 @@ using System.Collections.Generic;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Cursor;
using osu.Framework.Screens; using osu.Framework.Screens;
using osu.Game.Overlays; using osu.Game.Overlays;
using osu.Game.Overlays.Mods; using osu.Game.Overlays.Mods;
@ -23,6 +22,8 @@ namespace osu.Game.Screens.SelectV2
/// </summary> /// </summary>
public partial class SongSelectV2 : OsuScreen public partial class SongSelectV2 : OsuScreen
{ {
private const float logo_scale = 0.4f;
private readonly ModSelectOverlay modSelectOverlay = new SoloModSelectOverlay(); private readonly ModSelectOverlay modSelectOverlay = new SoloModSelectOverlay();
[Cached] [Cached]
@ -30,15 +31,14 @@ namespace osu.Game.Screens.SelectV2
public override bool ShowFooter => true; public override bool ShowFooter => true;
[Resolved]
private OsuLogo? logo { get; set; }
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load() private void load()
{ {
AddRangeInternal(new Drawable[] AddRangeInternal(new Drawable[]
{ {
new PopoverContainer
{
RelativeSizeAxes = Axes.Both,
},
modSelectOverlay, modSelectOverlay,
}); });
} }
@ -50,21 +50,43 @@ namespace osu.Game.Screens.SelectV2
new ScreenFooterButtonOptions(), new ScreenFooterButtonOptions(),
}; };
protected override void LoadComplete()
{
base.LoadComplete();
modSelectOverlay.State.BindValueChanged(v =>
{
logo?.ScaleTo(v.NewValue == Visibility.Visible ? 0f : logo_scale, 400, Easing.OutQuint)
.FadeTo(v.NewValue == Visibility.Visible ? 0f : 1f, 200, Easing.OutQuint);
}, true);
}
public override void OnEntering(ScreenTransitionEvent e) public override void OnEntering(ScreenTransitionEvent e)
{ {
this.FadeIn(); this.FadeIn();
modSelectOverlay.SelectedMods.BindTo(Mods);
base.OnEntering(e); base.OnEntering(e);
} }
public override void OnResuming(ScreenTransitionEvent e) public override void OnResuming(ScreenTransitionEvent e)
{ {
this.FadeIn(); this.FadeIn();
// required due to https://github.com/ppy/osu-framework/issues/3218
modSelectOverlay.SelectedMods.Disabled = false;
modSelectOverlay.SelectedMods.BindTo(Mods);
base.OnResuming(e); base.OnResuming(e);
} }
public override void OnSuspending(ScreenTransitionEvent e) public override void OnSuspending(ScreenTransitionEvent e)
{ {
this.Delay(400).FadeOut(); this.Delay(400).FadeOut();
modSelectOverlay.SelectedMods.UnbindFrom(Mods);
base.OnSuspending(e); base.OnSuspending(e);
} }
@ -74,17 +96,6 @@ namespace osu.Game.Screens.SelectV2
return base.OnExiting(e); return base.OnExiting(e);
} }
public override bool OnBackButton()
{
if (modSelectOverlay.State.Value == Visibility.Visible)
{
modSelectOverlay.Hide();
return true;
}
return false;
}
protected override void LogoArriving(OsuLogo logo, bool resuming) protected override void LogoArriving(OsuLogo logo, bool resuming)
{ {
base.LogoArriving(logo, resuming); base.LogoArriving(logo, resuming);
@ -99,7 +110,7 @@ namespace osu.Game.Screens.SelectV2
} }
logo.FadeIn(240, Easing.OutQuint); logo.FadeIn(240, Easing.OutQuint);
logo.ScaleTo(0.4f, 240, Easing.OutQuint); logo.ScaleTo(logo_scale, 240, Easing.OutQuint);
logo.Action = () => logo.Action = () =>
{ {
@ -122,14 +133,9 @@ namespace osu.Game.Screens.SelectV2
logo.FadeOut(120, Easing.Out); logo.FadeOut(120, Easing.Out);
} }
private partial class SoloModSelectOverlay : ModSelectOverlay private partial class SoloModSelectOverlay : UserModSelectOverlay
{ {
protected override bool ShowPresets => true; protected override bool ShowPresets => true;
public SoloModSelectOverlay()
: base(OverlayColourScheme.Aquamarine)
{
}
} }
private partial class PlayerLoaderV2 : PlayerLoader private partial class PlayerLoaderV2 : PlayerLoader

View File

@ -658,7 +658,6 @@ namespace osu.Game.Tests.Visual.Gameplay
private partial class TestModSelectOverlay : UserModSelectOverlay private partial class TestModSelectOverlay : UserModSelectOverlay
{ {
protected override bool ShowModEffects => true;
protected override bool ShowPresets => false; protected override bool ShowPresets => false;
public TestModSelectOverlay() public TestModSelectOverlay()

View File

@ -12,6 +12,7 @@ using osu.Framework.Testing;
using osu.Game.Graphics; using osu.Game.Graphics;
using osu.Game.Overlays; using osu.Game.Overlays;
using osu.Game.Screens; using osu.Game.Screens;
using osu.Game.Screens.Footer;
namespace osu.Game.Tests.Visual namespace osu.Game.Tests.Visual
{ {
@ -30,6 +31,9 @@ namespace osu.Game.Tests.Visual
[Cached(typeof(IDialogOverlay))] [Cached(typeof(IDialogOverlay))]
protected DialogOverlay DialogOverlay { get; private set; } protected DialogOverlay DialogOverlay { get; private set; }
[Cached]
private ScreenFooter footer;
protected ScreenTestScene() protected ScreenTestScene()
{ {
base.Content.AddRange(new Drawable[] base.Content.AddRange(new Drawable[]
@ -44,7 +48,8 @@ namespace osu.Game.Tests.Visual
{ {
RelativeSizeAxes = Axes.Both, RelativeSizeAxes = Axes.Both,
Child = DialogOverlay = new DialogOverlay() Child = DialogOverlay = new DialogOverlay()
} },
footer = new ScreenFooter(),
}); });
Stack.ScreenPushed += (_, newScreen) => Logger.Log($"{nameof(ScreenTestScene)} screen changed → {newScreen}"); Stack.ScreenPushed += (_, newScreen) => Logger.Log($"{nameof(ScreenTestScene)} screen changed → {newScreen}");