diff --git a/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs b/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs
index aaf3517c9c..8cb86bc108 100644
--- a/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs
+++ b/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs
@@ -225,26 +225,10 @@ namespace osu.Game.Rulesets.Osu.Edit
private void scaleHitObjects(OsuHitObject[] hitObjects, Anchor reference, Vector2 scale)
{
scale = getClampedScale(hitObjects, reference, scale);
-
- // move the selection before scaling if dragging from top or left anchors.
- float xOffset = ((reference & Anchor.x0) > 0) ? -scale.X : 0;
- float yOffset = ((reference & Anchor.y0) > 0) ? -scale.Y : 0;
-
Quad selectionQuad = getSurroundingQuad(hitObjects);
foreach (var h in hitObjects)
- {
- var newPosition = h.Position;
-
- // guard against no-ops and NaN.
- if (scale.X != 0 && selectionQuad.Width > 0)
- newPosition.X = selectionQuad.TopLeft.X + xOffset + (h.X - selectionQuad.TopLeft.X) / selectionQuad.Width * (selectionQuad.Width + scale.X);
-
- if (scale.Y != 0 && selectionQuad.Height > 0)
- newPosition.Y = selectionQuad.TopLeft.Y + yOffset + (h.Y - selectionQuad.TopLeft.Y) / selectionQuad.Height * (selectionQuad.Height + scale.Y);
-
- h.Position = newPosition;
- }
+ h.Position = GetScaledPosition(reference, scale, selectionQuad, h.Position);
}
private (bool X, bool Y) isQuadInBounds(Quad quad)
diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs
index bfd5ab7afa..26328b4dc7 100644
--- a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs
+++ b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs
@@ -375,6 +375,26 @@ namespace osu.Game.Screens.Edit.Compose.Components
return position;
}
+ ///
+ /// Given a scale vector, a surrounding quad for all selected objects, and a position,
+ /// will return the scaled position in screen space coordinates.
+ ///
+ protected static Vector2 GetScaledPosition(Anchor reference, Vector2 scale, Quad selectionQuad, Vector2 position)
+ {
+ // adjust the direction of scale depending on which side the user is dragging.
+ float xOffset = ((reference & Anchor.x0) > 0) ? -scale.X : 0;
+ float yOffset = ((reference & Anchor.y0) > 0) ? -scale.Y : 0;
+
+ // guard against no-ops and NaN.
+ if (scale.X != 0 && selectionQuad.Width > 0)
+ position.X = selectionQuad.TopLeft.X + xOffset + (position.X - selectionQuad.TopLeft.X) / selectionQuad.Width * (selectionQuad.Width + scale.X);
+
+ if (scale.Y != 0 && selectionQuad.Height > 0)
+ position.Y = selectionQuad.TopLeft.Y + yOffset + (position.Y - selectionQuad.TopLeft.Y) / selectionQuad.Height * (selectionQuad.Height + scale.Y);
+
+ return position;
+ }
+
///
/// Returns a quad surrounding the provided points.
///
diff --git a/osu.Game/Skinning/Editor/SkinSelectionHandler.cs b/osu.Game/Skinning/Editor/SkinSelectionHandler.cs
index 2cfb9d0f96..fcf1c44e69 100644
--- a/osu.Game/Skinning/Editor/SkinSelectionHandler.cs
+++ b/osu.Game/Skinning/Editor/SkinSelectionHandler.cs
@@ -8,6 +8,7 @@ using osu.Framework.Allocation;
using osu.Framework.Extensions.EnumExtensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.UserInterface;
+using osu.Framework.Utils;
using osu.Game.Extensions;
using osu.Game.Graphics.UserInterface;
using osu.Game.Rulesets.Edit;
@@ -32,11 +33,72 @@ namespace osu.Game.Skinning.Editor
public override bool HandleScale(Vector2 scale, Anchor anchor)
{
+ // convert scale to screen space
+ scale = ToScreenSpace(scale) - ToScreenSpace(Vector2.Zero);
+
adjustScaleFromAnchor(ref scale, anchor);
- foreach (var c in SelectedBlueprints)
- // TODO: this is temporary and will be fixed with a separate refactor of selection transform logic.
- ((Drawable)c.Item).Scale += scale * 0.02f;
+ // the selection quad is always upright, so use an AABB rect to make mutating the values easier.
+ var selectionRect = GetSurroundingQuad(SelectedBlueprints.SelectMany(b =>
+ b.Item.ScreenSpaceDrawQuad.GetVertices().ToArray())).AABBFloat;
+
+ // 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;
+
+ bool shouldAspectLock =
+ // for now aspect lock scale adjustments that occur at corners..
+ (!anchor.HasFlagFast(Anchor.x1) && !anchor.HasFlagFast(Anchor.y1))
+ // ..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).
+ || SelectedBlueprints.Any(b => !Precision.AlmostEquals(((Drawable)b.Item).Rotation, 0));
+
+ if (shouldAspectLock)
+ {
+ 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;
+ else
+ // in all other cases (arbitrarily) use the horizontal component for aspect lock.
+ scale.Y = scale.X / selectionRect.Width * selectionRect.Height;
+ }
+
+ 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;
+
+ // scale adjust applied to each individual item should match that of the quad itself.
+ var scaledDelta = new Vector2(
+ adjustedRect.Width / selectionRect.Width,
+ adjustedRect.Height / selectionRect.Height
+ );
+
+ foreach (var b in SelectedBlueprints)
+ {
+ var drawableItem = (Drawable)b.Item;
+
+ // each drawable's relative position should be maintained in the scaled quad.
+ var screenPosition = b.ScreenSpaceSelectionPoint;
+
+ var relativePositionInOriginal =
+ new Vector2(
+ (screenPosition.X - selectionRect.TopLeft.X) / selectionRect.Width,
+ (screenPosition.Y - selectionRect.TopLeft.Y) / selectionRect.Height
+ );
+
+ var newPositionInAdjusted = new Vector2(
+ adjustedRect.TopLeft.X + adjustedRect.Width * relativePositionInOriginal.X,
+ adjustedRect.TopLeft.Y + adjustedRect.Height * relativePositionInOriginal.Y
+ );
+
+ drawableItem.Position = drawableItem.Parent.ToLocalSpace(newPositionInAdjusted) - drawableItem.AnchorPosition;
+ drawableItem.Scale *= scaledDelta;
+ }
return true;
}
@@ -158,13 +220,6 @@ namespace osu.Game.Skinning.Editor
// 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;
-
- // for now aspect lock scale adjustments that occur at corners.
- if (!reference.HasFlagFast(Anchor.x1) && !reference.HasFlagFast(Anchor.y1))
- {
- // TODO: temporary implementation - only dragging the corner handles across the X axis changes size.
- scale.Y = scale.X;
- }
}
}
}