com.wallstop-studios.unity-helpers 2.0.0-rc76.5 → 2.0.0-rc76.7

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.
@@ -0,0 +1,133 @@
1
+ namespace WallstopStudios.UnityHelpers.Editor.AssetProcessors
2
+ {
3
+ #if UNITY_EDITOR
4
+ using System;
5
+ using System.Collections.Generic;
6
+ using System.Linq;
7
+ using Core.Extension;
8
+ using Core.Helper;
9
+ using UnityEditor;
10
+ using UnityEngine;
11
+ using Object = UnityEngine.Object;
12
+
13
+ public sealed class SpriteLabelProcessor : AssetPostprocessor
14
+ {
15
+ private static readonly Dictionary<string, string[]> CachedLabels = new(
16
+ StringComparer.OrdinalIgnoreCase
17
+ );
18
+
19
+ private static void OnPostprocessAllAssets(
20
+ string[] importedAssets,
21
+ string[] deletedAssets,
22
+ string[] movedAssets,
23
+ string[] movedFromAssetPaths
24
+ )
25
+ {
26
+ bool anyChanged = !CachedLabels.Any();
27
+ InitializeCacheIfNeeded();
28
+
29
+ foreach (string path in importedAssets)
30
+ {
31
+ if (
32
+ !path.EndsWith(".png", StringComparison.OrdinalIgnoreCase)
33
+ && !path.EndsWith(".jpg", StringComparison.OrdinalIgnoreCase)
34
+ && !path.EndsWith(".jpeg", StringComparison.OrdinalIgnoreCase)
35
+ )
36
+ {
37
+ continue;
38
+ }
39
+
40
+ TextureImporter ti = AssetImporter.GetAtPath(path) as TextureImporter;
41
+ if (ti == null || ti.textureType != TextureImporterType.Sprite)
42
+ {
43
+ continue;
44
+ }
45
+
46
+ Object mainObj = AssetDatabase.LoadMainAssetAtPath(path);
47
+ if (mainObj == null)
48
+ {
49
+ continue;
50
+ }
51
+
52
+ string[] newLabels = AssetDatabase.GetLabels(mainObj);
53
+ if (
54
+ !CachedLabels.TryGetValue(path, out string[] oldLabels)
55
+ || !AreEqual(oldLabels, newLabels)
56
+ )
57
+ {
58
+ Debug.Log(
59
+ $"[SpriteLabelProcessor] Labels changed on '{path}': {FormatLabels(oldLabels)} → {FormatLabels(newLabels)}"
60
+ );
61
+
62
+ string[] updated = new string[newLabels.Length];
63
+ Array.Copy(newLabels, updated, newLabels.Length);
64
+ anyChanged = true;
65
+ CachedLabels[path] = updated;
66
+ }
67
+ }
68
+
69
+ if (anyChanged)
70
+ {
71
+ Helpers.AllSpriteLabels = CachedLabels
72
+ .Values.SelectMany(x => x)
73
+ .Distinct()
74
+ .Ordered()
75
+ .ToArray();
76
+ }
77
+ }
78
+
79
+ private static void InitializeCacheIfNeeded()
80
+ {
81
+ if (CachedLabels.Count > 0)
82
+ {
83
+ return;
84
+ }
85
+
86
+ string[] guids = AssetDatabase.FindAssets("t:Sprite");
87
+ foreach (string guid in guids)
88
+ {
89
+ string path = AssetDatabase.GUIDToAssetPath(guid);
90
+ Object asset = AssetDatabase.LoadMainAssetAtPath(path);
91
+ if (asset == null)
92
+ {
93
+ continue;
94
+ }
95
+
96
+ CachedLabels[path] = AssetDatabase.GetLabels(asset);
97
+ }
98
+ }
99
+
100
+ private static bool AreEqual(string[] a, string[] b)
101
+ {
102
+ if (a == null && b == null)
103
+ {
104
+ return true;
105
+ }
106
+
107
+ if (a == null || b == null)
108
+ {
109
+ return false;
110
+ }
111
+
112
+ if (a.Length != b.Length)
113
+ {
114
+ return false;
115
+ }
116
+
117
+ HashSet<string> setA = new(a, StringComparer.OrdinalIgnoreCase);
118
+ HashSet<string> setB = new(b, StringComparer.OrdinalIgnoreCase);
119
+ return setA.SetEquals(setB);
120
+ }
121
+
122
+ private static string FormatLabels(string[] arr)
123
+ {
124
+ if (arr == null || arr.Length == 0)
125
+ {
126
+ return "(none)";
127
+ }
128
+
129
+ return string.Join(", ", arr);
130
+ }
131
+ }
132
+ #endif
133
+ }
@@ -0,0 +1,3 @@
1
+ fileFormatVersion: 2
2
+ guid: 36d579d02fe04cb3baab70ec40645cc9
3
+ timeCreated: 1748731957
@@ -0,0 +1,3 @@
1
+ fileFormatVersion: 2
2
+ guid: 46f7f82eccd54399947ba07a548e8a87
3
+ timeCreated: 1748731948
@@ -4,12 +4,25 @@
4
4
  using System;
5
5
  using UnityEditor;
6
6
  using UnityEngine;
7
- using WallstopStudios.UnityHelpers.Core.Helper;
7
+ using Core.Helper;
8
8
 
9
9
  [CustomPropertyDrawer(typeof(StringInList))]
10
- public class StringInListDrawer : PropertyDrawer
10
+ public sealed class StringInListDrawer : PropertyDrawer
11
11
  {
12
- // Draw the property inside the given rect
12
+ public override float GetPropertyHeight(SerializedProperty property, GUIContent label)
13
+ {
14
+ if (property.isArray && property.propertyType == SerializedPropertyType.Generic)
15
+ {
16
+ int arraySize = property.arraySize;
17
+
18
+ float singleLine = EditorGUIUtility.singleLineHeight;
19
+ float spacing = EditorGUIUtility.standardVerticalSpacing;
20
+ return singleLine + arraySize * (singleLine + spacing);
21
+ }
22
+
23
+ return base.GetPropertyHeight(property, label);
24
+ }
25
+
13
26
  public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
14
27
  {
15
28
  if (attribute is not StringInList stringInList)
@@ -19,37 +32,103 @@
19
32
 
20
33
  string[] list = stringInList.List;
21
34
 
22
- switch (property.propertyType)
35
+ if (property.propertyType == SerializedPropertyType.String)
23
36
  {
24
- case SerializedPropertyType.String:
37
+ int index = Mathf.Max(0, Array.IndexOf(list, property.stringValue));
38
+ index = EditorGUI.Popup(position, property.displayName, index, list);
39
+ if (index < 0 || list.Length <= index)
25
40
  {
26
- int index = Mathf.Max(0, Array.IndexOf(list, property.stringValue));
27
- index = EditorGUI.Popup(position, property.displayName, index, list);
28
- if (index < 0 || list.Length <= index)
29
- {
30
- base.OnGUI(position, property, label);
31
- return;
32
- }
33
-
34
- property.stringValue = list[index];
35
- break;
41
+ base.OnGUI(position, property, label);
42
+ return;
36
43
  }
37
- case SerializedPropertyType.Integer:
44
+
45
+ property.stringValue = list[index];
46
+ }
47
+ else if (property.propertyType == SerializedPropertyType.Integer)
48
+ {
49
+ property.intValue = EditorGUI.Popup(
50
+ position,
51
+ property.displayName,
52
+ property.intValue,
53
+ list
54
+ );
55
+ }
56
+ else if (property.isArray && property.propertyType == SerializedPropertyType.Generic)
57
+ {
58
+ EditorGUI.BeginProperty(position, label, property);
59
+
60
+ int originalIndent = EditorGUI.indentLevel;
61
+ EditorGUI.indentLevel++;
62
+ try
38
63
  {
39
- property.intValue = EditorGUI.Popup(
40
- position,
41
- property.displayName,
42
- property.intValue,
43
- list
64
+ Rect sizeRect = new(
65
+ position.x,
66
+ position.y,
67
+ position.width,
68
+ EditorGUIUtility.singleLineHeight
44
69
  );
45
- break;
70
+ int newSize = EditorGUI.IntField(
71
+ sizeRect,
72
+ property.displayName + " Size",
73
+ property.arraySize
74
+ );
75
+ if (newSize < 0)
76
+ {
77
+ newSize = 0;
78
+ }
79
+
80
+ if (newSize != property.arraySize)
81
+ {
82
+ property.arraySize = newSize;
83
+ }
84
+
85
+ for (int i = 0; i < property.arraySize; i++)
86
+ {
87
+ SerializedProperty elemProp = property.GetArrayElementAtIndex(i);
88
+ Rect elementRect = new(
89
+ position.x,
90
+ position.y
91
+ + (
92
+ EditorGUIUtility.singleLineHeight
93
+ + EditorGUIUtility.standardVerticalSpacing
94
+ ) * (i + 1),
95
+ position.width,
96
+ EditorGUIUtility.singleLineHeight
97
+ );
98
+
99
+ if (elemProp.propertyType == SerializedPropertyType.String)
100
+ {
101
+ int currentIndex = Mathf.Max(
102
+ 0,
103
+ Array.IndexOf(list, elemProp.stringValue)
104
+ );
105
+ currentIndex = EditorGUI.Popup(
106
+ elementRect,
107
+ $"Element {i}",
108
+ currentIndex,
109
+ list
110
+ );
111
+ if (currentIndex >= 0 && currentIndex < list.Length)
112
+ {
113
+ elemProp.stringValue = list[currentIndex];
114
+ }
115
+ }
116
+ else
117
+ {
118
+ EditorGUI.PropertyField(elementRect, elemProp);
119
+ }
120
+ }
46
121
  }
47
- default:
122
+ finally
48
123
  {
49
- base.OnGUI(position, property, label);
50
- break;
124
+ EditorGUI.indentLevel = originalIndent;
125
+ EditorGUI.EndProperty();
51
126
  }
52
127
  }
128
+ else
129
+ {
130
+ base.OnGUI(position, property, label);
131
+ }
53
132
  }
54
133
  }
55
134
  #endif
@@ -1,12 +1,12 @@
1
1
  namespace WallstopStudios.UnityHelpers.Editor.CustomDrawers
2
2
  {
3
3
  #if UNITY_EDITOR
4
+ using System;
4
5
  using System.Reflection;
5
6
  using Extensions;
6
7
  using UnityEditor;
7
8
  using UnityEngine;
8
- using WallstopStudios.UnityHelpers.Core.Attributes;
9
- using WallstopStudios.UnityHelpers.Core.Extension;
9
+ using Core.Attributes;
10
10
 
11
11
  [CustomPropertyDrawer(typeof(WShowIfAttribute))]
12
12
  public sealed class WShowIfPropertyDrawer : PropertyDrawer
@@ -49,11 +49,19 @@
49
49
  showIf.conditionField,
50
50
  BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic
51
51
  );
52
- if (conditionField?.GetValue(enclosingObject) is bool maybeCondition)
52
+ object fieldValue = conditionField?.GetValue(enclosingObject);
53
+ if (fieldValue is bool maybeCondition)
53
54
  {
54
55
  return showIf.inverse ? !maybeCondition : maybeCondition;
55
56
  }
56
- return true;
57
+
58
+ int index = Array.IndexOf(showIf.expectedValues, fieldValue);
59
+ if (showIf.inverse)
60
+ {
61
+ return index < 0;
62
+ }
63
+
64
+ return 0 <= index;
57
65
  }
58
66
 
59
67
  bool condition = conditionProperty.boolValue;
@@ -620,7 +620,10 @@
620
620
  }
621
621
  }
622
622
  }
623
- catch { }
623
+ catch
624
+ {
625
+ // Swalllow
626
+ }
624
627
  }
625
628
  if (!Directory.Exists(initialBrowsePath))
626
629
  {
@@ -38,9 +38,6 @@
38
38
  SerializedProperty folderPathProp = property.FindPropertyRelative(
39
39
  nameof(SourceFolderEntry.folderPath)
40
40
  );
41
- SerializedProperty regexesProp = property.FindPropertyRelative(
42
- nameof(SourceFolderEntry.regexes)
43
- );
44
41
 
45
42
  Rect folderPathLabelRect = new(
46
43
  startX,
@@ -134,68 +131,165 @@
134
131
  );
135
132
 
136
133
  currentY += EditorGUIUtility.standardVerticalSpacing;
137
- Rect regexFoldoutLabelRect = new(
134
+
135
+ SerializedProperty modeProp = property.FindPropertyRelative(
136
+ nameof(SourceFolderEntry.selectionMode)
137
+ );
138
+ SpriteSelectionMode modeValue = (SpriteSelectionMode)modeProp.intValue;
139
+ Rect selectionMode = new(
138
140
  startX,
139
141
  currentY,
140
142
  availableWidth,
141
143
  EditorGUIUtility.singleLineHeight
142
144
  );
145
+ modeValue = (SpriteSelectionMode)
146
+ EditorGUI.EnumFlagsField(
147
+ selectionMode,
148
+ new GUIContent("Selection Mode"),
149
+ modeValue
150
+ );
151
+ modeProp.intValue = (int)modeValue;
152
+ currentY +=
153
+ EditorGUIUtility.singleLineHeight + EditorGUIUtility.standardVerticalSpacing;
143
154
 
144
- string regexesFoldoutKey = GetRegexFoldoutKey(property);
145
- RegexesFoldoutState.TryAdd(regexesFoldoutKey, true);
146
- RegexesFoldoutState[regexesFoldoutKey] = EditorGUI.Foldout(
147
- regexFoldoutLabelRect,
148
- RegexesFoldoutState[regexesFoldoutKey],
149
- "Regexes (AND logic)",
150
- true
151
- );
152
- currentY += regexFoldoutLabelRect.height + EditorGUIUtility.standardVerticalSpacing;
155
+ bool useRegex = (modeValue & SpriteSelectionMode.Regex) != 0;
156
+ bool useLabels = (modeValue & SpriteSelectionMode.Labels) != 0;
153
157
 
154
- if (RegexesFoldoutState[regexesFoldoutKey])
158
+ if (useRegex)
155
159
  {
156
- int listElementIndentLvl = EditorGUI.indentLevel;
157
- EditorGUI.indentLevel++;
158
- float regexStartX = startX + 15f;
159
- float regexAvailableWidth = availableWidth - 15f;
160
- Rect sizeFieldRect = new(
161
- regexStartX,
160
+ Rect regexFoldoutLabelRect = new(
161
+ startX,
162
162
  currentY,
163
- regexAvailableWidth,
163
+ availableWidth,
164
164
  EditorGUIUtility.singleLineHeight
165
165
  );
166
166
 
167
- EditorGUI.BeginChangeCheck();
168
- int newSize = EditorGUI.IntField(sizeFieldRect, "Size", regexesProp.arraySize);
169
- if (EditorGUI.EndChangeCheck())
170
- {
171
- newSize = Mathf.Max(0, newSize);
172
- regexesProp.arraySize = newSize;
173
- }
174
- currentY += sizeFieldRect.height + EditorGUIUtility.standardVerticalSpacing;
167
+ string regexesFoldoutKey = GetRegexFoldoutKey(property);
168
+ RegexesFoldoutState.TryAdd(regexesFoldoutKey, true);
169
+ RegexesFoldoutState[regexesFoldoutKey] = EditorGUI.Foldout(
170
+ regexFoldoutLabelRect,
171
+ RegexesFoldoutState[regexesFoldoutKey],
172
+ "Regexes (AND logic)",
173
+ true
174
+ );
175
+ currentY +=
176
+ regexFoldoutLabelRect.height + EditorGUIUtility.standardVerticalSpacing;
175
177
 
176
- for (int i = 0; i < regexesProp.arraySize; i++)
178
+ if (RegexesFoldoutState[regexesFoldoutKey])
177
179
  {
178
- SerializedProperty elementProp = regexesProp.GetArrayElementAtIndex(i);
179
- Rect elementRect = new(
180
+ SerializedProperty regexesProp = property.FindPropertyRelative(
181
+ nameof(SourceFolderEntry.regexes)
182
+ );
183
+ float regexStartX = startX + 15f;
184
+ float regexWidth = availableWidth - 15f;
185
+
186
+ for (int i = 0; i < regexesProp.arraySize; i++)
187
+ {
188
+ SerializedProperty elemProp = regexesProp.GetArrayElementAtIndex(i);
189
+ Rect fieldRect = new(
190
+ regexStartX,
191
+ currentY,
192
+ regexWidth - 25f,
193
+ EditorGUIUtility.singleLineHeight
194
+ );
195
+ EditorGUI.BeginChangeCheck();
196
+ string newVal = EditorGUI.TextField(
197
+ fieldRect,
198
+ $"Regex {i}:",
199
+ elemProp.stringValue
200
+ );
201
+ if (EditorGUI.EndChangeCheck())
202
+ {
203
+ elemProp.stringValue = newVal;
204
+ }
205
+
206
+ Rect remRect = new(
207
+ fieldRect.xMax + 4f,
208
+ currentY,
209
+ 25f,
210
+ EditorGUIUtility.singleLineHeight
211
+ );
212
+ if (GUI.Button(remRect, "–"))
213
+ {
214
+ regexesProp.DeleteArrayElementAtIndex(i);
215
+ property.serializedObject.ApplyModifiedProperties();
216
+ }
217
+
218
+ currentY +=
219
+ EditorGUIUtility.singleLineHeight
220
+ + EditorGUIUtility.standardVerticalSpacing;
221
+ }
222
+
223
+ Rect addRect = new(
180
224
  regexStartX,
181
225
  currentY,
182
- regexAvailableWidth,
226
+ regexWidth,
183
227
  EditorGUIUtility.singleLineHeight
184
228
  );
185
- EditorGUI.BeginChangeCheck();
186
- string newStringValue = EditorGUI.TextField(
187
- elementRect,
188
- $"Element {i}",
189
- elementProp.stringValue
190
- );
191
- if (EditorGUI.EndChangeCheck())
229
+
230
+ if (GUI.Button(addRect, "+ Add Regex"))
192
231
  {
193
- elementProp.stringValue = newStringValue;
232
+ int idx = regexesProp.arraySize;
233
+ regexesProp.InsertArrayElementAtIndex(idx);
234
+ regexesProp.GetArrayElementAtIndex(idx).stringValue = string.Empty;
235
+ property.serializedObject.ApplyModifiedProperties();
194
236
  }
195
- currentY += elementRect.height + EditorGUIUtility.standardVerticalSpacing;
196
237
  }
197
- EditorGUI.indentLevel = listElementIndentLvl;
198
238
  }
239
+
240
+ if (useRegex && useLabels)
241
+ {
242
+ SerializedProperty booleanProp = property.FindPropertyRelative(
243
+ nameof(SourceFolderEntry.regexAndTagLogic)
244
+ );
245
+ currentY +=
246
+ EditorGUIUtility.singleLineHeight
247
+ + EditorGUIUtility.standardVerticalSpacing;
248
+ EditorGUI.PropertyField(
249
+ new Rect(
250
+ startX,
251
+ currentY,
252
+ availableWidth,
253
+ EditorGUIUtility.singleLineHeight
254
+ ),
255
+ booleanProp,
256
+ new GUIContent("Regex & Tags Logic")
257
+ );
258
+ currentY +=
259
+ EditorGUIUtility.singleLineHeight
260
+ + EditorGUIUtility.standardVerticalSpacing;
261
+ }
262
+
263
+ if (useLabels)
264
+ {
265
+ SerializedProperty labelModeProp = property.FindPropertyRelative(
266
+ "labelSelectionMode"
267
+ );
268
+ Rect rectLabelMode = new(
269
+ startX,
270
+ currentY,
271
+ availableWidth,
272
+ EditorGUIUtility.singleLineHeight
273
+ );
274
+ EditorGUI.PropertyField(
275
+ rectLabelMode,
276
+ labelModeProp,
277
+ new GUIContent("Label Selection Mode")
278
+ );
279
+ currentY +=
280
+ EditorGUIUtility.singleLineHeight
281
+ + EditorGUIUtility.standardVerticalSpacing;
282
+
283
+ SerializedProperty labelsProp = property.FindPropertyRelative(
284
+ nameof(SourceFolderEntry.labels)
285
+ );
286
+ float labelsHeight = EditorGUI.GetPropertyHeight(labelsProp, true);
287
+
288
+ Rect rectLabels = new(startX, currentY, availableWidth, labelsHeight);
289
+ EditorGUI.PropertyField(rectLabels, labelsProp, new GUIContent("Labels"), true);
290
+ currentY += labelsHeight + EditorGUIUtility.standardVerticalSpacing;
291
+ }
292
+
199
293
  EditorGUI.indentLevel = originalIndent;
200
294
  }
201
295
  EditorGUI.EndProperty();
@@ -225,19 +319,62 @@
225
319
 
226
320
  height += EditorGUIUtility.standardVerticalSpacing;
227
321
  height += EditorGUIUtility.singleLineHeight + EditorGUIUtility.standardVerticalSpacing;
228
- string regexesFoldoutKey = GetRegexFoldoutKey(property);
229
- bool isRegexesExpanded = RegexesFoldoutState.GetValueOrDefault(regexesFoldoutKey, true);
230
- if (isRegexesExpanded)
322
+
323
+ SerializedProperty modeProp = property.FindPropertyRelative(
324
+ nameof(SourceFolderEntry.selectionMode)
325
+ );
326
+ SpriteSelectionMode modeValue = (SpriteSelectionMode)modeProp.intValue;
327
+ bool useRegex = (modeValue & SpriteSelectionMode.Regex) != 0;
328
+ bool useLabels = (modeValue & SpriteSelectionMode.Labels) != 0;
329
+
330
+ if (useRegex)
231
331
  {
232
- SerializedProperty regexesProp = property.FindPropertyRelative(
233
- nameof(SourceFolderEntry.regexes)
332
+ string regexesFoldoutKey = GetRegexFoldoutKey(property);
333
+ bool isRegexesExpanded = RegexesFoldoutState.GetValueOrDefault(
334
+ regexesFoldoutKey,
335
+ true
234
336
  );
235
- height +=
236
- (1 + regexesProp.arraySize)
237
- * (
238
- EditorGUIUtility.singleLineHeight + EditorGUIUtility.standardVerticalSpacing
337
+ if (isRegexesExpanded)
338
+ {
339
+ SerializedProperty regexesProp = property.FindPropertyRelative(
340
+ nameof(SourceFolderEntry.regexes)
239
341
  );
342
+ height +=
343
+ (1 + regexesProp.arraySize)
344
+ * (
345
+ EditorGUIUtility.singleLineHeight
346
+ + EditorGUIUtility.standardVerticalSpacing
347
+ );
348
+ }
349
+
350
+ height +=
351
+ EditorGUIUtility.singleLineHeight + EditorGUIUtility.standardVerticalSpacing;
352
+ }
353
+
354
+ if (useRegex && useLabels)
355
+ {
356
+ height +=
357
+ EditorGUIUtility.singleLineHeight + EditorGUIUtility.standardVerticalSpacing;
240
358
  }
359
+
360
+ if (useLabels)
361
+ {
362
+ // 1) Draw the “Label Selection Mode” line (dropdown)
363
+ height += EditorGUIUtility.singleLineHeight;
364
+ height += EditorGUIUtility.standardVerticalSpacing;
365
+
366
+ // 2) Now figure out how tall “labels” really is. Let Unity handle foldout‐vs‐expanded.
367
+ SerializedProperty labelsProp = property.FindPropertyRelative(
368
+ nameof(SourceFolderEntry.labels)
369
+ );
370
+
371
+ // Passing `true` tells Unity: “Include children if expanded, or just header if collapsed.”
372
+ float labelsFullHeight = EditorGUI.GetPropertyHeight(labelsProp, true);
373
+
374
+ height += labelsFullHeight;
375
+ height += EditorGUIUtility.standardVerticalSpacing;
376
+ }
377
+
241
378
  height += EditorGUIUtility.standardVerticalSpacing;
242
379
  return height;
243
380
  }
@@ -27,6 +27,17 @@
27
27
  Type type = obj.GetType();
28
28
  string[] pathParts = property.propertyPath.Split('.');
29
29
 
30
+ if (
31
+ string.Equals(property.name, "data", StringComparison.Ordinal)
32
+ && pathParts.Length > 1
33
+ && pathParts[^1].Contains('[')
34
+ && pathParts[^1].Contains(']')
35
+ && string.Equals(pathParts[^2], "Array", StringComparison.Ordinal)
36
+ )
37
+ {
38
+ Array.Resize(ref pathParts, pathParts.Length - 2);
39
+ }
40
+
30
41
  // Traverse the path but stop at the second-to-last field
31
42
  for (int i = 0; i < pathParts.Length - 1; ++i)
32
43
  {