Permalink
Cannot retrieve contributors at this time
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
230 lines (198 sloc)
8.62 KB
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | |
} | |
} | |
} | |
} |