-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathRectTransformVisualizer.cs
More file actions
307 lines (254 loc) · 10.7 KB
/
Copy pathRectTransformVisualizer.cs
File metadata and controls
307 lines (254 loc) · 10.7 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
#nullable enable
using UnityEngine;
using UnityEngine.UI;
namespace Unity.AdvancedUI;
/// <summary>
/// Visualizes pivot, anchors and bounds of a <see cref="RectTransform"/> by emitting
/// geometry through Unity's Canvas mesh pipeline (<see cref="Graphic"/>).
/// <para>
/// Because it inherits <see cref="Graphic"/>, it participates in Canvas batching,
/// respects CanvasGroup alpha, and works in all three Canvas render modes without
/// any camera or GL matrix setup.
/// </para>
/// <para>
/// Add this component to the same GameObject whose RectTransform you want to inspect,
/// <b>or</b> to any child — the <see cref="Target"/> field lets you point at any
/// RectTransform in the hierarchy.
/// </para>
/// </summary>
[DisallowMultipleComponent, RequireComponent(typeof(CanvasRenderer))]
public sealed class RectTransformVisualizer : Graphic
{
/// <summary> RectTransform to visualize. Defaults to the one on this GameObject. </summary>
public RectTransform? Target { get => _Target; set => _Target = value; }
/// <summary> </summary>
public bool ShowBounds { get => _ShowBounds; set => _ShowBounds = value; }
/// <summary> </summary>
public bool ShowPivot { get => _ShowPivot; set => _ShowPivot = value; }
/// <summary> </summary>
public bool ShowAnchors { get => _ShowAnchors; set => _ShowAnchors = value; }
/// <summary> </summary>
public bool ShowMargins { get => _ShowMargins; set => _ShowMargins = value; }
/// <summary> </summary>
public Color BoundsColor { get => _BoundsColor; set => _BoundsColor = value; }
/// <summary> </summary>
public Color PivotColor { get => _PivotColor; set => _PivotColor = value; }
/// <summary> </summary>
public Color AnchorColor { get => _AnchorColor; set => _AnchorColor = value; }
/// <summary> </summary>
public Color MarginColor { get => _MarginColor; set => _MarginColor = value; }
/// <summary> </summary>
public float PivotSize { get => _PivotSize; set => _PivotSize = value; }
/// <summary> </summary>
public float AnchorSize { get => _AnchorSize; set => _AnchorSize = value; }
/// <summary> </summary>
public float LineWidth { get => _LineWidth; set => _LineWidth = value; }
/// <summary> This is a purely visual overlay — never a raycast target. </summary>
public override bool raycastTarget
{
get => false;
set { }
}
#region Backing fields
[SerializeField] private RectTransform? _Target;
[SerializeField] private bool _ShowAnchors = true;
[SerializeField] private bool _ShowMargins = true;
[SerializeField] private bool _ShowBounds = true;
[SerializeField] private bool _ShowPivot = true;
[SerializeField] private Color _BoundsColor = new Color(0.20f, 0.90f, 0.30f, 0.90f);
[SerializeField] private Color _PivotColor = new Color(0.95f, 0.25f, 0.25f, 1.00f);
[SerializeField] private Color _AnchorColor = new Color(1.00f, 0.60f, 0.10f, 0.90f);
[SerializeField] private Color _MarginColor = new Color(0.30f, 0.60f, 1.00f, 0.70f);
[SerializeField] private float _PivotSize = 6f;
[SerializeField] private float _AnchorSize = 9f;
[SerializeField] private float _LineWidth = 1.5f;
#endregion
/// <inheritdoc/>
protected override void OnEnable()
{
base.OnEnable();
// Stretch to fill the entire parent so the Graphic can draw
// anywhere inside the Canvas, not just inside its own rect.
rectTransform.pivot = new Vector2(0.5f, 0.5f);
rectTransform.anchorMin = Vector2.zero;
rectTransform.anchorMax = Vector2.one;
rectTransform.offsetMin = Vector2.zero;
rectTransform.offsetMax = Vector2.zero;
}
/// <inheritdoc/>
protected override void OnRectTransformDimensionsChange()
{
base.OnRectTransformDimensionsChange();
SetVerticesDirty();
}
// Redraw whenever the target RT changes at runtime
[System.Diagnostics.CodeAnalysis.SuppressMessage("Usage", "IDE0051")]
private void LateUpdate() => SetVerticesDirty();
// ── Mesh population ────────────────────────────────────────────────────
/// <summary>
/// Unity calls this whenever the mesh needs to be rebuilt.
/// All geometry is emitted here using <see cref="VertexHelper"/>.
/// </summary>
protected override void OnPopulateMesh(VertexHelper vh)
{
vh.Clear();
RectTransform rt = _Target ? _Target! : GetComponent<RectTransform>();
if (_ShowBounds) DrawBounds(vh, rt);
if (_ShowAnchors) DrawAnchors(vh, rt);
if (_ShowMargins) DrawMargins(vh, rt);
if (_ShowPivot) DrawPivot(vh, rt);
}
private void DrawBounds(VertexHelper vh, RectTransform rt)
{
Vector3[] wc = new Vector3[4]; // 0=BL 1=TL 2=TR 3=BR
rt.GetWorldCorners(wc);
Vector2 bl = W2L(wc[0]);
Vector2 tl = W2L(wc[1]);
Vector2 tr = W2L(wc[2]);
Vector2 br = W2L(wc[3]);
AddLine(vh, bl, tl, _LineWidth, _BoundsColor);
AddLine(vh, tl, tr, _LineWidth, _BoundsColor);
AddLine(vh, tr, br, _LineWidth, _BoundsColor);
AddLine(vh, br, bl, _LineWidth, _BoundsColor);
// Faint diagonals to mark the center
Color dim = _BoundsColor with { a = _BoundsColor.a * 0.22f };
AddLine(vh, bl, tr, _LineWidth * 0.5f, dim);
AddLine(vh, tl, br, _LineWidth * 0.5f, dim);
}
private void DrawAnchors(VertexHelper vh, RectTransform rt)
{
if (rt.parent is not RectTransform parentRt) return;
Vector3[] pw = new Vector3[4];
parentRt.GetWorldCorners(pw);
Vector2 pbl = W2L(pw[0]);
Vector2 ptl = W2L(pw[1]);
Vector2 ptr = W2L(pw[2]);
Vector2 pbr = W2L(pw[3]);
Vector2 aMin = Anchor2Local(rt.anchorMin, pbl, ptl, ptr, pbr);
Vector2 aMax = Anchor2Local(rt.anchorMax, pbl, ptl, ptr, pbr);
Vector2 aTL = Anchor2Local(new Vector2(rt.anchorMin.x, rt.anchorMax.y), pbl, ptl, ptr, pbr);
Vector2 aBR = Anchor2Local(new Vector2(rt.anchorMax.x, rt.anchorMin.y), pbl, ptl, ptr, pbr);
bool isRect = rt.anchorMin != rt.anchorMax;
// Anchor rect outline
if (isRect)
{
Color dimA = _AnchorColor with { a = _AnchorColor.a * 0.35f };
AddLine(vh, aMin, aTL, _LineWidth * 0.8f, dimA);
AddLine(vh, aTL, aMax, _LineWidth * 0.8f, dimA);
AddLine(vh, aMax, aBR, _LineWidth * 0.8f, dimA);
AddLine(vh, aBR, aMin, _LineWidth * 0.8f, dimA);
}
// Anchor crosses
AddCross(vh, aMin, _AnchorSize, _LineWidth, _AnchorColor);
if (isRect) AddCross(vh, aMax, _AnchorSize, _LineWidth, _AnchorColor);
// Guide lines: anchor corner → matching bounds corner
Vector3[] tc = new Vector3[4];
rt.GetWorldCorners(tc);
Color guide = _AnchorColor with { a = _AnchorColor.a * 0.40f };
AddLine(vh, aMin, W2L(tc[0]), _LineWidth * 0.5f, guide); // BL
AddLine(vh, aMax, W2L(tc[2]), _LineWidth * 0.5f, guide); // TR
}
private void DrawMargins(VertexHelper vh, RectTransform rt)
{
if (rt.parent is not RectTransform parentRt) return;
Vector3[] pw = new Vector3[4];
parentRt.GetWorldCorners(pw);
Vector2 pbl = W2L(pw[0]);
Vector2 ptl = W2L(pw[1]);
Vector2 ptr = W2L(pw[2]);
Vector2 pbr = W2L(pw[3]);
Vector2 abl = Anchor2Local(new Vector2(rt.anchorMin.x, rt.anchorMin.y), pbl, ptl, ptr, pbr);
Vector2 atl = Anchor2Local(new Vector2(rt.anchorMin.x, rt.anchorMax.y), pbl, ptl, ptr, pbr);
Vector2 atr = Anchor2Local(new Vector2(rt.anchorMax.x, rt.anchorMax.y), pbl, ptl, ptr, pbr);
Vector2 abr = Anchor2Local(new Vector2(rt.anchorMax.x, rt.anchorMin.y), pbl, ptl, ptr, pbr);
Vector3[] tc = new Vector3[4];
rt.GetWorldCorners(tc);
// offsetMin — anchor BL → bounds BL
AddLine(vh, abl, W2L(tc[0]), _LineWidth, _MarginColor);
// offsetMax — anchor TR → bounds TR
AddLine(vh, atr, W2L(tc[2]), _LineWidth, _MarginColor);
// TL / BR secondary margin lines (dimmed)
Color faint = _MarginColor with { a = _MarginColor.a * 0.40f };
AddLine(vh, atl, W2L(tc[1]), _LineWidth * 0.7f, faint);
AddLine(vh, abr, W2L(tc[3]), _LineWidth * 0.7f, faint);
}
private void DrawPivot(VertexHelper vh, RectTransform rt)
{
Vector3[] wc = new Vector3[4];
rt.GetWorldCorners(wc);
// Bilinear interpolation inside the four corners
Vector2 p = rt.pivot;
Vector3 bottom = Vector3.Lerp(wc[0], wc[3], p.x);
Vector3 top = Vector3.Lerp(wc[1], wc[2], p.x);
Vector2 center = W2L(Vector3.Lerp(bottom, top, p.y));
float s = _PivotSize;
Vector2 up = center + new Vector2(0, s);
Vector2 right = center + new Vector2(s, 0);
Vector2 down = center + new Vector2(0, -s);
Vector2 left = center + new Vector2(-s, 0);
// Filled diamond
AddTriangle(vh, center, up, right, _PivotColor);
AddTriangle(vh, center, right, down, _PivotColor);
AddTriangle(vh, center, down, left, _PivotColor);
AddTriangle(vh, center, left, up, _PivotColor);
// White outline
Color outline = UnityEngine.Color.white with { a = 0.9f };
float ow = _LineWidth * 0.75f;
AddLine(vh, up, right, ow, outline);
AddLine(vh, right, down, ow, outline);
AddLine(vh, down, left, ow, outline);
AddLine(vh, left, up, ow, outline);
}
/// <summary>
/// Emits a screen-aligned quad (a line with thickness) between two points.
/// </summary>
private static void AddLine(VertexHelper vh, Vector2 a, Vector2 b, float width, Color color)
{
Vector2 dir = (b - a);
float len = dir.magnitude;
if (len < 0.001f) return;
Vector2 perp = new Vector2(-dir.y, dir.x) / len * (width * 0.5f);
int i = vh.currentVertCount;
vh.AddVert(a - perp, color, Vector2.zero);
vh.AddVert(a + perp, color, Vector2.zero);
vh.AddVert(b + perp, color, Vector2.zero);
vh.AddVert(b - perp, color, Vector2.zero);
vh.AddTriangle(i, i + 1, i + 2);
vh.AddTriangle(i, i + 2, i + 3);
}
/// <summary> Emits a cross (two perpendicular lines) at <paramref name="center"/>. </summary>
private static void AddCross(VertexHelper vh, Vector2 center, float half, float width, Color color)
{
AddLine(vh, center - new Vector2(half, 0), center + new Vector2(half, 0), width, color);
AddLine(vh, center - new Vector2(0, half), center + new Vector2(0, half), width, color);
}
/// <summary> Emits a filled triangle. </summary>
private static void AddTriangle(VertexHelper vh, Vector2 a, Vector2 b, Vector2 c, Color color)
{
int i = vh.currentVertCount;
vh.AddVert(a, color, Vector2.zero);
vh.AddVert(b, color, Vector2.zero);
vh.AddVert(c, color, Vector2.zero);
vh.AddTriangle(i, i + 1, i + 2);
}
/// <summary>
/// World space → local space of this Graphic's <see cref="RectTransform"/> (drops Z).
/// </summary>
private Vector2 W2L(Vector3 world)
{
Vector3 local = rectTransform.InverseTransformPoint(world);
return local;
}
/// <summary>
/// Bilinear interpolation from normalized anchor coords (0–1) to local-space position
/// inside the parent's four corners (already in local space).
/// </summary>
private static Vector2 Anchor2Local(
Vector2 anchor,
Vector2 pbl, Vector2 ptl, Vector2 ptr, Vector2 pbr)
{
Vector2 bottom = Vector2.Lerp(pbl, pbr, anchor.x);
Vector2 top = Vector2.Lerp(ptl, ptr, anchor.x);
return Vector2.Lerp(bottom, top, anchor.y);
}
}