From 244ca4e8c75293d7f00c62aeae010316f95cdfc4 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Mon, 8 May 2023 11:54:33 +0200 Subject: [PATCH 01/91] Remove dependency on SamplesBindable --- .../Edit/Compose/Components/Timeline/SamplePointPiece.cs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs index b02cfb505e..1f673a7b10 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs @@ -26,12 +26,12 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline { public readonly HitObject HitObject; - private readonly BindableList samplesBindable; + [Resolved(canBeNull: true)] + private EditorBeatmap editorBeatmap { get; set; } = null!; public SamplePointPiece(HitObject hitObject) { HitObject = hitObject; - samplesBindable = hitObject.SamplesBindable.GetBoundCopy(); } protected override Color4 GetRepresentingColour(OsuColour colours) => colours.Pink; @@ -39,7 +39,8 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline [BackgroundDependencyLoader] private void load() { - samplesBindable.BindCollectionChanged((_, _) => updateText(), true); + HitObject.DefaultsApplied += _ => updateText(); + updateText(); } protected override bool OnClick(ClickEvent e) @@ -50,7 +51,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline private void updateText() { - Label.Text = $"{GetBankValue(samplesBindable)} {GetVolumeValue(samplesBindable)}"; + Label.Text = $"{GetBankValue(HitObject.Samples)} {GetVolumeValue(HitObject.Samples)}"; } public static string? GetBankValue(IEnumerable samples) From b447018e5b54009a297495d7ef96a48bbc5a1d18 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Mon, 8 May 2023 11:55:58 +0200 Subject: [PATCH 02/91] remove editor beatmap --- .../Edit/Compose/Components/Timeline/SamplePointPiece.cs | 3 --- 1 file changed, 3 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs index 1f673a7b10..4f3526b531 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs @@ -26,9 +26,6 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline { public readonly HitObject HitObject; - [Resolved(canBeNull: true)] - private EditorBeatmap editorBeatmap { get; set; } = null!; - public SamplePointPiece(HitObject hitObject) { HitObject = hitObject; From cb7b747d52dec03881c056cbc6545777a5c5dc47 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Mon, 8 May 2023 12:38:53 +0200 Subject: [PATCH 03/91] create NodeSamplePointPiece --- .../Timeline/NodeSamplePointPiece.cs | 54 +++++++++++++++++++ .../Components/Timeline/SamplePointPiece.cs | 10 ++-- 2 files changed, 61 insertions(+), 3 deletions(-) create mode 100644 osu.Game/Screens/Edit/Compose/Components/Timeline/NodeSamplePointPiece.cs diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/NodeSamplePointPiece.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/NodeSamplePointPiece.cs new file mode 100644 index 0000000000..dc91401c13 --- /dev/null +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/NodeSamplePointPiece.cs @@ -0,0 +1,54 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Collections.Generic; +using osu.Framework.Graphics.UserInterface; +using osu.Game.Audio; +using osu.Game.Graphics; +using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Objects.Types; +using osuTK.Graphics; + +namespace osu.Game.Screens.Edit.Compose.Components.Timeline +{ + public partial class NodeSamplePointPiece : SamplePointPiece + { + public readonly int NodeIndex; + + public NodeSamplePointPiece(HitObject hitObject, int nodeIndex) + : base(hitObject) + { + if (hitObject is not IHasRepeats) + throw new System.ArgumentException($"HitObject must implement {nameof(IHasRepeats)}", nameof(hitObject)); + + NodeIndex = nodeIndex; + } + + protected override Color4 GetRepresentingColour(OsuColour colours) => colours.Purple; + + protected override IList GetSamples() + { + var hasRepeats = (IHasRepeats)HitObject; + return NodeIndex < hasRepeats.NodeSamples.Count ? hasRepeats.NodeSamples[NodeIndex] : HitObject.Samples; + } + + public override Popover GetPopover() => new NodeSampleEditPopover(HitObject); + + public partial class NodeSampleEditPopover : SampleEditPopover + { + private readonly int nodeIndex; + + protected override IList GetSamples(HitObject ho) + { + var hasRepeats = (IHasRepeats)ho; + return nodeIndex < hasRepeats.NodeSamples.Count ? hasRepeats.NodeSamples[nodeIndex] : ho.Samples; + } + + public NodeSampleEditPopover(HitObject hitObject, int nodeIndex = 0) + : base(hitObject) + { + this.nodeIndex = nodeIndex; + } + } + } +} diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs index 4f3526b531..064e96c255 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs @@ -48,7 +48,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline private void updateText() { - Label.Text = $"{GetBankValue(HitObject.Samples)} {GetVolumeValue(HitObject.Samples)}"; + Label.Text = $"{GetBankValue(GetSamples())} {GetVolumeValue(GetSamples())}"; } public static string? GetBankValue(IEnumerable samples) @@ -61,7 +61,9 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline return samples.Count == 0 ? 0 : samples.Max(o => o.Volume); } - public Popover GetPopover() => new SampleEditPopover(HitObject); + protected virtual IList GetSamples() => HitObject.Samples; + + public virtual Popover GetPopover() => new SampleEditPopover(HitObject); public partial class SampleEditPopover : OsuPopover { @@ -70,6 +72,8 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline private LabelledTextBox bank = null!; private IndeterminateSliderWithTextBoxInput volume = null!; + protected virtual IList GetSamples(HitObject ho) => ho.Samples; + [Resolved(canBeNull: true)] private EditorBeatmap beatmap { get; set; } = null!; @@ -112,7 +116,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline // if the piece belongs to a currently selected object, assume that the user wants to change all selected objects. // if the piece belongs to an unselected object, operate on that object alone, independently of the selection. var relevantObjects = (beatmap.SelectedHitObjects.Contains(hitObject) ? beatmap.SelectedHitObjects : hitObject.Yield()).ToArray(); - var relevantSamples = relevantObjects.Select(h => h.Samples).ToArray(); + var relevantSamples = relevantObjects.Select(GetSamples).ToArray(); // even if there are multiple objects selected, we can still display sample volume or bank if they all have the same value. string? commonBank = getCommonBank(relevantSamples); From 32f945d304509a4e3f359f667caa4ccb3d473e19 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Mon, 8 May 2023 13:05:53 +0200 Subject: [PATCH 04/91] fix updating wrong samples --- .../Components/Timeline/NodeSamplePointPiece.cs | 4 ++-- .../Components/Timeline/SamplePointPiece.cs | 14 +++++++++----- 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/NodeSamplePointPiece.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/NodeSamplePointPiece.cs index dc91401c13..437d3c3d3a 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/NodeSamplePointPiece.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/NodeSamplePointPiece.cs @@ -32,7 +32,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline return NodeIndex < hasRepeats.NodeSamples.Count ? hasRepeats.NodeSamples[NodeIndex] : HitObject.Samples; } - public override Popover GetPopover() => new NodeSampleEditPopover(HitObject); + public override Popover GetPopover() => new NodeSampleEditPopover(HitObject, NodeIndex); public partial class NodeSampleEditPopover : SampleEditPopover { @@ -44,7 +44,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline return nodeIndex < hasRepeats.NodeSamples.Count ? hasRepeats.NodeSamples[nodeIndex] : ho.Samples; } - public NodeSampleEditPopover(HitObject hitObject, int nodeIndex = 0) + public NodeSampleEditPopover(HitObject hitObject, int nodeIndex) : base(hitObject) { this.nodeIndex = nodeIndex; diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs index 064e96c255..50b1ec80ff 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs @@ -158,9 +158,11 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline foreach (var h in objects) { - for (int i = 0; i < h.Samples.Count; i++) + var samples = GetSamples(h); + + for (int i = 0; i < samples.Count; i++) { - h.Samples[i] = h.Samples[i].With(newBank: newBank); + samples[i] = samples[i].With(newBank: newBank); } beatmap.Update(h); @@ -171,7 +173,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline private void updateBankPlaceholderText(IEnumerable objects) { - string? commonBank = getCommonBank(objects.Select(h => h.Samples).ToArray()); + string? commonBank = getCommonBank(objects.Select(GetSamples).ToArray()); bank.PlaceholderText = string.IsNullOrEmpty(commonBank) ? "(multiple)" : string.Empty; } @@ -184,9 +186,11 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline foreach (var h in objects) { - for (int i = 0; i < h.Samples.Count; i++) + var samples = GetSamples(h); + + for (int i = 0; i < samples.Count; i++) { - h.Samples[i] = h.Samples[i].With(newVolume: newVolume.Value); + samples[i] = samples[i].With(newVolume: newVolume.Value); } beatmap.Update(h); From e945846759e16fb51d589d665accae195ab9957a Mon Sep 17 00:00:00 2001 From: OliBomby Date: Mon, 8 May 2023 13:10:24 +0200 Subject: [PATCH 05/91] Add node sample pieces to timeline blueprint --- .../Timeline/TimelineHitObjectBlueprint.cs | 28 ++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs index ea063e9216..e7c14fc53d 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs @@ -32,6 +32,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline private const float circle_size = 38; private Container? repeatsContainer; + private Container? nodeSamplesContainer; public Action? OnDragHandled = null!; @@ -49,6 +50,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline private readonly Border border; private readonly Container colouredComponents; + private readonly Container sampleComponents; private readonly OsuSpriteText comboIndexText; [Resolved] @@ -101,10 +103,15 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline }, } }, + sampleComponents = new Container + { + RelativeSizeAxes = Axes.Both, + }, new SamplePointPiece(Item) { Anchor = Anchor.BottomLeft, - Origin = Anchor.TopCentre + Origin = Anchor.TopCentre, + X = Item is IHasRepeats ? -10 : 0 }, }); @@ -233,6 +240,25 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline X = (float)(i + 1) / (repeats.RepeatCount + 1) }); } + + // Add node sample pieces + nodeSamplesContainer?.Expire(); + + sampleComponents.Add(nodeSamplesContainer = new Container + { + RelativeSizeAxes = Axes.Both, + }); + + for (int i = 0; i < repeats.RepeatCount + 2; i++) + { + nodeSamplesContainer.Add(new NodeSamplePointPiece(Item, i) + { + X = (float)i / (repeats.RepeatCount + 1), + RelativePositionAxes = Axes.X, + Anchor = Anchor.BottomLeft, + Origin = Anchor.TopCentre, + }); + } } protected override bool ShouldBeConsideredForInput(Drawable child) => true; From 3b5bae774259451c62c16a4202c6f4e963cb9fde Mon Sep 17 00:00:00 2001 From: OliBomby Date: Mon, 8 May 2023 13:16:30 +0200 Subject: [PATCH 06/91] Abbreviate common bank names on timeline --- .../Compose/Components/Timeline/SamplePointPiece.cs | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs index 50b1ec80ff..1f3f5305b8 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs @@ -48,7 +48,18 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline private void updateText() { - Label.Text = $"{GetBankValue(GetSamples())} {GetVolumeValue(GetSamples())}"; + Label.Text = $"{abbreviateBank(GetBankValue(GetSamples()))} {GetVolumeValue(GetSamples())}"; + } + + private static string? abbreviateBank(string? bank) + { + return bank switch + { + "normal" => "N", + "soft" => "S", + "drum" => "D", + _ => bank + }; } public static string? GetBankValue(IEnumerable samples) From 88d840a60d143cd404c6f033380653abc7550055 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Mon, 8 May 2023 14:42:15 +0200 Subject: [PATCH 07/91] fix assigned hitsounds dont have bank or volume --- osu.Game/Rulesets/Objects/HitObject.cs | 4 ++-- .../Screens/Edit/Compose/Components/EditorSelectionHandler.cs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/Rulesets/Objects/HitObject.cs b/osu.Game/Rulesets/Objects/HitObject.cs index a4cb976d50..352ee72962 100644 --- a/osu.Game/Rulesets/Objects/HitObject.cs +++ b/osu.Game/Rulesets/Objects/HitObject.cs @@ -210,10 +210,10 @@ namespace osu.Game.Rulesets.Objects /// /// The name of the sample. /// A populated . - protected HitSampleInfo GetSampleInfo(string sampleName = HitSampleInfo.HIT_NORMAL) + public HitSampleInfo GetSampleInfo(string sampleName = HitSampleInfo.HIT_NORMAL) { var hitnormalSample = Samples.FirstOrDefault(s => s.Name == HitSampleInfo.HIT_NORMAL); - return hitnormalSample == null ? new HitSampleInfo(sampleName) : hitnormalSample.With(newName: sampleName); + return hitnormalSample == null ? new HitSampleInfo(sampleName, SampleControlPoint.DEFAULT_BANK, volume: 100) : hitnormalSample.With(newName: sampleName); } } diff --git a/osu.Game/Screens/Edit/Compose/Components/EditorSelectionHandler.cs b/osu.Game/Screens/Edit/Compose/Components/EditorSelectionHandler.cs index 357cc940f2..694b24c567 100644 --- a/osu.Game/Screens/Edit/Compose/Components/EditorSelectionHandler.cs +++ b/osu.Game/Screens/Edit/Compose/Components/EditorSelectionHandler.cs @@ -122,7 +122,7 @@ namespace osu.Game.Screens.Edit.Compose.Components if (h.Samples.Any(s => s.Name == sampleName)) return; - h.Samples.Add(new HitSampleInfo(sampleName)); + h.Samples.Add(h.GetSampleInfo(sampleName)); EditorBeatmap.Update(h); }); } From 4c365304351fdc3d9f62c6cab40d11e421be4ae3 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Mon, 8 May 2023 15:08:40 +0200 Subject: [PATCH 08/91] allow editing additions in sample point piece --- .../Components/ComposeBlueprintContainer.cs | 4 +- .../Compose/Components/SelectionHandler.cs | 2 +- .../Components/Timeline/SamplePointPiece.cs | 146 ++++++++++++++++++ 3 files changed, 149 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs index 453e4b9130..0a87314a2a 100644 --- a/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs @@ -206,10 +206,10 @@ namespace osu.Game.Screens.Edit.Compose.Components yield return new TernaryButton(NewCombo, "New combo", () => new SpriteIcon { Icon = FontAwesome.Regular.DotCircle }); foreach (var kvp in SelectionHandler.SelectionSampleStates) - yield return new TernaryButton(kvp.Value, kvp.Key.Replace("hit", string.Empty).Titleize(), () => getIconForSample(kvp.Key)); + yield return new TernaryButton(kvp.Value, kvp.Key.Replace("hit", string.Empty).Titleize(), () => GetIconForSample(kvp.Key)); } - private Drawable getIconForSample(string sampleName) + public static Drawable GetIconForSample(string sampleName) { switch (sampleName) { diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs index 9e4fb26688..93d51c849e 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs @@ -279,7 +279,7 @@ namespace osu.Game.Screens.Edit.Compose.Components /// /// Given a selection target and a function of truth, retrieve the correct ternary state for display. /// - protected static TernaryState GetStateFromSelection(IEnumerable selection, Func func) + public static TernaryState GetStateFromSelection(IEnumerable selection, Func func) { if (selection.Any(func)) return selection.All(func) ? TernaryState.True : TernaryState.Indeterminate; diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs index 1f3f5305b8..0cac914e2c 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Linq; +using Humanizer; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Extensions; @@ -14,11 +15,14 @@ using osu.Framework.Graphics.UserInterface; using osu.Framework.Input.Events; using osu.Game.Audio; using osu.Game.Graphics; +using osu.Game.Graphics.UserInterface; using osu.Game.Graphics.UserInterfaceV2; using osu.Game.Rulesets.Objects; +using osu.Game.Screens.Edit.Components.TernaryButtons; using osu.Game.Screens.Edit.Timing; using osuTK; using osuTK.Graphics; +using osuTK.Input; namespace osu.Game.Screens.Edit.Compose.Components.Timeline { @@ -83,6 +87,8 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline private LabelledTextBox bank = null!; private IndeterminateSliderWithTextBoxInput volume = null!; + private FillFlowContainer togglesCollection = null!; + protected virtual IList GetSamples(HitObject ho) => ho.Samples; [Resolved(canBeNull: true)] @@ -108,6 +114,13 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline Spacing = new Vector2(0, 10), Children = new Drawable[] { + togglesCollection = new FillFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Direction = FillDirection.Horizontal, + Spacing = new Vector2(5, 5), + }, bank = new LabelledTextBox { Label = "Bank Name", @@ -149,6 +162,10 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline bank.OnCommit += (_, _) => bank.Current.Value = getCommonBank(relevantSamples); volume.Current.BindValueChanged(val => updateVolumeFor(relevantObjects, val.NewValue)); + + createStateBindables(relevantObjects); + updateTernaryStates(relevantObjects); + togglesCollection.AddRange(createTernaryButtons().Select(b => new DrawableTernaryButton(b) { RelativeSizeAxes = Axes.None, Size = new Vector2(40, 40) })); } protected override void LoadComplete() @@ -209,6 +226,135 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline beatmap.EndChange(); } + + #region hitsound toggles + + private readonly Dictionary> selectionSampleStates = new Dictionary>(); + + private void createStateBindables(IEnumerable objects) + { + foreach (string sampleName in HitSampleInfo.AllAdditions) + { + var bindable = new Bindable + { + Description = sampleName.Replace("hit", string.Empty).Titleize() + }; + + bindable.ValueChanged += state => + { + switch (state.NewValue) + { + case TernaryState.False: + removeHitSampleFor(objects, sampleName); + break; + + case TernaryState.True: + addHitSampleFor(objects, sampleName); + break; + } + }; + + selectionSampleStates[sampleName] = bindable; + } + } + + private void updateTernaryStates(IEnumerable objects) + { + foreach ((string sampleName, var bindable) in selectionSampleStates) + { + bindable.Value = SelectionHandler.GetStateFromSelection(objects, h => GetSamples(h).Any(s => s.Name == sampleName)); + } + } + + private IEnumerable createTernaryButtons() + { + foreach ((string sampleName, var bindable) in selectionSampleStates) + yield return new TernaryButton(bindable, string.Empty, () => ComposeBlueprintContainer.GetIconForSample(sampleName)); + } + + private void addHitSampleFor(IEnumerable objects, string sampleName) + { + if (string.IsNullOrEmpty(sampleName)) + return; + + beatmap.BeginChange(); + + foreach (var h in objects) + { + var samples = GetSamples(h); + + // Make sure there isn't already an existing sample + if (samples.Any(s => s.Name == sampleName)) + return; + + samples.Add(h.GetSampleInfo(sampleName)); + beatmap.Update(h); + } + + beatmap.EndChange(); + } + + private void removeHitSampleFor(IEnumerable objects, string sampleName) + { + if (string.IsNullOrEmpty(sampleName)) + return; + + beatmap.BeginChange(); + + foreach (var h in objects) + { + var samples = GetSamples(h); + + for (int i = 0; i < samples.Count; i++) + { + if (samples[i].Name == sampleName) + samples.RemoveAt(i--); + } + + beatmap.Update(h); + } + + beatmap.EndChange(); + } + + protected override bool OnKeyDown(KeyDownEvent e) + { + if (e.ControlPressed || e.AltPressed || e.SuperPressed || e.ShiftPressed || !checkRightToggleFromKey(e.Key, out int rightIndex)) + return base.OnKeyDown(e); + + var item = togglesCollection.ElementAtOrDefault(rightIndex); + + if (item is not DrawableTernaryButton button) return base.OnKeyDown(e); + + button.Button.Toggle(); + return true; + } + + private bool checkRightToggleFromKey(Key key, out int index) + { + switch (key) + { + case Key.W: + index = 0; + break; + + case Key.E: + index = 1; + break; + + case Key.R: + index = 2; + break; + + default: + index = -1; + break; + } + + return index >= 0; + } + + #endregion } } } From bb8285e2ef99cba43b0a95e2082841fc1a6af7a5 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Mon, 8 May 2023 15:14:25 +0200 Subject: [PATCH 09/91] cleanup code duplication --- .../Components/Timeline/SamplePointPiece.cs | 62 +++++++------------ 1 file changed, 23 insertions(+), 39 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs index 0cac914e2c..69fff8a8a7 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs @@ -1,6 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System; using System.Collections.Generic; using System.Linq; using Humanizer; @@ -177,28 +178,34 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline private static string? getCommonBank(IList[] relevantSamples) => relevantSamples.Select(GetBankValue).Distinct().Count() == 1 ? GetBankValue(relevantSamples.First()) : null; private static int? getCommonVolume(IList[] relevantSamples) => relevantSamples.Select(GetVolumeValue).Distinct().Count() == 1 ? GetVolumeValue(relevantSamples.First()) : null; - private void updateBankFor(IEnumerable objects, string? newBank) + private void updateFor(IEnumerable objects, Action> updateAction) { - if (string.IsNullOrEmpty(newBank)) - return; - beatmap.BeginChange(); foreach (var h in objects) { var samples = GetSamples(h); - - for (int i = 0; i < samples.Count; i++) - { - samples[i] = samples[i].With(newBank: newBank); - } - + updateAction(h, samples); beatmap.Update(h); } beatmap.EndChange(); } + private void updateBankFor(IEnumerable objects, string? newBank) + { + if (string.IsNullOrEmpty(newBank)) + return; + + updateFor(objects, (_, samples) => + { + for (int i = 0; i < samples.Count; i++) + { + samples[i] = samples[i].With(newBank: newBank); + } + }); + } + private void updateBankPlaceholderText(IEnumerable objects) { string? commonBank = getCommonBank(objects.Select(GetSamples).ToArray()); @@ -210,21 +217,13 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline if (newVolume == null) return; - beatmap.BeginChange(); - - foreach (var h in objects) + updateFor(objects, (_, samples) => { - var samples = GetSamples(h); - for (int i = 0; i < samples.Count; i++) { samples[i] = samples[i].With(newVolume: newVolume.Value); } - - beatmap.Update(h); - } - - beatmap.EndChange(); + }); } #region hitsound toggles @@ -277,21 +276,14 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline if (string.IsNullOrEmpty(sampleName)) return; - beatmap.BeginChange(); - - foreach (var h in objects) + updateFor(objects, (h, samples) => { - var samples = GetSamples(h); - // Make sure there isn't already an existing sample if (samples.Any(s => s.Name == sampleName)) return; samples.Add(h.GetSampleInfo(sampleName)); - beatmap.Update(h); - } - - beatmap.EndChange(); + }); } private void removeHitSampleFor(IEnumerable objects, string sampleName) @@ -299,22 +291,14 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline if (string.IsNullOrEmpty(sampleName)) return; - beatmap.BeginChange(); - - foreach (var h in objects) + updateFor(objects, (_, samples) => { - var samples = GetSamples(h); - for (int i = 0; i < samples.Count; i++) { if (samples[i].Name == sampleName) samples.RemoveAt(i--); } - - beatmap.Update(h); - } - - beatmap.EndChange(); + }); } protected override bool OnKeyDown(KeyDownEvent e) From 7260dcac60f00b48489d3a100c907355d45f38f3 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Mon, 8 May 2023 15:57:30 +0200 Subject: [PATCH 10/91] fix crash on multiselect and node sample piece popup --- .../Edit/Compose/Components/Timeline/NodeSamplePointPiece.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/NodeSamplePointPiece.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/NodeSamplePointPiece.cs index 437d3c3d3a..de43e16e55 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/NodeSamplePointPiece.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/NodeSamplePointPiece.cs @@ -40,7 +40,9 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline protected override IList GetSamples(HitObject ho) { - var hasRepeats = (IHasRepeats)ho; + if (ho is not IHasRepeats hasRepeats) + return ho.Samples; + return nodeIndex < hasRepeats.NodeSamples.Count ? hasRepeats.NodeSamples[nodeIndex] : ho.Samples; } From dd0fceaec69eb322b75851ccfd17a0ddc97e1346 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Mon, 8 May 2023 16:12:03 +0200 Subject: [PATCH 11/91] add addition bank --- .../Components/EditorSelectionHandler.cs | 3 +- .../Components/Timeline/SamplePointPiece.cs | 53 ++++++++++++++++++- 2 files changed, 53 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/EditorSelectionHandler.cs b/osu.Game/Screens/Edit/Compose/Components/EditorSelectionHandler.cs index 694b24c567..71a01d9988 100644 --- a/osu.Game/Screens/Edit/Compose/Components/EditorSelectionHandler.cs +++ b/osu.Game/Screens/Edit/Compose/Components/EditorSelectionHandler.cs @@ -122,7 +122,8 @@ namespace osu.Game.Screens.Edit.Compose.Components if (h.Samples.Any(s => s.Name == sampleName)) return; - h.Samples.Add(h.GetSampleInfo(sampleName)); + var relevantSample = h.Samples.FirstOrDefault(s => s.Name != HitSampleInfo.HIT_NORMAL); + h.Samples.Add(relevantSample?.With(sampleName) ?? h.GetSampleInfo(sampleName)); EditorBeatmap.Update(h); }); } diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs index 69fff8a8a7..a6a7a59793 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs @@ -69,7 +69,12 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline public static string? GetBankValue(IEnumerable samples) { - return samples.FirstOrDefault()?.Bank; + return samples.FirstOrDefault(o => o.Name == HitSampleInfo.HIT_NORMAL)?.Bank; + } + + public static string? GetAdditionBankValue(IEnumerable samples) + { + return samples.FirstOrDefault(o => o.Name != HitSampleInfo.HIT_NORMAL)?.Bank ?? GetBankValue(samples); } public static int GetVolumeValue(ICollection samples) @@ -86,6 +91,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline private readonly HitObject hitObject; private LabelledTextBox bank = null!; + private LabelledTextBox additionBank = null!; private IndeterminateSliderWithTextBoxInput volume = null!; private FillFlowContainer togglesCollection = null!; @@ -126,6 +132,10 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline { Label = "Bank Name", }, + additionBank = new LabelledTextBox + { + Label = "Addition Bank", + }, volume = new IndeterminateSliderWithTextBoxInput("Volume", new BindableInt(100) { MinValue = 0, @@ -136,6 +146,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline }; bank.TabbableContentContainer = flow; + additionBank.TabbableContentContainer = flow; volume.TabbableContentContainer = flow; // if the piece belongs to a currently selected object, assume that the user wants to change all selected objects. @@ -148,6 +159,10 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline if (!string.IsNullOrEmpty(commonBank)) bank.Current.Value = commonBank; + string? commonAdditionBank = getCommonAdditionBank(relevantSamples); + if (!string.IsNullOrEmpty(commonAdditionBank)) + additionBank.Current.Value = commonAdditionBank; + int? commonVolume = getCommonVolume(relevantSamples); if (commonVolume != null) volume.Current.Value = commonVolume.Value; @@ -162,6 +177,14 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline // this ensures that committing empty text causes a revert to the previous value. bank.OnCommit += (_, _) => bank.Current.Value = getCommonBank(relevantSamples); + updateAdditionBankPlaceholderText(relevantObjects); + additionBank.Current.BindValueChanged(val => + { + updateAdditionBankFor(relevantObjects, val.NewValue); + updateAdditionBankPlaceholderText(relevantObjects); + }); + additionBank.OnCommit += (_, _) => additionBank.Current.Value = getCommonAdditionBank(relevantSamples); + volume.Current.BindValueChanged(val => updateVolumeFor(relevantObjects, val.NewValue)); createStateBindables(relevantObjects); @@ -176,6 +199,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline } private static string? getCommonBank(IList[] relevantSamples) => relevantSamples.Select(GetBankValue).Distinct().Count() == 1 ? GetBankValue(relevantSamples.First()) : null; + private static string? getCommonAdditionBank(IList[] relevantSamples) => relevantSamples.Select(GetAdditionBankValue).Distinct().Count() == 1 ? GetAdditionBankValue(relevantSamples.First()) : null; private static int? getCommonVolume(IList[] relevantSamples) => relevantSamples.Select(GetVolumeValue).Distinct().Count() == 1 ? GetVolumeValue(relevantSamples.First()) : null; private void updateFor(IEnumerable objects, Action> updateAction) @@ -201,6 +225,24 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline { for (int i = 0; i < samples.Count; i++) { + if (samples[i].Name != HitSampleInfo.HIT_NORMAL) continue; + + samples[i] = samples[i].With(newBank: newBank); + } + }); + } + + private void updateAdditionBankFor(IEnumerable objects, string? newBank) + { + if (string.IsNullOrEmpty(newBank)) + return; + + updateFor(objects, (_, samples) => + { + for (int i = 0; i < samples.Count; i++) + { + if (samples[i].Name == HitSampleInfo.HIT_NORMAL) continue; + samples[i] = samples[i].With(newBank: newBank); } }); @@ -212,6 +254,12 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline bank.PlaceholderText = string.IsNullOrEmpty(commonBank) ? "(multiple)" : string.Empty; } + private void updateAdditionBankPlaceholderText(IEnumerable objects) + { + string? commonAdditionBank = getCommonAdditionBank(objects.Select(GetSamples).ToArray()); + additionBank.PlaceholderText = string.IsNullOrEmpty(commonAdditionBank) ? "(multiple)" : string.Empty; + } + private void updateVolumeFor(IEnumerable objects, int? newVolume) { if (newVolume == null) @@ -282,7 +330,8 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline if (samples.Any(s => s.Name == sampleName)) return; - samples.Add(h.GetSampleInfo(sampleName)); + var relevantSample = samples.FirstOrDefault(s => s.Name != HitSampleInfo.HIT_NORMAL) ?? samples.FirstOrDefault(); + samples.Add(relevantSample?.With(sampleName) ?? h.GetSampleInfo(sampleName)); }); } From 114f12a79056cc20951d06aec1ade073899d684a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 30 May 2023 14:04:02 +0900 Subject: [PATCH 12/91] Adjust `CreateHitSampleInfo` to handle additions correctly, rather than implementing locally --- osu.Game/Rulesets/Objects/HitObject.cs | 12 ++++++++++-- .../Compose/Components/EditorSelectionHandler.cs | 6 ++---- .../Compose/Components/Timeline/SamplePointPiece.cs | 3 +-- 3 files changed, 13 insertions(+), 8 deletions(-) diff --git a/osu.Game/Rulesets/Objects/HitObject.cs b/osu.Game/Rulesets/Objects/HitObject.cs index ed3d3a6eb2..e87fb56b73 100644 --- a/osu.Game/Rulesets/Objects/HitObject.cs +++ b/osu.Game/Rulesets/Objects/HitObject.cs @@ -216,8 +216,16 @@ namespace osu.Game.Rulesets.Objects /// A populated . public HitSampleInfo CreateHitSampleInfo(string sampleName = HitSampleInfo.HIT_NORMAL) { - if (Samples.FirstOrDefault(s => s.Name == HitSampleInfo.HIT_NORMAL) is HitSampleInfo existingSample) - return existingSample.With(newName: sampleName); + // As per stable, all non-normal "addition" samples should use the same bank. + if (sampleName != HitSampleInfo.HIT_NORMAL) + { + if (Samples.FirstOrDefault(s => s.Name != HitSampleInfo.HIT_NORMAL) is HitSampleInfo existingAddition) + return existingAddition.With(newName: sampleName); + } + + // Fall back to using the normal sample bank otherwise. + if (Samples.FirstOrDefault(s => s.Name == HitSampleInfo.HIT_NORMAL) is HitSampleInfo existingNormal) + return existingNormal.With(newName: sampleName); return new HitSampleInfo(sampleName); } diff --git a/osu.Game/Screens/Edit/Compose/Components/EditorSelectionHandler.cs b/osu.Game/Screens/Edit/Compose/Components/EditorSelectionHandler.cs index 07622e4385..9bf6cfffe6 100644 --- a/osu.Game/Screens/Edit/Compose/Components/EditorSelectionHandler.cs +++ b/osu.Game/Screens/Edit/Compose/Components/EditorSelectionHandler.cs @@ -222,12 +222,10 @@ namespace osu.Game.Screens.Edit.Compose.Components if (h.Samples.Any(s => s.Name == sampleName)) return; - var existingNonNormalSample = h.Samples.FirstOrDefault(s => s.Name != HitSampleInfo.HIT_NORMAL); - var sampleToAdd = h.CreateHitSampleInfo(sampleName); - h.Samples.Add(existingNonNormalSample?.With(sampleName) ?? h.GetSampleInfo(sampleName)); - h.Samples.Add(h.CreateHitSampleInfo(sampleName)); + h.Samples.Add(sampleToAdd); + EditorBeatmap.Update(h); }); } diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs index a6a7a59793..64330e354a 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs @@ -330,8 +330,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline if (samples.Any(s => s.Name == sampleName)) return; - var relevantSample = samples.FirstOrDefault(s => s.Name != HitSampleInfo.HIT_NORMAL) ?? samples.FirstOrDefault(); - samples.Add(relevantSample?.With(sampleName) ?? h.GetSampleInfo(sampleName)); + samples.Add(h.CreateHitSampleInfo(sampleName)); }); } From 7a46b7b96177a234622031c77f51b632c816750d Mon Sep 17 00:00:00 2001 From: OliBomby Date: Wed, 31 May 2023 14:33:06 +0200 Subject: [PATCH 13/91] Invert colors --- .../Edit/Compose/Components/Timeline/NodeSamplePointPiece.cs | 4 ---- .../Edit/Compose/Components/Timeline/SamplePointPiece.cs | 4 +++- .../Components/Timeline/TimelineHitObjectBlueprint.cs | 5 +++-- 3 files changed, 6 insertions(+), 7 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/NodeSamplePointPiece.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/NodeSamplePointPiece.cs index de43e16e55..f168fb791f 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/NodeSamplePointPiece.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/NodeSamplePointPiece.cs @@ -4,10 +4,8 @@ using System.Collections.Generic; using osu.Framework.Graphics.UserInterface; using osu.Game.Audio; -using osu.Game.Graphics; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Types; -using osuTK.Graphics; namespace osu.Game.Screens.Edit.Compose.Components.Timeline { @@ -24,8 +22,6 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline NodeIndex = nodeIndex; } - protected override Color4 GetRepresentingColour(OsuColour colours) => colours.Purple; - protected override IList GetSamples() { var hasRepeats = (IHasRepeats)HitObject; diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs index 64330e354a..61004aae88 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs @@ -36,7 +36,9 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline HitObject = hitObject; } - protected override Color4 GetRepresentingColour(OsuColour colours) => colours.Pink; + public bool AlternativeColor { get; init; } + + protected override Color4 GetRepresentingColour(OsuColour colours) => AlternativeColor ? colours.Purple : colours.Pink; [BackgroundDependencyLoader] private void load() diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs index e7c14fc53d..ddac3bb667 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs @@ -111,7 +111,8 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline { Anchor = Anchor.BottomLeft, Origin = Anchor.TopCentre, - X = Item is IHasRepeats ? -10 : 0 + X = Item is IHasRepeats ? -10 : 0, + AlternativeColor = Item is IHasRepeats }, }); @@ -256,7 +257,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline X = (float)i / (repeats.RepeatCount + 1), RelativePositionAxes = Axes.X, Anchor = Anchor.BottomLeft, - Origin = Anchor.TopCentre, + Origin = Anchor.TopCentre }); } } From b7bc49b1f4ea438daa1e313b7cface55f62c84a7 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Wed, 31 May 2023 16:28:43 +0200 Subject: [PATCH 14/91] Fix regressed bank inheriting behaviour on node samples --- .../Edit/Compose/Components/Timeline/SamplePointPiece.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs index 61004aae88..b52463d8f3 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs @@ -332,7 +332,9 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline if (samples.Any(s => s.Name == sampleName)) return; - samples.Add(h.CreateHitSampleInfo(sampleName)); + // First try inheriting the sample info from the node samples instead of the samples of the hitobject + var relevantSample = samples.FirstOrDefault(s => s.Name != HitSampleInfo.HIT_NORMAL) ?? samples.FirstOrDefault(); + samples.Add(relevantSample?.With(sampleName) ?? h.CreateHitSampleInfo(sampleName)); }); } From fede432969fbf202f0a0d702ebedc02ab1e66a34 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Wed, 31 May 2023 20:00:19 +0200 Subject: [PATCH 15/91] Make relevantObject and relevantSamples instance variables --- .../Components/Timeline/SamplePointPiece.cs | 100 ++++++++++-------- 1 file changed, 57 insertions(+), 43 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs index b52463d8f3..bbc5380e70 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs @@ -98,6 +98,9 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline private FillFlowContainer togglesCollection = null!; + private HitObject[] relevantObjects = null!; + private IList[] relevantSamples = null!; + protected virtual IList GetSamples(HitObject ho) => ho.Samples; [Resolved(canBeNull: true)] @@ -153,44 +156,41 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline // if the piece belongs to a currently selected object, assume that the user wants to change all selected objects. // if the piece belongs to an unselected object, operate on that object alone, independently of the selection. - var relevantObjects = (beatmap.SelectedHitObjects.Contains(hitObject) ? beatmap.SelectedHitObjects : hitObject.Yield()).ToArray(); - var relevantSamples = relevantObjects.Select(GetSamples).ToArray(); + relevantObjects = (beatmap.SelectedHitObjects.Contains(hitObject) ? beatmap.SelectedHitObjects : hitObject.Yield()).ToArray(); + relevantSamples = relevantObjects.Select(GetSamples).ToArray(); // even if there are multiple objects selected, we can still display sample volume or bank if they all have the same value. - string? commonBank = getCommonBank(relevantSamples); + string? commonBank = getCommonBank(); if (!string.IsNullOrEmpty(commonBank)) bank.Current.Value = commonBank; - string? commonAdditionBank = getCommonAdditionBank(relevantSamples); - if (!string.IsNullOrEmpty(commonAdditionBank)) - additionBank.Current.Value = commonAdditionBank; - - int? commonVolume = getCommonVolume(relevantSamples); + int? commonVolume = getCommonVolume(); if (commonVolume != null) volume.Current.Value = commonVolume.Value; - updateBankPlaceholderText(relevantObjects); + updateBankPlaceholderText(); bank.Current.BindValueChanged(val => { - updateBankFor(relevantObjects, val.NewValue); - updateBankPlaceholderText(relevantObjects); + updateBank(val.NewValue); + updateBankPlaceholderText(); }); // on commit, ensure that the value is correct by sourcing it from the objects' samples again. // this ensures that committing empty text causes a revert to the previous value. - bank.OnCommit += (_, _) => bank.Current.Value = getCommonBank(relevantSamples); + bank.OnCommit += (_, _) => bank.Current.Value = getCommonBank(); - updateAdditionBankPlaceholderText(relevantObjects); + updateAdditionBankPlaceholderText(); + updateAdditionBankText(); additionBank.Current.BindValueChanged(val => { - updateAdditionBankFor(relevantObjects, val.NewValue); - updateAdditionBankPlaceholderText(relevantObjects); + updateAdditionBank(val.NewValue); + updateAdditionBankPlaceholderText(); }); - additionBank.OnCommit += (_, _) => additionBank.Current.Value = getCommonAdditionBank(relevantSamples); + additionBank.OnCommit += (_, _) => updateAdditionBankText(); - volume.Current.BindValueChanged(val => updateVolumeFor(relevantObjects, val.NewValue)); + volume.Current.BindValueChanged(val => updateVolume(val.NewValue)); - createStateBindables(relevantObjects); - updateTernaryStates(relevantObjects); + createStateBindables(); + updateTernaryStates(); togglesCollection.AddRange(createTernaryButtons().Select(b => new DrawableTernaryButton(b) { RelativeSizeAxes = Axes.None, Size = new Vector2(40, 40) })); } @@ -200,15 +200,15 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline ScheduleAfterChildren(() => GetContainingInputManager().ChangeFocus(volume)); } - private static string? getCommonBank(IList[] relevantSamples) => relevantSamples.Select(GetBankValue).Distinct().Count() == 1 ? GetBankValue(relevantSamples.First()) : null; - private static string? getCommonAdditionBank(IList[] relevantSamples) => relevantSamples.Select(GetAdditionBankValue).Distinct().Count() == 1 ? GetAdditionBankValue(relevantSamples.First()) : null; - private static int? getCommonVolume(IList[] relevantSamples) => relevantSamples.Select(GetVolumeValue).Distinct().Count() == 1 ? GetVolumeValue(relevantSamples.First()) : null; + private string? getCommonBank() => relevantSamples.Select(GetBankValue).Distinct().Count() == 1 ? GetBankValue(relevantSamples.First()) : null; + private string? getCommonAdditionBank() => relevantSamples.Select(GetAdditionBankValue).Distinct().Count() == 1 ? GetAdditionBankValue(relevantSamples.First()) : null; + private int? getCommonVolume() => relevantSamples.Select(GetVolumeValue).Distinct().Count() == 1 ? GetVolumeValue(relevantSamples.First()) : null; - private void updateFor(IEnumerable objects, Action> updateAction) + private void update(Action> updateAction) { beatmap.BeginChange(); - foreach (var h in objects) + foreach (var h in relevantObjects) { var samples = GetSamples(h); updateAction(h, samples); @@ -218,12 +218,12 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline beatmap.EndChange(); } - private void updateBankFor(IEnumerable objects, string? newBank) + private void updateBank(string? newBank) { if (string.IsNullOrEmpty(newBank)) return; - updateFor(objects, (_, samples) => + update((_, samples) => { for (int i = 0; i < samples.Count; i++) { @@ -234,12 +234,12 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline }); } - private void updateAdditionBankFor(IEnumerable objects, string? newBank) + private void updateAdditionBank(string? newBank) { if (string.IsNullOrEmpty(newBank)) return; - updateFor(objects, (_, samples) => + update((_, samples) => { for (int i = 0; i < samples.Count; i++) { @@ -250,24 +250,34 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline }); } - private void updateBankPlaceholderText(IEnumerable objects) + private void updateBankPlaceholderText() { - string? commonBank = getCommonBank(objects.Select(GetSamples).ToArray()); + string? commonBank = getCommonBank(); bank.PlaceholderText = string.IsNullOrEmpty(commonBank) ? "(multiple)" : string.Empty; } - private void updateAdditionBankPlaceholderText(IEnumerable objects) + private void updateAdditionBankPlaceholderText() { - string? commonAdditionBank = getCommonAdditionBank(objects.Select(GetSamples).ToArray()); + string? commonAdditionBank = getCommonAdditionBank(); additionBank.PlaceholderText = string.IsNullOrEmpty(commonAdditionBank) ? "(multiple)" : string.Empty; } - private void updateVolumeFor(IEnumerable objects, int? newVolume) + private void updateAdditionBankText() + { + if (additionBank.Current.Disabled) return; + + string? commonAdditionBank = getCommonAdditionBank(); + if (string.IsNullOrEmpty(commonAdditionBank)) return; + + additionBank.Current.Value = commonAdditionBank; + } + + private void updateVolume(int? newVolume) { if (newVolume == null) return; - updateFor(objects, (_, samples) => + update((_, samples) => { for (int i = 0; i < samples.Count; i++) { @@ -280,7 +290,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline private readonly Dictionary> selectionSampleStates = new Dictionary>(); - private void createStateBindables(IEnumerable objects) + private void createStateBindables() { foreach (string sampleName in HitSampleInfo.AllAdditions) { @@ -294,11 +304,11 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline switch (state.NewValue) { case TernaryState.False: - removeHitSampleFor(objects, sampleName); + removeHitSample(sampleName); break; case TernaryState.True: - addHitSampleFor(objects, sampleName); + addHitSample(sampleName); break; } }; @@ -307,11 +317,11 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline } } - private void updateTernaryStates(IEnumerable objects) + private void updateTernaryStates() { foreach ((string sampleName, var bindable) in selectionSampleStates) { - bindable.Value = SelectionHandler.GetStateFromSelection(objects, h => GetSamples(h).Any(s => s.Name == sampleName)); + bindable.Value = SelectionHandler.GetStateFromSelection(relevantObjects, h => GetSamples(h).Any(s => s.Name == sampleName)); } } @@ -321,12 +331,12 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline yield return new TernaryButton(bindable, string.Empty, () => ComposeBlueprintContainer.GetIconForSample(sampleName)); } - private void addHitSampleFor(IEnumerable objects, string sampleName) + private void addHitSample(string sampleName) { if (string.IsNullOrEmpty(sampleName)) return; - updateFor(objects, (h, samples) => + update((h, samples) => { // Make sure there isn't already an existing sample if (samples.Any(s => s.Name == sampleName)) @@ -336,14 +346,16 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline var relevantSample = samples.FirstOrDefault(s => s.Name != HitSampleInfo.HIT_NORMAL) ?? samples.FirstOrDefault(); samples.Add(relevantSample?.With(sampleName) ?? h.CreateHitSampleInfo(sampleName)); }); + + updateAdditionBankText(); } - private void removeHitSampleFor(IEnumerable objects, string sampleName) + private void removeHitSample(string sampleName) { if (string.IsNullOrEmpty(sampleName)) return; - updateFor(objects, (_, samples) => + update((_, samples) => { for (int i = 0; i < samples.Count; i++) { @@ -351,6 +363,8 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline samples.RemoveAt(i--); } }); + + updateAdditionBankText(); } protected override bool OnKeyDown(KeyDownEvent e) From 9e78a6b34e0deb25a4933b14d14f16cd91165bfd Mon Sep 17 00:00:00 2001 From: OliBomby Date: Wed, 31 May 2023 20:00:45 +0200 Subject: [PATCH 16/91] hide addition bank field when no additions active --- .../Compose/Components/Timeline/SamplePointPiece.cs | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs index bbc5380e70..37a6fa8a22 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs @@ -180,6 +180,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline updateAdditionBankPlaceholderText(); updateAdditionBankText(); + updateAdditionBankActivated(); additionBank.Current.BindValueChanged(val => { updateAdditionBank(val.NewValue); @@ -262,6 +263,15 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline additionBank.PlaceholderText = string.IsNullOrEmpty(commonAdditionBank) ? "(multiple)" : string.Empty; } + private void updateAdditionBankActivated() + { + bool anyAdditions = relevantSamples.Any(o => o.Any(s => s.Name != HitSampleInfo.HIT_NORMAL)); + if (anyAdditions) + additionBank.Show(); + else + additionBank.Hide(); + } + private void updateAdditionBankText() { if (additionBank.Current.Disabled) return; @@ -347,6 +357,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline samples.Add(relevantSample?.With(sampleName) ?? h.CreateHitSampleInfo(sampleName)); }); + updateAdditionBankActivated(); updateAdditionBankText(); } @@ -365,6 +376,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline }); updateAdditionBankText(); + updateAdditionBankActivated(); } protected override bool OnKeyDown(KeyDownEvent e) From 8acfe6b58b1de2de3466315fc74b000a428860c1 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Thu, 1 Jun 2023 07:41:30 +0200 Subject: [PATCH 17/91] Add PopoverContainer to TimelineTestScene --- .../Visual/Editing/TimelineTestScene.cs | 36 +++++++++++-------- 1 file changed, 22 insertions(+), 14 deletions(-) diff --git a/osu.Game.Tests/Visual/Editing/TimelineTestScene.cs b/osu.Game.Tests/Visual/Editing/TimelineTestScene.cs index cb45ad5a07..afec75e948 100644 --- a/osu.Game.Tests/Visual/Editing/TimelineTestScene.cs +++ b/osu.Game.Tests/Visual/Editing/TimelineTestScene.cs @@ -9,6 +9,7 @@ using osu.Framework.Audio; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Cursor; using osu.Framework.Graphics.Shapes; using osu.Framework.Testing; using osu.Game.Beatmaps; @@ -51,28 +52,35 @@ namespace osu.Game.Tests.Visual.Editing Composer.Alpha = 0; - Add(new OsuContextMenuContainer + Add(new PopoverContainer { RelativeSizeAxes = Axes.Both, Children = new Drawable[] { - EditorBeatmap, - Composer, - new FillFlowContainer + new OsuContextMenuContainer { - AutoSizeAxes = Axes.Both, - Direction = FillDirection.Vertical, - Spacing = new Vector2(0, 5), + RelativeSizeAxes = Axes.Both, Children = new Drawable[] { - new StartStopButton(), - new AudioVisualiser(), + EditorBeatmap, + Composer, + new FillFlowContainer + { + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Vertical, + Spacing = new Vector2(0, 5), + Children = new Drawable[] + { + new StartStopButton(), + new AudioVisualiser(), + } + }, + TimelineArea = new TimelineArea(CreateTestComponent()) + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + } } - }, - TimelineArea = new TimelineArea(CreateTestComponent()) - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, } } }); From 2812b2345779e6d9af397b0af0f4f30dff4e864b Mon Sep 17 00:00:00 2001 From: OliBomby Date: Thu, 1 Jun 2023 08:03:41 +0200 Subject: [PATCH 18/91] Add sample point piece test --- .../TestSceneTimelineHitObjectBlueprint.cs | 72 +++++++++++++++++++ 1 file changed, 72 insertions(+) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneTimelineHitObjectBlueprint.cs b/osu.Game.Tests/Visual/Editing/TestSceneTimelineHitObjectBlueprint.cs index 08e036248b..61bfaad64c 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneTimelineHitObjectBlueprint.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneTimelineHitObjectBlueprint.cs @@ -8,10 +8,14 @@ using NUnit.Framework; using osu.Framework.Graphics; using osu.Framework.Graphics.UserInterface; using osu.Framework.Testing; +using osu.Game.Audio; using osu.Game.Graphics.UserInterface; +using osu.Game.Graphics.UserInterfaceV2; using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Osu.Objects; +using osu.Game.Screens.Edit.Components.TernaryButtons; using osu.Game.Screens.Edit.Compose.Components.Timeline; +using osu.Game.Screens.Edit.Timing; using osuTK; using osuTK.Input; using static osu.Game.Screens.Edit.Compose.Components.Timeline.TimelineHitObjectBlueprint; @@ -111,5 +115,73 @@ namespace osu.Game.Tests.Visual.Editing AddAssert("object has zero repeats", () => EditorBeatmap.HitObjects.OfType().Single().RepeatCount == 0); } + + [Test] + public void TestSamplePointPiece() + { + SamplePointPiece samplePointPiece; + SamplePointPiece.SampleEditPopover popover = null!; + + AddStep("add circle", () => + { + EditorBeatmap.Clear(); + EditorBeatmap.Add(new HitCircle + { + Position = new Vector2(256, 256), + StartTime = 2700, + Samples = + { + new HitSampleInfo(HitSampleInfo.HIT_NORMAL) + } + }); + }); + + AddStep("open hitsound popover", () => + { + samplePointPiece = this.ChildrenOfType().Single(); + InputManager.MoveMouseTo(samplePointPiece); + InputManager.PressButton(MouseButton.Left); + InputManager.ReleaseButton(MouseButton.Left); + }); + + AddStep("add whistle addition", () => + { + popover = this.ChildrenOfType().First(); + var whistleTernaryButton = popover.ChildrenOfType().First(); + InputManager.MoveMouseTo(whistleTernaryButton); + InputManager.PressButton(MouseButton.Left); + InputManager.ReleaseButton(MouseButton.Left); + }); + + AddAssert("has whistle sample", () => EditorBeatmap.HitObjects.First().Samples.Any(o => o.Name == HitSampleInfo.HIT_WHISTLE)); + + AddStep("change bank name", () => + { + var bankTextBox = popover.ChildrenOfType().First(); + bankTextBox.Current.Value = "soft"; + }); + + AddAssert("bank name changed", () => + EditorBeatmap.HitObjects.First().Samples.Where(o => o.Name == HitSampleInfo.HIT_NORMAL).All(o => o.Bank == "soft") + && EditorBeatmap.HitObjects.First().Samples.Where(o => o.Name != HitSampleInfo.HIT_NORMAL).All(o => o.Bank == "normal")); + + AddStep("change addition bank name", () => + { + var bankTextBox = popover.ChildrenOfType().ToArray()[1]; + bankTextBox.Current.Value = "drum"; + }); + + AddAssert("addition bank name changed", () => + EditorBeatmap.HitObjects.First().Samples.Where(o => o.Name == HitSampleInfo.HIT_NORMAL).All(o => o.Bank == "soft") + && EditorBeatmap.HitObjects.First().Samples.Where(o => o.Name != HitSampleInfo.HIT_NORMAL).All(o => o.Bank == "drum")); + + AddStep("change volume", () => + { + var bankTextBox = popover.ChildrenOfType>().Single(); + bankTextBox.Current.Value = 30; + }); + + AddAssert("volume changed", () => EditorBeatmap.HitObjects.First().Samples.All(o => o.Volume == 30)); + } } } From aa52d86ea3304c0cf6922ef1e584fc4a6244cb39 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Thu, 1 Jun 2023 08:12:39 +0200 Subject: [PATCH 19/91] add nodesample piece test --- .../TestSceneTimelineHitObjectBlueprint.cs | 77 +++++++++++++++++++ 1 file changed, 77 insertions(+) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneTimelineHitObjectBlueprint.cs b/osu.Game.Tests/Visual/Editing/TestSceneTimelineHitObjectBlueprint.cs index 61bfaad64c..6524722687 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneTimelineHitObjectBlueprint.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneTimelineHitObjectBlueprint.cs @@ -3,6 +3,7 @@ #nullable disable +using System.Collections.Generic; using System.Linq; using NUnit.Framework; using osu.Framework.Graphics; @@ -11,6 +12,7 @@ using osu.Framework.Testing; using osu.Game.Audio; using osu.Game.Graphics.UserInterface; using osu.Game.Graphics.UserInterfaceV2; +using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Screens.Edit.Components.TernaryButtons; @@ -183,5 +185,80 @@ namespace osu.Game.Tests.Visual.Editing AddAssert("volume changed", () => EditorBeatmap.HitObjects.First().Samples.All(o => o.Volume == 30)); } + + [Test] + public void TestNodeSamplePointPiece() + { + Slider slider = null!; + SamplePointPiece samplePointPiece; + SamplePointPiece.SampleEditPopover popover = null!; + + AddStep("add slider", () => + { + EditorBeatmap.Clear(); + EditorBeatmap.Add(slider = new Slider + { + Position = new Vector2(256, 256), + StartTime = 2700, + Path = new SliderPath(new[] { new PathControlPoint(Vector2.Zero), new PathControlPoint(new Vector2(250, 0)) }), + Samples = + { + new HitSampleInfo(HitSampleInfo.HIT_NORMAL) + }, + NodeSamples = + { + new List { new HitSampleInfo(HitSampleInfo.HIT_NORMAL) }, + new List { new HitSampleInfo(HitSampleInfo.HIT_NORMAL) }, + } + }); + }); + + AddStep("open slider end hitsound popover", () => + { + samplePointPiece = this.ChildrenOfType().Last(); + InputManager.MoveMouseTo(samplePointPiece); + InputManager.PressButton(MouseButton.Left); + InputManager.ReleaseButton(MouseButton.Left); + }); + + AddStep("add whistle addition", () => + { + popover = this.ChildrenOfType().First(); + var whistleTernaryButton = popover.ChildrenOfType().First(); + InputManager.MoveMouseTo(whistleTernaryButton); + InputManager.PressButton(MouseButton.Left); + InputManager.ReleaseButton(MouseButton.Left); + }); + + AddAssert("has whistle sample", () => slider.NodeSamples[1].Any(o => o.Name == HitSampleInfo.HIT_WHISTLE)); + + AddStep("change bank name", () => + { + var bankTextBox = popover.ChildrenOfType().First(); + bankTextBox.Current.Value = "soft"; + }); + + AddAssert("bank name changed", () => + slider.NodeSamples[1].Where(o => o.Name == HitSampleInfo.HIT_NORMAL).All(o => o.Bank == "soft") + && slider.NodeSamples[1].Where(o => o.Name != HitSampleInfo.HIT_NORMAL).All(o => o.Bank == "normal")); + + AddStep("change addition bank name", () => + { + var bankTextBox = popover.ChildrenOfType().ToArray()[1]; + bankTextBox.Current.Value = "drum"; + }); + + AddAssert("addition bank name changed", () => + slider.NodeSamples[1].Where(o => o.Name == HitSampleInfo.HIT_NORMAL).All(o => o.Bank == "soft") + && slider.NodeSamples[1].Where(o => o.Name != HitSampleInfo.HIT_NORMAL).All(o => o.Bank == "drum")); + + AddStep("change volume", () => + { + var bankTextBox = popover.ChildrenOfType>().Single(); + bankTextBox.Current.Value = 30; + }); + + AddAssert("volume changed", () => slider.NodeSamples[1].All(o => o.Volume == 30)); + } } } From e70939005257e6b853117edbf3f7aa504f3d273e Mon Sep 17 00:00:00 2001 From: OliBomby Date: Thu, 1 Jun 2023 09:00:59 +0200 Subject: [PATCH 20/91] reset popover at the end of sample tests --- .../TestSceneTimelineHitObjectBlueprint.cs | 22 +++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneTimelineHitObjectBlueprint.cs b/osu.Game.Tests/Visual/Editing/TestSceneTimelineHitObjectBlueprint.cs index 6524722687..4410ae4112 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneTimelineHitObjectBlueprint.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneTimelineHitObjectBlueprint.cs @@ -121,7 +121,6 @@ namespace osu.Game.Tests.Visual.Editing [Test] public void TestSamplePointPiece() { - SamplePointPiece samplePointPiece; SamplePointPiece.SampleEditPopover popover = null!; AddStep("add circle", () => @@ -140,7 +139,7 @@ namespace osu.Game.Tests.Visual.Editing AddStep("open hitsound popover", () => { - samplePointPiece = this.ChildrenOfType().Single(); + var samplePointPiece = this.ChildrenOfType().Single(); InputManager.MoveMouseTo(samplePointPiece); InputManager.PressButton(MouseButton.Left); InputManager.ReleaseButton(MouseButton.Left); @@ -184,13 +183,20 @@ namespace osu.Game.Tests.Visual.Editing }); AddAssert("volume changed", () => EditorBeatmap.HitObjects.First().Samples.All(o => o.Volume == 30)); + + AddStep("close popover", () => + { + InputManager.MoveMouseTo(popover, new Vector2(200, 0)); + InputManager.PressButton(MouseButton.Left); + InputManager.ReleaseButton(MouseButton.Left); + popover = null; + }); } [Test] public void TestNodeSamplePointPiece() { Slider slider = null!; - SamplePointPiece samplePointPiece; SamplePointPiece.SampleEditPopover popover = null!; AddStep("add slider", () => @@ -215,7 +221,7 @@ namespace osu.Game.Tests.Visual.Editing AddStep("open slider end hitsound popover", () => { - samplePointPiece = this.ChildrenOfType().Last(); + var samplePointPiece = this.ChildrenOfType().Last(); InputManager.MoveMouseTo(samplePointPiece); InputManager.PressButton(MouseButton.Left); InputManager.ReleaseButton(MouseButton.Left); @@ -259,6 +265,14 @@ namespace osu.Game.Tests.Visual.Editing }); AddAssert("volume changed", () => slider.NodeSamples[1].All(o => o.Volume == 30)); + + AddStep("close popover", () => + { + InputManager.MoveMouseTo(popover, new Vector2(200, 0)); + InputManager.PressButton(MouseButton.Left); + InputManager.ReleaseButton(MouseButton.Left); + popover = null; + }); } } } From 63d9be9523a547c0c87d4e08cda6e79483ba359a Mon Sep 17 00:00:00 2001 From: OliBomby Date: Thu, 1 Jun 2023 09:27:04 +0200 Subject: [PATCH 21/91] merge updateAdditionBankPlaceholderText and updateAdditionBankActivated --- .../Components/Timeline/SamplePointPiece.cs | 16 +++++----------- 1 file changed, 5 insertions(+), 11 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs index 37a6fa8a22..997e342dad 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs @@ -178,13 +178,12 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline // this ensures that committing empty text causes a revert to the previous value. bank.OnCommit += (_, _) => bank.Current.Value = getCommonBank(); - updateAdditionBankPlaceholderText(); updateAdditionBankText(); - updateAdditionBankActivated(); + updateAdditionBankVisual(); additionBank.Current.BindValueChanged(val => { updateAdditionBank(val.NewValue); - updateAdditionBankPlaceholderText(); + updateAdditionBankVisual(); }); additionBank.OnCommit += (_, _) => updateAdditionBankText(); @@ -257,14 +256,11 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline bank.PlaceholderText = string.IsNullOrEmpty(commonBank) ? "(multiple)" : string.Empty; } - private void updateAdditionBankPlaceholderText() + private void updateAdditionBankVisual() { string? commonAdditionBank = getCommonAdditionBank(); additionBank.PlaceholderText = string.IsNullOrEmpty(commonAdditionBank) ? "(multiple)" : string.Empty; - } - private void updateAdditionBankActivated() - { bool anyAdditions = relevantSamples.Any(o => o.Any(s => s.Name != HitSampleInfo.HIT_NORMAL)); if (anyAdditions) additionBank.Show(); @@ -274,8 +270,6 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline private void updateAdditionBankText() { - if (additionBank.Current.Disabled) return; - string? commonAdditionBank = getCommonAdditionBank(); if (string.IsNullOrEmpty(commonAdditionBank)) return; @@ -357,7 +351,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline samples.Add(relevantSample?.With(sampleName) ?? h.CreateHitSampleInfo(sampleName)); }); - updateAdditionBankActivated(); + updateAdditionBankVisual(); updateAdditionBankText(); } @@ -376,7 +370,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline }); updateAdditionBankText(); - updateAdditionBankActivated(); + updateAdditionBankVisual(); } protected override bool OnKeyDown(KeyDownEvent e) From 1eb9b8e135ff2ae31f65bde9b7d5c01dae2a9afc Mon Sep 17 00:00:00 2001 From: OliBomby Date: Thu, 1 Jun 2023 09:34:21 +0200 Subject: [PATCH 22/91] added xmldoc and renamed GetSamples --- .../Components/Timeline/NodeSamplePointPiece.cs | 2 +- .../Components/Timeline/SamplePointPiece.cs | 14 ++++++++++---- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/NodeSamplePointPiece.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/NodeSamplePointPiece.cs index f168fb791f..ae3838bc41 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/NodeSamplePointPiece.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/NodeSamplePointPiece.cs @@ -34,7 +34,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline { private readonly int nodeIndex; - protected override IList GetSamples(HitObject ho) + protected override IList GetRelevantSamples(HitObject ho) { if (ho is not IHasRepeats hasRepeats) return ho.Samples; diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs index 997e342dad..26cdf87d02 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs @@ -101,7 +101,13 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline private HitObject[] relevantObjects = null!; private IList[] relevantSamples = null!; - protected virtual IList GetSamples(HitObject ho) => ho.Samples; + /// + /// Gets the sub-set of samples relevant to this sample point piece. + /// For example, to edit node samples this should return the samples at the index of the node. + /// + /// The hit object to get the relevant samples from. + /// The relevant list of samples. + protected virtual IList GetRelevantSamples(HitObject ho) => ho.Samples; [Resolved(canBeNull: true)] private EditorBeatmap beatmap { get; set; } = null!; @@ -157,7 +163,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline // if the piece belongs to a currently selected object, assume that the user wants to change all selected objects. // if the piece belongs to an unselected object, operate on that object alone, independently of the selection. relevantObjects = (beatmap.SelectedHitObjects.Contains(hitObject) ? beatmap.SelectedHitObjects : hitObject.Yield()).ToArray(); - relevantSamples = relevantObjects.Select(GetSamples).ToArray(); + relevantSamples = relevantObjects.Select(GetRelevantSamples).ToArray(); // even if there are multiple objects selected, we can still display sample volume or bank if they all have the same value. string? commonBank = getCommonBank(); @@ -210,7 +216,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline foreach (var h in relevantObjects) { - var samples = GetSamples(h); + var samples = GetRelevantSamples(h); updateAction(h, samples); beatmap.Update(h); } @@ -325,7 +331,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline { foreach ((string sampleName, var bindable) in selectionSampleStates) { - bindable.Value = SelectionHandler.GetStateFromSelection(relevantObjects, h => GetSamples(h).Any(s => s.Name == sampleName)); + bindable.Value = SelectionHandler.GetStateFromSelection(relevantObjects, h => GetRelevantSamples(h).Any(s => s.Name == sampleName)); } } From eb8ac8951361cf8ba77cad6f69f09fc371209a67 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Thu, 1 Jun 2023 09:50:14 +0200 Subject: [PATCH 23/91] rename sample update logic and add xmldoc for clarity --- .../Components/Timeline/SamplePointPiece.cs | 65 ++++++++++--------- 1 file changed, 35 insertions(+), 30 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs index 26cdf87d02..2060a8ddd3 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs @@ -99,7 +99,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline private FillFlowContainer togglesCollection = null!; private HitObject[] relevantObjects = null!; - private IList[] relevantSamples = null!; + private IList[] allRelevantSamples = null!; /// /// Gets the sub-set of samples relevant to this sample point piece. @@ -163,7 +163,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline // if the piece belongs to a currently selected object, assume that the user wants to change all selected objects. // if the piece belongs to an unselected object, operate on that object alone, independently of the selection. relevantObjects = (beatmap.SelectedHitObjects.Contains(hitObject) ? beatmap.SelectedHitObjects : hitObject.Yield()).ToArray(); - relevantSamples = relevantObjects.Select(GetRelevantSamples).ToArray(); + allRelevantSamples = relevantObjects.Select(GetRelevantSamples).ToArray(); // even if there are multiple objects selected, we can still display sample volume or bank if they all have the same value. string? commonBank = getCommonBank(); @@ -206,19 +206,24 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline ScheduleAfterChildren(() => GetContainingInputManager().ChangeFocus(volume)); } - private string? getCommonBank() => relevantSamples.Select(GetBankValue).Distinct().Count() == 1 ? GetBankValue(relevantSamples.First()) : null; - private string? getCommonAdditionBank() => relevantSamples.Select(GetAdditionBankValue).Distinct().Count() == 1 ? GetAdditionBankValue(relevantSamples.First()) : null; - private int? getCommonVolume() => relevantSamples.Select(GetVolumeValue).Distinct().Count() == 1 ? GetVolumeValue(relevantSamples.First()) : null; + private string? getCommonBank() => allRelevantSamples.Select(GetBankValue).Distinct().Count() == 1 ? GetBankValue(allRelevantSamples.First()) : null; + private string? getCommonAdditionBank() => allRelevantSamples.Select(GetAdditionBankValue).Distinct().Count() == 1 ? GetAdditionBankValue(allRelevantSamples.First()) : null; + private int? getCommonVolume() => allRelevantSamples.Select(GetVolumeValue).Distinct().Count() == 1 ? GetVolumeValue(allRelevantSamples.First()) : null; - private void update(Action> updateAction) + /// + /// Applies the given update action on all samples of + /// and invokes the necessary update notifiers for the beatmap and hit objects. + /// + /// The action to perform on each element of . + private void updateAllRelevantSamples(Action> updateAction) { beatmap.BeginChange(); - foreach (var h in relevantObjects) + foreach (var relevantHitObject in relevantObjects) { - var samples = GetRelevantSamples(h); - updateAction(h, samples); - beatmap.Update(h); + var relevantSamples = GetRelevantSamples(relevantHitObject); + updateAction(relevantHitObject, relevantSamples); + beatmap.Update(relevantHitObject); } beatmap.EndChange(); @@ -229,13 +234,13 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline if (string.IsNullOrEmpty(newBank)) return; - update((_, samples) => + updateAllRelevantSamples((_, relevantSamples) => { - for (int i = 0; i < samples.Count; i++) + for (int i = 0; i < relevantSamples.Count; i++) { - if (samples[i].Name != HitSampleInfo.HIT_NORMAL) continue; + if (relevantSamples[i].Name != HitSampleInfo.HIT_NORMAL) continue; - samples[i] = samples[i].With(newBank: newBank); + relevantSamples[i] = relevantSamples[i].With(newBank: newBank); } }); } @@ -245,13 +250,13 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline if (string.IsNullOrEmpty(newBank)) return; - update((_, samples) => + updateAllRelevantSamples((_, relevantSamples) => { - for (int i = 0; i < samples.Count; i++) + for (int i = 0; i < relevantSamples.Count; i++) { - if (samples[i].Name == HitSampleInfo.HIT_NORMAL) continue; + if (relevantSamples[i].Name == HitSampleInfo.HIT_NORMAL) continue; - samples[i] = samples[i].With(newBank: newBank); + relevantSamples[i] = relevantSamples[i].With(newBank: newBank); } }); } @@ -267,7 +272,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline string? commonAdditionBank = getCommonAdditionBank(); additionBank.PlaceholderText = string.IsNullOrEmpty(commonAdditionBank) ? "(multiple)" : string.Empty; - bool anyAdditions = relevantSamples.Any(o => o.Any(s => s.Name != HitSampleInfo.HIT_NORMAL)); + bool anyAdditions = allRelevantSamples.Any(o => o.Any(s => s.Name != HitSampleInfo.HIT_NORMAL)); if (anyAdditions) additionBank.Show(); else @@ -287,11 +292,11 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline if (newVolume == null) return; - update((_, samples) => + updateAllRelevantSamples((_, relevantSamples) => { - for (int i = 0; i < samples.Count; i++) + for (int i = 0; i < relevantSamples.Count; i++) { - samples[i] = samples[i].With(newVolume: newVolume.Value); + relevantSamples[i] = relevantSamples[i].With(newVolume: newVolume.Value); } }); } @@ -346,15 +351,15 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline if (string.IsNullOrEmpty(sampleName)) return; - update((h, samples) => + updateAllRelevantSamples((h, relevantSamples) => { // Make sure there isn't already an existing sample - if (samples.Any(s => s.Name == sampleName)) + if (relevantSamples.Any(s => s.Name == sampleName)) return; // First try inheriting the sample info from the node samples instead of the samples of the hitobject - var relevantSample = samples.FirstOrDefault(s => s.Name != HitSampleInfo.HIT_NORMAL) ?? samples.FirstOrDefault(); - samples.Add(relevantSample?.With(sampleName) ?? h.CreateHitSampleInfo(sampleName)); + var relevantSample = relevantSamples.FirstOrDefault(s => s.Name != HitSampleInfo.HIT_NORMAL) ?? relevantSamples.FirstOrDefault(); + relevantSamples.Add(relevantSample?.With(sampleName) ?? h.CreateHitSampleInfo(sampleName)); }); updateAdditionBankVisual(); @@ -366,12 +371,12 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline if (string.IsNullOrEmpty(sampleName)) return; - update((_, samples) => + updateAllRelevantSamples((_, relevantSamples) => { - for (int i = 0; i < samples.Count; i++) + for (int i = 0; i < relevantSamples.Count; i++) { - if (samples[i].Name == sampleName) - samples.RemoveAt(i--); + if (relevantSamples[i].Name == sampleName) + relevantSamples.RemoveAt(i--); } }); From 3110f87831014a044ed316c78a8125043965f8cd Mon Sep 17 00:00:00 2001 From: OliBomby Date: Thu, 1 Jun 2023 18:48:12 +0200 Subject: [PATCH 24/91] Revert "Add PopoverContainer to TimelineTestScene" This reverts commit 8acfe6b58b1de2de3466315fc74b000a428860c1. --- .../Visual/Editing/TimelineTestScene.cs | 36 ++++++++----------- 1 file changed, 14 insertions(+), 22 deletions(-) diff --git a/osu.Game.Tests/Visual/Editing/TimelineTestScene.cs b/osu.Game.Tests/Visual/Editing/TimelineTestScene.cs index afec75e948..cb45ad5a07 100644 --- a/osu.Game.Tests/Visual/Editing/TimelineTestScene.cs +++ b/osu.Game.Tests/Visual/Editing/TimelineTestScene.cs @@ -9,7 +9,6 @@ using osu.Framework.Audio; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Cursor; using osu.Framework.Graphics.Shapes; using osu.Framework.Testing; using osu.Game.Beatmaps; @@ -52,35 +51,28 @@ namespace osu.Game.Tests.Visual.Editing Composer.Alpha = 0; - Add(new PopoverContainer + Add(new OsuContextMenuContainer { RelativeSizeAxes = Axes.Both, Children = new Drawable[] { - new OsuContextMenuContainer + EditorBeatmap, + Composer, + new FillFlowContainer { - RelativeSizeAxes = Axes.Both, + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Vertical, + Spacing = new Vector2(0, 5), Children = new Drawable[] { - EditorBeatmap, - Composer, - new FillFlowContainer - { - AutoSizeAxes = Axes.Both, - Direction = FillDirection.Vertical, - Spacing = new Vector2(0, 5), - Children = new Drawable[] - { - new StartStopButton(), - new AudioVisualiser(), - } - }, - TimelineArea = new TimelineArea(CreateTestComponent()) - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - } + new StartStopButton(), + new AudioVisualiser(), } + }, + TimelineArea = new TimelineArea(CreateTestComponent()) + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, } } }); From 035163a7921ef12225b15973ff9302652a99a204 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Fri, 2 Jun 2023 00:40:00 +0200 Subject: [PATCH 25/91] add new behaviour tests to TestSceneHitObjectSampleAdjustments --- .../TestSceneHitObjectSampleAdjustments.cs | 151 ++++++++++++++++++ 1 file changed, 151 insertions(+) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneHitObjectSampleAdjustments.cs b/osu.Game.Tests/Visual/Editing/TestSceneHitObjectSampleAdjustments.cs index b0b51a5dbd..4136deda3e 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneHitObjectSampleAdjustments.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneHitObjectSampleAdjustments.cs @@ -14,9 +14,12 @@ using osu.Game.Graphics.UserInterface; using osu.Game.Graphics.UserInterfaceV2; using osu.Game.Rulesets; using osu.Game.Rulesets.Edit; +using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.UI; +using osu.Game.Screens.Edit.Components.TernaryButtons; using osu.Game.Screens.Edit.Compose.Components.Timeline; using osu.Game.Screens.Edit.Timing; using osu.Game.Tests.Beatmaps; @@ -227,6 +230,84 @@ namespace osu.Game.Tests.Visual.Editing samplePopoverHasSingleBank(HitSampleInfo.BANK_NORMAL); } + [Test] + public void TestPopoverAddSampleAddition() + { + clickSamplePiece(0); + + setBankViaPopover(HitSampleInfo.BANK_SOFT); + hitObjectHasSampleBank(0, HitSampleInfo.BANK_SOFT); + + toggleAdditionViaPopover(0); + + hitObjectHasSampleBank(0, HitSampleInfo.BANK_SOFT); + hitObjectHasSamples(0, HitSampleInfo.HIT_NORMAL, HitSampleInfo.HIT_WHISTLE); + + setAdditionBankViaPopover(HitSampleInfo.BANK_DRUM); + + hitObjectHasSampleNormalBank(0, HitSampleInfo.BANK_SOFT); + hitObjectHasSampleAdditionBank(0, HitSampleInfo.BANK_DRUM); + + toggleAdditionViaPopover(0); + + hitObjectHasSampleBank(0, HitSampleInfo.BANK_SOFT); + hitObjectHasSamples(0, HitSampleInfo.HIT_NORMAL); + } + + [Test] + public void TestNodeSamplePopover() + { + AddStep("add slider", () => + { + EditorBeatmap.Clear(); + EditorBeatmap.Add(new Slider + { + Position = new Vector2(256, 256), + StartTime = 0, + Path = new SliderPath(new[] { new PathControlPoint(Vector2.Zero), new PathControlPoint(new Vector2(250, 0)) }), + Samples = + { + new HitSampleInfo(HitSampleInfo.HIT_NORMAL) + }, + NodeSamples = + { + new List { new HitSampleInfo(HitSampleInfo.HIT_NORMAL) }, + new List { new HitSampleInfo(HitSampleInfo.HIT_NORMAL) }, + } + }); + }); + + clickNodeSamplePiece(0, 1); + + setBankViaPopover(HitSampleInfo.BANK_SOFT); + hitObjectNodeHasSampleBank(0, 0, HitSampleInfo.BANK_NORMAL); + hitObjectNodeHasSampleBank(0, 1, HitSampleInfo.BANK_SOFT); + + toggleAdditionViaPopover(0); + + hitObjectNodeHasSampleBank(0, 0, HitSampleInfo.BANK_NORMAL); + hitObjectNodeHasSampleBank(0, 1, HitSampleInfo.BANK_SOFT); + hitObjectNodeHasSamples(0, 0, HitSampleInfo.HIT_NORMAL); + hitObjectNodeHasSamples(0, 1, HitSampleInfo.HIT_NORMAL, HitSampleInfo.HIT_WHISTLE); + + setAdditionBankViaPopover(HitSampleInfo.BANK_DRUM); + + hitObjectNodeHasSampleBank(0, 0, HitSampleInfo.BANK_NORMAL); + hitObjectNodeHasSampleNormalBank(0, 1, HitSampleInfo.BANK_SOFT); + hitObjectNodeHasSampleAdditionBank(0, 1, HitSampleInfo.BANK_DRUM); + + toggleAdditionViaPopover(0); + + hitObjectNodeHasSampleBank(0, 1, HitSampleInfo.BANK_SOFT); + hitObjectNodeHasSamples(0, 0, HitSampleInfo.HIT_NORMAL); + hitObjectNodeHasSamples(0, 1, HitSampleInfo.HIT_NORMAL); + + setVolumeViaPopover(10); + + hitObjectNodeHasSampleVolume(0, 0, 100); + hitObjectNodeHasSampleVolume(0, 1, 10); + } + [Test] public void TestHotkeysMultipleSelectionWithSameSampleBank() { @@ -330,6 +411,14 @@ namespace osu.Game.Tests.Visual.Editing InputManager.Click(MouseButton.Left); }); + private void clickNodeSamplePiece(int objectIndex, int nodeIndex) => AddStep($"click {objectIndex.ToOrdinalWords()} object {nodeIndex.ToOrdinalWords()} node sample piece", () => + { + var samplePiece = this.ChildrenOfType().Where(piece => piece.HitObject == EditorBeatmap.HitObjects.ElementAt(objectIndex)).ToArray()[nodeIndex]; + + InputManager.MoveMouseTo(samplePiece); + InputManager.Click(MouseButton.Left); + }); + private void samplePopoverHasFocus() => AddUntilStep("sample popover textbox focused", () => { var popover = this.ChildrenOfType().SingleOrDefault(); @@ -391,6 +480,12 @@ namespace osu.Game.Tests.Visual.Editing return h.Samples.All(o => o.Volume == volume); }); + private void hitObjectNodeHasSampleVolume(int objectIndex, int nodeIndex, int volume) => AddAssert($"{objectIndex.ToOrdinalWords()} object {nodeIndex.ToOrdinalWords()} node has volume {volume}", () => + { + var h = EditorBeatmap.HitObjects.ElementAt(objectIndex) as IHasRepeats; + return h is not null && h.NodeSamples[nodeIndex].All(o => o.Volume == volume); + }); + private void setBankViaPopover(string bank) => AddStep($"set bank {bank} via popover", () => { var popover = this.ChildrenOfType().Single(); @@ -402,6 +497,26 @@ namespace osu.Game.Tests.Visual.Editing InputManager.Key(Key.Enter); }); + private void setAdditionBankViaPopover(string bank) => AddStep($"set addition bank {bank} via popover", () => + { + var popover = this.ChildrenOfType().Single(); + var textBox = popover.ChildrenOfType().ToArray()[1]; + textBox.Current.Value = bank; + // force a commit via keyboard. + // this is needed when testing attempting to set empty bank - which should revert to the previous value, but only on commit. + InputManager.ChangeFocus(textBox); + InputManager.Key(Key.Enter); + }); + + private void toggleAdditionViaPopover(int index) => AddStep($"toggle addition {index} via popover", () => + { + var popover = this.ChildrenOfType().First(); + var ternaryButton = popover.ChildrenOfType().ToArray()[index]; + InputManager.MoveMouseTo(ternaryButton); + InputManager.PressButton(MouseButton.Left); + InputManager.ReleaseButton(MouseButton.Left); + }); + private void hitObjectHasSamples(int objectIndex, params string[] samples) => AddAssert($"{objectIndex.ToOrdinalWords()} has samples {string.Join(',', samples)}", () => { var h = EditorBeatmap.HitObjects.ElementAt(objectIndex); @@ -413,5 +528,41 @@ namespace osu.Game.Tests.Visual.Editing var h = EditorBeatmap.HitObjects.ElementAt(objectIndex); return h.Samples.All(o => o.Bank == bank); }); + + private void hitObjectHasSampleNormalBank(int objectIndex, string bank) => AddAssert($"{objectIndex.ToOrdinalWords()} has normal bank {bank}", () => + { + var h = EditorBeatmap.HitObjects.ElementAt(objectIndex); + return h.Samples.Where(o => o.Name == HitSampleInfo.HIT_NORMAL).All(o => o.Bank == bank); + }); + + private void hitObjectHasSampleAdditionBank(int objectIndex, string bank) => AddAssert($"{objectIndex.ToOrdinalWords()} has addition bank {bank}", () => + { + var h = EditorBeatmap.HitObjects.ElementAt(objectIndex); + return h.Samples.Where(o => o.Name != HitSampleInfo.HIT_NORMAL).All(o => o.Bank == bank); + }); + + private void hitObjectNodeHasSamples(int objectIndex, int nodeIndex, params string[] samples) => AddAssert($"{objectIndex.ToOrdinalWords()} object {nodeIndex.ToOrdinalWords()} node has samples {string.Join(',', samples)}", () => + { + var h = EditorBeatmap.HitObjects.ElementAt(objectIndex) as IHasRepeats; + return h is not null && h.NodeSamples[nodeIndex].Select(s => s.Name).SequenceEqual(samples); + }); + + private void hitObjectNodeHasSampleBank(int objectIndex, int nodeIndex, string bank) => AddAssert($"{objectIndex.ToOrdinalWords()} object {nodeIndex.ToOrdinalWords()} node has bank {bank}", () => + { + var h = EditorBeatmap.HitObjects.ElementAt(objectIndex) as IHasRepeats; + return h is not null && h.NodeSamples[nodeIndex].All(o => o.Bank == bank); + }); + + private void hitObjectNodeHasSampleNormalBank(int objectIndex, int nodeIndex, string bank) => AddAssert($"{objectIndex.ToOrdinalWords()} object {nodeIndex.ToOrdinalWords()} node has normal bank {bank}", () => + { + var h = EditorBeatmap.HitObjects.ElementAt(objectIndex) as IHasRepeats; + return h is not null && h.NodeSamples[nodeIndex].Where(o => o.Name == HitSampleInfo.HIT_NORMAL).All(o => o.Bank == bank); + }); + + private void hitObjectNodeHasSampleAdditionBank(int objectIndex, int nodeIndex, string bank) => AddAssert($"{objectIndex.ToOrdinalWords()} object {nodeIndex.ToOrdinalWords()} node has addition bank {bank}", () => + { + var h = EditorBeatmap.HitObjects.ElementAt(objectIndex) as IHasRepeats; + return h is not null && h.NodeSamples[nodeIndex].Where(o => o.Name != HitSampleInfo.HIT_NORMAL).All(o => o.Bank == bank); + }); } } From acd8ff9a242f88a88997ef834469ea2b9a4f43ed Mon Sep 17 00:00:00 2001 From: OliBomby Date: Fri, 2 Jun 2023 00:42:36 +0200 Subject: [PATCH 26/91] revert add sample point piece tests --- .../TestSceneTimelineHitObjectBlueprint.cs | 163 ------------------ 1 file changed, 163 deletions(-) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneTimelineHitObjectBlueprint.cs b/osu.Game.Tests/Visual/Editing/TestSceneTimelineHitObjectBlueprint.cs index 4410ae4112..08e036248b 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneTimelineHitObjectBlueprint.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneTimelineHitObjectBlueprint.cs @@ -3,21 +3,15 @@ #nullable disable -using System.Collections.Generic; using System.Linq; using NUnit.Framework; using osu.Framework.Graphics; using osu.Framework.Graphics.UserInterface; using osu.Framework.Testing; -using osu.Game.Audio; using osu.Game.Graphics.UserInterface; -using osu.Game.Graphics.UserInterfaceV2; -using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Osu.Objects; -using osu.Game.Screens.Edit.Components.TernaryButtons; using osu.Game.Screens.Edit.Compose.Components.Timeline; -using osu.Game.Screens.Edit.Timing; using osuTK; using osuTK.Input; using static osu.Game.Screens.Edit.Compose.Components.Timeline.TimelineHitObjectBlueprint; @@ -117,162 +111,5 @@ namespace osu.Game.Tests.Visual.Editing AddAssert("object has zero repeats", () => EditorBeatmap.HitObjects.OfType().Single().RepeatCount == 0); } - - [Test] - public void TestSamplePointPiece() - { - SamplePointPiece.SampleEditPopover popover = null!; - - AddStep("add circle", () => - { - EditorBeatmap.Clear(); - EditorBeatmap.Add(new HitCircle - { - Position = new Vector2(256, 256), - StartTime = 2700, - Samples = - { - new HitSampleInfo(HitSampleInfo.HIT_NORMAL) - } - }); - }); - - AddStep("open hitsound popover", () => - { - var samplePointPiece = this.ChildrenOfType().Single(); - InputManager.MoveMouseTo(samplePointPiece); - InputManager.PressButton(MouseButton.Left); - InputManager.ReleaseButton(MouseButton.Left); - }); - - AddStep("add whistle addition", () => - { - popover = this.ChildrenOfType().First(); - var whistleTernaryButton = popover.ChildrenOfType().First(); - InputManager.MoveMouseTo(whistleTernaryButton); - InputManager.PressButton(MouseButton.Left); - InputManager.ReleaseButton(MouseButton.Left); - }); - - AddAssert("has whistle sample", () => EditorBeatmap.HitObjects.First().Samples.Any(o => o.Name == HitSampleInfo.HIT_WHISTLE)); - - AddStep("change bank name", () => - { - var bankTextBox = popover.ChildrenOfType().First(); - bankTextBox.Current.Value = "soft"; - }); - - AddAssert("bank name changed", () => - EditorBeatmap.HitObjects.First().Samples.Where(o => o.Name == HitSampleInfo.HIT_NORMAL).All(o => o.Bank == "soft") - && EditorBeatmap.HitObjects.First().Samples.Where(o => o.Name != HitSampleInfo.HIT_NORMAL).All(o => o.Bank == "normal")); - - AddStep("change addition bank name", () => - { - var bankTextBox = popover.ChildrenOfType().ToArray()[1]; - bankTextBox.Current.Value = "drum"; - }); - - AddAssert("addition bank name changed", () => - EditorBeatmap.HitObjects.First().Samples.Where(o => o.Name == HitSampleInfo.HIT_NORMAL).All(o => o.Bank == "soft") - && EditorBeatmap.HitObjects.First().Samples.Where(o => o.Name != HitSampleInfo.HIT_NORMAL).All(o => o.Bank == "drum")); - - AddStep("change volume", () => - { - var bankTextBox = popover.ChildrenOfType>().Single(); - bankTextBox.Current.Value = 30; - }); - - AddAssert("volume changed", () => EditorBeatmap.HitObjects.First().Samples.All(o => o.Volume == 30)); - - AddStep("close popover", () => - { - InputManager.MoveMouseTo(popover, new Vector2(200, 0)); - InputManager.PressButton(MouseButton.Left); - InputManager.ReleaseButton(MouseButton.Left); - popover = null; - }); - } - - [Test] - public void TestNodeSamplePointPiece() - { - Slider slider = null!; - SamplePointPiece.SampleEditPopover popover = null!; - - AddStep("add slider", () => - { - EditorBeatmap.Clear(); - EditorBeatmap.Add(slider = new Slider - { - Position = new Vector2(256, 256), - StartTime = 2700, - Path = new SliderPath(new[] { new PathControlPoint(Vector2.Zero), new PathControlPoint(new Vector2(250, 0)) }), - Samples = - { - new HitSampleInfo(HitSampleInfo.HIT_NORMAL) - }, - NodeSamples = - { - new List { new HitSampleInfo(HitSampleInfo.HIT_NORMAL) }, - new List { new HitSampleInfo(HitSampleInfo.HIT_NORMAL) }, - } - }); - }); - - AddStep("open slider end hitsound popover", () => - { - var samplePointPiece = this.ChildrenOfType().Last(); - InputManager.MoveMouseTo(samplePointPiece); - InputManager.PressButton(MouseButton.Left); - InputManager.ReleaseButton(MouseButton.Left); - }); - - AddStep("add whistle addition", () => - { - popover = this.ChildrenOfType().First(); - var whistleTernaryButton = popover.ChildrenOfType().First(); - InputManager.MoveMouseTo(whistleTernaryButton); - InputManager.PressButton(MouseButton.Left); - InputManager.ReleaseButton(MouseButton.Left); - }); - - AddAssert("has whistle sample", () => slider.NodeSamples[1].Any(o => o.Name == HitSampleInfo.HIT_WHISTLE)); - - AddStep("change bank name", () => - { - var bankTextBox = popover.ChildrenOfType().First(); - bankTextBox.Current.Value = "soft"; - }); - - AddAssert("bank name changed", () => - slider.NodeSamples[1].Where(o => o.Name == HitSampleInfo.HIT_NORMAL).All(o => o.Bank == "soft") - && slider.NodeSamples[1].Where(o => o.Name != HitSampleInfo.HIT_NORMAL).All(o => o.Bank == "normal")); - - AddStep("change addition bank name", () => - { - var bankTextBox = popover.ChildrenOfType().ToArray()[1]; - bankTextBox.Current.Value = "drum"; - }); - - AddAssert("addition bank name changed", () => - slider.NodeSamples[1].Where(o => o.Name == HitSampleInfo.HIT_NORMAL).All(o => o.Bank == "soft") - && slider.NodeSamples[1].Where(o => o.Name != HitSampleInfo.HIT_NORMAL).All(o => o.Bank == "drum")); - - AddStep("change volume", () => - { - var bankTextBox = popover.ChildrenOfType>().Single(); - bankTextBox.Current.Value = 30; - }); - - AddAssert("volume changed", () => slider.NodeSamples[1].All(o => o.Volume == 30)); - - AddStep("close popover", () => - { - InputManager.MoveMouseTo(popover, new Vector2(200, 0)); - InputManager.PressButton(MouseButton.Left); - InputManager.ReleaseButton(MouseButton.Left); - popover = null; - }); - } } } From da516b90390ef06682155e051911ddd21d2e95a5 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Fri, 2 Jun 2023 00:50:21 +0200 Subject: [PATCH 27/91] Change purple to darker pink --- .../Edit/Compose/Components/Timeline/SamplePointPiece.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs index 2060a8ddd3..de85435d02 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs @@ -38,7 +38,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline public bool AlternativeColor { get; init; } - protected override Color4 GetRepresentingColour(OsuColour colours) => AlternativeColor ? colours.Purple : colours.Pink; + protected override Color4 GetRepresentingColour(OsuColour colours) => AlternativeColor ? colours.PinkDarker : colours.Pink; [BackgroundDependencyLoader] private void load() From 848f0e305eafd6d5258e8bf86d1f7ea547b67cef Mon Sep 17 00:00:00 2001 From: OliBomby Date: Fri, 2 Jun 2023 00:55:37 +0200 Subject: [PATCH 28/91] Change position of sliderbody hitsound piece for less overlap --- .../Compose/Components/Timeline/TimelineHitObjectBlueprint.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs index ddac3bb667..c642b9f29f 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs @@ -111,7 +111,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline { Anchor = Anchor.BottomLeft, Origin = Anchor.TopCentre, - X = Item is IHasRepeats ? -10 : 0, + X = Item is IHasRepeats ? 30 : 0, AlternativeColor = Item is IHasRepeats }, }); From 3f96795bbf9c6f69b673a89a71f82586da25ac4d Mon Sep 17 00:00:00 2001 From: OliBomby Date: Fri, 2 Jun 2023 01:02:35 +0200 Subject: [PATCH 29/91] fix merge conflict --- .../Compose/Components/Timeline/TimelineHitObjectBlueprint.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs index 00bd1a7019..f41daf30ce 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs @@ -109,7 +109,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline { RelativeSizeAxes = Axes.Both, }, - new SamplePointPiece(Item) + samplePointPiece = new SamplePointPiece(Item) { Anchor = Anchor.BottomLeft, Origin = Anchor.TopCentre, From cc4e11a5ac3f6ab2debfa10f08c402621f8acb79 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Wed, 16 Aug 2023 20:48:52 +0200 Subject: [PATCH 30/91] Ensure populated node samples so new objects have unique node sample lists --- osu.Game.Rulesets.Catch/Objects/JuiceStream.cs | 2 ++ osu.Game.Rulesets.Osu/Objects/Slider.cs | 2 ++ osu.Game/Rulesets/Objects/Types/IHasRepeats.cs | 15 +++++++++++++++ 3 files changed, 19 insertions(+) diff --git a/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs b/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs index 169e99c90c..d9bbbedfcf 100644 --- a/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs +++ b/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs @@ -72,6 +72,8 @@ namespace osu.Game.Rulesets.Catch.Objects { base.CreateNestedHitObjects(cancellationToken); + this.PopulateNodeSamples(); + var dropletSamples = Samples.Select(s => s.With(@"slidertick")).ToList(); int nodeIndex = 0; diff --git a/osu.Game.Rulesets.Osu/Objects/Slider.cs b/osu.Game.Rulesets.Osu/Objects/Slider.cs index 4189f8ba1e..5ae76f1ac7 100644 --- a/osu.Game.Rulesets.Osu/Objects/Slider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Slider.cs @@ -246,6 +246,8 @@ namespace osu.Game.Rulesets.Osu.Objects protected void UpdateNestedSamples() { + this.PopulateNodeSamples(); + var firstSample = Samples.FirstOrDefault(s => s.Name == HitSampleInfo.HIT_NORMAL) ?? Samples.FirstOrDefault(); // TODO: remove this when guaranteed sort is present for samples (https://github.com/ppy/osu/issues/1933) var sampleList = new List(); diff --git a/osu.Game/Rulesets/Objects/Types/IHasRepeats.cs b/osu.Game/Rulesets/Objects/Types/IHasRepeats.cs index 2a4215b960..9677ac4fbd 100644 --- a/osu.Game/Rulesets/Objects/Types/IHasRepeats.cs +++ b/osu.Game/Rulesets/Objects/Types/IHasRepeats.cs @@ -3,6 +3,7 @@ using osu.Game.Audio; using System.Collections.Generic; +using System.Linq; namespace osu.Game.Rulesets.Objects.Types { @@ -45,5 +46,19 @@ namespace osu.Game.Rulesets.Objects.Types public static IList GetNodeSamples(this T obj, int nodeIndex) where T : HitObject, IHasRepeats => nodeIndex < obj.NodeSamples.Count ? obj.NodeSamples[nodeIndex] : obj.Samples; + + /// + /// Ensures that the list of node samples is at least as long as the number of nodes. + /// + /// The . + public static void PopulateNodeSamples(this T obj) + where T : HitObject, IHasRepeats + { + if (obj.NodeSamples.Count >= obj.RepeatCount + 2) + return; + + while (obj.NodeSamples.Count < obj.RepeatCount + 2) + obj.NodeSamples.Add(obj.Samples.Select(o => o.With()).ToList()); + } } } From 02b7c8f27be47d968728a1fec3eb0eb77a3a7cf5 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Wed, 16 Aug 2023 21:15:47 +0200 Subject: [PATCH 31/91] Move sliderbody hs to middle of first span --- .../Timeline/TimelineHitObjectBlueprint.cs | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs index f41daf30ce..d3a045db07 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs @@ -105,17 +105,17 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline }, } }, - sampleComponents = new Container - { - RelativeSizeAxes = Axes.Both, - }, samplePointPiece = new SamplePointPiece(Item) { Anchor = Anchor.BottomLeft, Origin = Anchor.TopCentre, - X = Item is IHasRepeats ? 30 : 0, + RelativePositionAxes = Axes.X, AlternativeColor = Item is IHasRepeats }, + sampleComponents = new Container + { + RelativeSizeAxes = Axes.Both, + }, }); if (item is IHasDuration) @@ -262,6 +262,8 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline Origin = Anchor.TopCentre }); } + + samplePointPiece.X = 1f / (repeats.RepeatCount + 1) / 2; } protected override bool ShouldBeConsideredForInput(Drawable child) => true; From a938b810b467d5d89acfe03ce43cf493a5e55d8d Mon Sep 17 00:00:00 2001 From: OliBomby Date: Wed, 16 Aug 2023 22:07:36 +0200 Subject: [PATCH 32/91] Add keybind for bank setting --- .../Components/Timeline/SamplePointPiece.cs | 31 ++++++++++++++++--- 1 file changed, 26 insertions(+), 5 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs index de85435d02..664998c267 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs @@ -182,7 +182,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline }); // on commit, ensure that the value is correct by sourcing it from the objects' samples again. // this ensures that committing empty text causes a revert to the previous value. - bank.OnCommit += (_, _) => bank.Current.Value = getCommonBank(); + bank.OnCommit += (_, _) => updateBankText(); updateAdditionBankText(); updateAdditionBankVisual(); @@ -261,6 +261,11 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline }); } + private void updateBankText() + { + bank.Current.Value = getCommonBank(); + } + private void updateBankPlaceholderText() { string? commonBank = getCommonBank(); @@ -305,6 +310,8 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline private readonly Dictionary> selectionSampleStates = new Dictionary>(); + private readonly List banks = new List(); + private void createStateBindables() { foreach (string sampleName in HitSampleInfo.AllAdditions) @@ -330,6 +337,8 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline selectionSampleStates[sampleName] = bindable; } + + banks.AddRange(HitSampleInfo.AllBanks); } private void updateTernaryStates() @@ -386,14 +395,26 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline protected override bool OnKeyDown(KeyDownEvent e) { - if (e.ControlPressed || e.AltPressed || e.SuperPressed || e.ShiftPressed || !checkRightToggleFromKey(e.Key, out int rightIndex)) + if (e.ControlPressed || e.AltPressed || e.SuperPressed || !checkRightToggleFromKey(e.Key, out int rightIndex)) return base.OnKeyDown(e); - var item = togglesCollection.ElementAtOrDefault(rightIndex); + if (e.ShiftPressed) + { + string? bank = banks.ElementAtOrDefault(rightIndex); + updateBank(bank); + updateBankText(); + updateAdditionBank(bank); + updateAdditionBankText(); + } + else + { + var item = togglesCollection.ElementAtOrDefault(rightIndex); - if (item is not DrawableTernaryButton button) return base.OnKeyDown(e); + if (item is not DrawableTernaryButton button) return base.OnKeyDown(e); + + button.Button.Toggle(); + } - button.Button.Toggle(); return true; } From fd54c329fa59b0b270bfe1d94791fcf9507eae5d Mon Sep 17 00:00:00 2001 From: OliBomby Date: Wed, 16 Aug 2023 22:08:49 +0200 Subject: [PATCH 33/91] dont focus volume, so keybinds immediately available --- .../Edit/Compose/Components/Timeline/SamplePointPiece.cs | 6 ------ 1 file changed, 6 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs index 664998c267..97397ba2da 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs @@ -200,12 +200,6 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline togglesCollection.AddRange(createTernaryButtons().Select(b => new DrawableTernaryButton(b) { RelativeSizeAxes = Axes.None, Size = new Vector2(40, 40) })); } - protected override void LoadComplete() - { - base.LoadComplete(); - ScheduleAfterChildren(() => GetContainingInputManager().ChangeFocus(volume)); - } - private string? getCommonBank() => allRelevantSamples.Select(GetBankValue).Distinct().Count() == 1 ? GetBankValue(allRelevantSamples.First()) : null; private string? getCommonAdditionBank() => allRelevantSamples.Select(GetAdditionBankValue).Distinct().Count() == 1 ? GetAdditionBankValue(allRelevantSamples.First()) : null; private int? getCommonVolume() => allRelevantSamples.Select(GetVolumeValue).Distinct().Count() == 1 ? GetVolumeValue(allRelevantSamples.First()) : null; From 4ff58c681803b323b3d4f7844d53908afcc7b97f Mon Sep 17 00:00:00 2001 From: OliBomby Date: Wed, 16 Aug 2023 22:10:59 +0200 Subject: [PATCH 34/91] fix no nodesample test case --- osu.Game.Rulesets.Osu.Tests/TestSceneSlider.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSlider.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSlider.cs index 4ad78a3190..3fac7c8c6d 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneSlider.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSlider.cs @@ -133,6 +133,7 @@ namespace osu.Game.Rulesets.Osu.Tests { slider = (DrawableSlider)createSlider(repeats: 1); Add(slider); + slider.HitObject.NodeSamples.Clear(); }); AddStep("change samples", () => slider.HitObject.Samples = new[] From 88e6fe72dc6e679f0c44719fd3db4c15013779ba Mon Sep 17 00:00:00 2001 From: OliBomby Date: Wed, 16 Aug 2023 23:09:04 +0200 Subject: [PATCH 35/91] fix code quality --- .../Edit/Compose/Components/Timeline/SamplePointPiece.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs index 97397ba2da..268fb073d7 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs @@ -394,10 +394,10 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline if (e.ShiftPressed) { - string? bank = banks.ElementAtOrDefault(rightIndex); - updateBank(bank); + string? newBank = banks.ElementAtOrDefault(rightIndex); + updateBank(newBank); updateBankText(); - updateAdditionBank(bank); + updateAdditionBank(newBank); updateAdditionBankText(); } else From 080d2b62f4f459f33a398ed1878d2ce0dca0e9a6 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Wed, 16 Aug 2023 23:16:57 +0200 Subject: [PATCH 36/91] fix focus test --- .../Editing/TestSceneHitObjectSampleAdjustments.cs | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneHitObjectSampleAdjustments.cs b/osu.Game.Tests/Visual/Editing/TestSceneHitObjectSampleAdjustments.cs index 050593ff94..b6a20dae54 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneHitObjectSampleAdjustments.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneHitObjectSampleAdjustments.cs @@ -81,10 +81,10 @@ namespace osu.Game.Tests.Visual.Editing } [Test] - public void TestPopoverHasFocus() + public void TestPopoverHasNoFocus() { clickSamplePiece(0); - samplePopoverHasFocus(); + samplePopoverHasNoFocus(); } [Test] @@ -417,13 +417,13 @@ namespace osu.Game.Tests.Visual.Editing InputManager.Click(MouseButton.Left); }); - private void samplePopoverHasFocus() => AddUntilStep("sample popover textbox focused", () => + private void samplePopoverHasNoFocus() => AddUntilStep("sample popover textbox not focused", () => { var popover = this.ChildrenOfType().SingleOrDefault(); var slider = popover?.ChildrenOfType>().Single(); var textbox = slider?.ChildrenOfType().Single(); - return textbox?.HasFocus == true; + return textbox?.HasFocus == false; }); private void samplePopoverHasSingleVolume(int volume) => AddUntilStep($"sample popover has volume {volume}", () => @@ -460,7 +460,6 @@ namespace osu.Game.Tests.Visual.Editing private void dismissPopover() { - AddStep("unfocus textbox", () => InputManager.Key(Key.Escape)); AddStep("dismiss popover", () => InputManager.Key(Key.Escape)); AddUntilStep("wait for dismiss", () => !this.ChildrenOfType().Any(popover => popover.IsPresent)); } From f5d2c549cb53b0e432c4515540be05cbd214a747 Mon Sep 17 00:00:00 2001 From: Givikap120 Date: Wed, 29 May 2024 19:25:58 +0300 Subject: [PATCH 37/91] Update CatchPerformanceCalculator.cs --- .../Difficulty/CatchPerformanceCalculator.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Catch/Difficulty/CatchPerformanceCalculator.cs b/osu.Game.Rulesets.Catch/Difficulty/CatchPerformanceCalculator.cs index d07f25ba90..55232a9598 100644 --- a/osu.Game.Rulesets.Catch/Difficulty/CatchPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Catch/Difficulty/CatchPerformanceCalculator.cs @@ -76,7 +76,7 @@ namespace osu.Game.Rulesets.Catch.Difficulty value *= Math.Pow(accuracy(), 5.5); if (score.Mods.Any(m => m is ModNoFail)) - value *= 0.90; + value *= Math.Max(0.90, 1.0 - 0.02 * numMiss); return new CatchPerformanceAttributes { From 7a8a37dae66e4851c9a63e9fc00fe6ca3e050eef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 6 Jun 2024 13:39:32 +0200 Subject: [PATCH 38/91] Use established constants --- .../Edit/Compose/Components/Timeline/SamplePointPiece.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs index 5e3d6c5239..d90f62f79d 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs @@ -63,9 +63,9 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline { return bank switch { - "normal" => "N", - "soft" => "S", - "drum" => "D", + HitSampleInfo.BANK_NORMAL => @"N", + HitSampleInfo.BANK_SOFT => @"S", + HitSampleInfo.BANK_DRUM => @"D", _ => bank }; } From dd9c77d248ddb5c61f0b90f613f39f512bb24274 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 6 Jun 2024 13:50:31 +0200 Subject: [PATCH 39/91] Fix obsoletion warning --- .../Visual/Editing/TestSceneHitObjectSampleAdjustments.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneHitObjectSampleAdjustments.cs b/osu.Game.Tests/Visual/Editing/TestSceneHitObjectSampleAdjustments.cs index 425eddbcdb..f02d2a1bb1 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneHitObjectSampleAdjustments.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneHitObjectSampleAdjustments.cs @@ -502,7 +502,7 @@ namespace osu.Game.Tests.Visual.Editing textBox.Current.Value = bank; // force a commit via keyboard. // this is needed when testing attempting to set empty bank - which should revert to the previous value, but only on commit. - InputManager.ChangeFocus(textBox); + ((IFocusManager)InputManager).ChangeFocus(textBox); InputManager.Key(Key.Enter); }); From 71ce400359dc69c1f9af1980e802cd630380eadd Mon Sep 17 00:00:00 2001 From: OliBomby Date: Thu, 6 Jun 2024 14:48:17 +0200 Subject: [PATCH 40/91] Fix wasteful recreating of container --- .../Components/Timeline/TimelineHitObjectBlueprint.cs | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs index ab9ccf6278..753856199a 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs @@ -33,7 +33,6 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline private const float circle_size = 38; private Container? repeatsContainer; - private Container? nodeSamplesContainer; public Action? OnDragHandled = null!; @@ -246,16 +245,11 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline } // Add node sample pieces - nodeSamplesContainer?.Expire(); - - sampleComponents.Add(nodeSamplesContainer = new Container - { - RelativeSizeAxes = Axes.Both, - }); + sampleComponents.Clear(); for (int i = 0; i < repeats.RepeatCount + 2; i++) { - nodeSamplesContainer.Add(new NodeSamplePointPiece(Item, i) + sampleComponents.Add(new NodeSamplePointPiece(Item, i) { X = (float)i / (repeats.RepeatCount + 1), RelativePositionAxes = Axes.X, From fcc8671cbd5dcb844ff58f50920c3b45ca488e06 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Thu, 6 Jun 2024 14:50:24 +0200 Subject: [PATCH 41/91] undo useless change --- .../Screens/Edit/Compose/Components/EditorSelectionHandler.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/EditorSelectionHandler.cs b/osu.Game/Screens/Edit/Compose/Components/EditorSelectionHandler.cs index c284ee2ebb..7c30b73122 100644 --- a/osu.Game/Screens/Edit/Compose/Components/EditorSelectionHandler.cs +++ b/osu.Game/Screens/Edit/Compose/Components/EditorSelectionHandler.cs @@ -226,9 +226,7 @@ namespace osu.Game.Screens.Edit.Compose.Components if (h.Samples.Any(s => s.Name == sampleName)) return; - var sampleToAdd = h.CreateHitSampleInfo(sampleName); - - h.Samples.Add(sampleToAdd); + h.Samples.Add(h.CreateHitSampleInfo(sampleName)); EditorBeatmap.Update(h); }); From e87369822182b75f8ee683547ffd3827d224303d Mon Sep 17 00:00:00 2001 From: OliBomby Date: Thu, 6 Jun 2024 14:57:25 +0200 Subject: [PATCH 42/91] Add some in-depth xmldoc to GetSamples --- .../Edit/Compose/Components/Timeline/SamplePointPiece.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs index d90f62f79d..64ad840591 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs @@ -85,6 +85,11 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline return samples.Count == 0 ? 0 : samples.Max(o => o.Volume); } + /// + /// Gets the samples to be edited by this sample point piece. + /// This could be the samples of the hit object itself, or of one of the nested hit objects. For example a slider repeat. + /// + /// The samples to be edited. protected virtual IList GetSamples() => HitObject.Samples; public virtual Popover GetPopover() => new SampleEditPopover(HitObject); From 0efa028e0a82dc192303e2b99bead7ce1db66280 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 10 Jun 2024 11:51:14 +0200 Subject: [PATCH 43/91] Restructure popover updates to be more centralised --- .../Components/Timeline/SamplePointPiece.cs | 117 ++++++++---------- 1 file changed, 54 insertions(+), 63 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs index 64ad840591..5f83937986 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs @@ -180,26 +180,35 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline if (commonVolume != null) volume.Current.Value = commonVolume.Value; - updateBankPlaceholderText(); + updatePrimaryBankState(); bank.Current.BindValueChanged(val => { - updateBank(val.NewValue); - updateBankPlaceholderText(); + if (string.IsNullOrEmpty(val.NewValue)) + return; + + setBank(val.NewValue); + updatePrimaryBankState(); }); // on commit, ensure that the value is correct by sourcing it from the objects' samples again. // this ensures that committing empty text causes a revert to the previous value. - bank.OnCommit += (_, _) => updateBankText(); + bank.OnCommit += (_, _) => updatePrimaryBankState(); - updateAdditionBankText(); - updateAdditionBankVisual(); + updateAdditionBankState(); additionBank.Current.BindValueChanged(val => { - updateAdditionBank(val.NewValue); - updateAdditionBankVisual(); - }); - additionBank.OnCommit += (_, _) => updateAdditionBankText(); + if (string.IsNullOrEmpty(val.NewValue)) + return; - volume.Current.BindValueChanged(val => updateVolume(val.NewValue)); + setAdditionBank(val.NewValue); + updateAdditionBankState(); + }); + additionBank.OnCommit += (_, _) => updateAdditionBankState(); + + volume.Current.BindValueChanged(val => + { + if (val.NewValue != null) + setVolume(val.NewValue.Value); + }); createStateBindables(); updateTernaryStates(); @@ -210,6 +219,26 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline private string? getCommonAdditionBank() => allRelevantSamples.Select(GetAdditionBankValue).Distinct().Count() == 1 ? GetAdditionBankValue(allRelevantSamples.First()) : null; private int? getCommonVolume() => allRelevantSamples.Select(GetVolumeValue).Distinct().Count() == 1 ? GetVolumeValue(allRelevantSamples.First()) : null; + private void updatePrimaryBankState() + { + string? commonBank = getCommonBank(); + bank.Current.Value = commonBank; + bank.PlaceholderText = string.IsNullOrEmpty(commonBank) ? "(multiple)" : string.Empty; + } + + private void updateAdditionBankState() + { + string? commonAdditionBank = getCommonAdditionBank(); + additionBank.PlaceholderText = string.IsNullOrEmpty(commonAdditionBank) ? "(multiple)" : string.Empty; + additionBank.Current.Value = commonAdditionBank; + + bool anyAdditions = allRelevantSamples.Any(o => o.Any(s => s.Name != HitSampleInfo.HIT_NORMAL)); + if (anyAdditions) + additionBank.Show(); + else + additionBank.Hide(); + } + /// /// Applies the given update action on all samples of /// and invokes the necessary update notifiers for the beatmap and hit objects. @@ -229,11 +258,8 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline beatmap.EndChange(); } - private void updateBank(string? newBank) + private void setBank(string newBank) { - if (string.IsNullOrEmpty(newBank)) - return; - updateAllRelevantSamples((_, relevantSamples) => { for (int i = 0; i < relevantSamples.Count; i++) @@ -245,11 +271,8 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline }); } - private void updateAdditionBank(string? newBank) + private void setAdditionBank(string newBank) { - if (string.IsNullOrEmpty(newBank)) - return; - updateAllRelevantSamples((_, relevantSamples) => { for (int i = 0; i < relevantSamples.Count; i++) @@ -261,47 +284,13 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline }); } - private void updateBankText() + private void setVolume(int newVolume) { - bank.Current.Value = getCommonBank(); - } - - private void updateBankPlaceholderText() - { - string? commonBank = getCommonBank(); - bank.PlaceholderText = string.IsNullOrEmpty(commonBank) ? "(multiple)" : string.Empty; - } - - private void updateAdditionBankVisual() - { - string? commonAdditionBank = getCommonAdditionBank(); - additionBank.PlaceholderText = string.IsNullOrEmpty(commonAdditionBank) ? "(multiple)" : string.Empty; - - bool anyAdditions = allRelevantSamples.Any(o => o.Any(s => s.Name != HitSampleInfo.HIT_NORMAL)); - if (anyAdditions) - additionBank.Show(); - else - additionBank.Hide(); - } - - private void updateAdditionBankText() - { - string? commonAdditionBank = getCommonAdditionBank(); - if (string.IsNullOrEmpty(commonAdditionBank)) return; - - additionBank.Current.Value = commonAdditionBank; - } - - private void updateVolume(int? newVolume) - { - if (newVolume == null) - return; - updateAllRelevantSamples((_, relevantSamples) => { for (int i = 0; i < relevantSamples.Count; i++) { - relevantSamples[i] = relevantSamples[i].With(newVolume: newVolume.Value); + relevantSamples[i] = relevantSamples[i].With(newVolume: newVolume); } }); } @@ -371,8 +360,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline relevantSamples.Add(relevantSample?.With(sampleName) ?? h.CreateHitSampleInfo(sampleName)); }); - updateAdditionBankVisual(); - updateAdditionBankText(); + updateAdditionBankState(); } private void removeHitSample(string sampleName) @@ -389,8 +377,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline } }); - updateAdditionBankText(); - updateAdditionBankVisual(); + updateAdditionBankState(); } protected override bool OnKeyDown(KeyDownEvent e) @@ -401,10 +388,14 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline if (e.ShiftPressed) { string? newBank = banks.ElementAtOrDefault(rightIndex); - updateBank(newBank); - updateBankText(); - updateAdditionBank(newBank); - updateAdditionBankText(); + + if (string.IsNullOrEmpty(newBank)) + return true; + + setBank(newBank); + updatePrimaryBankState(); + setAdditionBank(newBank); + updateAdditionBankState(); } else { From 7d5dc750e53938d60fa6cffedc1879d32c546118 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 10 Jun 2024 12:04:52 +0200 Subject: [PATCH 44/91] Use slightly lighter shade of pink for alternative colour --- .../Edit/Compose/Components/Timeline/SamplePointPiece.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs index 5f83937986..f318a52b28 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs @@ -39,7 +39,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline public bool AlternativeColor { get; init; } - protected override Color4 GetRepresentingColour(OsuColour colours) => AlternativeColor ? colours.PinkDarker : colours.Pink; + protected override Color4 GetRepresentingColour(OsuColour colours) => AlternativeColor ? colours.Pink2 : colours.Pink1; [BackgroundDependencyLoader] private void load() From b8e07045545fbfe3b46818b01fed515f12977fc8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 11 Jun 2024 09:14:46 +0200 Subject: [PATCH 45/91] Implement singular change events for `ControlPoint` --- .../Beatmaps/ControlPoints/ControlPoint.cs | 22 ++++++++++++++++++- .../ControlPoints/DifficultyControlPoint.cs | 5 +++++ .../ControlPoints/EffectControlPoint.cs | 6 +++++ .../ControlPoints/SampleControlPoint.cs | 6 +++++ .../ControlPoints/TimingControlPoint.cs | 7 ++++++ 5 files changed, 45 insertions(+), 1 deletion(-) diff --git a/osu.Game/Beatmaps/ControlPoints/ControlPoint.cs b/osu.Game/Beatmaps/ControlPoints/ControlPoint.cs index f46e4af332..f08a3d3f87 100644 --- a/osu.Game/Beatmaps/ControlPoints/ControlPoint.cs +++ b/osu.Game/Beatmaps/ControlPoints/ControlPoint.cs @@ -11,8 +11,28 @@ namespace osu.Game.Beatmaps.ControlPoints { public abstract class ControlPoint : IComparable, IDeepCloneable, IEquatable, IControlPoint { + /// + /// Invoked when any of this 's properties have changed. + /// + public event Action? Changed; + + protected void RaiseChanged() => Changed?.Invoke(this); + + private double time; + [JsonIgnore] - public double Time { get; set; } + public double Time + { + get => time; + set + { + if (time == value) + return; + + time = value; + RaiseChanged(); + } + } public void AttachGroup(ControlPointGroup pointGroup) => Time = pointGroup.Time; diff --git a/osu.Game/Beatmaps/ControlPoints/DifficultyControlPoint.cs b/osu.Game/Beatmaps/ControlPoints/DifficultyControlPoint.cs index 05230c85f4..9f8ed1b396 100644 --- a/osu.Game/Beatmaps/ControlPoints/DifficultyControlPoint.cs +++ b/osu.Game/Beatmaps/ControlPoints/DifficultyControlPoint.cs @@ -44,6 +44,11 @@ namespace osu.Game.Beatmaps.ControlPoints set => SliderVelocityBindable.Value = value; } + public DifficultyControlPoint() + { + SliderVelocityBindable.BindValueChanged(_ => RaiseChanged()); + } + public override bool IsRedundant(ControlPoint? existing) => existing is DifficultyControlPoint existingDifficulty && GenerateTicks == existingDifficulty.GenerateTicks diff --git a/osu.Game/Beatmaps/ControlPoints/EffectControlPoint.cs b/osu.Game/Beatmaps/ControlPoints/EffectControlPoint.cs index 0138ac7569..d48ed957ee 100644 --- a/osu.Game/Beatmaps/ControlPoints/EffectControlPoint.cs +++ b/osu.Game/Beatmaps/ControlPoints/EffectControlPoint.cs @@ -50,6 +50,12 @@ namespace osu.Game.Beatmaps.ControlPoints set => KiaiModeBindable.Value = value; } + public EffectControlPoint() + { + KiaiModeBindable.BindValueChanged(_ => RaiseChanged()); + ScrollSpeedBindable.BindValueChanged(_ => RaiseChanged()); + } + public override bool IsRedundant(ControlPoint? existing) => existing is EffectControlPoint existingEffect && KiaiMode == existingEffect.KiaiMode diff --git a/osu.Game/Beatmaps/ControlPoints/SampleControlPoint.cs b/osu.Game/Beatmaps/ControlPoints/SampleControlPoint.cs index ae4bdafd6f..800d9f9abc 100644 --- a/osu.Game/Beatmaps/ControlPoints/SampleControlPoint.cs +++ b/osu.Game/Beatmaps/ControlPoints/SampleControlPoint.cs @@ -56,6 +56,12 @@ namespace osu.Game.Beatmaps.ControlPoints set => SampleVolumeBindable.Value = value; } + public SampleControlPoint() + { + SampleBankBindable.BindValueChanged(_ => RaiseChanged()); + SampleVolumeBindable.BindValueChanged(_ => RaiseChanged()); + } + /// /// Create a SampleInfo based on the sample settings in this control point. /// diff --git a/osu.Game/Beatmaps/ControlPoints/TimingControlPoint.cs b/osu.Game/Beatmaps/ControlPoints/TimingControlPoint.cs index 4e69486e2d..9ac361cffe 100644 --- a/osu.Game/Beatmaps/ControlPoints/TimingControlPoint.cs +++ b/osu.Game/Beatmaps/ControlPoints/TimingControlPoint.cs @@ -82,6 +82,13 @@ namespace osu.Game.Beatmaps.ControlPoints /// public double BPM => 60000 / BeatLength; + public TimingControlPoint() + { + TimeSignatureBindable.BindValueChanged(_ => RaiseChanged()); + OmitFirstBarLineBindable.BindValueChanged(_ => RaiseChanged()); + BeatLengthBindable.BindValueChanged(_ => RaiseChanged()); + } + // Timing points are never redundant as they can change the time signature. public override bool IsRedundant(ControlPoint? existing) => false; From 694cfd0e259d2e5357a5692b4e05c8e680ddb49c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 11 Jun 2024 09:15:14 +0200 Subject: [PATCH 46/91] Expose singular coarse-grained change event on `ControlPointInfo` --- .../Beatmaps/ControlPoints/ControlPointGroup.cs | 5 +++++ .../Beatmaps/ControlPoints/ControlPointInfo.cs | 14 ++++++++++++++ 2 files changed, 19 insertions(+) diff --git a/osu.Game/Beatmaps/ControlPoints/ControlPointGroup.cs b/osu.Game/Beatmaps/ControlPoints/ControlPointGroup.cs index 1f34f3777d..c9c87dc85d 100644 --- a/osu.Game/Beatmaps/ControlPoints/ControlPointGroup.cs +++ b/osu.Game/Beatmaps/ControlPoints/ControlPointGroup.cs @@ -10,8 +10,11 @@ namespace osu.Game.Beatmaps.ControlPoints public class ControlPointGroup : IComparable, IEquatable { public event Action? ItemAdded; + public event Action? ItemChanged; public event Action? ItemRemoved; + private void raiseItemChanged(ControlPoint controlPoint) => ItemChanged?.Invoke(controlPoint); + /// /// The time at which the control point takes effect. /// @@ -39,12 +42,14 @@ namespace osu.Game.Beatmaps.ControlPoints controlPoints.Add(point); ItemAdded?.Invoke(point); + point.Changed += raiseItemChanged; } public void Remove(ControlPoint point) { controlPoints.Remove(point); ItemRemoved?.Invoke(point); + point.Changed -= raiseItemChanged; } public sealed override bool Equals(object? obj) diff --git a/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs b/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs index 1a15db98e4..cb7515b66c 100644 --- a/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs +++ b/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs @@ -19,6 +19,14 @@ namespace osu.Game.Beatmaps.ControlPoints [Serializable] public class ControlPointInfo : IDeepCloneable { + /// + /// Invoked on any change to the set of control points. + /// + [CanBeNull] + public event Action ControlPointsChanged; + + private void raiseControlPointsChanged([CanBeNull] ControlPoint _ = null) => ControlPointsChanged?.Invoke(); + /// /// All control points grouped by time. /// @@ -116,6 +124,7 @@ namespace osu.Game.Beatmaps.ControlPoints if (addIfNotExisting) { newGroup.ItemAdded += GroupItemAdded; + newGroup.ItemChanged += raiseControlPointsChanged; newGroup.ItemRemoved += GroupItemRemoved; groups.Insert(~i, newGroup); @@ -131,6 +140,7 @@ namespace osu.Game.Beatmaps.ControlPoints group.Remove(item); group.ItemAdded -= GroupItemAdded; + group.ItemChanged -= raiseControlPointsChanged; group.ItemRemoved -= GroupItemRemoved; groups.Remove(group); @@ -287,6 +297,8 @@ namespace osu.Game.Beatmaps.ControlPoints default: throw new ArgumentException($"A control point of unexpected type {controlPoint.GetType()} was added to this {nameof(ControlPointInfo)}"); } + + raiseControlPointsChanged(); } protected virtual void GroupItemRemoved(ControlPoint controlPoint) @@ -301,6 +313,8 @@ namespace osu.Game.Beatmaps.ControlPoints effectPoints.Remove(typed); break; } + + raiseControlPointsChanged(); } public ControlPointInfo DeepClone() From 922837dd3a6c71bd04163f48270ecb25682b6551 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 11 Jun 2024 09:33:25 +0200 Subject: [PATCH 47/91] Reload scrolling composer on control point changes --- .../Rulesets/Edit/ScrollingHitObjectComposer.cs | 17 +++++++++++++++++ osu.Game/Screens/Edit/Editor.cs | 9 +++++++++ 2 files changed, 26 insertions(+) diff --git a/osu.Game/Rulesets/Edit/ScrollingHitObjectComposer.cs b/osu.Game/Rulesets/Edit/ScrollingHitObjectComposer.cs index eb73cef01a..223b770b48 100644 --- a/osu.Game/Rulesets/Edit/ScrollingHitObjectComposer.cs +++ b/osu.Game/Rulesets/Edit/ScrollingHitObjectComposer.cs @@ -4,6 +4,7 @@ using System.Linq; using osu.Framework.Allocation; using osu.Framework.Bindables; +using osu.Framework.Extensions.ObjectExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; @@ -12,6 +13,7 @@ using osu.Game.Graphics.UserInterface; using osu.Game.Rulesets.Edit.Tools; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.UI.Scrolling; +using osu.Game.Screens.Edit; using osu.Game.Screens.Edit.Components.TernaryButtons; using osu.Game.Screens.Edit.Compose.Components; using osuTK; @@ -21,6 +23,9 @@ namespace osu.Game.Rulesets.Edit public abstract partial class ScrollingHitObjectComposer : HitObjectComposer where TObject : HitObject { + [Resolved] + private Editor? editor { get; set; } + private readonly Bindable showSpeedChanges = new Bindable(); private Bindable configShowSpeedChanges = null!; @@ -72,6 +77,8 @@ namespace osu.Game.Rulesets.Edit if (beatSnapGrid != null) AddInternal(beatSnapGrid); + + EditorBeatmap.ControlPointInfo.ControlPointsChanged += expireComposeScreenOnControlPointChange; } protected override void UpdateAfterChildren() @@ -104,5 +111,15 @@ namespace osu.Game.Rulesets.Edit beatSnapGrid.SelectionTimeRange = null; } } + + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + + if (EditorBeatmap.IsNotNull()) + EditorBeatmap.ControlPointInfo.ControlPointsChanged -= expireComposeScreenOnControlPointChange; + } + + private void expireComposeScreenOnControlPointChange() => editor?.ReloadComposeScreen(); } } diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index 3e3e772810..9703785856 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -996,6 +996,15 @@ namespace osu.Game.Screens.Edit } } + /// + /// Forces a reload of the compose screen after significant configuration changes. + /// + /// + /// This can be necessary for scrolling rulesets, as they do not easily support control points changing under them. + /// The reason that this works is that will re-instantiate the screen whenever it is requested next. + /// + public void ReloadComposeScreen() => screenContainer.SingleOrDefault(s => s.Type == EditorScreenMode.Compose)?.RemoveAndDisposeImmediately(); + [CanBeNull] private ScheduledDelegate playbackDisabledDebounce; From 12dd60736a37d5c173eb9a7d04e207c4a623d3d5 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Tue, 11 Jun 2024 21:20:42 +0200 Subject: [PATCH 48/91] remove code already covered by updatePrimaryBankState --- .../Edit/Compose/Components/Timeline/SamplePointPiece.cs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs index f318a52b28..0d392e9a99 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs @@ -172,10 +172,6 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline allRelevantSamples = relevantObjects.Select(GetRelevantSamples).ToArray(); // even if there are multiple objects selected, we can still display sample volume or bank if they all have the same value. - string? commonBank = getCommonBank(); - if (!string.IsNullOrEmpty(commonBank)) - bank.Current.Value = commonBank; - int? commonVolume = getCommonVolume(); if (commonVolume != null) volume.Current.Value = commonVolume.Value; From 869cd40195348a8056968bd9ba2f9927a1dc49ea Mon Sep 17 00:00:00 2001 From: OliBomby Date: Tue, 11 Jun 2024 21:31:18 +0200 Subject: [PATCH 49/91] Fixed samples without additions contributing to common addition bank while not having an editable addition bank --- .../Edit/Compose/Components/Timeline/SamplePointPiece.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs index 0d392e9a99..930b78b468 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs @@ -77,7 +77,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline public static string? GetAdditionBankValue(IEnumerable samples) { - return samples.FirstOrDefault(o => o.Name != HitSampleInfo.HIT_NORMAL)?.Bank ?? GetBankValue(samples); + return samples.FirstOrDefault(o => o.Name != HitSampleInfo.HIT_NORMAL)?.Bank; } public static int GetVolumeValue(ICollection samples) @@ -212,7 +212,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline } private string? getCommonBank() => allRelevantSamples.Select(GetBankValue).Distinct().Count() == 1 ? GetBankValue(allRelevantSamples.First()) : null; - private string? getCommonAdditionBank() => allRelevantSamples.Select(GetAdditionBankValue).Distinct().Count() == 1 ? GetAdditionBankValue(allRelevantSamples.First()) : null; + private string? getCommonAdditionBank() => allRelevantSamples.Select(GetAdditionBankValue).Where(o => o is not null).Distinct().Count() == 1 ? GetAdditionBankValue(allRelevantSamples.First()) : null; private int? getCommonVolume() => allRelevantSamples.Select(GetVolumeValue).Distinct().Count() == 1 ? GetVolumeValue(allRelevantSamples.First()) : null; private void updatePrimaryBankState() From 8c4aa84037fc420c6179cb65d778e01268445c5f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 31 May 2024 13:20:21 +0200 Subject: [PATCH 50/91] Implement event feed view for daily challenge screen --- .../TestSceneDailyChallengeEventFeed.cs | 76 ++++++++++ .../DailyChallenge/DailyChallengeEventFeed.cs | 136 ++++++++++++++++++ 2 files changed, 212 insertions(+) create mode 100644 osu.Game.Tests/Visual/DailyChallenge/TestSceneDailyChallengeEventFeed.cs create mode 100644 osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeEventFeed.cs diff --git a/osu.Game.Tests/Visual/DailyChallenge/TestSceneDailyChallengeEventFeed.cs b/osu.Game.Tests/Visual/DailyChallenge/TestSceneDailyChallengeEventFeed.cs new file mode 100644 index 0000000000..85499f0588 --- /dev/null +++ b/osu.Game.Tests/Visual/DailyChallenge/TestSceneDailyChallengeEventFeed.cs @@ -0,0 +1,76 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using NUnit.Framework; +using osu.Framework.Allocation; +using osu.Framework.Extensions.ObjectExtensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Utils; +using osu.Game.Overlays; +using osu.Game.Screens.OnlinePlay.DailyChallenge; +using osu.Game.Tests.Resources; + +namespace osu.Game.Tests.Visual.DailyChallenge +{ + public partial class TestSceneDailyChallengeEventFeed : OsuTestScene + { + [Cached] + private OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Plum); + + [Test] + public void TestBasicAppearance() + { + DailyChallengeEventFeed feed = null!; + + AddStep("create content", () => Children = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = colourProvider.Background4, + }, + feed = new DailyChallengeEventFeed + { + RelativeSizeAxes = Axes.Both, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + } + }); + AddSliderStep("adjust width", 0.1f, 1, 1, width => + { + if (feed.IsNotNull()) + feed.Width = width; + }); + AddSliderStep("adjust height", 0.1f, 1, 1, height => + { + if (feed.IsNotNull()) + feed.Height = height; + }); + + AddStep("add normal score", () => + { + var testScore = TestResources.CreateTestScoreInfo(); + testScore.TotalScore = RNG.Next(1_000_000); + + feed.AddNewScore(new DailyChallengeEventFeed.NewScoreEvent(testScore, null)); + }); + + AddStep("add new user best", () => + { + var testScore = TestResources.CreateTestScoreInfo(); + testScore.TotalScore = RNG.Next(1_000_000); + + feed.AddNewScore(new DailyChallengeEventFeed.NewScoreEvent(testScore, RNG.Next(1, 1000))); + }); + + AddStep("add top 10 score", () => + { + var testScore = TestResources.CreateTestScoreInfo(); + testScore.TotalScore = RNG.Next(1_000_000); + + feed.AddNewScore(new DailyChallengeEventFeed.NewScoreEvent(testScore, RNG.Next(1, 10))); + }); + } + } +} diff --git a/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeEventFeed.cs b/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeEventFeed.cs new file mode 100644 index 0000000000..10f4f2cf78 --- /dev/null +++ b/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeEventFeed.cs @@ -0,0 +1,136 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using System.Collections.Generic; +using System.Linq; +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Game.Graphics; +using osu.Game.Graphics.Containers; +using osu.Game.Graphics.UserInterface; +using osu.Game.Online.API.Requests.Responses; +using osu.Game.Scoring; +using osu.Game.Users.Drawables; +using osuTK; + +namespace osu.Game.Screens.OnlinePlay.DailyChallenge +{ + public partial class DailyChallengeEventFeed : CompositeDrawable + { + private DailyChallengeEventFeedFlow flow = null!; + + [BackgroundDependencyLoader] + private void load() + { + InternalChildren = new Drawable[] + { + new SectionHeader("Events"), + new Container + { + RelativeSizeAxes = Axes.Both, + Padding = new MarginPadding { Top = 35 }, + Child = flow = new DailyChallengeEventFeedFlow + { + RelativeSizeAxes = Axes.Both, + Direction = FillDirection.Vertical, + Origin = Anchor.BottomCentre, + Anchor = Anchor.BottomCentre, + Spacing = new Vector2(5), + Masking = true, + } + } + }; + } + + public void AddNewScore(NewScoreEvent newScoreEvent) + { + var row = new NewScoreEventRow(newScoreEvent) + { + Anchor = Anchor.BottomCentre, + Origin = Anchor.BottomCentre, + }; + flow.Add(row); + row.Delay(15000).Then().FadeOut(300, Easing.OutQuint).Expire(); + } + + protected override void Update() + { + base.Update(); + + for (int i = 0; i < flow.Count; ++i) + { + var row = flow[i]; + + if (row.Y < -flow.DrawHeight) + { + row.RemoveAndDisposeImmediately(); + i -= 1; + } + } + } + + public record NewScoreEvent( + IScoreInfo Score, + int? NewRank); + + private partial class DailyChallengeEventFeedFlow : FillFlowContainer + { + public override IEnumerable FlowingChildren => base.FlowingChildren.Reverse(); + } + + private partial class NewScoreEventRow : CompositeDrawable + { + public Action? PresentScore { get; set; } + + private readonly NewScoreEvent newScore; + + public NewScoreEventRow(NewScoreEvent newScore) + { + this.newScore = newScore; + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + LinkFlowContainer text; + + RelativeSizeAxes = Axes.X; + AutoSizeAxes = Axes.Y; + AutoSizeDuration = 300; + AutoSizeEasing = Easing.OutQuint; + + InternalChildren = new Drawable[] + { + // TODO: cast is temporary, will be removed later + new ClickableAvatar((APIUser)newScore.Score.User) + { + Size = new Vector2(16), + Masking = true, + CornerRadius = 8, + }, + text = new LinkFlowContainer(t => + { + t.Font = OsuFont.Default.With(weight: newScore.NewRank == null ? FontWeight.Medium : FontWeight.Bold); + t.Colour = newScore.NewRank < 10 ? colours.Orange1 : Colour4.White; + }) + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Padding = new MarginPadding { Left = 21 }, + } + }; + + text.AddUserLink(newScore.Score.User); + text.AddText(" got "); + text.AddLink($"{newScore.Score.TotalScore:N0} points", () => PresentScore?.Invoke(newScore.Score)); + + if (newScore.NewRank != null) + text.AddText($" and achieved rank #{newScore.NewRank.Value:N0}"); + + text.AddText("!"); + } + } + } +} From 253b7b046b663cee98177ac15c60e56029ab252b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 13 Jun 2024 15:03:38 +0200 Subject: [PATCH 51/91] Add test scene for taiko relax mod --- .../Mods/TestSceneTaikoModRelax.cs | 46 +++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100644 osu.Game.Rulesets.Taiko.Tests/Mods/TestSceneTaikoModRelax.cs diff --git a/osu.Game.Rulesets.Taiko.Tests/Mods/TestSceneTaikoModRelax.cs b/osu.Game.Rulesets.Taiko.Tests/Mods/TestSceneTaikoModRelax.cs new file mode 100644 index 0000000000..caf8aa8e76 --- /dev/null +++ b/osu.Game.Rulesets.Taiko.Tests/Mods/TestSceneTaikoModRelax.cs @@ -0,0 +1,46 @@ +// 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.Game.Rulesets.Taiko.Beatmaps; +using osu.Game.Rulesets.Taiko.Mods; +using osu.Game.Rulesets.Taiko.Objects; +using osu.Game.Rulesets.Taiko.Replays; + +namespace osu.Game.Rulesets.Taiko.Tests.Mods +{ + public partial class TestSceneTaikoModRelax : TaikoModTestScene + { + [Test] + public void TestRelax() + { + var beatmap = new TaikoBeatmap + { + HitObjects = + { + new Hit { StartTime = 0, Type = HitType.Centre, }, + new Hit { StartTime = 250, Type = HitType.Rim, }, + new DrumRoll { StartTime = 500, Duration = 500, }, + new Swell { StartTime = 1250, Duration = 500 }, + } + }; + foreach (var ho in beatmap.HitObjects) + ho.ApplyDefaults(beatmap.ControlPointInfo, beatmap.Difficulty); + + var replay = new TaikoAutoGenerator(beatmap).Generate(); + + foreach (var frame in replay.Frames.OfType().Where(r => r.Actions.Any())) + frame.Actions = [TaikoAction.LeftCentre]; + + CreateModTest(new ModTestData + { + Mod = new TaikoModRelax(), + Beatmap = beatmap, + ReplayFrames = replay.Frames, + Autoplay = false, + PassCondition = () => Player.ScoreProcessor.HasCompleted.Value && Player.ScoreProcessor.Accuracy.Value == 1, + }); + } + } +} From 173f1958343efe9d4ccdfea256b951512ef3b7c8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 13 Jun 2024 15:06:31 +0200 Subject: [PATCH 52/91] Add precautionary test coverage for alternating still being required by default for swells --- .../Judgements/TestSceneSwellJudgements.cs | 36 +++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/osu.Game.Rulesets.Taiko.Tests/Judgements/TestSceneSwellJudgements.cs b/osu.Game.Rulesets.Taiko.Tests/Judgements/TestSceneSwellJudgements.cs index 6e42ae7eb5..04661fe2cf 100644 --- a/osu.Game.Rulesets.Taiko.Tests/Judgements/TestSceneSwellJudgements.cs +++ b/osu.Game.Rulesets.Taiko.Tests/Judgements/TestSceneSwellJudgements.cs @@ -85,6 +85,42 @@ namespace osu.Game.Rulesets.Taiko.Tests.Judgements AssertResult(0, HitResult.IgnoreMiss); } + [Test] + public void TestAlternatingIsRequired() + { + const double hit_time = 1000; + + Swell swell = new Swell + { + StartTime = hit_time, + Duration = 1000, + RequiredHits = 10 + }; + + List frames = new List + { + new TaikoReplayFrame(0), + new TaikoReplayFrame(2001), + }; + + for (int i = 0; i < swell.RequiredHits; i++) + { + double frameTime = 1000 + i * 50; + frames.Add(new TaikoReplayFrame(frameTime, TaikoAction.LeftCentre)); + frames.Add(new TaikoReplayFrame(frameTime + 10)); + } + + PerformTest(frames, CreateBeatmap(swell)); + + AssertJudgementCount(11); + + AssertResult(0, HitResult.IgnoreHit); + for (int i = 1; i < swell.RequiredHits; i++) + AssertResult(i, HitResult.IgnoreMiss); + + AssertResult(0, HitResult.IgnoreMiss); + } + [Test] public void TestHitNoneSwell() { From d7997cc93c42c83a42bf01b4140154bcff21900f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 13 Jun 2024 14:49:56 +0200 Subject: [PATCH 53/91] Implement taiko relax mod --- osu.Game.Rulesets.Taiko/Mods/TaikoModRelax.cs | 25 +++++++++++++++++-- .../Objects/Drawables/DrawableHit.cs | 2 +- .../Objects/Drawables/DrawableSwell.cs | 7 +++++- 3 files changed, 30 insertions(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/Mods/TaikoModRelax.cs b/osu.Game.Rulesets.Taiko/Mods/TaikoModRelax.cs index e90ab589fc..ed09a85ebb 100644 --- a/osu.Game.Rulesets.Taiko/Mods/TaikoModRelax.cs +++ b/osu.Game.Rulesets.Taiko/Mods/TaikoModRelax.cs @@ -5,13 +5,34 @@ using System; using System.Linq; using osu.Framework.Localisation; using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Objects.Drawables; +using osu.Game.Rulesets.Taiko.Objects.Drawables; namespace osu.Game.Rulesets.Taiko.Mods { - public class TaikoModRelax : ModRelax + public class TaikoModRelax : ModRelax, IApplicableToDrawableHitObject { - public override LocalisableString Description => @"No ninja-like spinners, demanding drumrolls or unexpected katus."; + public override LocalisableString Description => @"No need to remember which key is correct anymore!"; public override Type[] IncompatibleMods => base.IncompatibleMods.Concat(new[] { typeof(TaikoModSingleTap) }).ToArray(); + + public void ApplyToDrawableHitObject(DrawableHitObject drawable) + { + var allActions = Enum.GetValues(); + + drawable.HitObjectApplied += dho => + { + switch (dho) + { + case DrawableHit hit: + hit.HitActions = allActions; + break; + + case DrawableSwell swell: + swell.MustAlternate = false; + break; + } + }; + } } } diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableHit.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableHit.cs index 4fb69056da..a5e63c373f 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableHit.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableHit.cs @@ -22,7 +22,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables /// /// A list of keys which can result in hits for this HitObject. /// - public TaikoAction[] HitActions { get; private set; } + public TaikoAction[] HitActions { get; internal set; } /// /// The action that caused this to be hit. diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwell.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwell.cs index e1fc28fe16..f2fcd185dd 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwell.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwell.cs @@ -43,6 +43,11 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables public override bool DisplayResult => false; + /// + /// Whether the player must alternate centre and rim hits. + /// + public bool MustAlternate { get; internal set; } = true; + public DrawableSwell() : this(null) { @@ -292,7 +297,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables bool isCentre = e.Action == TaikoAction.LeftCentre || e.Action == TaikoAction.RightCentre; // Ensure alternating centre and rim hits - if (lastWasCentre == isCentre) + if (lastWasCentre == isCentre && MustAlternate) return false; // If we've already successfully judged a tick this frame, do not judge more. From bfca64a98ebcb15e84bd22ff4b10653b3ddb00d2 Mon Sep 17 00:00:00 2001 From: Shiumano Date: Fri, 14 Jun 2024 20:07:27 +0900 Subject: [PATCH 54/91] Add failing test --- .../Gameplay/TestSceneBeatmapOffsetControl.cs | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneBeatmapOffsetControl.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneBeatmapOffsetControl.cs index 83fc5c2013..1de55fce8c 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneBeatmapOffsetControl.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneBeatmapOffsetControl.cs @@ -137,5 +137,30 @@ namespace osu.Game.Tests.Visual.Gameplay AddStep("Remove reference score", () => offsetControl.ReferenceScore.Value = null); AddAssert("No calibration button", () => !offsetControl.ChildrenOfType().Any()); } + + [Test] + public void TestCalibrationNoChange() + { + const double average_error = 0; + + AddAssert("Offset is neutral", () => offsetControl.Current.Value == 0); + AddAssert("No calibration button", () => !offsetControl.ChildrenOfType().Any()); + AddStep("Set reference score", () => + { + offsetControl.ReferenceScore.Value = new ScoreInfo + { + HitEvents = TestSceneHitEventTimingDistributionGraph.CreateDistributedHitEvents(average_error), + BeatmapInfo = Beatmap.Value.BeatmapInfo, + }; + }); + + AddUntilStep("Has calibration button", () => offsetControl.ChildrenOfType().Any()); + AddStep("Press button", () => offsetControl.ChildrenOfType().Single().TriggerClick()); + AddAssert("Offset is adjusted", () => offsetControl.Current.Value == -average_error); + + AddUntilStep("Button is disabled", () => !offsetControl.ChildrenOfType().Single().Enabled.Value); + AddStep("Remove reference score", () => offsetControl.ReferenceScore.Value = null); + AddAssert("No calibration button", () => !offsetControl.ChildrenOfType().Any()); + } } } From 67ca7e4135f5c3d3578da8db5b060b263ede984a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 14 Jun 2024 13:36:40 +0200 Subject: [PATCH 55/91] Implement toggling visibility of pass and fail storyboard layers Closes https://github.com/ppy/osu/issues/6842. This is a rather barebones implementation, just to get this in place somehow at least. The logic is simple - 50% health or above shows pass layer, anything below shows fail layer. This does not match stable logic all across the board because I have no idea how to package that. Stable defines "passing" in like fifty ways: - in mania it's >80% HP (https://github.com/peppy/osu-stable-reference/blob/bb57924c1552adbed11ee3d96cdcde47cf96f2b6/osu!/GameModes/Play/Rulesets/Mania/RulesetMania.cs#L333-L336) - in taiko it's >80% *accuracy* (https://github.com/peppy/osu-stable-reference/blob/bb57924c1552adbed11ee3d96cdcde47cf96f2b6/osu!/GameModes/Play/Rulesets/Taiko/RulesetTaiko.cs#L486-L492) - there's also the part where "geki additions" will unconditionally set passing state (https://github.com/peppy/osu-stable-reference/blob/bb57924c1552adbed11ee3d96cdcde47cf96f2b6/osu!/GameModes/Play/Player.cs#L3561-L3564) - and also the part where at the end of the map, the final passing state is determined by checking whether the user passed more sections than failed (https://github.com/peppy/osu-stable-reference/blob/bb57924c1552adbed11ee3d96cdcde47cf96f2b6/osu!/GameModes/Play/Player.cs#L3320) The biggest issues of these are probably the first two, and they can *probably* be fixed, but would require a new member on `Ruleset` and I'm not sure how to make one look, so I'm not doing that at this time pending collection of ideas on how to do that. --- .../Visual/Gameplay/TestSceneStoryboard.cs | 12 ++++--- osu.Game/Screens/Play/GameplayState.cs | 11 ++++++- osu.Game/Screens/Play/Player.cs | 2 +- .../Drawables/DrawableStoryboard.cs | 33 +++++++++---------- osu.Game/Tests/Gameplay/TestGameplayState.cs | 4 ++- 5 files changed, 37 insertions(+), 25 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboard.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboard.cs index 893b9f11f4..e918a93cbc 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboard.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboard.cs @@ -14,8 +14,11 @@ using osu.Game.Beatmaps; using osu.Game.Beatmaps.Formats; using osu.Game.IO; using osu.Game.Overlays; +using osu.Game.Rulesets.Osu; +using osu.Game.Screens.Play; using osu.Game.Storyboards; using osu.Game.Storyboards.Drawables; +using osu.Game.Tests.Gameplay; using osu.Game.Tests.Resources; using osuTK.Graphics; @@ -28,14 +31,14 @@ namespace osu.Game.Tests.Visual.Gameplay private DrawableStoryboard? storyboard; + [Cached] + private GameplayState testGameplayState = TestGameplayState.Create(new OsuRuleset()); + [Test] public void TestStoryboard() { AddStep("Restart", restart); - AddToggleStep("Passing", passing => - { - if (storyboard != null) storyboard.Passing = passing; - }); + AddToggleStep("Toggle passing state", passing => testGameplayState.HealthProcessor.Health.Value = passing ? 1 : 0); } [Test] @@ -109,7 +112,6 @@ namespace osu.Game.Tests.Visual.Gameplay storyboardContainer.Clock = new FramedClock(Beatmap.Value.Track); storyboard = toLoad.CreateDrawable(SelectedMods.Value); - storyboard.Passing = false; storyboardContainer.Add(storyboard); } diff --git a/osu.Game/Screens/Play/GameplayState.cs b/osu.Game/Screens/Play/GameplayState.cs index 8b0207a340..478acd7229 100644 --- a/osu.Game/Screens/Play/GameplayState.cs +++ b/osu.Game/Screens/Play/GameplayState.cs @@ -40,6 +40,7 @@ namespace osu.Game.Screens.Play public readonly Score Score; public readonly ScoreProcessor ScoreProcessor; + public readonly HealthProcessor HealthProcessor; /// /// The storyboard associated with the beatmap. @@ -68,7 +69,14 @@ namespace osu.Game.Screens.Play private readonly Bindable lastJudgementResult = new Bindable(); - public GameplayState(IBeatmap beatmap, Ruleset ruleset, IReadOnlyList? mods = null, Score? score = null, ScoreProcessor? scoreProcessor = null, Storyboard? storyboard = null) + public GameplayState( + IBeatmap beatmap, + Ruleset ruleset, + IReadOnlyList? mods = null, + Score? score = null, + ScoreProcessor? scoreProcessor = null, + HealthProcessor? healthProcessor = null, + Storyboard? storyboard = null) { Beatmap = beatmap; Ruleset = ruleset; @@ -82,6 +90,7 @@ namespace osu.Game.Screens.Play }; Mods = mods ?? Array.Empty(); ScoreProcessor = scoreProcessor ?? ruleset.CreateScoreProcessor(); + HealthProcessor = healthProcessor ?? ruleset.CreateHealthProcessor(beatmap.HitObjects[0].StartTime); Storyboard = storyboard ?? new Storyboard(); } diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 42ff1d74f3..3a08d3be24 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -260,7 +260,7 @@ namespace osu.Game.Screens.Play Score.ScoreInfo.Ruleset = ruleset.RulesetInfo; Score.ScoreInfo.Mods = gameplayMods; - dependencies.CacheAs(GameplayState = new GameplayState(playableBeatmap, ruleset, gameplayMods, Score, ScoreProcessor, Beatmap.Value.Storyboard)); + dependencies.CacheAs(GameplayState = new GameplayState(playableBeatmap, ruleset, gameplayMods, Score, ScoreProcessor, HealthProcessor, Beatmap.Value.Storyboard)); var rulesetSkinProvider = new RulesetSkinProvidingContainer(ruleset, playableBeatmap, Beatmap.Value.Skin); diff --git a/osu.Game/Storyboards/Drawables/DrawableStoryboard.cs b/osu.Game/Storyboards/Drawables/DrawableStoryboard.cs index fc5ef12fb8..8e7b3feacf 100644 --- a/osu.Game/Storyboards/Drawables/DrawableStoryboard.cs +++ b/osu.Game/Storyboards/Drawables/DrawableStoryboard.cs @@ -37,20 +37,6 @@ namespace osu.Game.Storyboards.Drawables protected override Vector2 DrawScale => new Vector2(Parent!.DrawHeight / 480); - private bool passing = true; - - public bool Passing - { - get => passing; - set - { - if (passing == value) return; - - passing = value; - updateLayerVisibility(); - } - } - public override bool RemoveCompletedTransforms => false; private double? lastEventEndTime; @@ -66,6 +52,9 @@ namespace osu.Game.Storyboards.Drawables private DependencyContainer dependencies = null!; + private BindableNumber health = null!; + private readonly BindableBool passing = new BindableBool(true); + protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) => dependencies = new DependencyContainer(base.CreateChildDependencies(parent)); @@ -91,8 +80,8 @@ namespace osu.Game.Storyboards.Drawables }); } - [BackgroundDependencyLoader(true)] - private void load(IGameplayClock? clock, CancellationToken? cancellationToken) + [BackgroundDependencyLoader] + private void load(IGameplayClock? clock, CancellationToken? cancellationToken, GameplayState? gameplayState) { if (clock != null) Clock = clock; @@ -110,6 +99,16 @@ namespace osu.Game.Storyboards.Drawables } lastEventEndTime = Storyboard.LatestEventTime; + + health = gameplayState?.HealthProcessor.Health.GetBoundCopy() ?? new BindableDouble(1); + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + health.BindValueChanged(val => passing.Value = val.NewValue >= 0.5, true); + passing.BindValueChanged(_ => updateLayerVisibility(), true); } protected virtual IResourceStore CreateResourceLookupStore() => new StoryboardResourceLookupStore(Storyboard, realm, host); @@ -125,7 +124,7 @@ namespace osu.Game.Storyboards.Drawables private void updateLayerVisibility() { foreach (var layer in Children) - layer.Enabled = passing ? layer.Layer.VisibleWhenPassing : layer.Layer.VisibleWhenFailing; + layer.Enabled = passing.Value ? layer.Layer.VisibleWhenPassing : layer.Layer.VisibleWhenFailing; } private class StoryboardResourceLookupStore : IResourceStore diff --git a/osu.Game/Tests/Gameplay/TestGameplayState.cs b/osu.Game/Tests/Gameplay/TestGameplayState.cs index bb82335543..8fad6d1e23 100644 --- a/osu.Game/Tests/Gameplay/TestGameplayState.cs +++ b/osu.Game/Tests/Gameplay/TestGameplayState.cs @@ -27,7 +27,9 @@ namespace osu.Game.Tests.Gameplay var scoreProcessor = ruleset.CreateScoreProcessor(); scoreProcessor.ApplyBeatmap(playableBeatmap); - return new GameplayState(playableBeatmap, ruleset, mods, score, scoreProcessor); + var healthProcessor = ruleset.CreateHealthProcessor(beatmap.HitObjects[0].StartTime); + + return new GameplayState(playableBeatmap, ruleset, mods, score, scoreProcessor, healthProcessor); } } } From 6c82f1de9b12700e3654b3eb305e284930f2ea97 Mon Sep 17 00:00:00 2001 From: Shiumano Date: Fri, 14 Jun 2024 21:43:17 +0900 Subject: [PATCH 56/91] Set to enable or disable at load time --- osu.Game/Screens/Play/PlayerSettings/BeatmapOffsetControl.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game/Screens/Play/PlayerSettings/BeatmapOffsetControl.cs b/osu.Game/Screens/Play/PlayerSettings/BeatmapOffsetControl.cs index 9039604471..7c0a2e73c0 100644 --- a/osu.Game/Screens/Play/PlayerSettings/BeatmapOffsetControl.cs +++ b/osu.Game/Screens/Play/PlayerSettings/BeatmapOffsetControl.cs @@ -237,6 +237,8 @@ namespace osu.Game.Screens.Play.PlayerSettings } }); + useAverageButton.Enabled.Value = !Precision.AlmostEquals(lastPlayAverage, 0, Current.Precision / 2); + if (settings != null) { globalOffsetText.AddText("You can also "); From 6bd633f8ce5fa2c608700075a4969c8954fe3121 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 15 Jun 2024 17:26:57 +0800 Subject: [PATCH 57/91] Apply NRT to test scene --- .../Visual/Gameplay/TestSceneBeatmapOffsetControl.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneBeatmapOffsetControl.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneBeatmapOffsetControl.cs index 1de55fce8c..3b88750013 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneBeatmapOffsetControl.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneBeatmapOffsetControl.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System.Linq; using NUnit.Framework; using osu.Framework.Graphics; @@ -19,7 +17,7 @@ namespace osu.Game.Tests.Visual.Gameplay { public partial class TestSceneBeatmapOffsetControl : OsuTestScene { - private BeatmapOffsetControl offsetControl; + private BeatmapOffsetControl offsetControl = null!; [SetUpSteps] public void SetUpSteps() From ff2d721029809c8e7cadd6620a6634c898e9004d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 15 Jun 2024 17:34:04 +0800 Subject: [PATCH 58/91] Inline enabled setting --- osu.Game/Screens/Play/PlayerSettings/BeatmapOffsetControl.cs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/Play/PlayerSettings/BeatmapOffsetControl.cs b/osu.Game/Screens/Play/PlayerSettings/BeatmapOffsetControl.cs index 7c0a2e73c0..7668d3e635 100644 --- a/osu.Game/Screens/Play/PlayerSettings/BeatmapOffsetControl.cs +++ b/osu.Game/Screens/Play/PlayerSettings/BeatmapOffsetControl.cs @@ -228,7 +228,8 @@ namespace osu.Game.Screens.Play.PlayerSettings useAverageButton = new SettingsButton { Text = BeatmapOffsetControlStrings.CalibrateUsingLastPlay, - Action = () => Current.Value = lastPlayBeatmapOffset - lastPlayAverage + Action = () => Current.Value = lastPlayBeatmapOffset - lastPlayAverage, + Enabled = { Value = !Precision.AlmostEquals(lastPlayAverage, 0, Current.Precision / 2) } }, globalOffsetText = new LinkFlowContainer { @@ -237,8 +238,6 @@ namespace osu.Game.Screens.Play.PlayerSettings } }); - useAverageButton.Enabled.Value = !Precision.AlmostEquals(lastPlayAverage, 0, Current.Precision / 2); - if (settings != null) { globalOffsetText.AddText("You can also "); From 97003b3679389869d7247044f0964f863fb9e965 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 17 Jun 2024 09:08:43 +0200 Subject: [PATCH 59/91] Account for osu! circle radius when drawing playfield border Addresses https://github.com/ppy/osu/discussions/13167. --- osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs b/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs index 93c3450904..3a04f92ec0 100644 --- a/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs +++ b/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs @@ -12,6 +12,7 @@ using osu.Game.Beatmaps; using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Drawables; +using osu.Game.Rulesets.Objects.Legacy; using osu.Game.Rulesets.Osu.Beatmaps; using osu.Game.Rulesets.Osu.Configuration; using osu.Game.Rulesets.Osu.Objects; @@ -27,6 +28,7 @@ namespace osu.Game.Rulesets.Osu.UI [Cached] public partial class OsuPlayfield : Playfield { + private readonly Container borderContainer; private readonly PlayfieldBorder playfieldBorder; private readonly ProxyContainer approachCircles; private readonly ProxyContainer spinnerProxies; @@ -54,7 +56,11 @@ namespace osu.Game.Rulesets.Osu.UI InternalChildren = new Drawable[] { - playfieldBorder = new PlayfieldBorder { RelativeSizeAxes = Axes.Both }, + borderContainer = new Container + { + RelativeSizeAxes = Axes.Both, + Child = playfieldBorder = new PlayfieldBorder { RelativeSizeAxes = Axes.Both }, + }, Smoke = new SmokeContainer { RelativeSizeAxes = Axes.Both }, spinnerProxies = new ProxyContainer { RelativeSizeAxes = Axes.Both }, FollowPoints = new FollowPointRenderer { RelativeSizeAxes = Axes.Both }, @@ -151,6 +157,9 @@ namespace osu.Game.Rulesets.Osu.UI RegisterPool(2, 20); RegisterPool(10, 200); RegisterPool(10, 200); + + if (beatmap != null) + borderContainer.Padding = new MarginPadding(OsuHitObject.OBJECT_RADIUS * -LegacyRulesetExtensions.CalculateScaleFromCircleSize(beatmap.Difficulty.CircleSize, true)); } protected override HitObjectLifetimeEntry CreateLifetimeEntry(HitObject hitObject) => new OsuHitObjectLifetimeEntry(hitObject); From 41446a08b678e585324f94b3e5a1b6b7238c4cdd Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Mon, 17 Jun 2024 16:19:33 +0900 Subject: [PATCH 60/91] Annotate ControlPoint and Mod for AOT trimming support --- osu.Game/Beatmaps/ControlPoints/ControlPoint.cs | 2 ++ osu.Game/Rulesets/Mods/Mod.cs | 2 ++ 2 files changed, 4 insertions(+) diff --git a/osu.Game/Beatmaps/ControlPoints/ControlPoint.cs b/osu.Game/Beatmaps/ControlPoints/ControlPoint.cs index f46e4af332..c90557b5f1 100644 --- a/osu.Game/Beatmaps/ControlPoints/ControlPoint.cs +++ b/osu.Game/Beatmaps/ControlPoints/ControlPoint.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Diagnostics.CodeAnalysis; using Newtonsoft.Json; using osu.Game.Graphics; using osu.Game.Utils; @@ -9,6 +10,7 @@ using osuTK.Graphics; namespace osu.Game.Beatmaps.ControlPoints { + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] public abstract class ControlPoint : IComparable, IDeepCloneable, IEquatable, IControlPoint { [JsonIgnore] diff --git a/osu.Game/Rulesets/Mods/Mod.cs b/osu.Game/Rulesets/Mods/Mod.cs index 50c867f41b..b9a937b1a2 100644 --- a/osu.Game/Rulesets/Mods/Mod.cs +++ b/osu.Game/Rulesets/Mods/Mod.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.Globalization; using System.Linq; using System.Reflection; @@ -21,6 +22,7 @@ namespace osu.Game.Rulesets.Mods /// /// The base class for gameplay modifiers. /// + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] public abstract class Mod : IMod, IEquatable, IDeepCloneable { [JsonIgnore] From 1b00d0181a6b5bf073e2cb7a55188e7fa5b2e733 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 17 Jun 2024 09:36:01 +0200 Subject: [PATCH 61/91] Fix playfield border size not updating in editor on circle size change --- .../Edit/DrawableOsuEditorRuleset.cs | 23 +++++++++++++++++++ osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs | 7 +++++- 2 files changed, 29 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Edit/DrawableOsuEditorRuleset.cs b/osu.Game.Rulesets.Osu/Edit/DrawableOsuEditorRuleset.cs index 68c565af4d..4dd718597a 100644 --- a/osu.Game.Rulesets.Osu/Edit/DrawableOsuEditorRuleset.cs +++ b/osu.Game.Rulesets.Osu/Edit/DrawableOsuEditorRuleset.cs @@ -2,10 +2,13 @@ // See the LICENCE file in the repository root for full licence text. using System.Collections.Generic; +using osu.Framework.Allocation; +using osu.Framework.Extensions.ObjectExtensions; using osu.Game.Beatmaps; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Osu.UI; using osu.Game.Rulesets.UI; +using osu.Game.Screens.Edit; using osuTK; namespace osu.Game.Rulesets.Osu.Edit @@ -23,12 +26,32 @@ namespace osu.Game.Rulesets.Osu.Edit private partial class OsuEditorPlayfield : OsuPlayfield { + [Resolved] + private EditorBeatmap editorBeatmap { get; set; } = null!; + protected override GameplayCursorContainer? CreateCursor() => null; public OsuEditorPlayfield() { HitPolicy = new AnyOrderHitPolicy(); } + + protected override void LoadComplete() + { + base.LoadComplete(); + + editorBeatmap.BeatmapReprocessed += onBeatmapReprocessed; + } + + private void onBeatmapReprocessed() => ApplyCircleSizeToPlayfieldBorder(editorBeatmap); + + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + + if (editorBeatmap.IsNotNull()) + editorBeatmap.BeatmapReprocessed -= onBeatmapReprocessed; + } } } } diff --git a/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs b/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs index 3a04f92ec0..b39fc34d5d 100644 --- a/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs +++ b/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs @@ -159,7 +159,12 @@ namespace osu.Game.Rulesets.Osu.UI RegisterPool(10, 200); if (beatmap != null) - borderContainer.Padding = new MarginPadding(OsuHitObject.OBJECT_RADIUS * -LegacyRulesetExtensions.CalculateScaleFromCircleSize(beatmap.Difficulty.CircleSize, true)); + ApplyCircleSizeToPlayfieldBorder(beatmap); + } + + protected void ApplyCircleSizeToPlayfieldBorder(IBeatmap beatmap) + { + borderContainer.Padding = new MarginPadding(OsuHitObject.OBJECT_RADIUS * -LegacyRulesetExtensions.CalculateScaleFromCircleSize(beatmap.Difficulty.CircleSize, true)); } protected override HitObjectLifetimeEntry CreateLifetimeEntry(HitObject hitObject) => new OsuHitObjectLifetimeEntry(hitObject); From f86e9c9a4a5eb2c7b16ddd5820766266dac07b14 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Mon, 17 Jun 2024 17:13:44 +0900 Subject: [PATCH 62/91] Also annotate ControlPointInfo Same deal with this class. Fully qualifying the type names because this has `#nullable disable` and makes use of `NotNull` which is also present in the `System.Diagnostics.CodeAnalysis` namespace and AAAAAAARGH NAMESPACE CONFLICTS. --- osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs b/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs index 1a15db98e4..c695112990 100644 --- a/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs +++ b/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs @@ -17,6 +17,7 @@ using osu.Game.Utils; namespace osu.Game.Beatmaps.ControlPoints { [Serializable] + [System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] public class ControlPointInfo : IDeepCloneable { /// From b42752c9f06d10da5985ff4ab10e8e728a1a5be0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 17 Jun 2024 10:16:40 +0200 Subject: [PATCH 63/91] Move timeline toggle controls to "view" menu --- osu.Game/Configuration/OsuConfigManager.cs | 5 ++ osu.Game/Localisation/EditorStrings.cs | 25 ++++++---- .../Compose/Components/Timeline/Timeline.cs | 17 +++---- .../Components/Timeline/TimelineArea.cs | 50 ------------------- osu.Game/Screens/Edit/Editor.cs | 20 +++++++- .../Screens/Edit/WaveformOpacityMenuItem.cs | 1 + 6 files changed, 47 insertions(+), 71 deletions(-) diff --git a/osu.Game/Configuration/OsuConfigManager.cs b/osu.Game/Configuration/OsuConfigManager.cs index affcaffe02..bef1cf2899 100644 --- a/osu.Game/Configuration/OsuConfigManager.cs +++ b/osu.Game/Configuration/OsuConfigManager.cs @@ -208,6 +208,9 @@ namespace osu.Game.Configuration SetDefault(OsuSetting.ComboColourNormalisationAmount, 0.2f, 0f, 1f, 0.01f); SetDefault(OsuSetting.UserOnlineStatus, null); + + SetDefault(OsuSetting.EditorTimelineShowTimingChanges, true); + SetDefault(OsuSetting.EditorTimelineShowTicks, true); } protected override bool CheckLookupContainsPrivateInformation(OsuSetting lookup) @@ -439,5 +442,7 @@ namespace osu.Game.Configuration UserOnlineStatus, MultiplayerRoomFilter, HideCountryFlags, + EditorTimelineShowTimingChanges, + EditorTimelineShowTicks, } } diff --git a/osu.Game/Localisation/EditorStrings.cs b/osu.Game/Localisation/EditorStrings.cs index 6ad12f54df..bcffc18d4d 100644 --- a/osu.Game/Localisation/EditorStrings.cs +++ b/osu.Game/Localisation/EditorStrings.cs @@ -99,16 +99,6 @@ namespace osu.Game.Localisation /// public static LocalisableString TestBeatmap => new TranslatableString(getKey(@"test_beatmap"), @"Test!"); - /// - /// "Waveform" - /// - public static LocalisableString TimelineWaveform => new TranslatableString(getKey(@"timeline_waveform"), @"Waveform"); - - /// - /// "Ticks" - /// - public static LocalisableString TimelineTicks => new TranslatableString(getKey(@"timeline_ticks"), @"Ticks"); - /// /// "{0:0}°" /// @@ -134,6 +124,21 @@ namespace osu.Game.Localisation /// public static LocalisableString FailedToParseEditorLink => new TranslatableString(getKey(@"failed_to_parse_edtior_link"), @"Failed to parse editor link"); + /// + /// "Timeline" + /// + public static LocalisableString Timeline => new TranslatableString(getKey(@"timeline"), @"Timeline"); + + /// + /// "Show timing changes" + /// + public static LocalisableString TimelineShowTimingChanges => new TranslatableString(getKey(@"timeline_show_timing_changes"), @"Show timing changes"); + + /// + /// "Show ticks" + /// + public static LocalisableString TimelineShowTicks => new TranslatableString(getKey(@"timeline_show_ticks"), @"Show ticks"); + private static string getKey(string key) => $@"{prefix}:{key}"; } } diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs index a2704e550c..fa9964b104 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs @@ -30,12 +30,6 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline private readonly Drawable userContent; - public readonly Bindable WaveformVisible = new Bindable(); - - public readonly Bindable ControlPointsVisible = new Bindable(); - - public readonly Bindable TicksVisible = new Bindable(); - [Resolved] private EditorClock editorClock { get; set; } @@ -88,6 +82,8 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline private Container mainContent; private Bindable waveformOpacity; + private Bindable controlPointsVisible; + private Bindable ticksVisible; private double trackLengthForZoom; @@ -139,6 +135,8 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline }); waveformOpacity = config.GetBindable(OsuSetting.EditorWaveformOpacity); + controlPointsVisible = config.GetBindable(OsuSetting.EditorTimelineShowTimingChanges); + ticksVisible = config.GetBindable(OsuSetting.EditorTimelineShowTicks); track.BindTo(editorClock.Track); track.BindValueChanged(_ => @@ -168,12 +166,11 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline { base.LoadComplete(); - WaveformVisible.BindValueChanged(_ => updateWaveformOpacity()); waveformOpacity.BindValueChanged(_ => updateWaveformOpacity(), true); - TicksVisible.BindValueChanged(visible => ticks.FadeTo(visible.NewValue ? 1 : 0, 200, Easing.OutQuint), true); + ticksVisible.BindValueChanged(visible => ticks.FadeTo(visible.NewValue ? 1 : 0, 200, Easing.OutQuint), true); - ControlPointsVisible.BindValueChanged(visible => + controlPointsVisible.BindValueChanged(visible => { if (visible.NewValue) { @@ -195,7 +192,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline } private void updateWaveformOpacity() => - waveform.FadeTo(WaveformVisible.Value ? waveformOpacity.Value : 0, 200, Easing.OutQuint); + waveform.FadeTo(waveformOpacity.Value, 200, Easing.OutQuint); protected override void Update() { diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineArea.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineArea.cs index b973ac3731..5631adf8bd 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineArea.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineArea.cs @@ -7,10 +7,7 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; using osu.Game.Graphics; -using osu.Game.Graphics.UserInterface; -using osu.Game.Localisation; using osu.Game.Overlays; -using osu.Game.Resources.Localisation.Web; using osu.Game.Rulesets.Edit; using osuTK; @@ -33,10 +30,6 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline [BackgroundDependencyLoader] private void load(OverlayColourProvider colourProvider, OsuColour colours) { - OsuCheckbox waveformCheckbox; - OsuCheckbox controlPointsCheckbox; - OsuCheckbox ticksCheckbox; - const float padding = 10; InternalChildren = new Drawable[] @@ -51,7 +44,6 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline }, ColumnDimensions = new[] { - new Dimension(GridSizeMode.Absolute, 135), new Dimension(), new Dimension(GridSizeMode.Absolute, 35), new Dimension(GridSizeMode.Absolute, HitObjectComposer.TOOLBOX_CONTRACTED_SIZE_RIGHT - padding * 2), @@ -60,44 +52,6 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline { new Drawable[] { - new Container - { - RelativeSizeAxes = Axes.Both, - Name = @"Toggle controls", - Children = new Drawable[] - { - new Box - { - RelativeSizeAxes = Axes.Both, - Colour = colourProvider.Background2, - }, - new FillFlowContainer - { - RelativeSizeAxes = Axes.Both, - Padding = new MarginPadding(padding), - Direction = FillDirection.Vertical, - Spacing = new Vector2(0, 4), - Children = new[] - { - waveformCheckbox = new OsuCheckbox(nubSize: 30f) - { - LabelText = EditorStrings.TimelineWaveform, - Current = { Value = true }, - }, - ticksCheckbox = new OsuCheckbox(nubSize: 30f) - { - LabelText = EditorStrings.TimelineTicks, - Current = { Value = true }, - }, - controlPointsCheckbox = new OsuCheckbox(nubSize: 30f) - { - LabelText = BeatmapsetsStrings.ShowStatsBpm, - Current = { Value = true }, - }, - } - } - } - }, new Container { RelativeSizeAxes = Axes.X, @@ -167,10 +121,6 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline }, } }; - - Timeline.WaveformVisible.BindTo(waveformCheckbox.Current); - Timeline.ControlPointsVisible.BindTo(controlPointsCheckbox.Current); - Timeline.TicksVisible.BindTo(ticksCheckbox.Current); } } } diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index a630a5df41..316772da6e 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -211,6 +211,8 @@ namespace osu.Game.Screens.Edit private Bindable editorHitMarkers; private Bindable editorAutoSeekOnPlacement; private Bindable editorLimitedDistanceSnap; + private Bindable editorTimelineShowTimingChanges; + private Bindable editorTimelineShowTicks; public Editor(EditorLoader loader = null) { @@ -305,6 +307,8 @@ namespace osu.Game.Screens.Edit editorHitMarkers = config.GetBindable(OsuSetting.EditorShowHitMarkers); editorAutoSeekOnPlacement = config.GetBindable(OsuSetting.EditorAutoSeekOnPlacement); editorLimitedDistanceSnap = config.GetBindable(OsuSetting.EditorLimitedDistanceSnap); + editorTimelineShowTimingChanges = config.GetBindable(OsuSetting.EditorTimelineShowTimingChanges); + editorTimelineShowTicks = config.GetBindable(OsuSetting.EditorTimelineShowTicks); AddInternal(new OsuContextMenuContainer { @@ -357,7 +361,21 @@ namespace osu.Game.Screens.Edit { Items = new MenuItem[] { - new WaveformOpacityMenuItem(config.GetBindable(OsuSetting.EditorWaveformOpacity)), + new MenuItem(EditorStrings.Timeline) + { + Items = + [ + new WaveformOpacityMenuItem(config.GetBindable(OsuSetting.EditorWaveformOpacity)), + new ToggleMenuItem(EditorStrings.TimelineShowTimingChanges) + { + State = { BindTarget = editorTimelineShowTimingChanges } + }, + new ToggleMenuItem(EditorStrings.TimelineShowTicks) + { + State = { BindTarget = editorTimelineShowTicks } + }, + ] + }, new BackgroundDimMenuItem(editorBackgroundDim), new ToggleMenuItem(EditorStrings.ShowHitMarkers) { diff --git a/osu.Game/Screens/Edit/WaveformOpacityMenuItem.cs b/osu.Game/Screens/Edit/WaveformOpacityMenuItem.cs index 9dc0ea0d07..c379e56940 100644 --- a/osu.Game/Screens/Edit/WaveformOpacityMenuItem.cs +++ b/osu.Game/Screens/Edit/WaveformOpacityMenuItem.cs @@ -20,6 +20,7 @@ namespace osu.Game.Screens.Edit { Items = new[] { + createMenuItem(0f), createMenuItem(0.25f), createMenuItem(0.5f), createMenuItem(0.75f), From 03049d45bb136826c70d632825150492ba36cbc1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 17 Jun 2024 10:23:00 +0200 Subject: [PATCH 64/91] Remove stuff that looks bad after moving timeline toggle controls --- .../Edit/Compose/Components/Timeline/TimelineArea.cs | 10 ---------- osu.Game/Screens/Edit/EditorScreenWithTimeline.cs | 1 - 2 files changed, 11 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineArea.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineArea.cs index 5631adf8bd..7bc884073a 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineArea.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineArea.cs @@ -58,16 +58,6 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline AutoSizeAxes = Axes.Y, Children = new Drawable[] { - // the out-of-bounds portion of the centre marker. - new Box - { - Width = 24, - Height = EditorScreenWithTimeline.PADDING, - Depth = float.MaxValue, - Colour = colours.Red1, - Anchor = Anchor.TopCentre, - Origin = Anchor.BottomCentre, - }, new Box { RelativeSizeAxes = Axes.Both, diff --git a/osu.Game/Screens/Edit/EditorScreenWithTimeline.cs b/osu.Game/Screens/Edit/EditorScreenWithTimeline.cs index 2b97d363f1..1b37223bb3 100644 --- a/osu.Game/Screens/Edit/EditorScreenWithTimeline.cs +++ b/osu.Game/Screens/Edit/EditorScreenWithTimeline.cs @@ -62,7 +62,6 @@ namespace osu.Game.Screens.Edit Name = "Timeline content", RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, - Padding = new MarginPadding { Horizontal = PADDING, Top = PADDING }, Content = new[] { new Drawable[] From f7910f774d8f8ed934209edb0e83b4bc08aff641 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 17 Jun 2024 10:54:52 +0200 Subject: [PATCH 65/91] Remove redundant type spec --- osu.Game/Screens/Edit/Editor.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index 316772da6e..c18acf8bb8 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -359,7 +359,7 @@ namespace osu.Game.Screens.Edit }, new MenuItem(CommonStrings.MenuBarView) { - Items = new MenuItem[] + Items = new[] { new MenuItem(EditorStrings.Timeline) { From 3884bce2393c583e4b99a143a8f52269a9e159cb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 17 Jun 2024 11:09:04 +0200 Subject: [PATCH 66/91] Remove unused delegate for now To silence inspections. --- .../OnlinePlay/DailyChallenge/DailyChallengeEventFeed.cs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeEventFeed.cs b/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeEventFeed.cs index 10f4f2cf78..6ddd2f1278 100644 --- a/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeEventFeed.cs +++ b/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeEventFeed.cs @@ -1,7 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using System; using System.Collections.Generic; using System.Linq; using osu.Framework.Allocation; @@ -82,8 +81,6 @@ namespace osu.Game.Screens.OnlinePlay.DailyChallenge private partial class NewScoreEventRow : CompositeDrawable { - public Action? PresentScore { get; set; } - private readonly NewScoreEvent newScore; public NewScoreEventRow(NewScoreEvent newScore) @@ -124,7 +121,7 @@ namespace osu.Game.Screens.OnlinePlay.DailyChallenge text.AddUserLink(newScore.Score.User); text.AddText(" got "); - text.AddLink($"{newScore.Score.TotalScore:N0} points", () => PresentScore?.Invoke(newScore.Score)); + text.AddLink($"{newScore.Score.TotalScore:N0} points", () => { }); // TODO: present the score here if (newScore.NewRank != null) text.AddText($" and achieved rank #{newScore.NewRank.Value:N0}"); From 07f1994a13e5602f85ff6537e00056ed571d2f8e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 17 Jun 2024 11:47:37 +0200 Subject: [PATCH 67/91] Align beat snap control width with right toolbox --- .../Screens/Edit/Compose/Components/Timeline/TimelineArea.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineArea.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineArea.cs index 7bc884073a..9af57ba9f6 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineArea.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineArea.cs @@ -46,7 +46,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline { new Dimension(), new Dimension(GridSizeMode.Absolute, 35), - new Dimension(GridSizeMode.Absolute, HitObjectComposer.TOOLBOX_CONTRACTED_SIZE_RIGHT - padding * 2), + new Dimension(GridSizeMode.Absolute, HitObjectComposer.TOOLBOX_CONTRACTED_SIZE_RIGHT), }, Content = new[] { From 7cfe8d8df2a7397b0b46fe103274546f693beee9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 17 Jun 2024 12:11:19 +0200 Subject: [PATCH 68/91] Reduce editor opacity of several editor components when hovering over composer Addresses https://github.com/ppy/osu/discussions/24384. --- osu.Game/Rulesets/Edit/HitObjectComposer.cs | 52 ++++++++++++++++--- osu.Game/Screens/Edit/BottomBar.cs | 25 ++++----- .../Edit/Components/BottomBarContainer.cs | 2 +- .../Edit/Components/PlaybackControl.cs | 4 +- .../Components/Timeline/TimelineArea.cs | 26 +++++++++- osu.Game/Screens/Edit/Editor.cs | 7 ++- .../Screens/Edit/EditorScreenWithTimeline.cs | 6 --- 7 files changed, 92 insertions(+), 30 deletions(-) diff --git a/osu.Game/Rulesets/Edit/HitObjectComposer.cs b/osu.Game/Rulesets/Edit/HitObjectComposer.cs index 4d92a08bed..d0c6078c9d 100644 --- a/osu.Game/Rulesets/Edit/HitObjectComposer.cs +++ b/osu.Game/Rulesets/Edit/HitObjectComposer.cs @@ -7,6 +7,7 @@ using System; using System.Collections.Generic; using System.Collections.Specialized; using System.Linq; +using JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Extensions.EnumExtensions; @@ -78,14 +79,16 @@ namespace osu.Game.Rulesets.Edit protected InputManager InputManager { get; private set; } + private Box leftToolboxBackground; + private Box rightToolboxBackground; + private EditorRadioButtonCollection toolboxCollection; - private FillFlowContainer togglesCollection; - private FillFlowContainer sampleBankTogglesCollection; private IBindable hasTiming; private Bindable autoSeekOnPlacement; + private readonly Bindable composerFocusMode = new Bindable(); protected DrawableRuleset DrawableRuleset { get; private set; } @@ -97,11 +100,14 @@ namespace osu.Game.Rulesets.Edit protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) => dependencies = new DependencyContainer(base.CreateChildDependencies(parent)); - [BackgroundDependencyLoader] - private void load(OsuConfigManager config) + [BackgroundDependencyLoader(true)] + private void load(OsuConfigManager config, [CanBeNull] Editor editor) { autoSeekOnPlacement = config.GetBindable(OsuSetting.EditorAutoSeekOnPlacement); + if (editor != null) + composerFocusMode.BindTo(editor.ComposerFocusMode); + Config = Dependencies.Get().GetConfigFor(Ruleset); try @@ -126,7 +132,7 @@ namespace osu.Game.Rulesets.Edit InternalChildren = new[] { - PlayfieldContentContainer = new Container + PlayfieldContentContainer = new ContentContainer { Name = "Playfield content", RelativeSizeAxes = Axes.Y, @@ -146,7 +152,7 @@ namespace osu.Game.Rulesets.Edit AutoSizeAxes = Axes.X, Children = new Drawable[] { - new Box + leftToolboxBackground = new Box { Colour = colourProvider.Background5, RelativeSizeAxes = Axes.Both, @@ -191,7 +197,7 @@ namespace osu.Game.Rulesets.Edit AutoSizeAxes = Axes.X, Children = new Drawable[] { - new Box + rightToolboxBackground = new Box { Colour = colourProvider.Background5, RelativeSizeAxes = Axes.Both, @@ -260,6 +266,13 @@ namespace osu.Game.Rulesets.Edit item.Selected.Disabled = !hasTiming.NewValue; } }, true); + + composerFocusMode.BindValueChanged(_ => + { + float targetAlpha = composerFocusMode.Value ? 0.5f : 1; + leftToolboxBackground.FadeTo(targetAlpha, 400, Easing.OutQuint); + rightToolboxBackground.FadeTo(targetAlpha, 400, Easing.OutQuint); + }, true); } protected override void Update() @@ -507,6 +520,31 @@ namespace osu.Game.Rulesets.Edit } #endregion + + private partial class ContentContainer : Container + { + public override bool HandlePositionalInput => true; + + private readonly Bindable composerFocusMode = new Bindable(); + + [BackgroundDependencyLoader(true)] + private void load([CanBeNull] Editor editor) + { + if (editor != null) + composerFocusMode.BindTo(editor.ComposerFocusMode); + } + + protected override bool OnHover(HoverEvent e) + { + composerFocusMode.Value = true; + return false; + } + + protected override void OnHoverLost(HoverLostEvent e) + { + composerFocusMode.Value = false; + } + } } /// diff --git a/osu.Game/Screens/Edit/BottomBar.cs b/osu.Game/Screens/Edit/BottomBar.cs index d43e675296..6118adc0d7 100644 --- a/osu.Game/Screens/Edit/BottomBar.cs +++ b/osu.Game/Screens/Edit/BottomBar.cs @@ -1,16 +1,13 @@ // 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.Bindables; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Effects; -using osu.Framework.Graphics.Shapes; -using osu.Game.Overlays; +using osu.Framework.Testing; using osu.Game.Rulesets.Edit; using osu.Game.Screens.Edit.Components; using osu.Game.Screens.Edit.Components.Timelines.Summary; @@ -21,12 +18,13 @@ namespace osu.Game.Screens.Edit { internal partial class BottomBar : CompositeDrawable { - public TestGameplayButton TestGameplayButton { get; private set; } + public TestGameplayButton TestGameplayButton { get; private set; } = null!; - private IBindable saveInProgress; + private IBindable saveInProgress = null!; + private Bindable composerFocusMode = null!; [BackgroundDependencyLoader] - private void load(OverlayColourProvider colourProvider, Editor editor) + private void load(Editor editor) { Anchor = Anchor.BottomLeft; Origin = Anchor.BottomLeft; @@ -45,11 +43,6 @@ namespace osu.Game.Screens.Edit InternalChildren = new Drawable[] { - new Box - { - RelativeSizeAxes = Axes.Both, - Colour = colourProvider.Background4, - }, new GridContainer { RelativeSizeAxes = Axes.Both, @@ -79,6 +72,7 @@ namespace osu.Game.Screens.Edit }; saveInProgress = editor.MutationTracker.InProgress.GetBoundCopy(); + composerFocusMode = editor.ComposerFocusMode.GetBoundCopy(); } protected override void LoadComplete() @@ -86,6 +80,13 @@ namespace osu.Game.Screens.Edit base.LoadComplete(); saveInProgress.BindValueChanged(_ => TestGameplayButton.Enabled.Value = !saveInProgress.Value, true); + composerFocusMode.BindValueChanged(_ => + { + float targetAlpha = composerFocusMode.Value ? 0.5f : 1; + + foreach (var c in this.ChildrenOfType()) + c.Background.FadeTo(targetAlpha, 400, Easing.OutQuint); + }, true); } } } diff --git a/osu.Game/Screens/Edit/Components/BottomBarContainer.cs b/osu.Game/Screens/Edit/Components/BottomBarContainer.cs index 0ba1ab9258..da71457004 100644 --- a/osu.Game/Screens/Edit/Components/BottomBarContainer.cs +++ b/osu.Game/Screens/Edit/Components/BottomBarContainer.cs @@ -20,7 +20,7 @@ namespace osu.Game.Screens.Edit.Components protected readonly IBindable Track = new Bindable(); - protected readonly Drawable Background; + public readonly Drawable Background; private readonly Container content; protected override Container Content => content; diff --git a/osu.Game/Screens/Edit/Components/PlaybackControl.cs b/osu.Game/Screens/Edit/Components/PlaybackControl.cs index a5ed0d680f..9e27f0e57d 100644 --- a/osu.Game/Screens/Edit/Components/PlaybackControl.cs +++ b/osu.Game/Screens/Edit/Components/PlaybackControl.cs @@ -32,8 +32,10 @@ namespace osu.Game.Screens.Edit.Components private readonly BindableNumber freqAdjust = new BindableDouble(1); [BackgroundDependencyLoader] - private void load() + private void load(OverlayColourProvider colourProvider) { + Background.Colour = colourProvider.Background4; + Children = new Drawable[] { playButton = new IconButton diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineArea.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineArea.cs index 9af57ba9f6..cee7212a5d 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineArea.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineArea.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; +using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; @@ -19,6 +20,9 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline private readonly Drawable userContent; + private Box timelineBackground = null!; + private readonly Bindable composerFocusMode = new Bindable(); + public TimelineArea(Drawable? content = null) { RelativeSizeAxes = Axes.X; @@ -28,12 +32,20 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline } [BackgroundDependencyLoader] - private void load(OverlayColourProvider colourProvider, OsuColour colours) + private void load(OverlayColourProvider colourProvider, OsuColour colours, Editor? editor) { const float padding = 10; InternalChildren = new Drawable[] { + new Box + { + Anchor = Anchor.TopRight, + Origin = Anchor.TopRight, + Width = 35 + HitObjectComposer.TOOLBOX_CONTRACTED_SIZE_RIGHT, + RelativeSizeAxes = Axes.Y, + Colour = colourProvider.Background4 + }, new GridContainer { RelativeSizeAxes = Axes.X, @@ -58,7 +70,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline AutoSizeAxes = Axes.Y, Children = new Drawable[] { - new Box + timelineBackground = new Box { RelativeSizeAxes = Axes.Both, Depth = float.MaxValue, @@ -111,6 +123,16 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline }, } }; + + if (editor != null) + composerFocusMode.BindTo(editor.ComposerFocusMode); + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + composerFocusMode.BindValueChanged(_ => timelineBackground.FadeTo(composerFocusMode.Value ? 0.5f : 1, 400, Easing.OutQuint), true); } } } diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index c18acf8bb8..02dcad46f7 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -214,6 +214,12 @@ namespace osu.Game.Screens.Edit private Bindable editorTimelineShowTimingChanges; private Bindable editorTimelineShowTicks; + /// + /// This controls the opacity of components like the timelines, sidebars, etc. + /// In "composer focus" mode the opacity of the aforementioned components is reduced so that the user can focus on the composer better. + /// + public Bindable ComposerFocusMode { get; } = new Bindable(); + public Editor(EditorLoader loader = null) { this.loader = loader; @@ -323,7 +329,6 @@ namespace osu.Game.Screens.Edit Child = screenContainer = new Container { RelativeSizeAxes = Axes.Both, - Masking = true } }, new Container diff --git a/osu.Game/Screens/Edit/EditorScreenWithTimeline.cs b/osu.Game/Screens/Edit/EditorScreenWithTimeline.cs index 1b37223bb3..cdc8a26c35 100644 --- a/osu.Game/Screens/Edit/EditorScreenWithTimeline.cs +++ b/osu.Game/Screens/Edit/EditorScreenWithTimeline.cs @@ -4,7 +4,6 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Shapes; using osu.Game.Graphics.UserInterface; using osu.Game.Overlays; using osu.Game.Screens.Edit.Compose.Components.Timeline; @@ -52,11 +51,6 @@ namespace osu.Game.Screens.Edit AutoSizeAxes = Axes.Y, Children = new Drawable[] { - new Box - { - RelativeSizeAxes = Axes.Both, - Colour = colourProvider.Background4 - }, new GridContainer { Name = "Timeline content", From 05596a391d481524582772f64d5a299efbdbecbb Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 17 Jun 2024 19:11:08 +0800 Subject: [PATCH 69/91] Disable collection expression inspections completely --- osu.sln.DotSettings | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.sln.DotSettings b/osu.sln.DotSettings index 04633a9348..25bbc4beb5 100644 --- a/osu.sln.DotSettings +++ b/osu.sln.DotSettings @@ -254,7 +254,7 @@ HINT DO_NOT_SHOW WARNING - HINT + DO_NOT_SHOW WARNING WARNING WARNING From d3d325c46c1c688ce2999cfa2d9480be3df45fbb Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 17 Jun 2024 19:16:23 +0800 Subject: [PATCH 70/91] Record on single line --- .../OnlinePlay/DailyChallenge/DailyChallengeEventFeed.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeEventFeed.cs b/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeEventFeed.cs index 6ddd2f1278..b415b15b65 100644 --- a/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeEventFeed.cs +++ b/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeEventFeed.cs @@ -70,9 +70,7 @@ namespace osu.Game.Screens.OnlinePlay.DailyChallenge } } - public record NewScoreEvent( - IScoreInfo Score, - int? NewRank); + public record NewScoreEvent(IScoreInfo Score, int? NewRank); private partial class DailyChallengeEventFeedFlow : FillFlowContainer { From da4160439e0113d2e5e4df6eed2d30f8f73ef794 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 18 Jun 2024 07:26:20 +0200 Subject: [PATCH 71/91] Update framework --- osu.Android.props | 2 +- osu.Game/osu.Game.csproj | 2 +- osu.iOS.props | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Android.props b/osu.Android.props index 3a20dd2fdb..3c115d1371 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -10,7 +10,7 @@ true - + diff --git a/osu.iOS.props b/osu.iOS.props index 2f64fcefa5..449e4b0032 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -23,6 +23,6 @@ iossimulator-x64 - + From 7f080080597094e9c8a49619d677369174381b24 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 18 Jun 2024 07:27:54 +0200 Subject: [PATCH 72/91] Adjust `AudioFilter` to framework-side changes Co-authored-by: Dan Balasescu --- osu.Game/Audio/Effects/AudioFilter.cs | 44 ++++++--------------------- 1 file changed, 10 insertions(+), 34 deletions(-) diff --git a/osu.Game/Audio/Effects/AudioFilter.cs b/osu.Game/Audio/Effects/AudioFilter.cs index bfa9b31242..8db457ae67 100644 --- a/osu.Game/Audio/Effects/AudioFilter.cs +++ b/osu.Game/Audio/Effects/AudioFilter.cs @@ -1,10 +1,8 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using System.Diagnostics; using ManagedBass.Fx; using osu.Framework.Audio.Mixing; -using osu.Framework.Caching; using osu.Framework.Graphics; namespace osu.Game.Audio.Effects @@ -26,8 +24,6 @@ namespace osu.Game.Audio.Effects private readonly BQFParameters filter; private readonly BQFType type; - private readonly Cached filterApplication = new Cached(); - private int cutoff; /// @@ -42,7 +38,7 @@ namespace osu.Game.Audio.Effects return; cutoff = value; - filterApplication.Invalidate(); + updateFilter(); } } @@ -64,18 +60,9 @@ namespace osu.Game.Audio.Effects fQ = 0.7f }; - Cutoff = getInitialCutoff(type); - } + cutoff = getInitialCutoff(type); - protected override void Update() - { - base.Update(); - - if (!filterApplication.IsValid) - { - updateFilter(cutoff); - filterApplication.Validate(); - } + updateFilter(); } private int getInitialCutoff(BQFType type) @@ -93,13 +80,13 @@ namespace osu.Game.Audio.Effects } } - private void updateFilter(int newValue) + private void updateFilter() { switch (type) { case BQFType.LowPass: // Workaround for weird behaviour when rapidly setting fCenter of a low-pass filter to nyquist - 1hz. - if (newValue >= MAX_LOWPASS_CUTOFF) + if (Cutoff >= MAX_LOWPASS_CUTOFF) { ensureDetached(); return; @@ -109,7 +96,7 @@ namespace osu.Game.Audio.Effects // Workaround for weird behaviour when rapidly setting fCenter of a high-pass filter to 1hz. case BQFType.HighPass: - if (newValue <= 1) + if (Cutoff <= 1) { ensureDetached(); return; @@ -120,17 +107,8 @@ namespace osu.Game.Audio.Effects ensureAttached(); - int filterIndex = mixer.Effects.IndexOf(filter); - - if (filterIndex < 0) return; - - if (mixer.Effects[filterIndex] is BQFParameters existingFilter) - { - existingFilter.fCenter = newValue; - - // required to update effect with new parameters. - mixer.Effects[filterIndex] = existingFilter; - } + filter.fCenter = Cutoff; + mixer.UpdateEffect(filter); } private void ensureAttached() @@ -138,8 +116,7 @@ namespace osu.Game.Audio.Effects if (IsAttached) return; - Debug.Assert(!mixer.Effects.Contains(filter)); - mixer.Effects.Add(filter); + mixer.AddEffect(filter); IsAttached = true; } @@ -148,8 +125,7 @@ namespace osu.Game.Audio.Effects if (!IsAttached) return; - Debug.Assert(mixer.Effects.Contains(filter)); - mixer.Effects.Remove(filter); + mixer.RemoveEffect(filter); IsAttached = false; } From 8a4ae5d23d902c52d5a1e0075849cc295e7e565a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 27 May 2024 11:02:51 +0200 Subject: [PATCH 73/91] Null-propagate all calls to `GetContainingFocusManager()` --- .../Screens/Ladder/Components/LadderEditorSettings.cs | 2 +- osu.Game/Graphics/UserInterface/FocusedTextBox.cs | 2 +- osu.Game/Graphics/UserInterfaceV2/LabelledTextBox.cs | 2 +- osu.Game/Graphics/UserInterfaceV2/SliderWithTextBoxInput.cs | 2 +- osu.Game/Overlays/AccountCreation/ScreenEntry.cs | 2 +- osu.Game/Overlays/Comments/ReplyCommentEditor.cs | 2 +- osu.Game/Overlays/Login/LoginForm.cs | 2 +- osu.Game/Overlays/Login/LoginPanel.cs | 2 +- osu.Game/Overlays/Login/SecondFactorAuthForm.cs | 2 +- osu.Game/Overlays/LoginOverlay.cs | 2 +- osu.Game/Overlays/Mods/AddPresetPopover.cs | 2 +- osu.Game/Overlays/Mods/EditPresetPopover.cs | 2 +- osu.Game/Overlays/Mods/ModSelectOverlay.cs | 2 +- osu.Game/Overlays/Settings/Sections/Input/KeyBindingRow.cs | 2 +- .../Settings/Sections/Input/KeyBindingsSubsection.cs | 2 +- osu.Game/Overlays/SettingsPanel.cs | 2 +- .../Screens/Edit/Compose/Components/BeatDivisorControl.cs | 2 +- .../Compose/Components/Timeline/DifficultyPointPiece.cs | 2 +- osu.Game/Screens/Edit/Setup/LabelledTextBoxWithPopover.cs | 2 +- osu.Game/Screens/Edit/Setup/MetadataSection.cs | 2 +- .../Edit/Timing/IndeterminateSliderWithTextBoxInput.cs | 2 +- osu.Game/Screens/OnlinePlay/Lounge/DrawableLoungeRoom.cs | 6 +++--- osu.Game/Screens/Select/FilterControl.cs | 2 +- osu.Game/Screens/SelectV2/Footer/BeatmapOptionsPopover.cs | 2 +- 24 files changed, 26 insertions(+), 26 deletions(-) diff --git a/osu.Game.Tournament/Screens/Ladder/Components/LadderEditorSettings.cs b/osu.Game.Tournament/Screens/Ladder/Components/LadderEditorSettings.cs index 08ed815253..775fd4fdf2 100644 --- a/osu.Game.Tournament/Screens/Ladder/Components/LadderEditorSettings.cs +++ b/osu.Game.Tournament/Screens/Ladder/Components/LadderEditorSettings.cs @@ -58,7 +58,7 @@ namespace osu.Game.Tournament.Screens.Ladder.Components editorInfo.Selected.ValueChanged += selection => { // ensure any ongoing edits are committed out to the *current* selection before changing to a new one. - GetContainingFocusManager().TriggerFocusContention(null); + GetContainingFocusManager()?.TriggerFocusContention(null); // Required to avoid cyclic failure in BindableWithCurrent (TriggerChange called during the Current_Set process). // Arguable a framework issue but since we haven't hit it anywhere else a local workaround seems best. diff --git a/osu.Game/Graphics/UserInterface/FocusedTextBox.cs b/osu.Game/Graphics/UserInterface/FocusedTextBox.cs index 4ec93995a4..928865ffb0 100644 --- a/osu.Game/Graphics/UserInterface/FocusedTextBox.cs +++ b/osu.Game/Graphics/UserInterface/FocusedTextBox.cs @@ -31,7 +31,7 @@ namespace osu.Game.Graphics.UserInterface if (!allowImmediateFocus) return; - Scheduler.Add(() => GetContainingFocusManager().ChangeFocus(this)); + Scheduler.Add(() => GetContainingFocusManager()?.ChangeFocus(this)); } public new void KillFocus() => base.KillFocus(); diff --git a/osu.Game/Graphics/UserInterfaceV2/LabelledTextBox.cs b/osu.Game/Graphics/UserInterfaceV2/LabelledTextBox.cs index 8dfe729ce7..42a72744d6 100644 --- a/osu.Game/Graphics/UserInterfaceV2/LabelledTextBox.cs +++ b/osu.Game/Graphics/UserInterfaceV2/LabelledTextBox.cs @@ -59,7 +59,7 @@ namespace osu.Game.Graphics.UserInterfaceV2 protected override void OnFocus(FocusEvent e) { base.OnFocus(e); - GetContainingFocusManager().ChangeFocus(Component); + GetContainingFocusManager()?.ChangeFocus(Component); } protected override OsuTextBox CreateComponent() => CreateTextBox().With(t => diff --git a/osu.Game/Graphics/UserInterfaceV2/SliderWithTextBoxInput.cs b/osu.Game/Graphics/UserInterfaceV2/SliderWithTextBoxInput.cs index f1f4fe3b46..50d8d763e1 100644 --- a/osu.Game/Graphics/UserInterfaceV2/SliderWithTextBoxInput.cs +++ b/osu.Game/Graphics/UserInterfaceV2/SliderWithTextBoxInput.cs @@ -85,7 +85,7 @@ namespace osu.Game.Graphics.UserInterfaceV2 Current.BindValueChanged(updateTextBoxFromSlider, true); } - public bool TakeFocus() => GetContainingFocusManager().ChangeFocus(textBox); + public bool TakeFocus() => GetContainingFocusManager()?.ChangeFocus(textBox) == true; public bool SelectAll() => textBox.SelectAll(); diff --git a/osu.Game/Overlays/AccountCreation/ScreenEntry.cs b/osu.Game/Overlays/AccountCreation/ScreenEntry.cs index 53e51e0611..e34e5c9521 100644 --- a/osu.Game/Overlays/AccountCreation/ScreenEntry.cs +++ b/osu.Game/Overlays/AccountCreation/ScreenEntry.cs @@ -243,7 +243,7 @@ namespace osu.Game.Overlays.AccountCreation if (nextTextBox != null) { - Schedule(() => GetContainingFocusManager().ChangeFocus(nextTextBox)); + Schedule(() => GetContainingFocusManager()?.ChangeFocus(nextTextBox)); return true; } diff --git a/osu.Game/Overlays/Comments/ReplyCommentEditor.cs b/osu.Game/Overlays/Comments/ReplyCommentEditor.cs index caf19829ee..0b4daea0c1 100644 --- a/osu.Game/Overlays/Comments/ReplyCommentEditor.cs +++ b/osu.Game/Overlays/Comments/ReplyCommentEditor.cs @@ -39,7 +39,7 @@ namespace osu.Game.Overlays.Comments base.LoadComplete(); if (!TextBox.ReadOnly) - GetContainingFocusManager().ChangeFocus(TextBox); + GetContainingFocusManager()?.ChangeFocus(TextBox); } protected override void OnCommit(string text) diff --git a/osu.Game/Overlays/Login/LoginForm.cs b/osu.Game/Overlays/Login/LoginForm.cs index 418721f371..cde97d45c1 100644 --- a/osu.Game/Overlays/Login/LoginForm.cs +++ b/osu.Game/Overlays/Login/LoginForm.cs @@ -150,7 +150,7 @@ namespace osu.Game.Overlays.Login protected override void OnFocus(FocusEvent e) { - Schedule(() => { GetContainingFocusManager().ChangeFocus(string.IsNullOrEmpty(username.Text) ? username : password); }); + Schedule(() => { GetContainingFocusManager()?.ChangeFocus(string.IsNullOrEmpty(username.Text) ? username : password); }); } } } diff --git a/osu.Game/Overlays/Login/LoginPanel.cs b/osu.Game/Overlays/Login/LoginPanel.cs index 845d20ccaf..9afaed335d 100644 --- a/osu.Game/Overlays/Login/LoginPanel.cs +++ b/osu.Game/Overlays/Login/LoginPanel.cs @@ -216,7 +216,7 @@ namespace osu.Game.Overlays.Login protected override void OnFocus(FocusEvent e) { - if (form != null) GetContainingFocusManager().ChangeFocus(form); + if (form != null) GetContainingFocusManager()?.ChangeFocus(form); base.OnFocus(e); } } diff --git a/osu.Game/Overlays/Login/SecondFactorAuthForm.cs b/osu.Game/Overlays/Login/SecondFactorAuthForm.cs index 82e328c036..c8e8b316fa 100644 --- a/osu.Game/Overlays/Login/SecondFactorAuthForm.cs +++ b/osu.Game/Overlays/Login/SecondFactorAuthForm.cs @@ -141,7 +141,7 @@ namespace osu.Game.Overlays.Login protected override void OnFocus(FocusEvent e) { - Schedule(() => { GetContainingFocusManager().ChangeFocus(codeTextBox); }); + Schedule(() => { GetContainingFocusManager()?.ChangeFocus(codeTextBox); }); } } } diff --git a/osu.Game/Overlays/LoginOverlay.cs b/osu.Game/Overlays/LoginOverlay.cs index 8dc454c0a0..576c66ff23 100644 --- a/osu.Game/Overlays/LoginOverlay.cs +++ b/osu.Game/Overlays/LoginOverlay.cs @@ -78,7 +78,7 @@ namespace osu.Game.Overlays this.FadeIn(transition_time, Easing.OutQuint); FadeEdgeEffectTo(WaveContainer.SHADOW_OPACITY, WaveContainer.APPEAR_DURATION, Easing.Out); - ScheduleAfterChildren(() => GetContainingFocusManager().ChangeFocus(panel)); + ScheduleAfterChildren(() => GetContainingFocusManager()?.ChangeFocus(panel)); } protected override void PopOut() diff --git a/osu.Game/Overlays/Mods/AddPresetPopover.cs b/osu.Game/Overlays/Mods/AddPresetPopover.cs index 50aa5a2eb4..e59b60a1f1 100644 --- a/osu.Game/Overlays/Mods/AddPresetPopover.cs +++ b/osu.Game/Overlays/Mods/AddPresetPopover.cs @@ -89,7 +89,7 @@ namespace osu.Game.Overlays.Mods { base.LoadComplete(); - ScheduleAfterChildren(() => GetContainingFocusManager().ChangeFocus(nameTextBox)); + ScheduleAfterChildren(() => GetContainingFocusManager()?.ChangeFocus(nameTextBox)); nameTextBox.Current.BindValueChanged(s => { diff --git a/osu.Game/Overlays/Mods/EditPresetPopover.cs b/osu.Game/Overlays/Mods/EditPresetPopover.cs index 8fa6b35162..88119f57b3 100644 --- a/osu.Game/Overlays/Mods/EditPresetPopover.cs +++ b/osu.Game/Overlays/Mods/EditPresetPopover.cs @@ -136,7 +136,7 @@ namespace osu.Game.Overlays.Mods { base.LoadComplete(); - ScheduleAfterChildren(() => GetContainingFocusManager().ChangeFocus(nameTextBox)); + ScheduleAfterChildren(() => GetContainingFocusManager()?.ChangeFocus(nameTextBox)); } public override bool OnPressed(KeyBindingPressEvent e) diff --git a/osu.Game/Overlays/Mods/ModSelectOverlay.cs b/osu.Game/Overlays/Mods/ModSelectOverlay.cs index 0dccc88ea0..13970e718a 100644 --- a/osu.Game/Overlays/Mods/ModSelectOverlay.cs +++ b/osu.Game/Overlays/Mods/ModSelectOverlay.cs @@ -949,7 +949,7 @@ namespace osu.Game.Overlays.Mods RequestScroll?.Invoke(this); // Killing focus is done here because it's the only feasible place on ModSelectOverlay you can click on without triggering any action. - Scheduler.Add(() => GetContainingFocusManager().ChangeFocus(null)); + Scheduler.Add(() => GetContainingFocusManager()?.ChangeFocus(null)); return true; } diff --git a/osu.Game/Overlays/Settings/Sections/Input/KeyBindingRow.cs b/osu.Game/Overlays/Settings/Sections/Input/KeyBindingRow.cs index 3f6eeca10e..36339c484e 100644 --- a/osu.Game/Overlays/Settings/Sections/Input/KeyBindingRow.cs +++ b/osu.Game/Overlays/Settings/Sections/Input/KeyBindingRow.cs @@ -465,7 +465,7 @@ namespace osu.Game.Overlays.Settings.Sections.Input } if (HasFocus) - GetContainingFocusManager().ChangeFocus(null); + GetContainingFocusManager()?.ChangeFocus(null); cancelAndClearButtons.FadeOut(300, Easing.OutQuint); cancelAndClearButtons.BypassAutoSizeAxes |= Axes.Y; diff --git a/osu.Game/Overlays/Settings/Sections/Input/KeyBindingsSubsection.cs b/osu.Game/Overlays/Settings/Sections/Input/KeyBindingsSubsection.cs index db3b56b9f0..cde9f10549 100644 --- a/osu.Game/Overlays/Settings/Sections/Input/KeyBindingsSubsection.cs +++ b/osu.Game/Overlays/Settings/Sections/Input/KeyBindingsSubsection.cs @@ -106,7 +106,7 @@ namespace osu.Game.Overlays.Settings.Sections.Input { var next = Children.SkipWhile(c => c != sender).Skip(1).FirstOrDefault(); if (next != null) - GetContainingFocusManager().ChangeFocus(next); + GetContainingFocusManager()?.ChangeFocus(next); } } } diff --git a/osu.Game/Overlays/SettingsPanel.cs b/osu.Game/Overlays/SettingsPanel.cs index d5c642d24f..a5a56d1e8b 100644 --- a/osu.Game/Overlays/SettingsPanel.cs +++ b/osu.Game/Overlays/SettingsPanel.cs @@ -201,7 +201,7 @@ namespace osu.Game.Overlays searchTextBox.HoldFocus = false; if (searchTextBox.HasFocus) - GetContainingFocusManager().ChangeFocus(null); + GetContainingFocusManager()?.ChangeFocus(null); } public override bool AcceptsFocus => true; diff --git a/osu.Game/Screens/Edit/Compose/Components/BeatDivisorControl.cs b/osu.Game/Screens/Edit/Compose/Components/BeatDivisorControl.cs index 005b96bfef..1751d01fb7 100644 --- a/osu.Game/Screens/Edit/Compose/Components/BeatDivisorControl.cs +++ b/osu.Game/Screens/Edit/Compose/Components/BeatDivisorControl.cs @@ -580,7 +580,7 @@ namespace osu.Game.Screens.Edit.Compose.Components { base.LoadComplete(); - GetContainingFocusManager().ChangeFocus(this); + GetContainingFocusManager()?.ChangeFocus(this); SelectAll(); } } diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/DifficultyPointPiece.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/DifficultyPointPiece.cs index d9084a7477..2c9da0446d 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/DifficultyPointPiece.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/DifficultyPointPiece.cs @@ -138,7 +138,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline protected override void LoadComplete() { base.LoadComplete(); - ScheduleAfterChildren(() => GetContainingFocusManager().ChangeFocus(sliderVelocitySlider)); + ScheduleAfterChildren(() => GetContainingFocusManager()?.ChangeFocus(sliderVelocitySlider)); } } } diff --git a/osu.Game/Screens/Edit/Setup/LabelledTextBoxWithPopover.cs b/osu.Game/Screens/Edit/Setup/LabelledTextBoxWithPopover.cs index 5abf40dda7..00cc07413f 100644 --- a/osu.Game/Screens/Edit/Setup/LabelledTextBoxWithPopover.cs +++ b/osu.Game/Screens/Edit/Setup/LabelledTextBoxWithPopover.cs @@ -45,7 +45,7 @@ namespace osu.Game.Screens.Edit.Setup OnFocused?.Invoke(); base.OnFocus(e); - GetContainingFocusManager().TriggerFocusContention(this); + GetContainingFocusManager()?.TriggerFocusContention(this); } } } diff --git a/osu.Game/Screens/Edit/Setup/MetadataSection.cs b/osu.Game/Screens/Edit/Setup/MetadataSection.cs index b575472a18..dd880891ba 100644 --- a/osu.Game/Screens/Edit/Setup/MetadataSection.cs +++ b/osu.Game/Screens/Edit/Setup/MetadataSection.cs @@ -70,7 +70,7 @@ namespace osu.Game.Screens.Edit.Setup base.LoadComplete(); if (string.IsNullOrEmpty(ArtistTextBox.Current.Value)) - ScheduleAfterChildren(() => GetContainingFocusManager().ChangeFocus(ArtistTextBox)); + ScheduleAfterChildren(() => GetContainingFocusManager()?.ChangeFocus(ArtistTextBox)); ArtistTextBox.Current.BindValueChanged(artist => transferIfRomanised(artist.NewValue, RomanisedArtistTextBox)); TitleTextBox.Current.BindValueChanged(title => transferIfRomanised(title.NewValue, RomanisedTitleTextBox)); diff --git a/osu.Game/Screens/Edit/Timing/IndeterminateSliderWithTextBoxInput.cs b/osu.Game/Screens/Edit/Timing/IndeterminateSliderWithTextBoxInput.cs index 4f7a1bf589..187aa3e897 100644 --- a/osu.Game/Screens/Edit/Timing/IndeterminateSliderWithTextBoxInput.cs +++ b/osu.Game/Screens/Edit/Timing/IndeterminateSliderWithTextBoxInput.cs @@ -126,7 +126,7 @@ namespace osu.Game.Screens.Edit.Timing protected override void OnFocus(FocusEvent e) { base.OnFocus(e); - GetContainingFocusManager().ChangeFocus(textBox); + GetContainingFocusManager()?.ChangeFocus(textBox); } private void updateState() diff --git a/osu.Game/Screens/OnlinePlay/Lounge/DrawableLoungeRoom.cs b/osu.Game/Screens/OnlinePlay/Lounge/DrawableLoungeRoom.cs index 2f6a220c82..6f06b8686c 100644 --- a/osu.Game/Screens/OnlinePlay/Lounge/DrawableLoungeRoom.cs +++ b/osu.Game/Screens/OnlinePlay/Lounge/DrawableLoungeRoom.cs @@ -248,21 +248,21 @@ namespace osu.Game.Screens.OnlinePlay.Lounge { base.LoadComplete(); - ScheduleAfterChildren(() => GetContainingFocusManager().ChangeFocus(passwordTextBox)); + ScheduleAfterChildren(() => GetContainingFocusManager()?.ChangeFocus(passwordTextBox)); passwordTextBox.OnCommit += (_, _) => performJoin(); } private void performJoin() { lounge?.Join(room, passwordTextBox.Text, null, joinFailed); - GetContainingFocusManager().TriggerFocusContention(passwordTextBox); + GetContainingFocusManager()?.TriggerFocusContention(passwordTextBox); } private void joinFailed(string error) => Schedule(() => { passwordTextBox.Text = string.Empty; - GetContainingFocusManager().ChangeFocus(passwordTextBox); + GetContainingFocusManager()?.ChangeFocus(passwordTextBox); errorText.Text = error; errorText diff --git a/osu.Game/Screens/Select/FilterControl.cs b/osu.Game/Screens/Select/FilterControl.cs index 30eb4a8491..497c1d4c9f 100644 --- a/osu.Game/Screens/Select/FilterControl.cs +++ b/osu.Game/Screens/Select/FilterControl.cs @@ -245,7 +245,7 @@ namespace osu.Game.Screens.Select searchTextBox.ReadOnly = true; searchTextBox.HoldFocus = false; if (searchTextBox.HasFocus) - GetContainingFocusManager().ChangeFocus(searchTextBox); + GetContainingFocusManager()?.ChangeFocus(searchTextBox); } public void Activate() diff --git a/osu.Game/Screens/SelectV2/Footer/BeatmapOptionsPopover.cs b/osu.Game/Screens/SelectV2/Footer/BeatmapOptionsPopover.cs index 4b5c0ee798..e8b8b49785 100644 --- a/osu.Game/Screens/SelectV2/Footer/BeatmapOptionsPopover.cs +++ b/osu.Game/Screens/SelectV2/Footer/BeatmapOptionsPopover.cs @@ -81,7 +81,7 @@ namespace osu.Game.Screens.SelectV2.Footer { base.LoadComplete(); - ScheduleAfterChildren(() => GetContainingFocusManager().ChangeFocus(this)); + ScheduleAfterChildren(() => GetContainingFocusManager()?.ChangeFocus(this)); beatmap.BindValueChanged(_ => Hide()); } From 659505f7115a8ba84ad88e0fead266cba8ef8021 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 27 May 2024 11:23:32 +0200 Subject: [PATCH 74/91] Adjust calls to `GetContainingInputManager()` --- .../Edit/Blueprints/JuiceStreamPlacementBlueprint.cs | 2 +- osu.Game.Rulesets.Catch/UI/CatcherArea.cs | 2 +- .../TestSceneDrawableManiaHitObject.cs | 2 +- osu.Game.Rulesets.Osu.Tests/TestSceneGameplayCursor.cs | 4 ++-- osu.Game.Rulesets.Osu.Tests/TestSceneHitCircleArea.cs | 2 +- osu.Game.Tests/Visual/Editing/TestScenePositionSnapGrid.cs | 2 +- osu.Game/Graphics/Cursor/GlobalCursorDisplay.cs | 2 +- osu.Game/Graphics/UserInterface/FocusedTextBox.cs | 2 +- osu.Game/Graphics/UserInterfaceV2/LabelledTextBox.cs | 2 +- osu.Game/Overlays/AccountCreation/ScreenEntry.cs | 2 +- osu.Game/Overlays/Comments/ReplyCommentEditor.cs | 2 +- osu.Game/Overlays/Login/LoginForm.cs | 2 +- osu.Game/Overlays/Login/LoginPanel.cs | 2 +- osu.Game/Overlays/Login/SecondFactorAuthForm.cs | 2 +- osu.Game/Overlays/LoginOverlay.cs | 2 +- osu.Game/Overlays/Mods/AddPresetPopover.cs | 2 +- osu.Game/Overlays/Mods/EditPresetPopover.cs | 2 +- osu.Game/Overlays/Mods/ModSelectOverlay.cs | 2 +- osu.Game/Overlays/Settings/Sections/Input/KeyBindingRow.cs | 2 +- osu.Game/Overlays/SettingsPanel.cs | 2 +- osu.Game/Overlays/SkinEditor/SkinEditor.cs | 2 +- .../Screens/Edit/Compose/Components/BeatDivisorControl.cs | 2 +- .../Edit/Compose/Components/Timeline/DifficultyPointPiece.cs | 2 +- osu.Game/Screens/Edit/Setup/LabelledTextBoxWithPopover.cs | 2 +- osu.Game/Screens/Edit/Setup/MetadataSection.cs | 2 +- .../Edit/Timing/IndeterminateSliderWithTextBoxInput.cs | 2 +- osu.Game/Screens/Edit/Verify/IssueTable.cs | 2 +- osu.Game/Screens/OnlinePlay/Lounge/DrawableLoungeRoom.cs | 4 ++-- osu.Game/Screens/Play/PlayerLoader.cs | 2 +- osu.Game/Screens/Select/BeatmapCarousel.cs | 2 +- osu.Game/Screens/Select/FilterControl.cs | 2 +- osu.Game/Screens/Select/PlaySongSelect.cs | 2 +- osu.Game/Screens/SelectV2/Footer/BeatmapOptionsPopover.cs | 2 +- .../Utility/SampleComponents/LatencySampleComponent.cs | 2 +- 34 files changed, 36 insertions(+), 36 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Edit/Blueprints/JuiceStreamPlacementBlueprint.cs b/osu.Game.Rulesets.Catch/Edit/Blueprints/JuiceStreamPlacementBlueprint.cs index c8c8db1ebd..7b57dac36e 100644 --- a/osu.Game.Rulesets.Catch/Edit/Blueprints/JuiceStreamPlacementBlueprint.cs +++ b/osu.Game.Rulesets.Catch/Edit/Blueprints/JuiceStreamPlacementBlueprint.cs @@ -49,7 +49,7 @@ namespace osu.Game.Rulesets.Catch.Edit.Blueprints { base.LoadComplete(); - inputManager = GetContainingInputManager(); + inputManager = GetContainingInputManager()!; BeginPlacement(); } diff --git a/osu.Game.Rulesets.Catch/UI/CatcherArea.cs b/osu.Game.Rulesets.Catch/UI/CatcherArea.cs index 567c288b47..21faec56de 100644 --- a/osu.Game.Rulesets.Catch/UI/CatcherArea.cs +++ b/osu.Game.Rulesets.Catch/UI/CatcherArea.cs @@ -84,7 +84,7 @@ namespace osu.Game.Rulesets.Catch.UI { base.Update(); - var replayState = (GetContainingInputManager().CurrentState as RulesetInputManagerInputState)?.LastReplayState as CatchFramedReplayInputHandler.CatchReplayState; + var replayState = (GetContainingInputManager()!.CurrentState as RulesetInputManagerInputState)?.LastReplayState as CatchFramedReplayInputHandler.CatchReplayState; SetCatcherPosition( replayState?.CatcherX ?? diff --git a/osu.Game.Rulesets.Mania.Tests/TestSceneDrawableManiaHitObject.cs b/osu.Game.Rulesets.Mania.Tests/TestSceneDrawableManiaHitObject.cs index 51c2bac6d1..7a0abb9e64 100644 --- a/osu.Game.Rulesets.Mania.Tests/TestSceneDrawableManiaHitObject.cs +++ b/osu.Game.Rulesets.Mania.Tests/TestSceneDrawableManiaHitObject.cs @@ -66,7 +66,7 @@ namespace osu.Game.Rulesets.Mania.Tests AddStep("Hold key", () => { clock.CurrentTime = 0; - note.OnPressed(new KeyBindingPressEvent(GetContainingInputManager().CurrentState, ManiaAction.Key1)); + note.OnPressed(new KeyBindingPressEvent(GetContainingInputManager()!.CurrentState, ManiaAction.Key1)); }); AddStep("progress time", () => clock.CurrentTime = 500); AddAssert("head is visible", () => note.Head.Alpha == 1); diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneGameplayCursor.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneGameplayCursor.cs index e6696032ae..98113a6513 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneGameplayCursor.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneGameplayCursor.cs @@ -161,9 +161,9 @@ namespace osu.Game.Rulesets.Osu.Tests pressed = value; if (value) - OnPressed(new KeyBindingPressEvent(GetContainingInputManager().CurrentState, OsuAction.LeftButton)); + OnPressed(new KeyBindingPressEvent(GetContainingInputManager()!.CurrentState, OsuAction.LeftButton)); else - OnReleased(new KeyBindingReleaseEvent(GetContainingInputManager().CurrentState, OsuAction.LeftButton)); + OnReleased(new KeyBindingReleaseEvent(GetContainingInputManager()!.CurrentState, OsuAction.LeftButton)); } } diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircleArea.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircleArea.cs index 71174e3295..5cac9843b8 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircleArea.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircleArea.cs @@ -100,7 +100,7 @@ namespace osu.Game.Rulesets.Osu.Tests private void scheduleHit() => AddStep("schedule action", () => { double delay = hitCircle.StartTime - hitCircle.HitWindows.WindowFor(HitResult.Great) - Time.Current; - Scheduler.AddDelayed(() => hitAreaReceptor.OnPressed(new KeyBindingPressEvent(GetContainingInputManager().CurrentState, OsuAction.LeftButton)), delay); + Scheduler.AddDelayed(() => hitAreaReceptor.OnPressed(new KeyBindingPressEvent(GetContainingInputManager()!.CurrentState, OsuAction.LeftButton)), delay); }); } } diff --git a/osu.Game.Tests/Visual/Editing/TestScenePositionSnapGrid.cs b/osu.Game.Tests/Visual/Editing/TestScenePositionSnapGrid.cs index 2721bc3602..bc2dee8534 100644 --- a/osu.Game.Tests/Visual/Editing/TestScenePositionSnapGrid.cs +++ b/osu.Game.Tests/Visual/Editing/TestScenePositionSnapGrid.cs @@ -92,7 +92,7 @@ namespace osu.Game.Tests.Visual.Editing { base.LoadComplete(); - updatePosition(GetContainingInputManager().CurrentState.Mouse.Position); + updatePosition(GetContainingInputManager()!.CurrentState.Mouse.Position); } protected override bool OnMouseMove(MouseMoveEvent e) diff --git a/osu.Game/Graphics/Cursor/GlobalCursorDisplay.cs b/osu.Game/Graphics/Cursor/GlobalCursorDisplay.cs index 85a2d68e55..d32544fc42 100644 --- a/osu.Game/Graphics/Cursor/GlobalCursorDisplay.cs +++ b/osu.Game/Graphics/Cursor/GlobalCursorDisplay.cs @@ -53,7 +53,7 @@ namespace osu.Game.Graphics.Cursor { base.LoadComplete(); - inputManager = GetContainingInputManager(); + inputManager = GetContainingInputManager()!; showDuringTouch = config.GetBindable(OsuSetting.GameplayCursorDuringTouch); } diff --git a/osu.Game/Graphics/UserInterface/FocusedTextBox.cs b/osu.Game/Graphics/UserInterface/FocusedTextBox.cs index 928865ffb0..f4ca00b7d0 100644 --- a/osu.Game/Graphics/UserInterface/FocusedTextBox.cs +++ b/osu.Game/Graphics/UserInterface/FocusedTextBox.cs @@ -31,7 +31,7 @@ namespace osu.Game.Graphics.UserInterface if (!allowImmediateFocus) return; - Scheduler.Add(() => GetContainingFocusManager()?.ChangeFocus(this)); + Scheduler.Add(() => GetContainingFocusManager()!.ChangeFocus(this)); } public new void KillFocus() => base.KillFocus(); diff --git a/osu.Game/Graphics/UserInterfaceV2/LabelledTextBox.cs b/osu.Game/Graphics/UserInterfaceV2/LabelledTextBox.cs index 42a72744d6..fabfde4333 100644 --- a/osu.Game/Graphics/UserInterfaceV2/LabelledTextBox.cs +++ b/osu.Game/Graphics/UserInterfaceV2/LabelledTextBox.cs @@ -59,7 +59,7 @@ namespace osu.Game.Graphics.UserInterfaceV2 protected override void OnFocus(FocusEvent e) { base.OnFocus(e); - GetContainingFocusManager()?.ChangeFocus(Component); + GetContainingFocusManager()!.ChangeFocus(Component); } protected override OsuTextBox CreateComponent() => CreateTextBox().With(t => diff --git a/osu.Game/Overlays/AccountCreation/ScreenEntry.cs b/osu.Game/Overlays/AccountCreation/ScreenEntry.cs index e34e5c9521..fb6a5796a1 100644 --- a/osu.Game/Overlays/AccountCreation/ScreenEntry.cs +++ b/osu.Game/Overlays/AccountCreation/ScreenEntry.cs @@ -243,7 +243,7 @@ namespace osu.Game.Overlays.AccountCreation if (nextTextBox != null) { - Schedule(() => GetContainingFocusManager()?.ChangeFocus(nextTextBox)); + Schedule(() => GetContainingFocusManager()!.ChangeFocus(nextTextBox)); return true; } diff --git a/osu.Game/Overlays/Comments/ReplyCommentEditor.cs b/osu.Game/Overlays/Comments/ReplyCommentEditor.cs index 0b4daea0c1..d5ae4f92ab 100644 --- a/osu.Game/Overlays/Comments/ReplyCommentEditor.cs +++ b/osu.Game/Overlays/Comments/ReplyCommentEditor.cs @@ -39,7 +39,7 @@ namespace osu.Game.Overlays.Comments base.LoadComplete(); if (!TextBox.ReadOnly) - GetContainingFocusManager()?.ChangeFocus(TextBox); + GetContainingFocusManager()!.ChangeFocus(TextBox); } protected override void OnCommit(string text) diff --git a/osu.Game/Overlays/Login/LoginForm.cs b/osu.Game/Overlays/Login/LoginForm.cs index cde97d45c1..13e528ff8f 100644 --- a/osu.Game/Overlays/Login/LoginForm.cs +++ b/osu.Game/Overlays/Login/LoginForm.cs @@ -150,7 +150,7 @@ namespace osu.Game.Overlays.Login protected override void OnFocus(FocusEvent e) { - Schedule(() => { GetContainingFocusManager()?.ChangeFocus(string.IsNullOrEmpty(username.Text) ? username : password); }); + Schedule(() => { GetContainingFocusManager()!.ChangeFocus(string.IsNullOrEmpty(username.Text) ? username : password); }); } } } diff --git a/osu.Game/Overlays/Login/LoginPanel.cs b/osu.Game/Overlays/Login/LoginPanel.cs index 9afaed335d..cb642f9b72 100644 --- a/osu.Game/Overlays/Login/LoginPanel.cs +++ b/osu.Game/Overlays/Login/LoginPanel.cs @@ -216,7 +216,7 @@ namespace osu.Game.Overlays.Login protected override void OnFocus(FocusEvent e) { - if (form != null) GetContainingFocusManager()?.ChangeFocus(form); + if (form != null) GetContainingFocusManager()!.ChangeFocus(form); base.OnFocus(e); } } diff --git a/osu.Game/Overlays/Login/SecondFactorAuthForm.cs b/osu.Game/Overlays/Login/SecondFactorAuthForm.cs index c8e8b316fa..77835b1f09 100644 --- a/osu.Game/Overlays/Login/SecondFactorAuthForm.cs +++ b/osu.Game/Overlays/Login/SecondFactorAuthForm.cs @@ -141,7 +141,7 @@ namespace osu.Game.Overlays.Login protected override void OnFocus(FocusEvent e) { - Schedule(() => { GetContainingFocusManager()?.ChangeFocus(codeTextBox); }); + Schedule(() => { GetContainingFocusManager()!.ChangeFocus(codeTextBox); }); } } } diff --git a/osu.Game/Overlays/LoginOverlay.cs b/osu.Game/Overlays/LoginOverlay.cs index 576c66ff23..d570983f98 100644 --- a/osu.Game/Overlays/LoginOverlay.cs +++ b/osu.Game/Overlays/LoginOverlay.cs @@ -78,7 +78,7 @@ namespace osu.Game.Overlays this.FadeIn(transition_time, Easing.OutQuint); FadeEdgeEffectTo(WaveContainer.SHADOW_OPACITY, WaveContainer.APPEAR_DURATION, Easing.Out); - ScheduleAfterChildren(() => GetContainingFocusManager()?.ChangeFocus(panel)); + ScheduleAfterChildren(() => GetContainingFocusManager()!.ChangeFocus(panel)); } protected override void PopOut() diff --git a/osu.Game/Overlays/Mods/AddPresetPopover.cs b/osu.Game/Overlays/Mods/AddPresetPopover.cs index e59b60a1f1..7df7d6339c 100644 --- a/osu.Game/Overlays/Mods/AddPresetPopover.cs +++ b/osu.Game/Overlays/Mods/AddPresetPopover.cs @@ -89,7 +89,7 @@ namespace osu.Game.Overlays.Mods { base.LoadComplete(); - ScheduleAfterChildren(() => GetContainingFocusManager()?.ChangeFocus(nameTextBox)); + ScheduleAfterChildren(() => GetContainingFocusManager()!.ChangeFocus(nameTextBox)); nameTextBox.Current.BindValueChanged(s => { diff --git a/osu.Game/Overlays/Mods/EditPresetPopover.cs b/osu.Game/Overlays/Mods/EditPresetPopover.cs index 88119f57b3..526ab6fc63 100644 --- a/osu.Game/Overlays/Mods/EditPresetPopover.cs +++ b/osu.Game/Overlays/Mods/EditPresetPopover.cs @@ -136,7 +136,7 @@ namespace osu.Game.Overlays.Mods { base.LoadComplete(); - ScheduleAfterChildren(() => GetContainingFocusManager()?.ChangeFocus(nameTextBox)); + ScheduleAfterChildren(() => GetContainingFocusManager()!.ChangeFocus(nameTextBox)); } public override bool OnPressed(KeyBindingPressEvent e) diff --git a/osu.Game/Overlays/Mods/ModSelectOverlay.cs b/osu.Game/Overlays/Mods/ModSelectOverlay.cs index 13970e718a..145f58fb55 100644 --- a/osu.Game/Overlays/Mods/ModSelectOverlay.cs +++ b/osu.Game/Overlays/Mods/ModSelectOverlay.cs @@ -949,7 +949,7 @@ namespace osu.Game.Overlays.Mods RequestScroll?.Invoke(this); // Killing focus is done here because it's the only feasible place on ModSelectOverlay you can click on without triggering any action. - Scheduler.Add(() => GetContainingFocusManager()?.ChangeFocus(null)); + Scheduler.Add(() => GetContainingFocusManager()!.ChangeFocus(null)); return true; } diff --git a/osu.Game/Overlays/Settings/Sections/Input/KeyBindingRow.cs b/osu.Game/Overlays/Settings/Sections/Input/KeyBindingRow.cs index 36339c484e..ddf831c23e 100644 --- a/osu.Game/Overlays/Settings/Sections/Input/KeyBindingRow.cs +++ b/osu.Game/Overlays/Settings/Sections/Input/KeyBindingRow.cs @@ -465,7 +465,7 @@ namespace osu.Game.Overlays.Settings.Sections.Input } if (HasFocus) - GetContainingFocusManager()?.ChangeFocus(null); + GetContainingFocusManager()!.ChangeFocus(null); cancelAndClearButtons.FadeOut(300, Easing.OutQuint); cancelAndClearButtons.BypassAutoSizeAxes |= Axes.Y; diff --git a/osu.Game/Overlays/SettingsPanel.cs b/osu.Game/Overlays/SettingsPanel.cs index a5a56d1e8b..df50e0f339 100644 --- a/osu.Game/Overlays/SettingsPanel.cs +++ b/osu.Game/Overlays/SettingsPanel.cs @@ -201,7 +201,7 @@ namespace osu.Game.Overlays searchTextBox.HoldFocus = false; if (searchTextBox.HasFocus) - GetContainingFocusManager()?.ChangeFocus(null); + GetContainingFocusManager()!.ChangeFocus(null); } public override bool AcceptsFocus => true; diff --git a/osu.Game/Overlays/SkinEditor/SkinEditor.cs b/osu.Game/Overlays/SkinEditor/SkinEditor.cs index 41fd701a09..484af34603 100644 --- a/osu.Game/Overlays/SkinEditor/SkinEditor.cs +++ b/osu.Game/Overlays/SkinEditor/SkinEditor.cs @@ -669,7 +669,7 @@ namespace osu.Game.Overlays.SkinEditor { SpriteName = { Value = file.Name }, Origin = Anchor.Centre, - Position = skinnableTarget.ToLocalSpace(GetContainingInputManager().CurrentState.Mouse.Position), + Position = skinnableTarget.ToLocalSpace(GetContainingInputManager()!.CurrentState.Mouse.Position), }; SelectedComponents.Clear(); diff --git a/osu.Game/Screens/Edit/Compose/Components/BeatDivisorControl.cs b/osu.Game/Screens/Edit/Compose/Components/BeatDivisorControl.cs index 1751d01fb7..faab5e7f78 100644 --- a/osu.Game/Screens/Edit/Compose/Components/BeatDivisorControl.cs +++ b/osu.Game/Screens/Edit/Compose/Components/BeatDivisorControl.cs @@ -580,7 +580,7 @@ namespace osu.Game.Screens.Edit.Compose.Components { base.LoadComplete(); - GetContainingFocusManager()?.ChangeFocus(this); + GetContainingFocusManager()!.ChangeFocus(this); SelectAll(); } } diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/DifficultyPointPiece.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/DifficultyPointPiece.cs index 2c9da0446d..3ad6095965 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/DifficultyPointPiece.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/DifficultyPointPiece.cs @@ -138,7 +138,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline protected override void LoadComplete() { base.LoadComplete(); - ScheduleAfterChildren(() => GetContainingFocusManager()?.ChangeFocus(sliderVelocitySlider)); + ScheduleAfterChildren(() => GetContainingFocusManager()!.ChangeFocus(sliderVelocitySlider)); } } } diff --git a/osu.Game/Screens/Edit/Setup/LabelledTextBoxWithPopover.cs b/osu.Game/Screens/Edit/Setup/LabelledTextBoxWithPopover.cs index 00cc07413f..f9e93e7b0e 100644 --- a/osu.Game/Screens/Edit/Setup/LabelledTextBoxWithPopover.cs +++ b/osu.Game/Screens/Edit/Setup/LabelledTextBoxWithPopover.cs @@ -45,7 +45,7 @@ namespace osu.Game.Screens.Edit.Setup OnFocused?.Invoke(); base.OnFocus(e); - GetContainingFocusManager()?.TriggerFocusContention(this); + GetContainingFocusManager()!.TriggerFocusContention(this); } } } diff --git a/osu.Game/Screens/Edit/Setup/MetadataSection.cs b/osu.Game/Screens/Edit/Setup/MetadataSection.cs index dd880891ba..19071dc806 100644 --- a/osu.Game/Screens/Edit/Setup/MetadataSection.cs +++ b/osu.Game/Screens/Edit/Setup/MetadataSection.cs @@ -70,7 +70,7 @@ namespace osu.Game.Screens.Edit.Setup base.LoadComplete(); if (string.IsNullOrEmpty(ArtistTextBox.Current.Value)) - ScheduleAfterChildren(() => GetContainingFocusManager()?.ChangeFocus(ArtistTextBox)); + ScheduleAfterChildren(() => GetContainingFocusManager()!.ChangeFocus(ArtistTextBox)); ArtistTextBox.Current.BindValueChanged(artist => transferIfRomanised(artist.NewValue, RomanisedArtistTextBox)); TitleTextBox.Current.BindValueChanged(title => transferIfRomanised(title.NewValue, RomanisedTitleTextBox)); diff --git a/osu.Game/Screens/Edit/Timing/IndeterminateSliderWithTextBoxInput.cs b/osu.Game/Screens/Edit/Timing/IndeterminateSliderWithTextBoxInput.cs index 187aa3e897..01e1856e6c 100644 --- a/osu.Game/Screens/Edit/Timing/IndeterminateSliderWithTextBoxInput.cs +++ b/osu.Game/Screens/Edit/Timing/IndeterminateSliderWithTextBoxInput.cs @@ -126,7 +126,7 @@ namespace osu.Game.Screens.Edit.Timing protected override void OnFocus(FocusEvent e) { base.OnFocus(e); - GetContainingFocusManager()?.ChangeFocus(textBox); + GetContainingFocusManager()!.ChangeFocus(textBox); } private void updateState() diff --git a/osu.Game/Screens/Edit/Verify/IssueTable.cs b/osu.Game/Screens/Edit/Verify/IssueTable.cs index ba5f98a772..8fb30fb726 100644 --- a/osu.Game/Screens/Edit/Verify/IssueTable.cs +++ b/osu.Game/Screens/Edit/Verify/IssueTable.cs @@ -53,7 +53,7 @@ namespace osu.Game.Screens.Edit.Verify if (issue.Time != null) { clock.Seek(issue.Time.Value); - editor.OnPressed(new KeyBindingPressEvent(GetContainingInputManager().CurrentState, GlobalAction.EditorComposeMode)); + editor.OnPressed(new KeyBindingPressEvent(GetContainingInputManager()!.CurrentState, GlobalAction.EditorComposeMode)); } if (!issue.HitObjects.Any()) diff --git a/osu.Game/Screens/OnlinePlay/Lounge/DrawableLoungeRoom.cs b/osu.Game/Screens/OnlinePlay/Lounge/DrawableLoungeRoom.cs index 6f06b8686c..fed47e847a 100644 --- a/osu.Game/Screens/OnlinePlay/Lounge/DrawableLoungeRoom.cs +++ b/osu.Game/Screens/OnlinePlay/Lounge/DrawableLoungeRoom.cs @@ -248,7 +248,7 @@ namespace osu.Game.Screens.OnlinePlay.Lounge { base.LoadComplete(); - ScheduleAfterChildren(() => GetContainingFocusManager()?.ChangeFocus(passwordTextBox)); + ScheduleAfterChildren(() => GetContainingFocusManager()!.ChangeFocus(passwordTextBox)); passwordTextBox.OnCommit += (_, _) => performJoin(); } @@ -262,7 +262,7 @@ namespace osu.Game.Screens.OnlinePlay.Lounge { passwordTextBox.Text = string.Empty; - GetContainingFocusManager()?.ChangeFocus(passwordTextBox); + GetContainingFocusManager()!.ChangeFocus(passwordTextBox); errorText.Text = error; errorText diff --git a/osu.Game/Screens/Play/PlayerLoader.cs b/osu.Game/Screens/Play/PlayerLoader.cs index 51a0c94ff0..12048ecbbe 100644 --- a/osu.Game/Screens/Play/PlayerLoader.cs +++ b/osu.Game/Screens/Play/PlayerLoader.cs @@ -249,7 +249,7 @@ namespace osu.Game.Screens.Play { base.LoadComplete(); - inputManager = GetContainingInputManager(); + inputManager = GetContainingInputManager()!; showStoryboards.BindValueChanged(val => epilepsyWarning?.FadeTo(val.NewValue ? 1 : 0, 250, Easing.OutQuint), true); epilepsyWarning?.FinishTransforms(true); diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index 32a1b5cb58..49c23bdbbf 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -1279,7 +1279,7 @@ namespace osu.Game.Screens.Select { // we need to block right click absolute scrolling when hovering a carousel item so context menus can display. // this can be reconsidered when we have an alternative to right click scrolling. - if (GetContainingInputManager().HoveredDrawables.OfType().Any()) + if (GetContainingInputManager()!.HoveredDrawables.OfType().Any()) { rightMouseScrollBlocked = true; return false; diff --git a/osu.Game/Screens/Select/FilterControl.cs b/osu.Game/Screens/Select/FilterControl.cs index 497c1d4c9f..877db75317 100644 --- a/osu.Game/Screens/Select/FilterControl.cs +++ b/osu.Game/Screens/Select/FilterControl.cs @@ -245,7 +245,7 @@ namespace osu.Game.Screens.Select searchTextBox.ReadOnly = true; searchTextBox.HoldFocus = false; if (searchTextBox.HasFocus) - GetContainingFocusManager()?.ChangeFocus(searchTextBox); + GetContainingFocusManager()!.ChangeFocus(searchTextBox); } public void Activate() diff --git a/osu.Game/Screens/Select/PlaySongSelect.cs b/osu.Game/Screens/Select/PlaySongSelect.cs index 52f49ba56a..7b1479f392 100644 --- a/osu.Game/Screens/Select/PlaySongSelect.cs +++ b/osu.Game/Screens/Select/PlaySongSelect.cs @@ -95,7 +95,7 @@ namespace osu.Game.Screens.Select modsAtGameplayStart = Mods.Value; // Ctrl+Enter should start map with autoplay enabled. - if (GetContainingInputManager().CurrentState?.Keyboard.ControlPressed == true) + if (GetContainingInputManager()?.CurrentState?.Keyboard.ControlPressed == true) { var autoInstance = getAutoplayMod(); diff --git a/osu.Game/Screens/SelectV2/Footer/BeatmapOptionsPopover.cs b/osu.Game/Screens/SelectV2/Footer/BeatmapOptionsPopover.cs index e8b8b49785..fb2e32dfdc 100644 --- a/osu.Game/Screens/SelectV2/Footer/BeatmapOptionsPopover.cs +++ b/osu.Game/Screens/SelectV2/Footer/BeatmapOptionsPopover.cs @@ -81,7 +81,7 @@ namespace osu.Game.Screens.SelectV2.Footer { base.LoadComplete(); - ScheduleAfterChildren(() => GetContainingFocusManager()?.ChangeFocus(this)); + ScheduleAfterChildren(() => GetContainingFocusManager()!.ChangeFocus(this)); beatmap.BindValueChanged(_ => Hide()); } diff --git a/osu.Game/Screens/Utility/SampleComponents/LatencySampleComponent.cs b/osu.Game/Screens/Utility/SampleComponents/LatencySampleComponent.cs index 690376cf52..922935f520 100644 --- a/osu.Game/Screens/Utility/SampleComponents/LatencySampleComponent.cs +++ b/osu.Game/Screens/Utility/SampleComponents/LatencySampleComponent.cs @@ -38,7 +38,7 @@ namespace osu.Game.Screens.Utility.SampleComponents { base.LoadComplete(); - inputManager = GetContainingInputManager(); + inputManager = GetContainingInputManager()!; IsActive.BindTo(latencyArea.IsActiveArea); } From 366ba26cb6d0dea7fa3f76b397863140bb056dce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 18 Jun 2024 07:30:41 +0200 Subject: [PATCH 75/91] Adjust calls to `DrawableExtensions.With()` --- .../Skinning/Legacy/LegacyBodyPiece.cs | 10 ++-------- .../Skinning/Legacy/LegacyHitExplosion.cs | 5 +---- 2 files changed, 3 insertions(+), 12 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyBodyPiece.cs b/osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyBodyPiece.cs index 1cba5b8cb3..087b428801 100644 --- a/osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyBodyPiece.cs +++ b/osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyBodyPiece.cs @@ -65,11 +65,8 @@ namespace osu.Game.Rulesets.Mania.Skinning.Legacy if (tmp is IFramedAnimation tmpAnimation && tmpAnimation.FrameCount > 0) frameLength = Math.Max(1000 / 60.0, 170.0 / tmpAnimation.FrameCount); - light = skin.GetAnimation(lightImage, true, true, frameLength: frameLength).With(d => + light = skin.GetAnimation(lightImage, true, true, frameLength: frameLength)?.With(d => { - if (d == null) - return; - d.Origin = Anchor.Centre; d.Blending = BlendingParameters.Additive; d.Scale = new Vector2(lightScale); @@ -91,11 +88,8 @@ namespace osu.Game.Rulesets.Mania.Skinning.Legacy direction.BindTo(scrollingInfo.Direction); isHitting.BindTo(holdNote.IsHitting); - bodySprite = skin.GetAnimation(imageName, wrapMode, wrapMode, true, true, frameLength: 30).With(d => + bodySprite = skin.GetAnimation(imageName, wrapMode, wrapMode, true, true, frameLength: 30)?.With(d => { - if (d == null) - return; - if (d is TextureAnimation animation) animation.IsPlaying = false; diff --git a/osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyHitExplosion.cs b/osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyHitExplosion.cs index 1ec218644c..95b00e32ea 100644 --- a/osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyHitExplosion.cs +++ b/osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyHitExplosion.cs @@ -43,11 +43,8 @@ namespace osu.Game.Rulesets.Mania.Skinning.Legacy if (tmp is IFramedAnimation tmpAnimation && tmpAnimation.FrameCount > 0) frameLength = Math.Max(1000 / 60.0, 170.0 / tmpAnimation.FrameCount); - explosion = skin.GetAnimation(imageName, true, false, frameLength: frameLength).With(d => + explosion = skin.GetAnimation(imageName, true, false, frameLength: frameLength)?.With(d => { - if (d == null) - return; - d.Origin = Anchor.Centre; d.Blending = BlendingParameters.Additive; d.Scale = new Vector2(explosionScale); From e6187ebec09a28aa13c41a6daf26846a092c51b8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 18 Jun 2024 15:13:52 +0800 Subject: [PATCH 76/91] Fix nullability insepction --- .../Skinning/Legacy/LegacyStageForeground.cs | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyStageForeground.cs b/osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyStageForeground.cs index 1a47fe5076..680198c1a6 100644 --- a/osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyStageForeground.cs +++ b/osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyStageForeground.cs @@ -28,13 +28,7 @@ namespace osu.Game.Rulesets.Mania.Skinning.Legacy string bottomImage = skin.GetManiaSkinConfig(LegacyManiaSkinConfigurationLookups.BottomStageImage)?.Value ?? "mania-stage-bottom"; - sprite = skin.GetAnimation(bottomImage, true, true)?.With(d => - { - if (d == null) - return; - - d.Scale = new Vector2(1.6f); - }); + sprite = skin.GetAnimation(bottomImage, true, true)?.With(d => d.Scale = new Vector2(1.6f)); if (sprite != null) InternalChild = sprite; From a9e662a2b6d593681acb0e54058cec9635f013ef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 18 Jun 2024 14:55:59 +0200 Subject: [PATCH 77/91] Add break display to editor timeline --- .../Components/Timeline/TimelineBreak.cs | 87 +++++++++++++++++ .../Timeline/TimelineBreakDisplay.cs | 94 +++++++++++++++++++ .../Screens/Edit/Compose/ComposeScreen.cs | 10 +- 3 files changed, 190 insertions(+), 1 deletion(-) create mode 100644 osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineBreak.cs create mode 100644 osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineBreakDisplay.cs diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineBreak.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineBreak.cs new file mode 100644 index 0000000000..dc54661644 --- /dev/null +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineBreak.cs @@ -0,0 +1,87 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Game.Beatmaps.Timing; +using osu.Game.Graphics; +using osu.Game.Graphics.Sprites; + +namespace osu.Game.Screens.Edit.Compose.Components.Timeline +{ + public partial class TimelineBreak : CompositeDrawable + { + public BreakPeriod Break { get; } + + public TimelineBreak(BreakPeriod b) + { + Break = b; + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + RelativePositionAxes = Axes.X; + RelativeSizeAxes = Axes.Both; + Origin = Anchor.TopLeft; + X = (float)Break.StartTime; + Width = (float)Break.Duration; + CornerRadius = 10; + Masking = true; + + InternalChildren = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = colours.GreyCarmineLight, + Alpha = 0.4f, + }, + new Circle + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + RelativeSizeAxes = Axes.Y, + Width = 10, + CornerRadius = 5, + Colour = colours.GreyCarmineLighter, + }, + new OsuSpriteText + { + Anchor = Anchor.TopLeft, + Origin = Anchor.TopLeft, + Text = "Break", + Margin = new MarginPadding + { + Left = 16, + Top = 3, + }, + Colour = colours.GreyCarmineLighter, + }, + new Circle + { + Anchor = Anchor.CentreRight, + Origin = Anchor.CentreRight, + RelativeSizeAxes = Axes.Y, + Width = 10, + CornerRadius = 5, + Colour = colours.GreyCarmineLighter, + }, + new OsuSpriteText + { + Anchor = Anchor.TopRight, + Origin = Anchor.TopRight, + Text = "Break", + Margin = new MarginPadding + { + Right = 16, + Top = 3, + }, + Colour = colours.GreyCarmineLighter, + }, + }; + } + } +} diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineBreakDisplay.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineBreakDisplay.cs new file mode 100644 index 0000000000..587db23e9a --- /dev/null +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineBreakDisplay.cs @@ -0,0 +1,94 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Caching; +using osu.Game.Beatmaps.Timing; +using osu.Game.Screens.Edit.Components.Timelines.Summary.Parts; + +namespace osu.Game.Screens.Edit.Compose.Components.Timeline +{ + public partial class TimelineBreakDisplay : TimelinePart + { + [Resolved] + private Timeline timeline { get; set; } = null!; + + /// + /// The visible time/position range of the timeline. + /// + private (float min, float max) visibleRange = (float.MinValue, float.MaxValue); + + private readonly Cached breakCache = new Cached(); + + private readonly BindableList breaks = new BindableList(); + + protected override void LoadBeatmap(EditorBeatmap beatmap) + { + base.LoadBeatmap(beatmap); + + // TODO: this will have to be mutable soon enough + breaks.AddRange(beatmap.Breaks); + } + + protected override void Update() + { + base.Update(); + + if (DrawWidth <= 0) return; + + (float, float) newRange = ( + (ToLocalSpace(timeline.ScreenSpaceDrawQuad.TopLeft).X) / DrawWidth * Content.RelativeChildSize.X, + (ToLocalSpace(timeline.ScreenSpaceDrawQuad.TopRight).X) / DrawWidth * Content.RelativeChildSize.X); + + if (visibleRange != newRange) + { + visibleRange = newRange; + breakCache.Invalidate(); + } + + if (!breakCache.IsValid) + { + recreateBreaks(); + breakCache.Validate(); + } + } + + private void recreateBreaks() + { + // Remove groups outside the visible range + foreach (TimelineBreak drawableBreak in this) + { + if (!shouldBeVisible(drawableBreak.Break)) + drawableBreak.Expire(); + } + + // Add remaining ones + for (int i = 0; i < breaks.Count; i++) + { + var breakPeriod = breaks[i]; + + if (!shouldBeVisible(breakPeriod)) + continue; + + bool alreadyVisible = false; + + foreach (var b in this) + { + if (ReferenceEquals(b.Break, breakPeriod)) + { + alreadyVisible = true; + break; + } + } + + if (alreadyVisible) + continue; + + Add(new TimelineBreak(breakPeriod)); + } + } + + private bool shouldBeVisible(BreakPeriod breakPeriod) => breakPeriod.EndTime >= visibleRange.min && breakPeriod.StartTime <= visibleRange.max; + } +} diff --git a/osu.Game/Screens/Edit/Compose/ComposeScreen.cs b/osu.Game/Screens/Edit/Compose/ComposeScreen.cs index 0a58b34da6..ed4ef896f5 100644 --- a/osu.Game/Screens/Edit/Compose/ComposeScreen.cs +++ b/osu.Game/Screens/Edit/Compose/ComposeScreen.cs @@ -69,7 +69,15 @@ namespace osu.Game.Screens.Edit.Compose if (ruleset == null || composer == null) return base.CreateTimelineContent(); - return wrapSkinnableContent(new TimelineBlueprintContainer(composer)); + return wrapSkinnableContent(new Container + { + RelativeSizeAxes = Axes.Both, + Children = new Drawable[] + { + new TimelineBreakDisplay { RelativeSizeAxes = Axes.Both, }, + new TimelineBlueprintContainer(composer) + } + }); } private Drawable wrapSkinnableContent(Drawable content) From 814f1e552fdfafddad2868e8cccbf4c721bd6c3b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 18 Jun 2024 15:41:43 +0200 Subject: [PATCH 78/91] Implement ability to manually adjust breaks --- .../Components/Timeline/TimelineBreak.cs | 211 ++++++++++++++---- .../Screens/Edit/Compose/ComposeScreen.cs | 2 +- 2 files changed, 171 insertions(+), 42 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineBreak.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineBreak.cs index dc54661644..785eba2042 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineBreak.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineBreak.cs @@ -1,13 +1,20 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System; +using System.Diagnostics; +using System.Linq; using osu.Framework.Allocation; +using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; +using osu.Framework.Input.Events; using osu.Game.Beatmaps.Timing; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; +using osu.Game.Rulesets.Objects; +using osuTK; namespace osu.Game.Screens.Edit.Compose.Components.Timeline { @@ -26,62 +33,184 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline RelativePositionAxes = Axes.X; RelativeSizeAxes = Axes.Both; Origin = Anchor.TopLeft; - X = (float)Break.StartTime; - Width = (float)Break.Duration; - CornerRadius = 10; - Masking = true; + Padding = new MarginPadding { Horizontal = -5 }; InternalChildren = new Drawable[] { - new Box + new Container { RelativeSizeAxes = Axes.Both, - Colour = colours.GreyCarmineLight, - Alpha = 0.4f, + Padding = new MarginPadding { Horizontal = 5 }, + Child = new Box + { + RelativeSizeAxes = Axes.Both, + Colour = colours.GreyCarmineLight, + Alpha = 0.4f, + }, }, - new Circle - { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - RelativeSizeAxes = Axes.Y, - Width = 10, - CornerRadius = 5, - Colour = colours.GreyCarmineLighter, - }, - new OsuSpriteText + new DragHandle(Break, isStartHandle: true) { Anchor = Anchor.TopLeft, Origin = Anchor.TopLeft, - Text = "Break", - Margin = new MarginPadding - { - Left = 16, - Top = 3, - }, - Colour = colours.GreyCarmineLighter, + Action = (time, breakPeriod) => breakPeriod.StartTime = time, }, - new Circle - { - Anchor = Anchor.CentreRight, - Origin = Anchor.CentreRight, - RelativeSizeAxes = Axes.Y, - Width = 10, - CornerRadius = 5, - Colour = colours.GreyCarmineLighter, - }, - new OsuSpriteText + new DragHandle(Break, isStartHandle: false) { Anchor = Anchor.TopRight, Origin = Anchor.TopRight, - Text = "Break", - Margin = new MarginPadding - { - Right = 16, - Top = 3, - }, - Colour = colours.GreyCarmineLighter, + Action = (time, breakPeriod) => breakPeriod.EndTime = time, }, }; } + + protected override void Update() + { + base.Update(); + + X = (float)Break.StartTime; + Width = (float)Break.Duration; + } + + private partial class DragHandle : FillFlowContainer + { + public new Anchor Anchor + { + get => base.Anchor; + init => base.Anchor = value; + } + + public Action? Action { get; init; } + + private readonly BreakPeriod breakPeriod; + private readonly bool isStartHandle; + + private Container handle = null!; + private (double min, double max)? allowedDragRange; + + [Resolved] + private EditorBeatmap beatmap { get; set; } = null!; + + [Resolved] + private Timeline timeline { get; set; } = null!; + + [Resolved] + private IEditorChangeHandler? changeHandler { get; set; } + + [Resolved] + private OsuColour colours { get; set; } = null!; + + public DragHandle(BreakPeriod breakPeriod, bool isStartHandle) + { + this.breakPeriod = breakPeriod; + this.isStartHandle = isStartHandle; + } + + [BackgroundDependencyLoader] + private void load() + { + AutoSizeAxes = Axes.X; + RelativeSizeAxes = Axes.Y; + Direction = FillDirection.Horizontal; + Spacing = new Vector2(5); + + Children = new Drawable[] + { + handle = new Container + { + Anchor = Anchor, + Origin = Anchor, + RelativeSizeAxes = Axes.Y, + CornerRadius = 5, + Masking = true, + Child = new Box + { + RelativeSizeAxes = Axes.Both, + Colour = Colour4.White, + }, + }, + new OsuSpriteText + { + BypassAutoSizeAxes = Axes.X, + Anchor = Anchor, + Origin = Anchor, + Text = "Break", + Margin = new MarginPadding { Top = 2, }, + }, + }; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + updateState(); + FinishTransforms(true); + } + + protected override bool OnHover(HoverEvent e) + { + updateState(); + return true; + } + + protected override void OnHoverLost(HoverLostEvent e) + { + updateState(); + base.OnHoverLost(e); + } + + protected override bool OnDragStart(DragStartEvent e) + { + changeHandler?.BeginChange(); + updateState(); + + double min = beatmap.HitObjects.Last(ho => ho.GetEndTime() <= breakPeriod.StartTime).GetEndTime(); + double max = beatmap.HitObjects.First(ho => ho.StartTime >= breakPeriod.EndTime).StartTime; + + if (isStartHandle) + max = Math.Min(max, breakPeriod.EndTime - BreakPeriod.MIN_BREAK_DURATION); + else + min = Math.Max(min, breakPeriod.StartTime + BreakPeriod.MIN_BREAK_DURATION); + + allowedDragRange = (min, max); + + return true; + } + + protected override void OnDrag(DragEvent e) + { + base.OnDrag(e); + + Debug.Assert(allowedDragRange != null); + + if (timeline.FindSnappedPositionAndTime(e.ScreenSpaceMousePosition).Time is double time + && time > allowedDragRange.Value.min + && time < allowedDragRange.Value.max) + { + Action?.Invoke(time, breakPeriod); + } + + updateState(); + } + + protected override void OnDragEnd(DragEndEvent e) + { + changeHandler?.EndChange(); + updateState(); + base.OnDragEnd(e); + } + + private void updateState() + { + bool active = IsHovered || IsDragged; + + var colour = colours.GreyCarmineLighter; + if (active) + colour = colour.Lighten(0.3f); + + this.FadeColour(colour, 400, Easing.OutQuint); + handle.ResizeWidthTo(active ? 20 : 10, 400, Easing.OutElastic); + } + } } } diff --git a/osu.Game/Screens/Edit/Compose/ComposeScreen.cs b/osu.Game/Screens/Edit/Compose/ComposeScreen.cs index ed4ef896f5..9b945e1d6d 100644 --- a/osu.Game/Screens/Edit/Compose/ComposeScreen.cs +++ b/osu.Game/Screens/Edit/Compose/ComposeScreen.cs @@ -74,8 +74,8 @@ namespace osu.Game.Screens.Edit.Compose RelativeSizeAxes = Axes.Both, Children = new Drawable[] { + new TimelineBlueprintContainer(composer), new TimelineBreakDisplay { RelativeSizeAxes = Axes.Both, }, - new TimelineBlueprintContainer(composer) } }); } From f88f05717a9d147da880720e8b3bf9df8f3d9a36 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 18 Jun 2024 15:54:34 +0200 Subject: [PATCH 79/91] Fix bottom timeline break visualisations not updating --- .../Timelines/Summary/Parts/BreakPart.cs | 20 +++++++++++++--- .../Visualisations/DurationVisualisation.cs | 23 ------------------- 2 files changed, 17 insertions(+), 26 deletions(-) delete mode 100644 osu.Game/Screens/Edit/Components/Timelines/Summary/Visualisations/DurationVisualisation.cs diff --git a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/BreakPart.cs b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/BreakPart.cs index e502dd951b..41ecb44d9d 100644 --- a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/BreakPart.cs +++ b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/BreakPart.cs @@ -2,9 +2,10 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Shapes; using osu.Game.Beatmaps.Timing; using osu.Game.Graphics; -using osu.Game.Screens.Edit.Components.Timelines.Summary.Visualisations; namespace osu.Game.Screens.Edit.Components.Timelines.Summary.Parts { @@ -20,11 +21,24 @@ namespace osu.Game.Screens.Edit.Components.Timelines.Summary.Parts Add(new BreakVisualisation(breakPeriod)); } - private partial class BreakVisualisation : DurationVisualisation + private partial class BreakVisualisation : Circle { + private readonly BreakPeriod breakPeriod; + public BreakVisualisation(BreakPeriod breakPeriod) - : base(breakPeriod.StartTime, breakPeriod.EndTime) { + this.breakPeriod = breakPeriod; + + RelativePositionAxes = Axes.X; + RelativeSizeAxes = Axes.Both; + } + + protected override void Update() + { + base.Update(); + + X = (float)breakPeriod.StartTime; + Width = (float)breakPeriod.Duration; } [BackgroundDependencyLoader] diff --git a/osu.Game/Screens/Edit/Components/Timelines/Summary/Visualisations/DurationVisualisation.cs b/osu.Game/Screens/Edit/Components/Timelines/Summary/Visualisations/DurationVisualisation.cs deleted file mode 100644 index bfb50a05ea..0000000000 --- a/osu.Game/Screens/Edit/Components/Timelines/Summary/Visualisations/DurationVisualisation.cs +++ /dev/null @@ -1,23 +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 osu.Framework.Graphics; -using osu.Framework.Graphics.Shapes; - -namespace osu.Game.Screens.Edit.Components.Timelines.Summary.Visualisations -{ - /// - /// Represents a spanning point on a timeline part. - /// - public partial class DurationVisualisation : Circle - { - protected DurationVisualisation(double startTime, double endTime) - { - RelativePositionAxes = Axes.X; - RelativeSizeAxes = Axes.Both; - - X = (float)startTime; - Width = (float)(endTime - startTime); - } - } -} From 6a6ccbc09fce5e23857afaf5feabaa77673dec65 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 19 Jun 2024 07:31:53 +0200 Subject: [PATCH 80/91] Make list of breaks bindable --- .../Mods/TestSceneCatchModNoScope.cs | 2 +- .../Mods/TestSceneOsuModAlternate.cs | 2 +- .../Mods/TestSceneOsuModNoScope.cs | 2 +- .../Mods/TestSceneOsuModSingleTap.cs | 2 +- .../Mods/TestSceneTaikoModSingleTap.cs | 2 +- osu.Game.Tests/Editing/Checks/CheckBreaksTest.cs | 15 +++++++-------- .../Editing/Checks/CheckDrainLengthTest.cs | 2 +- osu.Game/Beatmaps/Beatmap.cs | 3 ++- osu.Game/Beatmaps/IBeatmap.cs | 3 ++- .../Rulesets/Difficulty/DifficultyCalculator.cs | 3 ++- .../Components/Timeline/TimelineBreakDisplay.cs | 5 +++-- osu.Game/Screens/Edit/EditorBeatmap.cs | 2 +- 12 files changed, 23 insertions(+), 20 deletions(-) diff --git a/osu.Game.Rulesets.Catch.Tests/Mods/TestSceneCatchModNoScope.cs b/osu.Game.Rulesets.Catch.Tests/Mods/TestSceneCatchModNoScope.cs index c48bf7adc9..c8f7da1aae 100644 --- a/osu.Game.Rulesets.Catch.Tests/Mods/TestSceneCatchModNoScope.cs +++ b/osu.Game.Rulesets.Catch.Tests/Mods/TestSceneCatchModNoScope.cs @@ -74,7 +74,7 @@ namespace osu.Game.Rulesets.Catch.Tests.Mods StartTime = 5000, } }, - Breaks = new List + Breaks = { new BreakPeriod(2000, 4000), } diff --git a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModAlternate.cs b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModAlternate.cs index 88c81c7a39..7375617aa8 100644 --- a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModAlternate.cs +++ b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModAlternate.cs @@ -133,7 +133,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods Autoplay = false, Beatmap = new Beatmap { - Breaks = new List + Breaks = { new BreakPeriod(500, 2000), }, diff --git a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModNoScope.cs b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModNoScope.cs index 9dfa76fc8e..d3996ebc3b 100644 --- a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModNoScope.cs +++ b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModNoScope.cs @@ -46,7 +46,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods StartTime = 5000, } }, - Breaks = new List + Breaks = { new BreakPeriod(2000, 4000), } diff --git a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModSingleTap.cs b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModSingleTap.cs index 402c680b46..bd2b205ac8 100644 --- a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModSingleTap.cs +++ b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModSingleTap.cs @@ -132,7 +132,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods Autoplay = false, Beatmap = new Beatmap { - Breaks = new List + Breaks = { new BreakPeriod(500, 2000), }, diff --git a/osu.Game.Rulesets.Taiko.Tests/Mods/TestSceneTaikoModSingleTap.cs b/osu.Game.Rulesets.Taiko.Tests/Mods/TestSceneTaikoModSingleTap.cs index 0cd3b85f8e..3a11a91f82 100644 --- a/osu.Game.Rulesets.Taiko.Tests/Mods/TestSceneTaikoModSingleTap.cs +++ b/osu.Game.Rulesets.Taiko.Tests/Mods/TestSceneTaikoModSingleTap.cs @@ -177,7 +177,7 @@ namespace osu.Game.Rulesets.Taiko.Tests.Mods Autoplay = false, Beatmap = new Beatmap { - Breaks = new List + Breaks = { new BreakPeriod(100, 1600), }, diff --git a/osu.Game.Tests/Editing/Checks/CheckBreaksTest.cs b/osu.Game.Tests/Editing/Checks/CheckBreaksTest.cs index 28556566ba..f53dd9a62a 100644 --- a/osu.Game.Tests/Editing/Checks/CheckBreaksTest.cs +++ b/osu.Game.Tests/Editing/Checks/CheckBreaksTest.cs @@ -1,7 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using System.Collections.Generic; using System.Linq; using NUnit.Framework; using osu.Game.Beatmaps; @@ -29,7 +28,7 @@ namespace osu.Game.Tests.Editing.Checks { var beatmap = new Beatmap { - Breaks = new List + Breaks = { new BreakPeriod(0, 649) } @@ -52,7 +51,7 @@ namespace osu.Game.Tests.Editing.Checks new HitCircle { StartTime = 0 }, new HitCircle { StartTime = 1_200 } }, - Breaks = new List + Breaks = { new BreakPeriod(100, 751) } @@ -75,7 +74,7 @@ namespace osu.Game.Tests.Editing.Checks new HitCircle { StartTime = 0 }, new HitCircle { StartTime = 1_298 } }, - Breaks = new List + Breaks = { new BreakPeriod(200, 850) } @@ -98,7 +97,7 @@ namespace osu.Game.Tests.Editing.Checks new HitCircle { StartTime = 0 }, new HitCircle { StartTime = 1200 } }, - Breaks = new List + Breaks = { new BreakPeriod(1398, 2300) } @@ -121,7 +120,7 @@ namespace osu.Game.Tests.Editing.Checks new HitCircle { StartTime = 1100 }, new HitCircle { StartTime = 1500 } }, - Breaks = new List + Breaks = { new BreakPeriod(0, 652) } @@ -145,7 +144,7 @@ namespace osu.Game.Tests.Editing.Checks new HitCircle { StartTime = 1_297 }, new HitCircle { StartTime = 1_298 } }, - Breaks = new List + Breaks = { new BreakPeriod(200, 850) } @@ -168,7 +167,7 @@ namespace osu.Game.Tests.Editing.Checks new HitCircle { StartTime = 0 }, new HitCircle { StartTime = 1_300 } }, - Breaks = new List + Breaks = { new BreakPeriod(200, 850) } diff --git a/osu.Game.Tests/Editing/Checks/CheckDrainLengthTest.cs b/osu.Game.Tests/Editing/Checks/CheckDrainLengthTest.cs index 1b5c5c398f..be9aa711cb 100644 --- a/osu.Game.Tests/Editing/Checks/CheckDrainLengthTest.cs +++ b/osu.Game.Tests/Editing/Checks/CheckDrainLengthTest.cs @@ -53,7 +53,7 @@ namespace osu.Game.Tests.Editing.Checks new HitCircle { StartTime = 0 }, new HitCircle { StartTime = 40_000 } }, - Breaks = new List + Breaks = { new BreakPeriod(10_000, 21_000) } diff --git a/osu.Game/Beatmaps/Beatmap.cs b/osu.Game/Beatmaps/Beatmap.cs index ae77e4adcf..510410bc09 100644 --- a/osu.Game/Beatmaps/Beatmap.cs +++ b/osu.Game/Beatmaps/Beatmap.cs @@ -8,6 +8,7 @@ using System.Collections.Generic; using System.Linq; using osu.Game.Beatmaps.ControlPoints; using Newtonsoft.Json; +using osu.Framework.Bindables; using osu.Game.IO.Serialization.Converters; namespace osu.Game.Beatmaps @@ -61,7 +62,7 @@ namespace osu.Game.Beatmaps public ControlPointInfo ControlPointInfo { get; set; } = new ControlPointInfo(); - public List Breaks { get; set; } = new List(); + public BindableList Breaks { get; set; } = new BindableList(); public List UnhandledEventLines { get; set; } = new List(); diff --git a/osu.Game/Beatmaps/IBeatmap.cs b/osu.Game/Beatmaps/IBeatmap.cs index 5cc38e5b84..072e246a36 100644 --- a/osu.Game/Beatmaps/IBeatmap.cs +++ b/osu.Game/Beatmaps/IBeatmap.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.Linq; +using osu.Framework.Bindables; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Beatmaps.Timing; using osu.Game.Rulesets.Objects; @@ -40,7 +41,7 @@ namespace osu.Game.Beatmaps /// /// The breaks in this beatmap. /// - List Breaks { get; } + BindableList Breaks { get; } /// /// All lines from the [Events] section which aren't handled in the encoding process yet. diff --git a/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs b/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs index d37cfc28b9..97e8e15975 100644 --- a/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs +++ b/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs @@ -9,6 +9,7 @@ using System.Linq; using System.Threading; using JetBrains.Annotations; using osu.Framework.Audio.Track; +using osu.Framework.Bindables; using osu.Framework.Extensions.IEnumerableExtensions; using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; @@ -329,7 +330,7 @@ namespace osu.Game.Rulesets.Difficulty set => baseBeatmap.Difficulty = value; } - public List Breaks => baseBeatmap.Breaks; + public BindableList Breaks => baseBeatmap.Breaks; public List UnhandledEventLines => baseBeatmap.UnhandledEventLines; public double TotalBreakTime => baseBeatmap.TotalBreakTime; diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineBreakDisplay.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineBreakDisplay.cs index 587db23e9a..5fdfda25e5 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineBreakDisplay.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineBreakDisplay.cs @@ -27,8 +27,9 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline { base.LoadBeatmap(beatmap); - // TODO: this will have to be mutable soon enough - breaks.AddRange(beatmap.Breaks); + breaks.UnbindAll(); + breaks.BindTo(beatmap.Breaks); + breaks.BindCollectionChanged((_, _) => breakCache.Invalidate()); } protected override void Update() diff --git a/osu.Game/Screens/Edit/EditorBeatmap.cs b/osu.Game/Screens/Edit/EditorBeatmap.cs index 5be1d27805..f4be987547 100644 --- a/osu.Game/Screens/Edit/EditorBeatmap.cs +++ b/osu.Game/Screens/Edit/EditorBeatmap.cs @@ -172,7 +172,7 @@ namespace osu.Game.Screens.Edit set => PlayableBeatmap.ControlPointInfo = value; } - public List Breaks => PlayableBeatmap.Breaks; + public BindableList Breaks => PlayableBeatmap.Breaks; public List UnhandledEventLines => PlayableBeatmap.UnhandledEventLines; From 1f692f5fc7f450aa7336a235ee60affcaa0d2fb4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 19 Jun 2024 09:01:33 +0200 Subject: [PATCH 81/91] Make `BreakPeriod` a struct --- osu.Game/Beatmaps/Timing/BreakPeriod.cs | 11 +++-- .../Components/Timeline/TimelineBreak.cs | 48 +++++++++++-------- .../Timeline/TimelineBreakDisplay.cs | 29 +++-------- 3 files changed, 43 insertions(+), 45 deletions(-) diff --git a/osu.Game/Beatmaps/Timing/BreakPeriod.cs b/osu.Game/Beatmaps/Timing/BreakPeriod.cs index 4c90b16745..f16a3c27a1 100644 --- a/osu.Game/Beatmaps/Timing/BreakPeriod.cs +++ b/osu.Game/Beatmaps/Timing/BreakPeriod.cs @@ -1,11 +1,12 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System; using osu.Game.Screens.Play; namespace osu.Game.Beatmaps.Timing { - public class BreakPeriod + public readonly struct BreakPeriod : IEquatable { /// /// The minimum duration required for a break to have any effect. @@ -15,12 +16,12 @@ namespace osu.Game.Beatmaps.Timing /// /// The break start time. /// - public double StartTime; + public double StartTime { get; init; } /// /// The break end time. /// - public double EndTime; + public double EndTime { get; init; } /// /// The break duration. @@ -49,5 +50,9 @@ namespace osu.Game.Beatmaps.Timing /// The time to check in milliseconds. /// Whether the time falls within this . public bool Contains(double time) => time >= StartTime && time <= EndTime - BreakOverlay.BREAK_FADE_DURATION; + + public bool Equals(BreakPeriod other) => StartTime.Equals(other.StartTime) && EndTime.Equals(other.EndTime); + public override bool Equals(object? obj) => obj is BreakPeriod other && Equals(other); + public override int GetHashCode() => HashCode.Combine(StartTime, EndTime); } } diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineBreak.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineBreak.cs index 785eba2042..cec4b9b659 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineBreak.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineBreak.cs @@ -5,6 +5,7 @@ using System; using System.Diagnostics; using System.Linq; using osu.Framework.Allocation; +using osu.Framework.Bindables; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -20,11 +21,11 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline { public partial class TimelineBreak : CompositeDrawable { - public BreakPeriod Break { get; } + public Bindable Break { get; } = new Bindable(); public TimelineBreak(BreakPeriod b) { - Break = b; + Break.Value = b; } [BackgroundDependencyLoader] @@ -48,40 +49,46 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline Alpha = 0.4f, }, }, - new DragHandle(Break, isStartHandle: true) + new DragHandle(isStartHandle: true) { + Break = { BindTarget = Break }, Anchor = Anchor.TopLeft, Origin = Anchor.TopLeft, - Action = (time, breakPeriod) => breakPeriod.StartTime = time, + Action = (time, breakPeriod) => breakPeriod with { StartTime = time }, }, - new DragHandle(Break, isStartHandle: false) + new DragHandle(isStartHandle: false) { + Break = { BindTarget = Break }, Anchor = Anchor.TopRight, Origin = Anchor.TopRight, - Action = (time, breakPeriod) => breakPeriod.EndTime = time, + Action = (time, breakPeriod) => breakPeriod with { EndTime = time }, }, }; } - protected override void Update() + protected override void LoadComplete() { - base.Update(); + base.LoadComplete(); - X = (float)Break.StartTime; - Width = (float)Break.Duration; + Break.BindValueChanged(_ => + { + X = (float)Break.Value.StartTime; + Width = (float)Break.Value.Duration; + }, true); } private partial class DragHandle : FillFlowContainer { + public Bindable Break { get; } = new Bindable(); + public new Anchor Anchor { get => base.Anchor; init => base.Anchor = value; } - public Action? Action { get; init; } + public Func? Action { get; init; } - private readonly BreakPeriod breakPeriod; private readonly bool isStartHandle; private Container handle = null!; @@ -99,9 +106,8 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline [Resolved] private OsuColour colours { get; set; } = null!; - public DragHandle(BreakPeriod breakPeriod, bool isStartHandle) + public DragHandle(bool isStartHandle) { - this.breakPeriod = breakPeriod; this.isStartHandle = isStartHandle; } @@ -164,13 +170,13 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline changeHandler?.BeginChange(); updateState(); - double min = beatmap.HitObjects.Last(ho => ho.GetEndTime() <= breakPeriod.StartTime).GetEndTime(); - double max = beatmap.HitObjects.First(ho => ho.StartTime >= breakPeriod.EndTime).StartTime; + double min = beatmap.HitObjects.Last(ho => ho.GetEndTime() <= Break.Value.StartTime).GetEndTime(); + double max = beatmap.HitObjects.First(ho => ho.StartTime >= Break.Value.EndTime).StartTime; if (isStartHandle) - max = Math.Min(max, breakPeriod.EndTime - BreakPeriod.MIN_BREAK_DURATION); + max = Math.Min(max, Break.Value.EndTime - BreakPeriod.MIN_BREAK_DURATION); else - min = Math.Max(min, breakPeriod.StartTime + BreakPeriod.MIN_BREAK_DURATION); + min = Math.Max(min, Break.Value.StartTime + BreakPeriod.MIN_BREAK_DURATION); allowedDragRange = (min, max); @@ -183,11 +189,13 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline Debug.Assert(allowedDragRange != null); - if (timeline.FindSnappedPositionAndTime(e.ScreenSpaceMousePosition).Time is double time + if (Action != null + && timeline.FindSnappedPositionAndTime(e.ScreenSpaceMousePosition).Time is double time && time > allowedDragRange.Value.min && time < allowedDragRange.Value.max) { - Action?.Invoke(time, breakPeriod); + int index = beatmap.Breaks.IndexOf(Break.Value); + beatmap.Breaks[index] = Break.Value = Action.Invoke(time, Break.Value); } updateState(); diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineBreakDisplay.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineBreakDisplay.cs index 5fdfda25e5..eaa31aea1e 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineBreakDisplay.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineBreakDisplay.cs @@ -1,6 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System.Collections.Specialized; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Caching; @@ -29,7 +30,11 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline breaks.UnbindAll(); breaks.BindTo(beatmap.Breaks); - breaks.BindCollectionChanged((_, _) => breakCache.Invalidate()); + breaks.BindCollectionChanged((_, e) => + { + if (e.Action != NotifyCollectionChangedAction.Replace) + breakCache.Invalidate(); + }); } protected override void Update() @@ -57,14 +62,8 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline private void recreateBreaks() { - // Remove groups outside the visible range - foreach (TimelineBreak drawableBreak in this) - { - if (!shouldBeVisible(drawableBreak.Break)) - drawableBreak.Expire(); - } + Clear(); - // Add remaining ones for (int i = 0; i < breaks.Count; i++) { var breakPeriod = breaks[i]; @@ -72,20 +71,6 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline if (!shouldBeVisible(breakPeriod)) continue; - bool alreadyVisible = false; - - foreach (var b in this) - { - if (ReferenceEquals(b.Break, breakPeriod)) - { - alreadyVisible = true; - break; - } - } - - if (alreadyVisible) - continue; - Add(new TimelineBreak(breakPeriod)); } } From 4022a8b06cc056b210047f55bff4b3a59d7c24e1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 19 Jun 2024 10:11:04 +0200 Subject: [PATCH 82/91] Implement automatic break period generation --- osu.Game/Beatmaps/Timing/BreakPeriod.cs | 32 ++++++++-- osu.Game/Rulesets/Edit/Checks/CheckBreaks.cs | 14 ++-- .../Components/Timeline/TimelineBreak.cs | 4 +- osu.Game/Screens/Edit/EditorBeatmap.cs | 2 +- .../Screens/Edit/EditorBeatmapProcessor.cs | 64 +++++++++++++++++++ osu.Game/Screens/Edit/ManualBreakPeriod.cs | 15 +++++ 6 files changed, 113 insertions(+), 18 deletions(-) create mode 100644 osu.Game/Screens/Edit/EditorBeatmapProcessor.cs create mode 100644 osu.Game/Screens/Edit/ManualBreakPeriod.cs diff --git a/osu.Game/Beatmaps/Timing/BreakPeriod.cs b/osu.Game/Beatmaps/Timing/BreakPeriod.cs index f16a3c27a1..89f0fd6a55 100644 --- a/osu.Game/Beatmaps/Timing/BreakPeriod.cs +++ b/osu.Game/Beatmaps/Timing/BreakPeriod.cs @@ -6,22 +6,39 @@ using osu.Game.Screens.Play; namespace osu.Game.Beatmaps.Timing { - public readonly struct BreakPeriod : IEquatable + public record BreakPeriod { + /// + /// The minimum gap between the start of the break and the previous object. + /// + public const double GAP_BEFORE_BREAK = 200; + + /// + /// The minimum gap between the end of the break and the next object. + /// Based on osu! preempt time at AR=10. + /// See also: https://github.com/ppy/osu/issues/14330#issuecomment-1002158551 + /// + public const double GAP_AFTER_BREAK = 450; + /// /// The minimum duration required for a break to have any effect. /// public const double MIN_BREAK_DURATION = 650; + /// + /// The minimum required duration of a gap between two objects such that a break can be placed between them. + /// + public const double MIN_GAP_DURATION = GAP_BEFORE_BREAK + MIN_BREAK_DURATION + GAP_AFTER_BREAK; + /// /// The break start time. /// - public double StartTime { get; init; } + public double StartTime { get; } /// /// The break end time. /// - public double EndTime { get; init; } + public double EndTime { get; } /// /// The break duration. @@ -51,8 +68,13 @@ namespace osu.Game.Beatmaps.Timing /// Whether the time falls within this . public bool Contains(double time) => time >= StartTime && time <= EndTime - BreakOverlay.BREAK_FADE_DURATION; - public bool Equals(BreakPeriod other) => StartTime.Equals(other.StartTime) && EndTime.Equals(other.EndTime); - public override bool Equals(object? obj) => obj is BreakPeriod other && Equals(other); + public bool Intersects(BreakPeriod other) => StartTime <= other.EndTime && EndTime >= other.StartTime; + + public virtual bool Equals(BreakPeriod? other) => + other != null + && StartTime == other.StartTime + && EndTime == other.EndTime; + public override int GetHashCode() => HashCode.Combine(StartTime, EndTime); } } diff --git a/osu.Game/Rulesets/Edit/Checks/CheckBreaks.cs b/osu.Game/Rulesets/Edit/Checks/CheckBreaks.cs index 0842ff5453..f7be36beab 100644 --- a/osu.Game/Rulesets/Edit/Checks/CheckBreaks.cs +++ b/osu.Game/Rulesets/Edit/Checks/CheckBreaks.cs @@ -13,13 +13,7 @@ namespace osu.Game.Rulesets.Edit.Checks { // Breaks may be off by 1 ms. private const int leniency_threshold = 1; - private const double minimum_gap_before_break = 200; - // Break end time depends on the upcoming object's pre-empt time. - // As things stand, "pre-empt time" is only defined for osu! standard - // This is a generic value representing AR=10 - // Relevant: https://github.com/ppy/osu/issues/14330#issuecomment-1002158551 - private const double min_end_threshold = 450; public CheckMetadata Metadata => new CheckMetadata(CheckCategory.Events, "Breaks not achievable using the editor"); public IEnumerable PossibleTemplates => new IssueTemplate[] @@ -45,8 +39,8 @@ namespace osu.Game.Rulesets.Edit.Checks if (previousObjectEndTimeIndex >= 0) { double gapBeforeBreak = breakPeriod.StartTime - endTimes[previousObjectEndTimeIndex]; - if (gapBeforeBreak < minimum_gap_before_break - leniency_threshold) - yield return new IssueTemplateEarlyStart(this).Create(breakPeriod.StartTime, minimum_gap_before_break - gapBeforeBreak); + if (gapBeforeBreak < BreakPeriod.GAP_BEFORE_BREAK - leniency_threshold) + yield return new IssueTemplateEarlyStart(this).Create(breakPeriod.StartTime, BreakPeriod.GAP_BEFORE_BREAK - gapBeforeBreak); } int nextObjectStartTimeIndex = startTimes.BinarySearch(breakPeriod.EndTime); @@ -55,8 +49,8 @@ namespace osu.Game.Rulesets.Edit.Checks if (nextObjectStartTimeIndex < startTimes.Count) { double gapAfterBreak = startTimes[nextObjectStartTimeIndex] - breakPeriod.EndTime; - if (gapAfterBreak < min_end_threshold - leniency_threshold) - yield return new IssueTemplateLateEnd(this).Create(breakPeriod.StartTime, min_end_threshold - gapAfterBreak); + if (gapAfterBreak < BreakPeriod.GAP_AFTER_BREAK - leniency_threshold) + yield return new IssueTemplateLateEnd(this).Create(breakPeriod.StartTime, BreakPeriod.GAP_AFTER_BREAK - gapAfterBreak); } } } diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineBreak.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineBreak.cs index cec4b9b659..b9651ccd81 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineBreak.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineBreak.cs @@ -54,14 +54,14 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline Break = { BindTarget = Break }, Anchor = Anchor.TopLeft, Origin = Anchor.TopLeft, - Action = (time, breakPeriod) => breakPeriod with { StartTime = time }, + Action = (time, breakPeriod) => new ManualBreakPeriod(time, breakPeriod.EndTime), }, new DragHandle(isStartHandle: false) { Break = { BindTarget = Break }, Anchor = Anchor.TopRight, Origin = Anchor.TopRight, - Action = (time, breakPeriod) => breakPeriod with { EndTime = time }, + Action = (time, breakPeriod) => new ManualBreakPeriod(breakPeriod.StartTime, time), }, }; } diff --git a/osu.Game/Screens/Edit/EditorBeatmap.cs b/osu.Game/Screens/Edit/EditorBeatmap.cs index f4be987547..80586a923d 100644 --- a/osu.Game/Screens/Edit/EditorBeatmap.cs +++ b/osu.Game/Screens/Edit/EditorBeatmap.cs @@ -105,7 +105,7 @@ namespace osu.Game.Screens.Edit BeatmapSkin.BeatmapSkinChanged += SaveState; } - beatmapProcessor = playableBeatmap.BeatmapInfo.Ruleset.CreateInstance().CreateBeatmapProcessor(this); + beatmapProcessor = new EditorBeatmapProcessor(this, playableBeatmap.BeatmapInfo.Ruleset.CreateInstance()); foreach (var obj in HitObjects) trackStartTime(obj); diff --git a/osu.Game/Screens/Edit/EditorBeatmapProcessor.cs b/osu.Game/Screens/Edit/EditorBeatmapProcessor.cs new file mode 100644 index 0000000000..5b1cf281bb --- /dev/null +++ b/osu.Game/Screens/Edit/EditorBeatmapProcessor.cs @@ -0,0 +1,64 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using System.Linq; +using osu.Game.Beatmaps; +using osu.Game.Beatmaps.Timing; +using osu.Game.Rulesets; +using osu.Game.Rulesets.Objects; + +namespace osu.Game.Screens.Edit +{ + public class EditorBeatmapProcessor : IBeatmapProcessor + { + public IBeatmap Beatmap { get; } + + private readonly IBeatmapProcessor? rulesetBeatmapProcessor; + + public EditorBeatmapProcessor(IBeatmap beatmap, Ruleset ruleset) + { + Beatmap = beatmap; + rulesetBeatmapProcessor = ruleset.CreateBeatmapProcessor(beatmap); + } + + public void PreProcess() + { + rulesetBeatmapProcessor?.PreProcess(); + } + + public void PostProcess() + { + rulesetBeatmapProcessor?.PostProcess(); + + autoGenerateBreaks(); + } + + private void autoGenerateBreaks() + { + Beatmap.Breaks.RemoveAll(b => b is not ManualBreakPeriod); + + for (int i = 1; i < Beatmap.HitObjects.Count; ++i) + { + double previousObjectEndTime = Beatmap.HitObjects[i - 1].GetEndTime(); + double nextObjectStartTime = Beatmap.HitObjects[i].StartTime; + + if (nextObjectStartTime - previousObjectEndTime < BreakPeriod.MIN_GAP_DURATION) + continue; + + double breakStartTime = previousObjectEndTime + BreakPeriod.GAP_BEFORE_BREAK; + double breakEndTime = nextObjectStartTime - Math.Max(BreakPeriod.GAP_AFTER_BREAK, Beatmap.ControlPointInfo.TimingPointAt(nextObjectStartTime).BeatLength * 2); + + if (breakEndTime - breakStartTime < BreakPeriod.MIN_BREAK_DURATION) + continue; + + var breakPeriod = new BreakPeriod(breakStartTime, breakEndTime); + + if (Beatmap.Breaks.Any(b => b.Intersects(breakPeriod))) + continue; + + Beatmap.Breaks.Add(breakPeriod); + } + } + } +} diff --git a/osu.Game/Screens/Edit/ManualBreakPeriod.cs b/osu.Game/Screens/Edit/ManualBreakPeriod.cs new file mode 100644 index 0000000000..719784b500 --- /dev/null +++ b/osu.Game/Screens/Edit/ManualBreakPeriod.cs @@ -0,0 +1,15 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Game.Beatmaps.Timing; + +namespace osu.Game.Screens.Edit +{ + public record ManualBreakPeriod : BreakPeriod + { + public ManualBreakPeriod(double startTime, double endTime) + : base(startTime, endTime) + { + } + } +} From 58701b17f842b64c6824ed3c8806cef6565905ec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 19 Jun 2024 10:22:14 +0200 Subject: [PATCH 83/91] Add patcher support for breaks --- .../Edit/LegacyEditorBeatmapPatcher.cs | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/osu.Game/Screens/Edit/LegacyEditorBeatmapPatcher.cs b/osu.Game/Screens/Edit/LegacyEditorBeatmapPatcher.cs index bb9f702cb5..a1ee41fc48 100644 --- a/osu.Game/Screens/Edit/LegacyEditorBeatmapPatcher.cs +++ b/osu.Game/Screens/Edit/LegacyEditorBeatmapPatcher.cs @@ -45,6 +45,7 @@ namespace osu.Game.Screens.Edit editorBeatmap.BeginChange(); processHitObjects(result, () => newBeatmap ??= readBeatmap(newState)); processTimingPoints(() => newBeatmap ??= readBeatmap(newState)); + processBreaks(() => newBeatmap ??= readBeatmap(newState)); processHitObjectLocalData(() => newBeatmap ??= readBeatmap(newState)); editorBeatmap.EndChange(); } @@ -75,6 +76,27 @@ namespace osu.Game.Screens.Edit } } + private void processBreaks(Func getNewBeatmap) + { + var newBreaks = getNewBeatmap().Breaks.ToArray(); + + foreach (var oldBreak in editorBeatmap.Breaks.ToArray()) + { + if (newBreaks.Any(b => b.Equals(oldBreak))) + continue; + + editorBeatmap.Breaks.Remove(oldBreak); + } + + foreach (var newBreak in newBreaks) + { + if (editorBeatmap.Breaks.Any(b => b.Equals(newBreak))) + continue; + + editorBeatmap.Breaks.Add(newBreak); + } + } + private void processHitObjects(DiffResult result, Func getNewBeatmap) { findChangedIndices(result, LegacyDecoder.Section.HitObjects, out var removedIndices, out var addedIndices); From 7ed587b783ce5c69de2eceb36f37f36cbd8dae9c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 19 Jun 2024 10:26:01 +0200 Subject: [PATCH 84/91] Fix summary timeline not reloading properly on break addition/removal --- .../Timelines/Summary/Parts/BreakPart.cs | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/BreakPart.cs b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/BreakPart.cs index 41ecb44d9d..1ba552d646 100644 --- a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/BreakPart.cs +++ b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/BreakPart.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; +using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Shapes; using osu.Game.Beatmaps.Timing; @@ -14,11 +15,19 @@ namespace osu.Game.Screens.Edit.Components.Timelines.Summary.Parts /// public partial class BreakPart : TimelinePart { + private readonly BindableList breaks = new BindableList(); + protected override void LoadBeatmap(EditorBeatmap beatmap) { base.LoadBeatmap(beatmap); - foreach (var breakPeriod in beatmap.Breaks) - Add(new BreakVisualisation(breakPeriod)); + + breaks.UnbindAll(); + breaks.BindTo(beatmap.Breaks); + breaks.BindCollectionChanged((_, _) => + { + foreach (var breakPeriod in beatmap.Breaks) + Add(new BreakVisualisation(breakPeriod)); + }, true); } private partial class BreakVisualisation : Circle @@ -31,12 +40,6 @@ namespace osu.Game.Screens.Edit.Components.Timelines.Summary.Parts RelativePositionAxes = Axes.X; RelativeSizeAxes = Axes.Both; - } - - protected override void Update() - { - base.Update(); - X = (float)breakPeriod.StartTime; Width = (float)breakPeriod.Duration; } From 7311a7ffd711c46fd10b693c2ef960697fa8f8b2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 19 Jun 2024 10:51:37 +0200 Subject: [PATCH 85/91] Purge manual breaks if they intersect with an actual hitobject --- osu.Game/Screens/Edit/EditorBeatmapProcessor.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/osu.Game/Screens/Edit/EditorBeatmapProcessor.cs b/osu.Game/Screens/Edit/EditorBeatmapProcessor.cs index 5b1cf281bb..37bf915cec 100644 --- a/osu.Game/Screens/Edit/EditorBeatmapProcessor.cs +++ b/osu.Game/Screens/Edit/EditorBeatmapProcessor.cs @@ -38,6 +38,12 @@ namespace osu.Game.Screens.Edit { Beatmap.Breaks.RemoveAll(b => b is not ManualBreakPeriod); + foreach (var manualBreak in Beatmap.Breaks.ToList()) + { + if (Beatmap.HitObjects.Any(ho => ho.StartTime <= manualBreak.EndTime && ho.GetEndTime() >= manualBreak.StartTime)) + Beatmap.Breaks.Remove(manualBreak); + } + for (int i = 1; i < Beatmap.HitObjects.Count; ++i) { double previousObjectEndTime = Beatmap.HitObjects[i - 1].GetEndTime(); From 439079876157020272063e39d957590b0d5a6651 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 19 Jun 2024 11:14:38 +0200 Subject: [PATCH 86/91] Add test coverage for automatic break generation --- .../TestSceneEditorBeatmapProcessor.cs | 300 ++++++++++++++++++ 1 file changed, 300 insertions(+) create mode 100644 osu.Game.Tests/Editing/TestSceneEditorBeatmapProcessor.cs diff --git a/osu.Game.Tests/Editing/TestSceneEditorBeatmapProcessor.cs b/osu.Game.Tests/Editing/TestSceneEditorBeatmapProcessor.cs new file mode 100644 index 0000000000..02ce3815ec --- /dev/null +++ b/osu.Game.Tests/Editing/TestSceneEditorBeatmapProcessor.cs @@ -0,0 +1,300 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using NUnit.Framework; +using osu.Game.Beatmaps; +using osu.Game.Beatmaps.ControlPoints; +using osu.Game.Beatmaps.Timing; +using osu.Game.Rulesets.Osu; +using osu.Game.Rulesets.Osu.Objects; +using osu.Game.Screens.Edit; + +namespace osu.Game.Tests.Editing +{ + [TestFixture] + public class TestSceneEditorBeatmapProcessor + { + [Test] + public void TestEmptyBeatmap() + { + var controlPoints = new ControlPointInfo(); + controlPoints.Add(0, new TimingControlPoint { BeatLength = 500 }); + var beatmap = new Beatmap + { + ControlPointInfo = controlPoints, + }; + + var beatmapProcessor = new EditorBeatmapProcessor(beatmap, new OsuRuleset()); + beatmapProcessor.PreProcess(); + beatmapProcessor.PostProcess(); + + Assert.That(beatmap.Breaks, Is.Empty); + } + + [Test] + public void TestSingleObjectBeatmap() + { + var controlPoints = new ControlPointInfo(); + controlPoints.Add(0, new TimingControlPoint { BeatLength = 500 }); + var beatmap = new Beatmap + { + ControlPointInfo = controlPoints, + HitObjects = + { + new HitCircle { StartTime = 1000 }, + } + }; + + var beatmapProcessor = new EditorBeatmapProcessor(beatmap, new OsuRuleset()); + beatmapProcessor.PreProcess(); + beatmapProcessor.PostProcess(); + + Assert.That(beatmap.Breaks, Is.Empty); + } + + [Test] + public void TestTwoObjectsCloseTogether() + { + var controlPoints = new ControlPointInfo(); + controlPoints.Add(0, new TimingControlPoint { BeatLength = 500 }); + var beatmap = new Beatmap + { + ControlPointInfo = controlPoints, + HitObjects = + { + new HitCircle { StartTime = 1000 }, + new HitCircle { StartTime = 2000 }, + } + }; + + var beatmapProcessor = new EditorBeatmapProcessor(beatmap, new OsuRuleset()); + beatmapProcessor.PreProcess(); + beatmapProcessor.PostProcess(); + + Assert.That(beatmap.Breaks, Is.Empty); + } + + [Test] + public void TestTwoObjectsFarApart() + { + var controlPoints = new ControlPointInfo(); + controlPoints.Add(0, new TimingControlPoint { BeatLength = 500 }); + var beatmap = new Beatmap + { + ControlPointInfo = controlPoints, + HitObjects = + { + new HitCircle { StartTime = 1000 }, + new HitCircle { StartTime = 5000 }, + } + }; + + var beatmapProcessor = new EditorBeatmapProcessor(beatmap, new OsuRuleset()); + beatmapProcessor.PreProcess(); + beatmapProcessor.PostProcess(); + + Assert.Multiple(() => + { + Assert.That(beatmap.Breaks, Has.Count.EqualTo(1)); + Assert.That(beatmap.Breaks[0].StartTime, Is.EqualTo(1200)); + Assert.That(beatmap.Breaks[0].EndTime, Is.EqualTo(4000)); + }); + } + + [Test] + public void TestBreaksAreFused() + { + var controlPoints = new ControlPointInfo(); + controlPoints.Add(0, new TimingControlPoint { BeatLength = 500 }); + var beatmap = new Beatmap + { + ControlPointInfo = controlPoints, + HitObjects = + { + new HitCircle { StartTime = 1000 }, + new HitCircle { StartTime = 9000 }, + }, + Breaks = + { + new BreakPeriod(1200, 4000), + new BreakPeriod(5200, 8000), + } + }; + + var beatmapProcessor = new EditorBeatmapProcessor(beatmap, new OsuRuleset()); + beatmapProcessor.PreProcess(); + beatmapProcessor.PostProcess(); + + Assert.Multiple(() => + { + Assert.That(beatmap.Breaks, Has.Count.EqualTo(1)); + Assert.That(beatmap.Breaks[0].StartTime, Is.EqualTo(1200)); + Assert.That(beatmap.Breaks[0].EndTime, Is.EqualTo(8000)); + }); + } + + [Test] + public void TestBreaksAreSplit() + { + var controlPoints = new ControlPointInfo(); + controlPoints.Add(0, new TimingControlPoint { BeatLength = 500 }); + var beatmap = new Beatmap + { + ControlPointInfo = controlPoints, + HitObjects = + { + new HitCircle { StartTime = 1000 }, + new HitCircle { StartTime = 5000 }, + new HitCircle { StartTime = 9000 }, + }, + Breaks = + { + new BreakPeriod(1200, 8000), + } + }; + + var beatmapProcessor = new EditorBeatmapProcessor(beatmap, new OsuRuleset()); + beatmapProcessor.PreProcess(); + beatmapProcessor.PostProcess(); + + Assert.Multiple(() => + { + Assert.That(beatmap.Breaks, Has.Count.EqualTo(2)); + Assert.That(beatmap.Breaks[0].StartTime, Is.EqualTo(1200)); + Assert.That(beatmap.Breaks[0].EndTime, Is.EqualTo(4000)); + Assert.That(beatmap.Breaks[1].StartTime, Is.EqualTo(5200)); + Assert.That(beatmap.Breaks[1].EndTime, Is.EqualTo(8000)); + }); + } + + [Test] + public void TestBreaksAreNudged() + { + var controlPoints = new ControlPointInfo(); + controlPoints.Add(0, new TimingControlPoint { BeatLength = 500 }); + var beatmap = new Beatmap + { + ControlPointInfo = controlPoints, + HitObjects = + { + new HitCircle { StartTime = 1100 }, + new HitCircle { StartTime = 9000 }, + }, + Breaks = + { + new BreakPeriod(1200, 8000), + } + }; + + var beatmapProcessor = new EditorBeatmapProcessor(beatmap, new OsuRuleset()); + beatmapProcessor.PreProcess(); + beatmapProcessor.PostProcess(); + + Assert.Multiple(() => + { + Assert.That(beatmap.Breaks, Has.Count.EqualTo(1)); + Assert.That(beatmap.Breaks[0].StartTime, Is.EqualTo(1300)); + Assert.That(beatmap.Breaks[0].EndTime, Is.EqualTo(8000)); + }); + } + + [Test] + public void TestManualBreaksAreNotFused() + { + var controlPoints = new ControlPointInfo(); + controlPoints.Add(0, new TimingControlPoint { BeatLength = 500 }); + var beatmap = new Beatmap + { + ControlPointInfo = controlPoints, + HitObjects = + { + new HitCircle { StartTime = 1000 }, + new HitCircle { StartTime = 9000 }, + }, + Breaks = + { + new ManualBreakPeriod(1200, 4000), + new ManualBreakPeriod(5200, 8000), + } + }; + + var beatmapProcessor = new EditorBeatmapProcessor(beatmap, new OsuRuleset()); + beatmapProcessor.PreProcess(); + beatmapProcessor.PostProcess(); + + Assert.Multiple(() => + { + Assert.That(beatmap.Breaks, Has.Count.EqualTo(2)); + Assert.That(beatmap.Breaks[0].StartTime, Is.EqualTo(1200)); + Assert.That(beatmap.Breaks[0].EndTime, Is.EqualTo(4000)); + Assert.That(beatmap.Breaks[1].StartTime, Is.EqualTo(5200)); + Assert.That(beatmap.Breaks[1].EndTime, Is.EqualTo(8000)); + }); + } + + [Test] + public void TestManualBreaksAreSplit() + { + var controlPoints = new ControlPointInfo(); + controlPoints.Add(0, new TimingControlPoint { BeatLength = 500 }); + var beatmap = new Beatmap + { + ControlPointInfo = controlPoints, + HitObjects = + { + new HitCircle { StartTime = 1000 }, + new HitCircle { StartTime = 5000 }, + new HitCircle { StartTime = 9000 }, + }, + Breaks = + { + new ManualBreakPeriod(1200, 8000), + } + }; + + var beatmapProcessor = new EditorBeatmapProcessor(beatmap, new OsuRuleset()); + beatmapProcessor.PreProcess(); + beatmapProcessor.PostProcess(); + + Assert.Multiple(() => + { + Assert.That(beatmap.Breaks, Has.Count.EqualTo(2)); + Assert.That(beatmap.Breaks[0].StartTime, Is.EqualTo(1200)); + Assert.That(beatmap.Breaks[0].EndTime, Is.EqualTo(4000)); + Assert.That(beatmap.Breaks[1].StartTime, Is.EqualTo(5200)); + Assert.That(beatmap.Breaks[1].EndTime, Is.EqualTo(8000)); + }); + } + + [Test] + public void TestManualBreaksAreNotNudged() + { + var controlPoints = new ControlPointInfo(); + controlPoints.Add(0, new TimingControlPoint { BeatLength = 500 }); + var beatmap = new Beatmap + { + ControlPointInfo = controlPoints, + HitObjects = + { + new HitCircle { StartTime = 1000 }, + new HitCircle { StartTime = 9000 }, + }, + Breaks = + { + new ManualBreakPeriod(1200, 8800), + } + }; + + var beatmapProcessor = new EditorBeatmapProcessor(beatmap, new OsuRuleset()); + beatmapProcessor.PreProcess(); + beatmapProcessor.PostProcess(); + + Assert.Multiple(() => + { + Assert.That(beatmap.Breaks, Has.Count.EqualTo(1)); + Assert.That(beatmap.Breaks[0].StartTime, Is.EqualTo(1200)); + Assert.That(beatmap.Breaks[0].EndTime, Is.EqualTo(8800)); + }); + } + } +} From 2d9c3fbed24aed45f044db674b8ed6273ca9e9f8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 19 Jun 2024 11:21:57 +0200 Subject: [PATCH 87/91] Remove no-longer-necessary null propagation --- osu.Game/Screens/Edit/EditorBeatmap.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Edit/EditorBeatmap.cs b/osu.Game/Screens/Edit/EditorBeatmap.cs index 80586a923d..ae0fd9130f 100644 --- a/osu.Game/Screens/Edit/EditorBeatmap.cs +++ b/osu.Game/Screens/Edit/EditorBeatmap.cs @@ -349,13 +349,13 @@ namespace osu.Game.Screens.Edit if (batchPendingUpdates.Count == 0 && batchPendingDeletes.Count == 0 && batchPendingInserts.Count == 0) return; - beatmapProcessor?.PreProcess(); + beatmapProcessor.PreProcess(); foreach (var h in batchPendingDeletes) processHitObject(h); foreach (var h in batchPendingInserts) processHitObject(h); foreach (var h in batchPendingUpdates) processHitObject(h); - beatmapProcessor?.PostProcess(); + beatmapProcessor.PostProcess(); BeatmapReprocessed?.Invoke(); From 8757e08c2c98e23566af8dd60d7486142a9c8bd5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 19 Jun 2024 11:32:08 +0200 Subject: [PATCH 88/91] Fix test failures due to automatic break generation kicking in --- osu.Game.Tests/Visual/Editing/TestSceneEditorChangeStates.cs | 4 ++++ osu.Game/Tests/Beatmaps/TestBeatmap.cs | 4 +++- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneEditorChangeStates.cs b/osu.Game.Tests/Visual/Editing/TestSceneEditorChangeStates.cs index 278b6e9626..7827347b1f 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneEditorChangeStates.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneEditorChangeStates.cs @@ -4,10 +4,12 @@ #nullable disable using NUnit.Framework; +using osu.Game.Beatmaps; using osu.Game.Rulesets; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Osu.Objects; +using osu.Game.Tests.Beatmaps; namespace osu.Game.Tests.Visual.Editing { @@ -15,6 +17,8 @@ namespace osu.Game.Tests.Visual.Editing { protected override Ruleset CreateEditorRuleset() => new OsuRuleset(); + protected override IBeatmap CreateBeatmap(RulesetInfo ruleset) => new TestBeatmap(ruleset, false); + [Test] public void TestSelectedObjects() { diff --git a/osu.Game/Tests/Beatmaps/TestBeatmap.cs b/osu.Game/Tests/Beatmaps/TestBeatmap.cs index de7bcfcfaa..31ad2de62e 100644 --- a/osu.Game/Tests/Beatmaps/TestBeatmap.cs +++ b/osu.Game/Tests/Beatmaps/TestBeatmap.cs @@ -26,11 +26,13 @@ namespace osu.Game.Tests.Beatmaps BeatmapInfo = baseBeatmap.BeatmapInfo; ControlPointInfo = baseBeatmap.ControlPointInfo; - Breaks = baseBeatmap.Breaks; UnhandledEventLines = baseBeatmap.UnhandledEventLines; if (withHitObjects) + { HitObjects = baseBeatmap.HitObjects; + Breaks = baseBeatmap.Breaks; + } BeatmapInfo.Ruleset = ruleset; BeatmapInfo.Length = 75000; From 00a866b699403367fdf8de66e1a7e0e8e5f55af3 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 19 Jun 2024 20:30:43 +0800 Subject: [PATCH 89/91] Change colour to match bottom timeline (and adjust tween sligthly) --- .../Edit/Compose/Components/Timeline/TimelineBreak.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineBreak.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineBreak.cs index 785eba2042..ec963d08c9 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineBreak.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineBreak.cs @@ -44,7 +44,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline Child = new Box { RelativeSizeAxes = Axes.Both, - Colour = colours.GreyCarmineLight, + Colour = colours.PurpleLight, Alpha = 0.4f, }, }, @@ -204,12 +204,12 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline { bool active = IsHovered || IsDragged; - var colour = colours.GreyCarmineLighter; + var colour = colours.PurpleLighter; if (active) colour = colour.Lighten(0.3f); this.FadeColour(colour, 400, Easing.OutQuint); - handle.ResizeWidthTo(active ? 20 : 10, 400, Easing.OutElastic); + handle.ResizeWidthTo(active ? 20 : 10, 400, Easing.OutElasticHalf); } } } From 617c1341d722224862e6b0af8b375b61d290158e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 19 Jun 2024 14:53:09 +0200 Subject: [PATCH 90/91] Make `(Manual)BreakPeriod` a class again CodeFileSanity doesn't like records and it being a record wasn't doing much anymore anyway. --- osu.Game/Beatmaps/Timing/BreakPeriod.cs | 2 +- osu.Game/Screens/Edit/ManualBreakPeriod.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Beatmaps/Timing/BreakPeriod.cs b/osu.Game/Beatmaps/Timing/BreakPeriod.cs index 89f0fd6a55..d8b500227a 100644 --- a/osu.Game/Beatmaps/Timing/BreakPeriod.cs +++ b/osu.Game/Beatmaps/Timing/BreakPeriod.cs @@ -6,7 +6,7 @@ using osu.Game.Screens.Play; namespace osu.Game.Beatmaps.Timing { - public record BreakPeriod + public class BreakPeriod : IEquatable { /// /// The minimum gap between the start of the break and the previous object. diff --git a/osu.Game/Screens/Edit/ManualBreakPeriod.cs b/osu.Game/Screens/Edit/ManualBreakPeriod.cs index 719784b500..3ab77d84ce 100644 --- a/osu.Game/Screens/Edit/ManualBreakPeriod.cs +++ b/osu.Game/Screens/Edit/ManualBreakPeriod.cs @@ -5,7 +5,7 @@ using osu.Game.Beatmaps.Timing; namespace osu.Game.Screens.Edit { - public record ManualBreakPeriod : BreakPeriod + public class ManualBreakPeriod : BreakPeriod { public ManualBreakPeriod(double startTime, double endTime) : base(startTime, endTime) From a718af8af5eb8878d3237c728df66525fa72c2ac Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 19 Jun 2024 22:02:10 +0800 Subject: [PATCH 91/91] Adjust break colours to match closer to stable --- .../Edit/Components/Timelines/Summary/Parts/BreakPart.cs | 6 +----- .../Edit/Compose/Components/Timeline/TimelineBreak.cs | 6 +++--- 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/BreakPart.cs b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/BreakPart.cs index 1ba552d646..50062e8465 100644 --- a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/BreakPart.cs +++ b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/BreakPart.cs @@ -32,12 +32,8 @@ namespace osu.Game.Screens.Edit.Components.Timelines.Summary.Parts private partial class BreakVisualisation : Circle { - private readonly BreakPeriod breakPeriod; - public BreakVisualisation(BreakPeriod breakPeriod) { - this.breakPeriod = breakPeriod; - RelativePositionAxes = Axes.X; RelativeSizeAxes = Axes.Both; X = (float)breakPeriod.StartTime; @@ -45,7 +41,7 @@ namespace osu.Game.Screens.Edit.Components.Timelines.Summary.Parts } [BackgroundDependencyLoader] - private void load(OsuColour colours) => Colour = colours.GreyCarmineLight; + private void load(OsuColour colours) => Colour = colours.Gray7; } } } diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineBreak.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineBreak.cs index b1a26bbe14..608c2bdab1 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineBreak.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineBreak.cs @@ -45,8 +45,8 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline Child = new Box { RelativeSizeAxes = Axes.Both, - Colour = colours.PurpleLight, - Alpha = 0.4f, + Colour = colours.Gray5, + Alpha = 0.7f, }, }, new DragHandle(isStartHandle: true) @@ -212,7 +212,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline { bool active = IsHovered || IsDragged; - var colour = colours.PurpleLighter; + var colour = colours.Gray8; if (active) colour = colour.Lighten(0.3f);