mirror of
https://github.com/ppy/osu.git
synced 2025-01-28 21:23:04 +08:00
refactor skin editor scale
This commit is contained in:
parent
bc0e6baba7
commit
ed430a3df4
@ -7,10 +7,8 @@ using System.Linq;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Extensions.EnumExtensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Primitives;
|
||||
using osu.Framework.Graphics.UserInterface;
|
||||
using osu.Framework.Utils;
|
||||
using osu.Game.Extensions;
|
||||
using osu.Game.Graphics.UserInterface;
|
||||
using osu.Game.Rulesets.Edit;
|
||||
@ -31,148 +29,16 @@ namespace osu.Game.Overlays.SkinEditor
|
||||
UpdatePosition = updateDrawablePosition
|
||||
};
|
||||
|
||||
private bool allSelectedSupportManualSizing(Axes axis) => SelectedItems.All(b => (b as CompositeDrawable)?.AutoSizeAxes.HasFlagFast(axis) == false);
|
||||
|
||||
public override bool HandleScale(Vector2 scale, Anchor anchor)
|
||||
public override SelectionScaleHandler CreateScaleHandler()
|
||||
{
|
||||
Axes adjustAxis;
|
||||
|
||||
switch (anchor)
|
||||
var scaleHandler = new SkinSelectionScaleHandler
|
||||
{
|
||||
// for corners, adjust scale.
|
||||
case Anchor.TopLeft:
|
||||
case Anchor.TopRight:
|
||||
case Anchor.BottomLeft:
|
||||
case Anchor.BottomRight:
|
||||
adjustAxis = Axes.Both;
|
||||
break;
|
||||
UpdatePosition = updateDrawablePosition
|
||||
};
|
||||
|
||||
// for edges, adjust size.
|
||||
// autosize elements can't be easily handled so just disable sizing for now.
|
||||
case Anchor.TopCentre:
|
||||
case Anchor.BottomCentre:
|
||||
if (!allSelectedSupportManualSizing(Axes.Y))
|
||||
return false;
|
||||
scaleHandler.PerformFlipFromScaleHandles += a => SelectionBox.PerformFlipFromScaleHandles(a);
|
||||
|
||||
adjustAxis = Axes.Y;
|
||||
break;
|
||||
|
||||
case Anchor.CentreLeft:
|
||||
case Anchor.CentreRight:
|
||||
if (!allSelectedSupportManualSizing(Axes.X))
|
||||
return false;
|
||||
|
||||
adjustAxis = Axes.X;
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException(nameof(anchor), anchor, null);
|
||||
}
|
||||
|
||||
// convert scale to screen space
|
||||
scale = ToScreenSpace(scale) - ToScreenSpace(Vector2.Zero);
|
||||
|
||||
adjustScaleFromAnchor(ref scale, anchor);
|
||||
|
||||
// the selection quad is always upright, so use an AABB rect to make mutating the values easier.
|
||||
var selectionRect = getSelectionQuad().AABBFloat;
|
||||
|
||||
// If the selection has no area we cannot scale it
|
||||
if (selectionRect.Area == 0)
|
||||
return false;
|
||||
|
||||
// copy to mutate, as we will need to compare to the original later on.
|
||||
var adjustedRect = selectionRect;
|
||||
bool isRotated = false;
|
||||
|
||||
// for now aspect lock scale adjustments that occur at corners..
|
||||
if (!anchor.HasFlagFast(Anchor.x1) && !anchor.HasFlagFast(Anchor.y1))
|
||||
{
|
||||
// project scale vector along diagonal
|
||||
Vector2 diag = (selectionRect.TopLeft - selectionRect.BottomRight).Normalized();
|
||||
scale = Vector2.Dot(scale, diag) * diag;
|
||||
}
|
||||
// ..or if any of the selection have been rotated.
|
||||
// this is to avoid requiring skew logic (which would likely not be the user's expected transform anyway).
|
||||
else if (SelectedBlueprints.Any(b => !Precision.AlmostEquals(((Drawable)b.Item).Rotation % 90, 0)))
|
||||
{
|
||||
isRotated = true;
|
||||
if (anchor.HasFlagFast(Anchor.x1))
|
||||
// if dragging from the horizontal centre, only a vertical component is available.
|
||||
scale.X = scale.Y / selectionRect.Height * selectionRect.Width;
|
||||
else
|
||||
// in all other cases (arbitrarily) use the horizontal component for aspect lock.
|
||||
scale.Y = scale.X / selectionRect.Width * selectionRect.Height;
|
||||
}
|
||||
|
||||
if (anchor.HasFlagFast(Anchor.x0)) adjustedRect.X -= scale.X;
|
||||
if (anchor.HasFlagFast(Anchor.y0)) adjustedRect.Y -= scale.Y;
|
||||
|
||||
// Maintain the selection's centre position if dragging from the centre anchors and selection is rotated.
|
||||
if (isRotated && anchor.HasFlagFast(Anchor.x1)) adjustedRect.X -= scale.X / 2;
|
||||
if (isRotated && anchor.HasFlagFast(Anchor.y1)) adjustedRect.Y -= scale.Y / 2;
|
||||
|
||||
adjustedRect.Width += scale.X;
|
||||
adjustedRect.Height += scale.Y;
|
||||
|
||||
if (adjustedRect.Width <= 0 || adjustedRect.Height <= 0)
|
||||
{
|
||||
Axes toFlip = Axes.None;
|
||||
|
||||
if (adjustedRect.Width <= 0) toFlip |= Axes.X;
|
||||
if (adjustedRect.Height <= 0) toFlip |= Axes.Y;
|
||||
|
||||
SelectionBox.PerformFlipFromScaleHandles(toFlip);
|
||||
return true;
|
||||
}
|
||||
|
||||
// scale adjust applied to each individual item should match that of the quad itself.
|
||||
var scaledDelta = new Vector2(
|
||||
adjustedRect.Width / selectionRect.Width,
|
||||
adjustedRect.Height / selectionRect.Height
|
||||
);
|
||||
|
||||
foreach (var b in SelectedBlueprints)
|
||||
{
|
||||
var drawableItem = (Drawable)b.Item;
|
||||
|
||||
// each drawable's relative position should be maintained in the scaled quad.
|
||||
var screenPosition = b.ScreenSpaceSelectionPoint;
|
||||
|
||||
var relativePositionInOriginal =
|
||||
new Vector2(
|
||||
(screenPosition.X - selectionRect.TopLeft.X) / selectionRect.Width,
|
||||
(screenPosition.Y - selectionRect.TopLeft.Y) / selectionRect.Height
|
||||
);
|
||||
|
||||
var newPositionInAdjusted = new Vector2(
|
||||
adjustedRect.TopLeft.X + adjustedRect.Width * relativePositionInOriginal.X,
|
||||
adjustedRect.TopLeft.Y + adjustedRect.Height * relativePositionInOriginal.Y
|
||||
);
|
||||
|
||||
updateDrawablePosition(drawableItem, newPositionInAdjusted);
|
||||
|
||||
var currentScaledDelta = scaledDelta;
|
||||
if (Precision.AlmostEquals(MathF.Abs(drawableItem.Rotation) % 180, 90))
|
||||
currentScaledDelta = new Vector2(scaledDelta.Y, scaledDelta.X);
|
||||
|
||||
switch (adjustAxis)
|
||||
{
|
||||
case Axes.X:
|
||||
drawableItem.Width *= currentScaledDelta.X;
|
||||
break;
|
||||
|
||||
case Axes.Y:
|
||||
drawableItem.Height *= currentScaledDelta.Y;
|
||||
break;
|
||||
|
||||
case Axes.Both:
|
||||
drawableItem.Scale *= currentScaledDelta;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
return scaleHandler;
|
||||
}
|
||||
|
||||
public override bool HandleFlip(Direction direction, bool flipOverOrigin)
|
||||
@ -410,16 +276,5 @@ namespace osu.Game.Overlays.SkinEditor
|
||||
drawable.Anchor = anchor;
|
||||
drawable.Position -= drawable.AnchorPosition - previousAnchor;
|
||||
}
|
||||
|
||||
private static void adjustScaleFromAnchor(ref Vector2 scale, Anchor reference)
|
||||
{
|
||||
// cancel out scale in axes we don't care about (based on which drag handle was used).
|
||||
if ((reference & Anchor.x1) > 0) scale.X = 0;
|
||||
if ((reference & Anchor.y1) > 0) scale.Y = 0;
|
||||
|
||||
// reverse the scale direction if dragging from top or left.
|
||||
if ((reference & Anchor.x0) > 0) scale.X = -scale.X;
|
||||
if ((reference & Anchor.y0) > 0) scale.Y = -scale.Y;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
198
osu.Game/Overlays/SkinEditor/SkinSelectionScaleHandler.cs
Normal file
198
osu.Game/Overlays/SkinEditor/SkinSelectionScaleHandler.cs
Normal file
@ -0,0 +1,198 @@
|
||||
// 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.EnumExtensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Utils;
|
||||
using osu.Game.Screens.Edit;
|
||||
using osu.Game.Screens.Edit.Compose.Components;
|
||||
using osu.Game.Skinning;
|
||||
using osu.Game.Utils;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Overlays.SkinEditor
|
||||
{
|
||||
public partial class SkinSelectionScaleHandler : SelectionScaleHandler
|
||||
{
|
||||
public Action<Drawable, Vector2> UpdatePosition { get; init; } = null!;
|
||||
|
||||
public event Action<Axes>? PerformFlipFromScaleHandles;
|
||||
|
||||
[Resolved]
|
||||
private IEditorChangeHandler? changeHandler { get; set; }
|
||||
|
||||
private BindableList<ISerialisableDrawable> selectedItems { get; } = new BindableList<ISerialisableDrawable>();
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(SkinEditor skinEditor)
|
||||
{
|
||||
selectedItems.BindTo(skinEditor.SelectedComponents);
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
||||
selectedItems.CollectionChanged += (_, __) => updateState();
|
||||
updateState();
|
||||
}
|
||||
|
||||
private void updateState()
|
||||
{
|
||||
CanScaleX.Value = allSelectedSupportManualSizing(Axes.X);
|
||||
CanScaleY.Value = allSelectedSupportManualSizing(Axes.Y);
|
||||
CanScaleDiagonally.Value = true;
|
||||
}
|
||||
|
||||
private bool allSelectedSupportManualSizing(Axes axis) => selectedItems.All(b => (b as CompositeDrawable)?.AutoSizeAxes.HasFlagFast(axis) == false);
|
||||
|
||||
private Drawable[]? objectsInScale;
|
||||
|
||||
private Vector2? defaultOrigin;
|
||||
private Dictionary<Drawable, float>? originalWidths;
|
||||
private Dictionary<Drawable, float>? originalHeights;
|
||||
private Dictionary<Drawable, Vector2>? originalScales;
|
||||
private Dictionary<Drawable, Vector2>? originalPositions;
|
||||
|
||||
public override void Begin()
|
||||
{
|
||||
if (objectsInScale != null)
|
||||
throw new InvalidOperationException($"Cannot {nameof(Begin)} a scale operation while another is in progress!");
|
||||
|
||||
changeHandler?.BeginChange();
|
||||
|
||||
objectsInScale = selectedItems.Cast<Drawable>().ToArray();
|
||||
originalWidths = objectsInScale.ToDictionary(d => d, d => d.Width);
|
||||
originalHeights = objectsInScale.ToDictionary(d => d, d => d.Height);
|
||||
originalScales = objectsInScale.ToDictionary(d => d, d => d.Scale);
|
||||
originalPositions = objectsInScale.ToDictionary(d => d, d => d.ToScreenSpace(d.OriginPosition));
|
||||
OriginalSurroundingQuad = GeometryUtils.GetSurroundingQuad(objectsInScale.SelectMany(d => d.ScreenSpaceDrawQuad.GetVertices().ToArray()));
|
||||
defaultOrigin = OriginalSurroundingQuad.Value.Centre;
|
||||
}
|
||||
|
||||
public override void Update(Vector2 scale, Vector2? origin = null)
|
||||
{
|
||||
if (objectsInScale == null)
|
||||
throw new InvalidOperationException($"Cannot {nameof(Update)} a scale operation without calling {nameof(Begin)} first!");
|
||||
|
||||
Debug.Assert(originalWidths != null && originalHeights != null && originalScales != null && originalPositions != null && defaultOrigin != null && OriginalSurroundingQuad != null);
|
||||
|
||||
var actualOrigin = origin ?? defaultOrigin.Value;
|
||||
|
||||
Axes adjustAxis = scale.X == 0 ? Axes.Y : scale.Y == 0 ? Axes.X : Axes.Both;
|
||||
|
||||
if ((adjustAxis == Axes.Y && !allSelectedSupportManualSizing(Axes.Y)) ||
|
||||
(adjustAxis == Axes.X && !allSelectedSupportManualSizing(Axes.X)))
|
||||
return;
|
||||
|
||||
// the selection quad is always upright, so use an AABB rect to make mutating the values easier.
|
||||
var selectionRect = OriginalSurroundingQuad.Value.AABBFloat;
|
||||
|
||||
// If the selection has no area we cannot scale it
|
||||
if (selectionRect.Area == 0)
|
||||
return;
|
||||
|
||||
// copy to mutate, as we will need to compare to the original later on.
|
||||
var adjustedRect = selectionRect;
|
||||
|
||||
// for now aspect lock scale adjustments that occur at corners..
|
||||
if (adjustAxis == Axes.Both)
|
||||
{
|
||||
// project scale vector along diagonal
|
||||
Vector2 diag = new Vector2(1, 1).Normalized();
|
||||
scale = Vector2.Dot(scale, diag) * diag;
|
||||
}
|
||||
// ..or if any of the selection have been rotated.
|
||||
// this is to avoid requiring skew logic (which would likely not be the user's expected transform anyway).
|
||||
else if (objectsInScale.Any(b => !Precision.AlmostEquals(b.Rotation % 90, 0)))
|
||||
{
|
||||
if (adjustAxis == Axes.Y)
|
||||
// if dragging from the horizontal centre, only a vertical component is available.
|
||||
scale.X = scale.Y / selectionRect.Height * selectionRect.Width;
|
||||
else
|
||||
// in all other cases (arbitrarily) use the horizontal component for aspect lock.
|
||||
scale.Y = scale.X / selectionRect.Width * selectionRect.Height;
|
||||
}
|
||||
|
||||
adjustedRect.Location = GeometryUtils.GetScaledPosition(scale, actualOrigin, OriginalSurroundingQuad!.Value.TopLeft);
|
||||
adjustedRect.Size = OriginalSurroundingQuad!.Value.Size * scale;
|
||||
|
||||
if (adjustedRect.Width <= 0 || adjustedRect.Height <= 0)
|
||||
{
|
||||
Axes toFlip = Axes.None;
|
||||
|
||||
if (adjustedRect.Width <= 0) toFlip |= Axes.X;
|
||||
if (adjustedRect.Height <= 0) toFlip |= Axes.Y;
|
||||
|
||||
PerformFlipFromScaleHandles?.Invoke(toFlip);
|
||||
return;
|
||||
}
|
||||
|
||||
// scale adjust applied to each individual item should match that of the quad itself.
|
||||
var scaledDelta = new Vector2(
|
||||
adjustedRect.Width / selectionRect.Width,
|
||||
adjustedRect.Height / selectionRect.Height
|
||||
);
|
||||
|
||||
foreach (var b in objectsInScale)
|
||||
{
|
||||
// each drawable's relative position should be maintained in the scaled quad.
|
||||
var screenPosition = originalPositions[b];
|
||||
|
||||
var relativePositionInOriginal =
|
||||
new Vector2(
|
||||
(screenPosition.X - selectionRect.TopLeft.X) / selectionRect.Width,
|
||||
(screenPosition.Y - selectionRect.TopLeft.Y) / selectionRect.Height
|
||||
);
|
||||
|
||||
var newPositionInAdjusted = new Vector2(
|
||||
adjustedRect.TopLeft.X + adjustedRect.Width * relativePositionInOriginal.X,
|
||||
adjustedRect.TopLeft.Y + adjustedRect.Height * relativePositionInOriginal.Y
|
||||
);
|
||||
|
||||
UpdatePosition(b, newPositionInAdjusted);
|
||||
|
||||
var currentScaledDelta = scaledDelta;
|
||||
if (Precision.AlmostEquals(MathF.Abs(b.Rotation) % 180, 90))
|
||||
currentScaledDelta = new Vector2(scaledDelta.Y, scaledDelta.X);
|
||||
|
||||
switch (adjustAxis)
|
||||
{
|
||||
case Axes.X:
|
||||
b.Width = originalWidths[b] * currentScaledDelta.X;
|
||||
break;
|
||||
|
||||
case Axes.Y:
|
||||
b.Height = originalHeights[b] * currentScaledDelta.Y;
|
||||
break;
|
||||
|
||||
case Axes.Both:
|
||||
b.Scale = originalScales[b] * currentScaledDelta;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override void Commit()
|
||||
{
|
||||
if (objectsInScale == null)
|
||||
throw new InvalidOperationException($"Cannot {nameof(Commit)} a scale operation without calling {nameof(Begin)} first!");
|
||||
|
||||
changeHandler?.EndChange();
|
||||
|
||||
objectsInScale = null;
|
||||
originalPositions = null;
|
||||
originalWidths = null;
|
||||
originalHeights = null;
|
||||
originalScales = null;
|
||||
defaultOrigin = null;
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user