rotor-framework 0.7.1 → 0.7.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/package.json +1 -1
- package/src/source/RotorFramework.bs +77 -152
- package/src/source/RotorFrameworkTask.bs +46 -7
- package/src/source/base/BaseWidget.bs +11 -9
- package/src/source/base/{ListenerForDispatchers.bs → DispatcherCore.bs} +12 -4
- package/src/source/base/{DispatcherExternal.bs → DispatcherCrossThread.bs} +24 -27
- package/src/source/base/{DispatcherCreator.bs → DispatcherOriginal.bs} +10 -13
- package/src/source/engine/Constants.bs +1 -1
- package/src/source/engine/animator/Animator.bs +6 -2
- package/src/source/engine/builder/WidgetCreate.bs +0 -5
- package/src/source/engine/providers/Dispatcher.bs +17 -16
- package/src/source/engine/providers/DispatcherProvider.bs +5 -5
- package/src/source/engine/services/I18n.bs +3 -2
- package/src/source/engine/services/Tts.bs +16 -9
- package/src/source/plugins/FieldsPlugin.bs +2 -2
- package/src/source/plugins/FocusPlugin.bs +57 -6
- package/src/source/plugins/FontStylePlugin.bs +34 -109
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.3",
|
|
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.3
|
|
8
8
|
' © 2025 Balázs Molnár — Apache License 2.0
|
|
9
9
|
' =========================================================================
|
|
10
10
|
|
|
@@ -21,8 +21,8 @@ import "engine/animator/Animator.bs"
|
|
|
21
21
|
|
|
22
22
|
' base classes
|
|
23
23
|
import "base/BaseWidget.bs"
|
|
24
|
-
import "base/
|
|
25
|
-
import "base/
|
|
24
|
+
import "base/DispatcherOriginal.bs"
|
|
25
|
+
import "base/DispatcherCrossThread.bs"
|
|
26
26
|
import "base/BaseReducer.bs"
|
|
27
27
|
import "base/BaseModel.bs"
|
|
28
28
|
import "base/BaseStack.bs"
|
|
@@ -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.3"
|
|
90
90
|
|
|
91
91
|
config = {
|
|
92
92
|
tasks: invalid, ' @array (optional)
|
|
@@ -118,23 +118,14 @@ 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
|
|
|
128
|
-
' sync
|
|
129
|
-
|
|
130
|
-
taskSyncReadyFlag = {}
|
|
131
|
-
taskNodes = {}
|
|
124
|
+
' sync - task tracking with phase: pending → operational → synced
|
|
125
|
+
tasks = {} ' taskId -> { node, phase, dispatchers[] }
|
|
132
126
|
|
|
133
127
|
enableRendering = true
|
|
134
128
|
|
|
135
|
-
' helper vars
|
|
136
|
-
deviceInfo as object
|
|
137
|
-
|
|
138
129
|
'----------------------------------------------------------------------
|
|
139
130
|
' new - Initializes the Rotor Framework instance
|
|
140
131
|
'
|
|
@@ -161,8 +152,6 @@ namespace Rotor
|
|
|
161
152
|
|
|
162
153
|
m.builder.init(m)
|
|
163
154
|
|
|
164
|
-
m.initInfo()
|
|
165
|
-
|
|
166
155
|
' set root node
|
|
167
156
|
if m.config.rootNode = invalid
|
|
168
157
|
rootNode = globalScope.top
|
|
@@ -325,15 +314,6 @@ namespace Rotor
|
|
|
325
314
|
return m.animatorProvider.getFactory(animatorId, m)
|
|
326
315
|
end function
|
|
327
316
|
|
|
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
317
|
' ---------------------------------------------------------------------
|
|
338
318
|
' getNodePoolInfo - Gets node pool statistics
|
|
339
319
|
'
|
|
@@ -366,32 +346,6 @@ namespace Rotor
|
|
|
366
346
|
m.builder.pluginAdapter.registerPlugins(plugins)
|
|
367
347
|
end sub
|
|
368
348
|
|
|
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
349
|
' ---------------------------------------------------------------------
|
|
396
350
|
' setupTaskForSyncing - Prepares a task node for cross-thread syncing
|
|
397
351
|
'
|
|
@@ -404,15 +358,17 @@ namespace Rotor
|
|
|
404
358
|
taskNode = rootNode.createChild(taskName)
|
|
405
359
|
taskId = Rotor.Utils.getUUIDHex(16)
|
|
406
360
|
|
|
407
|
-
m.
|
|
408
|
-
|
|
361
|
+
m.tasks[taskId] = {
|
|
362
|
+
node: taskNode,
|
|
363
|
+
phase: "pending",
|
|
364
|
+
dispatchers: []
|
|
365
|
+
}
|
|
409
366
|
|
|
410
367
|
Rotor.Utils.setCustomFields(taskNode, {
|
|
411
368
|
taskId: taskId,
|
|
412
369
|
rootNode: rootNode
|
|
413
370
|
})
|
|
414
371
|
|
|
415
|
-
' taskNode.observeFieldScoped("rotorSync", "Rotor_syncCallback")
|
|
416
372
|
taskNode.control = "RUN"
|
|
417
373
|
end sub
|
|
418
374
|
|
|
@@ -422,136 +378,108 @@ namespace Rotor
|
|
|
422
378
|
' @param {object} taskList - Array of task names to set up
|
|
423
379
|
'
|
|
424
380
|
sub setupAdditionalTasks(taskList as object)
|
|
425
|
-
' check additional tasks
|
|
426
381
|
if taskList = invalid then return
|
|
427
|
-
|
|
428
|
-
taskNames = Rotor.Utils.ensureArray(taskList)
|
|
429
|
-
for each taskName in taskNames
|
|
382
|
+
for each taskName in Rotor.Utils.ensureArray(taskList)
|
|
430
383
|
m.setupTaskForSyncing(taskName)
|
|
431
384
|
end for
|
|
432
385
|
end sub
|
|
433
386
|
|
|
434
387
|
' ---------------------------------------------------------------------
|
|
435
|
-
'
|
|
388
|
+
' allTasksInPhase - Checks if all tasks are in given phase
|
|
436
389
|
'
|
|
437
|
-
' @
|
|
390
|
+
' @param {string} phase - Phase to check ("pending", "operational", "synced")
|
|
391
|
+
' @returns {boolean} True if all tasks are in the given phase
|
|
438
392
|
'
|
|
439
|
-
function
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
for each flag in m.taskOperationalFlag.Items()
|
|
444
|
-
if flag.value <> true then return false
|
|
393
|
+
function allTasksInPhase(phase as string) as boolean
|
|
394
|
+
if m.tasks.Count() = 0 then return false
|
|
395
|
+
for each taskId in m.tasks
|
|
396
|
+
if m.tasks[taskId].phase <> phase then return false
|
|
445
397
|
end for
|
|
446
|
-
|
|
447
398
|
return true
|
|
448
399
|
end function
|
|
449
400
|
|
|
450
401
|
' ---------------------------------------------------------------------
|
|
451
|
-
'
|
|
452
|
-
'
|
|
453
|
-
'
|
|
454
|
-
'
|
|
455
|
-
'
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
for each dispatcherId in dispatcherIds
|
|
465
|
-
dispatcherInstance = m.dispatcherProvider.get(dispatcherId)
|
|
466
|
-
externalTaskNode = dispatcherInstance.taskNode
|
|
467
|
-
|
|
468
|
-
if not taskNode.isSameNode(externalTaskNode)
|
|
469
|
-
m.taskSyncReadyFlag[taskNode.taskId] = false ' collection for later usage
|
|
470
|
-
if registrations[taskNode.taskId] = invalid then registrations[taskNode.taskId] = []
|
|
471
|
-
registrations[taskNode.taskId].push({
|
|
472
|
-
dispatcherId: dispatcherId,
|
|
473
|
-
externalTaskNode: externalTaskNode
|
|
474
|
-
})
|
|
475
|
-
end if
|
|
476
|
-
end for
|
|
402
|
+
' getDispatchersNotOnTask - Gets dispatchers that are NOT on given task
|
|
403
|
+
'
|
|
404
|
+
' @param {object} taskNode - Task node to exclude
|
|
405
|
+
' @returns {object} Array of { dispatcherId, stateNode } for foreign dispatchers
|
|
406
|
+
'
|
|
407
|
+
function getDispatchersNotOnTask(taskNode as object) as object
|
|
408
|
+
result = []
|
|
409
|
+
for each dispatcherId in m.dispatcherProvider.getAll().keys()
|
|
410
|
+
stateNode = m.dispatcherProvider.get(dispatcherId).stateNode
|
|
411
|
+
if not taskNode.isSameNode(stateNode)
|
|
412
|
+
result.push({ dispatcherId: dispatcherId, stateNode: stateNode })
|
|
413
|
+
end if
|
|
477
414
|
end for
|
|
478
|
-
|
|
479
|
-
return registrations
|
|
415
|
+
return result
|
|
480
416
|
end function
|
|
481
417
|
|
|
482
418
|
' ---------------------------------------------------------------------
|
|
483
|
-
'
|
|
484
|
-
'
|
|
485
|
-
'
|
|
486
|
-
'
|
|
487
|
-
sub
|
|
488
|
-
for each taskId in
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
' @returns {boolean} True if all tasks have synced successfully
|
|
501
|
-
'
|
|
502
|
-
function haveAllTasksSynced() as boolean
|
|
503
|
-
' Check if all nodes ready (very basic logic (< future improvement)
|
|
504
|
-
if m.taskSyncReadyFlag.Count() = 0 then return false
|
|
505
|
-
|
|
506
|
-
for each flag in m.taskSyncReadyFlag.Items()
|
|
507
|
-
if flag.value <> true then return false
|
|
419
|
+
' broadcastDispatchersToTasks - Sends foreign dispatchers to each task
|
|
420
|
+
'
|
|
421
|
+
' For each task, sends list of dispatchers that exist on other threads.
|
|
422
|
+
'
|
|
423
|
+
sub broadcastDispatchersToTasks()
|
|
424
|
+
for each taskId in m.tasks
|
|
425
|
+
task = m.tasks[taskId]
|
|
426
|
+
foreignDispatchers = m.getDispatchersNotOnTask(task.node)
|
|
427
|
+
|
|
428
|
+
if foreignDispatchers.Count() > 0
|
|
429
|
+
task.node.setField("rotorSync", {
|
|
430
|
+
type: Rotor.Const.ThreadSyncType.REGISTER_CROSS_THREAD_DISPATCHER,
|
|
431
|
+
crossThreadDispatcherList: foreignDispatchers
|
|
432
|
+
})
|
|
433
|
+
else
|
|
434
|
+
task.phase = "synced"
|
|
435
|
+
end if
|
|
508
436
|
end for
|
|
509
437
|
|
|
510
|
-
|
|
511
|
-
|
|
438
|
+
' If no tasks needed dispatchers, we're done
|
|
439
|
+
if m.allTasksInPhase("synced")
|
|
440
|
+
m.isReady()
|
|
441
|
+
end if
|
|
442
|
+
end sub
|
|
512
443
|
|
|
513
444
|
' ---------------------------------------------------------------------
|
|
514
445
|
' handleTaskSyncing - Handles task syncing phase
|
|
515
446
|
'
|
|
516
|
-
'
|
|
447
|
+
' Called when a task reports its dispatchers. Registers cross-thread
|
|
448
|
+
' dispatchers and broadcasts dispatcher map when all tasks are ready.
|
|
517
449
|
'
|
|
518
|
-
' @param {object} sync - Sync payload
|
|
450
|
+
' @param {object} sync - Sync payload { taskNode, dispatcherIds, tasks }
|
|
519
451
|
'
|
|
520
452
|
sub handleTaskSyncing(sync as object)
|
|
521
453
|
taskNode = sync.taskNode
|
|
454
|
+
taskId = taskNode.taskId
|
|
522
455
|
|
|
456
|
+
' Setup any additional tasks this task declared
|
|
523
457
|
m.setupAdditionalTasks(sync.tasks)
|
|
524
458
|
|
|
525
|
-
'
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
m.dispatcherProvider.registerExternalDispatchers(dispatcherIds, taskNode)
|
|
529
|
-
end if
|
|
459
|
+
' Store dispatcher IDs for this task
|
|
460
|
+
m.tasks[taskId].dispatchers = sync.dispatcherIds ?? []
|
|
461
|
+
m.tasks[taskId].phase = "operational"
|
|
530
462
|
|
|
531
|
-
'
|
|
532
|
-
|
|
463
|
+
' Register cross-thread dispatchers on render thread
|
|
464
|
+
if sync.dispatcherIds <> invalid
|
|
465
|
+
m.dispatcherProvider.registerCrossThreadDispatchers(sync.dispatcherIds, taskNode)
|
|
466
|
+
end if
|
|
533
467
|
|
|
534
|
-
|
|
468
|
+
' Wait for all tasks to be operational
|
|
469
|
+
if not m.allTasksInPhase("operational") then return
|
|
535
470
|
|
|
536
|
-
'
|
|
537
|
-
|
|
538
|
-
if registrations.Count() > 0
|
|
539
|
-
m.dispatchExternalDispatcherRegistrations(registrations)
|
|
540
|
-
else
|
|
541
|
-
m.isReady()
|
|
542
|
-
end if
|
|
471
|
+
' All tasks ready - broadcast dispatcher map
|
|
472
|
+
m.broadcastDispatchersToTasks()
|
|
543
473
|
end sub
|
|
544
474
|
|
|
545
475
|
' ---------------------------------------------------------------------
|
|
546
476
|
' handleTaskSynced - Handles task synced confirmation
|
|
547
477
|
'
|
|
548
|
-
' Marks task as synced and checks if all tasks are ready.
|
|
549
|
-
'
|
|
550
478
|
' @param {string} taskId - ID of the task that has synced
|
|
551
479
|
'
|
|
552
480
|
sub handleTaskSynced(taskId as string)
|
|
553
|
-
m.
|
|
554
|
-
if m.
|
|
481
|
+
m.tasks[taskId].phase = "synced"
|
|
482
|
+
if m.allTasksInPhase("synced")
|
|
555
483
|
m.isReady()
|
|
556
484
|
end if
|
|
557
485
|
end sub
|
|
@@ -593,15 +521,15 @@ namespace Rotor
|
|
|
593
521
|
public sub destroy()
|
|
594
522
|
|
|
595
523
|
rootNode = m.getRootNode()
|
|
596
|
-
for each taskId in m.
|
|
597
|
-
|
|
598
|
-
|
|
524
|
+
for each taskId in m.tasks
|
|
525
|
+
task = m.tasks[taskId]
|
|
526
|
+
task.node.setField("rotorSync", {
|
|
599
527
|
type: Rotor.Const.ThreadSyncType.DESTROY
|
|
600
528
|
})
|
|
601
|
-
rootNode.removeChild(
|
|
529
|
+
rootNode.removeChild(task.node)
|
|
602
530
|
end for
|
|
603
531
|
|
|
604
|
-
m.
|
|
532
|
+
m.tasks.Clear()
|
|
605
533
|
|
|
606
534
|
rootNode.unobserveFieldScoped("rotorSync")
|
|
607
535
|
|
|
@@ -619,9 +547,6 @@ namespace Rotor
|
|
|
619
547
|
}
|
|
620
548
|
m.config.rootNode = invalid
|
|
621
549
|
|
|
622
|
-
m.info.Clear()
|
|
623
|
-
m.deviceInfo = invalid
|
|
624
|
-
|
|
625
550
|
end sub
|
|
626
551
|
|
|
627
552
|
end class
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
' ▐▛▀▚▖▐▌ ▐▌ █ ▐▌ ▐▌▐▛▀▚▖ ▐▛▀▀▘▐▛▀▚▖▐▛▀▜▌▐▌ ▐▌▐▛▀▀▘▐▌ ▐▌▐▌ ▐▌▐▛▀▚▖▐▛▚▖
|
|
5
5
|
' ▐▌ ▐▌▝▚▄▞▘ █ ▝▚▄▞▘▐▌ ▐▌ ▐▌ ▐▌ ▐▌▐▌ ▐▌▐▌ ▐▌▐▙▄▄▖▐▙█▟▌▝▚▄▞▘▐▌ ▐▌▐▌ ▐▌
|
|
6
6
|
' Rotor Framework™
|
|
7
|
-
' Version 0.7.
|
|
7
|
+
' Version 0.7.3
|
|
8
8
|
' © 2025 Balázs Molnár — Apache License 2.0
|
|
9
9
|
' =========================================================================
|
|
10
10
|
|
|
@@ -16,8 +16,8 @@ import "engine/providers/DispatcherProvider.bs"
|
|
|
16
16
|
import "engine/providers/Dispatcher.bs"
|
|
17
17
|
|
|
18
18
|
' base classes
|
|
19
|
-
import "base/
|
|
20
|
-
import "base/
|
|
19
|
+
import "base/DispatcherOriginal.bs"
|
|
20
|
+
import "base/DispatcherCrossThread.bs"
|
|
21
21
|
import "base/BaseReducer.bs"
|
|
22
22
|
import "base/BaseModel.bs"
|
|
23
23
|
import "base/BaseStack.bs"
|
|
@@ -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.3"
|
|
74
74
|
|
|
75
75
|
config = {
|
|
76
76
|
tasks: invalid, ' optional
|
|
@@ -87,6 +87,7 @@ namespace Rotor
|
|
|
87
87
|
dispatcherProvider as object
|
|
88
88
|
port as object
|
|
89
89
|
asyncTransferRegistry = {} ' transferId -> { dispatcherId, context }
|
|
90
|
+
deviceInfoRegistry = {} ' deviceInfoId -> { dispatcherId, context, deviceInfo }
|
|
90
91
|
onTick as function
|
|
91
92
|
|
|
92
93
|
' ---------------------------------------------------------------------
|
|
@@ -148,6 +149,27 @@ namespace Rotor
|
|
|
148
149
|
}
|
|
149
150
|
end sub
|
|
150
151
|
|
|
152
|
+
' ---------------------------------------------------------------------
|
|
153
|
+
' registerDeviceInfo - Registers a roDeviceInfo for event listening
|
|
154
|
+
'
|
|
155
|
+
' This method associates a roDeviceInfo object with a dispatcher ID.
|
|
156
|
+
' When device info events occur (linkStatus, etc.), the framework will
|
|
157
|
+
' route them to the correct dispatcher's asyncReducerCallback.
|
|
158
|
+
'
|
|
159
|
+
' @param {string} deviceInfoId - Unique identifier for this registration
|
|
160
|
+
' @param {string} dispatcherId - Dispatcher ID that will handle events
|
|
161
|
+
' @param {object} deviceInfo - The roDeviceInfo object (must have SetMessagePort called)
|
|
162
|
+
' @param {object} context - Optional user data to pass back in callback
|
|
163
|
+
'
|
|
164
|
+
public sub registerDeviceInfo(deviceInfoId as string, dispatcherId as string, deviceInfo as object, context = invalid as dynamic)
|
|
165
|
+
deviceInfo.SetMessagePort(m.port)
|
|
166
|
+
m.deviceInfoRegistry[deviceInfoId] = {
|
|
167
|
+
dispatcherId: dispatcherId,
|
|
168
|
+
deviceInfo: deviceInfo,
|
|
169
|
+
context: context
|
|
170
|
+
}
|
|
171
|
+
end sub
|
|
172
|
+
|
|
151
173
|
' ---------------------------------------------------------------------
|
|
152
174
|
' sync - Starts the message loop for cross-thread communication
|
|
153
175
|
'
|
|
@@ -205,10 +227,10 @@ namespace Rotor
|
|
|
205
227
|
' taskIntent = Rotor.Utils.deepCopy(intent)
|
|
206
228
|
dispatcherInstance.dispatch(intent)
|
|
207
229
|
|
|
208
|
-
else if sync.type = Rotor.Const.ThreadSyncType.
|
|
230
|
+
else if sync.type = Rotor.Const.ThreadSyncType.REGISTER_CROSS_THREAD_DISPATCHER
|
|
209
231
|
|
|
210
|
-
for each item in sync.
|
|
211
|
-
m.dispatcherProvider.
|
|
232
|
+
for each item in sync.crossThreadDispatcherList
|
|
233
|
+
m.dispatcherProvider.registerCrossThreadDispatchers(item.dispatcherId, item.stateNode)
|
|
212
234
|
end for
|
|
213
235
|
|
|
214
236
|
m.notifySyncStatus(Rotor.Const.ThreadSyncType.TASK_SYNCED)
|
|
@@ -251,6 +273,22 @@ namespace Rotor
|
|
|
251
273
|
end if
|
|
252
274
|
|
|
253
275
|
end if
|
|
276
|
+
else if msgType = "roDeviceInfoEvent"
|
|
277
|
+
' Handle device info events (ignore appFocused, etc.)
|
|
278
|
+
if m.deviceInfoRegistry.count() > 0
|
|
279
|
+
eventInfo = msg.GetInfo()
|
|
280
|
+
' Only process linkStatus events, ignore appFocused and other events
|
|
281
|
+
if eventInfo?.linkStatus <> invalid
|
|
282
|
+
' Route to all registered device info dispatchers
|
|
283
|
+
for each deviceInfoId in m.deviceInfoRegistry
|
|
284
|
+
registryEntry = m.deviceInfoRegistry[deviceInfoId]
|
|
285
|
+
dispatcherInstance = m.dispatcherProvider.get(registryEntry.dispatcherId)
|
|
286
|
+
if dispatcherInstance <> invalid
|
|
287
|
+
dispatcherInstance.asyncReducerCallback(eventInfo, registryEntry?.context as dynamic)
|
|
288
|
+
end if
|
|
289
|
+
end for
|
|
290
|
+
end if
|
|
291
|
+
end if
|
|
254
292
|
end if
|
|
255
293
|
end if
|
|
256
294
|
end while
|
|
@@ -317,6 +355,7 @@ namespace Rotor
|
|
|
317
355
|
'
|
|
318
356
|
public sub destroy()
|
|
319
357
|
m.asyncTransferRegistry.clear()
|
|
358
|
+
m.deviceInfoRegistry.clear()
|
|
320
359
|
m.dispatcherProvider.destroy()
|
|
321
360
|
|
|
322
361
|
globalScope = GetGlobalAA()
|
|
@@ -87,22 +87,24 @@ namespace Rotor
|
|
|
87
87
|
' These are optionally injected by plugins
|
|
88
88
|
|
|
89
89
|
' Focus Plugin Methods (from FocusPlugin.bs)
|
|
90
|
-
optional enableFocusNavigation as function
|
|
90
|
+
optional enableFocusNavigation as function ' Enable focus navigation
|
|
91
91
|
optional isFocusNavigationEnabled as function ' Check if focus enabled
|
|
92
|
-
optional setFocus as function
|
|
93
|
-
optional getFocusedWidget as function
|
|
94
|
-
optional proceedLongPress as function
|
|
95
|
-
optional isLongPressActive as function
|
|
92
|
+
optional setFocus as function ' Set focus on widget
|
|
93
|
+
optional getFocusedWidget as function ' Get currently focused widget
|
|
94
|
+
optional proceedLongPress as function ' Trigger long press
|
|
95
|
+
optional isLongPressActive as function ' Check if long press active
|
|
96
|
+
optional triggerKeyPress as function ' Trigger key press event
|
|
97
|
+
optional setGroupLastFocusedId as function ' Set the last focused widget ID within the current focus group (used to restore focus when re-entering the group)
|
|
96
98
|
|
|
97
99
|
' =============================================================
|
|
98
100
|
' PRIVATE PROPERTIES (Engine Use Only)
|
|
99
101
|
' =============================================================
|
|
100
102
|
|
|
101
|
-
private HID as string
|
|
102
|
-
private vmHID as string
|
|
103
|
-
private isRootChild as boolean
|
|
103
|
+
private HID as string ' Hierarchical ID (e.g., "0.header.logo")
|
|
104
|
+
private vmHID as string ' Owning ViewModel's HID
|
|
105
|
+
private isRootChild as boolean ' True if direct child of root
|
|
104
106
|
private childrenHIDhash as object ' Hash of child HIDs for fast lookup
|
|
105
|
-
private parentHID as string
|
|
107
|
+
private parentHID as string ' Parent's HID
|
|
106
108
|
|
|
107
109
|
end class
|
|
108
110
|
|
|
@@ -16,10 +16,15 @@ end interface
|
|
|
16
16
|
namespace Rotor
|
|
17
17
|
|
|
18
18
|
' =====================================================================
|
|
19
|
-
'
|
|
19
|
+
' DispatcherCore - Core base class for dispatcher implementations
|
|
20
20
|
'
|
|
21
|
-
' Provides
|
|
22
|
-
'
|
|
21
|
+
' Provides shared properties and listener management for both
|
|
22
|
+
' DispatcherOriginal and DispatcherCrossThread.
|
|
23
|
+
'
|
|
24
|
+
' Shared Properties:
|
|
25
|
+
' - dispatcherId: Unique identifier for this dispatcher
|
|
26
|
+
' - stateNode: SceneGraph node for state exposure
|
|
27
|
+
' - listeners: Array of registered state change listeners
|
|
23
28
|
'
|
|
24
29
|
' Listener Features:
|
|
25
30
|
' - State mapping: mapStateToProps for updating widget props
|
|
@@ -33,12 +38,15 @@ namespace Rotor
|
|
|
33
38
|
' 3. Invoke callback functions
|
|
34
39
|
' 4. Remove 'once' listeners
|
|
35
40
|
' =====================================================================
|
|
36
|
-
class
|
|
41
|
+
class DispatcherCore
|
|
37
42
|
|
|
38
43
|
' =============================================================
|
|
39
44
|
' MEMBER VARIABLES
|
|
40
45
|
' =============================================================
|
|
41
46
|
|
|
47
|
+
dispatcherId as string ' Dispatcher identifier
|
|
48
|
+
stateNode as object ' Node for cross-thread state exposure
|
|
49
|
+
|
|
42
50
|
listeners = [] ' Array of listener configurations
|
|
43
51
|
|
|
44
52
|
' =============================================================
|