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

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

454 lines
17 KiB
C#

using System;
using System.Reflection;
using System.Text;
using UnityEditor;
using UnityEditorInternal;
using UnityEngine;
using UnityTcp.Editor.Helpers;
namespace UnityTcp.Editor.Native
{
internal static partial class NativeWindowBridgeHost
{
private const float kIconSelectorPopupWidth = 140f;
private const float kIconSelectorPopupHeightWithLabels = 126f;
private const float kIconSelectorPopupHeightNoLabels = 86f;
private const int kIconSelectorLabelCount = 8;
private const int kIconSelectorDotCount = 16;
private const string kIconSelectorPathNone = "__none__";
private static bool s_IconSelectorReflectionDone;
private static Type s_IconSelectorType;
private static FieldInfo s_IconSelectorTargetObjectField;
private static FieldInfo s_IconSelectorTargetObjectListField;
private static FieldInfo s_IconSelectorShowLabelIconsField;
private static FieldInfo s_IconSelectorMultipleSelectedIconsField;
private static MethodInfo s_IconSelectorCopyIconToImporterMethod;
private static EditorWindow s_CachedIconSelectorWindow;
private static UnityEngine.Object[] s_IconSelectorTargetObjects;
private static bool s_IconSelectorSessionActive;
private static bool s_IconSelectorShowLabelIcons;
private static string s_IconSelectorSelectedPath = "";
private static void EnsureIconSelectorReflection()
{
if (s_IconSelectorReflectionDone) return;
s_IconSelectorReflectionDone = true;
try
{
var editorAsm = typeof(EditorWindow).Assembly;
s_IconSelectorType = editorAsm.GetType("UnityEditor.IconSelector");
if (s_IconSelectorType == null)
{
CodelyLogger.LogWarning("[NWB-IconSelector] IconSelector type not found");
return;
}
const BindingFlags inst = BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public;
const BindingFlags stat = BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.Public;
s_IconSelectorTargetObjectField = s_IconSelectorType.GetField("m_TargetObject", inst);
s_IconSelectorTargetObjectListField = s_IconSelectorType.GetField("m_TargetObjectList", inst);
s_IconSelectorShowLabelIconsField = s_IconSelectorType.GetField("m_ShowLabelIcons", inst);
s_IconSelectorMultipleSelectedIconsField = s_IconSelectorType.GetField("m_MultipleSelectedIcons", inst);
s_IconSelectorCopyIconToImporterMethod = s_IconSelectorType.GetMethod(
"CopyIconToImporter", stat, null, new[] { typeof(MonoScript) }, null);
}
catch (Exception ex)
{
CodelyLogger.LogWarning($"[NWB-IconSelector] Reflection init failed: {ex.Message}");
}
}
private static bool TryExtractIconSelectorPopup(
EditorWindow hostedWindow, float relX, float relY, float popW, float popH)
{
EnsureIconSelectorReflection();
if (hostedWindow == null || s_IconSelectorType == null)
return false;
try
{
s_CachedIconSelectorWindow = hostedWindow;
s_IconSelectorTargetObjects = s_IconSelectorTargetObjectListField?.GetValue(hostedWindow) as UnityEngine.Object[];
if (s_IconSelectorTargetObjects == null || s_IconSelectorTargetObjects.Length == 0)
{
UnityEngine.Object single =
s_IconSelectorTargetObjectField?.GetValue(hostedWindow) as UnityEngine.Object;
if (single != null)
s_IconSelectorTargetObjects = new[] { single };
}
if (s_IconSelectorTargetObjects == null || s_IconSelectorTargetObjects.Length == 0)
{
LogVerbose("[NWB-IconSelector] No target objects on IconSelector");
return false;
}
s_IconSelectorShowLabelIcons = s_IconSelectorShowLabelIconsField != null &&
(bool)s_IconSelectorShowLabelIconsField.GetValue(hostedWindow);
bool multiple = s_IconSelectorMultipleSelectedIconsField != null &&
(bool)s_IconSelectorMultipleSelectedIconsField.GetValue(hostedWindow);
s_IconSelectorSelectedPath = multiple ? "" : ResolveIconSelectorSelectedPath(s_IconSelectorTargetObjects[0]);
TryHideIconSelectorNativeWindow(hostedWindow);
s_LastFrontendPopupX = relX;
s_LastFrontendPopupY = relY;
float height = s_IconSelectorShowLabelIcons
? kIconSelectorPopupHeightWithLabels
: kIconSelectorPopupHeightNoLabels;
float width = Mathf.Max(popW, kIconSelectorPopupWidth);
height = Mathf.Max(popH, height);
return TrySendIconSelectorPopup(relX, relY, width, height);
}
catch (Exception ex)
{
CodelyLogger.LogWarning($"[NWB-IconSelector] Extract error: {ex.Message}");
return false;
}
}
private static void TryHideIconSelectorNativeWindow(EditorWindow window)
{
if (window == null) return;
try
{
Rect pos = window.position;
pos.x = -20000f;
pos.y = -20000f;
window.position = pos;
}
catch (Exception ex)
{
CodelyLogger.LogWarning($"[NWB-IconSelector] Hide native window failed: {ex.Message}");
}
}
private static bool TrySendIconSelectorPopup(float relX, float relY, float popW, float popH)
{
try
{
s_FrontendPopupIdCounter++;
s_CurrentFrontendPopupId = "popup_" + s_FrontendPopupIdCounter;
s_PopupSourceType = "icon_selector";
s_IconSelectorSessionActive = true;
var sb = new StringBuilder(8192);
sb.Append("{\"type\":\"popup_show\",\"id\":\"");
sb.Append(s_CurrentFrontendPopupId);
sb.Append("\",\"sourceType\":\"icon_selector\",\"x\":");
sb.Append(Mathf.RoundToInt(relX));
sb.Append(",\"y\":");
sb.Append(Mathf.RoundToInt(relY));
sb.Append(",\"width\":");
sb.Append(Mathf.RoundToInt(popW));
sb.Append(",\"height\":");
sb.Append(Mathf.RoundToInt(popH));
sb.Append(",\"title\":\"Select Icon\",\"showLabelIcons\":");
sb.Append(s_IconSelectorShowLabelIcons ? "true" : "false");
sb.Append(",\"selectedPath\":\"");
sb.Append(EscapeJsonString(s_IconSelectorSelectedPath ?? ""));
sb.Append("\"");
if (s_IconSelectorShowLabelIcons)
{
sb.Append(",\"labelIcons\":[");
AppendIconSelectorIconArray(sb, "label", kIconSelectorLabelCount, true);
sb.Append(']');
}
sb.Append(",\"dotIcons\":[");
AppendIconSelectorIconArray(sb, "dot", kIconSelectorDotCount, false);
sb.Append("]}");
bool sent = SendDataChannelMessage(sb.ToString());
if (sent)
{
s_FrontendPopupSent = true;
LogVerbose("[NWB-IconSelector] Sent icon selector to frontend");
}
else
{
ClearIconSelectorPopupState();
}
return sent;
}
catch (Exception ex)
{
CodelyLogger.LogWarning($"[NWB-IconSelector] Send popup failed: {ex.Message}");
ClearIconSelectorPopupState();
return false;
}
}
private static void AppendIconSelectorIconArray(StringBuilder sb, string kind, int count, bool labelIcon)
{
for (int i = 0; i < count; i++)
{
if (i > 0) sb.Append(',');
string iconName = labelIcon
? "sv_icon_name" + i
: "sv_icon_dot" + i + "_sml";
string image = EncodeEditorIconDataUri(iconName);
sb.Append("{\"path\":\"");
sb.Append(kind);
sb.Append(':');
sb.Append(i);
sb.Append("\",\"image\":\"");
sb.Append(EscapeJsonString(image));
sb.Append("\"}");
}
}
private static string EncodeEditorIconDataUri(string iconName)
{
if (string.IsNullOrEmpty(iconName))
return "";
try
{
GUIContent content = EditorGUIUtility.IconContent(iconName);
return TryEncodeAnnotationIconPngDataUri(content?.image as Texture2D);
}
catch
{
return "";
}
}
private static MethodInfo s_GetIconForObjectMethod;
private static MethodInfo s_SetIconForObjectMethod;
private static Texture2D GetIconForObjectReflect(UnityEngine.Object obj)
{
if (obj == null) return null;
try
{
if (s_GetIconForObjectMethod == null)
{
s_GetIconForObjectMethod = typeof(EditorGUIUtility).GetMethod(
"GetIconForObject",
BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.Public,
null,
new[] { typeof(UnityEngine.Object) },
null);
}
return s_GetIconForObjectMethod?.Invoke(null, new object[] { obj }) as Texture2D;
}
catch
{
return null;
}
}
private static void SetIconForObjectReflect(UnityEngine.Object obj, Texture2D icon)
{
if (obj == null) return;
try
{
if (s_SetIconForObjectMethod == null)
{
s_SetIconForObjectMethod = typeof(EditorGUIUtility).GetMethod(
"SetIconForObject",
BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.Public,
null,
new[] { typeof(UnityEngine.Object), typeof(Texture2D) },
null);
}
s_SetIconForObjectMethod?.Invoke(null, new object[] { obj, icon });
}
catch (Exception ex)
{
CodelyLogger.LogWarning($"[NWB-IconSelector] SetIconForObject failed: {ex.Message}");
}
}
private static string ResolveIconSelectorSelectedPath(UnityEngine.Object target)
{
Texture2D icon = GetIconForObjectReflect(target);
if (icon == null)
return kIconSelectorPathNone;
if (s_IconSelectorShowLabelIcons)
{
for (int i = 0; i < kIconSelectorLabelCount; i++)
{
if (IconMatchesName(icon, "sv_icon_name" + i) || IconMatchesName(icon, "sv_label_" + i))
return "label:" + i;
}
}
for (int i = 0; i < kIconSelectorDotCount; i++)
{
if (IconMatchesName(icon, "sv_icon_dot" + i + "_sml") ||
IconMatchesName(icon, "sv_icon_dot" + i + "_pix16_gizmo"))
{
return "dot:" + i;
}
}
return "";
}
private static bool IconMatchesName(Texture2D icon, string iconName)
{
if (icon == null || string.IsNullOrEmpty(iconName))
return false;
GUIContent content = EditorGUIUtility.IconContent(iconName);
return content?.image != null && ReferenceEquals(content.image, icon);
}
private static Texture2D ResolveLargeIconTexture(string path)
{
if (string.IsNullOrEmpty(path))
return null;
if (path == kIconSelectorPathNone)
return null;
if (path.StartsWith("label:", StringComparison.Ordinal))
{
if (int.TryParse(path.Substring("label:".Length), out int index))
{
GUIContent content = EditorGUIUtility.IconContent("sv_label_" + index);
return content?.image as Texture2D;
}
}
if (path.StartsWith("dot:", StringComparison.Ordinal))
{
if (int.TryParse(path.Substring("dot:".Length), out int index))
{
GUIContent content = EditorGUIUtility.IconContent("sv_icon_dot" + index + "_pix16_gizmo");
return content?.image as Texture2D;
}
}
return null;
}
private static void ApplyIconToIconSelectorTargets(Texture2D icon)
{
UnityEngine.Object[] targets = s_IconSelectorTargetObjects;
if (targets == null || targets.Length == 0)
return;
Undo.RecordObjects(targets, "Set Icon On GameObject");
foreach (UnityEngine.Object target in targets)
{
if (target == null) continue;
SetIconForObjectReflect(target, icon);
if (target is MonoScript monoScript && icon != null)
s_IconSelectorCopyIconToImporterMethod?.Invoke(null, new object[] { monoScript });
}
TryForceReloadInspectors();
TryInvokeAnnotationWindowIconChanged();
}
private static void TryForceReloadInspectors()
{
try
{
MethodInfo method = typeof(EditorUtility).GetMethod(
"ForceReloadInspectors",
BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.Public);
method?.Invoke(null, null);
}
catch
{
// internal API; fall back to repaint-only below
}
InternalEditorUtility.RepaintAllViews();
}
private static void TryInvokeAnnotationWindowIconChanged()
{
try
{
Type annotationWindowType = typeof(EditorWindow).Assembly.GetType("UnityEditor.AnnotationWindow");
MethodInfo iconChanged = annotationWindowType?.GetMethod(
"IconChanged",
BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
iconChanged?.Invoke(null, null);
}
catch
{
// optional
}
}
/// <summary>
/// Returns true when the icon selector popup should stay open.
/// </summary>
private static bool TryHandleIconSelectorPopupSelect(string path)
{
if (!s_IconSelectorSessionActive)
return false;
if (path == kIconSelectorPathNone)
{
ApplyIconToIconSelectorTargets(null);
s_IconSelectorSelectedPath = kIconSelectorPathNone;
return true;
}
Texture2D icon = ResolveLargeIconTexture(path);
if (icon == null && path != kIconSelectorPathNone)
{
CodelyLogger.LogWarning($"[NWB-IconSelector] Unknown icon path: {path}");
return true;
}
ApplyIconToIconSelectorTargets(icon);
s_IconSelectorSelectedPath = path;
return true;
}
private static void CloseNativeIconSelectorWindow()
{
if (s_CachedIconSelectorWindow == null)
return;
try
{
s_CachedIconSelectorWindow.Close();
}
catch (Exception ex)
{
CodelyLogger.LogWarning($"[NWB-IconSelector] Close failed: {ex.Message}");
}
}
private static void CancelIconSelectorSession()
{
CloseNativeIconSelectorWindow();
ClearIconSelectorPopupState();
if (s_OffscreenTarget != null)
{
s_OffscreenTarget.Repaint();
InternalEditorUtility.RepaintAllViews();
}
}
private static void ClearIconSelectorPopupState()
{
s_CachedIconSelectorWindow = null;
s_IconSelectorTargetObjects = null;
s_IconSelectorSessionActive = false;
s_IconSelectorShowLabelIcons = false;
s_IconSelectorSelectedPath = "";
}
}
}