From 6015b5037a4eb49e82c4323537e583e910ecef1e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 10 Sep 2021 13:44:39 +0900 Subject: [PATCH 01/12] Display difficulty and sample control points associated with hitobjects --- .../Timeline/DifficultyPointPiece.cs | 6 +- .../Timeline/HitObjectPointPiece.cs | 63 +++++++++++++++++ .../Components/Timeline/SamplePointPiece.cs | 68 +++---------------- .../Timeline/TimelineHitObjectBlueprint.cs | 40 +++++++++++ 4 files changed, 114 insertions(+), 63 deletions(-) create mode 100644 osu.Game/Screens/Edit/Compose/Components/Timeline/HitObjectPointPiece.cs diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/DifficultyPointPiece.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/DifficultyPointPiece.cs index cd1ef9127e..2a46667806 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/DifficultyPointPiece.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/DifficultyPointPiece.cs @@ -2,11 +2,12 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Bindables; +using osu.Framework.Graphics.Containers; using osu.Game.Beatmaps.ControlPoints; namespace osu.Game.Screens.Edit.Compose.Components.Timeline { - public class DifficultyPointPiece : TopPointPiece + public class DifficultyPointPiece : HitObjectPointPiece { private readonly BindableNumber speedMultiplier; @@ -14,13 +15,12 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline : base(point) { speedMultiplier = point.SliderVelocityBindable.GetBoundCopy(); - - Y = Height; } protected override void LoadComplete() { base.LoadComplete(); + speedMultiplier.BindValueChanged(multiplier => Label.Text = $"{multiplier.NewValue:n2}x", true); } } diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/HitObjectPointPiece.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/HitObjectPointPiece.cs new file mode 100644 index 0000000000..6b62459c97 --- /dev/null +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/HitObjectPointPiece.cs @@ -0,0 +1,63 @@ +// 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.ControlPoints; +using osu.Game.Graphics; +using osu.Game.Graphics.Sprites; +using osuTK.Graphics; + +namespace osu.Game.Screens.Edit.Compose.Components.Timeline +{ + public class HitObjectPointPiece : CircularContainer + { + private readonly ControlPoint point; + + protected OsuSpriteText Label { get; private set; } + + protected HitObjectPointPiece(ControlPoint point) + { + this.point = point; + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + AutoSizeAxes = Axes.Both; + + Color4 colour = point.GetRepresentingColour(colours); + + InternalChildren = new Drawable[] + { + new Container + { + AutoSizeAxes = Axes.X, + Height = 16, + Masking = true, + CornerRadius = 8, + Anchor = Anchor.BottomCentre, + Origin = Anchor.BottomCentre, + Children = new Drawable[] + { + new Box + { + Colour = colour, + RelativeSizeAxes = Axes.Both, + }, + Label = new OsuSpriteText + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Padding = new MarginPadding(5), + Font = OsuFont.Default.With(size: 12, weight: FontWeight.SemiBold), + Colour = colours.B5, + } + } + }, + }; + } + } +} diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs index 9461f5e885..40ee7055f1 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs @@ -3,29 +3,20 @@ using osu.Framework.Allocation; using osu.Framework.Bindables; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Shapes; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Graphics; -using osu.Game.Graphics.Sprites; -using osuTK.Graphics; namespace osu.Game.Screens.Edit.Compose.Components.Timeline { - public class SamplePointPiece : CompositeDrawable + public class SamplePointPiece : HitObjectPointPiece { private readonly SampleControlPoint samplePoint; private readonly Bindable bank; private readonly BindableNumber volume; - private OsuSpriteText text; - private Container volumeBox; - - private const int max_volume_height = 22; - public SamplePointPiece(SampleControlPoint samplePoint) + : base(samplePoint) { this.samplePoint = samplePoint; volume = samplePoint.SampleVolumeBindable.GetBoundCopy(); @@ -35,56 +26,13 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline [BackgroundDependencyLoader] private void load(OsuColour colours) { - Margin = new MarginPadding { Vertical = 5 }; + volume.BindValueChanged(volume => updateText()); + bank.BindValueChanged(bank => updateText(), true); + } - Origin = Anchor.BottomCentre; - Anchor = Anchor.BottomCentre; - - AutoSizeAxes = Axes.X; - RelativeSizeAxes = Axes.Y; - - Color4 colour = samplePoint.GetRepresentingColour(colours); - - InternalChildren = new Drawable[] - { - volumeBox = new Circle - { - CornerRadius = 5, - Anchor = Anchor.BottomCentre, - Origin = Anchor.BottomCentre, - Y = -20, - Width = 10, - Colour = colour, - }, - new Container - { - AutoSizeAxes = Axes.X, - Height = 16, - Masking = true, - CornerRadius = 8, - Anchor = Anchor.BottomCentre, - Origin = Anchor.BottomCentre, - Children = new Drawable[] - { - new Box - { - Colour = colour, - RelativeSizeAxes = Axes.Both, - }, - text = new OsuSpriteText - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Padding = new MarginPadding(5), - Font = OsuFont.Default.With(size: 12, weight: FontWeight.SemiBold), - Colour = colours.B5, - } - } - }, - }; - - volume.BindValueChanged(volume => volumeBox.Height = max_volume_height * volume.NewValue / 100f, true); - bank.BindValueChanged(bank => text.Text = bank.NewValue, true); + private void updateText() + { + Label.Text = $"{bank.Value} {volume.Value}"; } } } diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs index 911c9fea51..35a176a635 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs @@ -14,6 +14,7 @@ using osu.Framework.Graphics.Primitives; using osu.Framework.Graphics.Shapes; using osu.Framework.Input.Events; using osu.Framework.Utils; +using osu.Game.Beatmaps.ControlPoints; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Rulesets.Edit; @@ -179,6 +180,15 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline colouredComponents.Colour = OsuColour.ForegroundTextColourFor(col); } + private SamplePointPiece sampleOverrideDisplay; + private DifficultyPointPiece difficultyOverrideDisplay; + + [Resolved] + private EditorBeatmap beatmap { get; set; } + + private DifficultyControlPoint difficultyControlPoint; + private SampleControlPoint sampleControlPoint; + protected override void Update() { base.Update(); @@ -194,6 +204,36 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline if (Item is IHasRepeats repeats) updateRepeats(repeats); } + + if (difficultyControlPoint != Item.DifficultyControlPoint) + { + difficultyControlPoint = Item.DifficultyControlPoint; + difficultyOverrideDisplay?.Expire(); + + if (Item.DifficultyControlPoint != null && Item is IHasDistance) + { + AddInternal(difficultyOverrideDisplay = new DifficultyPointPiece(Item.DifficultyControlPoint) + { + Anchor = Anchor.TopLeft, + Origin = Anchor.BottomCentre + }); + } + } + + if (sampleControlPoint != Item.SampleControlPoint) + { + sampleControlPoint = Item.SampleControlPoint; + sampleOverrideDisplay?.Expire(); + + if (Item.SampleControlPoint != null) + { + AddInternal(sampleOverrideDisplay = new SamplePointPiece(Item.SampleControlPoint) + { + Anchor = Anchor.BottomLeft, + Origin = Anchor.TopCentre + }); + } + } } private void updateRepeats(IHasRepeats repeats) From b19dc5e41f60030fc89c413a46ffc9b79aa90f81 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 14 Sep 2021 18:26:10 +0900 Subject: [PATCH 02/12] Remove all legacy `ControlPoint`s when entering the editor --- osu.Game/Screens/Edit/EditorBeatmap.cs | 27 ++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/osu.Game/Screens/Edit/EditorBeatmap.cs b/osu.Game/Screens/Edit/EditorBeatmap.cs index 3402bf653a..669371c835 100644 --- a/osu.Game/Screens/Edit/EditorBeatmap.cs +++ b/osu.Game/Screens/Edit/EditorBeatmap.cs @@ -10,6 +10,7 @@ using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; +using osu.Game.Beatmaps.Legacy; using osu.Game.Beatmaps.Timing; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Objects; @@ -69,6 +70,32 @@ namespace osu.Game.Screens.Edit public EditorBeatmap(IBeatmap playableBeatmap, ISkin beatmapSkin = null) { PlayableBeatmap = playableBeatmap; + + // ensure we are not working with legacy control points. + // if we leave the legacy points around they will be applied over any local changes on + // ApplyDefaults calls. this should eventually be removed once the default logic is moved to the decoder/converter. + if (PlayableBeatmap.ControlPointInfo is LegacyControlPointInfo) + { + var newControlPoints = new ControlPointInfo(); + + foreach (var controlPoint in PlayableBeatmap.ControlPointInfo.AllControlPoints) + { + switch (controlPoint) + { + case DifficultyControlPoint _: + case SampleControlPoint _: + // skip legacy types. + continue; + + default: + newControlPoints.Add(controlPoint.Time, controlPoint); + break; + } + } + + playableBeatmap.ControlPointInfo = newControlPoints; + } + if (beatmapSkin is Skin skin) BeatmapSkin = new EditorBeatmapSkin(skin); From 87cfcf706e56606648c44f58365509d39f4b207f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 14 Sep 2021 18:25:58 +0900 Subject: [PATCH 03/12] Add ability to change slider velocity with shift-drag --- .../Timeline/TimelineHitObjectBlueprint.cs | 28 ++++++++++++++----- 1 file changed, 21 insertions(+), 7 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs index 35a176a635..9a9e981719 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs @@ -382,15 +382,29 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline switch (hitObject) { case IHasRepeats repeatHitObject: - // find the number of repeats which can fit in the requested time. - var lengthOfOneRepeat = repeatHitObject.Duration / (repeatHitObject.RepeatCount + 1); - var proposedCount = Math.Max(0, (int)Math.Round((time - hitObject.StartTime) / lengthOfOneRepeat) - 1); + double proposedDuration = time - hitObject.StartTime; - if (proposedCount == repeatHitObject.RepeatCount) - return; + if (e.CurrentState.Keyboard.ShiftPressed) + { + if (hitObject.DifficultyControlPoint == DifficultyControlPoint.DEFAULT) + hitObject.DifficultyControlPoint = new DifficultyControlPoint(); + + hitObject.DifficultyControlPoint.SliderVelocity *= (repeatHitObject.Duration / proposedDuration); + beatmap.Update(hitObject); + } + else + { + // find the number of repeats which can fit in the requested time. + var lengthOfOneRepeat = repeatHitObject.Duration / (repeatHitObject.RepeatCount + 1); + var proposedCount = Math.Max(0, (int)Math.Round(proposedDuration / lengthOfOneRepeat) - 1); + + if (proposedCount == repeatHitObject.RepeatCount) + return; + + repeatHitObject.RepeatCount = proposedCount; + beatmap.Update(hitObject); + } - repeatHitObject.RepeatCount = proposedCount; - beatmap.Update(hitObject); break; case IHasDuration endTimeHitObject: From 2114a4729c20edfd45144fb5631c20ed2029a450 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 10 Sep 2021 18:03:27 +0900 Subject: [PATCH 04/12] Set a sane default for new slider blueprints --- .../Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs index 14853c6ba4..07b6a1bdc2 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs @@ -8,12 +8,14 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Input; using osu.Framework.Input.Events; +using osu.Game.Beatmaps.ControlPoints; using osu.Game.Graphics; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles.Components; using osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components; +using osu.Game.Screens.Edit; using osuTK; using osuTK.Input; @@ -67,6 +69,9 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders inputManager = GetContainingInputManager(); } + [Resolved] + private EditorBeatmap editorBeatmap { get; set; } + public override void UpdateTimeAndPosition(SnapResult result) { base.UpdateTimeAndPosition(result); @@ -75,6 +80,10 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders { case SliderPlacementState.Initial: BeginPlacement(); + + var nearestDifficultyPoint = editorBeatmap.HitObjects.LastOrDefault(h => h.GetEndTime() < HitObject.StartTime)?.DifficultyControlPoint?.DeepClone() as DifficultyControlPoint; + + HitObject.DifficultyControlPoint = nearestDifficultyPoint ?? new DifficultyControlPoint(); HitObject.Position = ToLocalSpace(result.ScreenSpacePosition); break; From 9551e77553aa43b0928814e83916d0979ebde629 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 14 Sep 2021 18:28:56 +0900 Subject: [PATCH 05/12] Remove difficulty and sample sections from timing screen --- .../Timeline/TimelineControlPointGroup.cs | 8 --- .../Edit/Timing/ControlPointSettings.cs | 2 - .../Screens/Edit/Timing/DifficultySection.cs | 56 ------------------ osu.Game/Screens/Edit/Timing/SampleSection.cs | 57 ------------------- 4 files changed, 123 deletions(-) delete mode 100644 osu.Game/Screens/Edit/Timing/DifficultySection.cs delete mode 100644 osu.Game/Screens/Edit/Timing/SampleSection.cs diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineControlPointGroup.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineControlPointGroup.cs index c4beb40f92..2b2e66fb18 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineControlPointGroup.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineControlPointGroup.cs @@ -45,17 +45,9 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline { switch (point) { - case DifficultyControlPoint difficultyPoint: - AddInternal(new DifficultyPointPiece(difficultyPoint) { Depth = -2 }); - break; - case TimingControlPoint timingPoint: AddInternal(new TimingPointPiece(timingPoint)); break; - - case SampleControlPoint samplePoint: - AddInternal(new SamplePointPiece(samplePoint) { Depth = -1 }); - break; } } }, true); diff --git a/osu.Game/Screens/Edit/Timing/ControlPointSettings.cs b/osu.Game/Screens/Edit/Timing/ControlPointSettings.cs index 48639789af..938c7f9cf0 100644 --- a/osu.Game/Screens/Edit/Timing/ControlPointSettings.cs +++ b/osu.Game/Screens/Edit/Timing/ControlPointSettings.cs @@ -12,8 +12,6 @@ namespace osu.Game.Screens.Edit.Timing { new GroupSection(), new TimingSection(), - new DifficultySection(), - new SampleSection(), new EffectSection(), }; } diff --git a/osu.Game/Screens/Edit/Timing/DifficultySection.cs b/osu.Game/Screens/Edit/Timing/DifficultySection.cs deleted file mode 100644 index 52d38f19f6..0000000000 --- a/osu.Game/Screens/Edit/Timing/DifficultySection.cs +++ /dev/null @@ -1,56 +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.Allocation; -using osu.Framework.Bindables; -using osu.Game.Beatmaps.ControlPoints; -using osu.Game.Beatmaps.Legacy; - -namespace osu.Game.Screens.Edit.Timing -{ - internal class DifficultySection : Section - { - private SliderWithTextBoxInput sliderVelocitySlider; - - [BackgroundDependencyLoader] - private void load() - { - Flow.AddRange(new[] - { - sliderVelocitySlider = new SliderWithTextBoxInput("Slider Velocity") - { - Current = new DifficultyControlPoint().SliderVelocityBindable, - KeyboardStep = 0.1f - } - }); - } - - protected override void OnControlPointChanged(ValueChangedEvent point) - { - if (point.NewValue != null) - { - var selectedPointBindable = point.NewValue.SliderVelocityBindable; - - // there may be legacy control points, which contain infinite precision for compatibility reasons (see LegacyDifficultyControlPoint). - // generally that level of precision could only be set by externally editing the .osu file, so at the point - // a user is looking to update this within the editor it should be safe to obliterate this additional precision. - double expectedPrecision = new DifficultyControlPoint().SliderVelocityBindable.Precision; - if (selectedPointBindable.Precision < expectedPrecision) - selectedPointBindable.Precision = expectedPrecision; - - sliderVelocitySlider.Current = selectedPointBindable; - sliderVelocitySlider.Current.BindValueChanged(_ => ChangeHandler?.SaveState()); - } - } - - protected override DifficultyControlPoint CreatePoint() - { - var reference = (Beatmap.ControlPointInfo as LegacyControlPointInfo)?.DifficultyPointAt(SelectedGroup.Value.Time) ?? DifficultyControlPoint.DEFAULT; - - return new DifficultyControlPoint - { - SliderVelocity = reference.SliderVelocity, - }; - } - } -} diff --git a/osu.Game/Screens/Edit/Timing/SampleSection.cs b/osu.Game/Screens/Edit/Timing/SampleSection.cs deleted file mode 100644 index 96a67ab046..0000000000 --- a/osu.Game/Screens/Edit/Timing/SampleSection.cs +++ /dev/null @@ -1,57 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -using osu.Framework.Allocation; -using osu.Framework.Bindables; -using osu.Framework.Graphics; -using osu.Game.Beatmaps.ControlPoints; -using osu.Game.Beatmaps.Legacy; -using osu.Game.Graphics.UserInterfaceV2; - -namespace osu.Game.Screens.Edit.Timing -{ - internal class SampleSection : Section - { - private LabelledTextBox bank; - private SliderWithTextBoxInput volume; - - [BackgroundDependencyLoader] - private void load() - { - Flow.AddRange(new Drawable[] - { - bank = new LabelledTextBox - { - Label = "Bank Name", - }, - volume = new SliderWithTextBoxInput("Volume") - { - Current = new SampleControlPoint().SampleVolumeBindable, - } - }); - } - - protected override void OnControlPointChanged(ValueChangedEvent point) - { - if (point.NewValue != null) - { - bank.Current = point.NewValue.SampleBankBindable; - bank.Current.BindValueChanged(_ => ChangeHandler?.SaveState()); - - volume.Current = point.NewValue.SampleVolumeBindable; - volume.Current.BindValueChanged(_ => ChangeHandler?.SaveState()); - } - } - - protected override SampleControlPoint CreatePoint() - { - var reference = (Beatmap.ControlPointInfo as LegacyControlPointInfo)?.SamplePointAt(SelectedGroup.Value.Time) ?? SampleControlPoint.DEFAULT; - - return new SampleControlPoint - { - SampleBank = reference.SampleBank, - SampleVolume = reference.SampleVolume, - }; - } - } -} From 9d17f84681f3a5dcfe1239067ed18f5ffc93f264 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 14 Sep 2021 18:33:24 +0900 Subject: [PATCH 06/12] Adjust timeline height to account for less global control points --- osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs index 4296581480..b8fa05e7eb 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs @@ -59,7 +59,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline private Track track; private const float timeline_height = 72; - private const float timeline_expanded_height = 156; + private const float timeline_expanded_height = 94; public Timeline(Drawable userContent) { @@ -159,7 +159,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline if (visible.NewValue) { this.ResizeHeightTo(timeline_expanded_height, 200, Easing.OutQuint); - mainContent.MoveToY(36, 200, Easing.OutQuint); + mainContent.MoveToY(20, 200, Easing.OutQuint); // delay the fade in else masking looks weird. controlPoints.Delay(180).FadeIn(400, Easing.OutQuint); From e4dd59aee235cc280444a7d33a752bb9030a5c52 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 14 Sep 2021 18:51:22 +0900 Subject: [PATCH 07/12] Add popovers to adjust SV and samples from the timeline --- .../Timeline/DifficultyPointPiece.cs | 80 ++++++++++++++++++- .../Components/Timeline/SamplePointPiece.cs | 65 ++++++++++++++- .../Timeline/TimelineHitObjectBlueprint.cs | 2 +- 3 files changed, 141 insertions(+), 6 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/DifficultyPointPiece.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/DifficultyPointPiece.cs index 2a46667806..12757febc4 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/DifficultyPointPiece.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/DifficultyPointPiece.cs @@ -1,20 +1,33 @@ // 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.Extensions; +using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Cursor; +using osu.Framework.Graphics.UserInterface; +using osu.Framework.Input.Events; using osu.Game.Beatmaps.ControlPoints; +using osu.Game.Graphics.UserInterfaceV2; +using osu.Game.Rulesets.Objects; +using osu.Game.Screens.Edit.Timing; namespace osu.Game.Screens.Edit.Compose.Components.Timeline { - public class DifficultyPointPiece : HitObjectPointPiece + public class DifficultyPointPiece : HitObjectPointPiece, IHasPopover { + private readonly HitObject hitObject; + private readonly BindableNumber speedMultiplier; - public DifficultyPointPiece(DifficultyControlPoint point) - : base(point) + public DifficultyPointPiece(HitObject hitObject) + : base(hitObject.DifficultyControlPoint) { - speedMultiplier = point.SliderVelocityBindable.GetBoundCopy(); + this.hitObject = hitObject; + + speedMultiplier = hitObject.DifficultyControlPoint.SliderVelocityBindable.GetBoundCopy(); } protected override void LoadComplete() @@ -23,5 +36,64 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline speedMultiplier.BindValueChanged(multiplier => Label.Text = $"{multiplier.NewValue:n2}x", true); } + + protected override bool OnClick(ClickEvent e) + { + this.ShowPopover(); + return true; + } + + public Popover GetPopover() => new DifficultyEditPopover(hitObject); + + public class DifficultyEditPopover : OsuPopover + { + private readonly HitObject hitObject; + private readonly DifficultyControlPoint point; + + private SliderWithTextBoxInput sliderVelocitySlider; + + [Resolved(canBeNull: true)] + private EditorBeatmap beatmap { get; set; } + + public DifficultyEditPopover(HitObject hitObject) + { + this.hitObject = hitObject; + point = hitObject.DifficultyControlPoint; + } + + [BackgroundDependencyLoader] + private void load() + { + Children = new Drawable[] + { + new FillFlowContainer + { + Width = 200, + Direction = FillDirection.Vertical, + AutoSizeAxes = Axes.Y, + Children = new Drawable[] + { + sliderVelocitySlider = new SliderWithTextBoxInput("Slider Velocity") + { + Current = new DifficultyControlPoint().SliderVelocityBindable, + KeyboardStep = 0.1f + } + } + } + }; + + var selectedPointBindable = point.SliderVelocityBindable; + + // there may be legacy control points, which contain infinite precision for compatibility reasons (see LegacyDifficultyControlPoint). + // generally that level of precision could only be set by externally editing the .osu file, so at the point + // a user is looking to update this within the editor it should be safe to obliterate this additional precision. + double expectedPrecision = new DifficultyControlPoint().SliderVelocityBindable.Precision; + if (selectedPointBindable.Precision < expectedPrecision) + selectedPointBindable.Precision = expectedPrecision; + + sliderVelocitySlider.Current = selectedPointBindable; + sliderVelocitySlider.Current.BindValueChanged(_ => beatmap?.Update(hitObject)); + } + } } } diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs index 40ee7055f1..ee6c1b9fb5 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs @@ -3,12 +3,20 @@ using osu.Framework.Allocation; using osu.Framework.Bindables; +using osu.Framework.Extensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Cursor; +using osu.Framework.Graphics.UserInterface; +using osu.Framework.Input.Events; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Graphics; +using osu.Game.Graphics.UserInterfaceV2; +using osu.Game.Screens.Edit.Timing; namespace osu.Game.Screens.Edit.Compose.Components.Timeline { - public class SamplePointPiece : HitObjectPointPiece + public class SamplePointPiece : HitObjectPointPiece, IHasPopover { private readonly SampleControlPoint samplePoint; @@ -30,9 +38,64 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline bank.BindValueChanged(bank => updateText(), true); } + protected override bool OnClick(ClickEvent e) + { + this.ShowPopover(); + return true; + } + private void updateText() { Label.Text = $"{bank.Value} {volume.Value}"; } + + public Popover GetPopover() => new SampleEditPopover(samplePoint); + + public class SampleEditPopover : OsuPopover + { + private readonly SampleControlPoint point; + + private LabelledTextBox bank; + private SliderWithTextBoxInput volume; + + [Resolved(canBeNull: true)] + protected IEditorChangeHandler ChangeHandler { get; private set; } + + public SampleEditPopover(SampleControlPoint point) + { + this.point = point; + } + + [BackgroundDependencyLoader] + private void load() + { + Children = new Drawable[] + { + new FillFlowContainer + { + Width = 200, + Direction = FillDirection.Vertical, + AutoSizeAxes = Axes.Y, + Children = new Drawable[] + { + bank = new LabelledTextBox + { + Label = "Bank Name", + }, + volume = new SliderWithTextBoxInput("Volume") + { + Current = new SampleControlPoint().SampleVolumeBindable, + } + } + } + }; + + bank.Current = point.SampleBankBindable; + bank.Current.BindValueChanged(_ => ChangeHandler?.SaveState()); + + volume.Current = point.SampleVolumeBindable; + volume.Current.BindValueChanged(_ => ChangeHandler?.SaveState()); + } + } } } diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs index 9a9e981719..ec9cac03b1 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs @@ -212,7 +212,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline if (Item.DifficultyControlPoint != null && Item is IHasDistance) { - AddInternal(difficultyOverrideDisplay = new DifficultyPointPiece(Item.DifficultyControlPoint) + AddInternal(difficultyOverrideDisplay = new DifficultyPointPiece(Item) { Anchor = Anchor.TopLeft, Origin = Anchor.BottomCentre From d825da3983b11be7a3a45247bfa9cd1eb1644396 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 14 Sep 2021 18:54:21 +0900 Subject: [PATCH 08/12] Add note about a better way to adjust velocity --- .../Compose/Components/Timeline/DifficultyPointPiece.cs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/DifficultyPointPiece.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/DifficultyPointPiece.cs index 12757febc4..21457ea273 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/DifficultyPointPiece.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/DifficultyPointPiece.cs @@ -10,6 +10,7 @@ using osu.Framework.Graphics.Cursor; using osu.Framework.Graphics.UserInterface; using osu.Framework.Input.Events; using osu.Game.Beatmaps.ControlPoints; +using osu.Game.Graphics.Containers; using osu.Game.Graphics.UserInterfaceV2; using osu.Game.Rulesets.Objects; using osu.Game.Screens.Edit.Timing; @@ -73,10 +74,16 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline AutoSizeAxes = Axes.Y, Children = new Drawable[] { - sliderVelocitySlider = new SliderWithTextBoxInput("Slider Velocity") + sliderVelocitySlider = new SliderWithTextBoxInput("Velocity") { Current = new DifficultyControlPoint().SliderVelocityBindable, KeyboardStep = 0.1f + }, + new OsuTextFlowContainer + { + AutoSizeAxes = Axes.Y, + RelativeSizeAxes = Axes.X, + Text = "Hold shift while dragging the end of an object to adjust velocity while snapping." } } } From 563bf925291f2737ccf4260503253ade60374e01 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 14 Sep 2021 19:21:23 +0900 Subject: [PATCH 09/12] Also update the full object on sample changes to make them apply immediately --- .../Components/Timeline/SamplePointPiece.cs | 27 ++++++++++--------- .../Timeline/TimelineHitObjectBlueprint.cs | 2 +- 2 files changed, 16 insertions(+), 13 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs index ee6c1b9fb5..6a26f69e41 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs @@ -12,23 +12,24 @@ using osu.Framework.Input.Events; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Graphics; using osu.Game.Graphics.UserInterfaceV2; +using osu.Game.Rulesets.Objects; using osu.Game.Screens.Edit.Timing; namespace osu.Game.Screens.Edit.Compose.Components.Timeline { public class SamplePointPiece : HitObjectPointPiece, IHasPopover { - private readonly SampleControlPoint samplePoint; + private readonly HitObject hitObject; private readonly Bindable bank; private readonly BindableNumber volume; - public SamplePointPiece(SampleControlPoint samplePoint) - : base(samplePoint) + public SamplePointPiece(HitObject hitObject) + : base(hitObject.SampleControlPoint) { - this.samplePoint = samplePoint; - volume = samplePoint.SampleVolumeBindable.GetBoundCopy(); - bank = samplePoint.SampleBankBindable.GetBoundCopy(); + this.hitObject = hitObject; + volume = hitObject.SampleControlPoint.SampleVolumeBindable.GetBoundCopy(); + bank = hitObject.SampleControlPoint.SampleBankBindable.GetBoundCopy(); } [BackgroundDependencyLoader] @@ -49,21 +50,23 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline Label.Text = $"{bank.Value} {volume.Value}"; } - public Popover GetPopover() => new SampleEditPopover(samplePoint); + public Popover GetPopover() => new SampleEditPopover(hitObject); public class SampleEditPopover : OsuPopover { + private readonly HitObject hitObject; private readonly SampleControlPoint point; private LabelledTextBox bank; private SliderWithTextBoxInput volume; [Resolved(canBeNull: true)] - protected IEditorChangeHandler ChangeHandler { get; private set; } + private EditorBeatmap beatmap { get; set; } - public SampleEditPopover(SampleControlPoint point) + public SampleEditPopover(HitObject hitObject) { - this.point = point; + this.hitObject = hitObject; + point = hitObject.SampleControlPoint; } [BackgroundDependencyLoader] @@ -91,10 +94,10 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline }; bank.Current = point.SampleBankBindable; - bank.Current.BindValueChanged(_ => ChangeHandler?.SaveState()); + bank.Current.BindValueChanged(_ => beatmap.Update(hitObject)); volume.Current = point.SampleVolumeBindable; - volume.Current.BindValueChanged(_ => ChangeHandler?.SaveState()); + volume.Current.BindValueChanged(_ => beatmap.Update(hitObject)); } } } diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs index ec9cac03b1..a1b2878719 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs @@ -227,7 +227,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline if (Item.SampleControlPoint != null) { - AddInternal(sampleOverrideDisplay = new SamplePointPiece(Item.SampleControlPoint) + AddInternal(sampleOverrideDisplay = new SamplePointPiece(Item) { Anchor = Anchor.BottomLeft, Origin = Anchor.TopCentre From 13f88cbc4ebc1b6769544b494b9f48b641f8086d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 14 Sep 2021 23:56:57 +0900 Subject: [PATCH 10/12] Fix `EditorClock` retaining a reference to potentially outdated `ControlPointInfo` --- osu.Game/Screens/Edit/EditorClock.cs | 20 ++++++------------- osu.Game/Tests/Visual/EditorClockTestScene.cs | 5 ++--- 2 files changed, 8 insertions(+), 17 deletions(-) diff --git a/osu.Game/Screens/Edit/EditorClock.cs b/osu.Game/Screens/Edit/EditorClock.cs index ba83261731..86e5729196 100644 --- a/osu.Game/Screens/Edit/EditorClock.cs +++ b/osu.Game/Screens/Edit/EditorClock.cs @@ -25,7 +25,9 @@ namespace osu.Game.Screens.Edit public double TrackLength => track.Value?.Length ?? 60000; - public ControlPointInfo ControlPointInfo; + public ControlPointInfo ControlPointInfo => Beatmap.ControlPointInfo; + + public IBeatmap Beatmap { get; set; } private readonly BindableBeatDivisor beatDivisor; @@ -42,25 +44,15 @@ namespace osu.Game.Screens.Edit /// public bool IsSeeking { get; private set; } - public EditorClock(IBeatmap beatmap, BindableBeatDivisor beatDivisor) - : this(beatmap.ControlPointInfo, beatDivisor) + public EditorClock(IBeatmap beatmap = null, BindableBeatDivisor beatDivisor = null) { - } + Beatmap = beatmap ?? new Beatmap(); - public EditorClock(ControlPointInfo controlPointInfo, BindableBeatDivisor beatDivisor) - { - this.beatDivisor = beatDivisor; - - ControlPointInfo = controlPointInfo; + this.beatDivisor = beatDivisor ?? new BindableBeatDivisor(); underlyingClock = new DecoupleableInterpolatingFramedClock(); } - public EditorClock() - : this(new ControlPointInfo(), new BindableBeatDivisor()) - { - } - /// /// Seek to the closest snappable beat from a time. /// diff --git a/osu.Game/Tests/Visual/EditorClockTestScene.cs b/osu.Game/Tests/Visual/EditorClockTestScene.cs index 34393fba7d..c2e9892735 100644 --- a/osu.Game/Tests/Visual/EditorClockTestScene.cs +++ b/osu.Game/Tests/Visual/EditorClockTestScene.cs @@ -5,7 +5,6 @@ using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Input.Events; using osu.Game.Beatmaps; -using osu.Game.Beatmaps.ControlPoints; using osu.Game.Screens.Edit; namespace osu.Game.Tests.Visual @@ -23,7 +22,7 @@ namespace osu.Game.Tests.Visual protected EditorClockTestScene() { - Clock = new EditorClock(new ControlPointInfo(), BeatDivisor) { IsCoupled = false }; + Clock = new EditorClock(new Beatmap(), BeatDivisor) { IsCoupled = false }; } protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) @@ -44,7 +43,7 @@ namespace osu.Game.Tests.Visual private void beatmapChanged(ValueChangedEvent e) { - Clock.ControlPointInfo = e.NewValue.Beatmap.ControlPointInfo; + Clock.Beatmap = e.NewValue.Beatmap; Clock.ChangeSource(e.NewValue.Track); Clock.ProcessFrame(); } From 3909fd8caa26d0f1ba784508923f71db2a36d6ef Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 15 Oct 2021 19:43:57 +0900 Subject: [PATCH 11/12] Fix wonkiness when dragging slider end at high input refresh rates --- .../Components/Timeline/TimelineHitObjectBlueprint.cs | 7 ++++++- 1 file changed, 6 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 a1b2878719..e44a30ad02 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs @@ -389,7 +389,12 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline if (hitObject.DifficultyControlPoint == DifficultyControlPoint.DEFAULT) hitObject.DifficultyControlPoint = new DifficultyControlPoint(); - hitObject.DifficultyControlPoint.SliderVelocity *= (repeatHitObject.Duration / proposedDuration); + var newVelocity = hitObject.DifficultyControlPoint.SliderVelocity * (repeatHitObject.Duration / proposedDuration); + + if (Precision.AlmostEquals(newVelocity, hitObject.DifficultyControlPoint.SliderVelocity)) + return; + + hitObject.DifficultyControlPoint.SliderVelocity = newVelocity; beatmap.Update(hitObject); } else From c47497923ab6e98d4b9bd27cd30d5a52de430086 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 15 Oct 2021 19:52:29 +0900 Subject: [PATCH 12/12] Schedule drag events for now --- .../Timeline/TimelineHitObjectBlueprint.cs | 87 ++++++++++--------- 1 file changed, 48 insertions(+), 39 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs index e44a30ad02..e2458d45c9 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs @@ -13,6 +13,7 @@ using osu.Framework.Graphics.Effects; using osu.Framework.Graphics.Primitives; using osu.Framework.Graphics.Shapes; using osu.Framework.Input.Events; +using osu.Framework.Threading; using osu.Framework.Utils; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Graphics; @@ -371,58 +372,66 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline return true; } + private ScheduledDelegate dragOperation; + protected override void OnDrag(DragEvent e) { base.OnDrag(e); - OnDragHandled?.Invoke(e); - - if (timeline.SnapScreenSpacePositionToValidTime(e.ScreenSpaceMousePosition).Time is double time) + // schedule is temporary to ensure we don't process multiple times on a single update frame. we need to find a better method of doing this. + // without it, a hitobject's endtime may not always be in a valid state (ie. sliders, which needs to recompute their path). + dragOperation?.Cancel(); + dragOperation = Scheduler.Add(() => { - switch (hitObject) + OnDragHandled?.Invoke(e); + + if (timeline.SnapScreenSpacePositionToValidTime(e.ScreenSpaceMousePosition).Time is double time) { - case IHasRepeats repeatHitObject: - double proposedDuration = time - hitObject.StartTime; + switch (hitObject) + { + case IHasRepeats repeatHitObject: + double proposedDuration = time - hitObject.StartTime; - if (e.CurrentState.Keyboard.ShiftPressed) - { - if (hitObject.DifficultyControlPoint == DifficultyControlPoint.DEFAULT) - hitObject.DifficultyControlPoint = new DifficultyControlPoint(); + if (e.CurrentState.Keyboard.ShiftPressed) + { + if (hitObject.DifficultyControlPoint == DifficultyControlPoint.DEFAULT) + hitObject.DifficultyControlPoint = new DifficultyControlPoint(); - var newVelocity = hitObject.DifficultyControlPoint.SliderVelocity * (repeatHitObject.Duration / proposedDuration); + var newVelocity = hitObject.DifficultyControlPoint.SliderVelocity * (repeatHitObject.Duration / proposedDuration); - if (Precision.AlmostEquals(newVelocity, hitObject.DifficultyControlPoint.SliderVelocity)) + if (Precision.AlmostEquals(newVelocity, hitObject.DifficultyControlPoint.SliderVelocity)) + return; + + hitObject.DifficultyControlPoint.SliderVelocity = newVelocity; + beatmap.Update(hitObject); + } + else + { + // find the number of repeats which can fit in the requested time. + var lengthOfOneRepeat = repeatHitObject.Duration / (repeatHitObject.RepeatCount + 1); + var proposedCount = Math.Max(0, (int)Math.Round(proposedDuration / lengthOfOneRepeat) - 1); + + if (proposedCount == repeatHitObject.RepeatCount) + return; + + repeatHitObject.RepeatCount = proposedCount; + beatmap.Update(hitObject); + } + + break; + + case IHasDuration endTimeHitObject: + var snappedTime = Math.Max(hitObject.StartTime, beatSnapProvider.SnapTime(time)); + + if (endTimeHitObject.EndTime == snappedTime || Precision.AlmostEquals(snappedTime, hitObject.StartTime, beatmap.GetBeatLengthAtTime(snappedTime))) return; - hitObject.DifficultyControlPoint.SliderVelocity = newVelocity; + endTimeHitObject.Duration = snappedTime - hitObject.StartTime; beatmap.Update(hitObject); - } - else - { - // find the number of repeats which can fit in the requested time. - var lengthOfOneRepeat = repeatHitObject.Duration / (repeatHitObject.RepeatCount + 1); - var proposedCount = Math.Max(0, (int)Math.Round(proposedDuration / lengthOfOneRepeat) - 1); - - if (proposedCount == repeatHitObject.RepeatCount) - return; - - repeatHitObject.RepeatCount = proposedCount; - beatmap.Update(hitObject); - } - - break; - - case IHasDuration endTimeHitObject: - var snappedTime = Math.Max(hitObject.StartTime, beatSnapProvider.SnapTime(time)); - - if (endTimeHitObject.EndTime == snappedTime || Precision.AlmostEquals(snappedTime, hitObject.StartTime, beatmap.GetBeatLengthAtTime(snappedTime))) - return; - - endTimeHitObject.Duration = snappedTime - hitObject.StartTime; - beatmap.Update(hitObject); - break; + break; + } } - } + }); } protected override void OnDragEnd(DragEndEvent e)