/* * Copyright 2022 Peter Han * Permission is hereby granted, free of charge, to any person obtaining a copy of this software * and associated documentation files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, copy, modify, merge, publish, * distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in all copies or * substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING * BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ using System; using System.Collections.Generic; using System.Reflection; using PeterHan.PLib.Core; using PeterHan.PLib.Detours; using UnityEngine; namespace PeterHan.PLib.Options { /// /// Registers types to options entry classes that can handle them. /// public static class OptionsHandlers { private delegate IOptionsEntry CreateOption(string field, IOptionSpec spec); private delegate IOptionsEntry CreateOptionType(string field, IOptionSpec spec, Type fieldType); private delegate IOptionsEntry CreateOptionLimit(string field, IOptionSpec spec, LimitAttribute limit); /// /// Maps types to the constructor delegate that can create an options entry for them. /// private static readonly IDictionary OPTIONS_HANDLERS = new Dictionary(64); /// /// Adds a custom type to handle all options entries of a specific type. The change /// will only affect this mod's options. /// /// The property type to be handled. /// The type which will handle all option attributes of /// this type. It must subclass from IOptionsEntry and have a constructor of the /// signature HandlerType(string, IOptionSpec). public static void AddOptionClass(Type optionType, Type handlerType) { if (optionType != null && handlerType != null && !OPTIONS_HANDLERS. ContainsKey(optionType) && typeof(IOptionsEntry).IsAssignableFrom( handlerType)) { var constructors = handlerType.GetConstructors(); int n = constructors.Length; for (int i = 0; i < n; i++) { var del = CreateDelegate(handlerType, constructors[i]); if (del != null) { OPTIONS_HANDLERS[optionType] = del; break; } } } } /// /// If a candidate options entry constructor is valid, creates a delegate which can /// call the constructor. /// /// The constructor to wrap. /// The type which will handle all option attributes of this type. /// If the constructor can be used to create an options entry, a delegate /// which calls the constructor using one of the delegate types declared in this class; /// otherwise, null. private static Delegate CreateDelegate(Type handlerType, ConstructorInfo constructor) { var param = constructor.GetParameters(); int n = param.Length; Delegate result = null; // Must begin with string, IOptionsSpec if (n > 1 && param[0].ParameterType.IsAssignableFrom(typeof(string)) && param[1].ParameterType.IsAssignableFrom(typeof(IOptionSpec))) { switch (n) { case 2: result = constructor.Detour(); break; case 3: var extraType = param[2].ParameterType; if (extraType.IsAssignableFrom(typeof(LimitAttribute))) result = constructor.Detour(); else if (extraType.IsAssignableFrom(typeof(Type))) result = constructor.Detour(); break; default: PUtil.LogWarning("Constructor on options handler type " + handlerType + " cannot be constructed by OptionsHandlers"); break; } } return result; } /// /// Creates an options entry wrapper for the specified property. /// /// The property to wrap. /// The option title and tool tip. /// An options wrapper, or null if none can handle this type. public static IOptionsEntry FindOptionClass(IOptionSpec spec, PropertyInfo info) { IOptionsEntry entry = null; if (spec != null && info != null) { var type = info.PropertyType; string field = info.Name; if (type.IsEnum) // Enumeration type entry = new SelectOneOptionsEntry(field, spec, type); else if (OPTIONS_HANDLERS.TryGetValue(type, out var handler)) { if (handler is CreateOption createOption) entry = createOption.Invoke(field, spec); else if (handler is CreateOptionLimit createOptionLimit) entry = createOptionLimit.Invoke(field, spec, info. GetCustomAttribute()); else if (handler is CreateOptionType createOptionType) entry = createOptionType.Invoke(field, spec, type); } } return entry; } /// /// Adds the predefined options classes. /// internal static void InitPredefinedOptions() { if (OPTIONS_HANDLERS.Count < 1) { AddOptionClass(typeof(bool), typeof(CheckboxOptionsEntry)); AddOptionClass(typeof(int), typeof(IntOptionsEntry)); AddOptionClass(typeof(int?), typeof(NullableIntOptionsEntry)); AddOptionClass(typeof(float), typeof(FloatOptionsEntry)); AddOptionClass(typeof(float?), typeof(NullableFloatOptionsEntry)); AddOptionClass(typeof(Color32), typeof(Color32OptionsEntry)); AddOptionClass(typeof(Color), typeof(ColorOptionsEntry)); AddOptionClass(typeof(int), typeof(IntOptionsEntry)); AddOptionClass(typeof(Action), typeof(ButtonOptionsEntry)); AddOptionClass(typeof(int), typeof(IntOptionsEntry)); AddOptionClass(typeof(LocText), typeof(TextBlockOptionsEntry)); } } } }