diff --git a/osu.Android.props b/osu.Android.props
index f4d08e443c..522d28dca7 100644
--- a/osu.Android.props
+++ b/osu.Android.props
@@ -11,7 +11,7 @@
manifestmerger.jar
-
+
diff --git a/osu.Desktop/OsuGameDesktop.cs b/osu.Desktop/OsuGameDesktop.cs
index d92fea27bf..21cea3ba76 100644
--- a/osu.Desktop/OsuGameDesktop.cs
+++ b/osu.Desktop/OsuGameDesktop.cs
@@ -2,12 +2,10 @@
// See the LICENCE file in the repository root for full licence text.
using System;
-using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Reflection;
using System.Runtime.Versioning;
-using System.Threading.Tasks;
using Microsoft.Win32;
using osu.Desktop.Security;
using osu.Framework.Platform;
@@ -17,7 +15,6 @@ using osu.Framework;
using osu.Framework.Logging;
using osu.Game.Updater;
using osu.Desktop.Windows;
-using osu.Framework.Threading;
using osu.Game.IO;
using osu.Game.IPC;
using osu.Game.Utils;
@@ -138,52 +135,10 @@ namespace osu.Desktop
desktopWindow.CursorState |= CursorState.Hidden;
desktopWindow.Title = Name;
- desktopWindow.DragDrop += f =>
- {
- // on macOS, URL associations are handled via SDL_DROPFILE events.
- if (f.StartsWith(OSU_PROTOCOL, StringComparison.Ordinal))
- {
- HandleLink(f);
- return;
- }
-
- fileDrop(new[] { f });
- };
}
protected override BatteryInfo CreateBatteryInfo() => new SDL2BatteryInfo();
- private readonly List importableFiles = new List();
- private ScheduledDelegate? importSchedule;
-
- private void fileDrop(string[] filePaths)
- {
- lock (importableFiles)
- {
- importableFiles.AddRange(filePaths);
-
- Logger.Log($"Adding {filePaths.Length} files for import");
-
- // File drag drop operations can potentially trigger hundreds or thousands of these calls on some platforms.
- // In order to avoid spawning multiple import tasks for a single drop operation, debounce a touch.
- importSchedule?.Cancel();
- importSchedule = Scheduler.AddDelayed(handlePendingImports, 100);
- }
- }
-
- private void handlePendingImports()
- {
- lock (importableFiles)
- {
- Logger.Log($"Handling batch import of {importableFiles.Count} files");
-
- string[] paths = importableFiles.ToArray();
- importableFiles.Clear();
-
- Task.Factory.StartNew(() => Import(paths), TaskCreationOptions.LongRunning);
- }
- }
-
protected override void Dispose(bool isDisposing)
{
base.Dispose(isDisposing);
diff --git a/osu.Game.Rulesets.Mania.Tests/Skinning/TestScenePlayfield.cs b/osu.Game.Rulesets.Mania.Tests/Skinning/TestScenePlayfield.cs
index f85e303940..6485cbb76b 100644
--- a/osu.Game.Rulesets.Mania.Tests/Skinning/TestScenePlayfield.cs
+++ b/osu.Game.Rulesets.Mania.Tests/Skinning/TestScenePlayfield.cs
@@ -8,6 +8,7 @@ using NUnit.Framework;
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Mania.Beatmaps;
using osu.Game.Rulesets.Mania.UI;
+using osuTK;
namespace osu.Game.Rulesets.Mania.Tests.Skinning
{
@@ -25,22 +26,35 @@ namespace osu.Game.Rulesets.Mania.Tests.Skinning
new StageDefinition(2)
};
- SetContents(_ => new ManiaPlayfield(stageDefinitions));
+ SetContents(_ => new ManiaInputManager(new ManiaRuleset().RulesetInfo, 2)
+ {
+ Child = new ManiaPlayfield(stageDefinitions)
+ });
});
}
- [Test]
- public void TestDualStages()
+ [TestCase(2)]
+ [TestCase(3)]
+ [TestCase(5)]
+ public void TestDualStages(int columnCount)
{
AddStep("create stage", () =>
{
stageDefinitions = new List
{
- new StageDefinition(2),
- new StageDefinition(2)
+ new StageDefinition(columnCount),
+ new StageDefinition(columnCount)
};
- SetContents(_ => new ManiaPlayfield(stageDefinitions));
+ SetContents(_ => new ManiaInputManager(new ManiaRuleset().RulesetInfo, (int)PlayfieldType.Dual + 2 * columnCount)
+ {
+ Child = new ManiaPlayfield(stageDefinitions)
+ {
+ // bit of a hack to make sure the dual stages fit on screen without overlapping each other.
+ Size = new Vector2(1.5f),
+ Scale = new Vector2(1 / 1.5f)
+ }
+ });
});
}
diff --git a/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs b/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs
index 632b7cdcc7..bdc5a00583 100644
--- a/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs
+++ b/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs
@@ -119,14 +119,12 @@ namespace osu.Game.Rulesets.Mania.Beatmaps
yield return obj;
}
- private readonly List prevNoteTimes = new List(max_notes_for_density);
+ private readonly LimitedCapacityQueue prevNoteTimes = new LimitedCapacityQueue(max_notes_for_density);
private double density = int.MaxValue;
private void computeDensity(double newNoteTime)
{
- if (prevNoteTimes.Count == max_notes_for_density)
- prevNoteTimes.RemoveAt(0);
- prevNoteTimes.Add(newNoteTime);
+ prevNoteTimes.Enqueue(newNoteTime);
if (prevNoteTimes.Count >= 2)
density = (prevNoteTimes[^1] - prevNoteTimes[0]) / prevNoteTimes.Count;
diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs
index 3f91328128..a8563d65c4 100644
--- a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs
+++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs
@@ -242,18 +242,23 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
bodyPiece.Y = (Direction.Value == ScrollingDirection.Up ? 1 : -1) * Head.Height / 2;
bodyPiece.Height = DrawHeight - Head.Height / 2 + Tail.Height / 2;
- // As the note is being held, adjust the size of the sizing container. This has two effects:
- // 1. The contained masking container will mask the body and ticks.
- // 2. The head note will move along with the new "head position" in the container.
- //
- // As per stable, this should not apply for early hits, waiting until the object starts to touch the
- // judgement area first.
- if (Head.IsHit && releaseTime == null && DrawHeight > 0 && Time.Current >= HitObject.StartTime)
+ if (Time.Current >= HitObject.StartTime)
{
- // How far past the hit target this hold note is.
- float yOffset = Direction.Value == ScrollingDirection.Up ? -Y : Y;
- sizingContainer.Height = 1 - yOffset / DrawHeight;
+ // As the note is being held, adjust the size of the sizing container. This has two effects:
+ // 1. The contained masking container will mask the body and ticks.
+ // 2. The head note will move along with the new "head position" in the container.
+ //
+ // As per stable, this should not apply for early hits, waiting until the object starts to touch the
+ // judgement area first.
+ if (Head.IsHit && releaseTime == null && DrawHeight > 0)
+ {
+ // How far past the hit target this hold note is.
+ float yOffset = Direction.Value == ScrollingDirection.Up ? -Y : Y;
+ sizingContainer.Height = 1 - yOffset / DrawHeight;
+ }
}
+ else
+ sizingContainer.Height = 1;
}
protected override void CheckForResult(bool userTriggered, double timeOffset)
diff --git a/osu.Game.Rulesets.Mania/Skinning/Default/ManiaTrianglesSkinTransformer.cs b/osu.Game.Rulesets.Mania/Skinning/Default/ManiaTrianglesSkinTransformer.cs
index eb51179cea..3e0fe8ed4b 100644
--- a/osu.Game.Rulesets.Mania/Skinning/Default/ManiaTrianglesSkinTransformer.cs
+++ b/osu.Game.Rulesets.Mania/Skinning/Default/ManiaTrianglesSkinTransformer.cs
@@ -35,10 +35,12 @@ namespace osu.Game.Rulesets.Mania.Skinning.Default
var stage = beatmap.GetStageForColumnIndex(column);
- if (stage.IsSpecialColumn(column))
+ int columnInStage = column % stage.Columns;
+
+ if (stage.IsSpecialColumn(columnInStage))
return SkinUtils.As(new Bindable(colourSpecial));
- int distanceToEdge = Math.Min(column, (stage.Columns - 1) - column);
+ int distanceToEdge = Math.Min(columnInStage, (stage.Columns - 1) - columnInStage);
return SkinUtils.As(new Bindable(distanceToEdge % 2 == 0 ? colourOdd : colourEven));
}
}
diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneFreeModSelectOverlay.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneFreeModSelectOverlay.cs
index 45f671618e..66ba908879 100644
--- a/osu.Game.Tests/Visual/Multiplayer/TestSceneFreeModSelectOverlay.cs
+++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneFreeModSelectOverlay.cs
@@ -8,6 +8,7 @@ using System.Linq;
using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
+using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Input;
using osu.Framework.Testing;
@@ -57,11 +58,36 @@ namespace osu.Game.Tests.Visual.Multiplayer
AddAssert("customisation area not expanded", () => this.ChildrenOfType().Single().Height == 0);
}
+ [Test]
+ public void TestSelectAllButtonUpdatesStateWhenSearchTermChanged()
+ {
+ createFreeModSelect();
+
+ AddStep("apply search term", () => freeModSelectOverlay.SearchTerm = "ea");
+
+ AddAssert("select all button enabled", () => this.ChildrenOfType().Single().Enabled.Value);
+
+ AddStep("click select all button", navigateAndClick);
+ AddAssert("select all button disabled", () => !this.ChildrenOfType().Single().Enabled.Value);
+
+ AddStep("change search term", () => freeModSelectOverlay.SearchTerm = "e");
+
+ AddAssert("select all button enabled", () => this.ChildrenOfType().Single().Enabled.Value);
+
+ void navigateAndClick() where T : Drawable
+ {
+ InputManager.MoveMouseTo(this.ChildrenOfType().Single());
+ InputManager.Click(MouseButton.Left);
+ }
+ }
+
[Test]
public void TestSelectDeselectAllViaKeyboard()
{
createFreeModSelect();
+ AddStep("kill search bar focus", () => freeModSelectOverlay.SearchTextBox.KillFocus());
+
AddStep("press ctrl+a", () => InputManager.Keys(PlatformAction.SelectAll));
AddUntilStep("all mods selected", assertAllAvailableModsSelected);
diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSongSelect.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSongSelect.cs
index 9b130071cc..947b7e5be6 100644
--- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSongSelect.cs
+++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSongSelect.cs
@@ -94,6 +94,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
[TestCase(typeof(OsuModHidden), typeof(OsuModTraceable))] // Incompatible.
public void TestAllowedModDeselectedWhenRequired(Type allowedMod, Type requiredMod)
{
+ AddStep("change ruleset", () => Ruleset.Value = new OsuRuleset().RulesetInfo);
AddStep($"select {allowedMod.ReadableName()} as allowed", () => songSelect.FreeMods.Value = new[] { (Mod)Activator.CreateInstance(allowedMod) });
AddStep($"select {requiredMod.ReadableName()} as required", () => songSelect.Mods.Value = new[] { (Mod)Activator.CreateInstance(requiredMod) });
@@ -102,17 +103,17 @@ namespace osu.Game.Tests.Visual.Multiplayer
// A previous test's mod overlay could still be fading out.
AddUntilStep("wait for only one freemod overlay", () => this.ChildrenOfType().Count() == 1);
- assertHasFreeModButton(allowedMod, false);
- assertHasFreeModButton(requiredMod, false);
+ assertFreeModNotShown(allowedMod);
+ assertFreeModNotShown(requiredMod);
}
- private void assertHasFreeModButton(Type type, bool hasButton = true)
+ private void assertFreeModNotShown(Type type)
{
- AddAssert($"{type.ReadableName()} {(hasButton ? "displayed" : "not displayed")} in freemod overlay",
+ AddAssert($"{type.ReadableName()} not displayed in freemod overlay",
() => this.ChildrenOfType()
.Single()
.ChildrenOfType()
- .Where(panel => !panel.Filtered.Value)
+ .Where(panel => panel.Visible)
.All(b => b.Mod.GetType() != type));
}
diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs
index 8816787ceb..a41eff067b 100644
--- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs
+++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs
@@ -203,7 +203,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
AddUntilStep("mod select contains only double time mod",
() => this.ChildrenOfType().Single().UserModsSelectOverlay
.ChildrenOfType()
- .SingleOrDefault(panel => !panel.Filtered.Value)?.Mod is OsuModDoubleTime);
+ .SingleOrDefault(panel => panel.Visible)?.Mod is OsuModDoubleTime);
}
[Test]
diff --git a/osu.Game.Tests/Visual/Online/TestSceneBeatmapSetOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneBeatmapSetOverlay.cs
index a27c4ddad2..d9763ef6c8 100644
--- a/osu.Game.Tests/Visual/Online/TestSceneBeatmapSetOverlay.cs
+++ b/osu.Game.Tests/Visual/Online/TestSceneBeatmapSetOverlay.cs
@@ -86,6 +86,7 @@ namespace osu.Game.Tests.Visual.Online
StarRating = 9.99,
DifficultyName = @"TEST",
Length = 456000,
+ HitLength = 400000,
RulesetID = 3,
CircleSize = 1,
DrainRate = 2.3f,
diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneModColumn.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneModColumn.cs
index a11000214c..18739c0275 100644
--- a/osu.Game.Tests/Visual/UserInterface/TestSceneModColumn.cs
+++ b/osu.Game.Tests/Visual/UserInterface/TestSceneModColumn.cs
@@ -106,26 +106,26 @@ namespace osu.Game.Tests.Visual.UserInterface
});
AddStep("set filter", () => setFilter(mod => mod.Name.Contains("Wind", StringComparison.CurrentCultureIgnoreCase)));
- AddUntilStep("two panels visible", () => column.ChildrenOfType().Count(panel => !panel.Filtered.Value) == 2);
+ AddUntilStep("two panels visible", () => column.ChildrenOfType().Count(panel => panel.Visible) == 2);
clickToggle();
AddUntilStep("wait for animation", () => !column.SelectionAnimationRunning);
- AddAssert("only visible items selected", () => column.ChildrenOfType().Where(panel => panel.Active.Value).All(panel => !panel.Filtered.Value));
+ AddAssert("only visible items selected", () => column.ChildrenOfType().Where(panel => panel.Active.Value).All(panel => panel.Visible));
AddStep("unset filter", () => setFilter(null));
- AddUntilStep("all panels visible", () => column.ChildrenOfType().All(panel => !panel.Filtered.Value));
+ AddUntilStep("all panels visible", () => column.ChildrenOfType().All(panel => panel.Visible));
AddAssert("checkbox not selected", () => !column.ChildrenOfType().Single().Current.Value);
AddStep("set filter", () => setFilter(mod => mod.Name.Contains("Wind", StringComparison.CurrentCultureIgnoreCase)));
- AddUntilStep("two panels visible", () => column.ChildrenOfType().Count(panel => !panel.Filtered.Value) == 2);
+ AddUntilStep("two panels visible", () => column.ChildrenOfType().Count(panel => panel.Visible) == 2);
AddAssert("checkbox selected", () => column.ChildrenOfType().Single().Current.Value);
AddStep("filter out everything", () => setFilter(_ => false));
- AddUntilStep("no panels visible", () => column.ChildrenOfType().All(panel => panel.Filtered.Value));
+ AddUntilStep("no panels visible", () => column.ChildrenOfType().All(panel => !panel.Visible));
AddUntilStep("checkbox hidden", () => !column.ChildrenOfType().Single().IsPresent);
AddStep("inset filter", () => setFilter(null));
- AddUntilStep("all panels visible", () => column.ChildrenOfType().All(panel => !panel.Filtered.Value));
+ AddUntilStep("all panels visible", () => column.ChildrenOfType().All(panel => panel.Visible));
AddUntilStep("checkbox visible", () => column.ChildrenOfType().Single().IsPresent);
void clickToggle() => AddStep("click toggle", () =>
@@ -288,10 +288,53 @@ namespace osu.Game.Tests.Visual.UserInterface
AddAssert("no change", () => this.ChildrenOfType().Count(panel => panel.Active.Value) == 2);
}
+ [Test]
+ public void TestApplySearchTerms()
+ {
+ Mod hidden = getExampleModsFor(ModType.DifficultyIncrease).Where(modState => modState.Mod is ModHidden).Select(modState => modState.Mod).Single();
+
+ ModColumn column = null!;
+ AddStep("create content", () => Child = new Container
+ {
+ RelativeSizeAxes = Axes.Both,
+ Padding = new MarginPadding(30),
+ Child = column = new ModColumn(ModType.DifficultyIncrease, false)
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ AvailableMods = getExampleModsFor(ModType.DifficultyIncrease)
+ }
+ });
+
+ applySearchAndAssert(hidden.Name);
+
+ clearSearch();
+
+ applySearchAndAssert(hidden.Acronym);
+
+ clearSearch();
+
+ applySearchAndAssert(hidden.Description.ToString());
+
+ void applySearchAndAssert(string searchTerm)
+ {
+ AddStep("search by mod name", () => column.SearchTerm = searchTerm);
+
+ AddAssert("only hidden is visible", () => column.ChildrenOfType().Where(panel => panel.Visible).All(panel => panel.Mod is ModHidden));
+ }
+
+ void clearSearch()
+ {
+ AddStep("clear search", () => column.SearchTerm = string.Empty);
+
+ AddAssert("all mods are visible", () => column.ChildrenOfType().All(panel => panel.Visible));
+ }
+ }
+
private void setFilter(Func? filter)
{
foreach (var modState in this.ChildrenOfType().Single().AvailableMods)
- modState.Filtered.Value = filter?.Invoke(modState.Mod) == false;
+ modState.ValidForSelection.Value = filter?.Invoke(modState.Mod) != false;
}
private partial class TestModColumn : ModColumn
diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneModPresetColumn.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneModPresetColumn.cs
index 3efdba8754..2d54a4e566 100644
--- a/osu.Game.Tests/Visual/UserInterface/TestSceneModPresetColumn.cs
+++ b/osu.Game.Tests/Visual/UserInterface/TestSceneModPresetColumn.cs
@@ -392,6 +392,28 @@ namespace osu.Game.Tests.Visual.UserInterface
new HashSet(this.ChildrenOfType().First().Preset.Value.Mods).SetEquals(mods));
}
+ [Test]
+ public void TestTextFiltering()
+ {
+ ModPresetColumn modPresetColumn = null!;
+
+ AddStep("clear mods", () => SelectedMods.Value = Array.Empty());
+ AddStep("create content", () => Child = modPresetColumn = new ModPresetColumn
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ });
+
+ AddUntilStep("items loaded", () => modPresetColumn.IsLoaded && modPresetColumn.ItemsLoaded);
+
+ AddStep("set osu! ruleset", () => Ruleset.Value = rulesets.GetRuleset(0));
+ AddStep("set text filter", () => modPresetColumn.SearchTerm = "First");
+ AddUntilStep("one panel visible", () => modPresetColumn.ChildrenOfType().Count(panel => panel.IsPresent), () => Is.EqualTo(1));
+
+ AddStep("set mania ruleset", () => Ruleset.Value = rulesets.GetRuleset(3));
+ AddUntilStep("no panels visible", () => modPresetColumn.ChildrenOfType().Count(panel => panel.IsPresent), () => Is.EqualTo(0));
+ }
+
private ICollection createTestPresets() => new[]
{
new ModPreset
diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs
index 5cf24c1960..d566a04261 100644
--- a/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs
+++ b/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs
@@ -490,15 +490,15 @@ namespace osu.Game.Tests.Visual.UserInterface
createScreen();
changeRuleset(0);
- AddAssert("double time visible", () => modSelectOverlay.ChildrenOfType().Where(panel => panel.Mod is OsuModDoubleTime).Any(panel => !panel.Filtered.Value));
+ AddAssert("double time visible", () => modSelectOverlay.ChildrenOfType().Where(panel => panel.Mod is OsuModDoubleTime).Any(panel => panel.Visible));
AddStep("make double time invalid", () => modSelectOverlay.IsValidMod = m => !(m is OsuModDoubleTime));
- AddUntilStep("double time not visible", () => modSelectOverlay.ChildrenOfType().Where(panel => panel.Mod is OsuModDoubleTime).All(panel => panel.Filtered.Value));
- AddAssert("nightcore still visible", () => modSelectOverlay.ChildrenOfType().Where(panel => panel.Mod is OsuModNightcore).Any(panel => !panel.Filtered.Value));
+ AddUntilStep("double time not visible", () => modSelectOverlay.ChildrenOfType().Where(panel => panel.Mod is OsuModDoubleTime).All(panel => !panel.Visible));
+ AddAssert("nightcore still visible", () => modSelectOverlay.ChildrenOfType().Where(panel => panel.Mod is OsuModNightcore).Any(panel => panel.Visible));
AddStep("make double time valid again", () => modSelectOverlay.IsValidMod = _ => true);
- AddUntilStep("double time visible", () => modSelectOverlay.ChildrenOfType().Where(panel => panel.Mod is OsuModDoubleTime).Any(panel => !panel.Filtered.Value));
- AddAssert("nightcore still visible", () => modSelectOverlay.ChildrenOfType().Where(b => b.Mod is OsuModNightcore).Any(panel => !panel.Filtered.Value));
+ AddUntilStep("double time visible", () => modSelectOverlay.ChildrenOfType().Where(panel => panel.Mod is OsuModDoubleTime).Any(panel => panel.Visible));
+ AddAssert("nightcore still visible", () => modSelectOverlay.ChildrenOfType().Where(b => b.Mod is OsuModNightcore).Any(panel => panel.Visible));
}
[Test]
@@ -524,7 +524,57 @@ namespace osu.Game.Tests.Visual.UserInterface
AddStep("set ruleset", () => Ruleset.Value = testRuleset.RulesetInfo);
waitForColumnLoad();
- AddAssert("unimplemented mod panel is filtered", () => getPanelForMod(typeof(TestUnimplementedMod)).Filtered.Value);
+ AddAssert("unimplemented mod panel is filtered", () => !getPanelForMod(typeof(TestUnimplementedMod)).Visible);
+ }
+
+ [Test]
+ public void TestFirstModSelectDeselect()
+ {
+ createScreen();
+
+ AddStep("apply search", () => modSelectOverlay.SearchTerm = "HD");
+
+ AddStep("press enter", () => InputManager.Key(Key.Enter));
+ AddAssert("hidden selected", () => getPanelForMod(typeof(OsuModHidden)).Active.Value);
+
+ AddStep("press enter again", () => InputManager.Key(Key.Enter));
+ AddAssert("hidden deselected", () => !getPanelForMod(typeof(OsuModHidden)).Active.Value);
+
+ AddStep("clear search", () => modSelectOverlay.SearchTerm = string.Empty);
+ AddStep("press enter", () => InputManager.Key(Key.Enter));
+ AddAssert("mod select hidden", () => modSelectOverlay.State.Value == Visibility.Hidden);
+ }
+
+ [Test]
+ public void TestSearchFocusChangeViaClick()
+ {
+ createScreen();
+
+ AddStep("click on search", navigateAndClick);
+ AddAssert("focused", () => modSelectOverlay.SearchTextBox.HasFocus);
+
+ AddStep("click on mod column", navigateAndClick);
+ AddAssert("lost focus", () => !modSelectOverlay.SearchTextBox.HasFocus);
+
+ void navigateAndClick() where T : Drawable
+ {
+ InputManager.MoveMouseTo(modSelectOverlay.ChildrenOfType().FirstOrDefault());
+ InputManager.Click(MouseButton.Left);
+ }
+ }
+
+ [Test]
+ public void TestSearchFocusChangeViaKey()
+ {
+ createScreen();
+
+ const Key focus_switch_key = Key.Tab;
+
+ AddStep("press tab", () => InputManager.Key(focus_switch_key));
+ AddAssert("focused", () => modSelectOverlay.SearchTextBox.HasFocus);
+
+ AddStep("press tab", () => InputManager.Key(focus_switch_key));
+ AddAssert("lost focus", () => !modSelectOverlay.SearchTextBox.HasFocus);
}
[Test]
@@ -533,6 +583,8 @@ namespace osu.Game.Tests.Visual.UserInterface
createScreen();
changeRuleset(0);
+ AddStep("kill search bar focus", () => modSelectOverlay.SearchTextBox.KillFocus());
+
AddStep("select DT + HD", () => SelectedMods.Value = new Mod[] { new OsuModDoubleTime(), new OsuModHidden() });
AddAssert("DT + HD selected", () => modSelectOverlay.ChildrenOfType().Count(panel => panel.Active.Value) == 2);
@@ -540,6 +592,26 @@ namespace osu.Game.Tests.Visual.UserInterface
AddUntilStep("all mods deselected", () => !SelectedMods.Value.Any());
}
+ [Test]
+ public void TestDeselectAllViaKey_WithSearchApplied()
+ {
+ createScreen();
+ changeRuleset(0);
+
+ AddStep("select DT + HD", () => SelectedMods.Value = new Mod[] { new OsuModDoubleTime(), new OsuModHidden() });
+ AddStep("focus on search", () => modSelectOverlay.SearchTextBox.TakeFocus());
+ AddStep("apply search", () => modSelectOverlay.SearchTerm = "Easy");
+ AddAssert("DT + HD selected and hidden", () => modSelectOverlay.ChildrenOfType().Count(panel => !panel.Visible && panel.Active.Value) == 2);
+
+ AddStep("press backspace", () => InputManager.Key(Key.BackSpace));
+ AddAssert("DT + HD still selected", () => modSelectOverlay.ChildrenOfType().Count(panel => panel.Active.Value) == 2);
+ AddAssert("search term changed", () => modSelectOverlay.SearchTerm == "Eas");
+
+ AddStep("kill focus", () => modSelectOverlay.SearchTextBox.KillFocus());
+ AddStep("press backspace", () => InputManager.Key(Key.BackSpace));
+ AddUntilStep("all mods deselected", () => !SelectedMods.Value.Any());
+ }
+
[Test]
public void TestDeselectAllViaButton()
{
@@ -561,6 +633,31 @@ namespace osu.Game.Tests.Visual.UserInterface
AddAssert("deselect all button disabled", () => !this.ChildrenOfType().Single().Enabled.Value);
}
+ [Test]
+ public void TestDeselectAllViaButton_WithSearchApplied()
+ {
+ createScreen();
+ changeRuleset(0);
+
+ AddAssert("deselect all button disabled", () => !this.ChildrenOfType().Single().Enabled.Value);
+
+ AddStep("select DT + HD + RD", () => SelectedMods.Value = new Mod[] { new OsuModDoubleTime(), new OsuModHidden(), new OsuModRandom() });
+ AddAssert("DT + HD + RD selected", () => modSelectOverlay.ChildrenOfType().Count(panel => panel.Active.Value) == 3);
+ AddAssert("deselect all button enabled", () => this.ChildrenOfType().Single().Enabled.Value);
+
+ AddStep("apply search", () => modSelectOverlay.SearchTerm = "Easy");
+ AddAssert("DT + HD + RD are hidden and selected", () => modSelectOverlay.ChildrenOfType().Count(panel => !panel.Visible && panel.Active.Value) == 3);
+ AddAssert("deselect all button enabled", () => this.ChildrenOfType().Single().Enabled.Value);
+
+ AddStep("click deselect all button", () =>
+ {
+ InputManager.MoveMouseTo(this.ChildrenOfType().Single());
+ InputManager.Click(MouseButton.Left);
+ });
+ AddUntilStep("all mods deselected", () => !SelectedMods.Value.Any());
+ AddAssert("deselect all button disabled", () => !this.ChildrenOfType().Single().Enabled.Value);
+ }
+
[Test]
public void TestCloseViaBackButton()
{
@@ -580,8 +677,11 @@ namespace osu.Game.Tests.Visual.UserInterface
AddAssert("mod select hidden", () => modSelectOverlay.State.Value == Visibility.Hidden);
}
+ ///
+ /// Covers columns hiding/unhiding on changes of .
+ ///
[Test]
- public void TestColumnHiding()
+ public void TestColumnHidingOnIsValidChange()
{
AddStep("create screen", () => Child = modSelectOverlay = new TestModSelectOverlay
{
@@ -610,6 +710,56 @@ namespace osu.Game.Tests.Visual.UserInterface
AddUntilStep("3 columns visible", () => this.ChildrenOfType().Count(col => col.IsPresent) == 3);
}
+ ///
+ /// Covers columns hiding/unhiding on changes of .
+ ///
+ [Test]
+ public void TestColumnHidingOnTextFilterChange()
+ {
+ AddStep("create screen", () => Child = modSelectOverlay = new TestModSelectOverlay
+ {
+ RelativeSizeAxes = Axes.Both,
+ State = { Value = Visibility.Visible },
+ SelectedMods = { BindTarget = SelectedMods }
+ });
+ waitForColumnLoad();
+ changeRuleset(0);
+
+ AddAssert("all columns visible", () => this.ChildrenOfType().All(col => col.IsPresent));
+
+ AddStep("set search", () => modSelectOverlay.SearchTerm = "HD");
+ AddAssert("one column visible", () => this.ChildrenOfType().Count(col => col.IsPresent) == 1);
+
+ AddStep("filter out everything", () => modSelectOverlay.SearchTerm = "Some long search term with no matches");
+ AddAssert("no columns visible", () => this.ChildrenOfType().All(col => !col.IsPresent));
+
+ AddStep("clear search bar", () => modSelectOverlay.SearchTerm = "");
+ AddAssert("all columns visible", () => this.ChildrenOfType().All(col => col.IsPresent));
+ }
+
+ [Test]
+ public void TestHidingOverlayClearsTextSearch()
+ {
+ AddStep("create screen", () => Child = modSelectOverlay = new TestModSelectOverlay
+ {
+ RelativeSizeAxes = Axes.Both,
+ State = { Value = Visibility.Visible },
+ SelectedMods = { BindTarget = SelectedMods }
+ });
+ waitForColumnLoad();
+ changeRuleset(0);
+
+ AddAssert("all columns visible", () => this.ChildrenOfType().All(col => col.IsPresent));
+
+ AddStep("set search", () => modSelectOverlay.SearchTerm = "fail");
+ AddAssert("one column visible", () => this.ChildrenOfType().Count(col => col.IsPresent) == 2);
+
+ AddStep("hide", () => modSelectOverlay.Hide());
+ AddStep("show", () => modSelectOverlay.Show());
+
+ AddAssert("all columns visible", () => this.ChildrenOfType().All(col => col.IsPresent));
+ }
+
[Test]
public void TestColumnHidingOnRulesetChange()
{
@@ -688,12 +838,10 @@ namespace osu.Game.Tests.Visual.UserInterface
{
public override string ShortName => "unimplemented";
- public override IEnumerable GetModsFor(ModType type)
- {
- if (type == ModType.Conversion) return base.GetModsFor(type).Concat(new[] { new TestUnimplementedMod() });
-
- return base.GetModsFor(type);
- }
+ public override IEnumerable GetModsFor(ModType type) =>
+ type == ModType.Conversion
+ ? base.GetModsFor(type).Concat(new[] { new TestUnimplementedMod() })
+ : base.GetModsFor(type);
}
}
}
diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneOverlayContainer.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneOverlayContainer.cs
index d9c2774611..bb94912c83 100644
--- a/osu.Game.Tests/Visual/UserInterface/TestSceneOverlayContainer.cs
+++ b/osu.Game.Tests/Visual/UserInterface/TestSceneOverlayContainer.cs
@@ -101,6 +101,10 @@ namespace osu.Game.Tests.Visual.UserInterface
},
};
}
+
+ protected override void PopIn()
+ {
+ }
}
}
}
diff --git a/osu.Game/Beatmaps/IBeatmapInfo.cs b/osu.Game/Beatmaps/IBeatmapInfo.cs
index 4f2c08f63d..b8c69cc525 100644
--- a/osu.Game/Beatmaps/IBeatmapInfo.cs
+++ b/osu.Game/Beatmaps/IBeatmapInfo.cs
@@ -33,7 +33,7 @@ namespace osu.Game.Beatmaps
IBeatmapSetInfo? BeatmapSet { get; }
///
- /// The playable length in milliseconds of this beatmap.
+ /// The total length in milliseconds of this beatmap.
///
double Length { get; }
diff --git a/osu.Game/Beatmaps/IBeatmapOnlineInfo.cs b/osu.Game/Beatmaps/IBeatmapOnlineInfo.cs
index e1634e7d24..707a0696ba 100644
--- a/osu.Game/Beatmaps/IBeatmapOnlineInfo.cs
+++ b/osu.Game/Beatmaps/IBeatmapOnlineInfo.cs
@@ -59,5 +59,10 @@ namespace osu.Game.Beatmaps
int PassCount { get; }
APIFailTimes? FailTimes { get; }
+
+ ///
+ /// The playable length in milliseconds of this beatmap.
+ ///
+ double HitLength { get; }
}
}
diff --git a/osu.Game/Collections/ManageCollectionsDialog.cs b/osu.Game/Collections/ManageCollectionsDialog.cs
index 36142cf26f..31016b807b 100644
--- a/osu.Game/Collections/ManageCollectionsDialog.cs
+++ b/osu.Game/Collections/ManageCollectionsDialog.cs
@@ -114,8 +114,6 @@ namespace osu.Game.Collections
protected override void PopIn()
{
- base.PopIn();
-
lowPassFilter.CutoffTo(300, 100, Easing.OutCubic);
this.FadeIn(enter_duration, Easing.OutQuint);
this.ScaleTo(0.9f).Then().ScaleTo(1f, enter_duration, Easing.OutQuint);
diff --git a/osu.Game/Configuration/OsuConfigManager.cs b/osu.Game/Configuration/OsuConfigManager.cs
index 365ad37f4c..193068193a 100644
--- a/osu.Game/Configuration/OsuConfigManager.cs
+++ b/osu.Game/Configuration/OsuConfigManager.cs
@@ -178,6 +178,7 @@ namespace osu.Game.Configuration
SetDefault(OsuSetting.EditorWaveformOpacity, 0.25f, 0f, 1f, 0.25f);
SetDefault(OsuSetting.EditorShowHitMarkers, true);
SetDefault(OsuSetting.EditorAutoSeekOnPlacement, true);
+ SetDefault(OsuSetting.EditorLimitedDistanceSnap, false);
SetDefault(OsuSetting.LastProcessedMetadataId, -1);
@@ -383,5 +384,6 @@ namespace osu.Game.Configuration
SafeAreaConsiderations,
ComboColourNormalisationAmount,
ProfileCoverExpanded,
+ EditorLimitedDistanceSnap
}
}
diff --git a/osu.Game/Database/RealmAccess.cs b/osu.Game/Database/RealmAccess.cs
index 48eb2826f5..da4caa42ba 100644
--- a/osu.Game/Database/RealmAccess.cs
+++ b/osu.Game/Database/RealmAccess.cs
@@ -77,8 +77,9 @@ namespace osu.Game.Database
/// 27 2023-06-06 Added EditorTimestamp to BeatmapInfo.
/// 28 2023-06-08 Added IsLegacyScore to ScoreInfo, parsed from replay files.
/// 29 2023-06-12 Run migration of old lazer scores to be best-effort in the new scoring number space. No actual realm changes.
+ /// 30 2023-06-16 Run migration of old lazer scores again. This time with more correct rounding considerations.
///
- private const int schema_version = 29;
+ private const int schema_version = 30;
///
/// Lock object which is held during sections, blocking realm retrieval during blocking periods.
@@ -938,6 +939,7 @@ namespace osu.Game.Database
}
case 29:
+ case 30:
{
var scores = migration.NewRealm
.All()
diff --git a/osu.Game/Database/StandardisedScoreMigrationTools.cs b/osu.Game/Database/StandardisedScoreMigrationTools.cs
index 66e64f3f7a..582a656efa 100644
--- a/osu.Game/Database/StandardisedScoreMigrationTools.cs
+++ b/osu.Game/Database/StandardisedScoreMigrationTools.cs
@@ -182,7 +182,7 @@ namespace osu.Game.Database
foreach (var mod in score.Mods)
modMultiplier *= mod.ScoreMultiplier;
- return (long)((1000000 * (accuracyPortion * accuracyScore + (1 - accuracyPortion) * comboScore) + bonusScore) * modMultiplier);
+ return (long)Math.Round((1000000 * (accuracyPortion * accuracyScore + (1 - accuracyPortion) * comboScore) + bonusScore) * modMultiplier);
}
private class FakeHit : HitObject
diff --git a/osu.Game/Graphics/Containers/OsuFocusedOverlayContainer.cs b/osu.Game/Graphics/Containers/OsuFocusedOverlayContainer.cs
index 07b5b53e0e..f92cfc2306 100644
--- a/osu.Game/Graphics/Containers/OsuFocusedOverlayContainer.cs
+++ b/osu.Game/Graphics/Containers/OsuFocusedOverlayContainer.cs
@@ -152,7 +152,6 @@ namespace osu.Game.Graphics.Containers
protected override void PopOut()
{
- base.PopOut();
previewTrackManager.StopAnyPlaying(this);
}
diff --git a/osu.Game/Graphics/UserInterface/FPSCounter.cs b/osu.Game/Graphics/UserInterface/FPSCounter.cs
index 9dbeba6449..c1ef573848 100644
--- a/osu.Game/Graphics/UserInterface/FPSCounter.cs
+++ b/osu.Game/Graphics/UserInterface/FPSCounter.cs
@@ -167,9 +167,12 @@ namespace osu.Game.Graphics.UserInterface
{
base.Update();
+ double elapsedDrawFrameTime = drawClock.ElapsedFrameTime;
+ double elapsedUpdateFrameTime = updateClock.ElapsedFrameTime;
+
// If the game goes into a suspended state (ie. debugger attached or backgrounded on a mobile device)
// we want to ignore really long periods of no processing.
- if (updateClock.ElapsedFrameTime > 10000)
+ if (elapsedUpdateFrameTime > 10000)
return;
mainContent.Width = Math.Max(mainContent.Width, counters.DrawWidth);
@@ -178,17 +181,17 @@ namespace osu.Game.Graphics.UserInterface
// frame limiter (we want to show the FPS as it's changing, even if it isn't an outlier).
bool aimRatesChanged = updateAimFPS();
- bool hasUpdateSpike = displayedFrameTime < spike_time_ms && updateClock.ElapsedFrameTime > spike_time_ms;
+ bool hasUpdateSpike = displayedFrameTime < spike_time_ms && elapsedUpdateFrameTime > spike_time_ms;
// use elapsed frame time rather then FramesPerSecond to better catch stutter frames.
- bool hasDrawSpike = displayedFpsCount > (1000 / spike_time_ms) && drawClock.ElapsedFrameTime > spike_time_ms;
+ bool hasDrawSpike = displayedFpsCount > (1000 / spike_time_ms) && elapsedDrawFrameTime > spike_time_ms;
const float damp_time = 100;
- displayedFrameTime = Interpolation.DampContinuously(displayedFrameTime, updateClock.ElapsedFrameTime, hasUpdateSpike ? 0 : damp_time, updateClock.ElapsedFrameTime);
+ displayedFrameTime = Interpolation.DampContinuously(displayedFrameTime, elapsedUpdateFrameTime, hasUpdateSpike ? 0 : damp_time, elapsedUpdateFrameTime);
if (hasDrawSpike)
// show spike time using raw elapsed value, to account for `FramesPerSecond` being so averaged spike frames don't show.
- displayedFpsCount = 1000 / drawClock.ElapsedFrameTime;
+ displayedFpsCount = 1000 / elapsedDrawFrameTime;
else
displayedFpsCount = Interpolation.DampContinuously(displayedFpsCount, drawClock.FramesPerSecond, damp_time, Time.Elapsed);
diff --git a/osu.Game/Graphics/UserInterface/ShearedSearchTextBox.cs b/osu.Game/Graphics/UserInterface/ShearedSearchTextBox.cs
index 7bd083f9d5..fb0a66cb8d 100644
--- a/osu.Game/Graphics/UserInterface/ShearedSearchTextBox.cs
+++ b/osu.Game/Graphics/UserInterface/ShearedSearchTextBox.cs
@@ -10,6 +10,7 @@ using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Graphics.UserInterface;
+using osu.Framework.Localisation;
using osu.Game.Graphics.Sprites;
using osu.Game.Overlays;
using osu.Game.Overlays.Mods;
@@ -37,6 +38,14 @@ namespace osu.Game.Graphics.UserInterface
set => textBox.HoldFocus = value;
}
+ public LocalisableString PlaceholderText
+ {
+ get => textBox.PlaceholderText;
+ set => textBox.PlaceholderText = value;
+ }
+
+ public new bool HasFocus => textBox.HasFocus;
+
public void TakeFocus() => textBox.TakeFocus();
public void KillFocus() => textBox.KillFocus();
diff --git a/osu.Game/Localisation/EditorStrings.cs b/osu.Game/Localisation/EditorStrings.cs
index 20258b9c35..077bd92d4f 100644
--- a/osu.Game/Localisation/EditorStrings.cs
+++ b/osu.Game/Localisation/EditorStrings.cs
@@ -109,6 +109,11 @@ namespace osu.Game.Localisation
///
public static LocalisableString RotationSnapped(float newRotation) => new TranslatableString(getKey(@"rotation_snapped"), @"{0:0}° (snapped)", newRotation);
+ ///
+ /// "Limit distance snap placement to current time"
+ ///
+ public static LocalisableString LimitedDistanceSnap => new TranslatableString(getKey(@"limited_distance_snap_grid"), @"Limit distance snap placement to current time");
+
private static string getKey(string key) => $@"{prefix}:{key}";
}
}
diff --git a/osu.Game/Localisation/ModSelectOverlayStrings.cs b/osu.Game/Localisation/ModSelectOverlayStrings.cs
index f11c52ee20..05dcf138d7 100644
--- a/osu.Game/Localisation/ModSelectOverlayStrings.cs
+++ b/osu.Game/Localisation/ModSelectOverlayStrings.cs
@@ -1,4 +1,4 @@
-// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using osu.Framework.Localisation;
@@ -39,6 +39,11 @@ namespace osu.Game.Localisation
///
public static LocalisableString UseCurrentMods => new TranslatableString(getKey(@"use_current_mods"), @"Use current mods");
+ ///
+ /// "tab to search..."
+ ///
+ public static LocalisableString TabToSearch => new TranslatableString(getKey(@"tab_to_search"), @"tab to search...");
+
private static string getKey(string key) => $@"{prefix}:{key}";
}
-}
+}
\ No newline at end of file
diff --git a/osu.Game/Online/API/Requests/Responses/APIBeatmap.cs b/osu.Game/Online/API/Requests/Responses/APIBeatmap.cs
index 7d6740ee46..902b651be9 100644
--- a/osu.Game/Online/API/Requests/Responses/APIBeatmap.cs
+++ b/osu.Game/Online/API/Requests/Responses/APIBeatmap.cs
@@ -63,6 +63,16 @@ namespace osu.Game.Online.API.Requests.Responses
set => Length = TimeSpan.FromSeconds(value).TotalMilliseconds;
}
+ [JsonIgnore]
+ public double HitLength { get; set; }
+
+ [JsonProperty(@"hit_length")]
+ private double hitLengthInSeconds
+ {
+ get => TimeSpan.FromMilliseconds(HitLength).TotalSeconds;
+ set => HitLength = TimeSpan.FromSeconds(value).TotalMilliseconds;
+ }
+
[JsonProperty(@"convert")]
public bool Convert { get; set; }
diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs
index 3768dad370..a80639d4ff 100644
--- a/osu.Game/OsuGame.cs
+++ b/osu.Game/OsuGame.cs
@@ -6,6 +6,7 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
+using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
@@ -28,6 +29,7 @@ using osu.Framework.Input.Events;
using osu.Framework.Input.Handlers.Tablet;
using osu.Framework.Localisation;
using osu.Framework.Logging;
+using osu.Framework.Platform;
using osu.Framework.Screens;
using osu.Framework.Threading;
using osu.Game.Beatmaps;
@@ -281,6 +283,52 @@ namespace osu.Game
protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) =>
dependencies = new DependencyContainer(base.CreateChildDependencies(parent));
+ private readonly List dragDropFiles = new List();
+ private ScheduledDelegate dragDropImportSchedule;
+
+ public override void SetHost(GameHost host)
+ {
+ base.SetHost(host);
+
+ if (host.Window is SDL2Window sdlWindow)
+ {
+ sdlWindow.DragDrop += path =>
+ {
+ // on macOS/iOS, URL associations are handled via SDL_DROPFILE events.
+ if (path.StartsWith(OSU_PROTOCOL, StringComparison.Ordinal))
+ {
+ HandleLink(path);
+ return;
+ }
+
+ lock (dragDropFiles)
+ {
+ dragDropFiles.Add(path);
+
+ Logger.Log($@"Adding ""{Path.GetFileName(path)}"" for import");
+
+ // File drag drop operations can potentially trigger hundreds or thousands of these calls on some platforms.
+ // In order to avoid spawning multiple import tasks for a single drop operation, debounce a touch.
+ dragDropImportSchedule?.Cancel();
+ dragDropImportSchedule = Scheduler.AddDelayed(handlePendingDragDropImports, 100);
+ }
+ };
+ }
+ }
+
+ private void handlePendingDragDropImports()
+ {
+ lock (dragDropFiles)
+ {
+ Logger.Log($"Handling batch import of {dragDropFiles.Count} files");
+
+ string[] paths = dragDropFiles.ToArray();
+ dragDropFiles.Clear();
+
+ Task.Factory.StartNew(() => Import(paths), TaskCreationOptions.LongRunning);
+ }
+ }
+
[BackgroundDependencyLoader]
private void load()
{
diff --git a/osu.Game/Overlays/AccountCreationOverlay.cs b/osu.Game/Overlays/AccountCreationOverlay.cs
index 6f79316670..ef2e055eae 100644
--- a/osu.Game/Overlays/AccountCreationOverlay.cs
+++ b/osu.Game/Overlays/AccountCreationOverlay.cs
@@ -90,7 +90,6 @@ namespace osu.Game.Overlays
protected override void PopIn()
{
- base.PopIn();
this.FadeIn(transition_time, Easing.OutQuint);
if (welcomeScreen.GetChildScreen() != null)
diff --git a/osu.Game/Overlays/BeatmapSet/BasicStats.cs b/osu.Game/Overlays/BeatmapSet/BasicStats.cs
index 4a9a3d8089..3cc655d561 100644
--- a/osu.Game/Overlays/BeatmapSet/BasicStats.cs
+++ b/osu.Game/Overlays/BeatmapSet/BasicStats.cs
@@ -68,13 +68,13 @@ namespace osu.Game.Overlays.BeatmapSet
}
else
{
- length.TooltipText = BeatmapsetsStrings.ShowStatsTotalLength(TimeSpan.FromMilliseconds(beatmapInfo.Length).ToFormattedDuration());
length.Value = TimeSpan.FromMilliseconds(beatmapInfo.Length).ToFormattedDuration();
- var onlineInfo = beatmapInfo as IBeatmapOnlineInfo;
+ if (beatmapInfo is not IBeatmapOnlineInfo onlineInfo) return;
- circleCount.Value = (onlineInfo?.CircleCount ?? 0).ToLocalisableString(@"N0");
- sliderCount.Value = (onlineInfo?.SliderCount ?? 0).ToLocalisableString(@"N0");
+ circleCount.Value = onlineInfo.CircleCount.ToLocalisableString(@"N0");
+ sliderCount.Value = onlineInfo.SliderCount.ToLocalisableString(@"N0");
+ length.TooltipText = BeatmapsetsStrings.ShowStatsTotalLength(TimeSpan.FromMilliseconds(onlineInfo.HitLength).ToFormattedDuration());
}
}
diff --git a/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs b/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs
index 6d89313979..b53b7826f3 100644
--- a/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs
+++ b/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs
@@ -47,9 +47,6 @@ namespace osu.Game.Overlays.BeatmapSet.Scores
[Resolved]
private RulesetStore rulesets { get; set; }
- [Resolved]
- private ScoreManager scoreManager { get; set; }
-
private GetScoresRequest getScoresRequest;
private CancellationTokenSource loadCancellationSource;
@@ -85,7 +82,7 @@ namespace osu.Game.Overlays.BeatmapSet.Scores
MD5Hash = apiBeatmap.MD5Hash
};
- var scores = scoreManager.OrderByTotalScore(value.Scores.Select(s => s.ToScoreInfo(rulesets, beatmapInfo))).ToArray();
+ var scores = value.Scores.Select(s => s.ToScoreInfo(rulesets, beatmapInfo)).OrderByTotalScore().ToArray();
var topScore = scores.First();
scoreTable.DisplayScores(scores, apiBeatmap.Status.GrantsPerformancePoints());
diff --git a/osu.Game/Overlays/ChatOverlay.cs b/osu.Game/Overlays/ChatOverlay.cs
index 96dbfe31f3..87df08ceec 100644
--- a/osu.Game/Overlays/ChatOverlay.cs
+++ b/osu.Game/Overlays/ChatOverlay.cs
@@ -276,8 +276,6 @@ namespace osu.Game.Overlays
protected override void PopIn()
{
- base.PopIn();
-
this.MoveToY(0, transition_length, Easing.OutQuint);
this.FadeIn(transition_length, Easing.OutQuint);
}
diff --git a/osu.Game/Overlays/DialogOverlay.cs b/osu.Game/Overlays/DialogOverlay.cs
index 098a5d0a33..005162bbcc 100644
--- a/osu.Game/Overlays/DialogOverlay.cs
+++ b/osu.Game/Overlays/DialogOverlay.cs
@@ -99,7 +99,6 @@ namespace osu.Game.Overlays
protected override void PopIn()
{
- base.PopIn();
lowPassFilter.CutoffTo(300, 100, Easing.OutCubic);
}
diff --git a/osu.Game/Overlays/LoginOverlay.cs b/osu.Game/Overlays/LoginOverlay.cs
index 536811dfcf..8b60024682 100644
--- a/osu.Game/Overlays/LoginOverlay.cs
+++ b/osu.Game/Overlays/LoginOverlay.cs
@@ -75,8 +75,6 @@ namespace osu.Game.Overlays
protected override void PopIn()
{
- base.PopIn();
-
panel.Bounding = true;
this.FadeIn(transition_time, Easing.OutQuint);
diff --git a/osu.Game/Overlays/MedalOverlay.cs b/osu.Game/Overlays/MedalOverlay.cs
index bd895fe6bf..eba35ec6f9 100644
--- a/osu.Game/Overlays/MedalOverlay.cs
+++ b/osu.Game/Overlays/MedalOverlay.cs
@@ -246,9 +246,13 @@ namespace osu.Game.Overlays
}
}
+ protected override void PopIn()
+ {
+ this.FadeIn(200);
+ }
+
protected override void PopOut()
{
- base.PopOut();
this.FadeOut(200);
}
diff --git a/osu.Game/Overlays/Mods/DeselectAllModsButton.cs b/osu.Game/Overlays/Mods/DeselectAllModsButton.cs
index 3e5a3b12d1..817b6beac3 100644
--- a/osu.Game/Overlays/Mods/DeselectAllModsButton.cs
+++ b/osu.Game/Overlays/Mods/DeselectAllModsButton.cs
@@ -6,16 +6,13 @@
using System.Collections.Generic;
using System.Linq;
using osu.Framework.Bindables;
-using osu.Framework.Input.Bindings;
-using osu.Framework.Input.Events;
using osu.Game.Graphics.UserInterface;
-using osu.Game.Input.Bindings;
using osu.Game.Localisation;
using osu.Game.Rulesets.Mods;
namespace osu.Game.Overlays.Mods
{
- public partial class DeselectAllModsButton : ShearedButton, IKeyBindingHandler
+ public partial class DeselectAllModsButton : ShearedButton
{
private readonly Bindable> selectedMods = new Bindable>();
@@ -39,18 +36,5 @@ namespace osu.Game.Overlays.Mods
{
Enabled.Value = selectedMods.Value.Any();
}
-
- public bool OnPressed(KeyBindingPressEvent e)
- {
- if (e.Repeat || e.Action != GlobalAction.DeselectAllMods)
- return false;
-
- TriggerClick();
- return true;
- }
-
- public void OnReleased(KeyBindingReleaseEvent e)
- {
- }
}
}
diff --git a/osu.Game/Overlays/Mods/Input/ClassicModHotkeyHandler.cs b/osu.Game/Overlays/Mods/Input/ClassicModHotkeyHandler.cs
index 4f3c18fc43..59a631a7b5 100644
--- a/osu.Game/Overlays/Mods/Input/ClassicModHotkeyHandler.cs
+++ b/osu.Game/Overlays/Mods/Input/ClassicModHotkeyHandler.cs
@@ -42,7 +42,7 @@ namespace osu.Game.Overlays.Mods.Input
if (!mod_type_lookup.TryGetValue(e.Key, out var typesToMatch))
return false;
- var matchingMods = availableMods.Where(modState => matches(modState, typesToMatch) && !modState.Filtered.Value).ToArray();
+ var matchingMods = availableMods.Where(modState => matches(modState, typesToMatch) && modState.Visible).ToArray();
if (matchingMods.Length == 0)
return false;
diff --git a/osu.Game/Overlays/Mods/Input/SequentialModHotkeyHandler.cs b/osu.Game/Overlays/Mods/Input/SequentialModHotkeyHandler.cs
index dedb556304..e638063438 100644
--- a/osu.Game/Overlays/Mods/Input/SequentialModHotkeyHandler.cs
+++ b/osu.Game/Overlays/Mods/Input/SequentialModHotkeyHandler.cs
@@ -48,7 +48,7 @@ namespace osu.Game.Overlays.Mods.Input
if (index < 0)
return false;
- var modState = availableMods.Where(modState => !modState.Filtered.Value).ElementAtOrDefault(index);
+ var modState = availableMods.Where(modState => modState.Visible).ElementAtOrDefault(index);
if (modState == null)
return false;
diff --git a/osu.Game/Overlays/Mods/ModColumn.cs b/osu.Game/Overlays/Mods/ModColumn.cs
index fe42cc0abf..d65c94d14d 100644
--- a/osu.Game/Overlays/Mods/ModColumn.cs
+++ b/osu.Game/Overlays/Mods/ModColumn.cs
@@ -46,7 +46,8 @@ namespace osu.Game.Overlays.Mods
foreach (var mod in availableMods)
{
mod.Active.BindValueChanged(_ => updateState());
- mod.Filtered.BindValueChanged(_ => updateState());
+ mod.MatchingTextFilter.BindValueChanged(_ => updateState());
+ mod.ValidForSelection.BindValueChanged(_ => updateState());
}
updateState();
@@ -145,12 +146,17 @@ namespace osu.Game.Overlays.Mods
private void updateState()
{
- Alpha = availableMods.All(mod => mod.Filtered.Value) ? 0 : 1;
+ Alpha = availableMods.All(mod => !mod.Visible) ? 0 : 1;
if (toggleAllCheckbox != null && !SelectionAnimationRunning)
{
- toggleAllCheckbox.Alpha = availableMods.Any(panel => !panel.Filtered.Value) ? 1 : 0;
- toggleAllCheckbox.Current.Value = availableMods.Where(panel => !panel.Filtered.Value).All(panel => panel.Active.Value);
+ bool anyPanelsVisible = availableMods.Any(panel => panel.Visible);
+
+ toggleAllCheckbox.Alpha = anyPanelsVisible ? 1 : 0;
+
+ // checking `anyPanelsVisible` is important since `.All()` returns `true` for empty enumerables.
+ if (anyPanelsVisible)
+ toggleAllCheckbox.Current.Value = availableMods.Where(panel => panel.Visible).All(panel => panel.Active.Value);
}
}
@@ -195,7 +201,7 @@ namespace osu.Game.Overlays.Mods
{
pendingSelectionOperations.Clear();
- foreach (var button in availableMods.Where(b => !b.Active.Value && !b.Filtered.Value))
+ foreach (var button in availableMods.Where(b => !b.Active.Value && b.Visible))
pendingSelectionOperations.Enqueue(() => button.Active.Value = true);
}
@@ -206,8 +212,13 @@ namespace osu.Game.Overlays.Mods
{
pendingSelectionOperations.Clear();
- foreach (var button in availableMods.Where(b => b.Active.Value && !b.Filtered.Value))
- pendingSelectionOperations.Enqueue(() => button.Active.Value = false);
+ foreach (var button in availableMods.Where(b => b.Active.Value))
+ {
+ if (!button.Visible)
+ button.Active.Value = false;
+ else
+ pendingSelectionOperations.Enqueue(() => button.Active.Value = false);
+ }
}
///
diff --git a/osu.Game/Overlays/Mods/ModPanel.cs b/osu.Game/Overlays/Mods/ModPanel.cs
index b5fee9d116..f294b1892d 100644
--- a/osu.Game/Overlays/Mods/ModPanel.cs
+++ b/osu.Game/Overlays/Mods/ModPanel.cs
@@ -1,9 +1,12 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
+using System.Collections.Generic;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
+using osu.Framework.Graphics.Containers;
+using osu.Framework.Localisation;
using osu.Game.Graphics;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.UI;
@@ -11,11 +14,10 @@ using osuTK;
namespace osu.Game.Overlays.Mods
{
- public partial class ModPanel : ModSelectPanel
+ public partial class ModPanel : ModSelectPanel, IFilterable
{
public Mod Mod => modState.Mod;
public override BindableBool Active => modState.Active;
- public BindableBool Filtered => modState.Filtered;
protected override float IdleSwitchWidth => 54;
protected override float ExpandedSwitchWidth => 70;
@@ -54,7 +56,8 @@ namespace osu.Game.Overlays.Mods
{
base.LoadComplete();
- Filtered.BindValueChanged(_ => updateFilterState(), true);
+ modState.ValidForSelection.BindValueChanged(_ => updateFilterState());
+ modState.MatchingTextFilter.BindValueChanged(_ => updateFilterState(), true);
}
protected override void Select()
@@ -71,9 +74,25 @@ namespace osu.Game.Overlays.Mods
#region Filtering support
+ ///
+ public bool Visible => modState.Visible;
+
+ public override IEnumerable FilterTerms => new[]
+ {
+ Mod.Name,
+ Mod.Acronym,
+ Mod.Description
+ };
+
+ public override bool MatchingFilter
+ {
+ get => modState.MatchingTextFilter.Value;
+ set => modState.MatchingTextFilter.Value = value;
+ }
+
private void updateFilterState()
{
- this.FadeTo(Filtered.Value ? 0 : 1);
+ this.FadeTo(Visible ? 1 : 0);
}
#endregion
diff --git a/osu.Game/Overlays/Mods/ModPresetPanel.cs b/osu.Game/Overlays/Mods/ModPresetPanel.cs
index 8bcb5e4e4e..00f6e36972 100644
--- a/osu.Game/Overlays/Mods/ModPresetPanel.cs
+++ b/osu.Game/Overlays/Mods/ModPresetPanel.cs
@@ -9,6 +9,7 @@ using osu.Framework.Bindables;
using osu.Framework.Extensions;
using osu.Framework.Graphics.Cursor;
using osu.Framework.Graphics.UserInterface;
+using osu.Framework.Localisation;
using osu.Game.Configuration;
using osu.Game.Database;
using osu.Game.Graphics;
@@ -81,6 +82,27 @@ namespace osu.Game.Overlays.Mods
Active.Value = new HashSet(Preset.Value.Mods).SetEquals(selectedMods.Value);
}
+ #region Filtering support
+
+ public override IEnumerable FilterTerms => getFilterTerms();
+
+ private IEnumerable getFilterTerms()
+ {
+ var preset = Preset.Value;
+
+ yield return preset.Name;
+ yield return preset.Description;
+
+ foreach (Mod mod in preset.Mods)
+ {
+ yield return mod.Name;
+ yield return mod.Acronym;
+ yield return mod.Description;
+ }
+ }
+
+ #endregion
+
#region IHasCustomTooltip
public ModPreset TooltipContent => Preset.Value;
diff --git a/osu.Game/Overlays/Mods/ModSearchContainer.cs b/osu.Game/Overlays/Mods/ModSearchContainer.cs
new file mode 100644
index 0000000000..8787530d5c
--- /dev/null
+++ b/osu.Game/Overlays/Mods/ModSearchContainer.cs
@@ -0,0 +1,27 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using osu.Framework.Graphics.Containers;
+
+namespace osu.Game.Overlays.Mods
+{
+ public partial class ModSearchContainer : SearchContainer
+ {
+ public new string SearchTerm
+ {
+ get => base.SearchTerm;
+ set
+ {
+ if (value == SearchTerm)
+ return;
+
+ base.SearchTerm = value;
+
+ // Manual filtering here is required because ModColumn can be hidden when search term applied,
+ // causing the whole SearchContainer to become non-present and never actually perform a subsequent
+ // filter.
+ Filter();
+ }
+ }
+ }
+}
diff --git a/osu.Game/Overlays/Mods/ModSelectColumn.cs b/osu.Game/Overlays/Mods/ModSelectColumn.cs
index e6d7bcd97d..338ebdaef4 100644
--- a/osu.Game/Overlays/Mods/ModSelectColumn.cs
+++ b/osu.Game/Overlays/Mods/ModSelectColumn.cs
@@ -43,10 +43,15 @@ namespace osu.Game.Overlays.Mods
///
public readonly Bindable Active = new BindableBool(true);
+ public string SearchTerm
+ {
+ set => ItemsFlow.SearchTerm = value;
+ }
+
protected override bool ReceivePositionalInputAtSubTree(Vector2 screenSpacePos) => base.ReceivePositionalInputAtSubTree(screenSpacePos) && Active.Value;
protected readonly Container ControlContainer;
- protected readonly FillFlowContainer ItemsFlow;
+ protected readonly ModSearchContainer ItemsFlow;
private readonly TextFlowContainer headerText;
private readonly Box headerBackground;
@@ -150,7 +155,7 @@ namespace osu.Game.Overlays.Mods
RelativeSizeAxes = Axes.Both,
ClampExtension = 100,
ScrollbarOverlapsContent = false,
- Child = ItemsFlow = new FillFlowContainer
+ Child = ItemsFlow = new ModSearchContainer
{
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
diff --git a/osu.Game/Overlays/Mods/ModSelectOverlay.cs b/osu.Game/Overlays/Mods/ModSelectOverlay.cs
index 38ae8c68cb..9035503723 100644
--- a/osu.Game/Overlays/Mods/ModSelectOverlay.cs
+++ b/osu.Game/Overlays/Mods/ModSelectOverlay.cs
@@ -12,6 +12,8 @@ using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Cursor;
+using osu.Framework.Input;
+using osu.Framework.Input.Bindings;
using osu.Framework.Input.Events;
using osu.Framework.Utils;
using osu.Game.Audio;
@@ -25,10 +27,11 @@ using osu.Game.Localisation;
using osu.Game.Rulesets.Mods;
using osu.Game.Utils;
using osuTK;
+using osuTK.Input;
namespace osu.Game.Overlays.Mods
{
- public abstract partial class ModSelectOverlay : ShearedOverlayContainer, ISamplePlaybackDisabler
+ public abstract partial class ModSelectOverlay : ShearedOverlayContainer, ISamplePlaybackDisabler, IKeyBindingHandler
{
public const int BUTTON_WIDTH = 200;
@@ -64,6 +67,14 @@ namespace osu.Game.Overlays.Mods
}
}
+ public string SearchTerm
+ {
+ get => SearchTextBox.Current.Value;
+ set => SearchTextBox.Current.Value = value;
+ }
+
+ public ShearedSearchTextBox SearchTextBox { get; private set; } = null!;
+
///
/// Whether the total score multiplier calculated from the current selected set of mods should be shown.
///
@@ -94,7 +105,7 @@ namespace osu.Game.Overlays.Mods
};
}
- yield return new DeselectAllModsButton(this);
+ yield return deselectAllModsButton = new DeselectAllModsButton(this);
}
private readonly Bindable>> globalAvailableMods = new Bindable>>();
@@ -107,11 +118,14 @@ namespace osu.Game.Overlays.Mods
private ColumnScrollContainer columnScroll = null!;
private ColumnFlowContainer columnFlow = null!;
private FillFlowContainer footerButtonFlow = null!;
+ private DeselectAllModsButton deselectAllModsButton = null!;
+ private Container aboveColumnsContent = null!;
private DifficultyMultiplierDisplay? multiplierDisplay;
protected ShearedButton BackButton { get; private set; } = null!;
protected ShearedToggleButton? CustomisationButton { get; private set; }
+ protected SelectAllModsButton? SelectAllModsButton { get; set; }
private Sample? columnAppearSample;
@@ -146,6 +160,17 @@ namespace osu.Game.Overlays.Mods
MainAreaContent.AddRange(new Drawable[]
{
+ aboveColumnsContent = new Container
+ {
+ RelativeSizeAxes = Axes.X,
+ Height = ModsEffectDisplay.HEIGHT,
+ Padding = new MarginPadding { Horizontal = 100 },
+ Child = SearchTextBox = new ShearedSearchTextBox
+ {
+ HoldFocus = false,
+ Width = 300
+ }
+ },
new OsuContextMenuContainer
{
RelativeSizeAxes = Axes.Both,
@@ -153,7 +178,7 @@ namespace osu.Game.Overlays.Mods
{
Padding = new MarginPadding
{
- Top = (ShowTotalMultiplier ? ModsEffectDisplay.HEIGHT : 0) + PADDING,
+ Top = ModsEffectDisplay.HEIGHT + PADDING,
Bottom = PADDING
},
RelativeSizeAxes = Axes.Both,
@@ -186,18 +211,10 @@ namespace osu.Game.Overlays.Mods
if (ShowTotalMultiplier)
{
- MainAreaContent.Add(new Container
+ aboveColumnsContent.Add(multiplierDisplay = new DifficultyMultiplierDisplay
{
Anchor = Anchor.TopRight,
- Origin = Anchor.TopRight,
- AutoSizeAxes = Axes.X,
- Height = ModsEffectDisplay.HEIGHT,
- Margin = new MarginPadding { Horizontal = 100 },
- Child = multiplierDisplay = new DifficultyMultiplierDisplay
- {
- Anchor = Anchor.Centre,
- Origin = Anchor.Centre
- },
+ Origin = Anchor.TopRight
});
}
@@ -226,6 +243,14 @@ namespace osu.Game.Overlays.Mods
globalAvailableMods.BindTo(game.AvailableMods);
}
+ public override void Hide()
+ {
+ base.Hide();
+
+ // clear search for next user interaction with mod overlay
+ SearchTextBox.Current.Value = string.Empty;
+ }
+
private ModSettingChangeTracker? modSettingChangeTracker;
protected override void LoadComplete()
@@ -263,6 +288,12 @@ namespace osu.Game.Overlays.Mods
customisationVisible.BindValueChanged(_ => updateCustomisationVisualState(), true);
+ SearchTextBox.Current.BindValueChanged(query =>
+ {
+ foreach (var column in columnFlow.Columns)
+ column.SearchTerm = query.NewValue;
+ }, true);
+
// Start scrolled slightly to the right to give the user a sense that
// there is more horizontal content available.
ScheduleAfterChildren(() =>
@@ -272,6 +303,13 @@ namespace osu.Game.Overlays.Mods
});
}
+ protected override void Update()
+ {
+ base.Update();
+
+ SearchTextBox.PlaceholderText = SearchTextBox.HasFocus ? Resources.Localisation.Web.CommonStrings.InputSearch : ModSelectOverlayStrings.TabToSearch;
+ }
+
///
/// Select all visible mods in all columns.
///
@@ -344,7 +382,7 @@ namespace osu.Game.Overlays.Mods
private void filterMods()
{
foreach (var modState in allAvailableMods)
- modState.Filtered.Value = !modState.Mod.HasImplementation || !IsValidMod.Invoke(modState.Mod);
+ modState.ValidForSelection.Value = modState.Mod.HasImplementation && IsValidMod.Invoke(modState.Mod);
}
private void updateMultiplier()
@@ -469,7 +507,7 @@ namespace osu.Game.Overlays.Mods
base.PopIn();
- multiplierDisplay?
+ aboveColumnsContent
.FadeIn(fade_in_duration, Easing.OutQuint)
.MoveToY(0, fade_in_duration, Easing.OutQuint);
@@ -479,7 +517,7 @@ namespace osu.Game.Overlays.Mods
{
var column = columnFlow[i].Column;
- bool allFiltered = column is ModColumn modColumn && modColumn.AvailableMods.All(modState => modState.Filtered.Value);
+ bool allFiltered = column is ModColumn modColumn && modColumn.AvailableMods.All(modState => !modState.Visible);
double delay = allFiltered ? 0 : nonFilteredColumnCount * 30;
double duration = allFiltered ? 0 : fade_in_duration;
@@ -527,7 +565,7 @@ namespace osu.Game.Overlays.Mods
base.PopOut();
- multiplierDisplay?
+ aboveColumnsContent
.FadeOut(fade_out_duration / 2, Easing.OutQuint)
.MoveToY(-distance, fade_out_duration / 2, Easing.OutQuint);
@@ -541,7 +579,7 @@ namespace osu.Game.Overlays.Mods
if (column is ModColumn modColumn)
{
- allFiltered = modColumn.AvailableMods.All(modState => modState.Filtered.Value);
+ allFiltered = modColumn.AvailableMods.All(modState => !modState.Visible);
modColumn.FlushPendingSelections();
}
@@ -578,10 +616,38 @@ namespace osu.Game.Overlays.Mods
// This is handled locally here because this overlay is being registered at the game level
// and therefore takes away keyboard focus from the screen stack.
case GlobalAction.ToggleModSelection:
+ // Pressing toggle should completely hide the overlay in one shot.
+ hideOverlay(true);
+ return true;
+
+ // This is handled locally here due to conflicts in input handling between the search text box and the deselect all mods button.
+ // Attempting to handle this action locally in both places leads to a possible scenario
+ // wherein activating the binding will both change the contents of the search text box and deselect all mods.
+ case GlobalAction.DeselectAllMods:
+ {
+ if (!SearchTextBox.HasFocus)
+ {
+ deselectAllModsButton.TriggerClick();
+ return true;
+ }
+
+ break;
+ }
+
case GlobalAction.Select:
{
- // Pressing toggle or select should completely hide the overlay in one shot.
- hideOverlay(true);
+ // Pressing select should select first filtered mod or completely hide the overlay in one shot if search term is empty.
+ if (string.IsNullOrEmpty(SearchTerm))
+ {
+ hideOverlay(true);
+ return true;
+ }
+
+ ModState? firstMod = columnFlow.Columns.OfType().FirstOrDefault(m => m.IsPresent)?.AvailableMods.FirstOrDefault(x => x.Visible);
+
+ if (firstMod is not null)
+ firstMod.Active.Value = !firstMod.Active.Value;
+
return true;
}
}
@@ -603,6 +669,39 @@ namespace osu.Game.Overlays.Mods
}
}
+ ///
+ ///
+ /// This is handled locally here due to conflicts in input handling between the search text box and the select all mods button.
+ /// Attempting to handle this action locally in both places leads to a possible scenario
+ /// wherein activating the "select all" platform binding will both select all text in the search box and select all mods.
+ /// >
+ public bool OnPressed(KeyBindingPressEvent e)
+ {
+ if (e.Repeat || e.Action != PlatformAction.SelectAll || SelectAllModsButton is null)
+ return false;
+
+ SelectAllModsButton.TriggerClick();
+ return true;
+ }
+
+ public void OnReleased(KeyBindingReleaseEvent e)
+ {
+ }
+
+ protected override bool OnKeyDown(KeyDownEvent e)
+ {
+ if (e.Repeat || e.Key != Key.Tab)
+ return false;
+
+ // TODO: should probably eventually support typical platform search shortcuts (`Ctrl-F`, `/`)
+ if (SearchTextBox.HasFocus)
+ SearchTextBox.KillFocus();
+ else
+ SearchTextBox.TakeFocus();
+
+ return true;
+ }
+
#endregion
#region Sample playback control
@@ -743,6 +842,9 @@ namespace osu.Game.Overlays.Mods
if (!Active.Value)
RequestScroll?.Invoke(this);
+ // Killing focus is done here because it's the only feasible place on ModSelectOverlay you can click on without triggering any action.
+ Scheduler.Add(() => GetContainingInputManager().ChangeFocus(null));
+
return true;
}
diff --git a/osu.Game/Overlays/Mods/ModSelectPanel.cs b/osu.Game/Overlays/Mods/ModSelectPanel.cs
index 1f42292590..29f4c93e88 100644
--- a/osu.Game/Overlays/Mods/ModSelectPanel.cs
+++ b/osu.Game/Overlays/Mods/ModSelectPanel.cs
@@ -1,6 +1,7 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
+using System.Collections.Generic;
using osu.Framework.Allocation;
using osu.Framework.Audio;
using osu.Framework.Audio.Sample;
@@ -25,7 +26,7 @@ using osuTK.Input;
namespace osu.Game.Overlays.Mods
{
- public abstract partial class ModSelectPanel : OsuClickableContainer, IHasAccentColour
+ public abstract partial class ModSelectPanel : OsuClickableContainer, IHasAccentColour, IFilterable
{
public abstract BindableBool Active { get; }
@@ -199,6 +200,9 @@ namespace osu.Game.Overlays.Mods
if (samplePlaybackDisabled.Value)
return;
+ if (!IsPresent)
+ return;
+
bool enoughTimePassedSinceLastPlayback = !lastPlaybackTime.Value.HasValue || Time.Current - lastPlaybackTime.Value >= SAMPLE_PLAYBACK_DELAY;
if (enoughTimePassedSinceLastPlayback)
@@ -277,5 +281,28 @@ namespace osu.Game.Overlays.Mods
TextBackground.FadeColour(foregroundColour, transitionDuration, Easing.OutQuint);
TextFlow.FadeColour(textColour, transitionDuration, Easing.OutQuint);
}
+
+ #region IFilterable
+
+ public abstract IEnumerable FilterTerms { get; }
+
+ private bool matchingFilter = true;
+
+ public virtual bool MatchingFilter
+ {
+ get => matchingFilter;
+ set
+ {
+ if (matchingFilter == value)
+ return;
+
+ matchingFilter = value;
+ this.FadeTo(value ? 1 : 0);
+ }
+ }
+
+ public bool FilteringActive { set { } }
+
+ #endregion
}
}
diff --git a/osu.Game/Overlays/Mods/ModState.cs b/osu.Game/Overlays/Mods/ModState.cs
index 3ee890e876..7a5bc0f3ae 100644
--- a/osu.Game/Overlays/Mods/ModState.cs
+++ b/osu.Game/Overlays/Mods/ModState.cs
@@ -1,8 +1,6 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-#nullable disable
-
using osu.Framework.Bindables;
using osu.Game.Rulesets.Mods;
@@ -32,9 +30,21 @@ namespace osu.Game.Overlays.Mods
public bool PendingConfiguration { get; set; }
///
- /// Whether the mod is currently filtered out due to not matching imposed criteria.
+ /// Whether the mod is currently valid for selection.
+ /// This can be in scenarios such as the free mod select overlay, where not all mods are selectable
+ /// regardless of search criteria imposed by the user selecting.
///
- public BindableBool Filtered { get; } = new BindableBool();
+ public BindableBool ValidForSelection { get; } = new BindableBool(true);
+
+ ///
+ /// Whether the mod is matching the current textual filter.
+ ///
+ public BindableBool MatchingTextFilter { get; } = new BindableBool(true);
+
+ ///
+ /// Whether the matches all applicable filters and visible for the user to select.
+ ///
+ public bool Visible => MatchingTextFilter.Value && ValidForSelection.Value;
public ModState(Mod mod)
{
diff --git a/osu.Game/Overlays/Mods/SelectAllModsButton.cs b/osu.Game/Overlays/Mods/SelectAllModsButton.cs
index f4b8025227..bb61cdc35d 100644
--- a/osu.Game/Overlays/Mods/SelectAllModsButton.cs
+++ b/osu.Game/Overlays/Mods/SelectAllModsButton.cs
@@ -1,14 +1,9 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-#nullable disable
-
using System.Collections.Generic;
using System.Linq;
using osu.Framework.Bindables;
-using osu.Framework.Input;
-using osu.Framework.Input.Bindings;
-using osu.Framework.Input.Events;
using osu.Game.Graphics.UserInterface;
using osu.Game.Localisation;
using osu.Game.Rulesets.Mods;
@@ -16,10 +11,11 @@ using osu.Game.Screens.OnlinePlay;
namespace osu.Game.Overlays.Mods
{
- public partial class SelectAllModsButton : ShearedButton, IKeyBindingHandler
+ public partial class SelectAllModsButton : ShearedButton
{
private readonly Bindable> selectedMods = new Bindable>();
private readonly Bindable>> availableMods = new Bindable>>();
+ private readonly Bindable searchTerm = new Bindable();
public SelectAllModsButton(FreeModSelectOverlay modSelectOverlay)
: base(ModSelectOverlay.BUTTON_WIDTH)
@@ -29,6 +25,7 @@ namespace osu.Game.Overlays.Mods
selectedMods.BindTo(modSelectOverlay.SelectedMods);
availableMods.BindTo(modSelectOverlay.AvailableMods);
+ searchTerm.BindTo(modSelectOverlay.SearchTextBox.Current);
}
protected override void LoadComplete()
@@ -37,6 +34,7 @@ namespace osu.Game.Overlays.Mods
selectedMods.BindValueChanged(_ => Scheduler.AddOnce(updateEnabledState));
availableMods.BindValueChanged(_ => Scheduler.AddOnce(updateEnabledState));
+ searchTerm.BindValueChanged(_ => Scheduler.AddOnce(updateEnabledState));
updateEnabledState();
}
@@ -44,20 +42,7 @@ namespace osu.Game.Overlays.Mods
{
Enabled.Value = availableMods.Value
.SelectMany(pair => pair.Value)
- .Any(modState => !modState.Active.Value && !modState.Filtered.Value);
- }
-
- public bool OnPressed(KeyBindingPressEvent e)
- {
- if (e.Repeat || e.Action != PlatformAction.SelectAll)
- return false;
-
- TriggerClick();
- return true;
- }
-
- public void OnReleased(KeyBindingReleaseEvent e)
- {
+ .Any(modState => !modState.Active.Value && modState.Visible);
}
}
}
diff --git a/osu.Game/Overlays/Mods/ShearedOverlayContainer.cs b/osu.Game/Overlays/Mods/ShearedOverlayContainer.cs
index 7f7b09a62c..a372ec70db 100644
--- a/osu.Game/Overlays/Mods/ShearedOverlayContainer.cs
+++ b/osu.Game/Overlays/Mods/ShearedOverlayContainer.cs
@@ -130,7 +130,6 @@ namespace osu.Game.Overlays.Mods
{
const double fade_in_duration = 400;
- base.PopIn();
this.FadeIn(fade_in_duration, Easing.OutQuint);
Header.MoveToY(0, fade_in_duration, Easing.OutQuint);
diff --git a/osu.Game/Overlays/NotificationOverlay.cs b/osu.Game/Overlays/NotificationOverlay.cs
index f2eefb6e4b..15e6c94b34 100644
--- a/osu.Game/Overlays/NotificationOverlay.cs
+++ b/osu.Game/Overlays/NotificationOverlay.cs
@@ -206,8 +206,6 @@ namespace osu.Game.Overlays
protected override void PopIn()
{
- base.PopIn();
-
this.MoveToX(0, TRANSITION_LENGTH, Easing.OutQuint);
mainContent.FadeTo(1, TRANSITION_LENGTH, Easing.OutQuint);
mainContent.FadeEdgeEffectTo(WaveContainer.SHADOW_OPACITY, WaveContainer.APPEAR_DURATION, Easing.Out);
diff --git a/osu.Game/Overlays/NowPlayingOverlay.cs b/osu.Game/Overlays/NowPlayingOverlay.cs
index e3e3b4bd80..15eefb2d9f 100644
--- a/osu.Game/Overlays/NowPlayingOverlay.cs
+++ b/osu.Game/Overlays/NowPlayingOverlay.cs
@@ -229,8 +229,6 @@ namespace osu.Game.Overlays
protected override void PopIn()
{
- base.PopIn();
-
this.FadeIn(transition_length, Easing.OutQuint);
dragContainer.ScaleTo(1, transition_length, Easing.OutElastic);
}
diff --git a/osu.Game/Overlays/SettingsPanel.cs b/osu.Game/Overlays/SettingsPanel.cs
index 1681187f82..d7f39a9d8f 100644
--- a/osu.Game/Overlays/SettingsPanel.cs
+++ b/osu.Game/Overlays/SettingsPanel.cs
@@ -163,8 +163,6 @@ namespace osu.Game.Overlays
protected override void PopIn()
{
- base.PopIn();
-
ContentContainer.MoveToX(ExpandedPosition, TRANSITION_LENGTH, Easing.OutQuint);
SectionsContainer.FadeEdgeEffectTo(WaveContainer.SHADOW_OPACITY, WaveContainer.APPEAR_DURATION, Easing.Out);
diff --git a/osu.Game/Overlays/WaveOverlayContainer.cs b/osu.Game/Overlays/WaveOverlayContainer.cs
index 00474cc0d8..34fbec93b7 100644
--- a/osu.Game/Overlays/WaveOverlayContainer.cs
+++ b/osu.Game/Overlays/WaveOverlayContainer.cs
@@ -34,8 +34,6 @@ namespace osu.Game.Overlays
protected override void PopIn()
{
- base.PopIn();
-
Waves.Show();
this.FadeIn(100, Easing.OutQuint);
}
diff --git a/osu.Game/Scoring/ScoreInfoExtensions.cs b/osu.Game/Scoring/ScoreInfoExtensions.cs
index 7979ca8aaa..85598076d6 100644
--- a/osu.Game/Scoring/ScoreInfoExtensions.cs
+++ b/osu.Game/Scoring/ScoreInfoExtensions.cs
@@ -1,9 +1,10 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-#nullable disable
-
+using System.Collections.Generic;
+using System.Linq;
using osu.Game.Beatmaps;
+using osu.Game.Rulesets.Scoring;
namespace osu.Game.Scoring
{
@@ -13,5 +14,23 @@ namespace osu.Game.Scoring
/// A user-presentable display title representing this score.
///
public static string GetDisplayTitle(this IScoreInfo scoreInfo) => $"{scoreInfo.User.Username} playing {scoreInfo.Beatmap.GetDisplayTitle()}";
+
+ ///
+ /// Orders an array of s by total score.
+ ///
+ /// The array of s to reorder.
+ /// The given ordered by decreasing total score.
+ public static IEnumerable OrderByTotalScore(this IEnumerable scores)
+ => scores.OrderByDescending(s => s.TotalScore)
+ .ThenBy(s => s.OnlineID)
+ // Local scores may not have an online ID. Fall back to date in these cases.
+ .ThenBy(s => s.Date);
+
+ ///
+ /// Retrieves the maximum achievable combo for the provided score.
+ ///
+ /// The to compute the maximum achievable combo for.
+ /// The maximum achievable combo.
+ public static int GetMaximumAchievableCombo(this ScoreInfo score) => score.MaximumStatistics.Where(kvp => kvp.Key.AffectsCombo()).Sum(kvp => kvp.Value);
}
}
diff --git a/osu.Game/Scoring/ScoreManager.cs b/osu.Game/Scoring/ScoreManager.cs
index d5509538fd..55bcb9f79d 100644
--- a/osu.Game/Scoring/ScoreManager.cs
+++ b/osu.Game/Scoring/ScoreManager.cs
@@ -69,17 +69,6 @@ namespace osu.Game.Scoring
return Realm.Run(r => r.All().FirstOrDefault(query)?.Detach());
}
- ///
- /// Orders an array of s by total score.
- ///
- /// The array of s to reorder.
- /// The given ordered by decreasing total score.
- public IEnumerable OrderByTotalScore(IEnumerable scores)
- => scores.OrderByDescending(s => s.TotalScore)
- .ThenBy(s => s.OnlineID)
- // Local scores may not have an online ID. Fall back to date in these cases.
- .ThenBy(s => s.Date);
-
///
/// Retrieves a bindable that represents the total score of a .
///
@@ -100,13 +89,6 @@ namespace osu.Game.Scoring
/// The bindable containing the formatted total score string.
public Bindable GetBindableTotalScoreString([NotNull] ScoreInfo score) => new TotalScoreStringBindable(GetBindableTotalScore(score));
- ///
- /// Retrieves the maximum achievable combo for the provided score.
- ///
- /// The to compute the maximum achievable combo for.
- /// The maximum achievable combo.
- public int GetMaximumAchievableCombo([NotNull] ScoreInfo score) => score.MaximumStatistics.Where(kvp => kvp.Key.AffectsCombo()).Sum(kvp => kvp.Value);
-
///
/// Provides the total score of a . Responds to changes in the currently-selected .
///
diff --git a/osu.Game/Screens/Edit/Compose/Components/CircularDistanceSnapGrid.cs b/osu.Game/Screens/Edit/Compose/Components/CircularDistanceSnapGrid.cs
index f27ecf60cc..cf8b0c14ed 100644
--- a/osu.Game/Screens/Edit/Compose/Components/CircularDistanceSnapGrid.cs
+++ b/osu.Game/Screens/Edit/Compose/Components/CircularDistanceSnapGrid.cs
@@ -18,6 +18,9 @@ namespace osu.Game.Screens.Edit.Compose.Components
{
public abstract partial class CircularDistanceSnapGrid : DistanceSnapGrid
{
+ [Resolved]
+ private EditorClock editorClock { get; set; }
+
protected CircularDistanceSnapGrid(HitObject referenceObject, Vector2 startPosition, double startTime, double? endTime = null)
: base(referenceObject, startPosition, startTime, endTime)
{
@@ -99,9 +102,12 @@ namespace osu.Game.Screens.Edit.Compose.Components
if (travelLength < DistanceBetweenTicks)
travelLength = DistanceBetweenTicks;
- // When interacting with the resolved snap provider, the distance spacing multiplier should first be removed
- // to allow for snapping at a non-multiplied ratio.
- float snappedDistance = SnapProvider.FindSnappedDistance(ReferenceObject, travelLength / distanceSpacingMultiplier);
+ float snappedDistance = LimitedDistanceSnap.Value
+ ? SnapProvider.DurationToDistance(ReferenceObject, editorClock.CurrentTime - ReferenceObject.GetEndTime())
+ // When interacting with the resolved snap provider, the distance spacing multiplier should first be removed
+ // to allow for snapping at a non-multiplied ratio.
+ : SnapProvider.FindSnappedDistance(ReferenceObject, travelLength / distanceSpacingMultiplier);
+
double snappedTime = StartTime + SnapProvider.DistanceToDuration(ReferenceObject, snappedDistance);
if (snappedTime > LatestEndTime)
diff --git a/osu.Game/Screens/Edit/Compose/Components/DistanceSnapGrid.cs b/osu.Game/Screens/Edit/Compose/Components/DistanceSnapGrid.cs
index 6092ebc08f..8aa2fa9f45 100644
--- a/osu.Game/Screens/Edit/Compose/Components/DistanceSnapGrid.cs
+++ b/osu.Game/Screens/Edit/Compose/Components/DistanceSnapGrid.cs
@@ -10,6 +10,7 @@ using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Layout;
+using osu.Game.Configuration;
using osu.Game.Graphics;
using osu.Game.Rulesets.Edit;
using osu.Game.Rulesets.Objects;
@@ -60,6 +61,18 @@ namespace osu.Game.Screens.Edit.Compose.Components
[Resolved]
private BindableBeatDivisor beatDivisor { get; set; }
+ ///
+ /// When enabled, distance snap should only snap to the current time (as per the editor clock).
+ /// This is to emulate stable behaviour.
+ ///
+ protected Bindable LimitedDistanceSnap { get; private set; }
+
+ [BackgroundDependencyLoader]
+ private void load(OsuConfigManager config)
+ {
+ LimitedDistanceSnap = config.GetBindable(OsuSetting.EditorLimitedDistanceSnap);
+ }
+
private readonly LayoutValue gridCache = new LayoutValue(Invalidation.RequiredParentSizeToFit);
protected readonly HitObject ReferenceObject;
diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs
index 74947aab09..41c11ce5c1 100644
--- a/osu.Game/Screens/Edit/Editor.cs
+++ b/osu.Game/Screens/Edit/Editor.cs
@@ -185,6 +185,7 @@ namespace osu.Game.Screens.Edit
private Bindable editorBackgroundDim;
private Bindable editorHitMarkers;
private Bindable editorAutoSeekOnPlacement;
+ private Bindable editorLimitedDistanceSnap;
public Editor(EditorLoader loader = null)
{
@@ -276,6 +277,7 @@ namespace osu.Game.Screens.Edit
editorBackgroundDim = config.GetBindable(OsuSetting.EditorDim);
editorHitMarkers = config.GetBindable(OsuSetting.EditorShowHitMarkers);
editorAutoSeekOnPlacement = config.GetBindable(OsuSetting.EditorAutoSeekOnPlacement);
+ editorLimitedDistanceSnap = config.GetBindable(OsuSetting.EditorLimitedDistanceSnap);
AddInternal(new OsuContextMenuContainer
{
@@ -337,6 +339,10 @@ namespace osu.Game.Screens.Edit
new ToggleMenuItem(EditorStrings.AutoSeekOnPlacement)
{
State = { BindTarget = editorAutoSeekOnPlacement },
+ },
+ new ToggleMenuItem(EditorStrings.LimitedDistanceSnap)
+ {
+ State = { BindTarget = editorLimitedDistanceSnap },
}
}
},
diff --git a/osu.Game/Screens/OnlinePlay/FreeModSelectOverlay.cs b/osu.Game/Screens/OnlinePlay/FreeModSelectOverlay.cs
index 6313d907a5..4d5d724089 100644
--- a/osu.Game/Screens/OnlinePlay/FreeModSelectOverlay.cs
+++ b/osu.Game/Screens/OnlinePlay/FreeModSelectOverlay.cs
@@ -1,8 +1,6 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-#nullable disable
-
using System;
using osu.Game.Overlays;
using System.Collections.Generic;
@@ -34,11 +32,12 @@ namespace osu.Game.Screens.OnlinePlay
protected override ModColumn CreateModColumn(ModType modType) => new ModColumn(modType, true);
- protected override IEnumerable CreateFooterButtons() => base.CreateFooterButtons().Prepend(
- new SelectAllModsButton(this)
- {
- Anchor = Anchor.BottomLeft,
- Origin = Anchor.BottomLeft,
- });
+ protected override IEnumerable CreateFooterButtons()
+ => base.CreateFooterButtons()
+ .Prepend(SelectAllModsButton = new SelectAllModsButton(this)
+ {
+ Anchor = Anchor.BottomLeft,
+ Origin = Anchor.BottomLeft,
+ });
}
}
diff --git a/osu.Game/Screens/OnlinePlay/Match/Components/RoomSettingsOverlay.cs b/osu.Game/Screens/OnlinePlay/Match/Components/RoomSettingsOverlay.cs
index 4d4fe4ea56..05232fe0e2 100644
--- a/osu.Game/Screens/OnlinePlay/Match/Components/RoomSettingsOverlay.cs
+++ b/osu.Game/Screens/OnlinePlay/Match/Components/RoomSettingsOverlay.cs
@@ -54,14 +54,12 @@ namespace osu.Game.Screens.OnlinePlay.Match.Components
protected override void PopIn()
{
- base.PopIn();
Settings.MoveToY(0, TRANSITION_DURATION, Easing.OutQuint);
Settings.FadeIn(TRANSITION_DURATION / 2);
}
protected override void PopOut()
{
- base.PopOut();
Settings.MoveToY(-1, TRANSITION_DURATION, Easing.InSine);
Settings.Delay(TRANSITION_DURATION / 2).FadeOut(TRANSITION_DURATION / 2);
}
diff --git a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsResultsScreen.cs b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsResultsScreen.cs
index d40d43cd54..aa72394ac9 100644
--- a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsResultsScreen.cs
+++ b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsResultsScreen.cs
@@ -182,7 +182,7 @@ namespace osu.Game.Screens.OnlinePlay.Playlists
/// An optional pivot around which the scores were retrieved.
private void performSuccessCallback([NotNull] Action> callback, [NotNull] List scores, [CanBeNull] MultiplayerScores pivot = null) => Schedule(() =>
{
- var scoreInfos = scoreManager.OrderByTotalScore(scores.Select(s => s.CreateScoreInfo(scoreManager, rulesets, playlistItem, Beatmap.Value.BeatmapInfo))).ToArray();
+ var scoreInfos = scores.Select(s => s.CreateScoreInfo(scoreManager, rulesets, playlistItem, Beatmap.Value.BeatmapInfo)).OrderByTotalScore().ToArray();
// Select a score if we don't already have one selected.
// Note: This is done before the callback so that the panel list centres on the selected score before panels are added (eliminating initial scroll).
diff --git a/osu.Game/Screens/Ranking/Expanded/ExpandedPanelMiddleContent.cs b/osu.Game/Screens/Ranking/Expanded/ExpandedPanelMiddleContent.cs
index fe74c1ba0d..82c429798e 100644
--- a/osu.Game/Screens/Ranking/Expanded/ExpandedPanelMiddleContent.cs
+++ b/osu.Game/Screens/Ranking/Expanded/ExpandedPanelMiddleContent.cs
@@ -73,7 +73,7 @@ namespace osu.Game.Screens.Ranking.Expanded
var topStatistics = new List
{
new AccuracyStatistic(score.Accuracy),
- new ComboStatistic(score.MaxCombo, scoreManager.GetMaximumAchievableCombo(score)),
+ new ComboStatistic(score.MaxCombo, score.GetMaximumAchievableCombo()),
new PerformanceStatistic(score),
};
diff --git a/osu.Game/Screens/Select/Carousel/TopLocalRank.cs b/osu.Game/Screens/Select/Carousel/TopLocalRank.cs
index a57a8b0f27..7c632b63db 100644
--- a/osu.Game/Screens/Select/Carousel/TopLocalRank.cs
+++ b/osu.Game/Screens/Select/Carousel/TopLocalRank.cs
@@ -29,9 +29,6 @@ namespace osu.Game.Screens.Select.Carousel
[Resolved]
private RealmAccess realm { get; set; } = null!;
- [Resolved]
- private ScoreManager scoreManager { get; set; } = null!;
-
[Resolved]
private IAPIProvider api { get; set; } = null!;
@@ -78,7 +75,7 @@ namespace osu.Game.Screens.Select.Carousel
if (changes?.HasCollectionChanges() == false)
return;
- ScoreInfo? topScore = scoreManager.OrderByTotalScore(sender.Detach()).FirstOrDefault();
+ ScoreInfo? topScore = sender.Detach().OrderByTotalScore().FirstOrDefault();
updateable.Rank = topScore?.Rank;
updateable.Alpha = topScore != null ? 1 : 0;
diff --git a/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs b/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs
index 2b40b9faf8..4c41ed3622 100644
--- a/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs
+++ b/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs
@@ -67,9 +67,6 @@ namespace osu.Game.Screens.Select.Leaderboards
}
}
- [Resolved]
- private ScoreManager scoreManager { get; set; } = null!;
-
[Resolved]
private IBindable ruleset { get; set; } = null!;
@@ -164,7 +161,7 @@ namespace osu.Game.Screens.Select.Leaderboards
return;
SetScores(
- scoreManager.OrderByTotalScore(response.Scores.Select(s => s.ToScoreInfo(rulesets, fetchBeatmapInfo))),
+ response.Scores.Select(s => s.ToScoreInfo(rulesets, fetchBeatmapInfo)).OrderByTotalScore(),
response.UserScore?.CreateScoreInfo(rulesets, fetchBeatmapInfo)
);
});
@@ -222,7 +219,7 @@ namespace osu.Game.Screens.Select.Leaderboards
scores = scores.Where(s => selectedMods.SetEquals(s.Mods.Select(m => m.Acronym)));
}
- scores = scoreManager.OrderByTotalScore(scores.Detach());
+ scores = scores.Detach().OrderByTotalScore();
SetScores(scores);
}
diff --git a/osu.Game/Screens/Select/Options/BeatmapOptionsOverlay.cs b/osu.Game/Screens/Select/Options/BeatmapOptionsOverlay.cs
index c92dc2e343..5753c268d9 100644
--- a/osu.Game/Screens/Select/Options/BeatmapOptionsOverlay.cs
+++ b/osu.Game/Screens/Select/Options/BeatmapOptionsOverlay.cs
@@ -86,8 +86,6 @@ namespace osu.Game.Screens.Select.Options
protected override void PopIn()
{
- base.PopIn();
-
this.FadeIn(transition_duration, Easing.OutQuint);
if (buttonsContainer.Position.X == 1 || Alpha == 0)
diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs
index 4d6a5398c5..47e5325baf 100644
--- a/osu.Game/Screens/Select/SongSelect.cs
+++ b/osu.Game/Screens/Select/SongSelect.cs
@@ -5,7 +5,6 @@ using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
-using Humanizer;
using osu.Framework.Allocation;
using osu.Framework.Audio;
using osu.Framework.Audio.Sample;
@@ -864,7 +863,7 @@ namespace osu.Game.Screens.Select
{
// Intentionally not localised until we have proper support for this (see https://github.com/ppy/osu-framework/pull/4918
// but also in this case we want support for formatting a number within a string).
- FilterControl.InformationalText = $"{"match".ToQuantity(Carousel.CountDisplayed, "#,0")}";
+ FilterControl.InformationalText = Carousel.CountDisplayed != 1 ? $"{Carousel.CountDisplayed:#,0} matches" : $"{Carousel.CountDisplayed:#,0} match";
}
private bool boundLocalBindables;
diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj
index e08b09aef9..d94c4a2df9 100644
--- a/osu.Game/osu.Game.csproj
+++ b/osu.Game/osu.Game.csproj
@@ -1,4 +1,4 @@
-
+
net6.0
Library
@@ -36,7 +36,7 @@
runtime; build; native; contentfiles; analyzers; buildtransitive
-
+
diff --git a/osu.iOS.props b/osu.iOS.props
index 9aafec6c50..96396ca4ad 100644
--- a/osu.iOS.props
+++ b/osu.iOS.props
@@ -16,6 +16,6 @@
iossimulator-x64
-
+