From 4c397085c5203aae8c70f58a63d5a48787cdbc2f Mon Sep 17 00:00:00 2001 From: tsrk Date: Tue, 6 Jun 2023 00:04:29 +0200 Subject: [PATCH 001/109] refactor: improve attachement flow This removes the hard reliance on individual classes and makes its usage easiser to ingreate anywhere else. --- osu.Game/Rulesets/UI/RulesetInputManager.cs | 52 +++++++++++++++++-- .../ClicksPerSecondCalculator.cs | 2 +- .../Screens/Play/HUD/KeyCounterDisplay.cs | 16 +++++- 3 files changed, 64 insertions(+), 6 deletions(-) diff --git a/osu.Game/Rulesets/UI/RulesetInputManager.cs b/osu.Game/Rulesets/UI/RulesetInputManager.cs index a24e22f22b..8065087341 100644 --- a/osu.Game/Rulesets/UI/RulesetInputManager.cs +++ b/osu.Game/Rulesets/UI/RulesetInputManager.cs @@ -158,9 +158,41 @@ namespace osu.Game.Rulesets.UI #endregion + #region Component attachement + + public void Attach(IAttachableSkinComponent skinComponent) + { + switch (skinComponent) + { + case KeyCounterDisplay keyCounterDisplay: + attachKeyCounter(keyCounterDisplay); + break; + + case ClicksPerSecondCalculator clicksPerSecondCalculator: + attachClicksPerSecond(clicksPerSecondCalculator); + break; + } + } + + public void Detach(IAttachableSkinComponent skinComponent) + { + switch (skinComponent) + { + case KeyCounterDisplay keyCounterDisplay: + detachKeyCounter(keyCounterDisplay); + break; + + case ClicksPerSecondCalculator clicksPerSecondCalculator: + detachClicksPerSecond(clicksPerSecondCalculator); + break; + } + } + + #endregion + #region Key Counter Attachment - public void Attach(KeyCounterDisplay keyCounter) + private void attachKeyCounter(KeyCounterDisplay keyCounter) { var receptor = new ActionReceptor(keyCounter); @@ -174,6 +206,10 @@ namespace osu.Game.Rulesets.UI .Select(action => new KeyCounterActionTrigger(action))); } + private void detachKeyCounter(KeyCounterDisplay keyCounter) + { + } + private partial class ActionReceptor : KeyCounterDisplay.Receptor, IKeyBindingHandler { public ActionReceptor(KeyCounterDisplay target) @@ -197,13 +233,17 @@ namespace osu.Game.Rulesets.UI #region Keys per second Counter Attachment - public void Attach(ClicksPerSecondCalculator calculator) + private void attachClicksPerSecond(ClicksPerSecondCalculator calculator) { var listener = new ActionListener(calculator); KeyBindingContainer.Add(listener); } + private void detachClicksPerSecond(ClicksPerSecondCalculator calculator) + { + } + private partial class ActionListener : Component, IKeyBindingHandler { private readonly ClicksPerSecondCalculator calculator; @@ -266,8 +306,12 @@ namespace osu.Game.Rulesets.UI /// public interface ICanAttachHUDPieces { - void Attach(KeyCounterDisplay keyCounter); - void Attach(ClicksPerSecondCalculator calculator); + void Attach(IAttachableSkinComponent component); + void Detach(IAttachableSkinComponent component); + } + + public interface IAttachableSkinComponent + { } public class RulesetInputManagerInputState : InputState diff --git a/osu.Game/Screens/Play/HUD/ClicksPerSecond/ClicksPerSecondCalculator.cs b/osu.Game/Screens/Play/HUD/ClicksPerSecond/ClicksPerSecondCalculator.cs index ba0c47dc8b..3e55e11f1c 100644 --- a/osu.Game/Screens/Play/HUD/ClicksPerSecond/ClicksPerSecondCalculator.cs +++ b/osu.Game/Screens/Play/HUD/ClicksPerSecond/ClicksPerSecondCalculator.cs @@ -8,7 +8,7 @@ using osu.Game.Rulesets.UI; namespace osu.Game.Screens.Play.HUD.ClicksPerSecond { - public partial class ClicksPerSecondCalculator : Component + public partial class ClicksPerSecondCalculator : Component, IAttachableSkinComponent { private readonly List timestamps = new List(); diff --git a/osu.Game/Screens/Play/HUD/KeyCounterDisplay.cs b/osu.Game/Screens/Play/HUD/KeyCounterDisplay.cs index 05427d3a32..b5c697ef13 100644 --- a/osu.Game/Screens/Play/HUD/KeyCounterDisplay.cs +++ b/osu.Game/Screens/Play/HUD/KeyCounterDisplay.cs @@ -11,6 +11,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Input.Events; using osu.Game.Configuration; +using osu.Game.Rulesets.UI; using osuTK; namespace osu.Game.Screens.Play.HUD @@ -18,7 +19,7 @@ namespace osu.Game.Screens.Play.HUD /// /// A flowing display of all gameplay keys. Individual keys can be added using implementations. /// - public abstract partial class KeyCounterDisplay : CompositeDrawable + public abstract partial class KeyCounterDisplay : CompositeDrawable, IAttachableSkinComponent { /// /// Whether the key counter should be visible regardless of the configuration value. @@ -44,6 +45,11 @@ namespace osu.Game.Screens.Play.HUD private Receptor? receptor; + /// + /// Sets a that will populate keybinding events to this . + /// + /// The receptor to set + /// When a is already active on this public void SetReceptor(Receptor receptor) { if (this.receptor != null) @@ -52,6 +58,14 @@ namespace osu.Game.Screens.Play.HUD this.receptor = receptor; } + /// + /// Clears any active + /// + public void ClearReceptor() + { + receptor = null; + } + /// /// Add a to this display. /// From 6fc6729677fb071f35d6ed203283a12f3c6cd600 Mon Sep 17 00:00:00 2001 From: tsrk Date: Tue, 6 Jun 2023 00:13:29 +0200 Subject: [PATCH 002/109] feat: integrate attachment flow in `SkinComponentsContainer` --- osu.Game/Rulesets/UI/DrawableRuleset.cs | 10 +++----- osu.Game/Screens/Play/HUDOverlay.cs | 1 + osu.Game/Skinning/SkinComponentsContainer.cs | 26 ++++++++++++++++++++ 3 files changed, 31 insertions(+), 6 deletions(-) diff --git a/osu.Game/Rulesets/UI/DrawableRuleset.cs b/osu.Game/Rulesets/UI/DrawableRuleset.cs index 4f22c0c617..d899b6bad1 100644 --- a/osu.Game/Rulesets/UI/DrawableRuleset.cs +++ b/osu.Game/Rulesets/UI/DrawableRuleset.cs @@ -30,8 +30,6 @@ using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Scoring; using osu.Game.Scoring; using osu.Game.Screens.Play; -using osu.Game.Screens.Play.HUD; -using osu.Game.Screens.Play.HUD.ClicksPerSecond; using osuTK; namespace osu.Game.Rulesets.UI @@ -329,11 +327,11 @@ namespace osu.Game.Rulesets.UI /// The representing . public abstract DrawableHitObject CreateDrawableRepresentation(TObject h); - public void Attach(KeyCounterDisplay keyCounter) => - (KeyBindingInputManager as ICanAttachHUDPieces)?.Attach(keyCounter); + public void Attach(IAttachableSkinComponent skinComponent) => + (KeyBindingInputManager as ICanAttachHUDPieces)?.Attach(skinComponent); - public void Attach(ClicksPerSecondCalculator calculator) => - (KeyBindingInputManager as ICanAttachHUDPieces)?.Attach(calculator); + public void Detach(IAttachableSkinComponent skinComponent) => + (KeyBindingInputManager as ICanAttachHUDPieces)?.Detach(skinComponent); /// /// Creates a key conversion input manager. An exception will be thrown if a valid is not returned. diff --git a/osu.Game/Screens/Play/HUDOverlay.cs b/osu.Game/Screens/Play/HUDOverlay.cs index 9f050a07bd..ae0e67867b 100644 --- a/osu.Game/Screens/Play/HUDOverlay.cs +++ b/osu.Game/Screens/Play/HUDOverlay.cs @@ -321,6 +321,7 @@ namespace osu.Game.Screens.Play { attachTarget.Attach(KeyCounter); attachTarget.Attach(clicksPerSecondCalculator); + mainComponents.SetAttachTarget(attachTarget); } replayLoaded.BindTo(drawableRuleset.HasReplayLoaded); diff --git a/osu.Game/Skinning/SkinComponentsContainer.cs b/osu.Game/Skinning/SkinComponentsContainer.cs index adf0a288b4..19c16d2177 100644 --- a/osu.Game/Skinning/SkinComponentsContainer.cs +++ b/osu.Game/Skinning/SkinComponentsContainer.cs @@ -6,8 +6,10 @@ using System.Collections.Generic; using System.Linq; using System.Threading; using osu.Framework.Bindables; +using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Game.Rulesets.UI; namespace osu.Game.Skinning { @@ -39,6 +41,8 @@ namespace osu.Game.Skinning private CancellationTokenSource? cancellationSource; + private ICanAttachHUDPieces? attachTarget; + public SkinComponentsContainer(SkinComponentsContainerLookup lookup) { Lookup = lookup; @@ -62,6 +66,10 @@ namespace osu.Game.Skinning public void Reload(Container? componentsContainer) { + components + .OfType() + .ForEach(c => attachTarget?.Detach(c)); + ClearInternal(); components.Clear(); ComponentsLoaded = false; @@ -77,6 +85,7 @@ namespace osu.Game.Skinning LoadComponentAsync(content, wrapper => { AddInternal(wrapper); + wrapper.Children.OfType().ForEach(c => attachTarget?.Attach(c)); components.AddRange(wrapper.Children.OfType()); ComponentsLoaded = true; }, (cancellationSource = new CancellationTokenSource()).Token); @@ -93,6 +102,9 @@ namespace osu.Game.Skinning if (!(component is Drawable drawable)) throw new ArgumentException($"Provided argument must be of type {nameof(Drawable)}.", nameof(component)); + if (component is IAttachableSkinComponent attachableSkinComponent) + attachTarget?.Attach(attachableSkinComponent); + content.Add(drawable); components.Add(component); } @@ -108,10 +120,24 @@ namespace osu.Game.Skinning if (!(component is Drawable drawable)) throw new ArgumentException($"Provided argument must be of type {nameof(Drawable)}.", nameof(component)); + if (component is IAttachableSkinComponent attachableSkinComponent) + attachTarget?.Detach(attachableSkinComponent); + content.Remove(drawable, disposeImmediately); components.Remove(component); } + public void SetAttachTarget(ICanAttachHUDPieces target) + { + attachTarget = target; + + foreach (var child in InternalChildren) + { + if (child is IAttachableSkinComponent attachable) + attachTarget.Attach(attachable); + } + } + protected override void SkinChanged(ISkinSource skin) { base.SkinChanged(skin); From 758831b983e86a3c178a0ae1e4efeaf9bdba2032 Mon Sep 17 00:00:00 2001 From: tsrk Date: Wed, 14 Jun 2023 13:25:24 +0200 Subject: [PATCH 003/109] test: remove hard usages of `KeyCounterDisplay` --- osu.Game.Tests/Visual/Gameplay/TestSceneAutoplay.cs | 7 ++++--- .../Visual/Gameplay/TestSceneGameplayRewinding.cs | 6 ++++-- osu.Game.Tests/Visual/Gameplay/TestSceneHUDOverlay.cs | 11 +++++++---- osu.Game.Tests/Visual/Gameplay/TestSceneReplay.cs | 5 ++++- .../Gameplay/TestSceneSkinEditorMultipleSkins.cs | 6 ------ .../Visual/Gameplay/TestSceneSkinnableHUDOverlay.cs | 6 +++--- 6 files changed, 22 insertions(+), 19 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneAutoplay.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneAutoplay.cs index f3f942b74b..f628709db0 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneAutoplay.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneAutoplay.cs @@ -13,6 +13,7 @@ using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Scoring; using osu.Game.Screens.Play; using osu.Game.Screens.Play.Break; +using osu.Game.Screens.Play.HUD; using osu.Game.Screens.Ranking; using osu.Game.Users.Drawables; @@ -35,14 +36,14 @@ namespace osu.Game.Tests.Visual.Gameplay var referenceBeatmap = CreateBeatmap(new OsuRuleset().RulesetInfo); AddUntilStep("score above zero", () => Player.ScoreProcessor.TotalScore.Value > 0); - AddUntilStep("key counter counted keys", () => Player.HUDOverlay.KeyCounter.Counters.Any(kc => kc.CountPresses.Value > 2)); + AddUntilStep("key counter counted keys", () => Player.HUDOverlay.ChildrenOfType().FirstOrDefault()?.Counters.Any(kc => kc.CountPresses.Value > 2) ?? false); seekTo(referenceBeatmap.Breaks[0].StartTime); - AddAssert("keys not counting", () => !Player.HUDOverlay.KeyCounter.IsCounting.Value); + AddAssert("keys not counting", () => !Player.HUDOverlay.ChildrenOfType().FirstOrDefault()?.IsCounting.Value ?? false); AddAssert("overlay displays 100% accuracy", () => Player.BreakOverlay.ChildrenOfType().Single().AccuracyDisplay.Current.Value == 1); AddStep("rewind", () => Player.GameplayClockContainer.Seek(-80000)); - AddUntilStep("key counter reset", () => Player.HUDOverlay.KeyCounter.Counters.All(kc => kc.CountPresses.Value == 0)); + AddUntilStep("key counter reset", () => Player.HUDOverlay.ChildrenOfType().FirstOrDefault()?.Counters.All(kc => kc.CountPresses.Value == 0) ?? false); seekTo(referenceBeatmap.HitObjects[^1].GetEndTime()); AddUntilStep("results displayed", () => getResultsScreen()?.IsLoaded == true); diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneGameplayRewinding.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneGameplayRewinding.cs index 751aeb4e13..3d920a3e92 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneGameplayRewinding.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneGameplayRewinding.cs @@ -7,11 +7,13 @@ using System.Linq; using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Audio; +using osu.Framework.Testing; using osu.Framework.Utils; using osu.Framework.Timing; using osu.Game.Beatmaps; using osu.Game.Rulesets; using osu.Game.Rulesets.Osu.Objects; +using osu.Game.Screens.Play.HUD; using osu.Game.Storyboards; using osuTK; @@ -31,11 +33,11 @@ namespace osu.Game.Tests.Visual.Gameplay AddUntilStep("wait for track to start running", () => Beatmap.Value.Track.IsRunning); addSeekStep(3000); AddAssert("all judged", () => Player.DrawableRuleset.Playfield.AllHitObjects.All(h => h.Judged)); - AddUntilStep("key counter counted keys", () => Player.HUDOverlay.KeyCounter.Counters.Select(kc => kc.CountPresses.Value).Sum() == 15); + AddUntilStep("key counter counted keys", () => Player.HUDOverlay.ChildrenOfType()?.FirstOrDefault()?.Counters.Select(kc => kc.CountPresses.Value).Sum() == 15); AddStep("clear results", () => Player.Results.Clear()); addSeekStep(0); AddAssert("none judged", () => Player.DrawableRuleset.Playfield.AllHitObjects.All(h => !h.Judged)); - AddUntilStep("key counters reset", () => Player.HUDOverlay.KeyCounter.Counters.All(kc => kc.CountPresses.Value == 0)); + AddUntilStep("key counters reset", () => Player.HUDOverlay.ChildrenOfType().FirstOrDefault()?.Counters.All(kc => kc.CountPresses.Value == 0) ?? false); AddAssert("no results triggered", () => Player.Results.Count == 0); } diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneHUDOverlay.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneHUDOverlay.cs index f97019e466..857d2b1d2d 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneHUDOverlay.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneHUDOverlay.cs @@ -6,6 +6,7 @@ using System.Diagnostics; using System.Linq; using NUnit.Framework; using osu.Framework.Allocation; +using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Extensions.ObjectExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -44,8 +45,8 @@ namespace osu.Game.Tests.Visual.Gameplay private readonly IGameplayClock gameplayClock = new GameplayClockContainer(new FramedClock()); // best way to check without exposing. - private Drawable hideTarget => hudOverlay.KeyCounter; - private Drawable keyCounterFlow => hudOverlay.KeyCounter.ChildrenOfType>().Single(); + private Drawable hideTarget => hudOverlay.ChildrenOfType().First(); + private Drawable keyCounterFlow => hudOverlay.ChildrenOfType().First().ChildrenOfType>().Single(); [BackgroundDependencyLoader] private void load() @@ -138,7 +139,9 @@ namespace osu.Game.Tests.Visual.Gameplay AddStep("hide key overlay", () => { localConfig.SetValue(OsuSetting.KeyOverlay, false); - hudOverlay.KeyCounter.AlwaysVisible.Value = false; + var kcd = hudOverlay.ChildrenOfType().FirstOrDefault(); + if (kcd != null) + kcd.AlwaysVisible.Value = false; }); AddStep("set showhud false", () => hudOverlay.ShowHud.Value = false); @@ -267,7 +270,7 @@ namespace osu.Game.Tests.Visual.Gameplay hudOverlay = new HUDOverlay(null, Array.Empty()); // Add any key just to display the key counter visually. - hudOverlay.KeyCounter.Add(new KeyCounterKeyboardTrigger(Key.Space)); + hudOverlay.ChildrenOfType().ForEach(k => k.Add(new KeyCounterKeyboardTrigger(Key.Space))); scoreProcessor.Combo.Value = 1; diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneReplay.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneReplay.cs index bf9b13b320..1fe7fc9063 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneReplay.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneReplay.cs @@ -6,11 +6,13 @@ using System; using System.ComponentModel; using System.Linq; +using osu.Framework.Testing; using osu.Game.Rulesets; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Scoring; using osu.Game.Scoring; using osu.Game.Screens.Play; +using osu.Game.Screens.Play.HUD; namespace osu.Game.Tests.Visual.Gameplay { @@ -27,7 +29,8 @@ namespace osu.Game.Tests.Visual.Gameplay protected override void AddCheckSteps() { AddUntilStep("score above zero", () => ((ScoreAccessibleReplayPlayer)Player).ScoreProcessor.TotalScore.Value > 0); - AddUntilStep("key counter counted keys", () => ((ScoreAccessibleReplayPlayer)Player).HUDOverlay.KeyCounter.Counters.Any(kc => kc.CountPresses.Value > 0)); + AddUntilStep("key counter counted keys", () => ((ScoreAccessibleReplayPlayer)Player).HUDOverlay.ChildrenOfType().FirstOrDefault()?.Counters.Any(kc => kc.CountPresses + .Value > 0) ?? false); AddAssert("cannot fail", () => !((ScoreAccessibleReplayPlayer)Player).AllowFail); } diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditorMultipleSkins.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditorMultipleSkins.cs index 4ae115a68d..25cbe54cc3 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditorMultipleSkins.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditorMultipleSkins.cs @@ -14,9 +14,7 @@ using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Scoring; using osu.Game.Screens.Edit; using osu.Game.Screens.Play; -using osu.Game.Screens.Play.HUD; using osu.Game.Tests.Gameplay; -using osuTK.Input; namespace osu.Game.Tests.Visual.Gameplay { @@ -57,10 +55,6 @@ namespace osu.Game.Tests.Visual.Gameplay Origin = Anchor.Centre, }; - // Add any key just to display the key counter visually. - hudOverlay.KeyCounter.Add(new KeyCounterKeyboardTrigger(Key.Space)); - scoreProcessor.Combo.Value = 1; - return new Container { RelativeSizeAxes = Axes.Both, diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableHUDOverlay.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableHUDOverlay.cs index 89432940ba..47f1ebf024 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableHUDOverlay.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableHUDOverlay.cs @@ -43,8 +43,8 @@ namespace osu.Game.Tests.Visual.Gameplay private IEnumerable hudOverlays => CreatedDrawables.OfType(); // best way to check without exposing. - private Drawable hideTarget => hudOverlay.KeyCounter; - private Drawable keyCounterFlow => hudOverlay.KeyCounter.ChildrenOfType>().Single(); + private Drawable hideTarget => hudOverlay.ChildrenOfType().First(); + private Drawable keyCounterFlow => hudOverlay.ChildrenOfType().First().ChildrenOfType>().Single(); [Test] public void TestComboCounterIncrementing() @@ -89,7 +89,7 @@ namespace osu.Game.Tests.Visual.Gameplay hudOverlay = new HUDOverlay(null, Array.Empty()); // Add any key just to display the key counter visually. - hudOverlay.KeyCounter.Add(new KeyCounterKeyboardTrigger(Key.Space)); + hudOverlay.ChildrenOfType().ForEach(k => k.Add(new KeyCounterKeyboardTrigger(Key.Space))); action?.Invoke(hudOverlay); From e9ef270e46b04a2d62d73106c9b3ae11d378c934 Mon Sep 17 00:00:00 2001 From: tsrk Date: Wed, 14 Jun 2023 19:39:28 +0200 Subject: [PATCH 004/109] refactor: move count logic in `InputTrigger` This will allow us to keep track of the real count regardless of whether the key counter has been placed mid-replay or not. --- osu.Game/Screens/Play/HUD/InputTrigger.cs | 30 ++++++++++++++++++++-- osu.Game/Screens/Play/HUD/KeyCounter.cs | 31 +++-------------------- 2 files changed, 32 insertions(+), 29 deletions(-) diff --git a/osu.Game/Screens/Play/HUD/InputTrigger.cs b/osu.Game/Screens/Play/HUD/InputTrigger.cs index b57f2cdf91..edc61ec142 100644 --- a/osu.Game/Screens/Play/HUD/InputTrigger.cs +++ b/osu.Game/Screens/Play/HUD/InputTrigger.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 osu.Framework.Bindables; using osu.Framework.Graphics; namespace osu.Game.Screens.Play.HUD @@ -25,13 +26,38 @@ namespace osu.Game.Screens.Play.HUD public event OnActivateCallback? OnActivate; public event OnDeactivateCallback? OnDeactivate; + private readonly Bindable activationCount = new BindableInt(); + private readonly Bindable isCounting = new BindableBool(true); + + /// + /// Number of times this has been activated. + /// + public IBindable ActivationCount => activationCount; + + /// + /// Whether any activation or deactivation of this impacts its + /// + public IBindable IsCounting => isCounting; + protected InputTrigger(string name) { Name = name; } - protected void Activate(bool forwardPlayback = true) => OnActivate?.Invoke(forwardPlayback); + protected void Activate(bool forwardPlayback = true) + { + if (forwardPlayback && isCounting.Value) + activationCount.Value++; - protected void Deactivate(bool forwardPlayback = true) => OnDeactivate?.Invoke(forwardPlayback); + OnActivate?.Invoke(forwardPlayback); + } + + protected void Deactivate(bool forwardPlayback = true) + { + if (!forwardPlayback && isCounting.Value) + activationCount.Value--; + + OnDeactivate?.Invoke(forwardPlayback); + } } } diff --git a/osu.Game/Screens/Play/HUD/KeyCounter.cs b/osu.Game/Screens/Play/HUD/KeyCounter.cs index 7cdd6b025f..8074b30e75 100644 --- a/osu.Game/Screens/Play/HUD/KeyCounter.cs +++ b/osu.Game/Screens/Play/HUD/KeyCounter.cs @@ -22,15 +22,10 @@ namespace osu.Game.Screens.Play.HUD /// public Bindable IsCounting { get; } = new BindableBool(true); - private readonly Bindable countPresses = new BindableInt - { - MinValue = 0 - }; - /// /// The current count of registered key presses. /// - public IBindable CountPresses => countPresses; + public IBindable CountPresses => Trigger.ActivationCount; private readonly Container content; @@ -49,46 +44,28 @@ namespace osu.Game.Screens.Play.HUD { RelativeSizeAxes = Axes.Both }, - Trigger = trigger, }; + Trigger = trigger; + Trigger.OnActivate += Activate; Trigger.OnDeactivate += Deactivate; } - private void increment() - { - if (!IsCounting.Value) - return; - - countPresses.Value++; - } - - private void decrement() - { - if (!IsCounting.Value) - return; - - countPresses.Value--; - } - protected virtual void Activate(bool forwardPlayback = true) { IsActive.Value = true; - if (forwardPlayback) - increment(); } protected virtual void Deactivate(bool forwardPlayback = true) { IsActive.Value = false; - if (!forwardPlayback) - decrement(); } protected override void Dispose(bool isDisposing) { base.Dispose(isDisposing); + Trigger.OnActivate -= Activate; Trigger.OnDeactivate -= Deactivate; } From c637fddf73a4c7ead1cc9d1a534e82d0ecb351a0 Mon Sep 17 00:00:00 2001 From: tsrk Date: Wed, 14 Jun 2023 21:13:35 +0200 Subject: [PATCH 005/109] refactor: decouple Trigger logic from `KeyCounterDisplay` This allows to keep a coeherent state regardless of the progress of the play --- .../Visual/Gameplay/TestSceneAutoplay.cs | 7 +- .../Gameplay/TestSceneGameplayRewinding.cs | 8 +- .../Visual/Gameplay/TestSceneHUDOverlay.cs | 3 +- .../Visual/Gameplay/TestSceneKeyCounter.cs | 69 ++++++++------ .../Visual/Gameplay/TestSceneReplay.cs | 5 +- .../TestSceneSkinEditorMultipleSkins.cs | 7 ++ .../Gameplay/TestSceneSkinnableHUDOverlay.cs | 2 +- osu.Game/Rulesets/UI/RulesetInputManager.cs | 23 +++-- .../Screens/Play/HUD/KeyCounterController.cs | 95 +++++++++++++++++++ .../Screens/Play/HUD/KeyCounterDisplay.cs | 78 +++------------ osu.Game/Screens/Play/HUDOverlay.cs | 19 ++-- osu.Game/Screens/Play/Player.cs | 1 - 12 files changed, 180 insertions(+), 137 deletions(-) create mode 100644 osu.Game/Screens/Play/HUD/KeyCounterController.cs diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneAutoplay.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneAutoplay.cs index f628709db0..c829b73f66 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneAutoplay.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneAutoplay.cs @@ -13,7 +13,6 @@ using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Scoring; using osu.Game.Screens.Play; using osu.Game.Screens.Play.Break; -using osu.Game.Screens.Play.HUD; using osu.Game.Screens.Ranking; using osu.Game.Users.Drawables; @@ -36,14 +35,14 @@ namespace osu.Game.Tests.Visual.Gameplay var referenceBeatmap = CreateBeatmap(new OsuRuleset().RulesetInfo); AddUntilStep("score above zero", () => Player.ScoreProcessor.TotalScore.Value > 0); - AddUntilStep("key counter counted keys", () => Player.HUDOverlay.ChildrenOfType().FirstOrDefault()?.Counters.Any(kc => kc.CountPresses.Value > 2) ?? false); + AddUntilStep("key counter counted keys", () => Player.HUDOverlay.KeyCounter.Triggers.Any(kc => kc.ActivationCount.Value > 2)); seekTo(referenceBeatmap.Breaks[0].StartTime); - AddAssert("keys not counting", () => !Player.HUDOverlay.ChildrenOfType().FirstOrDefault()?.IsCounting.Value ?? false); + AddAssert("keys not counting", () => !Player.HUDOverlay.KeyCounter.IsCounting.Value); AddAssert("overlay displays 100% accuracy", () => Player.BreakOverlay.ChildrenOfType().Single().AccuracyDisplay.Current.Value == 1); AddStep("rewind", () => Player.GameplayClockContainer.Seek(-80000)); - AddUntilStep("key counter reset", () => Player.HUDOverlay.ChildrenOfType().FirstOrDefault()?.Counters.All(kc => kc.CountPresses.Value == 0) ?? false); + AddUntilStep("key counter reset", () => Player.HUDOverlay.KeyCounter.Triggers.All(kc => kc.ActivationCount.Value == 0)); seekTo(referenceBeatmap.HitObjects[^1].GetEndTime()); AddUntilStep("results displayed", () => getResultsScreen()?.IsLoaded == true); diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneGameplayRewinding.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneGameplayRewinding.cs index 3d920a3e92..508cf192d3 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneGameplayRewinding.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneGameplayRewinding.cs @@ -7,13 +7,11 @@ using System.Linq; using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Audio; -using osu.Framework.Testing; -using osu.Framework.Utils; using osu.Framework.Timing; +using osu.Framework.Utils; using osu.Game.Beatmaps; using osu.Game.Rulesets; using osu.Game.Rulesets.Osu.Objects; -using osu.Game.Screens.Play.HUD; using osu.Game.Storyboards; using osuTK; @@ -33,11 +31,11 @@ namespace osu.Game.Tests.Visual.Gameplay AddUntilStep("wait for track to start running", () => Beatmap.Value.Track.IsRunning); addSeekStep(3000); AddAssert("all judged", () => Player.DrawableRuleset.Playfield.AllHitObjects.All(h => h.Judged)); - AddUntilStep("key counter counted keys", () => Player.HUDOverlay.ChildrenOfType()?.FirstOrDefault()?.Counters.Select(kc => kc.CountPresses.Value).Sum() == 15); + AddUntilStep("key counter counted keys", () => Player.HUDOverlay.KeyCounter.Triggers.Select(kc => kc.ActivationCount.Value).Sum() == 15); AddStep("clear results", () => Player.Results.Clear()); addSeekStep(0); AddAssert("none judged", () => Player.DrawableRuleset.Playfield.AllHitObjects.All(h => !h.Judged)); - AddUntilStep("key counters reset", () => Player.HUDOverlay.ChildrenOfType().FirstOrDefault()?.Counters.All(kc => kc.CountPresses.Value == 0) ?? false); + AddUntilStep("key counters reset", () => Player.HUDOverlay.KeyCounter.Triggers.All(kc => kc.ActivationCount.Value == 0)); AddAssert("no results triggered", () => Player.Results.Count == 0); } diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneHUDOverlay.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneHUDOverlay.cs index 857d2b1d2d..bbb10c5957 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneHUDOverlay.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneHUDOverlay.cs @@ -6,7 +6,6 @@ using System.Diagnostics; using System.Linq; using NUnit.Framework; using osu.Framework.Allocation; -using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Extensions.ObjectExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -270,7 +269,7 @@ namespace osu.Game.Tests.Visual.Gameplay hudOverlay = new HUDOverlay(null, Array.Empty()); // Add any key just to display the key counter visually. - hudOverlay.ChildrenOfType().ForEach(k => k.Add(new KeyCounterKeyboardTrigger(Key.Space))); + hudOverlay.KeyCounter.Add(new KeyCounterKeyboardTrigger(Key.Space)); scoreProcessor.Combo.Value = 1; diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneKeyCounter.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneKeyCounter.cs index 22f7111f68..7bf9738fb4 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneKeyCounter.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneKeyCounter.cs @@ -5,7 +5,9 @@ using System.Linq; using NUnit.Framework; +using osu.Framework.Allocation; using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; using osu.Framework.Utils; using osu.Game.Screens.Play; using osu.Game.Screens.Play.HUD; @@ -17,64 +19,69 @@ namespace osu.Game.Tests.Visual.Gameplay [TestFixture] public partial class TestSceneKeyCounter : OsuManualInputManagerTestScene { + [Cached] + private readonly KeyCounterController controller; + + private readonly KeyCounterDisplay defaultDisplay; + public TestSceneKeyCounter() { - KeyCounterDisplay defaultDisplay = new DefaultKeyCounterDisplay + Children = new Drawable[] { - Origin = Anchor.Centre, - Anchor = Anchor.Centre, - Position = new Vector2(0, 72.7f) + controller = new KeyCounterController(), + new FillFlowContainer + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.Both, + Direction = FillDirection.Vertical, + Spacing = new Vector2(72.7f), + Children = new[] + { + defaultDisplay = new DefaultKeyCounterDisplay + { + Origin = Anchor.Centre, + Anchor = Anchor.Centre, + }, + new ArgonKeyCounterDisplay + { + Origin = Anchor.Centre, + Anchor = Anchor.Centre, + } + } + } }; - KeyCounterDisplay argonDisplay = new ArgonKeyCounterDisplay - { - Origin = Anchor.Centre, - Anchor = Anchor.Centre, - Position = new Vector2(0, -72.7f) - }; - - defaultDisplay.AddRange(new InputTrigger[] - { - new KeyCounterKeyboardTrigger(Key.X), - new KeyCounterKeyboardTrigger(Key.X), - new KeyCounterMouseTrigger(MouseButton.Left), - new KeyCounterMouseTrigger(MouseButton.Right), - }); - - argonDisplay.AddRange(new InputTrigger[] + controller.AddRange(new InputTrigger[] { new KeyCounterKeyboardTrigger(Key.X), new KeyCounterKeyboardTrigger(Key.X), new KeyCounterMouseTrigger(MouseButton.Left), new KeyCounterMouseTrigger(MouseButton.Right), }); + } + [Test] + public void TestDoThings() + { var testCounter = (DefaultKeyCounter)defaultDisplay.Counters.First(); AddStep("Add random", () => { Key key = (Key)((int)Key.A + RNG.Next(26)); - defaultDisplay.Add(new KeyCounterKeyboardTrigger(key)); - argonDisplay.Add(new KeyCounterKeyboardTrigger(key)); + controller.Add(new KeyCounterKeyboardTrigger(key)); }); - Key testKey = ((KeyCounterKeyboardTrigger)defaultDisplay.Counters.First().Trigger).Key; + Key testKey = ((KeyCounterKeyboardTrigger)controller.Triggers.First()).Key; addPressKeyStep(); AddAssert($"Check {testKey} counter after keypress", () => testCounter.CountPresses.Value == 1); addPressKeyStep(); AddAssert($"Check {testKey} counter after keypress", () => testCounter.CountPresses.Value == 2); - AddStep("Disable counting", () => - { - argonDisplay.IsCounting.Value = false; - defaultDisplay.IsCounting.Value = false; - }); + AddStep("Disable counting", () => controller.IsCounting.Value = false); addPressKeyStep(); AddAssert($"Check {testKey} count has not changed", () => testCounter.CountPresses.Value == 2); - Add(defaultDisplay); - Add(argonDisplay); - void addPressKeyStep() => AddStep($"Press {testKey} key", () => InputManager.Key(testKey)); } } diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneReplay.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneReplay.cs index 1fe7fc9063..5fb9bf004f 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneReplay.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneReplay.cs @@ -6,13 +6,11 @@ using System; using System.ComponentModel; using System.Linq; -using osu.Framework.Testing; using osu.Game.Rulesets; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Scoring; using osu.Game.Scoring; using osu.Game.Screens.Play; -using osu.Game.Screens.Play.HUD; namespace osu.Game.Tests.Visual.Gameplay { @@ -29,8 +27,7 @@ namespace osu.Game.Tests.Visual.Gameplay protected override void AddCheckSteps() { AddUntilStep("score above zero", () => ((ScoreAccessibleReplayPlayer)Player).ScoreProcessor.TotalScore.Value > 0); - AddUntilStep("key counter counted keys", () => ((ScoreAccessibleReplayPlayer)Player).HUDOverlay.ChildrenOfType().FirstOrDefault()?.Counters.Any(kc => kc.CountPresses - .Value > 0) ?? false); + AddUntilStep("key counter counted keys", () => ((ScoreAccessibleReplayPlayer)Player).HUDOverlay.KeyCounter.Triggers.Any(kc => kc.ActivationCount.Value > 0)); AddAssert("cannot fail", () => !((ScoreAccessibleReplayPlayer)Player).AllowFail); } diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditorMultipleSkins.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditorMultipleSkins.cs index 25cbe54cc3..ac772b980e 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditorMultipleSkins.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditorMultipleSkins.cs @@ -14,7 +14,9 @@ using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Scoring; using osu.Game.Screens.Edit; using osu.Game.Screens.Play; +using osu.Game.Screens.Play.HUD; using osu.Game.Tests.Gameplay; +using osuTK.Input; namespace osu.Game.Tests.Visual.Gameplay { @@ -55,6 +57,11 @@ namespace osu.Game.Tests.Visual.Gameplay Origin = Anchor.Centre, }; + // Add any key just to display the key counter visually. + hudOverlay.KeyCounter.Add(new KeyCounterKeyboardTrigger(Key.Space)); + + scoreProcessor.Combo.Value = 1; + return new Container { RelativeSizeAxes = Axes.Both, diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableHUDOverlay.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableHUDOverlay.cs index 47f1ebf024..6532aa4ae5 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableHUDOverlay.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableHUDOverlay.cs @@ -89,7 +89,7 @@ namespace osu.Game.Tests.Visual.Gameplay hudOverlay = new HUDOverlay(null, Array.Empty()); // Add any key just to display the key counter visually. - hudOverlay.ChildrenOfType().ForEach(k => k.Add(new KeyCounterKeyboardTrigger(Key.Space))); + hudOverlay.KeyCounter.Add(new KeyCounterKeyboardTrigger(Key.Space)); action?.Invoke(hudOverlay); diff --git a/osu.Game/Rulesets/UI/RulesetInputManager.cs b/osu.Game/Rulesets/UI/RulesetInputManager.cs index 8065087341..294b72061b 100644 --- a/osu.Game/Rulesets/UI/RulesetInputManager.cs +++ b/osu.Game/Rulesets/UI/RulesetInputManager.cs @@ -164,9 +164,8 @@ namespace osu.Game.Rulesets.UI { switch (skinComponent) { - case KeyCounterDisplay keyCounterDisplay: - attachKeyCounter(keyCounterDisplay); - break; + case KeyCounterController keyCounterDisplay: + attachKeyCounter(keyCounterDisplay); break; case ClicksPerSecondCalculator clicksPerSecondCalculator: attachClicksPerSecond(clicksPerSecondCalculator); @@ -178,7 +177,7 @@ namespace osu.Game.Rulesets.UI { switch (skinComponent) { - case KeyCounterDisplay keyCounterDisplay: + case KeyCounterController keyCounterDisplay: detachKeyCounter(keyCounterDisplay); break; @@ -192,7 +191,7 @@ namespace osu.Game.Rulesets.UI #region Key Counter Attachment - private void attachKeyCounter(KeyCounterDisplay keyCounter) + private void attachKeyCounter(KeyCounterController keyCounter) { var receptor = new ActionReceptor(keyCounter); @@ -206,25 +205,25 @@ namespace osu.Game.Rulesets.UI .Select(action => new KeyCounterActionTrigger(action))); } - private void detachKeyCounter(KeyCounterDisplay keyCounter) + private void detachKeyCounter(KeyCounterController keyCounter) { + keyCounter.ClearReceptor(); } - private partial class ActionReceptor : KeyCounterDisplay.Receptor, IKeyBindingHandler + private partial class ActionReceptor : KeyCounterController.Receptor, IKeyBindingHandler { - public ActionReceptor(KeyCounterDisplay target) + public ActionReceptor(KeyCounterController target) : base(target) { } - public bool OnPressed(KeyBindingPressEvent e) => Target.Counters.Where(c => c.Trigger is KeyCounterActionTrigger) - .Select(c => (KeyCounterActionTrigger)c.Trigger) + public bool OnPressed(KeyBindingPressEvent e) => Target.Triggers + .OfType>() .Any(c => c.OnPressed(e.Action, Clock.Rate >= 0)); public void OnReleased(KeyBindingReleaseEvent e) { - foreach (var c - in Target.Counters.Where(c => c.Trigger is KeyCounterActionTrigger).Select(c => (KeyCounterActionTrigger)c.Trigger)) + foreach (var c in Target.Triggers.OfType>()) c.OnReleased(e.Action, Clock.Rate >= 0); } } diff --git a/osu.Game/Screens/Play/HUD/KeyCounterController.cs b/osu.Game/Screens/Play/HUD/KeyCounterController.cs new file mode 100644 index 0000000000..b138e64d6f --- /dev/null +++ b/osu.Game/Screens/Play/HUD/KeyCounterController.cs @@ -0,0 +1,95 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using System.Collections.Generic; +using osu.Framework.Bindables; +using osu.Framework.Extensions.IEnumerableExtensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Input.Events; +using osu.Game.Rulesets.UI; +using osuTK; + +namespace osu.Game.Screens.Play.HUD +{ + public partial class KeyCounterController : CompositeComponent, IAttachableSkinComponent + { + public readonly Bindable IsCounting = new BindableBool(true); + + private Receptor? receptor; + + public event Action? OnNewTrigger; + + private readonly Container triggers; + + public IReadOnlyList Triggers => triggers; + + public KeyCounterController() + { + InternalChild = triggers = new Container(); + } + + public void Add(InputTrigger trigger) + { + triggers.Add(trigger); + trigger.IsCounting.BindTo(IsCounting); + OnNewTrigger?.Invoke(trigger); + } + + public void AddRange(IEnumerable inputTriggers) => inputTriggers.ForEach(Add); + + /// + /// Sets a that will populate keybinding events to this . + /// + /// The receptor to set + /// When a is already active on this + public void SetReceptor(Receptor receptor) + { + if (this.receptor != null) + throw new InvalidOperationException("Cannot set a new receptor when one is already active"); + + this.receptor = receptor; + } + + /// + /// Clears any active + /// + public void ClearReceptor() + { + receptor = null; + } + + public override bool HandleNonPositionalInput => receptor == null; + + public override bool HandlePositionalInput => receptor == null; + + public partial class Receptor : Drawable + { + protected readonly KeyCounterController Target; + + public Receptor(KeyCounterController target) + { + RelativeSizeAxes = Axes.Both; + Depth = float.MinValue; + Target = target; + } + + public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => true; + + protected override bool Handle(UIEvent e) + { + switch (e) + { + case KeyDownEvent: + case KeyUpEvent: + case MouseDownEvent: + case MouseUpEvent: + return Target.TriggerEvent(e); + } + + return base.Handle(e); + } + } + } +} diff --git a/osu.Game/Screens/Play/HUD/KeyCounterDisplay.cs b/osu.Game/Screens/Play/HUD/KeyCounterDisplay.cs index b5c697ef13..b2d78216b2 100644 --- a/osu.Game/Screens/Play/HUD/KeyCounterDisplay.cs +++ b/osu.Game/Screens/Play/HUD/KeyCounterDisplay.cs @@ -1,18 +1,13 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using System; using System.Collections.Generic; -using System.Linq; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Extensions.IEnumerableExtensions; -using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Framework.Input.Events; using osu.Game.Configuration; using osu.Game.Rulesets.UI; -using osuTK; namespace osu.Game.Screens.Play.HUD { @@ -34,38 +29,13 @@ namespace osu.Game.Screens.Play.HUD protected abstract FillFlowContainer KeyFlow { get; } - /// - /// Whether the actions reported by all s within this should be counted. - /// - public Bindable IsCounting { get; } = new BindableBool(true); - protected readonly Bindable ConfigVisibility = new Bindable(); + [Resolved] + private KeyCounterController controller { get; set; } = null!; + protected abstract void UpdateVisibility(); - private Receptor? receptor; - - /// - /// Sets a that will populate keybinding events to this . - /// - /// The receptor to set - /// When a is already active on this - public void SetReceptor(Receptor receptor) - { - if (this.receptor != null) - throw new InvalidOperationException("Cannot set a new receptor when one is already active"); - - this.receptor = receptor; - } - - /// - /// Clears any active - /// - public void ClearReceptor() - { - receptor = null; - } - /// /// Add a to this display. /// @@ -74,8 +44,6 @@ namespace osu.Game.Screens.Play.HUD var keyCounter = CreateCounter(trigger); KeyFlow.Add(keyCounter); - - IsCounting.BindTo(keyCounter.IsCounting); } /// @@ -86,49 +54,29 @@ namespace osu.Game.Screens.Play.HUD protected abstract KeyCounter CreateCounter(InputTrigger trigger); [BackgroundDependencyLoader] - private void load(OsuConfigManager config) + private void load(OsuConfigManager config, DrawableRuleset? drawableRuleset) { config.BindWith(OsuSetting.KeyOverlay, ConfigVisibility); + + if (drawableRuleset != null) + AlwaysVisible.BindTo(drawableRuleset.HasReplayLoaded); } protected override void LoadComplete() { base.LoadComplete(); + controller.OnNewTrigger += Add; + AddRange(controller.Triggers); + AlwaysVisible.BindValueChanged(_ => UpdateVisibility()); ConfigVisibility.BindValueChanged(_ => UpdateVisibility(), true); } - public override bool HandleNonPositionalInput => receptor == null; - - public override bool HandlePositionalInput => receptor == null; - - public partial class Receptor : Drawable + protected override void Dispose(bool isDisposing) { - protected readonly KeyCounterDisplay Target; - - public Receptor(KeyCounterDisplay target) - { - RelativeSizeAxes = Axes.Both; - Depth = float.MinValue; - Target = target; - } - - public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => true; - - protected override bool Handle(UIEvent e) - { - switch (e) - { - case KeyDownEvent: - case KeyUpEvent: - case MouseDownEvent: - case MouseUpEvent: - return Target.InternalChildren.Any(c => c.TriggerEvent(e)); - } - - return base.Handle(e); - } + base.Dispose(isDisposing); + controller.OnNewTrigger -= Add; } } } diff --git a/osu.Game/Screens/Play/HUDOverlay.cs b/osu.Game/Screens/Play/HUDOverlay.cs index ae0e67867b..15a0e0688b 100644 --- a/osu.Game/Screens/Play/HUDOverlay.cs +++ b/osu.Game/Screens/Play/HUDOverlay.cs @@ -16,8 +16,10 @@ using osu.Framework.Input.Bindings; using osu.Framework.Input.Events; using osu.Game.Configuration; using osu.Game.Input.Bindings; +using osu.Game.Localisation; using osu.Game.Overlays; using osu.Game.Overlays.Notifications; +using osu.Game.Rulesets; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.UI; @@ -26,8 +28,6 @@ using osu.Game.Screens.Play.HUD.ClicksPerSecond; using osu.Game.Screens.Play.HUD.JudgementCounter; using osu.Game.Skinning; using osuTK; -using osu.Game.Localisation; -using osu.Game.Rulesets; namespace osu.Game.Screens.Play { @@ -54,7 +54,6 @@ namespace osu.Game.Screens.Play return child == bottomRightElements; } - public readonly KeyCounterDisplay KeyCounter; public readonly ModDisplay ModDisplay; public readonly HoldForMenuButton HoldToQuit; public readonly PlayerSettingsOverlay PlayerSettingsOverlay; @@ -62,6 +61,9 @@ namespace osu.Game.Screens.Play [Cached] private readonly ClicksPerSecondCalculator clicksPerSecondCalculator; + [Cached] + public readonly KeyCounterController KeyCounter; + [Cached] private readonly JudgementTally tally; @@ -145,7 +147,6 @@ namespace osu.Game.Screens.Play Direction = FillDirection.Vertical, Children = new Drawable[] { - KeyCounter = CreateKeyCounter(), HoldToQuit = CreateHoldForMenuButton(), } }, @@ -157,9 +158,10 @@ namespace osu.Game.Screens.Play Spacing = new Vector2(5) }, clicksPerSecondCalculator = new ClicksPerSecondCalculator(), + KeyCounter = new KeyCounterController() }; - hideTargets = new List { mainComponents, rulesetComponents, KeyCounter, topRightElements }; + hideTargets = new List { mainComponents, rulesetComponents, topRightElements }; if (!alwaysShowLeaderboard) hideTargets.Add(LeaderboardFlow); @@ -321,7 +323,6 @@ namespace osu.Game.Screens.Play { attachTarget.Attach(KeyCounter); attachTarget.Attach(clicksPerSecondCalculator); - mainComponents.SetAttachTarget(attachTarget); } replayLoaded.BindTo(drawableRuleset.HasReplayLoaded); @@ -332,12 +333,6 @@ namespace osu.Game.Screens.Play ShowHealth = { BindTarget = ShowHealthBar } }; - protected KeyCounterDisplay CreateKeyCounter() => new DefaultKeyCounterDisplay - { - Anchor = Anchor.BottomRight, - Origin = Anchor.BottomRight, - }; - protected HoldForMenuButton CreateHoldForMenuButton() => new HoldForMenuButton { Anchor = Anchor.BottomRight, diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 18ea9d0acb..9fc97162bf 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -438,7 +438,6 @@ namespace osu.Game.Screens.Play { Value = false }, - AlwaysVisible = { BindTarget = DrawableRuleset.HasReplayLoaded }, }, Anchor = Anchor.Centre, Origin = Anchor.Centre From e26aeea589ffa3ad4bf5a3a391a3574feb8a36ad Mon Sep 17 00:00:00 2001 From: tsrk Date: Wed, 14 Jun 2023 21:15:12 +0200 Subject: [PATCH 006/109] feat: make `KeyCounterDisplay` skinnable --- osu.Game/Screens/Play/HUD/KeyCounterDisplay.cs | 5 ++++- osu.Game/Skinning/ArgonSkin.cs | 10 ++++++++++ osu.Game/Skinning/TrianglesSkin.cs | 10 ++++++++++ 3 files changed, 24 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Play/HUD/KeyCounterDisplay.cs b/osu.Game/Screens/Play/HUD/KeyCounterDisplay.cs index b2d78216b2..d599d383a5 100644 --- a/osu.Game/Screens/Play/HUD/KeyCounterDisplay.cs +++ b/osu.Game/Screens/Play/HUD/KeyCounterDisplay.cs @@ -8,13 +8,14 @@ using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Graphics.Containers; using osu.Game.Configuration; using osu.Game.Rulesets.UI; +using osu.Game.Skinning; namespace osu.Game.Screens.Play.HUD { /// /// A flowing display of all gameplay keys. Individual keys can be added using implementations. /// - public abstract partial class KeyCounterDisplay : CompositeDrawable, IAttachableSkinComponent + public abstract partial class KeyCounterDisplay : CompositeDrawable, IAttachableSkinComponent, ISerialisableDrawable { /// /// Whether the key counter should be visible regardless of the configuration value. @@ -78,5 +79,7 @@ namespace osu.Game.Screens.Play.HUD base.Dispose(isDisposing); controller.OnNewTrigger -= Add; } + + public bool UsesFixedAnchor { get; set; } } } diff --git a/osu.Game/Skinning/ArgonSkin.cs b/osu.Game/Skinning/ArgonSkin.cs index a9b26f13e8..48326bfe60 100644 --- a/osu.Game/Skinning/ArgonSkin.cs +++ b/osu.Game/Skinning/ArgonSkin.cs @@ -12,6 +12,7 @@ using osu.Game.Audio; using osu.Game.Beatmaps.Formats; using osu.Game.Extensions; using osu.Game.IO; +using osu.Game.Screens.Play; using osu.Game.Screens.Play.HUD; using osu.Game.Screens.Play.HUD.HitErrorMeters; using osuTK; @@ -113,6 +114,7 @@ namespace osu.Game.Skinning var combo = container.OfType().FirstOrDefault(); var ppCounter = container.OfType().FirstOrDefault(); var songProgress = container.OfType().FirstOrDefault(); + var keyCounter = container.OfType().FirstOrDefault(); if (score != null) { @@ -168,6 +170,13 @@ namespace osu.Game.Skinning { songProgress.Position = new Vector2(0, -10); songProgress.Scale = new Vector2(0.9f, 1); + + if (keyCounter != null) + { + keyCounter.Anchor = Anchor.BottomLeft; + keyCounter.Origin = Anchor.BottomLeft; + keyCounter.Position = new Vector2(50, -57); + } } } }) @@ -179,6 +188,7 @@ namespace osu.Game.Skinning new DefaultAccuracyCounter(), new DefaultHealthDisplay(), new ArgonSongProgress(), + new ArgonKeyCounterDisplay(), new BarHitErrorMeter(), new BarHitErrorMeter(), new PerformancePointsCounter() diff --git a/osu.Game/Skinning/TrianglesSkin.cs b/osu.Game/Skinning/TrianglesSkin.cs index e88b827807..bb562468db 100644 --- a/osu.Game/Skinning/TrianglesSkin.cs +++ b/osu.Game/Skinning/TrianglesSkin.cs @@ -90,6 +90,8 @@ namespace osu.Game.Skinning var accuracy = container.OfType().FirstOrDefault(); var combo = container.OfType().FirstOrDefault(); var ppCounter = container.OfType().FirstOrDefault(); + var songProgress = container.OfType().FirstOrDefault(); + var keyCounter = container.OfType().FirstOrDefault(); if (score != null) { @@ -141,6 +143,13 @@ namespace osu.Game.Skinning hitError2.Origin = Anchor.CentreLeft; } } + + if (songProgress != null && keyCounter != null) + { + keyCounter.Anchor = Anchor.BottomRight; + keyCounter.Origin = Anchor.BottomRight; + keyCounter.Position = new Vector2(10, songProgress.Height + 10); + } }) { Children = new Drawable[] @@ -150,6 +159,7 @@ namespace osu.Game.Skinning new DefaultAccuracyCounter(), new DefaultHealthDisplay(), new DefaultSongProgress(), + new DefaultKeyCounterDisplay(), new BarHitErrorMeter(), new BarHitErrorMeter(), new PerformancePointsCounter() From 081190802e3f599b93559b84220909b9638130fe Mon Sep 17 00:00:00 2001 From: tsrk Date: Wed, 14 Jun 2023 21:19:08 +0200 Subject: [PATCH 007/109] revert: remove attachment logic from `SkinComponentContainer` This is no longer in the scope of the PR. --- osu.Game/Skinning/SkinComponentsContainer.cs | 26 -------------------- 1 file changed, 26 deletions(-) diff --git a/osu.Game/Skinning/SkinComponentsContainer.cs b/osu.Game/Skinning/SkinComponentsContainer.cs index 19c16d2177..adf0a288b4 100644 --- a/osu.Game/Skinning/SkinComponentsContainer.cs +++ b/osu.Game/Skinning/SkinComponentsContainer.cs @@ -6,10 +6,8 @@ using System.Collections.Generic; using System.Linq; using System.Threading; using osu.Framework.Bindables; -using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Game.Rulesets.UI; namespace osu.Game.Skinning { @@ -41,8 +39,6 @@ namespace osu.Game.Skinning private CancellationTokenSource? cancellationSource; - private ICanAttachHUDPieces? attachTarget; - public SkinComponentsContainer(SkinComponentsContainerLookup lookup) { Lookup = lookup; @@ -66,10 +62,6 @@ namespace osu.Game.Skinning public void Reload(Container? componentsContainer) { - components - .OfType() - .ForEach(c => attachTarget?.Detach(c)); - ClearInternal(); components.Clear(); ComponentsLoaded = false; @@ -85,7 +77,6 @@ namespace osu.Game.Skinning LoadComponentAsync(content, wrapper => { AddInternal(wrapper); - wrapper.Children.OfType().ForEach(c => attachTarget?.Attach(c)); components.AddRange(wrapper.Children.OfType()); ComponentsLoaded = true; }, (cancellationSource = new CancellationTokenSource()).Token); @@ -102,9 +93,6 @@ namespace osu.Game.Skinning if (!(component is Drawable drawable)) throw new ArgumentException($"Provided argument must be of type {nameof(Drawable)}.", nameof(component)); - if (component is IAttachableSkinComponent attachableSkinComponent) - attachTarget?.Attach(attachableSkinComponent); - content.Add(drawable); components.Add(component); } @@ -120,24 +108,10 @@ namespace osu.Game.Skinning if (!(component is Drawable drawable)) throw new ArgumentException($"Provided argument must be of type {nameof(Drawable)}.", nameof(component)); - if (component is IAttachableSkinComponent attachableSkinComponent) - attachTarget?.Detach(attachableSkinComponent); - content.Remove(drawable, disposeImmediately); components.Remove(component); } - public void SetAttachTarget(ICanAttachHUDPieces target) - { - attachTarget = target; - - foreach (var child in InternalChildren) - { - if (child is IAttachableSkinComponent attachable) - attachTarget.Attach(attachable); - } - } - protected override void SkinChanged(ISkinSource skin) { base.SkinChanged(skin); From 2f40f5bd1969863f86930beab52e031e35efd917 Mon Sep 17 00:00:00 2001 From: tsrk Date: Thu, 15 Jun 2023 12:01:38 +0200 Subject: [PATCH 008/109] fix: change key counter position in Triangles and Legacy skins --- osu.Game/Skinning/LegacySkin.cs | 10 ++++++++++ osu.Game/Skinning/TrianglesSkin.cs | 2 +- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/osu.Game/Skinning/LegacySkin.cs b/osu.Game/Skinning/LegacySkin.cs index e46eaf90c1..e35f8fbe4d 100644 --- a/osu.Game/Skinning/LegacySkin.cs +++ b/osu.Game/Skinning/LegacySkin.cs @@ -22,6 +22,7 @@ using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Scoring; using osu.Game.Screens.Play.HUD; using osu.Game.Screens.Play.HUD.HitErrorMeters; +using osuTK; using osuTK.Graphics; namespace osu.Game.Skinning @@ -372,12 +373,20 @@ namespace osu.Game.Skinning } var hitError = container.OfType().FirstOrDefault(); + var keyCounter = container.OfType().FirstOrDefault(); if (hitError != null) { hitError.Anchor = Anchor.BottomCentre; hitError.Origin = Anchor.CentreLeft; hitError.Rotation = -90; + + if (keyCounter != null) + { + keyCounter.Anchor = Anchor.BottomRight; + keyCounter.Origin = Anchor.BottomRight; + keyCounter.Position = new Vector2(10, -10 - hitError.Width); + } } }) { @@ -389,6 +398,7 @@ namespace osu.Game.Skinning new LegacyHealthDisplay(), new LegacySongProgress(), new BarHitErrorMeter(), + new DefaultKeyCounterDisplay() } }; } diff --git a/osu.Game/Skinning/TrianglesSkin.cs b/osu.Game/Skinning/TrianglesSkin.cs index bb562468db..424c477bda 100644 --- a/osu.Game/Skinning/TrianglesSkin.cs +++ b/osu.Game/Skinning/TrianglesSkin.cs @@ -148,7 +148,7 @@ namespace osu.Game.Skinning { keyCounter.Anchor = Anchor.BottomRight; keyCounter.Origin = Anchor.BottomRight; - keyCounter.Position = new Vector2(10, songProgress.Height + 10); + keyCounter.Position = new Vector2(10, -57 - 10); } }) { From 7e705987738729ddbb01de3554d99de24f0f7a87 Mon Sep 17 00:00:00 2001 From: tsrk Date: Thu, 15 Jun 2023 12:02:30 +0200 Subject: [PATCH 009/109] test: move back key counter tests in ctor --- .../Visual/Gameplay/TestSceneKeyCounter.cs | 21 +++++++------------ 1 file changed, 7 insertions(+), 14 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneKeyCounter.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneKeyCounter.cs index 7bf9738fb4..d7c2b1c7dd 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneKeyCounter.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneKeyCounter.cs @@ -22,8 +22,6 @@ namespace osu.Game.Tests.Visual.Gameplay [Cached] private readonly KeyCounterController controller; - private readonly KeyCounterDisplay defaultDisplay; - public TestSceneKeyCounter() { Children = new Drawable[] @@ -36,9 +34,9 @@ namespace osu.Game.Tests.Visual.Gameplay RelativeSizeAxes = Axes.Both, Direction = FillDirection.Vertical, Spacing = new Vector2(72.7f), - Children = new[] + Children = new KeyCounterDisplay[] { - defaultDisplay = new DefaultKeyCounterDisplay + new DefaultKeyCounterDisplay { Origin = Anchor.Centre, Anchor = Anchor.Centre, @@ -59,12 +57,6 @@ namespace osu.Game.Tests.Visual.Gameplay new KeyCounterMouseTrigger(MouseButton.Left), new KeyCounterMouseTrigger(MouseButton.Right), }); - } - - [Test] - public void TestDoThings() - { - var testCounter = (DefaultKeyCounter)defaultDisplay.Counters.First(); AddStep("Add random", () => { @@ -72,15 +64,16 @@ namespace osu.Game.Tests.Visual.Gameplay controller.Add(new KeyCounterKeyboardTrigger(key)); }); - Key testKey = ((KeyCounterKeyboardTrigger)controller.Triggers.First()).Key; + InputTrigger testTrigger = controller.Triggers.First(); + Key testKey = ((KeyCounterKeyboardTrigger)testTrigger).Key; addPressKeyStep(); - AddAssert($"Check {testKey} counter after keypress", () => testCounter.CountPresses.Value == 1); + AddAssert($"Check {testKey} counter after keypress", () => testTrigger.ActivationCount.Value == 1); addPressKeyStep(); - AddAssert($"Check {testKey} counter after keypress", () => testCounter.CountPresses.Value == 2); + AddAssert($"Check {testKey} counter after keypress", () => testTrigger.ActivationCount.Value == 2); AddStep("Disable counting", () => controller.IsCounting.Value = false); addPressKeyStep(); - AddAssert($"Check {testKey} count has not changed", () => testCounter.CountPresses.Value == 2); + AddAssert($"Check {testKey} count has not changed", () => testTrigger.ActivationCount.Value == 2); void addPressKeyStep() => AddStep($"Press {testKey} key", () => InputManager.Key(testKey)); } From b4cbcb210e71ea874de76e73a295940b3cf442fd Mon Sep 17 00:00:00 2001 From: tsrk Date: Thu, 15 Jun 2023 12:24:37 +0200 Subject: [PATCH 010/109] refactor: remove detachment logic No real use case, cleaning up the diff --- osu.Game/Rulesets/UI/DrawableRuleset.cs | 3 --- osu.Game/Rulesets/UI/RulesetInputManager.cs | 27 ++------------------- 2 files changed, 2 insertions(+), 28 deletions(-) diff --git a/osu.Game/Rulesets/UI/DrawableRuleset.cs b/osu.Game/Rulesets/UI/DrawableRuleset.cs index d899b6bad1..4fa18f53a7 100644 --- a/osu.Game/Rulesets/UI/DrawableRuleset.cs +++ b/osu.Game/Rulesets/UI/DrawableRuleset.cs @@ -330,9 +330,6 @@ namespace osu.Game.Rulesets.UI public void Attach(IAttachableSkinComponent skinComponent) => (KeyBindingInputManager as ICanAttachHUDPieces)?.Attach(skinComponent); - public void Detach(IAttachableSkinComponent skinComponent) => - (KeyBindingInputManager as ICanAttachHUDPieces)?.Detach(skinComponent); - /// /// Creates a key conversion input manager. An exception will be thrown if a valid is not returned. /// diff --git a/osu.Game/Rulesets/UI/RulesetInputManager.cs b/osu.Game/Rulesets/UI/RulesetInputManager.cs index 294b72061b..889890e711 100644 --- a/osu.Game/Rulesets/UI/RulesetInputManager.cs +++ b/osu.Game/Rulesets/UI/RulesetInputManager.cs @@ -165,7 +165,8 @@ namespace osu.Game.Rulesets.UI switch (skinComponent) { case KeyCounterController keyCounterDisplay: - attachKeyCounter(keyCounterDisplay); break; + attachKeyCounter(keyCounterDisplay); + break; case ClicksPerSecondCalculator clicksPerSecondCalculator: attachClicksPerSecond(clicksPerSecondCalculator); @@ -173,20 +174,6 @@ namespace osu.Game.Rulesets.UI } } - public void Detach(IAttachableSkinComponent skinComponent) - { - switch (skinComponent) - { - case KeyCounterController keyCounterDisplay: - detachKeyCounter(keyCounterDisplay); - break; - - case ClicksPerSecondCalculator clicksPerSecondCalculator: - detachClicksPerSecond(clicksPerSecondCalculator); - break; - } - } - #endregion #region Key Counter Attachment @@ -205,11 +192,6 @@ namespace osu.Game.Rulesets.UI .Select(action => new KeyCounterActionTrigger(action))); } - private void detachKeyCounter(KeyCounterController keyCounter) - { - keyCounter.ClearReceptor(); - } - private partial class ActionReceptor : KeyCounterController.Receptor, IKeyBindingHandler { public ActionReceptor(KeyCounterController target) @@ -239,10 +221,6 @@ namespace osu.Game.Rulesets.UI KeyBindingContainer.Add(listener); } - private void detachClicksPerSecond(ClicksPerSecondCalculator calculator) - { - } - private partial class ActionListener : Component, IKeyBindingHandler { private readonly ClicksPerSecondCalculator calculator; @@ -306,7 +284,6 @@ namespace osu.Game.Rulesets.UI public interface ICanAttachHUDPieces { void Attach(IAttachableSkinComponent component); - void Detach(IAttachableSkinComponent component); } public interface IAttachableSkinComponent From a61c1116f5f5d1d2b87e8176390e7c9338b88c87 Mon Sep 17 00:00:00 2001 From: tsrk Date: Thu, 15 Jun 2023 12:27:37 +0200 Subject: [PATCH 011/109] style: remove unused IAttachableSkinComponent on KCD --- osu.Game/Screens/Play/HUD/KeyCounterDisplay.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Play/HUD/KeyCounterDisplay.cs b/osu.Game/Screens/Play/HUD/KeyCounterDisplay.cs index d599d383a5..8b92a5e3b8 100644 --- a/osu.Game/Screens/Play/HUD/KeyCounterDisplay.cs +++ b/osu.Game/Screens/Play/HUD/KeyCounterDisplay.cs @@ -15,7 +15,7 @@ namespace osu.Game.Screens.Play.HUD /// /// A flowing display of all gameplay keys. Individual keys can be added using implementations. /// - public abstract partial class KeyCounterDisplay : CompositeDrawable, IAttachableSkinComponent, ISerialisableDrawable + public abstract partial class KeyCounterDisplay : CompositeDrawable, ISerialisableDrawable { /// /// Whether the key counter should be visible regardless of the configuration value. From 184c793f568f568e9d855656d7c3b91a355976b1 Mon Sep 17 00:00:00 2001 From: tsrk Date: Thu, 15 Jun 2023 12:33:26 +0200 Subject: [PATCH 012/109] style(KeyCounter): remove useless `Content` override --- osu.Game/Screens/Play/HUD/KeyCounter.cs | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/osu.Game/Screens/Play/HUD/KeyCounter.cs b/osu.Game/Screens/Play/HUD/KeyCounter.cs index 8074b30e75..7b99c34a55 100644 --- a/osu.Game/Screens/Play/HUD/KeyCounter.cs +++ b/osu.Game/Screens/Play/HUD/KeyCounter.cs @@ -2,7 +2,6 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Bindables; -using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; namespace osu.Game.Screens.Play.HUD @@ -27,10 +26,6 @@ namespace osu.Game.Screens.Play.HUD /// public IBindable CountPresses => Trigger.ActivationCount; - private readonly Container content; - - protected override Container Content => content; - /// /// Whether this is currently in the "activated" state because the associated key is currently pressed. /// @@ -38,14 +33,6 @@ namespace osu.Game.Screens.Play.HUD protected KeyCounter(InputTrigger trigger) { - InternalChildren = new Drawable[] - { - content = new Container - { - RelativeSizeAxes = Axes.Both - }, - }; - Trigger = trigger; Trigger.OnActivate += Activate; From fcdaf729158cf3461d64c784f47930487b24a8b6 Mon Sep 17 00:00:00 2001 From: tsrk Date: Thu, 15 Jun 2023 12:40:47 +0200 Subject: [PATCH 013/109] style(KeyCounter): remove useless `IsCounting` bindable Counting logic has been moved to the `Trigger` --- osu.Game/Screens/Play/HUD/KeyCounter.cs | 5 ----- 1 file changed, 5 deletions(-) diff --git a/osu.Game/Screens/Play/HUD/KeyCounter.cs b/osu.Game/Screens/Play/HUD/KeyCounter.cs index 7b99c34a55..f12d2166fc 100644 --- a/osu.Game/Screens/Play/HUD/KeyCounter.cs +++ b/osu.Game/Screens/Play/HUD/KeyCounter.cs @@ -16,11 +16,6 @@ namespace osu.Game.Screens.Play.HUD /// public readonly InputTrigger Trigger; - /// - /// Whether the actions reported by should be counted. - /// - public Bindable IsCounting { get; } = new BindableBool(true); - /// /// The current count of registered key presses. /// From 9d688733ac247169cc62fd29432e4ccd507f5440 Mon Sep 17 00:00:00 2001 From: tsrk Date: Thu, 15 Jun 2023 13:12:05 +0200 Subject: [PATCH 014/109] fix: correct key counter position in Triangles and Legacy skins --- osu.Game/Skinning/LegacySkin.cs | 2 +- osu.Game/Skinning/TrianglesSkin.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Skinning/LegacySkin.cs b/osu.Game/Skinning/LegacySkin.cs index e35f8fbe4d..e264af4c83 100644 --- a/osu.Game/Skinning/LegacySkin.cs +++ b/osu.Game/Skinning/LegacySkin.cs @@ -385,7 +385,7 @@ namespace osu.Game.Skinning { keyCounter.Anchor = Anchor.BottomRight; keyCounter.Origin = Anchor.BottomRight; - keyCounter.Position = new Vector2(10, -10 - hitError.Width); + keyCounter.Position = new Vector2(-10, -10 - hitError.Width); } } }) diff --git a/osu.Game/Skinning/TrianglesSkin.cs b/osu.Game/Skinning/TrianglesSkin.cs index 424c477bda..5f839fad0b 100644 --- a/osu.Game/Skinning/TrianglesSkin.cs +++ b/osu.Game/Skinning/TrianglesSkin.cs @@ -148,7 +148,7 @@ namespace osu.Game.Skinning { keyCounter.Anchor = Anchor.BottomRight; keyCounter.Origin = Anchor.BottomRight; - keyCounter.Position = new Vector2(10, -57 - 10); + keyCounter.Position = new Vector2(-10, -60 - 10); } }) { From f9321a24d9192ab49980eaa657bfbc2a412a051d Mon Sep 17 00:00:00 2001 From: tsrk Date: Fri, 16 Jun 2023 17:24:40 +0200 Subject: [PATCH 015/109] test: change hideTarget drawable and testing logic Doesn't change what it needs to test conceptually --- .../Visual/Gameplay/TestSceneHUDOverlay.cs | 20 +++++++++---------- .../TestSceneSkinEditorMultipleSkins.cs | 1 - .../Gameplay/TestSceneSkinnableHUDOverlay.cs | 5 +++-- 3 files changed, 13 insertions(+), 13 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneHUDOverlay.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneHUDOverlay.cs index bbb10c5957..5002281544 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneHUDOverlay.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneHUDOverlay.cs @@ -44,7 +44,7 @@ namespace osu.Game.Tests.Visual.Gameplay private readonly IGameplayClock gameplayClock = new GameplayClockContainer(new FramedClock()); // best way to check without exposing. - private Drawable hideTarget => hudOverlay.ChildrenOfType().First(); + private Drawable hideTarget => hudOverlay.ChildrenOfType().First(); private Drawable keyCounterFlow => hudOverlay.ChildrenOfType().First().ChildrenOfType>().Single(); [BackgroundDependencyLoader] @@ -73,7 +73,7 @@ namespace osu.Game.Tests.Visual.Gameplay AddAssert("showhud is set", () => hudOverlay.ShowHud.Value); - AddAssert("hidetarget is visible", () => hideTarget.IsPresent); + AddAssert("hidetarget is visible", () => hideTarget.Alpha, () => Is.GreaterThan(0)); AddAssert("key counter flow is visible", () => keyCounterFlow.IsPresent); AddAssert("pause button is visible", () => hudOverlay.HoldToQuit.IsPresent); } @@ -95,7 +95,7 @@ namespace osu.Game.Tests.Visual.Gameplay AddStep("set showhud false", () => hudOverlay.ShowHud.Value = false); - AddUntilStep("hidetarget is hidden", () => !hideTarget.IsPresent); + AddUntilStep("hidetarget is hidden", () => hideTarget.Alpha, () => Is.LessThanOrEqualTo(0)); AddAssert("pause button is still visible", () => hudOverlay.HoldToQuit.IsPresent); // Key counter flow container should not be affected by this, only the key counter display will be hidden as checked above. @@ -109,13 +109,13 @@ namespace osu.Game.Tests.Visual.Gameplay AddStep("set hud to never show", () => localConfig.SetValue(OsuSetting.HUDVisibilityMode, HUDVisibilityMode.Never)); - AddUntilStep("wait for fade", () => !hideTarget.IsPresent); + AddUntilStep("wait for fade", () => hideTarget.Alpha, () => Is.LessThanOrEqualTo(0)); AddStep("trigger momentary show", () => InputManager.PressKey(Key.ControlLeft)); - AddUntilStep("wait for visible", () => hideTarget.IsPresent); + AddUntilStep("wait for visible", () => hideTarget.Alpha, () => Is.GreaterThan(0)); AddStep("stop trigering", () => InputManager.ReleaseKey(Key.ControlLeft)); - AddUntilStep("wait for fade", () => !hideTarget.IsPresent); + AddUntilStep("wait for fade", () => hideTarget.Alpha, () => Is.LessThanOrEqualTo(0)); } [Test] @@ -144,11 +144,11 @@ namespace osu.Game.Tests.Visual.Gameplay }); AddStep("set showhud false", () => hudOverlay.ShowHud.Value = false); - AddUntilStep("hidetarget is hidden", () => !hideTarget.IsPresent); + AddUntilStep("hidetarget is hidden", () => hideTarget.Alpha, () => Is.LessThanOrEqualTo(0)); AddAssert("key counters hidden", () => !keyCounterFlow.IsPresent); AddStep("set showhud true", () => hudOverlay.ShowHud.Value = true); - AddUntilStep("hidetarget is visible", () => hideTarget.IsPresent); + AddUntilStep("hidetarget is visible", () => hideTarget.Alpha, () => Is.GreaterThan(0)); AddAssert("key counters still hidden", () => !keyCounterFlow.IsPresent); } @@ -171,7 +171,7 @@ namespace osu.Game.Tests.Visual.Gameplay }); AddStep("set showhud false", () => hudOverlay.ShowHud.Value = false); - AddUntilStep("hidetarget is hidden", () => !hideTarget.IsPresent); + AddUntilStep("hidetarget is hidden", () => hideTarget.Alpha, () => Is.LessThanOrEqualTo(0)); AddStep("attempt activate", () => { @@ -211,7 +211,7 @@ namespace osu.Game.Tests.Visual.Gameplay }); AddStep("set showhud false", () => hudOverlay.ShowHud.Value = false); - AddUntilStep("hidetarget is hidden", () => !hideTarget.IsPresent); + AddUntilStep("hidetarget is hidden", () => hideTarget.Alpha, () => Is.LessThanOrEqualTo(0)); AddStep("attempt seek", () => { diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditorMultipleSkins.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditorMultipleSkins.cs index ac772b980e..4ae115a68d 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditorMultipleSkins.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditorMultipleSkins.cs @@ -59,7 +59,6 @@ namespace osu.Game.Tests.Visual.Gameplay // Add any key just to display the key counter visually. hudOverlay.KeyCounter.Add(new KeyCounterKeyboardTrigger(Key.Space)); - scoreProcessor.Combo.Value = 1; return new Container diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableHUDOverlay.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableHUDOverlay.cs index 6532aa4ae5..cab52ddab5 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableHUDOverlay.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableHUDOverlay.cs @@ -19,6 +19,7 @@ using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Scoring; using osu.Game.Screens.Play; using osu.Game.Screens.Play.HUD; +using osu.Game.Skinning; using osu.Game.Tests.Gameplay; using osuTK.Input; @@ -43,7 +44,7 @@ namespace osu.Game.Tests.Visual.Gameplay private IEnumerable hudOverlays => CreatedDrawables.OfType(); // best way to check without exposing. - private Drawable hideTarget => hudOverlay.ChildrenOfType().First(); + private Drawable hideTarget => hudOverlay.ChildrenOfType().First(); private Drawable keyCounterFlow => hudOverlay.ChildrenOfType().First().ChildrenOfType>().Single(); [Test] @@ -73,7 +74,7 @@ namespace osu.Game.Tests.Visual.Gameplay AddStep("set showhud false", () => hudOverlays.ForEach(h => h.ShowHud.Value = false)); - AddUntilStep("hidetarget is hidden", () => !hideTarget.IsPresent); + AddUntilStep("hidetarget is hidden", () => hideTarget.Alpha, () => Is.LessThanOrEqualTo(0)); AddAssert("pause button is still visible", () => hudOverlay.HoldToQuit.IsPresent); // Key counter flow container should not be affected by this, only the key counter display will be hidden as checked above. From b9d6ba193422afe8ed72f62a43b5085885157b83 Mon Sep 17 00:00:00 2001 From: tsrk Date: Fri, 16 Jun 2023 18:26:30 +0200 Subject: [PATCH 016/109] test: add skin deserialisation test resource --- .../Archives/modified-argon-pro-20230616.osk | Bin 0 -> 1234 bytes osu.Game.Tests/Skins/SkinDeserialisationTest.cs | 2 ++ 2 files changed, 2 insertions(+) create mode 100644 osu.Game.Tests/Resources/Archives/modified-argon-pro-20230616.osk diff --git a/osu.Game.Tests/Resources/Archives/modified-argon-pro-20230616.osk b/osu.Game.Tests/Resources/Archives/modified-argon-pro-20230616.osk new file mode 100644 index 0000000000000000000000000000000000000000..5e5d7960dd585f9ed89c16e4b961e12fe968ace3 GIT binary patch literal 1234 zcmWIWW@Zs#U|`^2V3>R%?D;~^O|yZ#?GO=$;_S>kz0AB!)iZ{CO%4KX59hU5{|RzT zIudXx@@8aast#LOL+@_mlt?Fw*{$xMxRTb~pZ>ja?`fgyjTcuI?}~VNah1!oj#jk` zGFOAQ=vr=X>Pc))YxVogm&RwG_jiTjyNegLxQdos+c+V8%KnuP8O~4SakW}<^6!~L zjdzrN^=7a9zq-e1{iH;mu02*^p^bM|Z=9PNyyRqNUvrz3d1aU0B(?Ye6b~r1KeVm_ zI({+`^8zu*1DSbg`FdH!`FWzmv5J$tr%PSS)F z!{~CK4eDkWl6;$%OuBL*X~WwabN36D&lU;jc{fA)dPZf&i}O2m?#ZJ%(z=XQEtiRb z!4(+rVn9dwCT8Y>|EA=N9DWrRJ3sgI)VJJU0K9fk^HAE&l~R@`U;P2vGZYAVK=F z3GdW0{ZLWMhe9?zz3XIet$0;GpLyW~-P(n^OKv@2om>22?~flbkA8oDFSFdtrR=m5 zWBgN}t9BOOB{LpIteC*b9h1+Pemv*K=@Qmyt2O6N(9hva5mIcplA;sguPI%mPj+P}}SYNeP$wUOYW zF#C+eH74d|owesJ3)2N}En4z&SKjKl1wr1GxsTo*Y(K23KPB| zDtUjUbnfeU#%1jJUEp7%&9lhZ{{nk2EwKM=J;Sap`&dhxQ*r{&^o{4Lv_+)O?3|&L zk{x&d&4;~@G>p|JI$7+UnRipHI&W&O&Y{rH8|7U&H)+`Jo!Vqz#J=AL zr@Wq8s`fk3-{h;*v;GE7WBI)7`PN6Ta%_x$RblF0D4(pV?sY6V#IWhw^KBa(Wx&-KJdY)tX=bly$y-%lGl^ z>d?j?m(qUL7i^xyC|CHIGw@;ghIv_^CMGgkmd{yjI5D$w#>?*3pW#NmiJX^Mn!GvA z%yH+=T)t9t+rKxKK~Aa?Ej-Pp*KM9O%{H0O==wCxMd#|RX*@TiCL9e|)t!^rl5nzs zQ6gh<-nM=**QQp5Z%;Jk1plqveAD7vLDky-?Vowti+L=9ccd6KZG3d;Mb{k{YuQI> z=Ck}npFNA-(>L$4;BrUX-xDss3;OlF&OBqYr_HPK)eqKOx9r}0o8NrrhP2DaZ!SKx ziGM$1fHxzP2m|hd0GLW4pbE-AXFDTFQRKj&l3o(X^gmXNPssh Q8%P~95N-w1>?|N200~+DYybcN literal 0 HcmV?d00001 diff --git a/osu.Game.Tests/Skins/SkinDeserialisationTest.cs b/osu.Game.Tests/Skins/SkinDeserialisationTest.cs index bd8088cfb6..d60dd3da1c 100644 --- a/osu.Game.Tests/Skins/SkinDeserialisationTest.cs +++ b/osu.Game.Tests/Skins/SkinDeserialisationTest.cs @@ -51,6 +51,8 @@ namespace osu.Game.Tests.Skins "Archives/modified-default-20230117.osk", // Covers player avatar and flag. "Archives/modified-argon-20230305.osk", + // Covers key counters + "Archives/modified-argon-pro-20230616.osk" }; /// From b960741ff78a2926d334f81bcf042c83b26130d3 Mon Sep 17 00:00:00 2001 From: tsrk Date: Fri, 16 Jun 2023 18:54:19 +0200 Subject: [PATCH 017/109] test: adapt touch input test to changes --- .../TestSceneOsuTouchInput.cs | 58 ++++++++++++------- 1 file changed, 38 insertions(+), 20 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneOsuTouchInput.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneOsuTouchInput.cs index bb424eb587..4572970011 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneOsuTouchInput.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneOsuTouchInput.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; using System.Diagnostics; using System.Linq; using NUnit.Framework; @@ -38,6 +39,8 @@ namespace osu.Game.Rulesets.Osu.Tests private DefaultKeyCounter rightKeyCounter = null!; + private KeyCounterController controller = null!; + private OsuInputManager osuInputManager = null!; private Container mainContent = null!; @@ -53,35 +56,50 @@ namespace osu.Game.Rulesets.Osu.Tests { osuInputManager = new OsuInputManager(new OsuRuleset().RulesetInfo) { - Child = mainContent = new Container + Children = new Drawable[] { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Children = new Drawable[] + controller = new KeyCounterController(), + mainContent = new DependencyProvidingContainer { - leftKeyCounter = new DefaultKeyCounter(new TestActionKeyCounterTrigger(OsuAction.LeftButton)) + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + CachedDependencies = new (Type, object)[] { (typeof(KeyCounterController), controller) }, + Children = new Drawable[] { - Anchor = Anchor.Centre, - Origin = Anchor.CentreRight, - Depth = float.MinValue, - X = -100, + new OsuCursorContainer + { + Depth = float.MinValue, + } }, - rightKeyCounter = new DefaultKeyCounter(new TestActionKeyCounterTrigger(OsuAction.RightButton)) - { - Anchor = Anchor.Centre, - Origin = Anchor.CentreLeft, - Depth = float.MinValue, - X = 100, - }, - new OsuCursorContainer - { - Depth = float.MinValue, - } }, } }, new TouchVisualiser(), }; + + InputTrigger triggerLeft; + InputTrigger triggerRight; + + controller.Add(triggerLeft = new TestActionKeyCounterTrigger(OsuAction.LeftButton)); + controller.Add(triggerRight = new TestActionKeyCounterTrigger(OsuAction.RightButton)); + + mainContent.AddRange(new[] + { + leftKeyCounter = new DefaultKeyCounter(triggerLeft) + { + Anchor = Anchor.Centre, + Origin = Anchor.CentreRight, + Depth = float.MinValue, + X = -100, + }, + rightKeyCounter = new DefaultKeyCounter(triggerRight) + { + Anchor = Anchor.Centre, + Origin = Anchor.CentreLeft, + Depth = float.MinValue, + X = 100, + }, + }); }); } From 61101335cca6968ad37092230663932b660ce2d4 Mon Sep 17 00:00:00 2001 From: tsrk Date: Fri, 16 Jun 2023 19:00:09 +0200 Subject: [PATCH 018/109] test: fix `KeyCounterController` not provided as a dependency --- .../Visual/Gameplay/TestSceneBeatmapSkinFallbacks.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneBeatmapSkinFallbacks.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneBeatmapSkinFallbacks.cs index 514a2d7e84..56c405d81f 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneBeatmapSkinFallbacks.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneBeatmapSkinFallbacks.cs @@ -18,6 +18,7 @@ using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Osu.Skinning.Legacy; using osu.Game.Rulesets.Scoring; using osu.Game.Screens.Play; +using osu.Game.Screens.Play.HUD; using osu.Game.Skinning; using osu.Game.Storyboards; @@ -77,7 +78,8 @@ namespace osu.Game.Tests.Visual.Gameplay (typeof(ScoreProcessor), actualComponentsContainer.Dependencies.Get()), (typeof(HealthProcessor), actualComponentsContainer.Dependencies.Get()), (typeof(GameplayState), actualComponentsContainer.Dependencies.Get()), - (typeof(IGameplayClock), actualComponentsContainer.Dependencies.Get()) + (typeof(IGameplayClock), actualComponentsContainer.Dependencies.Get()), + (typeof(KeyCounterController), actualComponentsContainer.Dependencies.Get()) }, }; From 886a1e98da66b381244c114d271e8951456b2a9f Mon Sep 17 00:00:00 2001 From: tsrk Date: Sun, 18 Jun 2023 17:32:49 +0200 Subject: [PATCH 019/109] test(SkinDeserialisationTest): use more complete skin --- .../Archives/modified-argon-pro-20230616.osk | Bin 1234 -> 0 bytes .../Archives/modified-argon-pro-20230618.osk | Bin 0 -> 1628 bytes osu.Game.Tests/Skins/SkinDeserialisationTest.cs | 2 +- 3 files changed, 1 insertion(+), 1 deletion(-) delete mode 100644 osu.Game.Tests/Resources/Archives/modified-argon-pro-20230616.osk create mode 100644 osu.Game.Tests/Resources/Archives/modified-argon-pro-20230618.osk diff --git a/osu.Game.Tests/Resources/Archives/modified-argon-pro-20230616.osk b/osu.Game.Tests/Resources/Archives/modified-argon-pro-20230616.osk deleted file mode 100644 index 5e5d7960dd585f9ed89c16e4b961e12fe968ace3..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1234 zcmWIWW@Zs#U|`^2V3>R%?D;~^O|yZ#?GO=$;_S>kz0AB!)iZ{CO%4KX59hU5{|RzT zIudXx@@8aast#LOL+@_mlt?Fw*{$xMxRTb~pZ>ja?`fgyjTcuI?}~VNah1!oj#jk` zGFOAQ=vr=X>Pc))YxVogm&RwG_jiTjyNegLxQdos+c+V8%KnuP8O~4SakW}<^6!~L zjdzrN^=7a9zq-e1{iH;mu02*^p^bM|Z=9PNyyRqNUvrz3d1aU0B(?Ye6b~r1KeVm_ zI({+`^8zu*1DSbg`FdH!`FWzmv5J$tr%PSS)F z!{~CK4eDkWl6;$%OuBL*X~WwabN36D&lU;jc{fA)dPZf&i}O2m?#ZJ%(z=XQEtiRb z!4(+rVn9dwCT8Y>|EA=N9DWrRJ3sgI)VJJU0K9fk^HAE&l~R@`U;P2vGZYAVK=F z3GdW0{ZLWMhe9?zz3XIet$0;GpLyW~-P(n^OKv@2om>22?~flbkA8oDFSFdtrR=m5 zWBgN}t9BOOB{LpIteC*b9h1+Pemv*K=@Qmyt2O6N(9hva5mIcplA;sguPI%mPj+P}}SYNeP$wUOYW zF#C+eH74d|owesJ3)2N}En4z&SKjKl1wr1GxsTo*Y(K23KPB| zDtUjUbnfeU#%1jJUEp7%&9lhZ{{nk2EwKM=J;Sap`&dhxQ*r{&^o{4Lv_+)O?3|&L zk{x&d&4;~@G>p|JI$7+UnRipHI&W&O&Y{rH8|7U&H)+`Jo!Vqz#J=AL zr@Wq8s`fk3-{h;*v;GE7WBI)7`PN6Ta%_x$RblF0D4(pV?sY6V#IWhw^KBa(Wx&-KJdY)tX=bly$y-%lGl^ z>d?j?m(qUL7i^xyC|CHIGw@;ghIv_^CMGgkmd{yjI5D$w#>?*3pW#NmiJX^Mn!GvA z%yH+=T)t9t+rKxKK~Aa?Ej-Pp*KM9O%{H0O==wCxMd#|RX*@TiCL9e|)t!^rl5nzs zQ6gh<-nM=**QQp5Z%;Jk1plqveAD7vLDky-?Vowti+L=9ccd6KZG3d;Mb{k{YuQI> z=Ck}npFNA-(>L$4;BrUX-xDss3;OlF&OBqYr_HPK)eqKOx9r}0o8NrrhP2DaZ!SKx ziGM$1fHxzP2m|hd0GLW4pbE-AXFDTFQRKj&l3o(X^gmXNPssh Q8%P~95N-w1>?|N200~+DYybcN diff --git a/osu.Game.Tests/Resources/Archives/modified-argon-pro-20230618.osk b/osu.Game.Tests/Resources/Archives/modified-argon-pro-20230618.osk new file mode 100644 index 0000000000000000000000000000000000000000..dd25e06c06896c54d4ccbff5448d54d8252d9bb7 GIT binary patch literal 1628 zcmWIWW@Zs#U|`^2INE(F?D;~^O|yZ#?O+iGhT`nZJiW}kOw}`nd`%7lZV%_RSpNxf zOga*9De`7yW~vTbSwrt`x~yz7VnC9d2yA?w2oG_ z3o=)Ox9D1KZ|X^GPHXl1%$LSzpZ9l#;=79%wz!IxT-!Jyeailo4;ju+wLi43 z0y=&&5c2{t$OD;qY596t#rb)ry&Jg>IS8=+cKDZgSF5q$#MH7R#Y`80-6q^STU4Lq z?wAwN@qeBA#@fSsDyOmv#;5SrC~W(>adDq&Ut3>hr`y!~)$zfm(d(Pr_C0&Hd`{AY z6vOCppAG6}7m|FNmQ1>GA!)5JL>6@6D=Mn1SoS$2epO>0fQVe$O+wj2rTLuDk^SAsL_{bCHq#!%FB4I;s z=Bzfi-P2cf%_&fk>+3z|cYDRF`t{5UCur`uz_ilrQ`5fRkF5XPEx*D4{(fXwa?6)K zHko~$J9BEb$Q4~UAUK`zDcg=X=4)!3y5iEj4K}ugX#VsPR=6y{*rFutXKb`jYNFto z%u`De3cpH?CdA_iz%pgiuqbksRFUO_BTO#4qseP zRW9Bce8@#>*;%36v$_xLb)3`Zd}~{7`K+Y@X-;|5Z$y76EI%0^bUEqdo@H}Sm@JW5 zWjwXRl`lQNtC1tQ)ADJO_`gn_q{@5u^s1IR)Fys6sIJTQ+u0P9xWH6+;;C$HRjFe= z=LJ5QuFp67F|BaIjLQK^bK-ngOHer)(v{I<q^tSA@t&ex?&1-!X z+yCXv@?hrQR&QVZe^92&Ym@fNc+nC01e4Gw!bgRQ@~p2XD_LJnD~mgOrv6eS3n$0h zVzmR;4Y|b+`2;(^`4d~LvD4=wzs9faJ{ubfmu*gBn=*5?;KUQrlP@jb-eA(?6_+8T z{Dbo->w@(q+iuw{_jzQ(u#b1%qmB>$U(;S+cvfQlfA(jdvzCl=ynR-1Br46kuu9E5 z@Vs(a-Qth049xF8D*ZCYeNNbR-I&weW9UEC(6)O0PYbKi_@nycP0hjHpRC}j!S{?GV5 zrFWCxqlEJC2&+rpeGg6@ylueq?$B@M_kCaY-8eP(Ud651sX`Cm%sRQ#lrJvgqf6w` zUHO$3$L2>(?ehDuo$>RkX%pTb=jvp0J9i+eJij>U+^*H%<1a5bvmoh=Blqb=juY5g zSd)9r%i;{a3Qy*~_Sh?N-NJIq|FRX^dChiq@2(HVg++=vvVW7=+d|V3cAnZqUs@PXGurxL7cfLx49c8%O~Q5PkyEA*>)C E0DO_V8vp From c8afd057bd2e8781748d9c0a3777773a9647b967 Mon Sep 17 00:00:00 2001 From: tsrk Date: Sun, 18 Jun 2023 17:51:17 +0200 Subject: [PATCH 020/109] test(TestSceneOsuTouchInput): simplify draw hierarchy An InputTrigger is considered active as long as --- .../TestSceneOsuTouchInput.cs | 33 +++++++------------ 1 file changed, 12 insertions(+), 21 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneOsuTouchInput.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneOsuTouchInput.cs index 4572970011..9510935a31 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneOsuTouchInput.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneOsuTouchInput.cs @@ -1,7 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using System; using System.Diagnostics; using System.Linq; using NUnit.Framework; @@ -39,8 +38,6 @@ namespace osu.Game.Rulesets.Osu.Tests private DefaultKeyCounter rightKeyCounter = null!; - private KeyCounterController controller = null!; - private OsuInputManager osuInputManager = null!; private Container mainContent = null!; @@ -52,37 +49,31 @@ namespace osu.Game.Rulesets.Osu.Tests AddStep("Create tests", () => { + InputTrigger triggerLeft; + InputTrigger triggerRight; + Children = new Drawable[] { osuInputManager = new OsuInputManager(new OsuRuleset().RulesetInfo) { - Children = new Drawable[] + Child = mainContent = new Container { - controller = new KeyCounterController(), - mainContent = new DependencyProvidingContainer + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Children = new Drawable[] { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - CachedDependencies = new (Type, object)[] { (typeof(KeyCounterController), controller) }, - Children = new Drawable[] + new OsuCursorContainer { - new OsuCursorContainer - { - Depth = float.MinValue, - } + Depth = float.MinValue, }, + triggerLeft = new TestActionKeyCounterTrigger(OsuAction.LeftButton), + triggerRight = new TestActionKeyCounterTrigger(OsuAction.RightButton) }, - } + }, }, new TouchVisualiser(), }; - InputTrigger triggerLeft; - InputTrigger triggerRight; - - controller.Add(triggerLeft = new TestActionKeyCounterTrigger(OsuAction.LeftButton)); - controller.Add(triggerRight = new TestActionKeyCounterTrigger(OsuAction.RightButton)); - mainContent.AddRange(new[] { leftKeyCounter = new DefaultKeyCounter(triggerLeft) From cf1ee2ba35c69444b99ddbdd103a97154874c29f Mon Sep 17 00:00:00 2001 From: tsrk Date: Sun, 18 Jun 2023 18:26:08 +0200 Subject: [PATCH 021/109] test(TestSceneOsuTouchInput): fix `InputTrigger` depth --- osu.Game.Rulesets.Osu.Tests/TestSceneOsuTouchInput.cs | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneOsuTouchInput.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneOsuTouchInput.cs index 9510935a31..2e62689e2c 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneOsuTouchInput.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneOsuTouchInput.cs @@ -66,8 +66,14 @@ namespace osu.Game.Rulesets.Osu.Tests { Depth = float.MinValue, }, - triggerLeft = new TestActionKeyCounterTrigger(OsuAction.LeftButton), + triggerLeft = new TestActionKeyCounterTrigger(OsuAction.LeftButton) + { + Depth = float.MinValue + }, triggerRight = new TestActionKeyCounterTrigger(OsuAction.RightButton) + { + Depth = float.MinValue + } }, }, }, @@ -80,14 +86,12 @@ namespace osu.Game.Rulesets.Osu.Tests { Anchor = Anchor.Centre, Origin = Anchor.CentreRight, - Depth = float.MinValue, X = -100, }, rightKeyCounter = new DefaultKeyCounter(triggerRight) { Anchor = Anchor.Centre, Origin = Anchor.CentreLeft, - Depth = float.MinValue, X = 100, }, }); From 1a8219adf6e53f91e2b39487d4854825ff3853bb Mon Sep 17 00:00:00 2001 From: tsrk Date: Sun, 18 Jun 2023 20:20:56 +0200 Subject: [PATCH 022/109] style: guard event handler unsubscriptions --- osu.Game/Screens/Play/HUD/KeyCounter.cs | 8 ++++++-- osu.Game/Screens/Play/HUD/KeyCounterDisplay.cs | 5 ++++- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/Play/HUD/KeyCounter.cs b/osu.Game/Screens/Play/HUD/KeyCounter.cs index f12d2166fc..08d7e79e7c 100644 --- a/osu.Game/Screens/Play/HUD/KeyCounter.cs +++ b/osu.Game/Screens/Play/HUD/KeyCounter.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Bindables; +using osu.Framework.Extensions.ObjectExtensions; using osu.Framework.Graphics.Containers; namespace osu.Game.Screens.Play.HUD @@ -48,8 +49,11 @@ namespace osu.Game.Screens.Play.HUD { base.Dispose(isDisposing); - Trigger.OnActivate -= Activate; - Trigger.OnDeactivate -= Deactivate; + if (Trigger.IsNotNull()) + { + Trigger.OnActivate -= Activate; + Trigger.OnDeactivate -= Deactivate; + } } } } diff --git a/osu.Game/Screens/Play/HUD/KeyCounterDisplay.cs b/osu.Game/Screens/Play/HUD/KeyCounterDisplay.cs index 8b92a5e3b8..efe51d75b0 100644 --- a/osu.Game/Screens/Play/HUD/KeyCounterDisplay.cs +++ b/osu.Game/Screens/Play/HUD/KeyCounterDisplay.cs @@ -5,6 +5,7 @@ using System.Collections.Generic; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Extensions.IEnumerableExtensions; +using osu.Framework.Extensions.ObjectExtensions; using osu.Framework.Graphics.Containers; using osu.Game.Configuration; using osu.Game.Rulesets.UI; @@ -77,7 +78,9 @@ namespace osu.Game.Screens.Play.HUD protected override void Dispose(bool isDisposing) { base.Dispose(isDisposing); - controller.OnNewTrigger -= Add; + + if (controller.IsNotNull()) + controller.OnNewTrigger -= Add; } public bool UsesFixedAnchor { get; set; } From 141f9efad5cdf663c0d6fe1708dc8144eda272cc Mon Sep 17 00:00:00 2001 From: tsrk Date: Sun, 18 Jun 2023 21:26:16 +0200 Subject: [PATCH 023/109] style(KeyCounterController): remove reliance on `Receptor` --- osu.Game/Rulesets/UI/RulesetInputManager.cs | 23 +------- .../Play/HUD/KeyCounterActionTrigger.cs | 16 ++--- .../Screens/Play/HUD/KeyCounterController.cs | 59 +------------------ osu.Game/Screens/Play/HUDOverlay.cs | 2 +- 4 files changed, 13 insertions(+), 87 deletions(-) diff --git a/osu.Game/Rulesets/UI/RulesetInputManager.cs b/osu.Game/Rulesets/UI/RulesetInputManager.cs index 889890e711..d3bc381f72 100644 --- a/osu.Game/Rulesets/UI/RulesetInputManager.cs +++ b/osu.Game/Rulesets/UI/RulesetInputManager.cs @@ -180,11 +180,8 @@ namespace osu.Game.Rulesets.UI private void attachKeyCounter(KeyCounterController keyCounter) { - var receptor = new ActionReceptor(keyCounter); + KeyBindingContainer.Add(keyCounter); - KeyBindingContainer.Add(receptor); - - keyCounter.SetReceptor(receptor); keyCounter.AddRange(KeyBindingContainer.DefaultKeyBindings .Select(b => b.GetAction()) .Distinct() @@ -192,24 +189,6 @@ namespace osu.Game.Rulesets.UI .Select(action => new KeyCounterActionTrigger(action))); } - private partial class ActionReceptor : KeyCounterController.Receptor, IKeyBindingHandler - { - public ActionReceptor(KeyCounterController target) - : base(target) - { - } - - public bool OnPressed(KeyBindingPressEvent e) => Target.Triggers - .OfType>() - .Any(c => c.OnPressed(e.Action, Clock.Rate >= 0)); - - public void OnReleased(KeyBindingReleaseEvent e) - { - foreach (var c in Target.Triggers.OfType>()) - c.OnReleased(e.Action, Clock.Rate >= 0); - } - } - #endregion #region Keys per second Counter Attachment diff --git a/osu.Game/Screens/Play/HUD/KeyCounterActionTrigger.cs b/osu.Game/Screens/Play/HUD/KeyCounterActionTrigger.cs index e5951a8bf4..f2c4487854 100644 --- a/osu.Game/Screens/Play/HUD/KeyCounterActionTrigger.cs +++ b/osu.Game/Screens/Play/HUD/KeyCounterActionTrigger.cs @@ -2,10 +2,12 @@ // See the LICENCE file in the repository root for full licence text. using System.Collections.Generic; +using osu.Framework.Input.Bindings; +using osu.Framework.Input.Events; namespace osu.Game.Screens.Play.HUD { - public partial class KeyCounterActionTrigger : InputTrigger + public partial class KeyCounterActionTrigger : InputTrigger, IKeyBindingHandler where T : struct { public T Action { get; } @@ -16,21 +18,21 @@ namespace osu.Game.Screens.Play.HUD Action = action; } - public bool OnPressed(T action, bool forwards) + public bool OnPressed(KeyBindingPressEvent e) { - if (!EqualityComparer.Default.Equals(action, Action)) + if (!EqualityComparer.Default.Equals(e.Action, Action)) return false; - Activate(forwards); + Activate(Clock.Rate >= 0); return false; } - public void OnReleased(T action, bool forwards) + public void OnReleased(KeyBindingReleaseEvent e) { - if (!EqualityComparer.Default.Equals(action, Action)) + if (!EqualityComparer.Default.Equals(e.Action, Action)) return; - Deactivate(forwards); + Deactivate(Clock.Rate >= 0); } } } diff --git a/osu.Game/Screens/Play/HUD/KeyCounterController.cs b/osu.Game/Screens/Play/HUD/KeyCounterController.cs index b138e64d6f..0fa02afbb4 100644 --- a/osu.Game/Screens/Play/HUD/KeyCounterController.cs +++ b/osu.Game/Screens/Play/HUD/KeyCounterController.cs @@ -5,11 +5,8 @@ using System; using System.Collections.Generic; using osu.Framework.Bindables; using osu.Framework.Extensions.IEnumerableExtensions; -using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Framework.Input.Events; using osu.Game.Rulesets.UI; -using osuTK; namespace osu.Game.Screens.Play.HUD { @@ -17,8 +14,6 @@ namespace osu.Game.Screens.Play.HUD { public readonly Bindable IsCounting = new BindableBool(true); - private Receptor? receptor; - public event Action? OnNewTrigger; private readonly Container triggers; @@ -38,58 +33,8 @@ namespace osu.Game.Screens.Play.HUD } public void AddRange(IEnumerable inputTriggers) => inputTriggers.ForEach(Add); + public override bool HandleNonPositionalInput => true; - /// - /// Sets a that will populate keybinding events to this . - /// - /// The receptor to set - /// When a is already active on this - public void SetReceptor(Receptor receptor) - { - if (this.receptor != null) - throw new InvalidOperationException("Cannot set a new receptor when one is already active"); - - this.receptor = receptor; - } - - /// - /// Clears any active - /// - public void ClearReceptor() - { - receptor = null; - } - - public override bool HandleNonPositionalInput => receptor == null; - - public override bool HandlePositionalInput => receptor == null; - - public partial class Receptor : Drawable - { - protected readonly KeyCounterController Target; - - public Receptor(KeyCounterController target) - { - RelativeSizeAxes = Axes.Both; - Depth = float.MinValue; - Target = target; - } - - public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => true; - - protected override bool Handle(UIEvent e) - { - switch (e) - { - case KeyDownEvent: - case KeyUpEvent: - case MouseDownEvent: - case MouseUpEvent: - return Target.TriggerEvent(e); - } - - return base.Handle(e); - } - } + public override bool HandlePositionalInput => true; } } diff --git a/osu.Game/Screens/Play/HUDOverlay.cs b/osu.Game/Screens/Play/HUDOverlay.cs index 15a0e0688b..21636ac04c 100644 --- a/osu.Game/Screens/Play/HUDOverlay.cs +++ b/osu.Game/Screens/Play/HUDOverlay.cs @@ -158,8 +158,8 @@ namespace osu.Game.Screens.Play Spacing = new Vector2(5) }, clicksPerSecondCalculator = new ClicksPerSecondCalculator(), - KeyCounter = new KeyCounterController() }; + KeyCounter = new KeyCounterController(); hideTargets = new List { mainComponents, rulesetComponents, topRightElements }; From f83a4f495291a3983c15604d60392fa630cf6e56 Mon Sep 17 00:00:00 2001 From: tsrk Date: Sun, 18 Jun 2023 22:57:21 +0200 Subject: [PATCH 024/109] refactor: tidy up attachement flow TODO: find better naming and improve XMLDocs --- osu.Game/Rulesets/UI/DrawableRuleset.cs | 6 +- osu.Game/Rulesets/UI/IKeybindingListener.cs | 50 ++++++++++++ osu.Game/Rulesets/UI/RulesetInputManager.cs | 78 +++++++------------ .../ClicksPerSecondCalculator.cs | 19 ++++- .../Screens/Play/HUD/KeyCounterController.cs | 21 ++++- osu.Game/Screens/Play/HUDOverlay.cs | 12 +-- 6 files changed, 126 insertions(+), 60 deletions(-) create mode 100644 osu.Game/Rulesets/UI/IKeybindingListener.cs diff --git a/osu.Game/Rulesets/UI/DrawableRuleset.cs b/osu.Game/Rulesets/UI/DrawableRuleset.cs index 4fa18f53a7..e0a1533c4b 100644 --- a/osu.Game/Rulesets/UI/DrawableRuleset.cs +++ b/osu.Game/Rulesets/UI/DrawableRuleset.cs @@ -38,7 +38,7 @@ namespace osu.Game.Rulesets.UI /// Displays an interactive ruleset gameplay instance. /// /// The type of HitObject contained by this DrawableRuleset. - public abstract partial class DrawableRuleset : DrawableRuleset, IProvideCursor, ICanAttachHUDPieces + public abstract partial class DrawableRuleset : DrawableRuleset, IProvideCursor, IKeybindingEventsEmitter where TObject : HitObject { public override event Action NewResult; @@ -327,8 +327,8 @@ namespace osu.Game.Rulesets.UI /// The representing . public abstract DrawableHitObject CreateDrawableRepresentation(TObject h); - public void Attach(IAttachableSkinComponent skinComponent) => - (KeyBindingInputManager as ICanAttachHUDPieces)?.Attach(skinComponent); + public void Attach(IKeybindingListener skinComponent) => + (KeyBindingInputManager as IKeybindingEventsEmitter)?.Attach(skinComponent); /// /// Creates a key conversion input manager. An exception will be thrown if a valid is not returned. diff --git a/osu.Game/Rulesets/UI/IKeybindingListener.cs b/osu.Game/Rulesets/UI/IKeybindingListener.cs new file mode 100644 index 0000000000..f38ce8643e --- /dev/null +++ b/osu.Game/Rulesets/UI/IKeybindingListener.cs @@ -0,0 +1,50 @@ +// 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 osu.Framework.Graphics.Containers; +using osu.Framework.Input.Bindings; +using osu.Framework.Input.Events; + +namespace osu.Game.Rulesets.UI +{ + /// + /// Listens to events emitted by an . + /// Alternative to for classes that need to not depend on type parameters. + /// + public interface IKeybindingListener + { + /// + /// This class or a member of this class can already handle keybindings. + /// Signals to the that and + /// don't necessarily need to be called. + /// + /// + /// This is usually true for s and s that need to + /// pass s events to children that can already handle them. + /// + public bool CanHandleKeybindings { get; } + + /// + /// Prepares this class to receive events. + /// + /// The list of possible actions that can occur. + /// The type actions, commonly enums. + public void Setup(IEnumerable actions) where T : struct; + + /// + /// Called when an action is pressed. + /// + /// The event containing information about the pressed action. + /// The type of binding, commonly enums. + public void OnPressed(KeyBindingPressEvent action) where T : struct; + + /// + /// Called when an action is released. + /// + /// The event containing information about the released action. + /// The type of binding, commonly enums. + public void OnReleased(KeyBindingReleaseEvent action) where T : struct; + } +} diff --git a/osu.Game/Rulesets/UI/RulesetInputManager.cs b/osu.Game/Rulesets/UI/RulesetInputManager.cs index d3bc381f72..44c1f00cf7 100644 --- a/osu.Game/Rulesets/UI/RulesetInputManager.cs +++ b/osu.Game/Rulesets/UI/RulesetInputManager.cs @@ -19,13 +19,11 @@ using osu.Game.Input; using osu.Game.Input.Bindings; using osu.Game.Input.Handlers; using osu.Game.Rulesets.Scoring; -using osu.Game.Screens.Play.HUD; -using osu.Game.Screens.Play.HUD.ClicksPerSecond; using static osu.Game.Input.Handlers.ReplayInputHandler; namespace osu.Game.Rulesets.UI { - public abstract partial class RulesetInputManager : PassThroughInputManager, ICanAttachHUDPieces, IHasReplayHandler, IHasRecordingHandler + public abstract partial class RulesetInputManager : PassThroughInputManager, IKeybindingEventsEmitter, IHasReplayHandler, IHasRecordingHandler where T : struct { protected override bool AllowRightClickFromLongTouch => false; @@ -66,6 +64,7 @@ namespace osu.Game.Rulesets.UI InternalChild = KeyBindingContainer = CreateKeyBindingContainer(ruleset, variant, unique) .WithChild(content = new Container { RelativeSizeAxes = Axes.Both }); + KeyBindingContainer.Add(actionListener = new ActionListener()); } [BackgroundDependencyLoader(true)] @@ -160,63 +159,47 @@ namespace osu.Game.Rulesets.UI #region Component attachement - public void Attach(IAttachableSkinComponent skinComponent) + private readonly ActionListener actionListener; + + public void Attach(IKeybindingListener skinComponent) { - switch (skinComponent) - { - case KeyCounterController keyCounterDisplay: - attachKeyCounter(keyCounterDisplay); - break; - - case ClicksPerSecondCalculator clicksPerSecondCalculator: - attachClicksPerSecond(clicksPerSecondCalculator); - break; - } - } - - #endregion - - #region Key Counter Attachment - - private void attachKeyCounter(KeyCounterController keyCounter) - { - KeyBindingContainer.Add(keyCounter); - - keyCounter.AddRange(KeyBindingContainer.DefaultKeyBindings + skinComponent.Setup(KeyBindingContainer.DefaultKeyBindings .Select(b => b.GetAction()) .Distinct() - .OrderBy(action => action) - .Select(action => new KeyCounterActionTrigger(action))); - } + .OrderBy(a => a)); - #endregion + if (skinComponent.CanHandleKeybindings && skinComponent is Drawable component) + { + try + { + KeyBindingContainer.Add(component); + return; + } + catch (Exception) + { + return; + } + } - #region Keys per second Counter Attachment - - private void attachClicksPerSecond(ClicksPerSecondCalculator calculator) - { - var listener = new ActionListener(calculator); - - KeyBindingContainer.Add(listener); + actionListener.OnPressedEvent += skinComponent.OnPressed; + actionListener.OnReleasedEvent += skinComponent.OnReleased; } private partial class ActionListener : Component, IKeyBindingHandler { - private readonly ClicksPerSecondCalculator calculator; + public event Action> OnPressedEvent; - public ActionListener(ClicksPerSecondCalculator calculator) - { - this.calculator = calculator; - } + public event Action> OnReleasedEvent; public bool OnPressed(KeyBindingPressEvent e) { - calculator.AddInputTimestamp(); + OnPressedEvent?.Invoke(e); return false; } public void OnReleased(KeyBindingReleaseEvent e) { + OnReleasedEvent?.Invoke(e); } } @@ -257,16 +240,11 @@ namespace osu.Game.Rulesets.UI } /// - /// Supports attaching various HUD pieces. - /// Keys will be populated automatically and a receptor will be injected inside. + /// Sends events to a /// - public interface ICanAttachHUDPieces - { - void Attach(IAttachableSkinComponent component); - } - - public interface IAttachableSkinComponent + public interface IKeybindingEventsEmitter { + void Attach(IKeybindingListener component); } public class RulesetInputManagerInputState : InputState diff --git a/osu.Game/Screens/Play/HUD/ClicksPerSecond/ClicksPerSecondCalculator.cs b/osu.Game/Screens/Play/HUD/ClicksPerSecond/ClicksPerSecondCalculator.cs index 3e55e11f1c..e0e93cb66e 100644 --- a/osu.Game/Screens/Play/HUD/ClicksPerSecond/ClicksPerSecondCalculator.cs +++ b/osu.Game/Screens/Play/HUD/ClicksPerSecond/ClicksPerSecondCalculator.cs @@ -4,11 +4,12 @@ using System.Collections.Generic; using osu.Framework.Allocation; using osu.Framework.Graphics; +using osu.Framework.Input.Events; using osu.Game.Rulesets.UI; namespace osu.Game.Screens.Play.HUD.ClicksPerSecond { - public partial class ClicksPerSecondCalculator : Component, IAttachableSkinComponent + public partial class ClicksPerSecondCalculator : Component, IKeybindingListener { private readonly List timestamps = new List(); @@ -53,5 +54,21 @@ namespace osu.Game.Screens.Play.HUD.ClicksPerSecond Value = count; } + + #region IKeybindingListener + + bool IKeybindingListener.CanHandleKeybindings => false; + + void IKeybindingListener.Setup(IEnumerable actions) + { + } + + void IKeybindingListener.OnPressed(KeyBindingPressEvent action) => AddInputTimestamp(); + + void IKeybindingListener.OnReleased(KeyBindingReleaseEvent action) + { + } + + #endregion } } diff --git a/osu.Game/Screens/Play/HUD/KeyCounterController.cs b/osu.Game/Screens/Play/HUD/KeyCounterController.cs index 0fa02afbb4..2e678e55fc 100644 --- a/osu.Game/Screens/Play/HUD/KeyCounterController.cs +++ b/osu.Game/Screens/Play/HUD/KeyCounterController.cs @@ -3,14 +3,16 @@ using System; using System.Collections.Generic; +using System.Linq; using osu.Framework.Bindables; using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Graphics.Containers; +using osu.Framework.Input.Events; using osu.Game.Rulesets.UI; namespace osu.Game.Screens.Play.HUD { - public partial class KeyCounterController : CompositeComponent, IAttachableSkinComponent + public partial class KeyCounterController : CompositeComponent, IKeybindingListener { public readonly Bindable IsCounting = new BindableBool(true); @@ -36,5 +38,22 @@ namespace osu.Game.Screens.Play.HUD public override bool HandleNonPositionalInput => true; public override bool HandlePositionalInput => true; + + #region IKeybindingListener + + bool IKeybindingListener.CanHandleKeybindings => true; + + void IKeybindingListener.Setup(IEnumerable actions) + => AddRange(actions.Select(a => new KeyCounterActionTrigger(a))); + + void IKeybindingListener.OnPressed(KeyBindingPressEvent action) + { + } + + void IKeybindingListener.OnReleased(KeyBindingReleaseEvent action) + { + } + + #endregion } } diff --git a/osu.Game/Screens/Play/HUDOverlay.cs b/osu.Game/Screens/Play/HUDOverlay.cs index 21636ac04c..b74b5d835a 100644 --- a/osu.Game/Screens/Play/HUDOverlay.cs +++ b/osu.Game/Screens/Play/HUDOverlay.cs @@ -10,6 +10,7 @@ using JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Extensions.EnumExtensions; +using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Input.Bindings; @@ -102,6 +103,8 @@ namespace osu.Game.Screens.Play private readonly List hideTargets; + private readonly IEnumerable actionInjectionCandidates; + public HUDOverlay(DrawableRuleset drawableRuleset, IReadOnlyList mods, bool alwaysShowLeaderboard = true) { Drawable rulesetComponents; @@ -163,6 +166,8 @@ namespace osu.Game.Screens.Play hideTargets = new List { mainComponents, rulesetComponents, topRightElements }; + actionInjectionCandidates = new IKeybindingListener[] { clicksPerSecondCalculator, KeyCounter }; + if (!alwaysShowLeaderboard) hideTargets.Add(LeaderboardFlow); } @@ -319,11 +324,8 @@ namespace osu.Game.Screens.Play protected virtual void BindDrawableRuleset(DrawableRuleset drawableRuleset) { - if (drawableRuleset is ICanAttachHUDPieces attachTarget) - { - attachTarget.Attach(KeyCounter); - attachTarget.Attach(clicksPerSecondCalculator); - } + if (drawableRuleset is IKeybindingEventsEmitter attachTarget) + actionInjectionCandidates.ForEach(attachTarget.Attach); replayLoaded.BindTo(drawableRuleset.HasReplayLoaded); } From a7088ffe22f6b03475303dcda5425fde0aa9cee1 Mon Sep 17 00:00:00 2001 From: tsrk Date: Sun, 25 Jun 2023 15:04:39 +0200 Subject: [PATCH 025/109] revert: bring back old attachment flow As discussed, this would bring more problems that anything. Refs: 4c39708, f83a4f4 --- osu.Game/Rulesets/UI/DrawableRuleset.cs | 11 +++- osu.Game/Rulesets/UI/IKeybindingListener.cs | 50 --------------- osu.Game/Rulesets/UI/RulesetInputManager.cs | 63 ++++++++++--------- .../ClicksPerSecondCalculator.cs | 19 +----- .../Screens/Play/HUD/KeyCounterController.cs | 21 +------ osu.Game/Screens/Play/HUDOverlay.cs | 12 ++-- 6 files changed, 49 insertions(+), 127 deletions(-) delete mode 100644 osu.Game/Rulesets/UI/IKeybindingListener.cs diff --git a/osu.Game/Rulesets/UI/DrawableRuleset.cs b/osu.Game/Rulesets/UI/DrawableRuleset.cs index e0a1533c4b..57f5f54d8f 100644 --- a/osu.Game/Rulesets/UI/DrawableRuleset.cs +++ b/osu.Game/Rulesets/UI/DrawableRuleset.cs @@ -30,6 +30,8 @@ using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Scoring; using osu.Game.Scoring; using osu.Game.Screens.Play; +using osu.Game.Screens.Play.HUD; +using osu.Game.Screens.Play.HUD.ClicksPerSecond; using osuTK; namespace osu.Game.Rulesets.UI @@ -38,7 +40,7 @@ namespace osu.Game.Rulesets.UI /// Displays an interactive ruleset gameplay instance. /// /// The type of HitObject contained by this DrawableRuleset. - public abstract partial class DrawableRuleset : DrawableRuleset, IProvideCursor, IKeybindingEventsEmitter + public abstract partial class DrawableRuleset : DrawableRuleset, IProvideCursor, ICanAttachHUDPieces where TObject : HitObject { public override event Action NewResult; @@ -327,8 +329,11 @@ namespace osu.Game.Rulesets.UI /// The representing . public abstract DrawableHitObject CreateDrawableRepresentation(TObject h); - public void Attach(IKeybindingListener skinComponent) => - (KeyBindingInputManager as IKeybindingEventsEmitter)?.Attach(skinComponent); + public void Attach(KeyCounterController keyCounter) => + (KeyBindingInputManager as ICanAttachHUDPieces)?.Attach(keyCounter); + + public void Attach(ClicksPerSecondCalculator calculator) => + (KeyBindingInputManager as ICanAttachHUDPieces)?.Attach(calculator); /// /// Creates a key conversion input manager. An exception will be thrown if a valid is not returned. diff --git a/osu.Game/Rulesets/UI/IKeybindingListener.cs b/osu.Game/Rulesets/UI/IKeybindingListener.cs deleted file mode 100644 index f38ce8643e..0000000000 --- a/osu.Game/Rulesets/UI/IKeybindingListener.cs +++ /dev/null @@ -1,50 +0,0 @@ -// 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 osu.Framework.Graphics.Containers; -using osu.Framework.Input.Bindings; -using osu.Framework.Input.Events; - -namespace osu.Game.Rulesets.UI -{ - /// - /// Listens to events emitted by an . - /// Alternative to for classes that need to not depend on type parameters. - /// - public interface IKeybindingListener - { - /// - /// This class or a member of this class can already handle keybindings. - /// Signals to the that and - /// don't necessarily need to be called. - /// - /// - /// This is usually true for s and s that need to - /// pass s events to children that can already handle them. - /// - public bool CanHandleKeybindings { get; } - - /// - /// Prepares this class to receive events. - /// - /// The list of possible actions that can occur. - /// The type actions, commonly enums. - public void Setup(IEnumerable actions) where T : struct; - - /// - /// Called when an action is pressed. - /// - /// The event containing information about the pressed action. - /// The type of binding, commonly enums. - public void OnPressed(KeyBindingPressEvent action) where T : struct; - - /// - /// Called when an action is released. - /// - /// The event containing information about the released action. - /// The type of binding, commonly enums. - public void OnReleased(KeyBindingReleaseEvent action) where T : struct; - } -} diff --git a/osu.Game/Rulesets/UI/RulesetInputManager.cs b/osu.Game/Rulesets/UI/RulesetInputManager.cs index 44c1f00cf7..2c403139b6 100644 --- a/osu.Game/Rulesets/UI/RulesetInputManager.cs +++ b/osu.Game/Rulesets/UI/RulesetInputManager.cs @@ -19,11 +19,13 @@ using osu.Game.Input; using osu.Game.Input.Bindings; using osu.Game.Input.Handlers; using osu.Game.Rulesets.Scoring; +using osu.Game.Screens.Play.HUD; +using osu.Game.Screens.Play.HUD.ClicksPerSecond; using static osu.Game.Input.Handlers.ReplayInputHandler; namespace osu.Game.Rulesets.UI { - public abstract partial class RulesetInputManager : PassThroughInputManager, IKeybindingEventsEmitter, IHasReplayHandler, IHasRecordingHandler + public abstract partial class RulesetInputManager : PassThroughInputManager, ICanAttachHUDPieces, IHasReplayHandler, IHasRecordingHandler where T : struct { protected override bool AllowRightClickFromLongTouch => false; @@ -64,7 +66,6 @@ namespace osu.Game.Rulesets.UI InternalChild = KeyBindingContainer = CreateKeyBindingContainer(ruleset, variant, unique) .WithChild(content = new Container { RelativeSizeAxes = Axes.Both }); - KeyBindingContainer.Add(actionListener = new ActionListener()); } [BackgroundDependencyLoader(true)] @@ -157,49 +158,47 @@ namespace osu.Game.Rulesets.UI #endregion - #region Component attachement + #region Key Counter Attachment - private readonly ActionListener actionListener; - - public void Attach(IKeybindingListener skinComponent) + public void Attach(KeyCounterController keyCounter) { - skinComponent.Setup(KeyBindingContainer.DefaultKeyBindings + KeyBindingContainer.Add(keyCounter); + + keyCounter.AddRange(KeyBindingContainer.DefaultKeyBindings .Select(b => b.GetAction()) .Distinct() - .OrderBy(a => a)); + .OrderBy(action => action) + .Select(action => new KeyCounterActionTrigger(action))); + } - if (skinComponent.CanHandleKeybindings && skinComponent is Drawable component) - { - try - { - KeyBindingContainer.Add(component); - return; - } - catch (Exception) - { - return; - } - } + #endregion - actionListener.OnPressedEvent += skinComponent.OnPressed; - actionListener.OnReleasedEvent += skinComponent.OnReleased; + #region Keys per second Counter Attachment + + public void Attach(ClicksPerSecondCalculator calculator) + { + var listener = new ActionListener(calculator); + + KeyBindingContainer.Add(listener); } private partial class ActionListener : Component, IKeyBindingHandler { - public event Action> OnPressedEvent; + private readonly ClicksPerSecondCalculator calculator; - public event Action> OnReleasedEvent; + public ActionListener(ClicksPerSecondCalculator calculator) + { + this.calculator = calculator; + } public bool OnPressed(KeyBindingPressEvent e) { - OnPressedEvent?.Invoke(e); + calculator.AddInputTimestamp(); return false; } public void OnReleased(KeyBindingReleaseEvent e) { - OnReleasedEvent?.Invoke(e); } } @@ -240,11 +239,17 @@ namespace osu.Game.Rulesets.UI } /// - /// Sends events to a + /// Supports attaching various HUD pieces. + /// Keys will be populated automatically and a receptor will be injected inside. /// - public interface IKeybindingEventsEmitter + public interface ICanAttachHUDPieces + { + void Attach(KeyCounterController keyCounter); + void Attach(ClicksPerSecondCalculator calculator); + } + + public interface IAttachableSkinComponent { - void Attach(IKeybindingListener component); } public class RulesetInputManagerInputState : InputState diff --git a/osu.Game/Screens/Play/HUD/ClicksPerSecond/ClicksPerSecondCalculator.cs b/osu.Game/Screens/Play/HUD/ClicksPerSecond/ClicksPerSecondCalculator.cs index e0e93cb66e..3e55e11f1c 100644 --- a/osu.Game/Screens/Play/HUD/ClicksPerSecond/ClicksPerSecondCalculator.cs +++ b/osu.Game/Screens/Play/HUD/ClicksPerSecond/ClicksPerSecondCalculator.cs @@ -4,12 +4,11 @@ using System.Collections.Generic; using osu.Framework.Allocation; using osu.Framework.Graphics; -using osu.Framework.Input.Events; using osu.Game.Rulesets.UI; namespace osu.Game.Screens.Play.HUD.ClicksPerSecond { - public partial class ClicksPerSecondCalculator : Component, IKeybindingListener + public partial class ClicksPerSecondCalculator : Component, IAttachableSkinComponent { private readonly List timestamps = new List(); @@ -54,21 +53,5 @@ namespace osu.Game.Screens.Play.HUD.ClicksPerSecond Value = count; } - - #region IKeybindingListener - - bool IKeybindingListener.CanHandleKeybindings => false; - - void IKeybindingListener.Setup(IEnumerable actions) - { - } - - void IKeybindingListener.OnPressed(KeyBindingPressEvent action) => AddInputTimestamp(); - - void IKeybindingListener.OnReleased(KeyBindingReleaseEvent action) - { - } - - #endregion } } diff --git a/osu.Game/Screens/Play/HUD/KeyCounterController.cs b/osu.Game/Screens/Play/HUD/KeyCounterController.cs index 2e678e55fc..0fa02afbb4 100644 --- a/osu.Game/Screens/Play/HUD/KeyCounterController.cs +++ b/osu.Game/Screens/Play/HUD/KeyCounterController.cs @@ -3,16 +3,14 @@ using System; using System.Collections.Generic; -using System.Linq; using osu.Framework.Bindables; using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Graphics.Containers; -using osu.Framework.Input.Events; using osu.Game.Rulesets.UI; namespace osu.Game.Screens.Play.HUD { - public partial class KeyCounterController : CompositeComponent, IKeybindingListener + public partial class KeyCounterController : CompositeComponent, IAttachableSkinComponent { public readonly Bindable IsCounting = new BindableBool(true); @@ -38,22 +36,5 @@ namespace osu.Game.Screens.Play.HUD public override bool HandleNonPositionalInput => true; public override bool HandlePositionalInput => true; - - #region IKeybindingListener - - bool IKeybindingListener.CanHandleKeybindings => true; - - void IKeybindingListener.Setup(IEnumerable actions) - => AddRange(actions.Select(a => new KeyCounterActionTrigger(a))); - - void IKeybindingListener.OnPressed(KeyBindingPressEvent action) - { - } - - void IKeybindingListener.OnReleased(KeyBindingReleaseEvent action) - { - } - - #endregion } } diff --git a/osu.Game/Screens/Play/HUDOverlay.cs b/osu.Game/Screens/Play/HUDOverlay.cs index b74b5d835a..21636ac04c 100644 --- a/osu.Game/Screens/Play/HUDOverlay.cs +++ b/osu.Game/Screens/Play/HUDOverlay.cs @@ -10,7 +10,6 @@ using JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Extensions.EnumExtensions; -using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Input.Bindings; @@ -103,8 +102,6 @@ namespace osu.Game.Screens.Play private readonly List hideTargets; - private readonly IEnumerable actionInjectionCandidates; - public HUDOverlay(DrawableRuleset drawableRuleset, IReadOnlyList mods, bool alwaysShowLeaderboard = true) { Drawable rulesetComponents; @@ -166,8 +163,6 @@ namespace osu.Game.Screens.Play hideTargets = new List { mainComponents, rulesetComponents, topRightElements }; - actionInjectionCandidates = new IKeybindingListener[] { clicksPerSecondCalculator, KeyCounter }; - if (!alwaysShowLeaderboard) hideTargets.Add(LeaderboardFlow); } @@ -324,8 +319,11 @@ namespace osu.Game.Screens.Play protected virtual void BindDrawableRuleset(DrawableRuleset drawableRuleset) { - if (drawableRuleset is IKeybindingEventsEmitter attachTarget) - actionInjectionCandidates.ForEach(attachTarget.Attach); + if (drawableRuleset is ICanAttachHUDPieces attachTarget) + { + attachTarget.Attach(KeyCounter); + attachTarget.Attach(clicksPerSecondCalculator); + } replayLoaded.BindTo(drawableRuleset.HasReplayLoaded); } From ba7f4722478bb0ac4e84962f2946269fbed9fb4e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 26 Jun 2023 16:04:16 +0900 Subject: [PATCH 026/109] Split padding out into constant to fix weird looking math --- osu.Game/Skinning/LegacySkin.cs | 4 +++- osu.Game/Skinning/TrianglesSkin.cs | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/osu.Game/Skinning/LegacySkin.cs b/osu.Game/Skinning/LegacySkin.cs index e264af4c83..79f13686e8 100644 --- a/osu.Game/Skinning/LegacySkin.cs +++ b/osu.Game/Skinning/LegacySkin.cs @@ -383,9 +383,11 @@ namespace osu.Game.Skinning if (keyCounter != null) { + const float padding = 10; + keyCounter.Anchor = Anchor.BottomRight; keyCounter.Origin = Anchor.BottomRight; - keyCounter.Position = new Vector2(-10, -10 - hitError.Width); + keyCounter.Position = new Vector2(-padding, -(padding + hitError.Width)); } } }) diff --git a/osu.Game/Skinning/TrianglesSkin.cs b/osu.Game/Skinning/TrianglesSkin.cs index 5f839fad0b..a4cfef63d4 100644 --- a/osu.Game/Skinning/TrianglesSkin.cs +++ b/osu.Game/Skinning/TrianglesSkin.cs @@ -146,9 +146,11 @@ namespace osu.Game.Skinning if (songProgress != null && keyCounter != null) { + const float padding = 10; + keyCounter.Anchor = Anchor.BottomRight; keyCounter.Origin = Anchor.BottomRight; - keyCounter.Position = new Vector2(-10, -60 - 10); + keyCounter.Position = new Vector2(-padding, -(60 + padding)); } }) { From 52fdeeb4911c390a826aef8d6b15378bc8ae0d2e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 26 Jun 2023 16:20:51 +0900 Subject: [PATCH 027/109] Improve positioning and positioning code clarity for argon / triangles implementations --- osu.Game/Skinning/ArgonSkin.cs | 11 ++++++++--- osu.Game/Skinning/TrianglesSkin.cs | 5 ++++- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/osu.Game/Skinning/ArgonSkin.cs b/osu.Game/Skinning/ArgonSkin.cs index 48326bfe60..3570922a9e 100644 --- a/osu.Game/Skinning/ArgonSkin.cs +++ b/osu.Game/Skinning/ArgonSkin.cs @@ -168,14 +168,19 @@ namespace osu.Game.Skinning if (songProgress != null) { - songProgress.Position = new Vector2(0, -10); + const float padding = 10; + + songProgress.Position = new Vector2(0, -padding); songProgress.Scale = new Vector2(0.9f, 1); - if (keyCounter != null) + if (keyCounter != null && hitError != null) { + // Hard to find this at runtime, so taken from the most expanded state during replay. + const float song_progress_offset_height = 36 + padding; + keyCounter.Anchor = Anchor.BottomLeft; keyCounter.Origin = Anchor.BottomLeft; - keyCounter.Position = new Vector2(50, -57); + keyCounter.Position = new Vector2(hitError.Width + padding, -(padding * 2 + song_progress_offset_height)); } } } diff --git a/osu.Game/Skinning/TrianglesSkin.cs b/osu.Game/Skinning/TrianglesSkin.cs index a4cfef63d4..a68a7fd5b9 100644 --- a/osu.Game/Skinning/TrianglesSkin.cs +++ b/osu.Game/Skinning/TrianglesSkin.cs @@ -148,9 +148,12 @@ namespace osu.Game.Skinning { const float padding = 10; + // Hard to find this at runtime, so taken from the most expanded state during replay. + const float song_progress_offset_height = 73; + keyCounter.Anchor = Anchor.BottomRight; keyCounter.Origin = Anchor.BottomRight; - keyCounter.Position = new Vector2(-padding, -(60 + padding)); + keyCounter.Position = new Vector2(-padding, -(song_progress_offset_height + padding)); } }) { From 3a2dd0e7dd2eb5b19807194037c9751db354dbb2 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 26 Jun 2023 16:22:38 +0900 Subject: [PATCH 028/109] Move argon key counter to right to match stable expectations --- osu.Game/Skinning/ArgonSkin.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/Skinning/ArgonSkin.cs b/osu.Game/Skinning/ArgonSkin.cs index 3570922a9e..ba392386de 100644 --- a/osu.Game/Skinning/ArgonSkin.cs +++ b/osu.Game/Skinning/ArgonSkin.cs @@ -178,9 +178,9 @@ namespace osu.Game.Skinning // Hard to find this at runtime, so taken from the most expanded state during replay. const float song_progress_offset_height = 36 + padding; - keyCounter.Anchor = Anchor.BottomLeft; - keyCounter.Origin = Anchor.BottomLeft; - keyCounter.Position = new Vector2(hitError.Width + padding, -(padding * 2 + song_progress_offset_height)); + keyCounter.Anchor = Anchor.BottomRight; + keyCounter.Origin = Anchor.BottomRight; + keyCounter.Position = new Vector2(-(hitError.Width + padding), -(padding * 2 + song_progress_offset_height)); } } } From ec209428300ee776d515daaffd73059248e70f1c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 26 Jun 2023 16:24:36 +0900 Subject: [PATCH 029/109] Actuall add composite component to hierarchy --- osu.Game/Screens/Play/HUDOverlay.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Play/HUDOverlay.cs b/osu.Game/Screens/Play/HUDOverlay.cs index 21636ac04c..9f3b7d5a93 100644 --- a/osu.Game/Screens/Play/HUDOverlay.cs +++ b/osu.Game/Screens/Play/HUDOverlay.cs @@ -116,6 +116,7 @@ namespace osu.Game.Screens.Play CreateFailingLayer(), //Needs to be initialized before skinnable drawables. tally = new JudgementTally(), + KeyCounter = new KeyCounterController(), mainComponents = new HUDComponentsContainer { AlwaysPresent = true, }, rulesetComponents = drawableRuleset != null ? new HUDComponentsContainer(drawableRuleset.Ruleset.RulesetInfo) { AlwaysPresent = true, } @@ -159,7 +160,6 @@ namespace osu.Game.Screens.Play }, clicksPerSecondCalculator = new ClicksPerSecondCalculator(), }; - KeyCounter = new KeyCounterController(); hideTargets = new List { mainComponents, rulesetComponents, topRightElements }; From c8e081c2b6883c62fcf0cb5e8ceada3acff4b80c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 26 Jun 2023 16:32:04 +0900 Subject: [PATCH 030/109] Remove unused interface --- osu.Game/Rulesets/UI/RulesetInputManager.cs | 4 ---- .../Play/HUD/ClicksPerSecond/ClicksPerSecondCalculator.cs | 2 +- osu.Game/Screens/Play/HUD/KeyCounterController.cs | 3 +-- 3 files changed, 2 insertions(+), 7 deletions(-) diff --git a/osu.Game/Rulesets/UI/RulesetInputManager.cs b/osu.Game/Rulesets/UI/RulesetInputManager.cs index 2c403139b6..a8a5962762 100644 --- a/osu.Game/Rulesets/UI/RulesetInputManager.cs +++ b/osu.Game/Rulesets/UI/RulesetInputManager.cs @@ -248,10 +248,6 @@ namespace osu.Game.Rulesets.UI void Attach(ClicksPerSecondCalculator calculator); } - public interface IAttachableSkinComponent - { - } - public class RulesetInputManagerInputState : InputState where T : struct { diff --git a/osu.Game/Screens/Play/HUD/ClicksPerSecond/ClicksPerSecondCalculator.cs b/osu.Game/Screens/Play/HUD/ClicksPerSecond/ClicksPerSecondCalculator.cs index 3e55e11f1c..ba0c47dc8b 100644 --- a/osu.Game/Screens/Play/HUD/ClicksPerSecond/ClicksPerSecondCalculator.cs +++ b/osu.Game/Screens/Play/HUD/ClicksPerSecond/ClicksPerSecondCalculator.cs @@ -8,7 +8,7 @@ using osu.Game.Rulesets.UI; namespace osu.Game.Screens.Play.HUD.ClicksPerSecond { - public partial class ClicksPerSecondCalculator : Component, IAttachableSkinComponent + public partial class ClicksPerSecondCalculator : Component { private readonly List timestamps = new List(); diff --git a/osu.Game/Screens/Play/HUD/KeyCounterController.cs b/osu.Game/Screens/Play/HUD/KeyCounterController.cs index 0fa02afbb4..f64a0306c6 100644 --- a/osu.Game/Screens/Play/HUD/KeyCounterController.cs +++ b/osu.Game/Screens/Play/HUD/KeyCounterController.cs @@ -6,11 +6,10 @@ using System.Collections.Generic; using osu.Framework.Bindables; using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Graphics.Containers; -using osu.Game.Rulesets.UI; namespace osu.Game.Screens.Play.HUD { - public partial class KeyCounterController : CompositeComponent, IAttachableSkinComponent + public partial class KeyCounterController : CompositeComponent { public readonly Bindable IsCounting = new BindableBool(true); From 084354a8dc5a3ce266528571340f3180909c4efe Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 26 Jun 2023 16:34:57 +0900 Subject: [PATCH 031/109] Split out interfaces from `RulesetInputManager` and improve xmldoc --- osu.Game/Rulesets/UI/ICanAttachHUDPieces.cs | 21 ++++++++++++++++++ osu.Game/Rulesets/UI/IHasRecordingHandler.cs | 15 +++++++++++++ osu.Game/Rulesets/UI/IHasReplayHandler.cs | 16 ++++++++++++++ osu.Game/Rulesets/UI/RulesetInputManager.cs | 23 -------------------- 4 files changed, 52 insertions(+), 23 deletions(-) create mode 100644 osu.Game/Rulesets/UI/ICanAttachHUDPieces.cs create mode 100644 osu.Game/Rulesets/UI/IHasRecordingHandler.cs create mode 100644 osu.Game/Rulesets/UI/IHasReplayHandler.cs diff --git a/osu.Game/Rulesets/UI/ICanAttachHUDPieces.cs b/osu.Game/Rulesets/UI/ICanAttachHUDPieces.cs new file mode 100644 index 0000000000..b81af8556e --- /dev/null +++ b/osu.Game/Rulesets/UI/ICanAttachHUDPieces.cs @@ -0,0 +1,21 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Game.Screens.Play.HUD; +using osu.Game.Screens.Play.HUD.ClicksPerSecond; + +namespace osu.Game.Rulesets.UI +{ + /// + /// A target (generally always ) which can attach various skinnable components. + /// + /// + /// Attach methods will give the target permission to prepare the component into a usable state, usually via + /// calling methods on the component (attaching various gameplay devices). + /// + public interface ICanAttachHUDPieces + { + void Attach(KeyCounterController keyCounter); + void Attach(ClicksPerSecondCalculator calculator); + } +} diff --git a/osu.Game/Rulesets/UI/IHasRecordingHandler.cs b/osu.Game/Rulesets/UI/IHasRecordingHandler.cs new file mode 100644 index 0000000000..2a13272a98 --- /dev/null +++ b/osu.Game/Rulesets/UI/IHasRecordingHandler.cs @@ -0,0 +1,15 @@ +// 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.Input; + +namespace osu.Game.Rulesets.UI +{ + /// + /// Expose the in a capable . + /// + public interface IHasRecordingHandler + { + public ReplayRecorder Recorder { set; } + } +} diff --git a/osu.Game/Rulesets/UI/IHasReplayHandler.cs b/osu.Game/Rulesets/UI/IHasReplayHandler.cs new file mode 100644 index 0000000000..561c582b71 --- /dev/null +++ b/osu.Game/Rulesets/UI/IHasReplayHandler.cs @@ -0,0 +1,16 @@ +// 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.Input; +using osu.Game.Input.Handlers; + +namespace osu.Game.Rulesets.UI +{ + /// + /// Expose the in a capable . + /// + public interface IHasReplayHandler + { + ReplayInputHandler? ReplayInputHandler { get; set; } + } +} diff --git a/osu.Game/Rulesets/UI/RulesetInputManager.cs b/osu.Game/Rulesets/UI/RulesetInputManager.cs index a8a5962762..9f4fabc300 100644 --- a/osu.Game/Rulesets/UI/RulesetInputManager.cs +++ b/osu.Game/Rulesets/UI/RulesetInputManager.cs @@ -225,29 +225,6 @@ namespace osu.Game.Rulesets.UI } } - /// - /// Expose the in a capable . - /// - public interface IHasReplayHandler - { - ReplayInputHandler ReplayInputHandler { get; set; } - } - - public interface IHasRecordingHandler - { - public ReplayRecorder Recorder { set; } - } - - /// - /// Supports attaching various HUD pieces. - /// Keys will be populated automatically and a receptor will be injected inside. - /// - public interface ICanAttachHUDPieces - { - void Attach(KeyCounterController keyCounter); - void Attach(ClicksPerSecondCalculator calculator); - } - public class RulesetInputManagerInputState : InputState where T : struct { From 44c08f3944854cd1bcd5c7ac1469d1b8cd637154 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 26 Jun 2023 16:47:33 +0900 Subject: [PATCH 032/109] Add xmldoc for `KeyCounterController` --- osu.Game/Screens/Play/HUD/KeyCounterController.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/osu.Game/Screens/Play/HUD/KeyCounterController.cs b/osu.Game/Screens/Play/HUD/KeyCounterController.cs index f64a0306c6..93e0528e6b 100644 --- a/osu.Game/Screens/Play/HUD/KeyCounterController.cs +++ b/osu.Game/Screens/Play/HUD/KeyCounterController.cs @@ -9,6 +9,10 @@ using osu.Framework.Graphics.Containers; namespace osu.Game.Screens.Play.HUD { + /// + /// Keeps track of key press counts for a current play session, exposing bindable counts which can + /// be used for display purposes. + /// public partial class KeyCounterController : CompositeComponent { public readonly Bindable IsCounting = new BindableBool(true); From 14c95f45848c90457425fc85b302517d82db3968 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 26 Jun 2023 17:28:08 +0900 Subject: [PATCH 033/109] Apply NRT to `FilterCriteria` --- .../SongSelect/TestSceneFilterControl.cs | 2 +- osu.Game/Screens/Select/FilterCriteria.cs | 18 +++++++----------- 2 files changed, 8 insertions(+), 12 deletions(-) diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneFilterControl.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneFilterControl.cs index 64e2447cca..f020cd02f7 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneFilterControl.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneFilterControl.cs @@ -210,7 +210,7 @@ namespace osu.Game.Tests.Visual.SongSelect InputManager.Click(MouseButton.Left); }); - AddAssert("collection filter still selected", () => control.CreateCriteria().CollectionBeatmapMD5Hashes.Any()); + AddAssert("collection filter still selected", () => control.CreateCriteria().CollectionBeatmapMD5Hashes?.Any() ?? false); AddAssert("filter request not fired", () => !received); } diff --git a/osu.Game/Screens/Select/FilterCriteria.cs b/osu.Game/Screens/Select/FilterCriteria.cs index 320bfb1b45..2be8c71e21 100644 --- a/osu.Game/Screens/Select/FilterCriteria.cs +++ b/osu.Game/Screens/Select/FilterCriteria.cs @@ -1,12 +1,10 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +#nullable enable +// 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 System.Collections.Generic; using System.Linq; -using JetBrains.Annotations; using osu.Game.Beatmaps; using osu.Game.Collections; using osu.Game.Rulesets; @@ -20,7 +18,7 @@ namespace osu.Game.Screens.Select public GroupMode Group; public SortMode Sort; - public BeatmapSetInfo SelectedBeatmapSet; + public BeatmapSetInfo? SelectedBeatmapSet; public OptionalRange StarDifficulty; public OptionalRange ApproachRate; @@ -42,10 +40,10 @@ namespace osu.Game.Screens.Select public string[] SearchTerms = Array.Empty(); - public RulesetInfo Ruleset; + public RulesetInfo? Ruleset; public bool AllowConvertedBeatmaps; - private string searchText; + private string searchText = string.Empty; /// /// as a number (if it can be parsed as one). @@ -70,11 +68,9 @@ namespace osu.Game.Screens.Select /// /// Hashes from the to filter to. /// - [CanBeNull] - public IEnumerable CollectionBeatmapMD5Hashes { get; set; } + public IEnumerable? CollectionBeatmapMD5Hashes { get; set; } - [CanBeNull] - public IRulesetFilterCriteria RulesetCriteria { get; set; } + public IRulesetFilterCriteria? RulesetCriteria { get; set; } public struct OptionalRange : IEquatable> where T : struct From 1960cd08397ed18c67695391b70e0ce415e92fc4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 26 Jun 2023 18:26:39 +0900 Subject: [PATCH 034/109] Add test coverage of exact matching of search terms --- .../NonVisual/Filtering/FilterMatchingTest.cs | 24 +++++++++++++++++ .../Filtering/FilterQueryParserTest.cs | 27 +++++++++++++++++++ 2 files changed, 51 insertions(+) diff --git a/osu.Game.Tests/NonVisual/Filtering/FilterMatchingTest.cs b/osu.Game.Tests/NonVisual/Filtering/FilterMatchingTest.cs index 33204d33a7..8bd9407599 100644 --- a/osu.Game.Tests/NonVisual/Filtering/FilterMatchingTest.cs +++ b/osu.Game.Tests/NonVisual/Filtering/FilterMatchingTest.cs @@ -159,6 +159,30 @@ namespace osu.Game.Tests.NonVisual.Filtering Assert.AreEqual(filtered, carouselItem.Filtered.Value); } + [Test] + [TestCase("\"artist\"", false)] + [TestCase("\"arti\"", true)] + [TestCase("\"artist title author\"", true)] + [TestCase("\"artist\" \"title\" \"author\"", false)] + [TestCase("\"an artist\"", true)] + [TestCase("\"tags too\"", false)] + [TestCase("\"tags to\"", true)] + [TestCase("\"version\"", false)] + [TestCase("\"an auteur\"", true)] + public void TestCriteriaMatchingExactTerms(string terms, bool filtered) + { + var exampleBeatmapInfo = getExampleBeatmap(); + var criteria = new FilterCriteria + { + Ruleset = new RulesetInfo { OnlineID = 6 }, + AllowConvertedBeatmaps = true, + SearchText = terms + }; + var carouselItem = new CarouselBeatmap(exampleBeatmapInfo); + carouselItem.Filter(criteria); + Assert.AreEqual(filtered, carouselItem.Filtered.Value); + } + [Test] [TestCase("", false)] [TestCase("The", false)] diff --git a/osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs b/osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs index da32edb8fb..46f91f9a67 100644 --- a/osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs +++ b/osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs @@ -23,6 +23,31 @@ namespace osu.Game.Tests.NonVisual.Filtering Assert.AreEqual(4, filterCriteria.SearchTerms.Length); } + [Test] + public void TestApplyQueriesBareWordsWithExactMatch() + { + const string query = "looking for \"a beatmap\" like \"this\""; + var filterCriteria = new FilterCriteria(); + FilterQueryParser.ApplyQueries(filterCriteria, query); + Assert.AreEqual("looking for \"a beatmap\" like \"this\"", filterCriteria.SearchText); + Assert.AreEqual(5, filterCriteria.SearchTerms.Length); + + Assert.That(filterCriteria.SearchTerms[0].SearchTerm, Is.EqualTo("a beatmap")); + Assert.That(filterCriteria.SearchTerms[0].Exact, Is.True); + + Assert.That(filterCriteria.SearchTerms[1].SearchTerm, Is.EqualTo("this")); + Assert.That(filterCriteria.SearchTerms[1].Exact, Is.True); + + Assert.That(filterCriteria.SearchTerms[2].SearchTerm, Is.EqualTo("looking")); + Assert.That(filterCriteria.SearchTerms[2].Exact, Is.False); + + Assert.That(filterCriteria.SearchTerms[3].SearchTerm, Is.EqualTo("for")); + Assert.That(filterCriteria.SearchTerms[3].Exact, Is.False); + + Assert.That(filterCriteria.SearchTerms[4].SearchTerm, Is.EqualTo("like")); + Assert.That(filterCriteria.SearchTerms[4].Exact, Is.False); + } + /* * The following tests have been written a bit strangely (they don't check exact * bound equality with what the filter says). @@ -235,6 +260,7 @@ namespace osu.Game.Tests.NonVisual.Filtering Assert.AreEqual("find me songs by please", filterCriteria.SearchText.Trim()); Assert.AreEqual(5, filterCriteria.SearchTerms.Length); Assert.AreEqual("singer", filterCriteria.Artist.SearchTerm); + Assert.That(filterCriteria.Artist.Exact, Is.False); } [Test] @@ -246,6 +272,7 @@ namespace osu.Game.Tests.NonVisual.Filtering Assert.AreEqual("really like yes", filterCriteria.SearchText.Trim()); Assert.AreEqual(3, filterCriteria.SearchTerms.Length); Assert.AreEqual("name with space", filterCriteria.Artist.SearchTerm); + Assert.That(filterCriteria.Artist.Exact, Is.True); } [Test] From a74547c43cf0328d69ec5671635f2a4a4c05356a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 26 Jun 2023 17:30:08 +0900 Subject: [PATCH 035/109] Add exact match support at song select --- .../Select/Carousel/CarouselBeatmap.cs | 10 ++-- osu.Game/Screens/Select/FilterCriteria.cs | 47 +++++++++++++++++-- osu.Game/Screens/Select/FilterQueryParser.cs | 2 +- 3 files changed, 47 insertions(+), 12 deletions(-) diff --git a/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs b/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs index 7e48bc5cdd..18931c462f 100644 --- a/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs +++ b/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs @@ -1,7 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using System; using System.Linq; using osu.Game.Beatmaps; using osu.Game.Screens.Select.Filter; @@ -65,16 +64,16 @@ namespace osu.Game.Screens.Select.Carousel if (criteria.SearchTerms.Length > 0) { - var terms = BeatmapInfo.GetSearchableTerms(); + var searchableTerms = BeatmapInfo.GetSearchableTerms(); - foreach (string criteriaTerm in criteria.SearchTerms) + foreach (FilterCriteria.OptionalTextFilter criteriaTerm in criteria.SearchTerms) { bool any = false; // ReSharper disable once ForeachCanBeConvertedToQueryUsingAnotherGetEnumerator - foreach (string term in terms) + foreach (string searchTerm in searchableTerms) { - if (!term.Contains(criteriaTerm, StringComparison.InvariantCultureIgnoreCase)) continue; + if (!criteriaTerm.Matches(searchTerm)) continue; any = true; break; @@ -98,7 +97,6 @@ namespace osu.Game.Screens.Select.Carousel if (!match) return false; match &= criteria.CollectionBeatmapMD5Hashes?.Contains(BeatmapInfo.MD5Hash) ?? true; - if (match && criteria.RulesetCriteria != null) match &= criteria.RulesetCriteria.Matches(BeatmapInfo); diff --git a/osu.Game/Screens/Select/FilterCriteria.cs b/osu.Game/Screens/Select/FilterCriteria.cs index 2be8c71e21..3fcc8d5476 100644 --- a/osu.Game/Screens/Select/FilterCriteria.cs +++ b/osu.Game/Screens/Select/FilterCriteria.cs @@ -1,10 +1,14 @@ -#nullable enable +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +#nullable enable // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. using System; using System.Collections.Generic; using System.Linq; +using System.Text.RegularExpressions; using osu.Game.Beatmaps; using osu.Game.Collections; using osu.Game.Rulesets; @@ -38,7 +42,7 @@ namespace osu.Game.Screens.Select IsUpperInclusive = true }; - public string[] SearchTerms = Array.Empty(); + public OptionalTextFilter[] SearchTerms = Array.Empty(); public RulesetInfo? Ruleset; public bool AllowConvertedBeatmaps; @@ -56,11 +60,29 @@ namespace osu.Game.Screens.Select set { searchText = value; - SearchTerms = searchText.Split(' ', StringSplitOptions.RemoveEmptyEntries).ToArray(); + + List terms = new List(); + + string remainingText = value; + + // First handle quoted segments to ensure we keep inline spaces in exact matches. + foreach (Match quotedSegment in Regex.Matches(searchText, "(\"[^\"]+\")")) + { + terms.Add(new OptionalTextFilter { SearchTerm = quotedSegment.Value }); + remainingText = remainingText.Replace(quotedSegment.Value, string.Empty); + } + + // Then handle the rest splitting on any spaces. + terms.AddRange(remainingText.Split(' ', StringSplitOptions.RemoveEmptyEntries).Select(s => new OptionalTextFilter + { + SearchTerm = s + })); + + SearchTerms = terms.ToArray(); SearchNumber = null; - if (SearchTerms.Length == 1 && int.TryParse(SearchTerms[0], out int parsed)) + if (SearchTerms.Length == 1 && int.TryParse(SearchTerms[0].SearchTerm, out int parsed)) SearchNumber = parsed; } } @@ -120,6 +142,8 @@ namespace osu.Game.Screens.Select { public bool HasFilter => !string.IsNullOrEmpty(SearchTerm); + public bool Exact { get; private set; } + public bool Matches(string value) { if (!HasFilter) @@ -129,10 +153,23 @@ namespace osu.Game.Screens.Select if (string.IsNullOrEmpty(value)) return false; + if (Exact) + return Regex.IsMatch(value, $@"(^|\s){searchTerm}($|\s)", RegexOptions.IgnoreCase | RegexOptions.CultureInvariant); + return value.Contains(SearchTerm, StringComparison.InvariantCultureIgnoreCase); } - public string SearchTerm; + private string searchTerm; + + public string SearchTerm + { + get => searchTerm; + set + { + searchTerm = value.Trim('"'); + Exact = searchTerm != value; + } + } public bool Equals(OptionalTextFilter other) => SearchTerm == other.SearchTerm; } diff --git a/osu.Game/Screens/Select/FilterQueryParser.cs b/osu.Game/Screens/Select/FilterQueryParser.cs index c86554ddbc..474a9fdfea 100644 --- a/osu.Game/Screens/Select/FilterQueryParser.cs +++ b/osu.Game/Screens/Select/FilterQueryParser.cs @@ -161,7 +161,7 @@ namespace osu.Game.Screens.Select switch (op) { case Operator.Equal: - textFilter.SearchTerm = value.Trim('"'); + textFilter.SearchTerm = value; return true; default: From cc45ec4fff329f4b0dda00d5cf347f6518e27165 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 26 Jun 2023 18:52:05 +0200 Subject: [PATCH 036/109] Mark `IHasRecordingHandler.Recorder` nullable --- osu.Game/Rulesets/UI/IHasRecordingHandler.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/UI/IHasRecordingHandler.cs b/osu.Game/Rulesets/UI/IHasRecordingHandler.cs index 2a13272a98..f73398dd98 100644 --- a/osu.Game/Rulesets/UI/IHasRecordingHandler.cs +++ b/osu.Game/Rulesets/UI/IHasRecordingHandler.cs @@ -10,6 +10,6 @@ namespace osu.Game.Rulesets.UI /// public interface IHasRecordingHandler { - public ReplayRecorder Recorder { set; } + public ReplayRecorder? Recorder { set; } } } From dbd76c1193bf545342e39cb67f997e6561224f5b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 26 Jun 2023 19:02:49 +0200 Subject: [PATCH 037/109] Fix attempting to add key counter controller to hierarchy in multiple places --- osu.Game/Screens/Play/HUDOverlay.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Play/HUDOverlay.cs b/osu.Game/Screens/Play/HUDOverlay.cs index 9f3b7d5a93..b4f37f1df6 100644 --- a/osu.Game/Screens/Play/HUDOverlay.cs +++ b/osu.Game/Screens/Play/HUDOverlay.cs @@ -111,12 +111,14 @@ namespace osu.Game.Screens.Play RelativeSizeAxes = Axes.Both; + // intentionally not added to hierarchy here as it will be attached via `BindDrawableRuleset()`. + KeyCounter = new KeyCounterController(); + Children = new[] { CreateFailingLayer(), //Needs to be initialized before skinnable drawables. tally = new JudgementTally(), - KeyCounter = new KeyCounterController(), mainComponents = new HUDComponentsContainer { AlwaysPresent = true, }, rulesetComponents = drawableRuleset != null ? new HUDComponentsContainer(drawableRuleset.Ruleset.RulesetInfo) { AlwaysPresent = true, } From ff562e2dd7cb005a09d3443a7ac675f3739b90d3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 26 Jun 2023 19:12:49 +0200 Subject: [PATCH 038/109] Remove redundant guard In this particular case guarding with `.IsNull()` makes no sense, as the `Trigger` is supplied in constructor and cannot feasibly be null. Doing that only makes sense in scenarios where BDL / dependency injection is involved. --- osu.Game/Screens/Play/HUD/KeyCounter.cs | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/osu.Game/Screens/Play/HUD/KeyCounter.cs b/osu.Game/Screens/Play/HUD/KeyCounter.cs index 08d7e79e7c..f12d2166fc 100644 --- a/osu.Game/Screens/Play/HUD/KeyCounter.cs +++ b/osu.Game/Screens/Play/HUD/KeyCounter.cs @@ -2,7 +2,6 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Bindables; -using osu.Framework.Extensions.ObjectExtensions; using osu.Framework.Graphics.Containers; namespace osu.Game.Screens.Play.HUD @@ -49,11 +48,8 @@ namespace osu.Game.Screens.Play.HUD { base.Dispose(isDisposing); - if (Trigger.IsNotNull()) - { - Trigger.OnActivate -= Activate; - Trigger.OnDeactivate -= Deactivate; - } + Trigger.OnActivate -= Activate; + Trigger.OnDeactivate -= Deactivate; } } } From 4ac48e4cd86385c516e784580f146e593c8486aa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 26 Jun 2023 19:15:05 +0200 Subject: [PATCH 039/109] Group input handling members together --- osu.Game/Screens/Play/HUD/KeyCounterController.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Play/HUD/KeyCounterController.cs b/osu.Game/Screens/Play/HUD/KeyCounterController.cs index 93e0528e6b..e4d5968f26 100644 --- a/osu.Game/Screens/Play/HUD/KeyCounterController.cs +++ b/osu.Game/Screens/Play/HUD/KeyCounterController.cs @@ -36,8 +36,8 @@ namespace osu.Game.Screens.Play.HUD } public void AddRange(IEnumerable inputTriggers) => inputTriggers.ForEach(Add); - public override bool HandleNonPositionalInput => true; + public override bool HandleNonPositionalInput => true; public override bool HandlePositionalInput => true; } } From 8d91580dc19f34f5dc782df36236ca69e33d955e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 26 Jun 2023 19:27:42 +0200 Subject: [PATCH 040/109] Rename `{KeyCounter -> InputCount}Controller` --- osu.Game.Tests/Visual/Gameplay/TestSceneAutoplay.cs | 6 +++--- .../Visual/Gameplay/TestSceneBeatmapSkinFallbacks.cs | 2 +- .../Visual/Gameplay/TestSceneGameplayRewinding.cs | 4 ++-- osu.Game.Tests/Visual/Gameplay/TestSceneHUDOverlay.cs | 2 +- osu.Game.Tests/Visual/Gameplay/TestSceneKeyCounter.cs | 4 ++-- osu.Game.Tests/Visual/Gameplay/TestSceneReplay.cs | 2 +- .../Gameplay/TestSceneSkinEditorMultipleSkins.cs | 2 +- .../Visual/Gameplay/TestSceneSkinnableHUDOverlay.cs | 2 +- osu.Game/Rulesets/UI/DrawableRuleset.cs | 4 ++-- osu.Game/Rulesets/UI/ICanAttachHUDPieces.cs | 2 +- osu.Game/Rulesets/UI/RulesetInputManager.cs | 6 +++--- ...KeyCounterController.cs => InputCountController.cs} | 4 ++-- osu.Game/Screens/Play/HUD/KeyCounterDisplay.cs | 2 +- osu.Game/Screens/Play/HUDOverlay.cs | 10 +++++----- osu.Game/Screens/Play/Player.cs | 4 ++-- 15 files changed, 28 insertions(+), 28 deletions(-) rename osu.Game/Screens/Play/HUD/{KeyCounterController.cs => InputCountController.cs} (92%) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneAutoplay.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneAutoplay.cs index c829b73f66..3ac4d25028 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneAutoplay.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneAutoplay.cs @@ -35,14 +35,14 @@ namespace osu.Game.Tests.Visual.Gameplay var referenceBeatmap = CreateBeatmap(new OsuRuleset().RulesetInfo); AddUntilStep("score above zero", () => Player.ScoreProcessor.TotalScore.Value > 0); - AddUntilStep("key counter counted keys", () => Player.HUDOverlay.KeyCounter.Triggers.Any(kc => kc.ActivationCount.Value > 2)); + AddUntilStep("key counter counted keys", () => Player.HUDOverlay.InputCountController.Triggers.Any(kc => kc.ActivationCount.Value > 2)); seekTo(referenceBeatmap.Breaks[0].StartTime); - AddAssert("keys not counting", () => !Player.HUDOverlay.KeyCounter.IsCounting.Value); + AddAssert("keys not counting", () => !Player.HUDOverlay.InputCountController.IsCounting.Value); AddAssert("overlay displays 100% accuracy", () => Player.BreakOverlay.ChildrenOfType().Single().AccuracyDisplay.Current.Value == 1); AddStep("rewind", () => Player.GameplayClockContainer.Seek(-80000)); - AddUntilStep("key counter reset", () => Player.HUDOverlay.KeyCounter.Triggers.All(kc => kc.ActivationCount.Value == 0)); + AddUntilStep("key counter reset", () => Player.HUDOverlay.InputCountController.Triggers.All(kc => kc.ActivationCount.Value == 0)); seekTo(referenceBeatmap.HitObjects[^1].GetEndTime()); AddUntilStep("results displayed", () => getResultsScreen()?.IsLoaded == true); diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneBeatmapSkinFallbacks.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneBeatmapSkinFallbacks.cs index 56c405d81f..18a180589b 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneBeatmapSkinFallbacks.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneBeatmapSkinFallbacks.cs @@ -79,7 +79,7 @@ namespace osu.Game.Tests.Visual.Gameplay (typeof(HealthProcessor), actualComponentsContainer.Dependencies.Get()), (typeof(GameplayState), actualComponentsContainer.Dependencies.Get()), (typeof(IGameplayClock), actualComponentsContainer.Dependencies.Get()), - (typeof(KeyCounterController), actualComponentsContainer.Dependencies.Get()) + (typeof(InputCountController), actualComponentsContainer.Dependencies.Get()) }, }; diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneGameplayRewinding.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneGameplayRewinding.cs index 508cf192d3..0cb74ecde6 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneGameplayRewinding.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneGameplayRewinding.cs @@ -31,11 +31,11 @@ namespace osu.Game.Tests.Visual.Gameplay AddUntilStep("wait for track to start running", () => Beatmap.Value.Track.IsRunning); addSeekStep(3000); AddAssert("all judged", () => Player.DrawableRuleset.Playfield.AllHitObjects.All(h => h.Judged)); - AddUntilStep("key counter counted keys", () => Player.HUDOverlay.KeyCounter.Triggers.Select(kc => kc.ActivationCount.Value).Sum() == 15); + AddUntilStep("key counter counted keys", () => Player.HUDOverlay.InputCountController.Triggers.Select(kc => kc.ActivationCount.Value).Sum() == 15); AddStep("clear results", () => Player.Results.Clear()); addSeekStep(0); AddAssert("none judged", () => Player.DrawableRuleset.Playfield.AllHitObjects.All(h => !h.Judged)); - AddUntilStep("key counters reset", () => Player.HUDOverlay.KeyCounter.Triggers.All(kc => kc.ActivationCount.Value == 0)); + AddUntilStep("key counters reset", () => Player.HUDOverlay.InputCountController.Triggers.All(kc => kc.ActivationCount.Value == 0)); AddAssert("no results triggered", () => Player.Results.Count == 0); } diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneHUDOverlay.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneHUDOverlay.cs index 8cde2ab81f..7552c059a2 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneHUDOverlay.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneHUDOverlay.cs @@ -269,7 +269,7 @@ namespace osu.Game.Tests.Visual.Gameplay hudOverlay = new HUDOverlay(null, Array.Empty()); // Add any key just to display the key counter visually. - hudOverlay.KeyCounter.Add(new KeyCounterKeyboardTrigger(Key.Space)); + hudOverlay.InputCountController.Add(new KeyCounterKeyboardTrigger(Key.Space)); scoreProcessor.Combo.Value = 1; diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneKeyCounter.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneKeyCounter.cs index 786dcc873a..a2c227a76a 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneKeyCounter.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneKeyCounter.cs @@ -18,13 +18,13 @@ namespace osu.Game.Tests.Visual.Gameplay public partial class TestSceneKeyCounter : OsuManualInputManagerTestScene { [Cached] - private readonly KeyCounterController controller; + private readonly InputCountController controller; public TestSceneKeyCounter() { Children = new Drawable[] { - controller = new KeyCounterController(), + controller = new InputCountController(), new FillFlowContainer { Anchor = Anchor.Centre, diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneReplay.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneReplay.cs index 5fb9bf004f..94a25d064c 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneReplay.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneReplay.cs @@ -27,7 +27,7 @@ namespace osu.Game.Tests.Visual.Gameplay protected override void AddCheckSteps() { AddUntilStep("score above zero", () => ((ScoreAccessibleReplayPlayer)Player).ScoreProcessor.TotalScore.Value > 0); - AddUntilStep("key counter counted keys", () => ((ScoreAccessibleReplayPlayer)Player).HUDOverlay.KeyCounter.Triggers.Any(kc => kc.ActivationCount.Value > 0)); + AddUntilStep("key counter counted keys", () => ((ScoreAccessibleReplayPlayer)Player).HUDOverlay.InputCountController.Triggers.Any(kc => kc.ActivationCount.Value > 0)); AddAssert("cannot fail", () => !((ScoreAccessibleReplayPlayer)Player).AllowFail); } diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditorMultipleSkins.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditorMultipleSkins.cs index 4ae115a68d..89eb291ab0 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditorMultipleSkins.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditorMultipleSkins.cs @@ -58,7 +58,7 @@ namespace osu.Game.Tests.Visual.Gameplay }; // Add any key just to display the key counter visually. - hudOverlay.KeyCounter.Add(new KeyCounterKeyboardTrigger(Key.Space)); + hudOverlay.InputCountController.Add(new KeyCounterKeyboardTrigger(Key.Space)); scoreProcessor.Combo.Value = 1; return new Container diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableHUDOverlay.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableHUDOverlay.cs index cab52ddab5..be73e36e11 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableHUDOverlay.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableHUDOverlay.cs @@ -90,7 +90,7 @@ namespace osu.Game.Tests.Visual.Gameplay hudOverlay = new HUDOverlay(null, Array.Empty()); // Add any key just to display the key counter visually. - hudOverlay.KeyCounter.Add(new KeyCounterKeyboardTrigger(Key.Space)); + hudOverlay.InputCountController.Add(new KeyCounterKeyboardTrigger(Key.Space)); action?.Invoke(hudOverlay); diff --git a/osu.Game/Rulesets/UI/DrawableRuleset.cs b/osu.Game/Rulesets/UI/DrawableRuleset.cs index 3e390a8931..326c77e94c 100644 --- a/osu.Game/Rulesets/UI/DrawableRuleset.cs +++ b/osu.Game/Rulesets/UI/DrawableRuleset.cs @@ -335,8 +335,8 @@ namespace osu.Game.Rulesets.UI /// The representing . public abstract DrawableHitObject CreateDrawableRepresentation(TObject h); - public void Attach(KeyCounterController keyCounter) => - (KeyBindingInputManager as ICanAttachHUDPieces)?.Attach(keyCounter); + public void Attach(InputCountController inputCountController) => + (KeyBindingInputManager as ICanAttachHUDPieces)?.Attach(inputCountController); public void Attach(ClicksPerSecondCalculator calculator) => (KeyBindingInputManager as ICanAttachHUDPieces)?.Attach(calculator); diff --git a/osu.Game/Rulesets/UI/ICanAttachHUDPieces.cs b/osu.Game/Rulesets/UI/ICanAttachHUDPieces.cs index b81af8556e..1f93d25720 100644 --- a/osu.Game/Rulesets/UI/ICanAttachHUDPieces.cs +++ b/osu.Game/Rulesets/UI/ICanAttachHUDPieces.cs @@ -15,7 +15,7 @@ namespace osu.Game.Rulesets.UI /// public interface ICanAttachHUDPieces { - void Attach(KeyCounterController keyCounter); + void Attach(InputCountController inputCountController); void Attach(ClicksPerSecondCalculator calculator); } } diff --git a/osu.Game/Rulesets/UI/RulesetInputManager.cs b/osu.Game/Rulesets/UI/RulesetInputManager.cs index 9f4fabc300..2ec3985d36 100644 --- a/osu.Game/Rulesets/UI/RulesetInputManager.cs +++ b/osu.Game/Rulesets/UI/RulesetInputManager.cs @@ -160,11 +160,11 @@ namespace osu.Game.Rulesets.UI #region Key Counter Attachment - public void Attach(KeyCounterController keyCounter) + public void Attach(InputCountController inputCountController) { - KeyBindingContainer.Add(keyCounter); + KeyBindingContainer.Add(inputCountController); - keyCounter.AddRange(KeyBindingContainer.DefaultKeyBindings + inputCountController.AddRange(KeyBindingContainer.DefaultKeyBindings .Select(b => b.GetAction()) .Distinct() .OrderBy(action => action) diff --git a/osu.Game/Screens/Play/HUD/KeyCounterController.cs b/osu.Game/Screens/Play/HUD/InputCountController.cs similarity index 92% rename from osu.Game/Screens/Play/HUD/KeyCounterController.cs rename to osu.Game/Screens/Play/HUD/InputCountController.cs index e4d5968f26..47163eeb6d 100644 --- a/osu.Game/Screens/Play/HUD/KeyCounterController.cs +++ b/osu.Game/Screens/Play/HUD/InputCountController.cs @@ -13,7 +13,7 @@ namespace osu.Game.Screens.Play.HUD /// Keeps track of key press counts for a current play session, exposing bindable counts which can /// be used for display purposes. /// - public partial class KeyCounterController : CompositeComponent + public partial class InputCountController : CompositeComponent { public readonly Bindable IsCounting = new BindableBool(true); @@ -23,7 +23,7 @@ namespace osu.Game.Screens.Play.HUD public IReadOnlyList Triggers => triggers; - public KeyCounterController() + public InputCountController() { InternalChild = triggers = new Container(); } diff --git a/osu.Game/Screens/Play/HUD/KeyCounterDisplay.cs b/osu.Game/Screens/Play/HUD/KeyCounterDisplay.cs index efe51d75b0..e222099c63 100644 --- a/osu.Game/Screens/Play/HUD/KeyCounterDisplay.cs +++ b/osu.Game/Screens/Play/HUD/KeyCounterDisplay.cs @@ -34,7 +34,7 @@ namespace osu.Game.Screens.Play.HUD protected readonly Bindable ConfigVisibility = new Bindable(); [Resolved] - private KeyCounterController controller { get; set; } = null!; + private InputCountController controller { get; set; } = null!; protected abstract void UpdateVisibility(); diff --git a/osu.Game/Screens/Play/HUDOverlay.cs b/osu.Game/Screens/Play/HUDOverlay.cs index b4f37f1df6..dfc12c06d8 100644 --- a/osu.Game/Screens/Play/HUDOverlay.cs +++ b/osu.Game/Screens/Play/HUDOverlay.cs @@ -62,7 +62,7 @@ namespace osu.Game.Screens.Play private readonly ClicksPerSecondCalculator clicksPerSecondCalculator; [Cached] - public readonly KeyCounterController KeyCounter; + public readonly InputCountController InputCountController; [Cached] private readonly JudgementTally tally; @@ -112,7 +112,7 @@ namespace osu.Game.Screens.Play RelativeSizeAxes = Axes.Both; // intentionally not added to hierarchy here as it will be attached via `BindDrawableRuleset()`. - KeyCounter = new KeyCounterController(); + InputCountController = new InputCountController(); Children = new[] { @@ -307,13 +307,13 @@ namespace osu.Game.Screens.Play { PlayerSettingsOverlay.Show(); ModDisplay.FadeIn(200); - KeyCounter.Margin = new MarginPadding(10) { Bottom = 30 }; + InputCountController.Margin = new MarginPadding(10) { Bottom = 30 }; } else { PlayerSettingsOverlay.Hide(); ModDisplay.Delay(2000).FadeOut(200); - KeyCounter.Margin = new MarginPadding(10); + InputCountController.Margin = new MarginPadding(10); } updateVisibility(); @@ -323,7 +323,7 @@ namespace osu.Game.Screens.Play { if (drawableRuleset is ICanAttachHUDPieces attachTarget) { - attachTarget.Attach(KeyCounter); + attachTarget.Attach(InputCountController); attachTarget.Attach(clicksPerSecondCalculator); } diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 9fc97162bf..1c6d1fc6a5 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -432,7 +432,7 @@ namespace osu.Game.Screens.Play IsPaused = { BindTarget = GameplayClockContainer.IsPaused }, ReplayLoaded = { BindTarget = DrawableRuleset.HasReplayLoaded }, }, - KeyCounter = + InputCountController = { IsCounting = { @@ -477,7 +477,7 @@ namespace osu.Game.Screens.Play { updateGameplayState(); updatePauseOnFocusLostState(); - HUDOverlay.KeyCounter.IsCounting.Value = !isBreakTime.NewValue; + HUDOverlay.InputCountController.IsCounting.Value = !isBreakTime.NewValue; } private void updateGameplayState() From 7200855d46c4398aeca84ea2bca45fccc38736a5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 26 Jun 2023 19:30:04 +0200 Subject: [PATCH 041/109] Rename `Judgement{Tally -> CountController}` --- osu.Game.Tests/Visual/Gameplay/TestSceneJudgementCounter.cs | 6 +++--- .../{JudgementTally.cs => JudgementCountController.cs} | 2 +- .../Screens/Play/HUD/JudgementCounter/JudgementCounter.cs | 4 ++-- .../Play/HUD/JudgementCounter/JudgementCounterDisplay.cs | 6 +++--- osu.Game/Screens/Play/HUDOverlay.cs | 4 ++-- 5 files changed, 11 insertions(+), 11 deletions(-) rename osu.Game/Screens/Play/HUD/JudgementCounter/{JudgementTally.cs => JudgementCountController.cs} (96%) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneJudgementCounter.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneJudgementCounter.cs index 5a802e0d36..f117569657 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneJudgementCounter.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneJudgementCounter.cs @@ -22,7 +22,7 @@ namespace osu.Game.Tests.Visual.Gameplay public partial class TestSceneJudgementCounter : OsuTestScene { private ScoreProcessor scoreProcessor = null!; - private JudgementTally judgementTally = null!; + private JudgementCountController judgementCountController = null!; private TestJudgementCounterDisplay counterDisplay = null!; private DependencyProvidingContainer content = null!; @@ -47,11 +47,11 @@ namespace osu.Game.Tests.Visual.Gameplay CachedDependencies = new (Type, object)[] { (typeof(ScoreProcessor), scoreProcessor), (typeof(Ruleset), ruleset) }, Children = new Drawable[] { - judgementTally = new JudgementTally(), + judgementCountController = new JudgementCountController(), content = new DependencyProvidingContainer { RelativeSizeAxes = Axes.Both, - CachedDependencies = new (Type, object)[] { (typeof(JudgementTally), judgementTally) }, + CachedDependencies = new (Type, object)[] { (typeof(JudgementCountController), judgementCountController) }, } }, }; diff --git a/osu.Game/Screens/Play/HUD/JudgementCounter/JudgementTally.cs b/osu.Game/Screens/Play/HUD/JudgementCounter/JudgementCountController.cs similarity index 96% rename from osu.Game/Screens/Play/HUD/JudgementCounter/JudgementTally.cs rename to osu.Game/Screens/Play/HUD/JudgementCounter/JudgementCountController.cs index e9e3fde92a..98e74a0e7e 100644 --- a/osu.Game/Screens/Play/HUD/JudgementCounter/JudgementTally.cs +++ b/osu.Game/Screens/Play/HUD/JudgementCounter/JudgementCountController.cs @@ -16,7 +16,7 @@ namespace osu.Game.Screens.Play.HUD.JudgementCounter /// Keeps track of judgements for a current play session, exposing bindable counts which can /// be used for display purposes. /// - public partial class JudgementTally : CompositeDrawable + public partial class JudgementCountController : CompositeDrawable { [Resolved] private ScoreProcessor scoreProcessor { get; set; } = null!; diff --git a/osu.Game/Screens/Play/HUD/JudgementCounter/JudgementCounter.cs b/osu.Game/Screens/Play/HUD/JudgementCounter/JudgementCounter.cs index 7675d0cc4f..6c417faac2 100644 --- a/osu.Game/Screens/Play/HUD/JudgementCounter/JudgementCounter.cs +++ b/osu.Game/Screens/Play/HUD/JudgementCounter/JudgementCounter.cs @@ -18,9 +18,9 @@ namespace osu.Game.Screens.Play.HUD.JudgementCounter public BindableBool ShowName = new BindableBool(); public Bindable Direction = new Bindable(); - public readonly JudgementTally.JudgementCount Result; + public readonly JudgementCountController.JudgementCount Result; - public JudgementCounter(JudgementTally.JudgementCount result) => Result = result; + public JudgementCounter(JudgementCountController.JudgementCount result) => Result = result; public OsuSpriteText ResultName = null!; private FillFlowContainer flowContainer = null!; diff --git a/osu.Game/Screens/Play/HUD/JudgementCounter/JudgementCounterDisplay.cs b/osu.Game/Screens/Play/HUD/JudgementCounter/JudgementCounterDisplay.cs index a9b59a02b5..1dbee19ee3 100644 --- a/osu.Game/Screens/Play/HUD/JudgementCounter/JudgementCounterDisplay.cs +++ b/osu.Game/Screens/Play/HUD/JudgementCounter/JudgementCounterDisplay.cs @@ -34,7 +34,7 @@ namespace osu.Game.Screens.Play.HUD.JudgementCounter public BindableBool ShowMaxJudgement { get; set; } = new BindableBool(true); [Resolved] - private JudgementTally tally { get; set; } = null!; + private JudgementCountController judgementCountController { get; set; } = null!; protected FillFlowContainer CounterFlow = null!; @@ -49,7 +49,7 @@ namespace osu.Game.Screens.Play.HUD.JudgementCounter AutoSizeAxes = Axes.Both }; - foreach (var result in tally.Results) + foreach (var result in judgementCountController.Results) CounterFlow.Add(createCounter(result)); } @@ -123,7 +123,7 @@ namespace osu.Game.Screens.Play.HUD.JudgementCounter } } - private JudgementCounter createCounter(JudgementTally.JudgementCount info) => + private JudgementCounter createCounter(JudgementCountController.JudgementCount info) => new JudgementCounter(info) { State = { Value = Visibility.Hidden }, diff --git a/osu.Game/Screens/Play/HUDOverlay.cs b/osu.Game/Screens/Play/HUDOverlay.cs index dfc12c06d8..ae9c6a7d87 100644 --- a/osu.Game/Screens/Play/HUDOverlay.cs +++ b/osu.Game/Screens/Play/HUDOverlay.cs @@ -65,7 +65,7 @@ namespace osu.Game.Screens.Play public readonly InputCountController InputCountController; [Cached] - private readonly JudgementTally tally; + private readonly JudgementCountController judgementCountController; public Bindable ShowHealthBar = new Bindable(true); @@ -118,7 +118,7 @@ namespace osu.Game.Screens.Play { CreateFailingLayer(), //Needs to be initialized before skinnable drawables. - tally = new JudgementTally(), + judgementCountController = new JudgementCountController(), mainComponents = new HUDComponentsContainer { AlwaysPresent = true, }, rulesetComponents = drawableRuleset != null ? new HUDComponentsContainer(drawableRuleset.Ruleset.RulesetInfo) { AlwaysPresent = true, } From e1cbcabe0b4c68625cc4010f2e26cec228f95d85 Mon Sep 17 00:00:00 2001 From: timiimit Date: Mon, 22 May 2023 17:00:53 +0200 Subject: [PATCH 042/109] Fix skip not always triggering in multiplayer --- osu.Game/Screens/Play/Player.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 18ea9d0acb..9eec60f23a 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -378,9 +378,6 @@ namespace osu.Game.Screens.Play IsBreakTime.BindTo(breakTracker.IsBreakTime); IsBreakTime.BindValueChanged(onBreakTimeChanged, true); - if (Configuration.AutomaticallySkipIntro) - skipIntroOverlay.SkipWhenReady(); - loadLeaderboard(); } @@ -1087,6 +1084,9 @@ namespace osu.Game.Screens.Play throw new InvalidOperationException($"{nameof(StartGameplay)} should not be called when the gameplay clock is already running"); GameplayClockContainer.Reset(startClock: true); + + if (Configuration.AutomaticallySkipIntro) + skipIntroOverlay.SkipWhenReady(); } public override void OnSuspending(ScreenTransitionEvent e) From 8a7a42b7ec28fc7af008783cff236b3dbde67a98 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 26 Jun 2023 22:19:52 +0200 Subject: [PATCH 043/109] Remove weird nullable enable and double licence header --- osu.Game/Screens/Select/FilterCriteria.cs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/osu.Game/Screens/Select/FilterCriteria.cs b/osu.Game/Screens/Select/FilterCriteria.cs index 3fcc8d5476..0024c431f1 100644 --- a/osu.Game/Screens/Select/FilterCriteria.cs +++ b/osu.Game/Screens/Select/FilterCriteria.cs @@ -1,10 +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 enable -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - using System; using System.Collections.Generic; using System.Linq; From 4873aaf7ede1d7558cc551d0744dbec3637543b5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 26 Jun 2023 22:27:08 +0200 Subject: [PATCH 044/109] Add failing test case for filters potentially crashing due to invalid regex --- osu.Game.Tests/NonVisual/Filtering/FilterMatchingTest.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game.Tests/NonVisual/Filtering/FilterMatchingTest.cs b/osu.Game.Tests/NonVisual/Filtering/FilterMatchingTest.cs index 8bd9407599..d8a56ea478 100644 --- a/osu.Game.Tests/NonVisual/Filtering/FilterMatchingTest.cs +++ b/osu.Game.Tests/NonVisual/Filtering/FilterMatchingTest.cs @@ -169,6 +169,7 @@ namespace osu.Game.Tests.NonVisual.Filtering [TestCase("\"tags to\"", true)] [TestCase("\"version\"", false)] [TestCase("\"an auteur\"", true)] + [TestCase("\"\\\"", true)] // nasty case, covers properly escaping user input in underlying regex. public void TestCriteriaMatchingExactTerms(string terms, bool filtered) { var exampleBeatmapInfo = getExampleBeatmap(); From 4cb122dad46b3cff9de615b47e7220fd8488297d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 26 Jun 2023 22:27:48 +0200 Subject: [PATCH 045/109] Escape user input before embedding into regex --- osu.Game/Screens/Select/FilterCriteria.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Select/FilterCriteria.cs b/osu.Game/Screens/Select/FilterCriteria.cs index 0024c431f1..22780acfc3 100644 --- a/osu.Game/Screens/Select/FilterCriteria.cs +++ b/osu.Game/Screens/Select/FilterCriteria.cs @@ -150,7 +150,7 @@ namespace osu.Game.Screens.Select return false; if (Exact) - return Regex.IsMatch(value, $@"(^|\s){searchTerm}($|\s)", RegexOptions.IgnoreCase | RegexOptions.CultureInvariant); + return Regex.IsMatch(value, $@"(^|\s){Regex.Escape(searchTerm)}($|\s)", RegexOptions.IgnoreCase | RegexOptions.CultureInvariant); return value.Contains(SearchTerm, StringComparison.InvariantCultureIgnoreCase); } From e998be0eee006224e3212ed800901aa6237ecce3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 26 Jun 2023 22:29:20 +0200 Subject: [PATCH 046/109] Use `== true` rather than `?? false` --- osu.Game.Tests/Visual/SongSelect/TestSceneFilterControl.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneFilterControl.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneFilterControl.cs index f020cd02f7..00a0d4a849 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneFilterControl.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneFilterControl.cs @@ -210,7 +210,7 @@ namespace osu.Game.Tests.Visual.SongSelect InputManager.Click(MouseButton.Left); }); - AddAssert("collection filter still selected", () => control.CreateCriteria().CollectionBeatmapMD5Hashes?.Any() ?? false); + AddAssert("collection filter still selected", () => control.CreateCriteria().CollectionBeatmapMD5Hashes?.Any() == true); AddAssert("filter request not fired", () => !received); } From 40ceb4dfac1f978cd0e6bb33a20f9e61dcc0d40e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 26 Jun 2023 22:40:25 +0200 Subject: [PATCH 047/109] Fix incorrect indent size --- osu.Game/Rulesets/UI/RulesetInputManager.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game/Rulesets/UI/RulesetInputManager.cs b/osu.Game/Rulesets/UI/RulesetInputManager.cs index 2ec3985d36..08e180536f 100644 --- a/osu.Game/Rulesets/UI/RulesetInputManager.cs +++ b/osu.Game/Rulesets/UI/RulesetInputManager.cs @@ -165,10 +165,10 @@ namespace osu.Game.Rulesets.UI KeyBindingContainer.Add(inputCountController); inputCountController.AddRange(KeyBindingContainer.DefaultKeyBindings - .Select(b => b.GetAction()) - .Distinct() - .OrderBy(action => action) - .Select(action => new KeyCounterActionTrigger(action))); + .Select(b => b.GetAction()) + .Distinct() + .OrderBy(action => action) + .Select(action => new KeyCounterActionTrigger(action))); } #endregion From 9c87d42f2be938162544f6a3a6ac7dd0b97e605b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 26 Jun 2023 22:41:31 +0200 Subject: [PATCH 048/109] Attempt to remedy HUD overlay test failure by waiting more --- osu.Game.Tests/Visual/Gameplay/TestSceneHUDOverlay.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneHUDOverlay.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneHUDOverlay.cs index 7552c059a2..2d1af1386c 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneHUDOverlay.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneHUDOverlay.cs @@ -145,11 +145,11 @@ namespace osu.Game.Tests.Visual.Gameplay AddStep("set showhud false", () => hudOverlay.ShowHud.Value = false); AddUntilStep("hidetarget is hidden", () => hideTarget.Alpha, () => Is.LessThanOrEqualTo(0)); - AddAssert("key counters hidden", () => !keyCounterFlow.IsPresent); + AddUntilStep("key counters hidden", () => !keyCounterFlow.IsPresent); AddStep("set showhud true", () => hudOverlay.ShowHud.Value = true); AddUntilStep("hidetarget is visible", () => hideTarget.Alpha, () => Is.GreaterThan(0)); - AddAssert("key counters still hidden", () => !keyCounterFlow.IsPresent); + AddUntilStep("key counters still hidden", () => !keyCounterFlow.IsPresent); } [Test] From 702266198b011734a5b679f08d079d4e352331d9 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 27 Jun 2023 15:21:13 +0900 Subject: [PATCH 049/109] Add missing "title=" search support at song select --- .../NonVisual/Filtering/FilterMatchingTest.cs | 23 ++++++++++++++++++- .../Select/Carousel/CarouselBeatmap.cs | 2 ++ osu.Game/Screens/Select/FilterCriteria.cs | 1 + osu.Game/Screens/Select/FilterQueryParser.cs | 3 +++ 4 files changed, 28 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/NonVisual/Filtering/FilterMatchingTest.cs b/osu.Game.Tests/NonVisual/Filtering/FilterMatchingTest.cs index d8a56ea478..fe5511add1 100644 --- a/osu.Game.Tests/NonVisual/Filtering/FilterMatchingTest.cs +++ b/osu.Game.Tests/NonVisual/Filtering/FilterMatchingTest.cs @@ -33,7 +33,7 @@ namespace osu.Game.Tests.NonVisual.Filtering Artist = "The Artist", ArtistUnicode = "check unicode too", Title = "Title goes here", - TitleUnicode = "Title goes here", + TitleUnicode = "TitleUnicode goes here", Author = { Username = "The Author" }, Source = "unit tests", Tags = "look for tags too", @@ -204,6 +204,27 @@ namespace osu.Game.Tests.NonVisual.Filtering Assert.AreEqual(filtered, carouselItem.Filtered.Value); } + [Test] + [TestCase("", false)] + [TestCase("Goes", false)] + [TestCase("GOES", false)] + [TestCase("goes", false)] + [TestCase("title goes", false)] + [TestCase("title goes AND then something else", true)] + [TestCase("titleunicode", false)] + [TestCase("unknown", true)] + public void TestCriteriaMatchingTitle(string titleName, bool filtered) + { + var exampleBeatmapInfo = getExampleBeatmap(); + var criteria = new FilterCriteria + { + Title = new FilterCriteria.OptionalTextFilter { SearchTerm = titleName } + }; + var carouselItem = new CarouselBeatmap(exampleBeatmapInfo); + carouselItem.Filter(criteria); + Assert.AreEqual(filtered, carouselItem.Filtered.Value); + } + [Test] [TestCase("", false)] [TestCase("The", false)] diff --git a/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs b/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs index 18931c462f..6917bd1da2 100644 --- a/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs +++ b/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs @@ -57,6 +57,8 @@ namespace osu.Game.Screens.Select.Carousel match &= !criteria.Creator.HasFilter || criteria.Creator.Matches(BeatmapInfo.Metadata.Author.Username); match &= !criteria.Artist.HasFilter || criteria.Artist.Matches(BeatmapInfo.Metadata.Artist) || criteria.Artist.Matches(BeatmapInfo.Metadata.ArtistUnicode); + match &= !criteria.Title.HasFilter || criteria.Title.Matches(BeatmapInfo.Metadata.Title) || + criteria.Title.Matches(BeatmapInfo.Metadata.TitleUnicode); match &= !criteria.UserStarDifficulty.HasFilter || criteria.UserStarDifficulty.IsInRange(BeatmapInfo.StarRating); diff --git a/osu.Game/Screens/Select/FilterCriteria.cs b/osu.Game/Screens/Select/FilterCriteria.cs index 22780acfc3..680da21dbc 100644 --- a/osu.Game/Screens/Select/FilterCriteria.cs +++ b/osu.Game/Screens/Select/FilterCriteria.cs @@ -31,6 +31,7 @@ namespace osu.Game.Screens.Select public OptionalRange OnlineStatus; public OptionalTextFilter Creator; public OptionalTextFilter Artist; + public OptionalTextFilter Title; public OptionalRange UserStarDifficulty = new OptionalRange { diff --git a/osu.Game/Screens/Select/FilterQueryParser.cs b/osu.Game/Screens/Select/FilterQueryParser.cs index 474a9fdfea..20a5d5d1a6 100644 --- a/osu.Game/Screens/Select/FilterQueryParser.cs +++ b/osu.Game/Screens/Select/FilterQueryParser.cs @@ -73,6 +73,9 @@ namespace osu.Game.Screens.Select case "artist": return TryUpdateCriteriaText(ref criteria.Artist, op, value); + case "title": + return TryUpdateCriteriaText(ref criteria.Title, op, value); + default: return criteria.RulesetCriteria?.TryParseCustomKeywordCriteria(key, op, value) ?? false; } From c423f77d535b33a07d5930ea73499d0e621069f9 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 27 Jun 2023 15:34:33 +0900 Subject: [PATCH 050/109] Add support for matching full terms using suffixed `!` --- osu.Game/Screens/Select/FilterCriteria.cs | 53 +++++++++++++++++--- osu.Game/Screens/Select/FilterQueryParser.cs | 2 +- 2 files changed, 47 insertions(+), 8 deletions(-) diff --git a/osu.Game/Screens/Select/FilterCriteria.cs b/osu.Game/Screens/Select/FilterCriteria.cs index 22780acfc3..c8804528c7 100644 --- a/osu.Game/Screens/Select/FilterCriteria.cs +++ b/osu.Game/Screens/Select/FilterCriteria.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.Globalization; using System.Linq; using System.Text.RegularExpressions; using osu.Game.Beatmaps; @@ -62,7 +63,7 @@ namespace osu.Game.Screens.Select string remainingText = value; // First handle quoted segments to ensure we keep inline spaces in exact matches. - foreach (Match quotedSegment in Regex.Matches(searchText, "(\"[^\"]+\")")) + foreach (Match quotedSegment in Regex.Matches(searchText, "(\"[^\"]+\"[!]?)")) { terms.Add(new OptionalTextFilter { SearchTerm = quotedSegment.Value }); remainingText = remainingText.Replace(quotedSegment.Value, string.Empty); @@ -138,7 +139,7 @@ namespace osu.Game.Screens.Select { public bool HasFilter => !string.IsNullOrEmpty(SearchTerm); - public bool Exact { get; private set; } + public MatchMode MatchMode { get; private set; } public bool Matches(string value) { @@ -149,10 +150,18 @@ namespace osu.Game.Screens.Select if (string.IsNullOrEmpty(value)) return false; - if (Exact) - return Regex.IsMatch(value, $@"(^|\s){Regex.Escape(searchTerm)}($|\s)", RegexOptions.IgnoreCase | RegexOptions.CultureInvariant); + switch (MatchMode) + { + default: + case MatchMode.None: + return value.Contains(SearchTerm, StringComparison.InvariantCultureIgnoreCase); - return value.Contains(SearchTerm, StringComparison.InvariantCultureIgnoreCase); + case MatchMode.IsolatedPhrase: + return Regex.IsMatch(value, $@"(^|\s){Regex.Escape(searchTerm)}($|\s)", RegexOptions.IgnoreCase | RegexOptions.CultureInvariant); + + case MatchMode.FullPhrase: + return CultureInfo.InvariantCulture.CompareInfo.Compare(value, searchTerm, CompareOptions.IgnoreCase) == 0; + } } private string searchTerm; @@ -162,12 +171,42 @@ namespace osu.Game.Screens.Select get => searchTerm; set { - searchTerm = value.Trim('"'); - Exact = searchTerm != value; + searchTerm = value; + + if (searchTerm.EndsWith("\"!", StringComparison.Ordinal)) + { + searchTerm = searchTerm.Trim('!', '\"'); + MatchMode = MatchMode.FullPhrase; + } + else if (searchTerm.StartsWith('\"')) + { + searchTerm = searchTerm.Trim('\"'); + MatchMode = MatchMode.IsolatedPhrase; + } + else + MatchMode = MatchMode.None; } } public bool Equals(OptionalTextFilter other) => SearchTerm == other.SearchTerm; } + + public enum MatchMode + { + /// + /// Match using a simple "contains" substring match. + /// + None, + + /// + /// Match for the search phrase being isolated by spaces, or at the start or end of the text. + /// + IsolatedPhrase, + + /// + /// Match for the search phrase matching the full text in completion. + /// + FullPhrase, + } } } diff --git a/osu.Game/Screens/Select/FilterQueryParser.cs b/osu.Game/Screens/Select/FilterQueryParser.cs index 474a9fdfea..4bc4448291 100644 --- a/osu.Game/Screens/Select/FilterQueryParser.cs +++ b/osu.Game/Screens/Select/FilterQueryParser.cs @@ -16,7 +16,7 @@ namespace osu.Game.Screens.Select public static class FilterQueryParser { private static readonly Regex query_syntax_regex = new Regex( - @"\b(?\w+)(?(:|=|(>|<)(:|=)?))(?("".*"")|(\S*))", + @"\b(?\w+)(?(:|=|(>|<)(:|=)?))(?("".*""[!]?)|(\S*))", RegexOptions.Compiled | RegexOptions.IgnoreCase); internal static void ApplyQueries(FilterCriteria criteria, string query) From 19ec6d5455bdd68c01fee93ccce24b6bc5796640 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 27 Jun 2023 15:44:58 +0900 Subject: [PATCH 051/109] Add test coverage of new matching mode --- .../NonVisual/Filtering/FilterMatchingTest.cs | 4 +++ .../Filtering/FilterQueryParserTest.cs | 30 +++++++++++++------ 2 files changed, 25 insertions(+), 9 deletions(-) diff --git a/osu.Game.Tests/NonVisual/Filtering/FilterMatchingTest.cs b/osu.Game.Tests/NonVisual/Filtering/FilterMatchingTest.cs index d8a56ea478..89c3009950 100644 --- a/osu.Game.Tests/NonVisual/Filtering/FilterMatchingTest.cs +++ b/osu.Game.Tests/NonVisual/Filtering/FilterMatchingTest.cs @@ -169,6 +169,8 @@ namespace osu.Game.Tests.NonVisual.Filtering [TestCase("\"tags to\"", true)] [TestCase("\"version\"", false)] [TestCase("\"an auteur\"", true)] + [TestCase("\"Artist\"!", true)] + [TestCase("\"The Artist\"!", false)] [TestCase("\"\\\"", true)] // nasty case, covers properly escaping user input in underlying regex. public void TestCriteriaMatchingExactTerms(string terms, bool filtered) { @@ -213,6 +215,8 @@ namespace osu.Game.Tests.NonVisual.Filtering [TestCase("the artist AND then something else", true)] [TestCase("unicode too", false)] [TestCase("unknown", true)] + [TestCase("\"Artist\"!", true)] + [TestCase("\"The Artist\"!", false)] public void TestCriteriaMatchingArtist(string artistName, bool filtered) { var exampleBeatmapInfo = getExampleBeatmap(); diff --git a/osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs b/osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs index 46f91f9a67..7bf23f1a2e 100644 --- a/osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs +++ b/osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs @@ -26,26 +26,26 @@ namespace osu.Game.Tests.NonVisual.Filtering [Test] public void TestApplyQueriesBareWordsWithExactMatch() { - const string query = "looking for \"a beatmap\" like \"this\""; + const string query = "looking for \"a beatmap\"! like \"this\""; var filterCriteria = new FilterCriteria(); FilterQueryParser.ApplyQueries(filterCriteria, query); - Assert.AreEqual("looking for \"a beatmap\" like \"this\"", filterCriteria.SearchText); + Assert.AreEqual("looking for \"a beatmap\"! like \"this\"", filterCriteria.SearchText); Assert.AreEqual(5, filterCriteria.SearchTerms.Length); Assert.That(filterCriteria.SearchTerms[0].SearchTerm, Is.EqualTo("a beatmap")); - Assert.That(filterCriteria.SearchTerms[0].Exact, Is.True); + Assert.That(filterCriteria.SearchTerms[0].MatchMode, Is.EqualTo(FilterCriteria.MatchMode.FullPhrase)); Assert.That(filterCriteria.SearchTerms[1].SearchTerm, Is.EqualTo("this")); - Assert.That(filterCriteria.SearchTerms[1].Exact, Is.True); + Assert.That(filterCriteria.SearchTerms[1].MatchMode, Is.EqualTo(FilterCriteria.MatchMode.IsolatedPhrase)); Assert.That(filterCriteria.SearchTerms[2].SearchTerm, Is.EqualTo("looking")); - Assert.That(filterCriteria.SearchTerms[2].Exact, Is.False); + Assert.That(filterCriteria.SearchTerms[2].MatchMode, Is.EqualTo(FilterCriteria.MatchMode.None)); Assert.That(filterCriteria.SearchTerms[3].SearchTerm, Is.EqualTo("for")); - Assert.That(filterCriteria.SearchTerms[3].Exact, Is.False); + Assert.That(filterCriteria.SearchTerms[3].MatchMode, Is.EqualTo(FilterCriteria.MatchMode.None)); Assert.That(filterCriteria.SearchTerms[4].SearchTerm, Is.EqualTo("like")); - Assert.That(filterCriteria.SearchTerms[4].Exact, Is.False); + Assert.That(filterCriteria.SearchTerms[4].MatchMode, Is.EqualTo(FilterCriteria.MatchMode.None)); } /* @@ -260,7 +260,7 @@ namespace osu.Game.Tests.NonVisual.Filtering Assert.AreEqual("find me songs by please", filterCriteria.SearchText.Trim()); Assert.AreEqual(5, filterCriteria.SearchTerms.Length); Assert.AreEqual("singer", filterCriteria.Artist.SearchTerm); - Assert.That(filterCriteria.Artist.Exact, Is.False); + Assert.That(filterCriteria.Artist.MatchMode, Is.EqualTo(FilterCriteria.MatchMode.None)); } [Test] @@ -272,7 +272,19 @@ namespace osu.Game.Tests.NonVisual.Filtering Assert.AreEqual("really like yes", filterCriteria.SearchText.Trim()); Assert.AreEqual(3, filterCriteria.SearchTerms.Length); Assert.AreEqual("name with space", filterCriteria.Artist.SearchTerm); - Assert.That(filterCriteria.Artist.Exact, Is.True); + Assert.That(filterCriteria.Artist.MatchMode, Is.EqualTo(FilterCriteria.MatchMode.IsolatedPhrase)); + } + + [Test] + public void TestApplyArtistQueriesWithSpacesFullPhrase() + { + const string query = "artist=\"The Only One\"!"; + var filterCriteria = new FilterCriteria(); + FilterQueryParser.ApplyQueries(filterCriteria, query); + Assert.That(filterCriteria.SearchText.Trim(), Is.Empty); + Assert.AreEqual(0, filterCriteria.SearchTerms.Length); + Assert.AreEqual("The Only One", filterCriteria.Artist.SearchTerm); + Assert.That(filterCriteria.Artist.MatchMode, Is.EqualTo(FilterCriteria.MatchMode.FullPhrase)); } [Test] From e99de0eb5db05f2caf841b96672e664fea947d53 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 27 Jun 2023 16:04:34 +0900 Subject: [PATCH 052/109] Add safety to tests to ensure loaded --- osu.Game.Tests/Visual/Gameplay/TestSceneHUDOverlay.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneHUDOverlay.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneHUDOverlay.cs index 2d1af1386c..74249007e4 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneHUDOverlay.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneHUDOverlay.cs @@ -236,7 +236,6 @@ namespace osu.Game.Tests.Visual.Gameplay createNew(); - AddUntilStep("wait for hud load", () => hudOverlay.IsLoaded); AddUntilStep("wait for components to be hidden", () => hudOverlay.ChildrenOfType().Single().Alpha == 0); AddUntilStep("wait for hud load", () => hudOverlay.ChildrenOfType().All(c => c.ComponentsLoaded)); @@ -255,7 +254,6 @@ namespace osu.Game.Tests.Visual.Gameplay createNew(); - AddUntilStep("wait for hud load", () => hudOverlay.IsLoaded); AddUntilStep("wait for components to be hidden", () => hudOverlay.ChildrenOfType().Single().Alpha == 0); AddStep("reload components", () => hudOverlay.ChildrenOfType().Single().Reload()); @@ -277,6 +275,9 @@ namespace osu.Game.Tests.Visual.Gameplay Child = hudOverlay; }); + + AddUntilStep("wait for hud load", () => hudOverlay.IsLoaded); + AddUntilStep("wait for components present", () => hudOverlay.ChildrenOfType().FirstOrDefault() != null); } protected override void Dispose(bool isDisposing) From e21583ff1b642e404bd604d4dd045fd7d23cc2ae Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 27 Jun 2023 16:35:59 +0900 Subject: [PATCH 053/109] Refactor `InputCountController` to not require being added to foreign body via `Attach` I've made the flow match `ClicksPerSecondCalculator` as close as possible. Hopefully this reads better. --- .../Visual/Gameplay/TestSceneKeyCounter.cs | 7 +++- osu.Game/Rulesets/UI/RulesetInputManager.cs | 21 +++++----- .../Screens/Play/HUD/InputCountController.cs | 17 +++----- .../Screens/Play/HUD/KeyCounterDisplay.cs | 39 +++++-------------- osu.Game/Screens/Play/HUDOverlay.cs | 4 +- 5 files changed, 29 insertions(+), 59 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneKeyCounter.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneKeyCounter.cs index a2c227a76a..3df0f18558 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneKeyCounter.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneKeyCounter.cs @@ -48,13 +48,16 @@ namespace osu.Game.Tests.Visual.Gameplay } }; - controller.AddRange(new InputTrigger[] + var inputTriggers = new InputTrigger[] { new KeyCounterKeyboardTrigger(Key.X), new KeyCounterKeyboardTrigger(Key.X), new KeyCounterMouseTrigger(MouseButton.Left), new KeyCounterMouseTrigger(MouseButton.Right), - }); + }; + + AddRange(inputTriggers); + controller.AddRange(inputTriggers); AddStep("Add random", () => { diff --git a/osu.Game/Rulesets/UI/RulesetInputManager.cs b/osu.Game/Rulesets/UI/RulesetInputManager.cs index 08e180536f..9be210f4b2 100644 --- a/osu.Game/Rulesets/UI/RulesetInputManager.cs +++ b/osu.Game/Rulesets/UI/RulesetInputManager.cs @@ -162,25 +162,22 @@ namespace osu.Game.Rulesets.UI public void Attach(InputCountController inputCountController) { - KeyBindingContainer.Add(inputCountController); + var triggers = KeyBindingContainer.DefaultKeyBindings + .Select(b => b.GetAction()) + .Distinct() + .OrderBy(action => action) + .Select(action => new KeyCounterActionTrigger(action)) + .ToArray(); - inputCountController.AddRange(KeyBindingContainer.DefaultKeyBindings - .Select(b => b.GetAction()) - .Distinct() - .OrderBy(action => action) - .Select(action => new KeyCounterActionTrigger(action))); + KeyBindingContainer.AddRange(triggers); + inputCountController.AddRange(triggers); } #endregion #region Keys per second Counter Attachment - public void Attach(ClicksPerSecondCalculator calculator) - { - var listener = new ActionListener(calculator); - - KeyBindingContainer.Add(listener); - } + public void Attach(ClicksPerSecondCalculator calculator) => KeyBindingContainer.Add(new ActionListener(calculator)); private partial class ActionListener : Component, IKeyBindingHandler { diff --git a/osu.Game/Screens/Play/HUD/InputCountController.cs b/osu.Game/Screens/Play/HUD/InputCountController.cs index 47163eeb6d..4827f2315f 100644 --- a/osu.Game/Screens/Play/HUD/InputCountController.cs +++ b/osu.Game/Screens/Play/HUD/InputCountController.cs @@ -1,7 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using System; using System.Collections.Generic; using osu.Framework.Bindables; using osu.Framework.Extensions.IEnumerableExtensions; @@ -17,26 +16,20 @@ namespace osu.Game.Screens.Play.HUD { public readonly Bindable IsCounting = new BindableBool(true); - public event Action? OnNewTrigger; + private readonly BindableList triggers = new BindableList(); - private readonly Container triggers; + public IBindableList Triggers => triggers; - public IReadOnlyList Triggers => triggers; - - public InputCountController() - { - InternalChild = triggers = new Container(); - } + public void AddRange(IEnumerable triggers) => triggers.ForEach(Add); public void Add(InputTrigger trigger) { + // Note that these triggers are not added to the hierarchy here. It is presumed they are added externally at a + // more correct location (ie. inside a RulesetInputManager). triggers.Add(trigger); trigger.IsCounting.BindTo(IsCounting); - OnNewTrigger?.Invoke(trigger); } - public void AddRange(IEnumerable inputTriggers) => inputTriggers.ForEach(Add); - public override bool HandleNonPositionalInput => true; public override bool HandlePositionalInput => true; } diff --git a/osu.Game/Screens/Play/HUD/KeyCounterDisplay.cs b/osu.Game/Screens/Play/HUD/KeyCounterDisplay.cs index e222099c63..e7e866932e 100644 --- a/osu.Game/Screens/Play/HUD/KeyCounterDisplay.cs +++ b/osu.Game/Screens/Play/HUD/KeyCounterDisplay.cs @@ -1,11 +1,9 @@ // 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 System.Collections.Specialized; using osu.Framework.Allocation; using osu.Framework.Bindables; -using osu.Framework.Extensions.IEnumerableExtensions; -using osu.Framework.Extensions.ObjectExtensions; using osu.Framework.Graphics.Containers; using osu.Game.Configuration; using osu.Game.Rulesets.UI; @@ -24,35 +22,17 @@ namespace osu.Game.Screens.Play.HUD /// public Bindable AlwaysVisible { get; } = new Bindable(true); - /// - /// The s contained in this . - /// - public IEnumerable Counters => KeyFlow; - protected abstract FillFlowContainer KeyFlow { get; } protected readonly Bindable ConfigVisibility = new Bindable(); + private readonly IBindableList triggers = new BindableList(); + [Resolved] private InputCountController controller { get; set; } = null!; protected abstract void UpdateVisibility(); - /// - /// Add a to this display. - /// - public void Add(InputTrigger trigger) - { - var keyCounter = CreateCounter(trigger); - - KeyFlow.Add(keyCounter); - } - - /// - /// Add a range of to this display. - /// - public void AddRange(IEnumerable triggers) => triggers.ForEach(Add); - protected abstract KeyCounter CreateCounter(InputTrigger trigger); [BackgroundDependencyLoader] @@ -68,19 +48,18 @@ namespace osu.Game.Screens.Play.HUD { base.LoadComplete(); - controller.OnNewTrigger += Add; - AddRange(controller.Triggers); + triggers.BindTo(controller.Triggers); + triggers.BindCollectionChanged(triggersChanged, true); AlwaysVisible.BindValueChanged(_ => UpdateVisibility()); ConfigVisibility.BindValueChanged(_ => UpdateVisibility(), true); } - protected override void Dispose(bool isDisposing) + private void triggersChanged(object? sender, NotifyCollectionChangedEventArgs e) { - base.Dispose(isDisposing); - - if (controller.IsNotNull()) - controller.OnNewTrigger -= Add; + KeyFlow.Clear(); + foreach (var trigger in controller.Triggers) + KeyFlow.Add(CreateCounter(trigger)); } public bool UsesFixedAnchor { get; set; } diff --git a/osu.Game/Screens/Play/HUDOverlay.cs b/osu.Game/Screens/Play/HUDOverlay.cs index ae9c6a7d87..278c7b9de2 100644 --- a/osu.Game/Screens/Play/HUDOverlay.cs +++ b/osu.Game/Screens/Play/HUDOverlay.cs @@ -111,9 +111,6 @@ namespace osu.Game.Screens.Play RelativeSizeAxes = Axes.Both; - // intentionally not added to hierarchy here as it will be attached via `BindDrawableRuleset()`. - InputCountController = new InputCountController(); - Children = new[] { CreateFailingLayer(), @@ -161,6 +158,7 @@ namespace osu.Game.Screens.Play Spacing = new Vector2(5) }, clicksPerSecondCalculator = new ClicksPerSecondCalculator(), + InputCountController = new InputCountController(), }; hideTargets = new List { mainComponents, rulesetComponents, topRightElements }; From 113b570bd4473341a695c9848fb1b93f2a6572e1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 27 Jun 2023 16:37:22 +0900 Subject: [PATCH 054/109] Move controllers above skinnable elements in initialisation order --- osu.Game/Screens/Play/HUDOverlay.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Play/HUDOverlay.cs b/osu.Game/Screens/Play/HUDOverlay.cs index 278c7b9de2..339f1483bc 100644 --- a/osu.Game/Screens/Play/HUDOverlay.cs +++ b/osu.Game/Screens/Play/HUDOverlay.cs @@ -116,6 +116,8 @@ namespace osu.Game.Screens.Play CreateFailingLayer(), //Needs to be initialized before skinnable drawables. judgementCountController = new JudgementCountController(), + clicksPerSecondCalculator = new ClicksPerSecondCalculator(), + InputCountController = new InputCountController(), mainComponents = new HUDComponentsContainer { AlwaysPresent = true, }, rulesetComponents = drawableRuleset != null ? new HUDComponentsContainer(drawableRuleset.Ruleset.RulesetInfo) { AlwaysPresent = true, } @@ -157,8 +159,6 @@ namespace osu.Game.Screens.Play Padding = new MarginPadding(44), // enough margin to avoid the hit error display Spacing = new Vector2(5) }, - clicksPerSecondCalculator = new ClicksPerSecondCalculator(), - InputCountController = new InputCountController(), }; hideTargets = new List { mainComponents, rulesetComponents, topRightElements }; From de23a4691ed82b766f5e3b49f01639410f4d85b1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 27 Jun 2023 16:38:15 +0900 Subject: [PATCH 055/109] Change `JudgementCountController` to a `Component` --- .../Play/HUD/JudgementCounter/JudgementCountController.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Play/HUD/JudgementCounter/JudgementCountController.cs b/osu.Game/Screens/Play/HUD/JudgementCounter/JudgementCountController.cs index 98e74a0e7e..43c2ae442a 100644 --- a/osu.Game/Screens/Play/HUD/JudgementCounter/JudgementCountController.cs +++ b/osu.Game/Screens/Play/HUD/JudgementCounter/JudgementCountController.cs @@ -5,7 +5,7 @@ using System.Collections.Generic; using System.Linq; using osu.Framework.Allocation; using osu.Framework.Bindables; -using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics; using osu.Game.Rulesets; using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Scoring; @@ -16,7 +16,7 @@ namespace osu.Game.Screens.Play.HUD.JudgementCounter /// Keeps track of judgements for a current play session, exposing bindable counts which can /// be used for display purposes. /// - public partial class JudgementCountController : CompositeDrawable + public partial class JudgementCountController : Component { [Resolved] private ScoreProcessor scoreProcessor { get; set; } = null!; From 8bd6f7a46a745da8138bdbae0a889fdfb66c6d1a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 27 Jun 2023 16:38:46 +0900 Subject: [PATCH 056/109] Rename `ClicksPerSecondCalculator` to `ClicksPerSecondController` --- .../Gameplay/TestSceneClicksPerSecondCalculator.cs | 10 +++++----- osu.Game/Rulesets/UI/DrawableRuleset.cs | 4 ++-- osu.Game/Rulesets/UI/ICanAttachHUDPieces.cs | 2 +- osu.Game/Rulesets/UI/RulesetInputManager.cs | 10 +++++----- ...econdCalculator.cs => ClicksPerSecondController.cs} | 4 ++-- .../Play/HUD/ClicksPerSecond/ClicksPerSecondCounter.cs | 4 ++-- osu.Game/Screens/Play/HUDOverlay.cs | 6 +++--- 7 files changed, 20 insertions(+), 20 deletions(-) rename osu.Game/Screens/Play/HUD/ClicksPerSecond/{ClicksPerSecondCalculator.cs => ClicksPerSecondController.cs} (93%) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneClicksPerSecondCalculator.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneClicksPerSecondCalculator.cs index 6b8e0e1088..bcb5291108 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneClicksPerSecondCalculator.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneClicksPerSecondCalculator.cs @@ -17,7 +17,7 @@ namespace osu.Game.Tests.Visual.Gameplay { public partial class TestSceneClicksPerSecondCalculator : OsuTestScene { - private ClicksPerSecondCalculator calculator = null!; + private ClicksPerSecondController controller = null!; private TestGameplayClock manualGameplayClock = null!; @@ -34,11 +34,11 @@ namespace osu.Game.Tests.Visual.Gameplay CachedDependencies = new (Type, object)[] { (typeof(IGameplayClock), manualGameplayClock) }, Children = new Drawable[] { - calculator = new ClicksPerSecondCalculator(), + controller = new ClicksPerSecondController(), new DependencyProvidingContainer { RelativeSizeAxes = Axes.Both, - CachedDependencies = new (Type, object)[] { (typeof(ClicksPerSecondCalculator), calculator) }, + CachedDependencies = new (Type, object)[] { (typeof(ClicksPerSecondController), controller) }, Child = new ClicksPerSecondCounter { Anchor = Anchor.Centre, @@ -81,7 +81,7 @@ namespace osu.Game.Tests.Visual.Gameplay checkClicksPerSecondValue(6); } - private void checkClicksPerSecondValue(int i) => AddAssert("clicks/s is correct", () => calculator.Value, () => Is.EqualTo(i)); + private void checkClicksPerSecondValue(int i) => AddAssert("clicks/s is correct", () => controller.Value, () => Is.EqualTo(i)); private void seekClockImmediately(double time) => manualGameplayClock.CurrentTime = time; @@ -94,7 +94,7 @@ namespace osu.Game.Tests.Visual.Gameplay foreach (double timestamp in inputs) { seekClockImmediately(timestamp); - calculator.AddInputTimestamp(); + controller.AddInputTimestamp(); } seekClockImmediately(baseTime); diff --git a/osu.Game/Rulesets/UI/DrawableRuleset.cs b/osu.Game/Rulesets/UI/DrawableRuleset.cs index 326c77e94c..4aeb3d4862 100644 --- a/osu.Game/Rulesets/UI/DrawableRuleset.cs +++ b/osu.Game/Rulesets/UI/DrawableRuleset.cs @@ -338,8 +338,8 @@ namespace osu.Game.Rulesets.UI public void Attach(InputCountController inputCountController) => (KeyBindingInputManager as ICanAttachHUDPieces)?.Attach(inputCountController); - public void Attach(ClicksPerSecondCalculator calculator) => - (KeyBindingInputManager as ICanAttachHUDPieces)?.Attach(calculator); + public void Attach(ClicksPerSecondController controller) => + (KeyBindingInputManager as ICanAttachHUDPieces)?.Attach(controller); /// /// Creates a key conversion input manager. An exception will be thrown if a valid is not returned. diff --git a/osu.Game/Rulesets/UI/ICanAttachHUDPieces.cs b/osu.Game/Rulesets/UI/ICanAttachHUDPieces.cs index 1f93d25720..276881d17a 100644 --- a/osu.Game/Rulesets/UI/ICanAttachHUDPieces.cs +++ b/osu.Game/Rulesets/UI/ICanAttachHUDPieces.cs @@ -16,6 +16,6 @@ namespace osu.Game.Rulesets.UI public interface ICanAttachHUDPieces { void Attach(InputCountController inputCountController); - void Attach(ClicksPerSecondCalculator calculator); + void Attach(ClicksPerSecondController controller); } } diff --git a/osu.Game/Rulesets/UI/RulesetInputManager.cs b/osu.Game/Rulesets/UI/RulesetInputManager.cs index 9be210f4b2..26b9d06f73 100644 --- a/osu.Game/Rulesets/UI/RulesetInputManager.cs +++ b/osu.Game/Rulesets/UI/RulesetInputManager.cs @@ -177,20 +177,20 @@ namespace osu.Game.Rulesets.UI #region Keys per second Counter Attachment - public void Attach(ClicksPerSecondCalculator calculator) => KeyBindingContainer.Add(new ActionListener(calculator)); + public void Attach(ClicksPerSecondController controller) => KeyBindingContainer.Add(new ActionListener(controller)); private partial class ActionListener : Component, IKeyBindingHandler { - private readonly ClicksPerSecondCalculator calculator; + private readonly ClicksPerSecondController controller; - public ActionListener(ClicksPerSecondCalculator calculator) + public ActionListener(ClicksPerSecondController controller) { - this.calculator = calculator; + this.controller = controller; } public bool OnPressed(KeyBindingPressEvent e) { - calculator.AddInputTimestamp(); + controller.AddInputTimestamp(); return false; } diff --git a/osu.Game/Screens/Play/HUD/ClicksPerSecond/ClicksPerSecondCalculator.cs b/osu.Game/Screens/Play/HUD/ClicksPerSecond/ClicksPerSecondController.cs similarity index 93% rename from osu.Game/Screens/Play/HUD/ClicksPerSecond/ClicksPerSecondCalculator.cs rename to osu.Game/Screens/Play/HUD/ClicksPerSecond/ClicksPerSecondController.cs index ba0c47dc8b..f2dd20cc8e 100644 --- a/osu.Game/Screens/Play/HUD/ClicksPerSecond/ClicksPerSecondCalculator.cs +++ b/osu.Game/Screens/Play/HUD/ClicksPerSecond/ClicksPerSecondController.cs @@ -8,7 +8,7 @@ using osu.Game.Rulesets.UI; namespace osu.Game.Screens.Play.HUD.ClicksPerSecond { - public partial class ClicksPerSecondCalculator : Component + public partial class ClicksPerSecondController : Component { private readonly List timestamps = new List(); @@ -22,7 +22,7 @@ namespace osu.Game.Screens.Play.HUD.ClicksPerSecond private IGameplayClock clock => frameStableClock ?? gameplayClock; - public ClicksPerSecondCalculator() + public ClicksPerSecondController() { RelativeSizeAxes = Axes.Both; } diff --git a/osu.Game/Screens/Play/HUD/ClicksPerSecond/ClicksPerSecondCounter.cs b/osu.Game/Screens/Play/HUD/ClicksPerSecond/ClicksPerSecondCounter.cs index 1aa7c5e091..9b5ea309b0 100644 --- a/osu.Game/Screens/Play/HUD/ClicksPerSecond/ClicksPerSecondCounter.cs +++ b/osu.Game/Screens/Play/HUD/ClicksPerSecond/ClicksPerSecondCounter.cs @@ -17,7 +17,7 @@ namespace osu.Game.Screens.Play.HUD.ClicksPerSecond public partial class ClicksPerSecondCounter : RollingCounter, ISerialisableDrawable { [Resolved] - private ClicksPerSecondCalculator calculator { get; set; } = null!; + private ClicksPerSecondController controller { get; set; } = null!; protected override double RollingDuration => 350; @@ -38,7 +38,7 @@ namespace osu.Game.Screens.Play.HUD.ClicksPerSecond { base.Update(); - Current.Value = calculator.Value; + Current.Value = controller.Value; } protected override IHasText CreateText() => new TextComponent(); diff --git a/osu.Game/Screens/Play/HUDOverlay.cs b/osu.Game/Screens/Play/HUDOverlay.cs index 339f1483bc..f0a2975958 100644 --- a/osu.Game/Screens/Play/HUDOverlay.cs +++ b/osu.Game/Screens/Play/HUDOverlay.cs @@ -59,7 +59,7 @@ namespace osu.Game.Screens.Play public readonly PlayerSettingsOverlay PlayerSettingsOverlay; [Cached] - private readonly ClicksPerSecondCalculator clicksPerSecondCalculator; + private readonly ClicksPerSecondController clicksPerSecondController; [Cached] public readonly InputCountController InputCountController; @@ -116,7 +116,7 @@ namespace osu.Game.Screens.Play CreateFailingLayer(), //Needs to be initialized before skinnable drawables. judgementCountController = new JudgementCountController(), - clicksPerSecondCalculator = new ClicksPerSecondCalculator(), + clicksPerSecondController = new ClicksPerSecondController(), InputCountController = new InputCountController(), mainComponents = new HUDComponentsContainer { AlwaysPresent = true, }, rulesetComponents = drawableRuleset != null @@ -322,7 +322,7 @@ namespace osu.Game.Screens.Play if (drawableRuleset is ICanAttachHUDPieces attachTarget) { attachTarget.Attach(InputCountController); - attachTarget.Attach(clicksPerSecondCalculator); + attachTarget.Attach(clicksPerSecondController); } replayLoaded.BindTo(drawableRuleset.HasReplayLoaded); From 41890cfc65fd28e7f9e18ea586ff860c8a69ac3b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 27 Jun 2023 16:39:21 +0900 Subject: [PATCH 057/109] Change `JudgementCountController` to a `Component` and remove handling overrides --- osu.Game/Screens/Play/HUD/InputCountController.cs | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/osu.Game/Screens/Play/HUD/InputCountController.cs b/osu.Game/Screens/Play/HUD/InputCountController.cs index 4827f2315f..cfe17d8ce0 100644 --- a/osu.Game/Screens/Play/HUD/InputCountController.cs +++ b/osu.Game/Screens/Play/HUD/InputCountController.cs @@ -4,7 +4,7 @@ using System.Collections.Generic; using osu.Framework.Bindables; using osu.Framework.Extensions.IEnumerableExtensions; -using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics; namespace osu.Game.Screens.Play.HUD { @@ -12,7 +12,7 @@ namespace osu.Game.Screens.Play.HUD /// Keeps track of key press counts for a current play session, exposing bindable counts which can /// be used for display purposes. /// - public partial class InputCountController : CompositeComponent + public partial class InputCountController : Component { public readonly Bindable IsCounting = new BindableBool(true); @@ -29,8 +29,5 @@ namespace osu.Game.Screens.Play.HUD triggers.Add(trigger); trigger.IsCounting.BindTo(IsCounting); } - - public override bool HandleNonPositionalInput => true; - public override bool HandlePositionalInput => true; } } From 7ddbf4eaa7bfb01667bf9f6bbf16048e0499546f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 27 Jun 2023 17:01:06 +0900 Subject: [PATCH 058/109] Add a visual effect when keyboard shortcuts are used to trigger selection box buttons --- .../Edit/Compose/Components/SelectionBox.cs | 19 +++++++------------ .../Compose/Components/SelectionBoxButton.cs | 8 ++++---- .../Compose/Components/SelectionBoxControl.cs | 8 ++++---- 3 files changed, 15 insertions(+), 20 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionBox.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionBox.cs index 1c5faed0e5..50eb3a186d 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionBox.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionBox.cs @@ -32,6 +32,8 @@ namespace osu.Game.Screens.Edit.Compose.Components public Action OperationStarted; public Action OperationEnded; + private SelectionBoxButton reverseButton; + private bool canReverse; /// @@ -166,19 +168,10 @@ namespace osu.Game.Screens.Edit.Compose.Components if (e.Repeat || !e.ControlPressed) return false; - bool runOperationFromHotkey(Func operation) - { - operationStarted(); - bool result = operation?.Invoke() ?? false; - operationEnded(); - - return result; - } - switch (e.Key) { case Key.G: - return CanReverse && runOperationFromHotkey(OnReverse); + return reverseButton?.TriggerClick() ?? false; } return base.OnKeyDown(e); @@ -256,7 +249,7 @@ namespace osu.Game.Screens.Edit.Compose.Components if (CanFlipX) addXFlipComponents(); if (CanFlipY) addYFlipComponents(); if (CanRotate) addRotationComponents(); - if (CanReverse) addButton(FontAwesome.Solid.Backward, "Reverse pattern (Ctrl-G)", () => OnReverse?.Invoke()); + if (CanReverse) reverseButton = addButton(FontAwesome.Solid.Backward, "Reverse pattern (Ctrl-G)", () => OnReverse?.Invoke()); } private void addRotationComponents() @@ -300,7 +293,7 @@ namespace osu.Game.Screens.Edit.Compose.Components addButton(FontAwesome.Solid.ArrowsAltV, "Flip vertically", () => OnFlip?.Invoke(Direction.Vertical, false)); } - private void addButton(IconUsage icon, string tooltip, Action action) + private SelectionBoxButton addButton(IconUsage icon, string tooltip, Action action) { var button = new SelectionBoxButton(icon, tooltip) { @@ -310,6 +303,8 @@ namespace osu.Game.Screens.Edit.Compose.Components button.OperationStarted += operationStarted; button.OperationEnded += operationEnded; buttons.Add(button); + + return button; } private void addScaleHandle(Anchor anchor) diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionBoxButton.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionBoxButton.cs index 832d8b65e5..6108d44c81 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionBoxButton.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionBoxButton.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.Framework.Allocation; using osu.Framework.Graphics; @@ -17,11 +15,11 @@ namespace osu.Game.Screens.Edit.Compose.Components { public sealed partial class SelectionBoxButton : SelectionBoxControl, IHasTooltip { - private SpriteIcon icon; + private SpriteIcon icon = null!; private readonly IconUsage iconUsage; - public Action Action; + public Action? Action; public SelectionBoxButton(IconUsage iconUsage, string tooltip) { @@ -49,6 +47,8 @@ namespace osu.Game.Screens.Edit.Compose.Components protected override bool OnClick(ClickEvent e) { + Circle.FlashColour(Colours.GrayF, 300); + TriggerOperationStarted(); Action?.Invoke(); TriggerOperationEnded(); diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionBoxControl.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionBoxControl.cs index 35c67a1c67..3746c9652e 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionBoxControl.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionBoxControl.cs @@ -24,7 +24,7 @@ namespace osu.Game.Screens.Edit.Compose.Components public event Action OperationStarted; public event Action OperationEnded; - private Circle circle; + protected Circle Circle { get; private set; } /// /// Whether the user is currently holding the control with mouse. @@ -41,7 +41,7 @@ namespace osu.Game.Screens.Edit.Compose.Components InternalChildren = new Drawable[] { - circle = new Circle + Circle = new Circle { RelativeSizeAxes = Axes.Both, Anchor = Anchor.Centre, @@ -85,9 +85,9 @@ namespace osu.Game.Screens.Edit.Compose.Components protected virtual void UpdateHoverState() { if (IsHeld) - circle.FadeColour(Colours.GrayF, TRANSFORM_DURATION, Easing.OutQuint); + Circle.FadeColour(Colours.GrayF, TRANSFORM_DURATION, Easing.OutQuint); else - circle.FadeColour(IsHovered ? Colours.Red : Colours.YellowDark, TRANSFORM_DURATION, Easing.OutQuint); + Circle.FadeColour(IsHovered ? Colours.Red : Colours.YellowDark, TRANSFORM_DURATION, Easing.OutQuint); this.ScaleTo(IsHeld || IsHovered ? 1.5f : 1, TRANSFORM_DURATION, Easing.OutQuint); } From c6d952abe36a1ae16224291cd60d3012ef709140 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 27 Jun 2023 17:01:40 +0900 Subject: [PATCH 059/109] Add support for `Ctrl` + `<` / `>` to rotate selection in editor As discussed in https://github.com/ppy/osu/discussions/24048. --- .../Screens/Edit/Compose/Components/SelectionBox.cs | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionBox.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionBox.cs index 50eb3a186d..d8fd18ff8f 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionBox.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionBox.cs @@ -33,6 +33,8 @@ namespace osu.Game.Screens.Edit.Compose.Components public Action OperationEnded; private SelectionBoxButton reverseButton; + private SelectionBoxButton rotateClockwiseButton; + private SelectionBoxButton rotateCounterClockwiseButton; private bool canReverse; @@ -172,6 +174,12 @@ namespace osu.Game.Screens.Edit.Compose.Components { case Key.G: return reverseButton?.TriggerClick() ?? false; + + case Key.Comma: + return rotateCounterClockwiseButton?.TriggerClick() ?? false; + + case Key.Period: + return rotateClockwiseButton?.TriggerClick() ?? false; } return base.OnKeyDown(e); @@ -254,8 +262,8 @@ namespace osu.Game.Screens.Edit.Compose.Components private void addRotationComponents() { - addButton(FontAwesome.Solid.Undo, "Rotate 90 degrees counter-clockwise", () => OnRotation?.Invoke(-90)); - addButton(FontAwesome.Solid.Redo, "Rotate 90 degrees clockwise", () => OnRotation?.Invoke(90)); + rotateCounterClockwiseButton = addButton(FontAwesome.Solid.Undo, "Rotate 90 degrees counter-clockwise", () => OnRotation?.Invoke(-90)); + rotateClockwiseButton = addButton(FontAwesome.Solid.Redo, "Rotate 90 degrees clockwise", () => OnRotation?.Invoke(90)); addRotateHandle(Anchor.TopLeft); addRotateHandle(Anchor.TopRight); From 4ecc724841ac075f24f8d5695fb277672ccceca8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 27 Jun 2023 18:18:58 +0900 Subject: [PATCH 060/109] Add test coverage of save failure when beatmap is detached from set --- .../Visual/Editing/TestSceneEditorSaving.cs | 31 +++++++++++++++++-- 1 file changed, 28 insertions(+), 3 deletions(-) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneEditorSaving.cs b/osu.Game.Tests/Visual/Editing/TestSceneEditorSaving.cs index 64c48e74cf..7191ae6a57 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneEditorSaving.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneEditorSaving.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.Linq; using NUnit.Framework; using osu.Framework.Allocation; @@ -96,6 +94,33 @@ namespace osu.Game.Tests.Visual.Editing AddAssert("Beatmap still has correct timeline zoom", () => EditorBeatmap.BeatmapInfo.TimelineZoom == changedTimelineZoom); } + [Test] + public void TestSaveWithDetachedBeatmap() + { + AddStep("Set overall difficulty", () => EditorBeatmap.Difficulty.OverallDifficulty = 7); + + SaveEditor(); + + AddStep("Detach beatmap from set", () => + { + Realm.Write(r => + { + BeatmapSetInfo? beatmapSet = r.Find(EditorBeatmap.BeatmapInfo.BeatmapSet!.ID); + BeatmapInfo? beatmap = r.Find(EditorBeatmap.BeatmapInfo.ID); + + beatmapSet.Beatmaps.Remove(beatmap); + }); + }); + + SaveEditor(); + + AddAssert("Beatmap has correct overall difficulty", () => EditorBeatmap.Difficulty.OverallDifficulty == 7); + + ReloadEditorToSameBeatmap(); + + AddAssert("Beatmap still has correct overall difficulty", () => EditorBeatmap.Difficulty.OverallDifficulty == 7); + } + [Test] public void TestDifficulty() { @@ -130,7 +155,7 @@ namespace osu.Game.Tests.Visual.Editing [Test] public void TestLengthAndStarRatingUpdated() { - WorkingBeatmap working = null; + WorkingBeatmap working = null!; double lastStarRating = 0; double lastLength = 0; From 8e80e2fa323337f885ae8119a5a14b5b54e68596 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 27 Jun 2023 18:19:59 +0900 Subject: [PATCH 061/109] Fix incorrect realm copy logic when a beatmap becomes detached from its set The code here was assuming that if the beatmap which is having changes copied across does not exist within the `BeatmapSet.Beatmaps` list, it was not yet persisted to realm. In some edge case, it can happen that the beatmap *is* persisted to realm but not correctly attached to the beatmap set. I don't yet know how this occurs, but it has caused loss of data for at least two users. The fix here is to check realm-wide for the beatmap (using its primary key) rather than only in the list. We then handle the scenario where the beatmap needs to be reattached to the set as a seprate step. --- This does raise others questions like "are we even structuring this correctly? couldn't a single beatmap exist in two different sets?" Maybe, but let's deal with that if/when it comes up. --- osu.Game/Database/RealmObjectExtensions.cs | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/osu.Game/Database/RealmObjectExtensions.cs b/osu.Game/Database/RealmObjectExtensions.cs index a771aa04df..888c1cf8ce 100644 --- a/osu.Game/Database/RealmObjectExtensions.cs +++ b/osu.Game/Database/RealmObjectExtensions.cs @@ -52,10 +52,19 @@ namespace osu.Game.Database { foreach (var beatmap in s.Beatmaps) { - var existing = d.Beatmaps.FirstOrDefault(b => b.ID == beatmap.ID); + // Importantly, search all of realm for the beatmap (not just the set's beatmaps). + // It may have gotten detached, and if that's the case let's use this opportunity to fix + // things up. + var existingBeatmap = d.Realm.Find(beatmap.ID); - if (existing != null) - copyChangesToRealm(beatmap, existing); + if (existingBeatmap != null) + { + // As above, reattach if it happens to not be in the set's beatmaps. + if (!d.Beatmaps.Contains(existingBeatmap)) + d.Beatmaps.Add(existingBeatmap); + + copyChangesToRealm(beatmap, existingBeatmap); + } else { var newBeatmap = new BeatmapInfo @@ -64,6 +73,7 @@ namespace osu.Game.Database BeatmapSet = d, Ruleset = d.Realm.Find(beatmap.Ruleset.ShortName) }; + d.Beatmaps.Add(newBeatmap); copyChangesToRealm(beatmap, newBeatmap); } From ada9c48bde241592c4765ed90040b66a0f67746f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 27 Jun 2023 20:14:33 +0200 Subject: [PATCH 062/109] Attempt to fix more test failures --- osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableHUDOverlay.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableHUDOverlay.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableHUDOverlay.cs index be73e36e11..4d81d9f707 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableHUDOverlay.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableHUDOverlay.cs @@ -63,7 +63,6 @@ namespace osu.Game.Tests.Visual.Gameplay float? initialAlpha = null; createNew(h => h.OnLoadComplete += _ => initialAlpha = hideTarget.Alpha); - AddUntilStep("wait for load", () => hudOverlay.IsAlive); AddAssert("initial alpha was less than 1", () => initialAlpha < 1); } @@ -97,6 +96,7 @@ namespace osu.Game.Tests.Visual.Gameplay return hudOverlay; }); }); + AddUntilStep("wait for load", () => hudOverlay.IsAlive); } protected override Ruleset CreateRulesetForSkinProvider() => new OsuRuleset(); From 9681ee7eeb0d7dee7ddb44ee1a56c2afa593fd23 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 27 Jun 2023 20:29:27 +0200 Subject: [PATCH 063/109] Fix broken test step --- osu.Game.Tests/Visual/Gameplay/TestSceneKeyCounter.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneKeyCounter.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneKeyCounter.cs index 3df0f18558..5a66a5c7a6 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneKeyCounter.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneKeyCounter.cs @@ -62,7 +62,9 @@ namespace osu.Game.Tests.Visual.Gameplay AddStep("Add random", () => { Key key = (Key)((int)Key.A + RNG.Next(26)); - controller.Add(new KeyCounterKeyboardTrigger(key)); + var trigger = new KeyCounterKeyboardTrigger(key); + Add(trigger); + controller.Add(trigger); }); InputTrigger testTrigger = controller.Triggers.First(); From 11577d1df08e96008ec9764ea731c5747ae20996 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 27 Jun 2023 20:41:03 +0200 Subject: [PATCH 064/109] Add test coverage for title query parsing --- .../NonVisual/Filtering/FilterQueryParserTest.cs | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs b/osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs index 46f91f9a67..9f7ba9ac24 100644 --- a/osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs +++ b/osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs @@ -251,6 +251,18 @@ namespace osu.Game.Tests.NonVisual.Filtering Assert.AreEqual("my_fav", filterCriteria.Creator.SearchTerm); } + [Test] + public void TestApplyTitleQueries() + { + const string query = "find me songs with title=\"a certain title\" please"; + var filterCriteria = new FilterCriteria(); + FilterQueryParser.ApplyQueries(filterCriteria, query); + Assert.AreEqual("find me songs with please", filterCriteria.SearchText.Trim()); + Assert.AreEqual(5, filterCriteria.SearchTerms.Length); + Assert.AreEqual("a certain title", filterCriteria.Title.SearchTerm); + Assert.That(filterCriteria.Title.Exact, Is.True); + } + [Test] public void TestApplyArtistQueries() { From 37ee3a7bbd0f1f14e03a87e29b83abd05c800e18 Mon Sep 17 00:00:00 2001 From: Bastian Pedersen Date: Tue, 27 Jun 2023 20:56:35 +0200 Subject: [PATCH 065/109] Localise common game notifications --- osu.Game/Localisation/NotificationsStrings.cs | 25 +++++++++++++++++++ osu.Game/OsuGame.cs | 10 ++++---- 2 files changed, 30 insertions(+), 5 deletions(-) diff --git a/osu.Game/Localisation/NotificationsStrings.cs b/osu.Game/Localisation/NotificationsStrings.cs index 5e2600bc50..14ab3e7ff4 100644 --- a/osu.Game/Localisation/NotificationsStrings.cs +++ b/osu.Game/Localisation/NotificationsStrings.cs @@ -63,6 +63,31 @@ Please try changing your audio device to a working setting."); /// public static LocalisableString ScoreOverlayDisabled(LocalisableString arg0) => new TranslatableString(getKey(@"score_overlay_disabled"), @"The score overlay is currently disabled. You can toggle this by pressing {0}.", arg0); + /// + /// "The URL {0} has an unsupported or dangerous protocol and will not be opened" + /// + public static LocalisableString UnsupportedOrDangerousUrlProtocol(string url) => new TranslatableString(getKey(@"unsupported_or_dangerous_url_protocol"), @"The URL {0} has an unsupported or dangerous protocol and will not be opened.", url); + + /// + /// "Subsequent messages have been logged. Click to view log files" + /// + public static LocalisableString SubsequentMessagesLogged => new TranslatableString(getKey(@"subsequent_messages_logged"), @"Subsequent messages have been logged. Click to view log files"); + + /// + /// "Disabling tablet support due to error: "{0}"" + /// + public static LocalisableString TabletSupportDisabledDueToError(string message) => new TranslatableString(getKey(@"tablet_support_disabled_due_to_error"), @"Disabling tablet support due to error: ""{0}""", message); + + /// + /// "Encountered tablet warning, your tablet may not function correctly. Click here for a list of all tablets supported." + /// + public static LocalisableString EncounteredTabletWarning => new TranslatableString(getKey(@"encountered_tablet_warning"), @"Encountered tablet warning, your tablet may not function correctly. Click here for a list of all tablets supported."); + + /// + /// "This link type is not yet supported!" + /// + public static LocalisableString LinkTypeNotSupported => new TranslatableString(getKey(@"unsupported_link_type"), @"This link type is not yet supported!"); + private static string getKey(string key) => $@"{prefix}:{key}"; } } diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 8bfe48010b..fe98a8e286 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -435,7 +435,7 @@ namespace osu.Game case LinkAction.Spectate: waitForReady(() => Notifications, _ => Notifications.Post(new SimpleNotification { - Text = @"This link type is not yet supported!", + Text = NotificationsStrings.LinkTypeNotSupported, Icon = FontAwesome.Solid.LifeRing, })); break; @@ -477,7 +477,7 @@ namespace osu.Game { Notifications.Post(new SimpleErrorNotification { - Text = $"The URL {url} has an unsupported or dangerous protocol and will not be opened.", + Text = NotificationsStrings.UnsupportedOrDangerousUrlProtocol(url), }); return; @@ -1147,7 +1147,7 @@ namespace osu.Game Schedule(() => Notifications.Post(new SimpleNotification { Icon = FontAwesome.Solid.EllipsisH, - Text = "Subsequent messages have been logged. Click to view log files.", + Text = NotificationsStrings.SubsequentMessagesLogged, Activated = () => { Storage.GetStorageForDirectory(@"logs").PresentFileExternally(logFile); @@ -1179,7 +1179,7 @@ namespace osu.Game { Notifications.Post(new SimpleNotification { - Text = $"Disabling tablet support due to error: \"{message}\"", + Text = NotificationsStrings.TabletSupportDisabledDueToError(message), Icon = FontAwesome.Solid.PenSquare, IconColour = Colours.RedDark, }); @@ -1196,7 +1196,7 @@ namespace osu.Game { Schedule(() => Notifications.Post(new SimpleNotification { - Text = @"Encountered tablet warning, your tablet may not function correctly. Click here for a list of all tablets supported.", + Text = NotificationsStrings.EncounteredTabletWarning, Icon = FontAwesome.Solid.PenSquare, IconColour = Colours.YellowDark, Activated = () => From 8a2cd57f4eeca358d9f1b7bbc0042ae441fb23bb Mon Sep 17 00:00:00 2001 From: Bastian Pedersen Date: Tue, 27 Jun 2023 21:01:39 +0200 Subject: [PATCH 066/109] Add back missing punctunation --- osu.Game/Localisation/NotificationsStrings.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Localisation/NotificationsStrings.cs b/osu.Game/Localisation/NotificationsStrings.cs index 14ab3e7ff4..f568c47546 100644 --- a/osu.Game/Localisation/NotificationsStrings.cs +++ b/osu.Game/Localisation/NotificationsStrings.cs @@ -69,9 +69,9 @@ Please try changing your audio device to a working setting."); public static LocalisableString UnsupportedOrDangerousUrlProtocol(string url) => new TranslatableString(getKey(@"unsupported_or_dangerous_url_protocol"), @"The URL {0} has an unsupported or dangerous protocol and will not be opened.", url); /// - /// "Subsequent messages have been logged. Click to view log files" + /// "Subsequent messages have been logged. Click to view log files." /// - public static LocalisableString SubsequentMessagesLogged => new TranslatableString(getKey(@"subsequent_messages_logged"), @"Subsequent messages have been logged. Click to view log files"); + public static LocalisableString SubsequentMessagesLogged => new TranslatableString(getKey(@"subsequent_messages_logged"), @"Subsequent messages have been logged. Click to view log files."); /// /// "Disabling tablet support due to error: "{0}"" From 62dcd513caa6fd2e566902f503c49636598a9645 Mon Sep 17 00:00:00 2001 From: Bastian Pedersen Date: Tue, 27 Jun 2023 21:02:44 +0200 Subject: [PATCH 067/109] Fix XML doc not mirroring string --- osu.Game/Localisation/NotificationsStrings.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Localisation/NotificationsStrings.cs b/osu.Game/Localisation/NotificationsStrings.cs index f568c47546..b6f2a55e37 100644 --- a/osu.Game/Localisation/NotificationsStrings.cs +++ b/osu.Game/Localisation/NotificationsStrings.cs @@ -64,7 +64,7 @@ Please try changing your audio device to a working setting."); public static LocalisableString ScoreOverlayDisabled(LocalisableString arg0) => new TranslatableString(getKey(@"score_overlay_disabled"), @"The score overlay is currently disabled. You can toggle this by pressing {0}.", arg0); /// - /// "The URL {0} has an unsupported or dangerous protocol and will not be opened" + /// "The URL {0} has an unsupported or dangerous protocol and will not be opened." /// public static LocalisableString UnsupportedOrDangerousUrlProtocol(string url) => new TranslatableString(getKey(@"unsupported_or_dangerous_url_protocol"), @"The URL {0} has an unsupported or dangerous protocol and will not be opened.", url); From 7052f87eb81d727ac54718c2eb25a33ea4458ac3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 27 Jun 2023 21:20:59 +0200 Subject: [PATCH 068/109] Add even more safety against unloaded components --- .../Visual/Gameplay/TestSceneSkinnableHUDOverlay.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableHUDOverlay.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableHUDOverlay.cs index 4d81d9f707..162e279403 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableHUDOverlay.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableHUDOverlay.cs @@ -96,7 +96,9 @@ namespace osu.Game.Tests.Visual.Gameplay return hudOverlay; }); }); - AddUntilStep("wait for load", () => hudOverlay.IsAlive); + AddUntilStep("HUD overlay loaded", () => hudOverlay.IsAlive); + AddUntilStep("components container loaded", + () => hudOverlay.ChildrenOfType().Any(scc => scc.ComponentsLoaded)); } protected override Ruleset CreateRulesetForSkinProvider() => new OsuRuleset(); From e3d97b37f120e24e96e81b41faba96b6c5898e9f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 27 Jun 2023 21:28:37 +0200 Subject: [PATCH 069/109] Rename `MatchMode.{None -> Substring}` --- .../NonVisual/Filtering/FilterQueryParserTest.cs | 8 ++++---- osu.Game/Screens/Select/FilterCriteria.cs | 6 +++--- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs b/osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs index 7e7f7c95fe..ade8146774 100644 --- a/osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs +++ b/osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs @@ -39,13 +39,13 @@ namespace osu.Game.Tests.NonVisual.Filtering Assert.That(filterCriteria.SearchTerms[1].MatchMode, Is.EqualTo(FilterCriteria.MatchMode.IsolatedPhrase)); Assert.That(filterCriteria.SearchTerms[2].SearchTerm, Is.EqualTo("looking")); - Assert.That(filterCriteria.SearchTerms[2].MatchMode, Is.EqualTo(FilterCriteria.MatchMode.None)); + Assert.That(filterCriteria.SearchTerms[2].MatchMode, Is.EqualTo(FilterCriteria.MatchMode.Substring)); Assert.That(filterCriteria.SearchTerms[3].SearchTerm, Is.EqualTo("for")); - Assert.That(filterCriteria.SearchTerms[3].MatchMode, Is.EqualTo(FilterCriteria.MatchMode.None)); + Assert.That(filterCriteria.SearchTerms[3].MatchMode, Is.EqualTo(FilterCriteria.MatchMode.Substring)); Assert.That(filterCriteria.SearchTerms[4].SearchTerm, Is.EqualTo("like")); - Assert.That(filterCriteria.SearchTerms[4].MatchMode, Is.EqualTo(FilterCriteria.MatchMode.None)); + Assert.That(filterCriteria.SearchTerms[4].MatchMode, Is.EqualTo(FilterCriteria.MatchMode.Substring)); } /* @@ -272,7 +272,7 @@ namespace osu.Game.Tests.NonVisual.Filtering Assert.AreEqual("find me songs by please", filterCriteria.SearchText.Trim()); Assert.AreEqual(5, filterCriteria.SearchTerms.Length); Assert.AreEqual("singer", filterCriteria.Artist.SearchTerm); - Assert.That(filterCriteria.Artist.MatchMode, Is.EqualTo(FilterCriteria.MatchMode.None)); + Assert.That(filterCriteria.Artist.MatchMode, Is.EqualTo(FilterCriteria.MatchMode.Substring)); } [Test] diff --git a/osu.Game/Screens/Select/FilterCriteria.cs b/osu.Game/Screens/Select/FilterCriteria.cs index 6ac6b7d5fe..36e048fd3d 100644 --- a/osu.Game/Screens/Select/FilterCriteria.cs +++ b/osu.Game/Screens/Select/FilterCriteria.cs @@ -154,7 +154,7 @@ namespace osu.Game.Screens.Select switch (MatchMode) { default: - case MatchMode.None: + case MatchMode.Substring: return value.Contains(SearchTerm, StringComparison.InvariantCultureIgnoreCase); case MatchMode.IsolatedPhrase: @@ -185,7 +185,7 @@ namespace osu.Game.Screens.Select MatchMode = MatchMode.IsolatedPhrase; } else - MatchMode = MatchMode.None; + MatchMode = MatchMode.Substring; } } @@ -197,7 +197,7 @@ namespace osu.Game.Screens.Select /// /// Match using a simple "contains" substring match. /// - None, + Substring, /// /// Match for the search phrase being isolated by spaces, or at the start or end of the text. From aba380b0018770d31264dce4d886e3fc7a61005a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 27 Jun 2023 21:33:42 +0200 Subject: [PATCH 070/109] Add test case for full phrase match mode trimming chars from inside phrase --- .../Filtering/FilterQueryParserTest.cs | 32 +++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs b/osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs index ade8146774..be8b39b296 100644 --- a/osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs +++ b/osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs @@ -48,6 +48,38 @@ namespace osu.Game.Tests.NonVisual.Filtering Assert.That(filterCriteria.SearchTerms[4].MatchMode, Is.EqualTo(FilterCriteria.MatchMode.Substring)); } + [Test] + public void TestApplyFullPhraseQueryWithExclamationPointInTerm() + { + const string query = "looking for \"circles!\"!"; + var filterCriteria = new FilterCriteria(); + FilterQueryParser.ApplyQueries(filterCriteria, query); + Assert.AreEqual("looking for \"circles!\"!", filterCriteria.SearchText); + Assert.AreEqual(3, filterCriteria.SearchTerms.Length); + + Assert.That(filterCriteria.SearchTerms[0].SearchTerm, Is.EqualTo("circles!")); + Assert.That(filterCriteria.SearchTerms[0].MatchMode, Is.EqualTo(FilterCriteria.MatchMode.FullPhrase)); + + Assert.That(filterCriteria.SearchTerms[1].SearchTerm, Is.EqualTo("looking")); + Assert.That(filterCriteria.SearchTerms[1].MatchMode, Is.EqualTo(FilterCriteria.MatchMode.Substring)); + + Assert.That(filterCriteria.SearchTerms[2].SearchTerm, Is.EqualTo("for")); + Assert.That(filterCriteria.SearchTerms[2].MatchMode, Is.EqualTo(FilterCriteria.MatchMode.Substring)); + } + + [Test] + public void TestApplyBrokenFullPhraseQuery() + { + const string query = "\"!"; + var filterCriteria = new FilterCriteria(); + FilterQueryParser.ApplyQueries(filterCriteria, query); + Assert.AreEqual("\"!", filterCriteria.SearchText); + Assert.AreEqual(1, filterCriteria.SearchTerms.Length); + + Assert.That(filterCriteria.SearchTerms[0].SearchTerm, Is.EqualTo("\"!")); + Assert.That(filterCriteria.SearchTerms[0].MatchMode, Is.EqualTo(FilterCriteria.MatchMode.Substring)); + } + /* * The following tests have been written a bit strangely (they don't check exact * bound equality with what the filter says). From bf99fc61b863761b08e40345fcbb8d8eb287a771 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 27 Jun 2023 21:34:49 +0200 Subject: [PATCH 071/109] Trim full phrase filters in a more precise manner --- .../Filtering/FilterQueryParserTest.cs | 4 ++-- osu.Game/Screens/Select/FilterCriteria.cs | 20 +++++++++++-------- 2 files changed, 14 insertions(+), 10 deletions(-) diff --git a/osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs b/osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs index be8b39b296..ce95e921b9 100644 --- a/osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs +++ b/osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs @@ -76,8 +76,8 @@ namespace osu.Game.Tests.NonVisual.Filtering Assert.AreEqual("\"!", filterCriteria.SearchText); Assert.AreEqual(1, filterCriteria.SearchTerms.Length); - Assert.That(filterCriteria.SearchTerms[0].SearchTerm, Is.EqualTo("\"!")); - Assert.That(filterCriteria.SearchTerms[0].MatchMode, Is.EqualTo(FilterCriteria.MatchMode.Substring)); + Assert.That(filterCriteria.SearchTerms[0].SearchTerm, Is.EqualTo("!")); + Assert.That(filterCriteria.SearchTerms[0].MatchMode, Is.EqualTo(FilterCriteria.MatchMode.IsolatedPhrase)); } /* diff --git a/osu.Game/Screens/Select/FilterCriteria.cs b/osu.Game/Screens/Select/FilterCriteria.cs index 36e048fd3d..ab4f85fc92 100644 --- a/osu.Game/Screens/Select/FilterCriteria.cs +++ b/osu.Game/Screens/Select/FilterCriteria.cs @@ -174,15 +174,19 @@ namespace osu.Game.Screens.Select { searchTerm = value; - if (searchTerm.EndsWith("\"!", StringComparison.Ordinal)) + if (searchTerm.StartsWith('\"')) { - searchTerm = searchTerm.Trim('!', '\"'); - MatchMode = MatchMode.FullPhrase; - } - else if (searchTerm.StartsWith('\"')) - { - searchTerm = searchTerm.Trim('\"'); - MatchMode = MatchMode.IsolatedPhrase; + // length check ensures that the quote character in the `StartsWith()` check above and the `EndsWith()` check below is not the same character. + if (searchTerm.EndsWith("\"!", StringComparison.Ordinal) && searchTerm.Length >= 3) + { + searchTerm = searchTerm.TrimEnd('!').Trim('\"'); + MatchMode = MatchMode.FullPhrase; + } + else + { + searchTerm = searchTerm.Trim('\"'); + MatchMode = MatchMode.IsolatedPhrase; + } } else MatchMode = MatchMode.Substring; From bca1a910878cf48577d43bc72f92d7741fea038f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 27 Jun 2023 21:38:36 +0200 Subject: [PATCH 072/109] Add test cases covering full phrase case insensitivity --- osu.Game.Tests/NonVisual/Filtering/FilterMatchingTest.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game.Tests/NonVisual/Filtering/FilterMatchingTest.cs b/osu.Game.Tests/NonVisual/Filtering/FilterMatchingTest.cs index 44cff7fdd9..c7a32ebbc4 100644 --- a/osu.Game.Tests/NonVisual/Filtering/FilterMatchingTest.cs +++ b/osu.Game.Tests/NonVisual/Filtering/FilterMatchingTest.cs @@ -171,6 +171,7 @@ namespace osu.Game.Tests.NonVisual.Filtering [TestCase("\"an auteur\"", true)] [TestCase("\"Artist\"!", true)] [TestCase("\"The Artist\"!", false)] + [TestCase("\"the artist\"!", false)] [TestCase("\"\\\"", true)] // nasty case, covers properly escaping user input in underlying regex. public void TestCriteriaMatchingExactTerms(string terms, bool filtered) { @@ -238,6 +239,7 @@ namespace osu.Game.Tests.NonVisual.Filtering [TestCase("unknown", true)] [TestCase("\"Artist\"!", true)] [TestCase("\"The Artist\"!", false)] + [TestCase("\"the artist\"!", false)] public void TestCriteriaMatchingArtist(string artistName, bool filtered) { var exampleBeatmapInfo = getExampleBeatmap(); From ad3a470eafad8a22b678a45d6ff7a7da574764c4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 27 Jun 2023 22:01:44 +0200 Subject: [PATCH 073/109] Enable NRT in `SelectionBox` --- .../Edit/Compose/Components/SelectionBox.cs | 30 +++++++++---------- 1 file changed, 14 insertions(+), 16 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionBox.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionBox.cs index d8fd18ff8f..8ab2a821a9 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionBox.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionBox.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.Framework.Allocation; using osu.Framework.Graphics; @@ -24,17 +22,17 @@ namespace osu.Game.Screens.Edit.Compose.Components private const float button_padding = 5; - public Func OnRotation; - public Func OnScale; - public Func OnFlip; - public Func OnReverse; + public Func? OnRotation; + public Func? OnScale; + public Func? OnFlip; + public Func? OnReverse; - public Action OperationStarted; - public Action OperationEnded; + public Action? OperationStarted; + public Action? OperationEnded; - private SelectionBoxButton reverseButton; - private SelectionBoxButton rotateClockwiseButton; - private SelectionBoxButton rotateCounterClockwiseButton; + private SelectionBoxButton? reverseButton; + private SelectionBoxButton? rotateClockwiseButton; + private SelectionBoxButton? rotateCounterClockwiseButton; private bool canReverse; @@ -138,7 +136,7 @@ namespace osu.Game.Screens.Edit.Compose.Components } } - private string text; + private string text = string.Empty; public string Text { @@ -154,13 +152,13 @@ namespace osu.Game.Screens.Edit.Compose.Components } } - private SelectionBoxDragHandleContainer dragHandles; - private FillFlowContainer buttons; + private SelectionBoxDragHandleContainer dragHandles = null!; + private FillFlowContainer buttons = null!; - private OsuSpriteText selectionDetailsText; + private OsuSpriteText? selectionDetailsText; [Resolved] - private OsuColour colours { get; set; } + private OsuColour colours { get; set; } = null!; [BackgroundDependencyLoader] private void load() => recreate(); From 54280f06be5d8cb0dae7348061088278bbfd9a7e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 27 Jun 2023 22:02:15 +0200 Subject: [PATCH 074/109] Switch to `== true` --- osu.Game/Screens/Edit/Compose/Components/SelectionBox.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionBox.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionBox.cs index 8ab2a821a9..81b501b39e 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionBox.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionBox.cs @@ -171,13 +171,13 @@ namespace osu.Game.Screens.Edit.Compose.Components switch (e.Key) { case Key.G: - return reverseButton?.TriggerClick() ?? false; + return reverseButton?.TriggerClick() == true; case Key.Comma: - return rotateCounterClockwiseButton?.TriggerClick() ?? false; + return rotateCounterClockwiseButton?.TriggerClick() == true; case Key.Period: - return rotateClockwiseButton?.TriggerClick() ?? false; + return rotateClockwiseButton?.TriggerClick() == true; } return base.OnKeyDown(e); From 17ed45d07c2e8b6fba9995fc65a4963a613341ca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 27 Jun 2023 22:04:15 +0200 Subject: [PATCH 075/109] Mention hotkeys in button tooltips --- osu.Game/Screens/Edit/Compose/Components/SelectionBox.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionBox.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionBox.cs index 81b501b39e..05fe137732 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionBox.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionBox.cs @@ -260,8 +260,8 @@ namespace osu.Game.Screens.Edit.Compose.Components private void addRotationComponents() { - rotateCounterClockwiseButton = addButton(FontAwesome.Solid.Undo, "Rotate 90 degrees counter-clockwise", () => OnRotation?.Invoke(-90)); - rotateClockwiseButton = addButton(FontAwesome.Solid.Redo, "Rotate 90 degrees clockwise", () => OnRotation?.Invoke(90)); + rotateCounterClockwiseButton = addButton(FontAwesome.Solid.Undo, "Rotate 90 degrees counter-clockwise (Ctrl-<)", () => OnRotation?.Invoke(-90)); + rotateClockwiseButton = addButton(FontAwesome.Solid.Redo, "Rotate 90 degrees clockwise (Ctrl->)", () => OnRotation?.Invoke(90)); addRotateHandle(Anchor.TopLeft); addRotateHandle(Anchor.TopRight); From 444f71541ad8894077674b7a350faf3fe7993c4f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 27 Jun 2023 22:10:53 +0200 Subject: [PATCH 076/109] Add test coverage for rotate hotkeys --- .../Editing/TestSceneComposerSelection.cs | 32 +++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneComposerSelection.cs b/osu.Game.Tests/Visual/Editing/TestSceneComposerSelection.cs index b14025c9d8..4d99c47f77 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneComposerSelection.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneComposerSelection.cs @@ -101,6 +101,38 @@ namespace osu.Game.Tests.Visual.Editing AddAssert("objects reverted to original position", () => addedObjects[0].StartTime == 100); } + [Test] + public void TestRotateHotkeys() + { + HitCircle[] addedObjects = null; + + AddStep("add hitobjects", () => EditorBeatmap.AddRange(addedObjects = new[] + { + new HitCircle { StartTime = 100 }, + new HitCircle { StartTime = 200, Position = new Vector2(100) }, + new HitCircle { StartTime = 300, Position = new Vector2(200) }, + new HitCircle { StartTime = 400, Position = new Vector2(300) }, + })); + + AddStep("select objects", () => EditorBeatmap.SelectedHitObjects.AddRange(addedObjects)); + + AddStep("rotate clockwise", () => + { + InputManager.PressKey(Key.ControlLeft); + InputManager.Key(Key.Period); + InputManager.ReleaseKey(Key.ControlLeft); + }); + AddAssert("objects rotated clockwise", () => addedObjects[0].Position == new Vector2(300, 0)); + + AddStep("rotate counterclockwise", () => + { + InputManager.PressKey(Key.ControlLeft); + InputManager.Key(Key.Comma); + InputManager.ReleaseKey(Key.ControlLeft); + }); + AddAssert("objects reverted to original position", () => addedObjects[0].Position == new Vector2(0)); + } + [Test] public void TestBasicSelect() { From 9be2d9d62e131d22e98b650bdb82e48c97d8f18a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 27 Jun 2023 22:25:01 +0200 Subject: [PATCH 077/109] Fix hotkey presses generating unnecessary undo history The buttons don't check whether the operation they correspond to is possible to perform in the current state of the selection box, so not checking `Can{Reverse,Rotate}` causes superfluous undo states to be added without any real changes if an attempt is made to reverse or rotate a selection that cannot be reversed or rotated. --- osu.Game/Screens/Edit/Compose/Components/SelectionBox.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionBox.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionBox.cs index 05fe137732..e93b9f0691 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionBox.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionBox.cs @@ -171,13 +171,13 @@ namespace osu.Game.Screens.Edit.Compose.Components switch (e.Key) { case Key.G: - return reverseButton?.TriggerClick() == true; + return CanReverse && reverseButton?.TriggerClick() == true; case Key.Comma: - return rotateCounterClockwiseButton?.TriggerClick() == true; + return CanRotate && rotateCounterClockwiseButton?.TriggerClick() == true; case Key.Period: - return rotateClockwiseButton?.TriggerClick() == true; + return CanRotate && rotateClockwiseButton?.TriggerClick() == true; } return base.OnKeyDown(e); From d72a8da2951a23c526a4b9965b75871702e0a5ee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 27 Jun 2023 23:40:12 +0200 Subject: [PATCH 078/109] Add test coverage for deleted difficulties staying in realm --- .../Visual/Editing/TestSceneDifficultyDelete.cs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneDifficultyDelete.cs b/osu.Game.Tests/Visual/Editing/TestSceneDifficultyDelete.cs index 280e6de97e..12e00c4485 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneDifficultyDelete.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneDifficultyDelete.cs @@ -72,9 +72,13 @@ namespace osu.Game.Tests.Visual.Editing AddUntilStep("wait for dialog", () => DialogOverlay.CurrentDialog != null); AddStep("confirm", () => InputManager.Key(Key.Number1)); - AddAssert($"difficulty {i} is deleted", () => Beatmap.Value.BeatmapSetInfo.Beatmaps.Select(b => b.ID), () => Does.Not.Contain(deletedDifficultyID)); - AddAssert("count decreased by one", () => Beatmap.Value.BeatmapSetInfo.Beatmaps.Count, () => Is.EqualTo(countBeforeDeletion - 1)); + AddAssert($"difficulty {i} is unattached from set", + () => Beatmap.Value.BeatmapSetInfo.Beatmaps.Select(b => b.ID), () => Does.Not.Contain(deletedDifficultyID)); + AddAssert("beatmap set difficulty count decreased by one", + () => Beatmap.Value.BeatmapSetInfo.Beatmaps.Count, () => Is.EqualTo(countBeforeDeletion - 1)); AddAssert("set hash changed", () => Beatmap.Value.BeatmapSetInfo.Hash, () => Is.Not.EqualTo(beatmapSetHashBefore)); + AddAssert($"difficulty {i} is deleted from realm", + () => Realm.Run(r => r.Find(deletedDifficultyID)), () => Is.Null); } } } From 6876566530d842e90b2b7330cabcdaaf7c499faf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 27 Jun 2023 23:43:00 +0200 Subject: [PATCH 079/109] Fix difficulty deletion not deleting records from realm --- osu.Game/Beatmaps/BeatmapManager.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index 305dc01844..73811b2e62 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -339,6 +339,8 @@ namespace osu.Game.Beatmaps DeleteFile(setInfo, beatmapInfo.File); setInfo.Beatmaps.Remove(beatmapInfo); + r.Remove(beatmapInfo.Metadata); + r.Remove(beatmapInfo); updateHashAndMarkDirty(setInfo); workingBeatmapCache.Invalidate(setInfo); From b3f2a3ccdfd7d6bc3f9aa01a144058ca084d3220 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 28 Jun 2023 12:11:40 +0900 Subject: [PATCH 080/109] Use more correct localised string source for "sign out" text --- osu.Game/Overlays/Login/UserAction.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Login/UserAction.cs b/osu.Game/Overlays/Login/UserAction.cs index d4d639f2fb..aa2fad6cdb 100644 --- a/osu.Game/Overlays/Login/UserAction.cs +++ b/osu.Game/Overlays/Login/UserAction.cs @@ -18,7 +18,7 @@ namespace osu.Game.Overlays.Login [LocalisableDescription(typeof(LoginPanelStrings), nameof(LoginPanelStrings.AppearOffline))] AppearOffline, - [LocalisableDescription(typeof(UserVerificationStrings), nameof(UserVerificationStrings.BoxInfoLogoutLink))] + [LocalisableDescription(typeof(LayoutStrings), nameof(LayoutStrings.PopupUserLinksLogout))] SignOut, } } From 99e55bb9c03972fdee0569ee4ae7a5c37eac84a9 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 28 Jun 2023 12:21:05 +0900 Subject: [PATCH 081/109] Add logging and `Debug.Fail` on detached beatmap detection --- osu.Game/Database/RealmObjectExtensions.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/osu.Game/Database/RealmObjectExtensions.cs b/osu.Game/Database/RealmObjectExtensions.cs index 888c1cf8ce..5a6c2e3232 100644 --- a/osu.Game/Database/RealmObjectExtensions.cs +++ b/osu.Game/Database/RealmObjectExtensions.cs @@ -3,10 +3,12 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.Linq; using System.Runtime.Serialization; using AutoMapper; using AutoMapper.Internal; +using osu.Framework.Logging; using osu.Game.Beatmaps; using osu.Game.Input.Bindings; using osu.Game.Models; @@ -61,7 +63,11 @@ namespace osu.Game.Database { // As above, reattach if it happens to not be in the set's beatmaps. if (!d.Beatmaps.Contains(existingBeatmap)) + { + Debug.Fail("Beatmaps should never become detached under normal circumstances. If this ever triggers, it should be investigated further."); + Logger.Log("WARNING: One of the difficulties in a beatmap was detached from its set. Please save a copy of logs and report this to devs.", LoggingTarget.Database, LogLevel.Important); d.Beatmaps.Add(existingBeatmap); + } copyChangesToRealm(beatmap, existingBeatmap); } From 29376ffcc0c7a52620ddbbe6096261b7ca6dd3ab Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 28 Jun 2023 12:54:12 +0900 Subject: [PATCH 082/109] Trigger state change when flipping via hotkey in the editor This will trigger a change even if nothing happens. But I think that's okay (not easy to avoid) because the change handler should be aware that nothing changed, if anything. Closes https://github.com/ppy/osu/issues/24065. --- .../Edit/Compose/Components/SelectionHandler.cs | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs index 5cedf1ca42..9ec59cf833 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs @@ -163,10 +163,18 @@ namespace osu.Game.Screens.Edit.Compose.Components switch (e.Action) { case GlobalAction.EditorFlipHorizontally: - return HandleFlip(Direction.Horizontal, true); + ChangeHandler?.BeginChange(); + HandleFlip(Direction.Horizontal, true); + ChangeHandler?.EndChange(); + + return true; case GlobalAction.EditorFlipVertically: - return HandleFlip(Direction.Vertical, true); + ChangeHandler?.BeginChange(); + HandleFlip(Direction.Vertical, true); + ChangeHandler?.EndChange(); + + return true; } return false; From 91354b15705c5e7ea154d2229c715d7f43970c53 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 28 Jun 2023 14:51:19 +0900 Subject: [PATCH 083/109] Avoid performing any actions when `BeatmapAvailability` is updated to `Unknown` --- .../Multiplayer/MultiplayerMatchSubScreen.cs | 28 +++++++++++++------ 1 file changed, 19 insertions(+), 9 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs index 978d77b4f1..ecf38a956d 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs @@ -313,16 +313,26 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer client.ChangeBeatmapAvailability(availability.NewValue).FireAndForget(); - if (availability.NewValue.State != DownloadState.LocallyAvailable) + switch (availability.NewValue.State) { - // while this flow is handled server-side, this covers the edge case of the local user being in a ready state and then deleting the current beatmap. - if (client.LocalUser?.State == MultiplayerUserState.Ready) - client.ChangeState(MultiplayerUserState.Idle); - } - else if (client.LocalUser?.State == MultiplayerUserState.Spectating - && (client.Room?.State == MultiplayerRoomState.WaitingForLoad || client.Room?.State == MultiplayerRoomState.Playing)) - { - onLoadRequested(); + case DownloadState.LocallyAvailable: + if (client.LocalUser?.State == MultiplayerUserState.Spectating + && (client.Room?.State == MultiplayerRoomState.WaitingForLoad || client.Room?.State == MultiplayerRoomState.Playing)) + { + onLoadRequested(); + } + + break; + + case DownloadState.Unknown: + // Don't do anything rash in an unknown state. + break; + + default: + // while this flow is handled server-side, this covers the edge case of the local user being in a ready state and then deleting the current beatmap. + if (client.LocalUser?.State == MultiplayerUserState.Ready) + client.ChangeState(MultiplayerUserState.Idle); + break; } } From 664a2b2255b7e4e59ce6dac72ca04cca3323f528 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 28 Jun 2023 14:51:41 +0900 Subject: [PATCH 084/109] Force a beatmap availability state change when selected item is changed --- osu.Game/Online/Rooms/BeatmapAvailability.cs | 1 + .../Online/Rooms/OnlinePlayBeatmapAvailabilityTracker.cs | 9 +++++++++ 2 files changed, 10 insertions(+) diff --git a/osu.Game/Online/Rooms/BeatmapAvailability.cs b/osu.Game/Online/Rooms/BeatmapAvailability.cs index f2b981c075..a907ee0d3b 100644 --- a/osu.Game/Online/Rooms/BeatmapAvailability.cs +++ b/osu.Game/Online/Rooms/BeatmapAvailability.cs @@ -34,6 +34,7 @@ namespace osu.Game.Online.Rooms DownloadProgress = downloadProgress; } + public static BeatmapAvailability Unknown() => new BeatmapAvailability(DownloadState.Unknown); public static BeatmapAvailability NotDownloaded() => new BeatmapAvailability(DownloadState.NotDownloaded); public static BeatmapAvailability Downloading(float progress) => new BeatmapAvailability(DownloadState.Downloading, progress); public static BeatmapAvailability Importing() => new BeatmapAvailability(DownloadState.Importing); diff --git a/osu.Game/Online/Rooms/OnlinePlayBeatmapAvailabilityTracker.cs b/osu.Game/Online/Rooms/OnlinePlayBeatmapAvailabilityTracker.cs index cce633d46a..29f75bed97 100644 --- a/osu.Game/Online/Rooms/OnlinePlayBeatmapAvailabilityTracker.cs +++ b/osu.Game/Online/Rooms/OnlinePlayBeatmapAvailabilityTracker.cs @@ -60,6 +60,15 @@ namespace osu.Game.Online.Rooms if (item.NewValue == null) return; + // Initially set to unknown until we have attained a good state. + // This has the wanted side effect of forcing a state change when the current playlist + // item changes at the server but our local availability doesn't necessarily change + // (ie. we have both the previous and next item LocallyAvailable). + // + // Note that even without this, the server will trigger a state change and things will work. + // This is just for safety. + availability.Value = BeatmapAvailability.Unknown(); + downloadTracker?.RemoveAndDisposeImmediately(); selectedBeatmap = null; From 3883c28b1567eaf1e3e6286ea78fc559e8a29a02 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 28 Jun 2023 15:39:32 +0900 Subject: [PATCH 085/109] Add visual display in participants list when availability is still being established --- .../Multiplayer/TestSceneMultiplayerParticipantsList.cs | 1 + .../OnlinePlay/Multiplayer/Participants/StateDisplay.cs | 6 ++++++ 2 files changed, 7 insertions(+) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerParticipantsList.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerParticipantsList.cs index 2da29ccc95..a01d2bf9fc 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerParticipantsList.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerParticipantsList.cs @@ -107,6 +107,7 @@ namespace osu.Game.Tests.Visual.Multiplayer [Test] public void TestBeatmapDownloadingStates() { + AddStep("set to unknown", () => MultiplayerClient.ChangeBeatmapAvailability(BeatmapAvailability.Unknown())); AddStep("set to no map", () => MultiplayerClient.ChangeBeatmapAvailability(BeatmapAvailability.NotDownloaded())); AddStep("set to downloading map", () => MultiplayerClient.ChangeBeatmapAvailability(BeatmapAvailability.Downloading(0))); diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/StateDisplay.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/StateDisplay.cs index bfdc0c02ac..b0cc13d645 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/StateDisplay.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/StateDisplay.cs @@ -154,6 +154,12 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Participants this.FadeOut(fade_time); break; + case DownloadState.Unknown: + text.Text = "checking availability"; + icon.Icon = FontAwesome.Solid.Question; + icon.Colour = colours.Orange0; + break; + case DownloadState.NotDownloaded: text.Text = "no map"; icon.Icon = FontAwesome.Solid.MinusCircle; From fec086aec8d456eb7d6a71f4d29dcebe53f12516 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 28 Jun 2023 15:42:53 +0900 Subject: [PATCH 086/109] Fix `OnlinePlayBeatmapAvailabilityTracker` not passing through `Unknown` state --- osu.Game/Online/Rooms/OnlinePlayBeatmapAvailabilityTracker.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game/Online/Rooms/OnlinePlayBeatmapAvailabilityTracker.cs b/osu.Game/Online/Rooms/OnlinePlayBeatmapAvailabilityTracker.cs index 29f75bed97..331a471ad5 100644 --- a/osu.Game/Online/Rooms/OnlinePlayBeatmapAvailabilityTracker.cs +++ b/osu.Game/Online/Rooms/OnlinePlayBeatmapAvailabilityTracker.cs @@ -124,6 +124,9 @@ namespace osu.Game.Online.Rooms switch (downloadTracker.State.Value) { case DownloadState.Unknown: + availability.Value = BeatmapAvailability.Unknown(); + break; + case DownloadState.NotDownloaded: availability.Value = BeatmapAvailability.NotDownloaded(); break; From 5d209b3ffc2d1bfd6d3274616f5f2a5b4d4d7371 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 28 Jun 2023 16:38:20 +0900 Subject: [PATCH 087/109] Change default availability in `MultiplayerRoomUser` to `Unknown` --- osu.Game/Online/Multiplayer/MultiplayerRoomUser.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Online/Multiplayer/MultiplayerRoomUser.cs b/osu.Game/Online/Multiplayer/MultiplayerRoomUser.cs index d70a2797c4..f769b4c805 100644 --- a/osu.Game/Online/Multiplayer/MultiplayerRoomUser.cs +++ b/osu.Game/Online/Multiplayer/MultiplayerRoomUser.cs @@ -29,7 +29,7 @@ namespace osu.Game.Online.Multiplayer /// The availability state of the current beatmap. /// [Key(2)] - public BeatmapAvailability BeatmapAvailability { get; set; } = BeatmapAvailability.LocallyAvailable(); + public BeatmapAvailability BeatmapAvailability { get; set; } = BeatmapAvailability.Unknown(); /// /// Any mods applicable only to the local user. From 6ce0ca832e57f2b9d4e96d9a3b9a908d49896f28 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 28 Jun 2023 19:52:08 +0200 Subject: [PATCH 088/109] Delete test case covering beatmap detach scenario Due to being fundamentally incompatible with the `Debug.Fail()` call added in 99e55bb9c03972fdee0569ee4ae7a5c37eac84a9. This reverts commit 4ecc724841ac075f24f8d5695fb277672ccceca8. --- .../Visual/Editing/TestSceneEditorSaving.cs | 31 ++----------------- 1 file changed, 3 insertions(+), 28 deletions(-) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneEditorSaving.cs b/osu.Game.Tests/Visual/Editing/TestSceneEditorSaving.cs index 7191ae6a57..64c48e74cf 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneEditorSaving.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneEditorSaving.cs @@ -1,6 +1,8 @@ // 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.Linq; using NUnit.Framework; using osu.Framework.Allocation; @@ -94,33 +96,6 @@ namespace osu.Game.Tests.Visual.Editing AddAssert("Beatmap still has correct timeline zoom", () => EditorBeatmap.BeatmapInfo.TimelineZoom == changedTimelineZoom); } - [Test] - public void TestSaveWithDetachedBeatmap() - { - AddStep("Set overall difficulty", () => EditorBeatmap.Difficulty.OverallDifficulty = 7); - - SaveEditor(); - - AddStep("Detach beatmap from set", () => - { - Realm.Write(r => - { - BeatmapSetInfo? beatmapSet = r.Find(EditorBeatmap.BeatmapInfo.BeatmapSet!.ID); - BeatmapInfo? beatmap = r.Find(EditorBeatmap.BeatmapInfo.ID); - - beatmapSet.Beatmaps.Remove(beatmap); - }); - }); - - SaveEditor(); - - AddAssert("Beatmap has correct overall difficulty", () => EditorBeatmap.Difficulty.OverallDifficulty == 7); - - ReloadEditorToSameBeatmap(); - - AddAssert("Beatmap still has correct overall difficulty", () => EditorBeatmap.Difficulty.OverallDifficulty == 7); - } - [Test] public void TestDifficulty() { @@ -155,7 +130,7 @@ namespace osu.Game.Tests.Visual.Editing [Test] public void TestLengthAndStarRatingUpdated() { - WorkingBeatmap working = null!; + WorkingBeatmap working = null; double lastStarRating = 0; double lastLength = 0; From 0940ab1e11cb246975908f8f00974511f1864fa4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 28 Jun 2023 20:47:00 +0200 Subject: [PATCH 089/109] Add failing tests covering correct flip handling --- .../Editing/TestSceneComposerSelection.cs | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneComposerSelection.cs b/osu.Game.Tests/Visual/Editing/TestSceneComposerSelection.cs index 4d99c47f77..d6934a3770 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneComposerSelection.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneComposerSelection.cs @@ -133,6 +133,32 @@ namespace osu.Game.Tests.Visual.Editing AddAssert("objects reverted to original position", () => addedObjects[0].Position == new Vector2(0)); } + [Test] + public void TestGlobalFlipHotkeys() + { + HitCircle addedObject = null; + + AddStep("add hitobjects", () => EditorBeatmap.Add(addedObject = new HitCircle { StartTime = 100 })); + + AddStep("select objects", () => EditorBeatmap.SelectedHitObjects.Add(addedObject)); + + AddStep("flip horizontally across playfield", () => + { + InputManager.PressKey(Key.ControlLeft); + InputManager.Key(Key.H); + InputManager.ReleaseKey(Key.ControlLeft); + }); + AddAssert("objects flipped horizontally", () => addedObject.Position == new Vector2(OsuPlayfield.BASE_SIZE.X, 0)); + + AddStep("flip vertically across playfield", () => + { + InputManager.PressKey(Key.ControlLeft); + InputManager.Key(Key.J); + InputManager.ReleaseKey(Key.ControlLeft); + }); + AddAssert("objects flipped vertically", () => addedObject.Position == OsuPlayfield.BASE_SIZE); + } + [Test] public void TestBasicSelect() { From e4e08c0f5fb787091d28a38b4e67bc59b2b0b846 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 28 Jun 2023 20:48:22 +0200 Subject: [PATCH 090/109] Fix selection handlers eating hotkey presses they didn't handle --- .../Edit/Compose/Components/SelectionHandler.cs | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs index 9ec59cf833..052cb18a5d 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs @@ -160,21 +160,23 @@ namespace osu.Game.Screens.Edit.Compose.Components if (e.Repeat) return false; + bool handled; + switch (e.Action) { case GlobalAction.EditorFlipHorizontally: ChangeHandler?.BeginChange(); - HandleFlip(Direction.Horizontal, true); + handled = HandleFlip(Direction.Horizontal, true); ChangeHandler?.EndChange(); - return true; + return handled; case GlobalAction.EditorFlipVertically: ChangeHandler?.BeginChange(); - HandleFlip(Direction.Vertical, true); + handled = HandleFlip(Direction.Vertical, true); ChangeHandler?.EndChange(); - return true; + return handled; } return false; From ea8700053917628b69aa37a64d508753cac63f11 Mon Sep 17 00:00:00 2001 From: Bastian Pedersen Date: Wed, 28 Jun 2023 21:11:56 +0200 Subject: [PATCH 091/109] Localise chat related notifications --- osu.Game/Localisation/NotificationsStrings.cs | 10 ++++++++++ osu.Game/Online/Chat/MessageNotifier.cs | 5 +++-- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/osu.Game/Localisation/NotificationsStrings.cs b/osu.Game/Localisation/NotificationsStrings.cs index b6f2a55e37..44e440e8d9 100644 --- a/osu.Game/Localisation/NotificationsStrings.cs +++ b/osu.Game/Localisation/NotificationsStrings.cs @@ -88,6 +88,16 @@ Please try changing your audio device to a working setting."); /// public static LocalisableString LinkTypeNotSupported => new TranslatableString(getKey(@"unsupported_link_type"), @"This link type is not yet supported!"); + /// + /// "You received a private message from '{0}'. Click to read it!" + /// + public static LocalisableString PrivateMessageReceived(string username) => new TranslatableString(getKey(@"private_message_received"), @"You received a private message from '{0}'. Click to read it!", username); + + /// + /// "Your name was mentioned in chat by '{0}'. Click to find out why!" + /// + public static LocalisableString YourNameWasMentioned(string username) => new TranslatableString(getKey(@"your_name_was_mentioned"), @"Your name was mentioned in chat by '{0}'. Click to find out why!", username); + private static string getKey(string key) => $@"{prefix}:{key}"; } } diff --git a/osu.Game/Online/Chat/MessageNotifier.cs b/osu.Game/Online/Chat/MessageNotifier.cs index 52bdd36169..ae249d1b7f 100644 --- a/osu.Game/Online/Chat/MessageNotifier.cs +++ b/osu.Game/Online/Chat/MessageNotifier.cs @@ -15,6 +15,7 @@ using osu.Framework.Graphics.Sprites; using osu.Framework.Platform; using osu.Game.Configuration; using osu.Game.Graphics; +using osu.Game.Localisation; using osu.Game.Online.API; using osu.Game.Online.API.Requests.Responses; using osu.Game.Overlays; @@ -154,7 +155,7 @@ namespace osu.Game.Online.Chat : base(message, channel) { Icon = FontAwesome.Solid.Envelope; - Text = $"You received a private message from '{message.Sender.Username}'. Click to read it!"; + Text = NotificationsStrings.PrivateMessageReceived(message.Sender.Username); } } @@ -164,7 +165,7 @@ namespace osu.Game.Online.Chat : base(message, channel) { Icon = FontAwesome.Solid.At; - Text = $"Your name was mentioned in chat by '{message.Sender.Username}'. Click to find out why!"; + Text = NotificationsStrings.YourNameWasMentioned(message.Sender.Username); } } From 537404440d427dad03c1d784023ee4871b93a77c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 29 Jun 2023 13:17:42 +0900 Subject: [PATCH 092/109] Set the beatmap locally available for participants list tests --- .../Visual/Multiplayer/TestSceneMultiplayerParticipantsList.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerParticipantsList.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerParticipantsList.cs index a01d2bf9fc..95ae4c5e80 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerParticipantsList.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerParticipantsList.cs @@ -383,6 +383,8 @@ namespace osu.Game.Tests.Visual.Multiplayer }); AddUntilStep("wait for list to load", () => participantsList?.IsLoaded == true); + + AddStep("set beatmap available", () => MultiplayerClient.ChangeBeatmapAvailability(BeatmapAvailability.LocallyAvailable())); } private void checkProgressBarVisibility(bool visible) => From 34f53965c4fe2cc4c577a3c88aeb8b54f6c94644 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 29 Jun 2023 13:55:03 +0900 Subject: [PATCH 093/109] Never remove significant digits from stsar rating displays Closes https://github.com/ppy/osu/issues/24079. --- osu.Game.Tournament/Components/SongBar.cs | 2 +- osu.Game/Overlays/BeatmapSet/BeatmapPicker.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tournament/Components/SongBar.cs b/osu.Game.Tournament/Components/SongBar.cs index aeceece160..4f4a02ccf1 100644 --- a/osu.Game.Tournament/Components/SongBar.cs +++ b/osu.Game.Tournament/Components/SongBar.cs @@ -188,7 +188,7 @@ namespace osu.Game.Tournament.Components Children = new Drawable[] { new DiffPiece(stats), - new DiffPiece(("Star Rating", $"{beatmap.StarRating:0.##}{srExtra}")) + new DiffPiece(("Star Rating", $"{beatmap.StarRating:0.00}{srExtra}")) } }, new FillFlowContainer diff --git a/osu.Game/Overlays/BeatmapSet/BeatmapPicker.cs b/osu.Game/Overlays/BeatmapSet/BeatmapPicker.cs index 104f861df7..1f38e2ed6c 100644 --- a/osu.Game/Overlays/BeatmapSet/BeatmapPicker.cs +++ b/osu.Game/Overlays/BeatmapSet/BeatmapPicker.cs @@ -185,7 +185,7 @@ namespace osu.Game.Overlays.BeatmapSet OnHovered = beatmap => { showBeatmap(beatmap); - starRating.Text = beatmap.StarRating.ToLocalisableString(@"0.##"); + starRating.Text = beatmap.StarRating.ToLocalisableString(@"0.00"); starRatingContainer.FadeIn(100); }, OnClicked = beatmap => { Beatmap.Value = beatmap; }, From 351f217c8c54f96df61aebafb1ae7273087cc89c Mon Sep 17 00:00:00 2001 From: Cootz Date: Thu, 29 Jun 2023 08:07:43 +0300 Subject: [PATCH 094/109] Reassign existing scores to new/re-exported beatmap --- osu.Game/Beatmaps/BeatmapImporter.cs | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/osu.Game/Beatmaps/BeatmapImporter.cs b/osu.Game/Beatmaps/BeatmapImporter.cs index 7d367ef77d..04c00ed98a 100644 --- a/osu.Game/Beatmaps/BeatmapImporter.cs +++ b/osu.Game/Beatmaps/BeatmapImporter.cs @@ -20,6 +20,7 @@ using osu.Game.IO; using osu.Game.IO.Archives; using osu.Game.Overlays.Notifications; using osu.Game.Rulesets; +using osu.Game.Scoring; using Realms; namespace osu.Game.Beatmaps @@ -199,6 +200,16 @@ namespace osu.Game.Beatmaps LogForModel(beatmapSet, $"Found existing beatmap set with same OnlineID ({beatmapSet.OnlineID}). It will be disassociated and marked for deletion."); } } + + //Because of specific score storing in Osu! database can already contain scores for imported beatmap. + //To restore scores we need to manually reassign them to new/re-exported beatmap. + foreach (BeatmapInfo beatmap in beatmapSet.Beatmaps) + { + IQueryable scores = realm.All().Where(score => score.BeatmapHash == beatmap.Hash); + + if (scores.Any()) + scores.ForEach(score => score.BeatmapInfo = beatmap); //We intentionally ignore BeatmapHash because we checked hash equality + } } protected override void PostImport(BeatmapSetInfo model, Realm realm, ImportParameters parameters) From 47ccbddfb1d0d3a583a649dd531a6dfd63120ed2 Mon Sep 17 00:00:00 2001 From: Cootz Date: Thu, 29 Jun 2023 08:08:10 +0300 Subject: [PATCH 095/109] Reword comment --- osu.Game/Beatmaps/BeatmapImporter.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Beatmaps/BeatmapImporter.cs b/osu.Game/Beatmaps/BeatmapImporter.cs index 04c00ed98a..bf549f40c4 100644 --- a/osu.Game/Beatmaps/BeatmapImporter.cs +++ b/osu.Game/Beatmaps/BeatmapImporter.cs @@ -201,7 +201,7 @@ namespace osu.Game.Beatmaps } } - //Because of specific score storing in Osu! database can already contain scores for imported beatmap. + //Because of specific score storing in Osu! database, it can already contain scores for imported beatmap. //To restore scores we need to manually reassign them to new/re-exported beatmap. foreach (BeatmapInfo beatmap in beatmapSet.Beatmaps) { From e2db6159d6436e4b341d1a76864b4bdf10ef4d17 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 30 Jun 2023 13:46:02 +0900 Subject: [PATCH 096/109] Ensure "tablet support disabled" notification is only shown once --- osu.Game/OsuGame.cs | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index fe98a8e286..93dd97ea15 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -1164,7 +1164,9 @@ namespace osu.Game private void forwardTabletLogsToNotifications() { const string tablet_prefix = @"[Tablet] "; + bool notifyOnWarning = true; + bool notifyOnError = true; Logger.NewEntry += entry => { @@ -1175,6 +1177,11 @@ namespace osu.Game if (entry.Level == LogLevel.Error) { + if (!notifyOnError) + return; + + notifyOnError = false; + Schedule(() => { Notifications.Post(new SimpleNotification @@ -1213,7 +1220,11 @@ namespace osu.Game Schedule(() => { ITabletHandler tablet = Host.AvailableInputHandlers.OfType().SingleOrDefault(); - tablet?.Tablet.BindValueChanged(_ => notifyOnWarning = true, true); + tablet?.Tablet.BindValueChanged(_ => + { + notifyOnWarning = true; + notifyOnError = true; + }, true); }); } From 428383708c1ec2483ba2c39b8b6575aa28e09537 Mon Sep 17 00:00:00 2001 From: Cootz Date: Fri, 30 Jun 2023 20:13:14 +0300 Subject: [PATCH 097/109] Add test for import --- .../Database/BeatmapImporterTests.cs | 68 +++++++++++++++---- 1 file changed, 56 insertions(+), 12 deletions(-) diff --git a/osu.Game.Tests/Database/BeatmapImporterTests.cs b/osu.Game.Tests/Database/BeatmapImporterTests.cs index 446eb72b04..53e5fec075 100644 --- a/osu.Game.Tests/Database/BeatmapImporterTests.cs +++ b/osu.Game.Tests/Database/BeatmapImporterTests.cs @@ -18,6 +18,7 @@ using osu.Game.Extensions; using osu.Game.Models; using osu.Game.Overlays.Notifications; using osu.Game.Rulesets; +using osu.Game.Scoring; using osu.Game.Tests.Resources; using Realms; using SharpCompress.Archives; @@ -416,6 +417,51 @@ namespace osu.Game.Tests.Database }); } + [Test] + public void TestImport_ThenChangeMapWithScore_ThenImport() + { + RunTestWithRealmAsync(async (realm, storage) => + { + var importer = new BeatmapImporter(storage, realm); + + string? temp = TestResources.GetTestBeatmapForImport(); + + var imported = await LoadOszIntoStore(importer, realm.Realm); + + await createScoreForBeatmap(realm.Realm, imported.Beatmaps.First()); + + //editor work imitation + realm.Run(r => + { + r.Write(() => + { + BeatmapInfo beatmap = imported.Beatmaps.First(); + beatmap.Hash = "new_hash"; + beatmap.ResetOnlineInfo(); + }); + }); + + Assert.That(imported.Beatmaps.First().Scores.Any()); + + var importedSecondTime = await importer.Import(new ImportTask(temp)); + + EnsureLoaded(realm.Realm); + + // check the newly "imported" beatmap is not the original. + Assert.NotNull(importedSecondTime); + Debug.Assert(importedSecondTime != null); + Assert.That(imported.ID != importedSecondTime.ID); + + var importedFirstTimeBeatmap = imported.Beatmaps.First(); + var importedSecondTimeBeatmap = importedSecondTime.PerformRead(s => s.Beatmaps.First()); + + Assert.That(importedFirstTimeBeatmap.ID != importedSecondTimeBeatmap.ID); + Assert.That(importedFirstTimeBeatmap.Hash != importedSecondTimeBeatmap.Hash); + Assert.That(!importedFirstTimeBeatmap.Scores.Any()); + Assert.That(importedSecondTimeBeatmap.Scores.Count() == 1); + }); + } + [Test] public void TestImportThenImportWithChangedFile() { @@ -1074,18 +1120,16 @@ namespace osu.Game.Tests.Database Assert.IsTrue(realm.All().First(_ => true).DeletePending); } - private static Task createScoreForBeatmap(Realm realm, BeatmapInfo beatmap) - { - // TODO: reimplement when we have score support in realm. - // return ImportScoreTest.LoadScoreIntoOsu(osu, new ScoreInfo - // { - // OnlineID = 2, - // Beatmap = beatmap, - // BeatmapInfoID = beatmap.ID - // }, new ImportScoreTest.TestArchiveReader()); - - return Task.CompletedTask; - } + private static Task createScoreForBeatmap(Realm realm, BeatmapInfo beatmap) => + realm.WriteAsync(() => + { + realm.Add(new ScoreInfo + { + OnlineID = 2, + BeatmapInfo = beatmap, + BeatmapHash = beatmap.Hash + }); + }); private static void checkBeatmapSetCount(Realm realm, int expected, bool includeDeletePending = false) { From f5d3a2458272fe6de9b8024e56d250318df1b0d2 Mon Sep 17 00:00:00 2001 From: Cootz Date: Fri, 30 Jun 2023 20:15:38 +0300 Subject: [PATCH 098/109] Rename test --- osu.Game.Tests/Database/BeatmapImporterTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tests/Database/BeatmapImporterTests.cs b/osu.Game.Tests/Database/BeatmapImporterTests.cs index 53e5fec075..1f42979d11 100644 --- a/osu.Game.Tests/Database/BeatmapImporterTests.cs +++ b/osu.Game.Tests/Database/BeatmapImporterTests.cs @@ -418,7 +418,7 @@ namespace osu.Game.Tests.Database } [Test] - public void TestImport_ThenChangeMapWithScore_ThenImport() + public void TestImport_ThenModifyMapWithScore_ThenImport() { RunTestWithRealmAsync(async (realm, storage) => { From 2b1d637292621f9d5edfd73b142b30bb5485a6c7 Mon Sep 17 00:00:00 2001 From: Cootz Date: Sat, 1 Jul 2023 09:48:42 +0300 Subject: [PATCH 099/109] Add missing ruleset store --- osu.Game.Tests/Database/BeatmapImporterTests.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game.Tests/Database/BeatmapImporterTests.cs b/osu.Game.Tests/Database/BeatmapImporterTests.cs index 1f42979d11..69496630ce 100644 --- a/osu.Game.Tests/Database/BeatmapImporterTests.cs +++ b/osu.Game.Tests/Database/BeatmapImporterTests.cs @@ -423,6 +423,7 @@ namespace osu.Game.Tests.Database RunTestWithRealmAsync(async (realm, storage) => { var importer = new BeatmapImporter(storage, realm); + using var store = new RealmRulesetStore(realm, storage); string? temp = TestResources.GetTestBeatmapForImport(); From 8d25e2c3e1d419a18cf40ff99f2d9cd454991d91 Mon Sep 17 00:00:00 2001 From: Cootz Date: Sat, 1 Jul 2023 09:49:06 +0300 Subject: [PATCH 100/109] Add importer update test --- .../Database/BeatmapImporterUpdateTests.cs | 65 +++++++++++++++++++ osu.Game/Scoring/ScoreInfo.cs | 1 + 2 files changed, 66 insertions(+) diff --git a/osu.Game.Tests/Database/BeatmapImporterUpdateTests.cs b/osu.Game.Tests/Database/BeatmapImporterUpdateTests.cs index b94cff2a9a..20fe0bfca6 100644 --- a/osu.Game.Tests/Database/BeatmapImporterUpdateTests.cs +++ b/osu.Game.Tests/Database/BeatmapImporterUpdateTests.cs @@ -347,6 +347,71 @@ namespace osu.Game.Tests.Database }); } + [Test] + public void TestDandlingScoreTransferred() + { + RunTestWithRealmAsync(async (realm, storage) => + { + var importer = new BeatmapImporter(storage, realm); + using var rulesets = new RealmRulesetStore(realm, storage); + + using var __ = getBeatmapArchive(out string pathOriginal); + using var _ = getBeatmapArchive(out string pathOnlineCopy); + + var importBeforeUpdate = await importer.Import(new ImportTask(pathOriginal)); + + Assert.That(importBeforeUpdate, Is.Not.Null); + Debug.Assert(importBeforeUpdate != null); + + string scoreTargetBeatmapHash = string.Empty; + + //Set score + importBeforeUpdate.PerformWrite(s => + { + var beatmapInfo = s.Beatmaps.First(); + + scoreTargetBeatmapHash = beatmapInfo.Hash; + + s.Realm.Add(new ScoreInfo(beatmapInfo, s.Realm.All().First(), new RealmUser())); + }); + + //Modify beatmap + const string new_beatmap_hash = "new_hash"; + importBeforeUpdate.PerformWrite(s => + { + var beatmapInfo = s.Beatmaps.First(b => b.Hash == scoreTargetBeatmapHash); + + beatmapInfo.Hash = new_beatmap_hash; + beatmapInfo.ResetOnlineInfo(); + }); + + realm.Run(r => r.Refresh()); + + checkCount(realm, 1); + + //second import matches first before modification, + //in other words beatmap version where score have been set + var importAfterUpdate = await importer.ImportAsUpdate(new ProgressNotification(), new ImportTask(pathOnlineCopy), importBeforeUpdate.Value); + + Assert.That(importAfterUpdate, Is.Not.Null); + Debug.Assert(importAfterUpdate != null); + + realm.Run(r => r.Refresh()); + + //account modified beatmap + checkCount(realm, count_beatmaps + 1); + checkCount(realm, count_beatmaps + 1); + checkCount(realm, 2); + + // score is transferred across to the new set + checkCount(realm, 1); + + //score is transferred to new beatmap + Assert.That(importBeforeUpdate.Value.Beatmaps.First(b => b.Hash == new_beatmap_hash).Scores, Has.Count.EqualTo(0)); + Assert.That(importAfterUpdate.Value.Beatmaps.First(b => b.Hash == scoreTargetBeatmapHash).Scores, Has.Count.EqualTo(1)); + }); + } + [Test] public void TestScoreLostOnModification() { diff --git a/osu.Game/Scoring/ScoreInfo.cs b/osu.Game/Scoring/ScoreInfo.cs index d56338c6a4..6dfac419e5 100644 --- a/osu.Game/Scoring/ScoreInfo.cs +++ b/osu.Game/Scoring/ScoreInfo.cs @@ -82,6 +82,7 @@ namespace osu.Game.Scoring { Ruleset = ruleset ?? new RulesetInfo(); BeatmapInfo = beatmap ?? new BeatmapInfo(); + BeatmapHash = BeatmapInfo.Hash; RealmUser = realmUser ?? new RealmUser(); ID = Guid.NewGuid(); } From a4a9223726b35d5dcba09af3fde12c58d0b3063a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 1 Jul 2023 23:12:04 +0900 Subject: [PATCH 101/109] Move score re-attach to `PostImport` --- osu.Game/Beatmaps/BeatmapImporter.cs | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/osu.Game/Beatmaps/BeatmapImporter.cs b/osu.Game/Beatmaps/BeatmapImporter.cs index bf549f40c4..b0f58d0298 100644 --- a/osu.Game/Beatmaps/BeatmapImporter.cs +++ b/osu.Game/Beatmaps/BeatmapImporter.cs @@ -200,21 +200,22 @@ namespace osu.Game.Beatmaps LogForModel(beatmapSet, $"Found existing beatmap set with same OnlineID ({beatmapSet.OnlineID}). It will be disassociated and marked for deletion."); } } + } + + protected override void PostImport(BeatmapSetInfo model, Realm realm, ImportParameters parameters) + { + base.PostImport(model, realm, parameters); //Because of specific score storing in Osu! database, it can already contain scores for imported beatmap. //To restore scores we need to manually reassign them to new/re-exported beatmap. - foreach (BeatmapInfo beatmap in beatmapSet.Beatmaps) + foreach (BeatmapInfo beatmap in model.Beatmaps) { IQueryable scores = realm.All().Where(score => score.BeatmapHash == beatmap.Hash); if (scores.Any()) scores.ForEach(score => score.BeatmapInfo = beatmap); //We intentionally ignore BeatmapHash because we checked hash equality } - } - protected override void PostImport(BeatmapSetInfo model, Realm realm, ImportParameters parameters) - { - base.PostImport(model, realm, parameters); ProcessBeatmap?.Invoke(model, parameters.Batch ? MetadataLookupScope.LocalCacheFirst : MetadataLookupScope.OnlineFirst); } From 5bd91a531d37c4a89e7cc1925ca80888c81d276b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 1 Jul 2023 23:37:22 +0900 Subject: [PATCH 102/109] Tidy up comments and code --- osu.Game/Beatmaps/BeatmapImporter.cs | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/osu.Game/Beatmaps/BeatmapImporter.cs b/osu.Game/Beatmaps/BeatmapImporter.cs index b0f58d0298..da987eb752 100644 --- a/osu.Game/Beatmaps/BeatmapImporter.cs +++ b/osu.Game/Beatmaps/BeatmapImporter.cs @@ -206,14 +206,12 @@ namespace osu.Game.Beatmaps { base.PostImport(model, realm, parameters); - //Because of specific score storing in Osu! database, it can already contain scores for imported beatmap. - //To restore scores we need to manually reassign them to new/re-exported beatmap. + // Scores are stored separately from beatmaps, and persisted when a beatmap is modified or deleted. + // Let's reattach any matching scores that exist in the database, based on hash. foreach (BeatmapInfo beatmap in model.Beatmaps) { - IQueryable scores = realm.All().Where(score => score.BeatmapHash == beatmap.Hash); - - if (scores.Any()) - scores.ForEach(score => score.BeatmapInfo = beatmap); //We intentionally ignore BeatmapHash because we checked hash equality + foreach (var score in realm.All().Where(score => score.BeatmapHash == beatmap.Hash)) + score.BeatmapInfo = beatmap; } ProcessBeatmap?.Invoke(model, parameters.Batch ? MetadataLookupScope.LocalCacheFirst : MetadataLookupScope.OnlineFirst); From 5f880397a94ea968aba752e7ab658c28bab58312 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 2 Jul 2023 00:04:53 +0900 Subject: [PATCH 103/109] Increase the minimum size of the scroll bar Allows easier targetting when there is a lot of content in the scroll view As discussed in https://github.com/ppy/osu/discussions/24095#discussioncomment-6332398. --- osu.Game/Collections/CollectionDropdown.cs | 2 +- osu.Game/Graphics/Containers/OsuScrollContainer.cs | 11 ++++++----- osu.Game/Overlays/OverlaySidebar.cs | 2 +- .../OnlinePlay/Components/ParticipantsDisplay.cs | 2 +- 4 files changed, 9 insertions(+), 8 deletions(-) diff --git a/osu.Game/Collections/CollectionDropdown.cs b/osu.Game/Collections/CollectionDropdown.cs index 19fa3a3d66..e95565a5c8 100644 --- a/osu.Game/Collections/CollectionDropdown.cs +++ b/osu.Game/Collections/CollectionDropdown.cs @@ -188,7 +188,7 @@ namespace osu.Game.Collections { Anchor = Anchor.CentreRight, Origin = Anchor.CentreRight, - X = -OsuScrollContainer.SCROLL_BAR_HEIGHT, + X = -OsuScrollContainer.SCROLL_BAR_WIDTH, Scale = new Vector2(0.65f), Action = addOrRemove, }); diff --git a/osu.Game/Graphics/Containers/OsuScrollContainer.cs b/osu.Game/Graphics/Containers/OsuScrollContainer.cs index e39fd45a16..da6996c170 100644 --- a/osu.Game/Graphics/Containers/OsuScrollContainer.cs +++ b/osu.Game/Graphics/Containers/OsuScrollContainer.cs @@ -28,7 +28,7 @@ namespace osu.Game.Graphics.Containers public partial class OsuScrollContainer : ScrollContainer where T : Drawable { - public const float SCROLL_BAR_HEIGHT = 10; + public const float SCROLL_BAR_WIDTH = 10; public const float SCROLL_BAR_PADDING = 3; /// @@ -139,6 +139,8 @@ namespace osu.Game.Graphics.Containers private readonly Box box; + protected override float MinimumDimSize => SCROLL_BAR_WIDTH * 3; + public OsuScrollbar(Direction scrollDir) : base(scrollDir) { @@ -147,7 +149,7 @@ namespace osu.Game.Graphics.Containers CornerRadius = 5; // needs to be set initially for the ResizeTo to respect minimum size - Size = new Vector2(SCROLL_BAR_HEIGHT); + Size = new Vector2(SCROLL_BAR_WIDTH); const float margin = 3; @@ -173,11 +175,10 @@ namespace osu.Game.Graphics.Containers public override void ResizeTo(float val, int duration = 0, Easing easing = Easing.None) { - Vector2 size = new Vector2(SCROLL_BAR_HEIGHT) + this.ResizeTo(new Vector2(SCROLL_BAR_WIDTH) { [(int)ScrollDirection] = val - }; - this.ResizeTo(size, duration, easing); + }, duration, easing); } protected override bool OnHover(HoverEvent e) diff --git a/osu.Game/Overlays/OverlaySidebar.cs b/osu.Game/Overlays/OverlaySidebar.cs index f1bdfbddac..070f1f0c37 100644 --- a/osu.Game/Overlays/OverlaySidebar.cs +++ b/osu.Game/Overlays/OverlaySidebar.cs @@ -28,7 +28,7 @@ namespace osu.Game.Overlays scrollbarBackground = new Box { RelativeSizeAxes = Axes.Y, - Width = OsuScrollContainer.SCROLL_BAR_HEIGHT, + Width = OsuScrollContainer.SCROLL_BAR_WIDTH, Anchor = Anchor.TopRight, Origin = Anchor.TopRight, Alpha = 0.5f diff --git a/osu.Game/Screens/OnlinePlay/Components/ParticipantsDisplay.cs b/osu.Game/Screens/OnlinePlay/Components/ParticipantsDisplay.cs index e6999771d3..5128bc4c14 100644 --- a/osu.Game/Screens/OnlinePlay/Components/ParticipantsDisplay.cs +++ b/osu.Game/Screens/OnlinePlay/Components/ParticipantsDisplay.cs @@ -29,7 +29,7 @@ namespace osu.Game.Screens.OnlinePlay.Components RelativeSizeAxes = Axes.X; scroll.RelativeSizeAxes = Axes.X; - scroll.Height = ParticipantsList.TILE_SIZE + OsuScrollContainer.SCROLL_BAR_HEIGHT + OsuScrollContainer.SCROLL_BAR_PADDING * 2; + scroll.Height = ParticipantsList.TILE_SIZE + OsuScrollContainer.SCROLL_BAR_WIDTH + OsuScrollContainer.SCROLL_BAR_PADDING * 2; list.RelativeSizeAxes = Axes.Y; list.AutoSizeAxes = Axes.X; From 1ce60378be4ced5619533c2aaa25d46667dca777 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 1 Jul 2023 20:37:33 +0200 Subject: [PATCH 104/109] Rewrite comments further --- osu.Game.Tests/Database/BeatmapImporterTests.cs | 5 ++++- .../Database/BeatmapImporterUpdateTests.cs | 16 +++++++++------- osu.Game/Beatmaps/BeatmapImporter.cs | 2 +- 3 files changed, 14 insertions(+), 9 deletions(-) diff --git a/osu.Game.Tests/Database/BeatmapImporterTests.cs b/osu.Game.Tests/Database/BeatmapImporterTests.cs index 69496630ce..84e84f030c 100644 --- a/osu.Game.Tests/Database/BeatmapImporterTests.cs +++ b/osu.Game.Tests/Database/BeatmapImporterTests.cs @@ -431,7 +431,7 @@ namespace osu.Game.Tests.Database await createScoreForBeatmap(realm.Realm, imported.Beatmaps.First()); - //editor work imitation + // imitate making local changes via editor realm.Run(r => { r.Write(() => @@ -442,6 +442,9 @@ namespace osu.Game.Tests.Database }); }); + // for now, making changes to a beatmap doesn't remove the backlink from the score to the beatmap. + // the logic of ensuring that scores match the beatmap is upheld via comparing the hash in usages (see: https://github.com/ppy/osu/pull/22539). + // TODO: revisit when fixing https://github.com/ppy/osu/issues/24069. Assert.That(imported.Beatmaps.First().Scores.Any()); var importedSecondTime = await importer.Import(new ImportTask(temp)); diff --git a/osu.Game.Tests/Database/BeatmapImporterUpdateTests.cs b/osu.Game.Tests/Database/BeatmapImporterUpdateTests.cs index 20fe0bfca6..8f48a96ac9 100644 --- a/osu.Game.Tests/Database/BeatmapImporterUpdateTests.cs +++ b/osu.Game.Tests/Database/BeatmapImporterUpdateTests.cs @@ -365,7 +365,7 @@ namespace osu.Game.Tests.Database string scoreTargetBeatmapHash = string.Empty; - //Set score + // set a score on the beatmap importBeforeUpdate.PerformWrite(s => { var beatmapInfo = s.Beatmaps.First(); @@ -375,7 +375,7 @@ namespace osu.Game.Tests.Database s.Realm.Add(new ScoreInfo(beatmapInfo, s.Realm.All().First(), new RealmUser())); }); - //Modify beatmap + // locally modify beatmap const string new_beatmap_hash = "new_hash"; importBeforeUpdate.PerformWrite(s => { @@ -387,10 +387,12 @@ namespace osu.Game.Tests.Database realm.Run(r => r.Refresh()); + // for now, making changes to a beatmap doesn't remove the backlink from the score to the beatmap. + // the logic of ensuring that scores match the beatmap is upheld via comparing the hash in usages (https://github.com/ppy/osu/pull/22539). + // TODO: revisit when fixing https://github.com/ppy/osu/issues/24069. checkCount(realm, 1); - //second import matches first before modification, - //in other words beatmap version where score have been set + // reimport the original beatmap before local modifications var importAfterUpdate = await importer.ImportAsUpdate(new ProgressNotification(), new ImportTask(pathOnlineCopy), importBeforeUpdate.Value); Assert.That(importAfterUpdate, Is.Not.Null); @@ -398,15 +400,15 @@ namespace osu.Game.Tests.Database realm.Run(r => r.Refresh()); - //account modified beatmap + // both original and locally modified versions present checkCount(realm, count_beatmaps + 1); checkCount(realm, count_beatmaps + 1); checkCount(realm, 2); - // score is transferred across to the new set + // score is preserved checkCount(realm, 1); - //score is transferred to new beatmap + // score is transferred to new beatmap Assert.That(importBeforeUpdate.Value.Beatmaps.First(b => b.Hash == new_beatmap_hash).Scores, Has.Count.EqualTo(0)); Assert.That(importAfterUpdate.Value.Beatmaps.First(b => b.Hash == scoreTargetBeatmapHash).Scores, Has.Count.EqualTo(1)); }); diff --git a/osu.Game/Beatmaps/BeatmapImporter.cs b/osu.Game/Beatmaps/BeatmapImporter.cs index da987eb752..fd766490fc 100644 --- a/osu.Game/Beatmaps/BeatmapImporter.cs +++ b/osu.Game/Beatmaps/BeatmapImporter.cs @@ -206,7 +206,7 @@ namespace osu.Game.Beatmaps { base.PostImport(model, realm, parameters); - // Scores are stored separately from beatmaps, and persisted when a beatmap is modified or deleted. + // Scores are stored separately from beatmaps, and persist even when a beatmap is modified or deleted. // Let's reattach any matching scores that exist in the database, based on hash. foreach (BeatmapInfo beatmap in model.Beatmaps) { From b6e9422aa4c714799f5f5fcf26010a8a60baedba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 1 Jul 2023 20:38:00 +0200 Subject: [PATCH 105/109] Fix typo in test name --- osu.Game.Tests/Database/BeatmapImporterUpdateTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tests/Database/BeatmapImporterUpdateTests.cs b/osu.Game.Tests/Database/BeatmapImporterUpdateTests.cs index 8f48a96ac9..83cb54df3f 100644 --- a/osu.Game.Tests/Database/BeatmapImporterUpdateTests.cs +++ b/osu.Game.Tests/Database/BeatmapImporterUpdateTests.cs @@ -348,7 +348,7 @@ namespace osu.Game.Tests.Database } [Test] - public void TestDandlingScoreTransferred() + public void TestDanglingScoreTransferred() { RunTestWithRealmAsync(async (realm, storage) => { From 746212be7b4c1d8b44517a1d4aeecd5709ff64f0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 1 Jul 2023 20:42:34 +0200 Subject: [PATCH 106/109] Remove weird `.Run(r => r.Write(...))` construction --- osu.Game.Tests/Database/BeatmapImporterTests.cs | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/osu.Game.Tests/Database/BeatmapImporterTests.cs b/osu.Game.Tests/Database/BeatmapImporterTests.cs index 84e84f030c..84e6a6c00f 100644 --- a/osu.Game.Tests/Database/BeatmapImporterTests.cs +++ b/osu.Game.Tests/Database/BeatmapImporterTests.cs @@ -432,14 +432,12 @@ namespace osu.Game.Tests.Database await createScoreForBeatmap(realm.Realm, imported.Beatmaps.First()); // imitate making local changes via editor - realm.Run(r => + // ReSharper disable once MethodHasAsyncOverload + realm.Write(_ => { - r.Write(() => - { - BeatmapInfo beatmap = imported.Beatmaps.First(); - beatmap.Hash = "new_hash"; - beatmap.ResetOnlineInfo(); - }); + BeatmapInfo beatmap = imported.Beatmaps.First(); + beatmap.Hash = "new_hash"; + beatmap.ResetOnlineInfo(); }); // for now, making changes to a beatmap doesn't remove the backlink from the score to the beatmap. From 183777f8df4e0139818d7df1be50a38b5f0f848d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 1 Jul 2023 21:22:51 +0200 Subject: [PATCH 107/109] Fix edge cases where selection buttons go outside playfield bounds Addresses https://github.com/ppy/osu/discussions/23599#discussioncomment-6300885. --- osu.Game/Screens/Edit/Compose/Components/SelectionBox.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionBox.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionBox.cs index e93b9f0691..ad264dd8f0 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionBox.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionBox.cs @@ -381,6 +381,7 @@ namespace osu.Game.Screens.Edit.Compose.Components { buttons.Anchor = Anchor.BottomCentre; buttons.Origin = Anchor.BottomCentre; + buttons.Y = Math.Min(0, ToLocalSpace(Parent.ScreenSpaceDrawQuad.BottomLeft).Y - DrawHeight); } else if (topExcess > bottomExcess) { From 9eec1337b33169992abd67b7fc9063b7c6ce70a7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 1 Jul 2023 21:30:41 +0200 Subject: [PATCH 108/109] Use slightly different condition for better UX --- osu.Game/Screens/Edit/Compose/Components/SelectionBox.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionBox.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionBox.cs index ad264dd8f0..5d9fac739c 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionBox.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionBox.cs @@ -377,7 +377,9 @@ namespace osu.Game.Screens.Edit.Compose.Components float leftExcess = thisQuad.TopLeft.X - parentQuad.TopLeft.X; float rightExcess = parentQuad.TopRight.X - thisQuad.TopRight.X; - if (topExcess + bottomExcess < buttons.Height + button_padding) + float minHeight = buttons.ScreenSpaceDrawQuad.Height; + + if (topExcess < minHeight && bottomExcess < minHeight) { buttons.Anchor = Anchor.BottomCentre; buttons.Origin = Anchor.BottomCentre; From 95c30fe12a6906aae2475514261454dd71fa8141 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 2 Jul 2023 21:51:32 +0900 Subject: [PATCH 109/109] Duplicate sign out string for now --- osu.Game/Localisation/LoginPanelStrings.cs | 5 +++++ osu.Game/Overlays/Login/UserAction.cs | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/osu.Game/Localisation/LoginPanelStrings.cs b/osu.Game/Localisation/LoginPanelStrings.cs index 19b0ca3b52..925c2b9146 100644 --- a/osu.Game/Localisation/LoginPanelStrings.cs +++ b/osu.Game/Localisation/LoginPanelStrings.cs @@ -24,6 +24,11 @@ namespace osu.Game.Localisation /// public static LocalisableString SignedIn => new TranslatableString(getKey(@"signed_in"), @"Signed in"); + /// + /// "Sign out" + /// + public static LocalisableString SignOut => new TranslatableString(getKey(@"sign_out"), @"Sign out"); + /// /// "Account" /// diff --git a/osu.Game/Overlays/Login/UserAction.cs b/osu.Game/Overlays/Login/UserAction.cs index aa2fad6cdb..813968a053 100644 --- a/osu.Game/Overlays/Login/UserAction.cs +++ b/osu.Game/Overlays/Login/UserAction.cs @@ -18,7 +18,7 @@ namespace osu.Game.Overlays.Login [LocalisableDescription(typeof(LoginPanelStrings), nameof(LoginPanelStrings.AppearOffline))] AppearOffline, - [LocalisableDescription(typeof(LayoutStrings), nameof(LayoutStrings.PopupUserLinksLogout))] + [LocalisableDescription(typeof(LoginPanelStrings), nameof(LoginPanelStrings.SignOut))] SignOut, } }