2024-05-26 00:31:19 +08:00
|
|
|
// 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.
|
|
|
|
|
2024-05-26 02:17:27 +08:00
|
|
|
using System;
|
2024-05-26 00:31:19 +08:00
|
|
|
using System.Linq;
|
|
|
|
using osu.Framework.Allocation;
|
|
|
|
using osu.Framework.Bindables;
|
2024-10-17 03:53:56 +08:00
|
|
|
using osu.Framework.Extensions;
|
2024-05-26 00:31:19 +08:00
|
|
|
using osu.Framework.Graphics;
|
|
|
|
using osu.Framework.Graphics.Containers;
|
|
|
|
using osu.Framework.Graphics.Sprites;
|
2024-10-17 03:53:56 +08:00
|
|
|
using osu.Framework.Input.Events;
|
2024-11-03 14:20:45 +08:00
|
|
|
using osu.Game.Configuration;
|
2024-05-26 03:44:08 +08:00
|
|
|
using osu.Game.Graphics.UserInterface;
|
2024-05-26 00:31:19 +08:00
|
|
|
using osu.Game.Graphics.UserInterfaceV2;
|
2024-10-17 03:53:56 +08:00
|
|
|
using osu.Game.Input.Bindings;
|
2024-09-20 23:38:49 +08:00
|
|
|
using osu.Game.Rulesets.Objects;
|
|
|
|
using osu.Game.Rulesets.Osu.Objects;
|
2024-07-14 23:27:04 +08:00
|
|
|
using osu.Game.Rulesets.Osu.UI;
|
2024-09-20 23:38:49 +08:00
|
|
|
using osu.Game.Screens.Edit;
|
2024-05-26 00:31:19 +08:00
|
|
|
using osu.Game.Screens.Edit.Components.RadioButtons;
|
2024-11-03 14:20:45 +08:00
|
|
|
using osu.Game.Screens.Edit.Compose.Components;
|
2024-05-26 00:31:19 +08:00
|
|
|
using osuTK;
|
|
|
|
|
|
|
|
namespace osu.Game.Rulesets.Osu.Edit
|
|
|
|
{
|
|
|
|
public partial class PreciseScalePopover : OsuPopover
|
|
|
|
{
|
2024-05-28 23:12:16 +08:00
|
|
|
private readonly OsuSelectionScaleHandler scaleHandler;
|
2024-05-26 00:31:19 +08:00
|
|
|
|
2024-07-03 18:40:22 +08:00
|
|
|
private readonly OsuGridToolboxGroup gridToolbox;
|
|
|
|
|
2024-11-03 14:20:45 +08:00
|
|
|
private readonly Bindable<PreciseScaleInfo> scaleInfo = new Bindable<PreciseScaleInfo>(new PreciseScaleInfo(1, EditorOrigin.GridCentre, true, true));
|
2024-05-26 00:31:19 +08:00
|
|
|
|
|
|
|
private SliderWithTextBoxInput<float> scaleInput = null!;
|
2024-05-26 02:17:27 +08:00
|
|
|
private BindableNumber<float> scaleInputBindable = null!;
|
2024-05-26 00:31:19 +08:00
|
|
|
private EditorRadioButtonCollection scaleOrigin = null!;
|
|
|
|
|
2024-07-14 23:27:04 +08:00
|
|
|
private RadioButton gridCentreButton = null!;
|
2024-05-29 00:27:01 +08:00
|
|
|
private RadioButton playfieldCentreButton = null!;
|
2024-05-26 00:31:19 +08:00
|
|
|
private RadioButton selectionCentreButton = null!;
|
|
|
|
|
2024-05-26 03:44:08 +08:00
|
|
|
private OsuCheckbox xCheckBox = null!;
|
|
|
|
private OsuCheckbox yCheckBox = null!;
|
|
|
|
|
2024-11-03 14:20:45 +08:00
|
|
|
private Bindable<EditorOrigin> configScaleOrigin = null!;
|
|
|
|
|
2024-09-20 23:38:49 +08:00
|
|
|
private BindableList<HitObject> selectedItems { get; } = new BindableList<HitObject>();
|
|
|
|
|
2024-07-03 18:40:22 +08:00
|
|
|
public PreciseScalePopover(OsuSelectionScaleHandler scaleHandler, OsuGridToolboxGroup gridToolbox)
|
2024-05-26 00:31:19 +08:00
|
|
|
{
|
|
|
|
this.scaleHandler = scaleHandler;
|
2024-07-03 18:40:22 +08:00
|
|
|
this.gridToolbox = gridToolbox;
|
2024-05-26 00:31:19 +08:00
|
|
|
|
|
|
|
AllowableAnchors = new[] { Anchor.CentreLeft, Anchor.CentreRight };
|
|
|
|
}
|
|
|
|
|
|
|
|
[BackgroundDependencyLoader]
|
2024-11-03 14:20:45 +08:00
|
|
|
private void load(EditorBeatmap editorBeatmap, OsuConfigManager config)
|
2024-05-26 00:31:19 +08:00
|
|
|
{
|
2024-09-20 23:38:49 +08:00
|
|
|
selectedItems.BindTo(editorBeatmap.SelectedHitObjects);
|
|
|
|
|
2024-11-03 14:20:45 +08:00
|
|
|
configScaleOrigin = config.GetBindable<EditorOrigin>(OsuSetting.EditorScaleOrigin);
|
|
|
|
|
2024-05-26 00:31:19 +08:00
|
|
|
Child = new FillFlowContainer
|
|
|
|
{
|
|
|
|
Width = 220,
|
|
|
|
AutoSizeAxes = Axes.Y,
|
|
|
|
Spacing = new Vector2(20),
|
|
|
|
Children = new Drawable[]
|
|
|
|
{
|
|
|
|
scaleInput = new SliderWithTextBoxInput<float>("Scale:")
|
|
|
|
{
|
2024-05-26 02:17:27 +08:00
|
|
|
Current = scaleInputBindable = new BindableNumber<float>
|
2024-05-26 00:31:19 +08:00
|
|
|
{
|
2024-11-03 13:19:57 +08:00
|
|
|
MinValue = 0.05f,
|
2024-05-26 00:31:19 +08:00
|
|
|
MaxValue = 2,
|
|
|
|
Precision = 0.001f,
|
|
|
|
Value = 1,
|
|
|
|
Default = 1,
|
|
|
|
},
|
2024-10-17 03:18:48 +08:00
|
|
|
KeyboardStep = 0.01f,
|
2024-05-26 00:31:19 +08:00
|
|
|
Instantaneous = true
|
|
|
|
},
|
|
|
|
scaleOrigin = new EditorRadioButtonCollection
|
|
|
|
{
|
|
|
|
RelativeSizeAxes = Axes.X,
|
|
|
|
Items = new[]
|
|
|
|
{
|
2024-07-14 23:27:04 +08:00
|
|
|
gridCentreButton = new RadioButton("Grid centre",
|
2024-11-03 14:20:45 +08:00
|
|
|
() => setOrigin(EditorOrigin.GridCentre),
|
2024-07-14 23:13:22 +08:00
|
|
|
() => new SpriteIcon { Icon = FontAwesome.Regular.PlusSquare }),
|
2024-07-14 23:27:04 +08:00
|
|
|
playfieldCentreButton = new RadioButton("Playfield centre",
|
2024-11-03 14:20:45 +08:00
|
|
|
() => setOrigin(EditorOrigin.PlayfieldCentre),
|
2024-07-14 23:27:04 +08:00
|
|
|
() => new SpriteIcon { Icon = FontAwesome.Regular.Square }),
|
2024-05-26 00:31:19 +08:00
|
|
|
selectionCentreButton = new RadioButton("Selection centre",
|
2024-11-03 14:20:45 +08:00
|
|
|
() => setOrigin(EditorOrigin.SelectionCentre),
|
2024-05-26 00:31:19 +08:00
|
|
|
() => new SpriteIcon { Icon = FontAwesome.Solid.VectorSquare })
|
|
|
|
}
|
2024-05-26 03:44:08 +08:00
|
|
|
},
|
|
|
|
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 },
|
|
|
|
},
|
|
|
|
}
|
|
|
|
},
|
2024-05-26 00:31:19 +08:00
|
|
|
}
|
|
|
|
};
|
2024-07-14 23:27:04 +08:00
|
|
|
gridCentreButton.Selected.DisabledChanged += isDisabled =>
|
|
|
|
{
|
|
|
|
gridCentreButton.TooltipText = isDisabled ? "The current selection cannot be scaled relative to grid centre." : string.Empty;
|
|
|
|
};
|
2024-05-29 00:27:01 +08:00
|
|
|
playfieldCentreButton.Selected.DisabledChanged += isDisabled =>
|
|
|
|
{
|
2024-05-29 15:59:19 +08:00
|
|
|
playfieldCentreButton.TooltipText = isDisabled ? "The current selection cannot be scaled relative to playfield centre." : string.Empty;
|
2024-05-29 00:27:01 +08:00
|
|
|
};
|
2024-05-26 00:31:19 +08:00
|
|
|
selectionCentreButton.Selected.DisabledChanged += isDisabled =>
|
|
|
|
{
|
2024-05-29 15:59:19 +08:00
|
|
|
selectionCentreButton.TooltipText = isDisabled ? "The current selection cannot be scaled relative to its centre." : string.Empty;
|
2024-05-26 00:31:19 +08:00
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
protected override void LoadComplete()
|
|
|
|
{
|
|
|
|
base.LoadComplete();
|
|
|
|
|
2024-05-26 00:41:31 +08:00
|
|
|
ScheduleAfterChildren(() =>
|
|
|
|
{
|
|
|
|
scaleInput.TakeFocus();
|
|
|
|
scaleInput.SelectAll();
|
|
|
|
});
|
2024-05-26 00:31:19 +08:00
|
|
|
scaleInput.Current.BindValueChanged(scale => scaleInfo.Value = scaleInfo.Value with { Scale = scale.NewValue });
|
|
|
|
|
2024-10-15 20:11:33 +08:00
|
|
|
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();
|
|
|
|
});
|
2024-05-26 03:44:08 +08:00
|
|
|
|
2024-05-29 00:27:01 +08:00
|
|
|
selectionCentreButton.Selected.Disabled = !(scaleHandler.CanScaleX.Value || scaleHandler.CanScaleY.Value);
|
|
|
|
playfieldCentreButton.Selected.Disabled = scaleHandler.IsScalingSlider.Value && !selectionCentreButton.Selected.Disabled;
|
2024-07-14 23:27:04 +08:00
|
|
|
gridCentreButton.Selected.Disabled = playfieldCentreButton.Selected.Disabled;
|
2024-05-28 22:19:57 +08:00
|
|
|
|
2024-11-03 14:20:45 +08:00
|
|
|
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;
|
|
|
|
});
|
2024-05-28 22:57:24 +08:00
|
|
|
|
2024-05-26 00:31:19 +08:00
|
|
|
scaleInfo.BindValueChanged(scale =>
|
|
|
|
{
|
2024-07-03 22:23:19 +08:00
|
|
|
var newScale = new Vector2(scale.NewValue.Scale, scale.NewValue.Scale);
|
2024-07-14 23:03:17 +08:00
|
|
|
scaleHandler.Update(newScale, getOriginPosition(scale.NewValue), getAdjustAxis(scale.NewValue), getRotation(scale.NewValue));
|
2024-05-26 00:31:19 +08:00
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2024-10-15 20:11:33 +08:00
|
|
|
private void updateAxes()
|
|
|
|
{
|
|
|
|
scaleInfo.Value = scaleInfo.Value with { XAxis = xCheckBox.Current.Value, YAxis = yCheckBox.Current.Value };
|
2024-10-15 20:20:24 +08:00
|
|
|
updateMinMaxScale();
|
2024-10-15 20:11:33 +08:00
|
|
|
}
|
|
|
|
|
2024-05-28 23:24:31 +08:00
|
|
|
private void updateAxisCheckBoxesEnabled()
|
|
|
|
{
|
2024-11-03 14:20:45 +08:00
|
|
|
if (scaleInfo.Value.Origin != EditorOrigin.SelectionCentre)
|
2024-05-28 23:24:31 +08:00
|
|
|
{
|
2024-05-29 16:04:49 +08:00
|
|
|
toggleAxisAvailable(xCheckBox.Current, true);
|
|
|
|
toggleAxisAvailable(yCheckBox.Current, true);
|
2024-05-28 23:24:31 +08:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2024-05-29 16:04:49 +08:00
|
|
|
toggleAxisAvailable(xCheckBox.Current, scaleHandler.CanScaleX.Value);
|
|
|
|
toggleAxisAvailable(yCheckBox.Current, scaleHandler.CanScaleY.Value);
|
2024-05-28 23:24:31 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-05-29 16:04:49 +08:00
|
|
|
private void toggleAxisAvailable(Bindable<bool> axisBindable, bool available)
|
2024-05-28 23:24:31 +08:00
|
|
|
{
|
2024-05-29 16:04:49 +08:00
|
|
|
// 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;
|
2024-05-28 23:24:31 +08:00
|
|
|
}
|
|
|
|
|
2024-10-11 21:03:30 +08:00
|
|
|
private void updateMinMaxScale()
|
2024-05-26 02:17:27 +08:00
|
|
|
{
|
|
|
|
if (!scaleHandler.OriginalSurroundingQuad.HasValue)
|
|
|
|
return;
|
|
|
|
|
2024-11-04 17:36:49 +08:00
|
|
|
const float min_scale = 0.05f;
|
2024-05-26 02:17:27 +08:00
|
|
|
const float max_scale = 10;
|
2024-09-22 04:04:11 +08:00
|
|
|
|
2024-07-14 23:03:17 +08:00
|
|
|
var scale = scaleHandler.ClampScaleToPlayfieldBounds(new Vector2(max_scale), getOriginPosition(scaleInfo.Value), getAdjustAxis(scaleInfo.Value), getRotation(scaleInfo.Value));
|
2024-05-26 02:17:27 +08:00
|
|
|
|
|
|
|
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));
|
2024-09-22 02:34:29 +08:00
|
|
|
|
|
|
|
scale = scaleHandler.ClampScaleToPlayfieldBounds(new Vector2(min_scale), getOriginPosition(scaleInfo.Value), getAdjustAxis(scaleInfo.Value), getRotation(scaleInfo.Value));
|
|
|
|
|
|
|
|
if (!scaleInfo.Value.XAxis)
|
2024-09-22 04:04:11 +08:00
|
|
|
scale.X = min_scale;
|
2024-09-22 02:34:29 +08:00
|
|
|
if (!scaleInfo.Value.YAxis)
|
2024-09-22 04:04:11 +08:00
|
|
|
scale.Y = min_scale;
|
2024-09-22 02:34:29 +08:00
|
|
|
|
2024-09-22 04:04:11 +08:00
|
|
|
scaleInputBindable.MinValue = MathF.Min(1, MathF.Max(scale.X, scale.Y));
|
2024-05-26 02:17:27 +08:00
|
|
|
}
|
|
|
|
|
2024-11-03 14:20:45 +08:00
|
|
|
private void setOrigin(EditorOrigin origin)
|
2024-05-26 02:17:27 +08:00
|
|
|
{
|
|
|
|
scaleInfo.Value = scaleInfo.Value with { Origin = origin };
|
2024-10-11 21:03:30 +08:00
|
|
|
updateMinMaxScale();
|
2024-05-28 23:24:31 +08:00
|
|
|
updateAxisCheckBoxesEnabled();
|
2024-05-26 02:17:27 +08:00
|
|
|
}
|
|
|
|
|
2024-09-24 17:53:02 +08:00
|
|
|
private Vector2? getOriginPosition(PreciseScaleInfo scale)
|
|
|
|
{
|
|
|
|
switch (scale.Origin)
|
2024-07-14 23:27:04 +08:00
|
|
|
{
|
2024-11-03 14:20:45 +08:00
|
|
|
case EditorOrigin.GridCentre:
|
2024-09-24 17:53:02 +08:00
|
|
|
return gridToolbox.StartPosition.Value;
|
|
|
|
|
2024-11-03 14:20:45 +08:00
|
|
|
case EditorOrigin.PlayfieldCentre:
|
2024-09-24 17:53:02 +08:00
|
|
|
return OsuPlayfield.BASE_SIZE / 2;
|
|
|
|
|
2024-11-03 14:20:45 +08:00
|
|
|
case EditorOrigin.SelectionCentre:
|
2024-09-24 17:53:02 +08:00
|
|
|
if (selectedItems.Count == 1 && selectedItems.First() is Slider slider)
|
|
|
|
return slider.Position;
|
|
|
|
|
|
|
|
return null;
|
|
|
|
|
|
|
|
default:
|
|
|
|
throw new ArgumentOutOfRangeException(nameof(scale));
|
|
|
|
}
|
|
|
|
}
|
2024-05-26 02:17:27 +08:00
|
|
|
|
2024-10-11 21:18:43 +08:00
|
|
|
private Axes getAdjustAxis(PreciseScaleInfo scale)
|
|
|
|
{
|
|
|
|
var result = Axes.None;
|
|
|
|
|
|
|
|
if (scale.XAxis)
|
|
|
|
result |= Axes.X;
|
|
|
|
|
|
|
|
if (scale.YAxis)
|
|
|
|
result |= Axes.Y;
|
|
|
|
|
|
|
|
return result;
|
|
|
|
}
|
2024-07-03 22:23:19 +08:00
|
|
|
|
2024-11-03 14:20:45 +08:00
|
|
|
private float getRotation(PreciseScaleInfo scale) => scale.Origin == EditorOrigin.GridCentre ? gridToolbox.GridLinesRotation.Value : 0;
|
2024-07-14 23:03:17 +08:00
|
|
|
|
2024-05-26 00:31:19 +08:00
|
|
|
protected override void PopIn()
|
|
|
|
{
|
|
|
|
base.PopIn();
|
|
|
|
scaleHandler.Begin();
|
2024-10-11 21:03:30 +08:00
|
|
|
updateMinMaxScale();
|
2024-05-26 00:31:19 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
protected override void PopOut()
|
|
|
|
{
|
|
|
|
base.PopOut();
|
|
|
|
|
2024-05-29 00:27:01 +08:00
|
|
|
if (IsLoaded) scaleHandler.Commit();
|
2024-05-26 00:31:19 +08:00
|
|
|
}
|
2024-10-17 03:53:56 +08:00
|
|
|
|
|
|
|
public override bool OnPressed(KeyBindingPressEvent<GlobalAction> e)
|
|
|
|
{
|
|
|
|
if (e.Action == GlobalAction.Select && !e.Repeat)
|
|
|
|
{
|
|
|
|
this.HidePopover();
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
return base.OnPressed(e);
|
|
|
|
}
|
2024-05-26 00:31:19 +08:00
|
|
|
}
|
|
|
|
|
2024-11-03 14:20:45 +08:00
|
|
|
public record PreciseScaleInfo(float Scale, EditorOrigin Origin, bool XAxis, bool YAxis);
|
2024-05-26 00:31:19 +08:00
|
|
|
}
|