@mflrevan/ucp 0.6.2 → 0.6.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/bridge/com.ucp.bridge/CHANGELOG.md +48 -0
- package/bridge/com.ucp.bridge/Editor/Bridge/BridgeServer.cs +55 -6
- package/bridge/com.ucp.bridge/Editor/Bridge/EditorStateSummary.cs +165 -0
- package/bridge/com.ucp.bridge/Editor/Bridge/EditorStateSummary.cs.meta +11 -0
- package/bridge/com.ucp.bridge/Editor/Compatibility/UnityObjectCompat.cs +22 -5
- package/bridge/com.ucp.bridge/Editor/Controllers/EditorController.cs +1 -0
- package/bridge/com.ucp.bridge/Editor/Controllers/HierarchyController.cs +10 -10
- package/bridge/com.ucp.bridge/Editor/Controllers/LogsController.cs +71 -0
- package/bridge/com.ucp.bridge/Editor/Controllers/MaterialController.cs +2 -2
- package/bridge/com.ucp.bridge/Editor/Controllers/ObjectLocator.cs +4 -4
- package/bridge/com.ucp.bridge/Editor/Controllers/ObjectReferenceResolver.cs +3 -3
- package/bridge/com.ucp.bridge/Editor/Controllers/PrefabController.cs +6 -6
- package/bridge/com.ucp.bridge/Editor/Controllers/PropertyController.cs +7 -7
- package/bridge/com.ucp.bridge/Editor/Controllers/RecordingController.cs +8 -0
- package/bridge/com.ucp.bridge/Editor/Controllers/SceneChangeTracker.cs +3 -3
- package/bridge/com.ucp.bridge/Editor/Controllers/SceneController.cs +3 -3
- package/bridge/com.ucp.bridge/Editor/Controllers/SnapshotController.cs +5 -5
- package/bridge/com.ucp.bridge/Editor/Controllers/TestRunnerController.cs +1 -1
- package/bridge/com.ucp.bridge/Editor/Controllers/TransformController.cs +1 -1
- package/bridge/com.ucp.bridge/Editor/Controllers/UiController.cs +70 -0
- package/bridge/com.ucp.bridge/Editor/Controllers/UiController.cs.meta +11 -0
- package/bridge/com.ucp.bridge/Editor/Controllers/ViewController.cs +1 -1
- package/bridge/com.ucp.bridge/Editor/Protocol/MiniJson.cs +1 -1
- package/bridge/com.ucp.bridge/Editor/Ui/UiHost.cs +468 -0
- package/bridge/com.ucp.bridge/Editor/Ui/UiHost.cs.meta +11 -0
- package/bridge/com.ucp.bridge/Editor/Ui/UiLintService.cs +554 -0
- package/bridge/com.ucp.bridge/Editor/Ui/UiLintService.cs.meta +11 -0
- package/bridge/com.ucp.bridge/Editor/Ui/UiOperationManager.cs +870 -0
- package/bridge/com.ucp.bridge/Editor/Ui/UiOperationManager.cs.meta +11 -0
- package/bridge/com.ucp.bridge/Editor/Ui/UiScenarioApplier.cs +496 -0
- package/bridge/com.ucp.bridge/Editor/Ui/UiScenarioApplier.cs.meta +11 -0
- package/bridge/com.ucp.bridge/Editor/Ui/UiScenarioLoader.cs +854 -0
- package/bridge/com.ucp.bridge/Editor/Ui/UiScenarioLoader.cs.meta +11 -0
- package/bridge/com.ucp.bridge/Editor/Ui/UiScenarioModels.cs +374 -0
- package/bridge/com.ucp.bridge/Editor/Ui/UiScenarioModels.cs.meta +11 -0
- package/bridge/com.ucp.bridge/Editor/Ui/UiVisualTreeInspector.cs +690 -0
- package/bridge/com.ucp.bridge/Editor/Ui/UiVisualTreeInspector.cs.meta +11 -0
- package/bridge/com.ucp.bridge/{Runtime.meta → Editor/Ui.meta} +1 -1
- package/bridge/com.ucp.bridge/Tests/Editor/ControllerSmokeTests.cs +79 -4
- package/bridge/com.ucp.bridge/Tests/Editor/SpatialVisualControllerTests.cs +2 -2
- package/bridge/com.ucp.bridge/Tests/Editor/UiControllerTests.cs +547 -0
- package/bridge/com.ucp.bridge/Tests/Editor/UiControllerTests.cs.meta +11 -0
- package/bridge/com.ucp.bridge/Tests/Editor/UiLintServiceTests.cs +253 -0
- package/bridge/com.ucp.bridge/Tests/Editor/UiLintServiceTests.cs.meta +11 -0
- package/bridge/com.ucp.bridge/Tests/Editor/UiScenarioTests.cs +587 -0
- package/bridge/com.ucp.bridge/Tests/Editor/UiScenarioTests.cs.meta +11 -0
- package/bridge/com.ucp.bridge/package.json +1 -1
- package/package.json +1 -1
- package/bridge/com.ucp.bridge/Runtime/UCP.Bridge.Runtime.asmdef +0 -14
- package/bridge/com.ucp.bridge/Runtime/UCP.Bridge.Runtime.asmdef.meta +0 -7
|
@@ -0,0 +1,468 @@
|
|
|
1
|
+
#if UNITY_6000_0_OR_NEWER
|
|
2
|
+
using System;
|
|
3
|
+
using System.Collections.Generic;
|
|
4
|
+
using System.Globalization;
|
|
5
|
+
using System.IO;
|
|
6
|
+
using System.Security.Cryptography;
|
|
7
|
+
using System.Text;
|
|
8
|
+
using UnityEditor;
|
|
9
|
+
using UnityEditorInternal;
|
|
10
|
+
using UnityEngine;
|
|
11
|
+
using UnityEngine.Rendering;
|
|
12
|
+
using UnityEngine.UIElements;
|
|
13
|
+
|
|
14
|
+
namespace UCP.Bridge
|
|
15
|
+
{
|
|
16
|
+
/// <summary>
|
|
17
|
+
/// Transient editor-only host for deterministic UI Toolkit inspection and capture.
|
|
18
|
+
/// The compositor compensates for CaptureEditorWindow treating logical window points
|
|
19
|
+
/// as physical pixels on high-DPI displays.
|
|
20
|
+
/// </summary>
|
|
21
|
+
internal sealed class UiHostWindow : EditorWindow
|
|
22
|
+
{
|
|
23
|
+
private int _viewportWidth;
|
|
24
|
+
private int _viewportHeight;
|
|
25
|
+
private float _compositorScale = 1f;
|
|
26
|
+
|
|
27
|
+
internal VisualElement ContentRoot { get; private set; }
|
|
28
|
+
|
|
29
|
+
internal static UiHostWindow Open(int width, int height)
|
|
30
|
+
{
|
|
31
|
+
var window = CreateInstance<UiHostWindow>();
|
|
32
|
+
try
|
|
33
|
+
{
|
|
34
|
+
window.hideFlags = HideFlags.HideAndDontSave;
|
|
35
|
+
window._viewportWidth = width;
|
|
36
|
+
window._viewportHeight = height;
|
|
37
|
+
window._compositorScale = 1f / Mathf.Max(1f, EditorGUIUtility.pixelsPerPoint);
|
|
38
|
+
window.titleContent = new GUIContent("UCP UI Harness");
|
|
39
|
+
window.minSize = new Vector2(width, height);
|
|
40
|
+
window.maxSize = new Vector2(width, height);
|
|
41
|
+
window.position = new Rect(80f, 80f, width, height);
|
|
42
|
+
window.ShowUtility();
|
|
43
|
+
window.EnsureContent();
|
|
44
|
+
window.Focus();
|
|
45
|
+
return window;
|
|
46
|
+
}
|
|
47
|
+
catch
|
|
48
|
+
{
|
|
49
|
+
try
|
|
50
|
+
{
|
|
51
|
+
CloseAndDestroy(window);
|
|
52
|
+
}
|
|
53
|
+
catch
|
|
54
|
+
{
|
|
55
|
+
// Preserve the exception that prevented the host from opening.
|
|
56
|
+
}
|
|
57
|
+
throw;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
internal static void CloseAndDestroy(UiHostWindow window)
|
|
62
|
+
{
|
|
63
|
+
if (window == null)
|
|
64
|
+
return;
|
|
65
|
+
|
|
66
|
+
var error = UiCleanup.RunAll(
|
|
67
|
+
() => window.Close(),
|
|
68
|
+
() =>
|
|
69
|
+
{
|
|
70
|
+
if (window != null)
|
|
71
|
+
UnityEngine.Object.DestroyImmediate(window);
|
|
72
|
+
});
|
|
73
|
+
if (error != null)
|
|
74
|
+
throw error;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
public void CreateGUI()
|
|
78
|
+
{
|
|
79
|
+
EnsureContent();
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
internal void EnsureContent()
|
|
83
|
+
{
|
|
84
|
+
if (ContentRoot != null)
|
|
85
|
+
return;
|
|
86
|
+
|
|
87
|
+
rootVisualElement.Clear();
|
|
88
|
+
rootVisualElement.style.overflow = Overflow.Hidden;
|
|
89
|
+
|
|
90
|
+
ContentRoot = new VisualElement
|
|
91
|
+
{
|
|
92
|
+
name = "ucp-ui-content-root",
|
|
93
|
+
pickingMode = PickingMode.Position
|
|
94
|
+
};
|
|
95
|
+
ContentRoot.style.position = Position.Absolute;
|
|
96
|
+
ContentRoot.style.left = 0f;
|
|
97
|
+
ContentRoot.style.bottom = 0f;
|
|
98
|
+
ContentRoot.style.width = _viewportWidth;
|
|
99
|
+
ContentRoot.style.height = _viewportHeight;
|
|
100
|
+
ContentRoot.style.overflow = Overflow.Hidden;
|
|
101
|
+
ContentRoot.style.transformOrigin = new TransformOrigin(Length.Percent(0f), Length.Percent(100f));
|
|
102
|
+
ContentRoot.style.scale = new Scale(new Vector3(_compositorScale, _compositorScale, 1f));
|
|
103
|
+
rootVisualElement.Add(ContentRoot);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
internal void Pump()
|
|
107
|
+
{
|
|
108
|
+
EnsureContent();
|
|
109
|
+
Focus();
|
|
110
|
+
Repaint();
|
|
111
|
+
EditorApplication.QueuePlayerLoopUpdate();
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
internal readonly struct UiGeometrySample
|
|
116
|
+
{
|
|
117
|
+
internal UiGeometrySample(
|
|
118
|
+
string hash,
|
|
119
|
+
int elementCount,
|
|
120
|
+
int renderedElementCount,
|
|
121
|
+
int invalidGeometryCount,
|
|
122
|
+
bool panelAttached)
|
|
123
|
+
{
|
|
124
|
+
Hash = hash;
|
|
125
|
+
ElementCount = elementCount;
|
|
126
|
+
RenderedElementCount = renderedElementCount;
|
|
127
|
+
InvalidGeometryCount = invalidGeometryCount;
|
|
128
|
+
PanelAttached = panelAttached;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
internal string Hash { get; }
|
|
132
|
+
internal int ElementCount { get; }
|
|
133
|
+
internal int RenderedElementCount { get; }
|
|
134
|
+
internal int InvalidGeometryCount { get; }
|
|
135
|
+
internal bool PanelAttached { get; }
|
|
136
|
+
internal bool IsValid => PanelAttached && RenderedElementCount > 0 && InvalidGeometryCount == 0;
|
|
137
|
+
|
|
138
|
+
internal Dictionary<string, object> ToDictionary(int stableFrames)
|
|
139
|
+
{
|
|
140
|
+
return new Dictionary<string, object>
|
|
141
|
+
{
|
|
142
|
+
["panelAttached"] = PanelAttached,
|
|
143
|
+
["elementCount"] = ElementCount,
|
|
144
|
+
["renderedElementCount"] = RenderedElementCount,
|
|
145
|
+
["invalidGeometryCount"] = InvalidGeometryCount,
|
|
146
|
+
["stableFrames"] = stableFrames,
|
|
147
|
+
["geometryHash"] = Hash
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
internal static class UiGeometrySampler
|
|
153
|
+
{
|
|
154
|
+
internal static UiGeometrySample Measure(VisualElement root)
|
|
155
|
+
{
|
|
156
|
+
var hash = 1469598103934665603UL;
|
|
157
|
+
var elementCount = 0;
|
|
158
|
+
var renderedCount = 0;
|
|
159
|
+
var invalidCount = 0;
|
|
160
|
+
Visit(root, true, ref hash, ref elementCount, ref renderedCount, ref invalidCount);
|
|
161
|
+
return new UiGeometrySample(
|
|
162
|
+
hash.ToString("x16", CultureInfo.InvariantCulture),
|
|
163
|
+
elementCount,
|
|
164
|
+
renderedCount,
|
|
165
|
+
invalidCount,
|
|
166
|
+
root?.panel != null);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
private static void Visit(
|
|
170
|
+
VisualElement element,
|
|
171
|
+
bool ancestorsDisplayed,
|
|
172
|
+
ref ulong hash,
|
|
173
|
+
ref int elementCount,
|
|
174
|
+
ref int renderedCount,
|
|
175
|
+
ref int invalidCount)
|
|
176
|
+
{
|
|
177
|
+
if (element == null)
|
|
178
|
+
return;
|
|
179
|
+
|
|
180
|
+
elementCount++;
|
|
181
|
+
var displayed = ancestorsDisplayed && element.resolvedStyle.display != DisplayStyle.None;
|
|
182
|
+
Add(ref hash, element.GetType().FullName);
|
|
183
|
+
Add(ref hash, element.name);
|
|
184
|
+
Add(ref hash, displayed ? "displayed" : "not-displayed");
|
|
185
|
+
Add(ref hash, element.hierarchy.childCount.ToString(CultureInfo.InvariantCulture));
|
|
186
|
+
if (element is TextElement textElement)
|
|
187
|
+
Add(ref hash, textElement.text);
|
|
188
|
+
|
|
189
|
+
if (displayed)
|
|
190
|
+
{
|
|
191
|
+
renderedCount++;
|
|
192
|
+
AddRect(ref hash, element.layout, ref invalidCount);
|
|
193
|
+
AddRect(ref hash, element.worldBound, ref invalidCount);
|
|
194
|
+
Add(ref hash, element.resolvedStyle.visibility.ToString());
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
foreach (var child in element.hierarchy.Children())
|
|
198
|
+
Visit(child, displayed, ref hash, ref elementCount, ref renderedCount, ref invalidCount);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
private static void AddRect(ref ulong hash, Rect rect, ref int invalidCount)
|
|
202
|
+
{
|
|
203
|
+
if (!UiValue.IsFinite(rect.x) || !UiValue.IsFinite(rect.y) ||
|
|
204
|
+
!UiValue.IsFinite(rect.width) || !UiValue.IsFinite(rect.height))
|
|
205
|
+
{
|
|
206
|
+
invalidCount++;
|
|
207
|
+
Add(ref hash, "<non-finite>");
|
|
208
|
+
return;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
Add(ref hash, Math.Round(rect.x, 3).ToString(CultureInfo.InvariantCulture));
|
|
212
|
+
Add(ref hash, Math.Round(rect.y, 3).ToString(CultureInfo.InvariantCulture));
|
|
213
|
+
Add(ref hash, Math.Round(rect.width, 3).ToString(CultureInfo.InvariantCulture));
|
|
214
|
+
Add(ref hash, Math.Round(rect.height, 3).ToString(CultureInfo.InvariantCulture));
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
private static void Add(ref ulong hash, string value)
|
|
218
|
+
{
|
|
219
|
+
value ??= "<null>";
|
|
220
|
+
for (var i = 0; i < value.Length; i++)
|
|
221
|
+
{
|
|
222
|
+
hash ^= value[i];
|
|
223
|
+
hash *= 1099511628211UL;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
hash ^= 0xff;
|
|
227
|
+
hash *= 1099511628211UL;
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
internal sealed class UiCaptureSample
|
|
232
|
+
{
|
|
233
|
+
internal byte[] Png { get; set; }
|
|
234
|
+
internal string PixelHash { get; set; }
|
|
235
|
+
internal int Width { get; set; }
|
|
236
|
+
internal int Height { get; set; }
|
|
237
|
+
internal int SampledDistinctColors { get; set; }
|
|
238
|
+
internal int NonTransparentPixels { get; set; }
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
internal sealed class UiCaptureSurface : IDisposable
|
|
242
|
+
{
|
|
243
|
+
private RenderTexture _texture;
|
|
244
|
+
|
|
245
|
+
internal UiCaptureSurface(int width, int height)
|
|
246
|
+
{
|
|
247
|
+
_texture = new RenderTexture(width, height, 0, RenderTextureFormat.ARGB32, RenderTextureReadWrite.sRGB)
|
|
248
|
+
{
|
|
249
|
+
name = "UCP UI Capture",
|
|
250
|
+
antiAliasing = 1,
|
|
251
|
+
hideFlags = HideFlags.HideAndDontSave
|
|
252
|
+
};
|
|
253
|
+
_texture.Create();
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
internal UiCaptureSample Capture(UiHostWindow window)
|
|
257
|
+
{
|
|
258
|
+
if (window == null || _texture == null || !_texture.IsCreated())
|
|
259
|
+
throw new InvalidOperationException("The UI capture surface is unavailable");
|
|
260
|
+
|
|
261
|
+
_texture.DiscardContents();
|
|
262
|
+
if (!InternalEditorUtility.CaptureEditorWindow(window, _texture))
|
|
263
|
+
throw new InvalidOperationException("Unity could not capture the UI harness window");
|
|
264
|
+
|
|
265
|
+
var previous = RenderTexture.active;
|
|
266
|
+
Texture2D texture = null;
|
|
267
|
+
try
|
|
268
|
+
{
|
|
269
|
+
RenderTexture.active = _texture;
|
|
270
|
+
texture = new Texture2D(_texture.width, _texture.height, TextureFormat.RGBA32, false, false)
|
|
271
|
+
{
|
|
272
|
+
hideFlags = HideFlags.HideAndDontSave
|
|
273
|
+
};
|
|
274
|
+
texture.ReadPixels(new Rect(0f, 0f, _texture.width, _texture.height), 0, 0, false);
|
|
275
|
+
texture.Apply(false, false);
|
|
276
|
+
|
|
277
|
+
var pixels = texture.GetPixels32();
|
|
278
|
+
var distinct = new HashSet<uint>();
|
|
279
|
+
var nonTransparent = 0;
|
|
280
|
+
var stride = Mathf.Max(1, pixels.Length / 8192);
|
|
281
|
+
for (var i = 0; i < pixels.Length; i++)
|
|
282
|
+
{
|
|
283
|
+
var pixel = pixels[i];
|
|
284
|
+
if (pixel.a > 0)
|
|
285
|
+
nonTransparent++;
|
|
286
|
+
if (i % stride == 0)
|
|
287
|
+
{
|
|
288
|
+
distinct.Add((uint)(pixel.r << 24 | pixel.g << 16 | pixel.b << 8 | pixel.a));
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
var png = texture.EncodeToPNG();
|
|
293
|
+
return new UiCaptureSample
|
|
294
|
+
{
|
|
295
|
+
Png = png,
|
|
296
|
+
PixelHash = Sha256(png),
|
|
297
|
+
Width = _texture.width,
|
|
298
|
+
Height = _texture.height,
|
|
299
|
+
SampledDistinctColors = distinct.Count,
|
|
300
|
+
NonTransparentPixels = nonTransparent
|
|
301
|
+
};
|
|
302
|
+
}
|
|
303
|
+
finally
|
|
304
|
+
{
|
|
305
|
+
RenderTexture.active = previous;
|
|
306
|
+
if (texture != null)
|
|
307
|
+
UnityEngine.Object.DestroyImmediate(texture);
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
public void Dispose()
|
|
312
|
+
{
|
|
313
|
+
var texture = _texture;
|
|
314
|
+
_texture = null;
|
|
315
|
+
if (texture == null)
|
|
316
|
+
return;
|
|
317
|
+
|
|
318
|
+
var error = UiCleanup.RunAll(
|
|
319
|
+
() =>
|
|
320
|
+
{
|
|
321
|
+
if (RenderTexture.active == texture)
|
|
322
|
+
RenderTexture.active = null;
|
|
323
|
+
},
|
|
324
|
+
() => texture.Release(),
|
|
325
|
+
() =>
|
|
326
|
+
{
|
|
327
|
+
if (texture != null)
|
|
328
|
+
UnityEngine.Object.DestroyImmediate(texture);
|
|
329
|
+
});
|
|
330
|
+
if (error != null)
|
|
331
|
+
throw error;
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
private static string Sha256(byte[] value)
|
|
335
|
+
{
|
|
336
|
+
using (var sha = SHA256.Create())
|
|
337
|
+
{
|
|
338
|
+
var bytes = sha.ComputeHash(value);
|
|
339
|
+
var result = new StringBuilder(bytes.Length * 2);
|
|
340
|
+
foreach (var item in bytes)
|
|
341
|
+
result.Append(item.ToString("x2", CultureInfo.InvariantCulture));
|
|
342
|
+
return result.ToString();
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
internal static class UiHostCapabilities
|
|
348
|
+
{
|
|
349
|
+
internal static void EnsureAvailable(string operation)
|
|
350
|
+
{
|
|
351
|
+
if (Application.isBatchMode)
|
|
352
|
+
{
|
|
353
|
+
throw new UiOperationException(
|
|
354
|
+
"capability_unavailable",
|
|
355
|
+
$"ui/{operation} requires an interactive Unity Editor; batch mode supports ui/lint only",
|
|
356
|
+
Describe());
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
if (SystemInfo.graphicsDeviceType == GraphicsDeviceType.Null)
|
|
360
|
+
{
|
|
361
|
+
throw new UiOperationException(
|
|
362
|
+
"capability_unavailable",
|
|
363
|
+
$"ui/{operation} requires a graphics device; Unity is using the Null graphics device",
|
|
364
|
+
Describe());
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
internal static Dictionary<string, object> Describe()
|
|
369
|
+
{
|
|
370
|
+
return new Dictionary<string, object>
|
|
371
|
+
{
|
|
372
|
+
["batchMode"] = Application.isBatchMode,
|
|
373
|
+
["graphicsDeviceType"] = SystemInfo.graphicsDeviceType.ToString(),
|
|
374
|
+
["graphicsDeviceName"] = SystemInfo.graphicsDeviceName,
|
|
375
|
+
["pixelsPerPoint"] = UiValue.FiniteOrNull(EditorGUIUtility.pixelsPerPoint),
|
|
376
|
+
["editorHost"] = !Application.isBatchMode && SystemInfo.graphicsDeviceType != GraphicsDeviceType.Null,
|
|
377
|
+
["lint"] = true,
|
|
378
|
+
["layout"] = !Application.isBatchMode && SystemInfo.graphicsDeviceType != GraphicsDeviceType.Null,
|
|
379
|
+
["screenshot"] = !Application.isBatchMode && SystemInfo.graphicsDeviceType != GraphicsDeviceType.Null
|
|
380
|
+
};
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
internal static class UiCleanup
|
|
385
|
+
{
|
|
386
|
+
internal static Exception RunAll(params Action[] actions)
|
|
387
|
+
{
|
|
388
|
+
Exception firstError = null;
|
|
389
|
+
if (actions == null)
|
|
390
|
+
return null;
|
|
391
|
+
|
|
392
|
+
foreach (var action in actions)
|
|
393
|
+
{
|
|
394
|
+
if (action == null)
|
|
395
|
+
continue;
|
|
396
|
+
try
|
|
397
|
+
{
|
|
398
|
+
action();
|
|
399
|
+
}
|
|
400
|
+
catch (Exception exception)
|
|
401
|
+
{
|
|
402
|
+
firstError ??= exception;
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
return firstError;
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
internal sealed class UiOperationException : Exception
|
|
410
|
+
{
|
|
411
|
+
internal UiOperationException(string code, string message, object details = null)
|
|
412
|
+
: base(message)
|
|
413
|
+
{
|
|
414
|
+
Code = code;
|
|
415
|
+
Details = details;
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
internal string Code { get; }
|
|
419
|
+
internal object Details { get; }
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
internal static class UiValue
|
|
423
|
+
{
|
|
424
|
+
internal static bool IsFinite(float value)
|
|
425
|
+
{
|
|
426
|
+
return !float.IsNaN(value) && !float.IsInfinity(value);
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
internal static bool IsFinite(double value)
|
|
430
|
+
{
|
|
431
|
+
return !double.IsNaN(value) && !double.IsInfinity(value);
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
internal static object FiniteOrNull(float value)
|
|
435
|
+
{
|
|
436
|
+
return IsFinite(value) ? (object)value : null;
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
internal static object FiniteOrNull(double value)
|
|
440
|
+
{
|
|
441
|
+
return IsFinite(value) ? (object)value : null;
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
internal static string NormalizePath(string path)
|
|
445
|
+
{
|
|
446
|
+
return path?.Replace('\\', '/');
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
internal static string SafeFileName(string value)
|
|
450
|
+
{
|
|
451
|
+
if (string.IsNullOrWhiteSpace(value))
|
|
452
|
+
return "ui";
|
|
453
|
+
|
|
454
|
+
var invalid = Path.GetInvalidFileNameChars();
|
|
455
|
+
var builder = new StringBuilder(value.Length);
|
|
456
|
+
foreach (var character in value)
|
|
457
|
+
{
|
|
458
|
+
builder.Append(Array.IndexOf(invalid, character) >= 0 || character == '/' || character == '\\'
|
|
459
|
+
? '-'
|
|
460
|
+
: character);
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
var result = builder.ToString().Trim('.', ' ', '-');
|
|
464
|
+
return string.IsNullOrEmpty(result) ? "ui" : result;
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
#endif
|