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:
commit
2bea44fe94
@ -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)
|
||||
|
@ -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)
|
||||
{
|
||||
|
@ -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;
|
||||
|
@ -72,6 +72,8 @@ namespace osu.Game.Screens.Play.HUD
|
||||
}
|
||||
}
|
||||
|
||||
public bool UsesFixedAnchor { get; set; }
|
||||
|
||||
public DefaultHealthDisplay()
|
||||
{
|
||||
Size = new Vector2(1, 5);
|
||||
|
@ -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)
|
||||
{
|
||||
|
@ -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)
|
||||
{
|
||||
|
@ -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;
|
||||
|
@ -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>())
|
||||
|
@ -78,6 +78,8 @@ namespace osu.Game.Screens.Play
|
||||
|
||||
private IClock referenceClock;
|
||||
|
||||
public bool UsesFixedAnchor { get; set; }
|
||||
|
||||
public SongProgress()
|
||||
{
|
||||
RelativeSizeAxes = Axes.X;
|
||||
|
@ -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).
|
||||
|
@ -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; }
|
||||
}
|
||||
}
|
||||
|
@ -12,6 +12,8 @@ namespace osu.Game.Skinning
|
||||
{
|
||||
public class LegacyAccuracyCounter : GameplayAccuracyCounter, ISkinnableDrawable
|
||||
{
|
||||
public bool UsesFixedAnchor { get; set; }
|
||||
|
||||
public LegacyAccuracyCounter()
|
||||
{
|
||||
Anchor = Anchor.TopRight;
|
||||
|
@ -27,6 +27,8 @@ namespace osu.Game.Skinning
|
||||
|
||||
private bool isNewStyle;
|
||||
|
||||
public bool UsesFixedAnchor { get; set; }
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(ISkinSource source)
|
||||
{
|
||||
|
@ -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)
|
||||
{
|
||||
|
@ -17,6 +17,8 @@ namespace osu.Game.Skinning
|
||||
{
|
||||
public bool IsEditable => false;
|
||||
|
||||
public bool UsesFixedAnchor { get; set; }
|
||||
|
||||
private readonly Action<Container> applyDefaults;
|
||||
|
||||
/// <summary>
|
||||
|
Loading…
Reference in New Issue
Block a user