Skip to content

Commit 875d196

Browse files
committed
Implement full hierarchy of event types for Field/Reference Drive Receivers
1 parent 7a0c43a commit 875d196

4 files changed

Lines changed: 260 additions & 97 deletions

File tree

MonkeyLoader.Resonite.Integration/UI/ContextMenus/ContextMenuInjector.cs

Lines changed: 5 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,6 @@
1-
using Elements.Core;
2-
using FrooxEngine;
3-
using FrooxEngine.CommonAvatar;
4-
using FrooxEngine.UIX;
1+
using FrooxEngine;
52
using HarmonyLib;
63
using MonkeyLoader.Resonite.UI.Inspectors;
7-
using System;
8-
using System.Collections.Generic;
9-
using System.Linq;
10-
using System.Text;
11-
using System.Threading.Tasks;
124

135
namespace MonkeyLoader.Resonite.UI.ContextMenus
146
{
@@ -20,6 +12,9 @@ protected override bool OnLoaded()
2012
{
2113
ContextMenuItemsGenerationEvent.AddConcreteEvent<InspectorMemberActions>(static contextMenu => new InspectorMemberActionsMenuItemsGenerationEvent(contextMenu), true);
2214

15+
ContextMenuItemsGenerationEvent.AddConcreteEvent(typeof(FieldDriveReceiver<>), DriveReceiverMenuItemsGenerationEvent.CreateForDriveReceiver);
16+
ContextMenuItemsGenerationEvent.AddConcreteEvent(typeof(ReferenceDriveReceiver<>), DriveReceiverMenuItemsGenerationEvent.CreateForDriveReceiver);
17+
2318
return base.OnLoaded();
2419
}
2520

@@ -35,6 +30,7 @@ protected override bool OnLoaded()
3530
__instance.RunSynchronouslyAsync(async () =>
3631
{
3732
var eventData = ContextMenuItemsGenerationEvent.CreateFor(__instance);
33+
Logger.Info(() => $"Dispatching CM event: {eventData.GetType().CompactDescription()}");
3834

3935
// ContextMenuItemsGenerationEvent is a SubscribableBaseEvent and will trigger derived handlers
4036
await DispatchAsync(eventData);

MonkeyLoader.Resonite.Integration/UI/ContextMenus/ContextMenuItemsGenerationEvent.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -251,7 +251,7 @@ internal static ContextMenuItemsGenerationEvent CreateFor(ContextMenu contextMen
251251
{
252252
var currentGenericType = currentType.GetGenericTypeDefinition();
253253

254-
if (_contextMenuConstructorsBySummonerType.TryGetValue(currentType, out customContextMenuConstructor))
254+
if (_contextMenuConstructorsBySummonerType.TryGetValue(currentGenericType, out customContextMenuConstructor))
255255
{
256256
contextMenuConstructor = customContextMenuConstructor;
257257
break;
Lines changed: 254 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,254 @@
1+
using FrooxEngine;
2+
using HarmonyLib;
3+
using MonkeyLoader.Events;
4+
using MonkeyLoader.Resonite.UI.ContextMenus;
5+
6+
namespace MonkeyLoader.Resonite.UI.Inspectors
7+
{
8+
/// <summary>
9+
/// Represents a dispatchable base class for all <see cref="FieldDriveReceiver{T}"/> and
10+
/// <see cref="ReferenceDriveReceiver{T}"/> <see cref="ContextMenu"/> items generation events.
11+
/// </summary>
12+
[DispatchableBaseEvent, SubscribableBaseEvent]
13+
public abstract class DriveReceiverMenuItemsGenerationEvent : ContextMenuItemsGenerationEvent, ITargetSyncMemberEvent
14+
{
15+
private static readonly Type[] _contextMenuConstructorParameters = [typeof(ContextMenu)];
16+
17+
private static readonly Dictionary<Type, Func<ContextMenu, DriveReceiverMenuItemsGenerationEvent>> _contextMenuConstructorsBySummonerType = [];
18+
19+
private static readonly Type _fieldEventType = typeof(FieldDriveReceiverMenuItemsGenerationEvent<>);
20+
private static readonly Type _fieldReceiverType = typeof(FieldDriveReceiver<>);
21+
private static readonly Type _objectType = typeof(object);
22+
private static readonly Type _referenceEventType = typeof(ReferenceDriveReceiverMenuItemsGenerationEvent<>);
23+
private static readonly Type _referenceReceiverType = typeof(ReferenceDriveReceiver<>);
24+
25+
/// <inheritdoc/>
26+
public Slot? Slot => SlotCore;
27+
28+
/// <summary>
29+
/// Gets the <see cref="ISyncMember"/> that the summoning
30+
/// <see cref="FieldDriveReceiver{T}"/> or <see cref="ReferenceDriveReceiver{T}"/> targets.<br/>
31+
/// This will be an <see cref="IField{T}"/> for <see cref="FieldDriveReceiver{T}"/>s,
32+
/// and an <see cref="ISyncRef{T}"/> for <see cref="ReferenceDriveReceiver{T}"/>s.
33+
/// </summary>
34+
public ISyncMember Target => TargetCore;
35+
36+
/// <inheritdoc/>
37+
public User? User => UserCore;
38+
39+
/// <summary>
40+
/// Internal implementation of <see cref="Slot"/>.
41+
/// </summary>
42+
protected abstract Slot? SlotCore { get; }
43+
44+
/// <summary>
45+
/// Internal implementation of <see cref="Target"/>.
46+
/// </summary>
47+
protected abstract ISyncMember TargetCore { get; }
48+
49+
/// <summary>
50+
/// Internal implementation of <see cref="User"/>.
51+
/// </summary>
52+
protected abstract User? UserCore { get; }
53+
54+
/// <inheritdoc/>
55+
protected DriveReceiverMenuItemsGenerationEvent(ContextMenu contextMenu)
56+
: base(contextMenu)
57+
{ }
58+
59+
/// <summary>
60+
/// Creates a new <see cref="ContextMenu"/> items generation event for the given
61+
/// <paramref name="contextMenu"/> and its <see cref="Slot.ActiveUser">active</see> <see cref="User"/>,
62+
/// when the <see cref="ContextMenu.CurrentSummoner">summoner</see> is a <see cref="FieldDriveReceiver{T}"/>
63+
/// or <see cref="ReferenceDriveReceiver{T}"/>.
64+
/// </summary>
65+
/// <inheritdoc cref="DriveReceiverMenuItemsGenerationEvent(ContextMenu)"/>
66+
internal static DriveReceiverMenuItemsGenerationEvent CreateForDriveReceiver(ContextMenu contextMenu)
67+
{
68+
ArgumentNullException.ThrowIfNull(contextMenu);
69+
70+
if (contextMenu.CurrentSummoner is null)
71+
throw new ArgumentException($"Summoner was missing for Context Menu: {contextMenu.ParentHierarchyToString()}");
72+
73+
var summonerType = contextMenu.CurrentSummoner.GetType();
74+
75+
var currentType = summonerType;
76+
Func<ContextMenu, DriveReceiverMenuItemsGenerationEvent>? contextMenuConstructor = null;
77+
78+
while (currentType != _objectType)
79+
{
80+
if (_contextMenuConstructorsBySummonerType.TryGetValue(currentType, out var customContextMenuConstructor))
81+
{
82+
contextMenuConstructor = customContextMenuConstructor;
83+
break;
84+
}
85+
86+
if (currentType.IsGenericType && currentType.GenericTypeArguments.Length is 1)
87+
{
88+
var currentGenericType = currentType.GetGenericTypeDefinition();
89+
Type parameterType = currentType.GenericTypeArguments[0];
90+
91+
if (currentGenericType == _referenceReceiverType)
92+
{
93+
contextMenuConstructor = GetDriveReceiverContextMenuConstructor(_referenceEventType, parameterType);
94+
break;
95+
}
96+
97+
if (currentGenericType == _fieldReceiverType)
98+
{
99+
contextMenuConstructor = GetDriveReceiverContextMenuConstructor(_fieldEventType, parameterType);
100+
break;
101+
}
102+
}
103+
104+
// Will never be null since we stop when currentType is object
105+
currentType = currentType.BaseType!;
106+
}
107+
108+
if (contextMenuConstructor is null)
109+
throw new ArgumentException($"Summoner was not of type {typeof(FieldDriveReceiver<>).CompactDescription()} or {typeof(ReferenceDriveReceiver<>).CompactDescription()} or derived from them for Context Menu: {contextMenu.ParentHierarchyToString()}");
110+
111+
return contextMenuConstructor(contextMenu);
112+
}
113+
114+
private static Func<ContextMenu, DriveReceiverMenuItemsGenerationEvent> GetDriveReceiverContextMenuConstructor(Type rawEventType, Type parameterType)
115+
{
116+
var eventType = rawEventType.MakeGenericType(parameterType);
117+
var constructorMethod = AccessTools.DeclaredConstructor(eventType, _contextMenuConstructorParameters);
118+
return contextMenu => (DriveReceiverMenuItemsGenerationEvent)constructorMethod.Invoke([contextMenu]);
119+
}
120+
}
121+
122+
/// <summary>
123+
/// Represents a dispatchable base class for all<see cref="FieldDriveReceiver{T}"/> <see cref="ContextMenu"/> items generation events.
124+
/// </summary>
125+
[DispatchableBaseEvent, SubscribableBaseEvent]
126+
public abstract class FieldDriveReceiverMenuItemsGenerationEvent : DriveReceiverMenuItemsGenerationEvent
127+
{
128+
/// <summary>
129+
/// Gets the <see cref="IField"/> that the summoning <see cref="FieldDriveReceiver{T}"/> targets.
130+
/// </summary>
131+
public new IField Target => TargetFieldCore;
132+
133+
/// <summary>
134+
/// Internal implementation of <see cref="Target"/>.
135+
/// </summary>
136+
protected abstract IField TargetFieldCore { get; }
137+
138+
/// <inheritdoc/>
139+
protected FieldDriveReceiverMenuItemsGenerationEvent(ContextMenu contextMenu)
140+
: base(contextMenu)
141+
{ }
142+
}
143+
144+
/// <summary>
145+
/// Represents the event data for the concrete <see cref="FieldDriveReceiver{T}"/> <see cref="ContextMenu"/> items generation events.
146+
/// </summary>
147+
public sealed class FieldDriveReceiverMenuItemsGenerationEvent<T> : FieldDriveReceiverMenuItemsGenerationEvent
148+
{
149+
/// <summary>
150+
/// Gets the <see cref="FieldDriveReceiver{T}"/> that the <see cref="ContextMenu">ContextMenu</see> is being summoned by.
151+
/// </summary>
152+
public new FieldDriveReceiver<T> Summoner { get; }
153+
154+
/// <summary>
155+
/// Gets the <see cref="IField{T}"/> that the summoning <see cref="FieldDriveReceiver{T}"/> targets.
156+
/// </summary>
157+
public new IField<T> Target { get; }
158+
159+
/// <inheritdoc/>
160+
protected override sealed Slot? SlotCore { get; }
161+
162+
/// <inheritdoc/>
163+
protected override sealed IWorldElement SummonerCore => Summoner;
164+
165+
/// <inheritdoc/>
166+
protected override sealed ISyncMember TargetCore => Target;
167+
168+
/// <inheritdoc/>
169+
protected override sealed IField TargetFieldCore => Target;
170+
171+
/// <inheritdoc/>
172+
protected override sealed User? UserCore { get; }
173+
174+
/// <inheritdoc/>
175+
public FieldDriveReceiverMenuItemsGenerationEvent(ContextMenu contextMenu)
176+
: base(contextMenu)
177+
{
178+
Summoner = ContextMenu.CurrentSummoner as FieldDriveReceiver<T>
179+
?? throw new ArgumentException($"Summoner was null or not of type {typeof(FieldDriveReceiver<T>).CompactDescription()} for Context Menu: {contextMenu.ParentHierarchyToString()}");
180+
181+
Target = Summoner.Field.Target;
182+
183+
SlotCore = Target.FindNearestParent<Slot>();
184+
UserCore = Slot?.ActiveUser ?? Target.FindNearestParent<User>();
185+
}
186+
}
187+
188+
/// <summary>
189+
/// Represents a dispatchable base class for all <see cref="ReferenceDriveReceiver{T}"/> <see cref="ContextMenu"/> items generation events.
190+
/// </summary>
191+
[DispatchableBaseEvent, SubscribableBaseEvent]
192+
public abstract class ReferenceDriveReceiverMenuItemsGenerationEvent : DriveReceiverMenuItemsGenerationEvent
193+
{
194+
/// <summary>
195+
/// Gets the <see cref="ISyncRef"/> that the summoning <see cref="ReferenceDriveReceiver{T}"/> targets.
196+
/// </summary>
197+
public new ISyncRef Target => TargetSyncRefCore;
198+
199+
/// <summary>
200+
/// Internal implementation of <see cref="Target"/>.
201+
/// </summary>
202+
protected abstract ISyncRef TargetSyncRefCore { get; }
203+
204+
/// <inheritdoc/>
205+
protected ReferenceDriveReceiverMenuItemsGenerationEvent(ContextMenu contextMenu)
206+
: base(contextMenu)
207+
{ }
208+
}
209+
210+
/// <summary>
211+
/// Represents the event data for the concrete <see cref="ReferenceDriveReceiver{T}"/> <see cref="ContextMenu"/> items generation events.
212+
/// </summary>
213+
public sealed class ReferenceDriveReceiverMenuItemsGenerationEvent<T> : ReferenceDriveReceiverMenuItemsGenerationEvent
214+
where T : class, IWorldElement
215+
{
216+
/// <summary>
217+
/// Gets the <see cref="ReferenceDriveReceiver{T}"/> that the <see cref="ContextMenu">ContextMenu</see> is being summoned by.
218+
/// </summary>
219+
public new ReferenceDriveReceiver<T> Summoner { get; }
220+
221+
/// <summary>
222+
/// Gets the <see cref="ISyncRef{T}"/> that the summoning <see cref="ReferenceDriveReceiver{T}"/> targets.
223+
/// </summary>
224+
public new ISyncRef<T> Target { get; }
225+
226+
/// <inheritdoc/>
227+
protected override sealed Slot? SlotCore { get; }
228+
229+
/// <inheritdoc/>
230+
protected override sealed IWorldElement SummonerCore => Summoner;
231+
232+
/// <inheritdoc/>
233+
protected override sealed ISyncMember TargetCore => Target;
234+
235+
/// <inheritdoc/>
236+
protected override sealed ISyncRef TargetSyncRefCore => Target;
237+
238+
/// <inheritdoc/>
239+
protected override sealed User? UserCore { get; }
240+
241+
/// <inheritdoc/>
242+
public ReferenceDriveReceiverMenuItemsGenerationEvent(ContextMenu contextMenu)
243+
: base(contextMenu)
244+
{
245+
Summoner = ContextMenu.CurrentSummoner as ReferenceDriveReceiver<T>
246+
?? throw new ArgumentException($"Summoner was null or not of type {typeof(ReferenceDriveReceiver<T>).CompactDescription()} for Context Menu: {contextMenu.ParentHierarchyToString()}");
247+
248+
Target = Summoner.Reference.Target;
249+
250+
SlotCore = Target.FindNearestParent<Slot>();
251+
UserCore = Slot?.ActiveUser ?? Target.FindNearestParent<User>();
252+
}
253+
}
254+
}

MonkeyLoader.Resonite.Integration/UI/Inspectors/FieldDriveReceiverMenuItemsGenerationEvent.cs

Lines changed: 0 additions & 87 deletions
This file was deleted.

0 commit comments

Comments
 (0)