rotor-framework 0.3.7 β 0.4.1
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 +7 -1
- package/package.json +1 -1
- package/src/source/RotorFramework.bs +2 -2
- package/src/source/RotorFrameworkTask.bs +4 -4
- package/src/source/base/BaseWidget.bs +7 -3
- package/src/source/engine/builder/WidgetCreate.bs +24 -10
- package/src/source/engine/services/I18n.bs +0 -11
- package/src/source/plugins/FocusPlugin.bs +20 -13
package/README.md
CHANGED
|
@@ -22,6 +22,12 @@
|
|
|
22
22
|
|
|
23
23
|
---
|
|
24
24
|
|
|
25
|
+
## π― Quick Start with Starter Template
|
|
26
|
+
|
|
27
|
+
Want to get started quickly? Check out **[rotor-starter](https://github.com/mobalazs/rotor-starter)** - a ready-to-use project template with Rotor Framework pre-configured, sample components, and best practices built in.
|
|
28
|
+
|
|
29
|
+
---
|
|
30
|
+
|
|
25
31
|
## π¦ Installation
|
|
26
32
|
|
|
27
33
|
### Prerequisites
|
|
@@ -90,7 +96,7 @@ You can find [π±](./docs/ai/readme.opt.yaml) symbols in all documentation page
|
|
|
90
96
|
|
|
91
97
|
## π Learn More
|
|
92
98
|
|
|
93
|
-

|
|
94
100
|
|
|
95
101
|
### Framework Core
|
|
96
102
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "rotor-framework",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.1",
|
|
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": "MIT",
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
' βββββββ ββ β ββ βββββββ βββββββββββββββββ βββββββββ ββββ βββββββββββ
|
|
5
5
|
' ββ βββββββ β βββββββ ββ ββ ββ ββββ ββββ βββββββββββββββββββ ββββ ββ
|
|
6
6
|
' Rotor Frameworkβ’
|
|
7
|
-
' Version 0.
|
|
7
|
+
' Version 0.4.1
|
|
8
8
|
' Β© 2025 BalΓ‘zs MolnΓ‘r β MIT License
|
|
9
9
|
' =========================================================================
|
|
10
10
|
|
|
@@ -85,7 +85,7 @@ namespace Rotor
|
|
|
85
85
|
class Framework
|
|
86
86
|
|
|
87
87
|
name = "Rotor Framework"
|
|
88
|
-
version = "0.
|
|
88
|
+
version = "0.4.1"
|
|
89
89
|
|
|
90
90
|
config = {
|
|
91
91
|
tasks: invalid, ' @array (optional)
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
' βββββββ ββ β ββ βββββββ βββββββββββββββββ βββββββββ ββββ βββββββββββ
|
|
5
5
|
' ββ βββββββ β βββββββ ββ ββ ββ ββββ ββββ βββββββββββββββββββ ββββ ββ
|
|
6
6
|
' Rotor Frameworkβ’
|
|
7
|
-
' Version 0.
|
|
7
|
+
' Version 0.4.1
|
|
8
8
|
' Β© 2025 BalΓ‘zs MolnΓ‘r β MIT License
|
|
9
9
|
' =========================================================================
|
|
10
10
|
|
|
@@ -70,7 +70,7 @@ namespace Rotor
|
|
|
70
70
|
class FrameworkTask
|
|
71
71
|
|
|
72
72
|
name = "Rotor Framework"
|
|
73
|
-
version = "0.
|
|
73
|
+
version = "0.4.1"
|
|
74
74
|
|
|
75
75
|
config = {
|
|
76
76
|
tasks: invalid, ' optional
|
|
@@ -184,9 +184,9 @@ namespace Rotor
|
|
|
184
184
|
data = msg.getData()
|
|
185
185
|
extraInfo = msg.GetInfo() ' Info AA passed during observeFieldScoped
|
|
186
186
|
|
|
187
|
-
if extraInfo?.
|
|
187
|
+
if extraInfo?.dispatcherId <> invalid and m.dispatcherProvider.get(extraInfo?.dispatcherId) <> invalid
|
|
188
188
|
' Catch by dispatcherId
|
|
189
|
-
m.dispatcherProvider.get(extraInfo?.
|
|
189
|
+
m.dispatcherProvider.get(extraInfo?.dispatcherId).asyncReducerCallback(msg)
|
|
190
190
|
else
|
|
191
191
|
dispatcherId = fieldId
|
|
192
192
|
dispatcherInstance = m.dispatcherProvider.get(dispatcherId)
|
|
@@ -72,9 +72,13 @@ namespace Rotor
|
|
|
72
72
|
|
|
73
73
|
' Framework Integration
|
|
74
74
|
getFrameworkInstance as Function ' Get framework instance
|
|
75
|
-
getDispatcher as Function
|
|
76
|
-
createDispatcher as Function
|
|
77
|
-
animator as Function
|
|
75
|
+
getDispatcher as Function ' Get dispatcher facade by ID
|
|
76
|
+
createDispatcher as Function ' Create dispatcher
|
|
77
|
+
animator as Function ' Get animator factory
|
|
78
|
+
|
|
79
|
+
' i18n Integration
|
|
80
|
+
getLocale as String ' Get locale e.g: "en_US"
|
|
81
|
+
isRTL as Boolean ' Indicates whether the current locale uses right-to-left text
|
|
78
82
|
|
|
79
83
|
' =============================================================
|
|
80
84
|
' PLUGIN METHODS
|
|
@@ -96,6 +96,18 @@ namespace Rotor.ViewBuilder
|
|
|
96
96
|
return m.getFrameworkInstance().builder.widgetTree.getSubtreeClone(searchPattern, keyPathList, m.parentHID)
|
|
97
97
|
end function
|
|
98
98
|
|
|
99
|
+
' Get isRTL flag
|
|
100
|
+
widget.isRtl = function() as boolean
|
|
101
|
+
i18nService = m.getFrameworkInstance().i18nService
|
|
102
|
+
return i18nService.getIsRtl()
|
|
103
|
+
end function
|
|
104
|
+
|
|
105
|
+
' Get locale string
|
|
106
|
+
widget.getLocale = function() as string
|
|
107
|
+
i18nService = m.getFrameworkInstance().i18nService
|
|
108
|
+
return i18nService.getLocale()
|
|
109
|
+
end function
|
|
110
|
+
|
|
99
111
|
' render - Renders widget updates (self, descendants, or children) *'
|
|
100
112
|
widget.render = sub(payloads as dynamic, params = {} as object)
|
|
101
113
|
for each payload in Rotor.Utils.ensureArray(payloads)
|
|
@@ -133,6 +145,18 @@ namespace Rotor.ViewBuilder
|
|
|
133
145
|
return m.getFrameworkInstance().dispatcherProvider.getFacade(dispatcherId, m)
|
|
134
146
|
end function
|
|
135
147
|
|
|
148
|
+
' Dispatch shortcut - Dispatches an event via specific dispatcher'
|
|
149
|
+
widget.dispatchTo = sub(dispatcherId as string, dispatchObject as object)
|
|
150
|
+
dispatcherFaced = m.getDispatcher(dispatcherId)
|
|
151
|
+
dispatcherFaced.dispatch(dispatchObject)
|
|
152
|
+
end sub
|
|
153
|
+
|
|
154
|
+
' Listen shortcut - Listen to specific dispatcher'
|
|
155
|
+
widget.getStateFrom = function(dispatcherId as string)
|
|
156
|
+
dispatcherFaced = m.getDispatcher(dispatcherId)
|
|
157
|
+
return dispatcherFaced.getState()
|
|
158
|
+
end function
|
|
159
|
+
|
|
136
160
|
' animator - Gets animator factory by ID *'
|
|
137
161
|
widget.animator = function(animatorId) as object
|
|
138
162
|
return m.getFrameworkInstance().animatorProvider.getFactory(animatorId, m)
|
|
@@ -159,16 +183,6 @@ namespace Rotor.ViewBuilder
|
|
|
159
183
|
keysPaths = Rotor.Utils.isString(config.i18n?.path) ? config.i18n.path : Rotor.Utils.isArray(config.i18n?.paths) ? config.i18n.paths : invalid
|
|
160
184
|
widget.viewModelState.l10n = i18nService.getL10n(keysPaths)
|
|
161
185
|
|
|
162
|
-
' Include isRTL flag if requested
|
|
163
|
-
if config?.i18n?.includeIsRtl = true
|
|
164
|
-
widget.viewModelState.isRTL = i18nService.getIsRtl()
|
|
165
|
-
end if
|
|
166
|
-
|
|
167
|
-
' Include locale string if requested
|
|
168
|
-
if config?.i18n?.includeLocale = true
|
|
169
|
-
widget.viewModelState.locale = i18nService.getLocale()
|
|
170
|
-
end if
|
|
171
|
-
|
|
172
186
|
' Call lifecycle hook before template compilation
|
|
173
187
|
widget.onCreateView()
|
|
174
188
|
|
|
@@ -32,8 +32,6 @@ namespace Rotor.ViewBuilder
|
|
|
32
32
|
sub init(frameworkInstance as Rotor.Framework)
|
|
33
33
|
' Set current locale from framework device info
|
|
34
34
|
currentLocale = frameworkInstance.getInfo().device.currentLocale
|
|
35
|
-
' Store rootWidget reference
|
|
36
|
-
m.rootWidget = frameworkInstance.getRootWidget()
|
|
37
35
|
' Store current locale
|
|
38
36
|
m.setLocale(currentLocale)
|
|
39
37
|
end sub
|
|
@@ -46,9 +44,6 @@ namespace Rotor.ViewBuilder
|
|
|
46
44
|
sub setLocale(locale as string)
|
|
47
45
|
m.locale = locale
|
|
48
46
|
m.isRTL = m.detectRTL(locale)
|
|
49
|
-
' update root widget's locale and isRTL
|
|
50
|
-
m.rootWidget.viewModelState.locale = m.locale
|
|
51
|
-
m.rootWidget.viewModelState.isRTL = m.isRTL
|
|
52
47
|
end sub
|
|
53
48
|
|
|
54
49
|
' ---------------------------------------------------------------------
|
|
@@ -88,10 +83,6 @@ namespace Rotor.ViewBuilder
|
|
|
88
83
|
'
|
|
89
84
|
sub setL10n(l10n as object)
|
|
90
85
|
m.l10n = l10n
|
|
91
|
-
|
|
92
|
-
' Update root widget's l10n reference
|
|
93
|
-
m.rootWidget.viewModelState.l10n = m.l10n
|
|
94
|
-
|
|
95
86
|
m.refreshCache()
|
|
96
87
|
end sub
|
|
97
88
|
|
|
@@ -102,7 +93,6 @@ namespace Rotor.ViewBuilder
|
|
|
102
93
|
'
|
|
103
94
|
sub extendL10n(l10n as object)
|
|
104
95
|
Rotor.Utils.deepExtendAA(m.l10n, l10n)
|
|
105
|
-
m.rootWidget.viewModelState.l10n = m.l10n
|
|
106
96
|
m.refreshCache()
|
|
107
97
|
end sub
|
|
108
98
|
|
|
@@ -110,7 +100,6 @@ namespace Rotor.ViewBuilder
|
|
|
110
100
|
' destroy - Cleans up cache and l10n data
|
|
111
101
|
'
|
|
112
102
|
sub destroy()
|
|
113
|
-
m.rootWidget = invalid
|
|
114
103
|
m.cache.Clear()
|
|
115
104
|
m.l10n.Clear()
|
|
116
105
|
end sub
|
|
@@ -972,32 +972,34 @@ namespace Rotor
|
|
|
972
972
|
|
|
973
973
|
refSegmentTop = focused.metrics.segments[Rotor.Const.Segment.TOP]
|
|
974
974
|
refSegmentRight = focused.metrics.segments[Rotor.Const.Segment.RIGHT]
|
|
975
|
+
refSegmentLeft = focused.metrics.segments[Rotor.Const.Segment.LEFT]
|
|
976
|
+
refSegmentBottom = focused.metrics.segments[Rotor.Const.Segment.BOTTOM]
|
|
975
977
|
referencePoint = { x: (refSegmentTop.x1 + refSegmentRight.x2) / 2, y: (refSegmentTop.y1 + refSegmentRight.y2) / 2 }
|
|
976
978
|
|
|
977
979
|
validators = {
|
|
978
980
|
|
|
979
|
-
"left": function(referencePoint as object, segments as object) as object
|
|
981
|
+
"left": function(referencePoint as object, segments as object, refSegmentLeft as object, refSegmentRight as object) as object
|
|
980
982
|
right = segments[Rotor.Const.Segment.RIGHT]
|
|
981
|
-
'
|
|
982
|
-
return right.
|
|
983
|
+
' Candidate's right edge must be strictly left of focused element's left edge
|
|
984
|
+
return right.x2 <= refSegmentLeft.x1 ? { isValid: true, segment: right } : { isValid: false }
|
|
983
985
|
end function,
|
|
984
986
|
|
|
985
|
-
"up": function(referencePoint as object, segments as object) as object
|
|
987
|
+
"up": function(referencePoint as object, segments as object, refSegmentTop as object, refSegmentBottom as object) as object
|
|
986
988
|
bottom = segments[Rotor.Const.Segment.BOTTOM]
|
|
987
|
-
'
|
|
988
|
-
return bottom.
|
|
989
|
+
' Candidate's bottom edge must be strictly above focused element's top edge
|
|
990
|
+
return bottom.y2 <= refSegmentTop.y1 ? { isValid: true, segment: bottom } : { isValid: false }
|
|
989
991
|
end function,
|
|
990
992
|
|
|
991
|
-
"right": function(referencePoint as object, segments as object) as object
|
|
993
|
+
"right": function(referencePoint as object, segments as object, refSegmentLeft as object, refSegmentRight as object) as object
|
|
992
994
|
left = segments[Rotor.Const.Segment.LEFT]
|
|
993
|
-
'
|
|
994
|
-
return left.x1 >=
|
|
995
|
+
' Candidate's left edge must be strictly right of focused element's right edge
|
|
996
|
+
return left.x1 >= refSegmentRight.x2 ? { isValid: true, segment: left } : { isValid: false }
|
|
995
997
|
end function,
|
|
996
998
|
|
|
997
|
-
"down": function(referencePoint as object, segments as object) as object
|
|
999
|
+
"down": function(referencePoint as object, segments as object, refSegmentTop as object, refSegmentBottom as object) as object
|
|
998
1000
|
top = segments[Rotor.Const.Segment.TOP]
|
|
999
|
-
'
|
|
1000
|
-
return top.y1 >=
|
|
1001
|
+
' Candidate's top edge must be strictly below focused element's bottom edge
|
|
1002
|
+
return top.y1 >= refSegmentBottom.y2 ? { isValid: true, segment: top } : { isValid: false }
|
|
1001
1003
|
end function
|
|
1002
1004
|
}
|
|
1003
1005
|
segments = {}
|
|
@@ -1006,7 +1008,12 @@ namespace Rotor
|
|
|
1006
1008
|
if HID <> focused.HID
|
|
1007
1009
|
focusItem = m.focusItemStack.get(HID)
|
|
1008
1010
|
focusItem.refreshBounding()
|
|
1009
|
-
|
|
1011
|
+
' Pass appropriate reference segments based on direction
|
|
1012
|
+
if direction = "left" or direction = "right"
|
|
1013
|
+
result = validator(referencePoint, focusItem.metrics.segments, refSegmentLeft, refSegmentRight)
|
|
1014
|
+
else ' up or down
|
|
1015
|
+
result = validator(referencePoint, focusItem.metrics.segments, refSegmentTop, refSegmentBottom)
|
|
1016
|
+
end if
|
|
1010
1017
|
if result.isValid
|
|
1011
1018
|
segments[HID] = result.segment
|
|
1012
1019
|
end if
|