rotor-framework 0.3.2
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/LICENSE.md +21 -0
- package/README.md +120 -0
- package/package.json +59 -0
- package/src/source/RotorFramework.bs +654 -0
- package/src/source/RotorFrameworkTask.bs +278 -0
- package/src/source/base/BaseModel.bs +52 -0
- package/src/source/base/BasePlugin.bs +48 -0
- package/src/source/base/BaseReducer.bs +184 -0
- package/src/source/base/BaseStack.bs +92 -0
- package/src/source/base/BaseViewModel.bs +124 -0
- package/src/source/base/BaseWidget.bs +104 -0
- package/src/source/base/DispatcherCreator.bs +193 -0
- package/src/source/base/DispatcherExternal.bs +260 -0
- package/src/source/base/ListenerForDispatchers.bs +246 -0
- package/src/source/engine/Constants.bs +74 -0
- package/src/source/engine/animator/Animator.bs +334 -0
- package/src/source/engine/builder/Builder.bs +213 -0
- package/src/source/engine/builder/NodePool.bs +236 -0
- package/src/source/engine/builder/PluginAdapter.bs +139 -0
- package/src/source/engine/builder/PostProcessor.bs +331 -0
- package/src/source/engine/builder/Processor.bs +156 -0
- package/src/source/engine/builder/Tree.bs +278 -0
- package/src/source/engine/builder/TreeBase.bs +313 -0
- package/src/source/engine/builder/WidgetCreate.bs +322 -0
- package/src/source/engine/builder/WidgetRemove.bs +72 -0
- package/src/source/engine/builder/WidgetUpdate.bs +113 -0
- package/src/source/engine/providers/Dispatcher.bs +72 -0
- package/src/source/engine/providers/DispatcherProvider.bs +95 -0
- package/src/source/engine/services/I18n.bs +169 -0
- package/src/source/libs/animate/Animate.bs +753 -0
- package/src/source/libs/animate/LICENSE.txt +21 -0
- package/src/source/plugins/DispatcherProviderPlugin.bs +127 -0
- package/src/source/plugins/FieldsPlugin.bs +180 -0
- package/src/source/plugins/FocusPlugin.bs +1522 -0
- package/src/source/plugins/FontStylePlugin.bs +159 -0
- package/src/source/plugins/ObserverPlugin.bs +548 -0
- package/src/source/utils/ArrayUtils.bs +495 -0
- package/src/source/utils/GeneralUtils.bs +181 -0
- package/src/source/utils/NodeUtils.bs +180 -0
|
@@ -0,0 +1,495 @@
|
|
|
1
|
+
namespace Rotor.Utils
|
|
2
|
+
|
|
3
|
+
'==========================================================================
|
|
4
|
+
' Array and AssociativeArray Utility Functions
|
|
5
|
+
'
|
|
6
|
+
' Comprehensive collection of helper functions for array and associative array operations.
|
|
7
|
+
'
|
|
8
|
+
' Categories:
|
|
9
|
+
' - Conversion: Array to hash, object wrapping
|
|
10
|
+
' - Deep Operations: Deep copy, deep extend, cloning
|
|
11
|
+
' - Path Resolution: Get/clone by key path
|
|
12
|
+
' - Filtering & Search: Filter, find in arrays/AAs
|
|
13
|
+
' - Comparison: Difference checking
|
|
14
|
+
' - Array Helpers: Ensure array, extend arrays, remove duplicates
|
|
15
|
+
' - HID Operations: Ancestor/descendant checks
|
|
16
|
+
' - Index Helpers: Wrapped index calculation
|
|
17
|
+
'
|
|
18
|
+
'==========================================================================
|
|
19
|
+
|
|
20
|
+
'==========================================================================
|
|
21
|
+
' CONVERSION FUNCTIONS
|
|
22
|
+
'==========================================================================
|
|
23
|
+
|
|
24
|
+
' ---------------------------------------------------------------------
|
|
25
|
+
' wrapObject - Wraps a value in an associative array with specified key
|
|
26
|
+
'
|
|
27
|
+
' @param {string} key - Key name
|
|
28
|
+
' @param {dynamic} value - Value to wrap
|
|
29
|
+
' @returns {object} Associative array { key: value }
|
|
30
|
+
'
|
|
31
|
+
function wrapObject(key as string, value as dynamic) as object
|
|
32
|
+
obj = {}
|
|
33
|
+
obj[key] = value
|
|
34
|
+
return obj
|
|
35
|
+
end function
|
|
36
|
+
|
|
37
|
+
'==========================================================================
|
|
38
|
+
' DEEP OPERATIONS
|
|
39
|
+
'==========================================================================
|
|
40
|
+
|
|
41
|
+
' ---------------------------------------------------------------------
|
|
42
|
+
' deepExtendAA - Recursively merges source AA into target AA
|
|
43
|
+
'
|
|
44
|
+
' Merge behavior:
|
|
45
|
+
' - Both AA: Recursive merge
|
|
46
|
+
' - Source overwrites other types
|
|
47
|
+
' - Skips widgets (isWidget = true)
|
|
48
|
+
'
|
|
49
|
+
' @param {dynamic} target - Target associative array to extend
|
|
50
|
+
' @param {dynamic} source - Source associative array providing new values
|
|
51
|
+
' @returns {dynamic} Modified target associative array
|
|
52
|
+
'
|
|
53
|
+
function deepExtendAA(target as dynamic, source as dynamic) as dynamic
|
|
54
|
+
if type(target) <> "roAssociativeArray" or type(source) <> "roAssociativeArray"
|
|
55
|
+
return source
|
|
56
|
+
end if
|
|
57
|
+
|
|
58
|
+
for each key in source
|
|
59
|
+
sourceVal = source[key]
|
|
60
|
+
|
|
61
|
+
if target.doesExist(key)
|
|
62
|
+
targetVal = target[key]
|
|
63
|
+
sourceType = type(sourceVal)
|
|
64
|
+
targetType = type(targetVal)
|
|
65
|
+
|
|
66
|
+
' Recursively merge nested AAs (but not widgets)
|
|
67
|
+
if sourceType = "roAssociativeArray" and targetType = "roAssociativeArray" and not (sourceType = "roAssociativeArray" and sourceVal?.isWidget = true)
|
|
68
|
+
target[key] = deepExtendAA(targetVal, sourceVal)
|
|
69
|
+
else
|
|
70
|
+
' Overwrite with source value
|
|
71
|
+
target[key] = sourceVal
|
|
72
|
+
end if
|
|
73
|
+
else
|
|
74
|
+
' Key doesn't exist in target, assign directly
|
|
75
|
+
target[key] = sourceVal
|
|
76
|
+
end if
|
|
77
|
+
end for
|
|
78
|
+
|
|
79
|
+
return target
|
|
80
|
+
end function
|
|
81
|
+
|
|
82
|
+
' ---------------------------------------------------------------------
|
|
83
|
+
' deepCopy - Creates a deep copy of an object
|
|
84
|
+
'
|
|
85
|
+
' Recursively copies all nested arrays and associative arrays.
|
|
86
|
+
' Primitive values are copied by value.
|
|
87
|
+
'
|
|
88
|
+
' @param {dynamic} source - Source object to copy
|
|
89
|
+
' @returns {dynamic} Deep copy of source
|
|
90
|
+
'
|
|
91
|
+
function deepCopy(source as dynamic) as dynamic
|
|
92
|
+
if source = invalid
|
|
93
|
+
return invalid
|
|
94
|
+
end if
|
|
95
|
+
|
|
96
|
+
sourceType = type(source)
|
|
97
|
+
|
|
98
|
+
if sourceType = "roArray"
|
|
99
|
+
target = []
|
|
100
|
+
for each item in source
|
|
101
|
+
target.push(deepCopy(item))
|
|
102
|
+
end for
|
|
103
|
+
else if sourceType = "roAssociativeArray"
|
|
104
|
+
target = {}
|
|
105
|
+
for each key in source
|
|
106
|
+
target[key] = deepCopy(source[key])
|
|
107
|
+
end for
|
|
108
|
+
else
|
|
109
|
+
' Primitive type - return as-is
|
|
110
|
+
return source
|
|
111
|
+
end if
|
|
112
|
+
|
|
113
|
+
return target
|
|
114
|
+
end function
|
|
115
|
+
|
|
116
|
+
' ---------------------------------------------------------------------
|
|
117
|
+
' cloneExtendAA - Creates a deep copy of source and extends it with newData
|
|
118
|
+
'
|
|
119
|
+
' Combines deepCopy and deepExtendAA operations.
|
|
120
|
+
'
|
|
121
|
+
' @param {object} source - Source AA to clone
|
|
122
|
+
' @param {object} newData - Data to merge into clone
|
|
123
|
+
' @returns {object} Extended clone
|
|
124
|
+
'
|
|
125
|
+
function cloneExtendAA(source as object, newData as object)
|
|
126
|
+
clone = deepCopy(source)
|
|
127
|
+
return deepExtendAA(clone, newData)
|
|
128
|
+
end function
|
|
129
|
+
|
|
130
|
+
'==========================================================================
|
|
131
|
+
' PATH RESOLUTION
|
|
132
|
+
'==========================================================================
|
|
133
|
+
|
|
134
|
+
' ---------------------------------------------------------------------
|
|
135
|
+
' getValueByKeyPath - Gets value from nested object using dot-separated path
|
|
136
|
+
'
|
|
137
|
+
' Traverses nested AAs using path like "parent.child.grandchild".
|
|
138
|
+
'
|
|
139
|
+
' @param {object} source - Source object to traverse
|
|
140
|
+
' @param {string} keyPath - Dot-separated path (e.g., "user.address.city")
|
|
141
|
+
' @param {boolean} lastKeyAsProp - If true, wraps result in { lastKey: value }
|
|
142
|
+
' @param {string} separator - Path separator (default: ".")
|
|
143
|
+
' @returns {dynamic} Value at path, or invalid if not found
|
|
144
|
+
'
|
|
145
|
+
function getValueByKeyPath(source as object, keyPath as string, lastKeyAsProp = false as boolean, separator = "." as string) as object
|
|
146
|
+
keys = keyPath.split(separator)
|
|
147
|
+
keysCount = keys.Count()
|
|
148
|
+
current = source
|
|
149
|
+
index = 0
|
|
150
|
+
|
|
151
|
+
' Traverse path
|
|
152
|
+
while index < keysCount and current <> invalid and current.doesExist(keys[index])
|
|
153
|
+
current = current[keys[index]]
|
|
154
|
+
index++
|
|
155
|
+
end while
|
|
156
|
+
|
|
157
|
+
' Not found
|
|
158
|
+
if index < keysCount then return invalid
|
|
159
|
+
|
|
160
|
+
' Return format
|
|
161
|
+
if lastKeyAsProp and current <> invalid
|
|
162
|
+
obj = wrapObject(keys[keysCount - 1], current)
|
|
163
|
+
return obj
|
|
164
|
+
else
|
|
165
|
+
return current
|
|
166
|
+
end if
|
|
167
|
+
end function
|
|
168
|
+
|
|
169
|
+
' ---------------------------------------------------------------------
|
|
170
|
+
' getCloneByKeyPath - Gets a deep copy of nested path structure
|
|
171
|
+
'
|
|
172
|
+
' Creates a nested AA structure matching the path with deep copied leaf value.
|
|
173
|
+
'
|
|
174
|
+
' @param {object} source - Source object
|
|
175
|
+
' @param {string} keyPath - Dot-separated path
|
|
176
|
+
' @param {string} separator - Path separator (default: ".")
|
|
177
|
+
' @returns {object} Nested structure with cloned value, or invalid if not found
|
|
178
|
+
'
|
|
179
|
+
' Example:
|
|
180
|
+
' getCloneByKeyPath({a: {b: {c: 1}}}, "a.b.c") => {a: {b: {c: 1}}}
|
|
181
|
+
'
|
|
182
|
+
function getCloneByKeyPath(source as object, keyPath as string, separator = "." as string) as object
|
|
183
|
+
keys = keyPath.split(separator)
|
|
184
|
+
keysCount = keys.Count()
|
|
185
|
+
current = source
|
|
186
|
+
index = 0
|
|
187
|
+
fullPath = {}
|
|
188
|
+
path = fullPath
|
|
189
|
+
|
|
190
|
+
while index < keysCount and current <> invalid and current.doesExist(keys[index])
|
|
191
|
+
key = keys[index]
|
|
192
|
+
current = current[key]
|
|
193
|
+
|
|
194
|
+
if index = keysCount - 1
|
|
195
|
+
' Last key - deep copy value
|
|
196
|
+
path[key] = Rotor.Utils.deepCopy(current)
|
|
197
|
+
else
|
|
198
|
+
' Intermediate key - create nested object
|
|
199
|
+
path[key] = {}
|
|
200
|
+
path = path[key]
|
|
201
|
+
end if
|
|
202
|
+
|
|
203
|
+
index++
|
|
204
|
+
end while
|
|
205
|
+
|
|
206
|
+
' Not found
|
|
207
|
+
if index < keysCount then return invalid
|
|
208
|
+
|
|
209
|
+
return fullPath
|
|
210
|
+
end function
|
|
211
|
+
|
|
212
|
+
'==========================================================================
|
|
213
|
+
' FILTERING & SEARCH
|
|
214
|
+
'==========================================================================
|
|
215
|
+
|
|
216
|
+
' ---------------------------------------------------------------------
|
|
217
|
+
' filterArrayUseHandler - Filters array using handler function
|
|
218
|
+
'
|
|
219
|
+
' @param {object} array - Array to filter
|
|
220
|
+
' @param {function} handler - Filter function (item, context) => boolean
|
|
221
|
+
' @param {dynamic} context - Context passed to handler
|
|
222
|
+
' @returns {object} New array with filtered elements
|
|
223
|
+
'
|
|
224
|
+
function filterArrayUseHandler(array as object, handler as function, context) as object
|
|
225
|
+
index = 0
|
|
226
|
+
newArray = []
|
|
227
|
+
while index < array.Count()
|
|
228
|
+
if handler(array[index], context)
|
|
229
|
+
newArray.push(array[index])
|
|
230
|
+
end if
|
|
231
|
+
index++
|
|
232
|
+
end while
|
|
233
|
+
return newArray
|
|
234
|
+
end function
|
|
235
|
+
|
|
236
|
+
' ---------------------------------------------------------------------
|
|
237
|
+
' findInArray - Finds target value in array
|
|
238
|
+
'
|
|
239
|
+
' @param {object} array - Array to search
|
|
240
|
+
' @param {dynamic} target - Value to find
|
|
241
|
+
' @returns {integer} Index of target, or -1 if not found
|
|
242
|
+
'
|
|
243
|
+
function findInArray(array as object, target) as integer
|
|
244
|
+
index = 0
|
|
245
|
+
foundIndex = -1
|
|
246
|
+
while foundIndex = -1 and index < array.Count()
|
|
247
|
+
if array[index] = target
|
|
248
|
+
foundIndex = index
|
|
249
|
+
else
|
|
250
|
+
index++
|
|
251
|
+
end if
|
|
252
|
+
end while
|
|
253
|
+
return foundIndex
|
|
254
|
+
end function
|
|
255
|
+
|
|
256
|
+
' ---------------------------------------------------------------------
|
|
257
|
+
' findInArrayOfAA - Finds AA in array by key value
|
|
258
|
+
'
|
|
259
|
+
' Searches array of AAs for first element where element[key] = target.
|
|
260
|
+
'
|
|
261
|
+
' @param {object} array - Array of associative arrays
|
|
262
|
+
' @param {string} key - Key to check in each AA
|
|
263
|
+
' @param {dynamic} target - Target value to find
|
|
264
|
+
' @returns {integer} Index of matching AA, or -1 if not found
|
|
265
|
+
'
|
|
266
|
+
function findInArrayOfAA(array as object, key as string, target) as integer
|
|
267
|
+
index = 0
|
|
268
|
+
foundIndex = -1
|
|
269
|
+
while foundIndex = -1 and index < array.Count()
|
|
270
|
+
if array[index][key] = target
|
|
271
|
+
foundIndex = index
|
|
272
|
+
else
|
|
273
|
+
index++
|
|
274
|
+
end if
|
|
275
|
+
end while
|
|
276
|
+
return foundIndex
|
|
277
|
+
end function
|
|
278
|
+
|
|
279
|
+
' ---------------------------------------------------------------------
|
|
280
|
+
' checkArrayItemsByHandler - Finds array element matching handler condition
|
|
281
|
+
'
|
|
282
|
+
' Compares array elements using handler function to find best match.
|
|
283
|
+
'
|
|
284
|
+
' @param {object} array - Array to search
|
|
285
|
+
' @param {string} targetKey - Key to compare in each element
|
|
286
|
+
' @param {function} handlerFn - Comparison function (value1, value2) => boolean
|
|
287
|
+
' @returns {dynamic} Element matching condition
|
|
288
|
+
'
|
|
289
|
+
function checkArrayItemsByHandler(array as object, targetKey as string, handlerFn as function) as dynamic
|
|
290
|
+
targetIndex = 0
|
|
291
|
+
length = array.Count()
|
|
292
|
+
|
|
293
|
+
if length = 1
|
|
294
|
+
return array[0]
|
|
295
|
+
end if
|
|
296
|
+
|
|
297
|
+
for index = 1 to length - 1
|
|
298
|
+
if handlerFn(array[index][targetKey], array[targetIndex][targetKey]) = true
|
|
299
|
+
targetIndex = index
|
|
300
|
+
end if
|
|
301
|
+
end for
|
|
302
|
+
|
|
303
|
+
return array[targetIndex]
|
|
304
|
+
end function
|
|
305
|
+
|
|
306
|
+
' ---------------------------------------------------------------------
|
|
307
|
+
' findInAArrayByKey - Finds key in AA where nested value matches target
|
|
308
|
+
'
|
|
309
|
+
' Searches AA where each value is an AA, looking for value[key] = target.
|
|
310
|
+
' Case-insensitive for string comparisons.
|
|
311
|
+
'
|
|
312
|
+
' @param {object} aa - Associative array to search
|
|
313
|
+
' @param {string} key - Nested key to check
|
|
314
|
+
' @param {dynamic} value - Target value
|
|
315
|
+
' @returns {string} Key of matching entry, or empty string if not found
|
|
316
|
+
'
|
|
317
|
+
function findInAArrayByKey(aa as object, key as string, value as dynamic) as string
|
|
318
|
+
keys = aa.Keys()
|
|
319
|
+
keysCount = keys.Count()
|
|
320
|
+
index = 0
|
|
321
|
+
foundIndex = -1
|
|
322
|
+
isTypeString = Rotor.Utils.isString(value)
|
|
323
|
+
if isTypeString then value = LCase(value)
|
|
324
|
+
|
|
325
|
+
while foundIndex = -1 and index < keysCount
|
|
326
|
+
targetValue = aa[keys[index]][key]
|
|
327
|
+
if (isTypeString = true ? LCase(targetValue) : targetValue) = value
|
|
328
|
+
foundIndex = index
|
|
329
|
+
else
|
|
330
|
+
index++
|
|
331
|
+
end if
|
|
332
|
+
end while
|
|
333
|
+
|
|
334
|
+
return foundIndex > -1 ? keys[foundIndex] : ""
|
|
335
|
+
end function
|
|
336
|
+
|
|
337
|
+
' ---------------------------------------------------------------------
|
|
338
|
+
' findInArrayByKey - Finds element in array by nested key path
|
|
339
|
+
'
|
|
340
|
+
' Searches array of AAs using key path (e.g., "user.name").
|
|
341
|
+
' Case-insensitive for string comparisons.
|
|
342
|
+
'
|
|
343
|
+
' @param {object} array - Array to search (elements must be AAs)
|
|
344
|
+
' @param {string} key - Key path to search (e.g., "parent.child")
|
|
345
|
+
' @param {dynamic} value - Target value
|
|
346
|
+
' @returns {integer} Index of matching element, or -1 if not found
|
|
347
|
+
'
|
|
348
|
+
function findInArrayByKey(array as object, key as string, value as dynamic) as integer
|
|
349
|
+
arrayCount = array.Count()
|
|
350
|
+
index = 0
|
|
351
|
+
foundIndex = -1
|
|
352
|
+
if Rotor.Utils.isString(value) then value = LCase(value)
|
|
353
|
+
|
|
354
|
+
while foundIndex = -1 and index < arrayCount
|
|
355
|
+
targetValue = Rotor.Utils.getValueByKeyPath(array[index], key)
|
|
356
|
+
if Rotor.Utils.isString(targetValue) then targetValue = LCase(targetValue)
|
|
357
|
+
|
|
358
|
+
if targetValue = value
|
|
359
|
+
foundIndex = index
|
|
360
|
+
else
|
|
361
|
+
index++
|
|
362
|
+
end if
|
|
363
|
+
end while
|
|
364
|
+
|
|
365
|
+
return foundIndex
|
|
366
|
+
end function
|
|
367
|
+
|
|
368
|
+
'==========================================================================
|
|
369
|
+
' COMPARISON
|
|
370
|
+
'==========================================================================
|
|
371
|
+
|
|
372
|
+
' ---------------------------------------------------------------------
|
|
373
|
+
' isDifferent - Checks if two items are different
|
|
374
|
+
'
|
|
375
|
+
' Comparison logic:
|
|
376
|
+
' - roSGNode: Uses isSameNode()
|
|
377
|
+
' - AA/Array: JSON comparison
|
|
378
|
+
' - Function: String comparison
|
|
379
|
+
' - Other: Direct comparison
|
|
380
|
+
'
|
|
381
|
+
' @param {dynamic} item1 - First item
|
|
382
|
+
' @param {dynamic} item2 - Second item
|
|
383
|
+
' @returns {boolean} True if items are different
|
|
384
|
+
'
|
|
385
|
+
function isDifferent(item1, item2) as boolean
|
|
386
|
+
if type(item1) = "roSGNode"
|
|
387
|
+
return not item1.isSameNode(item2)
|
|
388
|
+
else if Rotor.Utils.isAssociativeArray(item1) or Rotor.Utils.isArray(item1)
|
|
389
|
+
return FormatJSON(item1) <> FormatJSON(item2)
|
|
390
|
+
else if Rotor.Utils.isFunction(item1)
|
|
391
|
+
return item1.ToStr() <> item2.ToStr()
|
|
392
|
+
else
|
|
393
|
+
return item1 <> item2
|
|
394
|
+
end if
|
|
395
|
+
end function
|
|
396
|
+
|
|
397
|
+
'==========================================================================
|
|
398
|
+
' ARRAY HELPERS
|
|
399
|
+
'==========================================================================
|
|
400
|
+
|
|
401
|
+
' ---------------------------------------------------------------------
|
|
402
|
+
' ensureArray - Ensures value is wrapped in array
|
|
403
|
+
'
|
|
404
|
+
' @param {dynamic} array - Input value or array
|
|
405
|
+
' @returns {object} Array containing the value, or original if already array
|
|
406
|
+
'
|
|
407
|
+
function ensureArray(array as dynamic) as object
|
|
408
|
+
if isArray(array)
|
|
409
|
+
resolvedArray = array
|
|
410
|
+
else
|
|
411
|
+
resolvedArray = [array]
|
|
412
|
+
end if
|
|
413
|
+
return resolvedArray
|
|
414
|
+
end function
|
|
415
|
+
|
|
416
|
+
' ---------------------------------------------------------------------
|
|
417
|
+
' extendArrayOfStrings - Merges two string arrays without duplicates
|
|
418
|
+
'
|
|
419
|
+
' Adds strings from sourceArray to targetArray only if not already present.
|
|
420
|
+
'
|
|
421
|
+
' @param {object} targetArray - Target array to extend
|
|
422
|
+
' @param {object} sourceArray - Source array to merge from
|
|
423
|
+
' @returns {object} Extended target array
|
|
424
|
+
'
|
|
425
|
+
function extendArrayOfStrings(targetArray = [] as object, sourceArray = [] as object) as object
|
|
426
|
+
for each itemStr in sourceArray
|
|
427
|
+
foundIndex = Rotor.Utils.findInArray(targetArray, itemStr)
|
|
428
|
+
if foundIndex = -1
|
|
429
|
+
targetArray.push(itemStr)
|
|
430
|
+
end if
|
|
431
|
+
end for
|
|
432
|
+
return targetArray
|
|
433
|
+
end function
|
|
434
|
+
|
|
435
|
+
' ---------------------------------------------------------------------
|
|
436
|
+
' removeRedundantValuesInArray - Removes duplicate values from array in-place
|
|
437
|
+
'
|
|
438
|
+
' Sorts array and removes consecutive duplicates.
|
|
439
|
+
' Modifies the original array.
|
|
440
|
+
'
|
|
441
|
+
' @param {object} array - Array to deduplicate
|
|
442
|
+
'
|
|
443
|
+
sub removeRedundantValuesInArray(array as object)
|
|
444
|
+
itemCount = array.Count()
|
|
445
|
+
if itemCount = 0 then return
|
|
446
|
+
|
|
447
|
+
array.Sort()
|
|
448
|
+
|
|
449
|
+
index = 0
|
|
450
|
+
while index + 1 < itemCount
|
|
451
|
+
if array[index] = array[index + 1]
|
|
452
|
+
array.delete(index + 1)
|
|
453
|
+
itemCount--
|
|
454
|
+
else
|
|
455
|
+
index++
|
|
456
|
+
end if
|
|
457
|
+
end while
|
|
458
|
+
end sub
|
|
459
|
+
|
|
460
|
+
'==========================================================================
|
|
461
|
+
' HID (Hierarchical ID) OPERATIONS
|
|
462
|
+
'==========================================================================
|
|
463
|
+
|
|
464
|
+
' ---------------------------------------------------------------------
|
|
465
|
+
' isAncestorHID - Checks if ancestorHID is an ancestor of HID
|
|
466
|
+
'
|
|
467
|
+
' Uses string prefix matching on HIDs.
|
|
468
|
+
'
|
|
469
|
+
' @param {string} ancestorHID - Potential ancestor HID
|
|
470
|
+
' @param {string} HID - HID to check
|
|
471
|
+
' @returns {boolean} True if ancestorHID is ancestor of HID
|
|
472
|
+
'
|
|
473
|
+
' Example:
|
|
474
|
+
' isAncestorHID("scene.header", "scene.header.logo") => true
|
|
475
|
+
'
|
|
476
|
+
function isAncestorHID(ancestorHID as string, HID as string) as boolean
|
|
477
|
+
ancestorHIDLen = Len(ancestorHID)
|
|
478
|
+
return Left(HID, ancestorHIDLen) = ancestorHID and ancestorHIDLen < Len(HID)
|
|
479
|
+
end function
|
|
480
|
+
|
|
481
|
+
' ---------------------------------------------------------------------
|
|
482
|
+
' isDescendantHID - Checks if descendantHID is a descendant of HID
|
|
483
|
+
'
|
|
484
|
+
' Inverse of isAncestorHID.
|
|
485
|
+
'
|
|
486
|
+
' @param {string} descendantHID - Potential descendant HID
|
|
487
|
+
' @param {string} HID - HID to check against
|
|
488
|
+
' @returns {boolean} True if descendantHID is descendant of HID
|
|
489
|
+
'
|
|
490
|
+
function isDescendantHID(descendantHID as string, HID as string) as boolean
|
|
491
|
+
HIDlen = Len(HID)
|
|
492
|
+
return Left(descendantHID, HIDlen) = HID and HIDlen < Len(descendantHID)
|
|
493
|
+
end function
|
|
494
|
+
|
|
495
|
+
end namespace
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
namespace Rotor.Utils
|
|
2
|
+
|
|
3
|
+
'==========================================================================
|
|
4
|
+
' TYPE CHECKING UTILITIES
|
|
5
|
+
'==========================================================================
|
|
6
|
+
|
|
7
|
+
' ---------------------------------------------------------------------
|
|
8
|
+
' isValid - Checks if a value is valid (not invalid or uninitialized)
|
|
9
|
+
'
|
|
10
|
+
' @param {dynamic} value - Value to check
|
|
11
|
+
' @returns {boolean} True if value is valid
|
|
12
|
+
'
|
|
13
|
+
function isValid(value as dynamic) as boolean
|
|
14
|
+
return Type(value) <> "<uninitialized>" and value <> invalid
|
|
15
|
+
end function
|
|
16
|
+
|
|
17
|
+
' ---------------------------------------------------------------------
|
|
18
|
+
' isAssociativeArray - Checks if value is an associative array
|
|
19
|
+
'
|
|
20
|
+
' @param {dynamic} value - Value to check
|
|
21
|
+
' @returns {boolean} True if value is an associative array
|
|
22
|
+
'
|
|
23
|
+
function isAssociativeArray(value as dynamic) as boolean
|
|
24
|
+
return IsValid(value) and GetInterface(value, "ifAssociativeArray") <> invalid
|
|
25
|
+
end function
|
|
26
|
+
|
|
27
|
+
' ---------------------------------------------------------------------
|
|
28
|
+
' isArray - Checks if value is an array
|
|
29
|
+
'
|
|
30
|
+
' @param {dynamic} arr - Value to check
|
|
31
|
+
' @returns {boolean} True if value is an array
|
|
32
|
+
'
|
|
33
|
+
function isArray(arr as dynamic) as boolean
|
|
34
|
+
return IsValid(arr) and GetInterface(arr, "ifArray") <> invalid
|
|
35
|
+
end function
|
|
36
|
+
|
|
37
|
+
' ---------------------------------------------------------------------
|
|
38
|
+
' isString - Checks if value is a string
|
|
39
|
+
'
|
|
40
|
+
' @param {dynamic} value - Value to check
|
|
41
|
+
' @returns {boolean} True if value is a string
|
|
42
|
+
'
|
|
43
|
+
function isString(value as dynamic) as boolean
|
|
44
|
+
return isValid(value) and GetInterface(value, "ifString") <> invalid
|
|
45
|
+
end function
|
|
46
|
+
|
|
47
|
+
' ---------------------------------------------------------------------
|
|
48
|
+
' isFunction - Checks if value is a function
|
|
49
|
+
'
|
|
50
|
+
' @param {dynamic} value - Value to check
|
|
51
|
+
' @returns {boolean} True if value is a function
|
|
52
|
+
'
|
|
53
|
+
function isFunction(value as dynamic) as boolean
|
|
54
|
+
return isValid(value) and GetInterface(value, "ifFunction") <> invalid
|
|
55
|
+
end function
|
|
56
|
+
|
|
57
|
+
' ---------------------------------------------------------------------
|
|
58
|
+
' isInteger - Checks if value is an integer
|
|
59
|
+
'
|
|
60
|
+
' @param {dynamic} value - Value to check
|
|
61
|
+
' @returns {boolean} True if value is an integer
|
|
62
|
+
'
|
|
63
|
+
function isInteger(value As Dynamic) As Boolean
|
|
64
|
+
Return isValid(value) And GetInterface(value, "ifInt") <> invalid And (Type(value) = "roInt" Or Type(value) = "roInteger" Or Type(value) = "Integer")
|
|
65
|
+
end function
|
|
66
|
+
|
|
67
|
+
' ---------------------------------------------------------------------
|
|
68
|
+
' isBoolean - Checks if value is a boolean
|
|
69
|
+
'
|
|
70
|
+
' @param {dynamic} value - Value to check
|
|
71
|
+
' @returns {boolean} True if value is a boolean
|
|
72
|
+
'
|
|
73
|
+
function isBoolean(value):
|
|
74
|
+
return getInterface(value, "ifBoolean") <> invalid
|
|
75
|
+
end function
|
|
76
|
+
|
|
77
|
+
'==========================================================================
|
|
78
|
+
' MATH UTILITIES
|
|
79
|
+
'==========================================================================
|
|
80
|
+
|
|
81
|
+
' ---------------------------------------------------------------------
|
|
82
|
+
' min - Returns the minimum of two values
|
|
83
|
+
'
|
|
84
|
+
' @param {dynamic} a - First value
|
|
85
|
+
' @param {dynamic} b - Second value
|
|
86
|
+
' @returns {dynamic} Minimum value
|
|
87
|
+
'
|
|
88
|
+
function min(a, b)
|
|
89
|
+
if a > b
|
|
90
|
+
return b
|
|
91
|
+
else
|
|
92
|
+
return a
|
|
93
|
+
end if
|
|
94
|
+
end function
|
|
95
|
+
|
|
96
|
+
' ---------------------------------------------------------------------
|
|
97
|
+
' max - Returns the maximum of two values
|
|
98
|
+
'
|
|
99
|
+
' @param {dynamic} a - First value
|
|
100
|
+
' @param {dynamic} b - Second value
|
|
101
|
+
' @returns {dynamic} Maximum value
|
|
102
|
+
'
|
|
103
|
+
function max(a, b)
|
|
104
|
+
if a < b
|
|
105
|
+
return b
|
|
106
|
+
else
|
|
107
|
+
return a
|
|
108
|
+
end if
|
|
109
|
+
end function
|
|
110
|
+
|
|
111
|
+
' ---------------------------------------------------------------------
|
|
112
|
+
' rotateSegment - Rotates a line segment around a center point
|
|
113
|
+
'
|
|
114
|
+
' @param {float} x1 - X coordinate of first point
|
|
115
|
+
' @param {float} y1 - Y coordinate of first point
|
|
116
|
+
' @param {float} x2 - X coordinate of second point
|
|
117
|
+
' @param {float} y2 - Y coordinate of second point
|
|
118
|
+
' @param {dynamic} rotation - Rotation angle in radians
|
|
119
|
+
' @param {dynamic} center - Center point array [x, y]
|
|
120
|
+
' @returns {object} Object with rotated coordinates {x1, y1, x2, y2}
|
|
121
|
+
'
|
|
122
|
+
function rotateSegment(x1 as float, y1 as float, x2 as float, y2 as float, rotation, center)
|
|
123
|
+
cosVal = Cos(rotation)
|
|
124
|
+
sinVal = Sin(rotation)
|
|
125
|
+
|
|
126
|
+
' Store original values before modification
|
|
127
|
+
origX1 = x1
|
|
128
|
+
origY1 = y1
|
|
129
|
+
origX2 = x2
|
|
130
|
+
origY2 = y2
|
|
131
|
+
|
|
132
|
+
' Rotate first point
|
|
133
|
+
newX1 = (cosVal * (origX1 - center[0])) + (sinVal * (origY1 - center[1])) + center[0]
|
|
134
|
+
newY1 = (cosVal * (origY1 - center[1])) - (sinVal * (origX1 - center[0])) + center[1]
|
|
135
|
+
|
|
136
|
+
' Rotate second point
|
|
137
|
+
newX2 = (cosVal * (origX2 - center[0])) + (sinVal * (origY2 - center[1])) + center[0]
|
|
138
|
+
newY2 = (cosVal * (origY2 - center[1])) - (sinVal * (origX2 - center[0])) + center[1]
|
|
139
|
+
|
|
140
|
+
return {
|
|
141
|
+
x1: newX1,
|
|
142
|
+
y1: newY1,
|
|
143
|
+
x2: newX2,
|
|
144
|
+
y2: newY2
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
end function
|
|
148
|
+
|
|
149
|
+
'==========================================================================
|
|
150
|
+
' UUID GENERATION
|
|
151
|
+
'==========================================================================
|
|
152
|
+
|
|
153
|
+
' ---------------------------------------------------------------------
|
|
154
|
+
' getUUID - Generates a random UUID using roDeviceInfo
|
|
155
|
+
'
|
|
156
|
+
' @returns {string} UUID string
|
|
157
|
+
'
|
|
158
|
+
function getUUID() as string
|
|
159
|
+
if m.deviceInfoNode = invalid
|
|
160
|
+
m.deviceInfoNode = CreateObject("roDeviceInfo")
|
|
161
|
+
end if
|
|
162
|
+
return m.deviceInfoNode.GetRandomUUID()
|
|
163
|
+
end function
|
|
164
|
+
|
|
165
|
+
' ---------------------------------------------------------------------
|
|
166
|
+
' getUUIDHex - Generates a hexadecimal UUID of specified length
|
|
167
|
+
'
|
|
168
|
+
' @param {integer} length - Length of hex UUID (default: 8)
|
|
169
|
+
' @returns {string} Hexadecimal UUID string
|
|
170
|
+
'
|
|
171
|
+
function getUUIDHex(length = 8 as Integer) as string
|
|
172
|
+
stack = ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "A", "B", "C", "D", "E", "F"]
|
|
173
|
+
uuid = ""
|
|
174
|
+
for i = 0 to length - 1
|
|
175
|
+
uuid += stack[rnd(16) - 1]
|
|
176
|
+
end for
|
|
177
|
+
return uuid
|
|
178
|
+
end function
|
|
179
|
+
|
|
180
|
+
end namespace
|
|
181
|
+
|