mirror of
https://github.com/ppy/osu.git
synced 2026-05-19 12:40:50 +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" />|
360 lines
13 KiB
C#
360 lines
13 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.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.Configuration;
|
|
using osu.Game.Graphics.UserInterface;
|
|
using osu.Game.Graphics.UserInterfaceV2;
|
|
using osu.Game.Input.Bindings;
|
|
using osu.Game.Rulesets.Objects;
|
|
using osu.Game.Rulesets.Osu.Objects;
|
|
using osu.Game.Rulesets.Osu.UI;
|
|
using osu.Game.Screens.Edit;
|
|
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 OsuSelectionScaleHandler scaleHandler;
|
|
|
|
private readonly OsuGridToolboxGroup gridToolbox;
|
|
|
|
private readonly Bindable<PreciseScaleInfo> scaleInfo = new Bindable<PreciseScaleInfo>(new PreciseScaleInfo(1, EditorOrigin.GridCentre, true, true));
|
|
|
|
private FormSliderBar<float> scaleInput { get; set; } = null!;
|
|
private BindableNumber<float> scaleInputBindable = null!;
|
|
private EditorRadioButtonCollection scaleOrigin = null!;
|
|
|
|
private RadioButton gridCentreButton = null!;
|
|
private RadioButton playfieldCentreButton = null!;
|
|
private RadioButton selectionCentreButton = null!;
|
|
|
|
private OsuCheckbox xCheckBox = null!;
|
|
private OsuCheckbox yCheckBox = null!;
|
|
|
|
private Bindable<EditorOrigin> configScaleOrigin = null!;
|
|
|
|
private BindableList<HitObject> selectedItems { get; } = new BindableList<HitObject>();
|
|
|
|
public PreciseScalePopover(OsuSelectionScaleHandler scaleHandler, OsuGridToolboxGroup gridToolbox)
|
|
{
|
|
this.scaleHandler = scaleHandler;
|
|
this.gridToolbox = gridToolbox;
|
|
|
|
AllowableAnchors = new[] { Anchor.CentreLeft, Anchor.CentreRight };
|
|
}
|
|
|
|
[BackgroundDependencyLoader]
|
|
private void load(EditorBeatmap editorBeatmap, OsuConfigManager config)
|
|
{
|
|
selectedItems.BindTo(editorBeatmap.SelectedHitObjects);
|
|
|
|
configScaleOrigin = config.GetBindable<EditorOrigin>(OsuSetting.EditorScaleOrigin);
|
|
|
|
Child = new FillFlowContainer
|
|
{
|
|
Width = 220,
|
|
AutoSizeAxes = Axes.Y,
|
|
Spacing = new Vector2(5),
|
|
Children = new Drawable[]
|
|
{
|
|
scaleInput = new FormSliderBar<float>
|
|
{
|
|
Caption = "Scale",
|
|
Current = scaleInputBindable = new BindableNumber<float>
|
|
{
|
|
MinValue = 0.05f,
|
|
MaxValue = 2,
|
|
Precision = 0.001f,
|
|
Value = 1,
|
|
Default = 1,
|
|
},
|
|
KeyboardStep = 0.01f,
|
|
TabbableContentContainer = this
|
|
},
|
|
scaleOrigin = new EditorRadioButtonCollection
|
|
{
|
|
RelativeSizeAxes = Axes.X,
|
|
Items = new[]
|
|
{
|
|
gridCentreButton = new RadioButton("Grid centre",
|
|
() => setOrigin(EditorOrigin.GridCentre),
|
|
() => new SpriteIcon { Icon = FontAwesome.Regular.PlusSquare }),
|
|
playfieldCentreButton = new RadioButton("Playfield centre",
|
|
() => setOrigin(EditorOrigin.PlayfieldCentre),
|
|
() => new SpriteIcon { Icon = FontAwesome.Regular.Square }),
|
|
selectionCentreButton = new RadioButton("Selection centre",
|
|
() => setOrigin(EditorOrigin.SelectionCentre),
|
|
() => new SpriteIcon { Icon = FontAwesome.Solid.VectorSquare })
|
|
}
|
|
},
|
|
new FillFlowContainer
|
|
{
|
|
RelativeSizeAxes = Axes.X,
|
|
AutoSizeAxes = Axes.Y,
|
|
Spacing = new Vector2(4),
|
|
Children = new Drawable[]
|
|
{
|
|
xCheckBox = new OsuCheckbox(false)
|
|
{
|
|
RelativeSizeAxes = Axes.X,
|
|
LabelText = "X-axis",
|
|
Current = { Value = true },
|
|
},
|
|
yCheckBox = new OsuCheckbox(false)
|
|
{
|
|
RelativeSizeAxes = Axes.X,
|
|
LabelText = "Y-axis",
|
|
Current = { Value = true },
|
|
},
|
|
}
|
|
},
|
|
}
|
|
};
|
|
gridCentreButton.Selected.DisabledChanged += isDisabled =>
|
|
{
|
|
gridCentreButton.TooltipText = isDisabled ? "The current selection cannot be scaled relative to grid centre." : string.Empty;
|
|
};
|
|
playfieldCentreButton.Selected.DisabledChanged += isDisabled =>
|
|
{
|
|
playfieldCentreButton.TooltipText = isDisabled ? "The current selection cannot be scaled relative to playfield centre." : string.Empty;
|
|
};
|
|
selectionCentreButton.Selected.DisabledChanged += isDisabled =>
|
|
{
|
|
selectionCentreButton.TooltipText = isDisabled ? "The current selection cannot be scaled relative to its centre." : string.Empty;
|
|
};
|
|
}
|
|
|
|
protected override void LoadComplete()
|
|
{
|
|
base.LoadComplete();
|
|
|
|
ScheduleAfterChildren(() => scaleInput.TakeFocus());
|
|
scaleInput.Current.BindValueChanged(scale => scaleInfo.Value = scaleInfo.Value with { Scale = scale.NewValue });
|
|
|
|
xCheckBox.Current.BindValueChanged(_ =>
|
|
{
|
|
if (!xCheckBox.Current.Value && !yCheckBox.Current.Value)
|
|
{
|
|
yCheckBox.Current.Value = true;
|
|
return;
|
|
}
|
|
|
|
updateAxes();
|
|
});
|
|
yCheckBox.Current.BindValueChanged(_ =>
|
|
{
|
|
if (!xCheckBox.Current.Value && !yCheckBox.Current.Value)
|
|
{
|
|
xCheckBox.Current.Value = true;
|
|
return;
|
|
}
|
|
|
|
updateAxes();
|
|
});
|
|
|
|
selectionCentreButton.Selected.Disabled = !(scaleHandler.CanScaleX.Value || scaleHandler.CanScaleY.Value);
|
|
playfieldCentreButton.Selected.Disabled = scaleHandler.IsScalingSlider.Value && !selectionCentreButton.Selected.Disabled;
|
|
gridCentreButton.Selected.Disabled = playfieldCentreButton.Selected.Disabled;
|
|
|
|
bool didSelect = false;
|
|
|
|
configScaleOrigin.BindValueChanged(val =>
|
|
{
|
|
switch (configScaleOrigin.Value)
|
|
{
|
|
case EditorOrigin.GridCentre:
|
|
if (!gridCentreButton.Selected.Disabled)
|
|
{
|
|
gridCentreButton.Select();
|
|
didSelect = true;
|
|
}
|
|
|
|
break;
|
|
|
|
case EditorOrigin.PlayfieldCentre:
|
|
if (!playfieldCentreButton.Selected.Disabled)
|
|
{
|
|
playfieldCentreButton.Select();
|
|
didSelect = true;
|
|
}
|
|
|
|
break;
|
|
|
|
case EditorOrigin.SelectionCentre:
|
|
if (!selectionCentreButton.Selected.Disabled)
|
|
{
|
|
selectionCentreButton.Select();
|
|
didSelect = true;
|
|
}
|
|
|
|
break;
|
|
}
|
|
}, true);
|
|
|
|
if (!didSelect)
|
|
scaleOrigin.Items.First(b => !b.Selected.Disabled).Select();
|
|
|
|
gridCentreButton.Selected.BindValueChanged(b =>
|
|
{
|
|
if (b.NewValue) configScaleOrigin.Value = EditorOrigin.GridCentre;
|
|
});
|
|
playfieldCentreButton.Selected.BindValueChanged(b =>
|
|
{
|
|
if (b.NewValue) configScaleOrigin.Value = EditorOrigin.PlayfieldCentre;
|
|
});
|
|
selectionCentreButton.Selected.BindValueChanged(b =>
|
|
{
|
|
if (b.NewValue) configScaleOrigin.Value = EditorOrigin.SelectionCentre;
|
|
});
|
|
|
|
scaleInfo.BindValueChanged(scale =>
|
|
{
|
|
// can happen if the popover is dismissed by a keyboard key press while dragging UI controls
|
|
if (!scaleHandler.OperationInProgress.Value)
|
|
return;
|
|
|
|
var newScale = new Vector2(scale.NewValue.Scale, scale.NewValue.Scale);
|
|
scaleHandler.Update(newScale, getOriginPosition(scale.NewValue), getAdjustAxis(scale.NewValue), getRotation(scale.NewValue));
|
|
});
|
|
}
|
|
|
|
private void updateAxes()
|
|
{
|
|
scaleInfo.Value = scaleInfo.Value with { XAxis = xCheckBox.Current.Value, YAxis = yCheckBox.Current.Value };
|
|
updateMinMaxScale();
|
|
}
|
|
|
|
private void updateAxisCheckBoxesEnabled()
|
|
{
|
|
if (scaleInfo.Value.Origin != EditorOrigin.SelectionCentre)
|
|
{
|
|
toggleAxisAvailable(xCheckBox.Current, true);
|
|
toggleAxisAvailable(yCheckBox.Current, true);
|
|
}
|
|
else
|
|
{
|
|
toggleAxisAvailable(xCheckBox.Current, scaleHandler.CanScaleX.Value);
|
|
toggleAxisAvailable(yCheckBox.Current, scaleHandler.CanScaleY.Value);
|
|
}
|
|
}
|
|
|
|
private void toggleAxisAvailable(Bindable<bool> axisBindable, bool available)
|
|
{
|
|
// enable the bindable to allow setting the value
|
|
axisBindable.Disabled = false;
|
|
// restore the presumed default value given the axis's new availability state
|
|
axisBindable.Value = available;
|
|
axisBindable.Disabled = !available;
|
|
}
|
|
|
|
private void updateMinMaxScale()
|
|
{
|
|
if (!scaleHandler.OriginalSurroundingQuad.HasValue)
|
|
return;
|
|
|
|
const float min_scale = 0.05f;
|
|
const float max_scale = 10;
|
|
|
|
var scale = scaleHandler.ClampScaleToPlayfieldBounds(new Vector2(max_scale), getOriginPosition(scaleInfo.Value), getAdjustAxis(scaleInfo.Value), getRotation(scaleInfo.Value));
|
|
|
|
if (!scaleInfo.Value.XAxis)
|
|
scale.X = max_scale;
|
|
if (!scaleInfo.Value.YAxis)
|
|
scale.Y = max_scale;
|
|
|
|
scaleInputBindable.MaxValue = MathF.Max(1, MathF.Min(scale.X, scale.Y));
|
|
|
|
scale = scaleHandler.ClampScaleToPlayfieldBounds(new Vector2(min_scale), getOriginPosition(scaleInfo.Value), getAdjustAxis(scaleInfo.Value), getRotation(scaleInfo.Value));
|
|
|
|
if (!scaleInfo.Value.XAxis)
|
|
scale.X = min_scale;
|
|
if (!scaleInfo.Value.YAxis)
|
|
scale.Y = min_scale;
|
|
|
|
scaleInputBindable.MinValue = MathF.Min(1, MathF.Max(scale.X, scale.Y));
|
|
}
|
|
|
|
private void setOrigin(EditorOrigin origin)
|
|
{
|
|
scaleInfo.Value = scaleInfo.Value with { Origin = origin };
|
|
updateMinMaxScale();
|
|
updateAxisCheckBoxesEnabled();
|
|
}
|
|
|
|
private Vector2? getOriginPosition(PreciseScaleInfo scale)
|
|
{
|
|
switch (scale.Origin)
|
|
{
|
|
case EditorOrigin.GridCentre:
|
|
return gridToolbox.StartPosition.Value;
|
|
|
|
case EditorOrigin.PlayfieldCentre:
|
|
return OsuPlayfield.BASE_SIZE / 2;
|
|
|
|
case EditorOrigin.SelectionCentre:
|
|
if (selectedItems.Count == 1 && selectedItems.First() is Slider slider)
|
|
return slider.Position;
|
|
|
|
return null;
|
|
|
|
default:
|
|
throw new ArgumentOutOfRangeException(nameof(scale));
|
|
}
|
|
}
|
|
|
|
private Axes getAdjustAxis(PreciseScaleInfo scale)
|
|
{
|
|
var result = Axes.None;
|
|
|
|
if (scale.XAxis)
|
|
result |= Axes.X;
|
|
|
|
if (scale.YAxis)
|
|
result |= Axes.Y;
|
|
|
|
return result;
|
|
}
|
|
|
|
private float getRotation(PreciseScaleInfo scale) => scale.Origin == EditorOrigin.GridCentre ? gridToolbox.GridLinesRotation.Value : 0;
|
|
|
|
protected override void PopIn()
|
|
{
|
|
base.PopIn();
|
|
scaleHandler.Begin();
|
|
updateMinMaxScale();
|
|
}
|
|
|
|
protected override void PopOut()
|
|
{
|
|
base.PopOut();
|
|
|
|
if (IsLoaded) scaleHandler.Commit();
|
|
}
|
|
|
|
public override bool OnPressed(KeyBindingPressEvent<GlobalAction> e)
|
|
{
|
|
if (e.Action == GlobalAction.Select && !e.Repeat)
|
|
{
|
|
this.HidePopover();
|
|
return true;
|
|
}
|
|
|
|
return base.OnPressed(e);
|
|
}
|
|
}
|
|
|
|
public record PreciseScaleInfo(float Scale, EditorOrigin Origin, bool XAxis, bool YAxis);
|
|
}
|