Skip to content

Commit 9fff578

Browse files
committed
Add ConfigurationManagerAttributes.CustomHotkeyDrawer
Closes #56
1 parent 093fee8 commit 9fff578

File tree

3 files changed

+80
-4
lines changed

3 files changed

+80
-4
lines changed

ConfigurationManager/SettingEntryBase.cs

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -31,10 +31,24 @@ public abstract class SettingEntryBase
3131
public bool? ShowRangeAsPercent { get; protected set; }
3232

3333
/// <summary>
34-
/// Custom setting draw action
34+
/// Custom setting draw action.
35+
/// Use either CustomDrawer or CustomHotkeyDrawer, using both at the same time leads to undefined behaviour.
3536
/// </summary>
3637
public Action<BepInEx.Configuration.ConfigEntryBase> CustomDrawer { get; private set; }
3738

39+
/// <summary>
40+
/// Custom setting draw action that allows polling keyboard input with the Input class.
41+
/// Use either CustomDrawer or CustomHotkeyDrawer, using both at the same time leads to undefined behaviour.
42+
/// </summary>
43+
public CustomHotkeyDrawerFunc CustomHotkeyDrawer { get; private set; }
44+
45+
/// <summary>
46+
/// Custom setting draw action that allows polling keyboard input with the Input class.
47+
/// </summary>
48+
/// <param name="setting">Setting currently being set, is available</param>
49+
/// <param name="isCurrentlyAcceptingInput">Set this ref parameter to true when you want the current setting drawer to receive Input events. Remember to set it to false after you are done!</param>
50+
public delegate void CustomHotkeyDrawerFunc(BepInEx.Configuration.ConfigEntryBase setting, ref bool isCurrentlyAcceptingInput);
51+
3852
/// <summary>
3953
/// Show this setting in the settings screen at all? If false, don't show.
4054
/// </summary>
@@ -144,7 +158,7 @@ internal void SetFromAttributes(object[] attribs, BaseUnityPlugin pluginInstance
144158
switch (attrib)
145159
{
146160
case null: break;
147-
161+
148162
case DisplayNameAttribute da:
149163
DispName = da.DisplayName;
150164
break;
@@ -163,7 +177,7 @@ internal void SetFromAttributes(object[] attribs, BaseUnityPlugin pluginInstance
163177
case BrowsableAttribute bro:
164178
Browsable = bro.Browsable;
165179
break;
166-
180+
167181
case Action<SettingEntryBase> newCustomDraw:
168182
CustomDrawer = _ => newCustomDraw(this);
169183
break;
@@ -189,7 +203,13 @@ internal void SetFromAttributes(object[] attribs, BaseUnityPlugin pluginInstance
189203
{
190204
var val = propertyPair.other.GetValue(attrib);
191205
if (val != null)
206+
{
207+
// Handle delegate covariance not working when using reflection by manually converting the delegate
208+
if (propertyPair.my.PropertyType != propertyPair.other.FieldType && typeof(Delegate).IsAssignableFrom(propertyPair.my.PropertyType))
209+
val = Delegate.CreateDelegate(propertyPair.my.PropertyType, ((Delegate)val).Target, ((Delegate)val).Method);
210+
192211
propertyPair.my.SetValue(this, val, null);
212+
}
193213
}
194214
catch (Exception ex)
195215
{

ConfigurationManager/SettingFieldDrawer.cs

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,16 @@ public void DrawSettingValue(SettingEntryBase setting)
5151
{
5252
if (setting.CustomDrawer != null)
5353
setting.CustomDrawer(setting is ConfigSettingEntry newSetting ? newSetting.Entry : null);
54+
else if (setting.CustomHotkeyDrawer != null)
55+
{
56+
var isBeingSet = _currentKeyboardShortcutToSet == setting;
57+
var isBeingSetOriginal = isBeingSet;
58+
59+
setting.CustomHotkeyDrawer(setting is ConfigSettingEntry newSetting ? newSetting.Entry : null, ref isBeingSet);
60+
61+
if (isBeingSet != isBeingSetOriginal)
62+
_currentKeyboardShortcutToSet = isBeingSet ? setting : null;
63+
}
5464
else if (setting.ShowRangeAsPercent != null && setting.AcceptableValueRange.Key != null)
5565
DrawRangeField(setting);
5666
else if (setting.AcceptableValues != null)
@@ -380,7 +390,7 @@ private static void DrawKeyboardShortcut(SettingEntryBase setting)
380390
{
381391
GUILayout.Label("Press any key combination", GUILayout.ExpandWidth(true));
382392
GUIUtility.keyboardControl = -1;
383-
393+
384394
var input = UnityInput.Current;
385395
if (_keysToCheck == null) _keysToCheck = input.SupportedKeyCodes.Except(new[] { KeyCode.Mouse0, KeyCode.None }).ToArray();
386396
foreach (var key in _keysToCheck)

ConfigurationManagerAttributes.cs

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,52 @@ internal sealed class ConfigurationManagerAttributes
3838
/// </summary>
3939
public System.Action<BepInEx.Configuration.ConfigEntryBase> CustomDrawer;
4040

41+
/// <summary>
42+
/// Custom setting editor that allows polling keyboard input with the Input (or UnityInput) class.
43+
/// Use either CustomDrawer or CustomHotkeyDrawer, using both at the same time leads to undefined behaviour.
44+
/// </summary>
45+
public CustomHotkeyDrawerFunc CustomHotkeyDrawer;
46+
47+
/// <summary>
48+
/// Custom setting draw action that allows polling keyboard input with the Input class.
49+
/// Note: Make sure to focus on your UI control when you are accepting input so user doesn't type in the search box or in another setting (best to do this on every frame).
50+
/// If you don't draw any selectable UI controls You can use `GUIUtility.keyboardControl = -1;` on every frame to make sure that nothing is selected.
51+
/// </summary>
52+
/// <example>
53+
/// CustomHotkeyDrawer = (ConfigEntryBase setting, ref bool isEditing) =>
54+
/// {
55+
/// if (isEditing)
56+
/// {
57+
/// // Make sure nothing else is selected since we aren't focusing on a text box with GUI.FocusControl.
58+
/// GUIUtility.keyboardControl = -1;
59+
///
60+
/// // Use Input.GetKeyDown and others here, remember to set isEditing to false after you're done!
61+
/// // It's best to check Input.anyKeyDown and set isEditing to false immediately if it's true,
62+
/// // so that the input doesn't have a chance to propagate to the game itself.
63+
///
64+
/// if (GUILayout.Button("Stop"))
65+
/// isEditing = false;
66+
/// }
67+
/// else
68+
/// {
69+
/// if (GUILayout.Button("Start"))
70+
/// isEditing = true;
71+
/// }
72+
///
73+
/// // This will only be true when isEditing is true and you hold any key
74+
/// GUILayout.Label("Any key pressed: " + Input.anyKey);
75+
/// }
76+
/// </example>
77+
/// <param name="setting">
78+
/// Setting currently being set (if available).
79+
/// </param>
80+
/// <param name="isCurrentlyAcceptingInput">
81+
/// Set this ref parameter to true when you want the current setting drawer to receive Input events.
82+
/// The value will persist after being set, use it to see if the current instance is being edited.
83+
/// Remember to set it to false after you are done!
84+
/// </param>
85+
public delegate void CustomHotkeyDrawerFunc(BepInEx.Configuration.ConfigEntryBase setting, ref bool isCurrentlyAcceptingInput);
86+
4187
/// <summary>
4288
/// Show this setting in the settings screen at all? If false, don't show.
4389
/// </summary>

0 commit comments

Comments
 (0)