mirror of
https://github.com/ppy/osu.git
synced 2026-05-19 06:31:19 +08:00
e05b6f44b9
(partially) Closes: #36233 Surpasses: #36244 This PR meant to be one of the last steps that finally make editor use the new forms. Initially it meant to only change one SliderWithTextBoxInput in "Effects section" in timing screen, however soon after it was obvious that there's many other places that still using it. This currently won't affect IndeterminateSliderWithTextBoxInput that is being used in hitsounds, for example, since I think it needs more consideration. Anyways, with this PR, SliderWithTextBoxInput, will no longer be used at all, as it's going to be replaced with modern FormSliderBar Comparison: |master|this PR| |:---:|:---:| |<img width="510" height="316" alt="532203751-eb965923-d3a8-441d-a7c8-5c364a6328ad" src="https://github.com/user-attachments/assets/268b45b8-e235-494f-91a5-d00db057dba8" />|<img width="540" height="321" alt="535466527-3a700a8b-bc3c-4610-998f-a4e55ee03eed" src="https://github.com/user-attachments/assets/20cd4b58-b0bd-49bc-8c48-7de5cf8556b3" />| |<img width="694" height="639" alt="534509844-f00e4da4-53c4-45e8-80ea-1be62da6c83b" src="https://github.com/user-attachments/assets/398c4484-a867-4df1-9de3-0940aa748a01" />|<img width="720" height="433" alt="изображение" src="https://github.com/user-attachments/assets/b6359443-a224-4a55-b171-07e8f013cf46" />| |<img width="715" height="353" alt="534509421-a6ac950f-16e8-4a16-bca6-1a781f82135f" src="https://github.com/user-attachments/assets/4854312b-772f-4b81-a800-89e58d4c715d" />|<img width="710" height="296" alt="изображение" src="https://github.com/user-attachments/assets/a7fed53e-e006-4285-92c9-bb84cb603f60" />| |<img width="717" height="374" alt="534509478-80222623-7766-481d-8682-088276d415ee" src="https://github.com/user-attachments/assets/8143b6dc-4599-45d5-bd3b-f059caf3d93d" />|<img width="718" height="328" alt="изображение" src="https://github.com/user-attachments/assets/bffa04de-983c-45ae-a1ec-373701ea0e49" />| |<img width="702" height="446" alt="534509935-58954060-7ac1-4392-8754-a58f909e86aa" src="https://github.com/user-attachments/assets/2bb67a2d-3f57-42a1-96ce-b30b4891e1a4" />|<img width="722" height="386" alt="изображение" src="https://github.com/user-attachments/assets/01b7fff4-7f31-4aac-90c9-353b15f4964e" />|
209 lines
7.9 KiB
C#
209 lines
7.9 KiB
C#
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
|
// See the LICENCE file in the repository root for full licence text.
|
|
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Diagnostics;
|
|
using System.Linq;
|
|
using osu.Framework.Allocation;
|
|
using osu.Framework.Bindables;
|
|
using osu.Framework.Extensions;
|
|
using osu.Game.Graphics.UserInterfaceV2;
|
|
using osu.Framework.Graphics;
|
|
using osu.Framework.Graphics.Containers;
|
|
using osu.Framework.Graphics.Primitives;
|
|
using osu.Framework.Input.Events;
|
|
using osu.Game.Extensions;
|
|
using osu.Game.Graphics.UserInterface;
|
|
using osu.Game.Input.Bindings;
|
|
using osu.Game.Rulesets.Objects;
|
|
using osu.Game.Rulesets.Objects.Types;
|
|
using osu.Game.Rulesets.Osu.Objects;
|
|
using osu.Game.Rulesets.Osu.UI;
|
|
using osu.Game.Screens.Edit;
|
|
using osu.Game.Utils;
|
|
using osuTK;
|
|
|
|
namespace osu.Game.Rulesets.Osu.Edit
|
|
{
|
|
public partial class PreciseMovementPopover : OsuPopover
|
|
{
|
|
[Resolved]
|
|
private EditorBeatmap editorBeatmap { get; set; } = null!;
|
|
|
|
private readonly Dictionary<HitObject, Vector2> initialPositions = new Dictionary<HitObject, Vector2>();
|
|
private RectangleF initialSurroundingQuad;
|
|
|
|
private BindableNumber<float> xBindable = null!;
|
|
private BindableNumber<float> yBindable = null!;
|
|
|
|
private FormSliderBar<float> xInput { get; set; } = null!;
|
|
private OsuCheckbox relativeCheckbox = null!;
|
|
|
|
public PreciseMovementPopover()
|
|
{
|
|
AllowableAnchors = new[] { Anchor.CentreLeft, Anchor.CentreRight };
|
|
}
|
|
|
|
[BackgroundDependencyLoader]
|
|
private void load()
|
|
{
|
|
Child = new FillFlowContainer
|
|
{
|
|
Width = 220,
|
|
AutoSizeAxes = Axes.Y,
|
|
Spacing = new Vector2(5),
|
|
Children = new Drawable[]
|
|
{
|
|
xInput = new FormSliderBar<float>
|
|
{
|
|
Caption = "X",
|
|
Current = xBindable = new BindableNumber<float>
|
|
{
|
|
Precision = 1,
|
|
},
|
|
TabbableContentContainer = this
|
|
},
|
|
new FormSliderBar<float>
|
|
{
|
|
Caption = "Y",
|
|
Current = yBindable = new BindableNumber<float>
|
|
{
|
|
Precision = 1,
|
|
},
|
|
TabbableContentContainer = this
|
|
},
|
|
relativeCheckbox = new OsuCheckbox(false)
|
|
{
|
|
RelativeSizeAxes = Axes.X,
|
|
LabelText = "Relative movement"
|
|
}
|
|
}
|
|
};
|
|
}
|
|
|
|
protected override void LoadComplete()
|
|
{
|
|
base.LoadComplete();
|
|
|
|
ScheduleAfterChildren(() => xInput.TakeFocus());
|
|
}
|
|
|
|
protected override void PopIn()
|
|
{
|
|
base.PopIn();
|
|
editorBeatmap.BeginChange();
|
|
initialPositions.AddRange(editorBeatmap.SelectedHitObjects.Where(ho => ho is not Spinner).Select(ho => new KeyValuePair<HitObject, Vector2>(ho, ((IHasPosition)ho).Position)));
|
|
initialSurroundingQuad = GeometryUtils.GetSurroundingQuad(initialPositions.Keys.Cast<IHasPosition>()).AABBFloat;
|
|
|
|
Debug.Assert(initialPositions.Count > 0);
|
|
|
|
if (initialPositions.Count > 1)
|
|
{
|
|
relativeCheckbox.Current.Value = true;
|
|
relativeCheckbox.Current.Disabled = true;
|
|
}
|
|
|
|
relativeCheckbox.Current.BindValueChanged(_ => relativeChanged(), true);
|
|
xBindable.BindValueChanged(_ => applyPosition());
|
|
yBindable.BindValueChanged(_ => applyPosition());
|
|
}
|
|
|
|
protected override void PopOut()
|
|
{
|
|
base.PopOut();
|
|
if (IsLoaded) editorBeatmap.EndChange();
|
|
}
|
|
|
|
private void relativeChanged()
|
|
{
|
|
// reset bindable bounds to something that is guaranteed to be larger than any previous value.
|
|
// this prevents crashes that can happen in the middle of changing the bounds, as updating both bound ends at the same is not atomic -
|
|
// if the old and new bounds are disjoint, assigning X first can produce a situation where MinValue > MaxValue.
|
|
(xBindable.MinValue, xBindable.MaxValue) = (float.MinValue, float.MaxValue);
|
|
(yBindable.MinValue, yBindable.MaxValue) = (float.MinValue, float.MaxValue);
|
|
|
|
float previousX = xBindable.Value;
|
|
float previousY = yBindable.Value;
|
|
|
|
if (relativeCheckbox.Current.Value)
|
|
{
|
|
xBindable.MinValue = 0 - Math.Max(initialSurroundingQuad.TopLeft.X, 0);
|
|
xBindable.MaxValue = OsuPlayfield.BASE_SIZE.X - Math.Min(initialSurroundingQuad.BottomRight.X, OsuPlayfield.BASE_SIZE.X);
|
|
|
|
yBindable.MinValue = 0 - Math.Max(initialSurroundingQuad.TopLeft.Y, 0);
|
|
yBindable.MaxValue = OsuPlayfield.BASE_SIZE.Y - Math.Min(initialSurroundingQuad.BottomRight.Y, OsuPlayfield.BASE_SIZE.Y);
|
|
|
|
xBindable.Default = yBindable.Default = 0;
|
|
|
|
if (initialPositions.Count == 1)
|
|
{
|
|
var initialPosition = initialPositions.Single().Value;
|
|
xBindable.Value = previousX - initialPosition.X;
|
|
yBindable.Value = previousY - initialPosition.Y;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
Debug.Assert(initialPositions.Count == 1);
|
|
var initialPosition = initialPositions.Single().Value;
|
|
|
|
var quadRelativeToPosition = new RectangleF(initialSurroundingQuad.Location - initialPosition, initialSurroundingQuad.Size);
|
|
|
|
if (initialSurroundingQuad.Width < OsuPlayfield.BASE_SIZE.X)
|
|
{
|
|
xBindable.MinValue = 0 - quadRelativeToPosition.TopLeft.X;
|
|
xBindable.MaxValue = OsuPlayfield.BASE_SIZE.X - quadRelativeToPosition.BottomRight.X;
|
|
}
|
|
else
|
|
xBindable.MinValue = xBindable.MaxValue = initialPosition.X;
|
|
|
|
if (initialSurroundingQuad.Height < OsuPlayfield.BASE_SIZE.Y)
|
|
{
|
|
yBindable.MinValue = 0 - quadRelativeToPosition.TopLeft.Y;
|
|
yBindable.MaxValue = OsuPlayfield.BASE_SIZE.Y - quadRelativeToPosition.BottomRight.Y;
|
|
}
|
|
else
|
|
yBindable.MinValue = yBindable.MaxValue = initialPosition.Y;
|
|
|
|
xBindable.Default = initialPosition.X;
|
|
yBindable.Default = initialPosition.Y;
|
|
|
|
xBindable.Value = xBindable.Default + previousX;
|
|
yBindable.Value = yBindable.Default + previousY;
|
|
}
|
|
}
|
|
|
|
private void applyPosition()
|
|
{
|
|
// can happen if popover is dismissed by a keyboard key press while dragging UI controls
|
|
// it doesn't cause a crash, but it looks wrong
|
|
if (!editorBeatmap.TransactionActive)
|
|
return;
|
|
|
|
editorBeatmap.PerformOnSelection(ho =>
|
|
{
|
|
if (!initialPositions.TryGetValue(ho, out var initialPosition))
|
|
return;
|
|
|
|
var pos = new Vector2(xBindable.Value, yBindable.Value);
|
|
if (relativeCheckbox.Current.Value)
|
|
((IHasPosition)ho).Position = initialPosition + pos;
|
|
else
|
|
((IHasPosition)ho).Position = pos;
|
|
});
|
|
}
|
|
|
|
public override bool OnPressed(KeyBindingPressEvent<GlobalAction> e)
|
|
{
|
|
if (e.Action == GlobalAction.Select && !e.Repeat)
|
|
{
|
|
this.HidePopover();
|
|
return true;
|
|
}
|
|
|
|
return base.OnPressed(e);
|
|
}
|
|
}
|
|
}
|