1
0
mirror of https://github.com/ppy/osu.git synced 2025-01-13 14:12:56 +08:00

Merge pull request #13349 from ribbanya/skin-editor-closest-anchor

Allow skin elements to find closest anchor
This commit is contained in:
Dean Herbert 2021-06-22 17:31:05 +09:00 committed by GitHub
commit 2bea44fe94
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 139 additions and 12 deletions

View File

@ -7,6 +7,7 @@ using osu.Framework.Graphics.Containers;
using osu.Framework.Input.Bindings;
using osu.Framework.Threading;
using osu.Game.Screens.Play.HUD;
using osu.Game.Skinning;
using osuTK;
namespace osu.Game.Extensions
@ -57,6 +58,9 @@ namespace osu.Game.Extensions
component.Anchor = info.Anchor;
component.Origin = info.Origin;
if (component is ISkinnableDrawable skinnable)
skinnable.UsesFixedAnchor = info.UsesFixedAnchor;
if (component is Container container)
{
foreach (var child in info.Children)

View File

@ -12,6 +12,8 @@ namespace osu.Game.Screens.Play.HUD
[Resolved(canBeNull: true)]
private HUDOverlay hud { get; set; }
public bool UsesFixedAnchor { get; set; }
[BackgroundDependencyLoader]
private void load(OsuColour colours)
{

View File

@ -17,6 +17,8 @@ namespace osu.Game.Screens.Play.HUD
[Resolved(canBeNull: true)]
private HUDOverlay hud { get; set; }
public bool UsesFixedAnchor { get; set; }
public DefaultComboCounter()
{
Current.Value = DisplayedCount = 0;

View File

@ -72,6 +72,8 @@ namespace osu.Game.Screens.Play.HUD
}
}
public bool UsesFixedAnchor { get; set; }
public DefaultHealthDisplay()
{
Size = new Vector2(1, 5);

View File

@ -20,6 +20,8 @@ namespace osu.Game.Screens.Play.HUD
[Resolved(canBeNull: true)]
private HUDOverlay hud { get; set; }
public bool UsesFixedAnchor { get; set; }
[BackgroundDependencyLoader]
private void load(OsuColour colours)
{

View File

@ -22,6 +22,8 @@ namespace osu.Game.Screens.Play.HUD.HitErrorMeters
[Resolved]
private OsuColour colours { get; set; }
public bool UsesFixedAnchor { get; set; }
[BackgroundDependencyLoader(true)]
private void load(DrawableRuleset drawableRuleset)
{

View File

@ -59,6 +59,8 @@ namespace osu.Game.Screens.Play.HUD
set => counterContainer.Alpha = value ? 1 : 0;
}
public bool UsesFixedAnchor { get; set; }
public LegacyComboCounter()
{
AutoSizeAxes = Axes.Both;

View File

@ -32,6 +32,9 @@ namespace osu.Game.Screens.Play.HUD
public Anchor Origin { get; set; }
/// <inheritdoc cref="ISkinnableDrawable.UsesFixedAnchor"/>
public bool UsesFixedAnchor { get; set; }
public List<SkinnableInfo> Children { get; } = new List<SkinnableInfo>();
[JsonConstructor]
@ -53,6 +56,9 @@ namespace osu.Game.Screens.Play.HUD
Anchor = component.Anchor;
Origin = component.Origin;
if (component is ISkinnableDrawable skinnable)
UsesFixedAnchor = skinnable.UsesFixedAnchor;
if (component is Container<Drawable> container)
{
foreach (var child in container.OfType<ISkinnableDrawable>().OfType<Drawable>())

View File

@ -78,6 +78,8 @@ namespace osu.Game.Screens.Play
private IClock referenceClock;
public bool UsesFixedAnchor { get; set; }
public SongProgress()
{
RelativeSizeAxes = Axes.X;

View File

@ -149,13 +149,21 @@ namespace osu.Game.Skinning.Editor
{
foreach (var c in SelectedBlueprints)
{
Drawable drawable = (Drawable)c.Item;
var item = c.Item;
Drawable drawable = (Drawable)item;
drawable.Position += drawable.ScreenSpaceDeltaToParentSpace(moveEvent.ScreenSpaceDelta);
if (item.UsesFixedAnchor) continue;
applyClosestAnchor(drawable);
}
return true;
}
private static void applyClosestAnchor(Drawable drawable) => applyAnchor(drawable, getClosestAnchor(drawable));
protected override void OnSelectionChanged()
{
base.OnSelectionChanged();
@ -171,20 +179,27 @@ namespace osu.Game.Skinning.Editor
protected override IEnumerable<MenuItem> GetContextMenuItemsForSelection(IEnumerable<SelectionBlueprint<ISkinnableDrawable>> selection)
{
var closestItem = new TernaryStateRadioMenuItem("Closest", MenuItemType.Standard, _ => applyClosestAnchors())
{
State = { Value = GetStateFromSelection(selection, c => !c.Item.UsesFixedAnchor) }
};
yield return new OsuMenuItem("Anchor")
{
Items = createAnchorItems(d => d.Anchor, applyAnchor).ToArray()
Items = createAnchorItems((d, a) => d.UsesFixedAnchor && ((Drawable)d).Anchor == a, applyFixedAnchors)
.Prepend(closestItem)
.ToArray()
};
yield return new OsuMenuItem("Origin")
{
Items = createAnchorItems(d => d.Origin, applyOrigin).ToArray()
Items = createAnchorItems((d, o) => ((Drawable)d).Origin == o, applyOrigins).ToArray()
};
foreach (var item in base.GetContextMenuItemsForSelection(selection))
yield return item;
IEnumerable<TernaryStateMenuItem> createAnchorItems(Func<Drawable, Anchor> checkFunction, Action<Anchor> applyFunction)
IEnumerable<TernaryStateMenuItem> createAnchorItems(Func<ISkinnableDrawable, Anchor, bool> checkFunction, Action<Anchor> applyFunction)
{
var displayableAnchors = new[]
{
@ -198,12 +213,11 @@ namespace osu.Game.Skinning.Editor
Anchor.BottomCentre,
Anchor.BottomRight,
};
return displayableAnchors.Select(a =>
{
return new TernaryStateRadioMenuItem(a.ToString(), MenuItemType.Standard, _ => applyFunction(a))
{
State = { Value = GetStateFromSelection(selection, c => checkFunction((Drawable)c.Item) == a) }
State = { Value = GetStateFromSelection(selection, c => checkFunction(c.Item, a)) }
};
});
}
@ -215,15 +229,21 @@ namespace osu.Game.Skinning.Editor
drawable.Parent.ToLocalSpace(screenSpacePosition) - drawable.AnchorPosition;
}
private void applyOrigin(Anchor anchor)
private void applyOrigins(Anchor origin)
{
foreach (var item in SelectedItems)
{
var drawable = (Drawable)item;
if (origin == drawable.Origin) continue;
var previousOrigin = drawable.OriginPosition;
drawable.Origin = anchor;
drawable.Origin = origin;
drawable.Position += drawable.OriginPosition - previousOrigin;
if (item.UsesFixedAnchor) continue;
applyClosestAnchor(drawable);
}
}
@ -234,18 +254,86 @@ namespace osu.Game.Skinning.Editor
private Quad getSelectionQuad() =>
GetSurroundingQuad(SelectedBlueprints.SelectMany(b => b.Item.ScreenSpaceDrawQuad.GetVertices().ToArray()));
private void applyAnchor(Anchor anchor)
private void applyFixedAnchors(Anchor anchor)
{
foreach (var item in SelectedItems)
{
var drawable = (Drawable)item;
var previousAnchor = drawable.AnchorPosition;
drawable.Anchor = anchor;
drawable.Position -= drawable.AnchorPosition - previousAnchor;
item.UsesFixedAnchor = true;
applyAnchor(drawable, anchor);
}
}
private void applyClosestAnchors()
{
foreach (var item in SelectedItems)
{
item.UsesFixedAnchor = false;
applyClosestAnchor((Drawable)item);
}
}
private static Anchor getClosestAnchor(Drawable drawable)
{
var parent = drawable.Parent;
if (parent == null)
return drawable.Anchor;
var screenPosition = getScreenPosition();
var absolutePosition = parent.ToLocalSpace(screenPosition);
var factor = parent.RelativeToAbsoluteFactor;
var result = default(Anchor);
static Anchor getAnchorFromPosition(float xOrY, Anchor anchor0, Anchor anchor1, Anchor anchor2)
{
if (xOrY >= 2 / 3f)
return anchor2;
if (xOrY >= 1 / 3f)
return anchor1;
return anchor0;
}
result |= getAnchorFromPosition(absolutePosition.X / factor.X, Anchor.x0, Anchor.x1, Anchor.x2);
result |= getAnchorFromPosition(absolutePosition.Y / factor.Y, Anchor.y0, Anchor.y1, Anchor.y2);
return result;
Vector2 getScreenPosition()
{
var quad = drawable.ScreenSpaceDrawQuad;
var origin = drawable.Origin;
var pos = quad.TopLeft;
if (origin.HasFlagFast(Anchor.x2))
pos.X += quad.Width;
else if (origin.HasFlagFast(Anchor.x1))
pos.X += quad.Width / 2f;
if (origin.HasFlagFast(Anchor.y2))
pos.Y += quad.Height;
else if (origin.HasFlagFast(Anchor.y1))
pos.Y += quad.Height / 2f;
return pos;
}
}
private static void applyAnchor(Drawable drawable, Anchor anchor)
{
if (anchor == drawable.Anchor) return;
var previousAnchor = drawable.AnchorPosition;
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).

View File

@ -14,5 +14,12 @@ namespace osu.Game.Skinning
/// Whether this component should be editable by an end user.
/// </summary>
bool IsEditable => true;
/// <summary>
/// In the context of the skin layout editor, whether this <see cref="ISkinnableDrawable"/> has a permanent anchor defined.
/// If <see langword="false"/>, this <see cref="ISkinnableDrawable"/>'s <see cref="Drawable.Anchor"/> is automatically determined by proximity,
/// If <see langword="true"/>, a fixed anchor point has been defined.
/// </summary>
bool UsesFixedAnchor { get; set; }
}
}

View File

@ -12,6 +12,8 @@ namespace osu.Game.Skinning
{
public class LegacyAccuracyCounter : GameplayAccuracyCounter, ISkinnableDrawable
{
public bool UsesFixedAnchor { get; set; }
public LegacyAccuracyCounter()
{
Anchor = Anchor.TopRight;

View File

@ -27,6 +27,8 @@ namespace osu.Game.Skinning
private bool isNewStyle;
public bool UsesFixedAnchor { get; set; }
[BackgroundDependencyLoader]
private void load(ISkinSource source)
{

View File

@ -13,6 +13,8 @@ namespace osu.Game.Skinning
protected override double RollingDuration => 1000;
protected override Easing RollingEasing => Easing.Out;
public bool UsesFixedAnchor { get; set; }
public LegacyScoreCounter()
: base(6)
{

View File

@ -17,6 +17,8 @@ namespace osu.Game.Skinning
{
public bool IsEditable => false;
public bool UsesFixedAnchor { get; set; }
private readonly Action<Container> applyDefaults;
/// <summary>