// 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 System.Collections.Generic; using System.Linq; using Newtonsoft.Json; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Logging; using osu.Game.Configuration; using osu.Game.Extensions; using osu.Game.Rulesets; using osuTK; namespace osu.Game.Skinning { /// /// Serialised backing data for s. /// Used for json serialisation in user skins. /// /// /// Can be created using . /// Can also be applied to an existing drawable using . /// [Serializable] public sealed class SerialisedDrawableInfo { public Type Type { get; set; } = null!; public Vector2 Position { get; set; } public float Rotation { get; set; } public Vector2 Scale { get; set; } = Vector2.One; public float? Width { get; set; } public float? Height { get; set; } public Anchor Anchor { get; set; } = Anchor.TopLeft; public Anchor Origin { get; set; } = Anchor.TopLeft; /// public bool UsesFixedAnchor { get; set; } public Dictionary Settings { get; set; } = new Dictionary(); public List Children { get; } = new List(); [JsonConstructor] public SerialisedDrawableInfo() { } /// /// Construct a new instance populating all attributes from the provided drawable. /// /// The drawable which attributes should be sourced from. public SerialisedDrawableInfo(Drawable component) { Type = component.GetType(); Position = component.Position; Rotation = component.Rotation; Scale = component.Scale; if ((component as CompositeDrawable)?.AutoSizeAxes.HasFlag(Axes.X) != true) Width = component.Width; if ((component as CompositeDrawable)?.AutoSizeAxes.HasFlag(Axes.Y) != true) Height = component.Height; Anchor = component.Anchor; Origin = component.Origin; if (component is ISerialisableDrawable serialisableDrawable) UsesFixedAnchor = serialisableDrawable.UsesFixedAnchor; foreach (var (_, property) in component.GetSettingsSourceProperties()) { var bindable = (IBindable)property.GetValue(component)!; Settings.Add(property.Name.ToSnakeCase(), bindable.GetUnderlyingSettingValue()); } if (component is Container container) { foreach (var child in container.OfType().OfType()) Children.Add(child.CreateSerialisedInfo()); } } /// /// Construct an instance of the drawable with all attributes applied. /// /// The new instance. public Drawable CreateInstance() { try { Drawable d = (Drawable)Activator.CreateInstance(Type)!; d.ApplySerialisedInfo(this); return d; } catch (Exception e) { Logger.Error(e, $"Unable to create skin component {Type.Name}"); return Drawable.Empty(); } } /// /// Retrieve all types available which support serialisation. /// /// The ruleset to filter results to. If null, global components will be returned instead. public static Type[] GetAllAvailableDrawables(RulesetInfo? ruleset = null) { return (ruleset?.CreateInstance().GetType() ?? typeof(OsuGame)) .Assembly.GetTypes() .Where(t => !t.IsInterface && !t.IsAbstract && t.IsPublic) .Where(t => typeof(ISerialisableDrawable).IsAssignableFrom(t)) .OrderBy(t => t.Name) .ToArray(); } } }