mirror of
https://github.com/ppy/osu.git
synced 2025-02-22 20:12:56 +08:00
Merge branch 'master' into bss/the-actual-submission
This commit is contained in:
commit
6335228fb0
@ -173,7 +173,7 @@ namespace osu.Desktop
|
||||
new Button
|
||||
{
|
||||
Label = "View beatmap",
|
||||
Url = $@"{api.EndpointConfiguration.WebsiteRootUrl}/beatmaps/{beatmapId}?mode={ruleset.Value.ShortName}"
|
||||
Url = $@"{api.Endpoints.WebsiteUrl}/beatmaps/{beatmapId}?mode={ruleset.Value.ShortName}"
|
||||
}
|
||||
};
|
||||
}
|
||||
|
@ -74,7 +74,6 @@ namespace osu.Game.Tests.Visual.Editing
|
||||
}
|
||||
|
||||
[Test]
|
||||
[Solo]
|
||||
public void TestCommitPlacementViaRightClick()
|
||||
{
|
||||
Playfield playfield = null!;
|
||||
|
@ -16,6 +16,7 @@ using osu.Game.Online.API;
|
||||
using osu.Game.Online.Rooms;
|
||||
using osu.Game.Online.Solo;
|
||||
using osu.Game.Rulesets;
|
||||
using osu.Game.Rulesets.Judgements;
|
||||
using osu.Game.Rulesets.Mania;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
@ -234,6 +235,31 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
AddAssert("ensure no submission", () => Player.SubmittedScore == null);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestNoSubmissionWhenScoreZero()
|
||||
{
|
||||
prepareTestAPI(true);
|
||||
|
||||
createPlayerTest();
|
||||
|
||||
AddUntilStep("wait for token request", () => Player.TokenCreationRequested);
|
||||
|
||||
AddUntilStep("wait for track to start running", () => Beatmap.Value.Track.IsRunning);
|
||||
AddUntilStep("wait for first result", () => Player.Results.Count > 0);
|
||||
|
||||
AddStep("add fake non-scoring hit", () =>
|
||||
{
|
||||
Player.ScoreProcessor.RevertResult(Player.Results.First());
|
||||
Player.ScoreProcessor.ApplyResult(new OsuJudgementResult(Beatmap.Value.Beatmap.HitObjects.First(), new IgnoreJudgement())
|
||||
{
|
||||
Type = HitResult.IgnoreHit,
|
||||
});
|
||||
});
|
||||
|
||||
AddStep("exit", () => Player.Exit());
|
||||
AddAssert("ensure no submission", () => Player.SubmittedScore == null);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestSubmissionOnExit()
|
||||
{
|
||||
|
@ -80,7 +80,7 @@ namespace osu.Game.Tests.Visual.Menus
|
||||
new APIMenuImage
|
||||
{
|
||||
Image = @"https://assets.ppy.sh/main-menu/project-loved-2@2x.png",
|
||||
Url = $@"{API.EndpointConfiguration.WebsiteRootUrl}/home/news/2023-12-21-project-loved-december-2023",
|
||||
Url = $@"{API.Endpoints.WebsiteUrl}/home/news/2023-12-21-project-loved-december-2023",
|
||||
}
|
||||
}
|
||||
});
|
||||
|
@ -165,7 +165,6 @@ namespace osu.Game.Tests.Visual.Navigation
|
||||
}
|
||||
|
||||
[Test]
|
||||
[Solo]
|
||||
public void TestEditorGameplayTestAlwaysUsesOriginalRuleset()
|
||||
{
|
||||
prepareBeatmap();
|
||||
|
@ -67,19 +67,19 @@ namespace osu.Game.Tests.Visual.Online
|
||||
[Test]
|
||||
public void TestLink()
|
||||
{
|
||||
AddStep("set current path", () => markdownContainer.CurrentPath = $"{API.EndpointConfiguration.WebsiteRootUrl}/wiki/Article_styling_criteria/");
|
||||
AddStep("set current path", () => markdownContainer.CurrentPath = $"{API.Endpoints.WebsiteUrl}/wiki/Article_styling_criteria/");
|
||||
|
||||
AddStep("set '/wiki/Main_page''", () => markdownContainer.Text = "[wiki main page](/wiki/Main_page)");
|
||||
AddAssert("check url", () => markdownContainer.Link.Url == $"{API.EndpointConfiguration.WebsiteRootUrl}/wiki/Main_page");
|
||||
AddAssert("check url", () => markdownContainer.Link.Url == $"{API.Endpoints.WebsiteUrl}/wiki/Main_page");
|
||||
|
||||
AddStep("set '../FAQ''", () => markdownContainer.Text = "[FAQ](../FAQ)");
|
||||
AddAssert("check url", () => markdownContainer.Link.Url == $"{API.EndpointConfiguration.WebsiteRootUrl}/wiki/FAQ");
|
||||
AddAssert("check url", () => markdownContainer.Link.Url == $"{API.Endpoints.WebsiteUrl}/wiki/FAQ");
|
||||
|
||||
AddStep("set './Writing''", () => markdownContainer.Text = "[wiki writing guidline](./Writing)");
|
||||
AddAssert("check url", () => markdownContainer.Link.Url == $"{API.EndpointConfiguration.WebsiteRootUrl}/wiki/Article_styling_criteria/Writing");
|
||||
AddAssert("check url", () => markdownContainer.Link.Url == $"{API.Endpoints.WebsiteUrl}/wiki/Article_styling_criteria/Writing");
|
||||
|
||||
AddStep("set 'Formatting''", () => markdownContainer.Text = "[wiki formatting guidline](Formatting)");
|
||||
AddAssert("check url", () => markdownContainer.Link.Url == $"{API.EndpointConfiguration.WebsiteRootUrl}/wiki/Article_styling_criteria/Formatting");
|
||||
AddAssert("check url", () => markdownContainer.Link.Url == $"{API.Endpoints.WebsiteUrl}/wiki/Article_styling_criteria/Formatting");
|
||||
}
|
||||
|
||||
[Test]
|
||||
|
@ -62,12 +62,6 @@ namespace osu.Game.Tests.Visual.Ranking
|
||||
if (beatmapInfo != null)
|
||||
Beatmap.Value = beatmaps.GetWorkingBeatmap(beatmapInfo);
|
||||
});
|
||||
|
||||
AddToggleStep("toggle legacy classic skin", v =>
|
||||
{
|
||||
if (skins != null)
|
||||
skins.CurrentSkinInfo.Value = v ? skins.DefaultClassicSkin.SkinInfo : skins.CurrentSkinInfo.Default;
|
||||
});
|
||||
}
|
||||
|
||||
[SetUp]
|
||||
@ -84,6 +78,16 @@ namespace osu.Game.Tests.Visual.Ranking
|
||||
}));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestLegacySkin()
|
||||
{
|
||||
AddToggleStep("toggle legacy classic skin", v =>
|
||||
{
|
||||
if (skins != null)
|
||||
skins.CurrentSkinInfo.Value = v ? skins.DefaultClassicSkin.SkinInfo : skins.CurrentSkinInfo.Default;
|
||||
});
|
||||
}
|
||||
|
||||
private int onlineScoreID = 1;
|
||||
|
||||
[TestCase(1, ScoreRank.X, 0)]
|
||||
|
@ -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;
|
||||
@ -16,10 +18,10 @@ using osu.Game.Database;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Graphics.Containers;
|
||||
using osu.Game.Screens.Select;
|
||||
using osu.Game.Screens.Select.Filter;
|
||||
using osu.Game.Screens.SelectV2;
|
||||
using osu.Game.Tests.Beatmaps;
|
||||
using osu.Game.Tests.Resources;
|
||||
using osuTK;
|
||||
using osuTK.Graphics;
|
||||
using osuTK.Input;
|
||||
using BeatmapCarousel = osu.Game.Screens.SelectV2.BeatmapCarousel;
|
||||
@ -53,16 +55,6 @@ namespace osu.Game.Tests.Visual.SongSelect
|
||||
Scheduler.AddDelayed(updateStats, 100, true);
|
||||
}
|
||||
|
||||
[SetUpSteps]
|
||||
public virtual void SetUpSteps()
|
||||
{
|
||||
RemoveAllBeatmaps();
|
||||
|
||||
CreateCarousel();
|
||||
|
||||
SortBy(new FilterCriteria { Sort = SortMode.Title });
|
||||
}
|
||||
|
||||
protected void CreateCarousel()
|
||||
{
|
||||
AddStep("create components", () =>
|
||||
@ -146,6 +138,9 @@ namespace osu.Game.Tests.Visual.SongSelect
|
||||
protected void CheckNoSelection() => AddAssert("has no selection", () => Carousel.CurrentSelection, () => Is.Null);
|
||||
protected void CheckHasSelection() => AddAssert("has selection", () => Carousel.CurrentSelection, () => Is.Not.Null);
|
||||
|
||||
protected BeatmapPanel? GetSelectedPanel() => Carousel.ChildrenOfType<BeatmapPanel>().SingleOrDefault(p => p.Selected.Value);
|
||||
protected GroupPanel? GetKeyboardSelectedPanel() => Carousel.ChildrenOfType<GroupPanel>().SingleOrDefault(p => p.KeyboardSelected.Value);
|
||||
|
||||
protected void WaitForGroupSelection(int group, int panel)
|
||||
{
|
||||
AddUntilStep($"selected is group{group} panel{panel}", () =>
|
||||
@ -171,6 +166,15 @@ namespace osu.Game.Tests.Visual.SongSelect
|
||||
});
|
||||
}
|
||||
|
||||
protected IEnumerable<T> GetVisiblePanels<T>()
|
||||
where T : Drawable
|
||||
{
|
||||
return Carousel.ChildrenOfType<UserTrackingScrollContainer>().Single()
|
||||
.ChildrenOfType<T>()
|
||||
.Where(p => ((ICarouselPanel)p).Item?.IsVisible == true)
|
||||
.OrderBy(p => p.Y);
|
||||
}
|
||||
|
||||
protected void ClickVisiblePanel<T>(int index)
|
||||
where T : Drawable
|
||||
{
|
||||
@ -185,17 +189,63 @@ namespace osu.Game.Tests.Visual.SongSelect
|
||||
});
|
||||
}
|
||||
|
||||
protected void ClickVisiblePanelWithOffset<T>(int index, Vector2 positionOffsetFromCentre)
|
||||
where T : Drawable
|
||||
{
|
||||
AddStep($"move mouse to panel {index} with offset {positionOffsetFromCentre}", () =>
|
||||
{
|
||||
var panel = Carousel.ChildrenOfType<UserTrackingScrollContainer>().Single()
|
||||
.ChildrenOfType<T>()
|
||||
.Where(p => ((ICarouselPanel)p).Item?.IsVisible == true)
|
||||
.OrderBy(p => p.Y)
|
||||
.ElementAt(index);
|
||||
|
||||
InputManager.MoveMouseTo(panel.ScreenSpaceDrawQuad.Centre + panel.ToScreenSpace(positionOffsetFromCentre) - panel.ToScreenSpace(Vector2.Zero));
|
||||
});
|
||||
|
||||
AddStep("click", () => InputManager.Click(MouseButton.Left));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Add requested beatmap sets count to list.
|
||||
/// </summary>
|
||||
/// <param name="count">The count of beatmap sets to add.</param>
|
||||
/// <param name="fixedDifficultiesPerSet">If not null, the number of difficulties per set. If null, randomised difficulty count will be used.</param>
|
||||
protected void AddBeatmaps(int count, int? fixedDifficultiesPerSet = null) => AddStep($"add {count} beatmaps", () =>
|
||||
/// <param name="randomMetadata">Whether to randomise the metadata to make groupings more uniform.</param>
|
||||
protected void AddBeatmaps(int count, int? fixedDifficultiesPerSet = null, bool randomMetadata = false) => AddStep($"add {count} beatmaps{(randomMetadata ? " with random data" : "")}", () =>
|
||||
{
|
||||
for (int i = 0; i < count; i++)
|
||||
BeatmapSets.Add(TestResources.CreateTestBeatmapSetInfo(fixedDifficultiesPerSet ?? RNG.Next(1, 4)));
|
||||
{
|
||||
var beatmapSetInfo = TestResources.CreateTestBeatmapSetInfo(fixedDifficultiesPerSet ?? RNG.Next(1, 4));
|
||||
|
||||
if (randomMetadata)
|
||||
{
|
||||
char randomCharacter = getRandomCharacter();
|
||||
|
||||
var metadata = new BeatmapMetadata
|
||||
{
|
||||
// Create random metadata, then we can check if sorting works based on these
|
||||
Artist = $"{randomCharacter}ome Artist " + RNG.Next(0, 9),
|
||||
Title = $"{randomCharacter}ome Song (set id {beatmapSetInfo.OnlineID:000}) {Guid.NewGuid()}",
|
||||
Author = { Username = $"{randomCharacter}ome Guy " + RNG.Next(0, 9) },
|
||||
};
|
||||
|
||||
foreach (var beatmap in beatmapSetInfo.Beatmaps)
|
||||
beatmap.Metadata = metadata.DeepClone();
|
||||
}
|
||||
|
||||
BeatmapSets.Add(beatmapSetInfo);
|
||||
}
|
||||
});
|
||||
|
||||
private static long randomCharPointer;
|
||||
|
||||
private static char getRandomCharacter()
|
||||
{
|
||||
const string chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz*";
|
||||
return chars[(int)((randomCharPointer++ / 2) % chars.Length)];
|
||||
}
|
||||
|
||||
protected void RemoveAllBeatmaps() => AddStep("clear all beatmaps", () => BeatmapSets.Clear());
|
||||
|
||||
protected void RemoveFirstBeatmap() =>
|
||||
|
@ -2,100 +2,65 @@
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Graphics.Primitives;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Framework.Utils;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Screens.Select;
|
||||
using osu.Game.Screens.Select.Filter;
|
||||
using osu.Game.Screens.SelectV2;
|
||||
using osu.Game.Tests.Resources;
|
||||
|
||||
namespace osu.Game.Tests.Visual.SongSelect
|
||||
{
|
||||
/// <summary>
|
||||
/// Currently covers adding and removing of items and scrolling.
|
||||
/// If we add more tests here, these two categories can likely be split out into separate scenes.
|
||||
/// Covers common steps which can be used for manual testing.
|
||||
/// </summary>
|
||||
[TestFixture]
|
||||
public partial class TestSceneBeatmapCarouselV2Basics : BeatmapCarouselV2TestScene
|
||||
public partial class TestSceneBeatmapCarouselV2 : BeatmapCarouselV2TestScene
|
||||
{
|
||||
[Test]
|
||||
[Explicit]
|
||||
public void TestBasics()
|
||||
{
|
||||
AddBeatmaps(1);
|
||||
CreateCarousel();
|
||||
RemoveAllBeatmaps();
|
||||
|
||||
AddBeatmaps(10, randomMetadata: true);
|
||||
AddBeatmaps(10);
|
||||
AddBeatmaps(1);
|
||||
}
|
||||
|
||||
[Test]
|
||||
[Explicit]
|
||||
public void TestSorting()
|
||||
{
|
||||
SortBy(new FilterCriteria { Sort = SortMode.Artist });
|
||||
SortBy(new FilterCriteria { Group = GroupMode.Difficulty, Sort = SortMode.Difficulty });
|
||||
SortBy(new FilterCriteria { Group = GroupMode.Artist, Sort = SortMode.Artist });
|
||||
}
|
||||
|
||||
[Test]
|
||||
[Explicit]
|
||||
public void TestRemovals()
|
||||
{
|
||||
RemoveFirstBeatmap();
|
||||
RemoveAllBeatmaps();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestOffScreenLoading()
|
||||
{
|
||||
AddStep("disable masking", () => Scroll.Masking = false);
|
||||
AddStep("enable masking", () => Scroll.Masking = true);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestAddRemoveOneByOne()
|
||||
[Explicit]
|
||||
public void TestAddRemoveRepeatedOps()
|
||||
{
|
||||
AddRepeatStep("add beatmaps", () => BeatmapSets.Add(TestResources.CreateTestBeatmapSetInfo(RNG.Next(1, 4))), 20);
|
||||
AddRepeatStep("remove beatmaps", () => BeatmapSets.RemoveAt(RNG.Next(0, BeatmapSets.Count)), 20);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestSorting()
|
||||
[Explicit]
|
||||
public void TestMasking()
|
||||
{
|
||||
AddBeatmaps(10);
|
||||
SortBy(new FilterCriteria { Group = GroupMode.Difficulty, Sort = SortMode.Difficulty });
|
||||
SortBy(new FilterCriteria { Sort = SortMode.Artist });
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestScrollPositionMaintainedOnAddSecondSelected()
|
||||
{
|
||||
Quad positionBefore = default;
|
||||
|
||||
AddBeatmaps(10);
|
||||
WaitForDrawablePanels();
|
||||
|
||||
AddStep("select middle beatmap", () => Carousel.CurrentSelection = BeatmapSets.ElementAt(BeatmapSets.Count - 2));
|
||||
AddStep("scroll to selected item", () => Scroll.ScrollTo(Scroll.ChildrenOfType<BeatmapPanel>().Single(p => p.Selected.Value)));
|
||||
|
||||
WaitForScrolling();
|
||||
|
||||
AddStep("save selected screen position", () => positionBefore = Carousel.ChildrenOfType<BeatmapPanel>().FirstOrDefault(p => p.Selected.Value)!.ScreenSpaceDrawQuad);
|
||||
|
||||
RemoveFirstBeatmap();
|
||||
WaitForSorting();
|
||||
|
||||
AddAssert("select screen position unchanged", () => Carousel.ChildrenOfType<BeatmapPanel>().Single(p => p.Selected.Value).ScreenSpaceDrawQuad,
|
||||
() => Is.EqualTo(positionBefore));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestScrollPositionMaintainedOnAddLastSelected()
|
||||
{
|
||||
Quad positionBefore = default;
|
||||
|
||||
AddBeatmaps(10);
|
||||
WaitForDrawablePanels();
|
||||
|
||||
AddStep("scroll to last item", () => Scroll.ScrollToEnd(false));
|
||||
|
||||
AddStep("select last beatmap", () => Carousel.CurrentSelection = BeatmapSets.Last());
|
||||
|
||||
WaitForScrolling();
|
||||
|
||||
AddStep("save selected screen position", () => positionBefore = Carousel.ChildrenOfType<BeatmapPanel>().FirstOrDefault(p => p.Selected.Value)!.ScreenSpaceDrawQuad);
|
||||
|
||||
RemoveFirstBeatmap();
|
||||
WaitForSorting();
|
||||
AddAssert("select screen position unchanged", () => Carousel.ChildrenOfType<BeatmapPanel>().Single(p => p.Selected.Value).ScreenSpaceDrawQuad,
|
||||
() => Is.EqualTo(positionBefore));
|
||||
AddStep("disable masking", () => Scroll.Masking = false);
|
||||
AddStep("enable masking", () => Scroll.Masking = true);
|
||||
}
|
||||
|
||||
[Test]
|
@ -0,0 +1,177 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Screens.Select;
|
||||
using osu.Game.Screens.Select.Filter;
|
||||
using osu.Game.Screens.SelectV2;
|
||||
|
||||
namespace osu.Game.Tests.Visual.SongSelect
|
||||
{
|
||||
[TestFixture]
|
||||
public partial class TestSceneBeatmapCarouselV2ArtistGrouping : BeatmapCarouselV2TestScene
|
||||
{
|
||||
[SetUpSteps]
|
||||
public void SetUpSteps()
|
||||
{
|
||||
RemoveAllBeatmaps();
|
||||
CreateCarousel();
|
||||
SortBy(new FilterCriteria { Group = GroupMode.Artist, Sort = SortMode.Artist });
|
||||
|
||||
AddBeatmaps(10, 3, true);
|
||||
WaitForDrawablePanels();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestOpenCloseGroupWithNoSelectionMouse()
|
||||
{
|
||||
AddAssert("no beatmaps visible", () => Carousel.ChildrenOfType<BeatmapPanel>().Count(p => p.Alpha > 0), () => Is.Zero);
|
||||
AddUntilStep("no sets visible", () => Carousel.ChildrenOfType<BeatmapSetPanel>().Count(p => p.Alpha > 0), () => Is.Zero);
|
||||
CheckNoSelection();
|
||||
|
||||
ClickVisiblePanel<GroupPanel>(0);
|
||||
|
||||
AddUntilStep("some sets visible", () => Carousel.ChildrenOfType<BeatmapSetPanel>().Count(p => p.Alpha > 0), () => Is.GreaterThan(0));
|
||||
AddUntilStep("no beatmaps visible", () => Carousel.ChildrenOfType<BeatmapPanel>().Count(p => p.Alpha > 0), () => Is.Zero);
|
||||
CheckNoSelection();
|
||||
|
||||
ClickVisiblePanel<GroupPanel>(0);
|
||||
|
||||
AddUntilStep("no sets visible", () => Carousel.ChildrenOfType<BeatmapSetPanel>().Count(p => p.Alpha > 0), () => Is.Zero);
|
||||
AddUntilStep("no beatmaps visible", () => Carousel.ChildrenOfType<BeatmapPanel>().Count(p => p.Alpha > 0), () => Is.Zero);
|
||||
CheckNoSelection();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestOpenCloseGroupWithNoSelectionKeyboard()
|
||||
{
|
||||
AddAssert("no beatmaps visible", () => Carousel.ChildrenOfType<BeatmapPanel>().Count(p => p.Alpha > 0), () => Is.Zero);
|
||||
AddUntilStep("no sets visible", () => Carousel.ChildrenOfType<BeatmapSetPanel>().Count(p => p.Alpha > 0), () => Is.Zero);
|
||||
CheckNoSelection();
|
||||
|
||||
SelectNextPanel();
|
||||
Select();
|
||||
|
||||
AddUntilStep("some sets visible", () => Carousel.ChildrenOfType<BeatmapSetPanel>().Count(p => p.Alpha > 0), () => Is.GreaterThan(0));
|
||||
AddUntilStep("no beatmaps visible", () => Carousel.ChildrenOfType<BeatmapPanel>().Count(p => p.Alpha > 0), () => Is.Zero);
|
||||
AddAssert("keyboard selected is expanded", () => GetKeyboardSelectedPanel()?.Expanded.Value, () => Is.True);
|
||||
CheckNoSelection();
|
||||
|
||||
Select();
|
||||
|
||||
AddUntilStep("no sets visible", () => Carousel.ChildrenOfType<BeatmapSetPanel>().Count(p => p.Alpha > 0), () => Is.Zero);
|
||||
AddUntilStep("no beatmaps visible", () => Carousel.ChildrenOfType<BeatmapPanel>().Count(p => p.Alpha > 0), () => Is.Zero);
|
||||
AddAssert("keyboard selected is collapsed", () => GetKeyboardSelectedPanel()?.Expanded.Value, () => Is.False);
|
||||
CheckNoSelection();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestCarouselRemembersSelection()
|
||||
{
|
||||
SelectNextGroup();
|
||||
|
||||
object? selection = null;
|
||||
|
||||
AddStep("store drawable selection", () => selection = GetSelectedPanel()?.Item?.Model);
|
||||
|
||||
CheckHasSelection();
|
||||
AddAssert("drawable selection non-null", () => selection, () => Is.Not.Null);
|
||||
AddAssert("drawable selection matches carousel selection", () => selection, () => Is.EqualTo(Carousel.CurrentSelection));
|
||||
|
||||
RemoveAllBeatmaps();
|
||||
AddUntilStep("no drawable selection", GetSelectedPanel, () => Is.Null);
|
||||
|
||||
AddBeatmaps(10);
|
||||
WaitForDrawablePanels();
|
||||
|
||||
CheckHasSelection();
|
||||
AddAssert("no drawable selection", GetSelectedPanel, () => Is.Null);
|
||||
|
||||
AddStep("add previous selection", () => BeatmapSets.Add(((BeatmapInfo)selection!).BeatmapSet!));
|
||||
|
||||
AddAssert("selection matches original carousel selection", () => selection, () => Is.EqualTo(Carousel.CurrentSelection));
|
||||
AddUntilStep("drawable selection restored", () => GetSelectedPanel()?.Item?.Model, () => Is.EqualTo(selection));
|
||||
AddAssert("carousel item is visible", () => GetSelectedPanel()?.Item?.IsVisible, () => Is.True);
|
||||
|
||||
ClickVisiblePanel<GroupPanel>(0);
|
||||
AddUntilStep("carousel item not visible", GetSelectedPanel, () => Is.Null);
|
||||
|
||||
ClickVisiblePanel<GroupPanel>(0);
|
||||
AddUntilStep("carousel item is visible", () => GetSelectedPanel()?.Item?.IsVisible, () => Is.True);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestGroupSelectionOnHeader()
|
||||
{
|
||||
SelectNextGroup();
|
||||
WaitForGroupSelection(0, 1);
|
||||
|
||||
SelectPrevPanel();
|
||||
SelectPrevPanel();
|
||||
|
||||
AddAssert("keyboard selected panel is expanded", () => GetKeyboardSelectedPanel()?.Expanded.Value, () => Is.True);
|
||||
|
||||
SelectPrevGroup();
|
||||
|
||||
WaitForGroupSelection(0, 1);
|
||||
AddAssert("keyboard selected panel is contracted", () => GetKeyboardSelectedPanel()?.Expanded.Value, () => Is.False);
|
||||
|
||||
SelectPrevGroup();
|
||||
|
||||
WaitForGroupSelection(0, 1);
|
||||
AddAssert("keyboard selected panel is expanded", () => GetKeyboardSelectedPanel()?.Expanded.Value, () => Is.True);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestKeyboardSelection()
|
||||
{
|
||||
SelectNextPanel();
|
||||
SelectNextPanel();
|
||||
SelectNextPanel();
|
||||
SelectNextPanel();
|
||||
CheckNoSelection();
|
||||
|
||||
// open first group
|
||||
Select();
|
||||
CheckNoSelection();
|
||||
AddUntilStep("some beatmaps visible", () => Carousel.ChildrenOfType<BeatmapSetPanel>().Count(p => p.Alpha > 0), () => Is.GreaterThan(0));
|
||||
|
||||
SelectNextPanel();
|
||||
Select();
|
||||
WaitForGroupSelection(3, 1);
|
||||
|
||||
SelectNextGroup();
|
||||
WaitForGroupSelection(3, 5);
|
||||
|
||||
SelectNextGroup();
|
||||
WaitForGroupSelection(4, 1);
|
||||
|
||||
SelectPrevGroup();
|
||||
WaitForGroupSelection(3, 5);
|
||||
|
||||
SelectNextGroup();
|
||||
WaitForGroupSelection(4, 1);
|
||||
|
||||
SelectNextGroup();
|
||||
WaitForGroupSelection(4, 5);
|
||||
|
||||
SelectNextGroup();
|
||||
WaitForGroupSelection(0, 1);
|
||||
|
||||
SelectNextPanel();
|
||||
SelectNextPanel();
|
||||
SelectNextPanel();
|
||||
SelectNextPanel();
|
||||
|
||||
SelectNextGroup();
|
||||
WaitForGroupSelection(0, 1);
|
||||
|
||||
SelectNextPanel();
|
||||
SelectNextGroup();
|
||||
WaitForGroupSelection(1, 1);
|
||||
}
|
||||
}
|
||||
}
|
@ -8,27 +8,27 @@ using osu.Game.Beatmaps;
|
||||
using osu.Game.Screens.Select;
|
||||
using osu.Game.Screens.Select.Filter;
|
||||
using osu.Game.Screens.SelectV2;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Tests.Visual.SongSelect
|
||||
{
|
||||
[TestFixture]
|
||||
public partial class TestSceneBeatmapCarouselV2GroupSelection : BeatmapCarouselV2TestScene
|
||||
public partial class TestSceneBeatmapCarouselV2DifficultyGrouping : BeatmapCarouselV2TestScene
|
||||
{
|
||||
public override void SetUpSteps()
|
||||
[SetUpSteps]
|
||||
public void SetUpSteps()
|
||||
{
|
||||
RemoveAllBeatmaps();
|
||||
|
||||
CreateCarousel();
|
||||
|
||||
SortBy(new FilterCriteria { Group = GroupMode.Difficulty, Sort = SortMode.Difficulty });
|
||||
|
||||
AddBeatmaps(10, 3);
|
||||
WaitForDrawablePanels();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestOpenCloseGroupWithNoSelectionMouse()
|
||||
{
|
||||
AddBeatmaps(10, 5);
|
||||
WaitForDrawablePanels();
|
||||
|
||||
AddAssert("no beatmaps visible", () => Carousel.ChildrenOfType<BeatmapPanel>().Count(p => p.Alpha > 0), () => Is.Zero);
|
||||
CheckNoSelection();
|
||||
|
||||
@ -44,86 +44,79 @@ namespace osu.Game.Tests.Visual.SongSelect
|
||||
[Test]
|
||||
public void TestOpenCloseGroupWithNoSelectionKeyboard()
|
||||
{
|
||||
AddBeatmaps(10, 5);
|
||||
WaitForDrawablePanels();
|
||||
|
||||
AddAssert("no beatmaps visible", () => Carousel.ChildrenOfType<BeatmapPanel>().Count(p => p.Alpha > 0), () => Is.Zero);
|
||||
CheckNoSelection();
|
||||
|
||||
SelectNextPanel();
|
||||
Select();
|
||||
AddUntilStep("some beatmaps visible", () => Carousel.ChildrenOfType<BeatmapPanel>().Count(p => p.Alpha > 0), () => Is.GreaterThan(0));
|
||||
AddAssert("keyboard selected is expanded", () => getKeyboardSelectedPanel()?.Expanded.Value, () => Is.True);
|
||||
AddAssert("keyboard selected is expanded", () => GetKeyboardSelectedPanel()?.Expanded.Value, () => Is.True);
|
||||
CheckNoSelection();
|
||||
|
||||
Select();
|
||||
AddUntilStep("no beatmaps visible", () => Carousel.ChildrenOfType<BeatmapPanel>().Count(p => p.Alpha > 0), () => Is.Zero);
|
||||
AddAssert("keyboard selected is collapsed", () => getKeyboardSelectedPanel()?.Expanded.Value, () => Is.False);
|
||||
AddAssert("keyboard selected is collapsed", () => GetKeyboardSelectedPanel()?.Expanded.Value, () => Is.False);
|
||||
CheckNoSelection();
|
||||
|
||||
GroupPanel? getKeyboardSelectedPanel() => Carousel.ChildrenOfType<GroupPanel>().SingleOrDefault(p => p.KeyboardSelected.Value);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestCarouselRemembersSelection()
|
||||
{
|
||||
AddBeatmaps(10);
|
||||
WaitForDrawablePanels();
|
||||
|
||||
SelectNextGroup();
|
||||
|
||||
object? selection = null;
|
||||
|
||||
AddStep("store drawable selection", () => selection = getSelectedPanel()?.Item?.Model);
|
||||
AddStep("store drawable selection", () => selection = GetSelectedPanel()?.Item?.Model);
|
||||
|
||||
CheckHasSelection();
|
||||
AddAssert("drawable selection non-null", () => selection, () => Is.Not.Null);
|
||||
AddAssert("drawable selection matches carousel selection", () => selection, () => Is.EqualTo(Carousel.CurrentSelection));
|
||||
|
||||
RemoveAllBeatmaps();
|
||||
AddUntilStep("no drawable selection", getSelectedPanel, () => Is.Null);
|
||||
AddUntilStep("no drawable selection", GetSelectedPanel, () => Is.Null);
|
||||
|
||||
AddBeatmaps(10);
|
||||
WaitForDrawablePanels();
|
||||
|
||||
CheckHasSelection();
|
||||
AddAssert("no drawable selection", getSelectedPanel, () => Is.Null);
|
||||
AddAssert("no drawable selection", GetSelectedPanel, () => Is.Null);
|
||||
|
||||
AddStep("add previous selection", () => BeatmapSets.Add(((BeatmapInfo)selection!).BeatmapSet!));
|
||||
|
||||
AddAssert("selection matches original carousel selection", () => selection, () => Is.EqualTo(Carousel.CurrentSelection));
|
||||
AddUntilStep("drawable selection restored", () => getSelectedPanel()?.Item?.Model, () => Is.EqualTo(selection));
|
||||
AddAssert("carousel item is visible", () => getSelectedPanel()?.Item?.IsVisible, () => Is.True);
|
||||
AddUntilStep("drawable selection restored", () => GetSelectedPanel()?.Item?.Model, () => Is.EqualTo(selection));
|
||||
AddAssert("carousel item is visible", () => GetSelectedPanel()?.Item?.IsVisible, () => Is.True);
|
||||
|
||||
ClickVisiblePanel<GroupPanel>(0);
|
||||
AddUntilStep("carousel item not visible", getSelectedPanel, () => Is.Null);
|
||||
AddUntilStep("carousel item not visible", GetSelectedPanel, () => Is.Null);
|
||||
|
||||
ClickVisiblePanel<GroupPanel>(0);
|
||||
AddUntilStep("carousel item is visible", () => getSelectedPanel()?.Item?.IsVisible, () => Is.True);
|
||||
|
||||
BeatmapPanel? getSelectedPanel() => Carousel.ChildrenOfType<BeatmapPanel>().SingleOrDefault(p => p.Selected.Value);
|
||||
AddUntilStep("carousel item is visible", () => GetSelectedPanel()?.Item?.IsVisible, () => Is.True);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestGroupSelectionOnHeader()
|
||||
{
|
||||
AddBeatmaps(10, 3);
|
||||
WaitForDrawablePanels();
|
||||
|
||||
SelectNextGroup();
|
||||
WaitForGroupSelection(0, 0);
|
||||
|
||||
SelectPrevPanel();
|
||||
AddAssert("keyboard selected panel is expanded", () => GetKeyboardSelectedPanel()?.Expanded.Value, () => Is.True);
|
||||
|
||||
SelectPrevGroup();
|
||||
WaitForGroupSelection(2, 9);
|
||||
|
||||
WaitForGroupSelection(0, 0);
|
||||
AddAssert("keyboard selected panel is contracted", () => GetKeyboardSelectedPanel()?.Expanded.Value, () => Is.False);
|
||||
|
||||
SelectPrevGroup();
|
||||
|
||||
WaitForGroupSelection(0, 0);
|
||||
AddAssert("keyboard selected panel is expanded", () => GetKeyboardSelectedPanel()?.Expanded.Value, () => Is.True);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestKeyboardSelection()
|
||||
{
|
||||
AddBeatmaps(10, 3);
|
||||
WaitForDrawablePanels();
|
||||
|
||||
SelectNextPanel();
|
||||
SelectNextPanel();
|
||||
SelectNextPanel();
|
||||
@ -154,5 +147,28 @@ namespace osu.Game.Tests.Visual.SongSelect
|
||||
SelectPrevGroup();
|
||||
WaitForGroupSelection(2, 9);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestInputHandlingWithinGaps()
|
||||
{
|
||||
AddAssert("no beatmaps visible", () => !GetVisiblePanels<BeatmapPanel>().Any());
|
||||
|
||||
// Clicks just above the first group panel should not actuate any action.
|
||||
ClickVisiblePanelWithOffset<GroupPanel>(0, new Vector2(0, -(GroupPanel.HEIGHT / 2 + 1)));
|
||||
|
||||
AddAssert("no beatmaps visible", () => !GetVisiblePanels<BeatmapPanel>().Any());
|
||||
|
||||
ClickVisiblePanelWithOffset<GroupPanel>(0, new Vector2(0, -(GroupPanel.HEIGHT / 2)));
|
||||
|
||||
AddUntilStep("wait for beatmaps visible", () => GetVisiblePanels<BeatmapPanel>().Any());
|
||||
CheckNoSelection();
|
||||
|
||||
// Beatmap panels expand their selection area to cover holes from spacing.
|
||||
ClickVisiblePanelWithOffset<BeatmapPanel>(0, new Vector2(0, -(CarouselItem.DEFAULT_HEIGHT / 2 + 1)));
|
||||
WaitForGroupSelection(0, 0);
|
||||
|
||||
ClickVisiblePanelWithOffset<BeatmapPanel>(1, new Vector2(0, (CarouselItem.DEFAULT_HEIGHT / 2 + 1)));
|
||||
WaitForGroupSelection(0, 1);
|
||||
}
|
||||
}
|
||||
}
|
@ -5,14 +5,25 @@ using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Screens.Select;
|
||||
using osu.Game.Screens.Select.Filter;
|
||||
using osu.Game.Screens.SelectV2;
|
||||
using osuTK;
|
||||
using osuTK.Input;
|
||||
|
||||
namespace osu.Game.Tests.Visual.SongSelect
|
||||
{
|
||||
[TestFixture]
|
||||
public partial class TestSceneBeatmapCarouselV2Selection : BeatmapCarouselV2TestScene
|
||||
public partial class TestSceneBeatmapCarouselV2NoGrouping : BeatmapCarouselV2TestScene
|
||||
{
|
||||
[SetUpSteps]
|
||||
public void SetUpSteps()
|
||||
{
|
||||
RemoveAllBeatmaps();
|
||||
CreateCarousel();
|
||||
SortBy(new FilterCriteria { Sort = SortMode.Title });
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Keyboard selection via up and down arrows doesn't actually change the selection until
|
||||
/// the select key is pressed.
|
||||
@ -77,28 +88,26 @@ namespace osu.Game.Tests.Visual.SongSelect
|
||||
|
||||
object? selection = null;
|
||||
|
||||
AddStep("store drawable selection", () => selection = getSelectedPanel()?.Item?.Model);
|
||||
AddStep("store drawable selection", () => selection = GetSelectedPanel()?.Item?.Model);
|
||||
|
||||
CheckHasSelection();
|
||||
AddAssert("drawable selection non-null", () => selection, () => Is.Not.Null);
|
||||
AddAssert("drawable selection matches carousel selection", () => selection, () => Is.EqualTo(Carousel.CurrentSelection));
|
||||
|
||||
RemoveAllBeatmaps();
|
||||
AddUntilStep("no drawable selection", getSelectedPanel, () => Is.Null);
|
||||
AddUntilStep("no drawable selection", GetSelectedPanel, () => Is.Null);
|
||||
|
||||
AddBeatmaps(10);
|
||||
WaitForDrawablePanels();
|
||||
|
||||
CheckHasSelection();
|
||||
AddAssert("no drawable selection", getSelectedPanel, () => Is.Null);
|
||||
AddAssert("no drawable selection", GetSelectedPanel, () => Is.Null);
|
||||
|
||||
AddStep("add previous selection", () => BeatmapSets.Add(((BeatmapInfo)selection!).BeatmapSet!));
|
||||
|
||||
AddAssert("selection matches original carousel selection", () => selection, () => Is.EqualTo(Carousel.CurrentSelection));
|
||||
AddUntilStep("drawable selection restored", () => getSelectedPanel()?.Item?.Model, () => Is.EqualTo(selection));
|
||||
AddAssert("carousel item is visible", () => getSelectedPanel()?.Item?.IsVisible, () => Is.True);
|
||||
|
||||
BeatmapPanel? getSelectedPanel() => Carousel.ChildrenOfType<BeatmapPanel>().SingleOrDefault(p => p.Selected.Value);
|
||||
AddUntilStep("drawable selection restored", () => GetSelectedPanel()?.Item?.Model, () => Is.EqualTo(selection));
|
||||
AddAssert("carousel item is visible", () => GetSelectedPanel()?.Item?.IsVisible, () => Is.True);
|
||||
}
|
||||
|
||||
[Test]
|
||||
@ -141,7 +150,11 @@ namespace osu.Game.Tests.Visual.SongSelect
|
||||
|
||||
SelectPrevPanel();
|
||||
SelectPrevGroup();
|
||||
WaitForSelection(0, 0);
|
||||
WaitForSelection(1, 0);
|
||||
|
||||
SelectPrevPanel();
|
||||
SelectNextGroup();
|
||||
WaitForSelection(1, 0);
|
||||
}
|
||||
|
||||
[Test]
|
||||
@ -194,6 +207,36 @@ namespace osu.Game.Tests.Visual.SongSelect
|
||||
CheckNoSelection();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestInputHandlingWithinGaps()
|
||||
{
|
||||
AddBeatmaps(2, 5);
|
||||
WaitForDrawablePanels();
|
||||
|
||||
AddAssert("no beatmaps visible", () => !GetVisiblePanels<BeatmapPanel>().Any());
|
||||
|
||||
// Clicks just above the first group panel should not actuate any action.
|
||||
ClickVisiblePanelWithOffset<BeatmapSetPanel>(0, new Vector2(0, -(BeatmapSetPanel.HEIGHT / 2 + 1)));
|
||||
|
||||
AddAssert("no beatmaps visible", () => !GetVisiblePanels<BeatmapPanel>().Any());
|
||||
|
||||
ClickVisiblePanelWithOffset<BeatmapSetPanel>(0, new Vector2(0, -(BeatmapSetPanel.HEIGHT / 2)));
|
||||
|
||||
AddUntilStep("wait for beatmaps visible", () => GetVisiblePanels<BeatmapPanel>().Any());
|
||||
WaitForSelection(0, 0);
|
||||
|
||||
// Beatmap panels expand their selection area to cover holes from spacing.
|
||||
ClickVisiblePanelWithOffset<BeatmapPanel>(1, new Vector2(0, -(CarouselItem.DEFAULT_HEIGHT / 2 + 1)));
|
||||
WaitForSelection(0, 0);
|
||||
|
||||
// Panels with higher depth will handle clicks in the gutters for simplicity.
|
||||
ClickVisiblePanelWithOffset<BeatmapPanel>(2, new Vector2(0, (CarouselItem.DEFAULT_HEIGHT / 2 + 1)));
|
||||
WaitForSelection(0, 2);
|
||||
|
||||
ClickVisiblePanelWithOffset<BeatmapPanel>(3, new Vector2(0, (CarouselItem.DEFAULT_HEIGHT / 2 + 1)));
|
||||
WaitForSelection(0, 3);
|
||||
}
|
||||
|
||||
private void checkSelectionIterating(bool isIterating)
|
||||
{
|
||||
object? selection = null;
|
@ -0,0 +1,65 @@
|
||||
// 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.Linq;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Graphics.Primitives;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Game.Screens.Select;
|
||||
using osu.Game.Screens.SelectV2;
|
||||
|
||||
namespace osu.Game.Tests.Visual.SongSelect
|
||||
{
|
||||
[TestFixture]
|
||||
public partial class TestSceneBeatmapCarouselV2Scrolling : BeatmapCarouselV2TestScene
|
||||
{
|
||||
[SetUpSteps]
|
||||
public void SetUpSteps()
|
||||
{
|
||||
RemoveAllBeatmaps();
|
||||
CreateCarousel();
|
||||
SortBy(new FilterCriteria());
|
||||
|
||||
AddBeatmaps(10);
|
||||
WaitForDrawablePanels();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestScrollPositionMaintainedOnAddSecondSelected()
|
||||
{
|
||||
Quad positionBefore = default;
|
||||
|
||||
AddStep("select middle beatmap", () => Carousel.CurrentSelection = BeatmapSets.ElementAt(BeatmapSets.Count - 2).Beatmaps.First());
|
||||
AddStep("scroll to selected item", () => Scroll.ScrollTo(Scroll.ChildrenOfType<BeatmapPanel>().Single(p => p.Selected.Value)));
|
||||
|
||||
WaitForScrolling();
|
||||
|
||||
AddStep("save selected screen position", () => positionBefore = Carousel.ChildrenOfType<BeatmapPanel>().FirstOrDefault(p => p.Selected.Value)!.ScreenSpaceDrawQuad);
|
||||
|
||||
RemoveFirstBeatmap();
|
||||
WaitForSorting();
|
||||
|
||||
AddAssert("select screen position unchanged", () => Carousel.ChildrenOfType<BeatmapPanel>().Single(p => p.Selected.Value).ScreenSpaceDrawQuad,
|
||||
() => Is.EqualTo(positionBefore));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestScrollPositionMaintainedOnAddLastSelected()
|
||||
{
|
||||
Quad positionBefore = default;
|
||||
|
||||
AddStep("scroll to last item", () => Scroll.ScrollToEnd(false));
|
||||
|
||||
AddStep("select last beatmap", () => Carousel.CurrentSelection = BeatmapSets.Last().Beatmaps.Last());
|
||||
|
||||
WaitForScrolling();
|
||||
|
||||
AddStep("save selected screen position", () => positionBefore = Carousel.ChildrenOfType<BeatmapPanel>().FirstOrDefault(p => p.Selected.Value)!.ScreenSpaceDrawQuad);
|
||||
|
||||
RemoveFirstBeatmap();
|
||||
WaitForSorting();
|
||||
AddAssert("select screen position unchanged", () => Carousel.ChildrenOfType<BeatmapPanel>().Single(p => p.Selected.Value).ScreenSpaceDrawQuad,
|
||||
() => Is.EqualTo(positionBefore));
|
||||
}
|
||||
}
|
||||
}
|
@ -1239,7 +1239,6 @@ namespace osu.Game.Tests.Visual.SongSelect
|
||||
}
|
||||
|
||||
[Test]
|
||||
[Solo]
|
||||
public void TestHardDeleteHandledCorrectly()
|
||||
{
|
||||
createSongSelect();
|
||||
|
@ -59,7 +59,7 @@ namespace osu.Game.Beatmaps
|
||||
if (beatmapInfo.OnlineID <= 0 || beatmapInfo.BeatmapSet == null)
|
||||
return null;
|
||||
|
||||
return $@"{api.EndpointConfiguration.WebsiteRootUrl}/beatmapsets/{beatmapInfo.BeatmapSet.OnlineID}#{ruleset?.ShortName ?? beatmapInfo.Ruleset.ShortName}/{beatmapInfo.OnlineID}";
|
||||
return $@"{api.Endpoints.WebsiteUrl}/beatmapsets/{beatmapInfo.BeatmapSet.OnlineID}#{ruleset?.ShortName ?? beatmapInfo.Ruleset.ShortName}/{beatmapInfo.OnlineID}";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -41,9 +41,9 @@ namespace osu.Game.Beatmaps
|
||||
return null;
|
||||
|
||||
if (ruleset != null)
|
||||
return $@"{api.EndpointConfiguration.WebsiteRootUrl}/beatmapsets/{beatmapSetInfo.OnlineID}#{ruleset.ShortName}";
|
||||
return $@"{api.Endpoints.WebsiteUrl}/beatmapsets/{beatmapSetInfo.OnlineID}#{ruleset.ShortName}";
|
||||
|
||||
return $@"{api.EndpointConfiguration.WebsiteRootUrl}/beatmapsets/{beatmapSetInfo.OnlineID}";
|
||||
return $@"{api.Endpoints.WebsiteUrl}/beatmapsets/{beatmapSetInfo.OnlineID}";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -40,7 +40,7 @@ namespace osu.Game.Online.API
|
||||
|
||||
private readonly Queue<APIRequest> queue = new Queue<APIRequest>();
|
||||
|
||||
public EndpointConfiguration EndpointConfiguration { get; }
|
||||
public EndpointConfiguration Endpoints { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The API response version.
|
||||
@ -73,7 +73,7 @@ namespace osu.Game.Online.API
|
||||
private readonly CancellationTokenSource cancellationToken = new CancellationTokenSource();
|
||||
private readonly Logger log;
|
||||
|
||||
public APIAccess(OsuGameBase game, OsuConfigManager config, EndpointConfiguration endpointConfiguration, string versionHash)
|
||||
public APIAccess(OsuGameBase game, OsuConfigManager config, EndpointConfiguration endpoints, string versionHash)
|
||||
{
|
||||
this.game = game;
|
||||
this.config = config;
|
||||
@ -87,13 +87,13 @@ namespace osu.Game.Online.API
|
||||
APIVersion = now.Year * 10000 + now.Month * 100 + now.Day;
|
||||
}
|
||||
|
||||
EndpointConfiguration = endpointConfiguration;
|
||||
Endpoints = endpoints;
|
||||
NotificationsClient = setUpNotificationsClient();
|
||||
|
||||
authentication = new OAuth(endpointConfiguration.APIClientID, endpointConfiguration.APIClientSecret, EndpointConfiguration.APIEndpointUrl);
|
||||
authentication = new OAuth(endpoints.APIClientID, endpoints.APIClientSecret, Endpoints.APIUrl);
|
||||
|
||||
log = Logger.GetLogger(LoggingTarget.Network);
|
||||
log.Add($@"API endpoint root: {EndpointConfiguration.APIEndpointUrl}");
|
||||
log.Add($@"API endpoint root: {Endpoints.APIUrl}");
|
||||
log.Add($@"API request version: {APIVersion}");
|
||||
|
||||
ProvidedUsername = config.Get<string>(OsuSetting.Username);
|
||||
@ -405,7 +405,7 @@ namespace osu.Game.Online.API
|
||||
|
||||
var req = new RegistrationRequest
|
||||
{
|
||||
Url = $@"{EndpointConfiguration.APIEndpointUrl}/users",
|
||||
Url = $@"{Endpoints.APIUrl}/users",
|
||||
Method = HttpMethod.Post,
|
||||
Username = username,
|
||||
Email = email,
|
||||
|
@ -71,7 +71,7 @@ namespace osu.Game.Online.API
|
||||
|
||||
protected virtual WebRequest CreateWebRequest() => new OsuWebRequest(Uri);
|
||||
|
||||
protected virtual string Uri => $@"{API!.EndpointConfiguration.APIEndpointUrl}/api/v2/{Target}";
|
||||
protected virtual string Uri => $@"{API!.Endpoints.APIUrl}/api/v2/{Target}";
|
||||
|
||||
protected IAPIProvider? API;
|
||||
|
||||
|
@ -41,10 +41,10 @@ namespace osu.Game.Online.API
|
||||
|
||||
public string ProvidedUsername => LocalUser.Value.Username;
|
||||
|
||||
public EndpointConfiguration EndpointConfiguration { get; } = new EndpointConfiguration
|
||||
public EndpointConfiguration Endpoints { get; } = new EndpointConfiguration
|
||||
{
|
||||
APIEndpointUrl = "http://localhost",
|
||||
WebsiteRootUrl = "http://localhost",
|
||||
APIUrl = "http://localhost",
|
||||
WebsiteUrl = "http://localhost",
|
||||
};
|
||||
|
||||
public int APIVersion => int.Parse(DateTime.Now.ToString("yyyyMMdd"));
|
||||
|
@ -53,7 +53,7 @@ namespace osu.Game.Online.API
|
||||
/// <summary>
|
||||
/// Holds configuration for online endpoints.
|
||||
/// </summary>
|
||||
EndpointConfiguration EndpointConfiguration { get; }
|
||||
EndpointConfiguration Endpoints { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The version of the API.
|
||||
|
@ -15,10 +15,10 @@ namespace osu.Game.Online.API.Requests
|
||||
get
|
||||
{
|
||||
// can be removed once the service has been successfully deployed to production
|
||||
if (API!.EndpointConfiguration.BeatmapSubmissionServiceUrl == null)
|
||||
if (API!.Endpoints.BeatmapSubmissionServiceUrl == null)
|
||||
throw new NotSupportedException("Beatmap submission not supported in this configuration!");
|
||||
|
||||
return $@"{API!.EndpointConfiguration.BeatmapSubmissionServiceUrl!}/beatmapsets/{BeatmapSetID}";
|
||||
return $@"{API!.Endpoints.BeatmapSubmissionServiceUrl!}/beatmapsets/{BeatmapSetID}";
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -21,10 +21,10 @@ namespace osu.Game.Online.API.Requests
|
||||
get
|
||||
{
|
||||
// can be removed once the service has been successfully deployed to production
|
||||
if (API!.EndpointConfiguration.BeatmapSubmissionServiceUrl == null)
|
||||
if (API!.Endpoints.BeatmapSubmissionServiceUrl == null)
|
||||
throw new NotSupportedException("Beatmap submission not supported in this configuration!");
|
||||
|
||||
return $@"{API!.EndpointConfiguration.BeatmapSubmissionServiceUrl}/beatmapsets";
|
||||
return $@"{API!.Endpoints.BeatmapSubmissionServiceUrl}/beatmapsets";
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -14,10 +14,10 @@ namespace osu.Game.Online.API.Requests
|
||||
get
|
||||
{
|
||||
// can be removed once the service has been successfully deployed to production
|
||||
if (API!.EndpointConfiguration.BeatmapSubmissionServiceUrl == null)
|
||||
if (API!.Endpoints.BeatmapSubmissionServiceUrl == null)
|
||||
throw new NotSupportedException("Beatmap submission not supported in this configuration!");
|
||||
|
||||
return $@"{API!.EndpointConfiguration.BeatmapSubmissionServiceUrl}/beatmapsets/{BeatmapSetID}";
|
||||
return $@"{API!.Endpoints.BeatmapSubmissionServiceUrl}/beatmapsets/{BeatmapSetID}";
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -49,12 +49,12 @@ namespace osu.Game.Online.Chat
|
||||
|
||||
if (url.StartsWith('/'))
|
||||
{
|
||||
url = $"{api.EndpointConfiguration.WebsiteRootUrl}{url}";
|
||||
url = $"{api.Endpoints.WebsiteUrl}{url}";
|
||||
isTrustedDomain = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
isTrustedDomain = url.StartsWith(api.EndpointConfiguration.WebsiteRootUrl, StringComparison.Ordinal);
|
||||
isTrustedDomain = url.StartsWith(api.Endpoints.WebsiteUrl, StringComparison.Ordinal);
|
||||
}
|
||||
|
||||
if (!url.CheckIsValidUrl())
|
||||
|
@ -95,7 +95,7 @@ namespace osu.Game.Online.Chat
|
||||
|
||||
string getBeatmapPart()
|
||||
{
|
||||
return beatmapOnlineID > 0 ? $"[{api.EndpointConfiguration.WebsiteRootUrl}/b/{beatmapOnlineID} {beatmapDisplayTitle}]" : beatmapDisplayTitle;
|
||||
return beatmapOnlineID > 0 ? $"[{api.Endpoints.WebsiteUrl}/b/{beatmapOnlineID} {beatmapDisplayTitle}]" : beatmapDisplayTitle;
|
||||
}
|
||||
|
||||
string getRulesetPart()
|
||||
|
@ -7,12 +7,12 @@ namespace osu.Game.Online
|
||||
{
|
||||
public DevelopmentEndpointConfiguration()
|
||||
{
|
||||
WebsiteRootUrl = APIEndpointUrl = @"https://dev.ppy.sh";
|
||||
WebsiteUrl = APIUrl = @"https://dev.ppy.sh";
|
||||
APIClientSecret = @"3LP2mhUrV89xxzD1YKNndXHEhWWCRLPNKioZ9ymT";
|
||||
APIClientID = "5";
|
||||
SpectatorEndpointUrl = $@"{APIEndpointUrl}/signalr/spectator";
|
||||
MultiplayerEndpointUrl = $@"{APIEndpointUrl}/signalr/multiplayer";
|
||||
MetadataEndpointUrl = $@"{APIEndpointUrl}/signalr/metadata";
|
||||
SpectatorUrl = $@"{APIUrl}/signalr/spectator";
|
||||
MultiplayerUrl = $@"{APIUrl}/signalr/multiplayer";
|
||||
MetadataUrl = $@"{APIUrl}/signalr/metadata";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -8,16 +8,6 @@ namespace osu.Game.Online
|
||||
/// </summary>
|
||||
public class EndpointConfiguration
|
||||
{
|
||||
/// <summary>
|
||||
/// The base URL for the website. Does not include a trailing slash.
|
||||
/// </summary>
|
||||
public string WebsiteRootUrl { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// The endpoint for the main (osu-web) API. Does not include a trailing slash.
|
||||
/// </summary>
|
||||
public string APIEndpointUrl { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// The OAuth client secret.
|
||||
/// </summary>
|
||||
@ -29,23 +19,33 @@ namespace osu.Game.Online
|
||||
public string APIClientID { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// The endpoint for the SignalR spectator server.
|
||||
/// The base URL for the website. Does not include a trailing slash.
|
||||
/// </summary>
|
||||
public string SpectatorEndpointUrl { get; set; } = string.Empty;
|
||||
public string WebsiteUrl { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// The endpoint for the SignalR multiplayer server.
|
||||
/// The endpoint for the main (osu-web) API. Does not include a trailing slash.
|
||||
/// </summary>
|
||||
public string MultiplayerEndpointUrl { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// The endpoint for the SignalR metadata server.
|
||||
/// </summary>
|
||||
public string MetadataEndpointUrl { get; set; } = string.Empty;
|
||||
public string APIUrl { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// The root URL for the service handling beatmap submission. Does not include a trailing slash.
|
||||
/// </summary>
|
||||
public string? BeatmapSubmissionServiceUrl { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The endpoint for the SignalR spectator server.
|
||||
/// </summary>
|
||||
public string SpectatorUrl { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// The endpoint for the SignalR multiplayer server.
|
||||
/// </summary>
|
||||
public string MultiplayerUrl { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// The endpoint for the SignalR metadata server.
|
||||
/// </summary>
|
||||
public string MetadataUrl { get; set; } = string.Empty;
|
||||
}
|
||||
}
|
||||
|
@ -436,7 +436,7 @@ namespace osu.Game.Online.Leaderboards
|
||||
items.Add(new OsuMenuItem("Use these mods", MenuItemType.Highlighted, () => songSelect.Mods.Value = Score.Mods));
|
||||
|
||||
if (Score.OnlineID > 0)
|
||||
items.Add(new OsuMenuItem(CommonStrings.CopyLink, MenuItemType.Standard, () => clipboard?.SetText($@"{api.EndpointConfiguration.WebsiteRootUrl}/scores/{Score.OnlineID}")));
|
||||
items.Add(new OsuMenuItem(CommonStrings.CopyLink, MenuItemType.Standard, () => clipboard?.SetText($@"{api.Endpoints.WebsiteUrl}/scores/{Score.OnlineID}")));
|
||||
|
||||
if (Score.Files.Count > 0)
|
||||
{
|
||||
|
@ -47,7 +47,7 @@ namespace osu.Game.Online.Metadata
|
||||
|
||||
public OnlineMetadataClient(EndpointConfiguration endpoints)
|
||||
{
|
||||
endpoint = endpoints.MetadataEndpointUrl;
|
||||
endpoint = endpoints.MetadataUrl;
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
|
@ -32,7 +32,7 @@ namespace osu.Game.Online.Multiplayer
|
||||
|
||||
public OnlineMultiplayerClient(EndpointConfiguration endpoints)
|
||||
{
|
||||
endpoint = endpoints.MultiplayerEndpointUrl;
|
||||
endpoint = endpoints.MultiplayerUrl;
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
|
@ -7,12 +7,12 @@ namespace osu.Game.Online
|
||||
{
|
||||
public ProductionEndpointConfiguration()
|
||||
{
|
||||
WebsiteRootUrl = APIEndpointUrl = @"https://osu.ppy.sh";
|
||||
WebsiteUrl = APIUrl = @"https://osu.ppy.sh";
|
||||
APIClientSecret = @"FGc9GAtyHzeQDshWP5Ah7dega8hJACAJpQtw6OXk";
|
||||
APIClientID = "5";
|
||||
SpectatorEndpointUrl = "https://spectator.ppy.sh/spectator";
|
||||
MultiplayerEndpointUrl = "https://spectator.ppy.sh/multiplayer";
|
||||
MetadataEndpointUrl = "https://spectator.ppy.sh/metadata";
|
||||
SpectatorUrl = "https://spectator.ppy.sh/spectator";
|
||||
MultiplayerUrl = "https://spectator.ppy.sh/multiplayer";
|
||||
MetadataUrl = "https://spectator.ppy.sh/metadata";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -24,7 +24,7 @@ namespace osu.Game.Online.Spectator
|
||||
|
||||
public OnlineSpectatorClient(EndpointConfiguration endpoints)
|
||||
{
|
||||
endpoint = endpoints.SpectatorEndpointUrl;
|
||||
endpoint = endpoints.SpectatorUrl;
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
|
@ -295,7 +295,7 @@ namespace osu.Game
|
||||
|
||||
EndpointConfiguration endpoints = CreateEndpoints();
|
||||
|
||||
MessageFormatter.WebsiteRootUrl = endpoints.WebsiteRootUrl;
|
||||
MessageFormatter.WebsiteRootUrl = endpoints.WebsiteUrl;
|
||||
|
||||
frameworkLocale = frameworkConfig.GetBindable<string>(FrameworkSetting.Locale);
|
||||
frameworkLocale.BindValueChanged(_ => updateLanguage());
|
||||
|
@ -419,7 +419,7 @@ namespace osu.Game.Overlays.Comments
|
||||
|
||||
private void copyUrl()
|
||||
{
|
||||
clipboard.SetText($@"{api.EndpointConfiguration.APIEndpointUrl}/comments/{Comment.Id}");
|
||||
clipboard.SetText($@"{api.Endpoints.APIUrl}/comments/{Comment.Id}");
|
||||
onScreenDisplay?.Display(new CopyUrlToast());
|
||||
}
|
||||
|
||||
|
@ -129,7 +129,7 @@ namespace osu.Game.Overlays.Login
|
||||
}
|
||||
};
|
||||
|
||||
forgottenPasswordLink.AddLink(LayoutStrings.PopupLoginLoginForgot, $"{api.EndpointConfiguration.WebsiteRootUrl}/home/password-reset");
|
||||
forgottenPasswordLink.AddLink(LayoutStrings.PopupLoginLoginForgot, $"{api.Endpoints.WebsiteUrl}/home/password-reset");
|
||||
|
||||
password.OnCommit += (_, _) => performLogin();
|
||||
|
||||
|
@ -98,7 +98,7 @@ namespace osu.Game.Overlays.Login
|
||||
explainText.AddParagraph(UserVerificationStrings.BoxInfoCheckSpam);
|
||||
// We can't support localisable strings with nested links yet. Not sure if we even can (probably need to allow markdown link formatting or something).
|
||||
explainText.AddParagraph("If you can't access your email or have forgotten what you used, please follow the ");
|
||||
explainText.AddLink(UserVerificationStrings.BoxInfoRecoverLink, $"{api.EndpointConfiguration.WebsiteRootUrl}/home/password-reset");
|
||||
explainText.AddLink(UserVerificationStrings.BoxInfoRecoverLink, $"{api.Endpoints.WebsiteUrl}/home/password-reset");
|
||||
explainText.AddText(". You can also ");
|
||||
explainText.AddLink(UserVerificationStrings.BoxInfoReissueLink, () =>
|
||||
{
|
||||
|
@ -124,12 +124,12 @@ namespace osu.Game.Overlays.Profile.Header
|
||||
}
|
||||
|
||||
topLinkContainer.AddText("Contributed ");
|
||||
topLinkContainer.AddLink("forum post".ToQuantity(user.PostCount, "#,##0"), $"{api.EndpointConfiguration.WebsiteRootUrl}/users/{user.Id}/posts", creationParameters: embolden);
|
||||
topLinkContainer.AddLink("forum post".ToQuantity(user.PostCount, "#,##0"), $"{api.Endpoints.WebsiteUrl}/users/{user.Id}/posts", creationParameters: embolden);
|
||||
|
||||
addSpacer(topLinkContainer);
|
||||
|
||||
topLinkContainer.AddText("Posted ");
|
||||
topLinkContainer.AddLink("comment".ToQuantity(user.CommentsCount, "#,##0"), $"{api.EndpointConfiguration.WebsiteRootUrl}/comments?user_id={user.Id}", creationParameters: embolden);
|
||||
topLinkContainer.AddLink("comment".ToQuantity(user.CommentsCount, "#,##0"), $"{api.Endpoints.WebsiteUrl}/comments?user_id={user.Id}", creationParameters: embolden);
|
||||
|
||||
string websiteWithoutProtocol = user.Website;
|
||||
|
||||
|
@ -33,7 +33,7 @@ namespace osu.Game.Overlays.Profile.Header.Components
|
||||
Texture = textures.Get(banner.Image),
|
||||
};
|
||||
|
||||
Action = () => game?.OpenUrlExternally($@"{api.EndpointConfiguration.WebsiteRootUrl}/community/tournaments/{banner.TournamentId}");
|
||||
Action = () => game?.OpenUrlExternally($@"{api.Endpoints.WebsiteUrl}/community/tournaments/{banner.TournamentId}");
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
|
@ -213,7 +213,7 @@ namespace osu.Game.Overlays.Profile.Header
|
||||
cover.User = user;
|
||||
avatar.User = user;
|
||||
usernameText.Text = user?.Username ?? string.Empty;
|
||||
openUserExternally.Link = $@"{api.EndpointConfiguration.WebsiteRootUrl}/users/{user?.Id ?? 0}";
|
||||
openUserExternally.Link = $@"{api.Endpoints.WebsiteUrl}/users/{user?.Id ?? 0}";
|
||||
userFlag.CountryCode = user?.CountryCode ?? default;
|
||||
userCountryText.Text = (user?.CountryCode ?? default).GetDescription();
|
||||
userCountryContainer.Action = () => rankingsOverlay?.ShowCountry(user?.CountryCode ?? default);
|
||||
|
@ -223,7 +223,7 @@ namespace osu.Game.Overlays.Profile.Sections.Recent
|
||||
private void addBeatmapsetLink()
|
||||
=> content.AddLink(activity.Beatmapset.AsNonNull().Title, LinkAction.OpenBeatmapSet, getLinkArgument(activity.Beatmapset.AsNonNull().Url), creationParameters: t => t.Font = getLinkFont());
|
||||
|
||||
private object getLinkArgument(string url) => MessageFormatter.GetLinkDetails($"{api.EndpointConfiguration.WebsiteRootUrl}{url}").Argument.AsNonNull();
|
||||
private object getLinkArgument(string url) => MessageFormatter.GetLinkDetails($"{api.Endpoints.WebsiteUrl}{url}").Argument.AsNonNull();
|
||||
|
||||
private FontUsage getLinkFont(FontWeight fontWeight = FontWeight.Regular)
|
||||
=> OsuFont.GetFont(size: font_size, weight: fontWeight, italics: true);
|
||||
|
@ -54,7 +54,7 @@ namespace osu.Game.Overlays.Wiki
|
||||
Padding = new MarginPadding(padding),
|
||||
Child = new WikiPanelMarkdownContainer(isFullWidth)
|
||||
{
|
||||
CurrentPath = $@"{api.EndpointConfiguration.WebsiteRootUrl}/wiki/",
|
||||
CurrentPath = $@"{api.Endpoints.WebsiteUrl}/wiki/",
|
||||
Text = text,
|
||||
RelativeSizeAxes = Axes.X,
|
||||
AutoSizeAxes = Axes.Y
|
||||
|
@ -167,7 +167,7 @@ namespace osu.Game.Overlays
|
||||
}
|
||||
else
|
||||
{
|
||||
LoadDisplay(articlePage = new WikiArticlePage($@"{api.EndpointConfiguration.WebsiteRootUrl}/wiki/{path.Value}/", response.Markdown));
|
||||
LoadDisplay(articlePage = new WikiArticlePage($@"{api.Endpoints.WebsiteUrl}/wiki/{path.Value}/", response.Markdown));
|
||||
}
|
||||
}
|
||||
|
||||
@ -176,7 +176,7 @@ namespace osu.Game.Overlays
|
||||
wikiData.Value = null;
|
||||
path.Value = "error";
|
||||
|
||||
LoadDisplay(articlePage = new WikiArticlePage($@"{api.EndpointConfiguration.WebsiteRootUrl}/wiki/",
|
||||
LoadDisplay(articlePage = new WikiArticlePage($@"{api.Endpoints.WebsiteUrl}/wiki/",
|
||||
$"Something went wrong when trying to fetch page \"{originalPath}\".\n\n[Return to the main page]({INDEX_PATH})."));
|
||||
}
|
||||
|
||||
|
@ -1264,7 +1264,7 @@ namespace osu.Game.Screens.Edit
|
||||
|
||||
bool isSetMadeOfLegacyRulesetBeatmaps = (isNewBeatmap && Ruleset.Value.IsLegacyRuleset())
|
||||
|| (!isNewBeatmap && Beatmap.Value.BeatmapSetInfo.Beatmaps.All(b => b.Ruleset.IsLegacyRuleset()));
|
||||
bool submissionAvailable = api.EndpointConfiguration.BeatmapSubmissionServiceUrl != null;
|
||||
bool submissionAvailable = api.Endpoints.BeatmapSubmissionServiceUrl != null;
|
||||
|
||||
if (isSetMadeOfLegacyRulesetBeatmaps && submissionAvailable)
|
||||
{
|
||||
|
@ -299,7 +299,7 @@ namespace osu.Game.Screens.Edit.Submission
|
||||
uploadStep.SetCompleted();
|
||||
|
||||
if (configManager.Get<bool>(OsuSetting.EditorSubmissionLoadInBrowserAfterSubmission))
|
||||
game?.OpenUrlExternally($"{api.EndpointConfiguration.WebsiteRootUrl}/beatmapsets/{beatmapSetId}");
|
||||
game?.OpenUrlExternally($"{api.Endpoints.WebsiteUrl}/beatmapsets/{beatmapSetId}");
|
||||
|
||||
await updateLocalBeatmap().ConfigureAwait(true);
|
||||
};
|
||||
@ -326,7 +326,7 @@ namespace osu.Game.Screens.Edit.Submission
|
||||
uploadStep.SetCompleted();
|
||||
|
||||
if (configManager.Get<bool>(OsuSetting.EditorSubmissionLoadInBrowserAfterSubmission))
|
||||
game?.OpenUrlExternally($"{api.EndpointConfiguration.WebsiteRootUrl}/beatmapsets/{beatmapSetId}");
|
||||
game?.OpenUrlExternally($"{api.Endpoints.WebsiteUrl}/beatmapsets/{beatmapSetId}");
|
||||
|
||||
await updateLocalBeatmap().ConfigureAwait(true);
|
||||
};
|
||||
|
@ -46,14 +46,14 @@ namespace osu.Game.Screens.Edit.Submission
|
||||
RelativeSizeAxes = Axes.X,
|
||||
Caption = BeatmapSubmissionStrings.MappingHelpForumDescription,
|
||||
ButtonText = BeatmapSubmissionStrings.MappingHelpForum,
|
||||
Action = () => game?.OpenUrlExternally($@"{api.EndpointConfiguration.WebsiteRootUrl}/community/forums/56"),
|
||||
Action = () => game?.OpenUrlExternally($@"{api.Endpoints.WebsiteUrl}/community/forums/56"),
|
||||
},
|
||||
new FormButton
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
Caption = BeatmapSubmissionStrings.ModdingQueuesForumDescription,
|
||||
ButtonText = BeatmapSubmissionStrings.ModdingQueuesForum,
|
||||
Action = () => game?.OpenUrlExternally($@"{api.EndpointConfiguration.WebsiteRootUrl}/community/forums/60"),
|
||||
Action = () => game?.OpenUrlExternally($@"{api.Endpoints.WebsiteUrl}/community/forums/60"),
|
||||
},
|
||||
},
|
||||
});
|
||||
|
@ -361,7 +361,7 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components
|
||||
|
||||
return items.ToArray();
|
||||
|
||||
string formatRoomUrl(long id) => $@"{api.EndpointConfiguration.WebsiteRootUrl}/multiplayer/rooms/{id}";
|
||||
string formatRoomUrl(long id) => $@"{api.Endpoints.WebsiteUrl}/multiplayer/rooms/{id}";
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -284,6 +284,13 @@ namespace osu.Game.Screens.Play
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
// zero scores should also never be submitted.
|
||||
if (score.ScoreInfo.TotalScore == 0)
|
||||
{
|
||||
Logger.Log("Zero score, skipping score submission");
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
// mind the timing of this.
|
||||
// once `scoreSubmissionSource` is created, it is presumed that submission is taking place in the background,
|
||||
// so all exceptional circumstances that would disallow submission must be handled above.
|
||||
|
@ -11,13 +11,15 @@ using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Effects;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Framework.Localisation;
|
||||
using osu.Game.Beatmaps.Drawables;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Graphics.Sprites;
|
||||
using osu.Game.Online.Leaderboards;
|
||||
using osu.Game.Resources.Localisation.Web;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Rulesets.Scoring;
|
||||
using osu.Game.Rulesets.UI;
|
||||
using osu.Game.Scoring;
|
||||
using osu.Game.Screens.Play.HUD;
|
||||
using osu.Game.Users;
|
||||
using osu.Game.Users.Drawables;
|
||||
using osu.Game.Utils;
|
||||
@ -67,7 +69,7 @@ namespace osu.Game.Screens.Ranking.Contracted
|
||||
Colour = Color4.Black.Opacity(0.25f),
|
||||
Type = EdgeEffectType.Shadow,
|
||||
Radius = 1,
|
||||
Offset = new Vector2(0, 4)
|
||||
Offset = new Vector2(0, 2)
|
||||
},
|
||||
Children = new Drawable[]
|
||||
{
|
||||
@ -100,10 +102,10 @@ namespace osu.Game.Screens.Ranking.Contracted
|
||||
CornerRadius = 20,
|
||||
EdgeEffect = new EdgeEffectParameters
|
||||
{
|
||||
Colour = Color4.Black.Opacity(0.25f),
|
||||
Colour = Color4.Black.Opacity(0.15f),
|
||||
Type = EdgeEffectType.Shadow,
|
||||
Radius = 8,
|
||||
Offset = new Vector2(0, 4),
|
||||
Offset = new Vector2(0, 1),
|
||||
}
|
||||
},
|
||||
new OsuSpriteText
|
||||
@ -134,14 +136,33 @@ namespace osu.Game.Screens.Ranking.Contracted
|
||||
createStatistic(BeatmapsetsStrings.ShowScoreboardHeadersAccuracy, $"{score.Accuracy.FormatAccuracy()}"),
|
||||
}
|
||||
},
|
||||
new ModFlowDisplay
|
||||
new FillFlowContainer
|
||||
{
|
||||
Anchor = Anchor.TopCentre,
|
||||
Origin = Anchor.TopCentre,
|
||||
AutoSizeAxes = Axes.Y,
|
||||
RelativeSizeAxes = Axes.X,
|
||||
Current = { Value = score.Mods },
|
||||
IconScale = 0.5f,
|
||||
AutoSizeAxes = Axes.Y,
|
||||
Direction = FillDirection.Full,
|
||||
Spacing = new Vector2(3),
|
||||
ChildrenEnumerable =
|
||||
[
|
||||
new DifficultyIcon(score.BeatmapInfo!, score.Ruleset)
|
||||
{
|
||||
Anchor = Anchor.TopCentre,
|
||||
Origin = Anchor.TopCentre,
|
||||
Size = new Vector2(20),
|
||||
TooltipType = DifficultyIconTooltipType.Extended,
|
||||
Margin = new MarginPadding { Right = 2 }
|
||||
},
|
||||
..
|
||||
score.Mods.AsOrdered().Select(m => new ModIcon(m)
|
||||
{
|
||||
Anchor = Anchor.TopCentre,
|
||||
Origin = Anchor.TopCentre,
|
||||
Scale = new Vector2(0.3f),
|
||||
Margin = new MarginPadding { Top = -6 }
|
||||
})
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -41,7 +41,6 @@ namespace osu.Game.Screens.Ranking.Expanded
|
||||
|
||||
private readonly List<StatisticDisplay> statisticDisplays = new List<StatisticDisplay>();
|
||||
|
||||
private FillFlowContainer starAndModDisplay;
|
||||
private RollingCounter<long> scoreCounter;
|
||||
|
||||
[Resolved]
|
||||
@ -139,12 +138,35 @@ namespace osu.Game.Screens.Ranking.Expanded
|
||||
Alpha = 0,
|
||||
AlwaysPresent = true
|
||||
},
|
||||
starAndModDisplay = new FillFlowContainer
|
||||
new FillFlowContainer
|
||||
{
|
||||
Anchor = Anchor.TopCentre,
|
||||
Origin = Anchor.TopCentre,
|
||||
AutoSizeAxes = Axes.Both,
|
||||
Spacing = new Vector2(5, 0),
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new StarRatingDisplay(beatmapDifficultyCache.GetDifficultyAsync(beatmap, score.Ruleset, score.Mods).GetResultSafely() ?? default)
|
||||
{
|
||||
Anchor = Anchor.CentreLeft,
|
||||
Origin = Anchor.CentreLeft
|
||||
},
|
||||
new DifficultyIcon(beatmap, score.Ruleset)
|
||||
{
|
||||
Anchor = Anchor.CentreLeft,
|
||||
Origin = Anchor.CentreLeft,
|
||||
Size = new Vector2(20),
|
||||
TooltipType = DifficultyIconTooltipType.Extended,
|
||||
},
|
||||
new ModDisplay
|
||||
{
|
||||
Anchor = Anchor.CentreLeft,
|
||||
Origin = Anchor.CentreLeft,
|
||||
ExpansionMode = ExpansionMode.AlwaysExpanded,
|
||||
Scale = new Vector2(0.5f),
|
||||
Current = { Value = score.Mods }
|
||||
}
|
||||
}
|
||||
},
|
||||
new FillFlowContainer
|
||||
{
|
||||
@ -225,29 +247,6 @@ namespace osu.Game.Screens.Ranking.Expanded
|
||||
|
||||
if (score.Date != default)
|
||||
AddInternal(new PlayedOnText(score.Date));
|
||||
|
||||
var starDifficulty = beatmapDifficultyCache.GetDifficultyAsync(beatmap, score.Ruleset, score.Mods).GetResultSafely();
|
||||
|
||||
if (starDifficulty != null)
|
||||
{
|
||||
starAndModDisplay.Add(new StarRatingDisplay(starDifficulty.Value)
|
||||
{
|
||||
Anchor = Anchor.CentreLeft,
|
||||
Origin = Anchor.CentreLeft
|
||||
});
|
||||
}
|
||||
|
||||
if (score.Mods.Any())
|
||||
{
|
||||
starAndModDisplay.Add(new ModDisplay
|
||||
{
|
||||
Anchor = Anchor.CentreLeft,
|
||||
Origin = Anchor.CentreLeft,
|
||||
ExpansionMode = ExpansionMode.AlwaysExpanded,
|
||||
Scale = new Vector2(0.5f),
|
||||
Current = { Value = score.Mods }
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
|
@ -20,12 +20,23 @@ namespace osu.Game.Screens.SelectV2
|
||||
[Cached]
|
||||
public partial class BeatmapCarousel : Carousel<BeatmapInfo>
|
||||
{
|
||||
public const float SPACING = 5f;
|
||||
|
||||
private IBindableList<BeatmapSetInfo> detachedBeatmaps = null!;
|
||||
|
||||
private readonly LoadingLayer loading;
|
||||
|
||||
private readonly BeatmapCarouselFilterGrouping grouping;
|
||||
|
||||
protected override float GetSpacingBetweenPanels(CarouselItem top, CarouselItem bottom)
|
||||
{
|
||||
if (top.Model is BeatmapInfo || bottom.Model is BeatmapInfo)
|
||||
// Beatmap difficulty panels do not overlap with themselves or any other panel.
|
||||
return SPACING;
|
||||
|
||||
return -SPACING;
|
||||
}
|
||||
|
||||
public BeatmapCarousel()
|
||||
{
|
||||
DebounceDelay = 100;
|
||||
@ -95,11 +106,9 @@ namespace osu.Game.Screens.SelectV2
|
||||
private GroupDefinition? lastSelectedGroup;
|
||||
private BeatmapInfo? lastSelectedBeatmap;
|
||||
|
||||
protected override bool HandleItemSelected(object? model)
|
||||
protected override void HandleItemActivated(CarouselItem item)
|
||||
{
|
||||
base.HandleItemSelected(model);
|
||||
|
||||
switch (model)
|
||||
switch (item.Model)
|
||||
{
|
||||
case GroupDefinition group:
|
||||
// Special case – collapsing an open group.
|
||||
@ -107,37 +116,42 @@ namespace osu.Game.Screens.SelectV2
|
||||
{
|
||||
setExpansionStateOfGroup(lastSelectedGroup, false);
|
||||
lastSelectedGroup = null;
|
||||
return false;
|
||||
return;
|
||||
}
|
||||
|
||||
setExpandedGroup(group);
|
||||
return false;
|
||||
return;
|
||||
|
||||
case BeatmapSetInfo setInfo:
|
||||
// Selecting a set isn't valid – let's re-select the first difficulty.
|
||||
CurrentSelection = setInfo.Beatmaps.First();
|
||||
return false;
|
||||
return;
|
||||
|
||||
case BeatmapInfo beatmapInfo:
|
||||
|
||||
// If we have groups, we need to account for them.
|
||||
if (Criteria.SplitOutDifficulties)
|
||||
{
|
||||
// Find the containing group. There should never be too many groups so iterating is efficient enough.
|
||||
GroupDefinition? group = grouping.GroupItems.SingleOrDefault(kvp => kvp.Value.Any(i => ReferenceEquals(i.Model, beatmapInfo))).Key;
|
||||
|
||||
if (group != null)
|
||||
setExpandedGroup(group);
|
||||
}
|
||||
else
|
||||
{
|
||||
setExpandedSet(beatmapInfo);
|
||||
}
|
||||
|
||||
return true;
|
||||
CurrentSelection = beatmapInfo;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
protected override void HandleItemSelected(object? model)
|
||||
{
|
||||
base.HandleItemSelected(model);
|
||||
|
||||
switch (model)
|
||||
{
|
||||
case BeatmapSetInfo:
|
||||
case GroupDefinition:
|
||||
throw new InvalidOperationException("Groups should never become selected");
|
||||
|
||||
case BeatmapInfo beatmapInfo:
|
||||
// Find any containing group. There should never be too many groups so iterating is efficient enough.
|
||||
GroupDefinition? containingGroup = grouping.GroupItems.SingleOrDefault(kvp => kvp.Value.Any(i => ReferenceEquals(i.Model, beatmapInfo))).Key;
|
||||
|
||||
if (containingGroup != null)
|
||||
setExpandedGroup(containingGroup);
|
||||
setExpandedSet(beatmapInfo);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
protected override bool CheckValidForGroupSelection(CarouselItem item)
|
||||
@ -148,7 +162,7 @@ namespace osu.Game.Screens.SelectV2
|
||||
return true;
|
||||
|
||||
case BeatmapInfo:
|
||||
return Criteria.SplitOutDifficulties;
|
||||
return !grouping.BeatmapSetsGroupedTogether;
|
||||
|
||||
case GroupDefinition:
|
||||
return false;
|
||||
@ -170,12 +184,46 @@ namespace osu.Game.Screens.SelectV2
|
||||
{
|
||||
if (grouping.GroupItems.TryGetValue(group, out var items))
|
||||
{
|
||||
foreach (var i in items)
|
||||
if (expanded)
|
||||
{
|
||||
if (i.Model is GroupDefinition)
|
||||
i.IsExpanded = expanded;
|
||||
else
|
||||
i.IsVisible = expanded;
|
||||
foreach (var i in items)
|
||||
{
|
||||
switch (i.Model)
|
||||
{
|
||||
case GroupDefinition:
|
||||
i.IsExpanded = true;
|
||||
break;
|
||||
|
||||
case BeatmapSetInfo set:
|
||||
// Case where there are set headers, header should be visible
|
||||
// and items should use the set's expanded state.
|
||||
i.IsVisible = true;
|
||||
setExpansionStateOfSetItems(set, i.IsExpanded);
|
||||
break;
|
||||
|
||||
default:
|
||||
// Case where there are no set headers, all items should be visible.
|
||||
if (!grouping.BeatmapSetsGroupedTogether)
|
||||
i.IsVisible = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (var i in items)
|
||||
{
|
||||
switch (i.Model)
|
||||
{
|
||||
case GroupDefinition:
|
||||
i.IsExpanded = false;
|
||||
break;
|
||||
|
||||
default:
|
||||
i.IsVisible = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -14,6 +14,8 @@ namespace osu.Game.Screens.SelectV2
|
||||
{
|
||||
public class BeatmapCarouselFilterGrouping : ICarouselFilter
|
||||
{
|
||||
public bool BeatmapSetsGroupedTogether { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Beatmap sets contain difficulties as related panels. This dictionary holds the relationships between set-difficulties to allow expanding them on selection.
|
||||
/// </summary>
|
||||
@ -36,8 +38,6 @@ namespace osu.Game.Screens.SelectV2
|
||||
|
||||
public async Task<IEnumerable<CarouselItem>> Run(IEnumerable<CarouselItem> items, CancellationToken cancellationToken) => await Task.Run(() =>
|
||||
{
|
||||
bool groupSetsTogether;
|
||||
|
||||
setItems.Clear();
|
||||
groupItems.Clear();
|
||||
|
||||
@ -48,12 +48,39 @@ namespace osu.Game.Screens.SelectV2
|
||||
switch (criteria.Group)
|
||||
{
|
||||
default:
|
||||
groupSetsTogether = true;
|
||||
BeatmapSetsGroupedTogether = true;
|
||||
newItems.AddRange(items);
|
||||
break;
|
||||
|
||||
case GroupMode.Artist:
|
||||
BeatmapSetsGroupedTogether = true;
|
||||
char groupChar = (char)0;
|
||||
|
||||
foreach (var item in items)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
var b = (BeatmapInfo)item.Model;
|
||||
|
||||
char beatmapFirstChar = char.ToUpperInvariant(b.Metadata.Artist[0]);
|
||||
|
||||
if (beatmapFirstChar > groupChar)
|
||||
{
|
||||
groupChar = beatmapFirstChar;
|
||||
var groupDefinition = new GroupDefinition($"{groupChar}");
|
||||
var groupItem = new CarouselItem(groupDefinition) { DrawHeight = GroupPanel.HEIGHT };
|
||||
|
||||
newItems.Add(groupItem);
|
||||
groupItems[groupDefinition] = new HashSet<CarouselItem> { groupItem };
|
||||
}
|
||||
|
||||
newItems.Add(item);
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case GroupMode.Difficulty:
|
||||
groupSetsTogether = false;
|
||||
BeatmapSetsGroupedTogether = false;
|
||||
int starGroup = int.MinValue;
|
||||
|
||||
foreach (var item in items)
|
||||
@ -66,7 +93,12 @@ namespace osu.Game.Screens.SelectV2
|
||||
{
|
||||
starGroup = (int)Math.Floor(b.StarRating);
|
||||
var groupDefinition = new GroupDefinition($"{starGroup} - {++starGroup} *");
|
||||
var groupItem = new CarouselItem(groupDefinition) { DrawHeight = GroupPanel.HEIGHT };
|
||||
|
||||
var groupItem = new CarouselItem(groupDefinition)
|
||||
{
|
||||
DrawHeight = GroupPanel.HEIGHT,
|
||||
DepthLayer = -2
|
||||
};
|
||||
|
||||
newItems.Add(groupItem);
|
||||
groupItems[groupDefinition] = new HashSet<CarouselItem> { groupItem };
|
||||
@ -81,7 +113,7 @@ namespace osu.Game.Screens.SelectV2
|
||||
// Add set headers wherever required.
|
||||
CarouselItem? lastItem = null;
|
||||
|
||||
if (groupSetsTogether)
|
||||
if (BeatmapSetsGroupedTogether)
|
||||
{
|
||||
for (int i = 0; i < newItems.Count; i++)
|
||||
{
|
||||
@ -91,11 +123,16 @@ namespace osu.Game.Screens.SelectV2
|
||||
|
||||
if (item.Model is BeatmapInfo beatmap)
|
||||
{
|
||||
bool newBeatmapSet = lastItem == null || (lastItem.Model is BeatmapInfo lastBeatmap && lastBeatmap.BeatmapSet!.ID != beatmap.BeatmapSet!.ID);
|
||||
bool newBeatmapSet = lastItem?.Model is not BeatmapInfo lastBeatmap || lastBeatmap.BeatmapSet!.ID != beatmap.BeatmapSet!.ID;
|
||||
|
||||
if (newBeatmapSet)
|
||||
{
|
||||
var setItem = new CarouselItem(beatmap.BeatmapSet!) { DrawHeight = BeatmapSetPanel.HEIGHT };
|
||||
var setItem = new CarouselItem(beatmap.BeatmapSet!)
|
||||
{
|
||||
DrawHeight = BeatmapSetPanel.HEIGHT,
|
||||
DepthLayer = -1
|
||||
};
|
||||
|
||||
setItems[beatmap.BeatmapSet!] = new HashSet<CarouselItem> { setItem };
|
||||
newItems.Insert(i, setItem);
|
||||
i++;
|
||||
|
@ -24,6 +24,19 @@ namespace osu.Game.Screens.SelectV2
|
||||
private Box activationFlash = null!;
|
||||
private OsuSpriteText text = null!;
|
||||
|
||||
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos)
|
||||
{
|
||||
var inputRectangle = DrawRectangle;
|
||||
|
||||
// Cover the gaps introduced by the spacing between BeatmapPanels so that clicks will not fall through the carousel.
|
||||
//
|
||||
// Caveat is that for simplicity, we are covering the full spacing, so panels with frontmost depth will have a slightly
|
||||
// larger hit target.
|
||||
inputRectangle = inputRectangle.Inflate(new MarginPadding { Vertical = BeatmapCarousel.SPACING });
|
||||
|
||||
return inputRectangle.Contains(ToLocalSpace(screenSpacePos));
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
@ -86,13 +99,7 @@ namespace osu.Game.Screens.SelectV2
|
||||
|
||||
protected override bool OnClick(ClickEvent e)
|
||||
{
|
||||
if (carousel.CurrentSelection != Item!.Model)
|
||||
{
|
||||
carousel.CurrentSelection = Item!.Model;
|
||||
return true;
|
||||
}
|
||||
|
||||
carousel.TryActivateSelection();
|
||||
carousel.Activate(Item!);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -1,7 +1,6 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
@ -83,7 +82,7 @@ namespace osu.Game.Screens.SelectV2
|
||||
|
||||
protected override bool OnClick(ClickEvent e)
|
||||
{
|
||||
carousel.CurrentSelection = Item!.Model;
|
||||
carousel.Activate(Item!);
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -98,8 +97,6 @@ namespace osu.Game.Screens.SelectV2
|
||||
|
||||
public void Activated()
|
||||
{
|
||||
// sets should never be activated.
|
||||
throw new InvalidOperationException();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
@ -16,6 +16,7 @@ using osu.Framework.Graphics.Cursor;
|
||||
using osu.Framework.Graphics.Pooling;
|
||||
using osu.Framework.Input.Bindings;
|
||||
using osu.Framework.Input.Events;
|
||||
using osu.Framework.Layout;
|
||||
using osu.Framework.Logging;
|
||||
using osu.Framework.Utils;
|
||||
using osu.Game.Graphics.Containers;
|
||||
@ -50,11 +51,6 @@ namespace osu.Game.Screens.SelectV2
|
||||
/// </summary>
|
||||
public float DistanceOffscreenToPreload { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Vertical space between panel layout. Negative value can be used to create an overlapping effect.
|
||||
/// </summary>
|
||||
protected float SpacingBetweenPanels { get; set; } = -5;
|
||||
|
||||
/// <summary>
|
||||
/// When a new request arrives to change filtering, the number of milliseconds to wait before performing the filter.
|
||||
/// Regardless of any external debouncing, this is a safety measure to avoid triggering too many threaded operations.
|
||||
@ -94,28 +90,46 @@ namespace osu.Game.Screens.SelectV2
|
||||
public object? CurrentSelection
|
||||
{
|
||||
get => currentSelection.Model;
|
||||
set => setSelection(value);
|
||||
set
|
||||
{
|
||||
if (currentSelection.Model != value)
|
||||
{
|
||||
HandleItemSelected(value);
|
||||
|
||||
if (currentSelection.Model != null)
|
||||
HandleItemDeselected(currentSelection.Model);
|
||||
|
||||
currentKeyboardSelection = new Selection(value);
|
||||
currentSelection = currentKeyboardSelection;
|
||||
selectionValid.Invalidate();
|
||||
}
|
||||
else if (currentKeyboardSelection.Model != value)
|
||||
{
|
||||
// Even if the current selection matches, let's ensure the keyboard selection is reset
|
||||
// to the newly selected object. This matches user expectations (for now).
|
||||
currentKeyboardSelection = currentSelection;
|
||||
selectionValid.Invalidate();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Activate the current selection, if a selection exists and matches keyboard selection.
|
||||
/// If keyboard selection does not match selection, this will transfer the selection on first invocation.
|
||||
/// Activate the specified item.
|
||||
/// </summary>
|
||||
public void TryActivateSelection()
|
||||
/// <param name="item"></param>
|
||||
public void Activate(CarouselItem item)
|
||||
{
|
||||
if (currentSelection.CarouselItem != currentKeyboardSelection.CarouselItem)
|
||||
{
|
||||
CurrentSelection = currentKeyboardSelection.Model;
|
||||
return;
|
||||
}
|
||||
(GetMaterialisedDrawableForItem(item) as ICarouselPanel)?.Activated();
|
||||
HandleItemActivated(item);
|
||||
|
||||
if (currentSelection.CarouselItem != null)
|
||||
{
|
||||
(GetMaterialisedDrawableForItem(currentSelection.CarouselItem) as ICarouselPanel)?.Activated();
|
||||
HandleItemActivated(currentSelection.CarouselItem);
|
||||
}
|
||||
selectionValid.Invalidate();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the vertical spacing between two given carousel items. Negative value can be used to create an overlapping effect.
|
||||
/// </summary>
|
||||
protected virtual float GetSpacingBetweenPanels(CarouselItem top, CarouselItem bottom) => 0f;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Properties and methods concerning implementations
|
||||
@ -176,30 +190,28 @@ namespace osu.Game.Screens.SelectV2
|
||||
protected virtual bool CheckValidForGroupSelection(CarouselItem item) => true;
|
||||
|
||||
/// <summary>
|
||||
/// Called when an item is "selected".
|
||||
/// Called after an item becomes the <see cref="CurrentSelection"/>.
|
||||
/// Should be used to handle any group expansion, item visibility changes, etc.
|
||||
/// </summary>
|
||||
/// <returns>Whether the item should be selected.</returns>
|
||||
protected virtual bool HandleItemSelected(object? model) => true;
|
||||
protected virtual void HandleItemSelected(object? model) { }
|
||||
|
||||
/// <summary>
|
||||
/// Called when an item is "deselected".
|
||||
/// Called when the <see cref="CurrentSelection"/> changes to a new selection.
|
||||
/// Should be used to handle any group expansion, item visibility changes, etc.
|
||||
/// </summary>
|
||||
protected virtual void HandleItemDeselected(object? model)
|
||||
{
|
||||
}
|
||||
protected virtual void HandleItemDeselected(object? model) { }
|
||||
|
||||
/// <summary>
|
||||
/// Called when an item is "activated".
|
||||
/// Called when an item is activated via user input (keyboard traversal or a mouse click).
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// An activated item should for instance:
|
||||
/// - Open or close a folder
|
||||
/// - Start gameplay on a beatmap difficulty.
|
||||
/// An activated item should decide to perform an action, such as:
|
||||
/// - Change its expanded state (and show / hide children items).
|
||||
/// - Set the item to the <see cref="CurrentSelection"/>.
|
||||
/// - Start gameplay on a beatmap difficulty if already selected.
|
||||
/// </remarks>
|
||||
/// <param name="item">The carousel item which was activated.</param>
|
||||
protected virtual void HandleItemActivated(CarouselItem item)
|
||||
{
|
||||
}
|
||||
protected virtual void HandleItemActivated(CarouselItem item) { }
|
||||
|
||||
#endregion
|
||||
|
||||
@ -255,7 +267,7 @@ namespace osu.Game.Screens.SelectV2
|
||||
}
|
||||
|
||||
log("Updating Y positions");
|
||||
updateYPositions(items, visibleHalfHeight, SpacingBetweenPanels);
|
||||
updateYPositions(items, visibleHalfHeight);
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
@ -281,17 +293,26 @@ namespace osu.Game.Screens.SelectV2
|
||||
void log(string text) => Logger.Log($"Carousel[op {cts.GetHashCode().ToString()}] {stopwatch.ElapsedMilliseconds} ms: {text}");
|
||||
}
|
||||
|
||||
private static void updateYPositions(IEnumerable<CarouselItem> carouselItems, float offset, float spacing)
|
||||
private void updateYPositions(IEnumerable<CarouselItem> carouselItems, float offset)
|
||||
{
|
||||
CarouselItem? previousVisible = null;
|
||||
|
||||
foreach (var item in carouselItems)
|
||||
updateItemYPosition(item, ref offset, spacing);
|
||||
updateItemYPosition(item, ref previousVisible, ref offset);
|
||||
}
|
||||
|
||||
private static void updateItemYPosition(CarouselItem item, ref float offset, float spacing)
|
||||
private void updateItemYPosition(CarouselItem item, ref CarouselItem? previousVisible, ref float offset)
|
||||
{
|
||||
float spacing = previousVisible == null || !item.IsVisible ? 0 : GetSpacingBetweenPanels(previousVisible, item);
|
||||
|
||||
offset += spacing;
|
||||
item.CarouselYPosition = offset;
|
||||
|
||||
if (item.IsVisible)
|
||||
offset += item.DrawHeight + spacing;
|
||||
{
|
||||
offset += item.DrawHeight;
|
||||
previousVisible = item;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
@ -303,7 +324,8 @@ namespace osu.Game.Screens.SelectV2
|
||||
switch (e.Action)
|
||||
{
|
||||
case GlobalAction.Select:
|
||||
TryActivateSelection();
|
||||
if (currentKeyboardSelection.CarouselItem != null)
|
||||
Activate(currentKeyboardSelection.CarouselItem);
|
||||
return true;
|
||||
|
||||
case GlobalAction.SelectNext:
|
||||
@ -372,32 +394,29 @@ namespace osu.Game.Screens.SelectV2
|
||||
|
||||
// If the user has a different keyboard selection and requests
|
||||
// group selection, first transfer the keyboard selection to actual selection.
|
||||
if (currentSelection.CarouselItem != currentKeyboardSelection.CarouselItem)
|
||||
if (currentKeyboardSelection.CarouselItem != null && currentSelection.CarouselItem != currentKeyboardSelection.CarouselItem)
|
||||
{
|
||||
TryActivateSelection();
|
||||
|
||||
// There's a chance this couldn't resolve, at which point continue with standard traversal.
|
||||
if (currentSelection.CarouselItem == currentKeyboardSelection.CarouselItem)
|
||||
return;
|
||||
Activate(currentKeyboardSelection.CarouselItem);
|
||||
return;
|
||||
}
|
||||
|
||||
int originalIndex;
|
||||
int newIndex;
|
||||
|
||||
if (currentSelection.Index == null)
|
||||
if (currentKeyboardSelection.Index == null)
|
||||
{
|
||||
// If there's no current selection, start from either end of the full list.
|
||||
newIndex = originalIndex = direction > 0 ? carouselItems.Count - 1 : 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
newIndex = originalIndex = currentSelection.Index.Value;
|
||||
newIndex = originalIndex = currentKeyboardSelection.Index.Value;
|
||||
|
||||
// As a second special case, if we're group selecting backwards and the current selection isn't a group,
|
||||
// make sure to go back to the group header this item belongs to, so that the block below doesn't find it and stop too early.
|
||||
if (direction < 0)
|
||||
{
|
||||
while (!CheckValidForGroupSelection(carouselItems[newIndex]))
|
||||
while (newIndex > 0 && !CheckValidForGroupSelection(carouselItems[newIndex]))
|
||||
newIndex--;
|
||||
}
|
||||
}
|
||||
@ -411,7 +430,7 @@ namespace osu.Game.Screens.SelectV2
|
||||
|
||||
if (CheckValidForGroupSelection(newItem))
|
||||
{
|
||||
setSelection(newItem.Model);
|
||||
HandleItemActivated(newItem);
|
||||
return;
|
||||
}
|
||||
} while (newIndex != originalIndex);
|
||||
@ -426,23 +445,6 @@ namespace osu.Game.Screens.SelectV2
|
||||
private Selection currentKeyboardSelection = new Selection();
|
||||
private Selection currentSelection = new Selection();
|
||||
|
||||
private void setSelection(object? model)
|
||||
{
|
||||
if (currentSelection.Model == model)
|
||||
return;
|
||||
|
||||
if (HandleItemSelected(model))
|
||||
{
|
||||
if (currentSelection.Model != null)
|
||||
HandleItemDeselected(currentSelection.Model);
|
||||
|
||||
currentKeyboardSelection = new Selection(model);
|
||||
currentSelection = currentKeyboardSelection;
|
||||
}
|
||||
|
||||
selectionValid.Invalidate();
|
||||
}
|
||||
|
||||
private void setKeyboardSelection(object? model)
|
||||
{
|
||||
currentKeyboardSelection = new Selection(model);
|
||||
@ -468,7 +470,7 @@ namespace osu.Game.Screens.SelectV2
|
||||
return;
|
||||
}
|
||||
|
||||
float spacing = SpacingBetweenPanels;
|
||||
CarouselItem? lastVisible = null;
|
||||
int count = carouselItems.Count;
|
||||
|
||||
Selection prevKeyboard = currentKeyboardSelection;
|
||||
@ -480,7 +482,7 @@ namespace osu.Game.Screens.SelectV2
|
||||
{
|
||||
var item = carouselItems[i];
|
||||
|
||||
updateItemYPosition(item, ref yPos, spacing);
|
||||
updateItemYPosition(item, ref lastVisible, ref yPos);
|
||||
|
||||
if (ReferenceEquals(item.Model, currentKeyboardSelection.Model))
|
||||
currentKeyboardSelection = new Selection(item.Model, item, item.CarouselYPosition, i);
|
||||
@ -548,6 +550,8 @@ namespace osu.Game.Screens.SelectV2
|
||||
updateDisplayedRange(range);
|
||||
}
|
||||
|
||||
double selectedYPos = currentSelection.CarouselItem?.CarouselYPosition ?? 0;
|
||||
|
||||
foreach (var panel in scroll.Panels)
|
||||
{
|
||||
var c = (ICarouselPanel)panel;
|
||||
@ -556,8 +560,8 @@ namespace osu.Game.Screens.SelectV2
|
||||
if (c.Item == null)
|
||||
continue;
|
||||
|
||||
double selectedYPos = currentSelection?.CarouselItem?.CarouselYPosition ?? 0;
|
||||
scroll.Panels.ChangeChildDepth(panel, (float)Math.Abs(c.DrawYPosition - selectedYPos));
|
||||
float normalisedDepth = (float)(Math.Abs(selectedYPos - c.DrawYPosition) / DrawHeight);
|
||||
scroll.Panels.ChangeChildDepth(panel, c.Item.DepthLayer + normalisedDepth);
|
||||
|
||||
if (c.DrawYPosition != c.Item.CarouselYPosition)
|
||||
c.DrawYPosition = Interpolation.DampContinuously(c.DrawYPosition, c.Item.CarouselYPosition, 50, Time.Elapsed);
|
||||
@ -676,6 +680,15 @@ namespace osu.Game.Screens.SelectV2
|
||||
carouselPanel.Expanded.Value = false;
|
||||
}
|
||||
|
||||
protected override bool OnInvalidate(Invalidation invalidation, InvalidationSource source)
|
||||
{
|
||||
// handles the vertical size of the carousel changing (ie. on window resize when aspect ratio has changed).
|
||||
if (invalidation.HasFlag(Invalidation.DrawSize))
|
||||
selectionValid.Invalidate();
|
||||
|
||||
return base.OnInvalidate(invalidation, source);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Internal helper classes
|
||||
|
@ -29,6 +29,11 @@ namespace osu.Game.Screens.SelectV2
|
||||
/// </summary>
|
||||
public float DrawHeight { get; set; } = DEFAULT_HEIGHT;
|
||||
|
||||
/// <summary>
|
||||
/// Defines the display depth relative to other <see cref="CarouselItem"/>s.
|
||||
/// </summary>
|
||||
public int DepthLayer { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Whether this item is visible or hidden.
|
||||
/// </summary>
|
||||
|
@ -1,7 +1,6 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
@ -96,7 +95,7 @@ namespace osu.Game.Screens.SelectV2
|
||||
|
||||
protected override bool OnClick(ClickEvent e)
|
||||
{
|
||||
carousel.CurrentSelection = Item!.Model;
|
||||
carousel.Activate(Item!);
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -111,8 +110,6 @@ namespace osu.Game.Screens.SelectV2
|
||||
|
||||
public void Activated()
|
||||
{
|
||||
// sets should never be activated.
|
||||
throw new InvalidOperationException();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
@ -778,7 +778,7 @@ namespace osu.Game.Screens.SelectV2.Leaderboards
|
||||
items.Add(new OsuMenuItem("Use these mods", MenuItemType.Highlighted, () => SelectedMods.Value = score.Mods.Where(m => IsValidMod.Invoke(m)).ToArray()));
|
||||
|
||||
if (score.OnlineID > 0)
|
||||
items.Add(new OsuMenuItem(CommonStrings.CopyLink, MenuItemType.Standard, () => clipboard?.SetText($@"{api.EndpointConfiguration.WebsiteRootUrl}/scores/{score.OnlineID}")));
|
||||
items.Add(new OsuMenuItem(CommonStrings.CopyLink, MenuItemType.Standard, () => clipboard?.SetText($@"{api.Endpoints.WebsiteUrl}/scores/{score.OnlineID}")));
|
||||
|
||||
if (score.Files.Count <= 0) return items.ToArray();
|
||||
|
||||
|
@ -41,7 +41,7 @@ namespace osu.Game.Utils
|
||||
{
|
||||
this.game = game;
|
||||
|
||||
if (!game.IsDeployedBuild || !game.CreateEndpoints().WebsiteRootUrl.EndsWith(@".ppy.sh", StringComparison.Ordinal))
|
||||
if (!game.IsDeployedBuild || !game.CreateEndpoints().WebsiteUrl.EndsWith(@".ppy.sh", StringComparison.Ordinal))
|
||||
return;
|
||||
|
||||
sentrySession = SentrySdk.Init(options =>
|
||||
|
Loading…
Reference in New Issue
Block a user