From 6aa92bcc4559ec52a85e8e026e579c03f1d7aca0 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Sat, 25 May 2024 18:31:19 +0200 Subject: [PATCH] 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" ///