// 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 System.Reflection; using JetBrains.Annotations; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.UserInterface; using osu.Game.Overlays.Settings; namespace osu.Game.Configuration { /// /// An attribute to mark a bindable as being exposed to the user via settings controls. /// Can be used in conjunction with to automatically create UI controls. /// /// /// All controls with OrderPosition set to an int greater than 0 will be placed first in ascending order. /// All controls with no OrderPosition will come afterward in default order. /// [MeansImplicitUse] [AttributeUsage(AttributeTargets.Property)] public class SettingSourceAttribute : Attribute { public string Label { get; } public string Description { get; } public int OrderPosition { get; } public SettingSourceAttribute(string label, string description = null, int orderPosition = -1) { Label = label ?? string.Empty; Description = description ?? string.Empty; OrderPosition = orderPosition; } } public static class SettingSourceExtensions { public static IEnumerable CreateSettingsControls(this object obj) { foreach (var (attr, property) in obj.GetOrderedSettingsSourceProperties()) { object value = property.GetValue(obj); switch (value) { case BindableNumber bNumber: yield return new SettingsSlider { LabelText = attr.Label, Bindable = bNumber, KeyboardStep = 0.1f, }; break; case BindableNumber bNumber: yield return new SettingsSlider { LabelText = attr.Label, Bindable = bNumber, KeyboardStep = 0.1f, }; break; case BindableNumber bNumber: yield return new SettingsSlider { LabelText = attr.Label, Bindable = bNumber }; break; case Bindable bBool: yield return new SettingsCheckbox { LabelText = attr.Label, Bindable = bBool }; break; case Bindable bString: yield return new SettingsTextBox { LabelText = attr.Label, Bindable = bString }; break; case IBindable bindable: var dropdownType = typeof(SettingsEnumDropdown<>).MakeGenericType(bindable.GetType().GetGenericArguments()[0]); var dropdown = (Drawable)Activator.CreateInstance(dropdownType); dropdown.GetType().GetProperty(nameof(IHasCurrentValue.Current))?.SetValue(dropdown, obj); yield return dropdown; break; default: throw new InvalidOperationException($"{nameof(SettingSourceAttribute)} was attached to an unsupported type ({value})"); } } } public static IEnumerable<(SettingSourceAttribute, PropertyInfo)> GetSettingsSourceProperties(this object obj) { foreach (var property in obj.GetType().GetProperties(BindingFlags.GetProperty | BindingFlags.Public | BindingFlags.Instance)) { var attr = property.GetCustomAttribute(true); if (attr == null) continue; yield return (attr, property); } } public static IEnumerable<(SettingSourceAttribute, PropertyInfo)> GetOrderedSettingsSourceProperties(this object obj) { var original = obj.GetSettingsSourceProperties(); var orderedRelative = original.Where(attr => attr.Item1.OrderPosition > -1).OrderBy(attr => attr.Item1.OrderPosition); var unordered = original.Except(orderedRelative); return orderedRelative.Concat(unordered); } } }