1
0
mirror of https://github.com/ppy/osu.git synced 2026-05-19 06:31:19 +08:00
Files
osu-lazer/osu.Game.Rulesets.Osu/Edit/PreciseMovementPopover.cs
T
De4n e05b6f44b9 Update editor slider controls to new design (#36346)
(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"
/>|
2026-01-16 20:08:52 +09:00

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);
}
}
}