diff --git a/osu.Game/Localisation/EditorStrings.cs b/osu.Game/Localisation/EditorStrings.cs index f4e23ae7cb..7c9b52275d 100644 --- a/osu.Game/Localisation/EditorStrings.cs +++ b/osu.Game/Localisation/EditorStrings.cs @@ -99,6 +99,16 @@ public static class EditorStrings /// public static LocalisableString TimelineTicks => new TranslatableString(getKey(@"timeline_ticks"), @"Ticks"); + /// + /// "{0:0.0}°" + /// + public static LocalisableString RotationUnsnapped(float newRotation) => new TranslatableString(getKey(@"rotation_unsnapped"), @"{0:0.0}°", newRotation); + + /// + /// "{0:0.0}° (snapped)" + /// + public static LocalisableString RotationSnapped(float newRotation) => new TranslatableString(getKey(@"rotation_snapped"), @"{0:0.0}° (snapped)", newRotation); + private static string getKey(string key) => $@"{prefix}:{key}"; } } diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionBoxRotationHandle.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionBoxRotationHandle.cs index 0f702e1c49..305f5bf3c4 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionBoxRotationHandle.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionBoxRotationHandle.cs @@ -7,14 +7,15 @@ using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Extensions.EnumExtensions; -using osu.Framework.Extensions.LocalisationExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Cursor; using osu.Framework.Graphics.Sprites; using osu.Framework.Input.Events; using osu.Framework.Localisation; +using osu.Game.Localisation; using osuTK; using osuTK.Graphics; +using Key = osuTK.Input.Key; namespace osu.Game.Screens.Edit.Compose.Components { @@ -26,6 +27,8 @@ public partial class SelectionBoxRotationHandle : SelectionBoxDragHandle, IHasTo private SpriteIcon icon; + private const float snap_step = 15; + private readonly Bindable cumulativeRotation = new Bindable(); [Resolved] @@ -50,18 +53,14 @@ private void load() }); } - protected override void LoadComplete() - { - base.LoadComplete(); - cumulativeRotation.BindValueChanged(_ => updateTooltipText(), true); - } - protected override void UpdateHoverState() { base.UpdateHoverState(); icon.FadeColour(!IsHeld && IsHovered ? Color4.White : Color4.Black, TRANSFORM_DURATION, Easing.OutQuint); } + private float rawCumulativeRotation; + protected override bool OnDragStart(DragStartEvent e) { bool handle = base.OnDragStart(e); @@ -74,21 +73,36 @@ protected override void OnDrag(DragEvent e) { base.OnDrag(e); - float instantaneousAngle = convertDragEventToAngleOfRotation(e); - cumulativeRotation.Value += instantaneousAngle; + rawCumulativeRotation += convertDragEventToAngleOfRotation(e); - if (cumulativeRotation.Value < -180) - cumulativeRotation.Value += 360; - else if (cumulativeRotation.Value > 180) - cumulativeRotation.Value -= 360; + applyRotation(shouldSnap: e.ShiftPressed); + } - HandleRotate?.Invoke(instantaneousAngle); + protected override bool OnKeyDown(KeyDownEvent e) + { + if (IsDragged && (e.Key == Key.ShiftLeft || e.Key == Key.ShiftRight)) + { + applyRotation(shouldSnap: true); + return true; + } + + return base.OnKeyDown(e); + } + + protected override void OnKeyUp(KeyUpEvent e) + { + base.OnKeyUp(e); + + if (IsDragged && (e.Key == Key.ShiftLeft || e.Key == Key.ShiftRight)) + applyRotation(shouldSnap: false); } protected override void OnDragEnd(DragEndEvent e) { base.OnDragEnd(e); cumulativeRotation.Value = null; + rawCumulativeRotation = 0; + TooltipText = default; } private float convertDragEventToAngleOfRotation(DragEvent e) @@ -100,9 +114,19 @@ private float convertDragEventToAngleOfRotation(DragEvent e) return (endAngle - startAngle) * 180 / MathF.PI; } - private void updateTooltipText() + private void applyRotation(bool shouldSnap) { - TooltipText = cumulativeRotation.Value?.ToLocalisableString("0.0°") ?? default; + float oldRotation = cumulativeRotation.Value ?? 0; + + float newRotation = shouldSnap ? snap(rawCumulativeRotation, snap_step) : rawCumulativeRotation; + newRotation = (newRotation - 180) % 360 + 180; + + cumulativeRotation.Value = newRotation; + + HandleRotate?.Invoke(newRotation - oldRotation); + TooltipText = shouldSnap ? EditorStrings.RotationSnapped(newRotation) : EditorStrings.RotationUnsnapped(newRotation); } + + private float snap(float value, float step) => MathF.Round(value / step) * step; } }