Last active
June 8, 2023 06:05
-
-
Save maluoi/e0395149999c4691c5607c677d34ad6f to your computer and use it in GitHub Desktop.
A reflection based class inspector/editor for StereoKit
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.Linq; | |
using System.Reflection; | |
namespace StereoKit.Framework | |
{ | |
/////////////////////////////////////////// | |
abstract class InspectorAttribute : Attribute | |
{ | |
public abstract void Show(PropertyInfo prop, object inst); | |
} | |
/////////////////////////////////////////// | |
class SliderAttribute : InspectorAttribute | |
{ | |
public float min, max, step; | |
public SliderAttribute(float min, float max, float step = 0) | |
{ | |
this.min = min; | |
this.max = max; | |
this.step = step; | |
} | |
public override void Show(PropertyInfo prop, object inst) | |
{ | |
if (prop.PropertyType == typeof(float)) | |
{ | |
float val = (float)prop.GetValue(inst); | |
if (UI.HSlider(prop.Name, ref val, min, max, step, 0, UIConfirm.Pinch)) | |
prop.SetValue(inst, val); | |
} | |
else if (prop.PropertyType == typeof(int)) | |
{ | |
float val = (float)(int)prop.GetValue(inst); | |
if (UI.HSlider(prop.Name, ref val, min, max, step, 0, UIConfirm.Pinch)) | |
prop.SetValue(inst, (int)val); | |
} | |
} | |
} | |
/////////////////////////////////////////// | |
class BrowseAttribute : InspectorAttribute | |
{ | |
public string[] filters; | |
public BrowseAttribute(params string[] filters) | |
{ | |
this.filters = filters; | |
} | |
public override void Show(PropertyInfo prop, object inst) | |
{ | |
string val = (string)prop.GetValue(inst); | |
if (UI.Button("Browse")) | |
{ | |
Platform.FilePicker(PickerMode.Open, (s) => | |
{ | |
prop.SetValue(inst, s); | |
}, null, filters); | |
} | |
UI.SameLine(); | |
UI.Label(val, V.XY(UI.LayoutRemaining.x-0.02f, 0)); | |
} | |
} | |
/////////////////////////////////////////// | |
class Inspector | |
{ | |
struct PropertyItem | |
{ | |
public PropertyInfo prop; | |
public InspectorAttribute attribute; | |
} | |
object item; | |
Type itemType; | |
PropertyItem[] properties; | |
public object Item { get => item; } | |
public Inspector() { } | |
public Inspector(object item) { SetObject(item); } | |
private bool ValidType(Type type) | |
{ | |
return type == typeof(int) | |
|| type == typeof(float); | |
} | |
public void SetObject(object newItem) | |
{ | |
item = newItem; | |
itemType = newItem.GetType(); | |
properties = itemType | |
.GetProperties() | |
.Where (p => p.GetCustomAttribute<InspectorAttribute>() != null || ValidType(p.PropertyType)) | |
.Select(p => new PropertyItem | |
{ | |
prop = p, | |
attribute = p.GetCustomAttribute<InspectorAttribute>() | |
}) | |
.ToArray(); | |
} | |
public void DrawUI() | |
{ | |
int curr = 1; | |
foreach (PropertyItem p in properties) | |
{ | |
UI.PushId(curr++); | |
UI.Label(p.prop.Name, new Vec2(0.06f, 0)); | |
UI.SameLine(); | |
if (p.attribute != null) { p.attribute.Show(p.prop, item); } | |
else | |
{ | |
switch (p.prop.GetValue(item)) | |
{ | |
case string val: if (UI.Input(p.prop.Name, ref val)) { p.prop.SetValue(item, val); } break; | |
case int val: | |
{ | |
if (UI.Button("-")) { val -= 1; p.prop.SetValue(item, val); } | |
UI.SameLine(); | |
UI.Label(val.ToString()); | |
UI.SameLine(); | |
if (UI.Button("+")) { val += 1; p.prop.SetValue(item, val); } | |
} | |
break; | |
} | |
} | |
UI.PopId(); | |
} | |
} | |
} | |
/////////////////////////////////////////// | |
class InspectorWindow : IStepper | |
{ | |
static InspectorWindow instance = null; | |
public static void Show(object item) | |
{ | |
if (instance == null) instance = new InspectorWindow(); | |
instance.inspector = new Inspector(item); | |
instance.pose = new Pose(Input.Head.position + Input.Head.Forward * 0.35f, Quat.FromAngles(0, 180, 0) * Input.Head.orientation); | |
PropertyInfo info = item.GetType().GetProperty("Name"); | |
instance.name = info != null | |
? info.GetValue(item).ToString() | |
: item.GetType().Name; | |
SK.AddStepper(instance); | |
} | |
public static void Hide() | |
{ | |
SK.RemoveStepper(instance); | |
instance.inspector = null; | |
} | |
string name; | |
Pose pose; | |
Inspector inspector; | |
public Action<object> OnDelete; | |
public bool Enabled => true; | |
public InspectorWindow() | |
{ | |
instance = this; | |
} | |
public InspectorWindow(Action<object> onDelete) | |
{ | |
instance = this; | |
OnDelete = onDelete; | |
} | |
public bool Initialize() => true; | |
public void Shutdown() { } | |
public void Step() | |
{ | |
if (!Enabled) return; | |
UI.WindowBegin(name, ref pose, V.XY(.3f,0)); | |
inspector.DrawUI(); | |
UI.HSeparator(); | |
if (UI.Button("Close")) | |
Hide(); | |
if (OnDelete != null) | |
{ | |
UI.SameLine(); | |
UI.PushTint(new(1,0,0,1)); | |
if (UI.Button("Delete")) | |
{ | |
OnDelete(inspector.Item); | |
Hide(); | |
} | |
UI.PopTint(); | |
} | |
UI.WindowEnd(); | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment