diff --git a/Prowl.Editor/Assets/Importers/MaterialImporter.cs b/Prowl.Editor/Assets/Importers/MaterialImporter.cs index 940c53e1..f6eac243 100644 --- a/Prowl.Editor/Assets/Importers/MaterialImporter.cs +++ b/Prowl.Editor/Assets/Importers/MaterialImporter.cs @@ -36,7 +36,7 @@ public override void Import(SerializedAsset ctx, FileInfo assetPath) public class MaterialImporterEditor : ScriptedEditor { private Material? _editingMaterial; - private MaterialEditor? _editor; + private ScriptedEditor? _editor; public override void OnEnable() @@ -44,7 +44,10 @@ public override void OnEnable() SerializedProperty tag = StringTagConverter.ReadFromFile((target as MetaFile).AssetPath); _editingMaterial = Serializer.Deserialize(tag); - _editor = new MaterialEditor(_editingMaterial, OnChange); + _editor = CreateEditor(_editingMaterial); + + if (_editor is MaterialEditor matEditor) + matEditor.onChange = OnChange; } diff --git a/Prowl.Editor/Assets/Importers/ModelImporter.cs b/Prowl.Editor/Assets/Importers/ModelImporter.cs index e32c2a36..ec049783 100644 --- a/Prowl.Editor/Assets/Importers/ModelImporter.cs +++ b/Prowl.Editor/Assets/Importers/ModelImporter.cs @@ -658,11 +658,10 @@ public MeshMaterialBinding(string meshName, Assimp.Mesh aMesh, AssetRef me [CustomEditor(typeof(ModelImporter))] public class ModelEditor : ScriptedEditor { + private int _selectedAnim; + private int _selectedAnimBone; - int selectedAnim; - int selectedAnimBone; - - int selectedTab; + private int _selectedTab; public override void OnInspectorGUI() { @@ -677,19 +676,19 @@ public override void OnInspectorGUI() using (gui.Node("Tabs").Width(Size.Percentage(1f)).MaxHeight(ItemSize).Layout(LayoutType.Row).ScaleChildren().Enter()) { if (EditorGUI.StyledButton("Meshes")) - selectedTab = 0; + _selectedTab = 0; if (EditorGUI.StyledButton("Materials")) - selectedTab = 1; + _selectedTab = 1; if (EditorGUI.StyledButton("Scene")) - selectedTab = 2; + _selectedTab = 2; if (EditorGUI.StyledButton("Animations")) - selectedTab = 3; + _selectedTab = 3; } using (gui.Node("Content").Width(Size.Percentage(1f)).MarginTop(5).Layout(LayoutType.Column).Scroll().Enter()) { - switch (selectedTab) + switch (_selectedTab) { case 0: Meshes(importer, serialized); @@ -802,14 +801,14 @@ private void Animations(ModelImporter importer, SerializedAsset? serialized) { if (EditorGUI.StyledButton(i + ": " + animations.ElementAt(i).Name)) { - selectedAnim = i + 1; + _selectedAnim = i + 1; } } } - if (selectedAnim > 0 && selectedAnim <= animations.Count()) + if (_selectedAnim > 0 && _selectedAnim <= animations.Count()) { - var anim = animations.ElementAt(selectedAnim - 1) as AnimationClip; + var anim = animations.ElementAt(_selectedAnim - 1) as AnimationClip; gui.TextNode("aName", $"Name: {anim.Name}").ExpandWidth().Height(ItemSize); gui.TextNode("aDuration", $"Duration: {anim.Duration}").ExpandWidth().Height(ItemSize); gui.TextNode("aTPS", $"Ticks Per Second: {anim.TicksPerSecond}").ExpandWidth().Height(ItemSize); @@ -825,14 +824,14 @@ private void Animations(ModelImporter importer, SerializedAsset? serialized) { if (EditorGUI.StyledButton(i + ": " + anim.Bones[i].BoneName)) { - selectedAnimBone = i; + _selectedAnimBone = i; } } } - if (selectedAnimBone > 0 && selectedAnimBone <= anim.Bones.Count) + if (_selectedAnimBone > 0 && _selectedAnimBone <= anim.Bones.Count) { - var bone = anim.Bones[selectedAnimBone - 1]; + var bone = anim.Bones[_selectedAnimBone - 1]; gui.TextNode("bName", $"Bone Name: {bone.BoneName}").ExpandWidth().Height(ItemSize); gui.TextNode("bPosKeys", $"Position Keys: {bone.PosX.Keys.Count}").ExpandWidth().Height(ItemSize); gui.TextNode("bRotKeys", $"Rotation Keys: {bone.RotX.Keys.Count}").ExpandWidth().Height(ItemSize); diff --git a/Prowl.Editor/Editor/AssetsTreeWindow.cs b/Prowl.Editor/Editor/AssetsTreeWindow.cs index 407171ff..964ef5ce 100644 --- a/Prowl.Editor/Editor/AssetsTreeWindow.cs +++ b/Prowl.Editor/Editor/AssetsTreeWindow.cs @@ -222,9 +222,11 @@ private static bool DrawCreateContextMenu(DirectoryInfo? directory, bool fromAss EditorGuiManager.Directory = directory; EditorGuiManager.fromAssetBrowser = fromAssetBrowser; var assetsPath = MenuItem.GetMenuPath("Assets"); + foreach (var child in assetsPath.Children) if (child.Path.Equals("Create", StringComparison.OrdinalIgnoreCase)) return MenuItem.DrawMenu(child, false, 1); + return false; } diff --git a/Prowl.Editor/Editor/EditorGUI.cs b/Prowl.Editor/Editor/EditorGUI.cs index 94a8b2cc..c0f9f1c1 100644 --- a/Prowl.Editor/Editor/EditorGUI.cs +++ b/Prowl.Editor/Editor/EditorGUI.cs @@ -434,4 +434,4 @@ internal static WidgetStyle GetInputStyle() #endregion -} \ No newline at end of file +} diff --git a/Prowl.Editor/Editor/InspectorWindow.cs b/Prowl.Editor/Editor/InspectorWindow.cs index 86be0f15..9abcfbb4 100644 --- a/Prowl.Editor/Editor/InspectorWindow.cs +++ b/Prowl.Editor/Editor/InspectorWindow.cs @@ -106,17 +106,15 @@ protected override void Draw() if (relativeAssetPath != null) { // The selected object is a path in our asset database, load its meta data and display a custom editor for the Importer if ones found - if (AssetDatabase.TryGetGuid(path, out var id)) + if (AssetDatabase.TryGetGuid(path, out Guid id)) { var meta = MetaFile.Load(path); if (meta != null) { - Type? editorType = ScriptedEditor.GetEditorType(meta.importer.GetType()); - if (editorType != null) + ScriptedEditor? editor = ScriptedEditor.CreateEditor(meta, meta.importer.GetType(), false); + if (editor != null) { - customEditor = (path, (ScriptedEditor)Activator.CreateInstance(editorType)); - customEditor.Value.Item2.target = meta; - customEditor.Value.Item2.OnEnable(); + customEditor = (path, editor); destroyCustomEditor = false; } else @@ -151,28 +149,12 @@ protected override void Draw() { if (customEditor == null) { - // Just selected a new object create the editor - Type? editorType = ScriptedEditor.GetEditorType(Selected.GetType()); - if (editorType != null) + ScriptedEditor? editor = ScriptedEditor.CreateEditor(Selected); + if (editor != null) { - customEditor = (Selected, (ScriptedEditor)Activator.CreateInstance(editorType)); - customEditor.Value.Item2.target = Selected; - customEditor.Value.Item2.OnEnable(); + customEditor = (Selected, editor); destroyCustomEditor = false; } - else - { - // No Editor, Just display Property Grid - if (EditorGUI.PropertyGrid("Default Drawer", ref Selected, EditorGUI.TargetFields.Serializable, EditorGUI.PropertyGridConfig.NoHeader)) - { - // Search for a function named "OnValidate()" - var method = Selected.GetType().GetMethod("OnValidate", BindingFlags.Public | BindingFlags.Instance); - if (method != null) - { - method.Invoke(Selected, null); - } - } - } } else if (customEditor.Value.Item1 == Selected) { diff --git a/Prowl.Editor/Editor/ScriptedEditors/ComponentEditor.cs b/Prowl.Editor/Editor/ScriptedEditors/ComponentEditor.cs new file mode 100644 index 00000000..49c8a1f3 --- /dev/null +++ b/Prowl.Editor/Editor/ScriptedEditors/ComponentEditor.cs @@ -0,0 +1,32 @@ +// This file is part of the Prowl Game Engine +// Licensed under the MIT License. See the LICENSE file in the project root for details. + +using Prowl.Runtime; +using Prowl.Editor.Assets; + +using static Prowl.Editor.EditorGUI; + +namespace Prowl.Editor; + +[CustomEditor(typeof(MonoBehaviour))] +public class ComponentEditor : ScriptedEditor +{ + private MonoBehaviour? _component; + + + public override void OnEnable() + { + _component = target as MonoBehaviour; + } + + + public override void OnInspectorGUI() + { + if (_component == null) + return; + + object componentRef = _component; + if (PropertyGrid("ComponentPropertyGrid", ref componentRef, TargetFields.Serializable | TargetFields.Properties, PropertyGridConfig.NoHeader | PropertyGridConfig.NoBorder | PropertyGridConfig.NoBackground)) + _component.OnValidate(); + } +} diff --git a/Prowl.Editor/Editor/ScriptedEditors/GameObjectEditor.cs b/Prowl.Editor/Editor/ScriptedEditors/GameObjectEditor.cs index 2328cae0..82e297cf 100644 --- a/Prowl.Editor/Editor/ScriptedEditors/GameObjectEditor.cs +++ b/Prowl.Editor/Editor/ScriptedEditors/GameObjectEditor.cs @@ -180,6 +180,7 @@ public override void OnInspectorGUI() // Draw Components HashSet editorsNeeded = []; List toDelete = []; + foreach (var comp in go.GetComponents()) { if (comp == null) continue; @@ -207,17 +208,18 @@ public override void OnInspectorGUI() DragnDrop.Drag(comp, comp!.GetType()); - var rect = gui.CurrentNode.LayoutData.InnerRect; + Rect rect = gui.CurrentNode.LayoutData.InnerRect; string cname = GetComponentDisplayName(cType); - var textSizeY = Font.DefaultFont.CalcTextSize(cname, 20).y; - var centerY = (rect.height / 2) - (textSizeY / 2); - gui.Draw2D.DrawText((compOpened ? FontAwesome6.ChevronDown : FontAwesome6.ChevronRight), gui.CurrentNode.LayoutData.GlobalContentPosition + new Vector2(8, centerY + 3)); + double textSizeY = Font.DefaultFont.CalcTextSize(cname, 20).y; + double centerY = (rect.height / 2) - (textSizeY / 2); + gui.Draw2D.DrawText(compOpened ? FontAwesome6.ChevronDown : FontAwesome6.ChevronRight, gui.CurrentNode.LayoutData.GlobalContentPosition + new Vector2(8, centerY + 3)); gui.Draw2D.DrawText(cname, 23, gui.CurrentNode.LayoutData.GlobalContentPosition + new Vector2(29, centerY + 3), Color.black * 0.8f); gui.Draw2D.DrawText(cname, 23, gui.CurrentNode.LayoutData.GlobalContentPosition + new Vector2(28, centerY + 2)); isEnabled = comp.Enabled; - if (gui.Checkbox("IsEnabledChk", ref isEnabled, Offset.Percentage(1f, -30), 0, out var chkNode, GetInputStyle())) + if (gui.Checkbox("IsEnabledChk", ref isEnabled, Offset.Percentage(1f, -30), 0, out LayoutNode? chkNode, GetInputStyle())) comp.Enabled = isEnabled; + gui.Tooltip("Is Component Enabled?"); @@ -248,37 +250,28 @@ public override void OnInspectorGUI() gui.Draw2D.DrawRectFilled(gui.CurrentNode.LayoutData.Rect, EditorStylePrefs.Instance.WindowBGOne * 0.6f, btnRoundness, 12); // Handle Editors for this type if we have any - if (compEditors.TryGetValue(comp.InstanceID, out var editor)) + if (compEditors.TryGetValue(comp.InstanceID, out ScriptedEditor? editor)) { editor.OnInspectorGUI(); - goto EndComponent; } else { - var editorType = GetEditorType(cType); - if (editorType != null) + editor = CreateEditor(comp); + if (editor != null) { - editor = Activator.CreateInstance(editorType) as ScriptedEditor; - if (editor != null) - { - compEditors[comp.InstanceID] = editor; - editor.target = comp; - editor.OnEnable(); - editor.OnInspectorGUI(); - goto EndComponent; - } + compEditors[comp.InstanceID] = editor; + editor.OnInspectorGUI(); } } + // ScriptedEditor.CreateEditor should provide a fallback default instead of providing // No Editor, Fallback to default Inspector - object compRef = comp; - if (PropertyGrid("CompPropertyGrid", ref compRef, TargetFields.Serializable | TargetFields.Properties, PropertyGridConfig.NoHeader | PropertyGridConfig.NoBorder | PropertyGridConfig.NoBackground)) - comp.OnValidate(); + // object compRef = comp; + // if (PropertyGrid("CompPropertyGrid", ref compRef, TargetFields.Serializable | TargetFields.Properties, PropertyGridConfig.NoHeader | PropertyGridConfig.NoBorder | PropertyGridConfig.NoBackground)) + // comp.OnValidate(); // Draw any Buttons - these should be in PropertyGrid probably //EditorGui.HandleAttributeButtons(comp); - - EndComponent:; } } @@ -288,7 +281,7 @@ public override void OnInspectorGUI() } // Handle Deletion - foreach (var comp in toDelete) + foreach (MonoBehaviour comp in toDelete) go.RemoveComponent(comp); // Remove any editors that are no longer needed diff --git a/Prowl.Editor/Editor/ScriptedEditors/MaterialEditor.cs b/Prowl.Editor/Editor/ScriptedEditors/MaterialEditor.cs index 20f508b6..2223f375 100644 --- a/Prowl.Editor/Editor/ScriptedEditors/MaterialEditor.cs +++ b/Prowl.Editor/Editor/ScriptedEditors/MaterialEditor.cs @@ -11,16 +11,7 @@ namespace Prowl.Editor.ScriptedEditors; [CustomEditor(typeof(Material))] public class MaterialEditor : ScriptedEditor { - private Action onChange; - - private MaterialEditor() { } - - public MaterialEditor(Material mat, Action onChange) - { - target = mat; - this.onChange = onChange; - } - + public Action onChange; public override void OnInspectorGUI() { diff --git a/Prowl.Editor/Editor/ScriptedEditors/ScriptedEditor.cs b/Prowl.Editor/Editor/ScriptedEditors/ScriptedEditor.cs index 5b070852..e64dceb0 100644 --- a/Prowl.Editor/Editor/ScriptedEditors/ScriptedEditor.cs +++ b/Prowl.Editor/Editor/ScriptedEditors/ScriptedEditor.cs @@ -12,29 +12,29 @@ namespace Prowl.Editor; public class ScriptedEditor { + private static readonly Dictionary s_editorLookup = []; + public Gui gui => Gui.ActiveGUI; - public object target { get; internal set; } + public object target { get; private set; } public virtual void OnEnable() { } - public virtual void OnInspectorGUI() - { - object t = target; + public virtual void OnInspectorGUI() => DrawDefaultInspector(); + + public virtual void OnDisable() { } - if (EditorGUI.PropertyGrid("Default Drawer", ref t, EditorGUI.TargetFields.Serializable, EditorGUI.PropertyGridConfig.NoHeader)) + public void DrawDefaultInspector() + { + // PropertyGrid would fall apart if this was a value type, but ScriptedEditors don't work on value types! + object refTarget = target; + if (EditorGUI.PropertyGrid("Default Drawer", ref refTarget, EditorGUI.TargetFields.Serializable, EditorGUI.PropertyGridConfig.NoHeader)) { MethodInfo? method = target.GetType().GetMethod("OnValidate", BindingFlags.Public | BindingFlags.Instance); - method?.Invoke(t, null); - - target = t; + method?.Invoke(refTarget, null); } } - public virtual void OnDisable() { } - - - private static readonly Dictionary s_editorLookup = []; [OnAssemblyLoad] public static void GenerateLookUp() @@ -42,6 +42,7 @@ public static void GenerateLookUp() List typesToSearch = AppDomain.CurrentDomain.GetAssemblies().SelectMany(x => x.GetTypes()).ToList(); List customEditors = []; + // Register all editors directly referencing types foreach (Type type in typesToSearch) { CustomEditorAttribute? attribute = type.GetCustomAttribute(); @@ -62,31 +63,85 @@ public static void GenerateLookUp() customEditors.Add(attribute); } + // Register all editors indirectly referencing types via inheritance + // i.e : Editor for type A will be applied to type B which inherits A foreach (Type type in typesToSearch) { Type? baseType = type; Type? editor = null; - while (baseType != null && !s_editorLookup.TryGetValue(baseType, out editor)) + // Walk up the inheritance tree until an editor that supports inheritance is found + while (baseType.BaseType != null) + { baseType = baseType.BaseType; + if (!s_editorLookup.TryGetValue(baseType, out editor)) + continue; + + // Does this editor apply only for the given type or for every other? + if (!editor.GetCustomAttribute()!.Inheritable) + { + editor = null; + continue; + } + + break; + } + if (editor != null) s_editorLookup[type] = editor; } } + [OnAssemblyUnload] public static void ClearLookUp() { s_editorLookup.Clear(); } - /// The editor type for that Extension - public static Type? GetEditorType(Type type) + + /// + /// Gets the first matching type that has a for the provided type. + /// + /// The to get a type for. + /// A type for the given , or null if none exists. + public static Type? GetEditorType(Type type, bool allowDefaultDrawer = true) { if (s_editorLookup.TryGetValue(type, out Type? editorType)) return editorType; + + // No dedicated editor found, so return the default + if (allowDefaultDrawer && type.IsClass) + return typeof(ScriptedEditor); + + return null; + } + + + public static ScriptedEditor? CreateEditor(object target, Type type, bool allowDefaultDrawer = true) + { + Type? editorType = GetEditorType(type, allowDefaultDrawer); + + if (editorType != null) + { + ScriptedEditor? customEditor = Activator.CreateInstance(editorType) as ScriptedEditor; + + if (customEditor != null) + { + customEditor.target = target; + customEditor.OnEnable(); + return customEditor; + } + } + return null; } + + + public static ScriptedEditor? CreateEditor(object target, bool allowDefaultDrawer = true) + { + return CreateEditor(target, target.GetType(), allowDefaultDrawer); + } }