rotor-framework 0.7.0 β 0.7.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/README.md +1 -1
- package/package.json +1 -1
- package/src/source/RotorFramework.bs +11 -50
- package/src/source/RotorFrameworkTask.bs +2 -2
- package/src/source/engine/animator/Animator.bs +6 -2
- package/src/source/engine/builder/WidgetCreate.bs +1 -6
- package/src/source/engine/services/I18n.bs +3 -2
- package/src/source/plugins/FieldsPlugin.bs +11 -2
- package/src/source/plugins/FocusPlugin.bs +57 -6
package/README.md
CHANGED
|
@@ -96,7 +96,7 @@ You can find [π±](./docs/ai/readme.opt.yaml) symbols in all documentation page
|
|
|
96
96
|
|
|
97
97
|
## π Learn More
|
|
98
98
|
|
|
99
|
-

|
|
100
100
|
|
|
101
101
|
### Framework Core
|
|
102
102
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "rotor-framework",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.2",
|
|
4
4
|
"description": "Roku toolkit library providing a ViewBuilder, full UI lifecycle with focus handling and many core features, plus MVI-based state management.",
|
|
5
5
|
"author": "BalΓ‘zs MolnΓ‘r",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
' βββββββ ββ β ββ βββββββ βββββββββββββββββ βββββββββ ββββ βββββββββββ
|
|
5
5
|
' ββ βββββββ β βββββββ ββ ββ ββ ββββ ββββ βββββββββββββββββββ ββββ ββ
|
|
6
6
|
' Rotor Frameworkβ’
|
|
7
|
-
' Version 0.7.
|
|
7
|
+
' Version 0.7.2
|
|
8
8
|
' Β© 2025 BalΓ‘zs MolnΓ‘r β Apache License 2.0
|
|
9
9
|
' =========================================================================
|
|
10
10
|
|
|
@@ -86,7 +86,7 @@ namespace Rotor
|
|
|
86
86
|
class Framework
|
|
87
87
|
|
|
88
88
|
name = "Rotor Framework"
|
|
89
|
-
version = "0.7.
|
|
89
|
+
version = "0.7.2"
|
|
90
90
|
|
|
91
91
|
config = {
|
|
92
92
|
tasks: invalid, ' @array (optional)
|
|
@@ -118,10 +118,6 @@ namespace Rotor
|
|
|
118
118
|
ttsService as object
|
|
119
119
|
dispatcherProvider as object
|
|
120
120
|
animatorProvider as object
|
|
121
|
-
info = {
|
|
122
|
-
device: {}
|
|
123
|
-
}
|
|
124
|
-
|
|
125
121
|
' plugin adapter workspace
|
|
126
122
|
plugins = {}
|
|
127
123
|
|
|
@@ -132,9 +128,6 @@ namespace Rotor
|
|
|
132
128
|
|
|
133
129
|
enableRendering = true
|
|
134
130
|
|
|
135
|
-
' helper vars
|
|
136
|
-
deviceInfo as object
|
|
137
|
-
|
|
138
131
|
'----------------------------------------------------------------------
|
|
139
132
|
' new - Initializes the Rotor Framework instance
|
|
140
133
|
'
|
|
@@ -161,8 +154,6 @@ namespace Rotor
|
|
|
161
154
|
|
|
162
155
|
m.builder.init(m)
|
|
163
156
|
|
|
164
|
-
m.initInfo()
|
|
165
|
-
|
|
166
157
|
' set root node
|
|
167
158
|
if m.config.rootNode = invalid
|
|
168
159
|
rootNode = globalScope.top
|
|
@@ -325,15 +316,6 @@ namespace Rotor
|
|
|
325
316
|
return m.animatorProvider.getFactory(animatorId, m)
|
|
326
317
|
end function
|
|
327
318
|
|
|
328
|
-
' ---------------------------------------------------------------------
|
|
329
|
-
' getInfo - Gets framework and device information
|
|
330
|
-
'
|
|
331
|
-
' @returns {object} Info object containing device details
|
|
332
|
-
'
|
|
333
|
-
public function getInfo() as object
|
|
334
|
-
return m.info
|
|
335
|
-
end function
|
|
336
|
-
|
|
337
319
|
' ---------------------------------------------------------------------
|
|
338
320
|
' getNodePoolInfo - Gets node pool statistics
|
|
339
321
|
'
|
|
@@ -366,32 +348,6 @@ namespace Rotor
|
|
|
366
348
|
m.builder.pluginAdapter.registerPlugins(plugins)
|
|
367
349
|
end sub
|
|
368
350
|
|
|
369
|
-
' ---------------------------------------------------------------------
|
|
370
|
-
' initInfo - Initializes device and framework information
|
|
371
|
-
'
|
|
372
|
-
' Collects device information (graphics platform, model, OS version, locale)
|
|
373
|
-
' and stores it in m.info for later access.
|
|
374
|
-
'
|
|
375
|
-
sub initInfo()
|
|
376
|
-
' Device info
|
|
377
|
-
di = CreateObject("roDeviceInfo")
|
|
378
|
-
' Make it available on framework instance
|
|
379
|
-
m.deviceInfo = di
|
|
380
|
-
' Generate app info
|
|
381
|
-
m.info.device.append({
|
|
382
|
-
graphicsPlatform: di.GetGraphicsPlatform(), ' ["opengl"|"directfb"]
|
|
383
|
-
modelDisplayName: di.GetModelDisplayName(),
|
|
384
|
-
OSVersion: di.GetOSVersion(),
|
|
385
|
-
currentLocale: di.GetCurrentLocale(), ' Example: "en_US"
|
|
386
|
-
countryCode: di.GetCountryCode() ' A value that indicates the Streaming Store associated with a user's Roku account.
|
|
387
|
-
})
|
|
388
|
-
#if debug
|
|
389
|
-
print `[DEVICEINFO] Graphics Platform: ${m.info.device.graphicsPlatform}`
|
|
390
|
-
print `[DEVICEINFO] modelDisplayName: ${m.info.device.modelDisplayName}`
|
|
391
|
-
print `[DEVICEINFO] OSVersion: ${m.info.device.OSVersion.major}.${m.info.device.OSVersion.minor}.${m.info.device.OSVersion.revision}.${m.info.device.OSVersion.build}`
|
|
392
|
-
#end if
|
|
393
|
-
end sub
|
|
394
|
-
|
|
395
351
|
' ---------------------------------------------------------------------
|
|
396
352
|
' setupTaskForSyncing - Prepares a task node for cross-thread syncing
|
|
397
353
|
'
|
|
@@ -619,9 +575,6 @@ namespace Rotor
|
|
|
619
575
|
}
|
|
620
576
|
m.config.rootNode = invalid
|
|
621
577
|
|
|
622
|
-
m.info.Clear()
|
|
623
|
-
m.deviceInfo = invalid
|
|
624
|
-
|
|
625
578
|
end sub
|
|
626
579
|
|
|
627
580
|
end class
|
|
@@ -646,7 +599,15 @@ namespace Rotor
|
|
|
646
599
|
|
|
647
600
|
taskNode = sync.taskNode
|
|
648
601
|
|
|
649
|
-
if sync.type = Rotor.Const.ThreadSyncType.
|
|
602
|
+
if sync.type = Rotor.Const.ThreadSyncType.DISPATCH
|
|
603
|
+
' Cross-thread dispatch from task thread to render thread
|
|
604
|
+
dispatcherId = sync.payload.dispatcherId
|
|
605
|
+
intent = sync.payload.intent
|
|
606
|
+
dispatcherInstance = framework.dispatcherProvider.stack.LookupCI(dispatcherId)
|
|
607
|
+
if dispatcherInstance <> invalid
|
|
608
|
+
dispatcherInstance.dispatch(intent)
|
|
609
|
+
end if
|
|
610
|
+
else if sync.type = Rotor.Const.ThreadSyncType.TASK_SYNCING
|
|
650
611
|
framework.handleTaskSyncing(sync)
|
|
651
612
|
else if sync.type = Rotor.Const.ThreadSyncType.TASK_SYNCED
|
|
652
613
|
framework.handleTaskSynced(taskNode.taskId)
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
' βββββββ ββ β ββ βββββββ βββββββββββββββββ βββββββββ ββββ βββββββββββ
|
|
5
5
|
' ββ βββββββ β βββββββ ββ ββ ββ ββββ ββββ βββββββββββββββββββ ββββ ββ
|
|
6
6
|
' Rotor Frameworkβ’
|
|
7
|
-
' Version 0.7.
|
|
7
|
+
' Version 0.7.2
|
|
8
8
|
' Β© 2025 BalΓ‘zs MolnΓ‘r β Apache License 2.0
|
|
9
9
|
' =========================================================================
|
|
10
10
|
|
|
@@ -70,7 +70,7 @@ namespace Rotor
|
|
|
70
70
|
class FrameworkTask
|
|
71
71
|
|
|
72
72
|
name = "Rotor Framework"
|
|
73
|
-
version = "0.7.
|
|
73
|
+
version = "0.7.2"
|
|
74
74
|
|
|
75
75
|
config = {
|
|
76
76
|
tasks: invalid, ' optional
|
|
@@ -120,7 +120,9 @@ namespace Rotor
|
|
|
120
120
|
m.scope.animators.delete(m.animatorId)
|
|
121
121
|
end if
|
|
122
122
|
|
|
123
|
-
m.scope.animators =
|
|
123
|
+
if m.scope.animators = invalid
|
|
124
|
+
m.scope.animators = {}
|
|
125
|
+
end if
|
|
124
126
|
m.scope.animators[m.animatorId] = {}
|
|
125
127
|
|
|
126
128
|
parsedParams = Rotor.animatorParamsIntegrationHelper(params)
|
|
@@ -171,7 +173,9 @@ namespace Rotor
|
|
|
171
173
|
m.scope.animators.delete(m.animatorId)
|
|
172
174
|
end if
|
|
173
175
|
|
|
174
|
-
m.scope.animators =
|
|
176
|
+
if m.scope.animators = invalid
|
|
177
|
+
m.scope.animators = {}
|
|
178
|
+
end if
|
|
175
179
|
m.scope.animators[m.animatorId] = {}
|
|
176
180
|
|
|
177
181
|
return {
|
|
@@ -36,11 +36,6 @@ namespace Rotor.ViewBuilder
|
|
|
36
36
|
return GetGlobalAA().rotor_framework_helper.frameworkInstance
|
|
37
37
|
end function
|
|
38
38
|
|
|
39
|
-
' getInfo - Returns framework info *'
|
|
40
|
-
widget.getInfo = function() as object
|
|
41
|
-
return m.getFrameworkInstance().getInfo()
|
|
42
|
-
end function
|
|
43
|
-
|
|
44
39
|
' refresh - Refreshes specific widget properties by key paths *'
|
|
45
40
|
widget.refresh = sub(featureKeyPaths as object)
|
|
46
41
|
iterateKeyPaths = Rotor.Utils.ensureArray(featureKeyPaths)
|
|
@@ -195,7 +190,7 @@ namespace Rotor.ViewBuilder
|
|
|
195
190
|
if template <> invalid and template.Count() > 0
|
|
196
191
|
templateChildren = template.children
|
|
197
192
|
template.delete("children")
|
|
198
|
-
config = Rotor.Utils.deepExtendAA(
|
|
193
|
+
config = Rotor.Utils.deepExtendAA(template, config)
|
|
199
194
|
config.children = templateChildren
|
|
200
195
|
end if
|
|
201
196
|
|
|
@@ -31,8 +31,9 @@ namespace Rotor.ViewBuilder
|
|
|
31
31
|
' @param {Rotor.Framework} frameworkInstance - Framework instance reference
|
|
32
32
|
'
|
|
33
33
|
sub init(frameworkInstance as Rotor.Framework)
|
|
34
|
-
' Set current locale from
|
|
35
|
-
|
|
34
|
+
' Set current locale from device info
|
|
35
|
+
deviceInfo = CreateObject("roDeviceInfo")
|
|
36
|
+
currentLocale = deviceInfo.GetCurrentLocale()
|
|
36
37
|
' Store current locale
|
|
37
38
|
m.setLocale(currentLocale)
|
|
38
39
|
end sub
|
|
@@ -12,6 +12,7 @@ namespace Rotor
|
|
|
12
12
|
' - Interpolates @-prefixed expressions (e.g., @viewModelState.value)
|
|
13
13
|
' - Supports dynamic field resolution from widget context
|
|
14
14
|
' - Automatically updates fields on widget lifecycle changes
|
|
15
|
+
' - Converts \n to Chr(10) for multi-line text support
|
|
15
16
|
'
|
|
16
17
|
' Expression Syntax:
|
|
17
18
|
' @key.path - Resolves from widget.viewModelState
|
|
@@ -27,8 +28,11 @@ namespace Rotor
|
|
|
27
28
|
|
|
28
29
|
' Regex pattern for matching @ and $ prefixed expressions
|
|
29
30
|
' @ = viewModelState, $ = widget properties
|
|
30
|
-
' Matches: @ or $ followed by
|
|
31
|
-
configRegex = /([\@\$])([
|
|
31
|
+
' Matches: @ or $ followed by valid key path characters (letters, numbers, underscore, dot)
|
|
32
|
+
configRegex = /([\@\$])([a-zA-Z0-9_\.]*)/i
|
|
33
|
+
|
|
34
|
+
' Regex pattern for matching \n (literal backslash-n) to convert to Chr(10)
|
|
35
|
+
newlineRegex = /\\n/
|
|
32
36
|
|
|
33
37
|
' =============================================================
|
|
34
38
|
' LIFECYCLE HOOKS
|
|
@@ -160,6 +164,11 @@ namespace Rotor
|
|
|
160
164
|
end for
|
|
161
165
|
end if
|
|
162
166
|
|
|
167
|
+
' Step 3: Convert \n to Chr(10) for multi-line text
|
|
168
|
+
if Rotor.Utils.isString(value)
|
|
169
|
+
value = m.newlineRegex.ReplaceAll(value, Chr(10))
|
|
170
|
+
end if
|
|
171
|
+
|
|
163
172
|
parsedFields[fieldId] = value
|
|
164
173
|
else
|
|
165
174
|
' Step 3: Direct value assignment
|
|
@@ -321,7 +321,58 @@ namespace Rotor
|
|
|
321
321
|
'
|
|
322
322
|
triggerKeyPress: function(key) as object
|
|
323
323
|
return m.getFrameworkInstance().plugins[PRIMARY_FOCUS_PLUGIN_KEY].onKeyEventHandler(key, true)
|
|
324
|
-
end function
|
|
324
|
+
end function,
|
|
325
|
+
|
|
326
|
+
' ---------------------------------------------------------------------
|
|
327
|
+
' setGroupLastFocusedHID - Updates the lastFocusedHID of this widget's focus group
|
|
328
|
+
'
|
|
329
|
+
' If called on a focusGroup widget, updates its own lastFocusedHID.
|
|
330
|
+
' If called on a focusItem widget, finds and updates the parent group's lastFocusedHID.
|
|
331
|
+
'
|
|
332
|
+
' @param {string} id - The widget id to set as lastFocusedHID
|
|
333
|
+
'
|
|
334
|
+
setGroupLastFocusedHID: sub(id as string)
|
|
335
|
+
plugin = m.getFrameworkInstance().plugins[PRIMARY_FOCUS_PLUGIN_KEY]
|
|
336
|
+
|
|
337
|
+
' Determine ancestorHID for search context
|
|
338
|
+
' If this is a focus item, use parent group's HID to find siblings
|
|
339
|
+
ancestorGroups = plugin.findAncestorGroups(m.HID)
|
|
340
|
+
if ancestorGroups.count() > 0
|
|
341
|
+
ancestorHID = ancestorGroups[0]
|
|
342
|
+
else
|
|
343
|
+
ancestorHID = m.HID
|
|
344
|
+
end if
|
|
345
|
+
|
|
346
|
+
' Resolve id to HID - check focusItemStack first, then groupStack
|
|
347
|
+
focusItem = plugin.focusItemStack.getByNodeId(id, ancestorHID)
|
|
348
|
+
if focusItem <> invalid
|
|
349
|
+
resolvedHID = focusItem.HID
|
|
350
|
+
else
|
|
351
|
+
group = plugin.groupStack.getByNodeId(id, ancestorHID)
|
|
352
|
+
if group <> invalid
|
|
353
|
+
resolvedHID = group.HID
|
|
354
|
+
else
|
|
355
|
+
return
|
|
356
|
+
end if
|
|
357
|
+
end if
|
|
358
|
+
|
|
359
|
+
' Check if this widget is a group
|
|
360
|
+
group = plugin.groupStack.get(m.HID)
|
|
361
|
+
if group <> invalid
|
|
362
|
+
group.setLastFocusedHID(resolvedHID)
|
|
363
|
+
return
|
|
364
|
+
end if
|
|
365
|
+
|
|
366
|
+
' This is a focus item - find parent group
|
|
367
|
+
ancestorGroups = plugin.findAncestorGroups(m.HID)
|
|
368
|
+
if ancestorGroups.count() > 0
|
|
369
|
+
parentGroupHID = ancestorGroups[0]
|
|
370
|
+
parentGroup = plugin.groupStack.get(parentGroupHID)
|
|
371
|
+
if parentGroup <> invalid
|
|
372
|
+
parentGroup.setLastFocusedHID(resolvedHID)
|
|
373
|
+
end if
|
|
374
|
+
end if
|
|
375
|
+
end sub
|
|
325
376
|
|
|
326
377
|
}
|
|
327
378
|
|
|
@@ -1130,7 +1181,7 @@ namespace Rotor
|
|
|
1130
1181
|
|
|
1131
1182
|
class BaseFocusConfig
|
|
1132
1183
|
|
|
1133
|
-
|
|
1184
|
+
autoSetIsFocusedState as boolean
|
|
1134
1185
|
staticDirection as object
|
|
1135
1186
|
|
|
1136
1187
|
sub new (config as object)
|
|
@@ -1142,7 +1193,7 @@ namespace Rotor
|
|
|
1142
1193
|
m.node = m.widget.node
|
|
1143
1194
|
m.isFocused = config.isFocused ?? false
|
|
1144
1195
|
|
|
1145
|
-
m.
|
|
1196
|
+
m.autoSetIsFocusedState = config.autoSetIsFocusedState ?? true
|
|
1146
1197
|
|
|
1147
1198
|
m.isEnabled = config.isEnabled ?? true
|
|
1148
1199
|
m.staticDirection = {}
|
|
@@ -1160,7 +1211,7 @@ namespace Rotor
|
|
|
1160
1211
|
Rotor.Utils.setCustomFields(m.node, { "isFocused": false }, true, true)
|
|
1161
1212
|
|
|
1162
1213
|
' convenience (usually this is used on viewModelState)
|
|
1163
|
-
if false = m.widget.viewModelState.DoesExist("isFocused") and true = m.
|
|
1214
|
+
if false = m.widget.viewModelState.DoesExist("isFocused") and true = m.autoSetIsFocusedState
|
|
1164
1215
|
m.widget.viewModelState.isFocused = false ' as default
|
|
1165
1216
|
end if
|
|
1166
1217
|
|
|
@@ -1346,7 +1397,7 @@ namespace Rotor
|
|
|
1346
1397
|
|
|
1347
1398
|
m.isFocused = isFocused
|
|
1348
1399
|
|
|
1349
|
-
if m.
|
|
1400
|
+
if m.autoSetIsFocusedState
|
|
1350
1401
|
m.widget.viewModelState.isInFocusChain = isFocused
|
|
1351
1402
|
end if
|
|
1352
1403
|
m.node.setField("isFocused", isFocused)
|
|
@@ -1470,7 +1521,7 @@ namespace Rotor
|
|
|
1470
1521
|
|
|
1471
1522
|
m.isFocused = isFocused
|
|
1472
1523
|
|
|
1473
|
-
if m.
|
|
1524
|
+
if m.autoSetIsFocusedState
|
|
1474
1525
|
m.widget.viewModelState.isFocused = isFocused
|
|
1475
1526
|
end if
|
|
1476
1527
|
|