-
Notifications
You must be signed in to change notification settings - Fork 0
/
ImGuiElement.cs
230 lines (198 loc) · 8.62 KB
/
ImGuiElement.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Text;
using ImGuiNET;
namespace FlatRedImGui
{
/// <summary>
/// Represents a set of UI controls that can be manipulated and rendered
/// </summary>
public abstract class ImGuiElement: INotifyPropertyChanged
{
private readonly Dictionary<string, object> _notifyPropertyChangedObjects = new Dictionary<string, object>();
private readonly Dictionary<string, byte[]> _propertyTextBuffers = new Dictionary<string, byte[]>();
private bool _disablePropertyNotificationEvents;
public event PropertyChangedEventHandler PropertyChanged;
public bool IsVisible { get; set; }
public ImGuiElement()
{
var propertiesRequiringTextBuffers = GetType()
.GetProperties(BindingFlags.Instance | BindingFlags.Public)
.Select(x => (Property: x, TextBufferAttribute: x.GetCustomAttribute<HasTextBufferAttribute>()))
.Where(x => x.TextBufferAttribute != null);
foreach (var (property, attribute) in propertiesRequiringTextBuffers)
{
if (property.PropertyType != typeof(string))
{
var message = $"{GetType().Name}'s {property.Name} property has the HasTextBufferAttribute but is not a string. " +
$"This attribute is only supported on string properties.";
throw new InvalidOperationException(message);
}
if (attribute.MaxLength <= 1)
{
var message = $"Text buffer max length for property {GetType().Name}'s {property.Name} property must be at least 1";
throw new InvalidOperationException(message);
}
var textBuffer = new byte[attribute.MaxLength];
_propertyTextBuffers[property.Name] = textBuffer;
}
}
public void Render()
{
if (IsVisible)
{
CustomRender();
}
}
/// <summary>
/// Custom logic for each item for how the item should be rendered. This will only be called if the element
/// has `IsVisible` set to `true`.
/// </summary>
protected abstract void CustomRender();
/// <summary>
/// Temporarily disables property changed events from being dispatched until the returned IDisposable
/// is disposed.
/// </summary>
/// <returns></returns>
protected IDisposable DisablePropertyChangedNotifications()
{
return new EventNotificationDisabler(this);
}
/// <summary>
/// Retrieves the current value of the specified property from the backing store, or the `default(T)` if no
/// value has been set yet.
/// </summary>
protected T Get<T>([CallerMemberName] string propertyName = null)
{
if (string.IsNullOrWhiteSpace(propertyName))
{
throw new ArgumentNullException(nameof(propertyName));
}
return _notifyPropertyChangedObjects.TryGetValue(propertyName, out var value)
? (T) value
: default;
}
/// <summary>
/// Sets the raw value of a property to the specified value. The object must be the same type (or castable
/// to the same type) as the corresponding `Get` type, or else an exception will occur at runtime.
/// </summary>
protected void Set(object value, [CallerMemberName] string propertyName = null)
{
if (string.IsNullOrWhiteSpace(propertyName))
{
throw new ArgumentNullException(nameof(propertyName));
}
var hasExistingValue = _notifyPropertyChangedObjects.TryGetValue(propertyName, out var existingValue);
if (hasExistingValue && existingValue == value)
{
return;
}
_notifyPropertyChangedObjects[propertyName] = value;
// If this field is backed by a text buffer, we need to update the text buffer with the new value, otherwise
// ImGui controls will display the wrong value.
if (_propertyTextBuffers.TryGetValue(propertyName, out var textBuffer))
{
var valueBytes = Encoding.ASCII.GetBytes((string) value);
Array.Clear(textBuffer, 0, textBuffer.Length);
// Not sure if we should truncate or exception if the value is larger than the specified max length.
// For now we'll truncate, but this should probably be revisited.
var length = Math.Min(valueBytes.Length, textBuffer.Length);
Array.Copy(valueBytes, textBuffer, length);
}
if (!_disablePropertyNotificationEvents)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
/// <summary>
/// Retrieves the text buffer for the specified property. Properties must be tagged with a
/// [HasTextBuffer] attribute in order for a text buffer to be retrieved
/// </summary>
protected byte[] GetTextBuffer(string property)
{
if (!_propertyTextBuffers.TryGetValue(property, out var buffer))
{
var message = $"Property {property} does not have a text buffer available. " +
"Make sure it's marked with the [HasTextBuffer] attribute";
throw new InvalidOperationException(message);
}
return buffer;
}
/// <summary>
/// Updates a string property's value from the value being stored in an underlying text buffer
/// </summary>
protected void UpdatePropertyFromTextBuffer(string propertyName)
{
if (!_propertyTextBuffers.TryGetValue(propertyName, out var buffer))
{
var message = $"Property {propertyName} does not have a text buffer available. " +
"Make sure it's marked with the [HasTextBuffer] attribute";
throw new InvalidOperationException(message);
}
var stringValue = Encoding.ASCII.GetString(buffer);
Set(stringValue, propertyName);
}
/// <summary>
/// Creates a standard ImGui text editor for a property
/// </summary>
protected void InputText(string property, string label)
{
var buffer = GetTextBuffer(property);
ImGui.InputText(label, buffer, (uint) buffer.Length);
UpdatePropertyFromTextBuffer(property);
}
/// <summary>
/// Creates a standard ImGui integer editor for a property
/// </summary>
protected void InputInt(string property, string label)
{
var intVal = Get<int>(property);
ImGui.InputInt(label, ref intVal);
Set(intVal, property);
}
/// <summary>
/// Creates a standard ImGui double editor for a property
/// </summary>
protected void InputDouble(string property, string label)
{
var value = Get<double>(property);
ImGui.InputDouble(label, ref value);
Set(value, property);
}
/// <summary>
/// Creates a standard ImGui float editor for a property
/// </summary>
protected void InputFloat(string property, string label)
{
var value = Get<float>(property);
ImGui.InputFloat(label, ref value);
Set(value, property);
}
/// <summary>
/// Creates a standard ImGui checkbox control for a property
/// </summary>
protected void Checkbox(string property, string label)
{
var value = Get<bool>(property);
ImGui.Checkbox(label, ref value);
Set(value, property);
}
private class EventNotificationDisabler : IDisposable
{
private readonly ImGuiElement _element;
public EventNotificationDisabler(ImGuiElement element)
{
_element = element;
_element._disablePropertyNotificationEvents = true;
}
public void Dispose()
{
_element._disablePropertyNotificationEvents = false;
}
}
}
}