diff --git a/osu.Game.Rulesets.Osu/Edit/OsuSelectionScaleHandler.cs b/osu.Game.Rulesets.Osu/Edit/OsuSelectionScaleHandler.cs
index af03c4d925..331e8de3f1 100644
--- a/osu.Game.Rulesets.Osu/Edit/OsuSelectionScaleHandler.cs
+++ b/osu.Game.Rulesets.Osu/Edit/OsuSelectionScaleHandler.cs
@@ -67,7 +67,7 @@ public override void Begin()
objectsInScale = selectedMovableObjects.ToDictionary(ho => ho, ho => new OriginalHitObjectState(ho));
OriginalSurroundingQuad = objectsInScale.Count == 1 && objectsInScale.First().Key is Slider slider
- ? GeometryUtils.GetSurroundingQuad(slider.Path.ControlPoints.Select(p => p.Position))
+ ? GeometryUtils.GetSurroundingQuad(slider.Path.ControlPoints.Select(p => slider.Position + p.Position))
: GeometryUtils.GetSurroundingQuad(objectsInScale.Keys);
defaultOrigin = OriginalSurroundingQuad.Value.Centre;
}
@@ -92,7 +92,7 @@ public override void Update(Vector2 scale, Vector2? origin = null, Axes adjustAx
}
else
{
- scale = getClampedScale(OriginalSurroundingQuad.Value, actualOrigin, scale);
+ scale = GetClampedScale(scale, actualOrigin);
foreach (var (ho, originalState) in objectsInScale)
{
@@ -155,30 +155,33 @@ private void scaleSlider(Slider slider, Vector2 scale, Vector2[] originalPathPos
return (xInBounds, yInBounds);
}
- ///
- /// Clamp scale for multi-object-scaling where selection does not exceed playfield bounds or flip.
- ///
- /// The quad surrounding the hitobjects
- /// The origin from which the scale operation is performed
- /// The scale to be clamped
- /// The clamped scale vector
- private Vector2 getClampedScale(Quad selectionQuad, Vector2 origin, Vector2 scale)
+ public override Vector2 GetClampedScale(Vector2 scale, Vector2? origin = null)
{
//todo: this is not always correct for selections involving sliders. This approximation assumes each point is scaled independently, but sliderends move with the sliderhead.
+ if (objectsInScale == null)
+ return scale;
- var tl1 = Vector2.Divide(-origin, selectionQuad.TopLeft - origin);
- var tl2 = Vector2.Divide(OsuPlayfield.BASE_SIZE - origin, selectionQuad.TopLeft - origin);
- var br1 = Vector2.Divide(-origin, selectionQuad.BottomRight - origin);
- var br2 = Vector2.Divide(OsuPlayfield.BASE_SIZE - origin, selectionQuad.BottomRight - origin);
+ Debug.Assert(defaultOrigin != null && OriginalSurroundingQuad != null);
- if (!Precision.AlmostEquals(selectionQuad.TopLeft.X - origin.X, 0))
- scale.X = selectionQuad.TopLeft.X - origin.X < 0 ? MathHelper.Clamp(scale.X, tl2.X, tl1.X) : MathHelper.Clamp(scale.X, tl1.X, tl2.X);
- if (!Precision.AlmostEquals(selectionQuad.TopLeft.Y - origin.Y, 0))
- scale.Y = selectionQuad.TopLeft.Y - origin.Y < 0 ? MathHelper.Clamp(scale.Y, tl2.Y, tl1.Y) : MathHelper.Clamp(scale.Y, tl1.Y, tl2.Y);
- if (!Precision.AlmostEquals(selectionQuad.BottomRight.X - origin.X, 0))
- scale.X = selectionQuad.BottomRight.X - origin.X < 0 ? MathHelper.Clamp(scale.X, br2.X, br1.X) : MathHelper.Clamp(scale.X, br1.X, br2.X);
- if (!Precision.AlmostEquals(selectionQuad.BottomRight.Y - origin.Y, 0))
- scale.Y = selectionQuad.BottomRight.Y - origin.Y < 0 ? MathHelper.Clamp(scale.Y, br2.Y, br1.Y) : MathHelper.Clamp(scale.Y, br1.Y, br2.Y);
+ if (objectsInScale.Count == 1 && objectsInScale.First().Key is Slider slider)
+ origin = slider.Position;
+
+ Vector2 actualOrigin = origin ?? defaultOrigin.Value;
+ var selectionQuad = OriginalSurroundingQuad.Value;
+
+ var tl1 = Vector2.Divide(-actualOrigin, selectionQuad.TopLeft - actualOrigin);
+ var tl2 = Vector2.Divide(OsuPlayfield.BASE_SIZE - actualOrigin, selectionQuad.TopLeft - actualOrigin);
+ var br1 = Vector2.Divide(-actualOrigin, selectionQuad.BottomRight - actualOrigin);
+ var br2 = Vector2.Divide(OsuPlayfield.BASE_SIZE - actualOrigin, selectionQuad.BottomRight - actualOrigin);
+
+ if (!Precision.AlmostEquals(selectionQuad.TopLeft.X - actualOrigin.X, 0))
+ scale.X = selectionQuad.TopLeft.X - actualOrigin.X < 0 ? MathHelper.Clamp(scale.X, tl2.X, tl1.X) : MathHelper.Clamp(scale.X, tl1.X, tl2.X);
+ if (!Precision.AlmostEquals(selectionQuad.TopLeft.Y - actualOrigin.Y, 0))
+ scale.Y = selectionQuad.TopLeft.Y - actualOrigin.Y < 0 ? MathHelper.Clamp(scale.Y, tl2.Y, tl1.Y) : MathHelper.Clamp(scale.Y, tl1.Y, tl2.Y);
+ if (!Precision.AlmostEquals(selectionQuad.BottomRight.X - actualOrigin.X, 0))
+ scale.X = selectionQuad.BottomRight.X - actualOrigin.X < 0 ? MathHelper.Clamp(scale.X, br2.X, br1.X) : MathHelper.Clamp(scale.X, br1.X, br2.X);
+ if (!Precision.AlmostEquals(selectionQuad.BottomRight.Y - actualOrigin.Y, 0))
+ scale.Y = selectionQuad.BottomRight.Y - actualOrigin.Y < 0 ? MathHelper.Clamp(scale.Y, br2.Y, br1.Y) : MathHelper.Clamp(scale.Y, br1.Y, br2.Y);
return Vector2.ComponentMax(scale, new Vector2(Precision.FLOAT_EPSILON));
}
diff --git a/osu.Game.Rulesets.Osu/Edit/PreciseScalePopover.cs b/osu.Game.Rulesets.Osu/Edit/PreciseScalePopover.cs
index ed52da56d3..50195ebd1e 100644
--- a/osu.Game.Rulesets.Osu/Edit/PreciseScalePopover.cs
+++ b/osu.Game.Rulesets.Osu/Edit/PreciseScalePopover.cs
@@ -1,6 +1,7 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
+using System;
using System.Linq;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
@@ -22,6 +23,7 @@ public partial class PreciseScalePopover : OsuPopover
private readonly Bindable scaleInfo = new Bindable(new PreciseScaleInfo(1, ScaleOrigin.PlayfieldCentre, true, true));
private SliderWithTextBoxInput scaleInput = null!;
+ private BindableNumber scaleInputBindable = null!;
private EditorRadioButtonCollection scaleOrigin = null!;
private RadioButton selectionCentreButton = null!;
@@ -45,7 +47,7 @@ private void load()
{
scaleInput = new SliderWithTextBoxInput("Scale:")
{
- Current = new BindableNumber
+ Current = scaleInputBindable = new BindableNumber
{
MinValue = 0.5f,
MaxValue = 2,
@@ -61,10 +63,10 @@ private void load()
Items = new[]
{
new RadioButton("Playfield centre",
- () => scaleInfo.Value = scaleInfo.Value with { Origin = ScaleOrigin.PlayfieldCentre },
+ () => setOrigin(ScaleOrigin.PlayfieldCentre),
() => new SpriteIcon { Icon = FontAwesome.Regular.Square }),
selectionCentreButton = new RadioButton("Selection centre",
- () => scaleInfo.Value = scaleInfo.Value with { Origin = ScaleOrigin.SelectionCentre },
+ () => setOrigin(ScaleOrigin.SelectionCentre),
() => new SpriteIcon { Icon = FontAwesome.Solid.VectorSquare })
}
}
@@ -96,14 +98,39 @@ protected override void LoadComplete()
scaleInfo.BindValueChanged(scale =>
{
var newScale = new Vector2(scale.NewValue.XAxis ? scale.NewValue.Scale : 1, scale.NewValue.YAxis ? scale.NewValue.Scale : 1);
- scaleHandler.Update(newScale, scale.NewValue.Origin == ScaleOrigin.PlayfieldCentre ? OsuPlayfield.BASE_SIZE / 2 : null);
+ scaleHandler.Update(newScale, getOriginPosition(scale.NewValue));
});
}
+ private void updateMaxScale()
+ {
+ if (!scaleHandler.OriginalSurroundingQuad.HasValue)
+ return;
+
+ const float max_scale = 10;
+ var scale = scaleHandler.GetClampedScale(new Vector2(max_scale), getOriginPosition(scaleInfo.Value));
+
+ if (!scaleInfo.Value.XAxis)
+ scale.X = max_scale;
+ if (!scaleInfo.Value.YAxis)
+ scale.Y = max_scale;
+
+ scaleInputBindable.MaxValue = MathF.Max(1, MathF.Min(scale.X, scale.Y));
+ }
+
+ private void setOrigin(ScaleOrigin origin)
+ {
+ scaleInfo.Value = scaleInfo.Value with { Origin = origin };
+ updateMaxScale();
+ }
+
+ private Vector2? getOriginPosition(PreciseScaleInfo scale) => scale.Origin == ScaleOrigin.PlayfieldCentre ? OsuPlayfield.BASE_SIZE / 2 : null;
+
protected override void PopIn()
{
base.PopIn();
scaleHandler.Begin();
+ updateMaxScale();
}
protected override void PopOut()
diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionScaleHandler.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionScaleHandler.cs
index a96f627e56..fb421c2329 100644
--- a/osu.Game/Screens/Edit/Compose/Components/SelectionScaleHandler.cs
+++ b/osu.Game/Screens/Edit/Compose/Components/SelectionScaleHandler.cs
@@ -34,6 +34,14 @@ public partial class SelectionScaleHandler : Component
public Quad? OriginalSurroundingQuad { get; protected set; }
+ ///
+ /// Clamp scale where selection does not exceed playfield bounds or flip.
+ ///
+ /// The origin from which the scale operation is performed
+ /// The scale to be clamped
+ /// The clamped scale vector
+ public virtual Vector2 GetClampedScale(Vector2 scale, Vector2? origin = null) => scale;
+
///
/// Performs a single, instant, atomic scale operation.
///