diff --git a/appveyor.yml b/appveyor.yml
index a4a0cedc66..845751ef07 100644
--- a/appveyor.yml
+++ b/appveyor.yml
@@ -17,7 +17,11 @@ build:
publish_nuget: true
after_build:
- ps: dotnet tool restore
- - ps: dotnet format --dry-run --check
+
+ # Temporarily disabled until the tool is upgraded to 5.0.
+ # The version specified in .config/dotnet-tools.json (3.1.37601) won't run on .NET hosts >=5.0.7.
+ # - ps: dotnet format --dry-run --check
+
- ps: .\InspectCode.ps1
test:
assemblies:
diff --git a/osu.Android.props b/osu.Android.props
index 13d45835be..c020b1d783 100644
--- a/osu.Android.props
+++ b/osu.Android.props
@@ -52,6 +52,6 @@
-
+
diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs
index 67cd720260..184a2e59da 100644
--- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs
+++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs
@@ -2,15 +2,22 @@
// 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.Audio;
using osu.Framework.Graphics;
+using osu.Framework.Platform;
+using osu.Framework.Testing;
using osu.Game.Beatmaps;
using osu.Game.Online.Leaderboards;
using osu.Game.Overlays;
+using osu.Game.Rulesets;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Osu.Mods;
using osu.Game.Scoring;
using osu.Game.Screens.Select.Leaderboards;
+using osu.Game.Tests.Resources;
using osu.Game.Users;
using osuTK;
@@ -23,32 +30,98 @@ namespace osu.Game.Tests.Visual.SongSelect
[Cached]
private readonly DialogOverlay dialogOverlay;
+ private ScoreManager scoreManager;
+
+ private RulesetStore rulesetStore;
+ private BeatmapManager beatmapManager;
+
+ protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent)
+ {
+ var dependencies = new DependencyContainer(base.CreateChildDependencies(parent));
+
+ dependencies.Cache(rulesetStore = new RulesetStore(ContextFactory));
+ dependencies.Cache(beatmapManager = new BeatmapManager(LocalStorage, ContextFactory, rulesetStore, null, dependencies.Get(), Resources, dependencies.Get(), Beatmap.Default));
+ dependencies.Cache(scoreManager = new ScoreManager(rulesetStore, () => beatmapManager, LocalStorage, null, ContextFactory));
+
+ return dependencies;
+ }
+
public TestSceneBeatmapLeaderboard()
{
- Add(dialogOverlay = new DialogOverlay
+ AddRange(new Drawable[]
{
- Depth = -1
+ dialogOverlay = new DialogOverlay
+ {
+ Depth = -1
+ },
+ leaderboard = new FailableLeaderboard
+ {
+ Origin = Anchor.Centre,
+ Anchor = Anchor.Centre,
+ Size = new Vector2(550f, 450f),
+ Scope = BeatmapLeaderboardScope.Global,
+ }
+ });
+ }
+
+ [Test]
+ public void TestLocalScoresDisplay()
+ {
+ BeatmapInfo beatmapInfo = null;
+
+ AddStep(@"Set scope", () => leaderboard.Scope = BeatmapLeaderboardScope.Local);
+
+ AddStep(@"Set beatmap", () =>
+ {
+ beatmapManager.Import(TestResources.GetQuickTestBeatmapForImport()).Wait();
+ beatmapInfo = beatmapManager.GetAllUsableBeatmapSets().First().Beatmaps.First();
+
+ leaderboard.Beatmap = beatmapInfo;
});
- Add(leaderboard = new FailableLeaderboard
- {
- Origin = Anchor.Centre,
- Anchor = Anchor.Centre,
- Size = new Vector2(550f, 450f),
- Scope = BeatmapLeaderboardScope.Global,
- });
+ clearScores();
+ checkCount(0);
- AddStep(@"New Scores", newScores);
+ loadMoreScores(() => beatmapInfo);
+ checkCount(10);
+
+ loadMoreScores(() => beatmapInfo);
+ checkCount(20);
+
+ clearScores();
+ checkCount(0);
+ }
+
+ [Test]
+ public void TestGlobalScoresDisplay()
+ {
+ AddStep(@"Set scope", () => leaderboard.Scope = BeatmapLeaderboardScope.Global);
+ AddStep(@"New Scores", () => leaderboard.Scores = generateSampleScores(null));
+ }
+
+ [Test]
+ public void TestPersonalBest()
+ {
AddStep(@"Show personal best", showPersonalBest);
+ AddStep("null personal best position", showPersonalBestWithNullPosition);
+ }
+
+ [Test]
+ public void TestPlaceholderStates()
+ {
AddStep(@"Empty Scores", () => leaderboard.SetRetrievalState(PlaceholderState.NoScores));
AddStep(@"Network failure", () => leaderboard.SetRetrievalState(PlaceholderState.NetworkFailure));
AddStep(@"No supporter", () => leaderboard.SetRetrievalState(PlaceholderState.NotSupporter));
AddStep(@"Not logged in", () => leaderboard.SetRetrievalState(PlaceholderState.NotLoggedIn));
AddStep(@"Unavailable", () => leaderboard.SetRetrievalState(PlaceholderState.Unavailable));
AddStep(@"None selected", () => leaderboard.SetRetrievalState(PlaceholderState.NoneSelected));
+ }
+
+ [Test]
+ public void TestBeatmapStates()
+ {
foreach (BeatmapSetOnlineStatus status in Enum.GetValues(typeof(BeatmapSetOnlineStatus)))
AddStep($"{status} beatmap", () => showBeatmapWithStatus(status));
- AddStep("null personal best position", showPersonalBestWithNullPosition);
}
private void showPersonalBestWithNullPosition()
@@ -96,9 +169,26 @@ namespace osu.Game.Tests.Visual.SongSelect
};
}
- private void newScores()
+ private void loadMoreScores(Func beatmapInfo)
{
- var scores = new[]
+ AddStep(@"Load new scores via manager", () =>
+ {
+ foreach (var score in generateSampleScores(beatmapInfo()))
+ scoreManager.Import(score).Wait();
+ });
+ }
+
+ private void clearScores()
+ {
+ AddStep("Clear all scores", () => scoreManager.Delete(scoreManager.GetAllUsableScores()));
+ }
+
+ private void checkCount(int expected) =>
+ AddUntilStep("Correct count displayed", () => leaderboard.ChildrenOfType().Count() == expected);
+
+ private static ScoreInfo[] generateSampleScores(BeatmapInfo beatmap)
+ {
+ return new[]
{
new ScoreInfo
{
@@ -107,6 +197,7 @@ namespace osu.Game.Tests.Visual.SongSelect
MaxCombo = 244,
TotalScore = 1707827,
//Mods = new Mod[] { new OsuModHidden(), new OsuModHardRock(), },
+ Beatmap = beatmap,
User = new User
{
Id = 6602580,
@@ -125,6 +216,7 @@ namespace osu.Game.Tests.Visual.SongSelect
MaxCombo = 244,
TotalScore = 1707827,
//Mods = new Mod[] { new OsuModHidden(), new OsuModHardRock(), },
+ Beatmap = beatmap,
User = new User
{
Id = 4608074,
@@ -143,6 +235,7 @@ namespace osu.Game.Tests.Visual.SongSelect
MaxCombo = 244,
TotalScore = 1707827,
//Mods = new Mod[] { new OsuModHidden(), new OsuModHardRock(), },
+ Beatmap = beatmap,
User = new User
{
Id = 1014222,
@@ -161,6 +254,7 @@ namespace osu.Game.Tests.Visual.SongSelect
MaxCombo = 244,
TotalScore = 1707827,
//Mods = new Mod[] { new OsuModHidden(), new OsuModHardRock(), },
+ Beatmap = beatmap,
User = new User
{
Id = 1541390,
@@ -179,6 +273,7 @@ namespace osu.Game.Tests.Visual.SongSelect
MaxCombo = 244,
TotalScore = 1707827,
//Mods = new Mod[] { new OsuModHidden(), new OsuModHardRock(), },
+ Beatmap = beatmap,
User = new User
{
Id = 2243452,
@@ -197,6 +292,7 @@ namespace osu.Game.Tests.Visual.SongSelect
MaxCombo = 244,
TotalScore = 1707827,
//Mods = new Mod[] { new OsuModHidden(), new OsuModHardRock(), },
+ Beatmap = beatmap,
User = new User
{
Id = 2705430,
@@ -215,6 +311,7 @@ namespace osu.Game.Tests.Visual.SongSelect
MaxCombo = 244,
TotalScore = 1707827,
//Mods = new Mod[] { new OsuModHidden(), new OsuModHardRock(), },
+ Beatmap = beatmap,
User = new User
{
Id = 7151382,
@@ -233,6 +330,7 @@ namespace osu.Game.Tests.Visual.SongSelect
MaxCombo = 244,
TotalScore = 1707827,
//Mods = new Mod[] { new OsuModHidden(), new OsuModHardRock(), },
+ Beatmap = beatmap,
User = new User
{
Id = 2051389,
@@ -251,6 +349,7 @@ namespace osu.Game.Tests.Visual.SongSelect
MaxCombo = 244,
TotalScore = 1707827,
//Mods = new Mod[] { new OsuModHidden(), new OsuModHardRock(), },
+ Beatmap = beatmap,
User = new User
{
Id = 6169483,
@@ -269,6 +368,7 @@ namespace osu.Game.Tests.Visual.SongSelect
MaxCombo = 244,
TotalScore = 1707827,
//Mods = new Mod[] { new OsuModHidden(), new OsuModHardRock(), },
+ Beatmap = beatmap,
User = new User
{
Id = 6702666,
@@ -281,8 +381,6 @@ namespace osu.Game.Tests.Visual.SongSelect
},
},
};
-
- leaderboard.Scores = scores;
}
private void showBeatmapWithStatus(BeatmapSetOnlineStatus status)
diff --git a/osu.Game/Configuration/RankingType.cs b/osu.Game/Configuration/RankingType.cs
deleted file mode 100644
index 7701e1dd1d..0000000000
--- a/osu.Game/Configuration/RankingType.cs
+++ /dev/null
@@ -1,20 +0,0 @@
-// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
-// See the LICENCE file in the repository root for full licence text.
-
-using System.ComponentModel;
-
-namespace osu.Game.Configuration
-{
- public enum RankingType
- {
- Local,
-
- [Description("Global")]
- Top,
-
- [Description("Selected Mods")]
- SelectedMod,
- Friends,
- Country
- }
-}
diff --git a/osu.Game/Localisation/ChatStrings.cs b/osu.Game/Localisation/ChatStrings.cs
index daddb602ad..636351470b 100644
--- a/osu.Game/Localisation/ChatStrings.cs
+++ b/osu.Game/Localisation/ChatStrings.cs
@@ -7,17 +7,17 @@ namespace osu.Game.Localisation
{
public static class ChatStrings
{
- private const string prefix = "osu.Game.Localisation.Chat";
+ private const string prefix = @"osu.Game.Localisation.Chat";
///
/// "chat"
///
- public static LocalisableString HeaderTitle => new TranslatableString(getKey("header_title"), "chat");
+ public static LocalisableString HeaderTitle => new TranslatableString(getKey(@"header_title"), @"chat");
///
/// "join the real-time discussion"
///
- public static LocalisableString HeaderDescription => new TranslatableString(getKey("header_description"), "join the real-time discussion");
+ public static LocalisableString HeaderDescription => new TranslatableString(getKey(@"header_description"), @"join the real-time discussion");
private static string getKey(string key) => $"{prefix}:{key}";
}
diff --git a/osu.Game/Localisation/CommonStrings.cs b/osu.Game/Localisation/CommonStrings.cs
index f448158191..ced0d80955 100644
--- a/osu.Game/Localisation/CommonStrings.cs
+++ b/osu.Game/Localisation/CommonStrings.cs
@@ -7,12 +7,12 @@ namespace osu.Game.Localisation
{
public static class CommonStrings
{
- private const string prefix = "osu.Game.Localisation.Common";
+ private const string prefix = @"osu.Game.Localisation.Common";
///
/// "Cancel"
///
- public static LocalisableString Cancel => new TranslatableString(getKey("cancel"), "Cancel");
+ public static LocalisableString Cancel => new TranslatableString(getKey(@"cancel"), @"Cancel");
private static string getKey(string key) => $"{prefix}:{key}";
}
diff --git a/osu.Game/Localisation/Language.cs b/osu.Game/Localisation/Language.cs
index edcf264c7f..a3e845f229 100644
--- a/osu.Game/Localisation/Language.cs
+++ b/osu.Game/Localisation/Language.cs
@@ -7,10 +7,10 @@ namespace osu.Game.Localisation
{
public enum Language
{
- [Description("English")]
+ [Description(@"English")]
en,
- [Description("日本語")]
+ [Description(@"日本語")]
ja
}
}
diff --git a/osu.Game/Localisation/NotificationsStrings.cs b/osu.Game/Localisation/NotificationsStrings.cs
index 092eec3a6b..ba28ef5560 100644
--- a/osu.Game/Localisation/NotificationsStrings.cs
+++ b/osu.Game/Localisation/NotificationsStrings.cs
@@ -7,17 +7,17 @@ namespace osu.Game.Localisation
{
public static class NotificationsStrings
{
- private const string prefix = "osu.Game.Localisation.Notifications";
+ private const string prefix = @"osu.Game.Localisation.Notifications";
///
/// "notifications"
///
- public static LocalisableString HeaderTitle => new TranslatableString(getKey("header_title"), "notifications");
+ public static LocalisableString HeaderTitle => new TranslatableString(getKey(@"header_title"), @"notifications");
///
/// "waiting for 'ya"
///
- public static LocalisableString HeaderDescription => new TranslatableString(getKey("header_description"), "waiting for 'ya");
+ public static LocalisableString HeaderDescription => new TranslatableString(getKey(@"header_description"), @"waiting for 'ya");
private static string getKey(string key) => $"{prefix}:{key}";
}
diff --git a/osu.Game/Localisation/NowPlayingStrings.cs b/osu.Game/Localisation/NowPlayingStrings.cs
index d742a56895..47646b0f68 100644
--- a/osu.Game/Localisation/NowPlayingStrings.cs
+++ b/osu.Game/Localisation/NowPlayingStrings.cs
@@ -7,17 +7,17 @@ namespace osu.Game.Localisation
{
public static class NowPlayingStrings
{
- private const string prefix = "osu.Game.Localisation.NowPlaying";
+ private const string prefix = @"osu.Game.Localisation.NowPlaying";
///
/// "now playing"
///
- public static LocalisableString HeaderTitle => new TranslatableString(getKey("header_title"), "now playing");
+ public static LocalisableString HeaderTitle => new TranslatableString(getKey(@"header_title"), @"now playing");
///
/// "manage the currently playing track"
///
- public static LocalisableString HeaderDescription => new TranslatableString(getKey("header_description"), "manage the currently playing track");
+ public static LocalisableString HeaderDescription => new TranslatableString(getKey(@"header_description"), @"manage the currently playing track");
private static string getKey(string key) => $"{prefix}:{key}";
}
diff --git a/osu.Game/Localisation/ResourceManagerLocalisationStore.cs b/osu.Game/Localisation/ResourceManagerLocalisationStore.cs
index 7b21e1af42..a35ce7a9c8 100644
--- a/osu.Game/Localisation/ResourceManagerLocalisationStore.cs
+++ b/osu.Game/Localisation/ResourceManagerLocalisationStore.cs
@@ -5,6 +5,7 @@ using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
+using System.Linq;
using System.Resources;
using System.Threading.Tasks;
using osu.Framework.Localisation;
@@ -34,7 +35,29 @@ namespace osu.Game.Localisation
lock (resourceManagers)
{
if (!resourceManagers.TryGetValue(ns, out var manager))
- resourceManagers[ns] = manager = new ResourceManager(ns, GetType().Assembly);
+ {
+ var loadedAssemblies = AppDomain.CurrentDomain.GetAssemblies();
+
+ // Traverse backwards through periods in the namespace to find a matching assembly.
+ string assemblyName = ns;
+
+ while (!string.IsNullOrEmpty(assemblyName))
+ {
+ var matchingAssembly = loadedAssemblies.FirstOrDefault(asm => asm.GetName().Name == assemblyName);
+
+ if (matchingAssembly != null)
+ {
+ resourceManagers[ns] = manager = new ResourceManager(ns, matchingAssembly);
+ break;
+ }
+
+ int lastIndex = Math.Max(0, assemblyName.LastIndexOf('.'));
+ assemblyName = assemblyName.Substring(0, lastIndex);
+ }
+ }
+
+ if (manager == null)
+ return null;
try
{
diff --git a/osu.Game/Localisation/SettingsStrings.cs b/osu.Game/Localisation/SettingsStrings.cs
index cfbd392691..f4b417fa28 100644
--- a/osu.Game/Localisation/SettingsStrings.cs
+++ b/osu.Game/Localisation/SettingsStrings.cs
@@ -7,17 +7,17 @@ namespace osu.Game.Localisation
{
public static class SettingsStrings
{
- private const string prefix = "osu.Game.Localisation.Settings";
+ private const string prefix = @"osu.Game.Localisation.Settings";
///
/// "settings"
///
- public static LocalisableString HeaderTitle => new TranslatableString(getKey("header_title"), "settings");
+ public static LocalisableString HeaderTitle => new TranslatableString(getKey(@"header_title"), @"settings");
///
/// "change the way osu! behaves"
///
- public static LocalisableString HeaderDescription => new TranslatableString(getKey("header_description"), "change the way osu! behaves");
+ public static LocalisableString HeaderDescription => new TranslatableString(getKey(@"header_description"), @"change the way osu! behaves");
private static string getKey(string key) => $"{prefix}:{key}";
}
diff --git a/osu.Game/Online/Leaderboards/Leaderboard.cs b/osu.Game/Online/Leaderboards/Leaderboard.cs
index d18f189a70..70e38e421d 100644
--- a/osu.Game/Online/Leaderboards/Leaderboard.cs
+++ b/osu.Game/Online/Leaderboards/Leaderboard.cs
@@ -44,9 +44,9 @@ namespace osu.Game.Online.Leaderboards
protected override Container Content => content;
- private IEnumerable scores;
+ private ICollection scores;
- public IEnumerable Scores
+ public ICollection Scores
{
get => scores;
set
@@ -126,7 +126,7 @@ namespace osu.Game.Online.Leaderboards
return;
scope = value;
- UpdateScores();
+ RefreshScores();
}
}
@@ -154,7 +154,7 @@ namespace osu.Game.Online.Leaderboards
case PlaceholderState.NetworkFailure:
replacePlaceholder(new ClickablePlaceholder(@"Couldn't fetch scores!", FontAwesome.Solid.Sync)
{
- Action = UpdateScores,
+ Action = RefreshScores
});
break;
@@ -254,8 +254,6 @@ namespace osu.Game.Online.Leaderboards
apiState.BindValueChanged(onlineStateChanged, true);
}
- public void RefreshScores() => UpdateScores();
-
private APIRequest getScoresRequest;
protected abstract bool IsOnlineScope { get; }
@@ -267,12 +265,14 @@ namespace osu.Game.Online.Leaderboards
case APIState.Online:
case APIState.Offline:
if (IsOnlineScope)
- UpdateScores();
+ RefreshScores();
break;
}
});
+ public void RefreshScores() => Scheduler.AddOnce(UpdateScores);
+
protected void UpdateScores()
{
// don't display any scores or placeholder until the first Scores_Set has been called.
@@ -290,7 +290,7 @@ namespace osu.Game.Online.Leaderboards
getScoresRequest = FetchScores(scores => Schedule(() =>
{
- Scores = scores;
+ Scores = scores.ToArray();
PlaceholderState = Scores.Any() ? PlaceholderState.Successful : PlaceholderState.NoScores;
}));
diff --git a/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs b/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs
index f478e37e3e..94cc7ed095 100644
--- a/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs
+++ b/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs
@@ -19,6 +19,20 @@ namespace osu.Game.Rulesets.UI.Scrolling
private readonly IBindable timeRange = new BindableDouble();
private readonly IBindable direction = new Bindable();
+ ///
+ /// Whether the scrolling direction is horizontal or vertical.
+ ///
+ private Direction scrollingAxis => direction.Value == ScrollingDirection.Left || direction.Value == ScrollingDirection.Right ? Direction.Horizontal : Direction.Vertical;
+
+ ///
+ /// The scrolling axis is inverted if objects temporally farther in the future have a smaller position value across the scrolling axis.
+ ///
+ ///
+ /// is inverted, because given two objects, one of which is at the current time and one of which is 1000ms in the future,
+ /// in the current time instant the future object is spatially above the current object, and therefore has a smaller value of the Y coordinate of its position.
+ ///
+ private bool axisInverted => direction.Value == ScrollingDirection.Down || direction.Value == ScrollingDirection.Right;
+
///
/// A set of top-level s which have an up-to-date layout.
///
@@ -48,99 +62,64 @@ namespace osu.Game.Rulesets.UI.Scrolling
}
///
- /// Given a position in screen space, return the time within this column.
+ /// Given a position at , return the time of the object corresponding to the position.
///
- public double TimeAtScreenSpacePosition(Vector2 screenSpacePosition)
+ ///
+ /// If there are multiple valid time values, one arbitrary time is returned.
+ ///
+ public double TimeAtPosition(float localPosition, double currentTime)
{
- // convert to local space of column so we can snap and fetch correct location.
- Vector2 localPosition = ToLocalSpace(screenSpacePosition);
-
- float position = 0;
-
- switch (scrollingInfo.Direction.Value)
- {
- case ScrollingDirection.Up:
- case ScrollingDirection.Down:
- position = localPosition.Y;
- break;
-
- case ScrollingDirection.Right:
- case ScrollingDirection.Left:
- position = localPosition.X;
- break;
- }
-
- flipPositionIfRequired(ref position);
-
- return scrollingInfo.Algorithm.TimeAt(position, Time.Current, scrollingInfo.TimeRange.Value, scrollLength);
+ float scrollPosition = axisInverted ? scrollLength - localPosition : localPosition;
+ return scrollingInfo.Algorithm.TimeAt(scrollPosition, currentTime, timeRange.Value, scrollLength);
}
///
- /// Given a time, return the screen space position within this column.
+ /// Given a position at the current time in screen space, return the time of the object corresponding the position.
+ ///
+ ///
+ /// If there are multiple valid time values, one arbitrary time is returned.
+ ///
+ public double TimeAtScreenSpacePosition(Vector2 screenSpacePosition)
+ {
+ Vector2 localPosition = ToLocalSpace(screenSpacePosition);
+ return TimeAtPosition(scrollingAxis == Direction.Horizontal ? localPosition.X : localPosition.Y, Time.Current);
+ }
+
+ ///
+ /// Given a time, return the position along the scrolling axis within this at time .
+ ///
+ public float PositionAtTime(double time, double currentTime)
+ {
+ float scrollPosition = scrollingInfo.Algorithm.PositionAt(time, currentTime, timeRange.Value, scrollLength);
+ return axisInverted ? scrollLength - scrollPosition : scrollPosition;
+ }
+
+ ///
+ /// Given a time, return the position along the scrolling axis within this at the current time.
+ ///
+ public float PositionAtTime(double time) => PositionAtTime(time, Time.Current);
+
+ ///
+ /// Given a time, return the screen space position within this .
+ /// In the non-scrolling axis, the center of this is returned.
///
public Vector2 ScreenSpacePositionAtTime(double time)
{
- var pos = scrollingInfo.Algorithm.PositionAt(time, Time.Current, scrollingInfo.TimeRange.Value, scrollLength);
-
- flipPositionIfRequired(ref pos);
-
- switch (scrollingInfo.Direction.Value)
- {
- case ScrollingDirection.Up:
- case ScrollingDirection.Down:
- return ToScreenSpace(new Vector2(getBreadth() / 2, pos));
-
- default:
- return ToScreenSpace(new Vector2(pos, getBreadth() / 2));
- }
+ float localPosition = PositionAtTime(time, Time.Current);
+ return scrollingAxis == Direction.Horizontal
+ ? ToScreenSpace(new Vector2(localPosition, DrawHeight / 2))
+ : ToScreenSpace(new Vector2(DrawWidth / 2, localPosition));
}
- private float scrollLength
+ ///
+ /// Given a start time and end time of a scrolling object, return the length of the object along the scrolling axis.
+ ///
+ public float LengthAtTime(double startTime, double endTime)
{
- get
- {
- switch (scrollingInfo.Direction.Value)
- {
- case ScrollingDirection.Left:
- case ScrollingDirection.Right:
- return DrawWidth;
-
- default:
- return DrawHeight;
- }
- }
+ return scrollingInfo.Algorithm.GetLength(startTime, endTime, timeRange.Value, scrollLength);
}
- private float getBreadth()
- {
- switch (scrollingInfo.Direction.Value)
- {
- case ScrollingDirection.Up:
- case ScrollingDirection.Down:
- return DrawWidth;
-
- default:
- return DrawHeight;
- }
- }
-
- private void flipPositionIfRequired(ref float position)
- {
- // We're dealing with screen coordinates in which the position decreases towards the centre of the screen resulting in an increase in start time.
- // The scrolling algorithm instead assumes a top anchor meaning an increase in time corresponds to an increase in position,
- // so when scrolling downwards the coordinates need to be flipped.
-
- switch (scrollingInfo.Direction.Value)
- {
- case ScrollingDirection.Down:
- position = DrawHeight - position;
- break;
-
- case ScrollingDirection.Right:
- position = DrawWidth - position;
- break;
- }
- }
+ private float scrollLength => scrollingAxis == Direction.Horizontal ? DrawWidth : DrawHeight;
protected override void AddDrawable(HitObjectLifetimeEntry entry, DrawableHitObject drawable)
{
@@ -237,18 +216,11 @@ namespace osu.Game.Rulesets.UI.Scrolling
{
if (hitObject.HitObject is IHasDuration e)
{
- switch (direction.Value)
- {
- case ScrollingDirection.Up:
- case ScrollingDirection.Down:
- hitObject.Height = scrollingInfo.Algorithm.GetLength(hitObject.HitObject.StartTime, e.EndTime, timeRange.Value, scrollLength);
- break;
-
- case ScrollingDirection.Left:
- case ScrollingDirection.Right:
- hitObject.Width = scrollingInfo.Algorithm.GetLength(hitObject.HitObject.StartTime, e.EndTime, timeRange.Value, scrollLength);
- break;
- }
+ float length = LengthAtTime(hitObject.HitObject.StartTime, e.EndTime);
+ if (scrollingAxis == Direction.Horizontal)
+ hitObject.Width = length;
+ else
+ hitObject.Height = length;
}
foreach (var obj in hitObject.NestedHitObjects)
@@ -262,24 +234,16 @@ namespace osu.Game.Rulesets.UI.Scrolling
private void updatePosition(DrawableHitObject hitObject, double currentTime)
{
- switch (direction.Value)
- {
- case ScrollingDirection.Up:
- hitObject.Y = scrollingInfo.Algorithm.PositionAt(hitObject.HitObject.StartTime, currentTime, timeRange.Value, scrollLength);
- break;
+ float position = PositionAtTime(hitObject.HitObject.StartTime, currentTime);
- case ScrollingDirection.Down:
- hitObject.Y = -scrollingInfo.Algorithm.PositionAt(hitObject.HitObject.StartTime, currentTime, timeRange.Value, scrollLength);
- break;
+ // The position returned from `PositionAtTime` is assuming the `TopLeft` anchor.
+ // A correction is needed because the hit objects are using a different anchor for each direction (e.g. `BottomCentre` for `Bottom` direction).
+ float anchorCorrection = axisInverted ? scrollLength : 0;
- case ScrollingDirection.Left:
- hitObject.X = scrollingInfo.Algorithm.PositionAt(hitObject.HitObject.StartTime, currentTime, timeRange.Value, scrollLength);
- break;
-
- case ScrollingDirection.Right:
- hitObject.X = -scrollingInfo.Algorithm.PositionAt(hitObject.HitObject.StartTime, currentTime, timeRange.Value, scrollLength);
- break;
- }
+ if (scrollingAxis == Direction.Horizontal)
+ hitObject.X = position - anchorCorrection;
+ else
+ hitObject.Y = position - anchorCorrection;
}
}
}
diff --git a/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs b/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs
index 8ddae67dba..a86a614a05 100644
--- a/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs
+++ b/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs
@@ -44,6 +44,8 @@ namespace osu.Game.Screens.Select.Leaderboards
private IBindable> itemRemoved;
+ private IBindable> itemAdded;
+
///
/// Whether to apply the game's currently selected mods as a filter when retrieving scores.
///
@@ -85,6 +87,9 @@ namespace osu.Game.Screens.Select.Leaderboards
itemRemoved = scoreManager.ItemRemoved.GetBoundCopy();
itemRemoved.BindValueChanged(onScoreRemoved);
+
+ itemAdded = scoreManager.ItemUpdated.GetBoundCopy();
+ itemAdded.BindValueChanged(onScoreAdded);
}
protected override void Reset()
@@ -93,7 +98,25 @@ namespace osu.Game.Screens.Select.Leaderboards
TopScore = null;
}
- private void onScoreRemoved(ValueChangedEvent> score) => Schedule(RefreshScores);
+ private void onScoreRemoved(ValueChangedEvent> score) =>
+ scoreStoreChanged(score);
+
+ private void onScoreAdded(ValueChangedEvent> score) =>
+ scoreStoreChanged(score);
+
+ private void scoreStoreChanged(ValueChangedEvent> score)
+ {
+ if (Scope != BeatmapLeaderboardScope.Local)
+ return;
+
+ if (score.NewValue.TryGetTarget(out var scoreInfo))
+ {
+ if (Beatmap?.ID != scoreInfo.BeatmapInfoID)
+ return;
+ }
+
+ RefreshScores();
+ }
protected override bool IsOnlineScope => Scope != BeatmapLeaderboardScope.Local;
diff --git a/osu.Game/Tests/Visual/OsuManualInputManagerTestScene.cs b/osu.Game/Tests/Visual/OsuManualInputManagerTestScene.cs
index c7edc0174a..01dd7a25c8 100644
--- a/osu.Game/Tests/Visual/OsuManualInputManagerTestScene.cs
+++ b/osu.Game/Tests/Visual/OsuManualInputManagerTestScene.cs
@@ -4,7 +4,6 @@
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
-using osu.Framework.Input;
using osu.Framework.Testing.Input;
using osu.Game.Graphics.Cursor;
using osu.Game.Graphics.Sprites;
@@ -49,7 +48,7 @@ namespace osu.Game.Tests.Visual
InputManager = new ManualInputManager
{
UseParentInput = true,
- Child = new PlatformActionContainer().WithChild(mainContent)
+ Child = mainContent
},
new Container
{
diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj
index 7eb3c84582..a7bd5f2e9f 100644
--- a/osu.Game/osu.Game.csproj
+++ b/osu.Game/osu.Game.csproj
@@ -34,7 +34,7 @@
all
runtime; build; native; contentfiles; analyzers; buildtransitive
-
+
diff --git a/osu.iOS.props b/osu.iOS.props
index 3e8facaf6e..5b3efb4ba4 100644
--- a/osu.iOS.props
+++ b/osu.iOS.props
@@ -70,7 +70,7 @@
-
+
@@ -93,7 +93,7 @@
-
+