// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. using System; using osu.Framework.Caching; using osu.Framework.Graphics; using osu.Framework.Graphics.Animations; using osuTK; namespace osu.Game.Skinning { /// /// A drawable which can be skinned via an . /// public class SkinnableDrawable : SkinReloadableDrawable { /// /// The displayed component. /// public Drawable Drawable { get; private set; } /// /// Whether the drawable component should be centered in available space. /// Defaults to true. /// public bool CentreComponent { get; set; } = true; public new Axes AutoSizeAxes { get => base.AutoSizeAxes; set => base.AutoSizeAxes = value; } private readonly ISkinComponent component; private readonly ConfineMode confineMode; /// /// Create a new skinnable drawable. /// /// The namespace-complete resource name for this skinnable element. /// A function to create the default skin implementation of this element. /// A conditional to decide whether to allow fallback to the default implementation if a skinned element is not present. /// How (if at all) the should be resize to fit within our own bounds. public SkinnableDrawable(ISkinComponent component, Func defaultImplementation = null, Func allowFallback = null, ConfineMode confineMode = ConfineMode.NoScaling) : this(component, allowFallback, confineMode) { createDefault = defaultImplementation; } protected SkinnableDrawable(ISkinComponent component, Func allowFallback = null, ConfineMode confineMode = ConfineMode.NoScaling) : base(allowFallback) { this.component = component; this.confineMode = confineMode; RelativeSizeAxes = Axes.Both; } /// /// Seeks to the 0-th frame if the content of this is an . /// public void ResetAnimation() => (Drawable as IFramedAnimation)?.GotoFrame(0); private readonly Func createDefault; private readonly Cached scaling = new Cached(); private bool isDefault; protected virtual Drawable CreateDefault(ISkinComponent component) => createDefault?.Invoke(component) ?? Empty(); /// /// Whether to apply size restrictions (specified via ) to the default implementation. /// protected virtual bool ApplySizeRestrictionsToDefault => false; protected override void SkinChanged(ISkinSource skin, bool allowFallback) { Drawable = skin.GetDrawableComponent(component); isDefault = false; if (Drawable == null && allowFallback) { Drawable = CreateDefault(component); isDefault = true; } if (Drawable != null) { scaling.Invalidate(); if (CentreComponent) { Drawable.Origin = Anchor.Centre; Drawable.Anchor = Anchor.Centre; } InternalChild = Drawable; } else ClearInternal(); } protected override void Update() { base.Update(); if (!scaling.IsValid) { try { if (Drawable == null || (isDefault && !ApplySizeRestrictionsToDefault)) return; switch (confineMode) { case ConfineMode.ScaleToFit: Drawable.RelativeSizeAxes = Axes.Both; Drawable.Size = Vector2.One; Drawable.Scale = Vector2.One; Drawable.FillMode = FillMode.Fit; break; } } finally { scaling.Validate(); } } } } public enum ConfineMode { /// /// Don't apply any scaling. This allows the user element to be of any size, exceeding specified bounds. /// NoScaling, ScaleToFit, } }