mirror of
https://github.com/SK-la/Ez2Lazer.git
synced 2026-03-13 11:20:28 +00:00
Merge remote-tracking branch 'upstream/HEAD' into dev
# Conflicts: # osu.Game/Screens/SelectV2/SongSelect.cs # osu.Game/osu.Game.csproj
This commit is contained in:
@@ -5,8 +5,9 @@ on:
|
||||
types: [opened]
|
||||
|
||||
permissions:
|
||||
issues: read # to read the labels of any linked issue(s)
|
||||
pull-requests: write # to put the found labels if any on the PR
|
||||
issues: write
|
||||
pull-requests: write
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
copy-labels:
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
<EmbedAssembliesIntoApk>true</EmbedAssembliesIntoApk>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="ppy.osu.Framework.Android" Version="2026.209.0" />
|
||||
<PackageReference Include="ppy.osu.Framework.Android" Version="2026.303.0" />
|
||||
</ItemGroup>
|
||||
<PropertyGroup>
|
||||
<!-- Fody does not handle Android build well, and warns when unchanged.
|
||||
|
||||
@@ -1,45 +1,64 @@
|
||||
// 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 osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using System;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Game.Overlays;
|
||||
using osu.Game.Screens;
|
||||
using osu.Game.Screens.Edit.Submission;
|
||||
using osu.Game.Screens.Footer;
|
||||
|
||||
namespace osu.Game.Tests.Visual.Editing
|
||||
{
|
||||
public partial class TestSceneBeatmapSubmissionOverlay : OsuTestScene
|
||||
public partial class TestSceneBeatmapSubmissionOverlay : ScreenTestScene
|
||||
{
|
||||
private ScreenFooter footer = null!;
|
||||
private TestBeatmapSubmissionOverlayScreen screen = null!;
|
||||
|
||||
[Cached]
|
||||
private readonly BeatmapSubmissionSettings beatmapSubmissionSettings = new BeatmapSubmissionSettings();
|
||||
|
||||
[SetUpSteps]
|
||||
public void SetUpSteps()
|
||||
public override void SetUpSteps()
|
||||
{
|
||||
AddStep("add overlay", () =>
|
||||
{
|
||||
var receptor = new ScreenFooter.BackReceptor();
|
||||
footer = new ScreenFooter(receptor);
|
||||
base.SetUpSteps();
|
||||
|
||||
Child = new DependencyProvidingContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
CachedDependencies = new[]
|
||||
{
|
||||
(typeof(ScreenFooter), (object)footer),
|
||||
(typeof(BeatmapSubmissionSettings), new BeatmapSubmissionSettings()),
|
||||
},
|
||||
Children = new Drawable[]
|
||||
{
|
||||
receptor,
|
||||
new BeatmapSubmissionOverlay
|
||||
{
|
||||
State = { Value = Visibility.Visible, },
|
||||
},
|
||||
footer,
|
||||
}
|
||||
};
|
||||
});
|
||||
AddStep("push screen", () => LoadScreen(screen = new TestBeatmapSubmissionOverlayScreen()));
|
||||
AddUntilStep("wait until screen is loaded", () => screen.IsLoaded, () => Is.True);
|
||||
AddStep("show overlay", () => screen.Overlay.Show());
|
||||
}
|
||||
|
||||
private partial class TestBeatmapSubmissionOverlayScreen : OsuScreen
|
||||
{
|
||||
public override bool ShowFooter => true;
|
||||
|
||||
public BeatmapSubmissionOverlay Overlay = null!;
|
||||
|
||||
private IDisposable? overlayRegistration;
|
||||
|
||||
[Resolved]
|
||||
private IOverlayManager? overlayManager { get; set; }
|
||||
|
||||
[Cached]
|
||||
private OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Blue);
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
LoadComponent(Overlay = new BeatmapSubmissionOverlay());
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
overlayRegistration = overlayManager?.RegisterBlockingOverlay(Overlay);
|
||||
}
|
||||
|
||||
protected override void Dispose(bool isDisposing)
|
||||
{
|
||||
base.Dispose(isDisposing);
|
||||
overlayRegistration?.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Allocation;
|
||||
@@ -15,6 +17,7 @@ using osu.Game.Database;
|
||||
using osu.Game.Online.Multiplayer;
|
||||
using osu.Game.Online.Rooms;
|
||||
using osu.Game.Rulesets;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Rulesets.Osu;
|
||||
using osu.Game.Screens.OnlinePlay;
|
||||
using osu.Game.Screens.OnlinePlay.Lounge;
|
||||
@@ -22,6 +25,7 @@ using osu.Game.Screens.OnlinePlay.Multiplayer;
|
||||
using osu.Game.Screens.OnlinePlay.Multiplayer.Match;
|
||||
using osu.Game.Screens.Play;
|
||||
using osu.Game.Tests.Resources;
|
||||
using osuTK.Input;
|
||||
|
||||
namespace osu.Game.Tests.Visual.Multiplayer
|
||||
{
|
||||
@@ -35,7 +39,8 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
protected IScreen CurrentScreen => multiplayerComponents.CurrentScreen;
|
||||
protected IScreen CurrentSubScreen => multiplayerComponents.MultiplayerScreen.CurrentSubScreen;
|
||||
|
||||
private BeatmapManager beatmaps = null!;
|
||||
protected BeatmapManager Beatmaps { get; private set; } = null!;
|
||||
|
||||
private BeatmapSetInfo importedSet = null!;
|
||||
private RulesetStore rulesets = null!;
|
||||
|
||||
@@ -49,7 +54,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
BeatmapStore beatmapStore;
|
||||
|
||||
Dependencies.Cache(rulesets = new RealmRulesetStore(Realm));
|
||||
Dependencies.Cache(beatmaps = new BeatmapManager(LocalStorage, Realm, null, audio, Resources, host, Beatmap.Default));
|
||||
Dependencies.Cache(Beatmaps = new BeatmapManager(LocalStorage, Realm, null, audio, Resources, host, Beatmap.Default));
|
||||
Dependencies.CacheAs(beatmapStore = new RealmDetachedBeatmapStore());
|
||||
Dependencies.Cache(Realm);
|
||||
|
||||
@@ -62,13 +67,13 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
|
||||
AddStep("import beatmap", () =>
|
||||
{
|
||||
beatmaps.Import(TestResources.GetQuickTestBeatmapForImport()).WaitSafely();
|
||||
Beatmaps.Import(TestResources.GetQuickTestBeatmapForImport()).WaitSafely();
|
||||
Realm.Write(r =>
|
||||
{
|
||||
foreach (var beatmapInfo in r.All<BeatmapInfo>())
|
||||
beatmapInfo.OnlineMD5Hash = beatmapInfo.MD5Hash;
|
||||
});
|
||||
importedSet = beatmaps.GetAllUsableBeatmapSets().First();
|
||||
importedSet = Beatmaps.GetAllUsableBeatmapSets().First();
|
||||
InitialBeatmap = importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0);
|
||||
OtherBeatmap = importedSet.Beatmaps.Last(b => b.Ruleset.OnlineID == 0);
|
||||
});
|
||||
@@ -118,6 +123,30 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
AddStep("exit player", () => multiplayerComponents.MultiplayerScreen.MakeCurrent());
|
||||
}
|
||||
|
||||
protected void AddBeatmapFromSongSelect(Func<BeatmapInfo> beatmap, RulesetInfo? ruleset = null, IReadOnlyList<Mod>? mods = null)
|
||||
{
|
||||
Screens.SelectV2.SongSelect? songSelect = null;
|
||||
|
||||
AddStep("click add button", () =>
|
||||
{
|
||||
InputManager.MoveMouseTo(this.ChildrenOfType<MultiplayerMatchSubScreen.AddItemButton>().Single());
|
||||
InputManager.Click(MouseButton.Left);
|
||||
});
|
||||
|
||||
AddUntilStep("wait for song select", () => (songSelect = CurrentSubScreen as Screens.SelectV2.SongSelect) != null);
|
||||
AddUntilStep("wait for loaded", () => songSelect.IsCurrentScreen() && !songSelect.AsNonNull().IsFiltering);
|
||||
|
||||
if (ruleset != null)
|
||||
AddStep($"set {ruleset.Name} ruleset", () => songSelect.AsNonNull().Ruleset.Value = ruleset);
|
||||
|
||||
if (mods != null)
|
||||
AddStep($"set mods to {string.Join(",", mods.Select(m => m.Acronym))}", () => songSelect.AsNonNull().Mods.Value = mods);
|
||||
|
||||
AddStep("select other beatmap", () => songSelect.AsNonNull().Beatmap.Value = Beatmaps.GetWorkingBeatmap(beatmap()));
|
||||
AddStep("confirm selection", () => InputManager.Key(Key.Enter));
|
||||
AddUntilStep("wait for return to match", () => CurrentSubScreen is MultiplayerMatchSubScreen);
|
||||
}
|
||||
|
||||
protected override void Dispose(bool isDisposing)
|
||||
{
|
||||
base.Dispose(isDisposing);
|
||||
|
||||
@@ -1,24 +1,16 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Extensions.ObjectExtensions;
|
||||
using osu.Framework.Screens;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Online.Multiplayer;
|
||||
using osu.Game.Rulesets;
|
||||
using osu.Game.Rulesets.Catch;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Rulesets.Osu;
|
||||
using osu.Game.Rulesets.Osu.Mods;
|
||||
using osu.Game.Screens.OnlinePlay.Multiplayer;
|
||||
using osu.Game.Screens.OnlinePlay.Multiplayer.Match;
|
||||
using osu.Game.Screens.Play;
|
||||
using osuTK.Input;
|
||||
|
||||
namespace osu.Game.Tests.Visual.Multiplayer
|
||||
{
|
||||
@@ -45,10 +37,10 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
[Test]
|
||||
public void TestItemAddedToTheEndOfQueue()
|
||||
{
|
||||
addItem(() => OtherBeatmap);
|
||||
AddBeatmapFromSongSelect(() => OtherBeatmap);
|
||||
AddUntilStep("playlist has 2 items", () => MultiplayerClient.ClientAPIRoom?.Playlist.Count == 2);
|
||||
|
||||
addItem(() => InitialBeatmap);
|
||||
AddBeatmapFromSongSelect(() => InitialBeatmap);
|
||||
AddUntilStep("playlist has 3 items", () => MultiplayerClient.ClientAPIRoom?.Playlist.Count == 3);
|
||||
|
||||
AddUntilStep("first item still selected", () => MultiplayerClient.ClientRoom?.Settings.PlaylistItemId == MultiplayerClient.ClientAPIRoom?.Playlist[0].ID);
|
||||
@@ -57,8 +49,8 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
[Test]
|
||||
public void TestNextItemSelectedAfterGameplayFinish()
|
||||
{
|
||||
addItem(() => OtherBeatmap);
|
||||
addItem(() => InitialBeatmap);
|
||||
AddBeatmapFromSongSelect(() => OtherBeatmap);
|
||||
AddBeatmapFromSongSelect(() => InitialBeatmap);
|
||||
|
||||
RunGameplay();
|
||||
|
||||
@@ -74,8 +66,8 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
[Test]
|
||||
public void TestItemsNotClearedWhenSwitchToHostOnlyMode()
|
||||
{
|
||||
addItem(() => OtherBeatmap);
|
||||
addItem(() => InitialBeatmap);
|
||||
AddBeatmapFromSongSelect(() => OtherBeatmap);
|
||||
AddBeatmapFromSongSelect(() => InitialBeatmap);
|
||||
|
||||
// Move to the "other" beatmap.
|
||||
RunGameplay();
|
||||
@@ -89,14 +81,14 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
[Test]
|
||||
public void TestCorrectItemSelectedAfterNewItemAdded()
|
||||
{
|
||||
addItem(() => OtherBeatmap);
|
||||
AddBeatmapFromSongSelect(() => OtherBeatmap);
|
||||
AddUntilStep("selected beatmap is initial beatmap", () => Beatmap.Value.BeatmapInfo.OnlineID == InitialBeatmap.OnlineID);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestCorrectRulesetSelectedAfterNewItemAdded()
|
||||
{
|
||||
addItem(() => OtherBeatmap, new CatchRuleset().RulesetInfo);
|
||||
AddBeatmapFromSongSelect(() => OtherBeatmap, new CatchRuleset().RulesetInfo);
|
||||
AddUntilStep("selected beatmap is initial beatmap", () => Beatmap.Value.BeatmapInfo.OnlineID == InitialBeatmap.OnlineID);
|
||||
|
||||
AddUntilStep("wait for idle", () => MultiplayerClient.LocalUser?.State == MultiplayerUserState.Idle);
|
||||
@@ -113,7 +105,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
[Test]
|
||||
public void TestCorrectModsSelectedAfterNewItemAdded()
|
||||
{
|
||||
addItem(() => OtherBeatmap, mods: new Mod[] { new OsuModDoubleTime() });
|
||||
AddBeatmapFromSongSelect(() => OtherBeatmap, mods: new Mod[] { new OsuModDoubleTime() });
|
||||
AddUntilStep("selected beatmap is initial beatmap", () => Beatmap.Value.BeatmapInfo.OnlineID == InitialBeatmap.OnlineID);
|
||||
|
||||
AddUntilStep("wait for idle", () => MultiplayerClient.LocalUser?.State == MultiplayerUserState.Idle);
|
||||
@@ -126,28 +118,5 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
AddAssert("mods are correct", () => !((Player)CurrentScreen).Mods.Value.Any());
|
||||
AddStep("exit player", () => CurrentScreen.Exit());
|
||||
}
|
||||
|
||||
private void addItem(Func<BeatmapInfo> beatmap, RulesetInfo? ruleset = null, IReadOnlyList<Mod>? mods = null)
|
||||
{
|
||||
Screens.Select.SongSelect? songSelect = null;
|
||||
|
||||
AddStep("click add button", () =>
|
||||
{
|
||||
InputManager.MoveMouseTo(this.ChildrenOfType<MultiplayerMatchSubScreen.AddItemButton>().Single());
|
||||
InputManager.Click(MouseButton.Left);
|
||||
});
|
||||
|
||||
AddUntilStep("wait for song select", () => (songSelect = CurrentSubScreen as Screens.Select.SongSelect) != null);
|
||||
AddUntilStep("wait for loaded", () => songSelect.AsNonNull().BeatmapSetsLoaded);
|
||||
|
||||
if (ruleset != null)
|
||||
AddStep($"set {ruleset.Name} ruleset", () => songSelect.AsNonNull().Ruleset.Value = ruleset);
|
||||
|
||||
if (mods != null)
|
||||
AddStep($"set mods to {string.Join(",", mods.Select(m => m.Acronym))}", () => songSelect.AsNonNull().Mods.Value = mods);
|
||||
|
||||
AddStep("select other beatmap", () => songSelect.AsNonNull().FinaliseSelection(beatmap()));
|
||||
AddUntilStep("wait for return to match", () => CurrentSubScreen is MultiplayerMatchSubScreen);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,14 +7,13 @@ using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using osu.Framework.Input;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Game.Overlays;
|
||||
using osu.Game.Overlays.Mods;
|
||||
using osu.Game.Rulesets.Osu.Mods;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Rulesets.Osu.Mods;
|
||||
using osu.Game.Screens;
|
||||
using osu.Game.Screens.Footer;
|
||||
using osu.Game.Screens.OnlinePlay;
|
||||
using osu.Game.Utils;
|
||||
@@ -22,12 +21,13 @@ using osuTK.Input;
|
||||
|
||||
namespace osu.Game.Tests.Visual.Multiplayer
|
||||
{
|
||||
public partial class TestSceneFreeModSelectOverlay : MultiplayerTestScene
|
||||
public partial class TestSceneFreeModSelectOverlay : ScreenTestScene
|
||||
{
|
||||
private FreeModSelectOverlay freeModSelectOverlay = null!;
|
||||
private FooterButtonFreeMods footerButtonFreeMods = null!;
|
||||
private ScreenFooter footer = null!;
|
||||
private TestFreeModSelectOverlayScreen screen = null!;
|
||||
private readonly Bindable<Dictionary<ModType, IReadOnlyList<Mod>>> availableMods = new Bindable<Dictionary<ModType, IReadOnlyList<Mod>>>();
|
||||
private readonly Bindable<IReadOnlyList<Mod>> freeMods = new Bindable<IReadOnlyList<Mod>>([]);
|
||||
|
||||
private FreeModSelectOverlay freeModSelectOverlay => screen.Overlay;
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(OsuGameBase osuGameBase)
|
||||
@@ -35,6 +35,14 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
availableMods.BindTo(osuGameBase.AvailableMods);
|
||||
}
|
||||
|
||||
[SetUpSteps]
|
||||
public override void SetUpSteps()
|
||||
{
|
||||
base.SetUpSteps();
|
||||
|
||||
AddStep("reset selected mods", () => freeMods.Value = []);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestFreeModSelect()
|
||||
{
|
||||
@@ -44,11 +52,6 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
() => this.ChildrenOfType<ModPanel>()
|
||||
.Where(panel => panel.IsPresent)
|
||||
.All(panel => panel.Mod.HasImplementation && panel.Mod.UserPlayable));
|
||||
|
||||
AddToggleStep("toggle visibility", visible =>
|
||||
{
|
||||
freeModSelectOverlay.State.Value = visible ? Visibility.Visible : Visibility.Hidden;
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
@@ -72,18 +75,16 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
|
||||
AddAssert("select all button enabled", () => this.ChildrenOfType<SelectAllModsButton>().Single().Enabled.Value);
|
||||
|
||||
AddStep("click select all button", navigateAndClick<SelectAllModsButton>);
|
||||
AddStep("click select all button", () =>
|
||||
{
|
||||
InputManager.MoveMouseTo(this.ChildrenOfType<SelectAllModsButton>().Single());
|
||||
InputManager.Click(MouseButton.Left);
|
||||
});
|
||||
AddAssert("select all button disabled", () => !this.ChildrenOfType<SelectAllModsButton>().Single().Enabled.Value);
|
||||
|
||||
AddStep("change search term", () => freeModSelectOverlay.SearchTerm = "e");
|
||||
|
||||
AddAssert("select all button enabled", () => this.ChildrenOfType<SelectAllModsButton>().Single().Enabled.Value);
|
||||
|
||||
void navigateAndClick<T>() where T : Drawable
|
||||
{
|
||||
InputManager.MoveMouseTo(this.ChildrenOfType<T>().Single());
|
||||
InputManager.Click(MouseButton.Left);
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
@@ -124,55 +125,14 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
AddAssert("select all button enabled", () => this.ChildrenOfType<SelectAllModsButton>().Single().Enabled.Value);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestSelectAllViaFooterButtonThenDeselectFromOverlay()
|
||||
{
|
||||
createFreeModSelect();
|
||||
|
||||
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"));
|
||||
|
||||
AddStep("click footer select all button", () =>
|
||||
{
|
||||
InputManager.MoveMouseTo(footerButtonFreeMods);
|
||||
InputManager.Click(MouseButton.Left);
|
||||
});
|
||||
|
||||
AddUntilStep("all mods selected", assertAllAvailableModsSelected);
|
||||
AddAssert("footer button displays all", () => footerButtonFreeMods.ChildrenOfType<IHasText>().Any(t => t.Text == "all"));
|
||||
|
||||
AddStep("click deselect all button", () =>
|
||||
{
|
||||
InputManager.MoveMouseTo(this.ChildrenOfType<DeselectAllModsButton>().Single());
|
||||
InputManager.Click(MouseButton.Left);
|
||||
});
|
||||
AddUntilStep("all mods deselected", () => !freeModSelectOverlay.SelectedMods.Value.Any());
|
||||
AddAssert("footer button displays off", () => footerButtonFreeMods.ChildrenOfType<IHasText>().Any(t => t.Text == "off"));
|
||||
}
|
||||
|
||||
private void createFreeModSelect()
|
||||
{
|
||||
AddStep("create free mod select screen", () => Child = new DependencyProvidingContainer
|
||||
AddStep("create free mod select screen", () => LoadScreen(screen = new TestFreeModSelectOverlayScreen
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
freeModSelectOverlay = new FreeModSelectOverlay
|
||||
{
|
||||
State = { Value = Visibility.Visible }
|
||||
},
|
||||
footerButtonFreeMods = new FooterButtonFreeMods(freeModSelectOverlay)
|
||||
{
|
||||
Anchor = Anchor.BottomRight,
|
||||
Origin = Anchor.BottomRight,
|
||||
Y = -ScreenFooter.HEIGHT,
|
||||
FreeMods = { BindTarget = freeModSelectOverlay.SelectedMods },
|
||||
},
|
||||
footer = new ScreenFooter(),
|
||||
},
|
||||
CachedDependencies = new (Type, object)[] { (typeof(ScreenFooter), footer) },
|
||||
});
|
||||
|
||||
FreeMods = { BindTarget = freeMods },
|
||||
}));
|
||||
AddUntilStep("wait until screen is loaded", () => screen.IsLoaded, () => Is.True);
|
||||
AddStep("show overlay", () => freeModSelectOverlay.Show());
|
||||
AddUntilStep("all column content loaded",
|
||||
() => freeModSelectOverlay.ChildrenOfType<ModColumn>().Any()
|
||||
&& freeModSelectOverlay.ChildrenOfType<ModColumn>().All(column => column.IsLoaded && column.ItemsLoaded));
|
||||
@@ -197,5 +157,50 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private partial class TestFreeModSelectOverlayScreen : OsuScreen
|
||||
{
|
||||
public override bool ShowFooter => true;
|
||||
|
||||
public FreeModSelectOverlay Overlay = null!;
|
||||
private IDisposable? overlayRegistration;
|
||||
|
||||
public readonly Bindable<IReadOnlyList<Mod>> FreeMods = new Bindable<IReadOnlyList<Mod>>([]);
|
||||
|
||||
[Resolved]
|
||||
private IOverlayManager? overlayManager { get; set; }
|
||||
|
||||
[Cached]
|
||||
private OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Blue);
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
LoadComponent(Overlay = new FreeModSelectOverlay
|
||||
{
|
||||
SelectedMods = { BindTarget = FreeMods }
|
||||
});
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
overlayRegistration = overlayManager?.RegisterBlockingOverlay(Overlay);
|
||||
}
|
||||
|
||||
public override IReadOnlyList<ScreenFooterButton> CreateFooterButtons() =>
|
||||
[
|
||||
new FooterButtonFreeMods(Overlay)
|
||||
{
|
||||
FreeMods = { BindTarget = FreeMods },
|
||||
},
|
||||
];
|
||||
|
||||
protected override void Dispose(bool isDisposing)
|
||||
{
|
||||
base.Dispose(isDisposing);
|
||||
overlayRegistration?.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,8 @@ using System;
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Extensions;
|
||||
using osu.Framework.Extensions.ObjectExtensions;
|
||||
using osu.Framework.Screens;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Online.Multiplayer;
|
||||
@@ -36,6 +38,14 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
AddUntilStep("second playlist item selected", () => MultiplayerClient.ClientRoom?.Settings.PlaylistItemId == MultiplayerClient.ClientAPIRoom?.Playlist[1].ID);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestItemStillSelectedAfterChangeToSameBeatmap()
|
||||
{
|
||||
selectNewItem(() => InitialBeatmap);
|
||||
|
||||
AddUntilStep("playlist item still selected", () => MultiplayerClient.ClientRoom?.Settings.PlaylistItemId == MultiplayerClient.ClientAPIRoom?.Playlist[0].ID);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestSettingsUpdatedWhenChangingQueueMode()
|
||||
{
|
||||
@@ -47,14 +57,6 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
AddUntilStep("api room updated", () => MultiplayerClient.ClientAPIRoom?.QueueMode == QueueMode.AllPlayers);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestItemStillSelectedAfterChangeToSameBeatmap()
|
||||
{
|
||||
selectNewItem(() => InitialBeatmap);
|
||||
|
||||
AddUntilStep("playlist item still selected", () => MultiplayerClient.ClientRoom?.Settings.PlaylistItemId == MultiplayerClient.ClientAPIRoom?.Playlist[0].ID);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestItemStillSelectedAfterChangeToOtherBeatmap()
|
||||
{
|
||||
@@ -80,13 +82,15 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
[Test]
|
||||
public void TestAddItemsAsHost()
|
||||
{
|
||||
addItem(() => OtherBeatmap);
|
||||
AddBeatmapFromSongSelect(() => OtherBeatmap);
|
||||
|
||||
AddUntilStep("playlist contains two items", () => MultiplayerClient.ClientAPIRoom?.Playlist.Count == 2);
|
||||
}
|
||||
|
||||
private void selectNewItem(Func<BeatmapInfo> beatmap)
|
||||
{
|
||||
Screens.SelectV2.SongSelect? songSelect = null;
|
||||
|
||||
AddUntilStep("wait for playlist panels to load", () =>
|
||||
{
|
||||
var queueList = this.ChildrenOfType<MultiplayerQueueList>().Single();
|
||||
@@ -99,26 +103,15 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
InputManager.Click(MouseButton.Left);
|
||||
});
|
||||
|
||||
AddUntilStep("wait for song select", () => CurrentSubScreen is Screens.Select.SongSelect select && select.BeatmapSetsLoaded);
|
||||
AddUntilStep("wait for song select", () => (songSelect = CurrentSubScreen as Screens.SelectV2.SongSelect) != null);
|
||||
AddUntilStep("wait for loaded", () => songSelect.IsCurrentScreen() && !songSelect.AsNonNull().IsFiltering);
|
||||
|
||||
BeatmapInfo otherBeatmap = null!;
|
||||
AddStep("select other beatmap", () => ((Screens.Select.SongSelect)CurrentSubScreen).FinaliseSelection(otherBeatmap = beatmap()));
|
||||
|
||||
AddStep("select other beatmap", () => songSelect.AsNonNull().Beatmap.Value = Beatmaps.GetWorkingBeatmap(otherBeatmap = beatmap()));
|
||||
AddStep("confirm selection", () => InputManager.Key(Key.Enter));
|
||||
AddUntilStep("wait for return to match", () => CurrentSubScreen is MultiplayerMatchSubScreen);
|
||||
|
||||
AddUntilStep("selected item is new beatmap", () => Beatmap.Value.BeatmapInfo.OnlineID == otherBeatmap.OnlineID);
|
||||
}
|
||||
|
||||
private void addItem(Func<BeatmapInfo> beatmap)
|
||||
{
|
||||
AddStep("click add button", () =>
|
||||
{
|
||||
InputManager.MoveMouseTo(this.ChildrenOfType<MultiplayerMatchSubScreen.AddItemButton>().Single());
|
||||
InputManager.Click(MouseButton.Left);
|
||||
});
|
||||
|
||||
AddUntilStep("wait for song select", () => CurrentSubScreen is Screens.Select.SongSelect select && select.BeatmapSetsLoaded);
|
||||
AddStep("select other beatmap", () => ((Screens.Select.SongSelect)CurrentSubScreen).FinaliseSelection(beatmap()));
|
||||
AddUntilStep("wait for return to match", () => CurrentSubScreen is MultiplayerMatchSubScreen);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -228,7 +228,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
|
||||
// edit playlist item
|
||||
AddStep("Press select", () => InputManager.Key(Key.Enter));
|
||||
AddUntilStep("wait for song select", () => InputManager.ChildrenOfType<MultiplayerMatchSongSelect>().FirstOrDefault()?.BeatmapSetsLoaded == true);
|
||||
waitForSongSelect();
|
||||
|
||||
// select beatmap
|
||||
AddStep("Press select", () => InputManager.Key(Key.Enter));
|
||||
@@ -451,7 +451,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
((MultiplayerMatchSubScreen)currentSubScreen).ShowSongSelect(item);
|
||||
});
|
||||
|
||||
AddUntilStep("wait for song select", () => this.ChildrenOfType<MultiplayerMatchSongSelect>().FirstOrDefault()?.BeatmapSetsLoaded == true);
|
||||
waitForSongSelect();
|
||||
|
||||
AddUntilStep("Beatmap matches current item", () => Beatmap.Value.BeatmapInfo.OnlineID == multiplayerClient.ClientRoom?.Playlist.First().BeatmapID);
|
||||
|
||||
@@ -492,7 +492,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
((MultiplayerMatchSubScreen)currentSubScreen).ShowSongSelect(item);
|
||||
});
|
||||
|
||||
AddUntilStep("wait for song select", () => this.ChildrenOfType<MultiplayerMatchSongSelect>().FirstOrDefault()?.BeatmapSetsLoaded == true);
|
||||
waitForSongSelect();
|
||||
|
||||
AddUntilStep("Ruleset matches current item", () => Ruleset.Value.OnlineID == multiplayerClient.ClientRoom?.Playlist.First().RulesetID);
|
||||
|
||||
@@ -533,7 +533,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
((MultiplayerMatchSubScreen)currentSubScreen).ShowSongSelect(item);
|
||||
});
|
||||
|
||||
AddUntilStep("wait for song select", () => this.ChildrenOfType<MultiplayerMatchSongSelect>().FirstOrDefault()?.BeatmapSetsLoaded == true);
|
||||
waitForSongSelect();
|
||||
|
||||
AddUntilStep("Mods match current item",
|
||||
() => SelectedMods.Value.Select(m => m.Acronym).SequenceEqual(multiplayerClient.ClientRoom.AsNonNull().Playlist.First().RequiredMods.Select(m => m.Acronym)));
|
||||
@@ -1051,7 +1051,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
AddStep("press edit on second item", () => this.ChildrenOfType<DrawableRoomPlaylistItem>().Single(i => i.Item.RulesetID == 1)
|
||||
.ChildrenOfType<DrawableRoomPlaylistItem.PlaylistEditButton>().Single().TriggerClick());
|
||||
|
||||
AddUntilStep("wait for song select", () => InputManager.ChildrenOfType<MultiplayerMatchSongSelect>().FirstOrDefault()?.BeatmapSetsLoaded == true);
|
||||
waitForSongSelect();
|
||||
AddAssert("ruleset is taiko", () => Ruleset.Value.OnlineID == 1);
|
||||
|
||||
AddStep("start match", () => multiplayerClient.StartMatch().WaitSafely());
|
||||
@@ -1249,6 +1249,15 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
AddUntilStep("wait for join", () => multiplayerClient.RoomJoined);
|
||||
}
|
||||
|
||||
private void waitForSongSelect()
|
||||
{
|
||||
AddUntilStep("wait for song select", () =>
|
||||
{
|
||||
var songSelect = InputManager.ChildrenOfType<MultiplayerMatchSongSelect>().FirstOrDefault();
|
||||
return songSelect != null && songSelect.IsCurrentScreen() && !songSelect.IsFiltering;
|
||||
});
|
||||
}
|
||||
|
||||
protected override void Dispose(bool isDisposing)
|
||||
{
|
||||
base.Dispose(isDisposing);
|
||||
|
||||
@@ -16,6 +16,7 @@ using osu.Framework.Testing;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Configuration;
|
||||
using osu.Game.Database;
|
||||
using osu.Game.Online.API;
|
||||
using osu.Game.Online.Rooms;
|
||||
using osu.Game.Overlays.Mods;
|
||||
using osu.Game.Rulesets;
|
||||
@@ -26,8 +27,8 @@ using osu.Game.Rulesets.Taiko;
|
||||
using osu.Game.Rulesets.Taiko.Mods;
|
||||
using osu.Game.Screens.OnlinePlay;
|
||||
using osu.Game.Screens.OnlinePlay.Multiplayer;
|
||||
using osu.Game.Screens.Select;
|
||||
using osu.Game.Tests.Resources;
|
||||
using osuTK.Input;
|
||||
|
||||
namespace osu.Game.Tests.Visual.Multiplayer
|
||||
{
|
||||
@@ -64,7 +65,11 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
{
|
||||
base.SetUpSteps();
|
||||
|
||||
AddStep("create room", () => room = CreateDefaultRoom());
|
||||
AddStep("create room", () =>
|
||||
{
|
||||
Ruleset.Value = new OsuRuleset().RulesetInfo;
|
||||
room = CreateDefaultRoom();
|
||||
});
|
||||
AddStep("join room", () => JoinRoom(room));
|
||||
WaitForJoined();
|
||||
}
|
||||
@@ -80,7 +85,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
LoadScreen(songSelect = new TestMultiplayerMatchSongSelect(room));
|
||||
});
|
||||
|
||||
AddUntilStep("wait for present", () => songSelect.IsCurrentScreen() && songSelect.BeatmapSetsLoaded);
|
||||
AddUntilStep("wait for present", () => songSelect.IsCurrentScreen() && !songSelect.IsFiltering);
|
||||
}
|
||||
|
||||
[Test]
|
||||
@@ -101,19 +106,21 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
setUp();
|
||||
|
||||
AddStep("change ruleset", () => Ruleset.Value = new TaikoRuleset().RulesetInfo);
|
||||
|
||||
AddUntilStep("wait for filtering", () => !songSelect.IsFiltering);
|
||||
AddStep("select beatmap",
|
||||
() => songSelect.Carousel.SelectBeatmap(selectedBeatmap = beatmaps.First(beatmap => beatmap.Ruleset.OnlineID == new TaikoRuleset().LegacyID)));
|
||||
() => songSelect.SelectBeatmap(selectedBeatmap = beatmaps.First(beatmap => beatmap.Ruleset.OnlineID == new TaikoRuleset().LegacyID)));
|
||||
|
||||
AddUntilStep("wait for selection", () => Beatmap.Value.BeatmapInfo.Equals(selectedBeatmap));
|
||||
AddUntilStep("wait for ongoing operation to complete", () => !OnlinePlayDependencies.OngoingOperationTracker.InProgress.Value);
|
||||
|
||||
AddStep("set mods", () => SelectedMods.Value = new[] { new TaikoModDoubleTime() });
|
||||
|
||||
AddStep("confirm selection", () => songSelect.FinaliseSelection());
|
||||
AddStep("confirm selection", () => InputManager.Key(Key.Enter));
|
||||
|
||||
AddUntilStep("song select exited", () => !songSelect.IsCurrentScreen());
|
||||
|
||||
AddAssert("beatmap not changed", () => Beatmap.Value.BeatmapInfo.Equals(selectedBeatmap));
|
||||
AddAssert("beatmap not changed", () => Beatmap.Value.BeatmapInfo, () => Is.EqualTo((selectedBeatmap)));
|
||||
AddAssert("ruleset not changed", () => Ruleset.Value.Equals(new TaikoRuleset().RulesetInfo));
|
||||
AddAssert("mods not changed", () => SelectedMods.Value.Single() is TaikoModDoubleTime);
|
||||
}
|
||||
@@ -133,10 +140,42 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
// A previous test's mod overlay could still be fading out.
|
||||
AddUntilStep("wait for only one freemod overlay", () => this.ChildrenOfType<FreeModSelectOverlay>().Count() == 1);
|
||||
|
||||
AddStep("open free mod overlay", () =>
|
||||
{
|
||||
InputManager.MoveMouseTo(this.ChildrenOfType<FooterButtonFreeMods>().Single());
|
||||
InputManager.Click(MouseButton.Left);
|
||||
});
|
||||
|
||||
assertFreeModNotShown(allowedMod);
|
||||
assertFreeModNotShown(requiredMod);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestFreeModsDisplayedOnEnter()
|
||||
{
|
||||
AddStep("set room freemods", () =>
|
||||
{
|
||||
var editedItem = MultiplayerClient.ClientRoom!.CurrentPlaylistItem.Clone();
|
||||
|
||||
editedItem.AllowedMods =
|
||||
[
|
||||
new APIMod(new OsuModHardRock()),
|
||||
];
|
||||
|
||||
MultiplayerClient.EditPlaylistItem(editedItem);
|
||||
});
|
||||
|
||||
setUp();
|
||||
|
||||
AddStep("open free mod overlay", () =>
|
||||
{
|
||||
InputManager.MoveMouseTo(this.ChildrenOfType<FooterButtonFreeMods>().Single());
|
||||
InputManager.Click(MouseButton.Left);
|
||||
});
|
||||
|
||||
assertFreeModShown(typeof(OsuModHardRock));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestChangeRulesetImmediatelyAfterLoadComplete()
|
||||
{
|
||||
@@ -154,16 +193,27 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
songSelect.OnLoadComplete += _ => Ruleset.Value = new TaikoRuleset().RulesetInfo;
|
||||
LoadScreen(songSelect);
|
||||
});
|
||||
AddUntilStep("wait for present", () => songSelect.IsCurrentScreen() && songSelect.BeatmapSetsLoaded);
|
||||
|
||||
AddStep("confirm selection", () => songSelect.FinaliseSelection());
|
||||
AddUntilStep("wait for present", () => songSelect.IsCurrentScreen() && !songSelect.IsFiltering);
|
||||
|
||||
AddStep("confirm selection", () => InputManager.Key(Key.Enter));
|
||||
AddAssert("beatmap is taiko", () => Beatmap.Value.BeatmapInfo.Ruleset.OnlineID, () => Is.EqualTo(1));
|
||||
AddAssert("ruleset is taiko", () => Ruleset.Value.OnlineID, () => Is.EqualTo(1));
|
||||
}
|
||||
|
||||
private void assertFreeModShown(Type type)
|
||||
{
|
||||
AddUntilStep($"{type.ReadableName()} displayed in freemod overlay",
|
||||
() => this.ChildrenOfType<FreeModSelectOverlay>()
|
||||
.Single()
|
||||
.ChildrenOfType<ModPanel>()
|
||||
.Where(panel => panel.Visible)
|
||||
.Any(b => b.Mod.GetType() == type));
|
||||
}
|
||||
|
||||
private void assertFreeModNotShown(Type type)
|
||||
{
|
||||
AddAssert($"{type.ReadableName()} not displayed in freemod overlay",
|
||||
AddUntilStep($"{type.ReadableName()} not displayed in freemod overlay",
|
||||
() => this.ChildrenOfType<FreeModSelectOverlay>()
|
||||
.Single()
|
||||
.ChildrenOfType<ModPanel>()
|
||||
@@ -185,12 +235,12 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
|
||||
public new Bindable<IReadOnlyList<Mod>> FreeMods => base.FreeMods;
|
||||
|
||||
public new BeatmapCarousel Carousel => base.Carousel;
|
||||
|
||||
public TestMultiplayerMatchSongSelect(Room room, PlaylistItem? itemToEdit = null)
|
||||
: base(room, itemToEdit)
|
||||
{
|
||||
}
|
||||
|
||||
public void SelectBeatmap(BeatmapInfo beatmap) => SelectAndRun(beatmap, () => { });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -164,20 +164,20 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
AddAssert("freestyle enabled", () => songSelect.Freestyle.Value, () => Is.True);
|
||||
AddStep("click icon in free mods button", () =>
|
||||
{
|
||||
InputManager.MoveMouseTo(this.ChildrenOfType<FooterButtonFreeModsV2>().Single());
|
||||
InputManager.MoveMouseTo(this.ChildrenOfType<FooterButtonFreeMods>().Single());
|
||||
InputManager.Click(MouseButton.Left);
|
||||
});
|
||||
AddAssert("mod select not visible", () => this.ChildrenOfType<FreeModSelectOverlay>().Single().State.Value, () => Is.EqualTo(Visibility.Hidden));
|
||||
|
||||
AddStep("toggle freestyle off", () =>
|
||||
{
|
||||
InputManager.MoveMouseTo(this.ChildrenOfType<FooterButtonFreestyleV2>().Single());
|
||||
InputManager.MoveMouseTo(this.ChildrenOfType<FooterButtonFreestyle>().Single());
|
||||
InputManager.Click(MouseButton.Left);
|
||||
});
|
||||
AddAssert("freestyle disabled", () => songSelect.Freestyle.Value, () => Is.False);
|
||||
AddStep("click icon in free mods button", () =>
|
||||
{
|
||||
InputManager.MoveMouseTo(this.ChildrenOfType<FooterButtonFreeModsV2>().Single());
|
||||
InputManager.MoveMouseTo(this.ChildrenOfType<FooterButtonFreeMods>().Single());
|
||||
InputManager.Click(MouseButton.Left);
|
||||
});
|
||||
AddAssert("mod select visible", () => this.ChildrenOfType<FreeModSelectOverlay>().Single().State.Value, () => Is.EqualTo(Visibility.Visible));
|
||||
|
||||
@@ -8,9 +8,13 @@ using NUnit.Framework;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using osu.Framework.Screens;
|
||||
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;
|
||||
using osu.Game.Screens.Footer;
|
||||
|
||||
@@ -23,10 +27,13 @@ namespace osu.Game.Tests.Visual.Navigation
|
||||
[Test]
|
||||
public void TestFooterButtonsOnScreenTransitions()
|
||||
{
|
||||
PushAndConfirm(() => new TestScreenOne());
|
||||
PushAndConfirm(() => new TestScreen
|
||||
{
|
||||
CreateButtons = () => [new ScreenFooterButton { Text = "Button One" }]
|
||||
});
|
||||
AddUntilStep("button one shown", () => screenFooter.ChildrenOfType<ScreenFooterButton>().First().Text.ToString(), () => Is.EqualTo("Button One"));
|
||||
|
||||
PushAndConfirm(() => new TestScreenTwo());
|
||||
PushAndConfirm(() => new TestScreen { CreateButtons = () => [new ScreenFooterButton { Text = "Button Two" }] });
|
||||
AddUntilStep("button two shown", () => screenFooter.ChildrenOfType<ScreenFooterButton>().First().Text.ToString(), () => Is.EqualTo("Button Two"));
|
||||
|
||||
AddStep("exit screen", () => Game.ScreenStack.Exit());
|
||||
@@ -40,7 +47,7 @@ namespace osu.Game.Tests.Visual.Navigation
|
||||
AddAssert("footer hidden", () => screenFooter.State.Value, () => Is.EqualTo(Visibility.Hidden));
|
||||
AddAssert("old back button shown", () => Game.BackButton.State.Value, () => Is.EqualTo(Visibility.Visible));
|
||||
|
||||
PushAndConfirm(() => new TestScreen(true));
|
||||
PushAndConfirm(() => new TestScreen());
|
||||
AddAssert("footer shown", () => screenFooter.State.Value, () => Is.EqualTo(Visibility.Visible));
|
||||
AddAssert("old back button hidden", () => Game.BackButton.State.Value, () => Is.EqualTo(Visibility.Hidden));
|
||||
|
||||
@@ -69,10 +76,16 @@ namespace osu.Game.Tests.Visual.Navigation
|
||||
AddAssert("footer hidden", () => screenFooter.State.Value, () => Is.EqualTo(Visibility.Hidden));
|
||||
AddAssert("old back button shown", () => Game.BackButton.State.Value, () => Is.EqualTo(Visibility.Visible));
|
||||
|
||||
pushSubScreenAndConfirm(() => screen, () => new TestScreenOne());
|
||||
pushSubScreenAndConfirm(() => screen, () => new TestScreen
|
||||
{
|
||||
CreateButtons = () => [new ScreenFooterButton { Text = "Button One" }]
|
||||
});
|
||||
AddUntilStep("button one shown", () => screenFooter.ChildrenOfType<ScreenFooterButton>().First().Text.ToString(), () => Is.EqualTo("Button One"));
|
||||
|
||||
pushSubScreenAndConfirm(() => screen, () => new TestScreenTwo());
|
||||
pushSubScreenAndConfirm(() => screen, () => new TestScreen
|
||||
{
|
||||
CreateButtons = () => [new ScreenFooterButton { Text = "Button Two" }]
|
||||
});
|
||||
AddUntilStep("button two shown", () => screenFooter.ChildrenOfType<ScreenFooterButton>().First().Text.ToString(), () => Is.EqualTo("Button Two"));
|
||||
|
||||
AddStep("exit sub screen", () => screen.ExitSubScreen());
|
||||
@@ -92,10 +105,16 @@ namespace osu.Game.Tests.Visual.Navigation
|
||||
TestScreenWithSubScreen screen = null!;
|
||||
|
||||
PushAndConfirm(() => screen = new TestScreenWithSubScreen());
|
||||
pushSubScreenAndConfirm(() => screen, () => new TestScreenOne());
|
||||
pushSubScreenAndConfirm(() => screen, () => new TestScreen
|
||||
{
|
||||
CreateButtons = () => [new ScreenFooterButton { Text = "Button One" }]
|
||||
});
|
||||
AddUntilStep("button one shown", () => screenFooter.ChildrenOfType<ScreenFooterButton>().First().Text.ToString(), () => Is.EqualTo("Button One"));
|
||||
|
||||
PushAndConfirm(() => new TestScreenTwo());
|
||||
PushAndConfirm(() => new TestScreen
|
||||
{
|
||||
CreateButtons = () => [new ScreenFooterButton { Text = "Button Two" }]
|
||||
});
|
||||
AddUntilStep("button two shown", () => screenFooter.ChildrenOfType<ScreenFooterButton>().First().Text.ToString(), () => Is.EqualTo("Button Two"));
|
||||
|
||||
AddStep("exit parent screen", () => Game.ScreenStack.Exit());
|
||||
@@ -111,14 +130,23 @@ namespace osu.Game.Tests.Visual.Navigation
|
||||
TestScreenWithSubScreen screen = null!;
|
||||
|
||||
PushAndConfirm(() => screen = new TestScreenWithSubScreen());
|
||||
pushSubScreenAndConfirm(() => screen, () => new TestScreenOne());
|
||||
pushSubScreenAndConfirm(() => screen, () => new TestScreen
|
||||
{
|
||||
CreateButtons = () => [new ScreenFooterButton { Text = "Button One" }]
|
||||
});
|
||||
AddUntilStep("button one shown", () => screenFooter.ChildrenOfType<ScreenFooterButton>().First().Text.ToString(), () => Is.EqualTo("Button One"));
|
||||
|
||||
PushAndConfirm(() => new TestScreenOne());
|
||||
PushAndConfirm(() => new TestScreen
|
||||
{
|
||||
CreateButtons = () => [new ScreenFooterButton { Text = "Button One" }]
|
||||
});
|
||||
AddUntilStep("button one shown", () => screenFooter.ChildrenOfType<ScreenFooterButton>().First().Text.ToString(), () => Is.EqualTo("Button One"));
|
||||
|
||||
// Can't use the helper method because the screen never loads
|
||||
AddStep("Push new sub screen", () => screen.PushSubScreen(new TestScreenTwo()));
|
||||
AddStep("Push new sub screen", () => screen.PushSubScreen(new TestScreen
|
||||
{
|
||||
CreateButtons = () => [new ScreenFooterButton { Text = "Button Two" }]
|
||||
}));
|
||||
AddWaitStep("wait for potential screen load", 5);
|
||||
AddUntilStep("button one still shown", () => screenFooter.ChildrenOfType<ScreenFooterButton>().First().Text.ToString(), () => Is.EqualTo("Button One"));
|
||||
|
||||
@@ -126,6 +154,83 @@ namespace osu.Game.Tests.Visual.Navigation
|
||||
AddUntilStep("button two shown", () => screenFooter.ChildrenOfType<ScreenFooterButton>().First().Text.ToString(), () => Is.EqualTo("Button Two"));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tests clicking the back button while an overlay is open.
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void TestBackButtonWhenOverlayOpen()
|
||||
{
|
||||
TestScreen screen = null!;
|
||||
|
||||
PushAndConfirm(() =>
|
||||
{
|
||||
ShearedOverlayContainer overlay = new TestShearedOverlayContainer();
|
||||
|
||||
return screen = new TestScreen
|
||||
{
|
||||
Overlay = overlay,
|
||||
CreateButtons = () =>
|
||||
[
|
||||
new ScreenFooterButton(overlay)
|
||||
{
|
||||
AccentColour = Dependencies.Get<OsuColour>().Orange1,
|
||||
Icon = FontAwesome.Solid.Toolbox,
|
||||
Text = "One",
|
||||
},
|
||||
new ScreenFooterButton { Text = "Two", Action = () => { } },
|
||||
new ScreenFooterButton { Text = "Three", Action = () => { } },
|
||||
],
|
||||
};
|
||||
});
|
||||
|
||||
AddStep("show overlay", () => screen.Overlay.Show());
|
||||
AddAssert("overlay shown", () => screen.Overlay.State.Value, () => Is.EqualTo(Visibility.Visible));
|
||||
|
||||
AddStep("press back", () => screenFooter.ChildrenOfType<ScreenBackButton>().Single().TriggerClick());
|
||||
AddAssert("overlay hidden", () => screen.Overlay.State.Value, () => Is.EqualTo(Visibility.Hidden));
|
||||
AddAssert("screen still shown", () => screen.IsCurrentScreen(), () => Is.True);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tests clicking the back button on an overlay with `BackButtonPressed` being overridden.
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void TestBackButtonWithCustomBackButtonPressed()
|
||||
{
|
||||
TestScreen screen = null!;
|
||||
TestShearedOverlayContainer overlay = null!;
|
||||
|
||||
PushAndConfirm(() =>
|
||||
{
|
||||
return screen = new TestScreen
|
||||
{
|
||||
Overlay = overlay = new TestShearedOverlayContainer(),
|
||||
CreateButtons = () =>
|
||||
[
|
||||
new ScreenFooterButton(overlay)
|
||||
{
|
||||
AccentColour = Dependencies.Get<OsuColour>().Orange1,
|
||||
Icon = FontAwesome.Solid.Toolbox,
|
||||
Text = "One",
|
||||
},
|
||||
new ScreenFooterButton { Text = "Two", Action = () => { } },
|
||||
new ScreenFooterButton { Text = "Three", Action = () => { } },
|
||||
],
|
||||
};
|
||||
});
|
||||
|
||||
AddStep("show overlay", () => screen.Overlay.Show());
|
||||
AddAssert("overlay shown", () => screen.Overlay.State.Value, () => Is.EqualTo(Visibility.Visible));
|
||||
AddStep("set block count", () => overlay.BackButtonCount = 1);
|
||||
|
||||
AddStep("press back", () => screenFooter.ChildrenOfType<ScreenBackButton>().Single().TriggerClick());
|
||||
AddAssert("overlay still shown", () => screen.Overlay.State.Value, () => Is.EqualTo(Visibility.Visible));
|
||||
|
||||
AddStep("press back again", () => screenFooter.ChildrenOfType<ScreenBackButton>().Single().TriggerClick());
|
||||
AddAssert("overlay hidden", () => screen.Overlay.State.Value, () => Is.EqualTo(Visibility.Hidden));
|
||||
AddAssert("screen still shown", () => screen.IsCurrentScreen(), () => Is.True);
|
||||
}
|
||||
|
||||
private void pushSubScreenAndConfirm(Func<TestScreenWithSubScreen> target, Func<Screen> newScreen)
|
||||
{
|
||||
Screen screen = null!;
|
||||
@@ -142,39 +247,45 @@ namespace osu.Game.Tests.Visual.Navigation
|
||||
&& (previousScreen == null || previousScreen.GetChildScreen() == screen));
|
||||
}
|
||||
|
||||
private partial class TestScreenOne : OsuScreen
|
||||
{
|
||||
public override bool ShowFooter => true;
|
||||
|
||||
[Cached]
|
||||
private readonly OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Blue);
|
||||
|
||||
public override IReadOnlyList<ScreenFooterButton> CreateFooterButtons() => new[]
|
||||
{
|
||||
new ScreenFooterButton { Text = "Button One" },
|
||||
};
|
||||
}
|
||||
|
||||
private partial class TestScreenTwo : OsuScreen
|
||||
{
|
||||
public override bool ShowFooter => true;
|
||||
|
||||
[Cached]
|
||||
private readonly OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Blue);
|
||||
|
||||
public override IReadOnlyList<ScreenFooterButton> CreateFooterButtons() => new[]
|
||||
{
|
||||
new ScreenFooterButton { Text = "Button Two" },
|
||||
};
|
||||
}
|
||||
|
||||
private partial class TestScreen : OsuScreen
|
||||
{
|
||||
public override bool ShowFooter { get; }
|
||||
|
||||
public TestScreen(bool footer)
|
||||
public Func<IReadOnlyList<ScreenFooterButton>> CreateButtons = Array.Empty<ScreenFooterButton>;
|
||||
|
||||
public ShearedOverlayContainer Overlay = new TestShearedOverlayContainer();
|
||||
|
||||
private IDisposable? overlayRegistration;
|
||||
|
||||
[Cached]
|
||||
private readonly OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Blue);
|
||||
|
||||
[Resolved]
|
||||
private IOverlayManager? overlayManager { get; set; }
|
||||
|
||||
public TestScreen(bool showFooter = true)
|
||||
{
|
||||
ShowFooter = footer;
|
||||
ShowFooter = showFooter;
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
LoadComponent(Overlay);
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
overlayRegistration = overlayManager?.RegisterBlockingOverlay(Overlay);
|
||||
}
|
||||
|
||||
public override IReadOnlyList<ScreenFooterButton> CreateFooterButtons() => CreateButtons.Invoke();
|
||||
|
||||
protected override void Dispose(bool isDisposing)
|
||||
{
|
||||
base.Dispose(isDisposing);
|
||||
overlayRegistration?.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -196,5 +307,66 @@ namespace osu.Game.Tests.Visual.Navigation
|
||||
|
||||
public void ExitSubScreen() => SubScreenStack.Exit();
|
||||
}
|
||||
|
||||
private partial class TestShearedOverlayContainer : ShearedOverlayContainer
|
||||
{
|
||||
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 VisibilityContainer CreateFooterContent() => new TestFooterContent();
|
||||
|
||||
public partial class TestFooterContent : VisibilityContainer
|
||||
{
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
AutoSizeAxes = 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -72,6 +72,10 @@ namespace osu.Game.Tests.Visual.Online
|
||||
Preview = @"https://b.ppy.sh/preview/12345.mp3",
|
||||
PlayCount = 123,
|
||||
FavouriteCount = 456,
|
||||
NominationStatus = new BeatmapSetNominationStatus
|
||||
{
|
||||
Current = 2,
|
||||
},
|
||||
Submitted = DateTime.Now,
|
||||
Ranked = DateTime.Now,
|
||||
BPM = 111,
|
||||
|
||||
@@ -17,13 +17,13 @@ namespace osu.Game.Tests.Visual.Playlists
|
||||
[Cached]
|
||||
private OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Aquamarine);
|
||||
|
||||
private readonly FooterButtonFreeModsV2 button;
|
||||
private readonly FooterButtonFreeMods button;
|
||||
|
||||
public TestSceneFooterButtonFreeModsV2()
|
||||
{
|
||||
ModSelectOverlay modSelectOverlay;
|
||||
Add(modSelectOverlay = new TestModSelectOverlay());
|
||||
Add(button = new FooterButtonFreeModsV2(modSelectOverlay)
|
||||
Add(button = new FooterButtonFreeMods(modSelectOverlay)
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.CentreLeft,
|
||||
|
||||
@@ -15,7 +15,7 @@ namespace osu.Game.Tests.Visual.Playlists
|
||||
|
||||
public TestSceneFooterButtonFreestyleV2()
|
||||
{
|
||||
Add(new FooterButtonFreestyleV2
|
||||
Add(new FooterButtonFreestyle
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.CentreLeft,
|
||||
|
||||
@@ -30,7 +30,6 @@ using osu.Game.Rulesets.Osu.Mods;
|
||||
using osu.Game.Rulesets.Taiko;
|
||||
using osu.Game.Rulesets.Taiko.Mods;
|
||||
using osu.Game.Screens;
|
||||
using osu.Game.Screens.Footer;
|
||||
using osu.Game.Screens.OnlinePlay;
|
||||
using osu.Game.Screens.OnlinePlay.Playlists;
|
||||
using osu.Game.Tests.Resources;
|
||||
@@ -676,19 +675,9 @@ namespace osu.Game.Tests.Visual.Playlists
|
||||
|
||||
public TestPlaylistsScreen(PlaylistsRoomSubScreen screen)
|
||||
{
|
||||
ScreenFooter footer;
|
||||
InternalChild = new DependencyProvidingContainer
|
||||
InternalChild = Stack = new OnlinePlaySubScreenStack
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
Stack = new OnlinePlaySubScreenStack
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both
|
||||
},
|
||||
footer = new ScreenFooter(),
|
||||
},
|
||||
CachedDependencies = new (Type, object)[] { (typeof(ScreenFooter), footer) },
|
||||
RelativeSizeAxes = Axes.Both
|
||||
};
|
||||
|
||||
Stack.Push(screen);
|
||||
|
||||
@@ -1,301 +0,0 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using 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;
|
||||
using osu.Game.Screens.SelectV2;
|
||||
|
||||
namespace osu.Game.Tests.Visual.SongSelectV2
|
||||
{
|
||||
public partial class TestSceneScreenFooter : OsuManualInputManagerTestScene
|
||||
{
|
||||
private DependencyProvidingContainer contentContainer = null!;
|
||||
private ScreenFooter screenFooter = null!;
|
||||
private UserModSelectOverlay modOverlay = null!;
|
||||
|
||||
[SetUp]
|
||||
public void SetUp() => Schedule(() =>
|
||||
{
|
||||
screenFooter = new ScreenFooter();
|
||||
|
||||
Child = contentContainer = new DependencyProvidingContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
CachedDependencies = new (Type, object)[]
|
||||
{
|
||||
(typeof(ScreenFooter), screenFooter)
|
||||
},
|
||||
Children = new Drawable[]
|
||||
{
|
||||
modOverlay = new UserModSelectOverlay { ShowPresets = true },
|
||||
new PopoverContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Depth = float.MinValue,
|
||||
Child = screenFooter,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
screenFooter.SetButtons(new ScreenFooterButton[]
|
||||
{
|
||||
new FooterButtonMods(modOverlay) { Current = SelectedMods },
|
||||
new FooterButtonRandom(),
|
||||
new FooterButtonOptions(),
|
||||
});
|
||||
});
|
||||
|
||||
[SetUpSteps]
|
||||
public void SetUpSteps()
|
||||
{
|
||||
AddStep("show footer", () => screenFooter.Show());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Transition when moving from a screen with no buttons to a screen with buttons.
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void TestButtonsIn()
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Transition when moving from a screen with buttons to a screen with no buttons.
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void TestButtonsOut()
|
||||
{
|
||||
AddStep("clear buttons", () => screenFooter.SetButtons(Array.Empty<ScreenFooterButton>()));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Transition when moving from a screen with buttons to a screen with buttons.
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void TestReplaceButtons()
|
||||
{
|
||||
AddStep("replace buttons", () => screenFooter.SetButtons(new[]
|
||||
{
|
||||
new ScreenFooterButton { Text = "One", Action = () => { } },
|
||||
new ScreenFooterButton { Text = "Two", Action = () => { } },
|
||||
new ScreenFooterButton { Text = "Three", Action = () => { } },
|
||||
}));
|
||||
}
|
||||
|
||||
[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());
|
||||
contentDisplayed();
|
||||
AddUntilStep("other buttons hidden", () => screenFooter.ChildrenOfType<ScreenFooterButton>().Skip(1).All(b => b.Child.Parent!.Y > 0));
|
||||
|
||||
AddStep("hide overlay", () => externalOverlay.Hide());
|
||||
contentHidden();
|
||||
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);
|
||||
contentDisplayed();
|
||||
|
||||
AddStep("hide external overlay", () => externalOverlay.Hide());
|
||||
AddAssert("footer hidden", () => screenFooter.State.Value == Visibility.Hidden);
|
||||
contentHidden();
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
[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);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestButtonResizedAfterFooterIsDisplayed()
|
||||
{
|
||||
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());
|
||||
contentDisplayed();
|
||||
AddUntilStep("other buttons hidden", () => screenFooter.ChildrenOfType<ScreenFooterButton>().Skip(1).All(b => b.Child.Parent!.Y > 0));
|
||||
|
||||
AddStep("resize active button", () => this.ChildrenOfType<ScreenFooterButton>().First().ResizeWidthTo(240, 300, Easing.OutQuint));
|
||||
AddStep("resize active button back", () => this.ChildrenOfType<ScreenFooterButton>().First().ResizeWidthTo(116, 300, Easing.OutQuint));
|
||||
|
||||
AddStep("hide overlay", () => externalOverlay.Hide());
|
||||
contentHidden();
|
||||
AddUntilStep("other buttons returned", () => screenFooter.ChildrenOfType<ScreenFooterButton>().Skip(1).All(b => b.ChildrenOfType<Container>().First().Y == 0));
|
||||
}
|
||||
|
||||
private void contentHidden()
|
||||
{
|
||||
AddUntilStep("content hidden from footer", () => screenFooter.ChildrenOfType<TestShearedOverlayContainer.TestFooterContent>().SingleOrDefault()?.IsPresent != true);
|
||||
}
|
||||
|
||||
private void contentDisplayed()
|
||||
{
|
||||
AddUntilStep("content displayed in footer", () => screenFooter.ChildrenOfType<TestShearedOverlayContainer.TestFooterContent>().Single().IsPresent);
|
||||
}
|
||||
|
||||
private partial class TestShearedOverlayContainer : ShearedOverlayContainer
|
||||
{
|
||||
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 VisibilityContainer CreateFooterContent() => new TestFooterContent();
|
||||
|
||||
public partial class TestFooterContent : VisibilityContainer
|
||||
{
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
AutoSizeAxes = 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -12,7 +12,6 @@ using NUnit.Framework;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Extensions.ObjectExtensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Screens;
|
||||
using osu.Framework.Testing;
|
||||
@@ -23,17 +22,16 @@ using osu.Game.Overlays;
|
||||
using osu.Game.Overlays.FirstRunSetup;
|
||||
using osu.Game.Overlays.Notifications;
|
||||
using osu.Game.Screens;
|
||||
using osu.Game.Screens.Footer;
|
||||
using osu.Game.Tests.Beatmaps;
|
||||
using osuTK;
|
||||
using osuTK.Input;
|
||||
|
||||
namespace osu.Game.Tests.Visual.UserInterface
|
||||
{
|
||||
public partial class TestSceneFirstRunSetupOverlay : OsuManualInputManagerTestScene
|
||||
public partial class TestSceneFirstRunSetupOverlay : ScreenTestScene
|
||||
{
|
||||
private FirstRunSetupOverlay overlay;
|
||||
private ScreenFooter footer;
|
||||
private TestFirstRunSetupOverlayScreen screen = null!;
|
||||
private FirstRunSetupOverlay overlay => screen.Overlay;
|
||||
|
||||
private readonly Mock<TestPerformerFromScreenRunner> performer = new Mock<TestPerformerFromScreenRunner>();
|
||||
|
||||
@@ -53,8 +51,10 @@ namespace osu.Game.Tests.Visual.UserInterface
|
||||
}
|
||||
|
||||
[SetUpSteps]
|
||||
public void SetUpSteps()
|
||||
public override void SetUpSteps()
|
||||
{
|
||||
base.SetUpSteps();
|
||||
|
||||
AddStep("setup dependencies", () =>
|
||||
{
|
||||
performer.Reset();
|
||||
@@ -67,16 +67,16 @@ namespace osu.Game.Tests.Visual.UserInterface
|
||||
.Callback((Notification n) => lastNotification = n);
|
||||
});
|
||||
|
||||
createOverlay();
|
||||
AddStep("reset first run", () => LocalConfig.SetValue(OsuSetting.ShowFirstRunSetup, true));
|
||||
|
||||
AddStep("show overlay", () => overlay.Show());
|
||||
createScreen();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestBasic()
|
||||
{
|
||||
AddAssert("overlay visible", () => overlay.State.Value == Visibility.Visible);
|
||||
AddAssert("footer visible", () => footer.State.Value == Visibility.Visible);
|
||||
AddAssert("footer visible", () => ScreenFooter.State.Value == Visibility.Visible);
|
||||
}
|
||||
|
||||
[Test]
|
||||
@@ -92,7 +92,8 @@ namespace osu.Game.Tests.Visual.UserInterface
|
||||
|
||||
AddAssert("first run false", () => !LocalConfig.Get<bool>(OsuSetting.ShowFirstRunSetup));
|
||||
|
||||
createOverlay();
|
||||
AddStep("exit screen", () => Stack.Exit());
|
||||
createScreen();
|
||||
|
||||
AddWaitStep("wait some", 5);
|
||||
|
||||
@@ -146,7 +147,7 @@ namespace osu.Game.Tests.Visual.UserInterface
|
||||
if (keyboard)
|
||||
InputManager.Key(Key.Escape);
|
||||
else
|
||||
footer.BackButton.TriggerClick();
|
||||
ScreenFooter.BackButton.TriggerClick();
|
||||
}
|
||||
|
||||
return overlay.CurrentScreen is ScreenWelcome;
|
||||
@@ -161,7 +162,7 @@ namespace osu.Game.Tests.Visual.UserInterface
|
||||
}
|
||||
else
|
||||
{
|
||||
AddStep("press back button", () => footer.BackButton.TriggerClick());
|
||||
AddStep("press back button", () => ScreenFooter.BackButton.TriggerClick());
|
||||
AddAssert("overlay dismissed", () => overlay.State.Value == Visibility.Hidden);
|
||||
}
|
||||
}
|
||||
@@ -204,25 +205,45 @@ namespace osu.Game.Tests.Visual.UserInterface
|
||||
AddAssert("is resumed", () => overlay.CurrentScreen is ScreenUIScale);
|
||||
}
|
||||
|
||||
private void createOverlay()
|
||||
private void createScreen()
|
||||
{
|
||||
AddStep("add overlay", () =>
|
||||
{
|
||||
var receptor = new ScreenFooter.BackReceptor();
|
||||
footer = new ScreenFooter(receptor);
|
||||
AddStep("push screen", () => LoadScreen(screen = new TestFirstRunSetupOverlayScreen()));
|
||||
AddUntilStep("wait until screen is loaded", () => screen.IsLoaded, () => Is.True);
|
||||
}
|
||||
|
||||
Child = new DependencyProvidingContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
CachedDependencies = new[] { (typeof(ScreenFooter), (object)footer) },
|
||||
Children = new Drawable[]
|
||||
{
|
||||
receptor,
|
||||
overlay = new FirstRunSetupOverlay(),
|
||||
footer,
|
||||
}
|
||||
};
|
||||
});
|
||||
private partial class TestFirstRunSetupOverlayScreen : OsuScreen
|
||||
{
|
||||
public override bool ShowFooter => true;
|
||||
|
||||
public FirstRunSetupOverlay Overlay = null!;
|
||||
|
||||
[CanBeNull]
|
||||
private IDisposable overlayRegistration;
|
||||
|
||||
[CanBeNull]
|
||||
[Resolved]
|
||||
private IOverlayManager overlayManager { get; set; }
|
||||
|
||||
[Cached]
|
||||
private OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Blue);
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
LoadComponent(Overlay = new FirstRunSetupOverlay());
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
overlayRegistration = overlayManager?.RegisterBlockingOverlay(Overlay);
|
||||
}
|
||||
|
||||
protected override void Dispose(bool isDisposing)
|
||||
{
|
||||
base.Dispose(isDisposing);
|
||||
overlayRegistration?.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
// interface mocks break hot reload, mocking this stub implementation instead works around it.
|
||||
|
||||
@@ -12,6 +12,7 @@ using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Input;
|
||||
using osu.Framework.Localisation;
|
||||
using osu.Framework.Screens;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Framework.Utils;
|
||||
using osu.Game.Configuration;
|
||||
@@ -25,6 +26,7 @@ using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Rulesets.Osu;
|
||||
using osu.Game.Rulesets.Osu.Mods;
|
||||
using osu.Game.Rulesets.Taiko.Mods;
|
||||
using osu.Game.Screens;
|
||||
using osu.Game.Screens.Footer;
|
||||
using osu.Game.Tests.Mods;
|
||||
using osuTK;
|
||||
@@ -33,17 +35,19 @@ using osuTK.Input;
|
||||
namespace osu.Game.Tests.Visual.UserInterface
|
||||
{
|
||||
[TestFixture]
|
||||
public partial class TestSceneModSelectOverlay : OsuManualInputManagerTestScene
|
||||
public partial class TestSceneModSelectOverlay : ScreenTestScene
|
||||
{
|
||||
protected override bool UseFreshStoragePerRun => true;
|
||||
|
||||
private RulesetStore rulesetStore = null!;
|
||||
|
||||
private TestModSelectOverlay modSelectOverlay = null!;
|
||||
private TestModSelectOverlayScreen screen = null!;
|
||||
|
||||
[Resolved]
|
||||
private OsuConfigManager configManager { get; set; } = null!;
|
||||
|
||||
private ModSelectOverlay modSelectOverlay => screen.Overlay;
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
@@ -52,9 +56,10 @@ namespace osu.Game.Tests.Visual.UserInterface
|
||||
}
|
||||
|
||||
[SetUpSteps]
|
||||
public void SetUpSteps()
|
||||
public override void SetUpSteps()
|
||||
{
|
||||
AddStep("clear contents", Clear);
|
||||
base.SetUpSteps();
|
||||
|
||||
AddStep("reset ruleset", () => Ruleset.Value = rulesetStore.GetRuleset(0));
|
||||
AddStep("reset mods", () => SelectedMods.SetDefault());
|
||||
AddStep("reset config", () => configManager.SetValue(OsuSetting.ModSelectTextSearchStartsActive, true));
|
||||
@@ -97,29 +102,8 @@ namespace osu.Game.Tests.Visual.UserInterface
|
||||
|
||||
private void createScreen()
|
||||
{
|
||||
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,
|
||||
State = { Value = Visibility.Visible },
|
||||
Beatmap = { Value = Beatmap.Value },
|
||||
SelectedMods = { BindTarget = SelectedMods },
|
||||
},
|
||||
footer,
|
||||
}
|
||||
};
|
||||
});
|
||||
AddStep("create screen", () => LoadScreen(screen = new TestModSelectOverlayScreen { SelectedMods = { BindTarget = SelectedMods } }));
|
||||
AddUntilStep("wait until screen is loaded", () => screen.IsLoaded, () => Is.True);
|
||||
waitForColumnLoad();
|
||||
}
|
||||
|
||||
@@ -306,29 +290,30 @@ namespace osu.Game.Tests.Visual.UserInterface
|
||||
[Test]
|
||||
public void TestSettingsNotCrossPolluting()
|
||||
{
|
||||
TestScreenWithTwoOverlays screenWithTwoOverlays = null!;
|
||||
Bindable<IReadOnlyList<Mod>> selectedMods2 = null!;
|
||||
ModSelectOverlay modSelectOverlay2 = null!;
|
||||
|
||||
createScreen();
|
||||
AddStep("push screen", () =>
|
||||
{
|
||||
selectedMods2 = new Bindable<IReadOnlyList<Mod>>(new Mod[] { new OsuModDifficultyAdjust() });
|
||||
|
||||
LoadScreen(screen = screenWithTwoOverlays = new TestScreenWithTwoOverlays
|
||||
{
|
||||
SelectedMods = { BindTarget = SelectedMods },
|
||||
SelectedMods2 = { BindTarget = selectedMods2 },
|
||||
});
|
||||
});
|
||||
AddStep("wait until screen is loaded", () => screenWithTwoOverlays.IsCurrentScreen());
|
||||
waitForColumnLoad();
|
||||
|
||||
AddStep("select difficulty adjust via panel", () => getPanelForMod(typeof(OsuModDifficultyAdjust)).TriggerClick());
|
||||
|
||||
AddStep("set setting", () => modSelectOverlay.ChildrenOfType<RoundedSliderBar<float>>().First().Current.Value = 8);
|
||||
AddStep("set setting", () => screenWithTwoOverlays.Overlay.ChildrenOfType<RoundedSliderBar<float>>().First().Current.Value = 8);
|
||||
|
||||
AddAssert("ensure setting is propagated", () => SelectedMods.Value.OfType<OsuModDifficultyAdjust>().Single().CircleSize.Value == 8);
|
||||
|
||||
AddStep("create second bindable", () => selectedMods2 = new Bindable<IReadOnlyList<Mod>>(new Mod[] { new OsuModDifficultyAdjust() }));
|
||||
|
||||
AddStep("create second overlay", () =>
|
||||
{
|
||||
Add(modSelectOverlay2 = new UserModSelectOverlay().With(d =>
|
||||
{
|
||||
d.Origin = Anchor.TopCentre;
|
||||
d.Anchor = Anchor.TopCentre;
|
||||
d.SelectedMods.BindTarget = selectedMods2;
|
||||
}));
|
||||
});
|
||||
|
||||
AddStep("show", () => modSelectOverlay2.Show());
|
||||
AddStep("hide first overlay", () => screenWithTwoOverlays.Overlay.Hide());
|
||||
AddStep("show second overlay", () => screenWithTwoOverlays.SecondOverlay.Show());
|
||||
|
||||
AddAssert("ensure first is unchanged", () => SelectedMods.Value.OfType<OsuModDifficultyAdjust>().Single().CircleSize.Value == 8);
|
||||
AddAssert("ensure second is default", () => selectedMods2.Value.OfType<OsuModDifficultyAdjust>().Single().CircleSize.Value == null);
|
||||
@@ -481,6 +466,7 @@ namespace osu.Game.Tests.Visual.UserInterface
|
||||
AddStep("set customized mod externally", () => SelectedMods.Value = new[] { new OsuModDoubleTime { SpeedChange = { Value = 1.01 } } });
|
||||
AddAssert("setting remains", () => (SelectedMods.Value.SingleOrDefault() as OsuModDoubleTime)?.SpeedChange.Value == 1.01);
|
||||
|
||||
AddStep("exit screen", () => Stack.Exit());
|
||||
createScreen();
|
||||
AddAssert("setting remains", () => (SelectedMods.Value.SingleOrDefault() as OsuModDoubleTime)?.SpeedChange.Value == 1.01);
|
||||
}
|
||||
@@ -797,16 +783,11 @@ namespace osu.Game.Tests.Visual.UserInterface
|
||||
[Test]
|
||||
public void TestColumnHidingOnIsValidChange()
|
||||
{
|
||||
AddStep("create screen", () => Child = modSelectOverlay = new TestModSelectOverlay
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
State = { Value = Visibility.Visible },
|
||||
SelectedMods = { BindTarget = SelectedMods },
|
||||
IsValidMod = mod => mod.Type == ModType.DifficultyIncrease || mod.Type == ModType.Conversion
|
||||
});
|
||||
waitForColumnLoad();
|
||||
createScreen();
|
||||
changeRuleset(0);
|
||||
|
||||
AddStep("set filter for 2 columns", () => modSelectOverlay.IsValidMod = mod => mod.Type is ModType.DifficultyIncrease or ModType.Conversion);
|
||||
|
||||
AddAssert("two columns visible", () => this.ChildrenOfType<ModColumn>().Count(col => col.IsPresent) == 2);
|
||||
|
||||
AddStep("unset filter", () => modSelectOverlay.IsValidMod = _ => true);
|
||||
@@ -816,9 +797,7 @@ namespace osu.Game.Tests.Visual.UserInterface
|
||||
AddAssert("no columns visible", () => this.ChildrenOfType<ModColumn>().All(col => !col.IsPresent));
|
||||
|
||||
AddStep("hide", () => modSelectOverlay.Hide());
|
||||
AddStep("set filter for 3 columns", () => modSelectOverlay.IsValidMod = mod => mod.Type == ModType.DifficultyReduction
|
||||
|| mod.Type == ModType.Automation
|
||||
|| mod.Type == ModType.Conversion);
|
||||
AddStep("set filter for 3 columns", () => modSelectOverlay.IsValidMod = mod => mod.Type is ModType.DifficultyReduction or ModType.Automation or ModType.Conversion);
|
||||
|
||||
AddStep("show", () => modSelectOverlay.Show());
|
||||
AddUntilStep("3 columns visible", () => this.ChildrenOfType<ModColumn>().Count(col => col.IsPresent) == 3);
|
||||
@@ -830,13 +809,7 @@ namespace osu.Game.Tests.Visual.UserInterface
|
||||
[Test]
|
||||
public void TestColumnHidingOnTextFilterChange()
|
||||
{
|
||||
AddStep("create screen", () => Child = modSelectOverlay = new TestModSelectOverlay
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
State = { Value = Visibility.Visible },
|
||||
SelectedMods = { BindTarget = SelectedMods }
|
||||
});
|
||||
waitForColumnLoad();
|
||||
createScreen();
|
||||
changeRuleset(0);
|
||||
|
||||
AddAssert("all columns visible", () => this.ChildrenOfType<ModColumn>().All(col => col.IsPresent));
|
||||
@@ -854,13 +827,7 @@ namespace osu.Game.Tests.Visual.UserInterface
|
||||
[Test]
|
||||
public void TestHidingOverlayClearsTextSearch()
|
||||
{
|
||||
AddStep("create screen", () => Child = modSelectOverlay = new TestModSelectOverlay
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
State = { Value = Visibility.Visible },
|
||||
SelectedMods = { BindTarget = SelectedMods }
|
||||
});
|
||||
waitForColumnLoad();
|
||||
createScreen();
|
||||
changeRuleset(0);
|
||||
|
||||
AddAssert("all columns visible", () => this.ChildrenOfType<ModColumn>().All(col => col.IsPresent));
|
||||
@@ -1019,8 +986,8 @@ namespace osu.Game.Tests.Visual.UserInterface
|
||||
{
|
||||
selectedMods = new Bindable<IReadOnlyList<Mod>>([]);
|
||||
|
||||
modSelectOverlay.SelectedMods.UnbindFrom(SelectedMods);
|
||||
modSelectOverlay.SelectedMods.BindTo(selectedMods);
|
||||
screen.SelectedMods.UnbindFrom(SelectedMods);
|
||||
screen.SelectedMods.BindTo(selectedMods);
|
||||
});
|
||||
|
||||
AddStep("activate PF", () => selectedMods.Value = [new OsuModPerfect()]);
|
||||
@@ -1066,11 +1033,79 @@ namespace osu.Game.Tests.Visual.UserInterface
|
||||
rulesetStore.Dispose();
|
||||
}
|
||||
|
||||
private partial class TestModSelectOverlay : UserModSelectOverlay
|
||||
private partial class TestModSelectOverlayScreen : OsuScreen
|
||||
{
|
||||
public TestModSelectOverlay()
|
||||
public readonly Bindable<IReadOnlyList<Mod>> SelectedMods = new Bindable<IReadOnlyList<Mod>>();
|
||||
|
||||
public override bool ShowFooter => true;
|
||||
|
||||
public ModSelectOverlay Overlay = null!;
|
||||
|
||||
private IDisposable? firstOverlayRegistration;
|
||||
|
||||
[Cached]
|
||||
private OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Blue);
|
||||
|
||||
[Resolved]
|
||||
protected IOverlayManager? OverlayManager { get; private set; }
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
ShowPresets = true;
|
||||
LoadComponent(Overlay = new UserModSelectOverlay
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
State = { Value = Visibility.Visible },
|
||||
Beatmap = { Value = Beatmap.Value },
|
||||
SelectedMods = { BindTarget = SelectedMods },
|
||||
ShowPresets = true,
|
||||
});
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
||||
firstOverlayRegistration = OverlayManager?.RegisterBlockingOverlay(Overlay);
|
||||
}
|
||||
|
||||
protected override void Dispose(bool isDisposing)
|
||||
{
|
||||
base.Dispose(isDisposing);
|
||||
firstOverlayRegistration?.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
private partial class TestScreenWithTwoOverlays : TestModSelectOverlayScreen
|
||||
{
|
||||
public readonly Bindable<IReadOnlyList<Mod>> SelectedMods2 = new Bindable<IReadOnlyList<Mod>>([]);
|
||||
|
||||
public ModSelectOverlay SecondOverlay = null!;
|
||||
|
||||
private IDisposable? secondOverlayRegistration;
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
LoadComponent(SecondOverlay = new UserModSelectOverlay
|
||||
{
|
||||
Origin = Anchor.TopCentre,
|
||||
Anchor = Anchor.TopCentre,
|
||||
SelectedMods = { BindTarget = SelectedMods2 },
|
||||
});
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
||||
secondOverlayRegistration = OverlayManager?.RegisterBlockingOverlay(SecondOverlay);
|
||||
}
|
||||
|
||||
protected override void Dispose(bool isDisposing)
|
||||
{
|
||||
base.Dispose(isDisposing);
|
||||
secondOverlayRegistration?.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
365
osu.Game.Tests/Visual/UserInterface/TestSceneScreenFooter.cs
Normal file
365
osu.Game.Tests/Visual/UserInterface/TestSceneScreenFooter.cs
Normal file
@@ -0,0 +1,365 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using osu.Framework.Screens;
|
||||
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;
|
||||
using osu.Game.Screens.Footer;
|
||||
using osu.Game.Screens.SelectV2;
|
||||
|
||||
namespace osu.Game.Tests.Visual.UserInterface
|
||||
{
|
||||
public partial class TestSceneScreenFooter : ScreenTestScene
|
||||
{
|
||||
[Test]
|
||||
public void TestButtonsIn()
|
||||
{
|
||||
AddStep("push empty screen", () => LoadScreen(new TestScreen()));
|
||||
AddStep("push screen", () => LoadScreen(new TestScreen
|
||||
{
|
||||
CreateButtons = () => new[]
|
||||
{
|
||||
new ScreenFooterButton { Text = "Button 1", Action = () => { } },
|
||||
new ScreenFooterButton { Text = "Button 2", Action = () => { } },
|
||||
new ScreenFooterButton { Text = "Button 3", Action = () => { } },
|
||||
},
|
||||
}));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestButtonsOut()
|
||||
{
|
||||
AddStep("push empty screen", () => LoadScreen(new TestScreen()));
|
||||
AddStep("push screen", () => LoadScreen(new TestScreen
|
||||
{
|
||||
CreateButtons = () => new[]
|
||||
{
|
||||
new ScreenFooterButton { Text = "Button 1", Action = () => { } },
|
||||
new ScreenFooterButton { Text = "Button 2", Action = () => { } },
|
||||
new ScreenFooterButton { Text = "Button 3", Action = () => { } },
|
||||
},
|
||||
}));
|
||||
AddStep("exit screen", () => Stack.Exit());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestReplaceButtons()
|
||||
{
|
||||
AddStep("push first screen", () => LoadScreen(new TestScreen
|
||||
{
|
||||
CreateButtons = () => new[]
|
||||
{
|
||||
new ScreenFooterButton { Text = "Button 1", Action = () => { } },
|
||||
new ScreenFooterButton { Text = "Button 2", Action = () => { } },
|
||||
new ScreenFooterButton { Text = "Button 3", Action = () => { } },
|
||||
},
|
||||
}));
|
||||
AddStep("push second screen", () => LoadScreen(new TestScreen
|
||||
{
|
||||
CreateButtons = () => new[]
|
||||
{
|
||||
new ScreenFooterButton { Text = "Button 4", Action = () => { } },
|
||||
new ScreenFooterButton { Text = "Button 5", Action = () => { } },
|
||||
new ScreenFooterButton { Text = "Button 6", Action = () => { } },
|
||||
},
|
||||
}));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestFooterVisibility()
|
||||
{
|
||||
TestScreen screen = null!;
|
||||
TestScreen screenWithoutFooter = null!;
|
||||
|
||||
AddAssert("footer hidden", () => ScreenFooter.State.Value, () => Is.EqualTo(Visibility.Hidden));
|
||||
|
||||
AddStep("push screen", () => LoadScreen(screen = new TestScreen
|
||||
{
|
||||
CreateButtons = () => new[]
|
||||
{
|
||||
new ScreenFooterButton { Text = "Button 1", Action = () => { } },
|
||||
new ScreenFooterButton { Text = "Button 2", Action = () => { } },
|
||||
new ScreenFooterButton { Text = "Button 3", Action = () => { } },
|
||||
},
|
||||
}));
|
||||
AddUntilStep("wait until screen is loaded", () => screen.IsCurrentScreen(), () => Is.True);
|
||||
AddAssert("footer shown", () => ScreenFooter.State.Value, () => Is.EqualTo(Visibility.Visible));
|
||||
|
||||
AddStep("push screen with no footer", () => LoadScreen(screenWithoutFooter = new TestScreen(showFooter: false)));
|
||||
AddUntilStep("wait until screen is loaded", () => screenWithoutFooter.IsCurrentScreen(), () => Is.True);
|
||||
AddAssert("footer hidden", () => ScreenFooter.State.Value, () => Is.EqualTo(Visibility.Hidden));
|
||||
|
||||
AddStep("exit screen", () => Stack.Exit());
|
||||
AddUntilStep("wait until screen is loaded", () => screen.IsCurrentScreen(), () => Is.True);
|
||||
AddAssert("footer shown", () => ScreenFooter.State.Value, () => Is.EqualTo(Visibility.Visible));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestExternalOverlayContent()
|
||||
{
|
||||
TestScreen screen = null!;
|
||||
|
||||
AddStep("push screen", () =>
|
||||
{
|
||||
ShearedOverlayContainer overlay = new TestShearedOverlayContainer();
|
||||
|
||||
LoadScreen(screen = new TestScreen
|
||||
{
|
||||
Overlay = overlay,
|
||||
CreateButtons = () => new[]
|
||||
{
|
||||
new ScreenFooterButton(overlay)
|
||||
{
|
||||
AccentColour = Dependencies.Get<OsuColour>().Orange1,
|
||||
Icon = FontAwesome.Solid.Toolbox,
|
||||
Text = "One",
|
||||
},
|
||||
new ScreenFooterButton { Text = "Two", Action = () => { } },
|
||||
new ScreenFooterButton { Text = "Three", Action = () => { } },
|
||||
},
|
||||
});
|
||||
});
|
||||
AddUntilStep("wait until screen is loaded", () => screen.IsCurrentScreen(), () => Is.True);
|
||||
|
||||
AddStep("show overlay", () => screen.Overlay.Show());
|
||||
contentDisplayed();
|
||||
AddAssert("other buttons hidden", () => ScreenFooter.ChildrenOfType<ScreenFooterButton>().Skip(1).All(b => b.Child.Parent!.Y > 0));
|
||||
|
||||
AddStep("hide overlay", () => screen.Overlay.Hide());
|
||||
contentHidden();
|
||||
AddAssert("other buttons returned", () => ScreenFooter.ChildrenOfType<ScreenFooterButton>().Skip(1).All(b => b.ChildrenOfType<Container>().First().Y == 0));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestTemporarilyShowFooter()
|
||||
{
|
||||
TestScreen screen = null!;
|
||||
|
||||
AddStep("push screen", () => LoadScreen(screen = new TestScreen(showFooter: false)));
|
||||
AddUntilStep("wait until screen is loaded", () => screen.IsCurrentScreen(), () => Is.True);
|
||||
AddAssert("footer hidden", () => ScreenFooter.State.Value, () => Is.EqualTo(Visibility.Hidden));
|
||||
|
||||
AddStep("show overlay", () => screen.Overlay.Show());
|
||||
AddAssert("footer shown", () => ScreenFooter.State.Value, () => Is.EqualTo(Visibility.Visible));
|
||||
contentDisplayed();
|
||||
|
||||
AddStep("hide overlay", () => screen.Overlay.Hide());
|
||||
AddAssert("footer hidden", () => ScreenFooter.State.Value, () => Is.EqualTo(Visibility.Hidden));
|
||||
contentHidden();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestShowOverlayHidesOtherOverlays()
|
||||
{
|
||||
TestScreen screen = null!;
|
||||
|
||||
AddStep("push screen", () =>
|
||||
{
|
||||
ShearedOverlayContainer overlay = new TestShearedOverlayContainer();
|
||||
ModSelectOverlay secondOverlay = new ModSelectOverlay();
|
||||
|
||||
LoadScreen(screen = new TestScreen
|
||||
{
|
||||
Overlay = overlay,
|
||||
SecondOverlay = secondOverlay,
|
||||
CreateButtons = () => new[]
|
||||
{
|
||||
new ScreenFooterButton(overlay)
|
||||
{
|
||||
AccentColour = Dependencies.Get<OsuColour>().Orange1,
|
||||
Icon = FontAwesome.Solid.Toolbox,
|
||||
Text = "One",
|
||||
},
|
||||
new FooterButtonMods(secondOverlay),
|
||||
new ScreenFooterButton { Text = "Two", Action = () => { } },
|
||||
new ScreenFooterButton { Text = "Three", Action = () => { } },
|
||||
},
|
||||
});
|
||||
});
|
||||
AddUntilStep("wait until screen is loaded", () => screen.IsCurrentScreen(), () => Is.True);
|
||||
|
||||
AddStep("show mods overlay", () => ScreenFooter.ChildrenOfType<FooterButtonMods>().First().TriggerClick());
|
||||
AddUntilStep("wait until overlay is shown", () => screen.SecondOverlay.State.Value, () => Is.EqualTo(Visibility.Visible));
|
||||
AddAssert("first button still visible", () => ScreenFooter.ChildrenOfType<ScreenFooterButton>().First(b => b.Text == "One").Y, () => Is.EqualTo(0));
|
||||
|
||||
AddStep("show test overlay", () => ScreenFooter.ChildrenOfType<ScreenFooterButton>().First(b => b.Text == "One").TriggerClick());
|
||||
AddUntilStep("wait until overlay is shown", () => screen.Overlay.State.Value, () => Is.EqualTo(Visibility.Visible));
|
||||
AddAssert("mod overlay is hidden", () => screen.SecondOverlay.State.Value, () => Is.EqualTo(Visibility.Hidden));
|
||||
|
||||
AddStep("hide test overlay", () => screen.Overlay.Hide());
|
||||
contentHidden();
|
||||
AddAssert("other buttons returned", () => ScreenFooter.ChildrenOfType<ScreenFooterButton>().Skip(1).All(b => b.ChildrenOfType<Container>().First().Y == 0));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestButtonResizedAfterFooterIsDisplayed()
|
||||
{
|
||||
TestScreen screen = null!;
|
||||
|
||||
const float initial_width = 116;
|
||||
const float width_increase = 124;
|
||||
|
||||
float secondButtonX = 0;
|
||||
float overlayContentX = 0;
|
||||
|
||||
AddStep("push screen", () =>
|
||||
{
|
||||
ShearedOverlayContainer overlay = new TestShearedOverlayContainer();
|
||||
|
||||
LoadScreen(screen = new TestScreen
|
||||
{
|
||||
Overlay = overlay,
|
||||
CreateButtons = () => new[]
|
||||
{
|
||||
new ScreenFooterButton(overlay)
|
||||
{
|
||||
AccentColour = Dependencies.Get<OsuColour>().Orange1,
|
||||
Icon = FontAwesome.Solid.Toolbox,
|
||||
Text = "One",
|
||||
},
|
||||
new ScreenFooterButton { Text = "Two", Action = () => { } },
|
||||
new ScreenFooterButton { Text = "Three", Action = () => { } },
|
||||
},
|
||||
});
|
||||
});
|
||||
AddUntilStep("wait until screen is loaded", () => screen.IsCurrentScreen(), () => Is.True);
|
||||
AddStep("save second button position", () => secondButtonX = ScreenFooter.ChildrenOfType<ScreenFooterButton>().ElementAt(1).X);
|
||||
|
||||
AddStep("resize active button", () => ScreenFooter.ChildrenOfType<ScreenFooterButton>().First().ResizeWidthTo(initial_width + width_increase, 300, Easing.OutQuint));
|
||||
AddUntilStep("second button moved", () => ScreenFooter.ChildrenOfType<ScreenFooterButton>().ElementAt(1).X, () => Is.EqualTo(secondButtonX + width_increase).Within(0.001));
|
||||
AddStep("resize active button back", () => this.ChildrenOfType<ScreenFooterButton>().First().ResizeWidthTo(initial_width, 300, Easing.OutQuint));
|
||||
AddUntilStep("second button moved back", () => ScreenFooter.ChildrenOfType<ScreenFooterButton>().ElementAt(1).X, () => Is.EqualTo(secondButtonX).Within(0.001));
|
||||
|
||||
AddStep("show overlay", () => screen.Overlay.Show());
|
||||
contentDisplayed();
|
||||
AddAssert("other buttons hidden", () => ScreenFooter.ChildrenOfType<ScreenFooterButton>().Skip(1).All(b => b.Child.Parent!.Y > 0));
|
||||
AddStep("save overlay content position", () => overlayContentX = ScreenFooter.ChildrenOfType<TestShearedOverlayContainer.TestFooterContent>().First().Parent!.Parent!.X);
|
||||
|
||||
AddStep("resize active button", () => ScreenFooter.ChildrenOfType<ScreenFooterButton>().First().ResizeWidthTo(initial_width + width_increase, 300, Easing.OutQuint));
|
||||
AddUntilStep("overlay content moved", () => ScreenFooter.ChildrenOfType<TestShearedOverlayContainer.TestFooterContent>().First().Parent!.Parent!.X, () => Is.EqualTo(overlayContentX + width_increase).Within(0.001));
|
||||
AddStep("resize active button back", () => this.ChildrenOfType<ScreenFooterButton>().First().ResizeWidthTo(initial_width, 300, Easing.OutQuint));
|
||||
AddUntilStep("overlay content moved back", () => ScreenFooter.ChildrenOfType<TestShearedOverlayContainer.TestFooterContent>().First().Parent!.Parent!.X, () => Is.EqualTo(overlayContentX).Within(0.001));
|
||||
|
||||
AddStep("hide overlay", () => screen.Overlay.Hide());
|
||||
contentHidden();
|
||||
AddUntilStep("other buttons returned", () => ScreenFooter.ChildrenOfType<ScreenFooterButton>().Skip(1).All(b => b.ChildrenOfType<Container>().First().Y == 0));
|
||||
}
|
||||
|
||||
private void contentHidden()
|
||||
{
|
||||
AddUntilStep("content hidden from footer", () => ScreenFooter.ChildrenOfType<TestShearedOverlayContainer.TestFooterContent>().SingleOrDefault()?.IsPresent != true);
|
||||
}
|
||||
|
||||
private void contentDisplayed()
|
||||
{
|
||||
AddUntilStep("content displayed in footer", () => ScreenFooter.ChildrenOfType<TestShearedOverlayContainer.TestFooterContent>().Single().IsPresent);
|
||||
}
|
||||
|
||||
private partial class TestScreen : OsuScreen
|
||||
{
|
||||
public override bool ShowFooter { get; }
|
||||
|
||||
public Func<IReadOnlyList<ScreenFooterButton>> CreateButtons = Array.Empty<ScreenFooterButton>;
|
||||
|
||||
public ShearedOverlayContainer Overlay = new TestShearedOverlayContainer();
|
||||
public ShearedOverlayContainer SecondOverlay = new TestShearedOverlayContainer();
|
||||
|
||||
private IDisposable? overlayRegistration;
|
||||
private IDisposable? secondOverlayRegistration;
|
||||
|
||||
[Cached]
|
||||
private readonly OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Blue);
|
||||
|
||||
[Resolved]
|
||||
private IOverlayManager? overlayManager { get; set; }
|
||||
|
||||
public TestScreen(bool showFooter = true)
|
||||
{
|
||||
ShowFooter = showFooter;
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
LoadComponent(Overlay);
|
||||
LoadComponent(SecondOverlay);
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
overlayRegistration = overlayManager?.RegisterBlockingOverlay(Overlay);
|
||||
secondOverlayRegistration = overlayManager?.RegisterBlockingOverlay(SecondOverlay);
|
||||
}
|
||||
|
||||
public override IReadOnlyList<ScreenFooterButton> CreateFooterButtons() => CreateButtons.Invoke();
|
||||
|
||||
protected override void Dispose(bool isDisposing)
|
||||
{
|
||||
base.Dispose(isDisposing);
|
||||
overlayRegistration?.Dispose();
|
||||
secondOverlayRegistration?.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
private partial class TestShearedOverlayContainer : ShearedOverlayContainer
|
||||
{
|
||||
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 override VisibilityContainer CreateFooterContent() => new TestFooterContent();
|
||||
|
||||
public partial class TestFooterContent : VisibilityContainer
|
||||
{
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
AutoSizeAxes = 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -296,20 +296,20 @@ namespace osu.Game.Beatmaps.Drawables.Cards
|
||||
return original;
|
||||
}
|
||||
|
||||
statisticsContainer.Content[0][0] = withMargin(new FavouritesStatistic(BeatmapSet)
|
||||
{
|
||||
Current = FavouriteState,
|
||||
});
|
||||
|
||||
statisticsContainer.Content[1][0] = withMargin(new PlayCountStatistic(BeatmapSet));
|
||||
|
||||
var hypesStatistic = HypesStatistic.CreateFor(BeatmapSet);
|
||||
if (hypesStatistic != null)
|
||||
statisticsContainer.Content[0][1] = withMargin(hypesStatistic);
|
||||
statisticsContainer.Content[0][0] = withMargin(hypesStatistic);
|
||||
|
||||
var nominationsStatistic = NominationsStatistic.CreateFor(BeatmapSet);
|
||||
if (nominationsStatistic != null)
|
||||
statisticsContainer.Content[1][1] = withMargin(nominationsStatistic);
|
||||
statisticsContainer.Content[1][0] = withMargin(nominationsStatistic);
|
||||
|
||||
statisticsContainer.Content[0][1] = withMargin(new PlayCountStatistic(BeatmapSet));
|
||||
|
||||
statisticsContainer.Content[1][1] = withMargin(new FavouritesStatistic(BeatmapSet)
|
||||
{
|
||||
Current = FavouriteState,
|
||||
});
|
||||
|
||||
var dateStatistic = BeatmapCardDateStatistic.CreateFor(BeatmapSet);
|
||||
if (dateStatistic != null)
|
||||
|
||||
@@ -278,8 +278,8 @@ namespace osu.Game.Beatmaps.Drawables.Cards
|
||||
if (nominationsStatistic != null)
|
||||
yield return nominationsStatistic;
|
||||
|
||||
yield return new FavouritesStatistic(BeatmapSet) { Current = FavouriteState };
|
||||
yield return new PlayCountStatistic(BeatmapSet);
|
||||
yield return new FavouritesStatistic(BeatmapSet) { Current = FavouriteState };
|
||||
|
||||
var dateStatistic = BeatmapCardDateStatistic.CreateFor(BeatmapSet);
|
||||
if (dateStatistic != null)
|
||||
|
||||
@@ -17,7 +17,7 @@ namespace osu.Game.Beatmaps.Drawables.Cards.Statistics
|
||||
{
|
||||
this.dateTime = dateTime;
|
||||
|
||||
Icon = FontAwesome.Regular.CheckCircle;
|
||||
Icon = FontAwesome.Solid.CheckCircle;
|
||||
Text = dateTime.ToLocalisedMediumDate();
|
||||
}
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@ namespace osu.Game.Beatmaps.Drawables.Cards.Statistics
|
||||
{
|
||||
public PlayCountStatistic(IBeatmapSetOnlineInfo onlineInfo)
|
||||
{
|
||||
Icon = FontAwesome.Regular.PlayCircle;
|
||||
Icon = FontAwesome.Solid.PlayCircle;
|
||||
Text = onlineInfo.PlayCount.ToMetric(decimals: 1);
|
||||
TooltipText = BeatmapsStrings.PanelPlaycount(onlineInfo.PlayCount.ToLocalisableString(@"N0"));
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@ using osu.Game.Beatmaps;
|
||||
using osu.Game.Extensions;
|
||||
using osu.Game.Models;
|
||||
using osu.Game.Overlays.Notifications;
|
||||
using osu.Game.Utils;
|
||||
using Realms;
|
||||
|
||||
namespace osu.Game.Database
|
||||
@@ -87,6 +88,10 @@ namespace osu.Game.Database
|
||||
public void AddFile(TModel item, Stream contents, string filename, Realm realm)
|
||||
{
|
||||
filename = filename.ToStandardisedPath();
|
||||
|
||||
if (FilesystemSanityCheckHelpers.IncursPathTraversalRisk(filename))
|
||||
throw new InvalidOperationException($@"Filename ""{filename}"" is not allowed.");
|
||||
|
||||
var existing = item.GetFile(filename);
|
||||
|
||||
if (existing != null)
|
||||
|
||||
@@ -17,6 +17,7 @@ using osu.Game.Extensions;
|
||||
using osu.Game.IO.Archives;
|
||||
using osu.Game.Models;
|
||||
using osu.Game.Overlays.Notifications;
|
||||
using osu.Game.Utils;
|
||||
using Realms;
|
||||
|
||||
namespace osu.Game.Database
|
||||
@@ -221,7 +222,15 @@ namespace osu.Game.Database
|
||||
foreach (string piece in realmFile.Filename.Split('/').Select(f => f.GetValidFilename()))
|
||||
destinationPath = Path.Combine(destinationPath, piece);
|
||||
|
||||
Directory.CreateDirectory(Path.GetDirectoryName(destinationPath)!);
|
||||
string destinationDirectory = Path.GetDirectoryName(destinationPath)!;
|
||||
|
||||
if (!FilesystemSanityCheckHelpers.IsSubDirectory(parent: mountedPath, child: destinationDirectory))
|
||||
{
|
||||
Logger.Log($@"Skipping attempt to mount {realmFile.Filename} due to detected escape out of mounted path.", LoggingTarget.Database);
|
||||
continue;
|
||||
}
|
||||
|
||||
Directory.CreateDirectory(destinationDirectory);
|
||||
|
||||
// Consider using hard links here to make this instant.
|
||||
using (var inStream = Files.Storage.GetStream(sourcePath))
|
||||
@@ -361,6 +370,9 @@ namespace osu.Game.Database
|
||||
// We intentionally delay adding to realm to avoid blocking on a write during disk operations.
|
||||
foreach (var filenames in getShortenedFilenames(archive))
|
||||
{
|
||||
if (FilesystemSanityCheckHelpers.IncursPathTraversalRisk(filenames.shortened))
|
||||
throw new InvalidOperationException($@"Filename ""{filenames.original}"" is not allowed.");
|
||||
|
||||
using (Stream s = archive.GetStream(filenames.original))
|
||||
files.Add(new RealmNamedFileUsage(Files.Add(s, realm, false, parameters.PreferHardLinks), filenames.shortened));
|
||||
}
|
||||
|
||||
@@ -33,6 +33,9 @@ namespace osu.Game.Graphics.Carousel
|
||||
/// </summary>
|
||||
protected partial class ScrollContainer : UserTrackingScrollContainer, IKeyBindingHandler<GlobalAction>
|
||||
{
|
||||
public Action? OnPageUp { get; init; }
|
||||
public Action? OnPageDown { get; init; }
|
||||
|
||||
public readonly Container Panels;
|
||||
|
||||
public void SetLayoutHeight(float height) => Panels.Height = height;
|
||||
@@ -127,6 +130,22 @@ namespace osu.Game.Graphics.Carousel
|
||||
|
||||
protected override bool IsDragging => base.IsDragging || AbsoluteScrolling;
|
||||
|
||||
protected override bool OnKeyDown(KeyDownEvent e)
|
||||
{
|
||||
switch (e.Key)
|
||||
{
|
||||
case Key.PageUp:
|
||||
OnPageUp?.Invoke();
|
||||
return true;
|
||||
|
||||
case Key.PageDown:
|
||||
OnPageDown?.Invoke();
|
||||
return true;
|
||||
}
|
||||
|
||||
return base.OnKeyDown(e);
|
||||
}
|
||||
|
||||
public bool OnPressed(KeyBindingPressEvent<GlobalAction> e)
|
||||
{
|
||||
switch (e.Action)
|
||||
@@ -201,6 +220,8 @@ namespace osu.Game.Graphics.Carousel
|
||||
|
||||
private readonly Drawable box;
|
||||
|
||||
private bool capturingMouseDown;
|
||||
|
||||
protected override float MinimumDimSize => SCROLL_BAR_WIDTH * 3;
|
||||
|
||||
private const float expanded_size_ratio = 2;
|
||||
@@ -261,6 +282,7 @@ namespace osu.Game.Graphics.Carousel
|
||||
{
|
||||
if (!base.OnMouseDown(e)) return false;
|
||||
|
||||
capturingMouseDown = true;
|
||||
updateVisuals(e);
|
||||
return true;
|
||||
}
|
||||
@@ -275,13 +297,14 @@ namespace osu.Game.Graphics.Carousel
|
||||
{
|
||||
if (e.Button != MouseButton.Left) return;
|
||||
|
||||
capturingMouseDown = false;
|
||||
updateVisuals(e);
|
||||
base.OnMouseUp(e);
|
||||
}
|
||||
|
||||
private void updateVisuals(MouseEvent e)
|
||||
{
|
||||
if (IsDragged || e.PressedButtons.Contains(MouseButton.Left))
|
||||
if (capturingMouseDown)
|
||||
box.FadeColour(highlightColour, 100);
|
||||
else if (IsHovered)
|
||||
box.FadeColour(hoverColour, 100);
|
||||
|
||||
@@ -317,6 +317,8 @@ namespace osu.Game.Graphics.Carousel
|
||||
{
|
||||
Masking = false,
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
OnPageUp = () => Scheduler.AddOnce(traverseFromKey, new TraversalOperation(TraversalType.Page, -1)),
|
||||
OnPageDown = () => Scheduler.AddOnce(traverseFromKey, new TraversalOperation(TraversalType.Page, 1)),
|
||||
};
|
||||
|
||||
Items.BindCollectionChanged((_, args) =>
|
||||
@@ -538,26 +540,30 @@ namespace osu.Game.Graphics.Carousel
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void traverseFromKey(TraversalOperation traversal)
|
||||
private void traverseFromKey(TraversalOperation traversal)
|
||||
{
|
||||
switch (traversal.Type)
|
||||
{
|
||||
switch (traversal.Type)
|
||||
{
|
||||
case TraversalType.Keyboard:
|
||||
traverseKeyboardSelection(traversal.Direction);
|
||||
break;
|
||||
case TraversalType.Keyboard:
|
||||
traverseKeyboardSelection(traversal.Direction);
|
||||
break;
|
||||
|
||||
case TraversalType.Set:
|
||||
traverseSetSelection(traversal.Direction);
|
||||
break;
|
||||
case TraversalType.Page:
|
||||
traverseKeyboardPage(traversal.Direction);
|
||||
break;
|
||||
|
||||
case TraversalType.Group:
|
||||
traverseGroupSelection(traversal.Direction);
|
||||
break;
|
||||
case TraversalType.Set:
|
||||
traverseSetSelection(traversal.Direction);
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException();
|
||||
}
|
||||
case TraversalType.Group:
|
||||
traverseGroupSelection(traversal.Direction);
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -565,6 +571,7 @@ namespace osu.Game.Graphics.Carousel
|
||||
{
|
||||
Keyboard,
|
||||
Set,
|
||||
Page,
|
||||
Group
|
||||
}
|
||||
|
||||
@@ -622,6 +629,59 @@ namespace osu.Game.Graphics.Carousel
|
||||
} while (newIndex != originalIndex);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Performs a page-wise keyboard traversal in the carousel, moving the selection by approximately one "page" of items.
|
||||
/// </summary>
|
||||
/// <param name="direction">Positive for downwards, negative for upwards.</param>
|
||||
private void traverseKeyboardPage(int direction)
|
||||
{
|
||||
if (carouselItems == null || carouselItems.Count == 0)
|
||||
return;
|
||||
|
||||
int startIndex = currentKeyboardSelection.Index ?? (direction > 0 ? carouselItems.Count - 1 : 0);
|
||||
|
||||
// Compute the number of visible panels to treat as one page.
|
||||
// Reduced by 50% to account for the search bar covering the top items.
|
||||
int visiblePanelsCount = Math.Max(1, Scroll.Panels.Count / 2);
|
||||
int visibleCount = 0;
|
||||
int i = startIndex;
|
||||
|
||||
while (i >= 0 && i < carouselItems.Count)
|
||||
{
|
||||
i += direction;
|
||||
|
||||
if (i < 0 || i >= carouselItems.Count)
|
||||
break;
|
||||
|
||||
var item = carouselItems[i];
|
||||
|
||||
if (!item.IsVisible)
|
||||
continue;
|
||||
|
||||
visibleCount++;
|
||||
|
||||
if (visibleCount >= visiblePanelsCount)
|
||||
{
|
||||
setKeyboardSelection(item.Model);
|
||||
ScrollToSelection();
|
||||
playTraversalSound();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// If we are at the beginning or end and there are not enough items left to scroll through a complete page, then we go to the last or first item.
|
||||
var fallback = direction > 0
|
||||
? carouselItems.LastOrDefault(x => x.IsVisible)
|
||||
: carouselItems.FirstOrDefault(x => x.IsVisible);
|
||||
|
||||
if (fallback != null && !CheckModelEquality(fallback.Model, currentKeyboardSelection.Model))
|
||||
{
|
||||
setKeyboardSelection(fallback.Model);
|
||||
ScrollToSelection();
|
||||
playTraversalSound();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Select the next valid group selection relative to a current selection.
|
||||
/// This is generally for keyboard based traversal.
|
||||
|
||||
@@ -157,7 +157,7 @@ namespace osu.Game.Graphics.UserInterfaceV2
|
||||
{
|
||||
Current = currentNumberInstantaneous,
|
||||
OnCommit = () => current.Value = currentNumberInstantaneous.Value,
|
||||
TooltipFormat = TooltipFormat,
|
||||
TooltipFormat = s => TooltipFormat(s),
|
||||
DisplayAsPercentage = DisplayAsPercentage,
|
||||
PlaySamplesOnAdjust = PlaySamplesOnAdjust,
|
||||
ResetToDefault = () =>
|
||||
|
||||
@@ -236,9 +236,11 @@ Click to see what's new!", version);
|
||||
public static LocalisableString ElevatedPrivileges(LocalisableString user) => new TranslatableString(getKey(@"elevated_privileges"), @"Running osu! as {0} does not improve performance, may break integrations and poses a security risk. Please run the game as a normal user.", user);
|
||||
|
||||
/// <summary>
|
||||
/// "Screenshot {0} saved!"
|
||||
/// "Screenshot saved! Click to view.
|
||||
/// {0}"
|
||||
/// </summary>
|
||||
public static LocalisableString ScreenshotSaved(string filename) => new TranslatableString(getKey(@"screenshot_saved"), @"Screenshot {0} saved!", filename);
|
||||
public static LocalisableString ScreenshotSaved(string filename) => new TranslatableString(getKey(@"screenshot_saved"), @"Screenshot saved! Click to view.
|
||||
{0}", filename);
|
||||
|
||||
/// <summary>
|
||||
/// "The multiplayer server will be right back..."
|
||||
|
||||
@@ -34,6 +34,7 @@ using osu.Game.Resources.Localisation.Web;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Utils;
|
||||
using CommonStrings = osu.Game.Localisation.CommonStrings;
|
||||
using SongSelect = osu.Game.Screens.SelectV2.SongSelect;
|
||||
using WebCommonStrings = osu.Game.Resources.Localisation.Web.CommonStrings;
|
||||
|
||||
namespace osu.Game.Online.Leaderboards
|
||||
|
||||
@@ -23,8 +23,19 @@ namespace osu.Game.Online.Matchmaking
|
||||
/// <see cref="IMatchmakingServer.MatchmakingDeclineInvitation">declined</see>,
|
||||
/// or ignored - in which case it will automatically be declined after a short timeout period.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Provided for compatibility with older clients - can be removed 20260825.
|
||||
/// </remarks>
|
||||
Task MatchmakingRoomInvited();
|
||||
|
||||
/// <summary>
|
||||
/// Signals that a match has been found and the local user is invited to it.
|
||||
/// The invitation may be <see cref="IMatchmakingServer.MatchmakingAcceptInvitation">accepted</see>,
|
||||
/// <see cref="IMatchmakingServer.MatchmakingDeclineInvitation">declined</see>,
|
||||
/// or ignored - in which case it will automatically be declined after a short timeout period.
|
||||
/// </summary>
|
||||
Task MatchmakingRoomInvitedWithParams(MatchmakingRoomInvitationParams invitation);
|
||||
|
||||
/// <summary>
|
||||
/// Signals that the matchmaking room is ready to be opened.
|
||||
/// </summary>
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using MessagePack;
|
||||
|
||||
namespace osu.Game.Online.Matchmaking
|
||||
{
|
||||
[MessagePackObject]
|
||||
[Serializable]
|
||||
public class MatchmakingRoomInvitationParams
|
||||
{
|
||||
[Key(0)]
|
||||
public MatchmakingPoolType Type { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -123,7 +123,7 @@ namespace osu.Game.Online.Multiplayer
|
||||
|
||||
public event Action? MatchmakingQueueJoined;
|
||||
public event Action? MatchmakingQueueLeft;
|
||||
public event Action? MatchmakingRoomInvited;
|
||||
public event Action<MatchmakingRoomInvitationParams>? MatchmakingRoomInvited;
|
||||
public event Action<long, string>? MatchmakingRoomReady;
|
||||
public event Action<MatchmakingLobbyStatus>? MatchmakingLobbyStatusChanged;
|
||||
public event Action<MatchmakingQueueStatus>? MatchmakingQueueStatusChanged;
|
||||
@@ -1085,7 +1085,13 @@ namespace osu.Game.Online.Multiplayer
|
||||
|
||||
Task IMatchmakingClient.MatchmakingRoomInvited()
|
||||
{
|
||||
Scheduler.Add(() => MatchmakingRoomInvited?.Invoke());
|
||||
// Compatibility with older servers.
|
||||
return ((IMatchmakingClient)this).MatchmakingRoomInvitedWithParams(new MatchmakingRoomInvitationParams { Type = MatchmakingPoolType.QuickPlay });
|
||||
}
|
||||
|
||||
Task IMatchmakingClient.MatchmakingRoomInvitedWithParams(MatchmakingRoomInvitationParams invitation)
|
||||
{
|
||||
Scheduler.Add(() => MatchmakingRoomInvited?.Invoke(invitation));
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
|
||||
@@ -76,6 +76,7 @@ namespace osu.Game.Online.Multiplayer
|
||||
connection.On(nameof(IMatchmakingClient.MatchmakingQueueJoined), ((IMatchmakingClient)this).MatchmakingQueueJoined);
|
||||
connection.On(nameof(IMatchmakingClient.MatchmakingQueueLeft), ((IMatchmakingClient)this).MatchmakingQueueLeft);
|
||||
connection.On(nameof(IMatchmakingClient.MatchmakingRoomInvited), ((IMatchmakingClient)this).MatchmakingRoomInvited);
|
||||
connection.On<MatchmakingRoomInvitationParams>(nameof(IMatchmakingClient.MatchmakingRoomInvitedWithParams), ((IMatchmakingClient)this).MatchmakingRoomInvitedWithParams);
|
||||
connection.On<long, string>(nameof(IMatchmakingClient.MatchmakingRoomReady), ((IMatchmakingClient)this).MatchmakingRoomReady);
|
||||
connection.On<MatchmakingLobbyStatus>(nameof(IMatchmakingClient.MatchmakingLobbyStatusChanged), ((IMatchmakingClient)this).MatchmakingLobbyStatusChanged);
|
||||
connection.On<MatchmakingQueueStatus>(nameof(IMatchmakingClient.MatchmakingQueueStatusChanged), ((IMatchmakingClient)this).MatchmakingQueueStatusChanged);
|
||||
|
||||
@@ -71,8 +71,8 @@ using osu.Game.Screens.OnlinePlay.Multiplayer;
|
||||
using osu.Game.Screens.OnlinePlay.Playlists;
|
||||
using osu.Game.Screens.Play;
|
||||
using osu.Game.Screens.Ranking;
|
||||
using osu.Game.Screens.Select;
|
||||
using osu.Game.Screens.Select.Leaderboards;
|
||||
using osu.Game.Screens.SelectV2;
|
||||
using osu.Game.Seasonal;
|
||||
using osu.Game.Skinning;
|
||||
using osu.Game.Updater;
|
||||
@@ -774,7 +774,7 @@ namespace osu.Game
|
||||
}
|
||||
}, validScreens: new[]
|
||||
{
|
||||
typeof(SongSelect), typeof(Screens.SelectV2.SongSelect), typeof(IHandlePresentBeatmap)
|
||||
typeof(SongSelect), typeof(IHandlePresentBeatmap)
|
||||
});
|
||||
}
|
||||
|
||||
@@ -877,7 +877,7 @@ namespace osu.Game
|
||||
// which may not match the score, and thus crash.
|
||||
IEnumerable<Type> validScreens =
|
||||
Beatmap.Value.BeatmapInfo.Equals(databasedBeatmap) && Ruleset.Value.Equals(databasedScore.ScoreInfo.Ruleset)
|
||||
? new[] { typeof(SongSelect), typeof(Screens.SelectV2.SongSelect), typeof(DailyChallenge) }
|
||||
? new[] { typeof(SongSelect), typeof(DailyChallenge) }
|
||||
: Array.Empty<Type>();
|
||||
|
||||
PerformFromScreen(screen =>
|
||||
@@ -1156,6 +1156,7 @@ namespace osu.Game
|
||||
},
|
||||
new PopoverContainer
|
||||
{
|
||||
// Ensure the footer is displayed above any content and/or overlays.
|
||||
Depth = -1,
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Child = screenStackFooter = new ScreenStackFooter(ScreenStack, backReceptor)
|
||||
|
||||
@@ -9,10 +9,12 @@ using osu.Framework.Bindables;
|
||||
using osu.Framework.Extensions.LocalisationExtensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Cursor;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using osu.Framework.Input.Events;
|
||||
using osu.Framework.Localisation;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Beatmaps.Drawables;
|
||||
using osu.Game.Extensions;
|
||||
using osu.Game.Graphics;
|
||||
@@ -32,7 +34,7 @@ namespace osu.Game.Overlays.BeatmapSet
|
||||
private const float tile_spacing = 2;
|
||||
|
||||
private readonly LinkFlowContainer infoContainer;
|
||||
private readonly Statistic plays, favourites;
|
||||
private readonly Statistic nominations, plays, favourites;
|
||||
|
||||
public readonly DifficultiesContainer Difficulties;
|
||||
|
||||
@@ -107,7 +109,14 @@ namespace osu.Game.Overlays.BeatmapSet
|
||||
Margin = new MarginPadding { Top = 5 },
|
||||
Children = new[]
|
||||
{
|
||||
plays = new Statistic(FontAwesome.Solid.PlayCircle),
|
||||
nominations = new Statistic(FontAwesome.Solid.ThumbsUp)
|
||||
{
|
||||
TooltipText = BeatmapsetsStrings.ShowStatsNominations,
|
||||
},
|
||||
plays = new Statistic(FontAwesome.Solid.PlayCircle)
|
||||
{
|
||||
TooltipText = BeatmapsetsStrings.ShowStatsPlaycount,
|
||||
},
|
||||
favourites = new Statistic(FontAwesome.Solid.Heart),
|
||||
},
|
||||
},
|
||||
@@ -176,8 +185,17 @@ namespace osu.Game.Overlays.BeatmapSet
|
||||
// Else just choose the first available difficulty for now.
|
||||
Beatmap.Value ??= Difficulties.FirstOrDefault()?.Beatmap;
|
||||
|
||||
if (beatmapSet?.Status == BeatmapOnlineStatus.Pending && beatmapSet.NominationStatus != null)
|
||||
{
|
||||
nominations.Show();
|
||||
nominations.Value = beatmapSet.NominationStatus.Current;
|
||||
}
|
||||
else
|
||||
nominations.Hide();
|
||||
|
||||
plays.Value = BeatmapSet?.PlayCount ?? 0;
|
||||
favourites.Value = BeatmapSet?.FavouriteCount ?? 0;
|
||||
favourites.TooltipText = BeatmapSet?.FavouriteCount > 0 ? BeatmapsetsStrings.ShowStatsFavourites : BeatmapsetsStrings.ShowStatsNoFavourites;
|
||||
|
||||
updateDifficultyButtons();
|
||||
}
|
||||
@@ -367,7 +385,7 @@ namespace osu.Game.Overlays.BeatmapSet
|
||||
}
|
||||
}
|
||||
|
||||
private partial class Statistic : FillFlowContainer
|
||||
private partial class Statistic : FillFlowContainer, IHasTooltip
|
||||
{
|
||||
private readonly OsuSpriteText text;
|
||||
|
||||
@@ -407,6 +425,8 @@ namespace osu.Game.Overlays.BeatmapSet
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
public LocalisableString TooltipText { get; set; }
|
||||
}
|
||||
|
||||
public enum DifficultySelectorState
|
||||
|
||||
@@ -14,7 +14,6 @@ using osu.Game.Localisation;
|
||||
using osu.Game.Screens;
|
||||
using osu.Game.Screens.SelectV2;
|
||||
using osuTK;
|
||||
using SongSelect = osu.Game.Screens.Select.SongSelect;
|
||||
|
||||
namespace osu.Game.Overlays.SkinEditor
|
||||
{
|
||||
|
||||
@@ -30,7 +30,7 @@ using osu.Game.Online.API.Requests;
|
||||
using osu.Game.Online.API.Requests.Responses;
|
||||
using osu.Game.Overlays;
|
||||
using osu.Game.Overlays.Notifications;
|
||||
using osu.Game.Screens.Select;
|
||||
using osu.Game.Screens.SelectV2;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Screens.Edit.Submission
|
||||
|
||||
@@ -1,146 +1,151 @@
|
||||
// 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.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Extensions.Color4Extensions;
|
||||
using osu.Framework.Extensions.LocalisationExtensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Effects;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using osu.Framework.Localisation;
|
||||
using osu.Framework.Utils;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Graphics.Sprites;
|
||||
using osu.Game.Graphics.UserInterface;
|
||||
using osu.Game.Localisation;
|
||||
using osu.Game.Overlays;
|
||||
using osu.Game.Overlays.Mods;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Screens.Select;
|
||||
using osu.Game.Screens.Footer;
|
||||
using osu.Game.Screens.Play.HUD;
|
||||
using osu.Game.Screens.SelectV2;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Screens.OnlinePlay
|
||||
{
|
||||
public partial class FooterButtonFreeMods : FooterButton
|
||||
public partial class FooterButtonFreeMods : ScreenFooterButton
|
||||
{
|
||||
public readonly Bindable<IReadOnlyList<Mod>> FreeMods = new Bindable<IReadOnlyList<Mod>>();
|
||||
public readonly IBindable<bool> Freestyle = new Bindable<bool>();
|
||||
|
||||
protected override bool IsActive => FreeMods.Value.Count > 0;
|
||||
public readonly Bindable<IReadOnlyList<Mod>> FreeMods = new Bindable<IReadOnlyList<Mod>>([]);
|
||||
public readonly Bindable<bool> Freestyle = new Bindable<bool>();
|
||||
|
||||
public new Action Action
|
||||
{
|
||||
set => throw new NotSupportedException("The click action is handled by the button itself.");
|
||||
}
|
||||
|
||||
private OsuSpriteText count = null!;
|
||||
private Circle circle = null!;
|
||||
|
||||
private readonly FreeModSelectOverlay freeModSelectOverlay;
|
||||
|
||||
public FooterButtonFreeMods(FreeModSelectOverlay freeModSelectOverlay)
|
||||
{
|
||||
this.freeModSelectOverlay = freeModSelectOverlay;
|
||||
|
||||
// Overwrite any external behaviour as we delegate the main toggle action to a sub-button.
|
||||
base.Action = toggleAllFreeMods;
|
||||
}
|
||||
|
||||
[Resolved]
|
||||
private OsuColour colours { get; set; } = null!;
|
||||
|
||||
[Resolved]
|
||||
private OverlayColourProvider colourProvider { get; set; } = null!;
|
||||
|
||||
private Container modsWedge = null!;
|
||||
private ModDisplay modDisplay = null!;
|
||||
private Container modContainer = null!;
|
||||
private FooterButtonMods.ModCountText overflowModCountDisplay = null!;
|
||||
|
||||
public FooterButtonFreeMods(ModSelectOverlay overlay)
|
||||
: base(overlay)
|
||||
{
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
ButtonContentContainer.AddRange(new[]
|
||||
Text = OnlinePlayStrings.FooterButtonFreemods;
|
||||
Icon = FontAwesome.Solid.ExchangeAlt;
|
||||
AccentColour = colours.Lime1;
|
||||
|
||||
Add(modsWedge = new InputBlockingContainer
|
||||
{
|
||||
new Container
|
||||
Y = -5f,
|
||||
Depth = float.MaxValue,
|
||||
Origin = Anchor.BottomLeft,
|
||||
Shear = OsuGame.SHEAR,
|
||||
CornerRadius = CORNER_RADIUS,
|
||||
Size = new Vector2(BUTTON_WIDTH, FooterButtonMods.BAR_HEIGHT),
|
||||
Masking = true,
|
||||
EdgeEffect = new EdgeEffectParameters
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
AutoSizeAxes = Axes.Both,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
circle = new Circle
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Colour = colours.YellowDark,
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
},
|
||||
count = new OsuSpriteText
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Padding = new MarginPadding(5),
|
||||
UseFullGlyphHeight = false,
|
||||
}
|
||||
}
|
||||
Type = EdgeEffectType.Shadow,
|
||||
Radius = 4,
|
||||
// Figma says 50% opacity, but it does not match up visually if taken at face value, and looks bad.
|
||||
Colour = Colour4.Black.Opacity(0.25f),
|
||||
Offset = new Vector2(0, 2),
|
||||
},
|
||||
new IconButton
|
||||
Alpha = 0,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Scale = new Vector2(0.8f),
|
||||
Icon = FontAwesome.Solid.Bars,
|
||||
Enabled = { BindTarget = Enabled },
|
||||
Action = () => freeModSelectOverlay.ToggleVisibility()
|
||||
new Box
|
||||
{
|
||||
Colour = colourProvider.Background4,
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
},
|
||||
modContainer = new Container
|
||||
{
|
||||
CornerRadius = CORNER_RADIUS,
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Masking = true,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
modDisplay = new ModDisplay(showExtendedInformation: true)
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Shear = -OsuGame.SHEAR,
|
||||
Scale = new Vector2(0.5f),
|
||||
Current = { BindTarget = FreeMods },
|
||||
ExpansionMode = ExpansionMode.AlwaysContracted,
|
||||
},
|
||||
overflowModCountDisplay = new FooterButtonMods.ModCountText
|
||||
{
|
||||
Mods = { BindTarget = FreeMods },
|
||||
},
|
||||
}
|
||||
},
|
||||
}
|
||||
});
|
||||
|
||||
SelectedColour = colours.Yellow;
|
||||
DeselectedColour = SelectedColour.Opacity(0.5f);
|
||||
Text = @"freemods";
|
||||
|
||||
TooltipText = MultiplayerMatchStrings.FreeModsButtonTooltip;
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
||||
Freestyle.BindValueChanged(_ => updateModDisplay());
|
||||
FreeMods.BindValueChanged(_ => updateModDisplay(), true);
|
||||
Freestyle.BindValueChanged(f =>
|
||||
{
|
||||
Enabled.Value = !f.NewValue;
|
||||
overflowModCountDisplay.CustomText = f.NewValue ? ModSelectOverlayStrings.AllMods.ToUpper() : (LocalisableString?)null;
|
||||
}, true);
|
||||
FreeMods.BindValueChanged(m =>
|
||||
{
|
||||
if (m.NewValue.Count == 0 && !Freestyle.Value)
|
||||
modsWedge.FadeOut(300, Easing.OutExpo);
|
||||
else
|
||||
modsWedge.FadeIn(300, Easing.OutExpo);
|
||||
}, true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Immediately toggle all free mods on/off.
|
||||
/// </summary>
|
||||
private void toggleAllFreeMods()
|
||||
protected override void Update()
|
||||
{
|
||||
var availableMods = allAvailableAndValidMods.ToArray();
|
||||
base.Update();
|
||||
|
||||
FreeMods.Value = FreeMods.Value.Count == availableMods.Length
|
||||
? Array.Empty<Mod>()
|
||||
: availableMods;
|
||||
}
|
||||
// If there are freemods selected but the display has no width, it's still loading.
|
||||
// Don't update visibility in this state or we will cause an awkward flash.
|
||||
if (FreeMods.Value.Count > 0 && Precision.AlmostEquals(modDisplay.DrawWidth, 0))
|
||||
return;
|
||||
|
||||
private void updateModDisplay()
|
||||
{
|
||||
int currentCount = FreeMods.Value.Count;
|
||||
bool showCountText =
|
||||
// When freestyle is enabled this text shows "ALL MODS"
|
||||
Freestyle.Value
|
||||
// Standard flow where mods are overflowing so we show count text.
|
||||
|| modDisplay.DrawWidth * modDisplay.Scale.X > modContainer.DrawWidth;
|
||||
|
||||
if (currentCount == allAvailableAndValidMods.Count() || Freestyle.Value)
|
||||
{
|
||||
count.Text = "all";
|
||||
count.FadeColour(colours.Gray2, 200, Easing.OutQuint);
|
||||
circle.FadeColour(colours.Yellow, 200, Easing.OutQuint);
|
||||
}
|
||||
else if (currentCount > 0)
|
||||
{
|
||||
count.Text = $"{currentCount} mods";
|
||||
count.FadeColour(colours.Gray2, 200, Easing.OutQuint);
|
||||
circle.FadeColour(colours.YellowDark, 200, Easing.OutQuint);
|
||||
}
|
||||
if (showCountText)
|
||||
overflowModCountDisplay.Show();
|
||||
else
|
||||
{
|
||||
count.Text = "off";
|
||||
count.FadeColour(colours.GrayF, 200, Easing.OutQuint);
|
||||
circle.FadeColour(colours.Gray4, 200, Easing.OutQuint);
|
||||
}
|
||||
overflowModCountDisplay.Hide();
|
||||
}
|
||||
|
||||
private IEnumerable<Mod> allAvailableAndValidMods => freeModSelectOverlay.AllAvailableMods
|
||||
.Where(state => state.ValidForSelection.Value)
|
||||
.Select(state => state.Mod);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,151 +0,0 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Extensions.LocalisationExtensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Effects;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using osu.Framework.Localisation;
|
||||
using osu.Framework.Utils;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Localisation;
|
||||
using osu.Game.Overlays;
|
||||
using osu.Game.Overlays.Mods;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Screens.Footer;
|
||||
using osu.Game.Screens.Play.HUD;
|
||||
using osu.Game.Screens.SelectV2;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Screens.OnlinePlay
|
||||
{
|
||||
public partial class FooterButtonFreeModsV2 : ScreenFooterButton
|
||||
{
|
||||
public readonly Bindable<IReadOnlyList<Mod>> FreeMods = new Bindable<IReadOnlyList<Mod>>([]);
|
||||
public readonly Bindable<bool> Freestyle = new Bindable<bool>();
|
||||
|
||||
public new Action Action
|
||||
{
|
||||
set => throw new NotSupportedException("The click action is handled by the button itself.");
|
||||
}
|
||||
|
||||
[Resolved]
|
||||
private OsuColour colours { get; set; } = null!;
|
||||
|
||||
[Resolved]
|
||||
private OverlayColourProvider colourProvider { get; set; } = null!;
|
||||
|
||||
private Container modsWedge = null!;
|
||||
private ModDisplay modDisplay = null!;
|
||||
private Container modContainer = null!;
|
||||
private FooterButtonMods.ModCountText overflowModCountDisplay = null!;
|
||||
|
||||
public FooterButtonFreeModsV2(ModSelectOverlay overlay)
|
||||
: base(overlay)
|
||||
{
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
Text = OnlinePlayStrings.FooterButtonFreemods;
|
||||
Icon = FontAwesome.Solid.ExchangeAlt;
|
||||
AccentColour = colours.Lime1;
|
||||
|
||||
Add(modsWedge = new InputBlockingContainer
|
||||
{
|
||||
Y = -5f,
|
||||
Depth = float.MaxValue,
|
||||
Origin = Anchor.BottomLeft,
|
||||
Shear = OsuGame.SHEAR,
|
||||
CornerRadius = CORNER_RADIUS,
|
||||
Size = new Vector2(BUTTON_WIDTH, FooterButtonMods.BAR_HEIGHT),
|
||||
Masking = true,
|
||||
EdgeEffect = new EdgeEffectParameters
|
||||
{
|
||||
Type = EdgeEffectType.Shadow,
|
||||
Radius = 4,
|
||||
// Figma says 50% opacity, but it does not match up visually if taken at face value, and looks bad.
|
||||
Colour = Colour4.Black.Opacity(0.25f),
|
||||
Offset = new Vector2(0, 2),
|
||||
},
|
||||
Alpha = 0,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new Box
|
||||
{
|
||||
Colour = colourProvider.Background4,
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
},
|
||||
modContainer = new Container
|
||||
{
|
||||
CornerRadius = CORNER_RADIUS,
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Masking = true,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
modDisplay = new ModDisplay(showExtendedInformation: true)
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Shear = -OsuGame.SHEAR,
|
||||
Scale = new Vector2(0.5f),
|
||||
Current = { BindTarget = FreeMods },
|
||||
ExpansionMode = ExpansionMode.AlwaysContracted,
|
||||
},
|
||||
overflowModCountDisplay = new FooterButtonMods.ModCountText
|
||||
{
|
||||
Mods = { BindTarget = FreeMods },
|
||||
},
|
||||
}
|
||||
},
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
||||
Freestyle.BindValueChanged(f =>
|
||||
{
|
||||
Enabled.Value = !f.NewValue;
|
||||
overflowModCountDisplay.CustomText = f.NewValue ? ModSelectOverlayStrings.AllMods.ToUpper() : (LocalisableString?)null;
|
||||
}, true);
|
||||
FreeMods.BindValueChanged(m =>
|
||||
{
|
||||
if (m.NewValue.Count == 0 && !Freestyle.Value)
|
||||
modsWedge.FadeOut(300, Easing.OutExpo);
|
||||
else
|
||||
modsWedge.FadeIn(300, Easing.OutExpo);
|
||||
}, true);
|
||||
}
|
||||
|
||||
protected override void Update()
|
||||
{
|
||||
base.Update();
|
||||
|
||||
// If there are freemods selected but the display has no width, it's still loading.
|
||||
// Don't update visibility in this state or we will cause an awkward flash.
|
||||
if (FreeMods.Value.Count > 0 && Precision.AlmostEquals(modDisplay.DrawWidth, 0))
|
||||
return;
|
||||
|
||||
bool showCountText =
|
||||
// When freestyle is enabled this text shows "ALL MODS"
|
||||
Freestyle.Value
|
||||
// Standard flow where mods are overflowing so we show count text.
|
||||
|| modDisplay.DrawWidth * modDisplay.Scale.X > modContainer.DrawWidth;
|
||||
|
||||
if (showCountText)
|
||||
overflowModCountDisplay.Show();
|
||||
else
|
||||
overflowModCountDisplay.Hide();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4,31 +4,23 @@
|
||||
using System;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Extensions.Color4Extensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Graphics.Sprites;
|
||||
using osu.Game.Localisation;
|
||||
using osu.Game.Screens.Select;
|
||||
using osu.Game.Screens.Footer;
|
||||
|
||||
namespace osu.Game.Screens.OnlinePlay
|
||||
{
|
||||
public partial class FooterButtonFreestyle : FooterButton
|
||||
public partial class FooterButtonFreestyle : ScreenFooterButton
|
||||
{
|
||||
public readonly Bindable<bool> Freestyle = new Bindable<bool>();
|
||||
|
||||
protected override bool IsActive => Freestyle.Value;
|
||||
|
||||
public new Action Action
|
||||
{
|
||||
set => throw new NotSupportedException("The click action is handled by the button itself.");
|
||||
}
|
||||
|
||||
private OsuSpriteText text = null!;
|
||||
private Circle circle = null!;
|
||||
|
||||
[Resolved]
|
||||
private OsuColour colours { get; set; } = null!;
|
||||
|
||||
@@ -41,61 +33,19 @@ namespace osu.Game.Screens.OnlinePlay
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
ButtonContentContainer.AddRange(new[]
|
||||
{
|
||||
new Container
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
AutoSizeAxes = Axes.Both,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
circle = new Circle
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Colour = colours.YellowDark,
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
},
|
||||
text = new OsuSpriteText
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Padding = new MarginPadding(5),
|
||||
UseFullGlyphHeight = false,
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
SelectedColour = colours.Yellow;
|
||||
DeselectedColour = SelectedColour.Opacity(0.5f);
|
||||
Text = @"freestyle";
|
||||
|
||||
TooltipText = MultiplayerMatchStrings.FreestyleButtonTooltip;
|
||||
Text = OnlinePlayStrings.FooterButtonFreestyle;
|
||||
Icon = FontAwesome.Solid.ExchangeAlt;
|
||||
AccentColour = colours.Lime1;
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
||||
Freestyle.BindValueChanged(_ => updateDisplay(), true);
|
||||
}
|
||||
|
||||
private void updateDisplay()
|
||||
{
|
||||
if (Freestyle.Value)
|
||||
Freestyle.BindValueChanged(active =>
|
||||
{
|
||||
text.Text = "on";
|
||||
text.FadeColour(colours.Gray2, 200, Easing.OutQuint);
|
||||
circle.FadeColour(colours.Yellow, 200, Easing.OutQuint);
|
||||
}
|
||||
else
|
||||
{
|
||||
text.Text = "off";
|
||||
text.FadeColour(colours.GrayF, 200, Easing.OutQuint);
|
||||
circle.FadeColour(colours.Gray4, 200, Easing.OutQuint);
|
||||
}
|
||||
OverlayState.Value = active.NewValue ? Visibility.Visible : Visibility.Hidden;
|
||||
}, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,51 +0,0 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Localisation;
|
||||
using osu.Game.Screens.Footer;
|
||||
|
||||
namespace osu.Game.Screens.OnlinePlay
|
||||
{
|
||||
public partial class FooterButtonFreestyleV2 : ScreenFooterButton
|
||||
{
|
||||
public readonly Bindable<bool> Freestyle = new Bindable<bool>();
|
||||
|
||||
public new Action Action
|
||||
{
|
||||
set => throw new NotSupportedException("The click action is handled by the button itself.");
|
||||
}
|
||||
|
||||
[Resolved]
|
||||
private OsuColour colours { get; set; } = null!;
|
||||
|
||||
public FooterButtonFreestyleV2()
|
||||
{
|
||||
// Overwrite any external behaviour as we delegate the main toggle action to a sub-button.
|
||||
base.Action = () => Freestyle.Value = !Freestyle.Value;
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
Text = OnlinePlayStrings.FooterButtonFreestyle;
|
||||
Icon = FontAwesome.Solid.ExchangeAlt;
|
||||
AccentColour = colours.Lime1;
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
||||
Freestyle.BindValueChanged(active =>
|
||||
{
|
||||
OverlayState.Value = active.NewValue ? Visibility.Visible : Visibility.Hidden;
|
||||
}, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -11,6 +11,7 @@ using osu.Framework.Graphics.Colour;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using osu.Framework.Screens;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Online.Matchmaking;
|
||||
using osu.Game.Online.Multiplayer;
|
||||
using osu.Game.Online.Rooms;
|
||||
using osu.Game.Overlays;
|
||||
@@ -94,15 +95,12 @@ namespace osu.Game.Screens.OnlinePlay.Matchmaking.Queue
|
||||
closeNotifications();
|
||||
});
|
||||
|
||||
private void onMatchmakingRoomInvited() => Scheduler.Add(() =>
|
||||
private void onMatchmakingRoomInvited(MatchmakingRoomInvitationParams invitation) => Scheduler.Add(() =>
|
||||
{
|
||||
CurrentState.Value = ScreenQueue.MatchmakingScreenState.PendingAccept;
|
||||
|
||||
if (backgroundNotification != null)
|
||||
{
|
||||
backgroundNotification.State = ProgressNotificationState.Completed;
|
||||
backgroundNotification = null;
|
||||
}
|
||||
backgroundNotification?.Complete(invitation);
|
||||
backgroundNotification = null;
|
||||
});
|
||||
|
||||
private void onMatchmakingRoomReady(long roomId, string password) => Scheduler.Add(() =>
|
||||
@@ -183,6 +181,17 @@ namespace osu.Game.Screens.OnlinePlay.Matchmaking.Queue
|
||||
return false;
|
||||
};
|
||||
|
||||
CancelRequested = () =>
|
||||
{
|
||||
client.MatchmakingLeaveQueue().FireAndForget();
|
||||
return true;
|
||||
};
|
||||
|
||||
matchFoundSample = audio.Samples.Get(@"Multiplayer/Matchmaking/match-found");
|
||||
}
|
||||
|
||||
public void Complete(MatchmakingRoomInvitationParams invitation)
|
||||
{
|
||||
CompletionClickAction = () =>
|
||||
{
|
||||
client.MatchmakingAcceptInvitation().FireAndForget();
|
||||
@@ -194,13 +203,7 @@ namespace osu.Game.Screens.OnlinePlay.Matchmaking.Queue
|
||||
return true;
|
||||
};
|
||||
|
||||
CancelRequested = () =>
|
||||
{
|
||||
client.MatchmakingLeaveQueue().FireAndForget();
|
||||
return true;
|
||||
};
|
||||
|
||||
matchFoundSample = audio.Samples.Get(@"Multiplayer/Matchmaking/match-found");
|
||||
State = ProgressNotificationState.Completed;
|
||||
}
|
||||
|
||||
protected override Notification CreateCompletionNotification()
|
||||
|
||||
@@ -23,9 +23,10 @@ using osu.Game.Overlays;
|
||||
using osu.Game.Overlays.Mods;
|
||||
using osu.Game.Rulesets;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Screens.Select;
|
||||
using osu.Game.Screens.Footer;
|
||||
using osu.Game.Users;
|
||||
using osu.Game.Utils;
|
||||
using osu.Game.Screens.SelectV2;
|
||||
|
||||
namespace osu.Game.Screens.OnlinePlay.Multiplayer
|
||||
{
|
||||
@@ -35,18 +36,20 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
|
||||
|
||||
public override string Title => ShortTitle.Humanize();
|
||||
|
||||
public override bool AllowEditing => false;
|
||||
|
||||
[Resolved]
|
||||
private MultiplayerClient client { get; set; } = null!;
|
||||
|
||||
[Resolved]
|
||||
private OngoingOperationTracker operationTracker { get; set; } = null!;
|
||||
|
||||
[Resolved]
|
||||
private IOverlayManager? overlayManager { get; set; }
|
||||
|
||||
private readonly Room room;
|
||||
private readonly IBindable<bool> operationInProgress = new Bindable<bool>();
|
||||
private readonly PlaylistItem? itemToEdit;
|
||||
|
||||
private ModSelectOverlay modSelect = null!;
|
||||
private LoadingLayer loadingLayer = null!;
|
||||
private IDisposable? selectionOperation;
|
||||
|
||||
@@ -64,7 +67,6 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
|
||||
|
||||
private readonly PlaylistItem? initialItem;
|
||||
private readonly FreeModSelectOverlay freeModSelect;
|
||||
private FooterButton freeModsFooterButton = null!;
|
||||
|
||||
private IDisposable? freeModSelectOverlayRegistration;
|
||||
|
||||
@@ -80,6 +82,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
|
||||
initialItem = itemToEdit ?? room.Playlist.LastOrDefault();
|
||||
|
||||
Padding = new MarginPadding { Horizontal = HORIZONTAL_OVERFLOW_PADDING };
|
||||
LeftPadding = new MarginPadding { Top = CORNER_RADIUS_HIDE_OFFSET + Header.HEIGHT };
|
||||
|
||||
freeModSelect = new FreeModSelectOverlay
|
||||
{
|
||||
@@ -91,10 +94,11 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
LeftArea.Padding = new MarginPadding { Top = Header.HEIGHT };
|
||||
|
||||
LoadComponent(freeModSelect);
|
||||
AddInternal(loadingLayer = new LoadingLayer(true));
|
||||
AddInternal(loadingLayer = new LoadingLayer(true)
|
||||
{
|
||||
BlockNonPositionalInput = true,
|
||||
});
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
@@ -138,7 +142,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
|
||||
Ruleset.BindValueChanged(onRulesetChanged);
|
||||
freestyle.BindValueChanged(onFreestyleChanged);
|
||||
|
||||
freeModSelectOverlayRegistration = OverlayManager?.RegisterBlockingOverlay(freeModSelect);
|
||||
freeModSelectOverlayRegistration = overlayManager?.RegisterBlockingOverlay(freeModSelect);
|
||||
|
||||
updateFooterButtons();
|
||||
updateValidMods();
|
||||
@@ -153,8 +157,6 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
|
||||
}, true);
|
||||
}
|
||||
|
||||
protected override BeatmapDetailArea CreateBeatmapDetailArea() => new PlayBeatmapDetailArea();
|
||||
|
||||
private void onFreestyleChanged(ValueChangedEvent<bool> enabled)
|
||||
{
|
||||
updateFooterButtons();
|
||||
@@ -184,12 +186,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
|
||||
private void updateFooterButtons()
|
||||
{
|
||||
if (freestyle.Value)
|
||||
{
|
||||
freeModsFooterButton.Enabled.Value = false;
|
||||
freeModSelect.Hide();
|
||||
}
|
||||
else
|
||||
freeModsFooterButton.Enabled.Value = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -206,11 +203,11 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
|
||||
if (!validFreeMods.SequenceEqual(FreeMods.Value))
|
||||
FreeMods.Value = validFreeMods;
|
||||
|
||||
ModSelect.IsValidMod = isValidRequiredMod;
|
||||
modSelect.IsValidMod = isValidRequiredMod;
|
||||
freeModSelect.IsValidMod = isValidAllowedMod;
|
||||
}
|
||||
|
||||
protected sealed override bool OnStart()
|
||||
protected sealed override void OnStart()
|
||||
{
|
||||
var item = new PlaylistItem(Beatmap.Value.BeatmapInfo)
|
||||
{
|
||||
@@ -220,7 +217,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
|
||||
Freestyle = freestyle.Value
|
||||
};
|
||||
|
||||
return selectItem(item);
|
||||
selectItem(item);
|
||||
}
|
||||
|
||||
private bool selectItem(PlaylistItem item)
|
||||
@@ -263,11 +260,6 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
|
||||
}, onError: _ =>
|
||||
{
|
||||
selectionOperation.Dispose();
|
||||
|
||||
Schedule(() =>
|
||||
{
|
||||
Carousel.AllowSelection = true;
|
||||
});
|
||||
});
|
||||
}
|
||||
else
|
||||
@@ -296,31 +288,31 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
|
||||
return base.OnExiting(e);
|
||||
}
|
||||
|
||||
protected override ModSelectOverlay CreateModSelectOverlay() => new UserModSelectOverlay(OverlayColourScheme.Plum)
|
||||
protected override ModSelectOverlay CreateModSelectOverlay() => modSelect = new UserModSelectOverlay(OverlayColourScheme.Plum)
|
||||
{
|
||||
IsValidMod = isValidRequiredMod
|
||||
};
|
||||
|
||||
protected override IEnumerable<(FooterButton button, OverlayContainer? overlay)> CreateSongSelectFooterButtons()
|
||||
public override IReadOnlyList<ScreenFooterButton> CreateFooterButtons()
|
||||
{
|
||||
var baseButtons = base.CreateSongSelectFooterButtons().ToList();
|
||||
var buttons = base.CreateFooterButtons().ToList();
|
||||
|
||||
baseButtons.Single(i => i.button is FooterButtonMods).button.TooltipText = MultiplayerMatchStrings.RequiredModsButtonTooltip;
|
||||
buttons.Single(i => i is FooterButtonMods).TooltipText = MultiplayerMatchStrings.RequiredModsButtonTooltip;
|
||||
|
||||
baseButtons.InsertRange(baseButtons.FindIndex(b => b.button is FooterButtonMods) + 1, new (FooterButton, OverlayContainer?)[]
|
||||
{
|
||||
(freeModsFooterButton = new FooterButtonFreeMods(freeModSelect)
|
||||
buttons.InsertRange(buttons.FindIndex(b => b is FooterButtonMods) + 1,
|
||||
[
|
||||
new FooterButtonFreeMods(freeModSelect)
|
||||
{
|
||||
FreeMods = { BindTarget = FreeMods },
|
||||
Freestyle = { BindTarget = freestyle }
|
||||
}, null),
|
||||
(new FooterButtonFreestyle
|
||||
},
|
||||
new FooterButtonFreestyle
|
||||
{
|
||||
Freestyle = { BindTarget = freestyle }
|
||||
}, null)
|
||||
});
|
||||
}
|
||||
]);
|
||||
|
||||
return baseButtons;
|
||||
return buttons;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -211,12 +211,12 @@ namespace osu.Game.Screens.OnlinePlay.Playlists
|
||||
|
||||
buttons.InsertRange(buttons.FindIndex(b => b is FooterButtonMods) + 1,
|
||||
[
|
||||
new FooterButtonFreeModsV2(freeModSelect)
|
||||
new FooterButtonFreeMods(freeModSelect)
|
||||
{
|
||||
FreeMods = { BindTarget = freeMods },
|
||||
Freestyle = { BindTarget = Freestyle }
|
||||
},
|
||||
new FooterButtonFreestyleV2
|
||||
new FooterButtonFreestyle
|
||||
{
|
||||
Freestyle = { BindTarget = Freestyle }
|
||||
}
|
||||
|
||||
@@ -578,7 +578,7 @@ namespace osu.Game.Screens.SelectV2
|
||||
/// </summary>
|
||||
/// <param name="beatmap">The beatmap which should be selected. If not provided, the current globally selected beatmap will be used.</param>
|
||||
/// <param name="startAction">The action to perform if conditions are met to be able to proceed. May not be invoked if in an invalid state.</param>
|
||||
public void SelectAndRun(BeatmapInfo beatmap, Action startAction)
|
||||
protected void SelectAndRun(BeatmapInfo beatmap, Action startAction)
|
||||
{
|
||||
if (!this.IsCurrentScreen())
|
||||
return;
|
||||
@@ -773,11 +773,20 @@ namespace osu.Game.Screens.SelectV2
|
||||
beginLooping();
|
||||
}
|
||||
|
||||
Beatmap.BindValueChanged(updateVariousState, true);
|
||||
}
|
||||
|
||||
private void updateVariousState(ValueChangedEvent<WorkingBeatmap> e)
|
||||
{
|
||||
if (!this.IsCurrentScreen())
|
||||
return;
|
||||
|
||||
ensureGlobalBeatmapValid();
|
||||
|
||||
ensurePlayingSelected();
|
||||
updateBackgroundDim();
|
||||
fetchOnlineInfo(force: true);
|
||||
updateWedgeVisibility();
|
||||
fetchOnlineInfo(force: ReferenceEquals(e.OldValue, e.NewValue));
|
||||
}
|
||||
|
||||
private void onLeavingScreen()
|
||||
@@ -787,6 +796,8 @@ namespace osu.Game.Screens.SelectV2
|
||||
if (manageCollectionsDialog?.FilteredBeatmapsProvider == getFilteredBeatmaps)
|
||||
manageCollectionsDialog.FilteredBeatmapsProvider = null;
|
||||
|
||||
Beatmap.ValueChanged -= updateVariousState;
|
||||
|
||||
modSelectOverlay.SelectedMods.UnbindFrom(Mods);
|
||||
modSelectOverlay.Beatmap.UnbindFrom(Beatmap);
|
||||
|
||||
|
||||
@@ -165,11 +165,11 @@ namespace osu.Game.Skinning
|
||||
|
||||
var userSkins = realm.All<SkinInfo>()
|
||||
.Where(s => !s.DeletePending && !s.Protected)
|
||||
.OrderBy(s => s.Name, StringComparer.OrdinalIgnoreCase)
|
||||
.AsEnumerable()
|
||||
.OrderBy(s => s.Name, StringComparer.OrdinalIgnoreCase)
|
||||
.Select(s => s.ToLive(Realm));
|
||||
|
||||
foreach (var s in userSkins.Where(s => !s.Value.Protected))
|
||||
foreach (var s in userSkins)
|
||||
skins.Add(s);
|
||||
});
|
||||
|
||||
|
||||
@@ -43,28 +43,29 @@ namespace osu.Game.Tests.Visual
|
||||
base.Content.AddRange(new Drawable[]
|
||||
{
|
||||
backReceptor = new ScreenFooter.BackReceptor(),
|
||||
Stack = new OsuScreenStack
|
||||
{
|
||||
Name = nameof(ScreenTestScene),
|
||||
RelativeSizeAxes = Axes.Both
|
||||
},
|
||||
new PopoverContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
Stack = new OsuScreenStack
|
||||
{
|
||||
Name = nameof(ScreenTestScene),
|
||||
RelativeSizeAxes = Axes.Both
|
||||
},
|
||||
// TODO: is this ever used? it probably shouldn't be.
|
||||
content = new Container { RelativeSizeAxes = Axes.Both },
|
||||
overlayContent = new Container
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Child = DialogOverlay = new DialogOverlay()
|
||||
},
|
||||
screenStackFooter = new ScreenStackFooter(Stack, backReceptor)
|
||||
{
|
||||
BackButtonPressed = () => Stack.Exit()
|
||||
BackButtonPressed = BackButtonPressed,
|
||||
}
|
||||
}
|
||||
},
|
||||
overlayContent = new Container
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Child = DialogOverlay = new DialogOverlay()
|
||||
},
|
||||
});
|
||||
|
||||
ScreenFooter = screenStackFooter.Footer;
|
||||
@@ -73,6 +74,8 @@ namespace osu.Game.Tests.Visual
|
||||
Stack.ScreenExited += (_, newScreen) => Logger.Log($"{nameof(ScreenTestScene)} screen changed ← {newScreen}");
|
||||
}
|
||||
|
||||
protected virtual void BackButtonPressed() => Stack.Exit();
|
||||
|
||||
protected void LoadScreen(OsuScreen screen) => Stack.Push(screen);
|
||||
|
||||
[SetUpSteps]
|
||||
|
||||
37
osu.Game/Utils/FilesystemSanityCheckHelpers.cs
Normal file
37
osu.Game/Utils/FilesystemSanityCheckHelpers.cs
Normal file
@@ -0,0 +1,37 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using System.IO;
|
||||
|
||||
namespace osu.Game.Utils
|
||||
{
|
||||
public static class FilesystemSanityCheckHelpers
|
||||
{
|
||||
/// <summary>
|
||||
/// Returns whether <paramref name="path"/> is potentially susceptible to path traversal style attacks.
|
||||
/// </summary>
|
||||
public static bool IncursPathTraversalRisk(string path)
|
||||
=> path.Contains("../", StringComparison.Ordinal) || path.Contains("..\\", StringComparison.Ordinal) || Path.IsPathRooted(path);
|
||||
|
||||
/// <summary>
|
||||
/// Returns whether <paramref name="child"/> is a subdirectory (direct or nested) of <paramref name="parent"/>.
|
||||
/// </summary>
|
||||
public static bool IsSubDirectory(string parent, string child)
|
||||
{
|
||||
// `Path.GetFullPath()` invocations are required to fully resolve the paths to unambiguous downwards-traversal-only paths.
|
||||
var parentInfo = new DirectoryInfo(Path.GetFullPath(parent));
|
||||
var childInfo = new DirectoryInfo(Path.GetFullPath(child));
|
||||
|
||||
while (childInfo != null)
|
||||
{
|
||||
if (parentInfo.FullName == childInfo.FullName)
|
||||
return true;
|
||||
|
||||
childInfo = childInfo.Parent;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup Label="Project">
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<OutputType>Library</OutputType>
|
||||
|
||||
@@ -17,6 +17,6 @@
|
||||
<MtouchInterpreter>-all</MtouchInterpreter>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="ppy.osu.Framework.iOS" Version="2026.209.0" />
|
||||
<PackageReference Include="ppy.osu.Framework.iOS" Version="2026.303.0" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
||||
Reference in New Issue
Block a user