// 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.Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; using osu.Framework.Input.Events; using osu.Game.Graphics.UserInterfaceV2; using osu.Game.Input.Bindings; 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 PreciseRotationPopover : OsuPopover { private readonly SelectionRotationHandler rotationHandler; private readonly OsuGridToolboxGroup gridToolbox; private readonly Bindable rotationInfo = new Bindable(new PreciseRotationInfo(0, RotationOrigin.GridCentre)); private SliderWithTextBoxInput angleInput = null!; private EditorRadioButtonCollection rotationOrigin = null!; private RadioButton selectionCentreButton = null!; public PreciseRotationPopover(SelectionRotationHandler rotationHandler, OsuGridToolboxGroup gridToolbox) { this.rotationHandler = rotationHandler; this.gridToolbox = gridToolbox; 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[] { angleInput = new SliderWithTextBoxInput("Angle (degrees):") { Current = new BindableNumber { MinValue = -360, MaxValue = 360, Precision = 1 }, KeyboardStep = 1f, Instantaneous = true }, rotationOrigin = new EditorRadioButtonCollection { RelativeSizeAxes = Axes.X, Items = new[] { new RadioButton("Grid centre", () => rotationInfo.Value = rotationInfo.Value with { Origin = RotationOrigin.GridCentre }, () => new SpriteIcon { Icon = FontAwesome.Regular.PlusSquare }), new RadioButton("Playfield centre", () => rotationInfo.Value = rotationInfo.Value with { Origin = RotationOrigin.PlayfieldCentre }, () => new SpriteIcon { Icon = FontAwesome.Regular.Square }), selectionCentreButton = new RadioButton("Selection centre", () => rotationInfo.Value = rotationInfo.Value with { Origin = RotationOrigin.SelectionCentre }, () => new SpriteIcon { Icon = FontAwesome.Solid.VectorSquare }) } } } }; selectionCentreButton.Selected.DisabledChanged += isDisabled => { selectionCentreButton.TooltipText = isDisabled ? "Select more than one object to perform selection-based rotation." : string.Empty; }; } protected override void LoadComplete() { base.LoadComplete(); ScheduleAfterChildren(() => { angleInput.TakeFocus(); angleInput.SelectAll(); }); angleInput.Current.BindValueChanged(angle => rotationInfo.Value = rotationInfo.Value with { Degrees = angle.NewValue }); rotationOrigin.Items.First().Select(); rotationHandler.CanRotateAroundSelectionOrigin.BindValueChanged(e => { selectionCentreButton.Selected.Disabled = !e.NewValue; }, true); rotationInfo.BindValueChanged(rotation => { rotationHandler.Update(rotation.NewValue.Degrees, getOriginPosition(rotation.NewValue)); }); } private Vector2? getOriginPosition(PreciseRotationInfo rotation) => rotation.Origin switch { RotationOrigin.GridCentre => gridToolbox.StartPosition.Value, RotationOrigin.PlayfieldCentre => OsuPlayfield.BASE_SIZE / 2, RotationOrigin.SelectionCentre => null, _ => throw new ArgumentOutOfRangeException(nameof(rotation)) }; protected override void PopIn() { base.PopIn(); rotationHandler.Begin(); } protected override void PopOut() { base.PopOut(); if (IsLoaded) rotationHandler.Commit(); } public override bool OnPressed(KeyBindingPressEvent e) { if (e.Action == GlobalAction.Select && !e.Repeat) { this.HidePopover(); return true; } return base.OnPressed(e); } } public enum RotationOrigin { GridCentre, PlayfieldCentre, SelectionCentre } public record PreciseRotationInfo(float Degrees, RotationOrigin Origin); }