From d2798c7a1ca1d9c79438fd8319662b5aba098c4e Mon Sep 17 00:00:00 2001 From: Wleter Date: Sun, 20 Aug 2023 17:55:19 +0200 Subject: [PATCH 1/9] don't allow negative scaling --- osu.Game/Overlays/SkinEditor/SkinSelectionHandler.cs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/osu.Game/Overlays/SkinEditor/SkinSelectionHandler.cs b/osu.Game/Overlays/SkinEditor/SkinSelectionHandler.cs index 72216f040e..a952cf3035 100644 --- a/osu.Game/Overlays/SkinEditor/SkinSelectionHandler.cs +++ b/osu.Game/Overlays/SkinEditor/SkinSelectionHandler.cs @@ -71,6 +71,9 @@ namespace osu.Game.Overlays.SkinEditor scale.Y = scale.X / selectionRect.Width * selectionRect.Height; } + // If scaling reverses the selection, don't scale. + if (adjustedRect.Width + scale.X < 0 || adjustedRect.Height + scale.Y < 0) return true; + if (anchor.HasFlagFast(Anchor.x0)) adjustedRect.X -= scale.X; if (anchor.HasFlagFast(Anchor.y0)) adjustedRect.Y -= scale.Y; @@ -79,8 +82,8 @@ namespace osu.Game.Overlays.SkinEditor // scale adjust applied to each individual item should match that of the quad itself. var scaledDelta = new Vector2( - MathF.Max(adjustedRect.Width / selectionRect.Width, 0), - MathF.Max(adjustedRect.Height / selectionRect.Height, 0) + adjustedRect.Width / selectionRect.Width, + adjustedRect.Height / selectionRect.Height ); foreach (var b in SelectedBlueprints) From 9f4f81c150895ddc08bb4680bbcbd981a03f9d0d Mon Sep 17 00:00:00 2001 From: Wleter Date: Mon, 21 Aug 2023 19:36:11 +0200 Subject: [PATCH 2/9] accumulating negative scaling --- .../SkinEditor/SkinSelectionHandler.cs | 29 +++++++++++++++++-- 1 file changed, 27 insertions(+), 2 deletions(-) diff --git a/osu.Game/Overlays/SkinEditor/SkinSelectionHandler.cs b/osu.Game/Overlays/SkinEditor/SkinSelectionHandler.cs index a952cf3035..c90a1d8edf 100644 --- a/osu.Game/Overlays/SkinEditor/SkinSelectionHandler.cs +++ b/osu.Game/Overlays/SkinEditor/SkinSelectionHandler.cs @@ -31,6 +31,8 @@ namespace osu.Game.Overlays.SkinEditor UpdatePosition = updateDrawablePosition }; + private float accumulatedNegativeScaling; + public override bool HandleScale(Vector2 scale, Anchor anchor) { // convert scale to screen space @@ -71,8 +73,25 @@ namespace osu.Game.Overlays.SkinEditor scale.Y = scale.X / selectionRect.Width * selectionRect.Height; } - // If scaling reverses the selection, don't scale. - if (adjustedRect.Width + scale.X < 0 || adjustedRect.Height + scale.Y < 0) return true; + // If scaling reverses the selection, don't scale and accumulate the amount of scaling. + if (adjustedRect.Width + scale.X < 0 || adjustedRect.Height + scale.Y < 0) + { + accumulatedNegativeScaling += scale.Length; // - new Vector2(selectionRect.Width, selectionRect.Height).Length; + + return true; + } + + // Compensate for accumulated negative scaling. + if (Precision.AlmostBigger(accumulatedNegativeScaling, 0) && !Precision.AlmostEquals(accumulatedNegativeScaling, 0)) + { + float length = scale.Length; + accumulatedNegativeScaling -= length; + + // If the accumulated negative scaling is still positive, don't scale. + if (Precision.AlmostBigger(accumulatedNegativeScaling, 0)) return true; + scale *= Math.Abs(accumulatedNegativeScaling) / length; + accumulatedNegativeScaling = 0; + } if (anchor.HasFlagFast(Anchor.x0)) adjustedRect.X -= scale.X; if (anchor.HasFlagFast(Anchor.y0)) adjustedRect.Y -= scale.Y; @@ -150,6 +169,12 @@ namespace osu.Game.Overlays.SkinEditor public static void ApplyClosestAnchor(Drawable drawable) => applyAnchor(drawable, getClosestAnchor(drawable)); + protected override void OnOperationEnded() + { + base.OnOperationEnded(); + accumulatedNegativeScaling = 0; + } + protected override void OnSelectionChanged() { base.OnSelectionChanged(); From 07e126241da902fcf7471e28576c5204fc8b826d Mon Sep 17 00:00:00 2001 From: Wleter Date: Mon, 28 Aug 2023 16:41:55 +0200 Subject: [PATCH 3/9] working negative scaling --- .../SkinEditor/SkinSelectionHandler.cs | 41 ++++++------------- .../Edit/Compose/Components/SelectionBox.cs | 2 + .../SelectionBoxDragHandleContainer.cs | 12 ++++++ 3 files changed, 27 insertions(+), 28 deletions(-) diff --git a/osu.Game/Overlays/SkinEditor/SkinSelectionHandler.cs b/osu.Game/Overlays/SkinEditor/SkinSelectionHandler.cs index c90a1d8edf..ae0a3d0635 100644 --- a/osu.Game/Overlays/SkinEditor/SkinSelectionHandler.cs +++ b/osu.Game/Overlays/SkinEditor/SkinSelectionHandler.cs @@ -31,8 +31,6 @@ namespace osu.Game.Overlays.SkinEditor UpdatePosition = updateDrawablePosition }; - private float accumulatedNegativeScaling; - public override bool HandleScale(Vector2 scale, Anchor anchor) { // convert scale to screen space @@ -73,32 +71,25 @@ namespace osu.Game.Overlays.SkinEditor scale.Y = scale.X / selectionRect.Width * selectionRect.Height; } - // If scaling reverses the selection, don't scale and accumulate the amount of scaling. - if (adjustedRect.Width + scale.X < 0 || adjustedRect.Height + scale.Y < 0) - { - accumulatedNegativeScaling += scale.Length; // - new Vector2(selectionRect.Width, selectionRect.Height).Length; - - return true; - } - - // Compensate for accumulated negative scaling. - if (Precision.AlmostBigger(accumulatedNegativeScaling, 0) && !Precision.AlmostEquals(accumulatedNegativeScaling, 0)) - { - float length = scale.Length; - accumulatedNegativeScaling -= length; - - // If the accumulated negative scaling is still positive, don't scale. - if (Precision.AlmostBigger(accumulatedNegativeScaling, 0)) return true; - scale *= Math.Abs(accumulatedNegativeScaling) / length; - accumulatedNegativeScaling = 0; - } - if (anchor.HasFlagFast(Anchor.x0)) adjustedRect.X -= scale.X; if (anchor.HasFlagFast(Anchor.y0)) adjustedRect.Y -= scale.Y; adjustedRect.Width += scale.X; adjustedRect.Height += scale.Y; + if (adjustedRect.Width < 0) + { + SelectionBox.ScaleHandlesFlip(Direction.Horizontal); + HandleFlip(Direction.Horizontal, false); + } + if (adjustedRect.Height < 0) + { + SelectionBox.ScaleHandlesFlip(Direction.Vertical); + HandleFlip(Direction.Vertical, false); + } + if (adjustedRect.Width < 0 || adjustedRect.Height < 0) + return true; + // scale adjust applied to each individual item should match that of the quad itself. var scaledDelta = new Vector2( adjustedRect.Width / selectionRect.Width, @@ -169,12 +160,6 @@ namespace osu.Game.Overlays.SkinEditor public static void ApplyClosestAnchor(Drawable drawable) => applyAnchor(drawable, getClosestAnchor(drawable)); - protected override void OnOperationEnded() - { - base.OnOperationEnded(); - accumulatedNegativeScaling = 0; - } - protected override void OnSelectionChanged() { base.OnSelectionChanged(); diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionBox.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionBox.cs index 876e8ccbe9..a261b635b3 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionBox.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionBox.cs @@ -307,6 +307,8 @@ namespace osu.Game.Screens.Edit.Compose.Components return button; } + public void ScaleHandlesFlip(Direction direction) => dragHandles.ScaleHandlesFlip(direction); + private void addScaleHandle(Anchor anchor) { var handle = new SelectionBoxScaleHandle diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionBoxDragHandleContainer.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionBoxDragHandleContainer.cs index 5c87271493..4fd2e9aba9 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionBoxDragHandleContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionBoxDragHandleContainer.cs @@ -7,6 +7,7 @@ using System.Collections.Generic; using System.Linq; using JetBrains.Annotations; using osu.Framework.Allocation; +using osu.Framework.Extensions.EnumExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -69,6 +70,17 @@ namespace osu.Game.Screens.Edit.Compose.Components allDragHandles.Add(handle); } + public void ScaleHandlesFlip(Direction direction) + { + foreach (var handle in scaleHandles) + { + if (direction == Direction.Horizontal && !handle.Anchor.HasFlagFast(Anchor.x1)) + handle.Anchor ^= Anchor.x0 | Anchor.x2; + if (direction == Direction.Vertical && !handle.Anchor.HasFlagFast(Anchor.y1)) + handle.Anchor ^= Anchor.y0 | Anchor.y2; + } + } + private SelectionBoxRotationHandle displayedRotationHandle; private SelectionBoxDragHandle activeHandle; From 3c575516ab2f642431e0e4db16f6303d5b3c7e42 Mon Sep 17 00:00:00 2001 From: Wleter Date: Tue, 29 Aug 2023 17:06:23 +0200 Subject: [PATCH 4/9] add correct scaling for 90 degrees rotation --- osu.Game/Overlays/SkinEditor/SkinSelectionHandler.cs | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/osu.Game/Overlays/SkinEditor/SkinSelectionHandler.cs b/osu.Game/Overlays/SkinEditor/SkinSelectionHandler.cs index ae0a3d0635..ff6b52ca4d 100644 --- a/osu.Game/Overlays/SkinEditor/SkinSelectionHandler.cs +++ b/osu.Game/Overlays/SkinEditor/SkinSelectionHandler.cs @@ -48,10 +48,6 @@ namespace osu.Game.Overlays.SkinEditor // copy to mutate, as we will need to compare to the original later on. var adjustedRect = selectionRect; - // first, remove any scale axis we are not interested in. - if (anchor.HasFlagFast(Anchor.x1)) scale.X = 0; - if (anchor.HasFlagFast(Anchor.y1)) scale.Y = 0; - // for now aspect lock scale adjustments that occur at corners.. if (!anchor.HasFlagFast(Anchor.x1) && !anchor.HasFlagFast(Anchor.y1)) { @@ -61,7 +57,7 @@ namespace osu.Game.Overlays.SkinEditor } // ..or if any of the selection have been rotated. // this is to avoid requiring skew logic (which would likely not be the user's expected transform anyway). - else if (SelectedBlueprints.Any(b => !Precision.AlmostEquals(((Drawable)b.Item).Rotation, 0))) + else if (SelectedBlueprints.Any(b => !Precision.AlmostEquals(((Drawable)b.Item).Rotation % 90, 0))) { if (anchor.HasFlagFast(Anchor.x1)) // if dragging from the horizontal centre, only a vertical component is available. @@ -115,6 +111,11 @@ namespace osu.Game.Overlays.SkinEditor ); updateDrawablePosition(drawableItem, newPositionInAdjusted); + + if (Precision.AlmostEquals(MathF.Abs(drawableItem.Rotation) % 180, 90)) + { + scaledDelta = new Vector2(scaledDelta.Y, scaledDelta.X); + } drawableItem.Scale *= scaledDelta; } From d56ab0fe9af86a96ebf2fe3047d0b059b29aedbb Mon Sep 17 00:00:00 2001 From: Wleter Date: Tue, 29 Aug 2023 17:25:52 +0200 Subject: [PATCH 5/9] change names --- .../SkinEditor/SkinSelectionHandler.cs | 25 ++++++++++--------- .../Edit/Compose/Components/SelectionBox.cs | 2 +- .../SelectionBoxDragHandleContainer.cs | 2 +- 3 files changed, 15 insertions(+), 14 deletions(-) diff --git a/osu.Game/Overlays/SkinEditor/SkinSelectionHandler.cs b/osu.Game/Overlays/SkinEditor/SkinSelectionHandler.cs index ff6b52ca4d..afa592dfba 100644 --- a/osu.Game/Overlays/SkinEditor/SkinSelectionHandler.cs +++ b/osu.Game/Overlays/SkinEditor/SkinSelectionHandler.cs @@ -73,18 +73,20 @@ namespace osu.Game.Overlays.SkinEditor adjustedRect.Width += scale.X; adjustedRect.Height += scale.Y; - if (adjustedRect.Width < 0) - { - SelectionBox.ScaleHandlesFlip(Direction.Horizontal); - HandleFlip(Direction.Horizontal, false); - } - if (adjustedRect.Height < 0) - { - SelectionBox.ScaleHandlesFlip(Direction.Vertical); - HandleFlip(Direction.Vertical, false); - } if (adjustedRect.Width < 0 || adjustedRect.Height < 0) + { + if (adjustedRect.Width < 0) + { + SelectionBox.FlipScaleHandles(Direction.Horizontal); + HandleFlip(Direction.Horizontal, false); + } + if (adjustedRect.Height < 0) + { + SelectionBox.FlipScaleHandles(Direction.Vertical); + HandleFlip(Direction.Vertical, false); + } return true; + } // scale adjust applied to each individual item should match that of the quad itself. var scaledDelta = new Vector2( @@ -113,9 +115,8 @@ namespace osu.Game.Overlays.SkinEditor updateDrawablePosition(drawableItem, newPositionInAdjusted); if (Precision.AlmostEquals(MathF.Abs(drawableItem.Rotation) % 180, 90)) - { scaledDelta = new Vector2(scaledDelta.Y, scaledDelta.X); - } + drawableItem.Scale *= scaledDelta; } diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionBox.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionBox.cs index a261b635b3..bbf9ea8c3c 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionBox.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionBox.cs @@ -307,7 +307,7 @@ namespace osu.Game.Screens.Edit.Compose.Components return button; } - public void ScaleHandlesFlip(Direction direction) => dragHandles.ScaleHandlesFlip(direction); + public void FlipScaleHandles(Direction direction) => dragHandles.FlipScaleHandles(direction); private void addScaleHandle(Anchor anchor) { diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionBoxDragHandleContainer.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionBoxDragHandleContainer.cs index 4fd2e9aba9..e7f69b7b37 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionBoxDragHandleContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionBoxDragHandleContainer.cs @@ -70,7 +70,7 @@ namespace osu.Game.Screens.Edit.Compose.Components allDragHandles.Add(handle); } - public void ScaleHandlesFlip(Direction direction) + public void FlipScaleHandles(Direction direction) { foreach (var handle in scaleHandles) { From 586ce6e8d3e6d9ebdbb629d3dff76a6539739cd4 Mon Sep 17 00:00:00 2001 From: Wleter Date: Tue, 29 Aug 2023 17:47:42 +0200 Subject: [PATCH 6/9] fix multiple selected --- osu.Game/Overlays/SkinEditor/SkinSelectionHandler.cs | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/osu.Game/Overlays/SkinEditor/SkinSelectionHandler.cs b/osu.Game/Overlays/SkinEditor/SkinSelectionHandler.cs index afa592dfba..971ea3d266 100644 --- a/osu.Game/Overlays/SkinEditor/SkinSelectionHandler.cs +++ b/osu.Game/Overlays/SkinEditor/SkinSelectionHandler.cs @@ -73,14 +73,14 @@ namespace osu.Game.Overlays.SkinEditor adjustedRect.Width += scale.X; adjustedRect.Height += scale.Y; - if (adjustedRect.Width < 0 || adjustedRect.Height < 0) + if (adjustedRect.Width <= 0 || adjustedRect.Height <= 0) { - if (adjustedRect.Width < 0) + if (adjustedRect.Width <= 0) { SelectionBox.FlipScaleHandles(Direction.Horizontal); HandleFlip(Direction.Horizontal, false); } - if (adjustedRect.Height < 0) + if (adjustedRect.Height <= 0) { SelectionBox.FlipScaleHandles(Direction.Vertical); HandleFlip(Direction.Vertical, false); @@ -114,10 +114,11 @@ namespace osu.Game.Overlays.SkinEditor updateDrawablePosition(drawableItem, newPositionInAdjusted); + var currentScaledDelta = scaledDelta; if (Precision.AlmostEquals(MathF.Abs(drawableItem.Rotation) % 180, 90)) - scaledDelta = new Vector2(scaledDelta.Y, scaledDelta.X); + currentScaledDelta = new Vector2(scaledDelta.Y, scaledDelta.X); - drawableItem.Scale *= scaledDelta; + drawableItem.Scale *= currentScaledDelta; } return true; From ce1bc7156727ec30c059ab44dab689c4c6d990cc Mon Sep 17 00:00:00 2001 From: Wleter Date: Tue, 29 Aug 2023 18:41:56 +0200 Subject: [PATCH 7/9] formatting --- osu.Game/Overlays/SkinEditor/SkinSelectionHandler.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game/Overlays/SkinEditor/SkinSelectionHandler.cs b/osu.Game/Overlays/SkinEditor/SkinSelectionHandler.cs index 971ea3d266..2acddff0e4 100644 --- a/osu.Game/Overlays/SkinEditor/SkinSelectionHandler.cs +++ b/osu.Game/Overlays/SkinEditor/SkinSelectionHandler.cs @@ -80,11 +80,13 @@ namespace osu.Game.Overlays.SkinEditor SelectionBox.FlipScaleHandles(Direction.Horizontal); HandleFlip(Direction.Horizontal, false); } + if (adjustedRect.Height <= 0) { SelectionBox.FlipScaleHandles(Direction.Vertical); HandleFlip(Direction.Vertical, false); } + return true; } From f277909470749c7ecadae849f4b25e30b1bf0260 Mon Sep 17 00:00:00 2001 From: Wleter Date: Wed, 30 Aug 2023 09:16:16 +0200 Subject: [PATCH 8/9] maintain rotated selection's centre position --- osu.Game/Overlays/SkinEditor/SkinSelectionHandler.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/osu.Game/Overlays/SkinEditor/SkinSelectionHandler.cs b/osu.Game/Overlays/SkinEditor/SkinSelectionHandler.cs index 2acddff0e4..ff53095e22 100644 --- a/osu.Game/Overlays/SkinEditor/SkinSelectionHandler.cs +++ b/osu.Game/Overlays/SkinEditor/SkinSelectionHandler.cs @@ -47,6 +47,7 @@ namespace osu.Game.Overlays.SkinEditor // copy to mutate, as we will need to compare to the original later on. var adjustedRect = selectionRect; + bool isRotated = false; // for now aspect lock scale adjustments that occur at corners.. if (!anchor.HasFlagFast(Anchor.x1) && !anchor.HasFlagFast(Anchor.y1)) @@ -59,6 +60,7 @@ namespace osu.Game.Overlays.SkinEditor // this is to avoid requiring skew logic (which would likely not be the user's expected transform anyway). else if (SelectedBlueprints.Any(b => !Precision.AlmostEquals(((Drawable)b.Item).Rotation % 90, 0))) { + isRotated = true; if (anchor.HasFlagFast(Anchor.x1)) // if dragging from the horizontal centre, only a vertical component is available. scale.X = scale.Y / selectionRect.Height * selectionRect.Width; @@ -70,6 +72,10 @@ namespace osu.Game.Overlays.SkinEditor if (anchor.HasFlagFast(Anchor.x0)) adjustedRect.X -= scale.X; if (anchor.HasFlagFast(Anchor.y0)) adjustedRect.Y -= scale.Y; + // Maintain the selection's centre position if dragging from the centre anchors and selection is rotated. + if (isRotated && anchor.HasFlagFast(Anchor.x1)) adjustedRect.X -= scale.X / 2; + if (isRotated && anchor.HasFlagFast(Anchor.y1)) adjustedRect.Y -= scale.Y / 2; + adjustedRect.Width += scale.X; adjustedRect.Height += scale.Y; From fc4069f794e91e6ea1eb4a4b28c334b12e301a85 Mon Sep 17 00:00:00 2001 From: Wleter Date: Fri, 1 Sep 2023 13:01:51 +0200 Subject: [PATCH 9/9] let SelectionBox perform flip with scale handles --- .../SkinEditor/SkinSelectionHandler.cs | 14 ++++--------- .../Edit/Compose/Components/SelectionBox.cs | 20 ++++++++++++++++++- 2 files changed, 23 insertions(+), 11 deletions(-) diff --git a/osu.Game/Overlays/SkinEditor/SkinSelectionHandler.cs b/osu.Game/Overlays/SkinEditor/SkinSelectionHandler.cs index ff53095e22..b30351f61b 100644 --- a/osu.Game/Overlays/SkinEditor/SkinSelectionHandler.cs +++ b/osu.Game/Overlays/SkinEditor/SkinSelectionHandler.cs @@ -81,18 +81,12 @@ namespace osu.Game.Overlays.SkinEditor if (adjustedRect.Width <= 0 || adjustedRect.Height <= 0) { - if (adjustedRect.Width <= 0) - { - SelectionBox.FlipScaleHandles(Direction.Horizontal); - HandleFlip(Direction.Horizontal, false); - } + Axes toFlip = Axes.None; - if (adjustedRect.Height <= 0) - { - SelectionBox.FlipScaleHandles(Direction.Vertical); - HandleFlip(Direction.Vertical, false); - } + if (adjustedRect.Width <= 0) toFlip |= Axes.X; + if (adjustedRect.Height <= 0) toFlip |= Axes.Y; + SelectionBox.PerformFlipFromScaleHandles(toFlip); return true; } diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionBox.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionBox.cs index bbf9ea8c3c..0c19f6c62e 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionBox.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionBox.cs @@ -4,6 +4,7 @@ using System; using osu.Framework.Allocation; using osu.Framework.Bindables; +using osu.Framework.Extensions.EnumExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; @@ -307,7 +308,24 @@ namespace osu.Game.Screens.Edit.Compose.Components return button; } - public void FlipScaleHandles(Direction direction) => dragHandles.FlipScaleHandles(direction); + /// + /// This method should be called when a selection needs to be flipped + /// because of an ongoing scale handle drag that would otherwise cause width or height to go negative. + /// + public void PerformFlipFromScaleHandles(Axes axes) + { + if (axes.HasFlagFast(Axes.X)) + { + dragHandles.FlipScaleHandles(Direction.Horizontal); + OnFlip?.Invoke(Direction.Horizontal, false); + } + + if (axes.HasFlagFast(Axes.Y)) + { + dragHandles.FlipScaleHandles(Direction.Vertical); + OnFlip?.Invoke(Direction.Vertical, false); + } + } private void addScaleHandle(Anchor anchor) {