From cd794eaa65ac483ce7f59d8a322b7e5890ba16fa Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 29 Sep 2020 19:07:40 +0900 Subject: [PATCH 01/18] Add basic selection box with drag handles --- .../Editing/TestSceneBlueprintSelectBox.cs | 39 +++ .../Compose/Components/ComposeSelectionBox.cs | 308 ++++++++++++++++++ .../Edit/Compose/Components/DragBox.cs | 2 +- .../Compose/Components/SelectionHandler.cs | 16 +- 4 files changed, 349 insertions(+), 16 deletions(-) create mode 100644 osu.Game.Tests/Visual/Editing/TestSceneBlueprintSelectBox.cs create mode 100644 osu.Game/Screens/Edit/Compose/Components/ComposeSelectionBox.cs diff --git a/osu.Game.Tests/Visual/Editing/TestSceneBlueprintSelectBox.cs b/osu.Game.Tests/Visual/Editing/TestSceneBlueprintSelectBox.cs new file mode 100644 index 0000000000..dd44472c09 --- /dev/null +++ b/osu.Game.Tests/Visual/Editing/TestSceneBlueprintSelectBox.cs @@ -0,0 +1,39 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Game.Screens.Edit.Compose.Components; +using osuTK; + +namespace osu.Game.Tests.Visual.Editing +{ + public class TestSceneComposeSelectBox : OsuTestScene + { + public TestSceneComposeSelectBox() + { + ComposeSelectionBox selectionBox = null; + + AddStep("create box", () => + Child = new Container + { + Size = new Vector2(300), + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Children = new Drawable[] + { + selectionBox = new ComposeSelectionBox + { + CanRotate = true, + CanScaleX = true, + CanScaleY = true + } + } + }); + + AddToggleStep("toggle rotation", state => selectionBox.CanRotate = state); + AddToggleStep("toggle x", state => selectionBox.CanScaleX = state); + AddToggleStep("toggle y", state => selectionBox.CanScaleY = state); + } + } +} diff --git a/osu.Game/Screens/Edit/Compose/Components/ComposeSelectionBox.cs b/osu.Game/Screens/Edit/Compose/Components/ComposeSelectionBox.cs new file mode 100644 index 0000000000..c7fc078b98 --- /dev/null +++ b/osu.Game/Screens/Edit/Compose/Components/ComposeSelectionBox.cs @@ -0,0 +1,308 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Graphics.Sprites; +using osu.Framework.Input.Events; +using osu.Game.Graphics; +using osuTK; +using osuTK.Graphics; + +namespace osu.Game.Screens.Edit.Compose.Components +{ + public class ComposeSelectionBox : CompositeDrawable + { + public Action OnRotation; + public Action OnScaleX; + public Action OnScaleY; + + private bool canRotate; + + public bool CanRotate + { + get => canRotate; + set + { + canRotate = value; + recreate(); + } + } + + private bool canScaleX; + + public bool CanScaleX + { + get => canScaleX; + set + { + canScaleX = value; + recreate(); + } + } + + private bool canScaleY; + + public bool CanScaleY + { + get => canScaleY; + set + { + canScaleY = value; + recreate(); + } + } + + public const float BORDER_RADIUS = 3; + + [Resolved] + private OsuColour colours { get; set; } + + [BackgroundDependencyLoader] + private void load() + { + RelativeSizeAxes = Axes.Both; + + recreate(); + } + + private void recreate() + { + if (LoadState < LoadState.Loading) + return; + + InternalChildren = new Drawable[] + { + new Container + { + Masking = true, + BorderThickness = BORDER_RADIUS, + BorderColour = colours.YellowDark, + RelativeSizeAxes = Axes.Both, + Children = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + + AlwaysPresent = true, + Alpha = 0 + }, + } + }, + }; + + if (CanRotate) + { + const float separation = 40; + + AddRangeInternal(new Drawable[] + { + new Box + { + Colour = colours.YellowLight, + Blending = BlendingParameters.Additive, + Alpha = 0.3f, + Size = new Vector2(BORDER_RADIUS, separation), + Anchor = Anchor.TopCentre, + Origin = Anchor.BottomCentre, + }, + new RotationDragHandle + { + Anchor = Anchor.TopCentre, + Y = -separation, + HandleDrag = e => OnRotation?.Invoke(e) + } + }); + } + + if (CanScaleY) + { + AddRangeInternal(new[] + { + new DragHandle + { + Anchor = Anchor.TopCentre, + HandleDrag = e => OnScaleY?.Invoke(e, Anchor.TopCentre) + }, + new DragHandle + { + Anchor = Anchor.BottomCentre, + HandleDrag = e => OnScaleY?.Invoke(e, Anchor.BottomCentre) + }, + }); + } + + if (CanScaleX) + { + AddRangeInternal(new[] + { + new DragHandle + { + Anchor = Anchor.CentreLeft, + HandleDrag = e => OnScaleX?.Invoke(e, Anchor.CentreLeft) + }, + new DragHandle + { + Anchor = Anchor.CentreRight, + HandleDrag = e => OnScaleX?.Invoke(e, Anchor.CentreRight) + }, + }); + } + + if (CanScaleX && CanScaleY) + { + AddRangeInternal(new[] + { + new DragHandle + { + Anchor = Anchor.TopLeft, + HandleDrag = e => + { + OnScaleX?.Invoke(e, Anchor.TopLeft); + OnScaleY?.Invoke(e, Anchor.TopLeft); + } + }, + new DragHandle + { + Anchor = Anchor.TopRight, + HandleDrag = e => + { + OnScaleX?.Invoke(e, Anchor.TopRight); + OnScaleY?.Invoke(e, Anchor.TopRight); + } + }, + new DragHandle + { + Anchor = Anchor.BottomLeft, + HandleDrag = e => + { + OnScaleX?.Invoke(e, Anchor.BottomLeft); + OnScaleY?.Invoke(e, Anchor.BottomLeft); + } + }, + new DragHandle + { + Anchor = Anchor.BottomRight, + HandleDrag = e => + { + OnScaleX?.Invoke(e, Anchor.BottomRight); + OnScaleY?.Invoke(e, Anchor.BottomRight); + } + }, + }); + } + } + + private class RotationDragHandle : DragHandle + { + private SpriteIcon icon; + + [BackgroundDependencyLoader] + private void load() + { + Size *= 2; + + AddInternal(icon = new SpriteIcon + { + RelativeSizeAxes = Axes.Both, + Size = new Vector2(0.5f), + Icon = FontAwesome.Solid.Redo, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + }); + } + + protected override void UpdateHoverState() + { + base.UpdateHoverState(); + icon.Colour = !HandlingMouse && IsHovered ? Color4.White : Color4.Black; + } + } + + private class DragHandle : Container + { + public Action HandleDrag { get; set; } + + private Circle circle; + + [Resolved] + private OsuColour colours { get; set; } + + [BackgroundDependencyLoader] + private void load() + { + Size = new Vector2(10); + Origin = Anchor.Centre; + + InternalChildren = new Drawable[] + { + circle = new Circle + { + RelativeSizeAxes = Axes.Both, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + }, + }; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + UpdateHoverState(); + } + + protected override bool OnHover(HoverEvent e) + { + UpdateHoverState(); + return base.OnHover(e); + } + + protected override void OnHoverLost(HoverLostEvent e) + { + base.OnHoverLost(e); + UpdateHoverState(); + } + + protected bool HandlingMouse; + + protected override bool OnMouseDown(MouseDownEvent e) + { + HandlingMouse = true; + UpdateHoverState(); + return true; + } + + protected override bool OnDragStart(DragStartEvent e) => true; + + protected override void OnDrag(DragEvent e) + { + HandleDrag?.Invoke(e); + base.OnDrag(e); + } + + protected override void OnDragEnd(DragEndEvent e) + { + HandlingMouse = false; + UpdateHoverState(); + base.OnDragEnd(e); + } + + protected override void OnMouseUp(MouseUpEvent e) + { + HandlingMouse = false; + UpdateHoverState(); + base.OnMouseUp(e); + } + + protected virtual void UpdateHoverState() + { + circle.Colour = HandlingMouse ? colours.GrayF : (IsHovered ? colours.Red : colours.YellowDark); + this.ScaleTo(HandlingMouse || IsHovered ? 1.5f : 1, 100, Easing.OutQuint); + } + } + } +} diff --git a/osu.Game/Screens/Edit/Compose/Components/DragBox.cs b/osu.Game/Screens/Edit/Compose/Components/DragBox.cs index 0615ebfc20..0ec981203a 100644 --- a/osu.Game/Screens/Edit/Compose/Components/DragBox.cs +++ b/osu.Game/Screens/Edit/Compose/Components/DragBox.cs @@ -45,7 +45,7 @@ namespace osu.Game.Screens.Edit.Compose.Components { Masking = true, BorderColour = Color4.White, - BorderThickness = SelectionHandler.BORDER_RADIUS, + BorderThickness = ComposeSelectionBox.BORDER_RADIUS, Child = new Box { RelativeSizeAxes = Axes.Both, diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs index a0220cf987..ef97403d02 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs @@ -32,8 +32,6 @@ namespace osu.Game.Screens.Edit.Compose.Components /// public class SelectionHandler : CompositeDrawable, IKeyBindingHandler, IHasContextMenu { - public const float BORDER_RADIUS = 2; - public IEnumerable SelectedBlueprints => selectedBlueprints; private readonly List selectedBlueprints; @@ -69,19 +67,7 @@ namespace osu.Game.Screens.Edit.Compose.Components { Children = new Drawable[] { - new Container - { - RelativeSizeAxes = Axes.Both, - Masking = true, - BorderThickness = BORDER_RADIUS, - BorderColour = colours.YellowDark, - Child = new Box - { - RelativeSizeAxes = Axes.Both, - AlwaysPresent = true, - Alpha = 0 - } - }, + new ComposeSelectionBox(), new Container { Name = "info text", From 265bba1a886db2e988bbed5a502597128badda1b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 29 Sep 2020 19:19:48 +0900 Subject: [PATCH 02/18] Add test coverage of event handling --- .../Editing/TestSceneBlueprintSelectBox.cs | 34 +++++++++++++++++-- 1 file changed, 31 insertions(+), 3 deletions(-) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneBlueprintSelectBox.cs b/osu.Game.Tests/Visual/Editing/TestSceneBlueprintSelectBox.cs index dd44472c09..4b12000fc3 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneBlueprintSelectBox.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneBlueprintSelectBox.cs @@ -3,6 +3,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Input.Events; using osu.Game.Screens.Edit.Compose.Components; using osuTK; @@ -10,23 +11,29 @@ namespace osu.Game.Tests.Visual.Editing { public class TestSceneComposeSelectBox : OsuTestScene { + private Container selectionArea; + public TestSceneComposeSelectBox() { ComposeSelectionBox selectionBox = null; AddStep("create box", () => - Child = new Container + Child = selectionArea = new Container { Size = new Vector2(300), + Position = -new Vector2(150), Anchor = Anchor.Centre, - Origin = Anchor.Centre, Children = new Drawable[] { selectionBox = new ComposeSelectionBox { CanRotate = true, CanScaleX = true, - CanScaleY = true + CanScaleY = true, + + OnRotation = handleRotation, + OnScaleX = handleScaleX, + OnScaleY = handleScaleY, } } }); @@ -35,5 +42,26 @@ namespace osu.Game.Tests.Visual.Editing AddToggleStep("toggle x", state => selectionBox.CanScaleX = state); AddToggleStep("toggle y", state => selectionBox.CanScaleY = state); } + + private void handleScaleY(DragEvent e, Anchor reference) + { + int direction = (reference & Anchor.y0) > 0 ? -1 : 1; + if (direction < 0) + selectionArea.Y += e.Delta.Y; + selectionArea.Height += direction * e.Delta.Y; + } + + private void handleScaleX(DragEvent e, Anchor reference) + { + int direction = (reference & Anchor.x0) > 0 ? -1 : 1; + if (direction < 0) + selectionArea.X += e.Delta.X; + selectionArea.Width += direction * e.Delta.X; + } + + private void handleRotation(DragEvent e) + { + selectionArea.Rotation += e.Delta.X; + } } } From 0a10e40ce0091d40dcd0bc7e7cecebffe912ea49 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 29 Sep 2020 19:43:50 +0900 Subject: [PATCH 03/18] Add scaling support to osu! editor --- .../Edit/OsuSelectionHandler.cs | 97 ++++++++++++++++++- .../Compose/Components/SelectionHandler.cs | 4 +- 2 files changed, 96 insertions(+), 5 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs b/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs index 9418565907..e29536d6b2 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs @@ -2,6 +2,8 @@ // See the LICENCE file in the repository root for full licence text. using System.Linq; +using osu.Framework.Graphics; +using osu.Framework.Input.Events; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Screens.Edit.Compose.Components; using osuTK; @@ -10,7 +12,55 @@ namespace osu.Game.Rulesets.Osu.Edit { public class OsuSelectionHandler : SelectionHandler { - public override bool HandleMovement(MoveSelectionEvent moveEvent) + public override ComposeSelectionBox CreateSelectionBox() + => new ComposeSelectionBox + { + CanRotate = true, + CanScaleX = true, + CanScaleY = true, + + // OnRotation = handleRotation, + OnScaleX = handleScaleX, + OnScaleY = handleScaleY, + }; + + private void handleScaleY(DragEvent e, Anchor reference) + { + int direction = (reference & Anchor.y0) > 0 ? -1 : 1; + + if (direction < 0) + { + // when resizing from a top drag handle, we want to move the selection first + if (!moveSelection(new Vector2(0, e.Delta.Y))) + return; + } + + scaleSelection(new Vector2(0, direction * e.Delta.Y)); + } + + private void handleScaleX(DragEvent e, Anchor reference) + { + int direction = (reference & Anchor.x0) > 0 ? -1 : 1; + + if (direction < 0) + { + // when resizing from a top drag handle, we want to move the selection first + if (!moveSelection(new Vector2(e.Delta.X, 0))) + return; + } + + scaleSelection(new Vector2(direction * e.Delta.X, 0)); + } + + private void handleRotation(DragEvent e) + { + // selectionArea.Rotation += e.Delta.X; + } + + public override bool HandleMovement(MoveSelectionEvent moveEvent) => + moveSelection(moveEvent.InstantDelta); + + private bool scaleSelection(Vector2 scale) { Vector2 minPosition = new Vector2(float.MaxValue, float.MaxValue); Vector2 maxPosition = new Vector2(float.MinValue, float.MinValue); @@ -25,8 +75,47 @@ namespace osu.Game.Rulesets.Osu.Edit } // Stacking is not considered - minPosition = Vector2.ComponentMin(minPosition, Vector2.ComponentMin(h.EndPosition + moveEvent.InstantDelta, h.Position + moveEvent.InstantDelta)); - maxPosition = Vector2.ComponentMax(maxPosition, Vector2.ComponentMax(h.EndPosition + moveEvent.InstantDelta, h.Position + moveEvent.InstantDelta)); + minPosition = Vector2.ComponentMin(minPosition, Vector2.ComponentMin(h.EndPosition, h.Position)); + maxPosition = Vector2.ComponentMax(maxPosition, Vector2.ComponentMax(h.EndPosition, h.Position)); + } + + Vector2 size = maxPosition - minPosition; + Vector2 newSize = size + scale; + + foreach (var h in SelectedHitObjects.OfType()) + { + if (h is Spinner) + { + // Spinners don't support position adjustments + continue; + } + + if (scale.X != 1) + h.Position = new Vector2(minPosition.X + (h.X - minPosition.X) / size.X * newSize.X, h.Y); + if (scale.Y != 1) + h.Position = new Vector2(h.X, minPosition.Y + (h.Y - minPosition.Y) / size.Y * newSize.Y); + } + + return true; + } + + private bool moveSelection(Vector2 delta) + { + Vector2 minPosition = new Vector2(float.MaxValue, float.MaxValue); + Vector2 maxPosition = new Vector2(float.MinValue, float.MinValue); + + // Go through all hitobjects to make sure they would remain in the bounds of the editor after movement, before any movement is attempted + foreach (var h in SelectedHitObjects.OfType()) + { + if (h is Spinner) + { + // Spinners don't support position adjustments + continue; + } + + // Stacking is not considered + minPosition = Vector2.ComponentMin(minPosition, Vector2.ComponentMin(h.EndPosition + delta, h.Position + delta)); + maxPosition = Vector2.ComponentMax(maxPosition, Vector2.ComponentMax(h.EndPosition + delta, h.Position + delta)); } if (minPosition.X < 0 || minPosition.Y < 0 || maxPosition.X > DrawWidth || maxPosition.Y > DrawHeight) @@ -40,7 +129,7 @@ namespace osu.Game.Rulesets.Osu.Edit continue; } - h.Position += moveEvent.InstantDelta; + h.Position += delta; } return true; diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs index ef97403d02..39e413ef05 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs @@ -67,7 +67,7 @@ namespace osu.Game.Screens.Edit.Compose.Components { Children = new Drawable[] { - new ComposeSelectionBox(), + CreateSelectionBox(), new Container { Name = "info text", @@ -91,6 +91,8 @@ namespace osu.Game.Screens.Edit.Compose.Components }; } + public virtual ComposeSelectionBox CreateSelectionBox() => new ComposeSelectionBox(); + #region User Input Handling /// From 33b24b6f46a6b9ffa596a443d5ddaf67aa40940e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 29 Sep 2020 19:50:03 +0900 Subject: [PATCH 04/18] Refactor to be able to get a quad for the current selection --- .../Edit/OsuSelectionHandler.cs | 69 ++++++++++++++----- 1 file changed, 52 insertions(+), 17 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs b/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs index e29536d6b2..7f4ee54243 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs @@ -1,8 +1,10 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System; using System.Linq; using osu.Framework.Graphics; +using osu.Framework.Graphics.Primitives; using osu.Framework.Input.Events; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Screens.Edit.Compose.Components; @@ -54,7 +56,6 @@ namespace osu.Game.Rulesets.Osu.Edit private void handleRotation(DragEvent e) { - // selectionArea.Rotation += e.Delta.X; } public override bool HandleMovement(MoveSelectionEvent moveEvent) => @@ -62,24 +63,11 @@ namespace osu.Game.Rulesets.Osu.Edit private bool scaleSelection(Vector2 scale) { - Vector2 minPosition = new Vector2(float.MaxValue, float.MaxValue); - Vector2 maxPosition = new Vector2(float.MinValue, float.MinValue); + Quad quad = getSelectionQuad(); - // Go through all hitobjects to make sure they would remain in the bounds of the editor after movement, before any movement is attempted - foreach (var h in SelectedHitObjects.OfType()) - { - if (h is Spinner) - { - // Spinners don't support position adjustments - continue; - } + Vector2 minPosition = quad.TopLeft; - // Stacking is not considered - minPosition = Vector2.ComponentMin(minPosition, Vector2.ComponentMin(h.EndPosition, h.Position)); - maxPosition = Vector2.ComponentMax(maxPosition, Vector2.ComponentMax(h.EndPosition, h.Position)); - } - - Vector2 size = maxPosition - minPosition; + Vector2 size = quad.Size; Vector2 newSize = size + scale; foreach (var h in SelectedHitObjects.OfType()) @@ -134,5 +122,52 @@ namespace osu.Game.Rulesets.Osu.Edit return true; } + + private Quad getSelectionQuad() + { + Vector2 minPosition = new Vector2(float.MaxValue, float.MaxValue); + Vector2 maxPosition = new Vector2(float.MinValue, float.MinValue); + + // Go through all hitobjects to make sure they would remain in the bounds of the editor after movement, before any movement is attempted + foreach (var h in SelectedHitObjects.OfType()) + { + if (h is Spinner) + { + // Spinners don't support position adjustments + continue; + } + + // Stacking is not considered + minPosition = Vector2.ComponentMin(minPosition, Vector2.ComponentMin(h.EndPosition, h.Position)); + maxPosition = Vector2.ComponentMax(maxPosition, Vector2.ComponentMax(h.EndPosition, h.Position)); + } + + Vector2 size = maxPosition - minPosition; + + return new Quad(minPosition.X, minPosition.Y, size.X, size.Y); + } + + /// + /// Returns rotated position from a given point. + /// + /// The point. + /// The center to rotate around. + /// The angle to rotate (in degrees). + internal static Vector2 Rotate(Vector2 p, Vector2 center, int angle) + { + angle = -angle; + + p.X -= center.X; + p.Y -= center.Y; + + Vector2 ret; + ret.X = (float)(p.X * Math.Cos(angle / 180f * Math.PI) + p.Y * Math.Sin(angle / 180f * Math.PI)); + ret.Y = (float)(p.X * -Math.Sin(angle / 180f * Math.PI) + p.Y * Math.Cos(angle / 180f * Math.PI)); + + ret.X += center.X; + ret.Y += center.Y; + + return ret; + } } } From 934db14e037cedb82666904b7f390df62b426f90 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 29 Sep 2020 20:00:19 +0900 Subject: [PATCH 05/18] Add rotation support --- .../Edit/OsuSelectionHandler.cs | 37 ++++++++++++++++++- 1 file changed, 35 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs b/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs index 7f4ee54243..84056a69c7 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs @@ -6,6 +6,7 @@ using System.Linq; using osu.Framework.Graphics; using osu.Framework.Graphics.Primitives; using osu.Framework.Input.Events; +using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Screens.Edit.Compose.Components; using osuTK; @@ -21,7 +22,7 @@ namespace osu.Game.Rulesets.Osu.Edit CanScaleX = true, CanScaleY = true, - // OnRotation = handleRotation, + OnRotation = handleRotation, OnScaleX = handleScaleX, OnScaleY = handleScaleY, }; @@ -54,13 +55,45 @@ namespace osu.Game.Rulesets.Osu.Edit scaleSelection(new Vector2(direction * e.Delta.X, 0)); } + private Vector2? centre; + private void handleRotation(DragEvent e) { + rotateSelection(e.Delta.X); } public override bool HandleMovement(MoveSelectionEvent moveEvent) => moveSelection(moveEvent.InstantDelta); + private bool rotateSelection(in float delta) + { + Quad quad = getSelectionQuad(); + + if (!centre.HasValue) + centre = quad.Centre; + + foreach (var h in SelectedHitObjects.OfType()) + { + if (h is Spinner) + { + // Spinners don't support position adjustments + continue; + } + + h.Position = Rotate(h.Position, centre.Value, delta); + + if (h is IHasPath path) + { + foreach (var point in path.Path.ControlPoints) + { + point.Position.Value = Rotate(point.Position.Value, Vector2.Zero, delta); + } + } + } + + return true; + } + private bool scaleSelection(Vector2 scale) { Quad quad = getSelectionQuad(); @@ -153,7 +186,7 @@ namespace osu.Game.Rulesets.Osu.Edit /// The point. /// The center to rotate around. /// The angle to rotate (in degrees). - internal static Vector2 Rotate(Vector2 p, Vector2 center, int angle) + internal static Vector2 Rotate(Vector2 p, Vector2 center, float angle) { angle = -angle; From a2e2cca396e2765221185f95644157afc0b51bd4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 29 Sep 2020 20:08:28 +0900 Subject: [PATCH 06/18] Add proper change handler support --- .../Edit/OsuSelectionHandler.cs | 14 ++++ .../Compose/Components/ComposeSelectionBox.cs | 64 ++++++++++++++++--- 2 files changed, 68 insertions(+), 10 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs b/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs index 84056a69c7..126fdf0932 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs @@ -22,11 +22,25 @@ namespace osu.Game.Rulesets.Osu.Edit CanScaleX = true, CanScaleY = true, + OperationStarted = onStart, + OperationEnded = onEnd, + OnRotation = handleRotation, OnScaleX = handleScaleX, OnScaleY = handleScaleY, }; + private void onEnd() + { + ChangeHandler.EndChange(); + centre = null; + } + + private void onStart() + { + ChangeHandler.BeginChange(); + } + private void handleScaleY(DragEvent e, Anchor reference) { int direction = (reference & Anchor.y0) > 0 ? -1 : 1; diff --git a/osu.Game/Screens/Edit/Compose/Components/ComposeSelectionBox.cs b/osu.Game/Screens/Edit/Compose/Components/ComposeSelectionBox.cs index c7fc078b98..dba1965569 100644 --- a/osu.Game/Screens/Edit/Compose/Components/ComposeSelectionBox.cs +++ b/osu.Game/Screens/Edit/Compose/Components/ComposeSelectionBox.cs @@ -20,6 +20,9 @@ namespace osu.Game.Screens.Edit.Compose.Components public Action OnScaleX; public Action OnScaleY; + public Action OperationStarted; + public Action OperationEnded; + private bool canRotate; public bool CanRotate @@ -114,7 +117,9 @@ namespace osu.Game.Screens.Edit.Compose.Components { Anchor = Anchor.TopCentre, Y = -separation, - HandleDrag = e => OnRotation?.Invoke(e) + HandleDrag = e => OnRotation?.Invoke(e), + OperationStarted = operationStarted, + OperationEnded = operationEnded } }); } @@ -126,12 +131,16 @@ namespace osu.Game.Screens.Edit.Compose.Components new DragHandle { Anchor = Anchor.TopCentre, - HandleDrag = e => OnScaleY?.Invoke(e, Anchor.TopCentre) + HandleDrag = e => OnScaleY?.Invoke(e, Anchor.TopCentre), + OperationStarted = operationStarted, + OperationEnded = operationEnded }, new DragHandle { Anchor = Anchor.BottomCentre, - HandleDrag = e => OnScaleY?.Invoke(e, Anchor.BottomCentre) + HandleDrag = e => OnScaleY?.Invoke(e, Anchor.BottomCentre), + OperationStarted = operationStarted, + OperationEnded = operationEnded }, }); } @@ -143,12 +152,16 @@ namespace osu.Game.Screens.Edit.Compose.Components new DragHandle { Anchor = Anchor.CentreLeft, - HandleDrag = e => OnScaleX?.Invoke(e, Anchor.CentreLeft) + HandleDrag = e => OnScaleX?.Invoke(e, Anchor.CentreLeft), + OperationStarted = operationStarted, + OperationEnded = operationEnded }, new DragHandle { Anchor = Anchor.CentreRight, - HandleDrag = e => OnScaleX?.Invoke(e, Anchor.CentreRight) + HandleDrag = e => OnScaleX?.Invoke(e, Anchor.CentreRight), + OperationStarted = operationStarted, + OperationEnded = operationEnded }, }); } @@ -164,7 +177,9 @@ namespace osu.Game.Screens.Edit.Compose.Components { OnScaleX?.Invoke(e, Anchor.TopLeft); OnScaleY?.Invoke(e, Anchor.TopLeft); - } + }, + OperationStarted = operationStarted, + OperationEnded = operationEnded }, new DragHandle { @@ -173,7 +188,9 @@ namespace osu.Game.Screens.Edit.Compose.Components { OnScaleX?.Invoke(e, Anchor.TopRight); OnScaleY?.Invoke(e, Anchor.TopRight); - } + }, + OperationStarted = operationStarted, + OperationEnded = operationEnded }, new DragHandle { @@ -182,7 +199,9 @@ namespace osu.Game.Screens.Edit.Compose.Components { OnScaleX?.Invoke(e, Anchor.BottomLeft); OnScaleY?.Invoke(e, Anchor.BottomLeft); - } + }, + OperationStarted = operationStarted, + OperationEnded = operationEnded }, new DragHandle { @@ -191,12 +210,28 @@ namespace osu.Game.Screens.Edit.Compose.Components { OnScaleX?.Invoke(e, Anchor.BottomRight); OnScaleY?.Invoke(e, Anchor.BottomRight); - } + }, + OperationStarted = operationStarted, + OperationEnded = operationEnded }, }); } } + private int activeOperations; + + private void operationEnded() + { + if (--activeOperations == 0) + OperationEnded?.Invoke(); + } + + private void operationStarted() + { + if (activeOperations++ == 0) + OperationStarted?.Invoke(); + } + private class RotationDragHandle : DragHandle { private SpriteIcon icon; @@ -225,6 +260,9 @@ namespace osu.Game.Screens.Edit.Compose.Components private class DragHandle : Container { + public Action OperationStarted; + public Action OperationEnded; + public Action HandleDrag { get; set; } private Circle circle; @@ -276,7 +314,11 @@ namespace osu.Game.Screens.Edit.Compose.Components return true; } - protected override bool OnDragStart(DragStartEvent e) => true; + protected override bool OnDragStart(DragStartEvent e) + { + OperationStarted?.Invoke(); + return true; + } protected override void OnDrag(DragEvent e) { @@ -287,6 +329,8 @@ namespace osu.Game.Screens.Edit.Compose.Components protected override void OnDragEnd(DragEndEvent e) { HandlingMouse = false; + OperationEnded?.Invoke(); + UpdateHoverState(); base.OnDragEnd(e); } From 5ae6b2cf5b0bea7c5f53fd3c84934a4b57492f34 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 29 Sep 2020 20:10:17 +0900 Subject: [PATCH 07/18] Fix syntax --- osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs b/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs index 126fdf0932..505b84e699 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs @@ -83,8 +83,7 @@ namespace osu.Game.Rulesets.Osu.Edit { Quad quad = getSelectionQuad(); - if (!centre.HasValue) - centre = quad.Centre; + centre ??= quad.Centre; foreach (var h in SelectedHitObjects.OfType()) { From f93c72dd920a2d239919380bb954dfa2cfdd4cb7 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 29 Sep 2020 20:21:13 +0900 Subject: [PATCH 08/18] Fix non-matching filename --- ...estSceneBlueprintSelectBox.cs => TestSceneComposeSelectBox.cs} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename osu.Game.Tests/Visual/Editing/{TestSceneBlueprintSelectBox.cs => TestSceneComposeSelectBox.cs} (100%) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneBlueprintSelectBox.cs b/osu.Game.Tests/Visual/Editing/TestSceneComposeSelectBox.cs similarity index 100% rename from osu.Game.Tests/Visual/Editing/TestSceneBlueprintSelectBox.cs rename to osu.Game.Tests/Visual/Editing/TestSceneComposeSelectBox.cs From 99a3801267d7e45daea36638c695d146385c7072 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 30 Sep 2020 13:02:05 +0900 Subject: [PATCH 09/18] Tidy up scale/rotation operation code --- .../Edit/OsuSelectionHandler.cs | 64 ++++++++----------- 1 file changed, 28 insertions(+), 36 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs b/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs index 505b84e699..c7be921a4e 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs @@ -6,6 +6,7 @@ using System.Linq; using osu.Framework.Graphics; using osu.Framework.Graphics.Primitives; using osu.Framework.Input.Events; +using osu.Framework.Utils; using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Screens.Edit.Compose.Components; @@ -22,24 +23,25 @@ namespace osu.Game.Rulesets.Osu.Edit CanScaleX = true, CanScaleY = true, - OperationStarted = onStart, - OperationEnded = onEnd, + OperationStarted = () => ChangeHandler.BeginChange(), + OperationEnded = () => + { + ChangeHandler.EndChange(); + referenceOrigin = null; + }, - OnRotation = handleRotation, + OnRotation = e => rotateSelection(e.Delta.X), OnScaleX = handleScaleX, OnScaleY = handleScaleY, }; - private void onEnd() - { - ChangeHandler.EndChange(); - centre = null; - } + public override bool HandleMovement(MoveSelectionEvent moveEvent) => + moveSelection(moveEvent.InstantDelta); - private void onStart() - { - ChangeHandler.BeginChange(); - } + /// + /// During a transform, the initial origin is stored so it can be used throughout the operation. + /// + private Vector2? referenceOrigin; private void handleScaleY(DragEvent e, Anchor reference) { @@ -61,7 +63,7 @@ namespace osu.Game.Rulesets.Osu.Edit if (direction < 0) { - // when resizing from a top drag handle, we want to move the selection first + // when resizing from a left drag handle, we want to move the selection first if (!moveSelection(new Vector2(e.Delta.X, 0))) return; } @@ -69,21 +71,11 @@ namespace osu.Game.Rulesets.Osu.Edit scaleSelection(new Vector2(direction * e.Delta.X, 0)); } - private Vector2? centre; - - private void handleRotation(DragEvent e) - { - rotateSelection(e.Delta.X); - } - - public override bool HandleMovement(MoveSelectionEvent moveEvent) => - moveSelection(moveEvent.InstantDelta); - private bool rotateSelection(in float delta) { Quad quad = getSelectionQuad(); - centre ??= quad.Centre; + referenceOrigin ??= quad.Centre; foreach (var h in SelectedHitObjects.OfType()) { @@ -93,13 +85,13 @@ namespace osu.Game.Rulesets.Osu.Edit continue; } - h.Position = Rotate(h.Position, centre.Value, delta); + h.Position = rotatePointAroundOrigin(h.Position, referenceOrigin.Value, delta); if (h is IHasPath path) { foreach (var point in path.Path.ControlPoints) { - point.Position.Value = Rotate(point.Position.Value, Vector2.Zero, delta); + point.Position.Value = rotatePointAroundOrigin(point.Position.Value, Vector2.Zero, delta); } } } @@ -194,24 +186,24 @@ namespace osu.Game.Rulesets.Osu.Edit } /// - /// Returns rotated position from a given point. + /// Rotate a point around an arbitrary origin. /// - /// The point. - /// The center to rotate around. + /// The point. + /// The centre origin to rotate around. /// The angle to rotate (in degrees). - internal static Vector2 Rotate(Vector2 p, Vector2 center, float angle) + private static Vector2 rotatePointAroundOrigin(Vector2 point, Vector2 origin, float angle) { angle = -angle; - p.X -= center.X; - p.Y -= center.Y; + point.X -= origin.X; + point.Y -= origin.Y; Vector2 ret; - ret.X = (float)(p.X * Math.Cos(angle / 180f * Math.PI) + p.Y * Math.Sin(angle / 180f * Math.PI)); - ret.Y = (float)(p.X * -Math.Sin(angle / 180f * Math.PI) + p.Y * Math.Cos(angle / 180f * Math.PI)); + ret.X = (float)(point.X * Math.Cos(MathUtils.DegreesToRadians(angle)) + point.Y * Math.Sin(angle / 180f * Math.PI)); + ret.Y = (float)(point.X * -Math.Sin(MathUtils.DegreesToRadians(angle)) + point.Y * Math.Cos(angle / 180f * Math.PI)); - ret.X += center.X; - ret.Y += center.Y; + ret.X += origin.X; + ret.Y += origin.Y; return ret; } From f2c26c0927c1cc7dfc5d55b74218be066eead32b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 30 Sep 2020 13:07:24 +0900 Subject: [PATCH 10/18] Move information text underneath the selection box --- osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs index 39e413ef05..afaa5b0f3d 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs @@ -67,7 +67,7 @@ namespace osu.Game.Screens.Edit.Compose.Components { Children = new Drawable[] { - CreateSelectionBox(), + // todo: should maybe be inside the SelectionBox? new Container { Name = "info text", @@ -86,7 +86,8 @@ namespace osu.Game.Screens.Edit.Compose.Components Font = OsuFont.Default.With(size: 11) } } - } + }, + CreateSelectionBox(), } }; } From 39b55a85df11a2dc36257990d176245ebb9d2500 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 30 Sep 2020 13:52:57 +0900 Subject: [PATCH 11/18] Move a lot of the implementation to base SelectionHandler --- .../Edit/OsuSelectionHandler.cs | 56 +++++++++------- .../Compose/Components/SelectionHandler.cs | 67 ++++++++++++++++++- 2 files changed, 94 insertions(+), 29 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs b/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs index c7be921a4e..a2642bda83 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs @@ -5,7 +5,6 @@ using System; using System.Linq; using osu.Framework.Graphics; using osu.Framework.Graphics.Primitives; -using osu.Framework.Input.Events; using osu.Framework.Utils; using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Osu.Objects; @@ -16,24 +15,22 @@ namespace osu.Game.Rulesets.Osu.Edit { public class OsuSelectionHandler : SelectionHandler { - public override ComposeSelectionBox CreateSelectionBox() - => new ComposeSelectionBox - { - CanRotate = true, - CanScaleX = true, - CanScaleY = true, + protected override void OnSelectionChanged() + { + base.OnSelectionChanged(); - OperationStarted = () => ChangeHandler.BeginChange(), - OperationEnded = () => - { - ChangeHandler.EndChange(); - referenceOrigin = null; - }, + bool canOperate = SelectedHitObjects.Count() > 1 || SelectedHitObjects.Any(s => s is Slider); - OnRotation = e => rotateSelection(e.Delta.X), - OnScaleX = handleScaleX, - OnScaleY = handleScaleY, - }; + SelectionBox.CanRotate = canOperate; + SelectionBox.CanScaleX = canOperate; + SelectionBox.CanScaleY = canOperate; + } + + protected override void OnDragOperationEnded() + { + base.OnDragOperationEnded(); + referenceOrigin = null; + } public override bool HandleMovement(MoveSelectionEvent moveEvent) => moveSelection(moveEvent.InstantDelta); @@ -43,35 +40,35 @@ namespace osu.Game.Rulesets.Osu.Edit /// private Vector2? referenceOrigin; - private void handleScaleY(DragEvent e, Anchor reference) + public override bool HandleScaleY(in float scale, Anchor reference) { int direction = (reference & Anchor.y0) > 0 ? -1 : 1; if (direction < 0) { // when resizing from a top drag handle, we want to move the selection first - if (!moveSelection(new Vector2(0, e.Delta.Y))) - return; + if (!moveSelection(new Vector2(0, scale))) + return false; } - scaleSelection(new Vector2(0, direction * e.Delta.Y)); + return scaleSelection(new Vector2(0, direction * scale)); } - private void handleScaleX(DragEvent e, Anchor reference) + public override bool HandleScaleX(in float scale, Anchor reference) { int direction = (reference & Anchor.x0) > 0 ? -1 : 1; if (direction < 0) { // when resizing from a left drag handle, we want to move the selection first - if (!moveSelection(new Vector2(e.Delta.X, 0))) - return; + if (!moveSelection(new Vector2(scale, 0))) + return false; } - scaleSelection(new Vector2(direction * e.Delta.X, 0)); + return scaleSelection(new Vector2(direction * scale, 0)); } - private bool rotateSelection(in float delta) + public override bool HandleRotation(float delta) { Quad quad = getSelectionQuad(); @@ -96,6 +93,7 @@ namespace osu.Game.Rulesets.Osu.Edit } } + // todo: not always return true; } @@ -161,8 +159,14 @@ namespace osu.Game.Rulesets.Osu.Edit return true; } + /// + /// Returns a gamefield-space quad surrounding the current selection. + /// private Quad getSelectionQuad() { + if (!SelectedHitObjects.Any()) + return new Quad(); + Vector2 minPosition = new Vector2(float.MaxValue, float.MaxValue); Vector2 maxPosition = new Vector2(float.MinValue, float.MinValue); diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs index afaa5b0f3d..6cd503b580 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs @@ -43,6 +43,8 @@ namespace osu.Game.Screens.Edit.Compose.Components private OsuSpriteText selectionDetailsText; + protected ComposeSelectionBox SelectionBox { get; private set; } + [Resolved(CanBeNull = true)] protected EditorBeatmap EditorBeatmap { get; private set; } @@ -87,12 +89,37 @@ namespace osu.Game.Screens.Edit.Compose.Components } } }, - CreateSelectionBox(), + SelectionBox = CreateSelectionBox(), } }; } - public virtual ComposeSelectionBox CreateSelectionBox() => new ComposeSelectionBox(); + public ComposeSelectionBox CreateSelectionBox() + => new ComposeSelectionBox + { + OperationStarted = OnDragOperationBegan, + OperationEnded = OnDragOperationEnded, + + OnRotation = e => HandleRotation(e.Delta.X), + OnScaleX = (e, anchor) => HandleScaleX(e.Delta.X, anchor), + OnScaleY = (e, anchor) => HandleScaleY(e.Delta.Y, anchor), + }; + + /// + /// Fired when a drag operation ends from the selection box. + /// + protected virtual void OnDragOperationBegan() + { + ChangeHandler.BeginChange(); + } + + /// + /// Fired when a drag operation begins from the selection box. + /// + protected virtual void OnDragOperationEnded() + { + ChangeHandler.EndChange(); + } #region User Input Handling @@ -108,7 +135,30 @@ namespace osu.Game.Screens.Edit.Compose.Components /// Whether any s could be moved. /// Returning true will also propagate StartTime changes provided by the closest . /// - public virtual bool HandleMovement(MoveSelectionEvent moveEvent) => true; + public virtual bool HandleMovement(MoveSelectionEvent moveEvent) => false; + + /// + /// Handles the selected s being rotated. + /// + /// The delta angle to apply to the selection. + /// Whether any s could be moved. + public virtual bool HandleRotation(float angle) => false; + + /// + /// Handles the selected s being scaled in a vertical direction. + /// + /// The delta scale to apply. + /// The point of reference where the scale is originating from. + /// Whether any s could be moved. + public virtual bool HandleScaleY(in float scale, Anchor anchor) => false; + + /// + /// Handles the selected s being scaled in a horizontal direction. + /// + /// The delta scale to apply. + /// The point of reference where the scale is originating from. + /// Whether any s could be moved. + public virtual bool HandleScaleX(in float scale, Anchor anchor) => false; public bool OnPressed(PlatformAction action) { @@ -211,11 +261,22 @@ namespace osu.Game.Screens.Edit.Compose.Components selectionDetailsText.Text = count > 0 ? count.ToString() : string.Empty; if (count > 0) + { Show(); + OnSelectionChanged(); + } else Hide(); } + /// + /// Triggered whenever more than one object is selected, on each change. + /// Should update the selection box's state to match supported operations. + /// + protected virtual void OnSelectionChanged() + { + } + protected override void Update() { base.Update(); From 313b0d149fa8d5da93dade143312f8d0d437d0f0 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 30 Sep 2020 14:41:32 +0900 Subject: [PATCH 12/18] Refactor scale and rotation operations to share code better Also adds support for scaling individual sliders. --- .../Edit/OsuSelectionHandler.cs | 160 ++++++++---------- 1 file changed, 69 insertions(+), 91 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs b/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs index a2642bda83..706c41c2e3 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Collections.Generic; using System.Linq; using osu.Framework.Graphics; using osu.Framework.Graphics.Primitives; @@ -40,84 +41,70 @@ namespace osu.Game.Rulesets.Osu.Edit /// private Vector2? referenceOrigin; - public override bool HandleScaleY(in float scale, Anchor reference) - { - int direction = (reference & Anchor.y0) > 0 ? -1 : 1; + public override bool HandleScaleY(in float scale, Anchor reference) => + scaleSelection(new Vector2(0, ((reference & Anchor.y0) > 0 ? -1 : 1) * scale), reference); - if (direction < 0) - { - // when resizing from a top drag handle, we want to move the selection first - if (!moveSelection(new Vector2(0, scale))) - return false; - } - - return scaleSelection(new Vector2(0, direction * scale)); - } - - public override bool HandleScaleX(in float scale, Anchor reference) - { - int direction = (reference & Anchor.x0) > 0 ? -1 : 1; - - if (direction < 0) - { - // when resizing from a left drag handle, we want to move the selection first - if (!moveSelection(new Vector2(scale, 0))) - return false; - } - - return scaleSelection(new Vector2(direction * scale, 0)); - } + public override bool HandleScaleX(in float scale, Anchor reference) => + scaleSelection(new Vector2(((reference & Anchor.x0) > 0 ? -1 : 1) * scale, 0), reference); public override bool HandleRotation(float delta) { - Quad quad = getSelectionQuad(); + var hitObjects = selectedMovableObjects; + + Quad quad = getSurroundingQuad(hitObjects); referenceOrigin ??= quad.Centre; - foreach (var h in SelectedHitObjects.OfType()) + foreach (var h in hitObjects) { - if (h is Spinner) - { - // Spinners don't support position adjustments - continue; - } - h.Position = rotatePointAroundOrigin(h.Position, referenceOrigin.Value, delta); if (h is IHasPath path) { foreach (var point in path.Path.ControlPoints) - { point.Position.Value = rotatePointAroundOrigin(point.Position.Value, Vector2.Zero, delta); - } } } - // todo: not always + // this isn't always the case but let's be lenient for now. return true; } - private bool scaleSelection(Vector2 scale) + private bool scaleSelection(Vector2 scale, Anchor reference) { - Quad quad = getSelectionQuad(); + var hitObjects = selectedMovableObjects; - Vector2 minPosition = quad.TopLeft; - - Vector2 size = quad.Size; - Vector2 newSize = size + scale; - - foreach (var h in SelectedHitObjects.OfType()) + // for the time being, allow resizing of slider paths only if the slider is + // the only hit object selected. with a group selection, it's likely the user + // is not looking to change the duration of the slider but expand the whole pattern. + if (hitObjects.Length == 1 && hitObjects.First() is Slider slider) { - if (h is Spinner) - { - // Spinners don't support position adjustments - continue; - } + var quad = getSurroundingQuad(slider.Path.ControlPoints.Select(p => p.Position.Value)); + Vector2 delta = Vector2.One + new Vector2(scale.X / quad.Width, scale.Y / quad.Height); - if (scale.X != 1) - h.Position = new Vector2(minPosition.X + (h.X - minPosition.X) / size.X * newSize.X, h.Y); - if (scale.Y != 1) - h.Position = new Vector2(h.X, minPosition.Y + (h.Y - minPosition.Y) / size.Y * newSize.Y); + foreach (var point in slider.Path.ControlPoints) + point.Position.Value *= delta; + } + else + { + // move the selection before scaling if dragging from top or left anchors. + if ((reference & Anchor.x0) > 0 && !moveSelection(new Vector2(-scale.X, 0))) return false; + if ((reference & Anchor.y0) > 0 && !moveSelection(new Vector2(0, -scale.Y))) return false; + + Quad quad = getSurroundingQuad(hitObjects); + + Vector2 minPosition = quad.TopLeft; + + Vector2 size = quad.Size; + Vector2 newSize = size + scale; + + foreach (var h in hitObjects) + { + if (scale.X != 1) + h.Position = new Vector2(minPosition.X + (h.X - minPosition.X) / size.X * newSize.X, h.Y); + if (scale.Y != 1) + h.Position = new Vector2(h.X, minPosition.Y + (h.Y - minPosition.Y) / size.Y * newSize.Y); + } } return true; @@ -125,44 +112,34 @@ namespace osu.Game.Rulesets.Osu.Edit private bool moveSelection(Vector2 delta) { - Vector2 minPosition = new Vector2(float.MaxValue, float.MaxValue); - Vector2 maxPosition = new Vector2(float.MinValue, float.MinValue); + var hitObjects = selectedMovableObjects; - // Go through all hitobjects to make sure they would remain in the bounds of the editor after movement, before any movement is attempted - foreach (var h in SelectedHitObjects.OfType()) - { - if (h is Spinner) - { - // Spinners don't support position adjustments - continue; - } + Quad quad = getSurroundingQuad(hitObjects); - // Stacking is not considered - minPosition = Vector2.ComponentMin(minPosition, Vector2.ComponentMin(h.EndPosition + delta, h.Position + delta)); - maxPosition = Vector2.ComponentMax(maxPosition, Vector2.ComponentMax(h.EndPosition + delta, h.Position + delta)); - } - - if (minPosition.X < 0 || minPosition.Y < 0 || maxPosition.X > DrawWidth || maxPosition.Y > DrawHeight) + if (quad.TopLeft.X + delta.X < 0 || + quad.TopLeft.Y + delta.Y < 0 || + quad.BottomRight.X + delta.X > DrawWidth || + quad.BottomRight.Y + delta.Y > DrawHeight) return false; - foreach (var h in SelectedHitObjects.OfType()) - { - if (h is Spinner) - { - // Spinners don't support position adjustments - continue; - } - + foreach (var h in hitObjects) h.Position += delta; - } return true; } /// - /// Returns a gamefield-space quad surrounding the current selection. + /// Returns a gamefield-space quad surrounding the provided hit objects. /// - private Quad getSelectionQuad() + /// The hit objects to calculate a quad for. + private Quad getSurroundingQuad(OsuHitObject[] hitObjects) => + getSurroundingQuad(hitObjects.SelectMany(h => new[] { h.Position, h.EndPosition })); + + /// + /// Returns a gamefield-space quad surrounding the provided points. + /// + /// The points to calculate a quad for. + private Quad getSurroundingQuad(IEnumerable points) { if (!SelectedHitObjects.Any()) return new Quad(); @@ -171,17 +148,10 @@ namespace osu.Game.Rulesets.Osu.Edit Vector2 maxPosition = new Vector2(float.MinValue, float.MinValue); // Go through all hitobjects to make sure they would remain in the bounds of the editor after movement, before any movement is attempted - foreach (var h in SelectedHitObjects.OfType()) + foreach (var p in points) { - if (h is Spinner) - { - // Spinners don't support position adjustments - continue; - } - - // Stacking is not considered - minPosition = Vector2.ComponentMin(minPosition, Vector2.ComponentMin(h.EndPosition, h.Position)); - maxPosition = Vector2.ComponentMax(maxPosition, Vector2.ComponentMax(h.EndPosition, h.Position)); + minPosition = Vector2.ComponentMin(minPosition, p); + maxPosition = Vector2.ComponentMax(maxPosition, p); } Vector2 size = maxPosition - minPosition; @@ -189,6 +159,14 @@ namespace osu.Game.Rulesets.Osu.Edit return new Quad(minPosition.X, minPosition.Y, size.X, size.Y); } + /// + /// All osu! hitobjects which can be moved/rotated/scaled. + /// + private OsuHitObject[] selectedMovableObjects => SelectedHitObjects + .OfType() + .Where(h => !(h is Spinner)) + .ToArray(); + /// /// Rotate a point around an arbitrary origin. /// From f1298bed798f8d31de5b17571f0c4f7bb4362f7e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 30 Sep 2020 15:08:56 +0900 Subject: [PATCH 13/18] Combine scale operations and tidy up scale drag handle construction --- .../Edit/OsuSelectionHandler.cs | 58 +++++------ .../Editing/TestSceneComposeSelectBox.cs | 31 +++--- .../Compose/Components/ComposeSelectionBox.cs | 99 +++++-------------- .../Compose/Components/SelectionHandler.cs | 19 +--- 4 files changed, 77 insertions(+), 130 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs b/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs index 706c41c2e3..1f250f078d 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs @@ -41,37 +41,16 @@ namespace osu.Game.Rulesets.Osu.Edit /// private Vector2? referenceOrigin; - public override bool HandleScaleY(in float scale, Anchor reference) => - scaleSelection(new Vector2(0, ((reference & Anchor.y0) > 0 ? -1 : 1) * scale), reference); - - public override bool HandleScaleX(in float scale, Anchor reference) => - scaleSelection(new Vector2(((reference & Anchor.x0) > 0 ? -1 : 1) * scale, 0), reference); - - public override bool HandleRotation(float delta) + public override bool HandleScale(Vector2 scale, Anchor reference) { - var hitObjects = selectedMovableObjects; + // cancel out scale in axes we don't care about (based on which drag handle was used). + if ((reference & Anchor.x1) > 0) scale.X = 0; + if ((reference & Anchor.y1) > 0) scale.Y = 0; - Quad quad = getSurroundingQuad(hitObjects); + // reverse the scale direction if dragging from top or left. + if ((reference & Anchor.x0) > 0) scale.X = -scale.X; + if ((reference & Anchor.y0) > 0) scale.Y = -scale.Y; - referenceOrigin ??= quad.Centre; - - foreach (var h in hitObjects) - { - h.Position = rotatePointAroundOrigin(h.Position, referenceOrigin.Value, delta); - - if (h is IHasPath path) - { - foreach (var point in path.Path.ControlPoints) - point.Position.Value = rotatePointAroundOrigin(point.Position.Value, Vector2.Zero, delta); - } - } - - // this isn't always the case but let's be lenient for now. - return true; - } - - private bool scaleSelection(Vector2 scale, Anchor reference) - { var hitObjects = selectedMovableObjects; // for the time being, allow resizing of slider paths only if the slider is @@ -110,6 +89,29 @@ namespace osu.Game.Rulesets.Osu.Edit return true; } + public override bool HandleRotation(float delta) + { + var hitObjects = selectedMovableObjects; + + Quad quad = getSurroundingQuad(hitObjects); + + referenceOrigin ??= quad.Centre; + + foreach (var h in hitObjects) + { + h.Position = rotatePointAroundOrigin(h.Position, referenceOrigin.Value, delta); + + if (h is IHasPath path) + { + foreach (var point in path.Path.ControlPoints) + point.Position.Value = rotatePointAroundOrigin(point.Position.Value, Vector2.Zero, delta); + } + } + + // this isn't always the case but let's be lenient for now. + return true; + } + private bool moveSelection(Vector2 delta) { var hitObjects = selectedMovableObjects; diff --git a/osu.Game.Tests/Visual/Editing/TestSceneComposeSelectBox.cs b/osu.Game.Tests/Visual/Editing/TestSceneComposeSelectBox.cs index 4b12000fc3..a1fb91024b 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneComposeSelectBox.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneComposeSelectBox.cs @@ -32,8 +32,7 @@ namespace osu.Game.Tests.Visual.Editing CanScaleY = true, OnRotation = handleRotation, - OnScaleX = handleScaleX, - OnScaleY = handleScaleY, + OnScale = handleScale } } }); @@ -43,24 +42,28 @@ namespace osu.Game.Tests.Visual.Editing AddToggleStep("toggle y", state => selectionBox.CanScaleY = state); } - private void handleScaleY(DragEvent e, Anchor reference) + private void handleScale(DragEvent e, Anchor reference) { - int direction = (reference & Anchor.y0) > 0 ? -1 : 1; - if (direction < 0) - selectionArea.Y += e.Delta.Y; - selectionArea.Height += direction * e.Delta.Y; - } + if ((reference & Anchor.y1) == 0) + { + int directionY = (reference & Anchor.y0) > 0 ? -1 : 1; + if (directionY < 0) + selectionArea.Y += e.Delta.Y; + selectionArea.Height += directionY * e.Delta.Y; + } - private void handleScaleX(DragEvent e, Anchor reference) - { - int direction = (reference & Anchor.x0) > 0 ? -1 : 1; - if (direction < 0) - selectionArea.X += e.Delta.X; - selectionArea.Width += direction * e.Delta.X; + if ((reference & Anchor.x1) == 0) + { + int directionX = (reference & Anchor.x0) > 0 ? -1 : 1; + if (directionX < 0) + selectionArea.X += e.Delta.X; + selectionArea.Width += directionX * e.Delta.X; + } } private void handleRotation(DragEvent e) { + // kinda silly and wrong, but just showing that the drag handles work. selectionArea.Rotation += e.Delta.X; } } diff --git a/osu.Game/Screens/Edit/Compose/Components/ComposeSelectionBox.cs b/osu.Game/Screens/Edit/Compose/Components/ComposeSelectionBox.cs index dba1965569..424705c755 100644 --- a/osu.Game/Screens/Edit/Compose/Components/ComposeSelectionBox.cs +++ b/osu.Game/Screens/Edit/Compose/Components/ComposeSelectionBox.cs @@ -17,8 +17,7 @@ namespace osu.Game.Screens.Edit.Compose.Components public class ComposeSelectionBox : CompositeDrawable { public Action OnRotation; - public Action OnScaleX; - public Action OnScaleY; + public Action OnScale; public Action OperationStarted; public Action OperationEnded; @@ -128,20 +127,8 @@ namespace osu.Game.Screens.Edit.Compose.Components { AddRangeInternal(new[] { - new DragHandle - { - Anchor = Anchor.TopCentre, - HandleDrag = e => OnScaleY?.Invoke(e, Anchor.TopCentre), - OperationStarted = operationStarted, - OperationEnded = operationEnded - }, - new DragHandle - { - Anchor = Anchor.BottomCentre, - HandleDrag = e => OnScaleY?.Invoke(e, Anchor.BottomCentre), - OperationStarted = operationStarted, - OperationEnded = operationEnded - }, + createDragHandle(Anchor.TopCentre), + createDragHandle(Anchor.BottomCentre), }); } @@ -149,20 +136,8 @@ namespace osu.Game.Screens.Edit.Compose.Components { AddRangeInternal(new[] { - new DragHandle - { - Anchor = Anchor.CentreLeft, - HandleDrag = e => OnScaleX?.Invoke(e, Anchor.CentreLeft), - OperationStarted = operationStarted, - OperationEnded = operationEnded - }, - new DragHandle - { - Anchor = Anchor.CentreRight, - HandleDrag = e => OnScaleX?.Invoke(e, Anchor.CentreRight), - OperationStarted = operationStarted, - OperationEnded = operationEnded - }, + createDragHandle(Anchor.CentreLeft), + createDragHandle(Anchor.CentreRight), }); } @@ -170,52 +145,20 @@ namespace osu.Game.Screens.Edit.Compose.Components { AddRangeInternal(new[] { - new DragHandle - { - Anchor = Anchor.TopLeft, - HandleDrag = e => - { - OnScaleX?.Invoke(e, Anchor.TopLeft); - OnScaleY?.Invoke(e, Anchor.TopLeft); - }, - OperationStarted = operationStarted, - OperationEnded = operationEnded - }, - new DragHandle - { - Anchor = Anchor.TopRight, - HandleDrag = e => - { - OnScaleX?.Invoke(e, Anchor.TopRight); - OnScaleY?.Invoke(e, Anchor.TopRight); - }, - OperationStarted = operationStarted, - OperationEnded = operationEnded - }, - new DragHandle - { - Anchor = Anchor.BottomLeft, - HandleDrag = e => - { - OnScaleX?.Invoke(e, Anchor.BottomLeft); - OnScaleY?.Invoke(e, Anchor.BottomLeft); - }, - OperationStarted = operationStarted, - OperationEnded = operationEnded - }, - new DragHandle - { - Anchor = Anchor.BottomRight, - HandleDrag = e => - { - OnScaleX?.Invoke(e, Anchor.BottomRight); - OnScaleY?.Invoke(e, Anchor.BottomRight); - }, - OperationStarted = operationStarted, - OperationEnded = operationEnded - }, + createDragHandle(Anchor.TopLeft), + createDragHandle(Anchor.TopRight), + createDragHandle(Anchor.BottomLeft), + createDragHandle(Anchor.BottomRight), }); } + + ScaleDragHandle createDragHandle(Anchor anchor) => + new ScaleDragHandle(anchor) + { + HandleDrag = e => OnScale?.Invoke(e, anchor), + OperationStarted = operationStarted, + OperationEnded = operationEnded + }; } private int activeOperations; @@ -232,6 +175,14 @@ namespace osu.Game.Screens.Edit.Compose.Components OperationStarted?.Invoke(); } + private class ScaleDragHandle : DragHandle + { + public ScaleDragHandle(Anchor anchor) + { + Anchor = anchor; + } + } + private class RotationDragHandle : DragHandle { private SpriteIcon icon; diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs index 6cd503b580..5ed9bb65a8 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs @@ -1,4 +1,4 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// copyright (c) ppy pty ltd . licensed under the mit licence. // See the LICENCE file in the repository root for full licence text. using System; @@ -101,8 +101,7 @@ namespace osu.Game.Screens.Edit.Compose.Components OperationEnded = OnDragOperationEnded, OnRotation = e => HandleRotation(e.Delta.X), - OnScaleX = (e, anchor) => HandleScaleX(e.Delta.X, anchor), - OnScaleY = (e, anchor) => HandleScaleY(e.Delta.Y, anchor), + OnScale = (e, anchor) => HandleScale(e.Delta, anchor), }; /// @@ -145,20 +144,12 @@ namespace osu.Game.Screens.Edit.Compose.Components public virtual bool HandleRotation(float angle) => false; /// - /// Handles the selected s being scaled in a vertical direction. + /// Handles the selected s being scaled. /// - /// The delta scale to apply. + /// The delta scale to apply, in playfield local coordinates. /// The point of reference where the scale is originating from. /// Whether any s could be moved. - public virtual bool HandleScaleY(in float scale, Anchor anchor) => false; - - /// - /// Handles the selected s being scaled in a horizontal direction. - /// - /// The delta scale to apply. - /// The point of reference where the scale is originating from. - /// Whether any s could be moved. - public virtual bool HandleScaleX(in float scale, Anchor anchor) => false; + public virtual bool HandleScale(Vector2 scale, Anchor anchor) => false; public bool OnPressed(PlatformAction action) { From 7fad9ce34ac7a94847143b3a5ffeeb62fba5c1b9 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 30 Sep 2020 15:17:27 +0900 Subject: [PATCH 14/18] Simplify HandleScale method --- .../Edit/OsuSelectionHandler.cs | 38 +++++++++---------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs b/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs index 1f250f078d..6b4f13db35 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs @@ -43,13 +43,7 @@ namespace osu.Game.Rulesets.Osu.Edit public override bool HandleScale(Vector2 scale, Anchor reference) { - // cancel out scale in axes we don't care about (based on which drag handle was used). - if ((reference & Anchor.x1) > 0) scale.X = 0; - if ((reference & Anchor.y1) > 0) scale.Y = 0; - - // reverse the scale direction if dragging from top or left. - if ((reference & Anchor.x0) > 0) scale.X = -scale.X; - if ((reference & Anchor.y0) > 0) scale.Y = -scale.Y; + adjustScaleFromAnchor(ref scale, reference); var hitObjects = selectedMovableObjects; @@ -58,11 +52,11 @@ namespace osu.Game.Rulesets.Osu.Edit // is not looking to change the duration of the slider but expand the whole pattern. if (hitObjects.Length == 1 && hitObjects.First() is Slider slider) { - var quad = getSurroundingQuad(slider.Path.ControlPoints.Select(p => p.Position.Value)); - Vector2 delta = Vector2.One + new Vector2(scale.X / quad.Width, scale.Y / quad.Height); + Quad quad = getSurroundingQuad(slider.Path.ControlPoints.Select(p => p.Position.Value)); + Vector2 pathRelativeDeltaScale = new Vector2(1 + scale.X / quad.Width, 1 + scale.Y / quad.Height); foreach (var point in slider.Path.ControlPoints) - point.Position.Value *= delta; + point.Position.Value *= pathRelativeDeltaScale; } else { @@ -72,23 +66,29 @@ namespace osu.Game.Rulesets.Osu.Edit Quad quad = getSurroundingQuad(hitObjects); - Vector2 minPosition = quad.TopLeft; - - Vector2 size = quad.Size; - Vector2 newSize = size + scale; - foreach (var h in hitObjects) { - if (scale.X != 1) - h.Position = new Vector2(minPosition.X + (h.X - minPosition.X) / size.X * newSize.X, h.Y); - if (scale.Y != 1) - h.Position = new Vector2(h.X, minPosition.Y + (h.Y - minPosition.Y) / size.Y * newSize.Y); + h.Position = new Vector2( + quad.TopLeft.X + (h.X - quad.TopLeft.X) / quad.Width * (quad.Width + scale.X), + quad.TopLeft.Y + (h.Y - quad.TopLeft.Y) / quad.Height * (quad.Height + scale.Y) + ); } } return true; } + private static void adjustScaleFromAnchor(ref Vector2 scale, Anchor reference) + { + // cancel out scale in axes we don't care about (based on which drag handle was used). + if ((reference & Anchor.x1) > 0) scale.X = 0; + if ((reference & Anchor.y1) > 0) scale.Y = 0; + + // reverse the scale direction if dragging from top or left. + if ((reference & Anchor.x0) > 0) scale.X = -scale.X; + if ((reference & Anchor.y0) > 0) scale.Y = -scale.Y; + } + public override bool HandleRotation(float delta) { var hitObjects = selectedMovableObjects; From ae9e884a483fd5940a429a4b924c5d1cb059653e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 30 Sep 2020 15:35:25 +0900 Subject: [PATCH 15/18] Fix header casing --- osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs index 5ed9bb65a8..ee094c6246 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs @@ -1,4 +1,4 @@ -// copyright (c) ppy pty ltd . licensed under the mit licence. +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. using System; From b1f2bdd579ee4a6b91d3f5b3b78e62ea204a97fa Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 1 Oct 2020 13:47:49 +0900 Subject: [PATCH 16/18] Add missing xmldoc --- .../Edit/Compose/Components/ComposeSelectionBox.cs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/osu.Game/Screens/Edit/Compose/Components/ComposeSelectionBox.cs b/osu.Game/Screens/Edit/Compose/Components/ComposeSelectionBox.cs index 424705c755..530c6007cf 100644 --- a/osu.Game/Screens/Edit/Compose/Components/ComposeSelectionBox.cs +++ b/osu.Game/Screens/Edit/Compose/Components/ComposeSelectionBox.cs @@ -24,6 +24,9 @@ namespace osu.Game.Screens.Edit.Compose.Components private bool canRotate; + /// + /// Whether rotation support should be enabled. + /// public bool CanRotate { get => canRotate; @@ -36,6 +39,9 @@ namespace osu.Game.Screens.Edit.Compose.Components private bool canScaleX; + /// + /// Whether vertical scale support should be enabled. + /// public bool CanScaleX { get => canScaleX; @@ -48,6 +54,9 @@ namespace osu.Game.Screens.Edit.Compose.Components private bool canScaleY; + /// + /// Whether horizontal scale support should be enabled. + /// public bool CanScaleY { get => canScaleY; From 538973e3942ea8c589e5e98f35890605cb69f5b7 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 1 Oct 2020 17:06:05 +0900 Subject: [PATCH 17/18] Use float methods for math operations --- osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs b/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs index 6b4f13db35..daf4a0102b 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs @@ -183,8 +183,8 @@ namespace osu.Game.Rulesets.Osu.Edit point.Y -= origin.Y; Vector2 ret; - ret.X = (float)(point.X * Math.Cos(MathUtils.DegreesToRadians(angle)) + point.Y * Math.Sin(angle / 180f * Math.PI)); - ret.Y = (float)(point.X * -Math.Sin(MathUtils.DegreesToRadians(angle)) + point.Y * Math.Cos(angle / 180f * Math.PI)); + ret.X = point.X * MathF.Cos(MathUtils.DegreesToRadians(angle)) + point.Y * MathF.Sin(angle / 180f * MathF.PI); + ret.Y = point.X * -MathF.Sin(MathUtils.DegreesToRadians(angle)) + point.Y * MathF.Cos(angle / 180f * MathF.PI); ret.X += origin.X; ret.Y += origin.Y; From b6dc8bb2d3f16fa41934187fe9957865db1d2f20 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 1 Oct 2020 18:10:05 +0900 Subject: [PATCH 18/18] Fix remaining manual degree-to-radian conversions --- osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs b/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs index daf4a0102b..a0f70ce408 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs @@ -183,8 +183,8 @@ namespace osu.Game.Rulesets.Osu.Edit point.Y -= origin.Y; Vector2 ret; - ret.X = point.X * MathF.Cos(MathUtils.DegreesToRadians(angle)) + point.Y * MathF.Sin(angle / 180f * MathF.PI); - ret.Y = point.X * -MathF.Sin(MathUtils.DegreesToRadians(angle)) + point.Y * MathF.Cos(angle / 180f * MathF.PI); + ret.X = point.X * MathF.Cos(MathUtils.DegreesToRadians(angle)) + point.Y * MathF.Sin(MathUtils.DegreesToRadians(angle)); + ret.Y = point.X * -MathF.Sin(MathUtils.DegreesToRadians(angle)) + point.Y * MathF.Cos(MathUtils.DegreesToRadians(angle)); ret.X += origin.X; ret.Y += origin.Y;