Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add saveable custom generic argument presets #5

Merged
merged 2 commits into from
Jul 7, 2024
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
57 changes: 57 additions & 0 deletions ComponentSelectorAdditions/Events/BuildCustomGenericBuilder.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
using FrooxEngine;
using FrooxEngine.UIX;
using MonkeyLoader;
using MonkeyLoader.Resonite.Events;
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace ComponentSelectorAdditions.Events
{
public sealed class BuildCustomGenericBuilder : BuildUIEvent
{
internal readonly HashSet<Button> OtherAddedButtonsSet = new();

[MemberNotNullWhen(true, nameof(CreateCustomTypeButton))]
public bool AddsCreateCustomTypeButton => CreateCustomTypeButton is not null;

public bool AddsGenericArgumentInputs { get; set; }

/// <summary>
/// Gets the Type of the <see cref="FrooxEngine.Component"/>
/// that the custom generic builder is meant for.
/// </summary>
public Type Component { get; }

/// <summary>
/// Gets or sets the button that triggers creating the custom type with the defined generic arguments.<br/>
/// Assigning the <see cref="ComponentSelector._customGenericTypeLabel">Label</see>
/// and <see cref="ComponentSelector._customGenericTypeColor">Color</see>
/// drives is handled by the event source.
/// </summary>
public Button? CreateCustomTypeButton { get; set; }

public Type[] GenericArguments { get; }
public IEnumerable<Button> OtherAddedButtons => OtherAddedButtonsSet.AsSafeEnumerable();
public ComponentSelector Selector { get; }

public BuildCustomGenericBuilder(ComponentSelector selector, UIBuilder ui, Type component) : base(ui)
{
Selector = selector;
Component = component;
GenericArguments = component.GetGenericArguments();
}

public bool AddOtherButton(Button button)
=> OtherAddedButtonsSet.Add(button);

public bool HasOtherButton(Button button)
=> OtherAddedButtonsSet.Contains(button);

public bool RemoveOtherButton(Button button)
=> OtherAddedButtonsSet.Remove(button);
}
}
4 changes: 2 additions & 2 deletions ComponentSelectorAdditions/Events/PostProcessButtonsEvent.cs
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ public sealed class PostProcessButtonsEvent : BuildUIEvent

public ComponentSelector Selector { get; }

internal PostProcessButtonsEvent(ComponentSelector selector, SelectorPath path, UIBuilder ui, Button? backButton, Button? customGenericButton, Button? cancelButton)
internal PostProcessButtonsEvent(ComponentSelector selector, SelectorPath path, UIBuilder ui, Button? backButton, Button? customGenericButton, Button? cancelButton, HashSet<Button>? otherAddedButtons)
: base(ui)
{
Selector = selector;
Expand All @@ -51,7 +51,7 @@ internal PostProcessButtonsEvent(ComponentSelector selector, SelectorPath path,
CancelButton = cancelButton;

var buttons = selector._uiRoot.Target
.GetComponentsInChildren<Button>(button => button != backButton && button != cancelButton)
.GetComponentsInChildren<Button>(button => button != backButton && button != cancelButton && button != customGenericButton && (otherAddedButtons is null || !otherAddedButtons.Contains(button)))
.Select(button => (Button: button, Relay: button.Slot.GetComponent<ButtonRelay<string>>()))
.Where(data => data.Relay != null)
.ToArray();
Expand Down
15 changes: 5 additions & 10 deletions ComponentSelectorAdditions/FavoritesCategories.cs
Original file line number Diff line number Diff line change
Expand Up @@ -39,9 +39,7 @@ public void Handle(EnumerateComponentsEvent eventData)
{
foreach (var element in eventData.RootCategory.Elements)
{
var name = element.FullName;

if (ConfigSection.Components.Contains(name) || ConfigSection.ProtoFluxNodes.Contains(name))
if (ConfigSection.Components.Contains(element) || ConfigSection.ProtoFluxNodes.Contains(element))
eventData.AddItem(new ComponentResult(eventData.RootCategory, element), -1000, true);
}
}
Expand All @@ -50,7 +48,6 @@ public void Handle(EnumerateComponentsEvent eventData)
}

var favoriteElements = (eventData.RootCategory == _favoritesCategory ? ConfigSection.Components : ConfigSection.ProtoFluxNodes)
.Select(typeName => WorkerManager.ParseNiceType(typeName))
.Where(type => type is not null)
.Select(type => (Type: type, Category: WorkerInitializer.ComponentLibrary.GetSubcategory(WorkerInitializer.GetInitInfo(type).CategoryPath)));

Expand Down Expand Up @@ -123,8 +120,6 @@ public void Handle(EnumerateConcreteGenericsEvent eventData)
{
var concreteGenerics = ConfigSection.Components
.Concat(ConfigSection.ProtoFluxNodes)
.Where(typeName => typeName.Contains('`')) // Simple filter check as the presence of this indicates a generic type
.Select(typeName => WorkerManager.ParseNiceType(typeName))
.Where(type => type is not null && type.IsGenericType && !type.ContainsGenericParameters && type.GetGenericTypeDefinition() == eventData.Component);

foreach (var concreteGeneric in concreteGenerics)
Expand Down Expand Up @@ -222,10 +217,10 @@ private bool IsFavoriteCategory(string name)
=> ConfigSection.Categories.Contains(name);

private bool IsFavoriteComponent(string name)
=> ConfigSection.Components.Contains(name);
=> ConfigSection.Components.Contains(WorkerManager.ParseNiceType(name));

private bool IsFavoriteProtoFluxNode(string name)
=> ConfigSection.ProtoFluxNodes.Contains(name);
=> ConfigSection.ProtoFluxNodes.Contains(WorkerManager.ParseNiceType(name));

private bool IsProtoFluxFavoriteCategory(string name)
=> ConfigSection.ProtoFluxCategories.Contains(name);
Expand All @@ -234,10 +229,10 @@ private bool ToggleFavoriteCategory(string name)
=> ToggleHashSetContains(ConfigSection.Categories, name);

private bool ToggleFavoriteComponent(string name)
=> ToggleHashSetContains(ConfigSection.Components, name);
=> ToggleHashSetContains(ConfigSection.Components, WorkerManager.ParseNiceType(name));

private bool ToggleFavoriteProtoFluxNode(string name)
=> ToggleHashSetContains(ConfigSection.ProtoFluxNodes, name);
=> ToggleHashSetContains(ConfigSection.ProtoFluxNodes, WorkerManager.ParseNiceType(name));

private bool ToggleProtoFluxFavoriteCategory(string name)
=> ToggleHashSetContains(ConfigSection.ProtoFluxCategories, name);
Expand Down
15 changes: 8 additions & 7 deletions ComponentSelectorAdditions/FavoritesConfig.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using MonkeyLoader.Configuration;
using FrooxEngine;
using MonkeyLoader.Configuration;
using System;
using System.Collections.Generic;
using System.Linq;
Expand All @@ -9,20 +10,20 @@ namespace ComponentSelectorAdditions
{
internal sealed class FavoritesConfig : ConfigSection
{
private static readonly DefiningConfigKey<HashSet<string>> _categoriesKey = new("FavoriteCategories", "Favorited Categories", () => new HashSet<string>() { "/Data/Dynamic" }, true);
private static readonly DefiningConfigKey<HashSet<string>> _componentsKey = new("FavoriteComponents", "Favorited Components", () => new HashSet<string>() { "FrooxEngine.ValueMultiDriver`1", "FrooxEngine.ReferenceMultiDriver`1" }, true);
private static readonly DefiningConfigKey<HashSet<string>> _protoFluxCategoriesKey = new("FavoriteProtoFluxCategories", "Favorited ProtoFlux Categories", () => new HashSet<string>() { }, true);
private static readonly DefiningConfigKey<HashSet<string>> _protoFluxNodesKey = new("FavoriteProtoFluxNodes", "Favorited ProtoFlux Nodes", () => new HashSet<string>() { }, true);
private static readonly DefiningConfigKey<HashSet<string>> _categoriesKey = new("FavoriteCategories", "Favorited Categories", () => new HashSet<string> { "/Data/Dynamic" }, true);
private static readonly DefiningConfigKey<HashSet<Type>> _componentsKey = new("FavoriteComponents", "Favorited Components", () => new HashSet<Type> { typeof(ValueMultiDriver<>), typeof(ReferenceMultiDriver<>) }, true);
private static readonly DefiningConfigKey<HashSet<string>> _protoFluxCategoriesKey = new("FavoriteProtoFluxCategories", "Favorited ProtoFlux Categories", () => new HashSet<string> { }, true);
private static readonly DefiningConfigKey<HashSet<Type>> _protoFluxNodesKey = new("FavoriteProtoFluxNodes", "Favorited ProtoFlux Nodes", () => new HashSet<Type> { }, true);
private static readonly DefiningConfigKey<bool> _sortFavoriteCategoriesToTop = new("SortFavoriteCategoriesToTop", "Sort favorited Categories above unfavorited ones.", () => false);
private static readonly DefiningConfigKey<bool> _sortFavoriteComponentsToTop = new("SortFavoriteComponentsToTop", "Sort favorited Components / Nodes above unfavorited ones.", () => true);
private static readonly DefiningConfigKey<bool> _sortFavoriteConcreteGenericsToTop = new("SortFavoriteConcreteGenericsToTop", "Sort favorited concrete generic Components / Nodes above unfavorited ones.", () => true);
public HashSet<string> Categories => _categoriesKey.GetValue()!;

public HashSet<string> Components => _componentsKey.GetValue()!;
public HashSet<Type> Components => _componentsKey.GetValue()!;
public override string Description => "Contains the favorited categories and components.";
public override string Id => "Favorites";
public HashSet<string> ProtoFluxCategories => _protoFluxCategoriesKey.GetValue()!;
public HashSet<string> ProtoFluxNodes => _protoFluxNodesKey.GetValue()!;
public HashSet<Type> ProtoFluxNodes => _protoFluxNodesKey.GetValue()!;
public bool SortFavoriteCategoriesToTop => _sortFavoriteCategoriesToTop.GetValue();
public bool SortFavoriteComponentsToTop => _sortFavoriteComponentsToTop.GetValue();
public bool SortFavoriteConcreteGenericsToTop => _sortFavoriteConcreteGenericsToTop.GetValue();
Expand Down
18 changes: 18 additions & 0 deletions ComponentSelectorAdditions/GenericPresetsConfig.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
using FrooxEngine;
using MonkeyLoader;
using MonkeyLoader.Configuration;
using System;
using System.Collections.Generic;

namespace ComponentSelectorAdditions
{
internal sealed class GenericPresetsConfig : ConfigSection
{
private static readonly DefiningConfigKey<HashSet<Sequence<Type>>> _presetsKey = new("Presets", "Generic argument presets to attempt to add as concrete types to the generic Component / Node selection.", () => new HashSet<Sequence<Type>> { new[] { typeof(User) }, new[] { typeof(Slot) } }, true);
public override string Description => "Contains options for adding more concrete versions of generic Components / Nodes to the selection.";
public HashSet<Sequence<Type>> GenericArgumentPresets => _presetsKey.GetValue()!;
public override string Id => "GenericPresets";
public override bool InternalAccessOnly => true;
public override Version Version { get; } = new Version(1, 0, 0);
}
}
160 changes: 160 additions & 0 deletions ComponentSelectorAdditions/GenericPresetsHandler.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
using ComponentSelectorAdditions.Events;
using Elements.Core;
using FrooxEngine;
using FrooxEngine.UIX;
using MonkeyLoader;
using MonkeyLoader.Events;
using MonkeyLoader.Patching;
using MonkeyLoader.Resonite;
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace ComponentSelectorAdditions
{
internal sealed class GenericPresetsHandler : ConfiguredResoniteMonkey<GenericPresetsHandler, GenericPresetsConfig>,
IEventHandler<BuildCustomGenericBuilder>, IEventHandler<EnumerateConcreteGenericsEvent>
{
public override bool CanBeDisabled => true;
public int Priority => HarmonyLib.Priority.Normal;

public void Handle(EnumerateConcreteGenericsEvent eventData)
{
if (!Enabled)
return;

var parameters = eventData.Component.GetGenericArguments().Length;
var concreteOptions = ConfigSection.GenericArgumentPresets
.Where(preset => preset.Length == parameters)
.Select(preset => MakeGenericType(eventData.Component, preset))
.Where(type => type is not null && type.IsValidGenericType(true));

foreach (var concreteOption in concreteOptions)
eventData.AddItem(concreteOption!);
}

public void Handle(BuildCustomGenericBuilder eventData)
{
if (!Enabled)
return;

var ui = eventData.UI;
var selector = eventData.Selector;

ui.PushStyle();
ui.Style.FlexibleWidth = 1;
ui.Style.ForceExpandHeight = true;

ui.HorizontalLayout(4, 0, Alignment.MiddleLeft);
ui.FitContent(SizeFit.Disabled, SizeFit.PreferredSize);
ui.VerticalLayout(8, 0);

foreach (var genericArgument in eventData.GenericArguments)
{
var textField = ui.HorizontalElementWithLabel(genericArgument.Name, .05f, () =>
{
var textField = ui.TextField(null, false, null, false);
textField.Text.NullContent.AssignLocaleString(Mod.GetLocaleString("EnterType"));

return textField;
}, out var label);

label.HorizontalAlign.Value = Elements.Assets.TextHorizontalAlignment.Center;

if (selector.GenericArgumentPrefiller.Target != null)
textField.TargetString = selector.GenericArgumentPrefiller.Target(eventData.Component, genericArgument);

selector._customGenericArguments.Add(textField);
}

ui.NestOut();
ui.Style.FlexibleWidth = -1;

ui.Style.PreferredHeight = 32;
ui.Style.PreferredWidth = 32;
var addPresetButton = ui.Button(OfficialAssets.Graphics.Icons.World_Categories.FeaturedRibbon, RadiantUI_Constants.BUTTON_COLOR, RadiantUI_Constants.Neutrals.DARKLIGHT);
var icon = addPresetButton.Slot.GetComponentsInChildren<Image>().Last();

addPresetButton.Slot.DestroyWhenLocalUserLeaves();
addPresetButton.StartTask(UpdatePresetButtonAsync);
addPresetButton.LocalPressed += (btn, btnEvent) => ToggleSavedPreset(selector.GetCustomGenericType());

eventData.AddsGenericArgumentInputs = true;
ui.PopStyle();
ui.NestOut();

async Task UpdatePresetButtonAsync()
{
while (addPresetButton.FilterWorldElement() is not null)
{
await default(NextUpdate);

var concreteType = selector.GetCustomGenericType();

addPresetButton.Enabled = IsValidConcreteType(concreteType);
icon.Tint.Value = addPresetButton.Enabled && IsPreset(concreteType.GenericTypeArguments) ?
RadiantUI_Constants.Hero.YELLOW : RadiantUI_Constants.Neutrals.DARKLIGHT;
}
}
}

protected override IEnumerable<IFeaturePatch> GetFeaturePatches() => Enumerable.Empty<IFeaturePatch>();

protected override bool OnEngineReady()
{
Mod.RegisterEventHandler<BuildCustomGenericBuilder>(this);
Mod.RegisterEventHandler<EnumerateConcreteGenericsEvent>(this);

return base.OnEngineReady();
}

protected override bool OnShutdown(bool applicationExiting)
{
if (!applicationExiting)
{
Mod.UnregisterEventHandler<BuildCustomGenericBuilder>(this);
Mod.RegisterEventHandler<EnumerateConcreteGenericsEvent>(this);
}

return base.OnShutdown(applicationExiting);
}

private static bool IsPreset(Sequence<Type> arguments)
=> ConfigSection.GenericArgumentPresets.Contains(arguments);

private static bool IsValidConcreteType([NotNullWhen(true)] Type? concreteType)
=> concreteType is not null && concreteType.IsValidGenericType(true);

private static Type? MakeGenericType(Type genericType, Type[] arguments)
{
try
{
return genericType.MakeGenericType(arguments);
}
catch
{
return null;
}
}

private static void ToggleSavedPreset(Type? concreteType)
{
if (!IsValidConcreteType(concreteType))
return;

var arguments = concreteType.GenericTypeArguments;

if (ConfigSection.GenericArgumentPresets.Add(arguments))
{
Config.Save();
return;
}

ConfigSection.GenericArgumentPresets.Remove(arguments);
Config.Save();
}
}
}
Loading
Loading