Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,18 @@ namespace MonkeyLoader.Resonite.Configuration
/// Represents a basic config key component which generate <see cref="DataFeedItem"/>s
/// for the config key to represent themselves, rather than requiring default handling.
/// </summary>
/// <inheritdoc cref="IConfigKeyCustomDataFeedItems{T}"/>
public abstract class ConfigKeyCustomDataFeedItems<T> : IConfigKeyCustomDataFeedItems<T>
{
/// <inheritdoc/>
public IDefiningConfigKey<T> ConfigKey { get; private set; } = null!;

/// <summary>
/// Gets the config item that <see cref="DataFeedItem"/>s will be generated for.
/// Creates a new instance of this component in derived classes.
/// </summary>
public IDefiningConfigKey<T> ConfigKey { get; private set; } = null!;
/// <inheritdoc cref="ConfigKeyCustomDataFeedItems{T}"/>
protected ConfigKeyCustomDataFeedItems()
{ }

/// <inheritdoc/>
public abstract IAsyncEnumerable<DataFeedItem> Enumerate(IReadOnlyList<string> path, IReadOnlyList<string> groupKeys, string? searchPhrase, object? viewData);
Expand Down Expand Up @@ -44,6 +50,17 @@ protected virtual void OnInitialize(IDefiningConfigKey<T> entity)
/// Defines the interface for config key components which generate <see cref="DataFeedItem"/>s
/// for the config key to represent themselves, rather than requiring default handling.
/// </summary>
/// <remarks>
/// Beware, that only the first of these components will be used,
/// should a <see cref="IDefiningConfigKey{T}">config key</see> have multiple.
/// </remarks>
/// <typeparam name="T">The type of the config item's value.</typeparam>
public interface IConfigKeyCustomDataFeedItems<T> : IConfigKeyComponent<IDefiningConfigKey<T>>, ICustomDataFeedItems
{ }
{
/// <summary>
/// Gets the config item that <see cref="DataFeedItem"/>s will be
/// <see cref="ICustomDataFeedItems.Enumerate">enumerated</see> for.
/// </summary>
public IDefiningConfigKey<T> ConfigKey { get; }
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
using FrooxEngine;
using MonkeyLoader.Configuration;
using MonkeyLoader.Resonite.DataFeeds.Settings;

namespace MonkeyLoader.Resonite.Configuration
{
/// <summary>
/// Implements a <see cref="IConfigKeyCustomDataFeedItems{T}">custom data feed items component</see>
/// that will use another config key as the <see cref="ConfigToSettingsExtensions.WithEnabledSource">enabled source</see>
/// for the default items generated for the <see cref="ConfigKeyCustomDataFeedItems{T}.ConfigKey">ConfigKey</see>.
/// </summary>
/// <typeparam name="T">The type of the config key </typeparam>
/// <inheritdoc/>
public sealed class ConfigKeyEnabledSource<T> : ConfigKeyCustomDataFeedItems<T>
{
/// <summary>
/// Gets the config item that will be used as the
/// <see cref="ConfigToSettingsExtensions.WithEnabledSource{TDataFeedItem}(TDataFeedItem, IDefiningConfigKey{bool})">enabled source</see>
/// for the default items generated for the <see cref="ConfigKeyCustomDataFeedItems{T}.ConfigKey">ConfigKey</see>.
/// </summary>
public IDefiningConfigKey<bool> EnabledSource { get; }

/// <summary>
/// Creates a new instance of this <see cref="IConfigKeyCustomDataFeedItems{T}">custom data feed items component</see>
/// that will use the given <see cref="IDefiningConfigKey{T}">config key</see> as the
/// <see cref="ConfigToSettingsExtensions.WithEnabledSource{TDataFeedItem}(TDataFeedItem, IDefiningConfigKey{bool})">enabled source</see>
/// for the default items generated for the <see cref="ConfigKeyCustomDataFeedItems{T}.ConfigKey">ConfigKey</see>.
/// </summary>
/// <param name="enabledSource"></param>
/// <inheritdoc cref="ConfigKeyCustomDataFeedItems{T}"/>
public ConfigKeyEnabledSource(IDefiningConfigKey<bool> enabledSource)
{
EnabledSource = enabledSource;
}

/// <inheritdoc/>
public override async IAsyncEnumerable<DataFeedItem> Enumerate(IReadOnlyList<string> path, IReadOnlyList<string> groupKeys, string? searchPhrase, object? viewData)
{
await foreach (var feedItem in ConfigKey.EnumerateDefaultItemsAsync(path, groupKeys))
yield return feedItem.WithEnabledSource(EnabledSource);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -63,10 +63,7 @@ private static async IAsyncEnumerable<DataFeedItem> EnumerateConfigAsync(Enumera
yield return sectionGroup;

var sectionGrouping = parameters.GroupKeys.Concat(configSection.Id).ToArray();

var sectionItems = configSection is ICustomDataFeedItems customItemsSection
? customItemsSection.Enumerate(parameters.Path, sectionGrouping, parameters.SearchPhrase, parameters.ViewData)
: configSection.EnumerateDefaultItemsAsync(parameters.Path, sectionGrouping, parameters.SearchPhrase, parameters.ViewData);
var sectionItems = configSection.EnumerateItemsAsync(parameters.Path, sectionGrouping, parameters.SearchPhrase, parameters.ViewData);

await foreach (var item in sectionItems)
yield return item;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,15 +13,75 @@

namespace MonkeyLoader.Resonite.DataFeeds.Settings
{
public static partial class SettingsHelpers
/// <summary>
/// Contains extension methods to generate <see cref="DataFeedItem">data feed items</see>
/// that represent <see cref="IDefiningConfigKey{T}">config keys</see>
/// in <see cref="IDataFeed">data feeds</see> - in particular, for the <see cref="SettingsDataFeed"/>.
/// </summary>
public static class ConfigToSettingsExtensions
{
private static readonly Type _dummyType = typeof(dummy);

private static readonly MethodInfo _enumerateConfigKeyItemsAsyncMethod = AccessTools.Method(typeof(SettingsHelpers), nameof(EnumerateConfigKeyItemsAsync));
private static readonly MethodInfo _enumerateEnumDefaultItemsAsyncMethod = AccessTools.Method(typeof(SettingsHelpers), nameof(EnumerateEnumDefaultItemsAsync));
private static readonly MethodInfo _enumerateNullableEnumDefaultItemsAsyncMethod = AccessTools.Method(typeof(SettingsHelpers), nameof(EnumerateNullableEnumDefaultItemsAsync));
private static readonly MethodInfo _enumerateConfigKeyItemsAsyncMethod = AccessTools.FirstMethod(typeof(ConfigToSettingsExtensions), method => method.IsGenericMethod && method.Name is nameof(EnumerateItemsAsync));
private static readonly MethodInfo _enumerateEnumDefaultItemsAsyncMethod = AccessTools.Method(typeof(ConfigToSettingsExtensions), nameof(EnumerateEnumDefaultItemsAsync));
private static readonly MethodInfo _enumerateNullableEnumDefaultItemsAsyncMethod = AccessTools.Method(typeof(ConfigToSettingsExtensions), nameof(EnumerateNullableEnumDefaultItemsAsync));

private static readonly MethodInfo _makeQuantityFieldMethod = AccessTools.FirstMethod(typeof(SettingsHelpers), method => method.GetParameters().Length is 4 && method.Name is nameof(MakeQuantityField));
private static readonly MethodInfo _makeQuantityFieldMethod = AccessTools.FirstMethod(typeof(ConfigToSettingsExtensions), method => method.GetParameters().Length is 4 && method.Name is nameof(MakeQuantityField));

private static Mod Mod => MonkeyLoaderRootCategorySettingsItems.Mod;

/// <summary>
/// Gets the data feed items that represent this config key.
/// </summary>
/// <remarks>
/// <b>Do not</b> use this method on the config section that you are implementing a
/// <see cref="ICustomDataFeedItems">custom items</see> for.<br/>
/// Use <c><paramref name="configSection"/>.<see cref="EnumerateDefaultItemsAsync(ConfigSection,
/// IReadOnlyList{string}, IReadOnlyList{string}, string?, object?)">EnumerateDefaultItemsAsync</see>(<paramref name="path"/>, <paramref name="groupKeys"/>,
/// <paramref name="searchPhrase"/>, <paramref name="viewData"/>)</c> instead
/// to avoid infinite recursion and a <see cref="StackOverflowException">stack overflow</see>.
/// </remarks>
/// <param name="configSection">The config section to enumerate the default or custom items for.</param>
/// <returns>
/// The <see cref="ICustomDataFeedItems">custom items</see> for this config section
/// if it has a <see cref="ICustomDataFeedItems">custom items</see> implementation;
/// otherwise, the <see cref="EnumerateDefaultItemsAsync(ConfigSection,
/// IReadOnlyList{string}, IReadOnlyList{string}, string?, object?)">default items</see>.
/// </returns>
/// <inheritdoc cref="EnumerateDefaultItemsAsync(ConfigSection, IReadOnlyList{string}, IReadOnlyList{string}, string?, object?)"/>
public static IAsyncEnumerable<DataFeedItem> EnumerateItemsAsync(this ConfigSection configSection, IReadOnlyList<string> path, IReadOnlyList<string> groupKeys, string? searchPhrase = null, object? viewData = null)
=> (configSection as ICustomDataFeedItems)?.Enumerate(path, groupKeys, searchPhrase, viewData)
?? configSection.EnumerateDefaultItemsAsync(path, groupKeys, searchPhrase, viewData);

/// <summary>
/// Gets the data feed items that represent this config key.
/// </summary>
/// <remarks>
/// <b>Do not</b> use this method on the config key that you are implementing a
/// <see cref="IConfigKeyCustomDataFeedItems{T}">custom items component</see> for.<br/>
/// Use <c><paramref name="configKey"/>.<see cref="EnumerateDefaultItemsAsync{T}(IDefiningConfigKey{T},
/// IReadOnlyList{string}, IReadOnlyList{string})">EnumerateDefaultItemsAsync</see>(<paramref name="path"/>, <paramref name="groupKeys"/>)</c>
/// instead to avoid infinite recursion and a <see cref="StackOverflowException">stack overflow</see>.
/// </remarks>
/// <param name="configKey">The config key to enumerate the default or custom items for.</param>
/// <returns>
/// The <see cref="ICustomDataFeedItems">custom items</see> for this config key
/// if it has a <see cref="IConfigKeyCustomDataFeedItems{T}">custom items component</see>;
/// otherwise, the <see cref="EnumerateDefaultItemsAsync{T}(IDefiningConfigKey{T},
/// IReadOnlyList{string}, IReadOnlyList{string})">default items</see>.
/// </returns>
/// <inheritdoc cref="EnumerateDefaultItemsAsync{T}(IDefiningConfigKey{T}, IReadOnlyList{string}, IReadOnlyList{string})"/>
public static IAsyncEnumerable<DataFeedItem> EnumerateItemsAsync<T>(this IDefiningConfigKey<T> configKey, IReadOnlyList<string> path, IReadOnlyList<string> groupKeys, string? searchPhrase = null, object? viewData = null)
{
IEntity<IDefiningConfigKey<T>> configKeyEntity = configKey;

// If the config key has a custom items component, it has priority.
if (configKeyEntity.Components.TryGet<IConfigKeyCustomDataFeedItems<T>>(out var customItems))
return customItems.Enumerate(path, groupKeys, searchPhrase, viewData);

// Otherwise, use the default items.
return configKey.EnumerateDefaultItemsAsync(path, groupKeys);
}

/// <summary>
/// Enumerates the default data feed items - as opposed to the
Expand Down Expand Up @@ -80,6 +140,9 @@ public static IAsyncEnumerable<DataFeedItem> EnumerateDefaultItemsAsync<T>(this
// return (DataFeedItem)_generateIndicator.MakeGenericMethod(type).Invoke(null, new object[4] { identity, setting, path, grouping });
//}

// If the config key is a dummy value, it's a spacer from an RML mod
// ResoniteModSettings displays the description instead of the name,
// so we do the same for spacers that have one, as they're used to indicate sections.
if (configKey.ValueType == _dummyType)
{
var dummyField = new DataFeedValueField<dummy>();
Expand All @@ -88,9 +151,11 @@ public static IAsyncEnumerable<DataFeedItem> EnumerateDefaultItemsAsync<T>(this
return dummyField.YieldAsync();
}

// If the config key is a boolean value, create a toggle item.
if (configKey.ValueType == typeof(bool))
return ((IDefiningConfigKey<bool>)configKey).MakeToggle(path, groupKeys).YieldAsync();

// If the config key is an enum value, create an enum selector.
if (configKey.ValueType.IsEnum)
{
var enumItems = (IAsyncEnumerable<DataFeedItem>)_enumerateEnumDefaultItemsAsyncMethod
Expand All @@ -100,10 +165,12 @@ public static IAsyncEnumerable<DataFeedItem> EnumerateDefaultItemsAsync<T>(this
return enumItems;
}

// If the config key is a nullable ...
if (configKey.ValueType.IsNullable())
{
var nullableType = configKey.ValueType.GetGenericArguments()[0];

// ... enum value, create a selector for nullable enums.
if (nullableType.IsEnum)
{
var nullableEnumItems = (IAsyncEnumerable<DataFeedItem>)_enumerateNullableEnumDefaultItemsAsyncMethod
Expand All @@ -114,8 +181,10 @@ public static IAsyncEnumerable<DataFeedItem> EnumerateDefaultItemsAsync<T>(this
}
}

// If the config key has a range component ...
if (configKeyEntity.Components.TryGet<IConfigKeyRange<T>>(out var range))
{
// ... and that is also a quantity component, create a quantity item.
if (configKeyEntity.Components.TryGet<IConfigKeyQuantity<T>>(out var quantity))
{
var quantityField = (DataFeedItem)_makeQuantityFieldMethod
Expand All @@ -125,10 +194,13 @@ public static IAsyncEnumerable<DataFeedItem> EnumerateDefaultItemsAsync<T>(this
return quantityField.YieldAsync();
}

// Otherwise, create a normal slider item.
var slider = configKey.MakeSlider(range, path, groupKeys);
return slider.YieldAsync();
}

// Finally, fall back to generic value field item.
// It's the callers responsibility to ensure that there's a suitable item template.
var valueField = configKey.MakeValueField(path, groupKeys);
return valueField.YieldAsync();
}
Expand Down Expand Up @@ -289,14 +361,35 @@ public static DataFeedValueField<T> MakeValueField<T>(this IDefiningConfigKey<T>
return valueField;
}

private static IAsyncEnumerable<DataFeedItem> EnumerateConfigKeyItemsAsync<T>(IDefiningConfigKey<T> configKey, IReadOnlyList<string> path, IReadOnlyList<string> groupKeys, string? searchPhrase = null, object? viewData = null)
/// <summary>
/// <see cref="DataFeedItem.InitEnabled">Initializes the enabled field</see> of this data feed item by
/// <see cref="FieldConfigKeySyncExtensions.SyncWithConfigKey{T}(IField{T}, IDefiningConfigKey{T}, string?,
/// bool)">synchronizing</see> it with the given boolean config key.
/// </summary>
/// <typeparam name="TDataFeedItem">The type of the data feed item being initialized.</typeparam>
/// <param name="feedItem">The data feed item being initialized.</param>
/// <param name="enabledSource">The boolean config key to synchronize this data feed item's enabled field with.</param>
/// <returns>This data feed item.</returns>
public static TDataFeedItem WithEnabledSource<TDataFeedItem>(this TDataFeedItem feedItem, IDefiningConfigKey<bool> enabledSource)
where TDataFeedItem : DataFeedItem
{
IEntity<IDefiningConfigKey<T>> configKeyEntity = configKey;

if (configKeyEntity.Components.TryGet<IConfigKeyCustomDataFeedItems<T>>(out var customItems))
return customItems.Enumerate(path, groupKeys, searchPhrase, viewData);
feedItem.InitEnabled(enabledField => enabledField.SyncWithConfigKey(enabledSource, allowWriteBack: false));
return feedItem;
}

return configKey.EnumerateDefaultItemsAsync(path, groupKeys);
/// <summary>
/// <see cref="DataFeedItem.InitEnabled">Initializes the enabled field</see>
/// of this data feed item by setting it to the given <paramref name="enabled"/> state.
/// </summary>
/// <typeparam name="TDataFeedItem">The type of the data feed item being initialized.</typeparam>
/// <param name="feedItem">The data feed item being initialized.</param>
/// <param name="enabled">The enabled state to set this data feed item's enabled field to.</param>
/// <returns>This data feed item.</returns>
public static TDataFeedItem WithEnabledState<TDataFeedItem>(this TDataFeedItem feedItem, bool enabled)
where TDataFeedItem : DataFeedItem
{
feedItem.InitEnabled(enabledField => enabledField.Value = enabled);
return feedItem;
}

private static IAsyncEnumerable<DataFeedItem> EnumerateEnumDefaultItemsAsync<T>(this IDefiningConfigKey configKey, IReadOnlyList<string> path, IReadOnlyList<string> groupKeys)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,9 +71,8 @@
private static readonly Dictionary<SettingsDataFeed, SettingsViewData> _settingsViewsByFeed = [];

private static Logger Logger => MonkeyLoaderRootCategorySettingsItems.Logger;
private static Mod Mod => MonkeyLoaderRootCategorySettingsItems.Mod;

public static async IAsyncEnumerable<DataFeedItem> EnumerateMonkeysAsync(EnumerateDataFeedParameters<SettingsDataFeed> parameters, Mod mod, string monkeyType, Mod localeMod, bool forceCheck = false, bool canBeDisabled = true)

Check warning on line 75 in MonkeyLoader.Resonite.Integration/DataFeeds/Settings/SettingsHelpers.cs

View workflow job for this annotation

GitHub Actions / Build

Missing XML comment for publicly visible type or member 'SettingsHelpers.EnumerateMonkeysAsync(EnumerateDataFeedParameters<SettingsDataFeed>, Mod, string, Mod, bool, bool)'

Check warning on line 75 in MonkeyLoader.Resonite.Integration/DataFeeds/Settings/SettingsHelpers.cs

View workflow job for this annotation

GitHub Actions / Build

Missing XML comment for publicly visible type or member 'SettingsHelpers.EnumerateMonkeysAsync(EnumerateDataFeedParameters<SettingsDataFeed>, Mod, string, Mod, bool, bool)'
{
await Task.CompletedTask;

Expand Down Expand Up @@ -190,7 +189,8 @@
/// </summary>
/// <typeparam name="T">The type to check.</typeparam>
/// <returns><c>true</c> if <typeparamref name="T"/> is suitable for a template; otherwise, <c>false</c>.</returns>
public static bool IsInjectableEditorType<T>() => IsInjectableEditorType(typeof(T));
public static bool IsInjectableEditorType<T>()
=> IsInjectableEditorType(typeof(T));

/// <summary>
/// Move up one element from the current path of the <see cref="EnumerateDataFeedParameters{TDataFeed}.DataFeed">DataFeed</see>.
Expand Down
Loading
Loading