Skip to content

Commit eef8de7

Browse files
Dragging scripts onto Inspector now attempts to add it to the gameobject
1 parent 559034c commit eef8de7

4 files changed

Lines changed: 129 additions & 12 deletions

File tree

Prowl.Editor/GUI/Panels/InspectorPanel.cs

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -165,6 +165,46 @@ public override void OnGUI(Paper paper, float width, float height)
165165

166166
paper.Box("insp_bottom_pad").Height(20);
167167
});
168+
169+
// Drag a script (.cs) from the Project panel onto the inspector to add it as a component.
170+
DrawScriptComponentDropZone(paper, font, width, height);
171+
}
172+
173+
/// <summary>
174+
/// While a single GameObject is selected and a script asset that resolves to a component type is
175+
/// being dragged, overlays the inspector with a drop target that adds that component on drop.
176+
/// </summary>
177+
private static void DrawScriptComponentDropZone(Paper paper, Scribe.FontFile font, float width, float height)
178+
{
179+
if (!DragDrop.IsDragging && !DragDrop.IsDropFrame) return;
180+
if (DragDrop.Payload is not AssetDragPayload payload) return;
181+
182+
var go = Selection.ActiveObject as GameObject;
183+
if (go == null || Selection.GetSelected<GameObject>().Count() != 1) return;
184+
185+
Type? componentType = Prowl.Editor.Projects.Scripting.ScriptComponentResolver.ResolveComponentType(payload);
186+
if (componentType == null) return;
187+
188+
using (paper.Box("insp_script_drop")
189+
.PositionType(PositionType.SelfDirected).Position(0, 0).Size(width, height)
190+
.Layer(Layer.Overlay)
191+
.BackgroundColor(Color.FromArgb(38, EditorTheme.Purple400))
192+
.BorderColor(EditorTheme.Purple400).BorderWidth(2).Rounded(6)
193+
.Enter())
194+
{
195+
paper.Box("insp_script_drop_lbl")
196+
.Width(UnitValue.Stretch()).Height(UnitValue.Stretch()).IsNotInteractable()
197+
.Text($"{EditorIcons.Plus} {Loc.Get("inspector.add_component")}: {componentType.Name}", font)
198+
.TextColor(EditorTheme.Purple400)
199+
.FontSize(EditorTheme.FontSize).Alignment(TextAlignment.MiddleCenter);
200+
201+
// Complete the drop: the drag has ended (released) while over this overlay.
202+
if (paper.IsParentHovered && !DragDrop.IsDragging)
203+
{
204+
Popups.AddComponentPopup.AddComponentWithUndo(go, componentType);
205+
DragDrop.EndDrag();
206+
}
207+
}
168208
}
169209

170210
private void DrawEmpty(Paper paper, Scribe.FontFile font, float width)

Prowl.Editor/GUI/Panels/ProjectPanel.cs

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -961,7 +961,7 @@ private Action<ContextBuilder> ItemContextMenu(ContentItem item, bool inTree = f
961961

962962
builder.Separator();
963963

964-
builder.Item(Loc.Get("project.rename"), () => StartRename(item, inTree), enabled: !isMulti && !isRoot, icon: EditorIcons.PenToSquare);
964+
builder.Item(Loc.Get("project.rename"), () => StartRename(item, inTree), enabled: !isMulti && !isRoot && CanRename(item), icon: EditorIcons.PenToSquare);
965965
builder.Item(Loc.Get("project.copy_path"), () => paper_SetClipboard(item.RelativePath), icon: EditorIcons.Copy);
966966
if (!item.IsFolder && item.Guid != Guid.Empty)
967967
builder.Item(Loc.Get("project.copy_guid"), () => paper_SetClipboard(item.Guid.ToString()), icon: EditorIcons.Fingerprint);
@@ -1103,8 +1103,20 @@ private void DeleteSelectedItems()
11031103
});
11041104
}
11051105

1106+
// Renaming a .cs file only renames the file, not the class it declares, breaking the file-name =
1107+
// class-name convention and every serialized reference to that component type. Disabled until proper
1108+
// script renaming (rename the class + serialized type aliasing) is supported.
1109+
private static bool CanRename(ContentItem item)
1110+
=> item.IsFolder || !string.Equals(Path.GetExtension(item.Name), ".cs", StringComparison.OrdinalIgnoreCase);
1111+
11061112
private void StartRename(ContentItem item, bool inTree = false)
11071113
{
1114+
if (!CanRename(item))
1115+
{
1116+
Toasts.Show(Loc.Get("toast.rename_failed"), "Renaming script files isn't supported yet - it would break the class/file-name link.", ToastType.Warning, 4f);
1117+
return;
1118+
}
1119+
11081120
string id = inTree ? $"proj_folder_{item.RelativePath}" : $"proj_asset_{item.RelativePath}";
11091121
string editName = item.IsFolder ? item.Name : Path.GetFileNameWithoutExtension(item.Name);
11101122

Prowl.Editor/GUI/Popups/AddComponentPopup.cs

Lines changed: 21 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -187,17 +187,7 @@ private static void DrawComponentItem(Paper paper, Prowl.Scribe.FontFile font, s
187187
{
188188
if (_targetGo != null)
189189
{
190-
var addedComp = _targetGo.AddComponent(type);
191-
if (addedComp != null)
192-
{
193-
var compId = addedComp.Identifier;
194-
var goId = _targetGo.Identifier;
195-
var serialized = Echo.Serializer.Serialize(addedComp.GetType(), addedComp);
196-
var compType = addedComp.GetType();
197-
Undo.RegisterAction("Add Component",
198-
undo: () => { var g = Undo.FindGO(goId); if (g == null) return; var c = g.GetComponentByIdentifier(compId); if (c != null) g.RemoveComponent(c); },
199-
redo: () => { var g = Undo.FindGO(goId); if (g == null) return; var c = Echo.Serializer.Deserialize(serialized, compType) as MonoBehaviour; if (c != null) { c.Identifier = compId; g.AddComponent(c); } });
200-
}
190+
AddComponentWithUndo(_targetGo, type);
201191
Close();
202192
}
203193
})
@@ -217,6 +207,26 @@ private static void DrawComponentItem(Paper paper, Prowl.Scribe.FontFile font, s
217207
}
218208
}
219209

210+
/// <summary>
211+
/// Adds a component of <paramref name="type"/> to <paramref name="go"/> and registers a matching
212+
/// undo/redo step. Shared by the popup and the drag-a-script-onto-the-inspector path.
213+
/// </summary>
214+
public static MonoBehaviour? AddComponentWithUndo(GameObject go, Type type)
215+
{
216+
var addedComp = go.AddComponent(type);
217+
if (addedComp != null)
218+
{
219+
var compId = addedComp.Identifier;
220+
var goId = go.Identifier;
221+
var serialized = Echo.Serializer.Serialize(addedComp.GetType(), addedComp);
222+
var compType = addedComp.GetType();
223+
Undo.RegisterAction("Add Component",
224+
undo: () => { var g = Undo.FindGO(goId); if (g == null) return; var c = g.GetComponentByIdentifier(compId); if (c != null) g.RemoveComponent(c); },
225+
redo: () => { var g = Undo.FindGO(goId); if (g == null) return; var c = Echo.Serializer.Deserialize(serialized, compType) as MonoBehaviour; if (c != null) { c.Identifier = compId; g.AddComponent(c); } });
226+
}
227+
return addedComp;
228+
}
229+
220230
private static List<ComponentEntry> GatherComponents()
221231
{
222232
var result = new List<ComponentEntry>();
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
// This file is part of the Prowl Game Engine
2+
// Licensed under the MIT License. See the LICENSE file in the project root for details.
3+
4+
using System;
5+
using System.IO;
6+
7+
using Prowl.Editor.GUI;
8+
using Prowl.Runtime;
9+
10+
namespace Prowl.Editor.Projects.Scripting;
11+
12+
/// <summary>
13+
/// Maps a dragged script asset (a <c>.cs</c> file) to the component <see cref="Type"/> it defines.
14+
/// Scripts are not runtime assets (the importer only triggers recompilation), so the type is resolved
15+
/// by matching the file name against a <see cref="MonoBehaviour"/> type of the same name in the loaded
16+
/// assemblies - the same file-name-equals-class-name convention the script pipeline already relies on.
17+
/// </summary>
18+
public static class ScriptComponentResolver
19+
{
20+
/// <summary>Resolve the component type a dragged asset represents, or null if it isn't a single
21+
/// script file that maps to a concrete <see cref="MonoBehaviour"/>.</summary>
22+
public static Type? ResolveComponentType(AssetDragPayload? payload)
23+
{
24+
if (payload == null || payload.IsMulti) return null;
25+
return ResolveComponentType(payload.AssetName);
26+
}
27+
28+
/// <summary>Resolve the component type for a script file name/path (must end in <c>.cs</c>).</summary>
29+
public static Type? ResolveComponentType(string? fileNameOrPath)
30+
{
31+
if (string.IsNullOrEmpty(fileNameOrPath)) return null;
32+
if (!fileNameOrPath.EndsWith(".cs", StringComparison.OrdinalIgnoreCase)) return null;
33+
34+
string typeName = Path.GetFileNameWithoutExtension(fileNameOrPath);
35+
if (string.IsNullOrEmpty(typeName)) return null;
36+
37+
foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies())
38+
{
39+
Type[] types;
40+
try { types = assembly.GetTypes(); }
41+
catch { continue; }
42+
43+
foreach (var type in types)
44+
{
45+
if (type.Name != typeName) continue;
46+
if (type.IsAbstract) continue;
47+
if (!typeof(MonoBehaviour).IsAssignableFrom(type)) continue;
48+
if (type == typeof(MonoBehaviour)) continue;
49+
return type;
50+
}
51+
}
52+
53+
return null;
54+
}
55+
}

0 commit comments

Comments
 (0)