From 6314694557e28d60b5bde1f90d2ce6ef9004c570 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 29 Mar 2018 17:13:45 +0900 Subject: [PATCH 01/61] Make HitObjectMaskLayer always create masks for all objects --- osu.Game/Rulesets/Edit/HitObjectComposer.cs | 9 ++----- .../Compose/Layers/HitObjectMaskLayer.cs | 24 ++++++++++++++----- 2 files changed, 20 insertions(+), 13 deletions(-) diff --git a/osu.Game/Rulesets/Edit/HitObjectComposer.cs b/osu.Game/Rulesets/Edit/HitObjectComposer.cs index c076b53f51..93a8980aa7 100644 --- a/osu.Game/Rulesets/Edit/HitObjectComposer.cs +++ b/osu.Game/Rulesets/Edit/HitObjectComposer.cs @@ -65,7 +65,7 @@ namespace osu.Game.Rulesets.Edit return; } - HitObjectMaskLayer hitObjectMaskLayer = new HitObjectMaskLayer(this); + HitObjectMaskLayer hitObjectMaskLayer = new HitObjectMaskLayer(rulesetContainer.Playfield, this); SelectionLayer selectionLayer = new SelectionLayer(rulesetContainer.Playfield); var layerBelowRuleset = new BorderLayer @@ -122,11 +122,6 @@ namespace osu.Game.Rulesets.Edit } }; - selectionLayer.ObjectSelected += hitObjectMaskLayer.AddOverlay; - selectionLayer.ObjectDeselected += hitObjectMaskLayer.RemoveOverlay; - selectionLayer.SelectionCleared += hitObjectMaskLayer.RemoveSelectionOverlay; - selectionLayer.SelectionFinished += hitObjectMaskLayer.AddSelectionOverlay; - toolboxCollection.Items = new[] { new RadioButton("Select", () => setCompositionTool(null)) } .Concat( @@ -267,7 +262,7 @@ namespace osu.Game.Rulesets.Edit /// and handles all hitobject movement/pattern adjustments. /// /// The overlays. - public virtual SelectionBox CreateSelectionOverlay(IReadOnlyList overlays) => new SelectionBox(overlays); + public virtual SelectionBox CreateSelectionBox(IReadOnlyList overlays) => new SelectionBox(overlays); /// /// Creates a which provides a layer above or below the . diff --git a/osu.Game/Screens/Edit/Screens/Compose/Layers/HitObjectMaskLayer.cs b/osu.Game/Screens/Edit/Screens/Compose/Layers/HitObjectMaskLayer.cs index 46b09e2c23..1412d98f31 100644 --- a/osu.Game/Screens/Edit/Screens/Compose/Layers/HitObjectMaskLayer.cs +++ b/osu.Game/Screens/Edit/Screens/Compose/Layers/HitObjectMaskLayer.cs @@ -2,31 +2,43 @@ // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE using System.Linq; +using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Objects.Drawables; +using osu.Game.Rulesets.UI; namespace osu.Game.Screens.Edit.Screens.Compose.Layers { public class HitObjectMaskLayer : CompositeDrawable { + private readonly Playfield playfield; private readonly HitObjectComposer composer; private readonly Container overlayContainer; - public HitObjectMaskLayer(HitObjectComposer composer) + public HitObjectMaskLayer(Playfield playfield, HitObjectComposer composer) { + this.playfield = playfield; this.composer = composer; + RelativeSizeAxes = Axes.Both; InternalChild = overlayContainer = new Container { RelativeSizeAxes = Axes.Both }; } + [BackgroundDependencyLoader] + private void load() + { + foreach (var obj in playfield.HitObjects.Objects) + addOverlay(obj); + } + /// /// Adds an overlay for a which adds movement support. /// /// The to create an overlay for. - public void AddOverlay(DrawableHitObject hitObject) + private void addOverlay(DrawableHitObject hitObject) { var overlay = composer.CreateMaskFor(hitObject); if (overlay == null) @@ -39,7 +51,7 @@ namespace osu.Game.Screens.Edit.Screens.Compose.Layers /// Removes the overlay for a . /// /// The to remove the overlay for. - public void RemoveOverlay(DrawableHitObject hitObject) + private void removeOverlay(DrawableHitObject hitObject) { var existing = overlayContainer.FirstOrDefault(h => h.HitObject == hitObject); if (existing == null) @@ -51,13 +63,13 @@ namespace osu.Game.Screens.Edit.Screens.Compose.Layers private SelectionBox currentSelectionBox; - public void AddSelectionOverlay() + private void addSelectionBox() { if (overlayContainer.Count > 0) - AddInternal(currentSelectionBox = composer.CreateSelectionOverlay(overlayContainer)); + AddInternal(currentSelectionBox = composer.CreateSelectionBox(overlayContainer)); } - public void RemoveSelectionOverlay() + private void removeSelectionBox() { currentSelectionBox?.Hide(); currentSelectionBox?.Expire(); From 4bdfc9dca9c288304129ae0e3b64c7bf000a7aa5 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 29 Mar 2018 17:19:14 +0900 Subject: [PATCH 02/61] Fix testcase --- osu.Game.Tests/Visual/TestCaseEditorSelectionLayer.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/TestCaseEditorSelectionLayer.cs b/osu.Game.Tests/Visual/TestCaseEditorSelectionLayer.cs index bbbfef477a..d56417f144 100644 --- a/osu.Game.Tests/Visual/TestCaseEditorSelectionLayer.cs +++ b/osu.Game.Tests/Visual/TestCaseEditorSelectionLayer.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using JetBrains.Annotations; using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Timing; @@ -32,7 +33,8 @@ namespace osu.Game.Tests.Visual typeof(HitObjectMask), typeof(HitCircleMask), typeof(SliderMask), - typeof(SliderCircleMask) + typeof(SliderCircleMask), + typeof(NotNullAttribute) }; private DependencyContainer dependencies; From 57e4281601feb42d49c851efb5ea93d50c2ef455 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 29 Mar 2018 17:19:24 +0900 Subject: [PATCH 03/61] Make HitObjectMasks VisibilityContainers --- osu.Game/Rulesets/Edit/HitObjectMask.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/Edit/HitObjectMask.cs b/osu.Game/Rulesets/Edit/HitObjectMask.cs index 051b42fec6..e79e540bce 100644 --- a/osu.Game/Rulesets/Edit/HitObjectMask.cs +++ b/osu.Game/Rulesets/Edit/HitObjectMask.cs @@ -9,13 +9,17 @@ namespace osu.Game.Rulesets.Edit /// /// A mask placed above a adding editing functionality. /// - public class HitObjectMask : Container + public class HitObjectMask : VisibilityContainer { public readonly DrawableHitObject HitObject; public HitObjectMask(DrawableHitObject hitObject) { HitObject = hitObject; + State = Visibility.Hidden; } + + protected override void PopIn() => Alpha = 1; + protected override void PopOut() => Alpha = 0; } } From 6d4f94756e339f1c06d11f3ba077b6aae006394d Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 29 Mar 2018 22:06:45 +0900 Subject: [PATCH 04/61] Rewrite the way drag + click selections happen The general idea here is that we need the masks to handle mouse down events, as they need to handle the drag (mousedown -> drag immediately). I've rewritten the editor selections to use events, as there are some 3 different components that handle/trigger selections in different ways. 1. All selections/deselections now propagate through `HitObjectMask.Select()`/`HitObjectMask.Deselect()`. 2. Components that react to changes in the selection bind to the masks' `Selected`/`Deselected` events, and track them/change their states locally. 3. Masks provide a `SingleSelectionRequested` event which is invoked on the mouse-down event. Various components bind to this event to perform state changes locally in this scenario. 4. `DragBox` now handles all drag input locally. It triggers `Select`/`Deselect` on the masks it needs to. 5. `SelectionBox` handles the display of itself locally. 6. `SelectionBox` handles movement of groups of masks locally. 7. `HitObjectMasks` handles movement of itself locally. --- .../Selection/Overlays/HitCircleMask.cs | 2 + .../Layers/Selection/Overlays/SliderMask.cs | 4 + .../Objects/Drawables/DrawableSlider.cs | 4 - .../Visual/TestCaseEditorSelectionLayer.cs | 1 - osu.Game/Rulesets/Edit/HitObjectComposer.cs | 14 +- osu.Game/Rulesets/Edit/HitObjectMask.cs | 80 ++++++ .../Objects/Drawables/DrawableHitObject.cs | 12 - .../Edit/Screens/Compose/Layers/DragBox.cs | 97 +++++++ .../Compose/Layers/HitObjectMaskLayer.cs | 54 +++- .../Screens/Compose/Layers/SelectionBox.cs | 157 ++++++++---- .../Screens/Compose/Layers/SelectionLayer.cs | 240 ------------------ 11 files changed, 334 insertions(+), 331 deletions(-) create mode 100644 osu.Game/Screens/Edit/Screens/Compose/Layers/DragBox.cs delete mode 100644 osu.Game/Screens/Edit/Screens/Compose/Layers/SelectionLayer.cs diff --git a/osu.Game.Rulesets.Osu/Edit/Layers/Selection/Overlays/HitCircleMask.cs b/osu.Game.Rulesets.Osu/Edit/Layers/Selection/Overlays/HitCircleMask.cs index b48dd73bb5..89a7686581 100644 --- a/osu.Game.Rulesets.Osu/Edit/Layers/Selection/Overlays/HitCircleMask.cs +++ b/osu.Game.Rulesets.Osu/Edit/Layers/Selection/Overlays/HitCircleMask.cs @@ -21,6 +21,8 @@ namespace osu.Game.Rulesets.Osu.Edit.Layers.Selection.Overlays Size = hitCircle.Size; Scale = hitCircle.Scale; + CornerRadius = Size.X / 2; + AddInternal(new RingPiece()); hitCircle.HitObject.PositionChanged += _ => Position = hitCircle.Position; diff --git a/osu.Game.Rulesets.Osu/Edit/Layers/Selection/Overlays/SliderMask.cs b/osu.Game.Rulesets.Osu/Edit/Layers/Selection/Overlays/SliderMask.cs index 53f02617cd..629bce1847 100644 --- a/osu.Game.Rulesets.Osu/Edit/Layers/Selection/Overlays/SliderMask.cs +++ b/osu.Game.Rulesets.Osu/Edit/Layers/Selection/Overlays/SliderMask.cs @@ -3,6 +3,7 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; +using osu.Framework.Graphics.Primitives; using osu.Game.Graphics; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Osu.Objects; @@ -59,5 +60,8 @@ namespace osu.Game.Rulesets.Osu.Edit.Layers.Selection.Overlays } public override bool ReceiveMouseInputAt(Vector2 screenSpacePos) => body.ReceiveMouseInputAt(screenSpacePos); + + public override Vector2 SelectionPoint => ToScreenSpace(OriginPosition); + public override Quad SelectionQuad => body.PathDrawQuad; } } diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs index 3872821b96..5373926138 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs @@ -10,7 +10,6 @@ using System.Linq; using osu.Framework.Allocation; using osu.Framework.Graphics.Containers; using osu.Game.Rulesets.Osu.Judgements; -using osu.Framework.Graphics.Primitives; using osu.Game.Configuration; using osu.Game.Rulesets.Scoring; using OpenTK.Graphics; @@ -177,8 +176,5 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables public Drawable ProxiedLayer => HeadCircle.ApproachCircle; public override bool ReceiveMouseInputAt(Vector2 screenSpacePos) => Body.ReceiveMouseInputAt(screenSpacePos); - - public override Vector2 SelectionPoint => ToScreenSpace(OriginPosition); - public override Quad SelectionQuad => Body.PathDrawQuad; } } diff --git a/osu.Game.Tests/Visual/TestCaseEditorSelectionLayer.cs b/osu.Game.Tests/Visual/TestCaseEditorSelectionLayer.cs index d56417f144..4e39548b5b 100644 --- a/osu.Game.Tests/Visual/TestCaseEditorSelectionLayer.cs +++ b/osu.Game.Tests/Visual/TestCaseEditorSelectionLayer.cs @@ -25,7 +25,6 @@ namespace osu.Game.Tests.Visual { public override IReadOnlyList RequiredTypes => new[] { - typeof(SelectionLayer), typeof(SelectionBox), typeof(HitObjectComposer), typeof(OsuHitObjectComposer), diff --git a/osu.Game/Rulesets/Edit/HitObjectComposer.cs b/osu.Game/Rulesets/Edit/HitObjectComposer.cs index 93a8980aa7..1a587bf8f5 100644 --- a/osu.Game/Rulesets/Edit/HitObjectComposer.cs +++ b/osu.Game/Rulesets/Edit/HitObjectComposer.cs @@ -65,9 +65,6 @@ namespace osu.Game.Rulesets.Edit return; } - HitObjectMaskLayer hitObjectMaskLayer = new HitObjectMaskLayer(rulesetContainer.Playfield, this); - SelectionLayer selectionLayer = new SelectionLayer(rulesetContainer.Playfield); - var layerBelowRuleset = new BorderLayer { RelativeSizeAxes = Axes.Both, @@ -75,12 +72,7 @@ namespace osu.Game.Rulesets.Edit }; var layerAboveRuleset = CreateLayerContainer(); - layerAboveRuleset.Children = new Drawable[] - { - selectionLayer, // Below object overlays for input - hitObjectMaskLayer, - selectionLayer.CreateProxy() // Proxy above object overlays for selections - }; + layerAboveRuleset.Child = new HitObjectMaskLayer(rulesetContainer.Playfield, this); layerContainers.Add(layerBelowRuleset); layerContainers.Add(layerAboveRuleset); @@ -259,10 +251,10 @@ namespace osu.Game.Rulesets.Edit /// /// Creates a which outlines s - /// and handles all hitobject movement/pattern adjustments. + /// and handles hitobject pattern adjustments. /// /// The overlays. - public virtual SelectionBox CreateSelectionBox(IReadOnlyList overlays) => new SelectionBox(overlays); + public virtual SelectionBox CreateSelectionBox() => new SelectionBox(); /// /// Creates a which provides a layer above or below the . diff --git a/osu.Game/Rulesets/Edit/HitObjectMask.cs b/osu.Game/Rulesets/Edit/HitObjectMask.cs index e79e540bce..44ee981783 100644 --- a/osu.Game/Rulesets/Edit/HitObjectMask.cs +++ b/osu.Game/Rulesets/Edit/HitObjectMask.cs @@ -1,8 +1,13 @@ // Copyright (c) 2007-2018 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +using System; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Primitives; +using osu.Framework.Input; +using osu.Game.Rulesets.Edit.Types; using osu.Game.Rulesets.Objects.Drawables; +using OpenTK; namespace osu.Game.Rulesets.Edit { @@ -11,15 +16,90 @@ namespace osu.Game.Rulesets.Edit /// public class HitObjectMask : VisibilityContainer { + public event Action Selected; + public event Action Deselected; + public event Action SingleSelectionRequested; + public readonly DrawableHitObject HitObject; + protected override bool ShouldBeAlive => HitObject.IsAlive || State == Visibility.Visible; + public override bool HandleMouseInput => true; + public HitObjectMask(DrawableHitObject hitObject) { HitObject = hitObject; + + AlwaysPresent = true; State = Visibility.Hidden; } + /// + /// Selects this , causing it to become visible. + /// + /// True if the was selected. False if the was already selected. + public bool Select() + { + if (State == Visibility.Visible) + return false; + + Show(); + Selected?.Invoke(this); + return true; + } + + /// + /// Deselects this , causing it to become invisible. + /// + /// True if the was deselected. False if the was already deselected. + public bool Deselect() + { + if (State == Visibility.Hidden) + return false; + + Hide(); + Deselected?.Invoke(this); + return true; + } + + protected override bool OnMouseDown(InputState state, MouseDownEventArgs args) + { + if (HitObject.IsPresent) + { + SingleSelectionRequested?.Invoke(this); + Select(); + return true; + } + + return false; + } + + protected override bool OnDragStart(InputState state) => true; + + protected override bool OnDrag(InputState state) + { + // Todo: Various forms of snapping + switch (HitObject.HitObject) + { + case IHasEditablePosition editablePosition: + editablePosition.OffsetPosition(state.Mouse.Delta); + break; + } + return true; + } + + protected override bool OnDragEnd(InputState state) => true; + protected override void PopIn() => Alpha = 1; protected override void PopOut() => Alpha = 0; + + /// + /// The screen-space point that causes this to be selected. + /// + public virtual Vector2 SelectionPoint => ScreenSpaceDrawQuad.Centre; + + /// + /// The screen-space quad that outlines this for selections. + /// + public virtual Quad SelectionQuad => ScreenSpaceDrawQuad; } } diff --git a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs index 348364a2bf..fdfef14a88 100644 --- a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs +++ b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs @@ -6,14 +6,12 @@ using System.Collections.Generic; using System.Linq; using osu.Framework.Allocation; using osu.Framework.Configuration; -using osu.Framework.Graphics.Primitives; using osu.Game.Audio; using osu.Game.Graphics; using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Scoring; using osu.Game.Skinning; -using OpenTK; using OpenTK.Graphics; namespace osu.Game.Rulesets.Objects.Drawables @@ -231,16 +229,6 @@ namespace osu.Game.Rulesets.Objects.Drawables protected virtual void CheckForJudgements(bool userTriggered, double timeOffset) { } - - /// - /// The screen-space point that causes this to be selected in the Editor. - /// - public virtual Vector2 SelectionPoint => ScreenSpaceDrawQuad.Centre; - - /// - /// The screen-space quad that outlines this for selections in the Editor. - /// - public virtual Quad SelectionQuad => ScreenSpaceDrawQuad; } public abstract class DrawableHitObject : DrawableHitObject diff --git a/osu.Game/Screens/Edit/Screens/Compose/Layers/DragBox.cs b/osu.Game/Screens/Edit/Screens/Compose/Layers/DragBox.cs new file mode 100644 index 0000000000..f70696f0f1 --- /dev/null +++ b/osu.Game/Screens/Edit/Screens/Compose/Layers/DragBox.cs @@ -0,0 +1,97 @@ +using System; +using System.Collections.Generic; +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; +using osu.Game.Rulesets.Edit; +using OpenTK.Graphics; + +namespace osu.Game.Screens.Edit.Screens.Compose.Layers +{ + /// + /// A box that represents a drag selection. + /// + public class DragBox : CompositeDrawable + { + public event Action DragEnd; + + private readonly IEnumerable hitObjectMasks; + + private Drawable box; + + /// + /// Creates a new . + /// + /// The selectable s. + public DragBox(IEnumerable hitObjectMasks) + { + this.hitObjectMasks = hitObjectMasks; + + RelativeSizeAxes = Axes.Both; + AlwaysPresent = true; + Alpha = 0; + } + + [BackgroundDependencyLoader] + private void load() + { + InternalChild = box = new Container + { + Masking = true, + BorderColour = Color4.White, + BorderThickness = SelectionBox.BORDER_RADIUS, + Child = new Box + { + RelativeSizeAxes = Axes.Both, + Alpha = 0.1f + } + }; + } + + protected override bool OnDragStart(InputState state) + { + this.FadeIn(250, Easing.OutQuint); + return true; + } + + protected override bool OnDrag(InputState state) + { + var dragPosition = state.Mouse.NativeState.Position; + var dragStartPosition = state.Mouse.NativeState.PositionMouseDown ?? dragPosition; + + var dragQuad = new Quad(dragStartPosition.X, dragStartPosition.Y, dragPosition.X - dragStartPosition.X, dragPosition.Y - dragStartPosition.Y); + + // We use AABBFloat instead of RectangleF since it handles negative sizes for us + SetDragRectangle(dragQuad.AABBFloat); + + return true; + } + + protected override bool OnDragEnd(InputState state) + { + this.FadeOut(250, Easing.OutQuint); + DragEnd?.Invoke(); + return true; + } + + public void SetDragRectangle(RectangleF screenSpaceRectangle) + { + var topLeft = ToLocalSpace(screenSpaceRectangle.TopLeft); + var bottomRight = ToLocalSpace(screenSpaceRectangle.BottomRight); + + box.Position = topLeft; + box.Size = bottomRight - topLeft; + + foreach (var mask in hitObjectMasks) + { + if (mask.IsAlive && mask.IsPresent && screenSpaceRectangle.Contains(mask.SelectionPoint)) + mask.Select(); + else + mask.Deselect(); + } + } + } +} diff --git a/osu.Game/Screens/Edit/Screens/Compose/Layers/HitObjectMaskLayer.cs b/osu.Game/Screens/Edit/Screens/Compose/Layers/HitObjectMaskLayer.cs index 1412d98f31..ac7ba76220 100644 --- a/osu.Game/Screens/Edit/Screens/Compose/Layers/HitObjectMaskLayer.cs +++ b/osu.Game/Screens/Edit/Screens/Compose/Layers/HitObjectMaskLayer.cs @@ -1,10 +1,12 @@ // Copyright (c) 2007-2018 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +using System.Collections.Generic; using System.Linq; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Input; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.UI; @@ -17,6 +19,10 @@ namespace osu.Game.Screens.Edit.Screens.Compose.Layers private readonly HitObjectComposer composer; private readonly Container overlayContainer; + private readonly SelectionBox selectionBox; + + private readonly HashSet selectedObjects = new HashSet(); + public HitObjectMaskLayer(Playfield playfield, HitObjectComposer composer) { this.playfield = playfield; @@ -24,7 +30,19 @@ namespace osu.Game.Screens.Edit.Screens.Compose.Layers RelativeSizeAxes = Axes.Both; - InternalChild = overlayContainer = new Container { RelativeSizeAxes = Axes.Both }; + overlayContainer = new Container(); + selectionBox = composer.CreateSelectionBox(); + + var dragBox = new DragBox(overlayContainer); + dragBox.DragEnd += () => selectionBox.FinishSelection(); + + InternalChildren = new Drawable[] + { + dragBox, + overlayContainer, + selectionBox, + dragBox.CreateProxy() + }; } [BackgroundDependencyLoader] @@ -44,7 +62,12 @@ namespace osu.Game.Screens.Edit.Screens.Compose.Layers if (overlay == null) return; + overlay.Selected += onSelected; + overlay.Deselected += onDeselected; + overlay.SingleSelectionRequested += onSingleSelectionRequested; + overlayContainer.Add(overlay); + selectionBox.AddMask(overlay); } /// @@ -57,22 +80,29 @@ namespace osu.Game.Screens.Edit.Screens.Compose.Layers if (existing == null) return; - existing.Hide(); - existing.Expire(); + existing.Selected -= onSelected; + existing.Deselected -= onDeselected; + existing.SingleSelectionRequested -= onSingleSelectionRequested; + + overlayContainer.Remove(existing); + selectionBox.RemoveMask(existing); } - private SelectionBox currentSelectionBox; + private void onSelected(HitObjectMask mask) => selectedObjects.Add(mask); - private void addSelectionBox() + private void onDeselected(HitObjectMask mask) => selectedObjects.Remove(mask); + + private void onSingleSelectionRequested(HitObjectMask mask) => DeselectAll(); + + protected override bool OnMouseDown(InputState state, MouseDownEventArgs args) { - if (overlayContainer.Count > 0) - AddInternal(currentSelectionBox = composer.CreateSelectionBox(overlayContainer)); + DeselectAll(); + return true; } - private void removeSelectionBox() - { - currentSelectionBox?.Hide(); - currentSelectionBox?.Expire(); - } + /// + /// Deselects all selected s. + /// + public void DeselectAll() => overlayContainer.ToList().ForEach(m => m.Deselect()); } } diff --git a/osu.Game/Screens/Edit/Screens/Compose/Layers/SelectionBox.cs b/osu.Game/Screens/Edit/Screens/Compose/Layers/SelectionBox.cs index 0e5d824559..8249c08a7a 100644 --- a/osu.Game/Screens/Edit/Screens/Compose/Layers/SelectionBox.cs +++ b/osu.Game/Screens/Edit/Screens/Compose/Layers/SelectionBox.cs @@ -1,6 +1,7 @@ // Copyright (c) 2007-2018 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +using System; using System.Collections.Generic; using System.Linq; using osu.Framework.Allocation; @@ -19,84 +20,138 @@ namespace osu.Game.Screens.Edit.Screens.Compose.Layers /// /// A box which surrounds s and provides interactive handles, context menus etc. /// - public class SelectionBox : VisibilityContainer + public class SelectionBox : CompositeDrawable { - private readonly IReadOnlyList overlays; - public const float BORDER_RADIUS = 2; - public SelectionBox(IReadOnlyList overlays) + private readonly HashSet selectedMasks = new HashSet(); + + private Drawable box; + + public SelectionBox() { - this.overlays = overlays; - - Masking = true; - BorderThickness = BORDER_RADIUS; - - InternalChild = new Box - { - RelativeSizeAxes = Axes.Both, - AlwaysPresent = true, - Alpha = 0 - }; - - State = Visibility.Visible; + RelativeSizeAxes = Axes.Both; + AlwaysPresent = true; + Alpha = 0; } [BackgroundDependencyLoader] private void load(OsuColour colours) { - BorderColour = colours.Yellow; + InternalChild = box = new Container + { + Masking = true, + BorderThickness = BORDER_RADIUS, + BorderColour = colours.Yellow, + Child = new Box + { + RelativeSizeAxes = Axes.Both, + AlwaysPresent = true, + Alpha = 0 + } + }; + } + + public void AddMask(HitObjectMask mask) + { + mask.Selected += onSelected; + mask.Deselected += onDeselected; + mask.SingleSelectionRequested += onSingleSelectionRequested; + } + + public void RemoveMask(HitObjectMask mask) + { + mask.Selected -= onSelected; + mask.Deselected -= onDeselected; + mask.SingleSelectionRequested -= onSingleSelectionRequested; + } + + private void onSelected(HitObjectMask mask) => selectedMasks.Add(mask); + + private void onDeselected(HitObjectMask mask) + { + selectedMasks.Remove(mask); + + if (selectedMasks.Count == 0) + FinishSelection(); + } + + private void onSingleSelectionRequested(HitObjectMask mask) + { + selectedMasks.Add(mask); + FinishSelection(); + } + + // Only handle clicks on the selected masks + public override bool ReceiveMouseInputAt(Vector2 screenSpacePos) => selectedMasks.Any(m => m.ReceiveMouseInputAt(screenSpacePos)); + + protected override bool OnMouseDown(InputState state, MouseDownEventArgs args) => true; + + protected override bool OnClick(InputState state) + { + if (state.Mouse.NativeState.PositionMouseDown == null) + throw new InvalidOperationException("Click event received without a mouse down position."); + + // If the mouse has moved slightly, but hasn't been dragged, select the mask which would've handled the mouse down + selectedMasks.First(m => m.ReceiveMouseInputAt(state.Mouse.NativeState.PositionMouseDown.Value)).TriggerOnMouseDown(state); + return true; + } + + protected override bool OnDragStart(InputState state) => true; + + protected override bool OnDrag(InputState state) + { + // Todo: Various forms of snapping + + foreach (var mask in selectedMasks) + { + switch (mask.HitObject) + { + case IHasEditablePosition editablePosition: + editablePosition.OffsetPosition(state.Mouse.Delta); + break; + } + } + + return true; + } + + protected override bool OnDragEnd(InputState state) => true; + + public void FinishSelection() + { + if (selectedMasks.Count > 0) + Show(); + else + Hide(); } protected override void Update() { base.Update(); + if (selectedMasks.Count == 0) + return; + // Todo: We might need to optimise this // Move the rectangle to cover the hitobjects var topLeft = new Vector2(float.MaxValue, float.MaxValue); var bottomRight = new Vector2(float.MinValue, float.MinValue); - foreach (var obj in overlays) + bool hasSelection = false; + + foreach (var mask in selectedMasks) { - topLeft = Vector2.ComponentMin(topLeft, Parent.ToLocalSpace(obj.HitObject.SelectionQuad.TopLeft)); - bottomRight = Vector2.ComponentMax(bottomRight, Parent.ToLocalSpace(obj.HitObject.SelectionQuad.BottomRight)); + topLeft = Vector2.ComponentMin(topLeft, ToLocalSpace(mask.SelectionQuad.TopLeft)); + bottomRight = Vector2.ComponentMax(bottomRight, ToLocalSpace(mask.SelectionQuad.BottomRight)); } topLeft -= new Vector2(5); bottomRight += new Vector2(5); - Size = bottomRight - topLeft; - Position = topLeft; + box.Size = bottomRight - topLeft; + box.Position = topLeft; } - - public override bool ReceiveMouseInputAt(Vector2 screenSpacePos) => overlays.Any(o => o.ReceiveMouseInputAt(screenSpacePos)); - - protected override bool OnMouseDown(InputState state, MouseDownEventArgs args) => true; - - protected override bool OnDragStart(InputState state) => true; - - protected override bool OnDrag(InputState state) - { - // Todo: Various forms of snapping - foreach (var hitObject in overlays.Select(o => o.HitObject.HitObject)) - { - switch (hitObject) - { - case IHasEditablePosition editablePosition: - editablePosition.OffsetPosition(state.Mouse.Delta); - break; - } - } - return true; - } - - protected override bool OnDragEnd(InputState state) => true; - - public override bool DisposeOnDeathRemoval => true; - - protected override void PopIn() => this.FadeIn(); - protected override void PopOut() => this.FadeOut(); } } diff --git a/osu.Game/Screens/Edit/Screens/Compose/Layers/SelectionLayer.cs b/osu.Game/Screens/Edit/Screens/Compose/Layers/SelectionLayer.cs deleted file mode 100644 index ab51385980..0000000000 --- a/osu.Game/Screens/Edit/Screens/Compose/Layers/SelectionLayer.cs +++ /dev/null @@ -1,240 +0,0 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System; -using System.Collections.Generic; -using System.Linq; -using osu.Framework.Extensions.IEnumerableExtensions; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Primitives; -using osu.Framework.Graphics.Shapes; -using osu.Framework.Input; -using osu.Game.Rulesets.Objects.Drawables; -using osu.Game.Rulesets.UI; -using OpenTK; -using OpenTK.Graphics; -using RectangleF = osu.Framework.Graphics.Primitives.RectangleF; - -namespace osu.Game.Screens.Edit.Screens.Compose.Layers -{ - public class SelectionLayer : CompositeDrawable - { - /// - /// Invoked when a is selected. - /// - public event Action ObjectSelected; - - /// - /// Invoked when a is deselected. - /// - public event Action ObjectDeselected; - - /// - /// Invoked when the selection has been cleared. - /// - public event Action SelectionCleared; - - /// - /// Invoked when the user has finished selecting all s. - /// - public event Action SelectionFinished; - - private readonly Playfield playfield; - - public SelectionLayer(Playfield playfield) - { - this.playfield = playfield; - - RelativeSizeAxes = Axes.Both; - } - - private DragBox dragBox; - - private readonly HashSet selectedHitObjects = new HashSet(); - - protected override bool OnMouseDown(InputState state, MouseDownEventArgs args) - { - DeselectAll(); - return true; - } - - protected override bool OnDragStart(InputState state) - { - AddInternal(dragBox = new DragBox()); - return true; - } - - protected override bool OnDrag(InputState state) - { - dragBox.Show(); - - var dragPosition = state.Mouse.NativeState.Position; - var dragStartPosition = state.Mouse.NativeState.PositionMouseDown ?? dragPosition; - - var screenSpaceDragQuad = new Quad(dragStartPosition.X, dragStartPosition.Y, dragPosition.X - dragStartPosition.X, dragPosition.Y - dragStartPosition.Y); - - dragBox.SetDragRectangle(screenSpaceDragQuad.AABBFloat); - selectQuad(screenSpaceDragQuad); - - return true; - } - - protected override bool OnDragEnd(InputState state) - { - dragBox.Hide(); - dragBox.Expire(); - - finishSelection(); - - return true; - } - - protected override bool OnClick(InputState state) - { - selectPoint(state.Mouse.NativeState.Position); - finishSelection(); - - return true; - } - - /// - /// Selects a . - /// - /// The to select. - public void Select(DrawableHitObject hitObject) - { - if (!select(hitObject)) - return; - - clearSelection(); - finishSelection(); - } - - /// - /// Selects a without performing capture updates. - /// - /// The to select. - /// Whether was selected. - private bool select(DrawableHitObject hitObject) - { - if (!selectedHitObjects.Add(hitObject)) - return false; - - ObjectSelected?.Invoke(hitObject); - return true; - } - - /// - /// Deselects a . - /// - /// The to deselect. - public void Deselect(DrawableHitObject hitObject) - { - if (!deselect(hitObject)) - return; - - clearSelection(); - finishSelection(); - } - - /// - /// Deselects a without performing capture updates. - /// - /// The to deselect. - /// Whether the was deselected. - private bool deselect(DrawableHitObject hitObject) - { - if (!selectedHitObjects.Remove(hitObject)) - return false; - - ObjectDeselected?.Invoke(hitObject); - return true; - } - - /// - /// Deselects all selected s. - /// - public void DeselectAll() - { - selectedHitObjects.ForEach(h => ObjectDeselected?.Invoke(h)); - selectedHitObjects.Clear(); - - clearSelection(); - } - - /// - /// Selects all hitobjects that are present within the area of a . - /// - /// The selection . - // Todo: If needed we can severely reduce allocations in this method - private void selectQuad(Quad screenSpaceQuad) - { - var expectedSelection = playfield.HitObjects.Objects.Where(h => h.IsAlive && h.IsPresent && screenSpaceQuad.Contains(h.SelectionPoint)).ToList(); - - var toRemove = selectedHitObjects.Except(expectedSelection).ToList(); - foreach (var obj in toRemove) - deselect(obj); - - expectedSelection.ForEach(h => select(h)); - } - - /// - /// Selects the top-most hitobject that is present under a specific point. - /// - /// The to select at. - private void selectPoint(Vector2 screenSpacePoint) - { - var target = playfield.HitObjects.Objects.Reverse().Where(h => h.IsAlive && h.IsPresent).FirstOrDefault(h => h.ReceiveMouseInputAt(screenSpacePoint)); - if (target == null) - return; - - select(target); - } - - private void clearSelection() => SelectionCleared?.Invoke(); - - private void finishSelection() - { - if (selectedHitObjects.Count == 0) - return; - SelectionFinished?.Invoke(); - } - - /// - /// A box that represents a drag selection. - /// - private class DragBox : VisibilityContainer - { - /// - /// Creates a new . - /// - public DragBox() - { - Masking = true; - BorderColour = Color4.White; - BorderThickness = SelectionBox.BORDER_RADIUS; - - Child = new Box - { - RelativeSizeAxes = Axes.Both, - Alpha = 0.1f - }; - } - - public void SetDragRectangle(RectangleF rectangle) - { - var topLeft = Parent.ToLocalSpace(rectangle.TopLeft); - var bottomRight = Parent.ToLocalSpace(rectangle.BottomRight); - - Position = topLeft; - Size = bottomRight - topLeft; - } - - public override bool DisposeOnDeathRemoval => true; - - protected override void PopIn() => this.FadeIn(250, Easing.OutQuint); - protected override void PopOut() => this.FadeOut(250, Easing.OutQuint); - } - } -} From 04874bcda44092241f53a301d0fc8c85eefd02f1 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 29 Mar 2018 22:09:22 +0900 Subject: [PATCH 05/61] "overlay" -> "mask" --- .../Compose/Layers/HitObjectMaskLayer.cs | 52 +++++++++---------- 1 file changed, 26 insertions(+), 26 deletions(-) diff --git a/osu.Game/Screens/Edit/Screens/Compose/Layers/HitObjectMaskLayer.cs b/osu.Game/Screens/Edit/Screens/Compose/Layers/HitObjectMaskLayer.cs index ac7ba76220..6370456053 100644 --- a/osu.Game/Screens/Edit/Screens/Compose/Layers/HitObjectMaskLayer.cs +++ b/osu.Game/Screens/Edit/Screens/Compose/Layers/HitObjectMaskLayer.cs @@ -17,7 +17,7 @@ namespace osu.Game.Screens.Edit.Screens.Compose.Layers { private readonly Playfield playfield; private readonly HitObjectComposer composer; - private readonly Container overlayContainer; + private readonly Container maskContainer; private readonly SelectionBox selectionBox; @@ -30,16 +30,16 @@ namespace osu.Game.Screens.Edit.Screens.Compose.Layers RelativeSizeAxes = Axes.Both; - overlayContainer = new Container(); + maskContainer = new Container(); selectionBox = composer.CreateSelectionBox(); - var dragBox = new DragBox(overlayContainer); + var dragBox = new DragBox(maskContainer); dragBox.DragEnd += () => selectionBox.FinishSelection(); InternalChildren = new Drawable[] { dragBox, - overlayContainer, + maskContainer, selectionBox, dragBox.CreateProxy() }; @@ -49,43 +49,43 @@ namespace osu.Game.Screens.Edit.Screens.Compose.Layers private void load() { foreach (var obj in playfield.HitObjects.Objects) - addOverlay(obj); + addMask(obj); } /// - /// Adds an overlay for a which adds movement support. + /// Adds a mask for a which adds movement support. /// - /// The to create an overlay for. - private void addOverlay(DrawableHitObject hitObject) + /// The to create a mask for. + private void addMask(DrawableHitObject hitObject) { - var overlay = composer.CreateMaskFor(hitObject); - if (overlay == null) + var mask = composer.CreateMaskFor(hitObject); + if (mask == null) return; - overlay.Selected += onSelected; - overlay.Deselected += onDeselected; - overlay.SingleSelectionRequested += onSingleSelectionRequested; + mask.Selected += onSelected; + mask.Deselected += onDeselected; + mask.SingleSelectionRequested += onSingleSelectionRequested; - overlayContainer.Add(overlay); - selectionBox.AddMask(overlay); + maskContainer.Add(mask); + selectionBox.AddMask(mask); } /// - /// Removes the overlay for a . + /// Removes the mask for a . /// - /// The to remove the overlay for. - private void removeOverlay(DrawableHitObject hitObject) + /// The to remove the mask for. + private void removeMask(DrawableHitObject hitObject) { - var existing = overlayContainer.FirstOrDefault(h => h.HitObject == hitObject); - if (existing == null) + var mask = maskContainer.FirstOrDefault(h => h.HitObject == hitObject); + if (mask == null) return; - existing.Selected -= onSelected; - existing.Deselected -= onDeselected; - existing.SingleSelectionRequested -= onSingleSelectionRequested; + mask.Selected -= onSelected; + mask.Deselected -= onDeselected; + mask.SingleSelectionRequested -= onSingleSelectionRequested; - overlayContainer.Remove(existing); - selectionBox.RemoveMask(existing); + maskContainer.Remove(mask); + selectionBox.RemoveMask(mask); } private void onSelected(HitObjectMask mask) => selectedObjects.Add(mask); @@ -103,6 +103,6 @@ namespace osu.Game.Screens.Edit.Screens.Compose.Layers /// /// Deselects all selected s. /// - public void DeselectAll() => overlayContainer.ToList().ForEach(m => m.Deselect()); + public void DeselectAll() => maskContainer.ToList().ForEach(m => m.Deselect()); } } From 346de77776f37038f217bbb742b56cddc428684e Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 29 Mar 2018 22:13:28 +0900 Subject: [PATCH 06/61] Cleanup DragBox --- .../Edit/Screens/Compose/Layers/DragBox.cs | 38 +++++++++---------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/osu.Game/Screens/Edit/Screens/Compose/Layers/DragBox.cs b/osu.Game/Screens/Edit/Screens/Compose/Layers/DragBox.cs index f70696f0f1..f28f47ba48 100644 --- a/osu.Game/Screens/Edit/Screens/Compose/Layers/DragBox.cs +++ b/osu.Game/Screens/Edit/Screens/Compose/Layers/DragBox.cs @@ -12,10 +12,13 @@ using OpenTK.Graphics; namespace osu.Game.Screens.Edit.Screens.Compose.Layers { /// - /// A box that represents a drag selection. + /// A box that handles and displays drag selection for a collection of s. /// public class DragBox : CompositeDrawable { + /// + /// Invoked when the drag selection has finished. + /// public event Action DragEnd; private readonly IEnumerable hitObjectMasks; @@ -65,7 +68,21 @@ namespace osu.Game.Screens.Edit.Screens.Compose.Layers var dragQuad = new Quad(dragStartPosition.X, dragStartPosition.Y, dragPosition.X - dragStartPosition.X, dragPosition.Y - dragStartPosition.Y); // We use AABBFloat instead of RectangleF since it handles negative sizes for us - SetDragRectangle(dragQuad.AABBFloat); + var dragRectangle = dragQuad.AABBFloat; + + var topLeft = ToLocalSpace(dragRectangle.TopLeft); + var bottomRight = ToLocalSpace(dragRectangle.BottomRight); + + box.Position = topLeft; + box.Size = bottomRight - topLeft; + + foreach (var mask in hitObjectMasks) + { + if (mask.IsAlive && mask.IsPresent && dragRectangle.Contains(mask.SelectionPoint)) + mask.Select(); + else + mask.Deselect(); + } return true; } @@ -76,22 +93,5 @@ namespace osu.Game.Screens.Edit.Screens.Compose.Layers DragEnd?.Invoke(); return true; } - - public void SetDragRectangle(RectangleF screenSpaceRectangle) - { - var topLeft = ToLocalSpace(screenSpaceRectangle.TopLeft); - var bottomRight = ToLocalSpace(screenSpaceRectangle.BottomRight); - - box.Position = topLeft; - box.Size = bottomRight - topLeft; - - foreach (var mask in hitObjectMasks) - { - if (mask.IsAlive && mask.IsPresent && screenSpaceRectangle.Contains(mask.SelectionPoint)) - mask.Select(); - else - mask.Deselect(); - } - } } } From 1018711cc90ebfd0a8d4aac3907328274d4b32fa Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 29 Mar 2018 22:20:56 +0900 Subject: [PATCH 07/61] Cleanup SelectionBox --- .../Compose/Layers/HitObjectMaskLayer.cs | 2 +- .../Screens/Compose/Layers/SelectionBox.cs | 26 ++++++++++++++----- 2 files changed, 20 insertions(+), 8 deletions(-) diff --git a/osu.Game/Screens/Edit/Screens/Compose/Layers/HitObjectMaskLayer.cs b/osu.Game/Screens/Edit/Screens/Compose/Layers/HitObjectMaskLayer.cs index 6370456053..7b140ac37b 100644 --- a/osu.Game/Screens/Edit/Screens/Compose/Layers/HitObjectMaskLayer.cs +++ b/osu.Game/Screens/Edit/Screens/Compose/Layers/HitObjectMaskLayer.cs @@ -34,7 +34,7 @@ namespace osu.Game.Screens.Edit.Screens.Compose.Layers selectionBox = composer.CreateSelectionBox(); var dragBox = new DragBox(maskContainer); - dragBox.DragEnd += () => selectionBox.FinishSelection(); + dragBox.DragEnd += () => selectionBox.UpdateVisibility(); InternalChildren = new Drawable[] { diff --git a/osu.Game/Screens/Edit/Screens/Compose/Layers/SelectionBox.cs b/osu.Game/Screens/Edit/Screens/Compose/Layers/SelectionBox.cs index 8249c08a7a..bfdcdb0456 100644 --- a/osu.Game/Screens/Edit/Screens/Compose/Layers/SelectionBox.cs +++ b/osu.Game/Screens/Edit/Screens/Compose/Layers/SelectionBox.cs @@ -12,13 +12,12 @@ using osu.Framework.Input; using osu.Game.Graphics; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Edit.Types; -using osu.Game.Rulesets.Objects.Drawables; using OpenTK; namespace osu.Game.Screens.Edit.Screens.Compose.Layers { /// - /// A box which surrounds s and provides interactive handles, context menus etc. + /// A box which surrounds s and provides interactive handles, context menus etc. /// public class SelectionBox : CompositeDrawable { @@ -52,6 +51,10 @@ namespace osu.Game.Screens.Edit.Screens.Compose.Layers }; } + /// + /// Tracks a selectable . + /// + /// The to track. public void AddMask(HitObjectMask mask) { mask.Selected += onSelected; @@ -59,6 +62,10 @@ namespace osu.Game.Screens.Edit.Screens.Compose.Layers mask.SingleSelectionRequested += onSingleSelectionRequested; } + /// + /// Stops tracking a . + /// + /// The to stop tracking. public void RemoveMask(HitObjectMask mask) { mask.Selected -= onSelected; @@ -72,17 +79,18 @@ namespace osu.Game.Screens.Edit.Screens.Compose.Layers { selectedMasks.Remove(mask); + // We don't want to update visibility if > 0, since we may be deselecting masks during drag-selection if (selectedMasks.Count == 0) - FinishSelection(); + UpdateVisibility(); } private void onSingleSelectionRequested(HitObjectMask mask) { selectedMasks.Add(mask); - FinishSelection(); + UpdateVisibility(); } - // Only handle clicks on the selected masks + // Only handle input on the selected masks public override bool ReceiveMouseInputAt(Vector2 screenSpacePos) => selectedMasks.Any(m => m.ReceiveMouseInputAt(screenSpacePos)); protected override bool OnMouseDown(InputState state, MouseDownEventArgs args) => true; @@ -92,7 +100,8 @@ namespace osu.Game.Screens.Edit.Screens.Compose.Layers if (state.Mouse.NativeState.PositionMouseDown == null) throw new InvalidOperationException("Click event received without a mouse down position."); - // If the mouse has moved slightly, but hasn't been dragged, select the mask which would've handled the mouse down + // We handled mousedown, but if the mouse has been clicked and not dragged, select the mask which would've handled the mouse down + // A mousedown event is triggered such that a single selection is requested selectedMasks.First(m => m.ReceiveMouseInputAt(state.Mouse.NativeState.PositionMouseDown.Value)).TriggerOnMouseDown(state); return true; } @@ -118,7 +127,10 @@ namespace osu.Game.Screens.Edit.Screens.Compose.Layers protected override bool OnDragEnd(InputState state) => true; - public void FinishSelection() + /// + /// Updates whether this is visible. + /// + public void UpdateVisibility() { if (selectedMasks.Count > 0) Show(); From d8f26f22609015ffde96cd57244e91c9998c0a7c Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 29 Mar 2018 22:22:42 +0900 Subject: [PATCH 08/61] Make HitObjectMaskLayer not iterate through all masks when deselecting --- .../Edit/Screens/Compose/Layers/HitObjectMaskLayer.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game/Screens/Edit/Screens/Compose/Layers/HitObjectMaskLayer.cs b/osu.Game/Screens/Edit/Screens/Compose/Layers/HitObjectMaskLayer.cs index 7b140ac37b..2907f48568 100644 --- a/osu.Game/Screens/Edit/Screens/Compose/Layers/HitObjectMaskLayer.cs +++ b/osu.Game/Screens/Edit/Screens/Compose/Layers/HitObjectMaskLayer.cs @@ -21,7 +21,7 @@ namespace osu.Game.Screens.Edit.Screens.Compose.Layers private readonly SelectionBox selectionBox; - private readonly HashSet selectedObjects = new HashSet(); + private readonly HashSet selectedMasks = new HashSet(); public HitObjectMaskLayer(Playfield playfield, HitObjectComposer composer) { @@ -88,9 +88,9 @@ namespace osu.Game.Screens.Edit.Screens.Compose.Layers selectionBox.RemoveMask(mask); } - private void onSelected(HitObjectMask mask) => selectedObjects.Add(mask); + private void onSelected(HitObjectMask mask) => selectedMasks.Add(mask); - private void onDeselected(HitObjectMask mask) => selectedObjects.Remove(mask); + private void onDeselected(HitObjectMask mask) => selectedMasks.Remove(mask); private void onSingleSelectionRequested(HitObjectMask mask) => DeselectAll(); @@ -103,6 +103,6 @@ namespace osu.Game.Screens.Edit.Screens.Compose.Layers /// /// Deselects all selected s. /// - public void DeselectAll() => maskContainer.ToList().ForEach(m => m.Deselect()); + public void DeselectAll() => selectedMasks.ToList().ForEach(m => m.Deselect()); } } From 4446aeaa0de1b4f92e2644d3065343042581741b Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 29 Mar 2018 22:27:14 +0900 Subject: [PATCH 09/61] Commenting + cleanup of HitObjectMask/HitObjectMaskLayer --- osu.Game/Rulesets/Edit/HitObjectMask.cs | 28 +++++++++++++------ .../Compose/Layers/HitObjectMaskLayer.cs | 12 ++++---- 2 files changed, 25 insertions(+), 15 deletions(-) diff --git a/osu.Game/Rulesets/Edit/HitObjectMask.cs b/osu.Game/Rulesets/Edit/HitObjectMask.cs index 44ee981783..8b0d40dadc 100644 --- a/osu.Game/Rulesets/Edit/HitObjectMask.cs +++ b/osu.Game/Rulesets/Edit/HitObjectMask.cs @@ -16,14 +16,29 @@ namespace osu.Game.Rulesets.Edit /// public class HitObjectMask : VisibilityContainer { + /// + /// Invoked when this has been selected. + /// public event Action Selected; + + /// + /// Invoked when this has been deselected. + /// public event Action Deselected; + + /// + /// Invoked when this is requesting to be the single selection. + /// This has not been selected at this point, but will be selected immediately afterwards. + /// public event Action SingleSelectionRequested; + /// + /// The which this applies to. + /// public readonly DrawableHitObject HitObject; protected override bool ShouldBeAlive => HitObject.IsAlive || State == Visibility.Visible; - public override bool HandleMouseInput => true; + public override bool HandleMouseInput => HitObject.IsPresent; public HitObjectMask(DrawableHitObject hitObject) { @@ -63,14 +78,9 @@ namespace osu.Game.Rulesets.Edit protected override bool OnMouseDown(InputState state, MouseDownEventArgs args) { - if (HitObject.IsPresent) - { - SingleSelectionRequested?.Invoke(this); - Select(); - return true; - } - - return false; + SingleSelectionRequested?.Invoke(this); + Select(); + return true; } protected override bool OnDragStart(InputState state) => true; diff --git a/osu.Game/Screens/Edit/Screens/Compose/Layers/HitObjectMaskLayer.cs b/osu.Game/Screens/Edit/Screens/Compose/Layers/HitObjectMaskLayer.cs index 2907f48568..ca161a6f37 100644 --- a/osu.Game/Screens/Edit/Screens/Compose/Layers/HitObjectMaskLayer.cs +++ b/osu.Game/Screens/Edit/Screens/Compose/Layers/HitObjectMaskLayer.cs @@ -17,9 +17,9 @@ namespace osu.Game.Screens.Edit.Screens.Compose.Layers { private readonly Playfield playfield; private readonly HitObjectComposer composer; - private readonly Container maskContainer; - private readonly SelectionBox selectionBox; + private Container maskContainer; + private SelectionBox selectionBox; private readonly HashSet selectedMasks = new HashSet(); @@ -29,7 +29,11 @@ namespace osu.Game.Screens.Edit.Screens.Compose.Layers this.composer = composer; RelativeSizeAxes = Axes.Both; + } + [BackgroundDependencyLoader] + private void load() + { maskContainer = new Container(); selectionBox = composer.CreateSelectionBox(); @@ -43,11 +47,7 @@ namespace osu.Game.Screens.Edit.Screens.Compose.Layers selectionBox, dragBox.CreateProxy() }; - } - [BackgroundDependencyLoader] - private void load() - { foreach (var obj in playfield.HitObjects.Objects) addMask(obj); } From d9c5a0c6d1e7af52da3393ca2c90344e2eba416e Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 29 Mar 2018 22:38:44 +0900 Subject: [PATCH 10/61] Fix position editing not working --- osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs | 3 ++- osu.Game/Screens/Edit/Screens/Compose/Layers/SelectionBox.cs | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs b/osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs index c00c30ced9..f64db6ba9e 100644 --- a/osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs +++ b/osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs @@ -7,10 +7,11 @@ using osu.Game.Rulesets.Objects; using OpenTK; using osu.Game.Rulesets.Objects.Types; using osu.Game.Beatmaps.ControlPoints; +using osu.Game.Rulesets.Edit.Types; namespace osu.Game.Rulesets.Osu.Objects { - public abstract class OsuHitObject : HitObject, IHasComboInformation, IHasPosition + public abstract class OsuHitObject : HitObject, IHasComboInformation, IHasEditablePosition { public const double OBJECT_RADIUS = 64; diff --git a/osu.Game/Screens/Edit/Screens/Compose/Layers/SelectionBox.cs b/osu.Game/Screens/Edit/Screens/Compose/Layers/SelectionBox.cs index bfdcdb0456..ad8c846bbf 100644 --- a/osu.Game/Screens/Edit/Screens/Compose/Layers/SelectionBox.cs +++ b/osu.Game/Screens/Edit/Screens/Compose/Layers/SelectionBox.cs @@ -114,7 +114,7 @@ namespace osu.Game.Screens.Edit.Screens.Compose.Layers foreach (var mask in selectedMasks) { - switch (mask.HitObject) + switch (mask.HitObject.HitObject) { case IHasEditablePosition editablePosition: editablePosition.OffsetPosition(state.Mouse.Delta); From 3129c2cc75fd82e2a324aaf29d8e7f0d6cb771fe Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 29 Mar 2018 22:41:49 +0900 Subject: [PATCH 11/61] Fix slider circle masks blocking input for now --- .../Edit/Layers/Selection/Overlays/SliderCircleMask.cs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/osu.Game.Rulesets.Osu/Edit/Layers/Selection/Overlays/SliderCircleMask.cs b/osu.Game.Rulesets.Osu/Edit/Layers/Selection/Overlays/SliderCircleMask.cs index 586b516a11..fa9896ff7b 100644 --- a/osu.Game.Rulesets.Osu/Edit/Layers/Selection/Overlays/SliderCircleMask.cs +++ b/osu.Game.Rulesets.Osu/Edit/Layers/Selection/Overlays/SliderCircleMask.cs @@ -3,6 +3,8 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Input; using osu.Game.Graphics; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Osu.Objects; @@ -38,6 +40,8 @@ namespace osu.Game.Rulesets.Osu.Edit.Layers.Selection.Overlays Scale = slider.HeadCircle.Scale; AddInternal(new RingPiece()); + + State = Visibility.Visible; } [BackgroundDependencyLoader] @@ -52,5 +56,8 @@ namespace osu.Game.Rulesets.Osu.Edit.Layers.Selection.Overlays RelativeAnchorPosition = hitObject.RelativeAnchorPosition; } + + // Todo: This is temporary, since the slider circle masks don't do anything special yet. In the future they will handle input. + protected override bool OnMouseDown(InputState state, MouseDownEventArgs args) => false; } } From 6767dd3d4aa51b0947ed54cad45e9fbbaa5cdb26 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 29 Mar 2018 22:42:42 +0900 Subject: [PATCH 12/61] Fix hitobject masks dying with no recovery --- osu.Game/Rulesets/Edit/HitObjectMask.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Rulesets/Edit/HitObjectMask.cs b/osu.Game/Rulesets/Edit/HitObjectMask.cs index 8b0d40dadc..c55e34f548 100644 --- a/osu.Game/Rulesets/Edit/HitObjectMask.cs +++ b/osu.Game/Rulesets/Edit/HitObjectMask.cs @@ -38,6 +38,7 @@ namespace osu.Game.Rulesets.Edit public readonly DrawableHitObject HitObject; protected override bool ShouldBeAlive => HitObject.IsAlive || State == Visibility.Visible; + public override bool RemoveWhenNotAlive => false; public override bool HandleMouseInput => HitObject.IsPresent; public HitObjectMask(DrawableHitObject hitObject) From 6b2ca3665776831e7bbdfac47eb49252090ac014 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 29 Mar 2018 22:52:42 +0900 Subject: [PATCH 13/61] Add license header --- osu.Game/Screens/Edit/Screens/Compose/Layers/DragBox.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Screens/Compose/Layers/DragBox.cs b/osu.Game/Screens/Edit/Screens/Compose/Layers/DragBox.cs index f28f47ba48..0817541a21 100644 --- a/osu.Game/Screens/Edit/Screens/Compose/Layers/DragBox.cs +++ b/osu.Game/Screens/Edit/Screens/Compose/Layers/DragBox.cs @@ -1,4 +1,7 @@ -using System; +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; using System.Collections.Generic; using osu.Framework.Allocation; using osu.Framework.Graphics; From f43b009b96a31d18652cd25c0546a9f2e1193a3c Mon Sep 17 00:00:00 2001 From: DrabWeb Date: Thu, 29 Mar 2018 15:29:45 -0300 Subject: [PATCH 14/61] Add optional strip to OsuTabControl. --- .../Graphics/UserInterface/OsuTabControl.cs | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/osu.Game/Graphics/UserInterface/OsuTabControl.cs b/osu.Game/Graphics/UserInterface/OsuTabControl.cs index 20385a7dae..89188b29d7 100644 --- a/osu.Game/Graphics/UserInterface/OsuTabControl.cs +++ b/osu.Game/Graphics/UserInterface/OsuTabControl.cs @@ -20,16 +20,28 @@ namespace osu.Game.Graphics.UserInterface { public class OsuTabControl : TabControl { + private readonly Box strip; + protected override Dropdown CreateDropdown() => new OsuTabDropdown(); protected override TabItem CreateTabItem(T value) => new OsuTabItem(value); + protected virtual float StripWidth() => TabContainer.Children.Sum(c => c.IsPresent ? c.DrawWidth + TabContainer.Spacing.X : 0) - TabContainer.Spacing.X; + private static bool isEnumType => typeof(T).IsEnum; public OsuTabControl() { TabContainer.Spacing = new Vector2(10f, 0f); + Add(strip = new Box + { + Anchor = Anchor.BottomLeft, + Origin = Anchor.BottomLeft, + Height = 1, + Colour = Color4.White.Opacity(0), + }); + if (isEnumType) foreach (var val in (T[])Enum.GetValues(typeof(T))) AddItem(val); @@ -57,6 +69,12 @@ namespace osu.Game.Graphics.UserInterface } } + public Color4 StripColour + { + get => strip.Colour; + set => strip.Colour = value; + } + protected override TabFillFlowContainer CreateTabFlow() => new OsuTabFillFlowContainer { Direction = FillDirection.Full, @@ -65,6 +83,15 @@ namespace osu.Game.Graphics.UserInterface Masking = true }; + protected override void UpdateAfterChildren() + { + base.UpdateAfterChildren(); + + // dont bother calculating if the strip is invisible + if (strip.Colour.MaxAlpha > 0) + strip.ResizeWidthTo(StripWidth(), 500, Easing.OutQuint); + } + public class OsuTabItem : TabItem, IHasAccentColour { protected readonly SpriteText Text; From 3d05798d8047d216c6b2aeda02beca3444effa56 Mon Sep 17 00:00:00 2001 From: DrabWeb Date: Thu, 29 Mar 2018 15:34:53 -0300 Subject: [PATCH 15/61] Change SearchableListOverlay to use OsuTabControl strip. --- osu.Game/Overlays/Direct/Header.cs | 1 - .../Overlays/SearchableList/SearchableListHeader.cs | 12 +----------- osu.Game/Overlays/Social/Header.cs | 1 - 3 files changed, 1 insertion(+), 13 deletions(-) diff --git a/osu.Game/Overlays/Direct/Header.cs b/osu.Game/Overlays/Direct/Header.cs index 2245f70ed3..252e732614 100644 --- a/osu.Game/Overlays/Direct/Header.cs +++ b/osu.Game/Overlays/Direct/Header.cs @@ -13,7 +13,6 @@ namespace osu.Game.Overlays.Direct public class Header : SearchableListHeader { protected override Color4 BackgroundColour => OsuColour.FromHex(@"252f3a"); - protected override float TabStripWidth => 298; protected override DirectTab DefaultTab => DirectTab.Search; protected override Drawable CreateHeaderText() => new OsuSpriteText { Text = @"osu!direct", TextSize = 25 }; diff --git a/osu.Game/Overlays/SearchableList/SearchableListHeader.cs b/osu.Game/Overlays/SearchableList/SearchableListHeader.cs index 0f2650ad40..e053f2f773 100644 --- a/osu.Game/Overlays/SearchableList/SearchableListHeader.cs +++ b/osu.Game/Overlays/SearchableList/SearchableListHeader.cs @@ -14,12 +14,9 @@ namespace osu.Game.Overlays.SearchableList { public abstract class SearchableListHeader : Container { - private readonly Box tabStrip; - public readonly HeaderTabControl Tabs; protected abstract Color4 BackgroundColour { get; } - protected abstract float TabStripWidth { get; } //can be removed once (if?) TabControl support auto sizing protected abstract T DefaultTab { get; } protected abstract Drawable CreateHeaderText(); protected abstract FontAwesome Icon { get; } @@ -63,13 +60,6 @@ namespace osu.Game.Overlays.SearchableList CreateHeaderText(), }, }, - tabStrip = new Box - { - Anchor = Anchor.BottomLeft, - Origin = Anchor.BottomLeft, - Width = TabStripWidth, - Height = 1, - }, Tabs = new HeaderTabControl { Anchor = Anchor.BottomLeft, @@ -87,7 +77,7 @@ namespace osu.Game.Overlays.SearchableList [BackgroundDependencyLoader] private void load(OsuColour colours) { - tabStrip.Colour = colours.Green; + Tabs.StripColour = colours.Green; } } } diff --git a/osu.Game/Overlays/Social/Header.cs b/osu.Game/Overlays/Social/Header.cs index 7bb4b4dde9..89224e1315 100644 --- a/osu.Game/Overlays/Social/Header.cs +++ b/osu.Game/Overlays/Social/Header.cs @@ -17,7 +17,6 @@ namespace osu.Game.Overlays.Social private OsuSpriteText browser; protected override Color4 BackgroundColour => OsuColour.FromHex(@"38202e"); - protected override float TabStripWidth => 438; protected override SocialTab DefaultTab => SocialTab.AllPlayers; protected override FontAwesome Icon => FontAwesome.fa_users; From e3218250d52654e912c74ee4736236fd5538301b Mon Sep 17 00:00:00 2001 From: DrabWeb Date: Thu, 29 Mar 2018 15:41:27 -0300 Subject: [PATCH 16/61] Fix tab strip in BreadcrumbControl, allow strip height to be overriden. --- osu.Game.Tests/Visual/TestCaseBreadcrumbs.cs | 20 ++++++++++++++----- .../UserInterface/BreadcrumbControl.cs | 2 ++ .../Graphics/UserInterface/OsuTabControl.cs | 3 ++- 3 files changed, 19 insertions(+), 6 deletions(-) diff --git a/osu.Game.Tests/Visual/TestCaseBreadcrumbs.cs b/osu.Game.Tests/Visual/TestCaseBreadcrumbs.cs index 20bdd6736c..e3cef06f2f 100644 --- a/osu.Game.Tests/Visual/TestCaseBreadcrumbs.cs +++ b/osu.Game.Tests/Visual/TestCaseBreadcrumbs.cs @@ -2,7 +2,9 @@ // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE using NUnit.Framework; +using osu.Framework.Allocation; using osu.Framework.Graphics; +using osu.Game.Graphics; using osu.Game.Graphics.UserInterface; namespace osu.Game.Tests.Visual @@ -10,10 +12,12 @@ namespace osu.Game.Tests.Visual [TestFixture] public class TestCaseBreadcrumbs : OsuTestCase { + private readonly BreadcrumbControl breadcrumbs; + public TestCaseBreadcrumbs() { - BreadcrumbControl c; - Add(c = new BreadcrumbControl + + Add(breadcrumbs = new BreadcrumbControl { Anchor = Anchor.Centre, Origin = Anchor.Centre, @@ -21,9 +25,15 @@ namespace osu.Game.Tests.Visual Width = 0.5f, }); - AddStep(@"first", () => c.Current.Value = BreadcrumbTab.Click); - AddStep(@"second", () => c.Current.Value = BreadcrumbTab.The); - AddStep(@"third", () => c.Current.Value = BreadcrumbTab.Circles); + AddStep(@"first", () => breadcrumbs.Current.Value = BreadcrumbTab.Click); + AddStep(@"second", () => breadcrumbs.Current.Value = BreadcrumbTab.The); + AddStep(@"third", () => breadcrumbs.Current.Value = BreadcrumbTab.Circles); + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + breadcrumbs.StripColour = colours.Blue; } private enum BreadcrumbTab diff --git a/osu.Game/Graphics/UserInterface/BreadcrumbControl.cs b/osu.Game/Graphics/UserInterface/BreadcrumbControl.cs index 5ee0aba9cf..7f30bd715a 100644 --- a/osu.Game/Graphics/UserInterface/BreadcrumbControl.cs +++ b/osu.Game/Graphics/UserInterface/BreadcrumbControl.cs @@ -17,6 +17,8 @@ namespace osu.Game.Graphics.UserInterface protected override TabItem CreateTabItem(T value) => new BreadcrumbTabItem(value); + protected override float StripWidth() => base.StripWidth() - (padding + 8); + public BreadcrumbControl() { Height = 26; diff --git a/osu.Game/Graphics/UserInterface/OsuTabControl.cs b/osu.Game/Graphics/UserInterface/OsuTabControl.cs index 89188b29d7..1624b255e0 100644 --- a/osu.Game/Graphics/UserInterface/OsuTabControl.cs +++ b/osu.Game/Graphics/UserInterface/OsuTabControl.cs @@ -27,6 +27,7 @@ namespace osu.Game.Graphics.UserInterface protected override TabItem CreateTabItem(T value) => new OsuTabItem(value); protected virtual float StripWidth() => TabContainer.Children.Sum(c => c.IsPresent ? c.DrawWidth + TabContainer.Spacing.X : 0) - TabContainer.Spacing.X; + protected virtual float StripHeight() => 1; private static bool isEnumType => typeof(T).IsEnum; @@ -38,7 +39,7 @@ namespace osu.Game.Graphics.UserInterface { Anchor = Anchor.BottomLeft, Origin = Anchor.BottomLeft, - Height = 1, + Height = StripHeight(), Colour = Color4.White.Opacity(0), }); From 4ad776bfde4c88da610e388c4394a836af444772 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 30 Mar 2018 14:14:04 +0900 Subject: [PATCH 17/61] Make slider circle masks not handle mouse input at all --- .../Edit/Layers/Selection/Overlays/SliderCircleMask.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/Layers/Selection/Overlays/SliderCircleMask.cs b/osu.Game.Rulesets.Osu/Edit/Layers/Selection/Overlays/SliderCircleMask.cs index fa9896ff7b..4e22b4f693 100644 --- a/osu.Game.Rulesets.Osu/Edit/Layers/Selection/Overlays/SliderCircleMask.cs +++ b/osu.Game.Rulesets.Osu/Edit/Layers/Selection/Overlays/SliderCircleMask.cs @@ -4,7 +4,6 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Framework.Input; using osu.Game.Graphics; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Osu.Objects; @@ -58,6 +57,6 @@ namespace osu.Game.Rulesets.Osu.Edit.Layers.Selection.Overlays } // Todo: This is temporary, since the slider circle masks don't do anything special yet. In the future they will handle input. - protected override bool OnMouseDown(InputState state, MouseDownEventArgs args) => false; + public override bool HandleMouseInput => false; } } From 082e5e4949f91267389a7a639667fa823db87a11 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 30 Mar 2018 15:06:52 +0900 Subject: [PATCH 18/61] Reduce iterations of DragBox --- .../Screens/Edit/Screens/Compose/Layers/DragBox.cs | 12 ++++++------ .../Screens/Compose/Layers/HitObjectMaskLayer.cs | 12 +++++++++--- 2 files changed, 15 insertions(+), 9 deletions(-) diff --git a/osu.Game/Screens/Edit/Screens/Compose/Layers/DragBox.cs b/osu.Game/Screens/Edit/Screens/Compose/Layers/DragBox.cs index 0817541a21..1de78d19ce 100644 --- a/osu.Game/Screens/Edit/Screens/Compose/Layers/DragBox.cs +++ b/osu.Game/Screens/Edit/Screens/Compose/Layers/DragBox.cs @@ -24,17 +24,17 @@ namespace osu.Game.Screens.Edit.Screens.Compose.Layers /// public event Action DragEnd; - private readonly IEnumerable hitObjectMasks; + private readonly IEnumerable selectableMasks; private Drawable box; /// /// Creates a new . /// - /// The selectable s. - public DragBox(IEnumerable hitObjectMasks) + /// The selectable s. + public DragBox(IEnumerable selectableMasks) { - this.hitObjectMasks = hitObjectMasks; + this.selectableMasks = selectableMasks; RelativeSizeAxes = Axes.Both; AlwaysPresent = true; @@ -79,9 +79,9 @@ namespace osu.Game.Screens.Edit.Screens.Compose.Layers box.Position = topLeft; box.Size = bottomRight - topLeft; - foreach (var mask in hitObjectMasks) + foreach (var mask in selectableMasks) { - if (mask.IsAlive && mask.IsPresent && dragRectangle.Contains(mask.SelectionPoint)) + if (mask.IsPresent && dragRectangle.Contains(mask.SelectionPoint)) mask.Select(); else mask.Deselect(); diff --git a/osu.Game/Screens/Edit/Screens/Compose/Layers/HitObjectMaskLayer.cs b/osu.Game/Screens/Edit/Screens/Compose/Layers/HitObjectMaskLayer.cs index ca161a6f37..2c8a308d5b 100644 --- a/osu.Game/Screens/Edit/Screens/Compose/Layers/HitObjectMaskLayer.cs +++ b/osu.Game/Screens/Edit/Screens/Compose/Layers/HitObjectMaskLayer.cs @@ -18,7 +18,7 @@ namespace osu.Game.Screens.Edit.Screens.Compose.Layers private readonly Playfield playfield; private readonly HitObjectComposer composer; - private Container maskContainer; + private MaskContainer maskContainer; private SelectionBox selectionBox; private readonly HashSet selectedMasks = new HashSet(); @@ -34,10 +34,11 @@ namespace osu.Game.Screens.Edit.Screens.Compose.Layers [BackgroundDependencyLoader] private void load() { - maskContainer = new Container(); + maskContainer = new MaskContainer(); + selectionBox = composer.CreateSelectionBox(); - var dragBox = new DragBox(maskContainer); + var dragBox = new DragBox(maskContainer.AliveChildren); dragBox.DragEnd += () => selectionBox.UpdateVisibility(); InternalChildren = new Drawable[] @@ -104,5 +105,10 @@ namespace osu.Game.Screens.Edit.Screens.Compose.Layers /// Deselects all selected s. /// public void DeselectAll() => selectedMasks.ToList().ForEach(m => m.Deselect()); + + private class MaskContainer : Container + { + public new IEnumerable AliveChildren => AliveInternalChildren.Cast(); + } } } From 1dca1663c32988040fa47667e8dc52b7f0994d7b Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 30 Mar 2018 15:50:55 +0900 Subject: [PATCH 19/61] Handle all selection events within SelectionBox (incl. single-mask) --- osu.Game/Rulesets/Edit/HitObjectComposer.cs | 4 +- osu.Game/Rulesets/Edit/HitObjectMask.cs | 31 ----- .../Edit/Screens/Compose/Layers/DragBox.cs | 11 +- .../Compose/Layers/HitObjectMaskLayer.cs | 35 +----- .../Screens/Compose/Layers/MaskContainer.cs | 50 ++++++++ .../Screens/Compose/Layers/SelectionBox.cs | 109 ++++++++++-------- 6 files changed, 120 insertions(+), 120 deletions(-) create mode 100644 osu.Game/Screens/Edit/Screens/Compose/Layers/MaskContainer.cs diff --git a/osu.Game/Rulesets/Edit/HitObjectComposer.cs b/osu.Game/Rulesets/Edit/HitObjectComposer.cs index 1a587bf8f5..7d5702ccbf 100644 --- a/osu.Game/Rulesets/Edit/HitObjectComposer.cs +++ b/osu.Game/Rulesets/Edit/HitObjectComposer.cs @@ -253,8 +253,8 @@ namespace osu.Game.Rulesets.Edit /// Creates a which outlines s /// and handles hitobject pattern adjustments. /// - /// The overlays. - public virtual SelectionBox CreateSelectionBox() => new SelectionBox(); + /// The container. + public virtual SelectionBox CreateSelectionBox(MaskContainer maskContainer) => new SelectionBox(maskContainer); /// /// Creates a which provides a layer above or below the . diff --git a/osu.Game/Rulesets/Edit/HitObjectMask.cs b/osu.Game/Rulesets/Edit/HitObjectMask.cs index c55e34f548..981e109747 100644 --- a/osu.Game/Rulesets/Edit/HitObjectMask.cs +++ b/osu.Game/Rulesets/Edit/HitObjectMask.cs @@ -4,8 +4,6 @@ using System; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Primitives; -using osu.Framework.Input; -using osu.Game.Rulesets.Edit.Types; using osu.Game.Rulesets.Objects.Drawables; using OpenTK; @@ -26,12 +24,6 @@ namespace osu.Game.Rulesets.Edit /// public event Action Deselected; - /// - /// Invoked when this is requesting to be the single selection. - /// This has not been selected at this point, but will be selected immediately afterwards. - /// - public event Action SingleSelectionRequested; - /// /// The which this applies to. /// @@ -77,29 +69,6 @@ namespace osu.Game.Rulesets.Edit return true; } - protected override bool OnMouseDown(InputState state, MouseDownEventArgs args) - { - SingleSelectionRequested?.Invoke(this); - Select(); - return true; - } - - protected override bool OnDragStart(InputState state) => true; - - protected override bool OnDrag(InputState state) - { - // Todo: Various forms of snapping - switch (HitObject.HitObject) - { - case IHasEditablePosition editablePosition: - editablePosition.OffsetPosition(state.Mouse.Delta); - break; - } - return true; - } - - protected override bool OnDragEnd(InputState state) => true; - protected override void PopIn() => Alpha = 1; protected override void PopOut() => Alpha = 0; diff --git a/osu.Game/Screens/Edit/Screens/Compose/Layers/DragBox.cs b/osu.Game/Screens/Edit/Screens/Compose/Layers/DragBox.cs index 1de78d19ce..ea170a0326 100644 --- a/osu.Game/Screens/Edit/Screens/Compose/Layers/DragBox.cs +++ b/osu.Game/Screens/Edit/Screens/Compose/Layers/DragBox.cs @@ -2,7 +2,6 @@ // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE using System; -using System.Collections.Generic; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -24,17 +23,17 @@ namespace osu.Game.Screens.Edit.Screens.Compose.Layers /// public event Action DragEnd; - private readonly IEnumerable selectableMasks; + private readonly MaskContainer maskContainer; private Drawable box; /// /// Creates a new . /// - /// The selectable s. - public DragBox(IEnumerable selectableMasks) + /// The selectable s. + public DragBox(MaskContainer maskContainer) { - this.selectableMasks = selectableMasks; + this.maskContainer = maskContainer; RelativeSizeAxes = Axes.Both; AlwaysPresent = true; @@ -79,7 +78,7 @@ namespace osu.Game.Screens.Edit.Screens.Compose.Layers box.Position = topLeft; box.Size = bottomRight - topLeft; - foreach (var mask in selectableMasks) + foreach (var mask in maskContainer.AliveMasks) { if (mask.IsPresent && dragRectangle.Contains(mask.SelectionPoint)) mask.Select(); diff --git a/osu.Game/Screens/Edit/Screens/Compose/Layers/HitObjectMaskLayer.cs b/osu.Game/Screens/Edit/Screens/Compose/Layers/HitObjectMaskLayer.cs index 2c8a308d5b..f972f9ac81 100644 --- a/osu.Game/Screens/Edit/Screens/Compose/Layers/HitObjectMaskLayer.cs +++ b/osu.Game/Screens/Edit/Screens/Compose/Layers/HitObjectMaskLayer.cs @@ -1,7 +1,6 @@ // Copyright (c) 2007-2018 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE -using System.Collections.Generic; using System.Linq; using osu.Framework.Allocation; using osu.Framework.Graphics; @@ -21,8 +20,6 @@ namespace osu.Game.Screens.Edit.Screens.Compose.Layers private MaskContainer maskContainer; private SelectionBox selectionBox; - private readonly HashSet selectedMasks = new HashSet(); - public HitObjectMaskLayer(Playfield playfield, HitObjectComposer composer) { this.playfield = playfield; @@ -36,9 +33,9 @@ namespace osu.Game.Screens.Edit.Screens.Compose.Layers { maskContainer = new MaskContainer(); - selectionBox = composer.CreateSelectionBox(); + selectionBox = composer.CreateSelectionBox(maskContainer); - var dragBox = new DragBox(maskContainer.AliveChildren); + var dragBox = new DragBox(maskContainer); dragBox.DragEnd += () => selectionBox.UpdateVisibility(); InternalChildren = new Drawable[] @@ -63,12 +60,7 @@ namespace osu.Game.Screens.Edit.Screens.Compose.Layers if (mask == null) return; - mask.Selected += onSelected; - mask.Deselected += onDeselected; - mask.SingleSelectionRequested += onSingleSelectionRequested; - maskContainer.Add(mask); - selectionBox.AddMask(mask); } /// @@ -81,34 +73,13 @@ namespace osu.Game.Screens.Edit.Screens.Compose.Layers if (mask == null) return; - mask.Selected -= onSelected; - mask.Deselected -= onDeselected; - mask.SingleSelectionRequested -= onSingleSelectionRequested; - maskContainer.Remove(mask); - selectionBox.RemoveMask(mask); } - private void onSelected(HitObjectMask mask) => selectedMasks.Add(mask); - - private void onDeselected(HitObjectMask mask) => selectedMasks.Remove(mask); - - private void onSingleSelectionRequested(HitObjectMask mask) => DeselectAll(); - protected override bool OnMouseDown(InputState state, MouseDownEventArgs args) { - DeselectAll(); + selectionBox.DeselectAll(); return true; } - - /// - /// Deselects all selected s. - /// - public void DeselectAll() => selectedMasks.ToList().ForEach(m => m.Deselect()); - - private class MaskContainer : Container - { - public new IEnumerable AliveChildren => AliveInternalChildren.Cast(); - } } } diff --git a/osu.Game/Screens/Edit/Screens/Compose/Layers/MaskContainer.cs b/osu.Game/Screens/Edit/Screens/Compose/Layers/MaskContainer.cs new file mode 100644 index 0000000000..4b3ea077bc --- /dev/null +++ b/osu.Game/Screens/Edit/Screens/Compose/Layers/MaskContainer.cs @@ -0,0 +1,50 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using osu.Framework.Graphics.Containers; +using osu.Game.Rulesets.Edit; + +namespace osu.Game.Screens.Edit.Screens.Compose.Layers +{ + public class MaskContainer : Container + { + /// + /// Invoked when any is selected. + /// + public event Action MaskSelected; + + /// + /// Invoked when any is deselected. + /// + public event Action MaskDeselected; + + /// + /// All the s with == true. + /// + public IEnumerable AliveMasks => AliveInternalChildren.Cast(); + + public override void Add(HitObjectMask drawable) + { + base.Add(drawable); + + drawable.Selected += onMaskSelected; + drawable.Deselected += onMaskDeselected; + } + + public override bool Remove(HitObjectMask drawable) + { + var result = base.Remove(drawable); + + if (result) + { + drawable.Selected -= onMaskSelected; + drawable.Deselected += onMaskDeselected; + } + + return result; + } + + private void onMaskSelected(HitObjectMask mask) => MaskSelected?.Invoke(mask); + private void onMaskDeselected(HitObjectMask mask) => MaskDeselected?.Invoke(mask); + } +} diff --git a/osu.Game/Screens/Edit/Screens/Compose/Layers/SelectionBox.cs b/osu.Game/Screens/Edit/Screens/Compose/Layers/SelectionBox.cs index ad8c846bbf..833c94d3f4 100644 --- a/osu.Game/Screens/Edit/Screens/Compose/Layers/SelectionBox.cs +++ b/osu.Game/Screens/Edit/Screens/Compose/Layers/SelectionBox.cs @@ -1,7 +1,6 @@ // Copyright (c) 2007-2018 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE -using System; using System.Collections.Generic; using System.Linq; using osu.Framework.Allocation; @@ -23,15 +22,23 @@ namespace osu.Game.Screens.Edit.Screens.Compose.Layers { public const float BORDER_RADIUS = 2; - private readonly HashSet selectedMasks = new HashSet(); + private readonly MaskContainer maskContainer; + + private readonly List selectedMasks = new List(); + private IEnumerable selectableMasks => maskContainer.AliveMasks; private Drawable box; - public SelectionBox() + public SelectionBox(MaskContainer maskContainer) { + this.maskContainer = maskContainer; + RelativeSizeAxes = Axes.Both; AlwaysPresent = true; Alpha = 0; + + maskContainer.MaskSelected += onSelected; + maskContainer.MaskDeselected += onDeselected; } [BackgroundDependencyLoader] @@ -51,58 +58,34 @@ namespace osu.Game.Screens.Edit.Screens.Compose.Layers }; } - /// - /// Tracks a selectable . - /// - /// The to track. - public void AddMask(HitObjectMask mask) + #region User Input Handling + + // Only handle input on selectable or selected masks + public override bool ReceiveMouseInputAt(Vector2 screenSpacePos) => selectableMasks.Concat(selectedMasks).Any(m => m.ReceiveMouseInputAt(screenSpacePos)); + + protected override bool OnMouseDown(InputState state, MouseDownEventArgs args) { - mask.Selected += onSelected; - mask.Deselected += onDeselected; - mask.SingleSelectionRequested += onSingleSelectionRequested; - } + if (selectedMasks.Any(m => m.ReceiveMouseInputAt(state.Mouse.NativeState.Position))) + return true; - /// - /// Stops tracking a . - /// - /// The to stop tracking. - public void RemoveMask(HitObjectMask mask) - { - mask.Selected -= onSelected; - mask.Deselected -= onDeselected; - mask.SingleSelectionRequested -= onSingleSelectionRequested; - } + DeselectAll(); + selectableMasks.First(m => m.ReceiveMouseInputAt(state.Mouse.NativeState.Position)).Select(); - private void onSelected(HitObjectMask mask) => selectedMasks.Add(mask); - - private void onDeselected(HitObjectMask mask) - { - selectedMasks.Remove(mask); - - // We don't want to update visibility if > 0, since we may be deselecting masks during drag-selection - if (selectedMasks.Count == 0) - UpdateVisibility(); - } - - private void onSingleSelectionRequested(HitObjectMask mask) - { - selectedMasks.Add(mask); UpdateVisibility(); + return true; } - // Only handle input on the selected masks - public override bool ReceiveMouseInputAt(Vector2 screenSpacePos) => selectedMasks.Any(m => m.ReceiveMouseInputAt(screenSpacePos)); - - protected override bool OnMouseDown(InputState state, MouseDownEventArgs args) => true; - protected override bool OnClick(InputState state) { - if (state.Mouse.NativeState.PositionMouseDown == null) - throw new InvalidOperationException("Click event received without a mouse down position."); + if (selectedMasks.Count == 1) + return true; - // We handled mousedown, but if the mouse has been clicked and not dragged, select the mask which would've handled the mouse down - // A mousedown event is triggered such that a single selection is requested - selectedMasks.First(m => m.ReceiveMouseInputAt(state.Mouse.NativeState.PositionMouseDown.Value)).TriggerOnMouseDown(state); + var toSelect = selectedMasks.First(m => m.ReceiveMouseInputAt(state.Mouse.NativeState.Position)); + + DeselectAll(); + toSelect.Select(); + + UpdateVisibility(); return true; } @@ -127,10 +110,32 @@ namespace osu.Game.Screens.Edit.Screens.Compose.Layers protected override bool OnDragEnd(InputState state) => true; + #endregion + + #region Selection Handling + + private void onSelected(HitObjectMask mask) => selectedMasks.Add(mask); + + private void onDeselected(HitObjectMask mask) + { + selectedMasks.Remove(mask); + + // We don't want to update visibility if > 0, since we may be deselecting masks during drag-selection + if (selectedMasks.Count == 0) + UpdateVisibility(); + } + + /// + /// Deselects all selected s. + /// + public void DeselectAll() => selectedMasks.ToList().ForEach(m => m.Deselect()); + + #endregion + /// /// Updates whether this is visible. /// - public void UpdateVisibility() + internal void UpdateVisibility() { if (selectedMasks.Count > 0) Show(); @@ -145,8 +150,6 @@ namespace osu.Game.Screens.Edit.Screens.Compose.Layers if (selectedMasks.Count == 0) return; - // Todo: We might need to optimise this - // Move the rectangle to cover the hitobjects var topLeft = new Vector2(float.MaxValue, float.MaxValue); var bottomRight = new Vector2(float.MinValue, float.MinValue); @@ -165,5 +168,13 @@ namespace osu.Game.Screens.Edit.Screens.Compose.Layers box.Size = bottomRight - topLeft; box.Position = topLeft; } + + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + + maskContainer.MaskSelected -= onSelected; + maskContainer.MaskDeselected -= onDeselected; + } } } From 5d0a636cc4fc24ccaa3f868007a97d3334a81d87 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 30 Mar 2018 15:51:38 +0900 Subject: [PATCH 20/61] Rename SelectionBox -> Selection --- .../Compose/Layers/{SelectionBox.cs => Selection.cs} | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) rename osu.Game/Screens/Edit/Screens/Compose/Layers/{SelectionBox.cs => Selection.cs} (93%) diff --git a/osu.Game/Screens/Edit/Screens/Compose/Layers/SelectionBox.cs b/osu.Game/Screens/Edit/Screens/Compose/Layers/Selection.cs similarity index 93% rename from osu.Game/Screens/Edit/Screens/Compose/Layers/SelectionBox.cs rename to osu.Game/Screens/Edit/Screens/Compose/Layers/Selection.cs index 833c94d3f4..661929c5b4 100644 --- a/osu.Game/Screens/Edit/Screens/Compose/Layers/SelectionBox.cs +++ b/osu.Game/Screens/Edit/Screens/Compose/Layers/Selection.cs @@ -27,7 +27,7 @@ namespace osu.Game.Screens.Edit.Screens.Compose.Layers private readonly List selectedMasks = new List(); private IEnumerable selectableMasks => maskContainer.AliveMasks; - private Drawable box; + private Drawable outline; public SelectionBox(MaskContainer maskContainer) { @@ -44,7 +44,7 @@ namespace osu.Game.Screens.Edit.Screens.Compose.Layers [BackgroundDependencyLoader] private void load(OsuColour colours) { - InternalChild = box = new Container + InternalChild = outline = new Container { Masking = true, BorderThickness = BORDER_RADIUS, @@ -165,8 +165,8 @@ namespace osu.Game.Screens.Edit.Screens.Compose.Layers topLeft -= new Vector2(5); bottomRight += new Vector2(5); - box.Size = bottomRight - topLeft; - box.Position = topLeft; + outline.Size = bottomRight - topLeft; + outline.Position = topLeft; } protected override void Dispose(bool isDisposing) From 53541a5c8da1a154c9a0d823704db500f9d59d0f Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 30 Mar 2018 15:53:31 +0900 Subject: [PATCH 21/61] Add license header --- .../Screens/Edit/Screens/Compose/Layers/MaskContainer.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Screens/Compose/Layers/MaskContainer.cs b/osu.Game/Screens/Edit/Screens/Compose/Layers/MaskContainer.cs index 4b3ea077bc..0662070857 100644 --- a/osu.Game/Screens/Edit/Screens/Compose/Layers/MaskContainer.cs +++ b/osu.Game/Screens/Edit/Screens/Compose/Layers/MaskContainer.cs @@ -1,4 +1,7 @@ -using System; +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; using System.Collections.Generic; using System.Linq; using osu.Framework.Graphics.Containers; From 69a7ddbf1e8ff160fcdd3fcc5a43e41e7b87e939 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 30 Mar 2018 16:23:17 +0900 Subject: [PATCH 22/61] Fix ordering of display/input of HitObjectMasks --- .../Edit/Screens/Compose/Layers/MaskContainer.cs | 15 +++++++++++++++ .../Edit/Screens/Compose/Layers/Selection.cs | 11 +++++++---- 2 files changed, 22 insertions(+), 4 deletions(-) diff --git a/osu.Game/Screens/Edit/Screens/Compose/Layers/MaskContainer.cs b/osu.Game/Screens/Edit/Screens/Compose/Layers/MaskContainer.cs index 0662070857..89bae004b5 100644 --- a/osu.Game/Screens/Edit/Screens/Compose/Layers/MaskContainer.cs +++ b/osu.Game/Screens/Edit/Screens/Compose/Layers/MaskContainer.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.Linq; +using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Rulesets.Edit; @@ -49,5 +50,19 @@ namespace osu.Game.Screens.Edit.Screens.Compose.Layers private void onMaskSelected(HitObjectMask mask) => MaskSelected?.Invoke(mask); private void onMaskDeselected(HitObjectMask mask) => MaskDeselected?.Invoke(mask); + + protected override int Compare(Drawable x, Drawable y) + { + if (!(x is HitObjectMask xMask) || !(y is HitObjectMask yMask)) + return base.Compare(x, y); + return Compare(xMask, yMask); + } + + public int Compare(HitObjectMask x, HitObjectMask y) + { + // Put earlier hitobjects towards the end of the list, so they handle input first + int i = y.HitObject.HitObject.StartTime.CompareTo(x.HitObject.HitObject.StartTime); + return i == 0 ? CompareReverseChildID(x, y) : i; + } } } diff --git a/osu.Game/Screens/Edit/Screens/Compose/Layers/Selection.cs b/osu.Game/Screens/Edit/Screens/Compose/Layers/Selection.cs index 661929c5b4..4ff1284f8a 100644 --- a/osu.Game/Screens/Edit/Screens/Compose/Layers/Selection.cs +++ b/osu.Game/Screens/Edit/Screens/Compose/Layers/Selection.cs @@ -8,6 +8,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Input; +using osu.Framework.Lists; using osu.Game.Graphics; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Edit.Types; @@ -24,7 +25,7 @@ namespace osu.Game.Screens.Edit.Screens.Compose.Layers private readonly MaskContainer maskContainer; - private readonly List selectedMasks = new List(); + private readonly SortedList selectedMasks; private IEnumerable selectableMasks => maskContainer.AliveMasks; private Drawable outline; @@ -33,6 +34,8 @@ namespace osu.Game.Screens.Edit.Screens.Compose.Layers { this.maskContainer = maskContainer; + selectedMasks = new SortedList(maskContainer.Compare); + RelativeSizeAxes = Axes.Both; AlwaysPresent = true; Alpha = 0; @@ -61,7 +64,7 @@ namespace osu.Game.Screens.Edit.Screens.Compose.Layers #region User Input Handling // Only handle input on selectable or selected masks - public override bool ReceiveMouseInputAt(Vector2 screenSpacePos) => selectableMasks.Concat(selectedMasks).Any(m => m.ReceiveMouseInputAt(screenSpacePos)); + public override bool ReceiveMouseInputAt(Vector2 screenSpacePos) => selectableMasks.Reverse().Concat(selectedMasks).Any(m => m.ReceiveMouseInputAt(screenSpacePos)); protected override bool OnMouseDown(InputState state, MouseDownEventArgs args) { @@ -69,7 +72,7 @@ namespace osu.Game.Screens.Edit.Screens.Compose.Layers return true; DeselectAll(); - selectableMasks.First(m => m.ReceiveMouseInputAt(state.Mouse.NativeState.Position)).Select(); + selectableMasks.Reverse().First(m => m.ReceiveMouseInputAt(state.Mouse.NativeState.Position)).Select(); UpdateVisibility(); return true; @@ -80,7 +83,7 @@ namespace osu.Game.Screens.Edit.Screens.Compose.Layers if (selectedMasks.Count == 1) return true; - var toSelect = selectedMasks.First(m => m.ReceiveMouseInputAt(state.Mouse.NativeState.Position)); + var toSelect = selectedMasks.Reverse().First(m => m.ReceiveMouseInputAt(state.Mouse.NativeState.Position)); DeselectAll(); toSelect.Select(); From f1f7d978ec92be92183755cd9a1fa646e1d3e1b3 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 30 Mar 2018 16:28:59 +0900 Subject: [PATCH 23/61] Add some comments --- osu.Game/Screens/Edit/Screens/Compose/Layers/Selection.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game/Screens/Edit/Screens/Compose/Layers/Selection.cs b/osu.Game/Screens/Edit/Screens/Compose/Layers/Selection.cs index 4ff1284f8a..427acbef5a 100644 --- a/osu.Game/Screens/Edit/Screens/Compose/Layers/Selection.cs +++ b/osu.Game/Screens/Edit/Screens/Compose/Layers/Selection.cs @@ -68,6 +68,7 @@ namespace osu.Game.Screens.Edit.Screens.Compose.Layers protected override bool OnMouseDown(InputState state, MouseDownEventArgs args) { + // If masks are overlapping, make sure we don't change the selection if the overlapped portion is pressed if (selectedMasks.Any(m => m.ReceiveMouseInputAt(state.Mouse.NativeState.Position))) return true; @@ -80,6 +81,7 @@ namespace osu.Game.Screens.Edit.Screens.Compose.Layers protected override bool OnClick(InputState state) { + // If there's only mask, this isn't going to change anything, so we can save on doing some processing here if (selectedMasks.Count == 1) return true; From 7fed8d64de9ce135ccc6fd40115d321f72af4e4c Mon Sep 17 00:00:00 2001 From: Ali Rizvi Date: Sat, 31 Mar 2018 04:59:08 +0900 Subject: [PATCH 24/61] Implement Judgement Colours --- .../Rulesets/Judgements/DrawableJudgement.cs | 27 ++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/Judgements/DrawableJudgement.cs b/osu.Game/Rulesets/Judgements/DrawableJudgement.cs index ca203e1cdb..4b01cc5ccd 100644 --- a/osu.Game/Rulesets/Judgements/DrawableJudgement.cs +++ b/osu.Game/Rulesets/Judgements/DrawableJudgement.cs @@ -23,6 +23,8 @@ namespace osu.Game.Rulesets.Judgements { private const float judgement_size = 80; + private OsuColour colours; + protected readonly Judgement Judgement; public readonly DrawableHitObject JudgedObject; @@ -45,11 +47,12 @@ namespace osu.Game.Rulesets.Judgements [BackgroundDependencyLoader] private void load(OsuColour colours) { + this.colours = colours; Child = new SkinnableDrawable($"Play/{Judgement.Result}", _ => JudgementText = new OsuSpriteText { Text = Judgement.Result.GetDescription().ToUpper(), Font = @"Venera", - Colour = Judgement.Result == HitResult.Miss ? colours.Red : Color4.White, + Colour = judgementColour(Judgement.Result), Scale = new Vector2(0.85f, 1), TextSize = 12 }, restrictSize: false); @@ -84,5 +87,27 @@ namespace osu.Game.Rulesets.Judgements Expire(true); } + + private Color4 judgementColour(HitResult judgement) + { + switch (judgement) + { + case HitResult.Perfect: + case HitResult.Great: + return colours.Blue; + + case HitResult.Ok: + case HitResult.Good: + return colours.Green; + + case HitResult.Meh: + return colours.Yellow; + + case HitResult.Miss: + return colours.Red; + } + + return Color4.White; + } } } From 551ba6ac4bc77c7f3097a369ab250339a2730064 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 2 Apr 2018 13:04:47 +0900 Subject: [PATCH 25/61] Fix ScalableContainer irrepairably altering content size --- osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs | 6 +-- .../Replays/OsuReplayInputHandler.cs | 2 +- .../Visual/TestCaseScrollingHitObjects.cs | 2 +- osu.Game/Input/Handlers/ReplayInputHandler.cs | 4 +- osu.Game/Rulesets/UI/RulesetContainer.cs | 2 +- osu.Game/Rulesets/UI/ScalableContainer.cs | 47 ++++++++++++------- 6 files changed, 38 insertions(+), 25 deletions(-) diff --git a/osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs b/osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs index 8bb206543b..2b6a7c41f4 100644 --- a/osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs +++ b/osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs @@ -30,10 +30,10 @@ namespace osu.Game.Rulesets.Catch.UI Anchor = Anchor.TopCentre; Origin = Anchor.TopCentre; - ScaledContent.Anchor = Anchor.BottomLeft; - ScaledContent.Origin = Anchor.BottomLeft; + base.Content.Anchor = Anchor.BottomLeft; + base.Content.Origin = Anchor.BottomLeft; - ScaledContent.AddRange(new Drawable[] + base.Content.AddRange(new Drawable[] { explodingFruitContainer = new Container { diff --git a/osu.Game.Rulesets.Osu/Replays/OsuReplayInputHandler.cs b/osu.Game.Rulesets.Osu/Replays/OsuReplayInputHandler.cs index 0a61b0f199..69154a1d0c 100644 --- a/osu.Game.Rulesets.Osu/Replays/OsuReplayInputHandler.cs +++ b/osu.Game.Rulesets.Osu/Replays/OsuReplayInputHandler.cs @@ -36,7 +36,7 @@ namespace osu.Game.Rulesets.Osu.Replays { new ReplayState { - Mouse = new ReplayMouseState(ToScreenSpace(Position ?? Vector2.Zero)), + Mouse = new ReplayMouseState(GamefieldToScreenSpace(Position ?? Vector2.Zero)), PressedActions = CurrentFrame.Actions } }; diff --git a/osu.Game.Tests/Visual/TestCaseScrollingHitObjects.cs b/osu.Game.Tests/Visual/TestCaseScrollingHitObjects.cs index 745ae9ad9d..0742dd68eb 100644 --- a/osu.Game.Tests/Visual/TestCaseScrollingHitObjects.cs +++ b/osu.Game.Tests/Visual/TestCaseScrollingHitObjects.cs @@ -122,7 +122,7 @@ namespace osu.Game.Tests.Visual Direction = direction; Padding = new MarginPadding(2); - ScaledContent.Masking = true; + Content.Masking = true; AddInternal(new Box { diff --git a/osu.Game/Input/Handlers/ReplayInputHandler.cs b/osu.Game/Input/Handlers/ReplayInputHandler.cs index 8aa3a53cc2..c431af0219 100644 --- a/osu.Game/Input/Handlers/ReplayInputHandler.cs +++ b/osu.Game/Input/Handlers/ReplayInputHandler.cs @@ -13,9 +13,9 @@ namespace osu.Game.Input.Handlers public abstract class ReplayInputHandler : InputHandler { /// - /// A function provided to convert replay coordinates from gamefield to screen space. + /// A function that converts coordinates from gamefield to screen space. /// - public Func ToScreenSpace { protected get; set; } + public Func GamefieldToScreenSpace { protected get; set; } /// /// Update the current frame based on an incoming time value. diff --git a/osu.Game/Rulesets/UI/RulesetContainer.cs b/osu.Game/Rulesets/UI/RulesetContainer.cs index 2201b6963f..81418fecd4 100644 --- a/osu.Game/Rulesets/UI/RulesetContainer.cs +++ b/osu.Game/Rulesets/UI/RulesetContainer.cs @@ -290,7 +290,7 @@ namespace osu.Game.Rulesets.UI base.SetReplay(replay); if (ReplayInputManager?.ReplayInputHandler != null) - ReplayInputManager.ReplayInputHandler.ToScreenSpace = input => Playfield.ScaledContent.ToScreenSpace(input); + ReplayInputManager.ReplayInputHandler.GamefieldToScreenSpace = Playfield.GamefieldToScreenSpace; } /// diff --git a/osu.Game/Rulesets/UI/ScalableContainer.cs b/osu.Game/Rulesets/UI/ScalableContainer.cs index 9762828e7d..04e6db9578 100644 --- a/osu.Game/Rulesets/UI/ScalableContainer.cs +++ b/osu.Game/Rulesets/UI/ScalableContainer.cs @@ -1,6 +1,7 @@ // Copyright (c) 2007-2018 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +using System; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using OpenTK; @@ -12,13 +13,16 @@ namespace osu.Game.Rulesets.UI /// public class ScalableContainer : Container { + /// + /// A function that converts coordinates from gamefield to screen space. + /// + public Func GamefieldToScreenSpace => scaledContent.GamefieldToScreenSpace; + /// /// The scaled content. /// - public readonly Container ScaledContent; - - protected override Container Content => content; - private readonly Container content; + private readonly ScaledContainer scaledContent; + protected override Container Content => scaledContent; /// /// A which can have its internal coordinate system scaled to a specific size. @@ -31,17 +35,21 @@ namespace osu.Game.Rulesets.UI /// public ScalableContainer(float? customWidth = null, float? customHeight = null) { - AddInternal(ScaledContent = new ScaledContainer + AddInternal(scaledContent = new ScaledContainer { CustomWidth = customWidth, CustomHeight = customHeight, RelativeSizeAxes = Axes.Both, - Child = content = new Container { RelativeSizeAxes = Axes.Both } }); } private class ScaledContainer : Container { + /// + /// A function that converts coordinates from gamefield to screen space. + /// + public Func GamefieldToScreenSpace => content.ToScreenSpace; + /// /// The value to scale the width of the content to match. /// If null, is used. @@ -54,6 +62,22 @@ namespace osu.Game.Rulesets.UI /// public float? CustomHeight; + private readonly Container content; + protected override Container Content => content; + + public ScaledContainer() + { + AddInternal(content = new Container { RelativeSizeAxes = Axes.Both }); + } + + protected override void Update() + { + base.Update(); + + content.Scale = sizeScale; + content.Size = Vector2.Divide(Vector2.One, sizeScale); + } + /// /// The scale that is required for the size of the content to match and . /// @@ -70,17 +94,6 @@ namespace osu.Game.Rulesets.UI return Vector2.One; } } - - /// - /// Scale the content to the required container size by multiplying by . - /// - protected override Vector2 DrawScale => sizeScale * base.DrawScale; - - protected override void Update() - { - base.Update(); - RelativeChildSize = new Vector2(CustomWidth.HasValue ? sizeScale.X : RelativeChildSize.X, CustomHeight.HasValue ? sizeScale.Y : RelativeChildSize.Y); - } } } } From b842f682eb32b38abee9a855e88d3778c695dab0 Mon Sep 17 00:00:00 2001 From: Huo Yaoyuan Date: Mon, 2 Apr 2018 12:06:34 +0800 Subject: [PATCH 26/61] Use Linq.Append and Prepend. --- osu.Game.Rulesets.Osu/Mods/OsuModAutoplay.cs | 2 +- osu.Game.Rulesets.Osu/Mods/OsuModNoFail.cs | 2 +- osu.Game.Rulesets.Osu/Mods/OsuModRelax.cs | 2 +- osu.Game.Rulesets.Osu/Mods/OsuModSuddenDeath.cs | 2 +- osu.Game/Beatmaps/Formats/Decoder.cs | 2 +- osu.Game/Input/Bindings/GlobalActionContainer.cs | 2 +- osu.Game/Overlays/KeyBinding/KeyBindingRow.cs | 2 +- osu.Game/Rulesets/Edit/HitObjectComposer.cs | 6 ++---- osu.Game/Screens/Select/PlaySongSelect.cs | 2 +- 9 files changed, 10 insertions(+), 12 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModAutoplay.cs b/osu.Game.Rulesets.Osu/Mods/OsuModAutoplay.cs index 42fe95356d..a06390a4ea 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModAutoplay.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModAutoplay.cs @@ -13,7 +13,7 @@ namespace osu.Game.Rulesets.Osu.Mods { public class OsuModAutoplay : ModAutoplay { - public override Type[] IncompatibleMods => base.IncompatibleMods.Concat(new[] { typeof(OsuModAutopilot), typeof(OsuModSpunOut) }).ToArray(); + public override Type[] IncompatibleMods => base.IncompatibleMods.Append(typeof(OsuModAutopilot)).Append(typeof(OsuModSpunOut)).ToArray(); protected override Score CreateReplayScore(Beatmap beatmap) { diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModNoFail.cs b/osu.Game.Rulesets.Osu/Mods/OsuModNoFail.cs index f94ee484fc..07128cb8ff 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModNoFail.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModNoFail.cs @@ -9,6 +9,6 @@ namespace osu.Game.Rulesets.Osu.Mods { public class OsuModNoFail : ModNoFail { - public override Type[] IncompatibleMods => base.IncompatibleMods.Concat(new[] { typeof(OsuModAutopilot) }).ToArray(); + public override Type[] IncompatibleMods => base.IncompatibleMods.Append(typeof(OsuModAutopilot)).ToArray(); } } diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModRelax.cs b/osu.Game.Rulesets.Osu/Mods/OsuModRelax.cs index c9def8c8cf..ed774f0d0a 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModRelax.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModRelax.cs @@ -10,6 +10,6 @@ namespace osu.Game.Rulesets.Osu.Mods public class OsuModRelax : ModRelax { public override string Description => @"You don't need to click. Give your clicking/tapping fingers a break from the heat of things."; - public override Type[] IncompatibleMods => base.IncompatibleMods.Concat(new[] { typeof(OsuModAutopilot) }).ToArray(); + public override Type[] IncompatibleMods => base.IncompatibleMods.Append(typeof(OsuModAutopilot)).ToArray(); } } diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModSuddenDeath.cs b/osu.Game.Rulesets.Osu/Mods/OsuModSuddenDeath.cs index 797e0af0ad..6c15095bfe 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModSuddenDeath.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModSuddenDeath.cs @@ -9,6 +9,6 @@ namespace osu.Game.Rulesets.Osu.Mods { public class OsuModSuddenDeath : ModSuddenDeath { - public override Type[] IncompatibleMods => base.IncompatibleMods.Concat(new[] { typeof(OsuModAutopilot) }).ToArray(); + public override Type[] IncompatibleMods => base.IncompatibleMods.Append(typeof(OsuModAutopilot)).ToArray(); } } diff --git a/osu.Game/Beatmaps/Formats/Decoder.cs b/osu.Game/Beatmaps/Formats/Decoder.cs index 9f10485c5f..c07bedc8a6 100644 --- a/osu.Game/Beatmaps/Formats/Decoder.cs +++ b/osu.Game/Beatmaps/Formats/Decoder.cs @@ -16,7 +16,7 @@ namespace osu.Game.Beatmaps.Formats public TOutput Decode(StreamReader primaryStream, params StreamReader[] otherStreams) { var output = CreateTemplateObject(); - foreach (StreamReader stream in new[] { primaryStream }.Concat(otherStreams)) + foreach (StreamReader stream in otherStreams.Prepend(primaryStream)) ParseStreamInto(stream, output); return output; } diff --git a/osu.Game/Input/Bindings/GlobalActionContainer.cs b/osu.Game/Input/Bindings/GlobalActionContainer.cs index 97e473a797..9e74a935ea 100644 --- a/osu.Game/Input/Bindings/GlobalActionContainer.cs +++ b/osu.Game/Input/Bindings/GlobalActionContainer.cs @@ -45,7 +45,7 @@ namespace osu.Game.Input.Bindings }; protected override IEnumerable KeyBindingInputQueue => - handler == null ? base.KeyBindingInputQueue : new[] { handler }.Concat(base.KeyBindingInputQueue); + handler == null ? base.KeyBindingInputQueue : base.KeyBindingInputQueue.Prepend(handler); } public enum GlobalAction diff --git a/osu.Game/Overlays/KeyBinding/KeyBindingRow.cs b/osu.Game/Overlays/KeyBinding/KeyBindingRow.cs index 379d25313e..71c346d404 100644 --- a/osu.Game/Overlays/KeyBinding/KeyBindingRow.cs +++ b/osu.Game/Overlays/KeyBinding/KeyBindingRow.cs @@ -47,7 +47,7 @@ namespace osu.Game.Overlays.KeyBinding private FillFlowContainer buttons; - public IEnumerable FilterTerms => new[] { text.Text }.Concat(bindings.Select(b => b.KeyCombination.ReadableString())); + public IEnumerable FilterTerms => bindings.Select(b => b.KeyCombination.ReadableString()).Prepend(text.Text); public KeyBindingRow(object action, IEnumerable bindings) { diff --git a/osu.Game/Rulesets/Edit/HitObjectComposer.cs b/osu.Game/Rulesets/Edit/HitObjectComposer.cs index c076b53f51..24a0a7e643 100644 --- a/osu.Game/Rulesets/Edit/HitObjectComposer.cs +++ b/osu.Game/Rulesets/Edit/HitObjectComposer.cs @@ -128,10 +128,8 @@ namespace osu.Game.Rulesets.Edit selectionLayer.SelectionFinished += hitObjectMaskLayer.AddSelectionOverlay; toolboxCollection.Items = - new[] { new RadioButton("Select", () => setCompositionTool(null)) } - .Concat( - CompositionTools.Select(t => new RadioButton(t.Name, () => setCompositionTool(t))) - ) + CompositionTools.Select(t => new RadioButton(t.Name, () => setCompositionTool(t))) + .Prepend(new RadioButton("Select", () => setCompositionTool(null))) .ToList(); toolboxCollection.Items[0].Select(); diff --git a/osu.Game/Screens/Select/PlaySongSelect.cs b/osu.Game/Screens/Select/PlaySongSelect.cs index d5a91e1a6b..a3ea3b056b 100644 --- a/osu.Game/Screens/Select/PlaySongSelect.cs +++ b/osu.Game/Screens/Select/PlaySongSelect.cs @@ -150,7 +150,7 @@ namespace osu.Game.Screens.Select var mods = modSelect.SelectedMods.Value; if (mods.All(m => m.GetType() != autoType)) { - modSelect.SelectedMods.Value = mods.Concat(new[] { auto }); + modSelect.SelectedMods.Value = mods.Append(auto); removeAutoModOnResume = true; } } From 0065724992cb505cbd7b51fc191bbf1bafe05909 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 3 Apr 2018 13:05:01 +0900 Subject: [PATCH 27/61] Update framework --- osu-framework | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu-framework b/osu-framework index 85b3494117..6852878ce3 160000 --- a/osu-framework +++ b/osu-framework @@ -1 +1 @@ -Subproject commit 85b3494117ccef1b396b70957e1cffaba06e2b54 +Subproject commit 6852878ce30e1bfde301282563d09c7927d9106c From 4f19059e55c381c6a7410a8c4bae8eaf5f465dc5 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 3 Apr 2018 21:29:49 +0900 Subject: [PATCH 28/61] DragBox -> DragLayer --- .../Screens/Compose/Layers/{DragBox.cs => DragLayer.cs} | 8 ++++---- .../Edit/Screens/Compose/Layers/HitObjectMaskLayer.cs | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) rename osu.Game/Screens/Edit/Screens/Compose/Layers/{DragBox.cs => DragLayer.cs} (89%) diff --git a/osu.Game/Screens/Edit/Screens/Compose/Layers/DragBox.cs b/osu.Game/Screens/Edit/Screens/Compose/Layers/DragLayer.cs similarity index 89% rename from osu.Game/Screens/Edit/Screens/Compose/Layers/DragBox.cs rename to osu.Game/Screens/Edit/Screens/Compose/Layers/DragLayer.cs index ea170a0326..5ad2aeb0e2 100644 --- a/osu.Game/Screens/Edit/Screens/Compose/Layers/DragBox.cs +++ b/osu.Game/Screens/Edit/Screens/Compose/Layers/DragLayer.cs @@ -14,9 +14,9 @@ using OpenTK.Graphics; namespace osu.Game.Screens.Edit.Screens.Compose.Layers { /// - /// A box that handles and displays drag selection for a collection of s. + /// A layer that handles and displays drag selection for a collection of s. /// - public class DragBox : CompositeDrawable + public class DragLayer : CompositeDrawable { /// /// Invoked when the drag selection has finished. @@ -28,10 +28,10 @@ namespace osu.Game.Screens.Edit.Screens.Compose.Layers private Drawable box; /// - /// Creates a new . + /// Creates a new . /// /// The selectable s. - public DragBox(MaskContainer maskContainer) + public DragLayer(MaskContainer maskContainer) { this.maskContainer = maskContainer; diff --git a/osu.Game/Screens/Edit/Screens/Compose/Layers/HitObjectMaskLayer.cs b/osu.Game/Screens/Edit/Screens/Compose/Layers/HitObjectMaskLayer.cs index f972f9ac81..259bddce46 100644 --- a/osu.Game/Screens/Edit/Screens/Compose/Layers/HitObjectMaskLayer.cs +++ b/osu.Game/Screens/Edit/Screens/Compose/Layers/HitObjectMaskLayer.cs @@ -35,7 +35,7 @@ namespace osu.Game.Screens.Edit.Screens.Compose.Layers selectionBox = composer.CreateSelectionBox(maskContainer); - var dragBox = new DragBox(maskContainer); + var dragBox = new DragLayer(maskContainer); dragBox.DragEnd += () => selectionBox.UpdateVisibility(); InternalChildren = new Drawable[] From 65f0e91734d847ec53c6ef7d7ebf3f442a4053be Mon Sep 17 00:00:00 2001 From: DrabWeb Date: Wed, 4 Apr 2018 03:04:26 -0300 Subject: [PATCH 29/61] Use Interpolation function directly for resizing tab strips. --- osu.Game/Graphics/UserInterface/OsuTabControl.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Graphics/UserInterface/OsuTabControl.cs b/osu.Game/Graphics/UserInterface/OsuTabControl.cs index 1624b255e0..8b692f32bc 100644 --- a/osu.Game/Graphics/UserInterface/OsuTabControl.cs +++ b/osu.Game/Graphics/UserInterface/OsuTabControl.cs @@ -14,6 +14,7 @@ using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.UserInterface; using osu.Framework.Input; +using osu.Framework.MathUtils; using osu.Game.Graphics.Sprites; namespace osu.Game.Graphics.UserInterface @@ -90,7 +91,7 @@ namespace osu.Game.Graphics.UserInterface // dont bother calculating if the strip is invisible if (strip.Colour.MaxAlpha > 0) - strip.ResizeWidthTo(StripWidth(), 500, Easing.OutQuint); + strip.Width = Interpolation.ValueAt(MathHelper.Clamp(Clock.ElapsedFrameTime, 0, 1000), strip.Width, StripWidth(), 0, 500, Easing.OutQuint); } public class OsuTabItem : TabItem, IHasAccentColour From 9df525a38d86d18b8048131ce796301556394730 Mon Sep 17 00:00:00 2001 From: Dan Balasescu <1329837+smoogipoo@users.noreply.github.com> Date: Wed, 4 Apr 2018 15:11:16 +0900 Subject: [PATCH 30/61] Remove extra whitespace In general we don't really do this elsewhere in the codebase. --- osu.Game/Rulesets/Judgements/DrawableJudgement.cs | 3 --- 1 file changed, 3 deletions(-) diff --git a/osu.Game/Rulesets/Judgements/DrawableJudgement.cs b/osu.Game/Rulesets/Judgements/DrawableJudgement.cs index 4b01cc5ccd..f20997e96d 100644 --- a/osu.Game/Rulesets/Judgements/DrawableJudgement.cs +++ b/osu.Game/Rulesets/Judgements/DrawableJudgement.cs @@ -95,14 +95,11 @@ namespace osu.Game.Rulesets.Judgements case HitResult.Perfect: case HitResult.Great: return colours.Blue; - case HitResult.Ok: case HitResult.Good: return colours.Green; - case HitResult.Meh: return colours.Yellow; - case HitResult.Miss: return colours.Red; } From bed46b1f7e38b3d92ce55f7bce63bb0680f92625 Mon Sep 17 00:00:00 2001 From: Dan Balasescu <1329837+smoogipoo@users.noreply.github.com> Date: Wed, 4 Apr 2018 15:13:42 +0900 Subject: [PATCH 31/61] Split local variable assignment from construction of children --- osu.Game/Rulesets/Judgements/DrawableJudgement.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Rulesets/Judgements/DrawableJudgement.cs b/osu.Game/Rulesets/Judgements/DrawableJudgement.cs index f20997e96d..a1a27c0d43 100644 --- a/osu.Game/Rulesets/Judgements/DrawableJudgement.cs +++ b/osu.Game/Rulesets/Judgements/DrawableJudgement.cs @@ -48,6 +48,7 @@ namespace osu.Game.Rulesets.Judgements private void load(OsuColour colours) { this.colours = colours; + Child = new SkinnableDrawable($"Play/{Judgement.Result}", _ => JudgementText = new OsuSpriteText { Text = Judgement.Result.GetDescription().ToUpper(), From d453c2589a33d9c303850a6dcc5a98ee8f5355a0 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 4 Apr 2018 16:02:20 +0900 Subject: [PATCH 32/61] Add an explanatory comment for weird override --- osu.Game/Screens/Edit/Screens/Compose/Layers/Selection.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Screens/Compose/Layers/Selection.cs b/osu.Game/Screens/Edit/Screens/Compose/Layers/Selection.cs index 427acbef5a..f81aa440ac 100644 --- a/osu.Game/Screens/Edit/Screens/Compose/Layers/Selection.cs +++ b/osu.Game/Screens/Edit/Screens/Compose/Layers/Selection.cs @@ -63,7 +63,10 @@ namespace osu.Game.Screens.Edit.Screens.Compose.Layers #region User Input Handling - // Only handle input on selectable or selected masks + /// + /// Handle input on currently selectable or already selected masks. + /// Keep in mind that selectedMasks may contain masks for non-current objects, which we still want to handle input while selected. + /// public override bool ReceiveMouseInputAt(Vector2 screenSpacePos) => selectableMasks.Reverse().Concat(selectedMasks).Any(m => m.ReceiveMouseInputAt(screenSpacePos)); protected override bool OnMouseDown(InputState state, MouseDownEventArgs args) From 364c3bca06122e0c04ed72240cd9a379381df19b Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 4 Apr 2018 16:24:03 +0900 Subject: [PATCH 33/61] Fix osu!catch autoplay missing starts/ends of JuiceStreams Fixes #2328. Would only happen when ticks and ends were spaced too far apart (or there were no ticks in a juicestream). --- .../TestCaseJuiceStream.cs | 62 +++++++++++++++++++ .../Replays/CatchAutoGenerator.cs | 1 + 2 files changed, 63 insertions(+) create mode 100644 osu.Game.Rulesets.Catch.Tests/TestCaseJuiceStream.cs diff --git a/osu.Game.Rulesets.Catch.Tests/TestCaseJuiceStream.cs b/osu.Game.Rulesets.Catch.Tests/TestCaseJuiceStream.cs new file mode 100644 index 0000000000..0af60cc452 --- /dev/null +++ b/osu.Game.Rulesets.Catch.Tests/TestCaseJuiceStream.cs @@ -0,0 +1,62 @@ +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System.Collections.Generic; +using System.Linq; +using osu.Game.Beatmaps; +using osu.Game.Rulesets.Catch.Objects; +using osu.Game.Rulesets.Catch.UI; +using osu.Game.Rulesets.Objects.Types; +using osu.Game.Screens.Play; +using osu.Game.Tests.Visual; +using OpenTK; + +namespace osu.Game.Rulesets.Catch.Tests +{ + public class TestCaseJuiceStream : TestCasePlayer + { + public TestCaseJuiceStream() + : base(new CatchRuleset()) + { + } + + protected override Beatmap CreateBeatmap(Ruleset ruleset) + { + var beatmap = new Beatmap + { + BeatmapInfo = new BeatmapInfo + { + BaseDifficulty = new BeatmapDifficulty { CircleSize = 6, SliderMultiplier = 3 }, + Ruleset = ruleset.RulesetInfo + } + }; + + for (int i = 0; i < 100; i++) + { + float width = (i % 10 + 1) / 20f; + + beatmap.HitObjects.Add(new JuiceStream + { + X = 0.5f - width / 2, + ControlPoints = new List + { + Vector2.Zero, + new Vector2(width * CatchPlayfield.BASE_WIDTH, 0) + }, + CurveType = CurveType.Linear, + Distance = width * CatchPlayfield.BASE_WIDTH, + StartTime = i * 2000, + NewCombo = i % 8 == 0 + }); + } + + return beatmap; + } + + protected override Player CreatePlayer(WorkingBeatmap beatmap, Ruleset ruleset) + { + beatmap.Mods.Value = beatmap.Mods.Value.Concat(new[] { ruleset.GetAutoplayMod() }); + return base.CreatePlayer(beatmap, ruleset); + } + } +} diff --git a/osu.Game.Rulesets.Catch/Replays/CatchAutoGenerator.cs b/osu.Game.Rulesets.Catch/Replays/CatchAutoGenerator.cs index f1503a14ee..244ab2b508 100644 --- a/osu.Game.Rulesets.Catch/Replays/CatchAutoGenerator.cs +++ b/osu.Game.Rulesets.Catch/Replays/CatchAutoGenerator.cs @@ -108,6 +108,7 @@ namespace osu.Game.Rulesets.Catch.Replays case BananaShower.Banana _: case TinyDroplet _: case Droplet _: + case Fruit _: moveToNext(nestedObj); break; } From 4196bb8c245d4b79d1d7d1ebb3f56b06708618c9 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 4 Apr 2018 16:24:13 +0900 Subject: [PATCH 34/61] Move selection logic to MaskContainer --- .../Edit/Screens/Compose/Layers/DragLayer.cs | 9 +-------- .../Edit/Screens/Compose/Layers/MaskContainer.cs | 16 ++++++++++++++++ 2 files changed, 17 insertions(+), 8 deletions(-) diff --git a/osu.Game/Screens/Edit/Screens/Compose/Layers/DragLayer.cs b/osu.Game/Screens/Edit/Screens/Compose/Layers/DragLayer.cs index 5ad2aeb0e2..8ed9cab79d 100644 --- a/osu.Game/Screens/Edit/Screens/Compose/Layers/DragLayer.cs +++ b/osu.Game/Screens/Edit/Screens/Compose/Layers/DragLayer.cs @@ -78,14 +78,7 @@ namespace osu.Game.Screens.Edit.Screens.Compose.Layers box.Position = topLeft; box.Size = bottomRight - topLeft; - foreach (var mask in maskContainer.AliveMasks) - { - if (mask.IsPresent && dragRectangle.Contains(mask.SelectionPoint)) - mask.Select(); - else - mask.Deselect(); - } - + maskContainer.Select(dragRectangle); return true; } diff --git a/osu.Game/Screens/Edit/Screens/Compose/Layers/MaskContainer.cs b/osu.Game/Screens/Edit/Screens/Compose/Layers/MaskContainer.cs index 89bae004b5..9aea17844a 100644 --- a/osu.Game/Screens/Edit/Screens/Compose/Layers/MaskContainer.cs +++ b/osu.Game/Screens/Edit/Screens/Compose/Layers/MaskContainer.cs @@ -7,6 +7,7 @@ using System.Linq; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Rulesets.Edit; +using RectangleF = osu.Framework.Graphics.Primitives.RectangleF; namespace osu.Game.Screens.Edit.Screens.Compose.Layers { @@ -48,6 +49,21 @@ namespace osu.Game.Screens.Edit.Screens.Compose.Layers return result; } + /// + /// Select all masks in a given rectangle selection area. + /// + /// The rectangle to perform a selection on in screen-space coordinates. + public void Select(RectangleF rect) + { + foreach (var mask in AliveMasks) + { + if (mask.IsPresent && rect.Contains(mask.SelectionPoint)) + mask.Select(); + else + mask.Deselect(); + } + } + private void onMaskSelected(HitObjectMask mask) => MaskSelected?.Invoke(mask); private void onMaskDeselected(HitObjectMask mask) => MaskDeselected?.Invoke(mask); From c712b29b5b28f0ac30c624fc34b2e67f991c75d9 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 4 Apr 2018 16:24:27 +0900 Subject: [PATCH 35/61] Rename dragBox to dragLayer --- .../Edit/Screens/Compose/Layers/HitObjectMaskLayer.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game/Screens/Edit/Screens/Compose/Layers/HitObjectMaskLayer.cs b/osu.Game/Screens/Edit/Screens/Compose/Layers/HitObjectMaskLayer.cs index 259bddce46..ca8525b842 100644 --- a/osu.Game/Screens/Edit/Screens/Compose/Layers/HitObjectMaskLayer.cs +++ b/osu.Game/Screens/Edit/Screens/Compose/Layers/HitObjectMaskLayer.cs @@ -35,15 +35,15 @@ namespace osu.Game.Screens.Edit.Screens.Compose.Layers selectionBox = composer.CreateSelectionBox(maskContainer); - var dragBox = new DragLayer(maskContainer); - dragBox.DragEnd += () => selectionBox.UpdateVisibility(); + var dragLayer = new DragLayer(maskContainer); + dragLayer.DragEnd += () => selectionBox.UpdateVisibility(); InternalChildren = new Drawable[] { - dragBox, + dragLayer, maskContainer, selectionBox, - dragBox.CreateProxy() + dragLayer.CreateProxy() }; foreach (var obj in playfield.HitObjects.Objects) From d4cb00e08f87f2a15e045a2c9fcfc2618dd82d59 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 4 Apr 2018 17:12:41 +0900 Subject: [PATCH 36/61] Don't display judgements in OsuEditPlayfield --- osu.Game.Rulesets.Osu/Edit/OsuEditPlayfield.cs | 1 + osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Edit/OsuEditPlayfield.cs b/osu.Game.Rulesets.Osu/Edit/OsuEditPlayfield.cs index 5f232b1889..46a3d8575f 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuEditPlayfield.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuEditPlayfield.cs @@ -8,5 +8,6 @@ namespace osu.Game.Rulesets.Osu.Edit public class OsuEditPlayfield : OsuPlayfield { protected override bool ProxyApproachCircles => false; + protected override bool DisplayJudgements => false; } } diff --git a/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs b/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs index 0c5d757474..9010f66acb 100644 --- a/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs +++ b/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs @@ -24,6 +24,7 @@ namespace osu.Game.Rulesets.Osu.UI // Todo: This should not be a thing, but is currently required for the editor // https://github.com/ppy/osu-framework/issues/1283 protected virtual bool ProxyApproachCircles => true; + protected virtual bool DisplayJudgements => true; public static readonly Vector2 BASE_SIZE = new Vector2(512, 384); @@ -73,7 +74,7 @@ namespace osu.Game.Rulesets.Osu.UI private void onJudgement(DrawableHitObject judgedObject, Judgement judgement) { - if (!judgedObject.DisplayJudgement) + if (!judgedObject.DisplayJudgement || !DisplayJudgements) return; DrawableOsuJudgement explosion = new DrawableOsuJudgement(judgement, judgedObject) From c2d371797ea4f5405ca28992e28ed9f1df500ad0 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 4 Apr 2018 17:38:34 +0900 Subject: [PATCH 37/61] Fix unbind failure --- osu.Game/Screens/Edit/Screens/Compose/Layers/MaskContainer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Screens/Compose/Layers/MaskContainer.cs b/osu.Game/Screens/Edit/Screens/Compose/Layers/MaskContainer.cs index 9aea17844a..9367d9e2dc 100644 --- a/osu.Game/Screens/Edit/Screens/Compose/Layers/MaskContainer.cs +++ b/osu.Game/Screens/Edit/Screens/Compose/Layers/MaskContainer.cs @@ -43,7 +43,7 @@ namespace osu.Game.Screens.Edit.Screens.Compose.Layers if (result) { drawable.Selected -= onMaskSelected; - drawable.Deselected += onMaskDeselected; + drawable.Deselected -= onMaskDeselected; } return result; From b6b8c5165729be6489fe0915eecbcf5d4a508d20 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 4 Apr 2018 18:20:32 +0900 Subject: [PATCH 38/61] Remove DragLayer dependency on MaskContainer --- .../Screens/Edit/Screens/Compose/Layers/DragLayer.cs | 11 +++++------ .../Edit/Screens/Compose/Layers/HitObjectMaskLayer.cs | 2 +- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/osu.Game/Screens/Edit/Screens/Compose/Layers/DragLayer.cs b/osu.Game/Screens/Edit/Screens/Compose/Layers/DragLayer.cs index 8ed9cab79d..376747ee20 100644 --- a/osu.Game/Screens/Edit/Screens/Compose/Layers/DragLayer.cs +++ b/osu.Game/Screens/Edit/Screens/Compose/Layers/DragLayer.cs @@ -18,23 +18,22 @@ namespace osu.Game.Screens.Edit.Screens.Compose.Layers /// public class DragLayer : CompositeDrawable { + private readonly Action performSelection; + /// /// Invoked when the drag selection has finished. /// public event Action DragEnd; - private readonly MaskContainer maskContainer; - private Drawable box; /// /// Creates a new . /// /// The selectable s. - public DragLayer(MaskContainer maskContainer) + public DragLayer(Action performSelection) { - this.maskContainer = maskContainer; - + this.performSelection = performSelection; RelativeSizeAxes = Axes.Both; AlwaysPresent = true; Alpha = 0; @@ -78,7 +77,7 @@ namespace osu.Game.Screens.Edit.Screens.Compose.Layers box.Position = topLeft; box.Size = bottomRight - topLeft; - maskContainer.Select(dragRectangle); + performSelection?.Invoke(dragRectangle); return true; } diff --git a/osu.Game/Screens/Edit/Screens/Compose/Layers/HitObjectMaskLayer.cs b/osu.Game/Screens/Edit/Screens/Compose/Layers/HitObjectMaskLayer.cs index ca8525b842..ad8e752d19 100644 --- a/osu.Game/Screens/Edit/Screens/Compose/Layers/HitObjectMaskLayer.cs +++ b/osu.Game/Screens/Edit/Screens/Compose/Layers/HitObjectMaskLayer.cs @@ -35,8 +35,8 @@ namespace osu.Game.Screens.Edit.Screens.Compose.Layers selectionBox = composer.CreateSelectionBox(maskContainer); - var dragLayer = new DragLayer(maskContainer); dragLayer.DragEnd += () => selectionBox.UpdateVisibility(); + var dragLayer = new DragLayer(maskContainer.Select); InternalChildren = new Drawable[] { From 4d71f2084cdd40d2d5c471e3150e863dfc84b088 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 4 Apr 2018 18:21:27 +0900 Subject: [PATCH 39/61] Move individual mask selection logic out of MaskSelection --- .../Visual/TestCaseEditorSelectionLayer.cs | 2 +- osu.Game/Rulesets/Edit/HitObjectComposer.cs | 4 +- osu.Game/Rulesets/Edit/HitObjectMask.cs | 17 +++- .../Edit/Screens/Compose/Layers/DragLayer.cs | 2 +- .../Compose/Layers/HitObjectMaskLayer.cs | 20 ++--- .../Screens/Compose/Layers/MaskContainer.cs | 17 +++- .../Layers/{Selection.cs => MaskSelection.cs} | 77 +++++++++---------- 7 files changed, 82 insertions(+), 57 deletions(-) rename osu.Game/Screens/Edit/Screens/Compose/Layers/{Selection.cs => MaskSelection.cs} (69%) diff --git a/osu.Game.Tests/Visual/TestCaseEditorSelectionLayer.cs b/osu.Game.Tests/Visual/TestCaseEditorSelectionLayer.cs index 4e39548b5b..1d110477f7 100644 --- a/osu.Game.Tests/Visual/TestCaseEditorSelectionLayer.cs +++ b/osu.Game.Tests/Visual/TestCaseEditorSelectionLayer.cs @@ -25,7 +25,7 @@ namespace osu.Game.Tests.Visual { public override IReadOnlyList RequiredTypes => new[] { - typeof(SelectionBox), + typeof(MaskSelection), typeof(HitObjectComposer), typeof(OsuHitObjectComposer), typeof(HitObjectMaskLayer), diff --git a/osu.Game/Rulesets/Edit/HitObjectComposer.cs b/osu.Game/Rulesets/Edit/HitObjectComposer.cs index 4cd5d857c0..d87d00d11f 100644 --- a/osu.Game/Rulesets/Edit/HitObjectComposer.cs +++ b/osu.Game/Rulesets/Edit/HitObjectComposer.cs @@ -248,11 +248,11 @@ namespace osu.Game.Rulesets.Edit public virtual HitObjectMask CreateMaskFor(DrawableHitObject hitObject) => null; /// - /// Creates a which outlines s + /// Creates a which outlines s /// and handles hitobject pattern adjustments. /// /// The container. - public virtual SelectionBox CreateSelectionBox(MaskContainer maskContainer) => new SelectionBox(maskContainer); + public virtual MaskSelection CreateSelectionBox(MaskContainer maskContainer) => new MaskSelection(maskContainer); /// /// Creates a which provides a layer above or below the . diff --git a/osu.Game/Rulesets/Edit/HitObjectMask.cs b/osu.Game/Rulesets/Edit/HitObjectMask.cs index 981e109747..ed6df54722 100644 --- a/osu.Game/Rulesets/Edit/HitObjectMask.cs +++ b/osu.Game/Rulesets/Edit/HitObjectMask.cs @@ -4,6 +4,7 @@ using System; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Primitives; +using osu.Framework.Input; using osu.Game.Rulesets.Objects.Drawables; using OpenTK; @@ -24,6 +25,13 @@ namespace osu.Game.Rulesets.Edit /// public event Action Deselected; + /// + /// Invoked when this has rqeuested selection. + /// Will fire even if already selected. + /// Does not actually perform selection. + /// + public event Action SelectionRequested; + /// /// The which this applies to. /// @@ -31,7 +39,8 @@ namespace osu.Game.Rulesets.Edit protected override bool ShouldBeAlive => HitObject.IsAlive || State == Visibility.Visible; public override bool RemoveWhenNotAlive => false; - public override bool HandleMouseInput => HitObject.IsPresent; + + public override bool HandleMouseInput => ShouldBeAlive; public HitObjectMask(DrawableHitObject hitObject) { @@ -55,6 +64,12 @@ namespace osu.Game.Rulesets.Edit return true; } + protected override bool OnMouseDown(InputState state, MouseDownEventArgs args) + { + SelectionRequested?.Invoke(this); + return base.OnMouseDown(state, args); + } + /// /// Deselects this , causing it to become invisible. /// diff --git a/osu.Game/Screens/Edit/Screens/Compose/Layers/DragLayer.cs b/osu.Game/Screens/Edit/Screens/Compose/Layers/DragLayer.cs index 376747ee20..67dc45a7b2 100644 --- a/osu.Game/Screens/Edit/Screens/Compose/Layers/DragLayer.cs +++ b/osu.Game/Screens/Edit/Screens/Compose/Layers/DragLayer.cs @@ -46,7 +46,7 @@ namespace osu.Game.Screens.Edit.Screens.Compose.Layers { Masking = true, BorderColour = Color4.White, - BorderThickness = SelectionBox.BORDER_RADIUS, + BorderThickness = MaskSelection.BORDER_RADIUS, Child = new Box { RelativeSizeAxes = Axes.Both, diff --git a/osu.Game/Screens/Edit/Screens/Compose/Layers/HitObjectMaskLayer.cs b/osu.Game/Screens/Edit/Screens/Compose/Layers/HitObjectMaskLayer.cs index ad8e752d19..06ae9140bf 100644 --- a/osu.Game/Screens/Edit/Screens/Compose/Layers/HitObjectMaskLayer.cs +++ b/osu.Game/Screens/Edit/Screens/Compose/Layers/HitObjectMaskLayer.cs @@ -18,7 +18,7 @@ namespace osu.Game.Screens.Edit.Screens.Compose.Layers private readonly HitObjectComposer composer; private MaskContainer maskContainer; - private SelectionBox selectionBox; + private MaskSelection maskSelection; public HitObjectMaskLayer(Playfield playfield, HitObjectComposer composer) { @@ -33,16 +33,16 @@ namespace osu.Game.Screens.Edit.Screens.Compose.Layers { maskContainer = new MaskContainer(); - selectionBox = composer.CreateSelectionBox(maskContainer); + maskSelection = composer.CreateSelectionBox(maskContainer); - dragLayer.DragEnd += () => selectionBox.UpdateVisibility(); var dragLayer = new DragLayer(maskContainer.Select); + dragLayer.DragEnd += () => maskSelection.UpdateVisibility(); InternalChildren = new Drawable[] { dragLayer, + maskSelection, maskContainer, - selectionBox, dragLayer.CreateProxy() }; @@ -50,6 +50,12 @@ namespace osu.Game.Screens.Edit.Screens.Compose.Layers addMask(obj); } + protected override bool OnMouseDown(InputState state, MouseDownEventArgs args) + { + maskContainer.DeselectAll(); + return true; + } + /// /// Adds a mask for a which adds movement support. /// @@ -75,11 +81,5 @@ namespace osu.Game.Screens.Edit.Screens.Compose.Layers maskContainer.Remove(mask); } - - protected override bool OnMouseDown(InputState state, MouseDownEventArgs args) - { - selectionBox.DeselectAll(); - return true; - } } } diff --git a/osu.Game/Screens/Edit/Screens/Compose/Layers/MaskContainer.cs b/osu.Game/Screens/Edit/Screens/Compose/Layers/MaskContainer.cs index 9367d9e2dc..4cfca2c93a 100644 --- a/osu.Game/Screens/Edit/Screens/Compose/Layers/MaskContainer.cs +++ b/osu.Game/Screens/Edit/Screens/Compose/Layers/MaskContainer.cs @@ -23,17 +23,25 @@ namespace osu.Game.Screens.Edit.Screens.Compose.Layers /// public event Action MaskDeselected; + public event Action MaskSelectionRequested; + /// /// All the s with == true. /// public IEnumerable AliveMasks => AliveInternalChildren.Cast(); + public MaskContainer() + { + RelativeSizeAxes = Axes.Both; + } + public override void Add(HitObjectMask drawable) { base.Add(drawable); drawable.Selected += onMaskSelected; drawable.Deselected += onMaskDeselected; + drawable.SelectionRequested += onSelectionRequested; } public override bool Remove(HitObjectMask drawable) @@ -44,6 +52,7 @@ namespace osu.Game.Screens.Edit.Screens.Compose.Layers { drawable.Selected -= onMaskSelected; drawable.Deselected -= onMaskDeselected; + drawable.SelectionRequested -= onSelectionRequested; } return result; @@ -59,13 +68,17 @@ namespace osu.Game.Screens.Edit.Screens.Compose.Layers { if (mask.IsPresent && rect.Contains(mask.SelectionPoint)) mask.Select(); - else - mask.Deselect(); } } + /// + /// Deselects all selected s. + /// + public void DeselectAll() => AliveMasks.ToList().ForEach(m => m.Deselect()); + private void onMaskSelected(HitObjectMask mask) => MaskSelected?.Invoke(mask); private void onMaskDeselected(HitObjectMask mask) => MaskDeselected?.Invoke(mask); + private void onSelectionRequested(HitObjectMask mask) => MaskSelectionRequested?.Invoke(mask); protected override int Compare(Drawable x, Drawable y) { diff --git a/osu.Game/Screens/Edit/Screens/Compose/Layers/Selection.cs b/osu.Game/Screens/Edit/Screens/Compose/Layers/MaskSelection.cs similarity index 69% rename from osu.Game/Screens/Edit/Screens/Compose/Layers/Selection.cs rename to osu.Game/Screens/Edit/Screens/Compose/Layers/MaskSelection.cs index f81aa440ac..666fe16afb 100644 --- a/osu.Game/Screens/Edit/Screens/Compose/Layers/Selection.cs +++ b/osu.Game/Screens/Edit/Screens/Compose/Layers/MaskSelection.cs @@ -1,7 +1,6 @@ // Copyright (c) 2007-2018 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE -using System.Collections.Generic; using System.Linq; using osu.Framework.Allocation; using osu.Framework.Graphics; @@ -19,19 +18,19 @@ namespace osu.Game.Screens.Edit.Screens.Compose.Layers /// /// A box which surrounds s and provides interactive handles, context menus etc. /// - public class SelectionBox : CompositeDrawable + public class MaskSelection : CompositeDrawable { public const float BORDER_RADIUS = 2; private readonly MaskContainer maskContainer; private readonly SortedList selectedMasks; - private IEnumerable selectableMasks => maskContainer.AliveMasks; private Drawable outline; - public SelectionBox(MaskContainer maskContainer) + public MaskSelection(MaskContainer maskContainer) { + // todo: remove this this.maskContainer = maskContainer; selectedMasks = new SortedList(maskContainer.Compare); @@ -42,6 +41,7 @@ namespace osu.Game.Screens.Edit.Screens.Compose.Layers maskContainer.MaskSelected += onSelected; maskContainer.MaskDeselected += onDeselected; + maskContainer.MaskSelectionRequested += onSelectionRequested; } [BackgroundDependencyLoader] @@ -63,42 +63,23 @@ namespace osu.Game.Screens.Edit.Screens.Compose.Layers #region User Input Handling - /// - /// Handle input on currently selectable or already selected masks. - /// Keep in mind that selectedMasks may contain masks for non-current objects, which we still want to handle input while selected. - /// - public override bool ReceiveMouseInputAt(Vector2 screenSpacePos) => selectableMasks.Reverse().Concat(selectedMasks).Any(m => m.ReceiveMouseInputAt(screenSpacePos)); + public override bool ReceiveMouseInputAt(Vector2 screenSpacePos) => true; - protected override bool OnMouseDown(InputState state, MouseDownEventArgs args) + protected override bool OnMouseDown(InputState state, MouseDownEventArgs args) => handleInput(state); + + protected override bool OnDragStart(InputState state) => handleInput(state); + + protected override bool OnDragEnd(InputState state) => true; + + private bool handleInput(InputState state) { - // If masks are overlapping, make sure we don't change the selection if the overlapped portion is pressed - if (selectedMasks.Any(m => m.ReceiveMouseInputAt(state.Mouse.NativeState.Position))) - return true; - - DeselectAll(); - selectableMasks.Reverse().First(m => m.ReceiveMouseInputAt(state.Mouse.NativeState.Position)).Select(); + if (!selectedMasks.Any(m => m.ReceiveMouseInputAt(state.Mouse.NativeState.Position))) + return false; UpdateVisibility(); return true; } - protected override bool OnClick(InputState state) - { - // If there's only mask, this isn't going to change anything, so we can save on doing some processing here - if (selectedMasks.Count == 1) - return true; - - var toSelect = selectedMasks.Reverse().First(m => m.ReceiveMouseInputAt(state.Mouse.NativeState.Position)); - - DeselectAll(); - toSelect.Select(); - - UpdateVisibility(); - return true; - } - - protected override bool OnDragStart(InputState state) => true; - protected override bool OnDrag(InputState state) { // Todo: Various forms of snapping @@ -116,8 +97,6 @@ namespace osu.Game.Screens.Edit.Screens.Compose.Layers return true; } - protected override bool OnDragEnd(InputState state) => true; - #endregion #region Selection Handling @@ -133,15 +112,32 @@ namespace osu.Game.Screens.Edit.Screens.Compose.Layers UpdateVisibility(); } - /// - /// Deselects all selected s. - /// - public void DeselectAll() => selectedMasks.ToList().ForEach(m => m.Deselect()); + private void onSelectionRequested(HitObjectMask mask) + { + if (GetContainingInputManager().CurrentState.Keyboard.ControlPressed) + { + if (mask.State == Visibility.Visible) + // we don't want this deselection to affect input for this frame. + Schedule(() => mask.Deselect()); + else + mask.Select(); + } + else + { + if (mask.State == Visibility.Visible) + return; + + maskContainer.DeselectAll(); + mask.Select(); + } + + UpdateVisibility(); + } #endregion /// - /// Updates whether this is visible. + /// Updates whether this is visible. /// internal void UpdateVisibility() { @@ -183,6 +179,7 @@ namespace osu.Game.Screens.Edit.Screens.Compose.Layers maskContainer.MaskSelected -= onSelected; maskContainer.MaskDeselected -= onDeselected; + maskContainer.MaskSelectionRequested -= onSelectionRequested; } } } From e69951b59f63769397da87caeec84e3cae6c2221 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 4 Apr 2018 18:48:19 +0900 Subject: [PATCH 40/61] Rename test to signify it's got auto enabled --- .../{TestCaseJuiceStream.cs => TestCaseAutoJuiceStream.cs} | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) rename osu.Game.Rulesets.Catch.Tests/{TestCaseJuiceStream.cs => TestCaseAutoJuiceStream.cs} (92%) diff --git a/osu.Game.Rulesets.Catch.Tests/TestCaseJuiceStream.cs b/osu.Game.Rulesets.Catch.Tests/TestCaseAutoJuiceStream.cs similarity index 92% rename from osu.Game.Rulesets.Catch.Tests/TestCaseJuiceStream.cs rename to osu.Game.Rulesets.Catch.Tests/TestCaseAutoJuiceStream.cs index 0af60cc452..11a22c69f3 100644 --- a/osu.Game.Rulesets.Catch.Tests/TestCaseJuiceStream.cs +++ b/osu.Game.Rulesets.Catch.Tests/TestCaseAutoJuiceStream.cs @@ -13,9 +13,9 @@ using OpenTK; namespace osu.Game.Rulesets.Catch.Tests { - public class TestCaseJuiceStream : TestCasePlayer + public class TestCaseAutoJuiceStream : TestCasePlayer { - public TestCaseJuiceStream() + public TestCaseAutoJuiceStream() : base(new CatchRuleset()) { } From bce114a37b018d67b3eafd4b17a91758f9bba25a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 4 Apr 2018 18:55:17 +0900 Subject: [PATCH 41/61] Make AliveMasks private --- .../Screens/Edit/Screens/Compose/Layers/MaskContainer.cs | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/osu.Game/Screens/Edit/Screens/Compose/Layers/MaskContainer.cs b/osu.Game/Screens/Edit/Screens/Compose/Layers/MaskContainer.cs index 4cfca2c93a..cbe064d179 100644 --- a/osu.Game/Screens/Edit/Screens/Compose/Layers/MaskContainer.cs +++ b/osu.Game/Screens/Edit/Screens/Compose/Layers/MaskContainer.cs @@ -25,10 +25,7 @@ namespace osu.Game.Screens.Edit.Screens.Compose.Layers public event Action MaskSelectionRequested; - /// - /// All the s with == true. - /// - public IEnumerable AliveMasks => AliveInternalChildren.Cast(); + private IEnumerable aliveMasks => AliveInternalChildren.Cast(); public MaskContainer() { @@ -64,7 +61,7 @@ namespace osu.Game.Screens.Edit.Screens.Compose.Layers /// The rectangle to perform a selection on in screen-space coordinates. public void Select(RectangleF rect) { - foreach (var mask in AliveMasks) + foreach (var mask in aliveMasks) { if (mask.IsPresent && rect.Contains(mask.SelectionPoint)) mask.Select(); @@ -74,7 +71,7 @@ namespace osu.Game.Screens.Edit.Screens.Compose.Layers /// /// Deselects all selected s. /// - public void DeselectAll() => AliveMasks.ToList().ForEach(m => m.Deselect()); + public void DeselectAll() => aliveMasks.ToList().ForEach(m => m.Deselect()); private void onMaskSelected(HitObjectMask mask) => MaskSelected?.Invoke(mask); private void onMaskDeselected(HitObjectMask mask) => MaskDeselected?.Invoke(mask); From 31a7db0a35555a34574a7d5239f24a9553caf1c3 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 4 Apr 2018 19:42:59 +0900 Subject: [PATCH 42/61] Fix drag mishaps --- osu.Game/Screens/Edit/Screens/Compose/Layers/MaskSelection.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/osu.Game/Screens/Edit/Screens/Compose/Layers/MaskSelection.cs b/osu.Game/Screens/Edit/Screens/Compose/Layers/MaskSelection.cs index 666fe16afb..65e85d29bc 100644 --- a/osu.Game/Screens/Edit/Screens/Compose/Layers/MaskSelection.cs +++ b/osu.Game/Screens/Edit/Screens/Compose/Layers/MaskSelection.cs @@ -63,8 +63,6 @@ namespace osu.Game.Screens.Edit.Screens.Compose.Layers #region User Input Handling - public override bool ReceiveMouseInputAt(Vector2 screenSpacePos) => true; - protected override bool OnMouseDown(InputState state, MouseDownEventArgs args) => handleInput(state); protected override bool OnDragStart(InputState state) => handleInput(state); @@ -73,7 +71,7 @@ namespace osu.Game.Screens.Edit.Screens.Compose.Layers private bool handleInput(InputState state) { - if (!selectedMasks.Any(m => m.ReceiveMouseInputAt(state.Mouse.NativeState.Position))) + if (!selectedMasks.Any(m => m.ReceiveMouseInputAt(state.Mouse.NativeState.PositionMouseDown ?? state.Mouse.NativeState.Position))) return false; UpdateVisibility(); From a997ec6139bc46672a49a72a8ca986fab2e3b2a2 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 4 Apr 2018 19:51:56 +0900 Subject: [PATCH 43/61] Fix ShouldBeAlive state --- osu.Game/Rulesets/Edit/HitObjectMask.cs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/osu.Game/Rulesets/Edit/HitObjectMask.cs b/osu.Game/Rulesets/Edit/HitObjectMask.cs index ed6df54722..79c671f335 100644 --- a/osu.Game/Rulesets/Edit/HitObjectMask.cs +++ b/osu.Game/Rulesets/Edit/HitObjectMask.cs @@ -37,10 +37,9 @@ namespace osu.Game.Rulesets.Edit /// public readonly DrawableHitObject HitObject; - protected override bool ShouldBeAlive => HitObject.IsAlive || State == Visibility.Visible; - public override bool RemoveWhenNotAlive => false; - + protected override bool ShouldBeAlive => HitObject.IsAlive && HitObject.IsPresent || State == Visibility.Visible; public override bool HandleMouseInput => ShouldBeAlive; + public override bool RemoveWhenNotAlive => false; public HitObjectMask(DrawableHitObject hitObject) { From 5c036b966b30eb1f2619a10f964d57f429fbdb5b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 4 Apr 2018 20:02:38 +0900 Subject: [PATCH 44/61] Formatting fixes --- osu.Game/Rulesets/Edit/HitObjectMask.cs | 5 ++--- osu.Game/Screens/Edit/Screens/Compose/Layers/DragLayer.cs | 1 + .../Screens/Edit/Screens/Compose/Layers/MaskContainer.cs | 3 +++ 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/osu.Game/Rulesets/Edit/HitObjectMask.cs b/osu.Game/Rulesets/Edit/HitObjectMask.cs index 79c671f335..910da712b4 100644 --- a/osu.Game/Rulesets/Edit/HitObjectMask.cs +++ b/osu.Game/Rulesets/Edit/HitObjectMask.cs @@ -26,9 +26,8 @@ namespace osu.Game.Rulesets.Edit public event Action Deselected; /// - /// Invoked when this has rqeuested selection. - /// Will fire even if already selected. - /// Does not actually perform selection. + /// Invoked when this has requested selection. + /// Will fire even if already selected. Does not actually perform selection. /// public event Action SelectionRequested; diff --git a/osu.Game/Screens/Edit/Screens/Compose/Layers/DragLayer.cs b/osu.Game/Screens/Edit/Screens/Compose/Layers/DragLayer.cs index 67dc45a7b2..51bb61b607 100644 --- a/osu.Game/Screens/Edit/Screens/Compose/Layers/DragLayer.cs +++ b/osu.Game/Screens/Edit/Screens/Compose/Layers/DragLayer.cs @@ -34,6 +34,7 @@ namespace osu.Game.Screens.Edit.Screens.Compose.Layers public DragLayer(Action performSelection) { this.performSelection = performSelection; + RelativeSizeAxes = Axes.Both; AlwaysPresent = true; Alpha = 0; diff --git a/osu.Game/Screens/Edit/Screens/Compose/Layers/MaskContainer.cs b/osu.Game/Screens/Edit/Screens/Compose/Layers/MaskContainer.cs index cbe064d179..c29a254cab 100644 --- a/osu.Game/Screens/Edit/Screens/Compose/Layers/MaskContainer.cs +++ b/osu.Game/Screens/Edit/Screens/Compose/Layers/MaskContainer.cs @@ -23,6 +23,9 @@ namespace osu.Game.Screens.Edit.Screens.Compose.Layers /// public event Action MaskDeselected; + /// + /// Invoked when any requests selection. + /// public event Action MaskSelectionRequested; private IEnumerable aliveMasks => AliveInternalChildren.Cast(); From 2b15555ede43b3657e75a6e14d669c76569f0b76 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 4 Apr 2018 20:05:01 +0900 Subject: [PATCH 45/61] Remove MaskContainer dependency in MaskSelection --- osu-framework | 2 +- osu.Game/Rulesets/Edit/HitObjectComposer.cs | 3 +- .../Compose/Layers/HitObjectMaskLayer.cs | 8 ++- .../Screens/Compose/Layers/MaskContainer.cs | 2 +- .../Screens/Compose/Layers/MaskSelection.cs | 49 ++++++++++--------- 5 files changed, 34 insertions(+), 30 deletions(-) diff --git a/osu-framework b/osu-framework index 6852878ce3..e72c85be22 160000 --- a/osu-framework +++ b/osu-framework @@ -1 +1 @@ -Subproject commit 6852878ce30e1bfde301282563d09c7927d9106c +Subproject commit e72c85be22b9d853df075b965cdd433eb9deccf3 diff --git a/osu.Game/Rulesets/Edit/HitObjectComposer.cs b/osu.Game/Rulesets/Edit/HitObjectComposer.cs index d87d00d11f..9b33ad2563 100644 --- a/osu.Game/Rulesets/Edit/HitObjectComposer.cs +++ b/osu.Game/Rulesets/Edit/HitObjectComposer.cs @@ -251,8 +251,7 @@ namespace osu.Game.Rulesets.Edit /// Creates a which outlines s /// and handles hitobject pattern adjustments. /// - /// The container. - public virtual MaskSelection CreateSelectionBox(MaskContainer maskContainer) => new MaskSelection(maskContainer); + public virtual MaskSelection CreateMaskSelection() => new MaskSelection(); /// /// Creates a which provides a layer above or below the . diff --git a/osu.Game/Screens/Edit/Screens/Compose/Layers/HitObjectMaskLayer.cs b/osu.Game/Screens/Edit/Screens/Compose/Layers/HitObjectMaskLayer.cs index 06ae9140bf..c407f363a5 100644 --- a/osu.Game/Screens/Edit/Screens/Compose/Layers/HitObjectMaskLayer.cs +++ b/osu.Game/Screens/Edit/Screens/Compose/Layers/HitObjectMaskLayer.cs @@ -18,7 +18,6 @@ namespace osu.Game.Screens.Edit.Screens.Compose.Layers private readonly HitObjectComposer composer; private MaskContainer maskContainer; - private MaskSelection maskSelection; public HitObjectMaskLayer(Playfield playfield, HitObjectComposer composer) { @@ -33,7 +32,12 @@ namespace osu.Game.Screens.Edit.Screens.Compose.Layers { maskContainer = new MaskContainer(); - maskSelection = composer.CreateSelectionBox(maskContainer); + var maskSelection = composer.CreateMaskSelection(); + + maskContainer.MaskSelected += maskSelection.HandleSelected; + maskContainer.MaskDeselected += maskSelection.HandleDeselected; + maskContainer.MaskSelectionRequested += maskSelection.HandleSelectionRequested; + maskSelection.DeselectAll = maskContainer.DeselectAll; var dragLayer = new DragLayer(maskContainer.Select); dragLayer.DragEnd += () => maskSelection.UpdateVisibility(); diff --git a/osu.Game/Screens/Edit/Screens/Compose/Layers/MaskContainer.cs b/osu.Game/Screens/Edit/Screens/Compose/Layers/MaskContainer.cs index c29a254cab..401cd16fef 100644 --- a/osu.Game/Screens/Edit/Screens/Compose/Layers/MaskContainer.cs +++ b/osu.Game/Screens/Edit/Screens/Compose/Layers/MaskContainer.cs @@ -87,7 +87,7 @@ namespace osu.Game.Screens.Edit.Screens.Compose.Layers return Compare(xMask, yMask); } - public int Compare(HitObjectMask x, HitObjectMask y) + public static int Compare(HitObjectMask x, HitObjectMask y) { // Put earlier hitobjects towards the end of the list, so they handle input first int i = y.HitObject.HitObject.StartTime.CompareTo(x.HitObject.HitObject.StartTime); diff --git a/osu.Game/Screens/Edit/Screens/Compose/Layers/MaskSelection.cs b/osu.Game/Screens/Edit/Screens/Compose/Layers/MaskSelection.cs index 65e85d29bc..c874d84997 100644 --- a/osu.Game/Screens/Edit/Screens/Compose/Layers/MaskSelection.cs +++ b/osu.Game/Screens/Edit/Screens/Compose/Layers/MaskSelection.cs @@ -1,6 +1,7 @@ // Copyright (c) 2007-2018 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +using System; using System.Linq; using osu.Framework.Allocation; using osu.Framework.Graphics; @@ -22,26 +23,17 @@ namespace osu.Game.Screens.Edit.Screens.Compose.Layers { public const float BORDER_RADIUS = 2; - private readonly MaskContainer maskContainer; - private readonly SortedList selectedMasks; private Drawable outline; - public MaskSelection(MaskContainer maskContainer) + public MaskSelection() { - // todo: remove this - this.maskContainer = maskContainer; - - selectedMasks = new SortedList(maskContainer.Compare); + selectedMasks = new SortedList(MaskContainer.Compare); RelativeSizeAxes = Axes.Both; AlwaysPresent = true; Alpha = 0; - - maskContainer.MaskSelected += onSelected; - maskContainer.MaskDeselected += onDeselected; - maskContainer.MaskSelectionRequested += onSelectionRequested; } [BackgroundDependencyLoader] @@ -99,9 +91,22 @@ namespace osu.Game.Screens.Edit.Screens.Compose.Layers #region Selection Handling - private void onSelected(HitObjectMask mask) => selectedMasks.Add(mask); + /// + /// Bind an action to deselect all selected masks. + /// + public Action DeselectAll { private get; set; } - private void onDeselected(HitObjectMask mask) + /// + /// Handle a mask becoming selected. + /// + /// The mask. + public void HandleSelected(HitObjectMask mask) => selectedMasks.Add(mask); + + /// + /// Handle a mask becoming deselected. + /// + /// The mask. + public void HandleDeselected(HitObjectMask mask) { selectedMasks.Remove(mask); @@ -110,7 +115,11 @@ namespace osu.Game.Screens.Edit.Screens.Compose.Layers UpdateVisibility(); } - private void onSelectionRequested(HitObjectMask mask) + /// + /// Handle a mask requesting selection. + /// + /// The mask. + public void HandleSelectionRequested(HitObjectMask mask) { if (GetContainingInputManager().CurrentState.Keyboard.ControlPressed) { @@ -125,7 +134,8 @@ namespace osu.Game.Screens.Edit.Screens.Compose.Layers if (mask.State == Visibility.Visible) return; - maskContainer.DeselectAll(); + + DeselectAll?.Invoke(); mask.Select(); } @@ -170,14 +180,5 @@ namespace osu.Game.Screens.Edit.Screens.Compose.Layers outline.Size = bottomRight - topLeft; outline.Position = topLeft; } - - protected override void Dispose(bool isDisposing) - { - base.Dispose(isDisposing); - - maskContainer.MaskSelected -= onSelected; - maskContainer.MaskDeselected -= onDeselected; - maskContainer.MaskSelectionRequested -= onSelectionRequested; - } } } From 94c3f385418263bc5c99af6b3cb99431c4ccfda4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 4 Apr 2018 20:06:45 +0900 Subject: [PATCH 46/61] Pass down input state instead of parent lookup --- osu.Game/Rulesets/Edit/HitObjectMask.cs | 4 ++-- .../Screens/Edit/Screens/Compose/Layers/MaskContainer.cs | 5 +++-- .../Screens/Edit/Screens/Compose/Layers/MaskSelection.cs | 4 ++-- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/osu.Game/Rulesets/Edit/HitObjectMask.cs b/osu.Game/Rulesets/Edit/HitObjectMask.cs index 910da712b4..741f600a21 100644 --- a/osu.Game/Rulesets/Edit/HitObjectMask.cs +++ b/osu.Game/Rulesets/Edit/HitObjectMask.cs @@ -29,7 +29,7 @@ namespace osu.Game.Rulesets.Edit /// Invoked when this has requested selection. /// Will fire even if already selected. Does not actually perform selection. /// - public event Action SelectionRequested; + public event Action SelectionRequested; /// /// The which this applies to. @@ -64,7 +64,7 @@ namespace osu.Game.Rulesets.Edit protected override bool OnMouseDown(InputState state, MouseDownEventArgs args) { - SelectionRequested?.Invoke(this); + SelectionRequested?.Invoke(this, state); return base.OnMouseDown(state, args); } diff --git a/osu.Game/Screens/Edit/Screens/Compose/Layers/MaskContainer.cs b/osu.Game/Screens/Edit/Screens/Compose/Layers/MaskContainer.cs index 401cd16fef..cade7daae2 100644 --- a/osu.Game/Screens/Edit/Screens/Compose/Layers/MaskContainer.cs +++ b/osu.Game/Screens/Edit/Screens/Compose/Layers/MaskContainer.cs @@ -6,6 +6,7 @@ using System.Collections.Generic; using System.Linq; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Input; using osu.Game.Rulesets.Edit; using RectangleF = osu.Framework.Graphics.Primitives.RectangleF; @@ -26,7 +27,7 @@ namespace osu.Game.Screens.Edit.Screens.Compose.Layers /// /// Invoked when any requests selection. /// - public event Action MaskSelectionRequested; + public event Action MaskSelectionRequested; private IEnumerable aliveMasks => AliveInternalChildren.Cast(); @@ -78,7 +79,7 @@ namespace osu.Game.Screens.Edit.Screens.Compose.Layers private void onMaskSelected(HitObjectMask mask) => MaskSelected?.Invoke(mask); private void onMaskDeselected(HitObjectMask mask) => MaskDeselected?.Invoke(mask); - private void onSelectionRequested(HitObjectMask mask) => MaskSelectionRequested?.Invoke(mask); + private void onSelectionRequested(HitObjectMask mask, InputState state) => MaskSelectionRequested?.Invoke(mask, state); protected override int Compare(Drawable x, Drawable y) { diff --git a/osu.Game/Screens/Edit/Screens/Compose/Layers/MaskSelection.cs b/osu.Game/Screens/Edit/Screens/Compose/Layers/MaskSelection.cs index c874d84997..249659c2a4 100644 --- a/osu.Game/Screens/Edit/Screens/Compose/Layers/MaskSelection.cs +++ b/osu.Game/Screens/Edit/Screens/Compose/Layers/MaskSelection.cs @@ -119,9 +119,9 @@ namespace osu.Game.Screens.Edit.Screens.Compose.Layers /// Handle a mask requesting selection. /// /// The mask. - public void HandleSelectionRequested(HitObjectMask mask) + public void HandleSelectionRequested(HitObjectMask mask, InputState state) { - if (GetContainingInputManager().CurrentState.Keyboard.ControlPressed) + if (state.Keyboard.ControlPressed) { if (mask.State == Visibility.Visible) // we don't want this deselection to affect input for this frame. From b7325d73e8c1ac260a346404d7b8a5c5175ab9aa Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 4 Apr 2018 20:44:07 +0900 Subject: [PATCH 47/61] Don't inherit VisbilityContainer --- .../Selection/Overlays/SliderCircleMask.cs | 3 +- osu.Game/Rulesets/Edit/HitObjectMask.cs | 92 +++++++++++++------ .../Screens/Compose/Layers/MaskSelection.cs | 7 +- 3 files changed, 67 insertions(+), 35 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/Layers/Selection/Overlays/SliderCircleMask.cs b/osu.Game.Rulesets.Osu/Edit/Layers/Selection/Overlays/SliderCircleMask.cs index 4e22b4f693..96ff14205e 100644 --- a/osu.Game.Rulesets.Osu/Edit/Layers/Selection/Overlays/SliderCircleMask.cs +++ b/osu.Game.Rulesets.Osu/Edit/Layers/Selection/Overlays/SliderCircleMask.cs @@ -3,7 +3,6 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; using osu.Game.Graphics; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Osu.Objects; @@ -40,7 +39,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Layers.Selection.Overlays AddInternal(new RingPiece()); - State = Visibility.Visible; + Select(); } [BackgroundDependencyLoader] diff --git a/osu.Game/Rulesets/Edit/HitObjectMask.cs b/osu.Game/Rulesets/Edit/HitObjectMask.cs index 741f600a21..4be79df285 100644 --- a/osu.Game/Rulesets/Edit/HitObjectMask.cs +++ b/osu.Game/Rulesets/Edit/HitObjectMask.cs @@ -2,6 +2,7 @@ // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE using System; +using osu.Framework; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Primitives; using osu.Framework.Input; @@ -13,7 +14,7 @@ namespace osu.Game.Rulesets.Edit /// /// A mask placed above a adding editing functionality. /// - public class HitObjectMask : VisibilityContainer + public class HitObjectMask : CompositeDrawable, IStateful { /// /// Invoked when this has been selected. @@ -36,7 +37,7 @@ namespace osu.Game.Rulesets.Edit /// public readonly DrawableHitObject HitObject; - protected override bool ShouldBeAlive => HitObject.IsAlive && HitObject.IsPresent || State == Visibility.Visible; + protected override bool ShouldBeAlive => HitObject.IsAlive && HitObject.IsPresent || State == SelectionState.Selected; public override bool HandleMouseInput => ShouldBeAlive; public override bool RemoveWhenNotAlive => false; @@ -45,45 +46,72 @@ namespace osu.Game.Rulesets.Edit HitObject = hitObject; AlwaysPresent = true; - State = Visibility.Hidden; + Alpha = 0; + } + + private SelectionState state; + + public event Action StateChanged; + + public SelectionState State + { + get => state; + set + { + if (state == value) return; + + state = value; + switch (state) + { + case SelectionState.Selected: + Show(); + Selected?.Invoke(this); + break; + case SelectionState.NotSelected: + Hide(); + Deselected?.Invoke(this); + break; + } + } } /// /// Selects this , causing it to become visible. /// - /// True if the was selected. False if the was already selected. - public bool Select() - { - if (State == Visibility.Visible) - return false; - - Show(); - Selected?.Invoke(this); - return true; - } - - protected override bool OnMouseDown(InputState state, MouseDownEventArgs args) - { - SelectionRequested?.Invoke(this, state); - return base.OnMouseDown(state, args); - } + public void Select() => State = SelectionState.Selected; /// /// Deselects this , causing it to become invisible. /// - /// True if the was deselected. False if the was already deselected. - public bool Deselect() - { - if (State == Visibility.Hidden) - return false; + public void Deselect() => State = SelectionState.NotSelected; - Hide(); - Deselected?.Invoke(this); - return true; + public bool IsSelected => State == SelectionState.Selected; + + private bool selectionRequested; + + protected override bool OnMouseDown(InputState state, MouseDownEventArgs args) + { + selectionRequested = false; + + if (State == SelectionState.NotSelected && !selectionRequested) + { + SelectionRequested?.Invoke(this, state); + selectionRequested = true; + } + + return base.OnMouseDown(state, args); } - protected override void PopIn() => Alpha = 1; - protected override void PopOut() => Alpha = 0; + protected override bool OnClick(InputState state) + { + if (State == SelectionState.Selected && !selectionRequested) + { + selectionRequested = true; + SelectionRequested?.Invoke(this, state); + } + + return base.OnClick(state); + } /// /// The screen-space point that causes this to be selected. @@ -95,4 +123,10 @@ namespace osu.Game.Rulesets.Edit /// public virtual Quad SelectionQuad => ScreenSpaceDrawQuad; } + + public enum SelectionState + { + NotSelected, + Selected + } } diff --git a/osu.Game/Screens/Edit/Screens/Compose/Layers/MaskSelection.cs b/osu.Game/Screens/Edit/Screens/Compose/Layers/MaskSelection.cs index 249659c2a4..54cbd9f1b5 100644 --- a/osu.Game/Screens/Edit/Screens/Compose/Layers/MaskSelection.cs +++ b/osu.Game/Screens/Edit/Screens/Compose/Layers/MaskSelection.cs @@ -123,15 +123,14 @@ namespace osu.Game.Screens.Edit.Screens.Compose.Layers { if (state.Keyboard.ControlPressed) { - if (mask.State == Visibility.Visible) - // we don't want this deselection to affect input for this frame. - Schedule(() => mask.Deselect()); + if (mask.IsSelected) + mask.Deselect(); else mask.Select(); } else { - if (mask.State == Visibility.Visible) + if (mask.IsSelected) return; From 216c4629e025ea71b317c50bd5d796e50a7ef5da Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 4 Apr 2018 20:44:22 +0900 Subject: [PATCH 48/61] Fix dragging backwards not deselecting pending selection --- osu.Game/Screens/Edit/Screens/Compose/Layers/MaskContainer.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game/Screens/Edit/Screens/Compose/Layers/MaskContainer.cs b/osu.Game/Screens/Edit/Screens/Compose/Layers/MaskContainer.cs index cade7daae2..aae4822826 100644 --- a/osu.Game/Screens/Edit/Screens/Compose/Layers/MaskContainer.cs +++ b/osu.Game/Screens/Edit/Screens/Compose/Layers/MaskContainer.cs @@ -69,6 +69,8 @@ namespace osu.Game.Screens.Edit.Screens.Compose.Layers { if (mask.IsPresent && rect.Contains(mask.SelectionPoint)) mask.Select(); + else + mask.Deselect(); } } From 0ad4b8a6f8cb7f00bc34a8336294f4672fe1d6a3 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 5 Apr 2018 15:55:47 +0900 Subject: [PATCH 49/61] Remove TestTestCase No longer necessary as we have restructured tests considerably. --- osu.Game/Tests/TestTestCase.cs | 15 --------------- 1 file changed, 15 deletions(-) delete mode 100644 osu.Game/Tests/TestTestCase.cs diff --git a/osu.Game/Tests/TestTestCase.cs b/osu.Game/Tests/TestTestCase.cs deleted file mode 100644 index 4efd57095e..0000000000 --- a/osu.Game/Tests/TestTestCase.cs +++ /dev/null @@ -1,15 +0,0 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using NUnit.Framework; -using osu.Framework.Testing; - -namespace osu.Game.Tests -{ - [TestFixture] - internal class TestTestCase : TestCase - { - // This TestCase is required for nunit to not throw errors - // See: https://github.com/nunit/nunit/issues/1118 - } -} From 37fb207abd55e7bdccd31fd7eab0cbe0dc0059d4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 5 Apr 2018 15:55:47 +0900 Subject: [PATCH 50/61] Remove TestTestCase No longer necessary as we have restructured tests considerably. --- osu-framework | 2 +- osu.Game/Tests/TestTestCase.cs | 15 --------------- 2 files changed, 1 insertion(+), 16 deletions(-) delete mode 100644 osu.Game/Tests/TestTestCase.cs diff --git a/osu-framework b/osu-framework index 6852878ce3..e4b0b57f5d 160000 --- a/osu-framework +++ b/osu-framework @@ -1 +1 @@ -Subproject commit 6852878ce30e1bfde301282563d09c7927d9106c +Subproject commit e4b0b57f5d3a80c09dcdfb6c8d30962e842b9fc3 diff --git a/osu.Game/Tests/TestTestCase.cs b/osu.Game/Tests/TestTestCase.cs deleted file mode 100644 index 4efd57095e..0000000000 --- a/osu.Game/Tests/TestTestCase.cs +++ /dev/null @@ -1,15 +0,0 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using NUnit.Framework; -using osu.Framework.Testing; - -namespace osu.Game.Tests -{ - [TestFixture] - internal class TestTestCase : TestCase - { - // This TestCase is required for nunit to not throw errors - // See: https://github.com/nunit/nunit/issues/1118 - } -} From 345cfb077d415d43c16dedeb51331c41ba2b177b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 5 Apr 2018 21:03:39 +0900 Subject: [PATCH 51/61] No need to sort list any more --- .../Screens/Edit/Screens/Compose/Layers/MaskContainer.cs | 2 +- .../Screens/Edit/Screens/Compose/Layers/MaskSelection.cs | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game/Screens/Edit/Screens/Compose/Layers/MaskContainer.cs b/osu.Game/Screens/Edit/Screens/Compose/Layers/MaskContainer.cs index aae4822826..12b5971051 100644 --- a/osu.Game/Screens/Edit/Screens/Compose/Layers/MaskContainer.cs +++ b/osu.Game/Screens/Edit/Screens/Compose/Layers/MaskContainer.cs @@ -90,7 +90,7 @@ namespace osu.Game.Screens.Edit.Screens.Compose.Layers return Compare(xMask, yMask); } - public static int Compare(HitObjectMask x, HitObjectMask y) + public int Compare(HitObjectMask x, HitObjectMask y) { // Put earlier hitobjects towards the end of the list, so they handle input first int i = y.HitObject.HitObject.StartTime.CompareTo(x.HitObject.HitObject.StartTime); diff --git a/osu.Game/Screens/Edit/Screens/Compose/Layers/MaskSelection.cs b/osu.Game/Screens/Edit/Screens/Compose/Layers/MaskSelection.cs index 54cbd9f1b5..38efb1ae45 100644 --- a/osu.Game/Screens/Edit/Screens/Compose/Layers/MaskSelection.cs +++ b/osu.Game/Screens/Edit/Screens/Compose/Layers/MaskSelection.cs @@ -2,13 +2,13 @@ // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE using System; +using System.Collections.Generic; using System.Linq; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Input; -using osu.Framework.Lists; using osu.Game.Graphics; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Edit.Types; @@ -23,13 +23,13 @@ namespace osu.Game.Screens.Edit.Screens.Compose.Layers { public const float BORDER_RADIUS = 2; - private readonly SortedList selectedMasks; + private readonly List selectedMasks; private Drawable outline; public MaskSelection() { - selectedMasks = new SortedList(MaskContainer.Compare); + selectedMasks = new List(); RelativeSizeAxes = Axes.Both; AlwaysPresent = true; From 5749e7156011259b3197716089986a99795fc6b7 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 5 Apr 2018 21:06:48 +0900 Subject: [PATCH 52/61] Apply review fixes --- osu.Game/Rulesets/Edit/HitObjectMask.cs | 2 +- osu.Game/Screens/Edit/Screens/Compose/Layers/MaskSelection.cs | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/osu.Game/Rulesets/Edit/HitObjectMask.cs b/osu.Game/Rulesets/Edit/HitObjectMask.cs index 4be79df285..d7984cdf0c 100644 --- a/osu.Game/Rulesets/Edit/HitObjectMask.cs +++ b/osu.Game/Rulesets/Edit/HitObjectMask.cs @@ -93,7 +93,7 @@ namespace osu.Game.Rulesets.Edit { selectionRequested = false; - if (State == SelectionState.NotSelected && !selectionRequested) + if (State == SelectionState.NotSelected) { SelectionRequested?.Invoke(this, state); selectionRequested = true; diff --git a/osu.Game/Screens/Edit/Screens/Compose/Layers/MaskSelection.cs b/osu.Game/Screens/Edit/Screens/Compose/Layers/MaskSelection.cs index 38efb1ae45..67bc5551da 100644 --- a/osu.Game/Screens/Edit/Screens/Compose/Layers/MaskSelection.cs +++ b/osu.Game/Screens/Edit/Screens/Compose/Layers/MaskSelection.cs @@ -133,7 +133,6 @@ namespace osu.Game.Screens.Edit.Screens.Compose.Layers if (mask.IsSelected) return; - DeselectAll?.Invoke(); mask.Select(); } From 24b9a8c9838c1801fe9f2a4997375589bf1eb3b2 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 6 Apr 2018 10:29:34 +0900 Subject: [PATCH 53/61] Allow HitObjectMasks to handle drag events directly --- osu.Game/Rulesets/Edit/HitObjectMask.cs | 16 +++++++++++++++- .../Compose/Layers/HitObjectMaskLayer.cs | 2 ++ .../Screens/Compose/Layers/MaskContainer.cs | 8 ++++++++ .../Screens/Compose/Layers/MaskSelection.cs | 19 +------------------ 4 files changed, 26 insertions(+), 19 deletions(-) diff --git a/osu.Game/Rulesets/Edit/HitObjectMask.cs b/osu.Game/Rulesets/Edit/HitObjectMask.cs index d7984cdf0c..9f055ffc5d 100644 --- a/osu.Game/Rulesets/Edit/HitObjectMask.cs +++ b/osu.Game/Rulesets/Edit/HitObjectMask.cs @@ -32,6 +32,11 @@ namespace osu.Game.Rulesets.Edit /// public event Action SelectionRequested; + /// + /// Invoked when this has requested drag. + /// + public event Action DragRequested; + /// /// The which this applies to. /// @@ -99,7 +104,7 @@ namespace osu.Game.Rulesets.Edit selectionRequested = true; } - return base.OnMouseDown(state, args); + return IsSelected; } protected override bool OnClick(InputState state) @@ -108,11 +113,20 @@ namespace osu.Game.Rulesets.Edit { selectionRequested = true; SelectionRequested?.Invoke(this, state); + return true; } return base.OnClick(state); } + protected override bool OnDragStart(InputState state) => true; + + protected override bool OnDrag(InputState state) + { + DragRequested?.Invoke(this, state); + return true; + } + /// /// The screen-space point that causes this to be selected. /// diff --git a/osu.Game/Screens/Edit/Screens/Compose/Layers/HitObjectMaskLayer.cs b/osu.Game/Screens/Edit/Screens/Compose/Layers/HitObjectMaskLayer.cs index c407f363a5..88f865be20 100644 --- a/osu.Game/Screens/Edit/Screens/Compose/Layers/HitObjectMaskLayer.cs +++ b/osu.Game/Screens/Edit/Screens/Compose/Layers/HitObjectMaskLayer.cs @@ -37,6 +37,8 @@ namespace osu.Game.Screens.Edit.Screens.Compose.Layers maskContainer.MaskSelected += maskSelection.HandleSelected; maskContainer.MaskDeselected += maskSelection.HandleDeselected; maskContainer.MaskSelectionRequested += maskSelection.HandleSelectionRequested; + maskContainer.MaskDragRequested += maskSelection.HandleDrag; + maskSelection.DeselectAll = maskContainer.DeselectAll; var dragLayer = new DragLayer(maskContainer.Select); diff --git a/osu.Game/Screens/Edit/Screens/Compose/Layers/MaskContainer.cs b/osu.Game/Screens/Edit/Screens/Compose/Layers/MaskContainer.cs index 12b5971051..10c0b15ff8 100644 --- a/osu.Game/Screens/Edit/Screens/Compose/Layers/MaskContainer.cs +++ b/osu.Game/Screens/Edit/Screens/Compose/Layers/MaskContainer.cs @@ -29,6 +29,11 @@ namespace osu.Game.Screens.Edit.Screens.Compose.Layers /// public event Action MaskSelectionRequested; + /// + /// Invoked when any requests drag. + /// + public event Action MaskDragRequested; + private IEnumerable aliveMasks => AliveInternalChildren.Cast(); public MaskContainer() @@ -43,6 +48,7 @@ namespace osu.Game.Screens.Edit.Screens.Compose.Layers drawable.Selected += onMaskSelected; drawable.Deselected += onMaskDeselected; drawable.SelectionRequested += onSelectionRequested; + drawable.DragRequested += onDragRequested; } public override bool Remove(HitObjectMask drawable) @@ -54,6 +60,7 @@ namespace osu.Game.Screens.Edit.Screens.Compose.Layers drawable.Selected -= onMaskSelected; drawable.Deselected -= onMaskDeselected; drawable.SelectionRequested -= onSelectionRequested; + drawable.DragRequested -= onDragRequested; } return result; @@ -82,6 +89,7 @@ namespace osu.Game.Screens.Edit.Screens.Compose.Layers private void onMaskSelected(HitObjectMask mask) => MaskSelected?.Invoke(mask); private void onMaskDeselected(HitObjectMask mask) => MaskDeselected?.Invoke(mask); private void onSelectionRequested(HitObjectMask mask, InputState state) => MaskSelectionRequested?.Invoke(mask, state); + private void onDragRequested(HitObjectMask mask, InputState state) => MaskDragRequested?.Invoke(mask, state); protected override int Compare(Drawable x, Drawable y) { diff --git a/osu.Game/Screens/Edit/Screens/Compose/Layers/MaskSelection.cs b/osu.Game/Screens/Edit/Screens/Compose/Layers/MaskSelection.cs index 67bc5551da..64041d8ccb 100644 --- a/osu.Game/Screens/Edit/Screens/Compose/Layers/MaskSelection.cs +++ b/osu.Game/Screens/Edit/Screens/Compose/Layers/MaskSelection.cs @@ -55,22 +55,7 @@ namespace osu.Game.Screens.Edit.Screens.Compose.Layers #region User Input Handling - protected override bool OnMouseDown(InputState state, MouseDownEventArgs args) => handleInput(state); - - protected override bool OnDragStart(InputState state) => handleInput(state); - - protected override bool OnDragEnd(InputState state) => true; - - private bool handleInput(InputState state) - { - if (!selectedMasks.Any(m => m.ReceiveMouseInputAt(state.Mouse.NativeState.PositionMouseDown ?? state.Mouse.NativeState.Position))) - return false; - - UpdateVisibility(); - return true; - } - - protected override bool OnDrag(InputState state) + public void HandleDrag(HitObjectMask m, InputState state) { // Todo: Various forms of snapping @@ -83,8 +68,6 @@ namespace osu.Game.Screens.Edit.Screens.Compose.Layers break; } } - - return true; } #endregion From 32e8d93596d95794b4b0d99e1b1ed810ceea2686 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 6 Apr 2018 11:22:36 +0900 Subject: [PATCH 54/61] Fix selection changing when clicking overlapping hitobjects --- .../Screens/Compose/Layers/MaskContainer.cs | 19 +++++++++++++++++-- .../Screens/Compose/Layers/MaskSelection.cs | 1 - 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/Edit/Screens/Compose/Layers/MaskContainer.cs b/osu.Game/Screens/Edit/Screens/Compose/Layers/MaskContainer.cs index 10c0b15ff8..5f9d0bd96a 100644 --- a/osu.Game/Screens/Edit/Screens/Compose/Layers/MaskContainer.cs +++ b/osu.Game/Screens/Edit/Screens/Compose/Layers/MaskContainer.cs @@ -86,8 +86,18 @@ namespace osu.Game.Screens.Edit.Screens.Compose.Layers /// public void DeselectAll() => aliveMasks.ToList().ForEach(m => m.Deselect()); - private void onMaskSelected(HitObjectMask mask) => MaskSelected?.Invoke(mask); - private void onMaskDeselected(HitObjectMask mask) => MaskDeselected?.Invoke(mask); + private void onMaskSelected(HitObjectMask mask) + { + MaskSelected?.Invoke(mask); + ChangeChildDepth(mask, 1); + } + + private void onMaskDeselected(HitObjectMask mask) + { + MaskDeselected?.Invoke(mask); + ChangeChildDepth(mask, 0); + } + private void onSelectionRequested(HitObjectMask mask, InputState state) => MaskSelectionRequested?.Invoke(mask, state); private void onDragRequested(HitObjectMask mask, InputState state) => MaskDragRequested?.Invoke(mask, state); @@ -100,6 +110,11 @@ namespace osu.Game.Screens.Edit.Screens.Compose.Layers public int Compare(HitObjectMask x, HitObjectMask y) { + // dpeth is used to denote selected status (we always want selected masks 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.HitObject.HitObject.StartTime.CompareTo(x.HitObject.HitObject.StartTime); return i == 0 ? CompareReverseChildID(x, y) : i; diff --git a/osu.Game/Screens/Edit/Screens/Compose/Layers/MaskSelection.cs b/osu.Game/Screens/Edit/Screens/Compose/Layers/MaskSelection.cs index 64041d8ccb..76b8027b07 100644 --- a/osu.Game/Screens/Edit/Screens/Compose/Layers/MaskSelection.cs +++ b/osu.Game/Screens/Edit/Screens/Compose/Layers/MaskSelection.cs @@ -3,7 +3,6 @@ using System; using System.Collections.Generic; -using System.Linq; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; From 5426432e463dcca1bb0dfbce8b095b11ab0fb4f7 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 6 Apr 2018 11:47:21 +0900 Subject: [PATCH 55/61] Fix drag select crashing --- osu.Game/Screens/Edit/Screens/Compose/Layers/MaskContainer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Screens/Compose/Layers/MaskContainer.cs b/osu.Game/Screens/Edit/Screens/Compose/Layers/MaskContainer.cs index 5f9d0bd96a..b631628c9e 100644 --- a/osu.Game/Screens/Edit/Screens/Compose/Layers/MaskContainer.cs +++ b/osu.Game/Screens/Edit/Screens/Compose/Layers/MaskContainer.cs @@ -72,7 +72,7 @@ namespace osu.Game.Screens.Edit.Screens.Compose.Layers /// The rectangle to perform a selection on in screen-space coordinates. public void Select(RectangleF rect) { - foreach (var mask in aliveMasks) + foreach (var mask in aliveMasks.ToList()) { if (mask.IsPresent && rect.Contains(mask.SelectionPoint)) mask.Select(); From 714326b6066ae523ae0282992d56e76a61e71274 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 6 Apr 2018 13:16:27 +0900 Subject: [PATCH 56/61] Fix TestCase not working with dynamic compilation --- osu.Game.Tests/Visual/TestCaseEditorSelectionLayer.cs | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/osu.Game.Tests/Visual/TestCaseEditorSelectionLayer.cs b/osu.Game.Tests/Visual/TestCaseEditorSelectionLayer.cs index 1d110477f7..9b50645b0d 100644 --- a/osu.Game.Tests/Visual/TestCaseEditorSelectionLayer.cs +++ b/osu.Game.Tests/Visual/TestCaseEditorSelectionLayer.cs @@ -13,7 +13,6 @@ using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Osu.Edit; -using osu.Game.Rulesets.Osu.Edit.Layers.Selection.Overlays; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Screens.Edit.Screens.Compose.Layers; using osu.Game.Tests.Beatmaps; @@ -26,13 +25,10 @@ namespace osu.Game.Tests.Visual public override IReadOnlyList RequiredTypes => new[] { typeof(MaskSelection), + typeof(DragLayer), typeof(HitObjectComposer), typeof(OsuHitObjectComposer), typeof(HitObjectMaskLayer), - typeof(HitObjectMask), - typeof(HitCircleMask), - typeof(SliderMask), - typeof(SliderCircleMask), typeof(NotNullAttribute) }; From acbdbcc3df48a0589b0c6c82f4df5782ee6a9b79 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 6 Apr 2018 13:17:26 +0900 Subject: [PATCH 57/61] Update AssemblyInfo in line with framework changes --- osu.Game.Rulesets.Catch/Properties/AssemblyInfo.cs | 3 +-- osu.Game.Rulesets.Mania/Properties/AssemblyInfo.cs | 3 +-- osu.Game.Rulesets.Osu/Properties/AssemblyInfo.cs | 3 +-- osu.Game.Rulesets.Taiko/Properties/AssemblyInfo.cs | 3 +-- osu.Game/Properties/AssemblyInfo.cs | 11 +++++++++++ 5 files changed, 15 insertions(+), 8 deletions(-) create mode 100644 osu.Game/Properties/AssemblyInfo.cs diff --git a/osu.Game.Rulesets.Catch/Properties/AssemblyInfo.cs b/osu.Game.Rulesets.Catch/Properties/AssemblyInfo.cs index 00fd8247d8..fed1013ae1 100644 --- a/osu.Game.Rulesets.Catch/Properties/AssemblyInfo.cs +++ b/osu.Game.Rulesets.Catch/Properties/AssemblyInfo.cs @@ -2,11 +2,10 @@ // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE using System.Runtime.CompilerServices; -using osu.Framework.Testing; // We publish our internal attributes to other sub-projects of the framework. // Note, that we omit visual tests as they are meant to test the framework // behavior "in the wild". [assembly: InternalsVisibleTo("osu.Game.Rulesets.Catch.Tests")] -[assembly: InternalsVisibleTo(DynamicClassCompiler.DYNAMIC_ASSEMBLY_NAME)] +[assembly: InternalsVisibleTo("osu.Game.Rulesets.Catch.Tests.Dynamic")] diff --git a/osu.Game.Rulesets.Mania/Properties/AssemblyInfo.cs b/osu.Game.Rulesets.Mania/Properties/AssemblyInfo.cs index c2c65433ec..515aeab9df 100644 --- a/osu.Game.Rulesets.Mania/Properties/AssemblyInfo.cs +++ b/osu.Game.Rulesets.Mania/Properties/AssemblyInfo.cs @@ -2,11 +2,10 @@ // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE using System.Runtime.CompilerServices; -using osu.Framework.Testing; // We publish our internal attributes to other sub-projects of the framework. // Note, that we omit visual tests as they are meant to test the framework // behavior "in the wild". [assembly: InternalsVisibleTo("osu.Game.Rulesets.Mania.Tests")] -[assembly: InternalsVisibleTo(DynamicClassCompiler.DYNAMIC_ASSEMBLY_NAME)] +[assembly: InternalsVisibleTo("osu.Game.Rulesets.Mania.Tests.Dynamic")] diff --git a/osu.Game.Rulesets.Osu/Properties/AssemblyInfo.cs b/osu.Game.Rulesets.Osu/Properties/AssemblyInfo.cs index 7532646a32..ea2c2c6729 100644 --- a/osu.Game.Rulesets.Osu/Properties/AssemblyInfo.cs +++ b/osu.Game.Rulesets.Osu/Properties/AssemblyInfo.cs @@ -2,11 +2,10 @@ // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE using System.Runtime.CompilerServices; -using osu.Framework.Testing; // We publish our internal attributes to other sub-projects of the framework. // Note, that we omit visual tests as they are meant to test the framework // behavior "in the wild". [assembly: InternalsVisibleTo("osu.Game.Rulesets.Osu.Tests")] -[assembly: InternalsVisibleTo(DynamicClassCompiler.DYNAMIC_ASSEMBLY_NAME)] +[assembly: InternalsVisibleTo("osu.Game.Rulesets.Osu.Tests.Dynamic")] diff --git a/osu.Game.Rulesets.Taiko/Properties/AssemblyInfo.cs b/osu.Game.Rulesets.Taiko/Properties/AssemblyInfo.cs index b7ed9f86b0..77218af5e1 100644 --- a/osu.Game.Rulesets.Taiko/Properties/AssemblyInfo.cs +++ b/osu.Game.Rulesets.Taiko/Properties/AssemblyInfo.cs @@ -2,11 +2,10 @@ // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE using System.Runtime.CompilerServices; -using osu.Framework.Testing; // We publish our internal attributes to other sub-projects of the framework. // Note, that we omit visual tests as they are meant to test the framework // behavior "in the wild". [assembly: InternalsVisibleTo("osu.Game.Rulesets.Taiko.Tests")] -[assembly: InternalsVisibleTo(DynamicClassCompiler.DYNAMIC_ASSEMBLY_NAME)] +[assembly: InternalsVisibleTo("osu.Game.Rulesets.Taiko.Tests.Dynamic")] diff --git a/osu.Game/Properties/AssemblyInfo.cs b/osu.Game/Properties/AssemblyInfo.cs new file mode 100644 index 0000000000..9384740308 --- /dev/null +++ b/osu.Game/Properties/AssemblyInfo.cs @@ -0,0 +1,11 @@ +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System.Runtime.CompilerServices; + +// We publish our internal attributes to other sub-projects of the framework. +// Note, that we omit visual tests as they are meant to test the framework +// behavior "in the wild". + +[assembly: InternalsVisibleTo("osu.Game.Tests")] +[assembly: InternalsVisibleTo("osu.Game.Tests.Dynamic")] From 768a5e5383d1855fc54cf546c9959a63f49faae1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 6 Apr 2018 15:20:09 +0900 Subject: [PATCH 58/61] Create ManualInputManagerTestCase A base class for running more input-driven tests. --- osu.Game.Tests/Visual/TestCaseCursors.cs | 129 ++++++++---------- .../Visual/ManualInputManagerTestCase.cs | 29 ++++ 2 files changed, 86 insertions(+), 72 deletions(-) create mode 100644 osu.Game/Tests/Visual/ManualInputManagerTestCase.cs diff --git a/osu.Game.Tests/Visual/TestCaseCursors.cs b/osu.Game.Tests/Visual/TestCaseCursors.cs index 72e699c54b..4f4fdbeb5b 100644 --- a/osu.Game.Tests/Visual/TestCaseCursors.cs +++ b/osu.Game.Tests/Visual/TestCaseCursors.cs @@ -9,7 +9,6 @@ using osu.Framework.Graphics.Cursor; using osu.Framework.Graphics.Shapes; using osu.Framework.Input; using osu.Framework.MathUtils; -using osu.Framework.Testing.Input; using osu.Game.Graphics.Cursor; using osu.Game.Graphics.Sprites; using OpenTK; @@ -18,88 +17,74 @@ using OpenTK.Graphics; namespace osu.Game.Tests.Visual { [TestFixture] - public class TestCaseCursors : OsuTestCase + public class TestCaseCursors : ManualInputManagerTestCase { - private readonly ManualInputManager inputManager; private readonly CursorOverrideContainer cursorOverrideContainer; private readonly CustomCursorBox[] cursorBoxes = new CustomCursorBox[6]; public TestCaseCursors() { - Child = inputManager = new ManualInputManager + Child = cursorOverrideContainer = new CursorOverrideContainer { - Child = cursorOverrideContainer = new CursorOverrideContainer + RelativeSizeAxes = Axes.Both, + Children = new[] { - RelativeSizeAxes = Axes.Both, - Children = new[] + // Middle user + cursorBoxes[0] = new CustomCursorBox(Color4.Green) { - // Middle user - cursorBoxes[0] = new CustomCursorBox(Color4.Green) - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - RelativeSizeAxes = Axes.Both, - Size = new Vector2(0.5f), - }, - // Top-left user - cursorBoxes[1] = new CustomCursorBox(Color4.Blue) - { - RelativeSizeAxes = Axes.Both, - Size = new Vector2(0.4f) - }, - // Bottom-right user - cursorBoxes[2] = new CustomCursorBox(Color4.Red) - { - Anchor = Anchor.BottomRight, - Origin = Anchor.BottomRight, - RelativeSizeAxes = Axes.Both, - Size = new Vector2(0.4f) - }, - // Bottom-left local - cursorBoxes[3] = new CustomCursorBox(Color4.Magenta, false) - { - Anchor = Anchor.BottomLeft, - Origin = Anchor.BottomLeft, - RelativeSizeAxes = Axes.Both, - Size = new Vector2(0.4f) - }, - // Top-right local - cursorBoxes[4] = new CustomCursorBox(Color4.Cyan, false) - { - Anchor = Anchor.TopRight, - Origin = Anchor.TopRight, - RelativeSizeAxes = Axes.Both, - Size = new Vector2(0.4f) - }, - // Left-local - cursorBoxes[5] = new CustomCursorBox(Color4.Yellow, false) - { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - RelativeSizeAxes = Axes.Both, - Size = new Vector2(0.2f, 1), - }, - } + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.Both, + Size = new Vector2(0.5f), + }, + // Top-left user + cursorBoxes[1] = new CustomCursorBox(Color4.Blue) + { + RelativeSizeAxes = Axes.Both, + Size = new Vector2(0.4f) + }, + // Bottom-right user + cursorBoxes[2] = new CustomCursorBox(Color4.Red) + { + Anchor = Anchor.BottomRight, + Origin = Anchor.BottomRight, + RelativeSizeAxes = Axes.Both, + Size = new Vector2(0.4f) + }, + // Bottom-left local + cursorBoxes[3] = new CustomCursorBox(Color4.Magenta, false) + { + Anchor = Anchor.BottomLeft, + Origin = Anchor.BottomLeft, + RelativeSizeAxes = Axes.Both, + Size = new Vector2(0.4f) + }, + // Top-right local + cursorBoxes[4] = new CustomCursorBox(Color4.Cyan, false) + { + Anchor = Anchor.TopRight, + Origin = Anchor.TopRight, + RelativeSizeAxes = Axes.Both, + Size = new Vector2(0.4f) + }, + // Left-local + cursorBoxes[5] = new CustomCursorBox(Color4.Yellow, false) + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + RelativeSizeAxes = Axes.Both, + Size = new Vector2(0.2f, 1), + }, } }; - returnUserInput(); - AddToggleStep("Smooth transitions", b => cursorBoxes.ForEach(box => box.SmoothTransition = b)); testUserCursor(); testLocalCursor(); testUserCursorOverride(); testMultipleLocalCursors(); - returnUserInput(); - } - - /// - /// Returns input back to the user. - /// - private void returnUserInput() - { - AddStep("Return user input", () => inputManager.UseParentState = true); + ReturnUserInput(); } /// @@ -109,7 +94,7 @@ namespace osu.Game.Tests.Visual /// private void testUserCursor() { - AddStep("Move to green area", () => inputManager.MoveMouseTo(cursorBoxes[0])); + AddStep("Move to green area", () => InputManager.MoveMouseTo(cursorBoxes[0])); AddAssert("Check green cursor visible", () => checkVisible(cursorBoxes[0].Cursor)); AddAssert("Check green cursor at mouse", () => checkAtMouse(cursorBoxes[0].Cursor)); AddStep("Move out", moveOut); @@ -124,7 +109,7 @@ namespace osu.Game.Tests.Visual /// private void testLocalCursor() { - AddStep("Move to purple area", () => inputManager.MoveMouseTo(cursorBoxes[3])); + AddStep("Move to purple area", () => InputManager.MoveMouseTo(cursorBoxes[3])); AddAssert("Check purple cursor visible", () => checkVisible(cursorBoxes[3].Cursor)); AddAssert("Check purple cursor at mouse", () => checkAtMouse(cursorBoxes[3].Cursor)); AddAssert("Check global cursor visible", () => checkVisible(cursorOverrideContainer.Cursor)); @@ -141,7 +126,7 @@ namespace osu.Game.Tests.Visual /// private void testUserCursorOverride() { - AddStep("Move to blue-green boundary", () => inputManager.MoveMouseTo(cursorBoxes[1].ScreenSpaceDrawQuad.BottomRight - new Vector2(10))); + AddStep("Move to blue-green boundary", () => InputManager.MoveMouseTo(cursorBoxes[1].ScreenSpaceDrawQuad.BottomRight - new Vector2(10))); AddAssert("Check blue cursor visible", () => checkVisible(cursorBoxes[1].Cursor)); AddAssert("Check green cursor invisible", () => !checkVisible(cursorBoxes[0].Cursor)); AddAssert("Check blue cursor at mouse", () => checkAtMouse(cursorBoxes[1].Cursor)); @@ -156,7 +141,7 @@ namespace osu.Game.Tests.Visual /// private void testMultipleLocalCursors() { - AddStep("Move to yellow-purple boundary", () => inputManager.MoveMouseTo(cursorBoxes[5].ScreenSpaceDrawQuad.BottomRight - new Vector2(10))); + AddStep("Move to yellow-purple boundary", () => InputManager.MoveMouseTo(cursorBoxes[5].ScreenSpaceDrawQuad.BottomRight - new Vector2(10))); AddAssert("Check purple cursor visible", () => checkVisible(cursorBoxes[3].Cursor)); AddAssert("Check purple cursor at mouse", () => checkAtMouse(cursorBoxes[3].Cursor)); AddAssert("Check yellow cursor visible", () => checkVisible(cursorBoxes[5].Cursor)); @@ -172,7 +157,7 @@ namespace osu.Game.Tests.Visual /// private void testUserOverrideWithLocal() { - AddStep("Move to yellow-blue boundary", () => inputManager.MoveMouseTo(cursorBoxes[5].ScreenSpaceDrawQuad.TopRight - new Vector2(10))); + AddStep("Move to yellow-blue boundary", () => InputManager.MoveMouseTo(cursorBoxes[5].ScreenSpaceDrawQuad.TopRight - new Vector2(10))); AddAssert("Check blue cursor visible", () => checkVisible(cursorBoxes[1].Cursor)); AddAssert("Check blue cursor at mouse", () => checkAtMouse(cursorBoxes[1].Cursor)); AddAssert("Check yellow cursor visible", () => checkVisible(cursorBoxes[5].Cursor)); @@ -186,7 +171,7 @@ namespace osu.Game.Tests.Visual /// Moves the cursor to a point not covered by any cursor containers. /// private void moveOut() - => inputManager.MoveMouseTo(new Vector2(inputManager.ScreenSpaceDrawQuad.Centre.X, inputManager.ScreenSpaceDrawQuad.TopLeft.Y)); + => InputManager.MoveMouseTo(new Vector2(InputManager.ScreenSpaceDrawQuad.Centre.X, InputManager.ScreenSpaceDrawQuad.TopLeft.Y)); /// /// Checks if a cursor is visible. @@ -199,7 +184,7 @@ namespace osu.Game.Tests.Visual /// /// The cursor to check. private bool checkAtMouse(CursorContainer cursorContainer) - => Precision.AlmostEquals(inputManager.CurrentState.Mouse.NativeState.Position, cursorContainer.ToScreenSpace(cursorContainer.ActiveCursor.DrawPosition)); + => Precision.AlmostEquals(InputManager.CurrentState.Mouse.NativeState.Position, cursorContainer.ToScreenSpace(cursorContainer.ActiveCursor.DrawPosition)); private class CustomCursorBox : Container, IProvideCursor { diff --git a/osu.Game/Tests/Visual/ManualInputManagerTestCase.cs b/osu.Game/Tests/Visual/ManualInputManagerTestCase.cs new file mode 100644 index 0000000000..c2595231c9 --- /dev/null +++ b/osu.Game/Tests/Visual/ManualInputManagerTestCase.cs @@ -0,0 +1,29 @@ +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Testing.Input; + +namespace osu.Game.Tests.Visual +{ + public abstract class ManualInputManagerTestCase : OsuTestCase + { + protected override Container Content => InputManager; + protected readonly ManualInputManager InputManager; + + protected ManualInputManagerTestCase() + { + base.Content.Add(InputManager = new ManualInputManager()); + ReturnUserInput(); + } + + /// + /// Returns input back to the user. + /// + protected void ReturnUserInput() + { + AddStep("Return user input", () => InputManager.UseParentState = true); + } + } +} From cd48cb18874f328e0df98b21a8d63defe71ddbfb Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 6 Apr 2018 15:17:39 +0900 Subject: [PATCH 59/61] Add comment --- .../Screens/Edit/Screens/Compose/Layers/HitObjectMaskLayer.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game/Screens/Edit/Screens/Compose/Layers/HitObjectMaskLayer.cs b/osu.Game/Screens/Edit/Screens/Compose/Layers/HitObjectMaskLayer.cs index 88f865be20..423cf0ed29 100644 --- a/osu.Game/Screens/Edit/Screens/Compose/Layers/HitObjectMaskLayer.cs +++ b/osu.Game/Screens/Edit/Screens/Compose/Layers/HitObjectMaskLayer.cs @@ -21,7 +21,9 @@ namespace osu.Game.Screens.Edit.Screens.Compose.Layers public HitObjectMaskLayer(Playfield playfield, HitObjectComposer composer) { + // we need the playfield as HitObjects may not be initialised until its BDL. this.playfield = playfield; + this.composer = composer; RelativeSizeAxes = Axes.Both; From ae2dce254aceb1439cd8dce7cc6efb37de652570 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 6 Apr 2018 15:18:01 +0900 Subject: [PATCH 60/61] Rename TestCase --- ...CaseEditorSelectionLayer.cs => TestCaseHitObjectComposer.cs} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename osu.Game.Tests/Visual/{TestCaseEditorSelectionLayer.cs => TestCaseHitObjectComposer.cs} (94%) diff --git a/osu.Game.Tests/Visual/TestCaseEditorSelectionLayer.cs b/osu.Game.Tests/Visual/TestCaseHitObjectComposer.cs similarity index 94% rename from osu.Game.Tests/Visual/TestCaseEditorSelectionLayer.cs rename to osu.Game.Tests/Visual/TestCaseHitObjectComposer.cs index 9b50645b0d..72d60d8e01 100644 --- a/osu.Game.Tests/Visual/TestCaseEditorSelectionLayer.cs +++ b/osu.Game.Tests/Visual/TestCaseHitObjectComposer.cs @@ -20,7 +20,7 @@ using osu.Game.Tests.Beatmaps; namespace osu.Game.Tests.Visual { [TestFixture] - public class TestCaseEditorSelectionLayer : OsuTestCase + public class TestCaseHitObjectComposer : OsuTestCase { public override IReadOnlyList RequiredTypes => new[] { From c80c1071e82dbd22f6c08dace146a4ae4bbf75da Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 6 Apr 2018 19:54:19 +0900 Subject: [PATCH 61/61] Update framework --- osu-framework | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu-framework b/osu-framework index e4b0b57f5d..02d7a0fa47 160000 --- a/osu-framework +++ b/osu-framework @@ -1 +1 @@ -Subproject commit e4b0b57f5d3a80c09dcdfb6c8d30962e842b9fc3 +Subproject commit 02d7a0fa4798d197cd08570ee48951edbb7c7860