rotor-framework 0.7.4 β 0.7.6
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
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.6",
|
|
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.6
|
|
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.6"
|
|
90
90
|
|
|
91
91
|
config = {
|
|
92
92
|
tasks: invalid, ' @array (optional)
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
' βββββββ ββ β ββ βββββββ βββββββββββββββββ βββββββββ ββββ βββββββββββ
|
|
5
5
|
' ββ βββββββ β βββββββ ββ ββ ββ ββββ ββββ βββββββββββββββββββ ββββ ββ
|
|
6
6
|
' Rotor Frameworkβ’
|
|
7
|
-
' Version 0.7.
|
|
7
|
+
' Version 0.7.6
|
|
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.6"
|
|
74
74
|
|
|
75
75
|
config = {
|
|
76
76
|
tasks: invalid, ' optional
|
|
@@ -121,6 +121,13 @@ namespace Rotor
|
|
|
121
121
|
' 2. group.defaultFocusId [CONFIGURED]
|
|
122
122
|
' 3. Deep search (if defaultFocusId not found immediately)
|
|
123
123
|
'
|
|
124
|
+
' RULE #11b: Deep Focus Tracking (trackDescendantFocus: true)
|
|
125
|
+
' When a FocusGroup has trackDescendantFocus: true, it stores lastFocusedHID
|
|
126
|
+
' for ANY descendant FocusItem (not just direct children).
|
|
127
|
+
' - Immediate parent ALWAYS stores lastFocusedHID (default behavior)
|
|
128
|
+
' - Ancestor groups with trackDescendantFocus: true ALSO store it
|
|
129
|
+
' - Enables "deep focus memory" - returning to deeply nested items
|
|
130
|
+
'
|
|
124
131
|
' RULE #12: DefaultFocusId Targets
|
|
125
132
|
' - FocusItem node ID β Focus goes directly to it
|
|
126
133
|
' - Group node ID β Capturing continues on that group
|
|
@@ -636,6 +643,7 @@ namespace Rotor
|
|
|
636
643
|
' Record the last focused item within its parent group for potential future use (e.g., returning focus)
|
|
637
644
|
lastFocusChainingGroups = m.findAncestorGroups(m.globalFocusHID)
|
|
638
645
|
if lastFocusChainingGroups.Count() > 0
|
|
646
|
+
' Always set on immediate parent (index 0)
|
|
639
647
|
parentGroupHID = lastFocusChainingGroups[0]
|
|
640
648
|
if parentGroupHID <> invalid and parentGroupHID <> ""
|
|
641
649
|
group = m.groupStack.get(parentGroupHID)
|
|
@@ -652,9 +660,20 @@ namespace Rotor
|
|
|
652
660
|
for each groupHID in focusChainGroups
|
|
653
661
|
allAffectedGroups.unshift(groupHID) ' Add in reverse order (highest ancestor first)
|
|
654
662
|
end for
|
|
655
|
-
for
|
|
663
|
+
for i = 0 to lastFocusChainingGroups.Count() - 1
|
|
664
|
+
groupHID = lastFocusChainingGroups[i]
|
|
665
|
+
|
|
666
|
+
' Add to allAffectedGroups if not present
|
|
656
667
|
if -1 = Rotor.Utils.findInArray(allAffectedGroups, groupHID)
|
|
657
|
-
allAffectedGroups.unshift(groupHID)
|
|
668
|
+
allAffectedGroups.unshift(groupHID)
|
|
669
|
+
end if
|
|
670
|
+
|
|
671
|
+
' Deep remember (skip index 0 - immediate parent is handled separately)
|
|
672
|
+
if i > 0 and lastFocused <> invalid
|
|
673
|
+
ancestorGroup = m.groupStack.get(groupHID)
|
|
674
|
+
if ancestorGroup <> invalid and ancestorGroup.trackDescendantFocus = true
|
|
675
|
+
ancestorGroup.setLastFocusedHID(m.globalFocusHID)
|
|
676
|
+
end if
|
|
658
677
|
end if
|
|
659
678
|
end for
|
|
660
679
|
|
|
@@ -778,17 +797,12 @@ namespace Rotor
|
|
|
778
797
|
if group = invalid then return ""
|
|
779
798
|
|
|
780
799
|
' Get fallback identifier for this group
|
|
781
|
-
|
|
800
|
+
' (enableSpatialEnter groups return the spatially closest member here)
|
|
801
|
+
newHID = group.getFallbackIdentifier(m.globalFocusHID)
|
|
782
802
|
|
|
783
803
|
' Check if we found a FocusItem
|
|
784
804
|
if m.focusItemStack.has(newHID)
|
|
785
|
-
'
|
|
786
|
-
if group.enableSpatialEnter = true and direction <> ""
|
|
787
|
-
focused = m.focusItemStack.get(m.globalFocusHID)
|
|
788
|
-
newSpatialHID = m.spatialNavigation(focused, direction, group.getGroupMembersHIDs())
|
|
789
|
-
if newSpatialHID <> "" then newHID = newSpatialHID
|
|
790
|
-
end if
|
|
791
|
-
|
|
805
|
+
' noop β direct focusItem resolved
|
|
792
806
|
else if newHID <> ""
|
|
793
807
|
' Try to find as group first, then deep search
|
|
794
808
|
newHID = m.capturingFocus_recursively(newHID, direction, group.HID)
|
|
@@ -800,7 +814,8 @@ namespace Rotor
|
|
|
800
814
|
end if
|
|
801
815
|
|
|
802
816
|
' Prevent capturing by fallback in the same group where original focus was
|
|
803
|
-
|
|
817
|
+
' Skip this guard for enableSpatialEnter groups (spatial enter explicitly targets a sibling group's member)
|
|
818
|
+
if not group.enableSpatialEnter and newHID <> "" and m.globalFocusHID <> ""
|
|
804
819
|
currentAncestors = m.findAncestorGroups(m.globalFocusHID)
|
|
805
820
|
newAncestors = m.findAncestorGroups(newHID)
|
|
806
821
|
if currentAncestors.Count() > 0 and newAncestors.Count() > 0
|
|
@@ -904,7 +919,6 @@ namespace Rotor
|
|
|
904
919
|
end if
|
|
905
920
|
' Prevent any navigation if it is disabled
|
|
906
921
|
#if debug
|
|
907
|
-
if m.enableFocusNavigation = false and press = true then print "[PLUGIN][FOCUS][INFO] Focus navigation is disabled. Call enableFocusNavigation(true) to make it enabled"
|
|
908
922
|
#end if
|
|
909
923
|
if m.enableFocusNavigation = false then return m.parseOnKeyEventResult(key, false, false)
|
|
910
924
|
' Execute action according to key press
|
|
@@ -1279,14 +1293,15 @@ namespace Rotor
|
|
|
1279
1293
|
m.defaultFocusId = config.defaultFocusId ?? ""
|
|
1280
1294
|
m.lastFocusedHID = config.lastFocusedHID ?? ""
|
|
1281
1295
|
m.enableSpatialEnter = config.enableSpatialEnter ?? false
|
|
1296
|
+
m.trackDescendantFocus = config.trackDescendantFocus ?? false
|
|
1282
1297
|
end sub
|
|
1283
1298
|
|
|
1284
1299
|
defaultFocusId as string
|
|
1285
1300
|
lastFocusedHID as string
|
|
1286
1301
|
enableSpatialEnter as boolean
|
|
1302
|
+
trackDescendantFocus as boolean
|
|
1287
1303
|
focusItemsRef as object
|
|
1288
1304
|
groupsRef as object
|
|
1289
|
-
|
|
1290
1305
|
isFocusItem = false
|
|
1291
1306
|
isGroup = true
|
|
1292
1307
|
|
|
@@ -1347,34 +1362,66 @@ namespace Rotor
|
|
|
1347
1362
|
end if
|
|
1348
1363
|
end function
|
|
1349
1364
|
|
|
1350
|
-
function getFallbackIdentifier() as string
|
|
1365
|
+
function getFallbackIdentifier(globalFocusHID = "" as string) as string
|
|
1351
1366
|
HID = ""
|
|
1352
|
-
|
|
1367
|
+
' enableSpatialEnter takes priority over lastFocusedHID
|
|
1368
|
+
' (lastFocusedHID may be stale from slot recycling)
|
|
1369
|
+
if not m.enableSpatialEnter and m.lastFocusedHID <> ""
|
|
1353
1370
|
return m.lastFocusedHID
|
|
1354
|
-
|
|
1355
|
-
if Rotor.Utils.isFunction(m.defaultFocusId)
|
|
1356
|
-
defaultFocusId = Rotor.Utils.callbackScoped(m.defaultFocusId, m.widget) ?? ""
|
|
1357
|
-
else
|
|
1358
|
-
defaultFocusId = m.defaultFocusId
|
|
1359
|
-
end if
|
|
1360
|
-
|
|
1361
|
-
if defaultFocusId <> ""
|
|
1362
|
-
focusItemsHIDlist = m.getGroupMembersHIDs()
|
|
1363
|
-
if focusItemsHIDlist.Count() > 0
|
|
1371
|
+
end if
|
|
1364
1372
|
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1373
|
+
' enableSpatialEnter: return the spatially closest member to the current focus
|
|
1374
|
+
if m.enableSpatialEnter = true and globalFocusHID <> ""
|
|
1375
|
+
prevFocused = m.focusItemsRef.get(globalFocusHID)
|
|
1376
|
+
if prevFocused <> invalid
|
|
1377
|
+
prevFocused.refreshBounding()
|
|
1378
|
+
refPoint = prevFocused.metrics.middlePoint
|
|
1379
|
+
members = m.getGroupMembersHIDs()
|
|
1380
|
+
minDist = 2147483647
|
|
1381
|
+
closestHID = ""
|
|
1382
|
+
for each memberHID in members
|
|
1383
|
+
if memberHID <> globalFocusHID
|
|
1384
|
+
member = m.focusItemsRef.get(memberHID)
|
|
1385
|
+
if member <> invalid
|
|
1386
|
+
member.refreshBounding()
|
|
1387
|
+
dx = member.metrics.middlePoint.x - refPoint.x
|
|
1388
|
+
dy = member.metrics.middlePoint.y - refPoint.y
|
|
1389
|
+
dist = dx * dx + dy * dy
|
|
1390
|
+
if dist < minDist
|
|
1391
|
+
minDist = dist
|
|
1392
|
+
closestHID = memberHID
|
|
1393
|
+
end if
|
|
1394
|
+
end if
|
|
1369
1395
|
end if
|
|
1396
|
+
end for
|
|
1397
|
+
if closestHID <> ""
|
|
1398
|
+
return closestHID
|
|
1399
|
+
end if
|
|
1400
|
+
end if
|
|
1401
|
+
end if
|
|
1370
1402
|
|
|
1371
|
-
|
|
1403
|
+
' Default: use defaultFocusId expression
|
|
1404
|
+
if Rotor.Utils.isFunction(m.defaultFocusId)
|
|
1405
|
+
defaultFocusId = Rotor.Utils.callbackScoped(m.defaultFocusId, m.widget) ?? ""
|
|
1406
|
+
else
|
|
1407
|
+
defaultFocusId = m.defaultFocusId
|
|
1408
|
+
end if
|
|
1372
1409
|
|
|
1373
|
-
|
|
1410
|
+
if defaultFocusId <> ""
|
|
1411
|
+
focusItemsHIDlist = m.getGroupMembersHIDs()
|
|
1412
|
+
if focusItemsHIDlist.Count() > 0
|
|
1374
1413
|
|
|
1414
|
+
' Try find valid HID in focusItems by node id
|
|
1415
|
+
focusItemHID = m.findHIDinFocusItemsByNodeId(defaultFocusId, focusItemsHIDlist)
|
|
1416
|
+
if focusItemHID <> ""
|
|
1417
|
+
HID = focusItemHID
|
|
1375
1418
|
end if
|
|
1376
|
-
end if
|
|
1377
1419
|
|
|
1420
|
+
else
|
|
1421
|
+
|
|
1422
|
+
return defaultFocusId
|
|
1423
|
+
|
|
1424
|
+
end if
|
|
1378
1425
|
end if
|
|
1379
1426
|
|
|
1380
1427
|
return HID
|