From 6aa92bcc4559ec52a85e8e026e579c03f1d7aca0 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Sat, 25 May 2024 18:31:19 +0200 Subject: [PATCH 01/20] Add simple scale tool --- .../Edit/OsuHitObjectComposer.cs | 6 +- .../Edit/PreciseScalePopover.cs | 121 ++++++++++++++++++ .../Edit/TransformToolboxGroup.cs | 33 ++++- .../Input/Bindings/GlobalActionContainer.cs | 4 + .../GlobalActionKeyBindingStrings.cs | 5 + 5 files changed, 167 insertions(+), 2 deletions(-) create mode 100644 osu.Game.Rulesets.Osu/Edit/PreciseScalePopover.cs diff --git a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs index 3ead61f64a..6f3ed9730e 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 = BlueprintContainer.SelectionHandler.ScaleHandler, + }, FreehandlSliderToolboxGroup } ); diff --git a/osu.Game.Rulesets.Osu/Edit/PreciseScalePopover.cs b/osu.Game.Rulesets.Osu/Edit/PreciseScalePopover.cs new file mode 100644 index 0000000000..62408e223d --- /dev/null +++ b/osu.Game.Rulesets.Osu/Edit/PreciseScalePopover.cs @@ -0,0 +1,121 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +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.UserInterfaceV2; +using osu.Game.Rulesets.Osu.UI; +using osu.Game.Screens.Edit.Components.RadioButtons; +using osu.Game.Screens.Edit.Compose.Components; +using osuTK; + +namespace osu.Game.Rulesets.Osu.Edit +{ + public partial class PreciseScalePopover : OsuPopover + { + private readonly SelectionScaleHandler scaleHandler; + + private readonly Bindable scaleInfo = new Bindable(new PreciseScaleInfo(1, ScaleOrigin.PlayfieldCentre, true, true)); + + private SliderWithTextBoxInput scaleInput = null!; + private EditorRadioButtonCollection scaleOrigin = null!; + + private RadioButton selectionCentreButton = null!; + + public PreciseScalePopover(SelectionScaleHandler 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 = new BindableNumber + { + MinValue = 0.5f, + MaxValue = 2, + Precision = 0.001f, + Value = 1, + Default = 1, + }, + Instantaneous = true + }, + scaleOrigin = new EditorRadioButtonCollection + { + RelativeSizeAxes = Axes.X, + Items = new[] + { + new RadioButton("Playfield centre", + () => scaleInfo.Value = scaleInfo.Value with { Origin = ScaleOrigin.PlayfieldCentre }, + () => new SpriteIcon { Icon = FontAwesome.Regular.Square }), + selectionCentreButton = new RadioButton("Selection centre", + () => scaleInfo.Value = scaleInfo.Value with { Origin = ScaleOrigin.SelectionCentre }, + () => new SpriteIcon { Icon = FontAwesome.Solid.VectorSquare }) + } + } + } + }; + selectionCentreButton.Selected.DisabledChanged += isDisabled => + { + selectionCentreButton.TooltipText = isDisabled ? "Select more than one object to perform selection-based scaling." : string.Empty; + }; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + ScheduleAfterChildren(() => scaleInput.TakeFocus()); + scaleInput.Current.BindValueChanged(scale => scaleInfo.Value = scaleInfo.Value with { Scale = scale.NewValue }); + scaleOrigin.Items.First().Select(); + + scaleHandler.CanScaleX.BindValueChanged(e => + { + selectionCentreButton.Selected.Disabled = !e.NewValue; + }, true); + + 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); + }); + } + + protected override void PopIn() + { + base.PopIn(); + scaleHandler.Begin(); + } + + 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..146d771e19 100644 --- a/osu.Game.Rulesets.Osu/Edit/TransformToolboxGroup.cs +++ b/osu.Game.Rulesets.Osu/Edit/TransformToolboxGroup.cs @@ -19,13 +19,20 @@ namespace osu.Game.Rulesets.Osu.Edit public partial class TransformToolboxGroup : EditorToolboxGroup, IKeyBindingHandler { private readonly Bindable canRotate = new BindableBool(); + private readonly Bindable canScale = new BindableBool(); private EditorToolButton rotateButton = null!; + private EditorToolButton scaleButton = null!; private Bindable canRotatePlayfieldOrigin = null!; private Bindable canRotateSelectionOrigin = null!; + private Bindable canScaleX = null!; + private Bindable canScaleY = null!; + private Bindable canScaleDiagonally = null!; + public SelectionRotationHandler RotationHandler { get; init; } = null!; + public SelectionScaleHandler ScaleHandler { get; init; } = null!; public TransformToolboxGroup() : base("transform") @@ -45,7 +52,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)) } }; } @@ -66,9 +75,25 @@ namespace osu.Game.Rulesets.Osu.Edit canRotate.Value = RotationHandler.CanRotatePlayfieldOrigin.Value || RotationHandler.CanRotateSelectionOrigin.Value; } + // aggregate three values into canScale + canScaleX = ScaleHandler.CanScaleX.GetBoundCopy(); + canScaleX.BindValueChanged(_ => updateCanScaleAggregate()); + + canScaleY = ScaleHandler.CanScaleY.GetBoundCopy(); + canScaleY.BindValueChanged(_ => updateCanScaleAggregate()); + + canScaleDiagonally = ScaleHandler.CanScaleDiagonally.GetBoundCopy(); + canScaleDiagonally.BindValueChanged(_ => updateCanScaleAggregate()); + + void updateCanScaleAggregate() + { + canScale.Value = ScaleHandler.CanScaleX.Value || ScaleHandler.CanScaleY.Value || ScaleHandler.CanScaleDiagonally.Value; + } + // 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); + canScale.BindValueChanged(_ => scaleButton.Enabled.Value = canScale.Value, true); } public bool OnPressed(KeyBindingPressEvent e) @@ -82,6 +107,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/Input/Bindings/GlobalActionContainer.cs b/osu.Game/Input/Bindings/GlobalActionContainer.cs index 09db7461d6..394cb98089 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.S }, 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" /// From 88314dc584f186cf8a99c631d16047f321135292 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Sat, 25 May 2024 18:41:31 +0200 Subject: [PATCH 02/20] select all input text on popup for an easy typing experience --- osu.Game.Rulesets.Osu/Edit/PreciseRotationPopover.cs | 6 +++++- osu.Game.Rulesets.Osu/Edit/PreciseScalePopover.cs | 6 +++++- osu.Game/Graphics/UserInterfaceV2/LabelledTextBox.cs | 2 ++ osu.Game/Graphics/UserInterfaceV2/SliderWithTextBoxInput.cs | 2 ++ 4 files changed, 14 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/PreciseRotationPopover.cs b/osu.Game.Rulesets.Osu/Edit/PreciseRotationPopover.cs index 88c3d7414b..812d622ae5 100644 --- a/osu.Game.Rulesets.Osu/Edit/PreciseRotationPopover.cs +++ b/osu.Game.Rulesets.Osu/Edit/PreciseRotationPopover.cs @@ -78,7 +78,11 @@ 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(); diff --git a/osu.Game.Rulesets.Osu/Edit/PreciseScalePopover.cs b/osu.Game.Rulesets.Osu/Edit/PreciseScalePopover.cs index 62408e223d..ed52da56d3 100644 --- a/osu.Game.Rulesets.Osu/Edit/PreciseScalePopover.cs +++ b/osu.Game.Rulesets.Osu/Edit/PreciseScalePopover.cs @@ -80,7 +80,11 @@ namespace osu.Game.Rulesets.Osu.Edit { base.LoadComplete(); - ScheduleAfterChildren(() => scaleInput.TakeFocus()); + ScheduleAfterChildren(() => + { + scaleInput.TakeFocus(); + scaleInput.SelectAll(); + }); scaleInput.Current.BindValueChanged(scale => scaleInfo.Value = scaleInfo.Value with { Scale = scale.NewValue }); scaleOrigin.Items.First().Select(); 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) From 4eeebdf60cd70eedcf6692cbad36cc8df6abc8de Mon Sep 17 00:00:00 2001 From: OliBomby Date: Sat, 25 May 2024 20:17:27 +0200 Subject: [PATCH 03/20] calculate max scale bounds for scale slider --- .../Edit/OsuSelectionScaleHandler.cs | 47 ++++++++++--------- .../Edit/PreciseScalePopover.cs | 35 ++++++++++++-- .../Components/SelectionScaleHandler.cs | 8 ++++ 3 files changed, 64 insertions(+), 26 deletions(-) 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 @@ 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 +92,7 @@ namespace osu.Game.Rulesets.Osu.Edit } else { - scale = getClampedScale(OriginalSurroundingQuad.Value, actualOrigin, scale); + scale = GetClampedScale(scale, actualOrigin); foreach (var (ho, originalState) in objectsInScale) { @@ -155,30 +155,33 @@ namespace osu.Game.Rulesets.Osu.Edit 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 @@ namespace osu.Game.Rulesets.Osu.Edit 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 @@ namespace osu.Game.Rulesets.Osu.Edit { scaleInput = new SliderWithTextBoxInput("Scale:") { - Current = new BindableNumber + Current = scaleInputBindable = new BindableNumber { MinValue = 0.5f, MaxValue = 2, @@ -61,10 +63,10 @@ namespace osu.Game.Rulesets.Osu.Edit 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 @@ namespace osu.Game.Rulesets.Osu.Edit 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 @@ namespace osu.Game.Screens.Edit.Compose.Components 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. /// From 37530eebccdc6b7bacee3742946830ac61e2e815 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Sat, 25 May 2024 20:35:06 +0200 Subject: [PATCH 04/20] Enable scale buttons at the correct times --- osu.Game.Rulesets.Osu/Edit/OsuSelectionScaleHandler.cs | 2 ++ osu.Game.Rulesets.Osu/Edit/PreciseScalePopover.cs | 2 +- osu.Game.Rulesets.Osu/Edit/TransformToolboxGroup.cs | 8 ++++---- .../Edit/Compose/Components/SelectionScaleHandler.cs | 10 ++++++++++ 4 files changed, 17 insertions(+), 5 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/OsuSelectionScaleHandler.cs b/osu.Game.Rulesets.Osu/Edit/OsuSelectionScaleHandler.cs index 331e8de3f1..e45494977f 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuSelectionScaleHandler.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuSelectionScaleHandler.cs @@ -53,6 +53,8 @@ namespace osu.Game.Rulesets.Osu.Edit CanScaleX.Value = quad.Width > 0; CanScaleY.Value = quad.Height > 0; CanScaleDiagonally.Value = CanScaleX.Value && CanScaleY.Value; + CanScaleSelectionOrigin.Value = CanScaleX.Value || CanScaleY.Value; + CanScalePlayfieldOrigin.Value = selectedMovableObjects.Any(); } private Dictionary? objectsInScale; diff --git a/osu.Game.Rulesets.Osu/Edit/PreciseScalePopover.cs b/osu.Game.Rulesets.Osu/Edit/PreciseScalePopover.cs index 50195ebd1e..355064f000 100644 --- a/osu.Game.Rulesets.Osu/Edit/PreciseScalePopover.cs +++ b/osu.Game.Rulesets.Osu/Edit/PreciseScalePopover.cs @@ -90,7 +90,7 @@ namespace osu.Game.Rulesets.Osu.Edit scaleInput.Current.BindValueChanged(scale => scaleInfo.Value = scaleInfo.Value with { Scale = scale.NewValue }); scaleOrigin.Items.First().Select(); - scaleHandler.CanScaleX.BindValueChanged(e => + scaleHandler.CanScaleSelectionOrigin.BindValueChanged(e => { selectionCentreButton.Selected.Disabled = !e.NewValue; }, true); diff --git a/osu.Game.Rulesets.Osu/Edit/TransformToolboxGroup.cs b/osu.Game.Rulesets.Osu/Edit/TransformToolboxGroup.cs index 146d771e19..e1f53846dc 100644 --- a/osu.Game.Rulesets.Osu/Edit/TransformToolboxGroup.cs +++ b/osu.Game.Rulesets.Osu/Edit/TransformToolboxGroup.cs @@ -29,7 +29,7 @@ namespace osu.Game.Rulesets.Osu.Edit private Bindable canScaleX = null!; private Bindable canScaleY = null!; - private Bindable canScaleDiagonally = null!; + private Bindable canScalePlayfieldOrigin = null!; public SelectionRotationHandler RotationHandler { get; init; } = null!; public SelectionScaleHandler ScaleHandler { get; init; } = null!; @@ -82,12 +82,12 @@ namespace osu.Game.Rulesets.Osu.Edit canScaleY = ScaleHandler.CanScaleY.GetBoundCopy(); canScaleY.BindValueChanged(_ => updateCanScaleAggregate()); - canScaleDiagonally = ScaleHandler.CanScaleDiagonally.GetBoundCopy(); - canScaleDiagonally.BindValueChanged(_ => updateCanScaleAggregate()); + canScalePlayfieldOrigin = ScaleHandler.CanScalePlayfieldOrigin.GetBoundCopy(); + canScalePlayfieldOrigin.BindValueChanged(_ => updateCanScaleAggregate()); void updateCanScaleAggregate() { - canScale.Value = ScaleHandler.CanScaleX.Value || ScaleHandler.CanScaleY.Value || ScaleHandler.CanScaleDiagonally.Value; + canScale.Value = ScaleHandler.CanScaleX.Value || ScaleHandler.CanScaleY.Value || ScaleHandler.CanScalePlayfieldOrigin.Value; } // bindings to `Enabled` on the buttons are decoupled on purpose diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionScaleHandler.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionScaleHandler.cs index fb421c2329..495cce7ad6 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionScaleHandler.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionScaleHandler.cs @@ -32,6 +32,16 @@ namespace osu.Game.Screens.Edit.Compose.Components /// public Bindable CanScaleDiagonally { get; private set; } = new BindableBool(); + /// + /// Whether scaling anchored by the selection origin can currently be performed. + /// + public Bindable CanScaleSelectionOrigin { get; private set; } = new BindableBool(); + + /// + /// Whether scaling anchored by the center of the playfield can currently be performed. + /// + public Bindable CanScalePlayfieldOrigin { get; private set; } = new BindableBool(); + public Quad? OriginalSurroundingQuad { get; protected set; } /// From d4489545f275fb29eae60ea96fbe4c84d8f82cf2 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Sat, 25 May 2024 21:44:08 +0200 Subject: [PATCH 05/20] add axis toggles --- .../Edit/PreciseScalePopover.cs | 36 ++++++++++++++++++- 1 file changed, 35 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Edit/PreciseScalePopover.cs b/osu.Game.Rulesets.Osu/Edit/PreciseScalePopover.cs index 355064f000..b76d778b3d 100644 --- a/osu.Game.Rulesets.Osu/Edit/PreciseScalePopover.cs +++ b/osu.Game.Rulesets.Osu/Edit/PreciseScalePopover.cs @@ -8,6 +8,7 @@ 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; @@ -28,6 +29,9 @@ namespace osu.Game.Rulesets.Osu.Edit private RadioButton selectionCentreButton = null!; + private OsuCheckbox xCheckBox = null!; + private OsuCheckbox yCheckBox = null!; + public PreciseScalePopover(SelectionScaleHandler scaleHandler) { this.scaleHandler = scaleHandler; @@ -69,7 +73,28 @@ namespace osu.Game.Rulesets.Osu.Edit () => 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 }, + }, + } + }, } }; selectionCentreButton.Selected.DisabledChanged += isDisabled => @@ -90,6 +115,9 @@ namespace osu.Game.Rulesets.Osu.Edit scaleInput.Current.BindValueChanged(scale => scaleInfo.Value = scaleInfo.Value with { Scale = scale.NewValue }); scaleOrigin.Items.First().Select(); + xCheckBox.Current.BindValueChanged(x => setAxis(x.NewValue, yCheckBox.Current.Value)); + yCheckBox.Current.BindValueChanged(y => setAxis(xCheckBox.Current.Value, y.NewValue)); + scaleHandler.CanScaleSelectionOrigin.BindValueChanged(e => { selectionCentreButton.Selected.Disabled = !e.NewValue; @@ -126,6 +154,12 @@ namespace osu.Game.Rulesets.Osu.Edit 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(); From 36453f621513ee8e7ad5971f3bc7c1a39404d700 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Tue, 28 May 2024 15:56:59 +0200 Subject: [PATCH 06/20] Change scale hotkey to Ctrl+T --- osu.Game/Input/Bindings/GlobalActionContainer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Input/Bindings/GlobalActionContainer.cs b/osu.Game/Input/Bindings/GlobalActionContainer.cs index 394cb98089..fd8e6fd6d0 100644 --- a/osu.Game/Input/Bindings/GlobalActionContainer.cs +++ b/osu.Game/Input/Bindings/GlobalActionContainer.cs @@ -142,7 +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.S }, GlobalAction.EditorToggleScaleControl), + new KeyBinding(new[] { InputKey.Control, InputKey.T }, GlobalAction.EditorToggleScaleControl), }; private static IEnumerable inGameKeyBindings => new[] From a89ba33b475a4f73167f0c81e53eec8f082c16ec Mon Sep 17 00:00:00 2001 From: OliBomby Date: Tue, 28 May 2024 16:14:16 +0200 Subject: [PATCH 07/20] rename CanScaleSelectionOrigin/PlayfieldOrigin to make clear its not the origin being scaled --- .../Edit/OsuSelectionRotationHandler.cs | 4 ++-- osu.Game.Rulesets.Osu/Edit/OsuSelectionScaleHandler.cs | 4 ++-- osu.Game.Rulesets.Osu/Edit/PreciseRotationPopover.cs | 2 +- osu.Game.Rulesets.Osu/Edit/PreciseScalePopover.cs | 2 +- osu.Game.Rulesets.Osu/Edit/TransformToolboxGroup.cs | 10 +++++----- .../Visual/Editing/TestSceneComposeSelectBox.cs | 2 +- .../SkinEditor/SkinSelectionRotationHandler.cs | 2 +- .../Screens/Edit/Compose/Components/SelectionBox.cs | 2 +- .../Compose/Components/SelectionRotationHandler.cs | 4 ++-- .../Edit/Compose/Components/SelectionScaleHandler.cs | 4 ++-- 10 files changed, 18 insertions(+), 18 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/OsuSelectionRotationHandler.cs b/osu.Game.Rulesets.Osu/Edit/OsuSelectionRotationHandler.cs index d48bc6a90b..b581e3fdea 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(); + CanRotateFromSelectionOrigin.Value = quad.Width > 0 || quad.Height > 0; + CanRotateFromPlayfieldOrigin.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 e45494977f..a9cbc1b8f1 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuSelectionScaleHandler.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuSelectionScaleHandler.cs @@ -53,8 +53,8 @@ namespace osu.Game.Rulesets.Osu.Edit CanScaleX.Value = quad.Width > 0; CanScaleY.Value = quad.Height > 0; CanScaleDiagonally.Value = CanScaleX.Value && CanScaleY.Value; - CanScaleSelectionOrigin.Value = CanScaleX.Value || CanScaleY.Value; - CanScalePlayfieldOrigin.Value = selectedMovableObjects.Any(); + CanScaleFromSelectionOrigin.Value = CanScaleX.Value || CanScaleY.Value; + CanScaleFromPlayfieldOrigin.Value = selectedMovableObjects.Any(); } private Dictionary? objectsInScale; diff --git a/osu.Game.Rulesets.Osu/Edit/PreciseRotationPopover.cs b/osu.Game.Rulesets.Osu/Edit/PreciseRotationPopover.cs index 812d622ae5..ea6b28b215 100644 --- a/osu.Game.Rulesets.Osu/Edit/PreciseRotationPopover.cs +++ b/osu.Game.Rulesets.Osu/Edit/PreciseRotationPopover.cs @@ -86,7 +86,7 @@ namespace osu.Game.Rulesets.Osu.Edit angleInput.Current.BindValueChanged(angle => rotationInfo.Value = rotationInfo.Value with { Degrees = angle.NewValue }); rotationOrigin.Items.First().Select(); - rotationHandler.CanRotateSelectionOrigin.BindValueChanged(e => + rotationHandler.CanRotateFromSelectionOrigin.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 index b76d778b3d..124a79390a 100644 --- a/osu.Game.Rulesets.Osu/Edit/PreciseScalePopover.cs +++ b/osu.Game.Rulesets.Osu/Edit/PreciseScalePopover.cs @@ -118,7 +118,7 @@ namespace osu.Game.Rulesets.Osu.Edit xCheckBox.Current.BindValueChanged(x => setAxis(x.NewValue, yCheckBox.Current.Value)); yCheckBox.Current.BindValueChanged(y => setAxis(xCheckBox.Current.Value, y.NewValue)); - scaleHandler.CanScaleSelectionOrigin.BindValueChanged(e => + scaleHandler.CanScaleFromSelectionOrigin.BindValueChanged(e => { selectionCentreButton.Selected.Disabled = !e.NewValue; }, true); diff --git a/osu.Game.Rulesets.Osu/Edit/TransformToolboxGroup.cs b/osu.Game.Rulesets.Osu/Edit/TransformToolboxGroup.cs index e1f53846dc..67baf7d165 100644 --- a/osu.Game.Rulesets.Osu/Edit/TransformToolboxGroup.cs +++ b/osu.Game.Rulesets.Osu/Edit/TransformToolboxGroup.cs @@ -64,15 +64,15 @@ namespace osu.Game.Rulesets.Osu.Edit base.LoadComplete(); // aggregate two values into canRotate - canRotatePlayfieldOrigin = RotationHandler.CanRotatePlayfieldOrigin.GetBoundCopy(); + canRotatePlayfieldOrigin = RotationHandler.CanRotateFromPlayfieldOrigin.GetBoundCopy(); canRotatePlayfieldOrigin.BindValueChanged(_ => updateCanRotateAggregate()); - canRotateSelectionOrigin = RotationHandler.CanRotateSelectionOrigin.GetBoundCopy(); + canRotateSelectionOrigin = RotationHandler.CanRotateFromSelectionOrigin.GetBoundCopy(); canRotateSelectionOrigin.BindValueChanged(_ => updateCanRotateAggregate()); void updateCanRotateAggregate() { - canRotate.Value = RotationHandler.CanRotatePlayfieldOrigin.Value || RotationHandler.CanRotateSelectionOrigin.Value; + canRotate.Value = RotationHandler.CanRotateFromPlayfieldOrigin.Value || RotationHandler.CanRotateFromSelectionOrigin.Value; } // aggregate three values into canScale @@ -82,12 +82,12 @@ namespace osu.Game.Rulesets.Osu.Edit canScaleY = ScaleHandler.CanScaleY.GetBoundCopy(); canScaleY.BindValueChanged(_ => updateCanScaleAggregate()); - canScalePlayfieldOrigin = ScaleHandler.CanScalePlayfieldOrigin.GetBoundCopy(); + canScalePlayfieldOrigin = ScaleHandler.CanScaleFromPlayfieldOrigin.GetBoundCopy(); canScalePlayfieldOrigin.BindValueChanged(_ => updateCanScaleAggregate()); void updateCanScaleAggregate() { - canScale.Value = ScaleHandler.CanScaleX.Value || ScaleHandler.CanScaleY.Value || ScaleHandler.CanScalePlayfieldOrigin.Value; + canScale.Value = ScaleHandler.CanScaleX.Value || ScaleHandler.CanScaleY.Value || ScaleHandler.CanScaleFromPlayfieldOrigin.Value; } // bindings to `Enabled` on the buttons are decoupled on purpose diff --git a/osu.Game.Tests/Visual/Editing/TestSceneComposeSelectBox.cs b/osu.Game.Tests/Visual/Editing/TestSceneComposeSelectBox.cs index 6bd6c4a8c4..d12f7ebde9 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; + CanRotateFromSelectionOrigin.Value = true; } [CanBeNull] diff --git a/osu.Game/Overlays/SkinEditor/SkinSelectionRotationHandler.cs b/osu.Game/Overlays/SkinEditor/SkinSelectionRotationHandler.cs index 7ecf116b68..3a3eb9457b 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; + CanRotateFromSelectionOrigin.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..9f709f8c64 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.CanRotateFromSelectionOrigin); 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..8c35dc07b7 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 CanRotateFromSelectionOrigin { 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 CanRotateFromPlayfieldOrigin { get; private set; } = new BindableBool(); /// /// Performs a single, instant, atomic rotation operation. diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionScaleHandler.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionScaleHandler.cs index 495cce7ad6..b72c3406f1 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionScaleHandler.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionScaleHandler.cs @@ -35,12 +35,12 @@ namespace osu.Game.Screens.Edit.Compose.Components /// /// Whether scaling anchored by the selection origin can currently be performed. /// - public Bindable CanScaleSelectionOrigin { get; private set; } = new BindableBool(); + public Bindable CanScaleFromSelectionOrigin { get; private set; } = new BindableBool(); /// /// Whether scaling anchored by the center of the playfield can currently be performed. /// - public Bindable CanScalePlayfieldOrigin { get; private set; } = new BindableBool(); + public Bindable CanScaleFromPlayfieldOrigin { get; private set; } = new BindableBool(); public Quad? OriginalSurroundingQuad { get; protected set; } From 8eb23f8a604818b6ce94dc0810a551c2a5455a4d Mon Sep 17 00:00:00 2001 From: OliBomby Date: Tue, 28 May 2024 16:19:57 +0200 Subject: [PATCH 08/20] remove redundant CanScaleFromSelectionOrigin --- .../Edit/OsuSelectionScaleHandler.cs | 1 - .../Edit/PreciseScalePopover.cs | 16 ++++++++++++---- .../Compose/Components/SelectionScaleHandler.cs | 5 ----- 3 files changed, 12 insertions(+), 10 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/OsuSelectionScaleHandler.cs b/osu.Game.Rulesets.Osu/Edit/OsuSelectionScaleHandler.cs index a9cbc1b8f1..f120c8bd75 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuSelectionScaleHandler.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuSelectionScaleHandler.cs @@ -53,7 +53,6 @@ namespace osu.Game.Rulesets.Osu.Edit CanScaleX.Value = quad.Width > 0; CanScaleY.Value = quad.Height > 0; CanScaleDiagonally.Value = CanScaleX.Value && CanScaleY.Value; - CanScaleFromSelectionOrigin.Value = CanScaleX.Value || CanScaleY.Value; CanScaleFromPlayfieldOrigin.Value = selectedMovableObjects.Any(); } diff --git a/osu.Game.Rulesets.Osu/Edit/PreciseScalePopover.cs b/osu.Game.Rulesets.Osu/Edit/PreciseScalePopover.cs index 124a79390a..dca262cf5a 100644 --- a/osu.Game.Rulesets.Osu/Edit/PreciseScalePopover.cs +++ b/osu.Game.Rulesets.Osu/Edit/PreciseScalePopover.cs @@ -32,6 +32,9 @@ namespace osu.Game.Rulesets.Osu.Edit private OsuCheckbox xCheckBox = null!; private OsuCheckbox yCheckBox = null!; + private Bindable canScaleX = null!; + private Bindable canScaleY = null!; + public PreciseScalePopover(SelectionScaleHandler scaleHandler) { this.scaleHandler = scaleHandler; @@ -118,10 +121,15 @@ namespace osu.Game.Rulesets.Osu.Edit xCheckBox.Current.BindValueChanged(x => setAxis(x.NewValue, yCheckBox.Current.Value)); yCheckBox.Current.BindValueChanged(y => setAxis(xCheckBox.Current.Value, y.NewValue)); - scaleHandler.CanScaleFromSelectionOrigin.BindValueChanged(e => - { - selectionCentreButton.Selected.Disabled = !e.NewValue; - }, true); + // aggregate two values into canScaleFromSelectionCentre + canScaleX = scaleHandler.CanScaleX.GetBoundCopy(); + canScaleX.BindValueChanged(_ => updateCanScaleFromSelectionCentre()); + + canScaleY = scaleHandler.CanScaleY.GetBoundCopy(); + canScaleY.BindValueChanged(_ => updateCanScaleFromSelectionCentre(), true); + + void updateCanScaleFromSelectionCentre() => + selectionCentreButton.Selected.Disabled = !(scaleHandler.CanScaleY.Value || scaleHandler.CanScaleFromPlayfieldOrigin.Value); scaleInfo.BindValueChanged(scale => { diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionScaleHandler.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionScaleHandler.cs index b72c3406f1..2c8b413560 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionScaleHandler.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionScaleHandler.cs @@ -32,11 +32,6 @@ namespace osu.Game.Screens.Edit.Compose.Components /// public Bindable CanScaleDiagonally { get; private set; } = new BindableBool(); - /// - /// Whether scaling anchored by the selection origin can currently be performed. - /// - public Bindable CanScaleFromSelectionOrigin { get; private set; } = new BindableBool(); - /// /// Whether scaling anchored by the center of the playfield can currently be performed. /// From 7cdc755c1614ba666d63d3eba5d8062a9313e1a4 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Tue, 28 May 2024 16:57:24 +0200 Subject: [PATCH 09/20] Bind axis checkbox disabled state to CanScaleX/Y --- .../Edit/PreciseScalePopover.cs | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/osu.Game.Rulesets.Osu/Edit/PreciseScalePopover.cs b/osu.Game.Rulesets.Osu/Edit/PreciseScalePopover.cs index dca262cf5a..ac6e2849c9 100644 --- a/osu.Game.Rulesets.Osu/Edit/PreciseScalePopover.cs +++ b/osu.Game.Rulesets.Osu/Edit/PreciseScalePopover.cs @@ -35,6 +35,8 @@ namespace osu.Game.Rulesets.Osu.Edit private Bindable canScaleX = null!; private Bindable canScaleY = null!; + private bool scaleInProgress; + public PreciseScalePopover(SelectionScaleHandler scaleHandler) { this.scaleHandler = scaleHandler; @@ -124,15 +126,26 @@ namespace osu.Game.Rulesets.Osu.Edit // aggregate two values into canScaleFromSelectionCentre canScaleX = scaleHandler.CanScaleX.GetBoundCopy(); canScaleX.BindValueChanged(_ => updateCanScaleFromSelectionCentre()); + canScaleX.BindValueChanged(e => updateAxisCheckBoxEnabled(e.NewValue, xCheckBox.Current), true); canScaleY = scaleHandler.CanScaleY.GetBoundCopy(); canScaleY.BindValueChanged(_ => updateCanScaleFromSelectionCentre(), true); + canScaleY.BindValueChanged(e => updateAxisCheckBoxEnabled(e.NewValue, yCheckBox.Current), true); void updateCanScaleFromSelectionCentre() => selectionCentreButton.Selected.Disabled = !(scaleHandler.CanScaleY.Value || scaleHandler.CanScaleFromPlayfieldOrigin.Value); + void updateAxisCheckBoxEnabled(bool enabled, Bindable current) + { + current.Disabled = false; // enable the bindable to allow setting the value + current.Value = enabled; + current.Disabled = !enabled; + } + scaleInfo.BindValueChanged(scale => { + if (!scaleInProgress) return; + var newScale = new Vector2(scale.NewValue.XAxis ? scale.NewValue.Scale : 1, scale.NewValue.YAxis ? scale.NewValue.Scale : 1); scaleHandler.Update(newScale, getOriginPosition(scale.NewValue)); }); @@ -172,6 +185,7 @@ namespace osu.Game.Rulesets.Osu.Edit { base.PopIn(); scaleHandler.Begin(); + scaleInProgress = true; updateMaxScale(); } @@ -180,7 +194,10 @@ namespace osu.Game.Rulesets.Osu.Edit base.PopOut(); if (IsLoaded) + { scaleHandler.Commit(); + scaleInProgress = false; + } } } From d143a697d2efbd7d57fc7d7ff7d3d4e3cfc13a7d Mon Sep 17 00:00:00 2001 From: OliBomby Date: Tue, 28 May 2024 17:12:16 +0200 Subject: [PATCH 10/20] refactor CanScaleFromPlayfieldOrigin and GetClampedScale to derived class --- osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs | 2 +- .../Edit/OsuSelectionScaleHandler.cs | 13 ++++++++++++- osu.Game.Rulesets.Osu/Edit/PreciseScalePopover.cs | 5 ++--- osu.Game.Rulesets.Osu/Edit/TransformToolboxGroup.cs | 2 +- .../Compose/Components/SelectionScaleHandler.cs | 13 ------------- 5 files changed, 16 insertions(+), 19 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs index 6f3ed9730e..cc1d1fe89f 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs @@ -104,7 +104,7 @@ namespace osu.Game.Rulesets.Osu.Edit new TransformToolboxGroup { RotationHandler = BlueprintContainer.SelectionHandler.RotationHandler, - ScaleHandler = BlueprintContainer.SelectionHandler.ScaleHandler, + ScaleHandler = (OsuSelectionScaleHandler)BlueprintContainer.SelectionHandler.ScaleHandler, }, FreehandlSliderToolboxGroup } diff --git a/osu.Game.Rulesets.Osu/Edit/OsuSelectionScaleHandler.cs b/osu.Game.Rulesets.Osu/Edit/OsuSelectionScaleHandler.cs index f120c8bd75..32057bd7fe 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuSelectionScaleHandler.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuSelectionScaleHandler.cs @@ -24,6 +24,11 @@ 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(); + [Resolved] private IEditorChangeHandler? changeHandler { get; set; } @@ -156,7 +161,13 @@ namespace osu.Game.Rulesets.Osu.Edit return (xInBounds, yInBounds); } - public override Vector2 GetClampedScale(Vector2 scale, Vector2? origin = null) + /// + /// Clamp scale for multi-object-scaling 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 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) diff --git a/osu.Game.Rulesets.Osu/Edit/PreciseScalePopover.cs b/osu.Game.Rulesets.Osu/Edit/PreciseScalePopover.cs index ac6e2849c9..f14e166d3b 100644 --- a/osu.Game.Rulesets.Osu/Edit/PreciseScalePopover.cs +++ b/osu.Game.Rulesets.Osu/Edit/PreciseScalePopover.cs @@ -12,14 +12,13 @@ using osu.Game.Graphics.UserInterface; using osu.Game.Graphics.UserInterfaceV2; using osu.Game.Rulesets.Osu.UI; using osu.Game.Screens.Edit.Components.RadioButtons; -using osu.Game.Screens.Edit.Compose.Components; using osuTK; namespace osu.Game.Rulesets.Osu.Edit { public partial class PreciseScalePopover : OsuPopover { - private readonly SelectionScaleHandler scaleHandler; + private readonly OsuSelectionScaleHandler scaleHandler; private readonly Bindable scaleInfo = new Bindable(new PreciseScaleInfo(1, ScaleOrigin.PlayfieldCentre, true, true)); @@ -37,7 +36,7 @@ namespace osu.Game.Rulesets.Osu.Edit private bool scaleInProgress; - public PreciseScalePopover(SelectionScaleHandler scaleHandler) + public PreciseScalePopover(OsuSelectionScaleHandler scaleHandler) { this.scaleHandler = scaleHandler; diff --git a/osu.Game.Rulesets.Osu/Edit/TransformToolboxGroup.cs b/osu.Game.Rulesets.Osu/Edit/TransformToolboxGroup.cs index 67baf7d165..e70be8d93c 100644 --- a/osu.Game.Rulesets.Osu/Edit/TransformToolboxGroup.cs +++ b/osu.Game.Rulesets.Osu/Edit/TransformToolboxGroup.cs @@ -32,7 +32,7 @@ namespace osu.Game.Rulesets.Osu.Edit private Bindable canScalePlayfieldOrigin = null!; public SelectionRotationHandler RotationHandler { get; init; } = null!; - public SelectionScaleHandler ScaleHandler { get; init; } = null!; + public OsuSelectionScaleHandler ScaleHandler { get; init; } = null!; public TransformToolboxGroup() : base("transform") diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionScaleHandler.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionScaleHandler.cs index 2c8b413560..a96f627e56 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionScaleHandler.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionScaleHandler.cs @@ -32,21 +32,8 @@ namespace osu.Game.Screens.Edit.Compose.Components /// public Bindable CanScaleDiagonally { get; private set; } = new BindableBool(); - /// - /// Whether scaling anchored by the center of the playfield can currently be performed. - /// - public Bindable CanScaleFromPlayfieldOrigin { get; private set; } = new BindableBool(); - 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. /// From 9548585b15fe4154e7e7da80d21da9779486e898 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Tue, 28 May 2024 17:24:31 +0200 Subject: [PATCH 11/20] fix axis checkboxes being disabled in playfield origin scale --- .../Edit/PreciseScalePopover.cs | 35 +++++++++++++------ 1 file changed, 25 insertions(+), 10 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/PreciseScalePopover.cs b/osu.Game.Rulesets.Osu/Edit/PreciseScalePopover.cs index f14e166d3b..d45c4020b9 100644 --- a/osu.Game.Rulesets.Osu/Edit/PreciseScalePopover.cs +++ b/osu.Game.Rulesets.Osu/Edit/PreciseScalePopover.cs @@ -125,21 +125,14 @@ namespace osu.Game.Rulesets.Osu.Edit // aggregate two values into canScaleFromSelectionCentre canScaleX = scaleHandler.CanScaleX.GetBoundCopy(); canScaleX.BindValueChanged(_ => updateCanScaleFromSelectionCentre()); - canScaleX.BindValueChanged(e => updateAxisCheckBoxEnabled(e.NewValue, xCheckBox.Current), true); + canScaleX.BindValueChanged(e => updateAxisCheckBoxesEnabled()); canScaleY = scaleHandler.CanScaleY.GetBoundCopy(); canScaleY.BindValueChanged(_ => updateCanScaleFromSelectionCentre(), true); - canScaleY.BindValueChanged(e => updateAxisCheckBoxEnabled(e.NewValue, yCheckBox.Current), true); + canScaleY.BindValueChanged(e => updateAxisCheckBoxesEnabled(), true); void updateCanScaleFromSelectionCentre() => - selectionCentreButton.Selected.Disabled = !(scaleHandler.CanScaleY.Value || scaleHandler.CanScaleFromPlayfieldOrigin.Value); - - void updateAxisCheckBoxEnabled(bool enabled, Bindable current) - { - current.Disabled = false; // enable the bindable to allow setting the value - current.Value = enabled; - current.Disabled = !enabled; - } + selectionCentreButton.Selected.Disabled = !(scaleHandler.CanScaleX.Value || scaleHandler.CanScaleY.Value); scaleInfo.BindValueChanged(scale => { @@ -150,6 +143,27 @@ namespace osu.Game.Rulesets.Osu.Edit }); } + private void updateAxisCheckBoxesEnabled() + { + if (scaleInfo.Value.Origin == ScaleOrigin.PlayfieldCentre) + { + setBindableEnabled(true, xCheckBox.Current); + setBindableEnabled(true, yCheckBox.Current); + } + else + { + setBindableEnabled(canScaleX.Value, xCheckBox.Current); + setBindableEnabled(canScaleY.Value, yCheckBox.Current); + } + } + + private void setBindableEnabled(bool enabled, Bindable current) + { + current.Disabled = false; // enable the bindable to allow setting the value + current.Value = enabled; + current.Disabled = !enabled; + } + private void updateMaxScale() { if (!scaleHandler.OriginalSurroundingQuad.HasValue) @@ -170,6 +184,7 @@ namespace osu.Game.Rulesets.Osu.Edit { scaleInfo.Value = scaleInfo.Value with { Origin = origin }; updateMaxScale(); + updateAxisCheckBoxesEnabled(); } private Vector2? getOriginPosition(PreciseScaleInfo scale) => scale.Origin == ScaleOrigin.PlayfieldCentre ? OsuPlayfield.BASE_SIZE / 2 : null; From 9a18ba2699883a0278adba889b2141b3e21f044b Mon Sep 17 00:00:00 2001 From: OliBomby Date: Tue, 28 May 2024 18:27:01 +0200 Subject: [PATCH 12/20] disable playfield centre origin when scaling slider and simplify logic --- .../Edit/OsuSelectionScaleHandler.cs | 6 +++ .../Edit/PreciseScalePopover.cs | 42 +++++++------------ 2 files changed, 21 insertions(+), 27 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/OsuSelectionScaleHandler.cs b/osu.Game.Rulesets.Osu/Edit/OsuSelectionScaleHandler.cs index 32057bd7fe..9d1c3bc78f 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuSelectionScaleHandler.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuSelectionScaleHandler.cs @@ -29,6 +29,11 @@ namespace osu.Game.Rulesets.Osu.Edit /// 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; } @@ -59,6 +64,7 @@ namespace osu.Game.Rulesets.Osu.Edit 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; diff --git a/osu.Game.Rulesets.Osu/Edit/PreciseScalePopover.cs b/osu.Game.Rulesets.Osu/Edit/PreciseScalePopover.cs index d45c4020b9..b792baf428 100644 --- a/osu.Game.Rulesets.Osu/Edit/PreciseScalePopover.cs +++ b/osu.Game.Rulesets.Osu/Edit/PreciseScalePopover.cs @@ -26,16 +26,12 @@ namespace osu.Game.Rulesets.Osu.Edit private BindableNumber scaleInputBindable = null!; private EditorRadioButtonCollection scaleOrigin = null!; + private RadioButton playfieldCentreButton = null!; private RadioButton selectionCentreButton = null!; private OsuCheckbox xCheckBox = null!; private OsuCheckbox yCheckBox = null!; - private Bindable canScaleX = null!; - private Bindable canScaleY = null!; - - private bool scaleInProgress; - public PreciseScalePopover(OsuSelectionScaleHandler scaleHandler) { this.scaleHandler = scaleHandler; @@ -70,7 +66,7 @@ namespace osu.Game.Rulesets.Osu.Edit RelativeSizeAxes = Axes.X, Items = new[] { - new RadioButton("Playfield centre", + playfieldCentreButton = new RadioButton("Playfield centre", () => setOrigin(ScaleOrigin.PlayfieldCentre), () => new SpriteIcon { Icon = FontAwesome.Regular.Square }), selectionCentreButton = new RadioButton("Selection centre", @@ -101,6 +97,10 @@ namespace osu.Game.Rulesets.Osu.Edit }, } }; + playfieldCentreButton.Selected.DisabledChanged += isDisabled => + { + playfieldCentreButton.TooltipText = isDisabled ? "Select something other than a single slider to perform playfield-based scaling." : string.Empty; + }; selectionCentreButton.Selected.DisabledChanged += isDisabled => { selectionCentreButton.TooltipText = isDisabled ? "Select more than one object to perform selection-based scaling." : string.Empty; @@ -117,27 +117,20 @@ namespace osu.Game.Rulesets.Osu.Edit scaleInput.SelectAll(); }); scaleInput.Current.BindValueChanged(scale => scaleInfo.Value = scaleInfo.Value with { Scale = scale.NewValue }); - scaleOrigin.Items.First().Select(); xCheckBox.Current.BindValueChanged(x => setAxis(x.NewValue, yCheckBox.Current.Value)); yCheckBox.Current.BindValueChanged(y => setAxis(xCheckBox.Current.Value, y.NewValue)); - // aggregate two values into canScaleFromSelectionCentre - canScaleX = scaleHandler.CanScaleX.GetBoundCopy(); - canScaleX.BindValueChanged(_ => updateCanScaleFromSelectionCentre()); - canScaleX.BindValueChanged(e => updateAxisCheckBoxesEnabled()); + selectionCentreButton.Selected.Disabled = !(scaleHandler.CanScaleX.Value || scaleHandler.CanScaleY.Value); + playfieldCentreButton.Selected.Disabled = scaleHandler.IsScalingSlider.Value && !selectionCentreButton.Selected.Disabled; - canScaleY = scaleHandler.CanScaleY.GetBoundCopy(); - canScaleY.BindValueChanged(_ => updateCanScaleFromSelectionCentre(), true); - canScaleY.BindValueChanged(e => updateAxisCheckBoxesEnabled(), true); - - void updateCanScaleFromSelectionCentre() => - selectionCentreButton.Selected.Disabled = !(scaleHandler.CanScaleX.Value || scaleHandler.CanScaleY.Value); + if (playfieldCentreButton.Selected.Disabled) + scaleOrigin.Items.Last().Select(); + else + scaleOrigin.Items.First().Select(); scaleInfo.BindValueChanged(scale => { - if (!scaleInProgress) return; - var newScale = new Vector2(scale.NewValue.XAxis ? scale.NewValue.Scale : 1, scale.NewValue.YAxis ? scale.NewValue.Scale : 1); scaleHandler.Update(newScale, getOriginPosition(scale.NewValue)); }); @@ -152,8 +145,8 @@ namespace osu.Game.Rulesets.Osu.Edit } else { - setBindableEnabled(canScaleX.Value, xCheckBox.Current); - setBindableEnabled(canScaleY.Value, yCheckBox.Current); + setBindableEnabled(scaleHandler.CanScaleX.Value, xCheckBox.Current); + setBindableEnabled(scaleHandler.CanScaleY.Value, yCheckBox.Current); } } @@ -199,7 +192,6 @@ namespace osu.Game.Rulesets.Osu.Edit { base.PopIn(); scaleHandler.Begin(); - scaleInProgress = true; updateMaxScale(); } @@ -207,11 +199,7 @@ namespace osu.Game.Rulesets.Osu.Edit { base.PopOut(); - if (IsLoaded) - { - scaleHandler.Commit(); - scaleInProgress = false; - } + if (IsLoaded) scaleHandler.Commit(); } } From 4c881b56331626feb5272e0b33442ec388765acf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 29 May 2024 09:40:29 +0200 Subject: [PATCH 13/20] Use better name if we're renaming --- osu.Game.Rulesets.Osu/Edit/OsuSelectionRotationHandler.cs | 4 ++-- osu.Game.Rulesets.Osu/Edit/PreciseRotationPopover.cs | 2 +- osu.Game.Rulesets.Osu/Edit/TransformToolboxGroup.cs | 6 +++--- osu.Game.Tests/Visual/Editing/TestSceneComposeSelectBox.cs | 2 +- .../Overlays/SkinEditor/SkinSelectionRotationHandler.cs | 2 +- osu.Game/Screens/Edit/Compose/Components/SelectionBox.cs | 2 +- .../Edit/Compose/Components/SelectionRotationHandler.cs | 4 ++-- 7 files changed, 11 insertions(+), 11 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/OsuSelectionRotationHandler.cs b/osu.Game.Rulesets.Osu/Edit/OsuSelectionRotationHandler.cs index b581e3fdea..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); - CanRotateFromSelectionOrigin.Value = quad.Width > 0 || quad.Height > 0; - CanRotateFromPlayfieldOrigin.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/PreciseRotationPopover.cs b/osu.Game.Rulesets.Osu/Edit/PreciseRotationPopover.cs index ea6b28b215..3a0d3c4763 100644 --- a/osu.Game.Rulesets.Osu/Edit/PreciseRotationPopover.cs +++ b/osu.Game.Rulesets.Osu/Edit/PreciseRotationPopover.cs @@ -86,7 +86,7 @@ namespace osu.Game.Rulesets.Osu.Edit angleInput.Current.BindValueChanged(angle => rotationInfo.Value = rotationInfo.Value with { Degrees = angle.NewValue }); rotationOrigin.Items.First().Select(); - rotationHandler.CanRotateFromSelectionOrigin.BindValueChanged(e => + rotationHandler.CanRotateAroundSelectionOrigin.BindValueChanged(e => { selectionCentreButton.Selected.Disabled = !e.NewValue; }, true); diff --git a/osu.Game.Rulesets.Osu/Edit/TransformToolboxGroup.cs b/osu.Game.Rulesets.Osu/Edit/TransformToolboxGroup.cs index e70be8d93c..4da1593fb7 100644 --- a/osu.Game.Rulesets.Osu/Edit/TransformToolboxGroup.cs +++ b/osu.Game.Rulesets.Osu/Edit/TransformToolboxGroup.cs @@ -64,15 +64,15 @@ namespace osu.Game.Rulesets.Osu.Edit base.LoadComplete(); // aggregate two values into canRotate - canRotatePlayfieldOrigin = RotationHandler.CanRotateFromPlayfieldOrigin.GetBoundCopy(); + canRotatePlayfieldOrigin = RotationHandler.CanRotateAroundPlayfieldOrigin.GetBoundCopy(); canRotatePlayfieldOrigin.BindValueChanged(_ => updateCanRotateAggregate()); - canRotateSelectionOrigin = RotationHandler.CanRotateFromSelectionOrigin.GetBoundCopy(); + canRotateSelectionOrigin = RotationHandler.CanRotateAroundSelectionOrigin.GetBoundCopy(); canRotateSelectionOrigin.BindValueChanged(_ => updateCanRotateAggregate()); void updateCanRotateAggregate() { - canRotate.Value = RotationHandler.CanRotateFromPlayfieldOrigin.Value || RotationHandler.CanRotateFromSelectionOrigin.Value; + canRotate.Value = RotationHandler.CanRotateAroundPlayfieldOrigin.Value || RotationHandler.CanRotateAroundSelectionOrigin.Value; } // aggregate three values into canScale diff --git a/osu.Game.Tests/Visual/Editing/TestSceneComposeSelectBox.cs b/osu.Game.Tests/Visual/Editing/TestSceneComposeSelectBox.cs index d12f7ebde9..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; - CanRotateFromSelectionOrigin.Value = true; + CanRotateAroundSelectionOrigin.Value = true; } [CanBeNull] diff --git a/osu.Game/Overlays/SkinEditor/SkinSelectionRotationHandler.cs b/osu.Game/Overlays/SkinEditor/SkinSelectionRotationHandler.cs index 3a3eb9457b..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() { - CanRotateFromSelectionOrigin.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 9f709f8c64..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.CanRotateFromSelectionOrigin); + 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 8c35dc07b7..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 CanRotateFromSelectionOrigin { 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 CanRotateFromPlayfieldOrigin { get; private set; } = new BindableBool(); + public Bindable CanRotateAroundPlayfieldOrigin { get; private set; } = new BindableBool(); /// /// Performs a single, instant, atomic rotation operation. From 4a8273b6ed8defecef61bcf6e77a937372a1de4f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 29 May 2024 09:43:09 +0200 Subject: [PATCH 14/20] Rename another method --- osu.Game.Rulesets.Osu/Edit/OsuSelectionScaleHandler.cs | 4 ++-- osu.Game.Rulesets.Osu/Edit/PreciseScalePopover.cs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/OsuSelectionScaleHandler.cs b/osu.Game.Rulesets.Osu/Edit/OsuSelectionScaleHandler.cs index 9d1c3bc78f..de00aa6ad3 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuSelectionScaleHandler.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuSelectionScaleHandler.cs @@ -104,7 +104,7 @@ namespace osu.Game.Rulesets.Osu.Edit } else { - scale = GetClampedScale(scale, actualOrigin); + scale = ClampScaleToPlayfieldBounds(scale, actualOrigin); foreach (var (ho, originalState) in objectsInScale) { @@ -173,7 +173,7 @@ namespace osu.Game.Rulesets.Osu.Edit /// The origin from which the scale operation is performed /// The scale to be clamped /// The clamped scale vector - public Vector2 GetClampedScale(Vector2 scale, Vector2? origin = null) + 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) diff --git a/osu.Game.Rulesets.Osu/Edit/PreciseScalePopover.cs b/osu.Game.Rulesets.Osu/Edit/PreciseScalePopover.cs index b792baf428..b7202b9310 100644 --- a/osu.Game.Rulesets.Osu/Edit/PreciseScalePopover.cs +++ b/osu.Game.Rulesets.Osu/Edit/PreciseScalePopover.cs @@ -163,7 +163,7 @@ namespace osu.Game.Rulesets.Osu.Edit return; const float max_scale = 10; - var scale = scaleHandler.GetClampedScale(new Vector2(max_scale), getOriginPosition(scaleInfo.Value)); + var scale = scaleHandler.ClampScaleToPlayfieldBounds(new Vector2(max_scale), getOriginPosition(scaleInfo.Value)); if (!scaleInfo.Value.XAxis) scale.X = max_scale; From bd5060965f4a9e0fcbed9fc6044126e66e5d36d7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 29 May 2024 09:49:16 +0200 Subject: [PATCH 15/20] Simplify toolbox button enable logic --- .../Edit/TransformToolboxGroup.cs | 38 +++++-------------- 1 file changed, 9 insertions(+), 29 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/TransformToolboxGroup.cs b/osu.Game.Rulesets.Osu/Edit/TransformToolboxGroup.cs index 4da1593fb7..278d38b2d9 100644 --- a/osu.Game.Rulesets.Osu/Edit/TransformToolboxGroup.cs +++ b/osu.Game.Rulesets.Osu/Edit/TransformToolboxGroup.cs @@ -18,8 +18,8 @@ namespace osu.Game.Rulesets.Osu.Edit { public partial class TransformToolboxGroup : EditorToolboxGroup, IKeyBindingHandler { - private readonly Bindable canRotate = new BindableBool(); - private readonly Bindable canScale = 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 EditorToolButton scaleButton = null!; @@ -63,37 +63,17 @@ namespace osu.Game.Rulesets.Osu.Edit { base.LoadComplete(); - // aggregate two values into canRotate - canRotatePlayfieldOrigin = RotationHandler.CanRotateAroundPlayfieldOrigin.GetBoundCopy(); - canRotatePlayfieldOrigin.BindValueChanged(_ => updateCanRotateAggregate()); + canRotate.AddSource(RotationHandler.CanRotateAroundPlayfieldOrigin); + canRotate.AddSource(RotationHandler.CanRotateAroundSelectionOrigin); - canRotateSelectionOrigin = RotationHandler.CanRotateAroundSelectionOrigin.GetBoundCopy(); - canRotateSelectionOrigin.BindValueChanged(_ => updateCanRotateAggregate()); - - void updateCanRotateAggregate() - { - canRotate.Value = RotationHandler.CanRotateAroundPlayfieldOrigin.Value || RotationHandler.CanRotateAroundSelectionOrigin.Value; - } - - // aggregate three values into canScale - canScaleX = ScaleHandler.CanScaleX.GetBoundCopy(); - canScaleX.BindValueChanged(_ => updateCanScaleAggregate()); - - canScaleY = ScaleHandler.CanScaleY.GetBoundCopy(); - canScaleY.BindValueChanged(_ => updateCanScaleAggregate()); - - canScalePlayfieldOrigin = ScaleHandler.CanScaleFromPlayfieldOrigin.GetBoundCopy(); - canScalePlayfieldOrigin.BindValueChanged(_ => updateCanScaleAggregate()); - - void updateCanScaleAggregate() - { - canScale.Value = ScaleHandler.CanScaleX.Value || ScaleHandler.CanScaleY.Value || ScaleHandler.CanScaleFromPlayfieldOrigin.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); - canScale.BindValueChanged(_ => scaleButton.Enabled.Value = canScale.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) From 96a8bdf92064f04c00120a3274b451cf9ebd1ea9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 29 May 2024 09:59:19 +0200 Subject: [PATCH 16/20] Use more generic tooltip copy --- osu.Game.Rulesets.Osu/Edit/PreciseScalePopover.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/PreciseScalePopover.cs b/osu.Game.Rulesets.Osu/Edit/PreciseScalePopover.cs index b7202b9310..ac1b40e235 100644 --- a/osu.Game.Rulesets.Osu/Edit/PreciseScalePopover.cs +++ b/osu.Game.Rulesets.Osu/Edit/PreciseScalePopover.cs @@ -99,11 +99,11 @@ namespace osu.Game.Rulesets.Osu.Edit }; playfieldCentreButton.Selected.DisabledChanged += isDisabled => { - playfieldCentreButton.TooltipText = isDisabled ? "Select something other than a single slider to perform playfield-based scaling." : string.Empty; + playfieldCentreButton.TooltipText = isDisabled ? "The current selection cannot be scaled relative to playfield centre." : string.Empty; }; selectionCentreButton.Selected.DisabledChanged += isDisabled => { - selectionCentreButton.TooltipText = isDisabled ? "Select more than one object to perform selection-based scaling." : string.Empty; + selectionCentreButton.TooltipText = isDisabled ? "The current selection cannot be scaled relative to its centre." : string.Empty; }; } From ba4073735649eaf4a581ce3f0ae1f566fab38ddc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 29 May 2024 10:01:04 +0200 Subject: [PATCH 17/20] Simplify logic --- osu.Game.Rulesets.Osu/Edit/PreciseScalePopover.cs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/PreciseScalePopover.cs b/osu.Game.Rulesets.Osu/Edit/PreciseScalePopover.cs index ac1b40e235..d3e1b491b0 100644 --- a/osu.Game.Rulesets.Osu/Edit/PreciseScalePopover.cs +++ b/osu.Game.Rulesets.Osu/Edit/PreciseScalePopover.cs @@ -124,10 +124,7 @@ namespace osu.Game.Rulesets.Osu.Edit selectionCentreButton.Selected.Disabled = !(scaleHandler.CanScaleX.Value || scaleHandler.CanScaleY.Value); playfieldCentreButton.Selected.Disabled = scaleHandler.IsScalingSlider.Value && !selectionCentreButton.Selected.Disabled; - if (playfieldCentreButton.Selected.Disabled) - scaleOrigin.Items.Last().Select(); - else - scaleOrigin.Items.First().Select(); + scaleOrigin.Items.First(b => !b.Selected.Disabled).Select(); scaleInfo.BindValueChanged(scale => { From 9bd4b0d61303fb0dde3892d51b595174a61a2b4d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 29 May 2024 10:04:49 +0200 Subject: [PATCH 18/20] Rename method --- .../Edit/PreciseScalePopover.cs | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/PreciseScalePopover.cs b/osu.Game.Rulesets.Osu/Edit/PreciseScalePopover.cs index d3e1b491b0..a299eebbce 100644 --- a/osu.Game.Rulesets.Osu/Edit/PreciseScalePopover.cs +++ b/osu.Game.Rulesets.Osu/Edit/PreciseScalePopover.cs @@ -137,21 +137,23 @@ namespace osu.Game.Rulesets.Osu.Edit { if (scaleInfo.Value.Origin == ScaleOrigin.PlayfieldCentre) { - setBindableEnabled(true, xCheckBox.Current); - setBindableEnabled(true, yCheckBox.Current); + toggleAxisAvailable(xCheckBox.Current, true); + toggleAxisAvailable(yCheckBox.Current, true); } else { - setBindableEnabled(scaleHandler.CanScaleX.Value, xCheckBox.Current); - setBindableEnabled(scaleHandler.CanScaleY.Value, yCheckBox.Current); + toggleAxisAvailable(xCheckBox.Current, scaleHandler.CanScaleX.Value); + toggleAxisAvailable(yCheckBox.Current, scaleHandler.CanScaleY.Value); } } - private void setBindableEnabled(bool enabled, Bindable current) + private void toggleAxisAvailable(Bindable axisBindable, bool available) { - current.Disabled = false; // enable the bindable to allow setting the value - current.Value = enabled; - current.Disabled = !enabled; + // 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() From 9477e3b67de6f33a6866d9761b14a2cda20785b7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 29 May 2024 10:14:47 +0200 Subject: [PATCH 19/20] Change editor scale hotkey to Ctrl-E Forgot that Ctrl-T was taken by the game-global toolbar already, so it wasn't working. --- osu.Game/Input/Bindings/GlobalActionContainer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Input/Bindings/GlobalActionContainer.cs b/osu.Game/Input/Bindings/GlobalActionContainer.cs index fd8e6fd6d0..2af564d8ba 100644 --- a/osu.Game/Input/Bindings/GlobalActionContainer.cs +++ b/osu.Game/Input/Bindings/GlobalActionContainer.cs @@ -142,7 +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.T }, GlobalAction.EditorToggleScaleControl), + new KeyBinding(new[] { InputKey.Control, InputKey.E }, GlobalAction.EditorToggleScaleControl), }; private static IEnumerable inGameKeyBindings => new[] From 84513343d6789cf38bd2d8e355a233e277382f19 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 29 May 2024 10:18:22 +0200 Subject: [PATCH 20/20] Remove unused fields --- osu.Game.Rulesets.Osu/Edit/TransformToolboxGroup.cs | 7 ------- 1 file changed, 7 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/TransformToolboxGroup.cs b/osu.Game.Rulesets.Osu/Edit/TransformToolboxGroup.cs index 278d38b2d9..28d0f8320f 100644 --- a/osu.Game.Rulesets.Osu/Edit/TransformToolboxGroup.cs +++ b/osu.Game.Rulesets.Osu/Edit/TransformToolboxGroup.cs @@ -24,13 +24,6 @@ namespace osu.Game.Rulesets.Osu.Edit private EditorToolButton rotateButton = null!; private EditorToolButton scaleButton = null!; - private Bindable canRotatePlayfieldOrigin = null!; - private Bindable canRotateSelectionOrigin = null!; - - private Bindable canScaleX = null!; - private Bindable canScaleY = null!; - private Bindable canScalePlayfieldOrigin = null!; - public SelectionRotationHandler RotationHandler { get; init; } = null!; public OsuSelectionScaleHandler ScaleHandler { get; init; } = null!;