Replace abstract class with interface, attached to the actual components (not skinnable wrapper)

This commit is contained in:
Dean Herbert 2021-04-28 18:34:34 +09:00
parent defa350aa7
commit fd587a82ff
19 changed files with 52 additions and 144 deletions

View File

@ -9,16 +9,12 @@ using osu.Framework.Allocation;
using osu.Framework.Extensions.IEnumerableExtensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Testing;
using osu.Game.Configuration;
using osu.Game.Graphics.Sprites;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Osu;
using osu.Game.Screens.Play;
using osu.Game.Screens.Play.HUD;
using osuTK.Graphics;
using osuTK.Input;
namespace osu.Game.Tests.Visual.Gameplay
@ -78,29 +74,6 @@ namespace osu.Game.Tests.Visual.Gameplay
AddAssert("key counter flow not affected", () => keyCounterFlow.IsPresent);
}
private IReadOnlyList<Drawable> createSkinSourceComponents()
{
Drawable[] hudComponents = typeof(SkinnableHUDComponent).Assembly
.GetTypes()
.Where(t => typeof(SkinnableHUDComponent).IsAssignableFrom(t))
.Where(t => !t.IsAbstract)
.Select(t => Activator.CreateInstance(t) as Drawable)
.ToArray();
List<Drawable> drawables = new List<Drawable>();
foreach (var component in hudComponents)
{
drawables.Add(new OsuSpriteText
{
Text = component.GetType().Name
});
drawables.AddRange(component.CreateSettingsControls());
}
return drawables;
}
private void createNew(Action<HUDOverlay> action = null)
{
AddStep("create overlay", () =>

View File

@ -9,7 +9,7 @@ using osuTK;
namespace osu.Game.Screens.Play.HUD
{
public class DefaultAccuracyCounter : PercentageCounter, IAccuracyCounter
public class DefaultAccuracyCounter : PercentageCounter, IAccuracyCounter, ISkinnableComponent
{
private readonly Vector2 offset = new Vector2(-20, 5);

View File

@ -11,7 +11,7 @@ using osuTK;
namespace osu.Game.Screens.Play.HUD
{
public class DefaultComboCounter : RollingCounter<int>, IComboCounter
public class DefaultComboCounter : RollingCounter<int>, IComboCounter, ISkinnableComponent
{
private readonly Vector2 offset = new Vector2(20, 5);

View File

@ -16,7 +16,7 @@ using osu.Framework.Utils;
namespace osu.Game.Screens.Play.HUD
{
public class DefaultHealthDisplay : HealthDisplay, IHasAccentColour
public class DefaultHealthDisplay : HealthDisplay, IHasAccentColour, ISkinnableComponent
{
/// <summary>
/// The base opacity of the glow.

View File

@ -8,7 +8,7 @@ using osu.Game.Graphics.UserInterface;
namespace osu.Game.Screens.Play.HUD
{
public class DefaultScoreCounter : ScoreCounter
public class DefaultScoreCounter : ScoreCounter, ISkinnableComponent
{
public DefaultScoreCounter()
: base(6)

View File

@ -18,7 +18,7 @@ using osuTK.Graphics;
namespace osu.Game.Screens.Play.HUD.HitErrorMeters
{
public class BarHitErrorMeter : HitErrorMeter
public class BarHitErrorMeter : HitErrorMeter, ISkinnableComponent
{
private readonly Anchor alignment;

View File

@ -0,0 +1,14 @@
// 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 osu.Framework.Graphics;
namespace osu.Game.Screens.Play.HUD
{
/// <summary>
/// Denotes a drawable which, as a drawable, can be adjusted via skinning specifications.
/// </summary>
public interface ISkinnableComponent : IDrawable
{
}
}

View File

@ -14,7 +14,7 @@ namespace osu.Game.Screens.Play.HUD
/// <summary>
/// Uses the 'x' symbol and has a pop-out effect while rolling over.
/// </summary>
public class LegacyComboCounter : CompositeDrawable, IComboCounter
public class LegacyComboCounter : CompositeDrawable, IComboCounter, ISkinnableComponent
{
public Bindable<int> Current { get; } = new BindableInt { MinValue = 0, };

View File

@ -2,12 +2,11 @@
// See the LICENCE file in the repository root for full licence text.
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Game.Skinning;
namespace osu.Game.Screens.Play.HUD
{
public class SkinnableAccuracyCounter : SkinnableHUDComponent, IAccuracyCounter
public class SkinnableAccuracyCounter : SkinnableDrawable, IAccuracyCounter
{
public Bindable<double> Current { get; } = new Bindable<double>();
@ -15,7 +14,6 @@ namespace osu.Game.Screens.Play.HUD
: base(new HUDSkinComponent(HUDSkinComponents.AccuracyCounter), _ => new DefaultAccuracyCounter())
{
CentreComponent = false;
AutoSizeAxes = Axes.Both;
}
private IAccuracyCounter skinnedCounter;

View File

@ -2,12 +2,11 @@
// See the LICENCE file in the repository root for full licence text.
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Game.Skinning;
namespace osu.Game.Screens.Play.HUD
{
public class SkinnableComboCounter : SkinnableHUDComponent, IComboCounter
public class SkinnableComboCounter : SkinnableDrawable, IComboCounter
{
public Bindable<int> Current { get; } = new Bindable<int>();
@ -15,7 +14,6 @@ namespace osu.Game.Screens.Play.HUD
: base(new HUDSkinComponent(HUDSkinComponents.ComboCounter), skinComponent => new DefaultComboCounter())
{
CentreComponent = false;
AutoSizeAxes = Axes.Both;
}
private IComboCounter skinnedCounter;

View File

@ -1,68 +0,0 @@
// 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 osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Layout;
using osu.Game.Configuration;
using osu.Game.Skinning;
using osuTK;
namespace osu.Game.Screens.Play.HUD
{
/// <summary>
/// A skinnable HUD component which can be scaled and repositioned at a skinner/user's will.
/// </summary>
public abstract class SkinnableHUDComponent : SkinnableDrawable
{
[SettingSource("ScaleX", "The horizontal scale at which this component should be displayed.")]
public BindableNumber<float> SkinScaleX { get; } = new BindableFloat(1);
[SettingSource("ScaleY", "The vertical scale at which this component should be displayed.")]
public BindableNumber<float> SkinScaleY { get; } = new BindableFloat(1);
[SettingSource("PositionX", "The horizontal position at which this component should be displayed.")]
public BindableNumber<float> SkinPositionX { get; } = new BindableFloat();
[SettingSource("PositionY", "The vertical position at which this component should be displayed.")]
public BindableNumber<float> SkinPositionY { get; } = new BindableFloat();
[SettingSource("Rotation", "The rotation at which this component should be displayed.")]
public BindableNumber<float> SkinRotation { get; } = new BindableFloat();
[SettingSource("Anchor", "The screen edge this component should align to.")]
public Bindable<Anchor> SkinAnchor { get; } = new Bindable<Anchor>();
protected SkinnableHUDComponent(ISkinComponent component, Func<ISkinComponent, Drawable> defaultImplementation, Func<ISkinSource, bool> allowFallback = null, ConfineMode confineMode = ConfineMode.NoScaling)
: base(component, defaultImplementation, allowFallback, confineMode)
{
SkinScaleX.BindValueChanged(x => Scale = new Vector2(x.NewValue, Scale.Y));
SkinScaleY.BindValueChanged(y => Scale = new Vector2(Scale.X, y.NewValue));
SkinPositionX.BindValueChanged(x => Position = new Vector2(x.NewValue, Position.Y));
SkinPositionY.BindValueChanged(y => Position = new Vector2(Position.X, y.NewValue));
SkinRotation.BindValueChanged(rotation => Rotation = rotation.NewValue);
SkinAnchor.BindValueChanged(anchor => { Anchor = anchor.NewValue; });
// reset everything and require each component to specify what they want,
// as if they were just drawables. maybe we want to change SkinnableDrawable to not default
// to RelativeSizeAxes.Both...
RelativeSizeAxes = Axes.None;
AutoSizeAxes = Axes.None;
}
protected override bool OnInvalidate(Invalidation invalidation, InvalidationSource source)
{
SkinScaleX.Value = Scale.X;
SkinScaleY.Value = Scale.Y;
SkinPositionX.Value = Position.X;
SkinPositionY.Value = Position.Y;
SkinRotation.Value = Rotation;
SkinAnchor.Value = Anchor;
return base.OnInvalidate(invalidation, source);
}
}
}

View File

@ -3,14 +3,13 @@
using System;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Scoring;
using osu.Game.Skinning;
namespace osu.Game.Screens.Play.HUD
{
public class SkinnableHealthDisplay : SkinnableHUDComponent, IHealthDisplay
public class SkinnableHealthDisplay : SkinnableDrawable, IHealthDisplay
{
public Bindable<double> Current { get; } = new BindableDouble(1)
{
@ -36,8 +35,6 @@ namespace osu.Game.Screens.Play.HUD
: base(new HUDSkinComponent(HUDSkinComponents.HealthDisplay), _ => new DefaultHealthDisplay())
{
CentreComponent = false;
AutoSizeAxes = Axes.Y;
RelativeSizeAxes = Axes.X;
}
private IHealthDisplay skinnedCounter;

View File

@ -4,14 +4,13 @@
using System;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Game.Configuration;
using osu.Game.Rulesets.Scoring;
using osu.Game.Skinning;
namespace osu.Game.Screens.Play.HUD
{
public class SkinnableScoreCounter : SkinnableHUDComponent, IScoreCounter
public class SkinnableScoreCounter : SkinnableDrawable, IScoreCounter
{
public Bindable<double> Current { get; } = new Bindable<double>();
@ -23,7 +22,6 @@ namespace osu.Game.Screens.Play.HUD
: base(new HUDSkinComponent(HUDSkinComponents.ScoreCounter), _ => new DefaultScoreCounter())
{
CentreComponent = false;
AutoSizeAxes = Axes.Both;
}
[BackgroundDependencyLoader]

View File

@ -15,24 +15,25 @@ using osuTK;
namespace osu.Game.Skinning.Editor
{
public class SkinBlueprint : SelectionBlueprint<SkinnableHUDComponent>
public class SkinBlueprint : SelectionBlueprint<ISkinnableComponent>
{
/// <summary>
/// The <see cref="DrawableHitObject"/> which this <see cref="OverlaySelectionBlueprint"/> applies to.
/// </summary>
public readonly SkinnableHUDComponent Component;
public readonly ISkinnableComponent Component;
private Container box;
private Drawable drawable => Component.Drawable;
private Drawable drawable => (Drawable)Component;
/// <summary>
/// Whether the blueprint should be shown even when the <see cref="Component"/> is not alive.
/// </summary>
protected virtual bool AlwaysShowWhenSelected => false;
protected override bool ShouldBeAlive => (Component.IsAlive && Component.IsPresent) || (AlwaysShowWhenSelected && State == SelectionState.Selected);
protected override bool ShouldBeAlive => (drawable.IsAlive && Component.IsPresent) || (AlwaysShowWhenSelected && State == SelectionState.Selected);
public SkinBlueprint(SkinnableHUDComponent component)
public SkinBlueprint(ISkinnableComponent component)
: base(component)
{
Component = component;
@ -72,15 +73,15 @@ namespace osu.Game.Skinning.Editor
box.Position = quad.TopLeft;
box.Size = quad.Size;
box.Rotation = Component.Rotation;
box.Rotation = drawable.Rotation;
}
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => drawable.ReceivePositionalInputAt(screenSpacePos);
public override Vector2 ScreenSpaceSelectionPoint => Component.ToScreenSpace(Vector2.Zero);
public override Vector2 ScreenSpaceSelectionPoint => drawable.ToScreenSpace(Vector2.Zero);
public override Quad SelectionQuad => drawable.ScreenSpaceDrawQuad;
public override Vector2 GetInstantDelta(Vector2 screenSpacePosition) => Component.Parent.ToLocalSpace(screenSpacePosition) - Component.Position;
public override Vector2 GetInstantDelta(Vector2 screenSpacePosition) => Component.Parent.ToLocalSpace(screenSpacePosition) - drawable.Position;
}
}

View File

@ -10,7 +10,7 @@ using osu.Game.Screens.Play.HUD;
namespace osu.Game.Skinning.Editor
{
public class SkinBlueprintContainer : BlueprintContainer<SkinnableHUDComponent>
public class SkinBlueprintContainer : BlueprintContainer<ISkinnableComponent>
{
private readonly Drawable target;
@ -23,14 +23,14 @@ namespace osu.Game.Skinning.Editor
{
base.LoadComplete();
SkinnableHUDComponent[] components = target.ChildrenOfType<SkinnableHUDComponent>().ToArray();
ISkinnableComponent[] components = target.ChildrenOfType<ISkinnableComponent>().ToArray();
foreach (var c in components) AddBlueprintFor(c);
}
protected override SelectionHandler<SkinnableHUDComponent> CreateSelectionHandler() => new SkinSelectionHandler();
protected override SelectionHandler<ISkinnableComponent> CreateSelectionHandler() => new SkinSelectionHandler();
protected override SelectionBlueprint<SkinnableHUDComponent> CreateBlueprintFor(SkinnableHUDComponent component)
protected override SelectionBlueprint<ISkinnableComponent> CreateBlueprintFor(ISkinnableComponent component)
=> new SkinBlueprint(component);
}
}

View File

@ -14,15 +14,15 @@ using osuTK;
namespace osu.Game.Skinning.Editor
{
public class SkinSelectionHandler : SelectionHandler<SkinnableHUDComponent>
public class SkinSelectionHandler : SelectionHandler<ISkinnableComponent>
{
protected override void DeleteItems(IEnumerable<SkinnableHUDComponent> items)
protected override void DeleteItems(IEnumerable<ISkinnableComponent> items)
{
foreach (var i in items)
i.Drawable.Expire();
i.Hide();
}
protected override IEnumerable<MenuItem> GetContextMenuItemsForSelection(IEnumerable<SelectionBlueprint<SkinnableHUDComponent>> selection)
protected override IEnumerable<MenuItem> GetContextMenuItemsForSelection(IEnumerable<SelectionBlueprint<ISkinnableComponent>> selection)
{
yield return new OsuMenuItem("Anchor")
{
@ -49,7 +49,7 @@ namespace osu.Game.Skinning.Editor
return displayableAnchors.Select(a =>
{
var countExisting = selection.Count(b => b.Item.SkinAnchor.Value == a);
var countExisting = selection.Count(b => ((Drawable)b.Item).Anchor == a);
var countTotal = selection.Count();
TernaryState state;
@ -72,7 +72,7 @@ namespace osu.Game.Skinning.Editor
private void applyAnchor(Anchor anchor)
{
foreach (var item in SelectedItems)
item.SkinAnchor.Value = anchor;
((Drawable)item).Anchor = anchor;
}
protected override void OnSelectionChanged()
@ -88,7 +88,7 @@ namespace osu.Game.Skinning.Editor
public override bool HandleRotation(float angle)
{
foreach (var c in SelectedBlueprints)
c.Item.SkinRotation.Value += angle;
((Drawable)c.Item).Rotation += angle;
return base.HandleRotation(angle);
}
@ -98,20 +98,16 @@ namespace osu.Game.Skinning.Editor
adjustScaleFromAnchor(ref scale, anchor);
foreach (var c in SelectedBlueprints)
{
c.Item.SkinScaleX.Value += scale.X * 0.01f;
c.Item.SkinScaleY.Value += scale.Y * 0.01f;
}
((Drawable)c.Item).Scale += scale * 0.01f;
return true;
}
public override bool HandleMovement(MoveSelectionEvent<SkinnableHUDComponent> moveEvent)
public override bool HandleMovement(MoveSelectionEvent<ISkinnableComponent> moveEvent)
{
foreach (var c in SelectedBlueprints)
{
c.Item.SkinPositionX.Value += moveEvent.InstantDelta.X;
c.Item.SkinPositionY.Value += moveEvent.InstantDelta.Y;
((Drawable)c.Item).Position += moveEvent.InstantDelta;
}
return true;
@ -130,7 +126,7 @@ namespace osu.Game.Skinning.Editor
public class AnchorMenuItem : TernaryStateMenuItem
{
public AnchorMenuItem(Anchor anchor, IEnumerable<SelectionBlueprint<SkinnableHUDComponent>> selection, Action<TernaryState> action)
public AnchorMenuItem(Anchor anchor, IEnumerable<SelectionBlueprint<ISkinnableComponent>> selection, Action<TernaryState> action)
: base(anchor.ToString(), getNextState, MenuItemType.Standard, action)
{
}

View File

@ -11,7 +11,7 @@ using osuTK;
namespace osu.Game.Skinning
{
public class LegacyAccuracyCounter : PercentageCounter, IAccuracyCounter
public class LegacyAccuracyCounter : PercentageCounter, IAccuracyCounter, ISkinnableComponent
{
private readonly ISkin skin;

View File

@ -16,7 +16,7 @@ using osuTK.Graphics;
namespace osu.Game.Skinning
{
public class LegacyHealthDisplay : CompositeDrawable, IHealthDisplay
public class LegacyHealthDisplay : CompositeDrawable, IHealthDisplay, ISkinnableComponent
{
private const double epic_cutoff = 0.5;

View File

@ -5,11 +5,12 @@ using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Game.Graphics.Sprites;
using osu.Game.Graphics.UserInterface;
using osu.Game.Screens.Play.HUD;
using osuTK;
namespace osu.Game.Skinning
{
public class LegacyScoreCounter : ScoreCounter
public class LegacyScoreCounter : ScoreCounter, ISkinnableComponent
{
private readonly ISkin skin;