DesignResolutionConverterWindow.cs 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237
  1. using System.Globalization;
  2. using UnityEditor;
  3. using UnityEngine;
  4. using UnityEngine.UI;
  5. namespace AppUI.Editor
  6. {
  7. /// <summary>
  8. /// 设计稿尺寸与 Canvas Reference Resolution 之间的换算工具窗口。
  9. /// </summary>
  10. public class DesignResolutionConverterWindow : EditorWindow
  11. {
  12. private const string PrefDesignW = "AppUI.DesignResolutionConverter.DesignW";
  13. private const string PrefDesignH = "AppUI.DesignResolutionConverter.DesignH";
  14. private const string PrefCanvasW = "AppUI.DesignResolutionConverter.CanvasW";
  15. private const string PrefCanvasH = "AppUI.DesignResolutionConverter.CanvasH";
  16. private Vector2 _designArtboard = new Vector2(DesignResolutionConverter.DefaultDesignWidth, DesignResolutionConverter.DefaultDesignHeight);
  17. private Vector2 _canvasReference = new Vector2(DesignResolutionConverter.DefaultDesignWidth, DesignResolutionConverter.DefaultDesignHeight);
  18. private Vector2 _sampleDesignSize = new Vector2(100f, 40f);
  19. private Vector2 _sampleCanvasSize = new Vector2(100f, 40f);
  20. private const string PrefScalarValue = "AppUI.DesignResolutionConverter.ScalarValue";
  21. private const string PrefScalarDir = "AppUI.DesignResolutionConverter.ScalarDir";
  22. private const string PrefScalarAxis = "AppUI.DesignResolutionConverter.ScalarAxis";
  23. private float _scalarInput = 100f;
  24. private int _scalarDirection; // 0: 设计稿→Canvas, 1: Canvas→设计稿
  25. private int _scalarAxis; // 0: 宽度轴 scaleX, 1: 高度轴 scaleY
  26. private Vector2 _scroll;
  27. [MenuItem("Tools/AppUI/设计稿与 Canvas 分辨率换算")]
  28. public static void Open()
  29. {
  30. var win = GetWindow<DesignResolutionConverterWindow>();
  31. win.titleContent = new GUIContent("设计稿 ↔ Canvas 换算");
  32. win.minSize = new Vector2(380f, 420f);
  33. }
  34. private void OnEnable()
  35. {
  36. _designArtboard.x = EditorPrefs.GetFloat(PrefDesignW, DesignResolutionConverter.DefaultDesignWidth);
  37. _designArtboard.y = EditorPrefs.GetFloat(PrefDesignH, DesignResolutionConverter.DefaultDesignHeight);
  38. _canvasReference.x = EditorPrefs.GetFloat(PrefCanvasW, DesignResolutionConverter.DefaultDesignWidth);
  39. _canvasReference.y = EditorPrefs.GetFloat(PrefCanvasH, DesignResolutionConverter.DefaultDesignHeight);
  40. _scalarInput = EditorPrefs.GetFloat(PrefScalarValue, 100f);
  41. _scalarDirection = EditorPrefs.GetInt(PrefScalarDir, 0);
  42. _scalarAxis = EditorPrefs.GetInt(PrefScalarAxis, 0);
  43. }
  44. private void SavePrefs()
  45. {
  46. EditorPrefs.SetFloat(PrefDesignW, _designArtboard.x);
  47. EditorPrefs.SetFloat(PrefDesignH, _designArtboard.y);
  48. EditorPrefs.SetFloat(PrefCanvasW, _canvasReference.x);
  49. EditorPrefs.SetFloat(PrefCanvasH, _canvasReference.y);
  50. }
  51. private void OnGUI()
  52. {
  53. _scroll = EditorGUILayout.BeginScrollView(_scroll);
  54. EditorGUILayout.LabelField("基准尺寸", EditorStyles.boldLabel);
  55. EditorGUILayout.HelpBox(
  56. "设计稿宽高:你在 Figma / Sketch 等中的画板像素尺寸(默认 402×874)。\n" +
  57. "Canvas 参考分辨率:当前场景 CanvasScaler 的 Reference Resolution。",
  58. MessageType.Info);
  59. EditorGUI.BeginChangeCheck();
  60. _designArtboard = EditorGUILayout.Vector2Field("设计稿宽高 (px)", _designArtboard);
  61. _canvasReference = EditorGUILayout.Vector2Field("Canvas Reference Resolution", _canvasReference);
  62. if (EditorGUI.EndChangeCheck())
  63. SavePrefs();
  64. EditorGUILayout.BeginHorizontal();
  65. if (GUILayout.Button("从选中对象读取 CanvasScaler"))
  66. PullFromSelection();
  67. if (GUILayout.Button("将设计稿尺寸填到 Canvas(与左栏相同)"))
  68. {
  69. _canvasReference = _designArtboard;
  70. SavePrefs();
  71. }
  72. EditorGUILayout.EndHorizontal();
  73. bool valid = _designArtboard.x > 0f && _designArtboard.y > 0f && _canvasReference.x > 0f && _canvasReference.y > 0f;
  74. if (!valid)
  75. EditorGUILayout.HelpBox("设计稿与 Canvas 的宽高均需大于 0。", MessageType.Warning);
  76. Vector2 scale = valid
  77. ? DesignResolutionConverter.GetScaleFactors(_designArtboard, _canvasReference)
  78. : Vector2.one;
  79. EditorGUILayout.Space(6f);
  80. EditorGUILayout.LabelField("轴向缩放系数", EditorStyles.boldLabel);
  81. if (valid)
  82. {
  83. EditorGUILayout.LabelField($"scaleX = {_canvasReference.x:F4} / {_designArtboard.x:F4} = {scale.x:F6}");
  84. EditorGUILayout.LabelField($"scaleY = {_canvasReference.y:F4} / {_designArtboard.y:F4} = {scale.y:F6}");
  85. }
  86. EditorGUILayout.Space(10f);
  87. EditorGUILayout.LabelField("单值换算", EditorStyles.boldLabel);
  88. EditorGUILayout.HelpBox("输入一个数,按所选轴向换算;结果可复制到剪贴板(Inspector / 代码中粘贴)。", MessageType.None);
  89. EditorGUI.BeginChangeCheck();
  90. _scalarDirection = GUILayout.Toolbar(_scalarDirection, new[] { "设计稿 → Canvas", "Canvas → 设计稿" });
  91. _scalarAxis = GUILayout.Toolbar(_scalarAxis, new[] { "按宽度轴 (scaleX)", "按高度轴 (scaleY)" });
  92. _scalarInput = EditorGUILayout.FloatField(_scalarDirection == 0 ? "输入值 (设计稿 px)" : "输入值 (Canvas 单位)", _scalarInput);
  93. if (EditorGUI.EndChangeCheck())
  94. {
  95. EditorPrefs.SetFloat(PrefScalarValue, _scalarInput);
  96. EditorPrefs.SetInt(PrefScalarDir, _scalarDirection);
  97. EditorPrefs.SetInt(PrefScalarAxis, _scalarAxis);
  98. }
  99. if (valid)
  100. {
  101. bool useWidthAxis = _scalarAxis == 0;
  102. float scalarResult = _scalarDirection == 0
  103. ? DesignResolutionConverter.DesignLengthToCanvas(_scalarInput, useWidthAxis, _designArtboard, _canvasReference)
  104. : DesignResolutionConverter.CanvasLengthToDesign(_scalarInput, useWidthAxis, _designArtboard, _canvasReference);
  105. string resultLabel = _scalarDirection == 0 ? "Canvas 单位" : "设计稿 px";
  106. EditorGUILayout.LabelField($"换算结果 ({resultLabel})", EditorStyles.boldLabel);
  107. string resultText = scalarResult.ToString("G9", CultureInfo.InvariantCulture);
  108. EditorGUILayout.SelectableLabel(resultText, GUILayout.Height(18f));
  109. EditorGUILayout.BeginHorizontal();
  110. if (GUILayout.Button("复制结果"))
  111. {
  112. EditorGUIUtility.systemCopyBuffer = resultText;
  113. ShowNotification(new GUIContent("已复制到剪贴板"));
  114. }
  115. if (GUILayout.Button("复制(保留 4 位小数)"))
  116. {
  117. string rounded = scalarResult.ToString("F4", CultureInfo.InvariantCulture);
  118. EditorGUIUtility.systemCopyBuffer = rounded;
  119. ShowNotification(new GUIContent("已复制(F4)"));
  120. }
  121. EditorGUILayout.EndHorizontal();
  122. }
  123. EditorGUILayout.Space(10f);
  124. EditorGUILayout.LabelField("尺寸换算", EditorStyles.boldLabel);
  125. _sampleDesignSize = EditorGUILayout.Vector2Field("设计稿上的宽×高 (px)", _sampleDesignSize);
  126. if (valid)
  127. {
  128. Vector2 toCanvas = DesignResolutionConverter.DesignSizeToCanvasUnits(_sampleDesignSize, _designArtboard, _canvasReference);
  129. EditorGUILayout.SelectableLabel($"→ Canvas 单位: ({toCanvas.x:F4}, {toCanvas.y:F4})", GUILayout.Height(18f));
  130. if (GUILayout.Button("将换算结果应用到选中 RectTransform(宽×高)"))
  131. ApplyCanvasSizeToSelection(toCanvas);
  132. }
  133. EditorGUILayout.Space(6f);
  134. _sampleCanvasSize = EditorGUILayout.Vector2Field("Canvas 上的宽×高", _sampleCanvasSize);
  135. if (valid)
  136. {
  137. Vector2 toDesign = DesignResolutionConverter.CanvasUnitsToDesignSize(_sampleCanvasSize, _designArtboard, _canvasReference);
  138. EditorGUILayout.SelectableLabel($"→ 设计稿 px: ({toDesign.x:F4}, {toDesign.y:F4})", GUILayout.Height(18f));
  139. if (GUILayout.Button("将当前 Canvas 宽×高应用到选中 RectTransform"))
  140. ApplyCanvasSizeToSelection(_sampleCanvasSize);
  141. }
  142. EditorGUILayout.Space(10f);
  143. EditorGUILayout.LabelField("文档", EditorStyles.boldLabel);
  144. if (GUILayout.Button("打开说明文档 (AppUI/Docs)"))
  145. {
  146. const string docPath = "Assets/AppUI/Docs/UI设计稿与Canvas分辨率换算.md";
  147. var obj = AssetDatabase.LoadAssetAtPath<Object>(docPath);
  148. if (obj != null)
  149. AssetDatabase.OpenAsset(obj);
  150. else
  151. EditorUtility.DisplayDialog("未找到", $"请确认文件存在:{docPath}", "确定");
  152. }
  153. EditorGUILayout.EndScrollView();
  154. }
  155. private void PullFromSelection()
  156. {
  157. GameObject go = Selection.activeGameObject;
  158. if (go == null)
  159. {
  160. EditorUtility.DisplayDialog("未选中", "请选中带有 CanvasScaler 的 GameObject(通常为 Canvas)。", "确定");
  161. return;
  162. }
  163. var scaler = go.GetComponent<CanvasScaler>();
  164. if (scaler == null)
  165. scaler = go.GetComponentInParent<CanvasScaler>();
  166. if (scaler == null)
  167. {
  168. EditorUtility.DisplayDialog("未找到 CanvasScaler", "选中对象及其父级上都没有 CanvasScaler。", "确定");
  169. return;
  170. }
  171. _canvasReference = scaler.referenceResolution;
  172. SavePrefs();
  173. ShowNotification(new GUIContent("已读取 Reference Resolution"));
  174. }
  175. /// <summary>
  176. /// 使用 RectTransform.SetSizeWithCurrentAnchors,在保持当前锚点的前提下写入宽、高。
  177. /// </summary>
  178. private void ApplyCanvasSizeToSelection(Vector2 sizeCanvasUnits)
  179. {
  180. GameObject[] gos = Selection.gameObjects;
  181. if (gos == null || gos.Length == 0)
  182. {
  183. EditorUtility.DisplayDialog("未选中", "请选中一个或多个带 RectTransform 的 UI 对象。", "确定");
  184. return;
  185. }
  186. int applied = 0;
  187. foreach (GameObject go in gos)
  188. {
  189. var rt = go.GetComponent<RectTransform>();
  190. if (rt == null)
  191. continue;
  192. Undo.RecordObject(rt, "Apply UI size from design converter");
  193. rt.SetSizeWithCurrentAnchors(RectTransform.Axis.Horizontal, sizeCanvasUnits.x);
  194. rt.SetSizeWithCurrentAnchors(RectTransform.Axis.Vertical, sizeCanvasUnits.y);
  195. EditorUtility.SetDirty(rt);
  196. if (PrefabUtility.IsPartOfPrefabInstance(rt))
  197. PrefabUtility.RecordPrefabInstancePropertyModifications(rt);
  198. applied++;
  199. }
  200. if (applied == 0)
  201. EditorUtility.DisplayDialog("无 RectTransform", "选中对象中没有任何 RectTransform 组件。", "确定");
  202. else
  203. ShowNotification(new GUIContent($"已更新 {applied} 个对象的宽×高"));
  204. }
  205. }
  206. }