From bd96cf94a6284f1d6b9be30e054fda5cfa6c4c62 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 21 Jan 2020 00:53:59 +0900 Subject: [PATCH 01/40] Begin refactoring SelectionBlueprint to handle non-drawable HitObjects --- .../Blueprints/ManiaSelectionBlueprint.cs | 2 +- .../Edit/ManiaBlueprintContainer.cs | 2 +- .../Edit/ManiaSelectionHandler.cs | 5 +- .../Edit/Masks/ManiaSelectionBlueprint.cs | 2 +- .../Edit/Blueprints/OsuSelectionBlueprint.cs | 4 +- .../Edit/OsuBlueprintContainer.cs | 2 +- .../Edit/OverlaySelectionBlueprint.cs | 32 ++++++++++++ osu.Game/Rulesets/Edit/SelectionBlueprint.cs | 35 +++++-------- .../Timelines/Summary/Parts/TimelinePart.cs | 20 +++++--- .../Compose/Components/BlueprintContainer.cs | 38 ++++---------- .../Components/ComposeBlueprintContainer.cs | 2 +- .../Compose/Components/MoveSelectionEvent.cs | 8 +-- .../Compose/Components/SelectionHandler.cs | 4 +- .../Timeline/TimelineHitObjectDisplay.cs | 51 ++++++------------- .../Visual/SelectionBlueprintTestScene.cs | 2 +- 15 files changed, 102 insertions(+), 107 deletions(-) create mode 100644 osu.Game/Rulesets/Edit/OverlaySelectionBlueprint.cs diff --git a/osu.Game.Rulesets.Mania/Edit/Blueprints/ManiaSelectionBlueprint.cs b/osu.Game.Rulesets.Mania/Edit/Blueprints/ManiaSelectionBlueprint.cs index 3bd7fb2d49..63e3d0fcc1 100644 --- a/osu.Game.Rulesets.Mania/Edit/Blueprints/ManiaSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Mania/Edit/Blueprints/ManiaSelectionBlueprint.cs @@ -13,7 +13,7 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints { - public class ManiaSelectionBlueprint : SelectionBlueprint + public class ManiaSelectionBlueprint : OverlaySelectionBlueprint { public Vector2 ScreenSpaceDragPosition { get; private set; } public Vector2 DragPosition { get; private set; } diff --git a/osu.Game.Rulesets.Mania/Edit/ManiaBlueprintContainer.cs b/osu.Game.Rulesets.Mania/Edit/ManiaBlueprintContainer.cs index 5f66ae7491..d744036b4c 100644 --- a/osu.Game.Rulesets.Mania/Edit/ManiaBlueprintContainer.cs +++ b/osu.Game.Rulesets.Mania/Edit/ManiaBlueprintContainer.cs @@ -17,7 +17,7 @@ public ManiaBlueprintContainer(IEnumerable drawableHitObjects { } - public override SelectionBlueprint CreateBlueprintFor(DrawableHitObject hitObject) + public override OverlaySelectionBlueprint CreateBlueprintFor(DrawableHitObject hitObject) { switch (hitObject) { diff --git a/osu.Game.Rulesets.Mania/Edit/ManiaSelectionHandler.cs b/osu.Game.Rulesets.Mania/Edit/ManiaSelectionHandler.cs index 618af3e772..f33fd3c640 100644 --- a/osu.Game.Rulesets.Mania/Edit/ManiaSelectionHandler.cs +++ b/osu.Game.Rulesets.Mania/Edit/ManiaSelectionHandler.cs @@ -5,6 +5,7 @@ using System.Linq; using osu.Framework.Allocation; using osu.Framework.Timing; +using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Mania.Edit.Blueprints; using osu.Game.Rulesets.Mania.Objects; using osu.Game.Rulesets.UI; @@ -72,8 +73,10 @@ private void performDragMovement(MoveSelectionEvent moveEvent) if (scrollingInfo.Direction.Value == ScrollingDirection.Down) delta -= moveEvent.Blueprint.DrawableObject.Parent.DrawHeight; - foreach (var b in SelectedBlueprints) + foreach (var selectionBlueprint in SelectedBlueprints) { + var b = (OverlaySelectionBlueprint)selectionBlueprint; + var hitObject = b.DrawableObject; var objectParent = (HitObjectContainer)hitObject.Parent; diff --git a/osu.Game.Rulesets.Mania/Edit/Masks/ManiaSelectionBlueprint.cs b/osu.Game.Rulesets.Mania/Edit/Masks/ManiaSelectionBlueprint.cs index ff8882124f..433db79ae0 100644 --- a/osu.Game.Rulesets.Mania/Edit/Masks/ManiaSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Mania/Edit/Masks/ManiaSelectionBlueprint.cs @@ -7,7 +7,7 @@ namespace osu.Game.Rulesets.Mania.Edit.Masks { - public abstract class ManiaSelectionBlueprint : SelectionBlueprint + public abstract class ManiaSelectionBlueprint : OverlaySelectionBlueprint { protected ManiaSelectionBlueprint(DrawableHitObject drawableObject) : base(drawableObject) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/OsuSelectionBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/OsuSelectionBlueprint.cs index a864257274..b0e13808a5 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/OsuSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/OsuSelectionBlueprint.cs @@ -7,10 +7,10 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints { - public abstract class OsuSelectionBlueprint : SelectionBlueprint + public abstract class OsuSelectionBlueprint : OverlaySelectionBlueprint where T : OsuHitObject { - protected T HitObject => (T)DrawableObject.HitObject; + protected new T HitObject => (T)DrawableObject.HitObject; protected OsuSelectionBlueprint(DrawableHitObject drawableObject) : base(drawableObject) diff --git a/osu.Game.Rulesets.Osu/Edit/OsuBlueprintContainer.cs b/osu.Game.Rulesets.Osu/Edit/OsuBlueprintContainer.cs index 30682616e6..330f34b85c 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuBlueprintContainer.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuBlueprintContainer.cs @@ -21,7 +21,7 @@ public OsuBlueprintContainer(IEnumerable drawableHitObjects) protected override SelectionHandler CreateSelectionHandler() => new OsuSelectionHandler(); - public override SelectionBlueprint CreateBlueprintFor(DrawableHitObject hitObject) + public override OverlaySelectionBlueprint CreateBlueprintFor(DrawableHitObject hitObject) { switch (hitObject) { diff --git a/osu.Game/Rulesets/Edit/OverlaySelectionBlueprint.cs b/osu.Game/Rulesets/Edit/OverlaySelectionBlueprint.cs new file mode 100644 index 0000000000..4c3898aa04 --- /dev/null +++ b/osu.Game/Rulesets/Edit/OverlaySelectionBlueprint.cs @@ -0,0 +1,32 @@ +// 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.Primitives; +using osu.Game.Graphics.UserInterface; +using osu.Game.Rulesets.Objects.Drawables; +using osuTK; + +namespace osu.Game.Rulesets.Edit +{ + public abstract class OverlaySelectionBlueprint : SelectionBlueprint + { + /// + /// The which this applies to. + /// + public readonly DrawableHitObject DrawableObject; + + protected override bool ShouldBeAlive => (DrawableObject.IsAlive && DrawableObject.IsPresent) || State == SelectionState.Selected; + + protected OverlaySelectionBlueprint(DrawableHitObject drawableObject) + : base(drawableObject.HitObject) + { + DrawableObject = drawableObject; + } + + public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => DrawableObject.ReceivePositionalInputAt(screenSpacePos); + + public override Vector2 SelectionPoint => DrawableObject.ScreenSpaceDrawQuad.Centre; + + public override Quad SelectionQuad => DrawableObject.ScreenSpaceDrawQuad; + } +} diff --git a/osu.Game/Rulesets/Edit/SelectionBlueprint.cs b/osu.Game/Rulesets/Edit/SelectionBlueprint.cs index bf99f83e0b..d8952a3932 100644 --- a/osu.Game/Rulesets/Edit/SelectionBlueprint.cs +++ b/osu.Game/Rulesets/Edit/SelectionBlueprint.cs @@ -1,4 +1,4 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. using System; @@ -20,6 +20,8 @@ namespace osu.Game.Rulesets.Edit /// public abstract class SelectionBlueprint : CompositeDrawable, IStateful { + public readonly HitObject HitObject; + /// /// Invoked when this has been selected. /// @@ -30,22 +32,15 @@ public abstract class SelectionBlueprint : CompositeDrawable, IStateful public event Action Deselected; - /// - /// The which this applies to. - /// - public readonly DrawableHitObject DrawableObject; - - protected override bool ShouldBeAlive => (DrawableObject.IsAlive && DrawableObject.IsPresent) || State == SelectionState.Selected; public override bool HandlePositionalInput => ShouldBeAlive; public override bool RemoveWhenNotAlive => false; [Resolved(CanBeNull = true)] private HitObjectComposer composer { get; set; } - protected SelectionBlueprint(DrawableHitObject drawableObject) + protected SelectionBlueprint(HitObject hitObject) { - DrawableObject = drawableObject; - + this.HitObject = hitObject; RelativeSizeAxes = Axes.Both; AlwaysPresent = true; @@ -87,37 +82,35 @@ public SelectionState State protected override bool ShouldBeConsideredForInput(Drawable child) => State == SelectionState.Selected; /// - /// Selects this , causing it to become visible. + /// Selects this , causing it to become visible. /// public void Select() => State = SelectionState.Selected; /// - /// Deselects this , causing it to become invisible. + /// Deselects this , causing it to become invisible. /// public void Deselect() => State = SelectionState.NotSelected; public bool IsSelected => State == SelectionState.Selected; /// - /// Updates the , invoking and re-processing the beatmap. + /// Updates the , invoking and re-processing the beatmap. /// - protected void UpdateHitObject() => composer?.UpdateHitObject(DrawableObject.HitObject); - - public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => DrawableObject.ReceivePositionalInputAt(screenSpacePos); + protected void UpdateHitObject() => composer?.UpdateHitObject(HitObject); /// - /// The s to be displayed in the context menu for this . + /// The s to be displayed in the context menu for this . /// public virtual MenuItem[] ContextMenuItems => Array.Empty(); /// - /// The screen-space point that causes this to be selected. + /// The screen-space point that causes this to be selected. /// - public virtual Vector2 SelectionPoint => DrawableObject.ScreenSpaceDrawQuad.Centre; + public virtual Vector2 SelectionPoint => ScreenSpaceDrawQuad.Centre; /// - /// The screen-space quad that outlines this for selections. + /// The screen-space quad that outlines this for selections. /// - public virtual Quad SelectionQuad => DrawableObject.ScreenSpaceDrawQuad; + public virtual Quad SelectionQuad => ScreenSpaceDrawQuad; } } diff --git a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/TimelinePart.cs b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/TimelinePart.cs index 119635ccd5..4a7c3f26bc 100644 --- a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/TimelinePart.cs +++ b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/TimelinePart.cs @@ -11,20 +11,24 @@ namespace osu.Game.Screens.Edit.Components.Timelines.Summary.Parts { + public class TimelinePart : TimelinePart + { + } + /// /// Represents a part of the summary timeline.. /// - public class TimelinePart : Container + public class TimelinePart : Container where T : Drawable { protected readonly IBindable Beatmap = new Bindable(); - private readonly Container timeline; + private readonly Container content; - protected override Container Content => timeline; + protected override Container Content => content; - public TimelinePart() + public TimelinePart(Container content = null) { - AddInternal(timeline = new Container { RelativeSizeAxes = Axes.Both }); + AddInternal(this.content = content ?? new Container { RelativeSizeAxes = Axes.Both }); Beatmap.ValueChanged += b => { @@ -44,17 +48,17 @@ private void updateRelativeChildSize() // the track may not be loaded completely (only has a length once it is). if (!Beatmap.Value.Track.IsLoaded) { - timeline.RelativeChildSize = Vector2.One; + content.RelativeChildSize = Vector2.One; Schedule(updateRelativeChildSize); return; } - timeline.RelativeChildSize = new Vector2((float)Math.Max(1, Beatmap.Value.Track.Length), 1); + content.RelativeChildSize = new Vector2((float)Math.Max(1, Beatmap.Value.Track.Length), 1); } protected virtual void LoadBeatmap(WorkingBeatmap beatmap) { - timeline.Clear(); + content.Clear(); } } } diff --git a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs index 81b0fb247f..e945955db6 100644 --- a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs @@ -59,7 +59,7 @@ private void load() { DragBox = CreateDragBox(select), selectionHandler, - selectionBlueprints = new SelectionBlueprintContainer { RelativeSizeAxes = Axes.Both }, + selectionBlueprints = CreateSelectionBlueprintContainer(), DragBox.CreateProxy().With(p => p.Depth = float.MinValue) }); @@ -67,6 +67,8 @@ private void load() AddBlueprintFor(obj); } + protected virtual SelectionBlueprintContainer CreateSelectionBlueprintContainer() => new SelectionBlueprintContainer { RelativeSizeAxes = Axes.Both }; + protected override void LoadComplete() { base.LoadComplete(); @@ -118,7 +120,7 @@ protected override bool OnDoubleClick(DoubleClickEvent e) if (clickedBlueprint == null) return false; - adjustableClock?.Seek(clickedBlueprint.DrawableObject.HitObject.StartTime); + adjustableClock?.Seek(clickedBlueprint.HitObject.StartTime); return true; } @@ -208,7 +210,7 @@ public bool OnPressed(PlatformAction action) private void removeBlueprintFor(HitObject hitObject) { - var blueprint = selectionBlueprints.SingleOrDefault(m => m.DrawableObject.HitObject == hitObject); + var blueprint = selectionBlueprints.SingleOrDefault(m => m.HitObject == hitObject); if (blueprint == null) return; @@ -346,8 +348,8 @@ private bool beginSelectionMovement() return false; // Movement is tracked from the blueprint of the earliest hitobject, since it only makes sense to distance snap from that hitobject - movementBlueprint = selectionHandler.SelectedBlueprints.OrderBy(b => b.DrawableObject.HitObject.StartTime).First(); - screenSpaceMovementStartPosition = movementBlueprint.DrawableObject.ToScreenSpace(movementBlueprint.DrawableObject.OriginPosition); + movementBlueprint = selectionHandler.SelectedBlueprints.OrderBy(b => b.HitObject.StartTime).First(); + screenSpaceMovementStartPosition = movementBlueprint.SelectionPoint; // todo: unsure if correct return true; } @@ -365,14 +367,14 @@ private bool moveCurrentSelection(DragEvent e) Debug.Assert(screenSpaceMovementStartPosition != null); Vector2 startPosition = screenSpaceMovementStartPosition.Value; - HitObject draggedObject = movementBlueprint.DrawableObject.HitObject; + HitObject draggedObject = movementBlueprint.HitObject; // The final movement position, relative to screenSpaceMovementStartPosition Vector2 movePosition = startPosition + e.ScreenSpaceMousePosition - e.ScreenSpaceMouseDownPosition; (Vector2 snappedPosition, double snappedTime) = snapProvider.GetSnappedPosition(ToLocalSpace(movePosition), draggedObject.StartTime); // Move the hitobjects - if (!selectionHandler.HandleMovement(new MoveSelectionEvent(movementBlueprint, startPosition, ToScreenSpace(snappedPosition)))) + if (!selectionHandler.HandleMovement(new MoveSelectionEvent((OverlaySelectionBlueprint)movementBlueprint, startPosition, ToScreenSpace(snappedPosition)))) return true; // Apply the start time at the newly snapped-to position @@ -411,29 +413,9 @@ protected override void Dispose(bool isDisposing) } } - private class SelectionBlueprintContainer : Container + protected class SelectionBlueprintContainer : Container { public IEnumerable AliveBlueprints => AliveInternalChildren.Cast(); - - protected override int Compare(Drawable x, Drawable y) - { - if (!(x is SelectionBlueprint xBlueprint) || !(y is SelectionBlueprint yBlueprint)) - return base.Compare(x, y); - - return Compare(xBlueprint, yBlueprint); - } - - public int Compare(SelectionBlueprint x, SelectionBlueprint y) - { - // dpeth is used to denote selected status (we always want selected blueprints to handle input first). - int d = x.Depth.CompareTo(y.Depth); - if (d != 0) - return d; - - // Put earlier hitobjects towards the end of the list, so they handle input first - int i = y.DrawableObject.HitObject.StartTime.CompareTo(x.DrawableObject.HitObject.StartTime); - return i == 0 ? CompareReverseChildID(x, y) : i; - } } } } diff --git a/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs index 1576def38e..3c41dead5d 100644 --- a/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs @@ -119,7 +119,7 @@ protected sealed override SelectionBlueprint CreateBlueprintFor(HitObject hitObj return CreateBlueprintFor(drawable); } - public virtual SelectionBlueprint CreateBlueprintFor(DrawableHitObject hitObject) => null; + public virtual OverlaySelectionBlueprint CreateBlueprintFor(DrawableHitObject hitObject) => null; protected override void AddBlueprintFor(HitObject hitObject) { diff --git a/osu.Game/Screens/Edit/Compose/Components/MoveSelectionEvent.cs b/osu.Game/Screens/Edit/Compose/Components/MoveSelectionEvent.cs index fe0a47aec8..8662347aeb 100644 --- a/osu.Game/Screens/Edit/Compose/Components/MoveSelectionEvent.cs +++ b/osu.Game/Screens/Edit/Compose/Components/MoveSelectionEvent.cs @@ -7,14 +7,14 @@ namespace osu.Game.Screens.Edit.Compose.Components { /// - /// An event which occurs when a is moved. + /// An event which occurs when a is moved. /// public class MoveSelectionEvent { /// - /// The that triggered this . + /// The that triggered this . /// - public readonly SelectionBlueprint Blueprint; + public readonly OverlaySelectionBlueprint Blueprint; /// /// The starting screen-space position of the hitobject. @@ -34,7 +34,7 @@ public class MoveSelectionEvent /// public readonly Vector2 InstantDelta; - public MoveSelectionEvent(SelectionBlueprint blueprint, Vector2 screenSpaceStartPosition, Vector2 screenSpacePosition) + public MoveSelectionEvent(OverlaySelectionBlueprint blueprint, Vector2 screenSpaceStartPosition, Vector2 screenSpacePosition) { Blueprint = blueprint; ScreenSpaceStartPosition = screenSpaceStartPosition; diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs index bff94e66ed..907a22b9ce 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs @@ -33,7 +33,7 @@ public class SelectionHandler : CompositeDrawable, IKeyBindingHandler SelectedBlueprints => selectedBlueprints; private readonly List selectedBlueprints; - public IEnumerable SelectedHitObjects => selectedBlueprints.Select(b => b.DrawableObject.HitObject); + public IEnumerable SelectedHitObjects => selectedBlueprints.Select(b => b.HitObject); private Drawable outline; @@ -146,7 +146,7 @@ internal void HandleSelectionRequested(SelectionBlueprint blueprint, InputState private void deleteSelected() { foreach (var h in selectedBlueprints.ToList()) - placementHandler.Delete(h.DrawableObject.HitObject); + placementHandler.Delete(h.HitObject); } #endregion diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectDisplay.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectDisplay.cs index f521d08ada..e1acaa34cb 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectDisplay.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectDisplay.cs @@ -2,13 +2,12 @@ // See the LICENCE file in the repository root for full licence text. using System; -using System.Linq; -using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Primitives; using osu.Framework.Graphics.Shapes; using osu.Framework.Input.Events; +using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Types; using osu.Game.Screens.Edit.Components.Timelines.Summary.Parts; @@ -19,32 +18,21 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline { internal class TimelineHitObjectDisplay : BlueprintContainer { - private EditorBeatmap beatmap { get; } - - private readonly TimelinePart content; - public TimelineHitObjectDisplay(EditorBeatmap beatmap) { RelativeSizeAxes = Axes.Both; - - this.beatmap = beatmap; - - AddInternal(content = new TimelinePart { RelativeSizeAxes = Axes.Both }); } - [BackgroundDependencyLoader] - private void load() - { - foreach (var h in beatmap.HitObjects) - add(h); + protected override SelectionBlueprintContainer CreateSelectionBlueprintContainer() => new TimelineSelectionBlueprintContainer { RelativeSizeAxes = Axes.Both }; - beatmap.HitObjectAdded += add; - beatmap.HitObjectRemoved += remove; - beatmap.StartTimeChanged += h => + protected class TimelineSelectionBlueprintContainer : SelectionBlueprintContainer + { + protected override Container Content { get; } + + public TimelineSelectionBlueprintContainer() { - remove(h); - add(h); - }; + AddInternal(new TimelinePart(Content = new Container { RelativeSizeAxes = Axes.Both }) { RelativeSizeAxes = Axes.Both }); + } } protected override void LoadComplete() @@ -53,24 +41,19 @@ protected override void LoadComplete() DragBox.Alpha = 0; } - private void remove(HitObject h) + protected override SelectionBlueprint CreateBlueprintFor(HitObject hitObject) { - foreach (var d in content.OfType().Where(c => c.HitObject == h)) - d.Expire(); - } + //var yOffset = content.Count(d => d.X == h.StartTime); + var yOffset = 0; - private void add(HitObject h) - { - var yOffset = content.Count(d => d.X == h.StartTime); - - content.Add(new TimelineHitObjectRepresentation(h) { Y = -yOffset * TimelineHitObjectRepresentation.THICKNESS }); + return new TimelineHitObjectRepresentation(hitObject) { Y = -yOffset * TimelineHitObjectRepresentation.THICKNESS }; } protected override bool OnMouseDown(MouseDownEvent e) { base.OnMouseDown(e); - return false; // tempoerary until we correctly handle selections. + return false; // temporary until we correctly handle selections. } protected override DragBox CreateDragBox(Action performSelect) => new NoDragDragBox(performSelect); @@ -85,15 +68,13 @@ public NoDragDragBox(Action performSelect) public override bool UpdateDrag(MouseButtonEvent e) => false; } - private class TimelineHitObjectRepresentation : CompositeDrawable + private class TimelineHitObjectRepresentation : SelectionBlueprint { public const float THICKNESS = 3; - public readonly HitObject HitObject; - public TimelineHitObjectRepresentation(HitObject hitObject) + : base(hitObject) { - HitObject = hitObject; Anchor = Anchor.CentreLeft; Origin = Anchor.CentreLeft; diff --git a/osu.Game/Tests/Visual/SelectionBlueprintTestScene.cs b/osu.Game/Tests/Visual/SelectionBlueprintTestScene.cs index 3233ee160d..6565f98666 100644 --- a/osu.Game/Tests/Visual/SelectionBlueprintTestScene.cs +++ b/osu.Game/Tests/Visual/SelectionBlueprintTestScene.cs @@ -22,7 +22,7 @@ protected SelectionBlueprintTestScene() }); } - protected void AddBlueprint(SelectionBlueprint blueprint) + protected void AddBlueprint(OverlaySelectionBlueprint blueprint) { Add(blueprint.With(d => { From 53fe0ce79086351260002e3162baccf63840c17d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 21 Jan 2020 14:20:52 +0900 Subject: [PATCH 02/40] Use AliveChildren --- .../Screens/Edit/Compose/Components/BlueprintContainer.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs index e945955db6..438be6ab54 100644 --- a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs @@ -258,7 +258,7 @@ private void beginClickSelection(MouseButtonEvent e) if (!allowDeselection && selectionHandler.SelectedBlueprints.Any(s => s.IsHovered)) return; - foreach (SelectionBlueprint blueprint in selectionBlueprints.AliveBlueprints) + foreach (SelectionBlueprint blueprint in selectionBlueprints.AliveChildren) { if (blueprint.IsHovered) { @@ -415,7 +415,7 @@ protected override void Dispose(bool isDisposing) protected class SelectionBlueprintContainer : Container { - public IEnumerable AliveBlueprints => AliveInternalChildren.Cast(); + //todo: remove } } } From 8f16c1cb049813cce2e1dabc07aa52e44e894038 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 21 Jan 2020 14:21:00 +0900 Subject: [PATCH 03/40] Add non-hiding selection state --- osu.Game/Rulesets/Edit/SelectionBlueprint.cs | 10 +++++++--- .../Components/Timeline/TimelineHitObjectDisplay.cs | 8 +++++++- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/osu.Game/Rulesets/Edit/SelectionBlueprint.cs b/osu.Game/Rulesets/Edit/SelectionBlueprint.cs index d8952a3932..d50a9dce16 100644 --- a/osu.Game/Rulesets/Edit/SelectionBlueprint.cs +++ b/osu.Game/Rulesets/Edit/SelectionBlueprint.cs @@ -44,7 +44,7 @@ protected SelectionBlueprint(HitObject hitObject) RelativeSizeAxes = Axes.Both; AlwaysPresent = true; - Alpha = 0; + OnDeselected(); } private SelectionState state; @@ -64,12 +64,12 @@ public SelectionState State switch (state) { case SelectionState.Selected: - Show(); + OnSelected(); Selected?.Invoke(this); break; case SelectionState.NotSelected: - Hide(); + OnDeselected(); Deselected?.Invoke(this); break; } @@ -78,6 +78,10 @@ public SelectionState State } } + protected virtual void OnDeselected() => Hide(); + + protected virtual void OnSelected() => Show(); + // When not selected, input is only required for the blueprint itself to receive IsHovering protected override bool ShouldBeConsideredForInput(Drawable child) => State == SelectionState.Selected; diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectDisplay.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectDisplay.cs index e1acaa34cb..5d1c4eeeae 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectDisplay.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectDisplay.cs @@ -70,6 +70,8 @@ public NoDragDragBox(Action performSelect) private class TimelineHitObjectRepresentation : SelectionBlueprint { + private Circle circle; + public const float THICKNESS = 3; public TimelineHitObjectRepresentation(HitObject hitObject) @@ -104,7 +106,7 @@ public TimelineHitObjectRepresentation(HitObject hitObject) }); } - AddInternal(new Circle + AddInternal(circle = new Circle { Size = new Vector2(16), Anchor = Anchor.CentreLeft, @@ -116,6 +118,10 @@ public TimelineHitObjectRepresentation(HitObject hitObject) BorderThickness = THICKNESS, }); } + + protected override void OnSelected() => circle.BorderColour = Color4.Orange; + + protected override void OnDeselected() => circle.BorderColour = Color4.Black; } } } From 195068ba9a9f6d35708159b2276ec74e70568456 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 21 Jan 2020 17:35:36 +0900 Subject: [PATCH 04/40] Cache EditorBeatmap in test --- osu.Game.Tests/Visual/Editor/TestSceneEditorComposeTimeline.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game.Tests/Visual/Editor/TestSceneEditorComposeTimeline.cs b/osu.Game.Tests/Visual/Editor/TestSceneEditorComposeTimeline.cs index 29575cb42e..e9372bd134 100644 --- a/osu.Game.Tests/Visual/Editor/TestSceneEditorComposeTimeline.cs +++ b/osu.Game.Tests/Visual/Editor/TestSceneEditorComposeTimeline.cs @@ -40,6 +40,8 @@ private void load(AudioManager audio) var editorBeatmap = new EditorBeatmap((Beatmap)Beatmap.Value.Beatmap); + Dependencies.Cache(editorBeatmap); + Children = new Drawable[] { new FillFlowContainer From 24a466ab249e6a88790a9f067f34ce44388e4521 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 21 Jan 2020 17:36:21 +0900 Subject: [PATCH 05/40] Avoid null by calling initial OnDeselected later --- osu.Game/Rulesets/Edit/SelectionBlueprint.cs | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/osu.Game/Rulesets/Edit/SelectionBlueprint.cs b/osu.Game/Rulesets/Edit/SelectionBlueprint.cs index d50a9dce16..1c82a8287b 100644 --- a/osu.Game/Rulesets/Edit/SelectionBlueprint.cs +++ b/osu.Game/Rulesets/Edit/SelectionBlueprint.cs @@ -40,11 +40,15 @@ public abstract class SelectionBlueprint : CompositeDrawable, IStateful for selections. /// public virtual Quad SelectionQuad => ScreenSpaceDrawQuad; + + public Vector2 GetInstantDelta(Vector2 screenSpacePosition) => Parent.ToLocalSpace(screenSpacePosition) - Position; } } From 79351976d5bc3eed44859e1a2f357d0f1b59f8e8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 21 Jan 2020 17:37:05 +0900 Subject: [PATCH 06/40] Allow timeline content to get more localised dependencies --- osu.Game/Screens/Edit/EditorScreenWithTimeline.cs | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/osu.Game/Screens/Edit/EditorScreenWithTimeline.cs b/osu.Game/Screens/Edit/EditorScreenWithTimeline.cs index aa8d99b517..8967f24185 100644 --- a/osu.Game/Screens/Edit/EditorScreenWithTimeline.cs +++ b/osu.Game/Screens/Edit/EditorScreenWithTimeline.cs @@ -20,7 +20,7 @@ public abstract class EditorScreenWithTimeline : EditorScreen private readonly BindableBeatDivisor beatDivisor = new BindableBeatDivisor(); - private TimelineArea timelineArea; + private Container timelineContainer; [BackgroundDependencyLoader(true)] private void load([CanBeNull] BindableBeatDivisor beatDivisor) @@ -62,11 +62,10 @@ private void load([CanBeNull] BindableBeatDivisor beatDivisor) { new Drawable[] { - new Container + timelineContainer = new Container { RelativeSizeAxes = Axes.Both, Padding = new MarginPadding { Right = 5 }, - Child = timelineArea = CreateTimelineArea() }, new BeatDivisorControl(beatDivisor) { RelativeSizeAxes = Axes.Both } }, @@ -100,14 +99,16 @@ private void load([CanBeNull] BindableBeatDivisor beatDivisor) mainContent.Add(content); content.FadeInFromZero(300, Easing.OutQuint); - LoadComponentAsync(CreateTimelineContent(), timelineArea.Add); + LoadComponentAsync(new TimelineArea + { + RelativeSizeAxes = Axes.Both, + Child = CreateTimelineContent() + }, timelineContainer.Add); }); } protected abstract Drawable CreateMainContent(); protected virtual Drawable CreateTimelineContent() => new Container(); - - protected TimelineArea CreateTimelineArea() => new TimelineArea { RelativeSizeAxes = Axes.Both }; } } From 353b74b04a81331a9343e5cd0f3ba41ba129a64e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 21 Jan 2020 17:37:35 +0900 Subject: [PATCH 07/40] Handle selection events in timeline --- .../Components/Timeline/TimelineHitObjectDisplay.cs | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectDisplay.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectDisplay.cs index 5d1c4eeeae..32efdd42a3 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectDisplay.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectDisplay.cs @@ -49,15 +49,6 @@ protected override SelectionBlueprint CreateBlueprintFor(HitObject hitObject) return new TimelineHitObjectRepresentation(hitObject) { Y = -yOffset * TimelineHitObjectRepresentation.THICKNESS }; } - protected override bool OnMouseDown(MouseDownEvent e) - { - base.OnMouseDown(e); - - return false; // temporary until we correctly handle selections. - } - - protected override DragBox CreateDragBox(Action performSelect) => new NoDragDragBox(performSelect); - internal class NoDragDragBox : DragBox { public NoDragDragBox(Action performSelect) @@ -74,6 +65,8 @@ private class TimelineHitObjectRepresentation : SelectionBlueprint public const float THICKNESS = 3; + public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => base.ReceivePositionalInputAt(screenSpacePos) || circle.ReceivePositionalInputAt(screenSpacePos); + public TimelineHitObjectRepresentation(HitObject hitObject) : base(hitObject) { From 6dd50572d242993a9c1a174ea4e5cf4344c0996a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 21 Jan 2020 17:37:43 +0900 Subject: [PATCH 08/40] Break mania more --- osu.Game.Rulesets.Mania/Edit/ManiaSelectionHandler.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Mania/Edit/ManiaSelectionHandler.cs b/osu.Game.Rulesets.Mania/Edit/ManiaSelectionHandler.cs index f33fd3c640..9069a636a8 100644 --- a/osu.Game.Rulesets.Mania/Edit/ManiaSelectionHandler.cs +++ b/osu.Game.Rulesets.Mania/Edit/ManiaSelectionHandler.cs @@ -71,7 +71,7 @@ private void performDragMovement(MoveSelectionEvent moveEvent) // When scrolling downwards the anchor position is at the bottom of the screen, however the movement event assumes the anchor is at the top of the screen. // This causes the delta to assume a positive hitobject position, and which can be corrected for by subtracting the parent height. if (scrollingInfo.Direction.Value == ScrollingDirection.Down) - delta -= moveEvent.Blueprint.DrawableObject.Parent.DrawHeight; + delta -= moveEvent.Blueprint.Parent.DrawHeight; // todo: probably wrong foreach (var selectionBlueprint in SelectedBlueprints) { From 6187b2e77cad79cb899af96adb66d400081bf9fd Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 21 Jan 2020 18:00:36 +0900 Subject: [PATCH 09/40] Implement IDistanceSnapProvider in timeline for now --- .../Compose/Components/Timeline/Timeline.cs | 52 ++++++++++++++++++- 1 file changed, 51 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs index b4f3b1f610..c866fb38c8 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs @@ -10,11 +10,15 @@ using osu.Framework.Input.Events; using osu.Framework.Timing; using osu.Game.Beatmaps; +using osu.Game.Beatmaps.ControlPoints; using osu.Game.Graphics; +using osu.Game.Rulesets.Edit; +using osuTK; namespace osu.Game.Screens.Edit.Compose.Components.Timeline { - public class Timeline : ZoomableScrollContainer + [Cached(typeof(IDistanceSnapProvider))] + public class Timeline : ZoomableScrollContainer, IDistanceSnapProvider { public readonly Bindable WaveformVisible = new Bindable(); public readonly IBindable Beatmap = new Bindable(); @@ -162,5 +166,51 @@ private void endUserDrag() if (trackWasPlaying) adjustableClock.Start(); } + + [Resolved] + private BindableBeatDivisor beatDivisor { get; set; } + + [Resolved] + private EditorBeatmap beatmap { get; set; } + + public (Vector2 position, double time) GetSnappedPosition(Vector2 position, double time) => (position, time); + + public float GetBeatSnapDistanceAt(double referenceTime) + { + DifficultyControlPoint difficultyPoint = beatmap.ControlPointInfo.DifficultyPointAt(referenceTime); + return (float)(100 * beatmap.BeatmapInfo.BaseDifficulty.SliderMultiplier * difficultyPoint.SpeedMultiplier / beatDivisor.Value); + } + + public float DurationToDistance(double referenceTime, double duration) + { + double beatLength = beatmap.ControlPointInfo.TimingPointAt(referenceTime).BeatLength / beatDivisor.Value; + return (float)(duration / beatLength * GetBeatSnapDistanceAt(referenceTime)); + } + + public double DistanceToDuration(double referenceTime, float distance) + { + double beatLength = beatmap.ControlPointInfo.TimingPointAt(referenceTime).BeatLength / beatDivisor.Value; + return distance / GetBeatSnapDistanceAt(referenceTime) * beatLength; + } + + public double GetSnappedDurationFromDistance(double referenceTime, float distance) + => beatSnap(referenceTime, DistanceToDuration(referenceTime, distance)); + + public float GetSnappedDistanceFromDistance(double referenceTime, float distance) + => DurationToDistance(referenceTime, beatSnap(referenceTime, DistanceToDuration(referenceTime, distance))); + + /// + /// Snaps a duration to the closest beat of a timing point applicable at the reference time. + /// + /// The time of the timing point which resides in. + /// The duration to snap. + /// A value that represents snapped to the closest beat of the timing point. + private double beatSnap(double referenceTime, double duration) + { + double beatLength = beatmap.ControlPointInfo.TimingPointAt(referenceTime).BeatLength / beatDivisor.Value; + + // A 1ms offset prevents rounding errors due to minute variations in duration + return (int)((duration + 1) / beatLength) * beatLength; + } } } From a888d148b653477e11ea7b5a292faf5a2c63b39d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 21 Jan 2020 18:01:10 +0900 Subject: [PATCH 10/40] Remove remaining cast --- .../Screens/Edit/Compose/Components/BlueprintContainer.cs | 2 +- .../Screens/Edit/Compose/Components/MoveSelectionEvent.cs | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs index 438be6ab54..b792065b43 100644 --- a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs @@ -374,7 +374,7 @@ private bool moveCurrentSelection(DragEvent e) (Vector2 snappedPosition, double snappedTime) = snapProvider.GetSnappedPosition(ToLocalSpace(movePosition), draggedObject.StartTime); // Move the hitobjects - if (!selectionHandler.HandleMovement(new MoveSelectionEvent((OverlaySelectionBlueprint)movementBlueprint, startPosition, ToScreenSpace(snappedPosition)))) + if (!selectionHandler.HandleMovement(new MoveSelectionEvent(movementBlueprint, startPosition, ToScreenSpace(snappedPosition)))) return true; // Apply the start time at the newly snapped-to position diff --git a/osu.Game/Screens/Edit/Compose/Components/MoveSelectionEvent.cs b/osu.Game/Screens/Edit/Compose/Components/MoveSelectionEvent.cs index 8662347aeb..48cb702c78 100644 --- a/osu.Game/Screens/Edit/Compose/Components/MoveSelectionEvent.cs +++ b/osu.Game/Screens/Edit/Compose/Components/MoveSelectionEvent.cs @@ -12,9 +12,9 @@ namespace osu.Game.Screens.Edit.Compose.Components public class MoveSelectionEvent { /// - /// The that triggered this . + /// The that triggered this . /// - public readonly OverlaySelectionBlueprint Blueprint; + public readonly SelectionBlueprint Blueprint; /// /// The starting screen-space position of the hitobject. @@ -34,13 +34,13 @@ public class MoveSelectionEvent /// public readonly Vector2 InstantDelta; - public MoveSelectionEvent(OverlaySelectionBlueprint blueprint, Vector2 screenSpaceStartPosition, Vector2 screenSpacePosition) + public MoveSelectionEvent(SelectionBlueprint blueprint, Vector2 screenSpaceStartPosition, Vector2 screenSpacePosition) { Blueprint = blueprint; ScreenSpaceStartPosition = screenSpaceStartPosition; ScreenSpacePosition = screenSpacePosition; - InstantDelta = Blueprint.DrawableObject.Parent.ToLocalSpace(ScreenSpacePosition) - Blueprint.DrawableObject.Position; + InstantDelta = Blueprint.GetInstantDelta(ScreenSpacePosition); } } } From f582c42bbd3a248c98d47bcc27470a94c7e7b6d4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 21 Jan 2020 18:56:09 +0900 Subject: [PATCH 11/40] Perform deletion directly via EditorBeatmap --- osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs index 907a22b9ce..9d9685af8a 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs @@ -38,7 +38,7 @@ public class SelectionHandler : CompositeDrawable, IKeyBindingHandler Date: Tue, 21 Jan 2020 19:51:44 +0900 Subject: [PATCH 12/40] Disable y offset for now --- .../Compose/Components/Timeline/TimelineHitObjectDisplay.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectDisplay.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectDisplay.cs index 32efdd42a3..d83336b21b 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectDisplay.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectDisplay.cs @@ -44,9 +44,9 @@ protected override void LoadComplete() protected override SelectionBlueprint CreateBlueprintFor(HitObject hitObject) { //var yOffset = content.Count(d => d.X == h.StartTime); - var yOffset = 0; + //var yOffset = 0; - return new TimelineHitObjectRepresentation(hitObject) { Y = -yOffset * TimelineHitObjectRepresentation.THICKNESS }; + return new TimelineHitObjectRepresentation(hitObject); } internal class NoDragDragBox : DragBox From c88bdbd4a0264f766de60f88af736d1da4b6a998 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 21 Jan 2020 20:46:39 +0900 Subject: [PATCH 13/40] Share selected hitobjects across multiple blueprint containers --- .../Compose/Components/BlueprintContainer.cs | 18 ++++++++++++++++++ .../Compose/Components/SelectionHandler.cs | 7 ++++++- osu.Game/Screens/Edit/EditorBeatmap.cs | 2 ++ 3 files changed, 26 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs index b792065b43..c5414542e4 100644 --- a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs @@ -6,6 +6,7 @@ using System.Diagnostics; using System.Linq; using osu.Framework.Allocation; +using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Primitives; @@ -41,6 +42,8 @@ public abstract class BlueprintContainer : CompositeDrawable, IKeyBindingHandler [Resolved] private EditorBeatmap beatmap { get; set; } + private readonly BindableList selectedHitObjects = new BindableList(); + [Resolved(canBeNull: true)] private IDistanceSnapProvider snapProvider { get; set; } @@ -65,6 +68,19 @@ private void load() foreach (var obj in beatmap.HitObjects) AddBlueprintFor(obj); + + selectedHitObjects.BindTo(beatmap.SelectedHitObjects); + selectedHitObjects.ItemsAdded += objects => + { + foreach (var o in objects) + selectionBlueprints.FirstOrDefault(b => b.HitObject == o)?.Select(); + }; + + selectedHitObjects.ItemsRemoved += objects => + { + foreach (var o in objects) + selectionBlueprints.FirstOrDefault(b => b.HitObject == o)?.Deselect(); + }; } protected virtual SelectionBlueprintContainer CreateSelectionBlueprintContainer() => new SelectionBlueprintContainer { RelativeSizeAxes = Axes.Both }; @@ -315,6 +331,7 @@ private void onBlueprintSelected(SelectionBlueprint blueprint) { selectionHandler.HandleSelected(blueprint); selectionBlueprints.ChangeChildDepth(blueprint, 1); + beatmap.SelectedHitObjects.Add(blueprint.HitObject); SelectionChanged?.Invoke(selectionHandler.SelectedHitObjects); } @@ -323,6 +340,7 @@ private void onBlueprintDeselected(SelectionBlueprint blueprint) { selectionHandler.HandleDeselected(blueprint); selectionBlueprints.ChangeChildDepth(blueprint, 0); + beatmap.SelectedHitObjects.Remove(blueprint.HitObject); SelectionChanged?.Invoke(selectionHandler.SelectedHitObjects); } diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs index 9d9685af8a..6ca110e518 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs @@ -102,7 +102,11 @@ public bool OnPressed(PlatformAction action) /// Handle a blueprint becoming selected. /// /// The blueprint. - internal void HandleSelected(SelectionBlueprint blueprint) => selectedBlueprints.Add(blueprint); + internal void HandleSelected(SelectionBlueprint blueprint) + { + selectedBlueprints.Add(blueprint); + editorBeatmap.SelectedHitObjects.Add(blueprint.HitObject); + } /// /// Handle a blueprint becoming deselected. @@ -111,6 +115,7 @@ public bool OnPressed(PlatformAction action) internal void HandleDeselected(SelectionBlueprint blueprint) { selectedBlueprints.Remove(blueprint); + editorBeatmap.SelectedHitObjects.Remove(blueprint.HitObject); // We don't want to update visibility if > 0, since we may be deselecting blueprints during drag-selection if (selectedBlueprints.Count == 0) diff --git a/osu.Game/Screens/Edit/EditorBeatmap.cs b/osu.Game/Screens/Edit/EditorBeatmap.cs index 6ed74dfdb0..2ff7563a88 100644 --- a/osu.Game/Screens/Edit/EditorBeatmap.cs +++ b/osu.Game/Screens/Edit/EditorBeatmap.cs @@ -29,6 +29,8 @@ public class EditorBeatmap : IBeatmap /// public event Action StartTimeChanged; + public BindableList SelectedHitObjects { get; } = new BindableList(); + public readonly IBeatmap PlayableBeatmap; private readonly Dictionary> startTimeBindables = new Dictionary>(); From a963d652bc1caee47081b5d4a8dc0ae93134a246 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 21 Jan 2020 20:54:50 +0900 Subject: [PATCH 14/40] Mark readonly --- .../Compose/Components/Timeline/TimelineHitObjectDisplay.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectDisplay.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectDisplay.cs index d83336b21b..b2ff9eee12 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectDisplay.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectDisplay.cs @@ -61,7 +61,7 @@ public NoDragDragBox(Action performSelect) private class TimelineHitObjectRepresentation : SelectionBlueprint { - private Circle circle; + private readonly Circle circle; public const float THICKNESS = 3; From 83fa4a9bb34d74e362cd7e684f0879c9d385a812 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 21 Jan 2020 22:24:04 +0900 Subject: [PATCH 15/40] Move circle size to a constant --- .../Compose/Components/Timeline/TimelineHitObjectDisplay.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectDisplay.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectDisplay.cs index b2ff9eee12..7932813bbc 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectDisplay.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectDisplay.cs @@ -65,6 +65,8 @@ private class TimelineHitObjectRepresentation : SelectionBlueprint public const float THICKNESS = 3; + private const float circle_size = 16; + public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => base.ReceivePositionalInputAt(screenSpacePos) || circle.ReceivePositionalInputAt(screenSpacePos); public TimelineHitObjectRepresentation(HitObject hitObject) @@ -101,7 +103,7 @@ public TimelineHitObjectRepresentation(HitObject hitObject) AddInternal(circle = new Circle { - Size = new Vector2(16), + Size = new Vector2(circle_size), Anchor = Anchor.CentreLeft, Origin = Anchor.Centre, RelativePositionAxes = Axes.X, From 98aaf3864953f31a942184bdc6adce5e3781e782 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 21 Jan 2020 23:58:51 +0900 Subject: [PATCH 16/40] Fix playfield movement regressing --- osu.Game/Rulesets/Edit/OverlaySelectionBlueprint.cs | 2 ++ osu.Game/Rulesets/Edit/SelectionBlueprint.cs | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/Edit/OverlaySelectionBlueprint.cs b/osu.Game/Rulesets/Edit/OverlaySelectionBlueprint.cs index 4c3898aa04..b4ae3f3fba 100644 --- a/osu.Game/Rulesets/Edit/OverlaySelectionBlueprint.cs +++ b/osu.Game/Rulesets/Edit/OverlaySelectionBlueprint.cs @@ -28,5 +28,7 @@ protected OverlaySelectionBlueprint(DrawableHitObject drawableObject) public override Vector2 SelectionPoint => DrawableObject.ScreenSpaceDrawQuad.Centre; public override Quad SelectionQuad => DrawableObject.ScreenSpaceDrawQuad; + + public override Vector2 GetInstantDelta(Vector2 screenSpacePosition) => DrawableObject.Parent.ToLocalSpace(screenSpacePosition) - DrawableObject.Position; } } diff --git a/osu.Game/Rulesets/Edit/SelectionBlueprint.cs b/osu.Game/Rulesets/Edit/SelectionBlueprint.cs index 1c82a8287b..9998254f76 100644 --- a/osu.Game/Rulesets/Edit/SelectionBlueprint.cs +++ b/osu.Game/Rulesets/Edit/SelectionBlueprint.cs @@ -121,6 +121,6 @@ public SelectionState State /// public virtual Quad SelectionQuad => ScreenSpaceDrawQuad; - public Vector2 GetInstantDelta(Vector2 screenSpacePosition) => Parent.ToLocalSpace(screenSpacePosition) - Position; + public virtual Vector2 GetInstantDelta(Vector2 screenSpacePosition) => Parent.ToLocalSpace(screenSpacePosition) - Position; } } From e3a2b20f631532dece1d51a4eabd89cc4c05bdff Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 22 Jan 2020 01:32:11 +0900 Subject: [PATCH 17/40] Fix SelectionHandler visibility on remote selection --- osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs index 6ca110e518..0aced5f957 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs @@ -106,6 +106,8 @@ internal void HandleSelected(SelectionBlueprint blueprint) { selectedBlueprints.Add(blueprint); editorBeatmap.SelectedHitObjects.Add(blueprint.HitObject); + + UpdateVisibility(); } /// @@ -144,8 +146,6 @@ internal void HandleSelectionRequested(SelectionBlueprint blueprint, InputState DeselectAll?.Invoke(); blueprint.Select(); } - - UpdateVisibility(); } private void deleteSelected() From a6775d1bd3a0432e5e2b3b907d3bb14851fc26fe Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 22 Jan 2020 14:58:15 +0900 Subject: [PATCH 18/40] Implement custom drag box and allow drag seeking once again --- .../Edit/Compose/Components/DragBox.cs | 36 ++++++++++--------- .../Timeline/TimelineHitObjectDisplay.cs | 35 ++++++++++++++++-- 2 files changed, 51 insertions(+), 20 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/DragBox.cs b/osu.Game/Screens/Edit/Compose/Components/DragBox.cs index f522ca356f..adbab1767b 100644 --- a/osu.Game/Screens/Edit/Compose/Components/DragBox.cs +++ b/osu.Game/Screens/Edit/Compose/Components/DragBox.cs @@ -17,9 +17,9 @@ namespace osu.Game.Screens.Edit.Compose.Components /// public class DragBox : CompositeDrawable { - private readonly Action performSelection; + protected readonly Action PerformSelection; - private Drawable box; + protected Drawable Box; /// /// Creates a new . @@ -27,7 +27,7 @@ public class DragBox : CompositeDrawable /// A delegate that performs drag selection. public DragBox(Action performSelection) { - this.performSelection = performSelection; + PerformSelection = performSelection; RelativeSizeAxes = Axes.Both; AlwaysPresent = true; @@ -37,19 +37,21 @@ public DragBox(Action performSelection) [BackgroundDependencyLoader] private void load() { - InternalChild = box = new Container - { - Masking = true, - BorderColour = Color4.White, - BorderThickness = SelectionHandler.BORDER_RADIUS, - Child = new Box - { - RelativeSizeAxes = Axes.Both, - Alpha = 0.1f - } - }; + InternalChild = Box = CreateBox(); } + protected virtual Drawable CreateBox() => new Container + { + Masking = true, + BorderColour = Color4.White, + BorderThickness = SelectionHandler.BORDER_RADIUS, + Child = new Box + { + RelativeSizeAxes = Axes.Both, + Alpha = 0.1f + } + }; + /// /// Handle a forwarded mouse event. /// @@ -68,10 +70,10 @@ public virtual bool UpdateDrag(MouseButtonEvent e) var topLeft = ToLocalSpace(dragRectangle.TopLeft); var bottomRight = ToLocalSpace(dragRectangle.BottomRight); - box.Position = topLeft; - box.Size = bottomRight - topLeft; + Box.Position = topLeft; + Box.Size = bottomRight - topLeft; - performSelection?.Invoke(dragRectangle); + PerformSelection?.Invoke(dragRectangle); return true; } } diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectDisplay.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectDisplay.cs index 7932813bbc..e68b088d78 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectDisplay.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectDisplay.cs @@ -21,6 +21,17 @@ internal class TimelineHitObjectDisplay : BlueprintContainer public TimelineHitObjectDisplay(EditorBeatmap beatmap) { RelativeSizeAxes = Axes.Both; + Anchor = Anchor.Centre; + Origin = Anchor.Centre; + + Height = 0.4f; + + AddInternal(new Box + { + Colour = Color4.Black, + RelativeSizeAxes = Axes.Both, + Alpha = 0.1f, + }); } protected override SelectionBlueprintContainer CreateSelectionBlueprintContainer() => new TimelineSelectionBlueprintContainer { RelativeSizeAxes = Axes.Both }; @@ -49,14 +60,32 @@ protected override SelectionBlueprint CreateBlueprintFor(HitObject hitObject) return new TimelineHitObjectRepresentation(hitObject); } - internal class NoDragDragBox : DragBox + protected override DragBox CreateDragBox(Action performSelect) => new CustomDragBox(performSelect); + + internal class CustomDragBox : DragBox { - public NoDragDragBox(Action performSelect) + public CustomDragBox(Action performSelect) : base(performSelect) { } - public override bool UpdateDrag(MouseButtonEvent e) => false; + protected override Drawable CreateBox() => new Box + { + RelativeSizeAxes = Axes.Y, + Alpha = 0.3f + }; + + public override bool UpdateDrag(MouseButtonEvent e) + { + float selection1 = e.MouseDownPosition.X; + float selection2 = e.MousePosition.X; + + Box.X = Math.Min(selection1, selection2); + Box.Width = Math.Abs(selection1 - selection2); + + PerformSelection?.Invoke(Box.ScreenSpaceDrawQuad.AABBFloat); + return true; + } } private class TimelineHitObjectRepresentation : SelectionBlueprint From 482409e776cd9def6f45af0106b1fd5367aff221 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 22 Jan 2020 15:31:58 +0900 Subject: [PATCH 19/40] Colour extension bars of long objects --- .../Timeline/TimelineHitObjectDisplay.cs | 33 +++++++++++++++++-- 1 file changed, 30 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectDisplay.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectDisplay.cs index e68b088d78..b09c1a5597 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectDisplay.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectDisplay.cs @@ -92,6 +92,8 @@ private class TimelineHitObjectRepresentation : SelectionBlueprint { private readonly Circle circle; + private Container extensionBar; + public const float THICKNESS = 3; private const float circle_size = 16; @@ -109,11 +111,13 @@ public TimelineHitObjectRepresentation(HitObject hitObject) X = (float)hitObject.StartTime; RelativePositionAxes = Axes.X; + RelativeSizeAxes = Axes.X; + AutoSizeAxes = Axes.Y; if (hitObject is IHasEndTime) { - AddInternal(new Container + AddInternal(extensionBar = new Container { CornerRadius = 2, Masking = true, @@ -143,9 +147,32 @@ public TimelineHitObjectRepresentation(HitObject hitObject) }); } - protected override void OnSelected() => circle.BorderColour = Color4.Orange; + protected override void OnSelected() + { + circle.BorderColour = Color4.Orange; + if (extensionBar != null) + extensionBar.Colour = Color4.Orange; + } - protected override void OnDeselected() => circle.BorderColour = Color4.Black; + protected override void OnDeselected() + { + circle.BorderColour = Color4.Black; + if (extensionBar != null) + extensionBar.Colour = Color4.Black; + } + + public override Quad SelectionQuad + { + get + { + // correctly include the circle in the selection quad region, as it is usually outside the blueprint itself. + var circleQuad = circle.ScreenSpaceDrawQuad; + var actualQuad = ScreenSpaceDrawQuad; + + return new Quad(circleQuad.TopLeft, Vector2.ComponentMax(actualQuad.TopRight, circleQuad.TopRight), + circleQuad.BottomLeft, Vector2.ComponentMax(actualQuad.BottomRight, circleQuad.BottomRight)); + } + } } } } From c4395b1cea45b242e2f19816a92b9aa4df2c727a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 22 Jan 2020 15:42:49 +0900 Subject: [PATCH 20/40] Clean up nested class implementations --- .../Timeline/TimelineHitObjectDisplay.cs | 43 +++++++++++-------- 1 file changed, 26 insertions(+), 17 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectDisplay.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectDisplay.cs index b09c1a5597..397c74b193 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectDisplay.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectDisplay.cs @@ -36,33 +36,32 @@ public TimelineHitObjectDisplay(EditorBeatmap beatmap) protected override SelectionBlueprintContainer CreateSelectionBlueprintContainer() => new TimelineSelectionBlueprintContainer { RelativeSizeAxes = Axes.Both }; - protected class TimelineSelectionBlueprintContainer : SelectionBlueprintContainer - { - protected override Container Content { get; } - - public TimelineSelectionBlueprintContainer() - { - AddInternal(new TimelinePart(Content = new Container { RelativeSizeAxes = Axes.Both }) { RelativeSizeAxes = Axes.Both }); - } - } - protected override void LoadComplete() { base.LoadComplete(); DragBox.Alpha = 0; } - protected override SelectionBlueprint CreateBlueprintFor(HitObject hitObject) + protected override bool OnDrag(DragEvent e) { - //var yOffset = content.Count(d => d.X == h.StartTime); - //var yOffset = 0; - - return new TimelineHitObjectRepresentation(hitObject); + lastDragEvent = e; + return base.OnDrag(e); } + protected override void Update() + { + if (IsDragged && lastDragEvent != null) + // trigger every frame so drags continue to update selection while playback is scrolling the timeline. + DragBox.UpdateDrag(lastDragEvent); + + base.Update(); + } + + protected override SelectionBlueprint CreateBlueprintFor(HitObject hitObject) => new TimelineHitObjectRepresentation(hitObject); + protected override DragBox CreateDragBox(Action performSelect) => new CustomDragBox(performSelect); - internal class CustomDragBox : DragBox + private class CustomDragBox : DragBox { public CustomDragBox(Action performSelect) : base(performSelect) @@ -88,11 +87,21 @@ public override bool UpdateDrag(MouseButtonEvent e) } } + protected class TimelineSelectionBlueprintContainer : SelectionBlueprintContainer + { + protected override Container Content { get; } + + public TimelineSelectionBlueprintContainer() + { + AddInternal(new TimelinePart(Content = new Container { RelativeSizeAxes = Axes.Both }) { RelativeSizeAxes = Axes.Both }); + } + } + private class TimelineHitObjectRepresentation : SelectionBlueprint { private readonly Circle circle; - private Container extensionBar; + private readonly Container extensionBar; public const float THICKNESS = 3; From a8ec4907c410dedb4d8c71bc5348db3c096d73ac Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 22 Jan 2020 15:58:03 +0900 Subject: [PATCH 21/40] Fix selections while scrolling timeline --- .../Components/Timeline/TimelineHitObjectDisplay.cs | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectDisplay.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectDisplay.cs index 397c74b193..d50616a66f 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectDisplay.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectDisplay.cs @@ -63,6 +63,9 @@ protected override void Update() private class CustomDragBox : DragBox { + private Vector2 lastMouseDown; + private float localMouseDown; + public CustomDragBox(Action performSelect) : base(performSelect) { @@ -76,7 +79,14 @@ public CustomDragBox(Action performSelect) public override bool UpdateDrag(MouseButtonEvent e) { - float selection1 = e.MouseDownPosition.X; + // store the original position of the mouse down, as we may be scrolled during selection. + if (lastMouseDown != e.ScreenSpaceMouseDownPosition) + { + lastMouseDown = e.ScreenSpaceMouseDownPosition; + localMouseDown = e.MouseDownPosition.X; + } + + float selection1 = localMouseDown; float selection2 = e.MousePosition.X; Box.X = Math.Min(selection1, selection2); From 89d90fdfa054620811404ab60c694d6cb12098c5 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 22 Jan 2020 16:33:23 +0900 Subject: [PATCH 22/40] Fix drag not updating until mouse is moved while scrolling timeline --- .../Components/Timeline/TimelineHitObjectDisplay.cs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectDisplay.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectDisplay.cs index d50616a66f..f812095a35 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectDisplay.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectDisplay.cs @@ -18,6 +18,8 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline { internal class TimelineHitObjectDisplay : BlueprintContainer { + private DragEvent lastDragEvent; + public TimelineHitObjectDisplay(EditorBeatmap beatmap) { RelativeSizeAxes = Axes.Both; @@ -50,9 +52,9 @@ protected override bool OnDrag(DragEvent e) protected override void Update() { - if (IsDragged && lastDragEvent != null) - // trigger every frame so drags continue to update selection while playback is scrolling the timeline. - DragBox.UpdateDrag(lastDragEvent); + // trigger every frame so drags continue to update selection while playback is scrolling the timeline. + if (IsDragged) + OnDrag(lastDragEvent); base.Update(); } From 63cef8b8b7189bb4c9215c26e90d532ffd5d83ef Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 22 Jan 2020 16:58:33 +0900 Subject: [PATCH 23/40] Rename nested classes to be more appropriate --- .../Components/Timeline/TimelineHitObjectDisplay.cs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectDisplay.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectDisplay.cs index f812095a35..10b470e4b7 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectDisplay.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectDisplay.cs @@ -59,16 +59,16 @@ protected override void Update() base.Update(); } - protected override SelectionBlueprint CreateBlueprintFor(HitObject hitObject) => new TimelineHitObjectRepresentation(hitObject); + protected override SelectionBlueprint CreateBlueprintFor(HitObject hitObject) => new TimelineHitObjectBlueprint(hitObject); - protected override DragBox CreateDragBox(Action performSelect) => new CustomDragBox(performSelect); + protected override DragBox CreateDragBox(Action performSelect) => new TimelineDragBox(performSelect); - private class CustomDragBox : DragBox + private class TimelineDragBox : DragBox { private Vector2 lastMouseDown; private float localMouseDown; - public CustomDragBox(Action performSelect) + public TimelineDragBox(Action performSelect) : base(performSelect) { } @@ -109,7 +109,7 @@ public TimelineSelectionBlueprintContainer() } } - private class TimelineHitObjectRepresentation : SelectionBlueprint + private class TimelineHitObjectBlueprint : SelectionBlueprint { private readonly Circle circle; @@ -121,7 +121,7 @@ private class TimelineHitObjectRepresentation : SelectionBlueprint public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => base.ReceivePositionalInputAt(screenSpacePos) || circle.ReceivePositionalInputAt(screenSpacePos); - public TimelineHitObjectRepresentation(HitObject hitObject) + public TimelineHitObjectBlueprint(HitObject hitObject) : base(hitObject) { Anchor = Anchor.CentreLeft; From f0d810fe2098a94a15ad34e439a98ca36e42b59b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 22 Jan 2020 17:11:37 +0900 Subject: [PATCH 24/40] Follow start time and duration changes --- .../Timeline/TimelineHitObjectDisplay.cs | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectDisplay.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectDisplay.cs index 10b470e4b7..a11958c8c9 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectDisplay.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectDisplay.cs @@ -2,6 +2,8 @@ // See the LICENCE file in the repository root for full licence text. using System; +using JetBrains.Annotations; +using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Primitives; @@ -115,6 +117,9 @@ private class TimelineHitObjectBlueprint : SelectionBlueprint private readonly Container extensionBar; + [UsedImplicitly] + private readonly Bindable startTime; + public const float THICKNESS = 3; private const float circle_size = 16; @@ -127,9 +132,8 @@ public TimelineHitObjectBlueprint(HitObject hitObject) Anchor = Anchor.CentreLeft; Origin = Anchor.CentreLeft; - Width = (float)(hitObject.GetEndTime() - hitObject.StartTime); - - X = (float)hitObject.StartTime; + startTime = hitObject.StartTimeBindable.GetBoundCopy(); + startTime.BindValueChanged(time => X = (float)time.NewValue, true); RelativePositionAxes = Axes.X; @@ -168,6 +172,14 @@ public TimelineHitObjectBlueprint(HitObject hitObject) }); } + protected override void Update() + { + base.Update(); + + // no bindable so we perform this every update + Width = (float)(HitObject.GetEndTime() - HitObject.StartTime); + } + protected override void OnSelected() { circle.BorderColour = Color4.Orange; From cb6e7425aebc9789ac80af0a21f29fd25bad2205 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 22 Jan 2020 17:54:11 +0900 Subject: [PATCH 25/40] Make dragbox stateful to fix blueprint movement --- .../Compose/Components/BlueprintContainer.cs | 29 +++++++++---------- .../Edit/Compose/Components/DragBox.cs | 26 +++++++++++++++-- .../Timeline/TimelineHitObjectDisplay.cs | 8 ++++- 3 files changed, 45 insertions(+), 18 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs index c5414542e4..583627f78e 100644 --- a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs @@ -152,15 +152,16 @@ protected override bool OnDragStart(DragStartEvent e) if (e.Button == MouseButton.Right) return false; - if (!beginSelectionMovement()) - { - if (!DragBox.UpdateDrag(e)) - return false; + if (beginSelectionMovement()) + return true; - DragBox.FadeIn(250, Easing.OutQuint); + if (DragBox.HandleDrag(e)) + { + DragBox.Show(); + return true; } - return true; + return false; } protected override bool OnDrag(DragEvent e) @@ -168,13 +169,10 @@ protected override bool OnDrag(DragEvent e) if (e.Button == MouseButton.Right) return false; - if (!moveCurrentSelection(e)) - { - if (!DragBox.UpdateDrag(e)) - return false; - } + if (DragBox.State == Visibility.Visible) + return DragBox.HandleDrag(e); - return true; + return moveCurrentSelection(e); } protected override bool OnDragEnd(DragEndEvent e) @@ -182,13 +180,14 @@ protected override bool OnDragEnd(DragEndEvent e) if (e.Button == MouseButton.Right) return false; - if (!finishSelectionMovement()) + if (DragBox.State == Visibility.Visible) { - DragBox.FadeOut(250, Easing.OutQuint); + DragBox.Hide(); selectionHandler.UpdateVisibility(); + return true; } - return true; + return finishSelectionMovement(); } protected override bool OnKeyDown(KeyDownEvent e) diff --git a/osu.Game/Screens/Edit/Compose/Components/DragBox.cs b/osu.Game/Screens/Edit/Compose/Components/DragBox.cs index adbab1767b..c5f1bd1575 100644 --- a/osu.Game/Screens/Edit/Compose/Components/DragBox.cs +++ b/osu.Game/Screens/Edit/Compose/Components/DragBox.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; +using osu.Framework; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -15,7 +16,7 @@ namespace osu.Game.Screens.Edit.Compose.Components /// /// A box that displays the drag selection and provides selection events for users to handle. /// - public class DragBox : CompositeDrawable + public class DragBox : CompositeDrawable, IStateful { protected readonly Action PerformSelection; @@ -57,7 +58,7 @@ private void load() /// /// The mouse event. /// Whether the event should be handled and blocking. - public virtual bool UpdateDrag(MouseButtonEvent e) + public virtual bool HandleDrag(MouseButtonEvent e) { var dragPosition = e.ScreenSpaceMousePosition; var dragStartPosition = e.ScreenSpaceMouseDownPosition; @@ -76,5 +77,26 @@ public virtual bool UpdateDrag(MouseButtonEvent e) PerformSelection?.Invoke(dragRectangle); return true; } + + private Visibility state; + + public Visibility State + { + get => state; + set + { + if (value == state) return; + + state = value; + this.FadeTo(state == Visibility.Hidden ? 0 : 1, 250, Easing.OutQuint); + StateChanged?.Invoke(state); + } + } + + public override void Hide() => State = Visibility.Hidden; + + public override void Show() => State = Visibility.Visible; + + public event Action StateChanged; } } diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectDisplay.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectDisplay.cs index a11958c8c9..7101fac310 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectDisplay.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectDisplay.cs @@ -52,6 +52,12 @@ protected override bool OnDrag(DragEvent e) return base.OnDrag(e); } + protected override bool OnDragEnd(DragEndEvent e) + { + lastDragEvent = null; + return base.OnDragEnd(e); + } + protected override void Update() { // trigger every frame so drags continue to update selection while playback is scrolling the timeline. @@ -81,7 +87,7 @@ public TimelineDragBox(Action performSelect) Alpha = 0.3f }; - public override bool UpdateDrag(MouseButtonEvent e) + public override bool HandleDrag(MouseButtonEvent e) { // store the original position of the mouse down, as we may be scrolled during selection. if (lastMouseDown != e.ScreenSpaceMouseDownPosition) From 53bdf72592ba86418da460560515553e63401437 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 22 Jan 2020 21:37:06 +0900 Subject: [PATCH 26/40] Allow basic timeline selection temporal movement --- .../Screens/Edit/Compose/Components/Timeline/Timeline.cs | 2 +- .../Components/Timeline/TimelineHitObjectDisplay.cs | 9 +++++++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs index c866fb38c8..21eb4b08b0 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs @@ -173,7 +173,7 @@ private void endUserDrag() [Resolved] private EditorBeatmap beatmap { get; set; } - public (Vector2 position, double time) GetSnappedPosition(Vector2 position, double time) => (position, time); + public (Vector2 position, double time) GetSnappedPosition(Vector2 position, double time) => (position, (position.X / Content.DrawWidth) * track.Length); public float GetBeatSnapDistanceAt(double referenceTime) { diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectDisplay.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectDisplay.cs index 7101fac310..5e6403bb05 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectDisplay.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectDisplay.cs @@ -67,6 +67,13 @@ protected override void Update() base.Update(); } + protected override SelectionHandler CreateSelectionHandler() => new TimelineSelectionHandler(); + + internal class TimelineSelectionHandler : SelectionHandler + { + public override bool HandleMovement(MoveSelectionEvent moveEvent) => true; + } + protected override SelectionBlueprint CreateBlueprintFor(HitObject hitObject) => new TimelineHitObjectBlueprint(hitObject); protected override DragBox CreateDragBox(Action performSelect) => new TimelineDragBox(performSelect); @@ -212,6 +219,8 @@ public override Quad SelectionQuad circleQuad.BottomLeft, Vector2.ComponentMax(actualQuad.BottomRight, circleQuad.BottomRight)); } } + + public override Vector2 SelectionPoint => ScreenSpaceDrawQuad.TopLeft; } } } From aa1a226ab7522b3207d3c3a949fbec1a905be345 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 22 Jan 2020 21:37:57 +0900 Subject: [PATCH 27/40] Remove unused ScreenSpaceStartPosition field --- .../Edit/Compose/Components/BlueprintContainer.cs | 2 +- .../Edit/Compose/Components/MoveSelectionEvent.cs | 11 +---------- 2 files changed, 2 insertions(+), 11 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs index 583627f78e..58cd4d0974 100644 --- a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs @@ -391,7 +391,7 @@ private bool moveCurrentSelection(DragEvent e) (Vector2 snappedPosition, double snappedTime) = snapProvider.GetSnappedPosition(ToLocalSpace(movePosition), draggedObject.StartTime); // Move the hitobjects - if (!selectionHandler.HandleMovement(new MoveSelectionEvent(movementBlueprint, startPosition, ToScreenSpace(snappedPosition)))) + if (!selectionHandler.HandleMovement(new MoveSelectionEvent(movementBlueprint, ToScreenSpace(snappedPosition)))) return true; // Apply the start time at the newly snapped-to position diff --git a/osu.Game/Screens/Edit/Compose/Components/MoveSelectionEvent.cs b/osu.Game/Screens/Edit/Compose/Components/MoveSelectionEvent.cs index 48cb702c78..0792d0f80e 100644 --- a/osu.Game/Screens/Edit/Compose/Components/MoveSelectionEvent.cs +++ b/osu.Game/Screens/Edit/Compose/Components/MoveSelectionEvent.cs @@ -16,11 +16,6 @@ public class MoveSelectionEvent /// public readonly SelectionBlueprint Blueprint; - /// - /// The starting screen-space position of the hitobject. - /// - public readonly Vector2 ScreenSpaceStartPosition; - /// /// The expected screen-space position of the hitobject at the current cursor position. /// @@ -29,15 +24,11 @@ public class MoveSelectionEvent /// /// The distance between and the hitobject's current position, in the coordinate-space of the hitobject's parent. /// - /// - /// This does not use and does not represent the cumulative movement distance. - /// public readonly Vector2 InstantDelta; - public MoveSelectionEvent(SelectionBlueprint blueprint, Vector2 screenSpaceStartPosition, Vector2 screenSpacePosition) + public MoveSelectionEvent(SelectionBlueprint blueprint, Vector2 screenSpacePosition) { Blueprint = blueprint; - ScreenSpaceStartPosition = screenSpaceStartPosition; ScreenSpacePosition = screenSpacePosition; InstantDelta = Blueprint.GetInstantDelta(ScreenSpacePosition); From c76f76e5aa441c526278345a706f34487e6c1eaa Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 22 Jan 2020 21:43:02 +0900 Subject: [PATCH 28/40] Fix being able to drag out of the blueprint intending to be moved --- .../Compose/Components/BlueprintContainer.cs | 25 +++++++++++-------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs index 58cd4d0974..ef4a06879e 100644 --- a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs @@ -109,6 +109,9 @@ protected override void LoadComplete() protected override bool OnMouseDown(MouseDownEvent e) { beginClickSelection(e); + + prepareSelectionMovement(); + return e.Button == MouseButton.Left; } @@ -144,6 +147,9 @@ protected override bool OnMouseUp(MouseUpEvent e) { // Special case for when a drag happened instead of a click Schedule(() => endClickSelection()); + + finishSelectionMovement(); + return e.Button == MouseButton.Left; } @@ -152,7 +158,7 @@ protected override bool OnDragStart(DragStartEvent e) if (e.Button == MouseButton.Right) return false; - if (beginSelectionMovement()) + if (movementBlueprint != null) return true; if (DragBox.HandleDrag(e)) @@ -184,10 +190,9 @@ protected override bool OnDragEnd(DragEndEvent e) { DragBox.Hide(); selectionHandler.UpdateVisibility(); - return true; } - return finishSelectionMovement(); + return true; } protected override bool OnKeyDown(KeyDownEvent e) @@ -348,14 +353,14 @@ private void onBlueprintDeselected(SelectionBlueprint blueprint) #region Selection Movement - private Vector2? screenSpaceMovementStartPosition; + private Vector2? movementBlueprintOriginalPosition; private SelectionBlueprint movementBlueprint; /// /// Attempts to begin the movement of any selected blueprints. /// /// Whether movement began. - private bool beginSelectionMovement() + private bool prepareSelectionMovement() { Debug.Assert(movementBlueprint == null); @@ -366,7 +371,7 @@ private bool beginSelectionMovement() // Movement is tracked from the blueprint of the earliest hitobject, since it only makes sense to distance snap from that hitobject movementBlueprint = selectionHandler.SelectedBlueprints.OrderBy(b => b.HitObject.StartTime).First(); - screenSpaceMovementStartPosition = movementBlueprint.SelectionPoint; // todo: unsure if correct + movementBlueprintOriginalPosition = movementBlueprint.SelectionPoint; // todo: unsure if correct return true; } @@ -381,13 +386,13 @@ private bool moveCurrentSelection(DragEvent e) if (movementBlueprint == null) return false; - Debug.Assert(screenSpaceMovementStartPosition != null); + Debug.Assert(movementBlueprintOriginalPosition != null); - Vector2 startPosition = screenSpaceMovementStartPosition.Value; HitObject draggedObject = movementBlueprint.HitObject; // The final movement position, relative to screenSpaceMovementStartPosition - Vector2 movePosition = startPosition + e.ScreenSpaceMousePosition - e.ScreenSpaceMouseDownPosition; + Vector2 movePosition = movementBlueprintOriginalPosition.Value + e.ScreenSpaceMousePosition - e.ScreenSpaceMouseDownPosition; + (Vector2 snappedPosition, double snappedTime) = snapProvider.GetSnappedPosition(ToLocalSpace(movePosition), draggedObject.StartTime); // Move the hitobjects @@ -411,7 +416,7 @@ private bool finishSelectionMovement() if (movementBlueprint == null) return false; - screenSpaceMovementStartPosition = null; + movementBlueprintOriginalPosition = null; movementBlueprint = null; return true; From 9d2a46df892587185343b8ebb7723823df750c33 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 22 Jan 2020 21:46:24 +0900 Subject: [PATCH 29/40] Add beat snapping to timeline movement --- .../Screens/Edit/Compose/Components/Timeline/Timeline.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs index 21eb4b08b0..98fb754931 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs @@ -173,7 +173,11 @@ private void endUserDrag() [Resolved] private EditorBeatmap beatmap { get; set; } - public (Vector2 position, double time) GetSnappedPosition(Vector2 position, double time) => (position, (position.X / Content.DrawWidth) * track.Length); + public (Vector2 position, double time) GetSnappedPosition(Vector2 position, double time) + { + var targetTime = (position.X / Content.DrawWidth) * track.Length; + return (position, beatSnap(targetTime, targetTime)); + } public float GetBeatSnapDistanceAt(double referenceTime) { From 477e1b7d278a9b1b490a43d4291c5c0af00b05f5 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 23 Jan 2020 12:17:46 +0900 Subject: [PATCH 30/40] Rename TimelineHitObjectDisplay to TimelineBlueprintContainer --- .../Visual/Editor/TestSceneEditorComposeTimeline.cs | 4 ++-- ...elineHitObjectDisplay.cs => TimelineBlueprintContainer.cs} | 4 ++-- osu.Game/Screens/Edit/Compose/ComposeScreen.cs | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) rename osu.Game/Screens/Edit/Compose/Components/Timeline/{TimelineHitObjectDisplay.cs => TimelineBlueprintContainer.cs} (98%) diff --git a/osu.Game.Tests/Visual/Editor/TestSceneEditorComposeTimeline.cs b/osu.Game.Tests/Visual/Editor/TestSceneEditorComposeTimeline.cs index e9372bd134..78cdeb3ac8 100644 --- a/osu.Game.Tests/Visual/Editor/TestSceneEditorComposeTimeline.cs +++ b/osu.Game.Tests/Visual/Editor/TestSceneEditorComposeTimeline.cs @@ -27,7 +27,7 @@ public class TestSceneEditorComposeTimeline : EditorClockTestScene public override IReadOnlyList RequiredTypes => new[] { typeof(TimelineArea), - typeof(TimelineHitObjectDisplay), + typeof(TimelineBlueprintContainer), typeof(Timeline), typeof(TimelineButton), typeof(CentreMarker) @@ -57,7 +57,7 @@ private void load(AudioManager audio) }, new TimelineArea { - Child = new TimelineHitObjectDisplay(editorBeatmap), + Child = new TimelineBlueprintContainer(editorBeatmap), Anchor = Anchor.Centre, Origin = Anchor.Centre, RelativeSizeAxes = Axes.X, diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectDisplay.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineBlueprintContainer.cs similarity index 98% rename from osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectDisplay.cs rename to osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineBlueprintContainer.cs index 5e6403bb05..c5603c0f67 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectDisplay.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineBlueprintContainer.cs @@ -18,11 +18,11 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline { - internal class TimelineHitObjectDisplay : BlueprintContainer + internal class TimelineBlueprintContainer : BlueprintContainer { private DragEvent lastDragEvent; - public TimelineHitObjectDisplay(EditorBeatmap beatmap) + public TimelineBlueprintContainer(EditorBeatmap beatmap) { RelativeSizeAxes = Axes.Both; Anchor = Anchor.Centre; diff --git a/osu.Game/Screens/Edit/Compose/ComposeScreen.cs b/osu.Game/Screens/Edit/Compose/ComposeScreen.cs index 1a6aae294a..20e1ef95d3 100644 --- a/osu.Game/Screens/Edit/Compose/ComposeScreen.cs +++ b/osu.Game/Screens/Edit/Compose/ComposeScreen.cs @@ -32,6 +32,6 @@ protected override Drawable CreateMainContent() return beatmapSkinProvider.WithChild(rulesetSkinProvider.WithChild(composer)); } - protected override Drawable CreateTimelineContent() => composer == null ? base.CreateTimelineContent() : new TimelineHitObjectDisplay(EditorBeatmap); + protected override Drawable CreateTimelineContent() => composer == null ? base.CreateTimelineContent() : new TimelineBlueprintContainer(EditorBeatmap); } } From cb09c2e1449ee26285d14a27a9f40c3fb0f937d6 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 23 Jan 2020 12:29:32 +0900 Subject: [PATCH 31/40] Add support for dragging outside visible extents --- .../Edit/Compose/Components/Timeline/Timeline.cs | 1 + .../Timeline/TimelineBlueprintContainer.cs | 16 ++++++++++++++++ 2 files changed, 17 insertions(+) diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs index 98fb754931..804c2f1e65 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs @@ -18,6 +18,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline { [Cached(typeof(IDistanceSnapProvider))] + [Cached] public class Timeline : ZoomableScrollContainer, IDistanceSnapProvider { public readonly Bindable WaveformVisible = new Bindable(); diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineBlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineBlueprintContainer.cs index c5603c0f67..16e1793c8c 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineBlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineBlueprintContainer.cs @@ -3,6 +3,7 @@ using System; using JetBrains.Annotations; +using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -20,6 +21,9 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline { internal class TimelineBlueprintContainer : BlueprintContainer { + [Resolved(CanBeNull = true)] + private Timeline timeline { get; set; } + private DragEvent lastDragEvent; public TimelineBlueprintContainer(EditorBeatmap beatmap) @@ -48,6 +52,18 @@ protected override void LoadComplete() protected override bool OnDrag(DragEvent e) { + if (timeline != null) + { + var timelineQuad = timeline.ScreenSpaceDrawQuad; + var mouseX = e.ScreenSpaceMousePosition.X; + + // scroll if in a drag and dragging outside visible extents + if (mouseX > timelineQuad.TopRight.X) + timeline.ScrollBy((float)((mouseX - timelineQuad.TopRight.X) / 10 * Clock.ElapsedFrameTime)); + else if (mouseX < timelineQuad.TopLeft.X) + timeline.ScrollBy((float)((mouseX - timelineQuad.TopLeft.X) / 10 * Clock.ElapsedFrameTime)); + } + lastDragEvent = e; return base.OnDrag(e); } From 56c044c44ac6cc6ea460fef8883d99fb9a88aadc Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 23 Jan 2020 13:33:55 +0900 Subject: [PATCH 32/40] Move beat snapping to its own interface --- ...tSceneHitObjectComposerDistanceSnapping.cs | 9 +++- .../Visual/Editor/TestSceneComposeScreen.cs | 2 + .../Editor/TestSceneEditorComposeTimeline.cs | 4 +- .../Editor/TestSceneHitObjectComposer.cs | 1 + osu.Game/Rulesets/Edit/HitObjectComposer.cs | 26 +++-------- osu.Game/Rulesets/Edit/IBeatSnapProvider.cs | 30 ++++++++++++ .../Compose/Components/Timeline/Timeline.cs | 46 ++++--------------- osu.Game/Screens/Edit/Editor.cs | 15 +++++- osu.Game/Screens/Edit/EditorBeatmap.cs | 20 +++++++- 9 files changed, 90 insertions(+), 63 deletions(-) create mode 100644 osu.Game/Rulesets/Edit/IBeatSnapProvider.cs diff --git a/osu.Game.Tests/Editor/TestSceneHitObjectComposerDistanceSnapping.cs b/osu.Game.Tests/Editor/TestSceneHitObjectComposerDistanceSnapping.cs index 2d336bd19c..e825df5a3f 100644 --- a/osu.Game.Tests/Editor/TestSceneHitObjectComposerDistanceSnapping.cs +++ b/osu.Game.Tests/Editor/TestSceneHitObjectComposerDistanceSnapping.cs @@ -5,6 +5,7 @@ using osu.Framework.Allocation; using osu.Framework.Testing; using osu.Game.Beatmaps.ControlPoints; +using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Osu.Beatmaps; using osu.Game.Rulesets.Osu.Edit; @@ -19,7 +20,13 @@ public class TestSceneHitObjectComposerDistanceSnapping : EditorClockTestScene private TestHitObjectComposer composer; [Cached(typeof(EditorBeatmap))] - private readonly EditorBeatmap editorBeatmap = new EditorBeatmap(new OsuBeatmap()); + [Cached(typeof(IBeatSnapProvider))] + private readonly EditorBeatmap editorBeatmap; + + public TestSceneHitObjectComposerDistanceSnapping() + { + editorBeatmap = new EditorBeatmap(new OsuBeatmap(), BeatDivisor); + } [SetUp] public void Setup() => Schedule(() => diff --git a/osu.Game.Tests/Visual/Editor/TestSceneComposeScreen.cs b/osu.Game.Tests/Visual/Editor/TestSceneComposeScreen.cs index 3562689482..a8830824c0 100644 --- a/osu.Game.Tests/Visual/Editor/TestSceneComposeScreen.cs +++ b/osu.Game.Tests/Visual/Editor/TestSceneComposeScreen.cs @@ -3,6 +3,7 @@ using NUnit.Framework; using osu.Framework.Allocation; +using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Osu.Beatmaps; using osu.Game.Screens.Edit; @@ -14,6 +15,7 @@ namespace osu.Game.Tests.Visual.Editor public class TestSceneComposeScreen : EditorClockTestScene { [Cached(typeof(EditorBeatmap))] + [Cached(typeof(IBeatSnapProvider))] private readonly EditorBeatmap editorBeatmap = new EditorBeatmap(new OsuBeatmap { diff --git a/osu.Game.Tests/Visual/Editor/TestSceneEditorComposeTimeline.cs b/osu.Game.Tests/Visual/Editor/TestSceneEditorComposeTimeline.cs index 78cdeb3ac8..5b0408268f 100644 --- a/osu.Game.Tests/Visual/Editor/TestSceneEditorComposeTimeline.cs +++ b/osu.Game.Tests/Visual/Editor/TestSceneEditorComposeTimeline.cs @@ -13,6 +13,7 @@ using osu.Framework.Timing; using osu.Game.Beatmaps; using osu.Game.Graphics.UserInterface; +using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Objects; using osu.Game.Screens.Edit; using osu.Game.Screens.Edit.Compose.Components.Timeline; @@ -38,9 +39,10 @@ private void load(AudioManager audio) { Beatmap.Value = new WaveformTestBeatmap(audio); - var editorBeatmap = new EditorBeatmap((Beatmap)Beatmap.Value.Beatmap); + var editorBeatmap = new EditorBeatmap((Beatmap)Beatmap.Value.Beatmap, BeatDivisor); Dependencies.Cache(editorBeatmap); + Dependencies.CacheAs(editorBeatmap); Children = new Drawable[] { diff --git a/osu.Game.Tests/Visual/Editor/TestSceneHitObjectComposer.cs b/osu.Game.Tests/Visual/Editor/TestSceneHitObjectComposer.cs index c001c83877..e41c2427fb 100644 --- a/osu.Game.Tests/Visual/Editor/TestSceneHitObjectComposer.cs +++ b/osu.Game.Tests/Visual/Editor/TestSceneHitObjectComposer.cs @@ -66,6 +66,7 @@ private void load() Dependencies.CacheAs(clock); Dependencies.CacheAs(clock); Dependencies.CacheAs(editorBeatmap); + Dependencies.CacheAs(editorBeatmap); Child = new OsuHitObjectComposer(new OsuRuleset()); } diff --git a/osu.Game/Rulesets/Edit/HitObjectComposer.cs b/osu.Game/Rulesets/Edit/HitObjectComposer.cs index 5eba31d149..50042d1e3b 100644 --- a/osu.Game/Rulesets/Edit/HitObjectComposer.cs +++ b/osu.Game/Rulesets/Edit/HitObjectComposer.cs @@ -46,7 +46,7 @@ public abstract class HitObjectComposer : HitObjectComposer, IPlacement private IAdjustableClock adjustableClock { get; set; } [Resolved] - private BindableBeatDivisor beatDivisor { get; set; } + private IBeatSnapProvider beatSnapProvider { get; set; } private IBeatmapProcessor beatmapProcessor; @@ -259,40 +259,26 @@ public void EndPlacement(HitObject hitObject) public override float GetBeatSnapDistanceAt(double referenceTime) { DifficultyControlPoint difficultyPoint = EditorBeatmap.ControlPointInfo.DifficultyPointAt(referenceTime); - return (float)(100 * EditorBeatmap.BeatmapInfo.BaseDifficulty.SliderMultiplier * difficultyPoint.SpeedMultiplier / beatDivisor.Value); + return (float)(100 * EditorBeatmap.BeatmapInfo.BaseDifficulty.SliderMultiplier * difficultyPoint.SpeedMultiplier / beatSnapProvider.BeatDivisor); } public override float DurationToDistance(double referenceTime, double duration) { - double beatLength = EditorBeatmap.ControlPointInfo.TimingPointAt(referenceTime).BeatLength / beatDivisor.Value; + double beatLength = beatSnapProvider.GetBeatLengthAtTime(referenceTime, beatSnapProvider.BeatDivisor); return (float)(duration / beatLength * GetBeatSnapDistanceAt(referenceTime)); } public override double DistanceToDuration(double referenceTime, float distance) { - double beatLength = EditorBeatmap.ControlPointInfo.TimingPointAt(referenceTime).BeatLength / beatDivisor.Value; + double beatLength = beatSnapProvider.GetBeatLengthAtTime(referenceTime, beatSnapProvider.BeatDivisor); return distance / GetBeatSnapDistanceAt(referenceTime) * beatLength; } public override double GetSnappedDurationFromDistance(double referenceTime, float distance) - => beatSnap(referenceTime, DistanceToDuration(referenceTime, distance)); + => beatSnapProvider.SnapTime(referenceTime, DistanceToDuration(referenceTime, distance), beatSnapProvider.BeatDivisor); public override float GetSnappedDistanceFromDistance(double referenceTime, float distance) - => DurationToDistance(referenceTime, beatSnap(referenceTime, DistanceToDuration(referenceTime, distance))); - - /// - /// Snaps a duration to the closest beat of a timing point applicable at the reference time. - /// - /// The time of the timing point which resides in. - /// The duration to snap. - /// A value that represents snapped to the closest beat of the timing point. - private double beatSnap(double referenceTime, double duration) - { - double beatLength = EditorBeatmap.ControlPointInfo.TimingPointAt(referenceTime).BeatLength / beatDivisor.Value; - - // A 1ms offset prevents rounding errors due to minute variations in duration - return (int)((duration + 1) / beatLength) * beatLength; - } + => DurationToDistance(referenceTime, beatSnapProvider.SnapTime(referenceTime, DistanceToDuration(referenceTime, distance), beatSnapProvider.BeatDivisor)); protected override void Dispose(bool isDisposing) { diff --git a/osu.Game/Rulesets/Edit/IBeatSnapProvider.cs b/osu.Game/Rulesets/Edit/IBeatSnapProvider.cs new file mode 100644 index 0000000000..ed6e08d054 --- /dev/null +++ b/osu.Game/Rulesets/Edit/IBeatSnapProvider.cs @@ -0,0 +1,30 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +namespace osu.Game.Rulesets.Edit +{ + public interface IBeatSnapProvider + { + /// + /// Snaps a duration to the closest beat of a timing point applicable at the reference time. + /// + /// The time of the timing point which resides in. + /// The duration to snap. + /// The divisor to use for snapping purposes. + /// A value that represents snapped to the closest beat of the timing point. + double SnapTime(double referenceTime, double duration, int beatDivisor); + + /// + /// Get the most appropriate beat length at a given time. + /// + /// A reference time used for lookup. + /// The divisor to use for snapping purposes. + /// The most appropriate beat length. + double GetBeatLengthAtTime(double referenceTime, int beatDivisor); + + /// + /// Returns the current beat divisor. + /// + int BeatDivisor { get; } + } +} diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs index 804c2f1e65..ec6f0bb923 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.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 osu.Framework.Allocation; using osu.Framework.Audio.Track; using osu.Framework.Bindables; @@ -10,7 +11,6 @@ using osu.Framework.Input.Events; using osu.Framework.Timing; using osu.Game.Beatmaps; -using osu.Game.Beatmaps.ControlPoints; using osu.Game.Graphics; using osu.Game.Rulesets.Edit; using osuTK; @@ -169,53 +169,25 @@ private void endUserDrag() } [Resolved] - private BindableBeatDivisor beatDivisor { get; set; } + private EditorBeatmap beatmap { get; set; } [Resolved] - private EditorBeatmap beatmap { get; set; } + private IBeatSnapProvider beatSnapProvider { get; set; } public (Vector2 position, double time) GetSnappedPosition(Vector2 position, double time) { var targetTime = (position.X / Content.DrawWidth) * track.Length; - return (position, beatSnap(targetTime, targetTime)); + return (position, beatSnapProvider.SnapTime(targetTime, targetTime, beatSnapProvider.BeatDivisor)); } - public float GetBeatSnapDistanceAt(double referenceTime) - { - DifficultyControlPoint difficultyPoint = beatmap.ControlPointInfo.DifficultyPointAt(referenceTime); - return (float)(100 * beatmap.BeatmapInfo.BaseDifficulty.SliderMultiplier * difficultyPoint.SpeedMultiplier / beatDivisor.Value); - } + public float GetBeatSnapDistanceAt(double referenceTime) => throw new NotImplementedException(); - public float DurationToDistance(double referenceTime, double duration) - { - double beatLength = beatmap.ControlPointInfo.TimingPointAt(referenceTime).BeatLength / beatDivisor.Value; - return (float)(duration / beatLength * GetBeatSnapDistanceAt(referenceTime)); - } + public float DurationToDistance(double referenceTime, double duration) => throw new NotImplementedException(); - public double DistanceToDuration(double referenceTime, float distance) - { - double beatLength = beatmap.ControlPointInfo.TimingPointAt(referenceTime).BeatLength / beatDivisor.Value; - return distance / GetBeatSnapDistanceAt(referenceTime) * beatLength; - } + public double DistanceToDuration(double referenceTime, float distance) => throw new NotImplementedException(); - public double GetSnappedDurationFromDistance(double referenceTime, float distance) - => beatSnap(referenceTime, DistanceToDuration(referenceTime, distance)); + public double GetSnappedDurationFromDistance(double referenceTime, float distance) => throw new NotImplementedException(); - public float GetSnappedDistanceFromDistance(double referenceTime, float distance) - => DurationToDistance(referenceTime, beatSnap(referenceTime, DistanceToDuration(referenceTime, distance))); - - /// - /// Snaps a duration to the closest beat of a timing point applicable at the reference time. - /// - /// The time of the timing point which resides in. - /// The duration to snap. - /// A value that represents snapped to the closest beat of the timing point. - private double beatSnap(double referenceTime, double duration) - { - double beatLength = beatmap.ControlPointInfo.TimingPointAt(referenceTime).BeatLength / beatDivisor.Value; - - // A 1ms offset prevents rounding errors due to minute variations in duration - return (int)((duration + 1) / beatLength) * beatLength; - } + public float GetSnappedDistanceFromDistance(double referenceTime, float distance) => throw new NotImplementedException(); } } diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index e212b429b9..26660c427c 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -26,6 +26,7 @@ using osu.Game.Beatmaps; using osu.Game.Graphics.Cursor; using osu.Game.Input.Bindings; +using osu.Game.Rulesets.Edit; using osu.Game.Screens.Edit.Compose; using osu.Game.Screens.Edit.Setup; using osu.Game.Screens.Edit.Timing; @@ -34,7 +35,8 @@ namespace osu.Game.Screens.Edit { - public class Editor : ScreenWithBeatmapBackground, IKeyBindingHandler + [Cached(typeof(IBeatSnapProvider))] + public class Editor : ScreenWithBeatmapBackground, IKeyBindingHandler, IBeatSnapProvider { public override float BackgroundParallaxAmount => 0.1f; @@ -77,11 +79,14 @@ private void load(OsuColour colours, GameHost host) clock.ChangeSource(sourceClock); playableBeatmap = Beatmap.Value.GetPlayableBeatmap(Beatmap.Value.BeatmapInfo.Ruleset); - editorBeatmap = new EditorBeatmap(playableBeatmap); + editorBeatmap = new EditorBeatmap(playableBeatmap, beatDivisor); dependencies.CacheAs(clock); dependencies.CacheAs(clock); + + // todo: remove caching of this and consume via editorBeatmap? dependencies.Cache(beatDivisor); + dependencies.CacheAs(editorBeatmap); EditorMenuBar menuBar; @@ -345,5 +350,11 @@ private void exportBeatmap() saveBeatmap(); beatmapManager.Export(Beatmap.Value.BeatmapSetInfo); } + + public double SnapTime(double referenceTime, double duration, int beatDivisor) => editorBeatmap.SnapTime(referenceTime, duration, beatDivisor); + + public double GetBeatLengthAtTime(double referenceTime, int beatDivisor) => editorBeatmap.GetBeatLengthAtTime(referenceTime, beatDivisor); + + public int BeatDivisor => beatDivisor.Value; } } diff --git a/osu.Game/Screens/Edit/EditorBeatmap.cs b/osu.Game/Screens/Edit/EditorBeatmap.cs index 2ff7563a88..b7dd94b6c5 100644 --- a/osu.Game/Screens/Edit/EditorBeatmap.cs +++ b/osu.Game/Screens/Edit/EditorBeatmap.cs @@ -8,11 +8,12 @@ using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Beatmaps.Timing; +using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Objects; namespace osu.Game.Screens.Edit { - public class EditorBeatmap : IBeatmap + public class EditorBeatmap : IBeatmap, IBeatSnapProvider { /// /// Invoked when a is added to this . @@ -33,11 +34,14 @@ public class EditorBeatmap : IBeatmap public readonly IBeatmap PlayableBeatmap; + private readonly BindableBeatDivisor beatDivisor; + private readonly Dictionary> startTimeBindables = new Dictionary>(); - public EditorBeatmap(IBeatmap playableBeatmap) + public EditorBeatmap(IBeatmap playableBeatmap, BindableBeatDivisor beatDivisor = null) { PlayableBeatmap = playableBeatmap; + this.beatDivisor = beatDivisor; foreach (var obj in HitObjects) trackStartTime(obj); @@ -123,5 +127,17 @@ private int findInsertionIndex(IReadOnlyList list, double startTime) return list.Count - 1; } + + public double SnapTime(double referenceTime, double duration, int beatDivisor) + { + double beatLength = GetBeatLengthAtTime(referenceTime, beatDivisor); + + // A 1ms offset prevents rounding errors due to minute variations in duration + return (int)((duration + 1) / beatLength) * beatLength; + } + + public double GetBeatLengthAtTime(double referenceTime, int beatDivisor) => ControlPointInfo.TimingPointAt(referenceTime).BeatLength / BeatDivisor; + + public int BeatDivisor => beatDivisor?.Value ?? 1; } } From 9d90799447027b3d74a92363514aed5a6b1911f9 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 23 Jan 2020 16:20:42 +0900 Subject: [PATCH 33/40] Remove useless container --- .../Edit/Compose/Components/BlueprintContainer.cs | 10 +++------- .../Components/Timeline/TimelineBlueprintContainer.cs | 4 ++-- 2 files changed, 5 insertions(+), 9 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs index 4ef8877795..23f62e4eaf 100644 --- a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs @@ -32,7 +32,7 @@ public abstract class BlueprintContainer : CompositeDrawable, IKeyBindingHandler protected DragBox DragBox { get; private set; } - private SelectionBlueprintContainer selectionBlueprints; + private Container selectionBlueprints; private SelectionHandler selectionHandler; @@ -83,7 +83,8 @@ private void load() }; } - protected virtual SelectionBlueprintContainer CreateSelectionBlueprintContainer() => new SelectionBlueprintContainer { RelativeSizeAxes = Axes.Both }; + protected virtual Container CreateSelectionBlueprintContainer() => + new Container { RelativeSizeAxes = Axes.Both }; protected override void LoadComplete() { @@ -430,10 +431,5 @@ protected override void Dispose(bool isDisposing) beatmap.HitObjectRemoved -= removeBlueprintFor; } } - - protected class SelectionBlueprintContainer : Container - { - //todo: remove - } } } diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineBlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineBlueprintContainer.cs index b0d3e2dcde..9fef2998bd 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineBlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineBlueprintContainer.cs @@ -42,7 +42,7 @@ public TimelineBlueprintContainer(EditorBeatmap beatmap) }); } - protected override SelectionBlueprintContainer CreateSelectionBlueprintContainer() => new TimelineSelectionBlueprintContainer { RelativeSizeAxes = Axes.Both }; + protected override Container CreateSelectionBlueprintContainer() => new TimelineSelectionBlueprintContainer { RelativeSizeAxes = Axes.Both }; protected override void LoadComplete() { @@ -130,7 +130,7 @@ public override bool HandleDrag(MouseButtonEvent e) } } - protected class TimelineSelectionBlueprintContainer : SelectionBlueprintContainer + protected class TimelineSelectionBlueprintContainer : Container { protected override Container Content { get; } From 5cadbb1ffb057f71f086b6a6053a65e15807001a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 23 Jan 2020 16:22:43 +0900 Subject: [PATCH 34/40] Move timeline blueprint to own class --- .../Timeline/TimelineBlueprintContainer.cs | 102 --------------- .../Timeline/TimelineHitObjectBlueprint.cs | 116 ++++++++++++++++++ 2 files changed, 116 insertions(+), 102 deletions(-) create mode 100644 osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineBlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineBlueprintContainer.cs index 9fef2998bd..c1458480a6 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineBlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineBlueprintContainer.cs @@ -2,9 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; -using JetBrains.Annotations; using osu.Framework.Allocation; -using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Primitives; @@ -12,7 +10,6 @@ using osu.Framework.Input.Events; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Objects; -using osu.Game.Rulesets.Objects.Types; using osu.Game.Screens.Edit.Components.Timelines.Summary.Parts; using osuTK; using osuTK.Graphics; @@ -139,104 +136,5 @@ public TimelineSelectionBlueprintContainer() AddInternal(new TimelinePart(Content = new Container { RelativeSizeAxes = Axes.Both }) { RelativeSizeAxes = Axes.Both }); } } - - private class TimelineHitObjectBlueprint : SelectionBlueprint - { - private readonly Circle circle; - - private readonly Container extensionBar; - - [UsedImplicitly] - private readonly Bindable startTime; - - public const float THICKNESS = 3; - - private const float circle_size = 16; - - public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => base.ReceivePositionalInputAt(screenSpacePos) || circle.ReceivePositionalInputAt(screenSpacePos); - - public TimelineHitObjectBlueprint(HitObject hitObject) - : base(hitObject) - { - Anchor = Anchor.CentreLeft; - Origin = Anchor.CentreLeft; - - startTime = hitObject.StartTimeBindable.GetBoundCopy(); - startTime.BindValueChanged(time => X = (float)time.NewValue, true); - - RelativePositionAxes = Axes.X; - - RelativeSizeAxes = Axes.X; - AutoSizeAxes = Axes.Y; - - if (hitObject is IHasEndTime) - { - AddInternal(extensionBar = new Container - { - CornerRadius = 2, - Masking = true, - Size = new Vector2(1, THICKNESS), - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - RelativePositionAxes = Axes.X, - RelativeSizeAxes = Axes.X, - Colour = Color4.Black, - Child = new Box - { - RelativeSizeAxes = Axes.Both, - } - }); - } - - AddInternal(circle = new Circle - { - Size = new Vector2(circle_size), - Anchor = Anchor.CentreLeft, - Origin = Anchor.Centre, - RelativePositionAxes = Axes.X, - AlwaysPresent = true, - Colour = Color4.White, - BorderColour = Color4.Black, - BorderThickness = THICKNESS, - }); - } - - protected override void Update() - { - base.Update(); - - // no bindable so we perform this every update - Width = (float)(HitObject.GetEndTime() - HitObject.StartTime); - } - - protected override void OnSelected() - { - circle.BorderColour = Color4.Orange; - if (extensionBar != null) - extensionBar.Colour = Color4.Orange; - } - - protected override void OnDeselected() - { - circle.BorderColour = Color4.Black; - if (extensionBar != null) - extensionBar.Colour = Color4.Black; - } - - public override Quad SelectionQuad - { - get - { - // correctly include the circle in the selection quad region, as it is usually outside the blueprint itself. - var circleQuad = circle.ScreenSpaceDrawQuad; - var actualQuad = ScreenSpaceDrawQuad; - - return new Quad(circleQuad.TopLeft, Vector2.ComponentMax(actualQuad.TopRight, circleQuad.TopRight), - circleQuad.BottomLeft, Vector2.ComponentMax(actualQuad.BottomRight, circleQuad.BottomRight)); - } - } - - public override Vector2 SelectionPoint => ScreenSpaceDrawQuad.TopLeft; - } } } diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs new file mode 100644 index 0000000000..2ed5471444 --- /dev/null +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs @@ -0,0 +1,116 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using JetBrains.Annotations; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Primitives; +using osu.Framework.Graphics.Shapes; +using osu.Game.Rulesets.Edit; +using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Objects.Types; +using osuTK; +using osuTK.Graphics; + +namespace osu.Game.Screens.Edit.Compose.Components.Timeline +{ + public class TimelineHitObjectBlueprint : SelectionBlueprint + { + private readonly Circle circle; + + private readonly Container extensionBar; + + [UsedImplicitly] + private readonly Bindable startTime; + + public const float THICKNESS = 3; + + private const float circle_size = 16; + + public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => base.ReceivePositionalInputAt(screenSpacePos) || circle.ReceivePositionalInputAt(screenSpacePos); + + public TimelineHitObjectBlueprint(HitObject hitObject) + : base(hitObject) + { + Anchor = Anchor.CentreLeft; + Origin = Anchor.CentreLeft; + + startTime = hitObject.StartTimeBindable.GetBoundCopy(); + startTime.BindValueChanged(time => X = (float)time.NewValue, true); + + RelativePositionAxes = Axes.X; + + RelativeSizeAxes = Axes.X; + AutoSizeAxes = Axes.Y; + + if (hitObject is IHasEndTime) + { + AddInternal(extensionBar = new Container + { + CornerRadius = 2, + Masking = true, + Size = new Vector2(1, THICKNESS), + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + RelativePositionAxes = Axes.X, + RelativeSizeAxes = Axes.X, + Colour = Color4.Black, + Child = new Box + { + RelativeSizeAxes = Axes.Both, + } + }); + } + + AddInternal(circle = new Circle + { + Size = new Vector2(circle_size), + Anchor = Anchor.CentreLeft, + Origin = Anchor.Centre, + RelativePositionAxes = Axes.X, + AlwaysPresent = true, + Colour = Color4.White, + BorderColour = Color4.Black, + BorderThickness = THICKNESS, + }); + } + + protected override void Update() + { + base.Update(); + + // no bindable so we perform this every update + Width = (float)(HitObject.GetEndTime() - HitObject.StartTime); + } + + protected override void OnSelected() + { + circle.BorderColour = Color4.Orange; + if (extensionBar != null) + extensionBar.Colour = Color4.Orange; + } + + protected override void OnDeselected() + { + circle.BorderColour = Color4.Black; + if (extensionBar != null) + extensionBar.Colour = Color4.Black; + } + + public override Quad SelectionQuad + { + get + { + // correctly include the circle in the selection quad region, as it is usually outside the blueprint itself. + var circleQuad = circle.ScreenSpaceDrawQuad; + var actualQuad = ScreenSpaceDrawQuad; + + return new Quad(circleQuad.TopLeft, Vector2.ComponentMax(actualQuad.TopRight, circleQuad.TopRight), + circleQuad.BottomLeft, Vector2.ComponentMax(actualQuad.BottomRight, circleQuad.BottomRight)); + } + } + + public override Vector2 SelectionPoint => ScreenSpaceDrawQuad.TopLeft; + } +} From 5646f7777e77bd6c7a5414debef4bfd9c7c769cd Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 23 Jan 2020 16:23:42 +0900 Subject: [PATCH 35/40] Add comment about custom SelectionHandler --- .../Components/Timeline/TimelineBlueprintContainer.cs | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineBlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineBlueprintContainer.cs index c1458480a6..6bfd323c13 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineBlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineBlueprintContainer.cs @@ -82,15 +82,16 @@ protected override void Update() protected override SelectionHandler CreateSelectionHandler() => new TimelineSelectionHandler(); - internal class TimelineSelectionHandler : SelectionHandler - { - public override bool HandleMovement(MoveSelectionEvent moveEvent) => true; - } - protected override SelectionBlueprint CreateBlueprintFor(HitObject hitObject) => new TimelineHitObjectBlueprint(hitObject); protected override DragBox CreateDragBox(Action performSelect) => new TimelineDragBox(performSelect); + internal class TimelineSelectionHandler : SelectionHandler + { + // for now we always allow movement. snapping is provided by the Timeline's "distance" snap implementation + public override bool HandleMovement(MoveSelectionEvent moveEvent) => true; + } + private class TimelineDragBox : DragBox { private Vector2 lastMouseDown; From 0e9ab8c76b843fcb35bd06d7ffd1c423549ba5f3 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 24 Jan 2020 13:39:47 +0900 Subject: [PATCH 36/40] Rename test scene to match --- ...mposeTimeline.cs => TestSceneTimelineBlueprintContainer.cs} | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) rename osu.Game.Tests/Visual/Editor/{TestSceneEditorComposeTimeline.cs => TestSceneTimelineBlueprintContainer.cs} (97%) diff --git a/osu.Game.Tests/Visual/Editor/TestSceneEditorComposeTimeline.cs b/osu.Game.Tests/Visual/Editor/TestSceneTimelineBlueprintContainer.cs similarity index 97% rename from osu.Game.Tests/Visual/Editor/TestSceneEditorComposeTimeline.cs rename to osu.Game.Tests/Visual/Editor/TestSceneTimelineBlueprintContainer.cs index 5b0408268f..34bf671eba 100644 --- a/osu.Game.Tests/Visual/Editor/TestSceneEditorComposeTimeline.cs +++ b/osu.Game.Tests/Visual/Editor/TestSceneTimelineBlueprintContainer.cs @@ -23,12 +23,11 @@ namespace osu.Game.Tests.Visual.Editor { [TestFixture] - public class TestSceneEditorComposeTimeline : EditorClockTestScene + public class TestSceneTimelineBlueprintContainer : EditorClockTestScene { public override IReadOnlyList RequiredTypes => new[] { typeof(TimelineArea), - typeof(TimelineBlueprintContainer), typeof(Timeline), typeof(TimelineButton), typeof(CentreMarker) From 28727bbafd1c7e5e3fb3bd2aabf73d47e900f24e Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 24 Jan 2020 17:30:37 +0900 Subject: [PATCH 37/40] Fix crash when deselecting via ctrl+click --- .../Edit/Compose/Components/BlueprintContainer.cs | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs index 23f62e4eaf..165fc93b9e 100644 --- a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs @@ -110,7 +110,6 @@ protected override void LoadComplete() protected override bool OnMouseDown(MouseDownEvent e) { beginClickSelection(e); - prepareSelectionMovement(); return e.Button == MouseButton.Left; @@ -356,21 +355,19 @@ private void onBlueprintDeselected(SelectionBlueprint blueprint) /// /// Attempts to begin the movement of any selected blueprints. /// - /// Whether movement began. - private bool prepareSelectionMovement() + private void prepareSelectionMovement() { - Debug.Assert(movementBlueprint == null); + if (!selectionHandler.SelectedBlueprints.Any()) + return; // Any selected blueprint that is hovered can begin the movement of the group, however only the earliest hitobject is used for movement // A special case is added for when a click selection occurred before the drag if (!clickSelectionBegan && !selectionHandler.SelectedBlueprints.Any(b => b.IsHovered)) - return false; + return; // Movement is tracked from the blueprint of the earliest hitobject, since it only makes sense to distance snap from that hitobject movementBlueprint = selectionHandler.SelectedBlueprints.OrderBy(b => b.HitObject.StartTime).First(); movementBlueprintOriginalPosition = movementBlueprint.SelectionPoint; // todo: unsure if correct - - return true; } /// From 6fc6a376ee285700940fcfbebac3da349e312d99 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 24 Jan 2020 17:50:09 +0900 Subject: [PATCH 38/40] Fix incorrect slider selection point --- .../Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs index 4fdead512a..c18b3b0ff3 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs @@ -170,7 +170,7 @@ private void updatePath() new OsuMenuItem("Add control point", MenuItemType.Standard, () => addControlPoint(rightClickPosition)), }; - public override Vector2 SelectionPoint => HeadBlueprint.SelectionPoint; + public override Vector2 SelectionPoint => ((DrawableSlider)DrawableObject).HeadCircle.ScreenSpaceDrawQuad.Centre; public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => BodyPiece.ReceivePositionalInputAt(screenSpacePos); From 811ddb02a4bca157bcb17b5fee4adbeb11415fd0 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 24 Jan 2020 17:50:36 +0900 Subject: [PATCH 39/40] General refactoring --- .../Blueprints/Sliders/SliderCircleSelectionBlueprint.cs | 1 + .../Visual/Editor/TestSceneTimelineBlueprintContainer.cs | 2 +- .../Screens/Edit/Compose/Components/BlueprintContainer.cs | 6 +++--- .../Components/Timeline/TimelineBlueprintContainer.cs | 6 +++--- osu.Game/Screens/Edit/Compose/ComposeScreen.cs | 2 +- 5 files changed, 9 insertions(+), 8 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderCircleSelectionBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderCircleSelectionBlueprint.cs index f09279ed73..a0392fe536 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderCircleSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderCircleSelectionBlueprint.cs @@ -17,6 +17,7 @@ public SliderCircleSelectionBlueprint(DrawableSlider slider, SliderPosition posi : base(slider) { this.position = position; + InternalChild = CirclePiece = new HitCirclePiece(); Select(); diff --git a/osu.Game.Tests/Visual/Editor/TestSceneTimelineBlueprintContainer.cs b/osu.Game.Tests/Visual/Editor/TestSceneTimelineBlueprintContainer.cs index 34bf671eba..e7b2508ac7 100644 --- a/osu.Game.Tests/Visual/Editor/TestSceneTimelineBlueprintContainer.cs +++ b/osu.Game.Tests/Visual/Editor/TestSceneTimelineBlueprintContainer.cs @@ -58,7 +58,7 @@ private void load(AudioManager audio) }, new TimelineArea { - Child = new TimelineBlueprintContainer(editorBeatmap), + Child = new TimelineBlueprintContainer(), Anchor = Anchor.Centre, Origin = Anchor.Centre, RelativeSizeAxes = Axes.X, diff --git a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs index 165fc93b9e..6b21f56567 100644 --- a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs @@ -83,9 +83,6 @@ private void load() }; } - protected virtual Container CreateSelectionBlueprintContainer() => - new Container { RelativeSizeAxes = Axes.Both }; - protected override void LoadComplete() { base.LoadComplete(); @@ -94,6 +91,9 @@ protected override void LoadComplete() beatmap.HitObjectRemoved += removeBlueprintFor; } + protected virtual Container CreateSelectionBlueprintContainer() => + new Container { RelativeSizeAxes = Axes.Both }; + /// /// Creates a which outlines s and handles movement of selections. /// diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineBlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineBlueprintContainer.cs index 6bfd323c13..3b9cb1df24 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineBlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineBlueprintContainer.cs @@ -23,7 +23,7 @@ internal class TimelineBlueprintContainer : BlueprintContainer private DragEvent lastDragEvent; - public TimelineBlueprintContainer(EditorBeatmap beatmap) + public TimelineBlueprintContainer() { RelativeSizeAxes = Axes.Both; Anchor = Anchor.Centre; @@ -39,14 +39,14 @@ public TimelineBlueprintContainer(EditorBeatmap beatmap) }); } - protected override Container CreateSelectionBlueprintContainer() => new TimelineSelectionBlueprintContainer { RelativeSizeAxes = Axes.Both }; - protected override void LoadComplete() { base.LoadComplete(); DragBox.Alpha = 0; } + protected override Container CreateSelectionBlueprintContainer() => new TimelineSelectionBlueprintContainer { RelativeSizeAxes = Axes.Both }; + protected override void OnDrag(DragEvent e) { if (timeline != null) diff --git a/osu.Game/Screens/Edit/Compose/ComposeScreen.cs b/osu.Game/Screens/Edit/Compose/ComposeScreen.cs index 20e1ef95d3..cdea200e10 100644 --- a/osu.Game/Screens/Edit/Compose/ComposeScreen.cs +++ b/osu.Game/Screens/Edit/Compose/ComposeScreen.cs @@ -32,6 +32,6 @@ protected override Drawable CreateMainContent() return beatmapSkinProvider.WithChild(rulesetSkinProvider.WithChild(composer)); } - protected override Drawable CreateTimelineContent() => composer == null ? base.CreateTimelineContent() : new TimelineBlueprintContainer(EditorBeatmap); + protected override Drawable CreateTimelineContent() => composer == null ? base.CreateTimelineContent() : new TimelineBlueprintContainer(); } } From 027778acc1dff50fcfd04ff116f0b94daf732a25 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 24 Jan 2020 17:51:24 +0900 Subject: [PATCH 40/40] Fix slider circles not being selected by default --- osu.Game/Rulesets/Edit/SelectionBlueprint.cs | 39 ++++++++++++-------- 1 file changed, 23 insertions(+), 16 deletions(-) diff --git a/osu.Game/Rulesets/Edit/SelectionBlueprint.cs b/osu.Game/Rulesets/Edit/SelectionBlueprint.cs index 9998254f76..a972d28480 100644 --- a/osu.Game/Rulesets/Edit/SelectionBlueprint.cs +++ b/osu.Game/Rulesets/Edit/SelectionBlueprint.cs @@ -41,14 +41,15 @@ public abstract class SelectionBlueprint : CompositeDrawable, IStateful Hide(); protected virtual void OnSelected() => Show();