com.wallstop-studios.unity-helpers 2.0.0-rc77.7 → 2.0.0-rc77.8

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.
Files changed (30) hide show
  1. package/Editor/Sprites/AnimationViewerWindow.cs +2 -1
  2. package/Editor/Tools/ImageBlurTool.cs +409 -0
  3. package/Editor/Tools/ImageBlurTool.cs.meta +3 -0
  4. package/Editor/Tools.meta +3 -0
  5. package/Editor/Visuals/EnhancedImageEditor.cs +131 -0
  6. package/Editor/Visuals/EnhancedImageEditor.cs.meta +11 -0
  7. package/Editor/Visuals.meta +3 -0
  8. package/Runtime/Visuals/AnimatedSpriteLayer.cs +130 -0
  9. package/Runtime/Visuals/AnimatedSpriteLayer.cs.meta +3 -0
  10. package/Runtime/Visuals/UGUI/EnhancedImage.cs +76 -0
  11. package/Runtime/Visuals/UGUI/EnhancedImage.cs.meta +3 -0
  12. package/Runtime/Visuals/UGUI.meta +3 -0
  13. package/Runtime/{UI → Visuals/UIToolkit}/LayeredImage.cs +4 -126
  14. package/Runtime/{UI → Visuals/UIToolkit}/MultiFileSelectorElement.cs +3 -3
  15. package/Runtime/Visuals/UIToolkit.meta +3 -0
  16. package/Shaders/Materials/BackgroundMask-Material.mat +59 -0
  17. package/Shaders/Materials/BackgroundMask-Material.mat.meta +8 -0
  18. package/Shaders/Materials.meta +8 -0
  19. package/Shaders/ShaderGraph/BackgroundMask.shadergraph +1653 -0
  20. package/Shaders/ShaderGraph/BackgroundMask.shadergraph.meta +10 -0
  21. package/Shaders/ShaderGraph.meta +8 -0
  22. package/Shaders.meta +8 -0
  23. package/URP/VolumeProfiles/Post Processing Bloom Profile(URP).asset +63 -0
  24. package/URP/VolumeProfiles/Post Processing Bloom Profile(URP).asset.meta +8 -0
  25. package/URP/VolumeProfiles.meta +8 -0
  26. package/URP.meta +8 -0
  27. package/package.json +2 -1
  28. /package/Runtime/{UI → Visuals/UIToolkit}/LayeredImage.cs.meta +0 -0
  29. /package/Runtime/{UI → Visuals/UIToolkit}/MultiFileSelectorElement.cs.meta +0 -0
  30. /package/Runtime/{UI.meta → Visuals.meta} +0 -0
@@ -12,7 +12,8 @@ namespace WallstopStudios.UnityHelpers.Editor.Sprites
12
12
  using UnityEditor.UIElements;
13
13
  using UnityEngine;
14
14
  using UnityEngine.UIElements;
15
- using UI;
15
+ using WallstopStudios.UnityHelpers.Visuals;
16
+ using WallstopStudios.UnityHelpers.Visuals.UIToolkit;
16
17
  using Object = UnityEngine.Object;
17
18
 
18
19
  public sealed class AnimationViewerWindow : EditorWindow
@@ -0,0 +1,409 @@
1
+ namespace WallstopStudios.UnityHelpers.Editor.Tools
2
+ {
3
+ using System;
4
+ using System.Collections.Generic;
5
+ using System.IO;
6
+ using System.Linq;
7
+ using System.Threading.Tasks;
8
+ using UnityEditor;
9
+ using UnityEngine;
10
+ using WallstopStudios.UnityHelpers.Core.Extension;
11
+ using WallstopStudios.UnityHelpers.Core.Helper;
12
+ using WallstopStudios.UnityHelpers.Editor.CustomEditors;
13
+ using Object = UnityEngine.Object;
14
+
15
+ public sealed class ImageBlurTool : EditorWindow
16
+ {
17
+ public List<Object> imageSources = new();
18
+
19
+ private readonly List<Texture2D> _orderedTextures = new();
20
+ private readonly List<Texture2D> _manualTextures = new();
21
+
22
+ private int _blurRadius = 1;
23
+ private Vector2 _scrollPosition;
24
+
25
+ private GUIStyle _impactButtonStyle;
26
+ private SerializedObject _serializedObject;
27
+ private SerializedProperty _imageSourcesProperty;
28
+
29
+ private readonly List<Object> _lastSeenImageSources = new();
30
+
31
+ [MenuItem("Tools/Wallstop Studios/Unity Helpers/Image Blur")]
32
+ public static void ShowWindow()
33
+ {
34
+ GetWindow<ImageBlurTool>("Image Blur Tool");
35
+ }
36
+
37
+ private void OnEnable()
38
+ {
39
+ _serializedObject = new SerializedObject(this);
40
+ _imageSourcesProperty = _serializedObject.FindProperty(nameof(imageSources));
41
+ }
42
+
43
+ private void OnGUI()
44
+ {
45
+ _serializedObject.Update();
46
+
47
+ _impactButtonStyle ??= new GUIStyle(GUI.skin.button)
48
+ {
49
+ normal = { textColor = Color.yellow },
50
+ fontStyle = FontStyle.Bold,
51
+ };
52
+
53
+ EditorGUILayout.LabelField("Image Blur Settings", EditorStyles.boldLabel);
54
+ EditorGUILayout.Space();
55
+
56
+ EditorGUILayout.LabelField("Manual Folder Selection", EditorStyles.boldLabel);
57
+ PersistentDirectoryGUI.PathSelectorObjectArray(
58
+ _imageSourcesProperty,
59
+ nameof(ImageBlurTool)
60
+ );
61
+
62
+ if (
63
+ _serializedObject.ApplyModifiedProperties()
64
+ || !_lastSeenImageSources.SequenceEqual(imageSources)
65
+ )
66
+ {
67
+ _lastSeenImageSources.Clear();
68
+ _lastSeenImageSources.AddRange(imageSources);
69
+ _manualTextures.Clear();
70
+ foreach (Object directory in imageSources.Where(Objects.NotNull))
71
+ {
72
+ string path = AssetDatabase.GetAssetPath(directory);
73
+ if (string.IsNullOrWhiteSpace(path))
74
+ {
75
+ continue;
76
+ }
77
+
78
+ TrySyncDirectory(path, _manualTextures);
79
+ }
80
+ }
81
+
82
+ Event evt = Event.current;
83
+ Rect dropArea = GUILayoutUtility.GetRect(0f, 75f, GUILayout.ExpandWidth(true));
84
+ GUI.Box(dropArea, "Drag & Drop Images/Folders Here");
85
+
86
+ switch (evt.type)
87
+ {
88
+ case EventType.DragUpdated:
89
+ case EventType.DragPerform:
90
+ {
91
+ if (!dropArea.Contains(evt.mousePosition))
92
+ {
93
+ return;
94
+ }
95
+
96
+ DragAndDrop.visualMode = DragAndDropVisualMode.Copy;
97
+
98
+ if (evt.type == EventType.DragPerform)
99
+ {
100
+ DragAndDrop.AcceptDrag();
101
+ foreach (Object draggedObject in DragAndDrop.objectReferences)
102
+ {
103
+ if (draggedObject == null)
104
+ {
105
+ continue;
106
+ }
107
+
108
+ string path = AssetDatabase.GetAssetPath(draggedObject);
109
+ if (string.IsNullOrWhiteSpace(path))
110
+ {
111
+ continue;
112
+ }
113
+
114
+ if (AssetDatabase.IsValidFolder(path))
115
+ {
116
+ TrySyncDirectory(path, _orderedTextures);
117
+ }
118
+ else if (
119
+ draggedObject is Texture2D texture
120
+ && !_orderedTextures.Contains(texture)
121
+ )
122
+ {
123
+ _orderedTextures.Add(texture);
124
+ }
125
+ }
126
+ }
127
+
128
+ break;
129
+ }
130
+ }
131
+
132
+ EditorGUILayout.Space();
133
+
134
+ if (_orderedTextures.Count > 0 || _manualTextures.Count > 0)
135
+ {
136
+ EditorGUILayout.LabelField("Selected Images:", EditorStyles.boldLabel);
137
+ _scrollPosition = EditorGUILayout.BeginScrollView(
138
+ _scrollPosition,
139
+ GUILayout.Height(200)
140
+ );
141
+ foreach (Texture2D tex in _manualTextures.Concat(_orderedTextures).Distinct())
142
+ {
143
+ EditorGUILayout.ObjectField(tex.name, tex, typeof(Texture2D), false);
144
+ }
145
+ EditorGUILayout.EndScrollView();
146
+
147
+ if (GUILayout.Button("Clear Selection", _impactButtonStyle))
148
+ {
149
+ _orderedTextures.Clear();
150
+ }
151
+ }
152
+ else
153
+ {
154
+ EditorGUILayout.HelpBox(
155
+ "Drag images or folders into the area above to select them for blurring.",
156
+ MessageType.Info
157
+ );
158
+ }
159
+
160
+ EditorGUILayout.Space();
161
+ _blurRadius = EditorGUILayout.IntSlider("Blur Radius", _blurRadius, 1, 200);
162
+ EditorGUILayout.Space();
163
+
164
+ if (GUILayout.Button("Apply Blur", _impactButtonStyle))
165
+ {
166
+ ApplyBlurToSelectedTextures();
167
+ }
168
+ }
169
+
170
+ private static void TrySyncDirectory(string directory, List<Texture2D> output)
171
+ {
172
+ if (!AssetDatabase.IsValidFolder(directory))
173
+ {
174
+ return;
175
+ }
176
+
177
+ string[] guids = AssetDatabase.FindAssets("t:Texture2D", new[] { directory });
178
+ foreach (string guid in guids)
179
+ {
180
+ Texture2D texture = AssetDatabase.LoadAssetAtPath<Texture2D>(
181
+ AssetDatabase.GUIDToAssetPath(guid)
182
+ );
183
+ if (texture != null && !output.Contains(texture))
184
+ {
185
+ output.Add(texture);
186
+ }
187
+ }
188
+ }
189
+
190
+ private void ApplyBlurToSelectedTextures()
191
+ {
192
+ int processedCount = 0;
193
+ Texture2D[] toProcess = _manualTextures.Concat(_orderedTextures).Distinct().ToArray();
194
+ foreach (Texture2D originalTexture in toProcess)
195
+ {
196
+ string assetPath = AssetDatabase.GetAssetPath(originalTexture);
197
+ EditorUtility.DisplayProgressBar(
198
+ "Applying Blur",
199
+ $"Processing {originalTexture.name}...",
200
+ (float)processedCount / toProcess.Length
201
+ );
202
+ try
203
+ {
204
+ TextureImporter importer =
205
+ AssetImporter.GetAtPath(assetPath) as TextureImporter;
206
+
207
+ bool importSettingsChanged = false;
208
+
209
+ if (importer != null)
210
+ {
211
+ if (!importer.isReadable)
212
+ {
213
+ importer.isReadable = true;
214
+ importSettingsChanged = true;
215
+ }
216
+
217
+ if (importer.textureCompression != TextureImporterCompression.Uncompressed)
218
+ {
219
+ importer.textureCompression = TextureImporterCompression.Uncompressed;
220
+ importSettingsChanged = true;
221
+ }
222
+
223
+ if (importSettingsChanged)
224
+ {
225
+ importer.SaveAndReimport();
226
+ }
227
+ }
228
+
229
+ Texture2D currentTexture = AssetDatabase.LoadAssetAtPath<Texture2D>(assetPath);
230
+
231
+ if (currentTexture == null || !currentTexture.isReadable)
232
+ {
233
+ this.LogError(
234
+ $"Texture is null or could not be made readable: {assetPath}. Please check 'Read/Write Enabled' in its import settings if the issue persists. Skipping."
235
+ );
236
+ processedCount++;
237
+ continue;
238
+ }
239
+
240
+ Texture2D blurredTexture = CreateBlurredTexture(currentTexture, _blurRadius);
241
+
242
+ if (blurredTexture != null)
243
+ {
244
+ string directory = Path.GetDirectoryName(assetPath);
245
+ if (string.IsNullOrWhiteSpace(directory))
246
+ {
247
+ continue;
248
+ }
249
+
250
+ string fileName = Path.GetFileNameWithoutExtension(assetPath);
251
+ string extension = Path.GetExtension(assetPath);
252
+ string newPathBase = Path.Combine(
253
+ directory,
254
+ $"{fileName}_blurred_{_blurRadius}"
255
+ );
256
+
257
+ string finalPath = newPathBase + extension;
258
+ int counter = 0;
259
+ while (File.Exists(finalPath))
260
+ {
261
+ counter++;
262
+ finalPath = $"{newPathBase}_{counter}{extension}";
263
+ }
264
+
265
+ byte[] bytes;
266
+ if (
267
+ string.Equals(extension, ".jpg", StringComparison.OrdinalIgnoreCase)
268
+ || string.Equals(extension, ".jpeg", StringComparison.OrdinalIgnoreCase)
269
+ )
270
+ {
271
+ bytes = blurredTexture.EncodeToJPG(100);
272
+ }
273
+ else
274
+ {
275
+ finalPath = Path.ChangeExtension(finalPath, ".png");
276
+ bytes = blurredTexture.EncodeToPNG();
277
+ }
278
+
279
+ if (bytes != null)
280
+ {
281
+ File.WriteAllBytes(finalPath, bytes);
282
+ AssetDatabase.Refresh();
283
+ this.Log($"Saved blurred image to: {finalPath}");
284
+ }
285
+ else
286
+ {
287
+ this.LogError($"Failed to encode texture: {currentTexture.name}.");
288
+ }
289
+ }
290
+ else
291
+ {
292
+ this.LogError(
293
+ $"Failed to create blurred texture for: {originalTexture.name}."
294
+ );
295
+ }
296
+
297
+ processedCount++;
298
+ }
299
+ finally
300
+ {
301
+ EditorUtility.ClearProgressBar();
302
+ EditorUtility.DisplayDialog(
303
+ "Blur Operation Complete",
304
+ $"Successfully blurred {processedCount} images.",
305
+ "OK"
306
+ );
307
+ }
308
+ }
309
+ }
310
+
311
+ private static Texture2D CreateBlurredTexture(Texture2D original, int radius)
312
+ {
313
+ Texture2D blurred = new(original.width, original.height, original.format, false);
314
+ Color[] pixels = original.GetPixels();
315
+ Color[] blurredPixels = new Color[pixels.Length];
316
+ int width = original.width;
317
+ int height = original.height;
318
+
319
+ // A temporary buffer for the first pass
320
+ Color[] tempPixels = new Color[pixels.Length];
321
+
322
+ // Generate the kernel for the weighted average
323
+ float[] kernel = GenerateGaussianKernel(radius);
324
+
325
+ // --- Horizontal Pass ---
326
+ Parallel.For(
327
+ 0,
328
+ height,
329
+ y =>
330
+ {
331
+ int yOffset = y * width;
332
+ for (int x = 0; x < width; x++)
333
+ {
334
+ Color weightedSum = Color.clear;
335
+ float weightTotal = 0f;
336
+
337
+ for (int k = -radius; k <= radius; k++)
338
+ {
339
+ int currentX = x + k;
340
+ if (currentX >= 0 && currentX < width)
341
+ {
342
+ float weight = kernel[k + radius];
343
+ weightedSum += pixels[yOffset + currentX] * weight;
344
+ weightTotal += weight;
345
+ }
346
+ }
347
+ tempPixels[yOffset + x] = weightedSum / weightTotal;
348
+ }
349
+ }
350
+ );
351
+
352
+ // --- Vertical Pass ---
353
+ Parallel.For(
354
+ 0,
355
+ width,
356
+ x =>
357
+ {
358
+ for (int y = 0; y < height; y++)
359
+ {
360
+ Color weightedSum = Color.clear;
361
+ float weightTotal = 0f;
362
+
363
+ for (int k = -radius; k <= radius; k++)
364
+ {
365
+ int currentY = y + k;
366
+ if (currentY >= 0 && currentY < height)
367
+ {
368
+ float weight = kernel[k + radius];
369
+ weightedSum += tempPixels[currentY * width + x] * weight;
370
+ weightTotal += weight;
371
+ }
372
+ }
373
+ blurredPixels[y * width + x] = weightedSum / weightTotal;
374
+ }
375
+ }
376
+ );
377
+
378
+ blurred.SetPixels(blurredPixels);
379
+ blurred.Apply();
380
+ return blurred;
381
+ }
382
+
383
+ private static float[] GenerateGaussianKernel(int radius)
384
+ {
385
+ int size = radius * 2 + 1;
386
+ float[] kernel = new float[size];
387
+ float sigma = radius / 3.0f; // A good rule of thumb for sigma
388
+ float twoSigmaSquare = 2.0f * sigma * sigma;
389
+ float sum = 0f;
390
+
391
+ for (int i = 0; i < size; i++)
392
+ {
393
+ int distance = i - radius;
394
+ kernel[i] =
395
+ Mathf.Exp(-(distance * distance) / twoSigmaSquare)
396
+ / (Mathf.Sqrt(Mathf.PI * twoSigmaSquare));
397
+ sum += kernel[i];
398
+ }
399
+
400
+ // Normalize the kernel so that the weights sum to 1
401
+ for (int i = 0; i < size; i++)
402
+ {
403
+ kernel[i] /= sum;
404
+ }
405
+
406
+ return kernel;
407
+ }
408
+ }
409
+ }
@@ -0,0 +1,3 @@
1
+ fileFormatVersion: 2
2
+ guid: 00ed1d951b9b4c9799f81af2fac5ed53
3
+ timeCreated: 1752523817
@@ -0,0 +1,3 @@
1
+ fileFormatVersion: 2
2
+ guid: 8f5ae51fef654b7c89c699d01e7a918e
3
+ timeCreated: 1752523806
@@ -0,0 +1,131 @@
1
+ namespace WallstopStudios.UnityHelpers.Editor.Visuals
2
+ {
3
+ #if UNITY_EDITOR
4
+ using System;
5
+ using UnityEditor;
6
+ using UnityEngine;
7
+ using UnityEngine.UI;
8
+ using WallstopStudios.UnityHelpers.Core.Helper;
9
+ using WallstopStudios.UnityHelpers.Visuals.UGUI;
10
+ using Object = UnityEngine.Object;
11
+
12
+ /*
13
+ This is needed because Unity already has a custom Inspector for Images which will not display our nice, cool new
14
+ fields.
15
+ */
16
+ [CustomEditor(typeof(EnhancedImage))]
17
+ [CanEditMultipleObjects]
18
+ public sealed class ImageExtendedEditor : UnityEditor.UI.ImageEditor
19
+ {
20
+ private SerializedProperty _hdrColorProperty;
21
+ private SerializedProperty _shapeMaskProperty;
22
+
23
+ private GUIStyle _impactButtonStyle;
24
+
25
+ protected override void OnEnable()
26
+ {
27
+ base.OnEnable();
28
+ _hdrColorProperty = serializedObject.FindProperty(nameof(EnhancedImage._hdrColor));
29
+ _shapeMaskProperty = serializedObject.FindProperty(nameof(EnhancedImage._shapeMask));
30
+ }
31
+
32
+ public override void OnInspectorGUI()
33
+ {
34
+ _impactButtonStyle ??= new GUIStyle(GUI.skin.button)
35
+ {
36
+ normal = { textColor = Color.yellow },
37
+ fontStyle = FontStyle.Bold,
38
+ };
39
+
40
+ serializedObject.Update();
41
+
42
+ EditorGUILayout.Space();
43
+ EditorGUILayout.LabelField("Extended Properties", EditorStyles.boldLabel);
44
+
45
+ EnhancedImage instance = target as EnhancedImage;
46
+ if (
47
+ instance != null
48
+ && (
49
+ instance.material == null
50
+ || string.Equals(
51
+ instance.material.name,
52
+ "Default UI Material",
53
+ StringComparison.Ordinal
54
+ )
55
+ )
56
+ && GUILayout.Button("Incorrect Material Detected - Try Fix?", _impactButtonStyle)
57
+ )
58
+ {
59
+ string currentPath = DirectoryHelper.GetCallerScriptDirectory();
60
+ string packagePath = DirectoryHelper.FindPackageRootPath(currentPath);
61
+ string materialPathFullPath =
62
+ $"{packagePath}/Shaders/Materials/BackgroundMask-Material.mat".SanitizePath();
63
+ string materialRelativePath = DirectoryHelper.AbsoluteToUnityRelativePath(
64
+ materialPathFullPath
65
+ );
66
+ Material defaultMask = AssetDatabase.LoadAssetAtPath<Material>(
67
+ materialRelativePath
68
+ );
69
+ if (defaultMask != null)
70
+ {
71
+ instance.material = defaultMask;
72
+ }
73
+ else
74
+ {
75
+ Debug.LogError($"Failed to load material at path '{materialRelativePath}'.");
76
+ }
77
+ }
78
+
79
+ EditorGUILayout.PropertyField(_hdrColorProperty, new GUIContent("HDR Color"));
80
+ EditorGUILayout.PropertyField(
81
+ _shapeMaskProperty,
82
+ new GUIContent(
83
+ "Shape Mask",
84
+ "Material shader must have an exposed _ShapeMask texture2D property to function. Otherwise, this does nothing."
85
+ )
86
+ );
87
+
88
+ EditorGUILayout.Space();
89
+
90
+ serializedObject.ApplyModifiedProperties();
91
+
92
+ serializedObject.Update();
93
+ DrawPropertiesExcluding(serializedObject, nameof(Image.material));
94
+ serializedObject.ApplyModifiedProperties();
95
+ }
96
+ }
97
+
98
+ // Set the icon for ImageExtended to match the icon of the Image component
99
+ [InitializeOnLoad]
100
+ public static class ImageExtendedIcon
101
+ {
102
+ private static EnhancedImage IconReference;
103
+
104
+ static ImageExtendedIcon()
105
+ {
106
+ IconReference = new GameObject("Icon Object")
107
+ {
108
+ hideFlags = HideFlags.HideAndDontSave,
109
+ }.AddComponent<EnhancedImage>();
110
+
111
+ EditorGUIUtility.SetIconForObject(
112
+ MonoScript.FromMonoBehaviour(IconReference),
113
+ EditorGUIUtility.IconContent("Image Icon").image as Texture2D
114
+ );
115
+
116
+ AssemblyReloadEvents.beforeAssemblyReload += OnBeforeAssemblyReload;
117
+ }
118
+
119
+ private static void OnBeforeAssemblyReload()
120
+ {
121
+ if (IconReference == null)
122
+ {
123
+ return;
124
+ }
125
+
126
+ Object.DestroyImmediate(IconReference.gameObject);
127
+ IconReference = null;
128
+ }
129
+ }
130
+ #endif
131
+ }
@@ -0,0 +1,11 @@
1
+ fileFormatVersion: 2
2
+ guid: 1dedfa29be876f448b0bc865eff4a20e
3
+ MonoImporter:
4
+ externalObjects: {}
5
+ serializedVersion: 2
6
+ defaultReferences: []
7
+ executionOrder: 0
8
+ icon: {instanceID: 0}
9
+ userData:
10
+ assetBundleName:
11
+ assetBundleVariant:
@@ -0,0 +1,3 @@
1
+ fileFormatVersion: 2
2
+ guid: 7755ce29cad84a99bc77c38557294e11
3
+ timeCreated: 1752518910