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.
Files changed (39) hide show
  1. package/LICENSE.md +21 -0
  2. package/README.md +120 -0
  3. package/package.json +59 -0
  4. package/src/source/RotorFramework.bs +654 -0
  5. package/src/source/RotorFrameworkTask.bs +278 -0
  6. package/src/source/base/BaseModel.bs +52 -0
  7. package/src/source/base/BasePlugin.bs +48 -0
  8. package/src/source/base/BaseReducer.bs +184 -0
  9. package/src/source/base/BaseStack.bs +92 -0
  10. package/src/source/base/BaseViewModel.bs +124 -0
  11. package/src/source/base/BaseWidget.bs +104 -0
  12. package/src/source/base/DispatcherCreator.bs +193 -0
  13. package/src/source/base/DispatcherExternal.bs +260 -0
  14. package/src/source/base/ListenerForDispatchers.bs +246 -0
  15. package/src/source/engine/Constants.bs +74 -0
  16. package/src/source/engine/animator/Animator.bs +334 -0
  17. package/src/source/engine/builder/Builder.bs +213 -0
  18. package/src/source/engine/builder/NodePool.bs +236 -0
  19. package/src/source/engine/builder/PluginAdapter.bs +139 -0
  20. package/src/source/engine/builder/PostProcessor.bs +331 -0
  21. package/src/source/engine/builder/Processor.bs +156 -0
  22. package/src/source/engine/builder/Tree.bs +278 -0
  23. package/src/source/engine/builder/TreeBase.bs +313 -0
  24. package/src/source/engine/builder/WidgetCreate.bs +322 -0
  25. package/src/source/engine/builder/WidgetRemove.bs +72 -0
  26. package/src/source/engine/builder/WidgetUpdate.bs +113 -0
  27. package/src/source/engine/providers/Dispatcher.bs +72 -0
  28. package/src/source/engine/providers/DispatcherProvider.bs +95 -0
  29. package/src/source/engine/services/I18n.bs +169 -0
  30. package/src/source/libs/animate/Animate.bs +753 -0
  31. package/src/source/libs/animate/LICENSE.txt +21 -0
  32. package/src/source/plugins/DispatcherProviderPlugin.bs +127 -0
  33. package/src/source/plugins/FieldsPlugin.bs +180 -0
  34. package/src/source/plugins/FocusPlugin.bs +1522 -0
  35. package/src/source/plugins/FontStylePlugin.bs +159 -0
  36. package/src/source/plugins/ObserverPlugin.bs +548 -0
  37. package/src/source/utils/ArrayUtils.bs +495 -0
  38. package/src/source/utils/GeneralUtils.bs +181 -0
  39. package/src/source/utils/NodeUtils.bs +180 -0
@@ -0,0 +1,213 @@
1
+ import "Processor.bs"
2
+ import "PostProcessor.bs"
3
+ import "NodePool.bs"
4
+ import "Tree.bs"
5
+ import "../../base/BaseWidget.bs"
6
+ import "WidgetCreate.bs"
7
+ import "WidgetUpdate.bs"
8
+ import "WidgetRemove.bs"
9
+ import "PluginAdapter.bs"
10
+
11
+ namespace Rotor.ViewBuilder
12
+
13
+ ' =====================================================================
14
+ ' Builder - Core ViewBuilder engine that orchestrates widget lifecycle operations
15
+ '
16
+ ' Manages widget tree, node pool, plugin adapter, and rendering queue.
17
+ ' Handles create, update, and remove operations with post-processing support.
18
+ ' =====================================================================
19
+ class Builder
20
+
21
+ widgetTree = new Rotor.ViewBuilder.WidgetTree()
22
+
23
+ pluginAdapter = new Rotor.ViewBuilder.pluginAdapter()
24
+
25
+ processor = new Rotor.ViewBuilder.Processor()
26
+ postProcessor = new Rotor.ViewBuilder.PostProcessor()
27
+ postProcessBuffer = new Rotor.ViewBuilder.postProcessBuffer()
28
+
29
+ nodePool = new Rotor.ViewBuilder.NodePool()
30
+
31
+ frameworkInstance as Rotor.Framework
32
+
33
+ isRenderProcessing = false
34
+ renderQueue = []
35
+ callbackQueue = []
36
+
37
+ ' ---------------------------------------------------------------------
38
+ ' init - Initializes the builder and all its subsystems
39
+ '
40
+ ' @param {object} frameworkInstance - Reference to the framework instance
41
+ '
42
+ sub init(frameworkInstance as object)
43
+ m.frameworkInstance = frameworkInstance
44
+ m.nodePool.init(frameworkInstance)
45
+ m.widgetTree.init()
46
+ m.pluginAdapter.init(frameworkInstance)
47
+ m.processor.init(frameworkInstance)
48
+ m.postProcessor.init(frameworkInstance)
49
+ m.postProcessBuffer.init(frameworkInstance)
50
+ end sub
51
+
52
+ ' ---------------------------------------------------------------------
53
+ ' destroy - Destroys the builder and cleans up all resources
54
+ '
55
+ sub destroy()
56
+ ' remove all widgets
57
+ rootKeys = m.widgetTree.tree.children.Keys()
58
+ m.erase(rootKeys, true)
59
+ ' destroy builder engine
60
+ m.pluginAdapter.destroy()
61
+ m.processor.destroy()
62
+ m.postProcessor.destroy()
63
+ m.postProcessBuffer.destroy()
64
+ m.widgetTree.destroy()
65
+ m.nodePool.destroy()
66
+ ' remove references
67
+ m.frameworkInstance = invalid
68
+ end sub
69
+
70
+ timer = CreateObject("roTimespan")
71
+
72
+ ' ---------------------------------------------------------------------
73
+ ' renderQueueFlush - Flushes the render queue and processes next queued render
74
+ '
75
+ sub renderQueueFlush()
76
+ if m.renderQueue.Count() > 0
77
+ nextQueuedConfig = m.renderQueue.shift()
78
+ m.renderProcessor(nextQueuedConfig.payload, nextQueuedConfig.params, true)
79
+ end if
80
+ end sub
81
+
82
+ ' ---------------------------------------------------------------------
83
+ ' render - Public render method that processes payloads through the render processor
84
+ '
85
+ ' @param {dynamic} payloads - Widget configuration payload(s) to render
86
+ ' @param {object} params - Optional rendering parameters (default: {})
87
+ '
88
+ sub render(payloads as dynamic, params = {} as object)
89
+ ' for each payload in Rotor.Utils.ensureArray(payloads)
90
+ ' m.renderProcessor(payload, params)
91
+ m.renderProcessor(payloads, params)
92
+ ' end for
93
+ end sub
94
+
95
+ ' ---------------------------------------------------------------------
96
+ ' renderProcessor - Core rendering processor that handles queuing and execution
97
+ '
98
+ ' @param {dynamic} payload - Widget configuration payload to process
99
+ ' @param {object} params - Rendering parameters (default: {})
100
+ ' @param {boolean} isNextProcess - Whether this is next in queue (default: false)
101
+ '
102
+ sub renderProcessor(payload as dynamic, params = {} as object, isNextProcess = false as boolean)
103
+ ' call custom callback
104
+ if Rotor.Utils.isValid(params.callback)
105
+ m.callbackQueue.push({
106
+ callback: params.callback,
107
+ callbackScope: params.callbackScope
108
+ })
109
+ params.delete("callback")
110
+ params.delete("callbackScope")
111
+ end if
112
+
113
+ renderingDisabled = m.frameworkInstance.enableRendering = false and not(params?.enableRenderBeforeReady ?? false)
114
+ if (m.isRenderProcessing = true and isNextProcess = false) or renderingDisabled
115
+ m.renderQueue.push({
116
+ payload: payload,
117
+ params: params
118
+ })
119
+ return
120
+ end if
121
+ m.isRenderProcessing = true
122
+
123
+ m.timer.Mark()
124
+ m.processor.run(m.postProcessBuffer, "0", payload, params)
125
+ m.timer.Mark()
126
+ m.postProcessor.run(m.postProcessBuffer)
127
+ m.postProcessBuffer.clearLifeCycleBuffers()
128
+
129
+ if m.renderQueue.Count() > 0
130
+ nextQueuedConfig = m.renderQueue.shift()
131
+ m.renderProcessor(nextQueuedConfig.payload, nextQueuedConfig.params, true)
132
+ else
133
+
134
+ m.isRenderProcessing = false
135
+
136
+ while m.callbackQueue.Count() > 0
137
+ callbackObj = m.callbackQueue.shift()
138
+ Rotor.Utils.callbackScoped(callbackObj.callback, callbackObj.callbackScope)
139
+ callbackObj.callbackScope = invalid
140
+ end while
141
+
142
+ end if
143
+
144
+ end sub
145
+
146
+ ' ---------------------------------------------------------------------
147
+ ' erase - Erases widgets from the tree by ID or configuration
148
+ '
149
+ ' @param {dynamic} payload - Widget ID(s) or configuration object(s) to erase
150
+ ' @param {boolean} shouldSkipNodePool - Whether to skip returning nodes to pool (default: false)
151
+ ' @param {string} HID - Hierarchical ID context for search (default: "0")
152
+ '
153
+ sub erase (payload as dynamic, shouldSkipNodePool = false as boolean, HID = "0" as string)
154
+
155
+ ' normalize shortcuts
156
+
157
+ identifiersToErase = []
158
+ if Rotor.Utils.isString(payload)
159
+ identifiersToErase.push(payload)
160
+ else if Rotor.Utils.isArray(payload)
161
+ for each item in payload
162
+ if Rotor.Utils.isString(item)
163
+ identifiersToErase.push(item)
164
+ else if Rotor.Utils.isAssociativeArray(item)
165
+ identifiersToErase.push(item?.id)
166
+ end if
167
+ end for
168
+ else if Rotor.Utils.isAssociativeArray(payload)
169
+ for each key in payload
170
+ if key = "id"
171
+ identifiersToErase.push(key)
172
+ else
173
+ identifiersToErase.push(payload[key])
174
+ end if
175
+ end for
176
+ end if
177
+
178
+ widgetsToRemove = []
179
+
180
+ for each id in identifiersToErase
181
+ result = m.widgetTree.find(id, HID)
182
+ if result <> invalid
183
+ widgetsToRemove.append(result)
184
+ end if
185
+ end for
186
+
187
+ if widgetsToRemove.Count() > 0
188
+
189
+ renderObject = []
190
+
191
+ for each widget in widgetsToRemove
192
+
193
+ if m.widgetTree.hasByHID(widget.HID)
194
+ renderObject.push({
195
+ HID: widget.HID,
196
+ parentHID: widget.parentHID,
197
+ id: widget.id,
198
+ markedToRemove: true,
199
+ shouldSkipNodePool: shouldSkipNodePool
200
+ })
201
+ end if
202
+
203
+ end for
204
+ ' renderObject.Clear()
205
+ m.render(renderObject)
206
+
207
+ end if
208
+
209
+ end sub
210
+
211
+ end class
212
+
213
+ end namespace
@@ -0,0 +1,236 @@
1
+ ' ===== NODE POOL =====
2
+ namespace Rotor.ViewBuilder
3
+
4
+ ' =====================================================================
5
+ ' NodePool - Manages reusable pools of SceneGraph nodes to improve performance. Optimized for pooling Group nodes and types extending Group (like Poster), as well as other common nodes (e.g., Rectangle, Label). Includes node reset logic upon release.
6
+ ' =====================================================================
7
+ class NodePool
8
+
9
+ frameworkInstance as Rotor.Framework
10
+
11
+ config = {
12
+ nodePool: [{
13
+ nodeType: "Group",
14
+ amount: 1
15
+ }, {
16
+ nodeType: "Rectangle",
17
+ amount: 1
18
+ }, {
19
+ nodeType: "Poster",
20
+ amount: 1
21
+ }, {
22
+ nodeType: "Label",
23
+ amount: 1
24
+ }
25
+ ]
26
+ }
27
+
28
+ pools = {}
29
+ poolInfo = {
30
+ totalAcquiredNodes: 0, ' Configured and acquired nodes from pools
31
+ totalReleasedNodes: 0, ' Released nodes back to pools
32
+ outOfStockNodes: {}, ' Nodes that out of stock
33
+ poolFulness: {} ' Current amount of nodes in each pool
34
+ }
35
+
36
+ ' ---------------------------------------------------------------------
37
+ ' new - Initializes the instance
38
+ '
39
+ ' @description Initializes the node pool
40
+ ' ---------------------------------------------------------------------
41
+ sub new()
42
+ end sub
43
+
44
+ ' ---------------------------------------------------------------------
45
+ ' init - Initializes the pool with framework instance reference
46
+ '
47
+
48
+ ' @param {Rotor.Framework} frameworkInstance - Framework instance reference
49
+ ' ---------------------------------------------------------------------
50
+ sub init(frameworkInstance as Rotor.Framework)
51
+ m.frameworkInstance = frameworkInstance
52
+ end sub
53
+
54
+ ' ---------------------------------------------------------------------
55
+ ' destroy - Cleans up framework instance reference and pools
56
+ ' ---------------------------------------------------------------------
57
+ sub destroy()
58
+ m.frameworkInstance = invalid
59
+ m.pools.Clear()
60
+ end sub
61
+
62
+ ' ---------------------------------------------------------------------
63
+ '
64
+
65
+ ' presetNodePool - Presets node pools with specified amounts of each node type
66
+ '
67
+ ' @param {object} config - Configuration with nodePool array
68
+ ' ---------------------------------------------------------------------
69
+ sub presetNodePool(config)
70
+ Rotor.Utils.deepExtendAA(m.config, config)
71
+ for each thisPool in m.config.nodePool
72
+ for index = 0 to thisPool.amount - 1
73
+ if not m.pools.doesExist(thisPool.nodeType)
74
+ m.pools[thisPool.nodeType] = []
75
+ #if debug
76
+ m.poolInfo.outOfStockNodes[thisPool.nodeType] = 0
77
+ #end if
78
+ end if
79
+ node = CreateObject("roSGNode", thisPool.nodeType)
80
+ m.pools[thisPool.nodeType].push(node)
81
+ end for
82
+ end for
83
+ end sub
84
+
85
+ ' ---------------------------------------------------------------------
86
+ ' acquireNode - Acquires a node from the pool or creates a new one
87
+ '
88
+
89
+ ' @param {string} nodeType - Type of SceneGraph node to acquire
90
+ ' @returns {object} SceneGraph node instance
91
+ ' ---------------------------------------------------------------------
92
+ function acquireNode(nodeType)
93
+ if m.pools.doesExist(nodeType) and m.pools[nodeType].Count() > 0
94
+ node = m.pools[nodeType].pop()
95
+ #if debug
96
+ m.poolInfo.totalAcquiredNodes++
97
+ #end if
98
+ else
99
+ node = CreateObject("roSGNode", nodeType)
100
+ #if debug
101
+ if m.pools.doesExist(nodeType)
102
+ m.poolInfo.outOfStockNodes[nodeType]++
103
+ end if
104
+ #end if
105
+ end if
106
+
107
+ return node
108
+ end function
109
+
110
+ ' ---------------------------------------------------------------------
111
+ ' releaseNodeBranch - Recursively releases a node and its children back to pools
112
+ '
113
+
114
+ ' @param {object} node - SceneGraph node to release
115
+ ' @param {object} parent - Parent node for removal (default: invalid)
116
+ ' ---------------------------------------------------------------------
117
+ sub releaseNodeBranch(node as object, parent = invalid as object)
118
+
119
+ children = node.getChildren(-1, 0)
120
+
121
+ if children <> invalid and children.count() > 0
122
+ for each child in children
123
+ m.releaseNodeBranch(child, node)
124
+ end for
125
+ end if
126
+
127
+ nodeType = node.subtype()
128
+
129
+ if m.pools.doesExist(nodeType)
130
+ if parent <> invalid
131
+ parent.removeChild(node)
132
+ end if
133
+ m.resetNode(node)
134
+ m.pools[nodeType].push(node)
135
+
136
+ #if debug
137
+ m.poolInfo.totalReleasedNodes++
138
+ #end if
139
+ end if
140
+
141
+ end sub
142
+
143
+ ' ---------------------------------------------------------------------
144
+ ' resetNode - Resets a node's properties to default values before returning to pool
145
+ '
146
+
147
+ ' @param {object} node - SceneGraph node to reset
148
+ ' @private
149
+ ' ---------------------------------------------------------------------
150
+ private sub resetNode(node as object)
151
+ if node = invalid then return
152
+
153
+ nodeType = node.subtype()
154
+
155
+ node.visible = true
156
+ node.opacity = 1.0
157
+ node.translation = [0.0, 0.0]
158
+ node.rotation = 0.0
159
+ node.scale = [1.0, 1.0]
160
+ node.scaleRotateCenter = [0.0, 0.0]
161
+ node.inheritParentTransform = true
162
+ node.inheritParentOpacity = true
163
+ node.muteAudioGuide = false
164
+ node.renderPass = 0
165
+ node.enableRenderTracking = false
166
+ ' TODO: test below properties to reset
167
+ ' node.childRenderOrder = "renderLast"
168
+ ' node.clippingRect = [0.0, 0.0, 0.0, 0.0]
169
+
170
+ if nodeType = "Rectangle"
171
+ node.setFields({
172
+ color: "#FFFFFFFF",
173
+ width: 0,
174
+ height: 0,
175
+ blendingEnabled: true
176
+ })
177
+
178
+ else if nodeType = "Poster"
179
+ node.setFields({
180
+ uri: "",
181
+ width: 0,
182
+ height: 0,
183
+ loadWidth: 0,
184
+ loadHeight: 0,
185
+ blendColor: "#FFFFFFFF",
186
+ loadDisplayMode: "noScale",
187
+ loadSync: false,
188
+ loadingBitmapUri: "",
189
+ failedBitmapUri: "",
190
+ loadingBitmapOpacity: 1.0,
191
+ failedBitmapOpacity: 1.0
192
+ })
193
+
194
+ else if nodeType = "Label"
195
+ node.setFields({
196
+ text: "",
197
+ color: "#FFFFFFFF",
198
+ horizAlign: "left",
199
+ vertAlign: "top",
200
+ width: 0,
201
+ height: 0,
202
+ wrap: false,
203
+ numLines: 0,
204
+ lineSpacing: 0.0,
205
+ maxLines: 0,
206
+ ellipsisText: "",
207
+ font: "font:MediumSystemFont"
208
+ })
209
+
210
+ end if
211
+ end sub
212
+
213
+ ' ---------------------------------------------------------------------
214
+ ' getNodePoolInfo - Gets node pool statistics (debug mode only)
215
+ '
216
+
217
+ ' @returns {object} Pool information with counts and stats
218
+ ' ---------------------------------------------------------------------
219
+ function getNodePoolInfo()
220
+ info = {}
221
+ #if debug
222
+ for each nodeType in m.pools
223
+ m.poolInfo.poolFulness[nodeType] = m.pools[nodeType].Count()
224
+ end for
225
+ info = m.poolInfo
226
+ #end if
227
+ return info
228
+ end function
229
+
230
+
231
+ end class
232
+
233
+
234
+
235
+
236
+ end namespace
@@ -0,0 +1,139 @@
1
+ namespace Rotor.ViewBuilder
2
+
3
+ ' =====================================================================
4
+ ' PluginHookClass - Container for plugin hook metadata and handler function
5
+ ' =====================================================================
6
+ class PluginHookClass
7
+
8
+ pluginKey as string
9
+ handlerFn as function
10
+
11
+ ' ---------------------------------------------------------------------
12
+ ' new - Initializes the instance
13
+ '
14
+ ' @description Initializes plugin hook with key and handler
15
+ ' @param {object} config - Configuration with pluginKey and handlerFn
16
+ ' ---------------------------------------------------------------------
17
+ sub new(config)
18
+ m.pluginKey = config.pluginKey
19
+ m.handlerFn = config.handlerFn
20
+ end sub
21
+
22
+ end class
23
+
24
+ ' =====================================================================
25
+ ' pluginAdapter - Manages plugin registration and lifecycle hook integration. Maintains plugin instances and maps lifecycle hooks to plugin handlers.
26
+ ' =====================================================================
27
+ class pluginAdapter
28
+
29
+ plugins as object
30
+
31
+ ' ---------------------------------------------------------------------
32
+ ' new - Initializes the instance
33
+ '
34
+ ' @description Initializes plugin hook storage for all lifecycle types
35
+ ' ---------------------------------------------------------------------
36
+ sub new()
37
+
38
+ ' dedicated plugin lifecycle hooks
39
+ for each hookType in [
40
+ Rotor.Const.LifeCycleHookType.BEFORE_MOUNT,
41
+ Rotor.Const.LifeCycleHookType.AFTER_MOUNTED,
42
+ Rotor.Const.LifeCycleHookType.BEFORE_UPDATE,
43
+ Rotor.Const.LifeCycleHookType.AFTER_UPDATED,
44
+ Rotor.Const.LifeCycleHookType.BEFORE_DESTROY
45
+ ]
46
+ m.pluginHooks[hookType] = {}
47
+ end for
48
+
49
+ end sub
50
+
51
+ ' Plugins has limited type of hooks to lifecycle (check the list below)
52
+ pluginHooks = {}
53
+ pluginKeyList = CreateObject("roList")
54
+
55
+ frameworkInstance as Rotor.Framework
56
+
57
+ ' ---------------------------------------------------------------------
58
+ ' registerPlugins - Registers plugins and their lifecycle hooks with the framework
59
+ '
60
+
61
+ ' @param {object} pluginConfig - Plugin configuration object or array
62
+ ' ---------------------------------------------------------------------
63
+ sub registerPlugins (pluginConfig as object)
64
+
65
+ plugins = Rotor.Utils.ensureArray(pluginConfig)
66
+
67
+ for each plugin in plugins
68
+
69
+ ' Plugin key
70
+ pluginKey = plugin.key
71
+
72
+ ' add plugin key to tree viewModelState
73
+ ' obj = Rotor.Utils.wrapObject(pluginKey, {})
74
+
75
+ ' create workspace for plugin in root of widget tree
76
+ ' m.frameworkInstance.builder.widgetTree.tree.extendContext(obj)
77
+
78
+ ' Setup hooks
79
+ hooks = plugin.hooks
80
+ if Rotor.Utils.isValid(hooks) and hooks.Count() > 0
81
+
82
+ for each hook in hooks
83
+ pluginHook = new PluginHookClass({
84
+ pluginKey: pluginKey,
85
+ handlerFn: hooks[hook]
86
+ })
87
+ m.pluginHooks[hook][pluginKey] = pluginHook
88
+ end for
89
+
90
+ end if
91
+
92
+ ' add plugin instance to viewBuilder
93
+ m.pluginKeyList.AddTail(pluginKey)
94
+ m.frameworkInstance.plugins[pluginKey] = plugin
95
+ m.frameworkInstance.plugins[pluginKey].frameworkInstance = m.frameworkInstance
96
+ if Rotor.Utils.isFunction(m.frameworkInstance.plugins[pluginKey].init)
97
+ m.frameworkInstance.plugins[pluginKey].init()
98
+ end if
99
+ end for
100
+
101
+ end sub
102
+
103
+ ' ---------------------------------------------------------------------
104
+ ' destroyPlugins - Destroys all registered plugins
105
+ ' ---------------------------------------------------------------------
106
+ sub destroyPlugins()
107
+ if m.plugins.Count() > 0
108
+ for each plugin in m.plugins
109
+ plugin.frameworkInstance = invalid
110
+ destroy = plugin.destroy
111
+ if Rotor.Utils.isFunction(plugin.destroy)
112
+ destroy()
113
+ end if
114
+ end for
115
+ end if
116
+ end sub
117
+
118
+ ' ---------------------------------------------------------------------
119
+ '
120
+
121
+ ' init - Initializes the adapter with framework instance reference
122
+ '
123
+ ' @param {object} frameworkInstance - Framework instance reference
124
+ ' ---------------------------------------------------------------------
125
+ sub init(frameworkInstance as object)
126
+ m.frameworkInstance = frameworkInstance
127
+ end sub
128
+
129
+ ' ---------------------------------------------------------------------
130
+ ' destroy - Cleans up framework instance reference
131
+ '
132
+ ' ---------------------------------------------------------------------
133
+ sub destroy()
134
+ m.frameworkInstance = invalid
135
+ end sub
136
+
137
+ end class
138
+
139
+ end namespace