Skip to content

Commit 695abd3

Browse files
committed
Mostly implement parsing generic types from the search bar
1 parent 56dc36a commit 695abd3

5 files changed

Lines changed: 92 additions & 12 deletions

File tree

ComponentSelectorAdditions/ComponentResult.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,11 @@ public sealed class ComponentResult
4242
[MemberNotNullWhen(true, nameof(Group), nameof(GroupName))]
4343
public bool HasGroup => Group is not null;
4444

45+
/// <summary>
46+
/// Gets whether this <see cref="Type">Type</see> is a concrete generic.
47+
/// </summary>
48+
public bool IsConcreteGeneric => Type.IsGenericType && !IsGeneric;
49+
4550
/// <summary>
4651
/// Gets whether this <see cref="Type">Type</see> is a generic type definition.
4752
/// </summary>

ComponentSelectorAdditions/DefaultConfig.cs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ public sealed class DefaultConfig : ConfigSection
1818
new ConfigKeyRange<float>(32, 64)
1919
};
2020

21+
private static readonly DefiningConfigKey<bool> _useSeparateConcreteGenericColor = new("UseSeparateConcreteGenericColor", "Use a blend between the generic component buttons' green and the non-generic component buttons' cyan for concrete generics.", () => true);
22+
2123
/// <summary>
2224
/// Gets this config's instance.
2325
/// </summary>
@@ -41,6 +43,11 @@ public sealed class DefaultConfig : ConfigSection
4143
/// <value>The height in canvas units.</value>
4244
public float IndirectButtonHeight => _indirectButtonHeight;
4345

46+
/// <summary>
47+
/// Gets whether to use a blend between the generic component buttons' green and the non-generic component buttons' cyan for concrete generics.
48+
/// </summary>
49+
public bool UseSeperateConcreteGenericColor => _useSeparateConcreteGenericColor;
50+
4451
/// <inheritdoc/>
4552
public override Version Version { get; } = new Version(1, 0, 0);
4653

ComponentSelectorAdditions/DefaultHandler.cs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ public sealed class DefaultHandler : ConfiguredResoniteMonkey<DefaultHandler, De
2323
ICancelableEventHandler<BuildCategoryButtonEvent>, ICancelableEventHandler<BuildGroupButtonEvent>, ICancelableEventHandler<BuildComponentButtonEvent>,
2424
IEventHandler<BuildCustomGenericBuilder>, IEventHandler<EnumerateConcreteGenericsEvent>
2525
{
26+
private static colorX _presetConcreteColor = MathX.Average(RadiantUI_Constants.Sub.GREEN, RadiantUI_Constants.Sub.CYAN);
27+
2628
/// <inheritdoc/>
2729
public int Priority => HarmonyLib.Priority.Normal;
2830

@@ -197,8 +199,11 @@ void ICancelableEventHandler<BuildComponentButtonEvent>.Handle(BuildComponentBut
197199
var selector = eventData.Selector;
198200
var component = eventData.Component;
199201

202+
var tint = component.IsConcreteGeneric && ConfigSection.UseSeperateConcreteGenericColor
203+
? _presetConcreteColor
204+
: component.IsGeneric ? RadiantUI_Constants.Sub.GREEN : RadiantUI_Constants.Sub.CYAN;
205+
200206
var category = GetPrettyPath(component.Category, eventData.RootCategory);
201-
var tint = component.IsGeneric ? RadiantUI_Constants.Sub.GREEN : RadiantUI_Constants.Sub.CYAN;
202207
ButtonEventHandler<string> callback = component.IsGeneric ? selector.OpenGenericTypesPressed : selector.OnAddComponentPressed;
203208
var argument = $"{(component.IsGeneric ? $"{path.Path}/{component.Type.AssemblyQualifiedName}" : selector.World.Types.EncodeType(component.Type))}{(component.IsGeneric && path.HasGroup ? $"?{path.Group}" : "")}";
204209

ComponentSelectorAdditions/SearchBar.cs

Lines changed: 30 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
using FrooxEngine;
44
using MonkeyLoader.Patching;
55
using MonkeyLoader.Resonite;
6-
using MonkeyLoader.Resonite.UI;
76
using System;
87
using System.Collections.Generic;
98
using System.Linq;
@@ -12,15 +11,17 @@
1211
using MonkeyLoader.Events;
1312
using System.Threading;
1413
using System.Globalization;
14+
using MonkeyLoader;
1515

1616
namespace ComponentSelectorAdditions
1717
{
1818
internal sealed class SearchBar : ConfiguredResoniteMonkey<SearchBar, SearchConfig>, IEventHandler<BuildSelectorHeaderEvent>,
1919
ICancelableEventHandler<EnumerateCategoriesEvent>, ICancelableEventHandler<EnumerateComponentsEvent>
2020
{
2121
private const string ProtoFluxPath = "/ProtoFlux/Runtimes/Execution/Nodes";
22-
private static readonly char[] _searchSplits = new[] { ' ', ',', '+', '|' };
22+
2323
public override bool CanBeDisabled => true;
24+
2425
public int Priority => HarmonyLib.Priority.VeryHigh;
2526

2627
public bool SkipCanceled => true;
@@ -60,12 +61,10 @@ public void Handle(BuildSelectorHeaderEvent eventData)
6061

6162
public void Handle(EnumerateCategoriesEvent eventData)
6263
{
63-
if (!eventData.Path.HasSearch || ((eventData.Path.IsSelectorRoot || ConfigSection.AlwaysSearchRoot) && eventData.Path.Search.Length < 3))
64+
if (!eventData.Path.HasSearch || ((eventData.Path.IsSelectorRoot || ConfigSection.AlwaysSearchRoot) && eventData.Path.Search.Length < 3 && eventData.Path.SearchFragments.Length > 0))
6465
return;
6566

66-
var search = eventData.Path.Search.Split(_searchSplits, StringSplitOptions.RemoveEmptyEntries);
67-
68-
foreach (var category in SearchCategories(PickSearchCategory(eventData), search))
67+
foreach (var category in SearchCategories(PickSearchCategory(eventData), eventData.Path.SearchFragments))
6968
eventData.AddItem(category);
7069

7170
eventData.Canceled = true;
@@ -76,26 +75,46 @@ public void Handle(EnumerateComponentsEvent eventData)
7675
if (!eventData.Path.HasSearch || (eventData.Path.IsSelectorRoot && eventData.Path.Search.Length < 3))
7776
return;
7877

79-
var search = eventData.Path.Search.Split(_searchSplits, StringSplitOptions.RemoveEmptyEntries);
8078
var searchCategory = PickSearchCategory(eventData);
8179

8280
var results = searchCategory.Elements
83-
.Select(type => (Category: searchCategory, Type: type, Matches: SearchContains(type.Name, search)))
81+
.Select(type => (Category: searchCategory, Type: type, Matches: SearchContains(type.Name, eventData.Path.SearchFragments)))
8482
.Concat(
8583
SearchCategories(searchCategory)
8684
.SelectMany(category => category.Elements
87-
.Select(type => (Category: category, Type: type, Matches: SearchContains(type.Name, search)))))
88-
.Where(match => match.Matches > 0)
89-
.OrderByDescending(match => match.Matches)
85+
.Select(type => (Category: category, Type: type, Matches: SearchContains(type.Name, eventData.Path.SearchFragments)))))
86+
.Where(match => match.Matches > 0) // Extra weight for generic results when there's a generic in the search:
87+
.OrderByDescending(match => match.Type.IsGenericTypeDefinition && eventData.Path.HasSearchGeneric ? match.Matches + 100 : match.Matches)
9088
.ThenBy(match => match.Type.Name)
9189
.Select(match => (Component: new ComponentResult(match.Category, match.Type), Order: -match.Matches));
9290

9391
var remaining = ConfigSection.MaxResultCount;
9492
var knownGroups = new HashSet<string>();
93+
var parsedGeneric = eventData.Path.HasSearchGeneric ? eventData.Selector.World.Types.ParseNiceType(eventData.Path.SearchGeneric, true) : null;
9594

9695
foreach (var result in results.TakeWhile(result => (!result.Component.HasGroup || knownGroups.Add(result.Component.Group) ? --remaining : remaining) >= 0))
96+
{
9797
eventData.AddItem(result.Component, result.Order);
9898

99+
if (result.Component.IsGeneric && parsedGeneric is not null)
100+
{
101+
try
102+
{
103+
var concreteType = result.Component.Type.MakeGenericType(parsedGeneric);
104+
105+
if (!concreteType.IsValidGenericType(true))
106+
continue;
107+
108+
--remaining;
109+
eventData.AddItem(new(result.Component.Category, concreteType), result.Order - 1);
110+
}
111+
catch (Exception ex)
112+
{
113+
Logger.Warn(ex.LogFormat($"Failed to make generic type for component [{result.Component.NiceName}] with [{parsedGeneric.GetNiceName()}] (from \"{eventData.Path.GenericType}\")!"));
114+
}
115+
}
116+
}
117+
99118
eventData.Canceled = true;
100119
}
101120

ComponentSelectorAdditions/SelectorPath.cs

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,13 @@ public sealed class SelectorPath
1818
/// </summary>
1919
public const string SearchSegment = "Search";
2020

21+
private static readonly char _genericParamEnd = '>';
22+
private static readonly char _genericParamStart = '<';
23+
2124
private static readonly char[] _pathSeparators = { '/', '\\' };
2225

26+
private static readonly char[] _searchSplits = new[] { ' ', ',', '+', '|' };
27+
2328
/// <summary>
2429
/// Gets whether this path targets a generic type.
2530
/// </summary>
@@ -42,6 +47,12 @@ public sealed class SelectorPath
4247
[MemberNotNullWhen(true, nameof(Search))]
4348
public bool HasSearch => !string.IsNullOrWhiteSpace(Search);
4449

50+
/// <summary>
51+
/// Gets whether this path has a <see cref="SearchGeneric">generic argument</see> for the search.
52+
/// </summary>
53+
[MemberNotNullWhen(true, nameof(SearchGeneric))]
54+
public bool HasSearchGeneric => !string.IsNullOrWhiteSpace(SearchGeneric);
55+
4556
/// <summary>
4657
/// Gets whether this path targets the root category.
4758
/// </summary>
@@ -72,9 +83,42 @@ public sealed class SelectorPath
7283
/// </summary>
7384
public string? Search { get; }
7485

86+
/// <summary>
87+
/// Gets this path's search fragments (before the <see cref="SearchGeneric">generic argument</see>).
88+
/// </summary>
89+
public string[] SearchFragments { get; } = Array.Empty<string>();
90+
91+
/// <summary>
92+
/// Gets this path's generic argument for the search.
93+
/// </summary>
94+
public string? SearchGeneric { get; }
95+
7596
internal SelectorPath(string? rawPath, string? search, bool genericType, string? group, bool isSelectorRoot)
7697
{
7798
Search = search;
99+
100+
if (!string.IsNullOrWhiteSpace(search))
101+
{
102+
var genericParamStartIndex = search!.IndexOf(_genericParamStart);
103+
104+
if (genericParamStartIndex > 0)
105+
{
106+
var generic = search[(genericParamStartIndex + 1)..];
107+
var starts = generic.Count(static c => c == _genericParamStart);
108+
var ends = generic.Count(static c => c == _genericParamEnd);
109+
110+
if (starts > ends) // Automatically add any missing >
111+
generic += new string(_genericParamEnd, starts - ends);
112+
else if (ends > starts) // Probably not gonna happen often, but if someone adds a closing > to much...
113+
generic = generic.Remove(generic.Length - (ends - starts));
114+
115+
SearchGeneric = generic;
116+
search = search[..genericParamStartIndex];
117+
}
118+
119+
SearchFragments = search.Split(_searchSplits, StringSplitOptions.RemoveEmptyEntries);
120+
}
121+
78122
GenericType = genericType;
79123
Group = group;
80124
IsSelectorRoot = isSelectorRoot;

0 commit comments

Comments
 (0)