Files
daluandou/.codely.packages/cn.tuanjie.codely.bridge@1.0.85-47f31100/Editor/Bridge/Native/NativeWindowBridgeHost.AddComponent.cs
T
huadexuan 13d0c80335 ```
chore(bridge): 删除桥接包的提交记录和Git属性配置

删除了 .codely-bridge-commit 文件中存储的提交哈希值
移除了 .gitattributes 文件中的行结束符配置规则
```
2026-09-24 21:41:14 +08:00

390 lines
17 KiB
C#

using System;
using System.Collections;
using System.Collections.Generic;
using System.Reflection;
using System.Text;
using UnityEditor;
using UnityEngine;
using UnityTcp.Editor.Helpers;
namespace UnityTcp.Editor.Native
{
internal static partial class NativeWindowBridgeHost
{
private static GameObject[] s_CachedAddComponentTargets;
private static string s_AddComponentSearchFilter = "";
private static float s_AddComponentPopupWidth;
private static float s_AddComponentPopupHeight;
private static bool s_AddComponentReflectionDone;
private static Type s_AddComponentDataSourceType;
private static Type s_ComponentDropdownItemType;
private static Type s_AdvancedDropdownDataSourceType;
private static Type s_AdvancedDropdownItemType;
private static FieldInfo s_AddComponentWindowStateField;
private static PropertyInfo s_AdvancedDropdownMainTreeProp;
private static PropertyInfo s_AdvancedDropdownSearchTreeProp;
private static PropertyInfo s_AdvancedDropdownItemChildrenProp;
private static PropertyInfo s_AdvancedDropdownItemDisplayNameProp;
private static PropertyInfo s_AdvancedDropdownItemHasChildrenProp;
private static PropertyInfo s_ComponentDropdownMenuPathProp;
private static MethodInfo s_AdvancedDropdownReloadDataMethod;
private static MethodInfo s_AdvancedDropdownRebuildSearchMethod;
private static MethodInfo s_ExecuteMenuItemOnGameObjectsMethod;
private static void EnsureAddComponentReflection()
{
if (s_AddComponentReflectionDone) return;
s_AddComponentReflectionDone = true;
try
{
var editorAsm = typeof(EditorWindow).Assembly;
s_AddComponentDataSourceType = editorAsm.GetType("UnityEditor.AddComponent.AddComponentDataSource");
s_ComponentDropdownItemType = editorAsm.GetType("UnityEditor.AddComponent.ComponentDropdownItem");
s_AdvancedDropdownDataSourceType = editorAsm.GetType("UnityEditor.IMGUI.Controls.AdvancedDropdownDataSource");
s_AdvancedDropdownItemType = editorAsm.GetType("UnityEditor.IMGUI.Controls.AdvancedDropdownItem");
Type addComponentWindowType = editorAsm.GetType("UnityEditor.AddComponent.AddComponentWindow");
if (addComponentWindowType != null)
{
s_AddComponentWindowStateField = addComponentWindowType.GetField(
"s_State",
BindingFlags.Static | BindingFlags.NonPublic);
}
if (s_AdvancedDropdownDataSourceType != null)
{
s_AdvancedDropdownMainTreeProp = s_AdvancedDropdownDataSourceType.GetProperty(
"mainTree",
BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
s_AdvancedDropdownSearchTreeProp = s_AdvancedDropdownDataSourceType.GetProperty(
"searchTree",
BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
s_AdvancedDropdownReloadDataMethod = s_AdvancedDropdownDataSourceType.GetMethod(
"ReloadData",
BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
s_AdvancedDropdownRebuildSearchMethod = s_AdvancedDropdownDataSourceType.GetMethod(
"RebuildSearch",
BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
}
if (s_AdvancedDropdownItemType != null)
{
s_AdvancedDropdownItemChildrenProp = s_AdvancedDropdownItemType.GetProperty(
"children",
BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
s_AdvancedDropdownItemHasChildrenProp = s_AdvancedDropdownItemType.GetProperty(
"hasChildren",
BindingFlags.Instance | BindingFlags.NonPublic);
for (Type t = s_AdvancedDropdownItemType; t != null && s_AdvancedDropdownItemDisplayNameProp == null; t = t.BaseType)
{
s_AdvancedDropdownItemDisplayNameProp = t.GetProperty(
"displayName",
BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
}
}
if (s_ComponentDropdownItemType != null)
{
s_ComponentDropdownMenuPathProp = s_ComponentDropdownItemType.GetProperty(
"menuPath",
BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
}
s_ExecuteMenuItemOnGameObjectsMethod = typeof(EditorApplication).GetMethod(
"ExecuteMenuItemOnGameObjects",
BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.Public,
null,
new[] { typeof(string), typeof(GameObject[]) },
null);
}
catch (Exception ex)
{
CodelyLogger.LogWarning($"[NWB-AddComponent] Reflection init error: {ex.Message}");
}
}
private static bool TryExtractAddComponentPopup(
EditorWindow hostedWindow, float relX, float relY, float popW, float popH)
{
EnsureAddComponentReflection();
if (hostedWindow == null) return false;
try
{
FieldInfo targetsField = hostedWindow.GetType().GetField(
"m_GameObjects",
BindingFlags.Instance | BindingFlags.NonPublic);
s_CachedAddComponentTargets = targetsField?.GetValue(hostedWindow) as GameObject[];
if (s_CachedAddComponentTargets == null || s_CachedAddComponentTargets.Length == 0)
{
LogVerbose("[NWB-AddComponent] No target GameObjects on AddComponentWindow");
return false;
}
PropertyInfo searchProp = hostedWindow.GetType().GetProperty(
"searchString",
BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
if (searchProp != null && string.IsNullOrEmpty(s_AddComponentSearchFilter))
{
s_AddComponentSearchFilter = searchProp.GetValue(hostedWindow) as string ?? "";
}
s_AddComponentPopupWidth = popW;
s_AddComponentPopupHeight = popH;
s_LastFrontendPopupX = relX;
s_LastFrontendPopupY = relY;
return TrySendAddComponentPopup(relX, relY, popW, popH);
}
catch (Exception ex)
{
CodelyLogger.LogWarning($"[NWB-AddComponent] Extract error: {ex.Message}\n{ex.StackTrace}");
return false;
}
}
private static bool TrySendAddComponentPopup(float relX, float relY, float popW, float popH)
{
EnsureAddComponentReflection();
if (s_CachedAddComponentTargets == null || s_CachedAddComponentTargets.Length == 0)
return false;
if (s_AddComponentDataSourceType == null ||
s_AddComponentWindowStateField == null ||
s_AdvancedDropdownMainTreeProp == null ||
s_AdvancedDropdownReloadDataMethod == null)
{
CodelyLogger.LogWarning("[NWB-AddComponent] Required reflection types not found");
return false;
}
try
{
object state = s_AddComponentWindowStateField.GetValue(null);
if (state == null)
state = Activator.CreateInstance(s_AddComponentWindowStateField.FieldType);
object dataSource = Activator.CreateInstance(
s_AddComponentDataSourceType,
state,
s_CachedAddComponentTargets);
s_AdvancedDropdownReloadDataMethod.Invoke(dataSource, null);
object mainTree = s_AdvancedDropdownMainTreeProp.GetValue(dataSource);
object treeToRender = mainTree;
if (!string.IsNullOrEmpty(s_AddComponentSearchFilter) &&
s_AdvancedDropdownRebuildSearchMethod != null &&
mainTree != null)
{
s_AdvancedDropdownRebuildSearchMethod.Invoke(
dataSource,
new object[] { s_AddComponentSearchFilter, mainTree });
object searchTree = s_AdvancedDropdownSearchTreeProp?.GetValue(dataSource);
if (searchTree != null)
treeToRender = searchTree;
}
if (treeToRender == null)
{
LogVerbose("[NWB-AddComponent] Component tree is null");
return false;
}
var pathList = new List<string>();
int itemIndex = 0;
int validCount = 0;
s_FrontendPopupIdCounter++;
s_CurrentFrontendPopupId = "popup_" + s_FrontendPopupIdCounter;
s_PopupSourceType = "add_component";
var sb = new StringBuilder(65536);
sb.Append("{\"type\":\"popup_show\",\"id\":\"");
sb.Append(s_CurrentFrontendPopupId);
sb.Append("\",\"sourceType\":\"add_component\",\"x\":");
sb.Append(Mathf.RoundToInt(relX));
sb.Append(",\"y\":");
sb.Append(Mathf.RoundToInt(relY));
sb.Append(",\"width\":");
sb.Append(Mathf.RoundToInt(Mathf.Max(popW, 200f)));
sb.Append(",\"height\":");
sb.Append(Mathf.RoundToInt(Mathf.Max(popH, 280f)));
sb.Append(",\"searchText\":\"");
sb.Append(EscapeJsonString(s_AddComponentSearchFilter ?? ""));
sb.Append("\",\"items\":[");
bool needComma = false;
AppendAddComponentDropdownItems(
sb,
GetAdvancedDropdownChildren(treeToRender),
ref itemIndex,
ref validCount,
ref needComma,
pathList);
sb.Append("]}");
if (validCount <= 0)
{
LogVerbose("[NWB-AddComponent] No selectable component items extracted");
return false;
}
s_CachedPopupItemPaths = pathList.ToArray();
string payload = sb.ToString();
int payloadBytes = Encoding.UTF8.GetByteCount(payload);
bool sent;
if (payloadBytes > kMaxDataChannelPayloadBytes)
{
sent = SendPopupPayloadChunked(s_CurrentFrontendPopupId, payload, payloadBytes);
if (sent)
LogVerbose($"[NWB-AddComponent] Sent chunked popup: bytes={payloadBytes} items={validCount}");
}
else
{
sent = SendDataChannelMessage(payload);
}
s_FrontendPopupSent = sent;
if (sent)
{
CancelNativeDelayedPopup();
LogVerbose($"[NWB-AddComponent] Sent popup: id={s_CurrentFrontendPopupId} items={validCount} search='{s_AddComponentSearchFilter}'");
}
else
{
CodelyLogger.LogWarning($"[NWB-AddComponent] Failed to send popup: id={s_CurrentFrontendPopupId}");
}
return sent;
}
catch (Exception ex)
{
CodelyLogger.LogWarning($"[NWB-AddComponent] Send error: {ex.Message}\n{ex.StackTrace}");
return false;
}
}
private static IEnumerable GetAdvancedDropdownChildren(object item)
{
if (item == null || s_AdvancedDropdownItemChildrenProp == null)
return new object[0];
return s_AdvancedDropdownItemChildrenProp.GetValue(item) as IEnumerable ?? new object[0];
}
private static void AppendAddComponentDropdownItems(
StringBuilder sb,
IEnumerable items,
ref int itemIndex,
ref int validCount,
ref bool needComma,
List<string> pathList)
{
if (items == null) return;
foreach (object item in items)
{
if (item == null) continue;
if (item.GetType().Name == "SeparatorDropdownItem")
continue;
string label = GetAdvancedDropdownDisplayName(item);
if (string.IsNullOrEmpty(label))
continue;
bool hasChildren = s_AdvancedDropdownItemHasChildrenProp != null &&
(bool)s_AdvancedDropdownItemHasChildrenProp.GetValue(item);
if (hasChildren)
{
if (needComma) sb.Append(',');
needComma = true;
sb.Append("{\"label\":\"");
sb.Append(EscapeJsonString(label));
sb.Append("\",\"enabled\":true,\"checked\":false,\"separator\":false,\"submenu\":true,\"children\":[");
bool childComma = false;
int childValid = 0;
AppendAddComponentDropdownItems(
sb,
GetAdvancedDropdownChildren(item),
ref itemIndex,
ref childValid,
ref childComma,
pathList);
sb.Append("]}");
if (childValid > 0)
validCount++;
continue;
}
if (s_ComponentDropdownItemType == null ||
!s_ComponentDropdownItemType.IsInstanceOfType(item) ||
s_ComponentDropdownMenuPathProp == null)
{
continue;
}
string menuPath = s_ComponentDropdownMenuPathProp.GetValue(item) as string;
if (string.IsNullOrEmpty(menuPath))
continue;
if (needComma) sb.Append(',');
needComma = true;
sb.Append("{\"index\":");
sb.Append(itemIndex);
sb.Append(",\"path\":\"");
sb.Append(EscapeJsonString(menuPath));
sb.Append("\",\"label\":\"");
sb.Append(EscapeJsonString(label));
sb.Append("\",\"enabled\":true,\"checked\":false,\"separator\":false}");
pathList.Add(menuPath);
itemIndex++;
validCount++;
}
}
private static string GetAdvancedDropdownDisplayName(object item)
{
if (item == null || s_AdvancedDropdownItemDisplayNameProp == null)
return "";
return s_AdvancedDropdownItemDisplayNameProp.GetValue(item) as string ?? "";
}
private static bool TryHandleAddComponentPopupSelect(string path)
{
if (string.IsNullOrEmpty(path) || s_CachedAddComponentTargets == null)
return false;
EnsureAddComponentReflection();
if (s_ExecuteMenuItemOnGameObjectsMethod == null)
{
CodelyLogger.LogWarning("[NWB-AddComponent] ExecuteMenuItemOnGameObjects not found");
return false;
}
try
{
bool ok = (bool)s_ExecuteMenuItemOnGameObjectsMethod.Invoke(
null,
new object[] { path, s_CachedAddComponentTargets });
LogVerbose($"[NWB-AddComponent] ExecuteMenuItemOnGameObjects path='{path}' ok={ok}");
return ok;
}
catch (Exception ex)
{
CodelyLogger.LogWarning($"[NWB-AddComponent] Execute error: {ex.Message}");
return false;
}
}
private static void ClearAddComponentPopupState()
{
s_CachedAddComponentTargets = null;
s_AddComponentSearchFilter = "";
s_AddComponentPopupWidth = 0f;
s_AddComponentPopupHeight = 0f;
}
}
}