From 579d5b51eb25e90e7bc28f284b28c5edc14fc249 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 19 Oct 2022 20:34:41 +0900 Subject: [PATCH 01/69] Add and consume sample bank constants --- .../TestSceneAutoJuiceStream.cs | 2 +- .../Editor/TestSceneSliderSplitting.cs | 4 +- .../Formats/LegacyBeatmapDecoderTest.cs | 14 +++--- ...estSceneHitObjectSamplePointAdjustments.cs | 45 ++++++++++--------- .../TestSceneGameplaySampleTriggerSource.cs | 4 +- osu.Game/Audio/HitSampleInfo.cs | 9 ++++ .../ControlPoints/SampleControlPoint.cs | 4 +- .../Beatmaps/Formats/LegacyBeatmapDecoder.cs | 3 +- .../Beatmaps/Formats/LegacyBeatmapEncoder.cs | 6 +-- 9 files changed, 51 insertions(+), 40 deletions(-) diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneAutoJuiceStream.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneAutoJuiceStream.cs index 202228c9e7..e47a687f24 100644 --- a/osu.Game.Rulesets.Catch.Tests/TestSceneAutoJuiceStream.cs +++ b/osu.Game.Rulesets.Catch.Tests/TestSceneAutoJuiceStream.cs @@ -45,7 +45,7 @@ namespace osu.Game.Rulesets.Catch.Tests NewCombo = i % 8 == 0, Samples = new List(new[] { - new HitSampleInfo(HitSampleInfo.HIT_NORMAL, "normal", volume: 100) + new HitSampleInfo(HitSampleInfo.HIT_NORMAL, HitSampleInfo.BANK_NORMAL, volume: 100) }) }); } diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderSplitting.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderSplitting.cs index 015952c59a..d642d6a5ed 100644 --- a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderSplitting.cs +++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderSplitting.cs @@ -181,7 +181,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor { if (slider is null) return; - slider.SampleControlPoint.SampleBank = "soft"; + slider.SampleControlPoint.SampleBank = HitSampleInfo.BANK_SOFT; slider.SampleControlPoint.SampleVolume = 70; sample = new HitSampleInfo("hitwhistle"); slider.Samples.Add(sample); @@ -207,7 +207,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor AddAssert("sliders have hitsounds", hasHitsounds); bool hasHitsounds() => sample is not null && - EditorBeatmap.HitObjects.All(o => o.SampleControlPoint.SampleBank == "soft" && + EditorBeatmap.HitObjects.All(o => o.SampleControlPoint.SampleBank == HitSampleInfo.BANK_SOFT && o.SampleControlPoint.SampleVolume == 70 && o.Samples.Contains(sample)); } diff --git a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs index fdd0167ed3..d9817802f3 100644 --- a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs +++ b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs @@ -206,17 +206,17 @@ namespace osu.Game.Tests.Beatmaps.Formats var soundPoint = controlPoints.SamplePointAt(0); Assert.AreEqual(956, soundPoint.Time); - Assert.AreEqual("soft", soundPoint.SampleBank); + Assert.AreEqual(HitSampleInfo.BANK_SOFT, soundPoint.SampleBank); Assert.AreEqual(60, soundPoint.SampleVolume); soundPoint = controlPoints.SamplePointAt(53373); Assert.AreEqual(53373, soundPoint.Time); - Assert.AreEqual("soft", soundPoint.SampleBank); + Assert.AreEqual(HitSampleInfo.BANK_SOFT, soundPoint.SampleBank); Assert.AreEqual(60, soundPoint.SampleVolume); soundPoint = controlPoints.SamplePointAt(119637); Assert.AreEqual(119637, soundPoint.Time); - Assert.AreEqual("soft", soundPoint.SampleBank); + Assert.AreEqual(HitSampleInfo.BANK_SOFT, soundPoint.SampleBank); Assert.AreEqual(80, soundPoint.SampleVolume); var effectPoint = controlPoints.EffectPointAt(0); @@ -261,10 +261,10 @@ namespace osu.Game.Tests.Beatmaps.Formats Assert.That(controlPoints.EffectPointAt(2500).KiaiMode, Is.False); Assert.That(controlPoints.EffectPointAt(3500).KiaiMode, Is.True); - Assert.That(controlPoints.SamplePointAt(500).SampleBank, Is.EqualTo("drum")); - Assert.That(controlPoints.SamplePointAt(1500).SampleBank, Is.EqualTo("drum")); - Assert.That(controlPoints.SamplePointAt(2500).SampleBank, Is.EqualTo("normal")); - Assert.That(controlPoints.SamplePointAt(3500).SampleBank, Is.EqualTo("drum")); + Assert.That(controlPoints.SamplePointAt(500).SampleBank, Is.EqualTo(HitSampleInfo.BANK_DRUM)); + Assert.That(controlPoints.SamplePointAt(1500).SampleBank, Is.EqualTo(HitSampleInfo.BANK_DRUM)); + Assert.That(controlPoints.SamplePointAt(2500).SampleBank, Is.EqualTo(HitSampleInfo.BANK_NORMAL)); + Assert.That(controlPoints.SamplePointAt(3500).SampleBank, Is.EqualTo(HitSampleInfo.BANK_DRUM)); Assert.That(controlPoints.TimingPointAt(500).BeatLength, Is.EqualTo(500).Within(0.1)); Assert.That(controlPoints.TimingPointAt(1500).BeatLength, Is.EqualTo(500).Within(0.1)); diff --git a/osu.Game.Tests/Visual/Editing/TestSceneHitObjectSamplePointAdjustments.cs b/osu.Game.Tests/Visual/Editing/TestSceneHitObjectSamplePointAdjustments.cs index 6313842dfd..31939f6971 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneHitObjectSamplePointAdjustments.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneHitObjectSamplePointAdjustments.cs @@ -7,6 +7,7 @@ using System.Linq; using Humanizer; using NUnit.Framework; using osu.Framework.Testing; +using osu.Game.Audio; using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Graphics.UserInterface; @@ -41,7 +42,7 @@ namespace osu.Game.Tests.Visual.Editing Position = (OsuPlayfield.BASE_SIZE - new Vector2(100, 0)) / 2, SampleControlPoint = new SampleControlPoint { - SampleBank = "normal", + SampleBank = HitSampleInfo.BANK_NORMAL, SampleVolume = 80 } }); @@ -52,7 +53,7 @@ namespace osu.Game.Tests.Visual.Editing Position = (OsuPlayfield.BASE_SIZE + new Vector2(100, 0)) / 2, SampleControlPoint = new SampleControlPoint { - SampleBank = "soft", + SampleBank = HitSampleInfo.BANK_SOFT, SampleVolume = 60 } }); @@ -70,7 +71,7 @@ namespace osu.Game.Tests.Visual.Editing public void TestSingleSelection() { clickSamplePiece(0); - samplePopoverHasSingleBank("normal"); + samplePopoverHasSingleBank(HitSampleInfo.BANK_NORMAL); samplePopoverHasSingleVolume(80); dismissPopover(); @@ -80,14 +81,14 @@ namespace osu.Game.Tests.Visual.Editing AddStep("select first object", () => EditorBeatmap.SelectedHitObjects.Add(EditorBeatmap.HitObjects.First())); clickSamplePiece(1); - samplePopoverHasSingleBank("soft"); + samplePopoverHasSingleBank(HitSampleInfo.BANK_SOFT); samplePopoverHasSingleVolume(60); setVolumeViaPopover(90); hitObjectHasSampleVolume(1, 90); - setBankViaPopover("drum"); - hitObjectHasSampleBank(1, "drum"); + setBankViaPopover(HitSampleInfo.BANK_DRUM); + hitObjectHasSampleBank(1, HitSampleInfo.BANK_DRUM); } [Test] @@ -136,27 +137,27 @@ namespace osu.Game.Tests.Visual.Editing AddStep("unify sample bank", () => { foreach (var h in EditorBeatmap.HitObjects) - h.SampleControlPoint.SampleBank = "soft"; + h.SampleControlPoint.SampleBank = HitSampleInfo.BANK_SOFT; }); AddStep("select both objects", () => EditorBeatmap.SelectedHitObjects.AddRange(EditorBeatmap.HitObjects)); clickSamplePiece(0); - samplePopoverHasSingleBank("soft"); + samplePopoverHasSingleBank(HitSampleInfo.BANK_SOFT); dismissPopover(); clickSamplePiece(1); - samplePopoverHasSingleBank("soft"); + samplePopoverHasSingleBank(HitSampleInfo.BANK_SOFT); setBankViaPopover(string.Empty); - hitObjectHasSampleBank(0, "soft"); - hitObjectHasSampleBank(1, "soft"); - samplePopoverHasSingleBank("soft"); + hitObjectHasSampleBank(0, HitSampleInfo.BANK_SOFT); + hitObjectHasSampleBank(1, HitSampleInfo.BANK_SOFT); + samplePopoverHasSingleBank(HitSampleInfo.BANK_SOFT); - setBankViaPopover("drum"); - hitObjectHasSampleBank(0, "drum"); - hitObjectHasSampleBank(1, "drum"); - samplePopoverHasSingleBank("drum"); + setBankViaPopover(HitSampleInfo.BANK_DRUM); + hitObjectHasSampleBank(0, HitSampleInfo.BANK_DRUM); + hitObjectHasSampleBank(1, HitSampleInfo.BANK_DRUM); + samplePopoverHasSingleBank(HitSampleInfo.BANK_DRUM); } [Test] @@ -172,14 +173,14 @@ namespace osu.Game.Tests.Visual.Editing samplePopoverHasIndeterminateBank(); setBankViaPopover(string.Empty); - hitObjectHasSampleBank(0, "normal"); - hitObjectHasSampleBank(1, "soft"); + hitObjectHasSampleBank(0, HitSampleInfo.BANK_NORMAL); + hitObjectHasSampleBank(1, HitSampleInfo.BANK_SOFT); samplePopoverHasIndeterminateBank(); - setBankViaPopover("normal"); - hitObjectHasSampleBank(0, "normal"); - hitObjectHasSampleBank(1, "normal"); - samplePopoverHasSingleBank("normal"); + setBankViaPopover(HitSampleInfo.BANK_NORMAL); + hitObjectHasSampleBank(0, HitSampleInfo.BANK_NORMAL); + hitObjectHasSampleBank(1, HitSampleInfo.BANK_NORMAL); + samplePopoverHasSingleBank(HitSampleInfo.BANK_NORMAL); } private void clickSamplePiece(int objectIndex) => AddStep($"click {objectIndex.ToOrdinalWords()} sample piece", () => diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneGameplaySampleTriggerSource.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneGameplaySampleTriggerSource.cs index b6da562bd0..6e53302624 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneGameplaySampleTriggerSource.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneGameplaySampleTriggerSource.cs @@ -57,13 +57,13 @@ namespace osu.Game.Tests.Visual.Gameplay { StartTime = t += spacing, Samples = new[] { new HitSampleInfo(HitSampleInfo.HIT_NORMAL) }, - SampleControlPoint = new SampleControlPoint { SampleBank = "soft" }, + SampleControlPoint = new SampleControlPoint { SampleBank = HitSampleInfo.BANK_SOFT }, }, new HitCircle { StartTime = t + spacing, Samples = new[] { new HitSampleInfo(HitSampleInfo.HIT_WHISTLE) }, - SampleControlPoint = new SampleControlPoint { SampleBank = "soft" }, + SampleControlPoint = new SampleControlPoint { SampleBank = HitSampleInfo.BANK_SOFT }, }, }); diff --git a/osu.Game/Audio/HitSampleInfo.cs b/osu.Game/Audio/HitSampleInfo.cs index efa5562cb8..81cfed23f5 100644 --- a/osu.Game/Audio/HitSampleInfo.cs +++ b/osu.Game/Audio/HitSampleInfo.cs @@ -19,11 +19,20 @@ namespace osu.Game.Audio public const string HIT_FINISH = @"hitfinish"; public const string HIT_CLAP = @"hitclap"; + public const string BANK_NORMAL = @"normal"; + public const string BANK_SOFT = @"soft"; + public const string BANK_DRUM = @"drum"; + /// /// All valid sample addition constants. /// public static IEnumerable AllAdditions => new[] { HIT_WHISTLE, HIT_FINISH, HIT_CLAP }; + /// + /// All valid bank constants. + /// + public static IEnumerable AllBanks => new[] { BANK_NORMAL, BANK_SOFT, BANK_DRUM }; + /// /// The name of the sample to load. /// diff --git a/osu.Game/Beatmaps/ControlPoints/SampleControlPoint.cs b/osu.Game/Beatmaps/ControlPoints/SampleControlPoint.cs index c454439c5c..e7b869cfa7 100644 --- a/osu.Game/Beatmaps/ControlPoints/SampleControlPoint.cs +++ b/osu.Game/Beatmaps/ControlPoints/SampleControlPoint.cs @@ -14,7 +14,7 @@ namespace osu.Game.Beatmaps.ControlPoints /// public class SampleControlPoint : ControlPoint, IEquatable { - public const string DEFAULT_BANK = "normal"; + public const string DEFAULT_BANK = HitSampleInfo.BANK_NORMAL; public static readonly SampleControlPoint DEFAULT = new SampleControlPoint { @@ -30,7 +30,7 @@ namespace osu.Game.Beatmaps.ControlPoints public readonly Bindable SampleBankBindable = new Bindable(DEFAULT_BANK) { Default = DEFAULT_BANK }; /// - /// The speed multiplier at this control point. + /// The sample bank at this control point. /// public string SampleBank { diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs index 75500fbc4e..b3e6f50366 100644 --- a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs @@ -10,6 +10,7 @@ using System.Linq; using osu.Framework.Extensions; using osu.Framework.Extensions.EnumExtensions; using osu.Framework.Logging; +using osu.Game.Audio; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Beatmaps.Legacy; using osu.Game.Beatmaps.Timing; @@ -412,7 +413,7 @@ namespace osu.Game.Beatmaps.Formats string stringSampleSet = sampleSet.ToString().ToLowerInvariant(); if (stringSampleSet == @"none") - stringSampleSet = @"normal"; + stringSampleSet = HitSampleInfo.HIT_NORMAL; if (timingChange) { diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs index 03c63ff4f2..52d1ea60a5 100644 --- a/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs @@ -547,13 +547,13 @@ namespace osu.Game.Beatmaps.Formats { switch (sampleBank?.ToLowerInvariant()) { - case "normal": + case HitSampleInfo.BANK_NORMAL: return LegacySampleBank.Normal; - case "soft": + case HitSampleInfo.BANK_SOFT: return LegacySampleBank.Soft; - case "drum": + case HitSampleInfo.BANK_DRUM: return LegacySampleBank.Drum; default: From 9222cb379f7c5cdb1ad7a25f73df28242e5b1406 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 19 Oct 2022 20:53:18 +0900 Subject: [PATCH 02/69] Add sample bank suport to editor selection handler --- .../Components/EditorSelectionHandler.cs | 73 ++++++++++++++++++- 1 file changed, 72 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/EditorSelectionHandler.cs b/osu.Game/Screens/Edit/Compose/Components/EditorSelectionHandler.cs index 0bdfc5b0a0..1670328e58 100644 --- a/osu.Game/Screens/Edit/Compose/Components/EditorSelectionHandler.cs +++ b/osu.Game/Screens/Edit/Compose/Components/EditorSelectionHandler.cs @@ -48,11 +48,40 @@ namespace osu.Game.Screens.Edit.Compose.Components /// public readonly Dictionary> SelectionSampleStates = new Dictionary>(); + /// + /// The state of each sample bank type for all selected hitobjects. + /// + public readonly Dictionary> SelectionBankStates = new Dictionary>(); + /// /// Set up ternary state bindables and bind them to selection/hitobject changes (in both directions) /// private void createStateBindables() { + foreach (string bankName in HitSampleInfo.AllBanks) + { + var bindable = new Bindable + { + Description = bankName.Titleize() + }; + + bindable.ValueChanged += state => + { + switch (state.NewValue) + { + case TernaryState.False: + RemoveSampleBank(bankName); + break; + + case TernaryState.True: + AddSampleBank(bankName); + break; + } + }; + + SelectionBankStates[bankName] = bindable; + } + foreach (string sampleName in HitSampleInfo.AllAdditions) { var bindable = new Bindable @@ -104,12 +133,48 @@ namespace osu.Game.Screens.Edit.Compose.Components { bindable.Value = GetStateFromSelection(SelectedItems, h => h.Samples.Any(s => s.Name == sampleName)); } + + foreach ((string bankName, var bindable) in SelectionBankStates) + { + bindable.Value = GetStateFromSelection(SelectedItems, h => h.SampleControlPoint.SampleBank == bankName); + } } #endregion #region Ternary state changes + /// + /// Adds a sample bank to all selected s. + /// + /// The name of the sample bank. + public void AddSampleBank(string bankName) + { + EditorBeatmap.PerformOnSelection(h => + { + if (h.SampleControlPoint.SampleBank == bankName) + return; + + h.SampleControlPoint.SampleBank = bankName; + EditorBeatmap.Update(h); + }); + } + + /// + /// Removes a sample bank from all selected s. + /// + /// The name of the sample bank. + public void RemoveSampleBank(string bankName) + { + EditorBeatmap.PerformOnSelection(h => + { + if (h.SampleControlPoint.SampleBank == bankName) + h.SampleControlPoint.SampleBankBindable.SetDefault(); + + EditorBeatmap.Update(h); + }); + } + /// /// Adds a hit sample to all selected s. /// @@ -174,11 +239,17 @@ namespace osu.Game.Screens.Edit.Compose.Components yield return new TernaryStateToggleMenuItem("New combo") { State = { BindTarget = SelectionNewComboState } }; } - yield return new OsuMenuItem("Sound") + yield return new OsuMenuItem("Sample") { Items = SelectionSampleStates.Select(kvp => new TernaryStateToggleMenuItem(kvp.Value.Description) { State = { BindTarget = kvp.Value } }).ToArray() }; + + yield return new OsuMenuItem("Bank") + { + Items = SelectionBankStates.Select(kvp => + new TernaryStateToggleMenuItem(kvp.Value.Description) { State = { BindTarget = kvp.Value } }).ToArray() + }; } #endregion From 50e24ddd872bcd8e6c5c543d8fa6fcecac0f7b3b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 19 Oct 2022 21:35:08 +0900 Subject: [PATCH 03/69] Add icon and radio button logic --- osu.Game/Rulesets/Edit/HitObjectComposer.cs | 20 ++++++++- .../Components/ComposeBlueprintContainer.cs | 42 ++++++++++++++++++- .../Components/EditorSelectionHandler.cs | 35 +++++++++++++++- 3 files changed, 91 insertions(+), 6 deletions(-) diff --git a/osu.Game/Rulesets/Edit/HitObjectComposer.cs b/osu.Game/Rulesets/Edit/HitObjectComposer.cs index 3bed835854..8857bb2dae 100644 --- a/osu.Game/Rulesets/Edit/HitObjectComposer.cs +++ b/osu.Game/Rulesets/Edit/HitObjectComposer.cs @@ -71,6 +71,8 @@ namespace osu.Game.Rulesets.Edit private FillFlowContainer togglesCollection; + private FillFlowContainer sampleBankTogglesCollection; + private IBindable hasTiming; protected HitObjectComposer(Ruleset ruleset) @@ -146,6 +148,16 @@ namespace osu.Game.Rulesets.Edit Direction = FillDirection.Vertical, Spacing = new Vector2(0, 5), }, + }, + new EditorToolboxGroup("bank (Shift-Q~R)") + { + Child = sampleBankTogglesCollection = new FillFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Direction = FillDirection.Vertical, + Spacing = new Vector2(0, 5), + }, } } }, @@ -161,6 +173,8 @@ namespace osu.Game.Rulesets.Edit TernaryStates = CreateTernaryButtons().ToArray(); togglesCollection.AddRange(TernaryStates.Select(b => new DrawableTernaryButton(b))); + sampleBankTogglesCollection.AddRange(BlueprintContainer.SampleBankTernaryStates.Select(b => new DrawableTernaryButton(b))); + setSelectTool(); EditorBeatmap.SelectedHitObjects.CollectionChanged += selectionChanged; @@ -213,7 +227,7 @@ namespace osu.Game.Rulesets.Edit /// /// Create all ternary states required to be displayed to the user. /// - protected virtual IEnumerable CreateTernaryButtons() => BlueprintContainer.TernaryStates; + protected virtual IEnumerable CreateTernaryButtons() => BlueprintContainer.MainTernaryStates; /// /// Construct a relevant blueprint container. This will manage hitobject selection/placement input handling and display logic. @@ -255,7 +269,9 @@ namespace osu.Game.Rulesets.Edit if (checkRightToggleFromKey(e.Key, out int rightIndex)) { - var item = togglesCollection.ElementAtOrDefault(rightIndex); + var item = e.ShiftPressed + ? sampleBankTogglesCollection.ElementAtOrDefault(rightIndex) + : togglesCollection.ElementAtOrDefault(rightIndex); if (item is DrawableTernaryButton button) { diff --git a/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs index ec07da43a0..5adc60f6a7 100644 --- a/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs @@ -14,6 +14,8 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; using osu.Framework.Input.Events; using osu.Game.Audio; +using osu.Game.Graphics; +using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Edit.Tools; @@ -55,7 +57,8 @@ namespace osu.Game.Screens.Edit.Compose.Components [BackgroundDependencyLoader] private void load() { - TernaryStates = CreateTernaryButtons().ToArray(); + MainTernaryStates = CreateTernaryButtons().ToArray(); + SampleBankTernaryStates = createSampleBankTernaryButtons().ToArray(); AddInternal(placementBlueprintContainer); } @@ -172,7 +175,9 @@ namespace osu.Game.Screens.Edit.Compose.Components /// /// A collection of states which will be displayed to the user in the toolbox. /// - public TernaryButton[] TernaryStates { get; private set; } + public TernaryButton[] MainTernaryStates { get; private set; } + + public TernaryButton[] SampleBankTernaryStates { get; private set; } /// /// Create all ternary states required to be displayed to the user. @@ -186,6 +191,39 @@ namespace osu.Game.Screens.Edit.Compose.Components yield return new TernaryButton(kvp.Value, kvp.Key.Replace("hit", string.Empty).Titleize(), () => getIconForSample(kvp.Key)); } + private IEnumerable createSampleBankTernaryButtons() + { + foreach (var kvp in SelectionHandler.SelectionBankStates) + yield return new TernaryButton(kvp.Value, kvp.Key.Titleize(), () => getIconForBank(kvp.Key)); + } + + private Drawable getIconForBank(string sampleName) + { + return new Container + { + Size = new Vector2(30, 20), + Children = new Drawable[] + { + new SpriteIcon + { + Size = new Vector2(8), + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Icon = FontAwesome.Solid.VolumeOff + }, + new OsuSpriteText + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + X = 10, + Y = -1, + Font = OsuFont.Default.With(weight: FontWeight.Bold, size: 20), + Text = $"{char.ToUpper(sampleName.First())}" + } + } + }; + } + private Drawable getIconForSample(string sampleName) { switch (sampleName) diff --git a/osu.Game/Screens/Edit/Compose/Components/EditorSelectionHandler.cs b/osu.Game/Screens/Edit/Compose/Components/EditorSelectionHandler.cs index 1670328e58..750dedac20 100644 --- a/osu.Game/Screens/Edit/Compose/Components/EditorSelectionHandler.cs +++ b/osu.Game/Screens/Edit/Compose/Components/EditorSelectionHandler.cs @@ -11,6 +11,7 @@ using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics.UserInterface; using osu.Game.Audio; +using osu.Game.Beatmaps.ControlPoints; using osu.Game.Graphics.UserInterface; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Objects; @@ -70,11 +71,38 @@ namespace osu.Game.Screens.Edit.Compose.Components switch (state.NewValue) { case TernaryState.False: - RemoveSampleBank(bankName); + if (SelectedItems.Count == 0) + { + // Ensure that if this is the last selected bank, it should remain selected. + if (SelectionBankStates.Values.All(b => b.Value == TernaryState.False)) + bindable.Value = TernaryState.True; + } + else + { + // Never remove a sample bank. + // These are basically radio buttons, not toggles. + if (SelectedItems.All(h => h.SampleControlPoint.SampleBank == bankName)) + bindable.Value = TernaryState.True; + } + break; case TernaryState.True: - AddSampleBank(bankName); + if (SelectedItems.Count == 0) + { + // Ensure the user can't stack multiple bank selections when there's no hitobject selection. + // Note that in normal scenarios this is sorted out by the feedback from applying the bank to the selected objects. + foreach (var other in SelectionBankStates.Values) + { + if (other != bindable) + other.Value = TernaryState.False; + } + } + else + { + AddSampleBank(bankName); + } + break; } }; @@ -82,6 +110,9 @@ namespace osu.Game.Screens.Edit.Compose.Components SelectionBankStates[bankName] = bindable; } + // start with normal selected. + SelectionBankStates[SampleControlPoint.DEFAULT_BANK].Value = TernaryState.True; + foreach (string sampleName in HitSampleInfo.AllAdditions) { var bindable = new Bindable From 372a655be1637d8823d89532d24a28f283798906 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 19 Oct 2022 21:39:51 +0900 Subject: [PATCH 04/69] Ensure placement uses currently selected bank --- .../Components/ComposeBlueprintContainer.cs | 20 +++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs index 5adc60f6a7..1c9ac83630 100644 --- a/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs @@ -77,9 +77,10 @@ namespace osu.Game.Screens.Edit.Compose.Components // we own SelectionHandler so don't need to worry about making bindable copies (for simplicity) foreach (var kvp in SelectionHandler.SelectionSampleStates) - { kvp.Value.BindValueChanged(_ => updatePlacementSamples()); - } + + foreach (var kvp in SelectionHandler.SelectionBankStates) + kvp.Value.BindValueChanged(_ => updatePlacementSamples()); } protected override void TransferBlueprintFor(HitObject hitObject, DrawableHitObject drawableObject) @@ -146,6 +147,9 @@ namespace osu.Game.Screens.Edit.Compose.Components foreach (var kvp in SelectionHandler.SelectionSampleStates) sampleChanged(kvp.Key, kvp.Value.Value); + + foreach (var kvp in SelectionHandler.SelectionBankStates) + bankChanged(kvp.Key, kvp.Value.Value); } private void sampleChanged(string sampleName, TernaryState state) @@ -170,6 +174,18 @@ namespace osu.Game.Screens.Edit.Compose.Components } } + private void bankChanged(string bankName, TernaryState state) + { + if (currentPlacement == null) return; + + switch (state) + { + case TernaryState.True: + currentPlacement.HitObject.SampleControlPoint.SampleBank = bankName; + break; + } + } + public readonly Bindable NewCombo = new Bindable { Description = "New Combo" }; /// From b9f41611a7777951b8eebbb83ab5c9a6b27e1e12 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 19 Oct 2022 21:48:18 +0900 Subject: [PATCH 05/69] Fix bank potentially being overwritten during placement --- osu.Game/Rulesets/Edit/PlacementBlueprint.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/osu.Game/Rulesets/Edit/PlacementBlueprint.cs b/osu.Game/Rulesets/Edit/PlacementBlueprint.cs index c8196b6865..132214a0bd 100644 --- a/osu.Game/Rulesets/Edit/PlacementBlueprint.cs +++ b/osu.Game/Rulesets/Edit/PlacementBlueprint.cs @@ -74,9 +74,13 @@ namespace osu.Game.Rulesets.Edit /// Whether this call is committing a value for HitObject.StartTime and continuing with further adjustments. protected void BeginPlacement(bool commitStart = false) { + // Store and copy the bank, since it is managed by the editor UI. + string bank = HitObject.SampleControlPoint.SampleBank; + var nearestSampleControlPoint = beatmap.HitObjects.LastOrDefault(h => h.GetEndTime() < HitObject.StartTime)?.SampleControlPoint?.DeepClone() as SampleControlPoint; HitObject.SampleControlPoint = nearestSampleControlPoint ?? new SampleControlPoint(); + HitObject.SampleControlPoint.SampleBank = bank; placementHandler.BeginPlacement(HitObject); if (commitStart) From 677b8d09f8773626b36f0e686520fb871b53220d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 19 Oct 2022 23:54:12 +0900 Subject: [PATCH 06/69] Fix huge oversight causing test failures --- osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs index b3e6f50366..ae57ee6f5a 100644 --- a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs @@ -413,7 +413,7 @@ namespace osu.Game.Beatmaps.Formats string stringSampleSet = sampleSet.ToString().ToLowerInvariant(); if (stringSampleSet == @"none") - stringSampleSet = HitSampleInfo.HIT_NORMAL; + stringSampleSet = HitSampleInfo.BANK_NORMAL; if (timingChange) { From 849b50a38fe821720944fff0b560f33dceb25286 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 21 Oct 2022 00:16:33 +0900 Subject: [PATCH 07/69] Use `ToUpperInvariant` for added safety MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Bartłomiej Dach --- .../Edit/Compose/Components/ComposeBlueprintContainer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs index 1c9ac83630..eebc4c8e0e 100644 --- a/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs @@ -234,7 +234,7 @@ namespace osu.Game.Screens.Edit.Compose.Components X = 10, Y = -1, Font = OsuFont.Default.With(weight: FontWeight.Bold, size: 20), - Text = $"{char.ToUpper(sampleName.First())}" + Text = $"{char.ToUpperInvariant(sampleName.First())}" } } }; From 10d6f9ea5a9e64eaa92aae278b6f8466485081a5 Mon Sep 17 00:00:00 2001 From: timiimit Date: Tue, 16 May 2023 09:52:26 +0200 Subject: [PATCH 08/69] Create common online play pill --- .../Lounge/Components/OnlinePlayPill.cs | 37 +++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 osu.Game/Screens/OnlinePlay/Lounge/Components/OnlinePlayPill.cs diff --git a/osu.Game/Screens/OnlinePlay/Lounge/Components/OnlinePlayPill.cs b/osu.Game/Screens/OnlinePlay/Lounge/Components/OnlinePlayPill.cs new file mode 100644 index 0000000000..45a0fd2a1b --- /dev/null +++ b/osu.Game/Screens/OnlinePlay/Lounge/Components/OnlinePlayPill.cs @@ -0,0 +1,37 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +#nullable disable + +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Game.Graphics; +using osu.Game.Graphics.Containers; + +namespace osu.Game.Screens.OnlinePlay.Lounge.Components +{ + public partial class OnlinePlayPill : OnlinePlayComposite + { + protected PillContainer pill; + protected OsuTextFlowContainer textFlow; + + public OnlinePlayPill() + { + AutoSizeAxes = Axes.Both; + } + + [BackgroundDependencyLoader] + private void load() + { + InternalChild = pill = new PillContainer + { + Child = textFlow = new OsuTextFlowContainer(s => s.Font = OsuFont.GetFont(size: 12)) + { + AutoSizeAxes = Axes.Both, + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft + } + }; + } + } +} From 04893064f06a673e60ce7e61bb23fb07571b41b7 Mon Sep 17 00:00:00 2001 From: timiimit Date: Tue, 16 May 2023 09:53:11 +0200 Subject: [PATCH 09/69] Make used pills inherit from a common one --- .../Lounge/Components/MatchTypePill.cs | 23 +---------- .../Lounge/Components/PlaylistCountPill.cs | 33 +++------------ .../Lounge/Components/QueueModePill.cs | 27 +------------ .../Components/RoomSpecialCategoryPill.cs | 40 +++---------------- .../Lounge/Components/RoomStatusPill.cs | 32 ++------------- 5 files changed, 17 insertions(+), 138 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/Lounge/Components/MatchTypePill.cs b/osu.Game/Screens/OnlinePlay/Lounge/Components/MatchTypePill.cs index f96d547747..dd5f52155c 100644 --- a/osu.Game/Screens/OnlinePlay/Lounge/Components/MatchTypePill.cs +++ b/osu.Game/Screens/OnlinePlay/Lounge/Components/MatchTypePill.cs @@ -13,29 +13,8 @@ using osu.Game.Online.Rooms; namespace osu.Game.Screens.OnlinePlay.Lounge.Components { - public partial class MatchTypePill : OnlinePlayComposite + public partial class MatchTypePill : OnlinePlayPill { - private OsuTextFlowContainer textFlow; - - public MatchTypePill() - { - AutoSizeAxes = Axes.Both; - } - - [BackgroundDependencyLoader] - private void load() - { - InternalChild = new PillContainer - { - Child = textFlow = new OsuTextFlowContainer(s => s.Font = OsuFont.GetFont(size: 12)) - { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - AutoSizeAxes = Axes.Both, - } - }; - } - protected override void LoadComplete() { base.LoadComplete(); diff --git a/osu.Game/Screens/OnlinePlay/Lounge/Components/PlaylistCountPill.cs b/osu.Game/Screens/OnlinePlay/Lounge/Components/PlaylistCountPill.cs index 81ba48d135..c3118ab285 100644 --- a/osu.Game/Screens/OnlinePlay/Lounge/Components/PlaylistCountPill.cs +++ b/osu.Game/Screens/OnlinePlay/Lounge/Components/PlaylistCountPill.cs @@ -5,40 +5,17 @@ using System.Linq; using Humanizer; -using osu.Framework.Allocation; using osu.Framework.Extensions.LocalisationExtensions; using osu.Framework.Graphics; using osu.Game.Graphics; -using osu.Game.Graphics.Containers; namespace osu.Game.Screens.OnlinePlay.Lounge.Components { /// /// A pill that displays the playlist item count. /// - public partial class PlaylistCountPill : OnlinePlayComposite + public partial class PlaylistCountPill : OnlinePlayPill { - private OsuTextFlowContainer count; - - public PlaylistCountPill() - { - AutoSizeAxes = Axes.Both; - } - - [BackgroundDependencyLoader] - private void load() - { - InternalChild = new PillContainer - { - Child = count = new OsuTextFlowContainer(s => s.Font = OsuFont.GetFont(size: 12)) - { - AutoSizeAxes = Axes.Both, - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - } - }; - } - protected override void LoadComplete() { base.LoadComplete(); @@ -55,10 +32,10 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components ? Playlist.Count(i => !i.Expired) : PlaylistItemStats.Value.CountActive; - count.Clear(); - count.AddText(activeItems.ToLocalisableString(), s => s.Font = s.Font.With(weight: FontWeight.Bold)); - count.AddText(" "); - count.AddText("Beatmap".ToQuantity(activeItems, ShowQuantityAs.None)); + textFlow.Clear(); + textFlow.AddText(activeItems.ToLocalisableString(), s => s.Font = s.Font.With(weight: FontWeight.Bold)); + textFlow.AddText(" "); + textFlow.AddText("Beatmap".ToQuantity(activeItems, ShowQuantityAs.None)); } } } diff --git a/osu.Game/Screens/OnlinePlay/Lounge/Components/QueueModePill.cs b/osu.Game/Screens/OnlinePlay/Lounge/Components/QueueModePill.cs index 0175418a96..d37dede961 100644 --- a/osu.Game/Screens/OnlinePlay/Lounge/Components/QueueModePill.cs +++ b/osu.Game/Screens/OnlinePlay/Lounge/Components/QueueModePill.cs @@ -3,39 +3,14 @@ #nullable disable -using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Extensions; -using osu.Framework.Graphics; -using osu.Game.Graphics; -using osu.Game.Graphics.Containers; using osu.Game.Online.Multiplayer; namespace osu.Game.Screens.OnlinePlay.Lounge.Components { - public partial class QueueModePill : OnlinePlayComposite + public partial class QueueModePill : OnlinePlayPill { - private OsuTextFlowContainer textFlow; - - public QueueModePill() - { - AutoSizeAxes = Axes.Both; - } - - [BackgroundDependencyLoader] - private void load() - { - InternalChild = new PillContainer - { - Child = textFlow = new OsuTextFlowContainer(s => s.Font = OsuFont.GetFont(size: 12)) - { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - AutoSizeAxes = Axes.Both, - } - }; - } - protected override void LoadComplete() { base.LoadComplete(); diff --git a/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomSpecialCategoryPill.cs b/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomSpecialCategoryPill.cs index 5d67a18d1f..a01d14ab40 100644 --- a/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomSpecialCategoryPill.cs +++ b/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomSpecialCategoryPill.cs @@ -5,54 +5,26 @@ using osu.Framework.Allocation; using osu.Framework.Extensions; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Sprites; using osu.Game.Graphics; -using osu.Game.Graphics.Sprites; -using osuTK.Graphics; namespace osu.Game.Screens.OnlinePlay.Lounge.Components { - public partial class RoomSpecialCategoryPill : OnlinePlayComposite + public partial class RoomSpecialCategoryPill : OnlinePlayPill { - private SpriteText text; - private PillContainer pill; - [Resolved] private OsuColour colours { get; set; } - public RoomSpecialCategoryPill() - { - AutoSizeAxes = Axes.Both; - } - - [BackgroundDependencyLoader] - private void load() - { - InternalChild = pill = new PillContainer - { - Background = - { - Colour = colours.Pink, - Alpha = 1 - }, - Child = text = new OsuSpriteText - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Font = OsuFont.GetFont(weight: FontWeight.SemiBold, size: 12), - Colour = Color4.Black - } - }; - } - protected override void LoadComplete() { base.LoadComplete(); + pill.Background.Colour = colours.Pink; + pill.Background.Alpha = 1; + Category.BindValueChanged(c => { - text.Text = c.NewValue.GetLocalisableDescription(); + textFlow.Clear(); + textFlow.AddText(c.NewValue.GetLocalisableDescription()); var backgroundColour = colours.ForRoomCategory(Category.Value); if (backgroundColour != null) diff --git a/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomStatusPill.cs b/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomStatusPill.cs index 201314851e..a4fdd39236 100644 --- a/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomStatusPill.cs +++ b/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomStatusPill.cs @@ -6,46 +6,20 @@ using System; using osu.Framework.Allocation; using osu.Framework.Graphics; -using osu.Framework.Graphics.Sprites; using osu.Game.Graphics; -using osu.Game.Graphics.Sprites; using osu.Game.Online.Rooms; using osu.Game.Online.Rooms.RoomStatuses; -using osuTK.Graphics; namespace osu.Game.Screens.OnlinePlay.Lounge.Components { /// /// A pill that displays the room's current status. /// - public partial class RoomStatusPill : OnlinePlayComposite + public partial class RoomStatusPill : OnlinePlayPill { [Resolved] private OsuColour colours { get; set; } - private PillContainer pill; - private SpriteText statusText; - - public RoomStatusPill() - { - AutoSizeAxes = Axes.Both; - } - - [BackgroundDependencyLoader] - private void load() - { - InternalChild = pill = new PillContainer - { - Child = statusText = new OsuSpriteText - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Font = OsuFont.GetFont(weight: FontWeight.SemiBold, size: 12), - Colour = Color4.Black - } - }; - } - protected override void LoadComplete() { base.LoadComplete(); @@ -62,7 +36,9 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components pill.Background.Alpha = 1; pill.Background.FadeColour(status.GetAppropriateColour(colours), 100); - statusText.Text = status.Message; + + textFlow.Clear(); + textFlow.AddText(status.Message); } private RoomStatus getDisplayStatus() From 40daa41a5272c78746f8c743ac0f72d0d0e917e4 Mon Sep 17 00:00:00 2001 From: timiimit Date: Tue, 16 May 2023 10:06:48 +0200 Subject: [PATCH 10/69] Fix naming & namespaces --- .../OnlinePlay/Lounge/Components/MatchTypePill.cs | 8 ++------ .../OnlinePlay/Lounge/Components/OnlinePlayPill.cs | 8 ++++---- .../OnlinePlay/Lounge/Components/PlaylistCountPill.cs | 8 ++++---- .../OnlinePlay/Lounge/Components/QueueModePill.cs | 4 ++-- .../Lounge/Components/RoomSpecialCategoryPill.cs | 10 +++++----- .../OnlinePlay/Lounge/Components/RoomStatusPill.cs | 8 ++++---- 6 files changed, 21 insertions(+), 25 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/Lounge/Components/MatchTypePill.cs b/osu.Game/Screens/OnlinePlay/Lounge/Components/MatchTypePill.cs index dd5f52155c..e019de0f6f 100644 --- a/osu.Game/Screens/OnlinePlay/Lounge/Components/MatchTypePill.cs +++ b/osu.Game/Screens/OnlinePlay/Lounge/Components/MatchTypePill.cs @@ -3,12 +3,8 @@ #nullable disable -using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Extensions; -using osu.Framework.Graphics; -using osu.Game.Graphics; -using osu.Game.Graphics.Containers; using osu.Game.Online.Rooms; namespace osu.Game.Screens.OnlinePlay.Lounge.Components @@ -24,8 +20,8 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components private void onMatchTypeChanged(ValueChangedEvent type) { - textFlow.Clear(); - textFlow.AddText(type.NewValue.GetLocalisableDescription()); + TextFlow.Clear(); + TextFlow.AddText(type.NewValue.GetLocalisableDescription()); } } } diff --git a/osu.Game/Screens/OnlinePlay/Lounge/Components/OnlinePlayPill.cs b/osu.Game/Screens/OnlinePlay/Lounge/Components/OnlinePlayPill.cs index 45a0fd2a1b..80e82c3856 100644 --- a/osu.Game/Screens/OnlinePlay/Lounge/Components/OnlinePlayPill.cs +++ b/osu.Game/Screens/OnlinePlay/Lounge/Components/OnlinePlayPill.cs @@ -12,8 +12,8 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components { public partial class OnlinePlayPill : OnlinePlayComposite { - protected PillContainer pill; - protected OsuTextFlowContainer textFlow; + protected PillContainer Pill; + protected OsuTextFlowContainer TextFlow; public OnlinePlayPill() { @@ -23,9 +23,9 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components [BackgroundDependencyLoader] private void load() { - InternalChild = pill = new PillContainer + InternalChild = Pill = new PillContainer { - Child = textFlow = new OsuTextFlowContainer(s => s.Font = OsuFont.GetFont(size: 12)) + Child = TextFlow = new OsuTextFlowContainer(s => s.Font = OsuFont.GetFont(size: 12)) { AutoSizeAxes = Axes.Both, Anchor = Anchor.CentreLeft, diff --git a/osu.Game/Screens/OnlinePlay/Lounge/Components/PlaylistCountPill.cs b/osu.Game/Screens/OnlinePlay/Lounge/Components/PlaylistCountPill.cs index c3118ab285..6687f30bc0 100644 --- a/osu.Game/Screens/OnlinePlay/Lounge/Components/PlaylistCountPill.cs +++ b/osu.Game/Screens/OnlinePlay/Lounge/Components/PlaylistCountPill.cs @@ -32,10 +32,10 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components ? Playlist.Count(i => !i.Expired) : PlaylistItemStats.Value.CountActive; - textFlow.Clear(); - textFlow.AddText(activeItems.ToLocalisableString(), s => s.Font = s.Font.With(weight: FontWeight.Bold)); - textFlow.AddText(" "); - textFlow.AddText("Beatmap".ToQuantity(activeItems, ShowQuantityAs.None)); + TextFlow.Clear(); + TextFlow.AddText(activeItems.ToLocalisableString(), s => s.Font = s.Font.With(weight: FontWeight.Bold)); + TextFlow.AddText(" "); + TextFlow.AddText("Beatmap".ToQuantity(activeItems, ShowQuantityAs.None)); } } } diff --git a/osu.Game/Screens/OnlinePlay/Lounge/Components/QueueModePill.cs b/osu.Game/Screens/OnlinePlay/Lounge/Components/QueueModePill.cs index d37dede961..e402653c2f 100644 --- a/osu.Game/Screens/OnlinePlay/Lounge/Components/QueueModePill.cs +++ b/osu.Game/Screens/OnlinePlay/Lounge/Components/QueueModePill.cs @@ -20,8 +20,8 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components private void onQueueModeChanged(ValueChangedEvent mode) { - textFlow.Clear(); - textFlow.AddText(mode.NewValue.GetLocalisableDescription()); + TextFlow.Clear(); + TextFlow.AddText(mode.NewValue.GetLocalisableDescription()); } } } diff --git a/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomSpecialCategoryPill.cs b/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomSpecialCategoryPill.cs index a01d14ab40..d266104cf5 100644 --- a/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomSpecialCategoryPill.cs +++ b/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomSpecialCategoryPill.cs @@ -18,17 +18,17 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components { base.LoadComplete(); - pill.Background.Colour = colours.Pink; - pill.Background.Alpha = 1; + Pill.Background.Colour = colours.Pink; + Pill.Background.Alpha = 1; Category.BindValueChanged(c => { - textFlow.Clear(); - textFlow.AddText(c.NewValue.GetLocalisableDescription()); + TextFlow.Clear(); + TextFlow.AddText(c.NewValue.GetLocalisableDescription()); var backgroundColour = colours.ForRoomCategory(Category.Value); if (backgroundColour != null) - pill.Background.Colour = backgroundColour.Value; + Pill.Background.Colour = backgroundColour.Value; }, true); } } diff --git a/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomStatusPill.cs b/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomStatusPill.cs index a4fdd39236..fa5eba4b69 100644 --- a/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomStatusPill.cs +++ b/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomStatusPill.cs @@ -34,11 +34,11 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components { RoomStatus status = getDisplayStatus(); - pill.Background.Alpha = 1; - pill.Background.FadeColour(status.GetAppropriateColour(colours), 100); + Pill.Background.Alpha = 1; + Pill.Background.FadeColour(status.GetAppropriateColour(colours), 100); - textFlow.Clear(); - textFlow.AddText(status.Message); + TextFlow.Clear(); + TextFlow.AddText(status.Message); } private RoomStatus getDisplayStatus() From a9991d9dd4bce7145fde56a8c4cae6c266313ae9 Mon Sep 17 00:00:00 2001 From: timiimit Date: Tue, 16 May 2023 10:14:14 +0200 Subject: [PATCH 11/69] Remove another unused namespace --- .../Screens/OnlinePlay/Lounge/Components/PlaylistCountPill.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game/Screens/OnlinePlay/Lounge/Components/PlaylistCountPill.cs b/osu.Game/Screens/OnlinePlay/Lounge/Components/PlaylistCountPill.cs index 6687f30bc0..d1365c02f3 100644 --- a/osu.Game/Screens/OnlinePlay/Lounge/Components/PlaylistCountPill.cs +++ b/osu.Game/Screens/OnlinePlay/Lounge/Components/PlaylistCountPill.cs @@ -6,7 +6,6 @@ using System.Linq; using Humanizer; using osu.Framework.Extensions.LocalisationExtensions; -using osu.Framework.Graphics; using osu.Game.Graphics; namespace osu.Game.Screens.OnlinePlay.Lounge.Components From 81b7737660260a2a5b3168afaf1c171278c1f5e9 Mon Sep 17 00:00:00 2001 From: timiimit Date: Tue, 16 May 2023 10:20:16 +0200 Subject: [PATCH 12/69] Fix text colour of RoomSpecialCategoryPill --- .../OnlinePlay/Lounge/Components/RoomSpecialCategoryPill.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomSpecialCategoryPill.cs b/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomSpecialCategoryPill.cs index d266104cf5..b869ede9bd 100644 --- a/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomSpecialCategoryPill.cs +++ b/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomSpecialCategoryPill.cs @@ -6,6 +6,7 @@ using osu.Framework.Allocation; using osu.Framework.Extensions; using osu.Game.Graphics; +using osuTK.Graphics; namespace osu.Game.Screens.OnlinePlay.Lounge.Components { @@ -20,6 +21,7 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components Pill.Background.Colour = colours.Pink; Pill.Background.Alpha = 1; + TextFlow.Colour = Color4.Black; Category.BindValueChanged(c => { From 4ef74eb3f960257d2693b23a33b6358d91a7f954 Mon Sep 17 00:00:00 2001 From: timiimit Date: Tue, 16 May 2023 10:22:52 +0200 Subject: [PATCH 13/69] Fix `RoomStatusPill` text colour --- .../Screens/OnlinePlay/Lounge/Components/RoomStatusPill.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomStatusPill.cs b/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomStatusPill.cs index fa5eba4b69..4206aaf167 100644 --- a/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomStatusPill.cs +++ b/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomStatusPill.cs @@ -9,6 +9,7 @@ using osu.Framework.Graphics; using osu.Game.Graphics; using osu.Game.Online.Rooms; using osu.Game.Online.Rooms.RoomStatuses; +using osuTK.Graphics; namespace osu.Game.Screens.OnlinePlay.Lounge.Components { @@ -28,6 +29,8 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components Status.BindValueChanged(_ => updateDisplay(), true); FinishTransforms(true); + + TextFlow.Colour = Colour4.Black; } private void updateDisplay() From cc6e4df93e3e1ec8ea06f14e434c1e4e0194b8d0 Mon Sep 17 00:00:00 2001 From: timiimit Date: Tue, 16 May 2023 10:27:46 +0200 Subject: [PATCH 14/69] Remove wrongly added back namepspace --- osu.Game/Screens/OnlinePlay/Lounge/Components/RoomStatusPill.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomStatusPill.cs b/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomStatusPill.cs index 4206aaf167..f260d168a7 100644 --- a/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomStatusPill.cs +++ b/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomStatusPill.cs @@ -9,7 +9,6 @@ using osu.Framework.Graphics; using osu.Game.Graphics; using osu.Game.Online.Rooms; using osu.Game.Online.Rooms.RoomStatuses; -using osuTK.Graphics; namespace osu.Game.Screens.OnlinePlay.Lounge.Components { From 9fe787acd89e04cdeb108129eb0055864343bbea Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 17 May 2023 15:03:59 +0900 Subject: [PATCH 15/69] Enable NRT on `AccuracyHeatmap` --- osu.Game.Rulesets.Osu/Statistics/AccuracyHeatmap.cs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Statistics/AccuracyHeatmap.cs b/osu.Game.Rulesets.Osu/Statistics/AccuracyHeatmap.cs index 0249b6d9b1..495fccf03a 100644 --- a/osu.Game.Rulesets.Osu/Statistics/AccuracyHeatmap.cs +++ b/osu.Game.Rulesets.Osu/Statistics/AccuracyHeatmap.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 System.Diagnostics; using System.Linq; @@ -36,8 +34,8 @@ namespace osu.Game.Rulesets.Osu.Statistics private const float rotation = 45; - private BufferedContainer bufferedGrid; - private GridContainer pointGrid; + private BufferedContainer bufferedGrid = null!; + private GridContainer pointGrid = null!; private readonly ScoreInfo score; private readonly IBeatmap playableBeatmap; From 3054348c730db26c981cf5e06a36f2cc392da6ae Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 17 May 2023 15:22:48 +0900 Subject: [PATCH 16/69] Add text hints to accuracy heat map to better describe travel direction --- .../Statistics/AccuracyHeatmap.cs | 80 ++++++++++++++----- 1 file changed, 59 insertions(+), 21 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Statistics/AccuracyHeatmap.cs b/osu.Game.Rulesets.Osu/Statistics/AccuracyHeatmap.cs index 495fccf03a..9d9b3fb84a 100644 --- a/osu.Game.Rulesets.Osu/Statistics/AccuracyHeatmap.cs +++ b/osu.Game.Rulesets.Osu/Statistics/AccuracyHeatmap.cs @@ -11,6 +11,7 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Utils; using osu.Game.Beatmaps; +using osu.Game.Graphics.Sprites; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Scoring; using osuTK; @@ -64,34 +65,36 @@ namespace osu.Game.Rulesets.Osu.Statistics FillMode = FillMode.Fit, Children = new Drawable[] { - new CircularContainer - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - RelativeSizeAxes = Axes.Both, - Size = new Vector2(inner_portion), - Masking = true, - BorderThickness = line_thickness, - BorderColour = Color4.White, - Child = new Box - { - RelativeSizeAxes = Axes.Both, - Colour = Color4Extensions.FromHex("#202624") - } - }, new Container { RelativeSizeAxes = Axes.Both, Children = new Drawable[] { + new CircularContainer + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.Both, + Size = new Vector2(inner_portion), + Masking = true, + BorderThickness = line_thickness, + BorderColour = Color4.White, + Child = new Box + { + RelativeSizeAxes = Axes.Both, + Colour = Color4Extensions.FromHex("#202624") + } + }, new Container { RelativeSizeAxes = Axes.Both, Padding = new MarginPadding(1), + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Rotation = rotation, Child = new Container { RelativeSizeAxes = Axes.Both, - Masking = true, Children = new Drawable[] { new Box @@ -100,9 +103,9 @@ namespace osu.Game.Rulesets.Osu.Statistics Origin = Anchor.Centre, EdgeSmoothness = new Vector2(1), RelativeSizeAxes = Axes.Y, - Height = 2, // We're rotating along a diagonal - we don't really care how big this is. - Width = line_thickness / 2, - Rotation = -rotation, + Width = line_thickness / 2, // adjust for edgesmoothness + Height = MathF.Sqrt(2), + Rotation = -rotation * 2, Alpha = 0.3f, }, new Box @@ -111,9 +114,44 @@ namespace osu.Game.Rulesets.Osu.Statistics Origin = Anchor.Centre, EdgeSmoothness = new Vector2(1), RelativeSizeAxes = Axes.Y, - Height = 2, // We're rotating along a diagonal - we don't really care how big this is. Width = line_thickness / 2, // adjust for edgesmoothness - Rotation = rotation + Height = MathF.Sqrt(2), + }, + new OsuSpriteText + { + Text = "Next", + Anchor = Anchor.Centre, + Origin = Anchor.BottomRight, + Padding = new MarginPadding(3), + RelativePositionAxes = Axes.Both, + Y = -inner_portion / 2, + }, + new OsuSpriteText + { + Text = "object", + Anchor = Anchor.Centre, + Origin = Anchor.BottomLeft, + Padding = new MarginPadding(3), + RelativePositionAxes = Axes.Both, + Y = -inner_portion / 2, + }, + new OsuSpriteText + { + Text = "Last", + Anchor = Anchor.Centre, + Origin = Anchor.TopRight, + Padding = new MarginPadding(3), + RelativePositionAxes = Axes.Both, + Y = inner_portion / 2, + }, + new OsuSpriteText + { + Text = "object", + Anchor = Anchor.Centre, + Origin = Anchor.TopLeft, + Padding = new MarginPadding(3), + RelativePositionAxes = Axes.Both, + Y = inner_portion / 2, }, } }, From e97d027230362277c9a12dab6581d387cc91f9a8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 17 May 2023 16:13:03 +0900 Subject: [PATCH 17/69] Make `OnlinePlayPill` class `abstract` --- .../Screens/OnlinePlay/Lounge/Components/OnlinePlayPill.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/Lounge/Components/OnlinePlayPill.cs b/osu.Game/Screens/OnlinePlay/Lounge/Components/OnlinePlayPill.cs index 80e82c3856..1235e5234c 100644 --- a/osu.Game/Screens/OnlinePlay/Lounge/Components/OnlinePlayPill.cs +++ b/osu.Game/Screens/OnlinePlay/Lounge/Components/OnlinePlayPill.cs @@ -10,12 +10,12 @@ using osu.Game.Graphics.Containers; namespace osu.Game.Screens.OnlinePlay.Lounge.Components { - public partial class OnlinePlayPill : OnlinePlayComposite + public abstract partial class OnlinePlayPill : OnlinePlayComposite { protected PillContainer Pill; protected OsuTextFlowContainer TextFlow; - public OnlinePlayPill() + protected OnlinePlayPill() { AutoSizeAxes = Axes.Both; } From 730bc3a9616e03d129224704373273fe93fc293f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 17 May 2023 16:13:54 +0900 Subject: [PATCH 18/69] Apply NRT and simplify some remaining code --- .../OnlinePlay/Lounge/Components/MatchTypePill.cs | 3 +-- .../OnlinePlay/Lounge/Components/OnlinePlayPill.cs | 6 ++---- .../OnlinePlay/Lounge/Components/QueueModePill.cs | 3 +-- .../Lounge/Components/RoomSpecialCategoryPill.cs | 9 ++------- .../OnlinePlay/Lounge/Components/RoomStatusPill.cs | 6 ++---- 5 files changed, 8 insertions(+), 19 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/Lounge/Components/MatchTypePill.cs b/osu.Game/Screens/OnlinePlay/Lounge/Components/MatchTypePill.cs index e019de0f6f..35e0482f2b 100644 --- a/osu.Game/Screens/OnlinePlay/Lounge/Components/MatchTypePill.cs +++ b/osu.Game/Screens/OnlinePlay/Lounge/Components/MatchTypePill.cs @@ -20,8 +20,7 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components private void onMatchTypeChanged(ValueChangedEvent type) { - TextFlow.Clear(); - TextFlow.AddText(type.NewValue.GetLocalisableDescription()); + TextFlow.Text = type.NewValue.GetLocalisableDescription(); } } } diff --git a/osu.Game/Screens/OnlinePlay/Lounge/Components/OnlinePlayPill.cs b/osu.Game/Screens/OnlinePlay/Lounge/Components/OnlinePlayPill.cs index 1235e5234c..983fab8525 100644 --- a/osu.Game/Screens/OnlinePlay/Lounge/Components/OnlinePlayPill.cs +++ b/osu.Game/Screens/OnlinePlay/Lounge/Components/OnlinePlayPill.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Game.Graphics; @@ -12,8 +10,8 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components { public abstract partial class OnlinePlayPill : OnlinePlayComposite { - protected PillContainer Pill; - protected OsuTextFlowContainer TextFlow; + protected PillContainer Pill { get; private set; } = null!; + protected OsuTextFlowContainer TextFlow { get; private set; } = null!; protected OnlinePlayPill() { diff --git a/osu.Game/Screens/OnlinePlay/Lounge/Components/QueueModePill.cs b/osu.Game/Screens/OnlinePlay/Lounge/Components/QueueModePill.cs index e402653c2f..208c11c155 100644 --- a/osu.Game/Screens/OnlinePlay/Lounge/Components/QueueModePill.cs +++ b/osu.Game/Screens/OnlinePlay/Lounge/Components/QueueModePill.cs @@ -20,8 +20,7 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components private void onQueueModeChanged(ValueChangedEvent mode) { - TextFlow.Clear(); - TextFlow.AddText(mode.NewValue.GetLocalisableDescription()); + TextFlow.Text = mode.NewValue.GetLocalisableDescription(); } } } diff --git a/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomSpecialCategoryPill.cs b/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomSpecialCategoryPill.cs index b869ede9bd..e88624f877 100644 --- a/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomSpecialCategoryPill.cs +++ b/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomSpecialCategoryPill.cs @@ -19,18 +19,13 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components { base.LoadComplete(); - Pill.Background.Colour = colours.Pink; Pill.Background.Alpha = 1; TextFlow.Colour = Color4.Black; Category.BindValueChanged(c => { - TextFlow.Clear(); - TextFlow.AddText(c.NewValue.GetLocalisableDescription()); - - var backgroundColour = colours.ForRoomCategory(Category.Value); - if (backgroundColour != null) - Pill.Background.Colour = backgroundColour.Value; + TextFlow.Text = c.NewValue.GetLocalisableDescription(); + Pill.Background.Colour = colours.ForRoomCategory(c.NewValue) ?? colours.Pink; }, true); } } diff --git a/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomStatusPill.cs b/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomStatusPill.cs index f260d168a7..ab3d293db8 100644 --- a/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomStatusPill.cs +++ b/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomStatusPill.cs @@ -30,17 +30,15 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components FinishTransforms(true); TextFlow.Colour = Colour4.Black; + Pill.Background.Alpha = 1; } private void updateDisplay() { RoomStatus status = getDisplayStatus(); - Pill.Background.Alpha = 1; Pill.Background.FadeColour(status.GetAppropriateColour(colours), 100); - - TextFlow.Clear(); - TextFlow.AddText(status.Message); + TextFlow.Text = status.Message; } private RoomStatus getDisplayStatus() From ec2b9165d59a1ebdba6a2703cfa908f406ec931f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 17 May 2023 16:33:58 +0900 Subject: [PATCH 19/69] Adjust `BeatDivisorControl` test to show control mmuch larger --- osu.Game.Tests/Visual/Editing/TestSceneBeatDivisorControl.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneBeatDivisorControl.cs b/osu.Game.Tests/Visual/Editing/TestSceneBeatDivisorControl.cs index 56b16301be..c3d5ecac5c 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneBeatDivisorControl.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneBeatDivisorControl.cs @@ -42,7 +42,8 @@ namespace osu.Game.Tests.Visual.Editing { Anchor = Anchor.Centre, Origin = Anchor.Centre, - Size = new Vector2(90, 90) + Size = new Vector2(90, 90), + Scale = new Vector2(3), } }; }); From 87ff28b0220c40691eab57038f98f366b2601986 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 17 May 2023 16:34:10 +0900 Subject: [PATCH 20/69] Update beat divisor control to show ticks in more visually correct locations As proposed in https://github.com/ppy/osu/discussions/23527. --- .../Edit/Compose/Components/BeatDivisorControl.cs | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/BeatDivisorControl.cs b/osu.Game/Screens/Edit/Compose/Components/BeatDivisorControl.cs index 9f422d5aa9..3bfe81e6a7 100644 --- a/osu.Game/Screens/Edit/Compose/Components/BeatDivisorControl.cs +++ b/osu.Game/Screens/Edit/Compose/Components/BeatDivisorControl.cs @@ -410,6 +410,16 @@ namespace osu.Game.Screens.Edit.Compose.Components }); } + // Add a fake 1/1 at the end to give context. + AddInternal(new Tick(1) + { + Anchor = Anchor.CentreRight, + Origin = Anchor.Centre, + Depth = float.MaxValue, + Alpha = 0.05f, + Colour = BindableBeatDivisor.GetColourFor(1, colours), + }); + AddInternal(marker = new Marker()); CurrentNumber.ValueChanged += moveMarker; CurrentNumber.TriggerChange(); @@ -483,7 +493,7 @@ namespace osu.Game.Screens.Edit.Compose.Components OnUserChange(Current.Value); } - private float getMappedPosition(float divisor) => MathF.Pow((divisor - 1) / (beatDivisor.ValidDivisors.Value.Presets.Last() - 1), 0.90f); + private float getMappedPosition(float divisor) => 1 - 1 / divisor; private partial class Tick : Circle { From c8303d55cd72c2e152ad40393325f5ec887ff4f8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 23 May 2023 16:21:56 +0900 Subject: [PATCH 21/69] Adjust text and alignment --- .../Statistics/AccuracyHeatmap.cs | 72 ++++++++----------- 1 file changed, 30 insertions(+), 42 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Statistics/AccuracyHeatmap.cs b/osu.Game.Rulesets.Osu/Statistics/AccuracyHeatmap.cs index 9d9b3fb84a..5d2f6a14c7 100644 --- a/osu.Game.Rulesets.Osu/Statistics/AccuracyHeatmap.cs +++ b/osu.Game.Rulesets.Osu/Statistics/AccuracyHeatmap.cs @@ -57,6 +57,8 @@ namespace osu.Game.Rulesets.Osu.Statistics [BackgroundDependencyLoader] private void load() { + const float line_extension = 0.2f; + InternalChild = new Container { Anchor = Anchor.Centre, @@ -97,81 +99,67 @@ namespace osu.Game.Rulesets.Osu.Statistics RelativeSizeAxes = Axes.Both, Children = new Drawable[] { - new Box + new Circle { Anchor = Anchor.Centre, Origin = Anchor.Centre, - EdgeSmoothness = new Vector2(1), RelativeSizeAxes = Axes.Y, - Width = line_thickness / 2, // adjust for edgesmoothness - Height = MathF.Sqrt(2), + Width = line_thickness, + Height = inner_portion + line_extension, Rotation = -rotation * 2, - Alpha = 0.3f, + Alpha = 0.6f, }, - new Box + new Circle { Anchor = Anchor.Centre, Origin = Anchor.Centre, - EdgeSmoothness = new Vector2(1), RelativeSizeAxes = Axes.Y, - Width = line_thickness / 2, // adjust for edgesmoothness - Height = MathF.Sqrt(2), + Width = line_thickness, + Height = inner_portion + line_extension, }, new OsuSpriteText { - Text = "Next", + Text = "Overshoot", Anchor = Anchor.Centre, - Origin = Anchor.BottomRight, + Origin = Anchor.BottomCentre, Padding = new MarginPadding(3), RelativePositionAxes = Axes.Both, - Y = -inner_portion / 2, + Y = -(inner_portion + line_extension) / 2, }, new OsuSpriteText { - Text = "object", + Text = "Undershoot", Anchor = Anchor.Centre, - Origin = Anchor.BottomLeft, + Origin = Anchor.TopCentre, Padding = new MarginPadding(3), RelativePositionAxes = Axes.Both, - Y = -inner_portion / 2, + Y = (inner_portion + line_extension) / 2, }, - new OsuSpriteText + new Circle { - Text = "Last", Anchor = Anchor.Centre, - Origin = Anchor.TopRight, - Padding = new MarginPadding(3), + Origin = Anchor.TopCentre, RelativePositionAxes = Axes.Both, - Y = inner_portion / 2, + Y = -(inner_portion + line_extension) / 2, + Margin = new MarginPadding(-line_thickness / 2), + Width = line_thickness, + Height = 10, + Rotation = 45, }, - new OsuSpriteText + new Circle { - Text = "object", Anchor = Anchor.Centre, - Origin = Anchor.TopLeft, - Padding = new MarginPadding(3), + Origin = Anchor.TopCentre, RelativePositionAxes = Axes.Both, - Y = inner_portion / 2, - }, + Y = -(inner_portion + line_extension) / 2, + Margin = new MarginPadding(-line_thickness / 2), + Width = line_thickness, + Height = 10, + Rotation = -45, + } } }, }, - new Box - { - Anchor = Anchor.TopRight, - Origin = Anchor.TopRight, - Width = 10, - EdgeSmoothness = new Vector2(1), - Height = line_thickness / 2, // adjust for edgesmoothness - }, - new Box - { - Anchor = Anchor.TopRight, - Origin = Anchor.TopRight, - EdgeSmoothness = new Vector2(1), - Width = line_thickness / 2, // adjust for edgesmoothness - Height = 10, - } } }, bufferedGrid = new BufferedContainer(cachedFrameBuffer: true) From adf9a596b5fd4cb5e71519e79c990b371dbb7132 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 23 May 2023 17:58:10 +0900 Subject: [PATCH 22/69] Fix weird state when attempting to enter gameplay skin editor scene from multiplayer Closes https://github.com/ppy/osu/issues/23626. --- osu.Game/Overlays/SkinEditor/SkinEditorSceneLibrary.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/SkinEditor/SkinEditorSceneLibrary.cs b/osu.Game/Overlays/SkinEditor/SkinEditorSceneLibrary.cs index 61195d7175..9b021632cf 100644 --- a/osu.Game/Overlays/SkinEditor/SkinEditorSceneLibrary.cs +++ b/osu.Game/Overlays/SkinEditor/SkinEditorSceneLibrary.cs @@ -113,7 +113,7 @@ namespace osu.Game.Overlays.SkinEditor if (replayGeneratingMod != null) screen.Push(new PlayerLoader(() => new ReplayPlayer((beatmap, mods) => replayGeneratingMod.CreateScoreFromReplayData(beatmap, mods)))); - }, new[] { typeof(Player), typeof(SongSelect) }) + }, new[] { typeof(Player), typeof(PlaySongSelect) }) }, } }, From 6f4e2b37edfdf443f993caf679c061bb38909d38 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 23 May 2023 18:47:56 +0900 Subject: [PATCH 23/69] Add shadow to notifications and settings overlays to better distinguish from other overlays --- osu.Game/Overlays/NotificationOverlay.cs | 13 +++++++++++++ osu.Game/Overlays/SettingsPanel.cs | 13 +++++++++++++ 2 files changed, 26 insertions(+) diff --git a/osu.Game/Overlays/NotificationOverlay.cs b/osu.Game/Overlays/NotificationOverlay.cs index 71a4c58afd..4a69fb6240 100644 --- a/osu.Game/Overlays/NotificationOverlay.cs +++ b/osu.Game/Overlays/NotificationOverlay.cs @@ -5,9 +5,11 @@ using System.Linq; using osu.Framework.Allocation; using osu.Framework.Audio; using osu.Framework.Bindables; +using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Effects; using osu.Framework.Graphics.Shapes; using osu.Framework.Localisation; using osu.Framework.Logging; @@ -16,6 +18,7 @@ using osu.Game.Graphics.Containers; using osu.Game.Overlays.Notifications; using osu.Game.Resources.Localisation.Web; using osuTK; +using osuTK.Graphics; using NotificationsStrings = osu.Game.Localisation.NotificationsStrings; namespace osu.Game.Overlays @@ -72,6 +75,14 @@ namespace osu.Game.Overlays mainContent = new Container { RelativeSizeAxes = Axes.Both, + Masking = true, + EdgeEffect = new EdgeEffectParameters + { + Colour = Color4.Black.Opacity(0), + Type = EdgeEffectType.Shadow, + Radius = 10, + Hollow = true, + }, Children = new Drawable[] { new Box @@ -199,6 +210,7 @@ namespace osu.Game.Overlays this.MoveToX(0, TRANSITION_LENGTH, Easing.OutQuint); mainContent.FadeTo(1, TRANSITION_LENGTH, Easing.OutQuint); + mainContent.FadeEdgeEffectTo(0.4f, WaveContainer.APPEAR_DURATION, Easing.Out); toastTray.FlushAllToasts(); } @@ -211,6 +223,7 @@ namespace osu.Game.Overlays this.MoveToX(WIDTH, TRANSITION_LENGTH, Easing.OutQuint); mainContent.FadeTo(0, TRANSITION_LENGTH, Easing.OutQuint); + mainContent.FadeEdgeEffectTo(0, WaveContainer.DISAPPEAR_DURATION, Easing.In); } private void notificationClosed() => Schedule(() => diff --git a/osu.Game/Overlays/SettingsPanel.cs b/osu.Game/Overlays/SettingsPanel.cs index aefaccdb5d..382423eb57 100644 --- a/osu.Game/Overlays/SettingsPanel.cs +++ b/osu.Game/Overlays/SettingsPanel.cs @@ -10,15 +10,18 @@ using System.Threading.Tasks; using osuTK; using osu.Framework.Allocation; using osu.Framework.Bindables; +using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Effects; using osu.Framework.Graphics.Primitives; using osu.Framework.Graphics.Shapes; using osu.Framework.Input.Events; using osu.Game.Graphics.Containers; using osu.Game.Graphics.UserInterface; using osu.Game.Overlays.Settings; +using osuTK.Graphics; namespace osu.Game.Overlays { @@ -105,6 +108,13 @@ namespace osu.Game.Overlays Add(SectionsContainer = new SettingsSectionsContainer { Masking = true, + EdgeEffect = new EdgeEffectParameters + { + Colour = Color4.Black.Opacity(0), + Type = EdgeEffectType.Shadow, + Hollow = true, + Radius = 10 + }, RelativeSizeAxes = Axes.Both, ExpandableHeader = CreateHeader(), SelectedSection = { BindTarget = CurrentSection }, @@ -156,6 +166,8 @@ namespace osu.Game.Overlays ContentContainer.MoveToX(ExpandedPosition, TRANSITION_LENGTH, Easing.OutQuint); + SectionsContainer.FadeEdgeEffectTo(0.4f, WaveContainer.APPEAR_DURATION, Easing.Out); + // delay load enough to ensure it doesn't overlap with the initial animation. // this is done as there is still a brief stutter during load completion which is more visible if the transition is in progress. // the eventual goal would be to remove the need for this by splitting up load into smaller work pieces, or fixing the remaining @@ -175,6 +187,7 @@ namespace osu.Game.Overlays { base.PopOut(); + SectionsContainer.FadeEdgeEffectTo(0, WaveContainer.DISAPPEAR_DURATION, Easing.In); ContentContainer.MoveToX(-WIDTH + ExpandedPosition, TRANSITION_LENGTH, Easing.OutQuint); Sidebar?.MoveToX(-sidebar_width, TRANSITION_LENGTH, Easing.OutQuint); From 02d8e3a11e4d245f7b5587c59b4217418718d00a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 23 May 2023 18:48:10 +0900 Subject: [PATCH 24/69] Mark `FullscreenOverlay`'s shadow effect as `Hollow` to save on shader overhead --- osu.Game/Overlays/FullscreenOverlay.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Overlays/FullscreenOverlay.cs b/osu.Game/Overlays/FullscreenOverlay.cs index 2cc8354e50..007791387c 100644 --- a/osu.Game/Overlays/FullscreenOverlay.cs +++ b/osu.Game/Overlays/FullscreenOverlay.cs @@ -56,6 +56,7 @@ namespace osu.Game.Overlays { Colour = Color4.Black.Opacity(0), Type = EdgeEffectType.Shadow, + Hollow = true, Radius = 10 }; From ebda35c3c9a896861ec526bad90ebc2e154b3131 Mon Sep 17 00:00:00 2001 From: Johannes vd Berg Date: Tue, 23 May 2023 12:57:25 +0200 Subject: [PATCH 25/69] Add ghost ticks to exhibit current divisor on `BeatDivisorControl` --- .../Compose/Components/BeatDivisorControl.cs | 37 +++++++++++-------- 1 file changed, 22 insertions(+), 15 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/BeatDivisorControl.cs b/osu.Game/Screens/Edit/Compose/Components/BeatDivisorControl.cs index 3bfe81e6a7..431fb2f659 100644 --- a/osu.Game/Screens/Edit/Compose/Components/BeatDivisorControl.cs +++ b/osu.Game/Screens/Edit/Compose/Components/BeatDivisorControl.cs @@ -398,28 +398,25 @@ namespace osu.Game.Screens.Edit.Compose.Components ClearInternal(); CurrentNumber.ValueChanged -= moveMarker; - foreach (int divisor in beatDivisor.ValidDivisors.Value.Presets) + int largestDivisor = beatDivisor.ValidDivisors.Value.Presets.Max(); + for (int tickIndex = 0; tickIndex <= largestDivisor; tickIndex++) { - AddInternal(new Tick(divisor) + int divisor = largestDivisor; + foreach (int validDivisor in beatDivisor.ValidDivisors.Value.Presets) + { + if (divisor > validDivisor && (tickIndex * validDivisor) % largestDivisor == 0) + divisor = validDivisor; + } + bool solidTick = divisor * (largestDivisor - tickIndex) == largestDivisor; + AddInternal(new Tick(solidTick, divisor) { Anchor = Anchor.CentreLeft, Origin = Anchor.Centre, RelativePositionAxes = Axes.Both, Colour = BindableBeatDivisor.GetColourFor(divisor, colours), - X = getMappedPosition(divisor), + X = tickIndex / (float)largestDivisor, }); } - - // Add a fake 1/1 at the end to give context. - AddInternal(new Tick(1) - { - Anchor = Anchor.CentreRight, - Origin = Anchor.Centre, - Depth = float.MaxValue, - Alpha = 0.05f, - Colour = BindableBeatDivisor.GetColourFor(1, colours), - }); - AddInternal(marker = new Marker()); CurrentNumber.ValueChanged += moveMarker; CurrentNumber.TriggerChange(); @@ -428,6 +425,12 @@ namespace osu.Game.Screens.Edit.Compose.Components private void moveMarker(ValueChangedEvent divisor) { marker.MoveToX(getMappedPosition(divisor.NewValue), 100, Easing.OutQuint); + + foreach (Tick child in InternalChildren.OfType()) + { + float newAlpha = child.Solid ? 1f : divisor.NewValue % child.Divisor == 0 ? 0.2f : 0f; + child.FadeTo(newAlpha); + } } protected override void UpdateValue(float value) @@ -497,8 +500,12 @@ namespace osu.Game.Screens.Edit.Compose.Components private partial class Tick : Circle { - public Tick(int divisor) + public bool Solid; + public int Divisor; + public Tick(bool solid, int divisor) { + Solid = solid; + Divisor = divisor; Size = new Vector2(6f, 12) * BindableBeatDivisor.GetSize(divisor); InternalChild = new Box { RelativeSizeAxes = Axes.Both }; } From 37a796306d65a5c940d8cd715021083bdef9990a Mon Sep 17 00:00:00 2001 From: Gyoshi Date: Tue, 23 May 2023 14:30:35 +0200 Subject: [PATCH 26/69] Small format & comment --- .../Screens/Edit/Compose/Components/BeatDivisorControl.cs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/BeatDivisorControl.cs b/osu.Game/Screens/Edit/Compose/Components/BeatDivisorControl.cs index 431fb2f659..04cc736e7d 100644 --- a/osu.Game/Screens/Edit/Compose/Components/BeatDivisorControl.cs +++ b/osu.Game/Screens/Edit/Compose/Components/BeatDivisorControl.cs @@ -402,13 +402,15 @@ namespace osu.Game.Screens.Edit.Compose.Components for (int tickIndex = 0; tickIndex <= largestDivisor; tickIndex++) { int divisor = largestDivisor; + // Find lowest divisor that the tick fits into foreach (int validDivisor in beatDivisor.ValidDivisors.Value.Presets) { if (divisor > validDivisor && (tickIndex * validDivisor) % largestDivisor == 0) divisor = validDivisor; } - bool solidTick = divisor * (largestDivisor - tickIndex) == largestDivisor; - AddInternal(new Tick(solidTick, divisor) + + bool isSolidTick = divisor * (largestDivisor - tickIndex) == largestDivisor; + AddInternal(new Tick(isSolidTick, divisor) { Anchor = Anchor.CentreLeft, Origin = Anchor.Centre, @@ -417,6 +419,7 @@ namespace osu.Game.Screens.Edit.Compose.Components X = tickIndex / (float)largestDivisor, }); } + AddInternal(marker = new Marker()); CurrentNumber.ValueChanged += moveMarker; CurrentNumber.TriggerChange(); From 1b32370c6a489ec618e7022b197f3c82518a183a Mon Sep 17 00:00:00 2001 From: Gyoshi Date: Tue, 23 May 2023 15:05:38 +0200 Subject: [PATCH 27/69] Remove duplicate code by making `GetDivisorForBeatIndex` method more general --- osu.Game/Screens/Edit/BindableBeatDivisor.cs | 7 +++++-- .../Screens/Edit/Compose/Components/BeatDivisorControl.cs | 8 +------- 2 files changed, 6 insertions(+), 9 deletions(-) diff --git a/osu.Game/Screens/Edit/BindableBeatDivisor.cs b/osu.Game/Screens/Edit/BindableBeatDivisor.cs index aa8e202e22..f1b97571bd 100644 --- a/osu.Game/Screens/Edit/BindableBeatDivisor.cs +++ b/osu.Game/Screens/Edit/BindableBeatDivisor.cs @@ -154,12 +154,15 @@ namespace osu.Game.Screens.Edit /// /// The 0-based beat index. /// The beat divisor. + /// The list of valid divisors which can be chosen from. Assumes ordered from low to high. /// The applicable divisor. - public static int GetDivisorForBeatIndex(int index, int beatDivisor) + public static int GetDivisorForBeatIndex(int index, int beatDivisor, int[] validDivisors = null) { + validDivisors ??= PREDEFINED_DIVISORS; + int beat = index % beatDivisor; - foreach (int divisor in PREDEFINED_DIVISORS) + foreach (int divisor in validDivisors) { if ((beat * divisor) % beatDivisor == 0) return divisor; diff --git a/osu.Game/Screens/Edit/Compose/Components/BeatDivisorControl.cs b/osu.Game/Screens/Edit/Compose/Components/BeatDivisorControl.cs index 04cc736e7d..2dd5791943 100644 --- a/osu.Game/Screens/Edit/Compose/Components/BeatDivisorControl.cs +++ b/osu.Game/Screens/Edit/Compose/Components/BeatDivisorControl.cs @@ -401,13 +401,7 @@ namespace osu.Game.Screens.Edit.Compose.Components int largestDivisor = beatDivisor.ValidDivisors.Value.Presets.Max(); for (int tickIndex = 0; tickIndex <= largestDivisor; tickIndex++) { - int divisor = largestDivisor; - // Find lowest divisor that the tick fits into - foreach (int validDivisor in beatDivisor.ValidDivisors.Value.Presets) - { - if (divisor > validDivisor && (tickIndex * validDivisor) % largestDivisor == 0) - divisor = validDivisor; - } + int divisor = BindableBeatDivisor.GetDivisorForBeatIndex(tickIndex, largestDivisor, (int[])beatDivisor.ValidDivisors.Value.Presets); bool isSolidTick = divisor * (largestDivisor - tickIndex) == largestDivisor; AddInternal(new Tick(isSolidTick, divisor) From 7b1e8ede54498bf66d54965769220dc626240c1f Mon Sep 17 00:00:00 2001 From: Gyoshi Date: Tue, 23 May 2023 15:11:27 +0200 Subject: [PATCH 28/69] Small format --- osu.Game/Screens/Edit/Compose/Components/BeatDivisorControl.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/BeatDivisorControl.cs b/osu.Game/Screens/Edit/Compose/Components/BeatDivisorControl.cs index 2dd5791943..1af7d25dcd 100644 --- a/osu.Game/Screens/Edit/Compose/Components/BeatDivisorControl.cs +++ b/osu.Game/Screens/Edit/Compose/Components/BeatDivisorControl.cs @@ -402,8 +402,8 @@ namespace osu.Game.Screens.Edit.Compose.Components for (int tickIndex = 0; tickIndex <= largestDivisor; tickIndex++) { int divisor = BindableBeatDivisor.GetDivisorForBeatIndex(tickIndex, largestDivisor, (int[])beatDivisor.ValidDivisors.Value.Presets); - bool isSolidTick = divisor * (largestDivisor - tickIndex) == largestDivisor; + AddInternal(new Tick(isSolidTick, divisor) { Anchor = Anchor.CentreLeft, From 921d7e4d89351a50fe6ec4e65f1ec01b3d650a36 Mon Sep 17 00:00:00 2001 From: Gyoshi Date: Tue, 23 May 2023 16:46:08 +0200 Subject: [PATCH 29/69] More fitting tests for new layout --- .../Editing/TestSceneBeatDivisorControl.cs | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneBeatDivisorControl.cs b/osu.Game.Tests/Visual/Editing/TestSceneBeatDivisorControl.cs index c3d5ecac5c..b74e9f436b 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneBeatDivisorControl.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneBeatDivisorControl.cs @@ -65,17 +65,24 @@ namespace osu.Game.Tests.Visual.Editing InputManager.MoveMouseTo(tickMarkerHead.ScreenSpaceDrawQuad.Centre); InputManager.PressButton(MouseButton.Left); }); - AddStep("move to 8 and release", () => + AddStep("move to 1", () => InputManager.MoveMouseTo(getPositionForDivisor(1))); + AddStep("move to 16 and release", () => { - InputManager.MoveMouseTo(tickSliderBar.ScreenSpaceDrawQuad.Centre); + InputManager.MoveMouseTo(getPositionForDivisor(16)); InputManager.ReleaseButton(MouseButton.Left); }); - AddAssert("divisor is 8", () => bindableBeatDivisor.Value == 8); + AddAssert("divisor is 16", () => bindableBeatDivisor.Value == 16); AddStep("hold marker", () => InputManager.PressButton(MouseButton.Left)); - AddStep("move to 16", () => InputManager.MoveMouseTo(getPositionForDivisor(16))); - AddStep("move to ~10 and release", () => + AddStep("move to ~6 and release", () => + { + InputManager.MoveMouseTo(getPositionForDivisor(6)); + InputManager.ReleaseButton(MouseButton.Left); + }); + AddAssert("divisor clamped to 8", () => bindableBeatDivisor.Value == 8); + AddStep("move to ~10 and click", () => { InputManager.MoveMouseTo(getPositionForDivisor(10)); + InputManager.PressButton(MouseButton.Left); InputManager.ReleaseButton(MouseButton.Left); }); AddAssert("divisor clamped to 8", () => bindableBeatDivisor.Value == 8); From fa00f8b92a56db6669835efa70131e69308c9f67 Mon Sep 17 00:00:00 2001 From: Gyoshi Date: Tue, 23 May 2023 16:46:40 +0200 Subject: [PATCH 30/69] replace manual code with existing method --- .../Visual/Editing/TestSceneBeatDivisorControl.cs | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneBeatDivisorControl.cs b/osu.Game.Tests/Visual/Editing/TestSceneBeatDivisorControl.cs index b74e9f436b..a4b36ef93a 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneBeatDivisorControl.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneBeatDivisorControl.cs @@ -90,12 +90,11 @@ namespace osu.Game.Tests.Visual.Editing private Vector2 getPositionForDivisor(int divisor) { - float relativePosition = (float)Math.Clamp(divisor, 0, 16) / 16; - var sliderDrawQuad = tickSliderBar.ScreenSpaceDrawQuad; - return new Vector2( - sliderDrawQuad.TopLeft.X + sliderDrawQuad.Width * relativePosition, - sliderDrawQuad.Centre.Y - ); + float localX = 1 - 1 / (float)divisor; + return tickSliderBar.ToScreenSpace(new Vector2( + localX, + 0.5f + )); } [Test] From b5f8093941873b6aa95bc62a61f7eb5d29e9ce52 Mon Sep 17 00:00:00 2001 From: Gyoshi Date: Tue, 23 May 2023 17:59:07 +0200 Subject: [PATCH 31/69] Use `RangePadding` to align mouse with slider --- osu.Game.Tests/Visual/Editing/TestSceneBeatDivisorControl.cs | 4 ++-- .../Screens/Edit/Compose/Components/BeatDivisorControl.cs | 3 ++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneBeatDivisorControl.cs b/osu.Game.Tests/Visual/Editing/TestSceneBeatDivisorControl.cs index a4b36ef93a..353acfa4ba 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneBeatDivisorControl.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneBeatDivisorControl.cs @@ -90,10 +90,10 @@ namespace osu.Game.Tests.Visual.Editing private Vector2 getPositionForDivisor(int divisor) { - float localX = 1 - 1 / (float)divisor; + float localX = (1 - 1 / (float)divisor) * tickSliderBar.UsableWidth + tickSliderBar.RangePadding; return tickSliderBar.ToScreenSpace(new Vector2( localX, - 0.5f + tickSliderBar.DrawHeight / 2 )); } diff --git a/osu.Game/Screens/Edit/Compose/Components/BeatDivisorControl.cs b/osu.Game/Screens/Edit/Compose/Components/BeatDivisorControl.cs index 3bfe81e6a7..cec9806e94 100644 --- a/osu.Game/Screens/Edit/Compose/Components/BeatDivisorControl.cs +++ b/osu.Game/Screens/Edit/Compose/Components/BeatDivisorControl.cs @@ -383,7 +383,8 @@ namespace osu.Game.Screens.Edit.Compose.Components { CurrentNumber.BindTo(this.beatDivisor = beatDivisor); - Padding = new MarginPadding { Horizontal = 5 }; + RangePadding = 5; + Padding = new MarginPadding { Horizontal = RangePadding }; } protected override void LoadComplete() From c5ef3ae1811f9216c2023c92de67ae2d548f4a66 Mon Sep 17 00:00:00 2001 From: Gyoshi Date: Tue, 23 May 2023 18:44:20 +0200 Subject: [PATCH 32/69] Code styling --- .../Screens/Edit/Compose/Components/BeatDivisorControl.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/BeatDivisorControl.cs b/osu.Game/Screens/Edit/Compose/Components/BeatDivisorControl.cs index 1af7d25dcd..4c3c9872a0 100644 --- a/osu.Game/Screens/Edit/Compose/Components/BeatDivisorControl.cs +++ b/osu.Game/Screens/Edit/Compose/Components/BeatDivisorControl.cs @@ -425,7 +425,7 @@ namespace osu.Game.Screens.Edit.Compose.Components foreach (Tick child in InternalChildren.OfType()) { - float newAlpha = child.Solid ? 1f : divisor.NewValue % child.Divisor == 0 ? 0.2f : 0f; + float newAlpha = child.IsSolid ? 1f : divisor.NewValue % child.Divisor == 0 ? 0.2f : 0f; child.FadeTo(newAlpha); } } @@ -497,11 +497,11 @@ namespace osu.Game.Screens.Edit.Compose.Components private partial class Tick : Circle { - public bool Solid; + public bool IsSolid; public int Divisor; - public Tick(bool solid, int divisor) + public Tick(bool isSolid, int divisor) { - Solid = solid; + IsSolid = isSolid; Divisor = divisor; Size = new Vector2(6f, 12) * BindableBeatDivisor.GetSize(divisor); InternalChild = new Box { RelativeSizeAxes = Axes.Both }; From 7fe19d1992633fc13e3cf68c35fb21e3734d39dd Mon Sep 17 00:00:00 2001 From: Gyoshi Date: Tue, 23 May 2023 18:45:49 +0200 Subject: [PATCH 33/69] `Last` instead of `Max` divisor to match code elsewhere --- osu.Game/Screens/Edit/Compose/Components/BeatDivisorControl.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/BeatDivisorControl.cs b/osu.Game/Screens/Edit/Compose/Components/BeatDivisorControl.cs index 4c3c9872a0..4982ab9ef2 100644 --- a/osu.Game/Screens/Edit/Compose/Components/BeatDivisorControl.cs +++ b/osu.Game/Screens/Edit/Compose/Components/BeatDivisorControl.cs @@ -398,7 +398,7 @@ namespace osu.Game.Screens.Edit.Compose.Components ClearInternal(); CurrentNumber.ValueChanged -= moveMarker; - int largestDivisor = beatDivisor.ValidDivisors.Value.Presets.Max(); + int largestDivisor = beatDivisor.ValidDivisors.Value.Presets.Last(); for (int tickIndex = 0; tickIndex <= largestDivisor; tickIndex++) { int divisor = BindableBeatDivisor.GetDivisorForBeatIndex(tickIndex, largestDivisor, (int[])beatDivisor.ValidDivisors.Value.Presets); From 067328233cf5715e8ce00936a6ce6bbe1b9795cd Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Tue, 23 May 2023 13:14:57 -0700 Subject: [PATCH 34/69] Remove `OsuScreen.ApplyLogoArrivingDefaults()` --- osu.Game/Screens/OsuScreen.cs | 24 +++++++----------------- 1 file changed, 7 insertions(+), 17 deletions(-) diff --git a/osu.Game/Screens/OsuScreen.cs b/osu.Game/Screens/OsuScreen.cs index bc4cc2b00f..9c098794a6 100644 --- a/osu.Game/Screens/OsuScreen.cs +++ b/osu.Game/Screens/OsuScreen.cs @@ -233,7 +233,13 @@ namespace osu.Game.Screens /// protected virtual void LogoArriving(OsuLogo logo, bool resuming) { - ApplyLogoArrivingDefaults(logo); + logo.Action = null; + logo.FadeOut(300, Easing.OutQuint); + logo.Anchor = Anchor.TopLeft; + logo.Origin = Anchor.Centre; + logo.RelativePositionAxes = Axes.Both; + logo.Triangles = true; + logo.Ripple = true; } private void applyArrivingDefaults(bool isResuming) @@ -244,22 +250,6 @@ namespace osu.Game.Screens }, true); } - /// - /// Applies default animations to an arriving logo. - /// Todo: This should not exist. - /// - /// The logo to apply animations to. - public static void ApplyLogoArrivingDefaults(OsuLogo logo) - { - logo.Action = null; - logo.FadeOut(300, Easing.OutQuint); - logo.Anchor = Anchor.TopLeft; - logo.Origin = Anchor.Centre; - logo.RelativePositionAxes = Axes.Both; - logo.Triangles = true; - logo.Ripple = true; - } - private void onExitingLogo() { logo?.AppendAnimatingAction(() => LogoExiting(logo), false); From e5451d1d79c7cceb3479515b8611c0954706f95c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 24 May 2023 12:38:27 +0900 Subject: [PATCH 35/69] Centralise definition of overlay shadow opacity and reduce slightly --- osu.Game/Graphics/Containers/WaveContainer.cs | 1 + osu.Game/Overlays/FullscreenOverlay.cs | 2 +- osu.Game/Overlays/NotificationOverlay.cs | 2 +- osu.Game/Overlays/SettingsPanel.cs | 3 ++- 4 files changed, 5 insertions(+), 3 deletions(-) diff --git a/osu.Game/Graphics/Containers/WaveContainer.cs b/osu.Game/Graphics/Containers/WaveContainer.cs index 952ef3f182..05a666721a 100644 --- a/osu.Game/Graphics/Containers/WaveContainer.cs +++ b/osu.Game/Graphics/Containers/WaveContainer.cs @@ -17,6 +17,7 @@ namespace osu.Game.Graphics.Containers { public const float APPEAR_DURATION = 800; public const float DISAPPEAR_DURATION = 500; + public const float SHADOW_OPACITY = 0.2f; private const Easing easing_show = Easing.OutSine; private const Easing easing_hide = Easing.InSine; diff --git a/osu.Game/Overlays/FullscreenOverlay.cs b/osu.Game/Overlays/FullscreenOverlay.cs index 007791387c..032821f215 100644 --- a/osu.Game/Overlays/FullscreenOverlay.cs +++ b/osu.Game/Overlays/FullscreenOverlay.cs @@ -102,7 +102,7 @@ namespace osu.Game.Overlays protected override void PopIn() { base.PopIn(); - FadeEdgeEffectTo(0.4f, WaveContainer.APPEAR_DURATION, Easing.Out); + FadeEdgeEffectTo(WaveContainer.SHADOW_OPACITY, WaveContainer.APPEAR_DURATION, Easing.Out); } protected override void PopOut() diff --git a/osu.Game/Overlays/NotificationOverlay.cs b/osu.Game/Overlays/NotificationOverlay.cs index 4a69fb6240..f2eefb6e4b 100644 --- a/osu.Game/Overlays/NotificationOverlay.cs +++ b/osu.Game/Overlays/NotificationOverlay.cs @@ -210,7 +210,7 @@ namespace osu.Game.Overlays this.MoveToX(0, TRANSITION_LENGTH, Easing.OutQuint); mainContent.FadeTo(1, TRANSITION_LENGTH, Easing.OutQuint); - mainContent.FadeEdgeEffectTo(0.4f, WaveContainer.APPEAR_DURATION, Easing.Out); + mainContent.FadeEdgeEffectTo(WaveContainer.SHADOW_OPACITY, WaveContainer.APPEAR_DURATION, Easing.Out); toastTray.FlushAllToasts(); } diff --git a/osu.Game/Overlays/SettingsPanel.cs b/osu.Game/Overlays/SettingsPanel.cs index 382423eb57..d571557993 100644 --- a/osu.Game/Overlays/SettingsPanel.cs +++ b/osu.Game/Overlays/SettingsPanel.cs @@ -115,6 +115,7 @@ namespace osu.Game.Overlays Hollow = true, Radius = 10 }, + MaskingSmoothness = 0, RelativeSizeAxes = Axes.Both, ExpandableHeader = CreateHeader(), SelectedSection = { BindTarget = CurrentSection }, @@ -166,7 +167,7 @@ namespace osu.Game.Overlays ContentContainer.MoveToX(ExpandedPosition, TRANSITION_LENGTH, Easing.OutQuint); - SectionsContainer.FadeEdgeEffectTo(0.4f, WaveContainer.APPEAR_DURATION, Easing.Out); + SectionsContainer.FadeEdgeEffectTo(WaveContainer.SHADOW_OPACITY, WaveContainer.APPEAR_DURATION, Easing.Out); // delay load enough to ensure it doesn't overlap with the initial animation. // this is done as there is still a brief stutter during load completion which is more visible if the transition is in progress. From 561b759bf980216beca1ff2639da1018bc05c742 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 24 May 2023 13:49:29 +0900 Subject: [PATCH 36/69] Tidy up implementation and ensure non-solid ticks start at zero alpha --- .../Compose/Components/BeatDivisorControl.cs | 21 ++++++++++++------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/BeatDivisorControl.cs b/osu.Game/Screens/Edit/Compose/Components/BeatDivisorControl.cs index 07330a6e10..432c5ea280 100644 --- a/osu.Game/Screens/Edit/Compose/Components/BeatDivisorControl.cs +++ b/osu.Game/Screens/Edit/Compose/Components/BeatDivisorControl.cs @@ -400,12 +400,13 @@ namespace osu.Game.Screens.Edit.Compose.Components CurrentNumber.ValueChanged -= moveMarker; int largestDivisor = beatDivisor.ValidDivisors.Value.Presets.Last(); + for (int tickIndex = 0; tickIndex <= largestDivisor; tickIndex++) { int divisor = BindableBeatDivisor.GetDivisorForBeatIndex(tickIndex, largestDivisor, (int[])beatDivisor.ValidDivisors.Value.Presets); bool isSolidTick = divisor * (largestDivisor - tickIndex) == largestDivisor; - AddInternal(new Tick(isSolidTick, divisor) + AddInternal(new Tick(divisor, isSolidTick) { Anchor = Anchor.CentreLeft, Origin = Anchor.Centre, @@ -424,10 +425,9 @@ namespace osu.Game.Screens.Edit.Compose.Components { marker.MoveToX(getMappedPosition(divisor.NewValue), 100, Easing.OutQuint); - foreach (Tick child in InternalChildren.OfType()) + foreach (Tick tick in InternalChildren.OfType().Where(t => !t.AlwaysDisplayed)) { - float newAlpha = child.IsSolid ? 1f : divisor.NewValue % child.Divisor == 0 ? 0.2f : 0f; - child.FadeTo(newAlpha); + tick.FadeTo(divisor.NewValue % tick.Divisor == 0 ? 0.2f : 0f, 100, Easing.OutQuint); } } @@ -498,13 +498,18 @@ namespace osu.Game.Screens.Edit.Compose.Components private partial class Tick : Circle { - public bool IsSolid; - public int Divisor; - public Tick(bool isSolid, int divisor) + public readonly bool AlwaysDisplayed; + + public readonly int Divisor; + + public Tick(int divisor, bool alwaysDisplayed) { - IsSolid = isSolid; + AlwaysDisplayed = alwaysDisplayed; Divisor = divisor; + Size = new Vector2(6f, 12) * BindableBeatDivisor.GetSize(divisor); + Alpha = alwaysDisplayed ? 1 : 0; + InternalChild = new Box { RelativeSizeAxes = Axes.Both }; } } From e68ba6366c88554db3bee4332be5f1563e4257c1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 24 May 2023 14:04:10 +0900 Subject: [PATCH 37/69] Update new usages of "soft" to use the new constant --- .../Editor/TestSceneSliderSplitting.cs | 2 +- .../TestSceneDrumSampleTriggerSource.cs | 24 +++++++++---------- .../TestSceneHitObjectSampleAdjustments.cs | 10 ++++---- .../TestSceneGameplaySampleTriggerSource.cs | 2 +- 4 files changed, 19 insertions(+), 19 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderSplitting.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderSplitting.cs index a104433ea9..605771fb20 100644 --- a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderSplitting.cs +++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderSplitting.cs @@ -181,7 +181,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor { if (slider is null) return; - sample = new HitSampleInfo("hitwhistle", "soft", volume: 70); + sample = new HitSampleInfo("hitwhistle", HitSampleInfo.BANK_SOFT, volume: 70); slider.Samples.Add(sample.With()); }); diff --git a/osu.Game.Rulesets.Taiko.Tests/TestSceneDrumSampleTriggerSource.cs b/osu.Game.Rulesets.Taiko.Tests/TestSceneDrumSampleTriggerSource.cs index 74da69e3eb..287d90b406 100644 --- a/osu.Game.Rulesets.Taiko.Tests/TestSceneDrumSampleTriggerSource.cs +++ b/osu.Game.Rulesets.Taiko.Tests/TestSceneDrumSampleTriggerSource.cs @@ -91,7 +91,7 @@ namespace osu.Game.Rulesets.Taiko.Tests StartTime = 100, Samples = new List { - new HitSampleInfo(HitSampleInfo.HIT_NORMAL, "soft") + new HitSampleInfo(HitSampleInfo.HIT_NORMAL, HitSampleInfo.BANK_SOFT) } }; hit.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty()); @@ -100,13 +100,13 @@ namespace osu.Game.Rulesets.Taiko.Tests }); AddAssert("most valid object is hit", () => triggerSource.GetMostValidObject(), Is.InstanceOf); - checkSound(HitType.Centre, HitSampleInfo.HIT_NORMAL, "soft"); - checkSound(HitType.Rim, HitSampleInfo.HIT_CLAP, "soft"); + checkSound(HitType.Centre, HitSampleInfo.HIT_NORMAL, HitSampleInfo.BANK_SOFT); + checkSound(HitType.Rim, HitSampleInfo.HIT_CLAP, HitSampleInfo.BANK_SOFT); AddStep("seek past hit", () => manualClock.CurrentTime = 200); AddAssert("most valid object is hit", () => triggerSource.GetMostValidObject(), Is.InstanceOf); - checkSound(HitType.Centre, HitSampleInfo.HIT_NORMAL, "soft"); - checkSound(HitType.Rim, HitSampleInfo.HIT_CLAP, "soft"); + checkSound(HitType.Centre, HitSampleInfo.HIT_NORMAL, HitSampleInfo.BANK_SOFT); + checkSound(HitType.Rim, HitSampleInfo.HIT_CLAP, HitSampleInfo.BANK_SOFT); } [Test] @@ -183,7 +183,7 @@ namespace osu.Game.Rulesets.Taiko.Tests EndTime = 1100, Samples = new List { - new HitSampleInfo(HitSampleInfo.HIT_NORMAL, "soft") + new HitSampleInfo(HitSampleInfo.HIT_NORMAL, HitSampleInfo.BANK_SOFT) } }; drumRoll.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty()); @@ -192,18 +192,18 @@ namespace osu.Game.Rulesets.Taiko.Tests }); AddAssert("most valid object is drum roll tick", () => triggerSource.GetMostValidObject(), Is.InstanceOf); - checkSound(HitType.Centre, HitSampleInfo.HIT_NORMAL, "soft"); - checkSound(HitType.Rim, HitSampleInfo.HIT_CLAP, "soft"); + checkSound(HitType.Centre, HitSampleInfo.HIT_NORMAL, HitSampleInfo.BANK_SOFT); + checkSound(HitType.Rim, HitSampleInfo.HIT_CLAP, HitSampleInfo.BANK_SOFT); AddStep("seek to middle of drum roll", () => manualClock.CurrentTime = 600); AddAssert("most valid object is drum roll tick", () => triggerSource.GetMostValidObject(), Is.InstanceOf); - checkSound(HitType.Centre, HitSampleInfo.HIT_NORMAL, "soft"); - checkSound(HitType.Rim, HitSampleInfo.HIT_CLAP, "soft"); + checkSound(HitType.Centre, HitSampleInfo.HIT_NORMAL, HitSampleInfo.BANK_SOFT); + checkSound(HitType.Rim, HitSampleInfo.HIT_CLAP, HitSampleInfo.BANK_SOFT); AddStep("seek past drum roll", () => manualClock.CurrentTime = 1200); AddAssert("most valid object is drum roll", () => triggerSource.GetMostValidObject(), Is.InstanceOf); - checkSound(HitType.Centre, HitSampleInfo.HIT_NORMAL, "soft"); - checkSound(HitType.Rim, HitSampleInfo.HIT_CLAP, "soft"); + checkSound(HitType.Centre, HitSampleInfo.HIT_NORMAL, HitSampleInfo.BANK_SOFT); + checkSound(HitType.Rim, HitSampleInfo.HIT_CLAP, HitSampleInfo.BANK_SOFT); } [Test] diff --git a/osu.Game.Tests/Visual/Editing/TestSceneHitObjectSampleAdjustments.cs b/osu.Game.Tests/Visual/Editing/TestSceneHitObjectSampleAdjustments.cs index 0581ff269c..eb39221211 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneHitObjectSampleAdjustments.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneHitObjectSampleAdjustments.cs @@ -52,7 +52,7 @@ namespace osu.Game.Tests.Visual.Editing Position = (OsuPlayfield.BASE_SIZE + new Vector2(100, 0)) / 2, Samples = new List { - new HitSampleInfo(HitSampleInfo.HIT_NORMAL, "soft", volume: 60) + new HitSampleInfo(HitSampleInfo.HIT_NORMAL, HitSampleInfo.BANK_SOFT, volume: 60) } }); }); @@ -67,14 +67,14 @@ namespace osu.Game.Tests.Visual.Editing hitObjectHasSampleBank(0, "normal"); hitObjectHasSamples(0, HitSampleInfo.HIT_NORMAL, HitSampleInfo.HIT_CLAP); - hitObjectHasSampleBank(1, "soft"); + hitObjectHasSampleBank(1, HitSampleInfo.BANK_SOFT); hitObjectHasSamples(1, HitSampleInfo.HIT_NORMAL, HitSampleInfo.HIT_CLAP); AddStep("remove clap addition", () => InputManager.Key(Key.R)); hitObjectHasSampleBank(0, "normal"); hitObjectHasSamples(0, HitSampleInfo.HIT_NORMAL); - hitObjectHasSampleBank(1, "soft"); + hitObjectHasSampleBank(1, HitSampleInfo.BANK_SOFT); hitObjectHasSamples(1, HitSampleInfo.HIT_NORMAL); } @@ -113,7 +113,7 @@ namespace osu.Game.Tests.Visual.Editing public void TestUndo() { clickSamplePiece(1); - samplePopoverHasSingleBank("soft"); + samplePopoverHasSingleBank(HitSampleInfo.BANK_SOFT); samplePopoverHasSingleVolume(60); setVolumeViaPopover(90); @@ -178,7 +178,7 @@ namespace osu.Game.Tests.Visual.Editing { for (int i = 0; i < h.Samples.Count; i++) { - h.Samples[i] = h.Samples[i].With(newBank: "soft"); + h.Samples[i] = h.Samples[i].With(newBank: HitSampleInfo.BANK_SOFT); } } }); diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneGameplaySampleTriggerSource.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneGameplaySampleTriggerSource.cs index bf69da8c12..e52ec6f8cc 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneGameplaySampleTriggerSource.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneGameplaySampleTriggerSource.cs @@ -83,7 +83,7 @@ namespace osu.Game.Tests.Visual.Gameplay { StartTime = t += spacing, Path = new SliderPath(PathType.Linear, new[] { Vector2.Zero, Vector2.UnitY * 200 }), - Samples = new[] { new HitSampleInfo(HitSampleInfo.HIT_WHISTLE, "soft") }, + Samples = new[] { new HitSampleInfo(HitSampleInfo.HIT_WHISTLE, HitSampleInfo.BANK_SOFT) }, }, }); From a9ba16a2be5d8355248d658ddb90867799dc86ba Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 24 May 2023 14:20:38 +0900 Subject: [PATCH 38/69] Update to support non-control-point sample changes --- .../Components/ComposeBlueprintContainer.cs | 4 ++-- .../Components/EditorSelectionHandler.cs | 23 ++++--------------- 2 files changed, 6 insertions(+), 21 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs index b4f2236847..3f3b4ad327 100644 --- a/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs @@ -199,12 +199,12 @@ namespace osu.Game.Screens.Edit.Compose.Components private void bankChanged(string bankName, TernaryState state) { - if (currentPlacement == null) return; + if (CurrentPlacement == null) return; switch (state) { case TernaryState.True: - currentPlacement.HitObject.SampleControlPoint.SampleBank = bankName; + CurrentPlacement.HitObject.Samples = CurrentPlacement.HitObject.Samples.Select(s => s.With(newBank: bankName)).ToList(); break; } } diff --git a/osu.Game/Screens/Edit/Compose/Components/EditorSelectionHandler.cs b/osu.Game/Screens/Edit/Compose/Components/EditorSelectionHandler.cs index 5d2eb33d8d..fa3a4cddaa 100644 --- a/osu.Game/Screens/Edit/Compose/Components/EditorSelectionHandler.cs +++ b/osu.Game/Screens/Edit/Compose/Components/EditorSelectionHandler.cs @@ -81,7 +81,7 @@ namespace osu.Game.Screens.Edit.Compose.Components { // Never remove a sample bank. // These are basically radio buttons, not toggles. - if (SelectedItems.All(h => h.SampleControlPoint.SampleBank == bankName)) + if (SelectedItems.All(h => h.Samples.All(s => s.Bank == bankName))) bindable.Value = TernaryState.True; } @@ -167,7 +167,7 @@ namespace osu.Game.Screens.Edit.Compose.Components foreach ((string bankName, var bindable) in SelectionBankStates) { - bindable.Value = GetStateFromSelection(SelectedItems, h => h.SampleControlPoint.SampleBank == bankName); + bindable.Value = GetStateFromSelection(SelectedItems, h => h.Samples.All(s => s.Bank == bankName)); } } @@ -183,25 +183,10 @@ namespace osu.Game.Screens.Edit.Compose.Components { EditorBeatmap.PerformOnSelection(h => { - if (h.SampleControlPoint.SampleBank == bankName) + if (h.Samples.All(s => s.Bank == bankName)) return; - h.SampleControlPoint.SampleBank = bankName; - EditorBeatmap.Update(h); - }); - } - - /// - /// Removes a sample bank from all selected s. - /// - /// The name of the sample bank. - public void RemoveSampleBank(string bankName) - { - EditorBeatmap.PerformOnSelection(h => - { - if (h.SampleControlPoint.SampleBank == bankName) - h.SampleControlPoint.SampleBankBindable.SetDefault(); - + h.Samples = h.Samples.Select(s => s.With(newBank: bankName)).ToList(); EditorBeatmap.Update(h); }); } From a22ad98cb79567018833c14e9cc845e02bf564cb Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 24 May 2023 14:56:11 +0900 Subject: [PATCH 39/69] Fix hotkeys not actually working --- osu.Game/Rulesets/Edit/HitObjectComposer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/Edit/HitObjectComposer.cs b/osu.Game/Rulesets/Edit/HitObjectComposer.cs index 62c3211e85..106ffc8ee7 100644 --- a/osu.Game/Rulesets/Edit/HitObjectComposer.cs +++ b/osu.Game/Rulesets/Edit/HitObjectComposer.cs @@ -288,7 +288,7 @@ namespace osu.Game.Rulesets.Edit protected override bool OnKeyDown(KeyDownEvent e) { - if (e.ControlPressed || e.AltPressed || e.SuperPressed || e.ShiftPressed) + if (e.ControlPressed || e.AltPressed || e.SuperPressed) return false; if (checkLeftToggleFromKey(e.Key, out int leftIndex)) From fc22c754641b2b11c4f4ecaf714df70cf1fd6b0c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 24 May 2023 16:14:05 +0900 Subject: [PATCH 40/69] Don't use `switch` for single `case` statement --- .../Edit/Compose/Components/ComposeBlueprintContainer.cs | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs index 3f3b4ad327..d2dd83eb1a 100644 --- a/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs @@ -201,12 +201,8 @@ namespace osu.Game.Screens.Edit.Compose.Components { if (CurrentPlacement == null) return; - switch (state) - { - case TernaryState.True: - CurrentPlacement.HitObject.Samples = CurrentPlacement.HitObject.Samples.Select(s => s.With(newBank: bankName)).ToList(); - break; - } + if (state == TernaryState.True) + CurrentPlacement.HitObject.Samples = CurrentPlacement.HitObject.Samples.Select(s => s.With(newBank: bankName)).ToList(); } public readonly Bindable NewCombo = new Bindable { Description = "New Combo" }; From 3a05dffa506aa0babb6dec23356a8cc21c763d10 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 24 May 2023 17:11:12 +0900 Subject: [PATCH 41/69] Add "auto" bank selection during placement --- osu.Game/Rulesets/Edit/PlacementBlueprint.cs | 18 +++++++++++++----- .../Components/ComposeBlueprintContainer.cs | 2 ++ .../Components/EditorSelectionHandler.cs | 16 +++++++++++++++- 3 files changed, 30 insertions(+), 6 deletions(-) diff --git a/osu.Game/Rulesets/Edit/PlacementBlueprint.cs b/osu.Game/Rulesets/Edit/PlacementBlueprint.cs index 551e557599..a0a04c13d0 100644 --- a/osu.Game/Rulesets/Edit/PlacementBlueprint.cs +++ b/osu.Game/Rulesets/Edit/PlacementBlueprint.cs @@ -32,6 +32,11 @@ namespace osu.Game.Rulesets.Edit /// public PlacementState PlacementActive { get; private set; } + /// + /// Whether the sample bank should be taken from the previous hit object. + /// + public bool AutomaticBankAssignment; + /// /// The that is being placed. /// @@ -86,11 +91,6 @@ namespace osu.Game.Rulesets.Edit /// Whether this call is committing a value for HitObject.StartTime and continuing with further adjustments. protected void BeginPlacement(bool commitStart = false) { - // Take the hitnormal sample of the last hit object - var lastHitNormal = getPreviousHitObject()?.Samples?.FirstOrDefault(o => o.Name == HitSampleInfo.HIT_NORMAL); - if (lastHitNormal != null) - HitObject.Samples[0] = lastHitNormal; - placementHandler.BeginPlacement(HitObject); if (commitStart) PlacementActive = PlacementState.Active; @@ -155,6 +155,14 @@ namespace osu.Game.Rulesets.Edit if (HitObject is IHasComboInformation comboInformation) comboInformation.UpdateComboInformation(getPreviousHitObject() as IHasComboInformation); } + + if (AutomaticBankAssignment) + { + // Take the hitnormal sample of the last hit object + var lastHitNormal = getPreviousHitObject()?.Samples?.FirstOrDefault(o => o.Name == HitSampleInfo.HIT_NORMAL); + if (lastHitNormal != null) + HitObject.Samples[0] = lastHitNormal; + } } /// diff --git a/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs index d2dd83eb1a..25babae6ff 100644 --- a/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs @@ -201,6 +201,8 @@ namespace osu.Game.Screens.Edit.Compose.Components { if (CurrentPlacement == null) return; + if (bankName == EditorSelectionHandler.HIT_BANK_AUTO) + CurrentPlacement.AutomaticBankAssignment = state == TernaryState.True; if (state == TernaryState.True) CurrentPlacement.HitObject.Samples = CurrentPlacement.HitObject.Samples.Select(s => s.With(newBank: bankName)).ToList(); } diff --git a/osu.Game/Screens/Edit/Compose/Components/EditorSelectionHandler.cs b/osu.Game/Screens/Edit/Compose/Components/EditorSelectionHandler.cs index fa3a4cddaa..dc7e12ea93 100644 --- a/osu.Game/Screens/Edit/Compose/Components/EditorSelectionHandler.cs +++ b/osu.Game/Screens/Edit/Compose/Components/EditorSelectionHandler.cs @@ -21,6 +21,12 @@ namespace osu.Game.Screens.Edit.Compose.Components { public partial class EditorSelectionHandler : SelectionHandler { + /// + /// A special bank name that is only used in the editor UI. + /// When selected and in placement mode, the bank of the last hit object will always be used. + /// + public const string HIT_BANK_AUTO = "auto"; + [Resolved] protected EditorBeatmap EditorBeatmap { get; private set; } @@ -59,7 +65,7 @@ namespace osu.Game.Screens.Edit.Compose.Components /// private void createStateBindables() { - foreach (string bankName in HitSampleInfo.AllBanks) + foreach (string bankName in HitSampleInfo.AllBanks.Prepend(HIT_BANK_AUTO)) { var bindable = new Bindable { @@ -100,6 +106,14 @@ namespace osu.Game.Screens.Edit.Compose.Components } else { + // Auto should just not apply if there's a selection already made. + // Maybe we could make it a disabled button in the future, but right now the editor buttons don't support disabled state. + if (bankName == HIT_BANK_AUTO) + { + bindable.Value = TernaryState.False; + break; + } + AddSampleBank(bankName); } From 8e5ba2208d29451882378130e2be0854a7b142a6 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 24 May 2023 17:33:48 +0900 Subject: [PATCH 42/69] Add test coverage of new hotkeys --- .../TestSceneHitObjectSampleAdjustments.cs | 100 +++++++++++++++++- 1 file changed, 98 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneHitObjectSampleAdjustments.cs b/osu.Game.Tests/Visual/Editing/TestSceneHitObjectSampleAdjustments.cs index eb39221211..b0b51a5dbd 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneHitObjectSampleAdjustments.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneHitObjectSampleAdjustments.cs @@ -13,6 +13,7 @@ using osu.Game.Beatmaps; using osu.Game.Graphics.UserInterface; using osu.Game.Graphics.UserInterfaceV2; using osu.Game.Rulesets; +using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.UI; @@ -170,7 +171,7 @@ namespace osu.Game.Tests.Visual.Editing } [Test] - public void TestMultipleSelectionWithSameSampleBank() + public void TestPopoverMultipleSelectionWithSameSampleBank() { AddStep("unify sample bank", () => { @@ -204,7 +205,7 @@ namespace osu.Game.Tests.Visual.Editing } [Test] - public void TestMultipleSelectionWithDifferentSampleBank() + public void TestPopoverMultipleSelectionWithDifferentSampleBank() { AddStep("select both objects", () => EditorBeatmap.SelectedHitObjects.AddRange(EditorBeatmap.HitObjects)); clickSamplePiece(0); @@ -226,6 +227,101 @@ namespace osu.Game.Tests.Visual.Editing samplePopoverHasSingleBank(HitSampleInfo.BANK_NORMAL); } + [Test] + public void TestHotkeysMultipleSelectionWithSameSampleBank() + { + AddStep("unify sample bank", () => + { + foreach (var h in EditorBeatmap.HitObjects) + { + for (int i = 0; i < h.Samples.Count; i++) + { + h.Samples[i] = h.Samples[i].With(newBank: HitSampleInfo.BANK_SOFT); + } + } + }); + + AddStep("select both objects", () => EditorBeatmap.SelectedHitObjects.AddRange(EditorBeatmap.HitObjects)); + + hitObjectHasSampleBank(0, HitSampleInfo.BANK_SOFT); + hitObjectHasSampleBank(1, HitSampleInfo.BANK_SOFT); + + AddStep("Press normal bank shortcut", () => + { + InputManager.PressKey(Key.ShiftLeft); + InputManager.Key(Key.W); + InputManager.ReleaseKey(Key.ShiftLeft); + }); + + hitObjectHasSampleBank(0, HitSampleInfo.BANK_NORMAL); + hitObjectHasSampleBank(1, HitSampleInfo.BANK_NORMAL); + + AddStep("Press drum bank shortcut", () => + { + InputManager.PressKey(Key.ShiftLeft); + InputManager.Key(Key.R); + InputManager.ReleaseKey(Key.ShiftLeft); + }); + + hitObjectHasSampleBank(0, HitSampleInfo.BANK_DRUM); + hitObjectHasSampleBank(1, HitSampleInfo.BANK_DRUM); + + AddStep("Press auto bank shortcut", () => + { + InputManager.PressKey(Key.ShiftLeft); + InputManager.Key(Key.Q); + InputManager.ReleaseKey(Key.ShiftLeft); + }); + + // Should be a noop. + hitObjectHasSampleBank(0, HitSampleInfo.BANK_DRUM); + hitObjectHasSampleBank(1, HitSampleInfo.BANK_DRUM); + } + + [Test] + public void TestHotkeysDuringPlacement() + { + AddStep("Enter placement mode", () => InputManager.Key(Key.Number2)); + AddStep("Move mouse to centre", () => InputManager.MoveMouseTo(Editor.ChildrenOfType().First().ScreenSpaceDrawQuad.Centre)); + + AddStep("Move between two objects", () => EditorClock.Seek(250)); + + AddStep("Press normal bank shortcut", () => + { + InputManager.PressKey(Key.ShiftLeft); + InputManager.Key(Key.W); + InputManager.ReleaseKey(Key.ShiftLeft); + }); + + checkPlacementSample(HitSampleInfo.BANK_NORMAL); + + AddStep("Press drum bank shortcut", () => + { + InputManager.PressKey(Key.ShiftLeft); + InputManager.Key(Key.R); + InputManager.ReleaseKey(Key.ShiftLeft); + }); + + checkPlacementSample(HitSampleInfo.BANK_DRUM); + + AddStep("Press auto bank shortcut", () => + { + InputManager.PressKey(Key.ShiftLeft); + InputManager.Key(Key.Q); + InputManager.ReleaseKey(Key.ShiftLeft); + }); + + checkPlacementSample(HitSampleInfo.BANK_NORMAL); + + AddStep("Move after second object", () => EditorClock.Seek(750)); + checkPlacementSample(HitSampleInfo.BANK_SOFT); + + AddStep("Move to first object", () => EditorClock.Seek(0)); + checkPlacementSample(HitSampleInfo.BANK_NORMAL); + + void checkPlacementSample(string expected) => AddAssert($"Placement sample is {expected}", () => EditorBeatmap.PlacementObject.Value.Samples.First().Bank, () => Is.EqualTo(expected)); + } + private void clickSamplePiece(int objectIndex) => AddStep($"click {objectIndex.ToOrdinalWords()} sample piece", () => { var samplePiece = this.ChildrenOfType().Single(piece => piece.HitObject == EditorBeatmap.HitObjects.ElementAt(objectIndex)); From 8ada8b1c8cee225a02b99232b77d4f10f42c6dca Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 24 May 2023 17:48:34 +0900 Subject: [PATCH 43/69] Remove description line from pause/fail screen These were in the designs but read pretty bad / evil. I can't think of any text to go in their place that makes sense, so let's just nuke it. --- osu.Game/Screens/Play/FailOverlay.cs | 1 - osu.Game/Screens/Play/GameplayMenuOverlay.cs | 10 ---------- osu.Game/Screens/Play/PauseOverlay.cs | 1 - 3 files changed, 12 deletions(-) diff --git a/osu.Game/Screens/Play/FailOverlay.cs b/osu.Game/Screens/Play/FailOverlay.cs index 4fbc937b59..f1dd2abc4a 100644 --- a/osu.Game/Screens/Play/FailOverlay.cs +++ b/osu.Game/Screens/Play/FailOverlay.cs @@ -23,7 +23,6 @@ namespace osu.Game.Screens.Play public Func> SaveReplay; public override string Header => "failed"; - public override string Description => "you're dead, try again?"; [BackgroundDependencyLoader] private void load(OsuColour colours) diff --git a/osu.Game/Screens/Play/GameplayMenuOverlay.cs b/osu.Game/Screens/Play/GameplayMenuOverlay.cs index 81146a4ea6..de0c4fe4f1 100644 --- a/osu.Game/Screens/Play/GameplayMenuOverlay.cs +++ b/osu.Game/Screens/Play/GameplayMenuOverlay.cs @@ -53,8 +53,6 @@ namespace osu.Game.Screens.Play public abstract string Header { get; } - public abstract string Description { get; } - protected SelectionCycleFillFlowContainer InternalButtons; public IReadOnlyList Buttons => InternalButtons; @@ -107,14 +105,6 @@ namespace osu.Game.Screens.Play Shadow = true, ShadowColour = new Color4(0, 0, 0, 0.25f) }, - new OsuSpriteText - { - Text = Description, - Origin = Anchor.TopCentre, - Anchor = Anchor.TopCentre, - Shadow = true, - ShadowColour = new Color4(0, 0, 0, 0.25f) - } } }, InternalButtons = new SelectionCycleFillFlowContainer diff --git a/osu.Game/Screens/Play/PauseOverlay.cs b/osu.Game/Screens/Play/PauseOverlay.cs index db42998c45..984f43d77a 100644 --- a/osu.Game/Screens/Play/PauseOverlay.cs +++ b/osu.Game/Screens/Play/PauseOverlay.cs @@ -24,7 +24,6 @@ namespace osu.Game.Screens.Play public override bool IsPresent => base.IsPresent || pauseLoop.IsPlaying; public override string Header => "paused"; - public override string Description => "you're not going to do what i think you're going to do, are ya?"; private SkinnableSound pauseLoop; From 456f3005d61292a714d43d4acb82d673a5e30677 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 24 May 2023 17:58:48 +0900 Subject: [PATCH 44/69] Apply nullability to `GameplayMenuOverlay` and use `TextFlowContainer` for text --- osu.Game/Screens/Play/GameplayMenuOverlay.cs | 76 ++++++-------------- 1 file changed, 20 insertions(+), 56 deletions(-) diff --git a/osu.Game/Screens/Play/GameplayMenuOverlay.cs b/osu.Game/Screens/Play/GameplayMenuOverlay.cs index de0c4fe4f1..f1e10912ac 100644 --- a/osu.Game/Screens/Play/GameplayMenuOverlay.cs +++ b/osu.Game/Screens/Play/GameplayMenuOverlay.cs @@ -1,12 +1,9 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System; using System.Collections.Generic; using System.Linq; -using Humanizer; using osu.Framework.Allocation; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; @@ -20,6 +17,7 @@ using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; using osu.Game.Input.Bindings; +using osu.Game.Rulesets.UI; using osuTK; using osuTK.Graphics; @@ -38,8 +36,8 @@ namespace osu.Game.Screens.Play public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => true; - public Action OnRetry; - public Action OnQuit; + public Action? OnRetry; + public Action? OnQuit; /// /// Action that is invoked when is triggered. @@ -53,10 +51,13 @@ namespace osu.Game.Screens.Play public abstract string Header { get; } - protected SelectionCycleFillFlowContainer InternalButtons; + protected SelectionCycleFillFlowContainer InternalButtons = null!; public IReadOnlyList Buttons => InternalButtons; - private FillFlowContainer retryCounterContainer; + private TextFlowContainer playInfoText = null!; + + [Resolved] + private GlobalActionContainer globalAction { get; set; } = null!; protected GameplayMenuOverlay() { @@ -84,28 +85,13 @@ namespace osu.Game.Screens.Play Anchor = Anchor.Centre, Children = new Drawable[] { - new FillFlowContainer + new OsuSpriteText { + Text = Header, + Font = OsuFont.GetFont(size: 48), Origin = Anchor.TopCentre, Anchor = Anchor.TopCentre, - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Direction = FillDirection.Vertical, - Spacing = new Vector2(0, 20), - Children = new Drawable[] - { - new OsuSpriteText - { - Text = Header, - Font = OsuFont.GetFont(size: 30), - Spacing = new Vector2(5, 0), - Origin = Anchor.TopCentre, - Anchor = Anchor.TopCentre, - Colour = colours.Yellow, - Shadow = true, - ShadowColour = new Color4(0, 0, 0, 0.25f) - }, - } + Colour = colours.Yellow, }, InternalButtons = new SelectionCycleFillFlowContainer { @@ -122,10 +108,11 @@ namespace osu.Game.Screens.Play Radius = 50 }, }, - retryCounterContainer = new FillFlowContainer + playInfoText = new OsuTextFlowContainer(cp => cp.Font = OsuFont.GetFont(size: 18)) { Origin = Anchor.TopCentre, Anchor = Anchor.TopCentre, + TextAnchor = Anchor.TopCentre, AutoSizeAxes = Axes.Both, } } @@ -147,7 +134,8 @@ namespace osu.Game.Screens.Play return; retries = value; - if (retryCounterContainer != null) + + if (IsLoaded) updateRetryCount(); } } @@ -160,7 +148,7 @@ namespace osu.Game.Screens.Play protected override bool OnMouseMove(MouseMoveEvent e) => true; - protected void AddButton(string text, Color4 colour, Action action) + protected void AddButton(string text, Color4 colour, Action? action) { var button = new Button { @@ -212,30 +200,9 @@ namespace osu.Game.Screens.Play // "You've retried 1,065 times in this session" // "You've retried 1 time in this session" - retryCounterContainer.Children = new Drawable[] - { - new OsuSpriteText - { - Text = "You've retried ", - Shadow = true, - ShadowColour = new Color4(0, 0, 0, 0.25f), - Font = OsuFont.GetFont(size: 18), - }, - new OsuSpriteText - { - Text = "time".ToQuantity(retries), - Font = OsuFont.GetFont(weight: FontWeight.Bold, size: 18), - Shadow = true, - ShadowColour = new Color4(0, 0, 0, 0.25f), - }, - new OsuSpriteText - { - Text = " in this session", - Shadow = true, - ShadowColour = new Color4(0, 0, 0, 0.25f), - Font = OsuFont.GetFont(size: 18), - } - }; + playInfoText.Clear(); + playInfoText.AddText("Retry count: "); + playInfoText.AddText(retries.ToString(), cp => cp.Font = cp.Font.With(weight: FontWeight.Bold)); } private partial class Button : DialogButton @@ -250,9 +217,6 @@ namespace osu.Game.Screens.Play } } - [Resolved] - private GlobalActionContainer globalAction { get; set; } - protected override bool Handle(UIEvent e) { switch (e) From 79c9a48ff7b7e926b27be6321569b95baee44860 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 24 May 2023 18:09:19 +0900 Subject: [PATCH 45/69] Show song progress at pause/fail screen --- osu.Game/Screens/Play/GameplayMenuOverlay.cs | 36 ++++++++++++++++---- 1 file changed, 29 insertions(+), 7 deletions(-) diff --git a/osu.Game/Screens/Play/GameplayMenuOverlay.cs b/osu.Game/Screens/Play/GameplayMenuOverlay.cs index f1e10912ac..4e1913463c 100644 --- a/osu.Game/Screens/Play/GameplayMenuOverlay.cs +++ b/osu.Game/Screens/Play/GameplayMenuOverlay.cs @@ -17,6 +17,7 @@ using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; using osu.Game.Input.Bindings; +using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.UI; using osuTK; using osuTK.Graphics; @@ -121,7 +122,7 @@ namespace osu.Game.Screens.Play State.ValueChanged += _ => InternalButtons.Deselect(); - updateRetryCount(); + updateInfoText(); } private int retries; @@ -136,11 +137,16 @@ namespace osu.Game.Screens.Play retries = value; if (IsLoaded) - updateRetryCount(); + updateInfoText(); } } - protected override void PopIn() => this.FadeIn(TRANSITION_DURATION, Easing.In); + protected override void PopIn() + { + this.FadeIn(TRANSITION_DURATION, Easing.In); + updateInfoText(); + } + protected override void PopOut() => this.FadeOut(TRANSITION_DURATION, Easing.In); // Don't let mouse down events through the overlay or people can click circles while paused. @@ -195,14 +201,30 @@ namespace osu.Game.Screens.Play { } - private void updateRetryCount() - { - // "You've retried 1,065 times in this session" - // "You've retried 1 time in this session" + [Resolved] + private IGameplayClock? gameplayClock { get; set; } + [Resolved] + private DrawableRuleset? drawableRuleset { get; set; } + + private void updateInfoText() + { playInfoText.Clear(); playInfoText.AddText("Retry count: "); playInfoText.AddText(retries.ToString(), cp => cp.Font = cp.Font.With(weight: FontWeight.Bold)); + + if (gameplayClock != null && drawableRuleset != null) + { + double firstHitTime = drawableRuleset.Objects.FirstOrDefault()?.StartTime ?? 0; + //TODO: this isn't always correct (consider mania where a non-last object may last for longer than the last in the list). + double lastHitTime = drawableRuleset.Objects.LastOrDefault()?.GetEndTime() ?? 0; + + double progress = Math.Clamp((gameplayClock.CurrentTime - firstHitTime) / (lastHitTime - firstHitTime), 0, 1); + + playInfoText.NewLine(); + playInfoText.AddText("Song progress: "); + playInfoText.AddText(progress.ToString("0%"), cp => cp.Font = cp.Font.With(weight: FontWeight.Bold)); + } } private partial class Button : DialogButton From 3b9e1e8a9491c8db305320951c58360622f57846 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 24 May 2023 19:16:51 +0900 Subject: [PATCH 46/69] Ensure editor selection buttons remain on screen when selection is near edge Addresses https://github.com/ppy/osu/discussions/23599. --- .../Edit/Compose/Components/SelectionBox.cs | 40 +++++++++++++++++-- 1 file changed, 37 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionBox.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionBox.cs index 17790547ed..677b0cd76e 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionBox.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionBox.cs @@ -22,6 +22,8 @@ namespace osu.Game.Screens.Edit.Compose.Components { public const float BORDER_RADIUS = 3; + private const float button_padding = 5; + public Func OnRotation; public Func OnScale; public Func OnFlip; @@ -182,6 +184,13 @@ namespace osu.Game.Screens.Edit.Compose.Components return base.OnKeyDown(e); } + protected override void Update() + { + base.Update(); + + ensureButtonsOnScreen(); + } + private void recreate() { if (LoadState < LoadState.Loading) @@ -234,11 +243,12 @@ namespace osu.Game.Screens.Edit.Compose.Components }, buttons = new FillFlowContainer { - Y = 20, - AutoSizeAxes = Axes.Both, + AutoSizeAxes = Axes.X, + Height = 30, Direction = FillDirection.Horizontal, + Margin = new MarginPadding(button_padding), Anchor = Anchor.BottomCentre, - Origin = Anchor.Centre + Origin = Anchor.TopCentre } }; @@ -352,5 +362,29 @@ namespace osu.Game.Screens.Edit.Compose.Components if (activeOperations++ == 0) OperationStarted?.Invoke(); } + + private void ensureButtonsOnScreen() + { + buttons.Position = Vector2.Zero; + + var buttonsQuad = buttons.ScreenSpaceDrawQuad; + var thisQuad = ScreenSpaceDrawQuad; + + // Shrink the parent quad to give a bit of padding so the buttons don't stick *right* on the border. + // AABBFloat assumes no rotation. one would hope the whole editor is not being rotated. + var parentQuad = Parent.ScreenSpaceDrawQuad.AABBFloat.Shrink(ToLocalSpace(thisQuad.TopLeft + new Vector2(button_padding * 2))); + + float leftExcess = buttonsQuad.TopLeft.X - parentQuad.TopLeft.X; + float rightExcess = parentQuad.TopRight.X - buttonsQuad.TopRight.X; + float bottomExcess = thisQuad.BottomLeft.Y + buttonsQuad.Height - parentQuad.BottomLeft.Y; + + if (leftExcess < 0) + buttons.X += ToLocalSpace(thisQuad.TopLeft - new Vector2(leftExcess)).X; + else if (rightExcess < 0) + buttons.X -= ToLocalSpace(thisQuad.TopLeft - new Vector2(rightExcess)).X; + + if (bottomExcess > 0) + buttons.Y += ToLocalSpace(thisQuad.TopLeft - new Vector2(bottomExcess)).X; + } } } From b14b1072c29a039c68d2cbe76d05023cd93b7e70 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 24 May 2023 19:24:14 +0900 Subject: [PATCH 47/69] Allow deselecting any selection in the editor using the `Back` binding (escape key) --- .../Compose/Components/BlueprintContainer.cs | 27 ++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs index cb7c083d87..0fcf84ec8e 100644 --- a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs @@ -16,6 +16,7 @@ using osu.Framework.Input; using osu.Framework.Input.Bindings; using osu.Framework.Input.Events; using osu.Game.Graphics.UserInterface; +using osu.Game.Input.Bindings; using osu.Game.Rulesets.Edit; using osuTK; using osuTK.Input; @@ -26,7 +27,7 @@ namespace osu.Game.Screens.Edit.Compose.Components /// A container which provides a "blueprint" display of items. /// Includes selection and manipulation support via a . /// - public abstract partial class BlueprintContainer : CompositeDrawable, IKeyBindingHandler + public abstract partial class BlueprintContainer : CompositeDrawable, IKeyBindingHandler, IKeyBindingHandler where T : class { protected DragBox DragBox { get; private set; } @@ -279,6 +280,30 @@ namespace osu.Game.Screens.Edit.Compose.Components { } + public bool OnPressed(KeyBindingPressEvent e) + { + if (e.Repeat) + return false; + + switch (e.Action) + { + case GlobalAction.Back: + if (SelectedItems.Count > 0) + { + DeselectAll(); + return true; + } + + break; + } + + return false; + } + + public void OnReleased(KeyBindingReleaseEvent e) + { + } + #region Blueprint Addition/Removal protected virtual void AddBlueprintFor(T item) From 663cec1ff6307a87e527b6ac6d76fa57d3d9b1e3 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 24 May 2023 23:51:28 +0900 Subject: [PATCH 48/69] Combine editor navigation test scenes --- .../Editing/TestSceneEditorNavigation.cs | 57 ------------------- .../TestSceneBeatmapEditorNavigation.cs | 44 ++++++++++++++ 2 files changed, 44 insertions(+), 57 deletions(-) delete mode 100644 osu.Game.Tests/Visual/Editing/TestSceneEditorNavigation.cs diff --git a/osu.Game.Tests/Visual/Editing/TestSceneEditorNavigation.cs b/osu.Game.Tests/Visual/Editing/TestSceneEditorNavigation.cs deleted file mode 100644 index 5914290d40..0000000000 --- a/osu.Game.Tests/Visual/Editing/TestSceneEditorNavigation.cs +++ /dev/null @@ -1,57 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -using System.Linq; -using NUnit.Framework; -using osu.Framework.Extensions; -using osu.Framework.Extensions.IEnumerableExtensions; -using osu.Framework.Extensions.ObjectExtensions; -using osu.Game.Beatmaps; -using osu.Game.Database; -using osu.Game.Rulesets.Mania; -using osu.Game.Rulesets.Osu; -using osu.Game.Screens.Edit; -using osu.Game.Screens.Edit.GameplayTest; -using osu.Game.Screens.Select; -using osu.Game.Tests.Resources; - -namespace osu.Game.Tests.Visual.Editing -{ - public partial class TestSceneEditorNavigation : OsuGameTestScene - { - [Test] - public void TestEditorGameplayTestAlwaysUsesOriginalRuleset() - { - BeatmapSetInfo beatmapSet = null!; - - AddStep("import test beatmap", () => Game.BeatmapManager.Import(TestResources.GetTestBeatmapForImport()).WaitSafely()); - AddStep("retrieve beatmap", () => beatmapSet = Game.BeatmapManager.QueryBeatmapSet(set => !set.Protected).AsNonNull().Value.Detach()); - - AddStep("present beatmap", () => Game.PresentBeatmap(beatmapSet)); - AddUntilStep("wait for song select", - () => Game.Beatmap.Value.BeatmapSetInfo.Equals(beatmapSet) - && Game.ScreenStack.CurrentScreen is PlaySongSelect songSelect - && songSelect.IsLoaded); - AddStep("switch ruleset", () => Game.Ruleset.Value = new ManiaRuleset().RulesetInfo); - - AddStep("open editor", () => ((PlaySongSelect)Game.ScreenStack.CurrentScreen).Edit(beatmapSet.Beatmaps.First(beatmap => beatmap.Ruleset.OnlineID == 0))); - AddUntilStep("wait for editor open", () => Game.ScreenStack.CurrentScreen is Editor editor && editor.ReadyForUse); - AddStep("test gameplay", () => ((Editor)Game.ScreenStack.CurrentScreen).TestGameplay()); - - AddUntilStep("wait for player", () => - { - // notifications may fire at almost any inopportune time and cause annoying test failures. - // relentlessly attempt to dismiss any and all interfering overlays, which includes notifications. - // this is theoretically not foolproof, but it's the best that can be done here. - Game.CloseAllOverlays(); - return Game.ScreenStack.CurrentScreen is EditorPlayer editorPlayer && editorPlayer.IsLoaded; - }); - - AddAssert("current ruleset is osu!", () => Game.Ruleset.Value.Equals(new OsuRuleset().RulesetInfo)); - - AddStep("exit to song select", () => Game.PerformFromScreen(_ => { }, typeof(PlaySongSelect).Yield())); - AddUntilStep("wait for song select", () => Game.ScreenStack.CurrentScreen is PlaySongSelect); - AddAssert("previous ruleset restored", () => Game.Ruleset.Value.Equals(new ManiaRuleset().RulesetInfo)); - } - } -} diff --git a/osu.Game.Tests/Visual/Navigation/TestSceneBeatmapEditorNavigation.cs b/osu.Game.Tests/Visual/Navigation/TestSceneBeatmapEditorNavigation.cs index 603573058e..0307bc7ce5 100644 --- a/osu.Game.Tests/Visual/Navigation/TestSceneBeatmapEditorNavigation.cs +++ b/osu.Game.Tests/Visual/Navigation/TestSceneBeatmapEditorNavigation.cs @@ -3,15 +3,59 @@ using System.Linq; using NUnit.Framework; +using osu.Framework.Extensions; +using osu.Framework.Extensions.IEnumerableExtensions; +using osu.Framework.Extensions.ObjectExtensions; using osu.Framework.Screens; using osu.Game.Beatmaps; +using osu.Game.Database; +using osu.Game.Rulesets.Mania; +using osu.Game.Rulesets.Osu; using osu.Game.Screens.Edit; +using osu.Game.Screens.Edit.GameplayTest; using osu.Game.Screens.Menu; +using osu.Game.Screens.Select; +using osu.Game.Tests.Resources; namespace osu.Game.Tests.Visual.Navigation { public partial class TestSceneBeatmapEditorNavigation : OsuGameTestScene { + [Test] + public void TestEditorGameplayTestAlwaysUsesOriginalRuleset() + { + BeatmapSetInfo beatmapSet = null!; + + AddStep("import test beatmap", () => Game.BeatmapManager.Import(TestResources.GetTestBeatmapForImport()).WaitSafely()); + AddStep("retrieve beatmap", () => beatmapSet = Game.BeatmapManager.QueryBeatmapSet(set => !set.Protected).AsNonNull().Value.Detach()); + + AddStep("present beatmap", () => Game.PresentBeatmap(beatmapSet)); + AddUntilStep("wait for song select", + () => Game.Beatmap.Value.BeatmapSetInfo.Equals(beatmapSet) + && Game.ScreenStack.CurrentScreen is PlaySongSelect songSelect + && songSelect.IsLoaded); + AddStep("switch ruleset", () => Game.Ruleset.Value = new ManiaRuleset().RulesetInfo); + + AddStep("open editor", () => ((PlaySongSelect)Game.ScreenStack.CurrentScreen).Edit(beatmapSet.Beatmaps.First(beatmap => beatmap.Ruleset.OnlineID == 0))); + AddUntilStep("wait for editor open", () => Game.ScreenStack.CurrentScreen is Editor editor && editor.ReadyForUse); + AddStep("test gameplay", () => ((Editor)Game.ScreenStack.CurrentScreen).TestGameplay()); + + AddUntilStep("wait for player", () => + { + // notifications may fire at almost any inopportune time and cause annoying test failures. + // relentlessly attempt to dismiss any and all interfering overlays, which includes notifications. + // this is theoretically not foolproof, but it's the best that can be done here. + Game.CloseAllOverlays(); + return Game.ScreenStack.CurrentScreen is EditorPlayer editorPlayer && editorPlayer.IsLoaded; + }); + + AddAssert("current ruleset is osu!", () => Game.Ruleset.Value.Equals(new OsuRuleset().RulesetInfo)); + + AddStep("exit to song select", () => Game.PerformFromScreen(_ => { }, typeof(PlaySongSelect).Yield())); + AddUntilStep("wait for song select", () => Game.ScreenStack.CurrentScreen is PlaySongSelect); + AddAssert("previous ruleset restored", () => Game.Ruleset.Value.Equals(new ManiaRuleset().RulesetInfo)); + } + /// /// When entering the editor, a new beatmap is created as part of the asynchronous load process. /// This test ensures that in the case of an early exit from the editor (ie. while it's still loading) From 604432718116abaadf32778fa47bedc353f504d0 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 24 May 2023 23:57:37 +0900 Subject: [PATCH 49/69] Add test coverage for escape deselecting any active selection --- .../TestSceneBeatmapEditorNavigation.cs | 60 +++++++++++++++++++ 1 file changed, 60 insertions(+) diff --git a/osu.Game.Tests/Visual/Navigation/TestSceneBeatmapEditorNavigation.cs b/osu.Game.Tests/Visual/Navigation/TestSceneBeatmapEditorNavigation.cs index 0307bc7ce5..5f026468c7 100644 --- a/osu.Game.Tests/Visual/Navigation/TestSceneBeatmapEditorNavigation.cs +++ b/osu.Game.Tests/Visual/Navigation/TestSceneBeatmapEditorNavigation.cs @@ -7,6 +7,7 @@ using osu.Framework.Extensions; using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Extensions.ObjectExtensions; using osu.Framework.Screens; +using osu.Framework.Testing; using osu.Game.Beatmaps; using osu.Game.Database; using osu.Game.Rulesets.Mania; @@ -16,6 +17,7 @@ using osu.Game.Screens.Edit.GameplayTest; using osu.Game.Screens.Menu; using osu.Game.Screens.Select; using osu.Game.Tests.Resources; +using osuTK.Input; namespace osu.Game.Tests.Visual.Navigation { @@ -82,5 +84,63 @@ namespace osu.Game.Tests.Visual.Navigation BeatmapSetInfo[] allBeatmapSets() => Game.Realm.Run(realm => realm.All().Where(x => !x.DeletePending).ToArray()); } + + [Test] + public void TestExitEditorWithoutSelection() + { + BeatmapSetInfo beatmapSet = null!; + + AddStep("import test beatmap", () => Game.BeatmapManager.Import(TestResources.GetTestBeatmapForImport()).WaitSafely()); + AddStep("retrieve beatmap", () => beatmapSet = Game.BeatmapManager.QueryBeatmapSet(set => !set.Protected).AsNonNull().Value.Detach()); + + AddStep("present beatmap", () => Game.PresentBeatmap(beatmapSet)); + AddUntilStep("wait for song select", + () => Game.Beatmap.Value.BeatmapSetInfo.Equals(beatmapSet) + && Game.ScreenStack.CurrentScreen is PlaySongSelect songSelect + && songSelect.IsLoaded); + + AddStep("open editor", () => ((PlaySongSelect)Game.ScreenStack.CurrentScreen).Edit(beatmapSet.Beatmaps.First(beatmap => beatmap.Ruleset.OnlineID == 0))); + AddUntilStep("wait for editor open", () => Game.ScreenStack.CurrentScreen is Editor editor && editor.ReadyForUse); + + AddStep("escape once", () => InputManager.Key(Key.Escape)); + + AddUntilStep("wait for editor exit", () => Game.ScreenStack.CurrentScreen is not Editor); + } + + [Test] + public void TestExitEditorWithSelection() + { + BeatmapSetInfo beatmapSet = null!; + + AddStep("import test beatmap", () => Game.BeatmapManager.Import(TestResources.GetTestBeatmapForImport()).WaitSafely()); + AddStep("retrieve beatmap", () => beatmapSet = Game.BeatmapManager.QueryBeatmapSet(set => !set.Protected).AsNonNull().Value.Detach()); + + AddStep("present beatmap", () => Game.PresentBeatmap(beatmapSet)); + AddUntilStep("wait for song select", + () => Game.Beatmap.Value.BeatmapSetInfo.Equals(beatmapSet) + && Game.ScreenStack.CurrentScreen is PlaySongSelect songSelect + && songSelect.IsLoaded); + + AddStep("open editor", () => ((PlaySongSelect)Game.ScreenStack.CurrentScreen).Edit(beatmapSet.Beatmaps.First(beatmap => beatmap.Ruleset.OnlineID == 0))); + AddUntilStep("wait for editor open", () => Game.ScreenStack.CurrentScreen is Editor editor && editor.ReadyForUse); + + AddStep("make selection", () => + { + var beatmap = getEditorBeatmap(); + beatmap.SelectedHitObjects.AddRange(beatmap.HitObjects.Take(5)); + }); + + AddAssert("selection exists", () => getEditorBeatmap().SelectedHitObjects, () => Has.Count.GreaterThan(0)); + + AddStep("escape once", () => InputManager.Key(Key.Escape)); + + AddAssert("selection exists", () => getEditorBeatmap().SelectedHitObjects, () => Has.Count.Zero); + + AddStep("escape again", () => InputManager.Key(Key.Escape)); + + AddUntilStep("wait for editor exit", () => Game.ScreenStack.CurrentScreen is not Editor); + } + + private EditorBeatmap getEditorBeatmap() => ((Editor)Game.ScreenStack.CurrentScreen).ChildrenOfType().Single(); } } From cd3602406b5999efe741b4d2fd3a3f97a2cf272c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 24 May 2023 18:54:48 +0200 Subject: [PATCH 50/69] Remove unused using directive --- osu.Game/Screens/Play/GameplayMenuOverlay.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game/Screens/Play/GameplayMenuOverlay.cs b/osu.Game/Screens/Play/GameplayMenuOverlay.cs index f1e10912ac..7f4979b840 100644 --- a/osu.Game/Screens/Play/GameplayMenuOverlay.cs +++ b/osu.Game/Screens/Play/GameplayMenuOverlay.cs @@ -17,7 +17,6 @@ using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; using osu.Game.Input.Bindings; -using osu.Game.Rulesets.UI; using osuTK; using osuTK.Graphics; From 07b5874eeee3a5854898318eda111bd9a64870da Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 24 May 2023 20:18:36 +0200 Subject: [PATCH 51/69] Fix test step name --- .../Visual/Navigation/TestSceneBeatmapEditorNavigation.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Navigation/TestSceneBeatmapEditorNavigation.cs b/osu.Game.Tests/Visual/Navigation/TestSceneBeatmapEditorNavigation.cs index 5f026468c7..1b2bb57b84 100644 --- a/osu.Game.Tests/Visual/Navigation/TestSceneBeatmapEditorNavigation.cs +++ b/osu.Game.Tests/Visual/Navigation/TestSceneBeatmapEditorNavigation.cs @@ -134,7 +134,7 @@ namespace osu.Game.Tests.Visual.Navigation AddStep("escape once", () => InputManager.Key(Key.Escape)); - AddAssert("selection exists", () => getEditorBeatmap().SelectedHitObjects, () => Has.Count.Zero); + AddAssert("selection empty", () => getEditorBeatmap().SelectedHitObjects, () => Has.Count.Zero); AddStep("escape again", () => InputManager.Key(Key.Escape)); From 6ec4ecfdd734d7db0cd430055e6c01b5753540d5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 24 May 2023 22:17:51 +0200 Subject: [PATCH 52/69] Mention fallback default in `GetDivisorForBeatIndex()` --- osu.Game/Screens/Edit/BindableBeatDivisor.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/BindableBeatDivisor.cs b/osu.Game/Screens/Edit/BindableBeatDivisor.cs index f1b97571bd..1da224d850 100644 --- a/osu.Game/Screens/Edit/BindableBeatDivisor.cs +++ b/osu.Game/Screens/Edit/BindableBeatDivisor.cs @@ -154,7 +154,7 @@ namespace osu.Game.Screens.Edit /// /// The 0-based beat index. /// The beat divisor. - /// The list of valid divisors which can be chosen from. Assumes ordered from low to high. + /// The list of valid divisors which can be chosen from. Assumes ordered from low to high. Defaults to if omitted. /// The applicable divisor. public static int GetDivisorForBeatIndex(int index, int beatDivisor, int[] validDivisors = null) { From 058edb5d5fe4e5003b045f4aeeabb187a977661f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 25 May 2023 17:15:31 +0900 Subject: [PATCH 53/69] Centralise beatmap playable duration and bounds lookups --- .../UserInterface/TestSceneSegmentedGraph.cs | 4 +- osu.Game/Beatmaps/BeatmapUpdater.cs | 18 +------- osu.Game/Beatmaps/IBeatmap.cs | 44 +++++++++++++++++++ .../Play/HUD/ArgonSongProgressGraph.cs | 4 +- .../Play/HUD/DefaultSongProgressGraph.cs | 4 +- osu.Game/Screens/Play/HUD/SongProgress.cs | 8 ++-- 6 files changed, 55 insertions(+), 27 deletions(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneSegmentedGraph.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneSegmentedGraph.cs index 1144b9053d..320ec48e07 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneSegmentedGraph.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneSegmentedGraph.cs @@ -7,6 +7,7 @@ using System.Diagnostics; using System.Linq; using osu.Framework.Graphics; using osu.Framework.Graphics.Colour; +using osu.Game.Beatmaps; using osu.Game.Graphics.UserInterface; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Osu; @@ -137,8 +138,7 @@ namespace osu.Game.Tests.Visual.UserInterface if (!objects.Any()) return; - double firstHit = objects.First().StartTime; - double lastHit = objects.Max(o => o.GetEndTime()); + (double firstHit, double lastHit) = BeatmapExtensions.CalculatePlayableBounds(objects); if (lastHit == 0) lastHit = objects.Last().StartTime; diff --git a/osu.Game/Beatmaps/BeatmapUpdater.cs b/osu.Game/Beatmaps/BeatmapUpdater.cs index af9f32f834..046adb8327 100644 --- a/osu.Game/Beatmaps/BeatmapUpdater.cs +++ b/osu.Game/Beatmaps/BeatmapUpdater.cs @@ -3,7 +3,6 @@ using System; using System.Diagnostics; -using System.Linq; using System.Threading.Tasks; using osu.Framework.Extensions.ObjectExtensions; using osu.Framework.Logging; @@ -11,7 +10,6 @@ using osu.Framework.Platform; using osu.Framework.Threading; using osu.Game.Database; using osu.Game.Online.API; -using osu.Game.Rulesets.Objects; namespace osu.Game.Beatmaps { @@ -74,7 +72,7 @@ namespace osu.Game.Beatmaps var calculator = ruleset.CreateDifficultyCalculator(working); beatmap.StarRating = calculator.Calculate().StarRating; - beatmap.Length = calculateLength(working.Beatmap); + beatmap.Length = working.Beatmap.CalculatePlayableLength(); beatmap.BPM = 60000 / working.Beatmap.GetMostCommonBeatLength(); } @@ -82,20 +80,6 @@ namespace osu.Game.Beatmaps workingBeatmapCache.Invalidate(beatmapSet); }); - private double calculateLength(IBeatmap b) - { - if (!b.HitObjects.Any()) - return 0; - - var lastObject = b.HitObjects.Last(); - - //TODO: this isn't always correct (consider mania where a non-last object may last for longer than the last in the list). - double endTime = lastObject.GetEndTime(); - double startTime = b.HitObjects.First().StartTime; - - return endTime - startTime; - } - #region Implementation of IDisposable public void Dispose() diff --git a/osu.Game/Beatmaps/IBeatmap.cs b/osu.Game/Beatmaps/IBeatmap.cs index f6771f7adf..671f5ce8db 100644 --- a/osu.Game/Beatmaps/IBeatmap.cs +++ b/osu.Game/Beatmaps/IBeatmap.cs @@ -104,6 +104,19 @@ namespace osu.Game.Beatmaps } } + /// + /// Find the total milliseconds between the first and last hittable objects. + /// + /// + /// This is cached to , so using that is preferrable when available. + /// + public static double CalculatePlayableLength(this IBeatmap beatmap) => CalculatePlayableLength(beatmap.HitObjects); + + /// + /// Find the timestamps in milliseconds of the start and end of the playable region. + /// + public static (double start, double end) CalculatePlayableBounds(this IBeatmap beatmap) => CalculatePlayableBounds(beatmap.HitObjects); + /// /// Find the absolute end time of the latest in a beatmap. Will throw if beatmap contains no objects. /// @@ -114,5 +127,36 @@ namespace osu.Game.Beatmaps /// It's not super efficient so calls should be kept to a minimum. /// public static double GetLastObjectTime(this IBeatmap beatmap) => beatmap.HitObjects.Max(h => h.GetEndTime()); + + #region Helper methods + + /// + /// Find the total milliseconds between the first and last hittable objects. + /// + /// + /// This is cached to , so using that is preferrable when available. + /// + public static double CalculatePlayableLength(IEnumerable objects) + { + (double start, double end) = CalculatePlayableBounds(objects); + + return end - start; + } + + /// + /// Find the timestamps in milliseconds of the start and end of the playable region. + /// + public static (double start, double end) CalculatePlayableBounds(IEnumerable objects) + { + if (!objects.Any()) + return (0, 0); + + double lastObjectTime = objects.Max(o => o.GetEndTime()); + double firstObjectTime = objects.First().StartTime; + + return (firstObjectTime, lastObjectTime); + } + + #endregion } } diff --git a/osu.Game/Screens/Play/HUD/ArgonSongProgressGraph.cs b/osu.Game/Screens/Play/HUD/ArgonSongProgressGraph.cs index 0899476ed4..63ab9d15e0 100644 --- a/osu.Game/Screens/Play/HUD/ArgonSongProgressGraph.cs +++ b/osu.Game/Screens/Play/HUD/ArgonSongProgressGraph.cs @@ -5,6 +5,7 @@ using System.Collections.Generic; using System.Diagnostics; using System.Linq; using osu.Framework.Graphics; +using osu.Game.Beatmaps; using osu.Game.Rulesets.Objects; using osu.Game.Graphics.UserInterface; @@ -26,8 +27,7 @@ namespace osu.Game.Screens.Play.HUD if (!objects.Any()) return; - double firstHit = objects.First().StartTime; - double lastHit = objects.Max(o => o.GetEndTime()); + (double firstHit, double lastHit) = BeatmapExtensions.CalculatePlayableBounds(objects); if (lastHit == 0) lastHit = objects.Last().StartTime; diff --git a/osu.Game/Screens/Play/HUD/DefaultSongProgressGraph.cs b/osu.Game/Screens/Play/HUD/DefaultSongProgressGraph.cs index bee5978817..047c64a4a4 100644 --- a/osu.Game/Screens/Play/HUD/DefaultSongProgressGraph.cs +++ b/osu.Game/Screens/Play/HUD/DefaultSongProgressGraph.cs @@ -6,6 +6,7 @@ using System.Linq; using System.Collections.Generic; using System.Diagnostics; +using osu.Game.Beatmaps; using osu.Game.Rulesets.Objects; namespace osu.Game.Screens.Play.HUD @@ -26,8 +27,7 @@ namespace osu.Game.Screens.Play.HUD if (!objects.Any()) return; - double firstHit = objects.First().StartTime; - double lastHit = objects.Max(o => o.GetEndTime()); + (double firstHit, double lastHit) = BeatmapExtensions.CalculatePlayableBounds(objects); if (lastHit == 0) lastHit = objects.Last().StartTime; diff --git a/osu.Game/Screens/Play/HUD/SongProgress.cs b/osu.Game/Screens/Play/HUD/SongProgress.cs index ebe2fb83e6..4391193df8 100644 --- a/osu.Game/Screens/Play/HUD/SongProgress.cs +++ b/osu.Game/Screens/Play/HUD/SongProgress.cs @@ -2,12 +2,12 @@ // See the LICENCE file in the repository root for full licence text. using System.Collections.Generic; -using System.Linq; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Timing; +using osu.Game.Beatmaps; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.UI; using osu.Game.Skinning; @@ -52,9 +52,9 @@ namespace osu.Game.Screens.Play.HUD set { objects = value; - FirstHitTime = objects.FirstOrDefault()?.StartTime ?? 0; - //TODO: this isn't always correct (consider mania where a non-last object may last for longer than the last in the list). - LastHitTime = objects.LastOrDefault()?.GetEndTime() ?? 0; + + (FirstHitTime, LastHitTime) = BeatmapExtensions.CalculatePlayableBounds(objects); + UpdateObjects(objects); } } From 37c6e632d0f325b36368e91ba749bde0634bdf46 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 25 May 2023 17:38:35 +0900 Subject: [PATCH 54/69] Switch to using new extension method for length calculations --- osu.Game/Screens/Play/GameplayMenuOverlay.cs | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/osu.Game/Screens/Play/GameplayMenuOverlay.cs b/osu.Game/Screens/Play/GameplayMenuOverlay.cs index 4e1913463c..52251db898 100644 --- a/osu.Game/Screens/Play/GameplayMenuOverlay.cs +++ b/osu.Game/Screens/Play/GameplayMenuOverlay.cs @@ -12,13 +12,12 @@ using osu.Framework.Graphics.Effects; using osu.Framework.Graphics.Shapes; using osu.Framework.Input.Bindings; using osu.Framework.Input.Events; +using osu.Game.Beatmaps; using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; using osu.Game.Input.Bindings; -using osu.Game.Rulesets.Objects; -using osu.Game.Rulesets.UI; using osuTK; using osuTK.Graphics; @@ -205,7 +204,7 @@ namespace osu.Game.Screens.Play private IGameplayClock? gameplayClock { get; set; } [Resolved] - private DrawableRuleset? drawableRuleset { get; set; } + private GameplayState? gameplayState { get; set; } private void updateInfoText() { @@ -213,11 +212,9 @@ namespace osu.Game.Screens.Play playInfoText.AddText("Retry count: "); playInfoText.AddText(retries.ToString(), cp => cp.Font = cp.Font.With(weight: FontWeight.Bold)); - if (gameplayClock != null && drawableRuleset != null) + if (gameplayState != null && gameplayClock != null) { - double firstHitTime = drawableRuleset.Objects.FirstOrDefault()?.StartTime ?? 0; - //TODO: this isn't always correct (consider mania where a non-last object may last for longer than the last in the list). - double lastHitTime = drawableRuleset.Objects.LastOrDefault()?.GetEndTime() ?? 0; + (double firstHitTime, double lastHitTime) = gameplayState.Beatmap.CalculatePlayableBounds(); double progress = Math.Clamp((gameplayClock.CurrentTime - firstHitTime) / (lastHitTime - firstHitTime), 0, 1); From 1e14b024938a584dc61725e4ed5d423459a8f29c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 25 May 2023 17:57:37 +0900 Subject: [PATCH 55/69] Fix bindable feedback loop --- .../Edit/Compose/Components/EditorSelectionHandler.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/osu.Game/Screens/Edit/Compose/Components/EditorSelectionHandler.cs b/osu.Game/Screens/Edit/Compose/Components/EditorSelectionHandler.cs index dc7e12ea93..d618541685 100644 --- a/osu.Game/Screens/Edit/Compose/Components/EditorSelectionHandler.cs +++ b/osu.Game/Screens/Edit/Compose/Components/EditorSelectionHandler.cs @@ -85,6 +85,11 @@ namespace osu.Game.Screens.Edit.Compose.Components } else { + // Auto should never apply when there is a selection made. + // This is also required to stop a bindable feedback loop when a HitObject has zero samples (and LINQ `All` below becomes true). + if (bankName == HIT_BANK_AUTO) + break; + // Never remove a sample bank. // These are basically radio buttons, not toggles. if (SelectedItems.All(h => h.Samples.All(s => s.Bank == bankName))) From 1049257b56b5dc0e2d71dc2db7d5a31c1062de78 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 25 May 2023 18:46:31 +0900 Subject: [PATCH 56/69] Simplify `SelectionHandler`'s `DeselectAll` implementation We are already doing other operations in this class directly on `SelectedItems`, so might as well change this one to match --- .../Screens/Edit/Compose/Components/BlueprintContainer.cs | 1 - .../Screens/Edit/Compose/Components/SelectionHandler.cs | 6 +++--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs index 0fcf84ec8e..56a6b18433 100644 --- a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs @@ -92,7 +92,6 @@ namespace osu.Game.Screens.Edit.Compose.Components }; SelectionHandler = CreateSelectionHandler(); - SelectionHandler.DeselectAll = DeselectAll; SelectionHandler.SelectedItems.BindTo(SelectedItems); AddRangeInternal(new[] diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs index 9e4fb26688..f73a25c339 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs @@ -197,9 +197,9 @@ namespace osu.Game.Screens.Edit.Compose.Components #region Selection Handling /// - /// Bind an action to deselect all selected blueprints. + /// Deselect all selected items. /// - internal Action DeselectAll { private get; set; } + protected void DeselectAll() => SelectedItems.Clear(); /// /// Handle a blueprint becoming selected. @@ -303,7 +303,7 @@ namespace osu.Game.Screens.Edit.Compose.Components if (blueprint.IsSelected) return false; - DeselectAll?.Invoke(); + DeselectAll(); blueprint.Select(); return true; } From 609c7227eee913dbb602b5eefdd628bb5c9c216d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 25 May 2023 19:55:11 +0900 Subject: [PATCH 57/69] Fix changes to font weight in a couple of combined implementations --- .../Screens/OnlinePlay/Lounge/Components/OnlinePlayPill.cs | 4 +++- .../OnlinePlay/Lounge/Components/RoomSpecialCategoryPill.cs | 3 +++ .../Screens/OnlinePlay/Lounge/Components/RoomStatusPill.cs | 3 +++ 3 files changed, 9 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/OnlinePlay/Lounge/Components/OnlinePlayPill.cs b/osu.Game/Screens/OnlinePlay/Lounge/Components/OnlinePlayPill.cs index 983fab8525..3e6d7a2e54 100644 --- a/osu.Game/Screens/OnlinePlay/Lounge/Components/OnlinePlayPill.cs +++ b/osu.Game/Screens/OnlinePlay/Lounge/Components/OnlinePlayPill.cs @@ -3,6 +3,7 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; +using osu.Framework.Graphics.Sprites; using osu.Game.Graphics; using osu.Game.Graphics.Containers; @@ -12,6 +13,7 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components { protected PillContainer Pill { get; private set; } = null!; protected OsuTextFlowContainer TextFlow { get; private set; } = null!; + protected virtual FontUsage Font => OsuFont.GetFont(size: 12); protected OnlinePlayPill() { @@ -23,7 +25,7 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components { InternalChild = Pill = new PillContainer { - Child = TextFlow = new OsuTextFlowContainer(s => s.Font = OsuFont.GetFont(size: 12)) + Child = TextFlow = new OsuTextFlowContainer(s => s.Font = Font) { AutoSizeAxes = Axes.Both, Anchor = Anchor.CentreLeft, diff --git a/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomSpecialCategoryPill.cs b/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomSpecialCategoryPill.cs index e88624f877..10f6e59260 100644 --- a/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomSpecialCategoryPill.cs +++ b/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomSpecialCategoryPill.cs @@ -5,6 +5,7 @@ using osu.Framework.Allocation; using osu.Framework.Extensions; +using osu.Framework.Graphics.Sprites; using osu.Game.Graphics; using osuTK.Graphics; @@ -15,6 +16,8 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components [Resolved] private OsuColour colours { get; set; } + protected override FontUsage Font => base.Font.With(weight: FontWeight.SemiBold); + protected override void LoadComplete() { base.LoadComplete(); diff --git a/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomStatusPill.cs b/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomStatusPill.cs index ab3d293db8..ca9917ad00 100644 --- a/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomStatusPill.cs +++ b/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomStatusPill.cs @@ -6,6 +6,7 @@ using System; using osu.Framework.Allocation; using osu.Framework.Graphics; +using osu.Framework.Graphics.Sprites; using osu.Game.Graphics; using osu.Game.Online.Rooms; using osu.Game.Online.Rooms.RoomStatuses; @@ -20,6 +21,8 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components [Resolved] private OsuColour colours { get; set; } + protected override FontUsage Font => base.Font.With(weight: FontWeight.SemiBold); + protected override void LoadComplete() { base.LoadComplete(); From 804671ca74995c1be6f429523b1b673dc00df184 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 25 May 2023 21:41:19 +0900 Subject: [PATCH 58/69] Split out grid snapping modes into "relative" and "global" types --- .../Edit/CatchHitObjectComposer.cs | 2 +- .../Edit/OsuHitObjectComposer.cs | 7 +++++-- .../Editing/TestSceneDistanceSnapGrid.cs | 2 +- osu.Game/Rulesets/Edit/HitObjectComposer.cs | 2 +- osu.Game/Rulesets/Edit/SnapType.cs | 20 +++++++++++++++++-- 5 files changed, 26 insertions(+), 7 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Edit/CatchHitObjectComposer.cs b/osu.Game.Rulesets.Catch/Edit/CatchHitObjectComposer.cs index cd8894753f..611e69d614 100644 --- a/osu.Game.Rulesets.Catch/Edit/CatchHitObjectComposer.cs +++ b/osu.Game.Rulesets.Catch/Edit/CatchHitObjectComposer.cs @@ -132,7 +132,7 @@ namespace osu.Game.Rulesets.Catch.Edit result.ScreenSpacePosition.X = screenSpacePosition.X; - if (snapType.HasFlagFast(SnapType.Grids)) + if (snapType.HasFlagFast(SnapType.GlobalGrids)) { if (distanceSnapGrid.IsPresent && distanceSnapGrid.GetSnappedPosition(result.ScreenSpacePosition) is SnapResult snapResult && Vector2.Distance(snapResult.ScreenSpacePosition, result.ScreenSpacePosition) < distance_snap_radius) diff --git a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs index ff1e208186..ad6af6d74e 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs @@ -143,7 +143,7 @@ namespace osu.Game.Rulesets.Osu.Edit // We want to ensure that in this particular case, the time-snapping component of distance snap is still applied. // The easiest way to ensure this is to attempt application of distance snap after a nearby object is found, and copy over // the time value if the proposed positions are roughly the same. - if (snapType.HasFlagFast(SnapType.Grids) && DistanceSnapToggle.Value == TernaryState.True && distanceSnapGrid != null) + if (snapType.HasFlagFast(SnapType.RelativeGrids) && DistanceSnapToggle.Value == TernaryState.True && distanceSnapGrid != null) { (Vector2 distanceSnappedPosition, double distanceSnappedTime) = distanceSnapGrid.GetSnappedPosition(distanceSnapGrid.ToLocalSpace(snapResult.ScreenSpacePosition)); if (Precision.AlmostEquals(distanceSnapGrid.ToScreenSpace(distanceSnappedPosition), snapResult.ScreenSpacePosition, 1)) @@ -155,7 +155,7 @@ namespace osu.Game.Rulesets.Osu.Edit SnapResult result = base.FindSnappedPositionAndTime(screenSpacePosition, snapType); - if (snapType.HasFlagFast(SnapType.Grids)) + if (snapType.HasFlagFast(SnapType.RelativeGrids)) { if (DistanceSnapToggle.Value == TernaryState.True && distanceSnapGrid != null) { @@ -164,7 +164,10 @@ namespace osu.Game.Rulesets.Osu.Edit result.ScreenSpacePosition = distanceSnapGrid.ToScreenSpace(pos); result.Time = time; } + } + if (snapType.HasFlagFast(SnapType.GlobalGrids)) + { if (rectangularGridSnapToggle.Value == TernaryState.True) { Vector2 pos = rectangularPositionSnapGrid.GetSnappedPosition(rectangularPositionSnapGrid.ToLocalSpace(result.ScreenSpacePosition)); diff --git a/osu.Game.Tests/Visual/Editing/TestSceneDistanceSnapGrid.cs b/osu.Game.Tests/Visual/Editing/TestSceneDistanceSnapGrid.cs index 21b925a257..70e4420a45 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneDistanceSnapGrid.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneDistanceSnapGrid.cs @@ -187,7 +187,7 @@ namespace osu.Game.Tests.Visual.Editing private class SnapProvider : IDistanceSnapProvider { - public SnapResult FindSnappedPositionAndTime(Vector2 screenSpacePosition, SnapType snapType = SnapType.Grids) => new SnapResult(screenSpacePosition, 0); + public SnapResult FindSnappedPositionAndTime(Vector2 screenSpacePosition, SnapType snapType = SnapType.AllGrids) => new SnapResult(screenSpacePosition, 0); public Bindable DistanceSpacingMultiplier { get; } = new BindableDouble(1); diff --git a/osu.Game/Rulesets/Edit/HitObjectComposer.cs b/osu.Game/Rulesets/Edit/HitObjectComposer.cs index e2dbd2acdc..9ce6d957fc 100644 --- a/osu.Game/Rulesets/Edit/HitObjectComposer.cs +++ b/osu.Game/Rulesets/Edit/HitObjectComposer.cs @@ -428,7 +428,7 @@ namespace osu.Game.Rulesets.Edit var playfield = PlayfieldAtScreenSpacePosition(screenSpacePosition); double? targetTime = null; - if (snapType.HasFlagFast(SnapType.Grids)) + if (snapType.HasFlagFast(SnapType.GlobalGrids)) { if (playfield is ScrollingPlayfield scrollingPlayfield) { diff --git a/osu.Game/Rulesets/Edit/SnapType.cs b/osu.Game/Rulesets/Edit/SnapType.cs index 6eb46457c8..f5f9ab0437 100644 --- a/osu.Game/Rulesets/Edit/SnapType.cs +++ b/osu.Game/Rulesets/Edit/SnapType.cs @@ -11,8 +11,24 @@ namespace osu.Game.Rulesets.Edit public enum SnapType { None = 0, + + /// + /// Snapping to visible nearby objects. + /// NearbyObjects = 1 << 0, - Grids = 1 << 1, - All = NearbyObjects | Grids, + + /// + /// Grids which are global to the playfield. + /// + GlobalGrids = 1 << 1, + + /// + /// Grids which are relative to other nearby hit objects. + /// + RelativeGrids = 1 << 2, + + AllGrids = RelativeGrids | GlobalGrids, + + All = NearbyObjects | GlobalGrids | RelativeGrids, } } From 1cd69220ef640424b79f19784c557ac183333c2d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 25 May 2023 21:41:42 +0900 Subject: [PATCH 59/69] Fix slider path placement snapping non-head nodes to distanced snapping grid As discussed at https://github.com/ppy/osu/discussions/23531. --- .../Blueprints/Sliders/Components/PathControlPointVisualiser.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs index 17d0fc457a..c56ffcb140 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs @@ -309,7 +309,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components } else { - var result = snapProvider?.FindSnappedPositionAndTime(Parent.ToScreenSpace(e.MousePosition)); + var result = snapProvider?.FindSnappedPositionAndTime(Parent.ToScreenSpace(e.MousePosition), SnapType.GlobalGrids); Vector2 movementDelta = Parent.ToLocalSpace(result?.ScreenSpacePosition ?? Parent.ToScreenSpace(e.MousePosition)) - dragStartPositions[draggedControlPointIndex] - hitObject.Position; From beeca5a8dd1bc84f036f25caa5f4e00d4204d417 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 25 May 2023 16:17:44 +0200 Subject: [PATCH 60/69] Use alternative layouting implementation --- .../Edit/Compose/Components/SelectionBox.cs | 28 +++++++++++-------- 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionBox.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionBox.cs index 677b0cd76e..fc57cbf17f 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionBox.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionBox.cs @@ -247,8 +247,6 @@ namespace osu.Game.Screens.Edit.Compose.Components Height = 30, Direction = FillDirection.Horizontal, Margin = new MarginPadding(button_padding), - Anchor = Anchor.BottomCentre, - Origin = Anchor.TopCentre } }; @@ -367,24 +365,30 @@ namespace osu.Game.Screens.Edit.Compose.Components { buttons.Position = Vector2.Zero; - var buttonsQuad = buttons.ScreenSpaceDrawQuad; var thisQuad = ScreenSpaceDrawQuad; // Shrink the parent quad to give a bit of padding so the buttons don't stick *right* on the border. // AABBFloat assumes no rotation. one would hope the whole editor is not being rotated. var parentQuad = Parent.ScreenSpaceDrawQuad.AABBFloat.Shrink(ToLocalSpace(thisQuad.TopLeft + new Vector2(button_padding * 2))); - float leftExcess = buttonsQuad.TopLeft.X - parentQuad.TopLeft.X; - float rightExcess = parentQuad.TopRight.X - buttonsQuad.TopRight.X; - float bottomExcess = thisQuad.BottomLeft.Y + buttonsQuad.Height - parentQuad.BottomLeft.Y; + float topExcess = thisQuad.TopLeft.Y - parentQuad.TopLeft.Y; + float bottomExcess = parentQuad.BottomLeft.Y - thisQuad.BottomLeft.Y; + float leftExcess = thisQuad.TopLeft.X - parentQuad.TopLeft.X; + float rightExcess = parentQuad.TopRight.X - thisQuad.TopRight.X; - if (leftExcess < 0) - buttons.X += ToLocalSpace(thisQuad.TopLeft - new Vector2(leftExcess)).X; - else if (rightExcess < 0) - buttons.X -= ToLocalSpace(thisQuad.TopLeft - new Vector2(rightExcess)).X; + if (topExcess > bottomExcess) + { + buttons.Anchor = Anchor.TopCentre; + buttons.Origin = Anchor.BottomCentre; + } + else + { + buttons.Anchor = Anchor.BottomCentre; + buttons.Origin = Anchor.TopCentre; + } - if (bottomExcess > 0) - buttons.Y += ToLocalSpace(thisQuad.TopLeft - new Vector2(bottomExcess)).X; + if (leftExcess < 0) buttons.X += ToLocalSpace(thisQuad.TopLeft - new Vector2(leftExcess)).X; + if (rightExcess < 0) buttons.X += ToLocalSpace(thisQuad.TopLeft + new Vector2(rightExcess)).X; } } } From 949de35664fa32e6abe115a1c7bea1d30843e1b4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 25 May 2023 23:19:55 +0900 Subject: [PATCH 61/69] Ensure selection is reset after immediately deleting objects Closes https://github.com/ppy/osu/issues/23518. --- osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs index f73a25c339..5cedf1ca42 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs @@ -311,6 +311,7 @@ namespace osu.Game.Screens.Edit.Compose.Components protected void DeleteSelected() { DeleteItems(SelectedItems.ToArray()); + DeselectAll(); } #endregion From 3ad5f8b9c9973788780e540cb98e5b260367df98 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 25 May 2023 16:39:54 +0200 Subject: [PATCH 62/69] Polish a few extreme edge cases --- .../Screens/Edit/Compose/Components/SelectionBox.cs | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionBox.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionBox.cs index fc57cbf17f..1c5faed0e5 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionBox.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionBox.cs @@ -376,7 +376,12 @@ 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) + if (topExcess + bottomExcess < buttons.Height + button_padding) + { + buttons.Anchor = Anchor.BottomCentre; + buttons.Origin = Anchor.BottomCentre; + } + else if (topExcess > bottomExcess) { buttons.Anchor = Anchor.TopCentre; buttons.Origin = Anchor.BottomCentre; @@ -387,8 +392,7 @@ namespace osu.Game.Screens.Edit.Compose.Components buttons.Origin = Anchor.TopCentre; } - if (leftExcess < 0) buttons.X += ToLocalSpace(thisQuad.TopLeft - new Vector2(leftExcess)).X; - if (rightExcess < 0) buttons.X += ToLocalSpace(thisQuad.TopLeft + new Vector2(rightExcess)).X; + buttons.X += ToLocalSpace(thisQuad.TopLeft - new Vector2(Math.Min(0, leftExcess)) + new Vector2(Math.Min(0, rightExcess))).X; } } } From 78f41f71095b25dd01a490aeddb3eb8e83aa47c7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 25 May 2023 17:33:41 +0200 Subject: [PATCH 63/69] Fix spelling --- osu.Game/Beatmaps/IBeatmap.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Beatmaps/IBeatmap.cs b/osu.Game/Beatmaps/IBeatmap.cs index 671f5ce8db..9dc3084cb5 100644 --- a/osu.Game/Beatmaps/IBeatmap.cs +++ b/osu.Game/Beatmaps/IBeatmap.cs @@ -108,7 +108,7 @@ namespace osu.Game.Beatmaps /// Find the total milliseconds between the first and last hittable objects. /// /// - /// This is cached to , so using that is preferrable when available. + /// This is cached to , so using that is preferable when available. /// public static double CalculatePlayableLength(this IBeatmap beatmap) => CalculatePlayableLength(beatmap.HitObjects); @@ -134,7 +134,7 @@ namespace osu.Game.Beatmaps /// Find the total milliseconds between the first and last hittable objects. /// /// - /// This is cached to , so using that is preferrable when available. + /// This is cached to , so using that is preferable when available. /// public static double CalculatePlayableLength(IEnumerable objects) { From adee624a8fe86da21a3f1377aae10aa96eec9007 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 25 May 2023 21:32:19 +0200 Subject: [PATCH 64/69] Change `PlacementBlueprint.AutomaticBankAssignment` to property Mostly for consistency. --- osu.Game/Rulesets/Edit/PlacementBlueprint.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/Edit/PlacementBlueprint.cs b/osu.Game/Rulesets/Edit/PlacementBlueprint.cs index a0a04c13d0..717c026ded 100644 --- a/osu.Game/Rulesets/Edit/PlacementBlueprint.cs +++ b/osu.Game/Rulesets/Edit/PlacementBlueprint.cs @@ -35,7 +35,7 @@ namespace osu.Game.Rulesets.Edit /// /// Whether the sample bank should be taken from the previous hit object. /// - public bool AutomaticBankAssignment; + public bool AutomaticBankAssignment { get; set; } /// /// The that is being placed. From 7d8f08c0ea0e2e1f6d2952926f85557ff9447350 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 25 May 2023 21:49:29 +0200 Subject: [PATCH 65/69] Fix `ComposeBlueprintContainer` briefly assigning `auto` bank Seems to have had no consequence due to the way `AutomaticBankAssignment` works (that flag is checked in `PlacementBlueprint.UpdateTimeAndPosition()`, which runs essentially every frame), but let's avoid putting it there at all ever. --- .../Edit/Compose/Components/ComposeBlueprintContainer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs index 25babae6ff..c8cfac454a 100644 --- a/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs @@ -203,7 +203,7 @@ namespace osu.Game.Screens.Edit.Compose.Components if (bankName == EditorSelectionHandler.HIT_BANK_AUTO) CurrentPlacement.AutomaticBankAssignment = state == TernaryState.True; - if (state == TernaryState.True) + else if (state == TernaryState.True) CurrentPlacement.HitObject.Samples = CurrentPlacement.HitObject.Samples.Select(s => s.With(newBank: bankName)).ToList(); } From 3c69956b55e29484888e9ec3564d1df30267aae4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 26 May 2023 10:41:29 +0900 Subject: [PATCH 66/69] Fix incorrect catch grid specification --- osu.Game.Rulesets.Catch/Edit/CatchHitObjectComposer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Catch/Edit/CatchHitObjectComposer.cs b/osu.Game.Rulesets.Catch/Edit/CatchHitObjectComposer.cs index 611e69d614..8afeca3e51 100644 --- a/osu.Game.Rulesets.Catch/Edit/CatchHitObjectComposer.cs +++ b/osu.Game.Rulesets.Catch/Edit/CatchHitObjectComposer.cs @@ -132,7 +132,7 @@ namespace osu.Game.Rulesets.Catch.Edit result.ScreenSpacePosition.X = screenSpacePosition.X; - if (snapType.HasFlagFast(SnapType.GlobalGrids)) + if (snapType.HasFlagFast(SnapType.RelativeGrids)) { if (distanceSnapGrid.IsPresent && distanceSnapGrid.GetSnappedPosition(result.ScreenSpacePosition) is SnapResult snapResult && Vector2.Distance(snapResult.ScreenSpacePosition, result.ScreenSpacePosition) < distance_snap_radius) From cb468fa4eca95567cbbbad4c19ba2b507792182b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 26 May 2023 19:59:19 +0900 Subject: [PATCH 67/69] Fix `OverlappingScrollAlgorithm` returning incorrect results for `TimeAt` before first control point --- .../Algorithms/OverlappingScrollAlgorithm.cs | 29 +++++-------------- 1 file changed, 7 insertions(+), 22 deletions(-) diff --git a/osu.Game/Rulesets/UI/Scrolling/Algorithms/OverlappingScrollAlgorithm.cs b/osu.Game/Rulesets/UI/Scrolling/Algorithms/OverlappingScrollAlgorithm.cs index 54079c7895..ead893c0af 100644 --- a/osu.Game/Rulesets/UI/Scrolling/Algorithms/OverlappingScrollAlgorithm.cs +++ b/osu.Game/Rulesets/UI/Scrolling/Algorithms/OverlappingScrollAlgorithm.cs @@ -1,9 +1,7 @@ // 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.Diagnostics; using System.Linq; using osu.Framework.Lists; using osu.Game.Beatmaps.ControlPoints; @@ -40,29 +38,16 @@ namespace osu.Game.Rulesets.UI.Scrolling.Algorithms public double TimeAt(float position, double currentTime, double timeRange, float scrollLength) { - // Find the control point relating to the position. + Debug.Assert(controlPoints.Count > 0); + + // Iterate over control points and find the most relevant for the provided position. // Note: Due to velocity adjustments, overlapping control points will provide multiple valid time values for a single position // As such, this operation provides unexpected results by using the latter of the control points. + var relevantControlPoint = controlPoints.LastOrDefault(cp => PositionAt(cp.Time, currentTime, timeRange, scrollLength) <= position) ?? controlPoints.First(); - int i = 0; - float pos = 0; + float positionAtControlPoint = PositionAt(relevantControlPoint.Time, currentTime, timeRange, scrollLength); - for (; i < controlPoints.Count; i++) - { - float lastPos = pos; - pos = PositionAt(controlPoints[i].Time, currentTime, timeRange, scrollLength); - - if (pos > position) - { - i--; - pos = lastPos; - break; - } - } - - i = Math.Clamp(i, 0, controlPoints.Count - 1); - - return controlPoints[i].Time + (position - pos) * timeRange / controlPoints[i].Multiplier / scrollLength; + return relevantControlPoint.Time + (position - positionAtControlPoint) * timeRange / relevantControlPoint.Multiplier / scrollLength; } public void Reset() From 6b0e215246021777389876701ef7114c64bb4e89 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 26 May 2023 20:39:22 +0900 Subject: [PATCH 68/69] Add `(int)` flooring and handle potential `NaN` value --- osu.Game/Screens/Play/GameplayMenuOverlay.cs | 23 ++++++++++++++----- osu.Game/Screens/Play/HUD/SongProgressInfo.cs | 4 ++-- 2 files changed, 19 insertions(+), 8 deletions(-) diff --git a/osu.Game/Screens/Play/GameplayMenuOverlay.cs b/osu.Game/Screens/Play/GameplayMenuOverlay.cs index 52251db898..3a2e381f97 100644 --- a/osu.Game/Screens/Play/GameplayMenuOverlay.cs +++ b/osu.Game/Screens/Play/GameplayMenuOverlay.cs @@ -212,18 +212,29 @@ namespace osu.Game.Screens.Play playInfoText.AddText("Retry count: "); playInfoText.AddText(retries.ToString(), cp => cp.Font = cp.Font.With(weight: FontWeight.Bold)); - if (gameplayState != null && gameplayClock != null) + if (getSongProgress() is int progress) { - (double firstHitTime, double lastHitTime) = gameplayState.Beatmap.CalculatePlayableBounds(); - - double progress = Math.Clamp((gameplayClock.CurrentTime - firstHitTime) / (lastHitTime - firstHitTime), 0, 1); - playInfoText.NewLine(); playInfoText.AddText("Song progress: "); - playInfoText.AddText(progress.ToString("0%"), cp => cp.Font = cp.Font.With(weight: FontWeight.Bold)); + playInfoText.AddText($"{progress}%", cp => cp.Font = cp.Font.With(weight: FontWeight.Bold)); } } + private int? getSongProgress() + { + if (gameplayClock == null || gameplayState == null) + return null; + + (double firstHitTime, double lastHitTime) = gameplayState.Beatmap.CalculatePlayableBounds(); + + double playableLength = (lastHitTime - firstHitTime); + + if (playableLength == 0) + return 0; + + return (int)Math.Clamp(((gameplayClock.CurrentTime - firstHitTime) / playableLength) * 100, 0, 100); + } + private partial class Button : DialogButton { // required to ensure keyboard navigation always starts from an extremity (unless the cursor is moved) diff --git a/osu.Game/Screens/Play/HUD/SongProgressInfo.cs b/osu.Game/Screens/Play/HUD/SongProgressInfo.cs index c04ecd671f..2f137f7e78 100644 --- a/osu.Game/Screens/Play/HUD/SongProgressInfo.cs +++ b/osu.Game/Screens/Play/HUD/SongProgressInfo.cs @@ -145,12 +145,12 @@ namespace osu.Game.Screens.Play.HUD double time = gameplayClock?.CurrentTime ?? Time.Current; double songCurrentTime = time - startTime; - int currentPercent = Math.Max(0, Math.Min(100, (int)(songCurrentTime / songLength * 100))); + int currentPercent = songLength == 0 ? 0 : Math.Max(0, Math.Min(100, (int)(songCurrentTime / songLength * 100))); int currentSecond = (int)Math.Floor(songCurrentTime / 1000.0); if (currentPercent != previousPercent) { - progress.Text = currentPercent + @"%"; + progress.Text = $@"{currentPercent}%"; previousPercent = currentPercent; } From e35201cb991aac8b27d68cb3dd49eb431d68d844 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 26 May 2023 19:51:20 +0200 Subject: [PATCH 69/69] Don't snap non-head slider nodes to distance grid during placement either 1cd69220ef640424b79f19784c557ac183333c2d only disabled snapping the aforementioned nodes to distance grid for already-placed sliders. `SliderPlacementBlueprint` has its own logic for placement, so the fix needs to be mirrored there too. --- .../Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs index 28ceb80627..966092c6fe 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs @@ -198,7 +198,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders } // Update the cursor position. - var result = snapProvider?.FindSnappedPositionAndTime(inputManager.CurrentState.Mouse.Position); + var result = snapProvider?.FindSnappedPositionAndTime(inputManager.CurrentState.Mouse.Position, state == SliderPlacementState.Body ? SnapType.GlobalGrids : SnapType.All); cursor.Position = ToLocalSpace(result?.ScreenSpacePosition ?? inputManager.CurrentState.Mouse.Position) - HitObject.Position; } else if (cursor != null)