diff --git a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs
index 3ead61f64a..cc1d1fe89f 100644
--- a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs
+++ b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs
@@ -101,7 +101,11 @@ namespace osu.Game.Rulesets.Osu.Edit
RightToolbox.AddRange(new EditorToolboxGroup[]
{
- new TransformToolboxGroup { RotationHandler = BlueprintContainer.SelectionHandler.RotationHandler, },
+ new TransformToolboxGroup
+ {
+ RotationHandler = BlueprintContainer.SelectionHandler.RotationHandler,
+ ScaleHandler = (OsuSelectionScaleHandler)BlueprintContainer.SelectionHandler.ScaleHandler,
+ },
FreehandlSliderToolboxGroup
}
);
diff --git a/osu.Game.Rulesets.Osu/Edit/OsuSelectionRotationHandler.cs b/osu.Game.Rulesets.Osu/Edit/OsuSelectionRotationHandler.cs
index d48bc6a90b..7624b2f27e 100644
--- a/osu.Game.Rulesets.Osu/Edit/OsuSelectionRotationHandler.cs
+++ b/osu.Game.Rulesets.Osu/Edit/OsuSelectionRotationHandler.cs
@@ -41,8 +41,8 @@ namespace osu.Game.Rulesets.Osu.Edit
private void updateState()
{
var quad = GeometryUtils.GetSurroundingQuad(selectedMovableObjects);
- CanRotateSelectionOrigin.Value = quad.Width > 0 || quad.Height > 0;
- CanRotatePlayfieldOrigin.Value = selectedMovableObjects.Any();
+ CanRotateAroundSelectionOrigin.Value = quad.Width > 0 || quad.Height > 0;
+ CanRotateAroundPlayfieldOrigin.Value = selectedMovableObjects.Any();
}
private OsuHitObject[]? objectsInRotation;
diff --git a/osu.Game.Rulesets.Osu/Edit/OsuSelectionScaleHandler.cs b/osu.Game.Rulesets.Osu/Edit/OsuSelectionScaleHandler.cs
index af03c4d925..de00aa6ad3 100644
--- a/osu.Game.Rulesets.Osu/Edit/OsuSelectionScaleHandler.cs
+++ b/osu.Game.Rulesets.Osu/Edit/OsuSelectionScaleHandler.cs
@@ -24,6 +24,16 @@ namespace osu.Game.Rulesets.Osu.Edit
{
public partial class OsuSelectionScaleHandler : SelectionScaleHandler
{
+ ///
+ /// Whether scaling anchored by the center of the playfield can currently be performed.
+ ///
+ public Bindable CanScaleFromPlayfieldOrigin { get; private set; } = new BindableBool();
+
+ ///
+ /// Whether a single slider is currently selected, which results in a different scaling behaviour.
+ ///
+ public Bindable IsScalingSlider { get; private set; } = new BindableBool();
+
[Resolved]
private IEditorChangeHandler? changeHandler { get; set; }
@@ -53,6 +63,8 @@ namespace osu.Game.Rulesets.Osu.Edit
CanScaleX.Value = quad.Width > 0;
CanScaleY.Value = quad.Height > 0;
CanScaleDiagonally.Value = CanScaleX.Value && CanScaleY.Value;
+ CanScaleFromPlayfieldOrigin.Value = selectedMovableObjects.Any();
+ IsScalingSlider.Value = selectedMovableObjects.Count() == 1 && selectedMovableObjects.First() is Slider;
}
private Dictionary? objectsInScale;
@@ -67,7 +79,7 @@ namespace osu.Game.Rulesets.Osu.Edit
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 +104,7 @@ namespace osu.Game.Rulesets.Osu.Edit
}
else
{
- scale = getClampedScale(OriginalSurroundingQuad.Value, actualOrigin, scale);
+ scale = ClampScaleToPlayfieldBounds(scale, actualOrigin);
foreach (var (ho, originalState) in objectsInScale)
{
@@ -158,27 +170,36 @@ namespace osu.Game.Rulesets.Osu.Edit
///
/// 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 Vector2 ClampScaleToPlayfieldBounds(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/PreciseRotationPopover.cs b/osu.Game.Rulesets.Osu/Edit/PreciseRotationPopover.cs
index 88c3d7414b..3a0d3c4763 100644
--- a/osu.Game.Rulesets.Osu/Edit/PreciseRotationPopover.cs
+++ b/osu.Game.Rulesets.Osu/Edit/PreciseRotationPopover.cs
@@ -78,11 +78,15 @@ namespace osu.Game.Rulesets.Osu.Edit
{
base.LoadComplete();
- ScheduleAfterChildren(() => angleInput.TakeFocus());
+ ScheduleAfterChildren(() =>
+ {
+ angleInput.TakeFocus();
+ angleInput.SelectAll();
+ });
angleInput.Current.BindValueChanged(angle => rotationInfo.Value = rotationInfo.Value with { Degrees = angle.NewValue });
rotationOrigin.Items.First().Select();
- rotationHandler.CanRotateSelectionOrigin.BindValueChanged(e =>
+ rotationHandler.CanRotateAroundSelectionOrigin.BindValueChanged(e =>
{
selectionCentreButton.Selected.Disabled = !e.NewValue;
}, true);
diff --git a/osu.Game.Rulesets.Osu/Edit/PreciseScalePopover.cs b/osu.Game.Rulesets.Osu/Edit/PreciseScalePopover.cs
new file mode 100644
index 0000000000..a299eebbce
--- /dev/null
+++ b/osu.Game.Rulesets.Osu/Edit/PreciseScalePopover.cs
@@ -0,0 +1,212 @@
+// 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;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Containers;
+using osu.Framework.Graphics.Sprites;
+using osu.Game.Graphics.UserInterface;
+using osu.Game.Graphics.UserInterfaceV2;
+using osu.Game.Rulesets.Osu.UI;
+using osu.Game.Screens.Edit.Components.RadioButtons;
+using osuTK;
+
+namespace osu.Game.Rulesets.Osu.Edit
+{
+ public partial class PreciseScalePopover : OsuPopover
+ {
+ private readonly OsuSelectionScaleHandler scaleHandler;
+
+ 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 playfieldCentreButton = null!;
+ private RadioButton selectionCentreButton = null!;
+
+ private OsuCheckbox xCheckBox = null!;
+ private OsuCheckbox yCheckBox = null!;
+
+ public PreciseScalePopover(OsuSelectionScaleHandler scaleHandler)
+ {
+ this.scaleHandler = scaleHandler;
+
+ AllowableAnchors = new[] { Anchor.CentreLeft, Anchor.CentreRight };
+ }
+
+ [BackgroundDependencyLoader]
+ private void load()
+ {
+ Child = new FillFlowContainer
+ {
+ Width = 220,
+ AutoSizeAxes = Axes.Y,
+ Spacing = new Vector2(20),
+ Children = new Drawable[]
+ {
+ scaleInput = new SliderWithTextBoxInput("Scale:")
+ {
+ Current = scaleInputBindable = new BindableNumber
+ {
+ MinValue = 0.5f,
+ MaxValue = 2,
+ Precision = 0.001f,
+ Value = 1,
+ Default = 1,
+ },
+ Instantaneous = true
+ },
+ scaleOrigin = new EditorRadioButtonCollection
+ {
+ RelativeSizeAxes = Axes.X,
+ Items = new[]
+ {
+ playfieldCentreButton = new RadioButton("Playfield centre",
+ () => setOrigin(ScaleOrigin.PlayfieldCentre),
+ () => new SpriteIcon { Icon = FontAwesome.Regular.Square }),
+ selectionCentreButton = new RadioButton("Selection centre",
+ () => setOrigin(ScaleOrigin.SelectionCentre),
+ () => new SpriteIcon { Icon = FontAwesome.Solid.VectorSquare })
+ }
+ },
+ new FillFlowContainer
+ {
+ RelativeSizeAxes = Axes.X,
+ AutoSizeAxes = Axes.Y,
+ Spacing = new Vector2(4),
+ Children = new Drawable[]
+ {
+ xCheckBox = new OsuCheckbox(false)
+ {
+ RelativeSizeAxes = Axes.X,
+ LabelText = "X-axis",
+ Current = { Value = true },
+ },
+ yCheckBox = new OsuCheckbox(false)
+ {
+ RelativeSizeAxes = Axes.X,
+ LabelText = "Y-axis",
+ Current = { Value = true },
+ },
+ }
+ },
+ }
+ };
+ playfieldCentreButton.Selected.DisabledChanged += isDisabled =>
+ {
+ playfieldCentreButton.TooltipText = isDisabled ? "The current selection cannot be scaled relative to playfield centre." : string.Empty;
+ };
+ selectionCentreButton.Selected.DisabledChanged += isDisabled =>
+ {
+ selectionCentreButton.TooltipText = isDisabled ? "The current selection cannot be scaled relative to its centre." : string.Empty;
+ };
+ }
+
+ protected override void LoadComplete()
+ {
+ base.LoadComplete();
+
+ ScheduleAfterChildren(() =>
+ {
+ scaleInput.TakeFocus();
+ scaleInput.SelectAll();
+ });
+ scaleInput.Current.BindValueChanged(scale => scaleInfo.Value = scaleInfo.Value with { Scale = scale.NewValue });
+
+ xCheckBox.Current.BindValueChanged(x => setAxis(x.NewValue, yCheckBox.Current.Value));
+ yCheckBox.Current.BindValueChanged(y => setAxis(xCheckBox.Current.Value, y.NewValue));
+
+ selectionCentreButton.Selected.Disabled = !(scaleHandler.CanScaleX.Value || scaleHandler.CanScaleY.Value);
+ playfieldCentreButton.Selected.Disabled = scaleHandler.IsScalingSlider.Value && !selectionCentreButton.Selected.Disabled;
+
+ scaleOrigin.Items.First(b => !b.Selected.Disabled).Select();
+
+ scaleInfo.BindValueChanged(scale =>
+ {
+ var newScale = new Vector2(scale.NewValue.XAxis ? scale.NewValue.Scale : 1, scale.NewValue.YAxis ? scale.NewValue.Scale : 1);
+ scaleHandler.Update(newScale, getOriginPosition(scale.NewValue));
+ });
+ }
+
+ private void updateAxisCheckBoxesEnabled()
+ {
+ if (scaleInfo.Value.Origin == ScaleOrigin.PlayfieldCentre)
+ {
+ toggleAxisAvailable(xCheckBox.Current, true);
+ toggleAxisAvailable(yCheckBox.Current, true);
+ }
+ else
+ {
+ toggleAxisAvailable(xCheckBox.Current, scaleHandler.CanScaleX.Value);
+ toggleAxisAvailable(yCheckBox.Current, scaleHandler.CanScaleY.Value);
+ }
+ }
+
+ private void toggleAxisAvailable(Bindable axisBindable, bool available)
+ {
+ // enable the bindable to allow setting the value
+ axisBindable.Disabled = false;
+ // restore the presumed default value given the axis's new availability state
+ axisBindable.Value = available;
+ axisBindable.Disabled = !available;
+ }
+
+ private void updateMaxScale()
+ {
+ if (!scaleHandler.OriginalSurroundingQuad.HasValue)
+ return;
+
+ const float max_scale = 10;
+ var scale = scaleHandler.ClampScaleToPlayfieldBounds(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();
+ updateAxisCheckBoxesEnabled();
+ }
+
+ private Vector2? getOriginPosition(PreciseScaleInfo scale) => scale.Origin == ScaleOrigin.PlayfieldCentre ? OsuPlayfield.BASE_SIZE / 2 : null;
+
+ private void setAxis(bool x, bool y)
+ {
+ scaleInfo.Value = scaleInfo.Value with { XAxis = x, YAxis = y };
+ updateMaxScale();
+ }
+
+ protected override void PopIn()
+ {
+ base.PopIn();
+ scaleHandler.Begin();
+ updateMaxScale();
+ }
+
+ protected override void PopOut()
+ {
+ base.PopOut();
+
+ if (IsLoaded) scaleHandler.Commit();
+ }
+ }
+
+ public enum ScaleOrigin
+ {
+ PlayfieldCentre,
+ SelectionCentre
+ }
+
+ public record PreciseScaleInfo(float Scale, ScaleOrigin Origin, bool XAxis, bool YAxis);
+}
diff --git a/osu.Game.Rulesets.Osu/Edit/TransformToolboxGroup.cs b/osu.Game.Rulesets.Osu/Edit/TransformToolboxGroup.cs
index 9499bacade..28d0f8320f 100644
--- a/osu.Game.Rulesets.Osu/Edit/TransformToolboxGroup.cs
+++ b/osu.Game.Rulesets.Osu/Edit/TransformToolboxGroup.cs
@@ -18,14 +18,14 @@ namespace osu.Game.Rulesets.Osu.Edit
{
public partial class TransformToolboxGroup : EditorToolboxGroup, IKeyBindingHandler
{
- private readonly Bindable canRotate = new BindableBool();
+ private readonly AggregateBindable canRotate = new AggregateBindable((x, y) => x || y);
+ private readonly AggregateBindable canScale = new AggregateBindable((x, y) => x || y);
private EditorToolButton rotateButton = null!;
-
- private Bindable canRotatePlayfieldOrigin = null!;
- private Bindable canRotateSelectionOrigin = null!;
+ private EditorToolButton scaleButton = null!;
public SelectionRotationHandler RotationHandler { get; init; } = null!;
+ public OsuSelectionScaleHandler ScaleHandler { get; init; } = null!;
public TransformToolboxGroup()
: base("transform")
@@ -45,7 +45,9 @@ namespace osu.Game.Rulesets.Osu.Edit
rotateButton = new EditorToolButton("Rotate",
() => new SpriteIcon { Icon = FontAwesome.Solid.Undo },
() => new PreciseRotationPopover(RotationHandler)),
- // TODO: scale
+ scaleButton = new EditorToolButton("Scale",
+ () => new SpriteIcon { Icon = FontAwesome.Solid.ArrowsAlt },
+ () => new PreciseScalePopover(ScaleHandler))
}
};
}
@@ -54,21 +56,17 @@ namespace osu.Game.Rulesets.Osu.Edit
{
base.LoadComplete();
- // aggregate two values into canRotate
- canRotatePlayfieldOrigin = RotationHandler.CanRotatePlayfieldOrigin.GetBoundCopy();
- canRotatePlayfieldOrigin.BindValueChanged(_ => updateCanRotateAggregate());
+ canRotate.AddSource(RotationHandler.CanRotateAroundPlayfieldOrigin);
+ canRotate.AddSource(RotationHandler.CanRotateAroundSelectionOrigin);
- canRotateSelectionOrigin = RotationHandler.CanRotateSelectionOrigin.GetBoundCopy();
- canRotateSelectionOrigin.BindValueChanged(_ => updateCanRotateAggregate());
-
- void updateCanRotateAggregate()
- {
- canRotate.Value = RotationHandler.CanRotatePlayfieldOrigin.Value || RotationHandler.CanRotateSelectionOrigin.Value;
- }
+ canScale.AddSource(ScaleHandler.CanScaleX);
+ canScale.AddSource(ScaleHandler.CanScaleY);
+ canScale.AddSource(ScaleHandler.CanScaleFromPlayfieldOrigin);
// bindings to `Enabled` on the buttons are decoupled on purpose
// due to the weird `OsuButton` behaviour of resetting `Enabled` to `false` when `Action` is set.
- canRotate.BindValueChanged(_ => rotateButton.Enabled.Value = canRotate.Value, true);
+ canRotate.Result.BindValueChanged(rotate => rotateButton.Enabled.Value = rotate.NewValue, true);
+ canScale.Result.BindValueChanged(scale => scaleButton.Enabled.Value = scale.NewValue, true);
}
public bool OnPressed(KeyBindingPressEvent e)
@@ -82,6 +80,12 @@ namespace osu.Game.Rulesets.Osu.Edit
rotateButton.TriggerClick();
return true;
}
+
+ case GlobalAction.EditorToggleScaleControl:
+ {
+ scaleButton.TriggerClick();
+ return true;
+ }
}
return false;
diff --git a/osu.Game.Tests/Visual/Editing/TestSceneComposeSelectBox.cs b/osu.Game.Tests/Visual/Editing/TestSceneComposeSelectBox.cs
index 6bd6c4a8c4..79a808bbd2 100644
--- a/osu.Game.Tests/Visual/Editing/TestSceneComposeSelectBox.cs
+++ b/osu.Game.Tests/Visual/Editing/TestSceneComposeSelectBox.cs
@@ -69,7 +69,7 @@ namespace osu.Game.Tests.Visual.Editing
{
this.getTargetContainer = getTargetContainer;
- CanRotateSelectionOrigin.Value = true;
+ CanRotateAroundSelectionOrigin.Value = true;
}
[CanBeNull]
diff --git a/osu.Game/Graphics/UserInterfaceV2/LabelledTextBox.cs b/osu.Game/Graphics/UserInterfaceV2/LabelledTextBox.cs
index 863ad5a173..8dfe729ce7 100644
--- a/osu.Game/Graphics/UserInterfaceV2/LabelledTextBox.cs
+++ b/osu.Game/Graphics/UserInterfaceV2/LabelledTextBox.cs
@@ -50,6 +50,8 @@ namespace osu.Game.Graphics.UserInterfaceV2
Component.BorderColour = colours.Blue;
}
+ public bool SelectAll() => Component.SelectAll();
+
protected virtual OsuTextBox CreateTextBox() => new OsuTextBox();
public override bool AcceptsFocus => true;
diff --git a/osu.Game/Graphics/UserInterfaceV2/SliderWithTextBoxInput.cs b/osu.Game/Graphics/UserInterfaceV2/SliderWithTextBoxInput.cs
index 4c16cb4951..f1f4fe3b46 100644
--- a/osu.Game/Graphics/UserInterfaceV2/SliderWithTextBoxInput.cs
+++ b/osu.Game/Graphics/UserInterfaceV2/SliderWithTextBoxInput.cs
@@ -87,6 +87,8 @@ namespace osu.Game.Graphics.UserInterfaceV2
public bool TakeFocus() => GetContainingFocusManager().ChangeFocus(textBox);
+ public bool SelectAll() => textBox.SelectAll();
+
private bool updatingFromTextBox;
private void textChanged(ValueChangedEvent change)
diff --git a/osu.Game/Input/Bindings/GlobalActionContainer.cs b/osu.Game/Input/Bindings/GlobalActionContainer.cs
index 09db7461d6..2af564d8ba 100644
--- a/osu.Game/Input/Bindings/GlobalActionContainer.cs
+++ b/osu.Game/Input/Bindings/GlobalActionContainer.cs
@@ -142,6 +142,7 @@ namespace osu.Game.Input.Bindings
new KeyBinding(new[] { InputKey.Control, InputKey.Shift, InputKey.MouseWheelRight }, GlobalAction.EditorCyclePreviousBeatSnapDivisor),
new KeyBinding(new[] { InputKey.Control, InputKey.Shift, InputKey.MouseWheelLeft }, GlobalAction.EditorCycleNextBeatSnapDivisor),
new KeyBinding(new[] { InputKey.Control, InputKey.R }, GlobalAction.EditorToggleRotateControl),
+ new KeyBinding(new[] { InputKey.Control, InputKey.E }, GlobalAction.EditorToggleScaleControl),
};
private static IEnumerable inGameKeyBindings => new[]
@@ -411,6 +412,9 @@ namespace osu.Game.Input.Bindings
[LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.EditorToggleRotateControl))]
EditorToggleRotateControl,
+ [LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.EditorToggleScaleControl))]
+ EditorToggleScaleControl,
+
[LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.IncreaseOffset))]
IncreaseOffset,
diff --git a/osu.Game/Localisation/GlobalActionKeyBindingStrings.cs b/osu.Game/Localisation/GlobalActionKeyBindingStrings.cs
index 18a1d3e4fe..2e44b96625 100644
--- a/osu.Game/Localisation/GlobalActionKeyBindingStrings.cs
+++ b/osu.Game/Localisation/GlobalActionKeyBindingStrings.cs
@@ -369,6 +369,11 @@ namespace osu.Game.Localisation
///
public static LocalisableString EditorToggleRotateControl => new TranslatableString(getKey(@"editor_toggle_rotate_control"), @"Toggle rotate control");
+ ///
+ /// "Toggle scale control"
+ ///
+ public static LocalisableString EditorToggleScaleControl => new TranslatableString(getKey(@"editor_toggle_scale_control"), @"Toggle scale control");
+
///
/// "Increase mod speed"
///
diff --git a/osu.Game/Overlays/SkinEditor/SkinSelectionRotationHandler.cs b/osu.Game/Overlays/SkinEditor/SkinSelectionRotationHandler.cs
index 7ecf116b68..6a118a73a8 100644
--- a/osu.Game/Overlays/SkinEditor/SkinSelectionRotationHandler.cs
+++ b/osu.Game/Overlays/SkinEditor/SkinSelectionRotationHandler.cs
@@ -41,7 +41,7 @@ namespace osu.Game.Overlays.SkinEditor
private void updateState()
{
- CanRotateSelectionOrigin.Value = selectedItems.Count > 0;
+ CanRotateAroundSelectionOrigin.Value = selectedItems.Count > 0;
}
private Drawable[]? objectsInRotation;
diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionBox.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionBox.cs
index 31a5c30fff..fec3224fad 100644
--- a/osu.Game/Screens/Edit/Compose/Components/SelectionBox.cs
+++ b/osu.Game/Screens/Edit/Compose/Components/SelectionBox.cs
@@ -127,7 +127,7 @@ namespace osu.Game.Screens.Edit.Compose.Components
private void load()
{
if (rotationHandler != null)
- canRotate.BindTo(rotationHandler.CanRotateSelectionOrigin);
+ canRotate.BindTo(rotationHandler.CanRotateAroundSelectionOrigin);
if (scaleHandler != null)
{
diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionRotationHandler.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionRotationHandler.cs
index 459e4b0c41..787716a38c 100644
--- a/osu.Game/Screens/Edit/Compose/Components/SelectionRotationHandler.cs
+++ b/osu.Game/Screens/Edit/Compose/Components/SelectionRotationHandler.cs
@@ -15,12 +15,12 @@ namespace osu.Game.Screens.Edit.Compose.Components
///
/// Whether rotation anchored by the selection origin can currently be performed.
///
- public Bindable CanRotateSelectionOrigin { get; private set; } = new BindableBool();
+ public Bindable CanRotateAroundSelectionOrigin { get; private set; } = new BindableBool();
///
/// Whether rotation anchored by the center of the playfield can currently be performed.
///
- public Bindable CanRotatePlayfieldOrigin { get; private set; } = new BindableBool();
+ public Bindable CanRotateAroundPlayfieldOrigin { get; private set; } = new BindableBool();
///
/// Performs a single, instant, atomic rotation operation.