@rbxts/app-forge 0.7.0 → 0.7.2-alpha.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 +26 -13
- package/out/react/hooks/usePx.d.ts +15 -0
- package/out/react/hooks/usePx.luau +117 -0
- package/out/vide/classes/renders.d.ts +4 -0
- package/out/vide/classes/renders.luau +50 -45
- package/out/vide/classes/rules/exclusiveGroup.luau +22 -15
- package/out/vide/classes/rules/init.luau +17 -13
- package/out/vide/classes/rules/parent.luau +8 -4
- package/out/vide/context.d.ts +1 -1
- package/out/vide/debug/debugger.d.ts +15 -0
- package/out/vide/debug/debugger.luau +83 -0
- package/out/vide/debug/index.d.ts +3 -0
- package/out/vide/debug/init.luau +8 -0
- package/out/vide/debug/logger.d.ts +5 -0
- package/out/vide/debug/logger.luau +37 -0
- package/out/vide/decorator.d.ts +8 -0
- package/out/vide/decorator.luau +41 -4
- package/out/vide/hooks/useAppContext.d.ts +1 -1
- package/out/vide/hooks/useAppContext.luau +5 -1
- package/out/vide/hooks/useEventListener.d.ts +17 -0
- package/out/vide/hooks/useEventListener.luau +61 -0
- package/out/vide/hooks/usePx.d.ts +11 -0
- package/out/vide/hooks/usePx.luau +103 -0
- package/out/vide/index.d.ts +5 -2
- package/out/vide/init.luau +85 -24
- package/out/vide/types.d.ts +1 -1
- package/package.json +9 -3
package/README.md
CHANGED
|
@@ -32,14 +32,34 @@ If you’ve ever ended up with tangled UI state, duplicated visibility logic, or
|
|
|
32
32
|
|
|
33
33
|
## 📦 Installation
|
|
34
34
|
|
|
35
|
+
### Using **bun**
|
|
36
|
+
|
|
35
37
|
```bash
|
|
36
38
|
bun add @rbxts/app-forge
|
|
37
39
|
```
|
|
38
40
|
|
|
39
|
-
Peer dependencies:
|
|
41
|
+
Peer dependencies (choose one renderer):
|
|
40
42
|
|
|
41
43
|
```bash
|
|
42
|
-
bun add @rbxts/vide
|
|
44
|
+
bun add @rbxts/vide
|
|
45
|
+
# or
|
|
46
|
+
bun add @rbxts/react
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
---
|
|
50
|
+
|
|
51
|
+
### Using **npm**
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
npm install @rbxts/app-forge
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
Peer dependencies (choose one renderer):
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
npm install @rbxts/vide
|
|
61
|
+
# or
|
|
62
|
+
npm install @rbxts/react
|
|
43
63
|
```
|
|
44
64
|
|
|
45
65
|
---
|
|
@@ -127,9 +147,7 @@ forge.mount(
|
|
|
127
147
|
ResetOnSpawn={false}
|
|
128
148
|
/>
|
|
129
149
|
),
|
|
130
|
-
|
|
131
|
-
props,
|
|
132
|
-
},
|
|
150
|
+
props,
|
|
133
151
|
Players.LocalPlayer.WaitForChild("PlayerGui"),
|
|
134
152
|
);
|
|
135
153
|
```
|
|
@@ -157,9 +175,7 @@ forge.mount(
|
|
|
157
175
|
ResetOnSpawn={false}
|
|
158
176
|
/>
|
|
159
177
|
),
|
|
160
|
-
|
|
161
|
-
props,
|
|
162
|
-
},
|
|
178
|
+
props,
|
|
163
179
|
Players.LocalPlayer.WaitForChild("PlayerGui"),
|
|
164
180
|
);
|
|
165
181
|
```
|
|
@@ -177,9 +193,7 @@ This:
|
|
|
177
193
|
```ts
|
|
178
194
|
forge.mount(
|
|
179
195
|
() => <screengui ResetOnSpawn={false} />,
|
|
180
|
-
|
|
181
|
-
props: {},
|
|
182
|
-
},
|
|
196
|
+
props,
|
|
183
197
|
playerGui,
|
|
184
198
|
);
|
|
185
199
|
```
|
|
@@ -352,7 +366,6 @@ AppForge provides `forge.story` for **isolated rendering**, commonly used with *
|
|
|
352
366
|
const forge = new CreateVideForge();
|
|
353
367
|
|
|
354
368
|
return forge.story({
|
|
355
|
-
forge,
|
|
356
369
|
props,
|
|
357
370
|
config: {
|
|
358
371
|
px: {
|
|
@@ -430,7 +443,7 @@ AppForge
|
|
|
430
443
|
|
|
431
444
|
## ⚛️ React Support (Planned)
|
|
432
445
|
|
|
433
|
-
AppForge is designed as a **renderer-agnostic App Manager**.
|
|
446
|
+
AppForge is designed as a **renderer-agnostic App Manager**.b
|
|
434
447
|
|
|
435
448
|
Currently:
|
|
436
449
|
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Scaled pixel unit helper.
|
|
3
|
+
*/
|
|
4
|
+
export declare const px: ((value: number) => number) & {
|
|
5
|
+
scale: (value: number) => number;
|
|
6
|
+
even: (value: number) => number;
|
|
7
|
+
floor: (value: number) => number;
|
|
8
|
+
ceil: (value: number) => number;
|
|
9
|
+
};
|
|
10
|
+
/**
|
|
11
|
+
* Initializes global px scaling.
|
|
12
|
+
*
|
|
13
|
+
* Should be called exactly once at app mount.
|
|
14
|
+
*/
|
|
15
|
+
export declare function usePx(target?: GuiObject | Camera, baseResolution?: Vector2, minScale?: number): void;
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
-- Compiled with roblox-ts v3.0.0
|
|
2
|
+
local TS = _G[script]
|
|
3
|
+
-- Services
|
|
4
|
+
local Workspace = TS.import(script, TS.getModule(script, "@rbxts", "services")).Workspace
|
|
5
|
+
-- React
|
|
6
|
+
local useEffect = TS.import(script, TS.getModule(script, "@rbxts", "react")).useEffect
|
|
7
|
+
--* Default reference resolution for px calculations
|
|
8
|
+
local BASE_RESOLUTION = Vector2.new(1920, 1080)
|
|
9
|
+
--* Minimum allowed scale to prevent unreadable UI
|
|
10
|
+
local MIN_SCALE = 0.5
|
|
11
|
+
--[[
|
|
12
|
+
*
|
|
13
|
+
* Interpolates between width- and height-based scaling.
|
|
14
|
+
* 0 = width-driven, 1 = height-driven
|
|
15
|
+
|
|
16
|
+
]]
|
|
17
|
+
local DOMINANT_AXIS = 0.5
|
|
18
|
+
local TARGET = Workspace.CurrentCamera
|
|
19
|
+
local SCALE = 1
|
|
20
|
+
local GLOBAL_INITIALIZED = false
|
|
21
|
+
--[[
|
|
22
|
+
*
|
|
23
|
+
* Assigns a call signature to an object.
|
|
24
|
+
|
|
25
|
+
]]
|
|
26
|
+
local function callable(callback, object)
|
|
27
|
+
return setmetatable(object, {
|
|
28
|
+
__call = function(_, ...)
|
|
29
|
+
local args = { ... }
|
|
30
|
+
return callback(unpack(args))
|
|
31
|
+
end,
|
|
32
|
+
})
|
|
33
|
+
end
|
|
34
|
+
--[[
|
|
35
|
+
*
|
|
36
|
+
* Scaled pixel unit helper.
|
|
37
|
+
|
|
38
|
+
]]
|
|
39
|
+
local px = callable(function(value)
|
|
40
|
+
return math.round(value * SCALE)
|
|
41
|
+
end, {
|
|
42
|
+
scale = function(value)
|
|
43
|
+
return value * SCALE
|
|
44
|
+
end,
|
|
45
|
+
even = function(value)
|
|
46
|
+
return math.round(value * SCALE * 0.5) * 2
|
|
47
|
+
end,
|
|
48
|
+
floor = function(value)
|
|
49
|
+
return math.floor(value * SCALE)
|
|
50
|
+
end,
|
|
51
|
+
ceil = function(value)
|
|
52
|
+
return math.ceil(value * SCALE)
|
|
53
|
+
end,
|
|
54
|
+
})
|
|
55
|
+
--[[
|
|
56
|
+
*
|
|
57
|
+
* Recalculates the current scale factor.
|
|
58
|
+
|
|
59
|
+
]]
|
|
60
|
+
local function calculateScale()
|
|
61
|
+
local target = TARGET
|
|
62
|
+
if not target then
|
|
63
|
+
return nil
|
|
64
|
+
end
|
|
65
|
+
local size = if target:IsA("Camera") then target.ViewportSize elseif target:IsA("GuiObject") then target.AbsoluteSize else nil
|
|
66
|
+
if not size then
|
|
67
|
+
return nil
|
|
68
|
+
end
|
|
69
|
+
if BASE_RESOLUTION.X <= 0 or BASE_RESOLUTION.Y <= 0 then
|
|
70
|
+
return nil
|
|
71
|
+
end
|
|
72
|
+
local width = math.log(size.X / BASE_RESOLUTION.X, 2)
|
|
73
|
+
local height = math.log(size.Y / BASE_RESOLUTION.Y, 2)
|
|
74
|
+
local centered = width + (height - width) * DOMINANT_AXIS
|
|
75
|
+
local scale = 2 ^ centered
|
|
76
|
+
SCALE = math.max(scale, MIN_SCALE)
|
|
77
|
+
end
|
|
78
|
+
--[[
|
|
79
|
+
*
|
|
80
|
+
* Initializes global px scaling.
|
|
81
|
+
*
|
|
82
|
+
* Should be called exactly once at app mount.
|
|
83
|
+
|
|
84
|
+
]]
|
|
85
|
+
local function usePx(target, baseResolution, minScale)
|
|
86
|
+
useEffect(function()
|
|
87
|
+
if GLOBAL_INITIALIZED then
|
|
88
|
+
warn("usePx() may only be called once globally")
|
|
89
|
+
return nil
|
|
90
|
+
end
|
|
91
|
+
GLOBAL_INITIALIZED = true
|
|
92
|
+
if baseResolution then
|
|
93
|
+
BASE_RESOLUTION = baseResolution
|
|
94
|
+
end
|
|
95
|
+
if minScale ~= nil then
|
|
96
|
+
MIN_SCALE = minScale
|
|
97
|
+
end
|
|
98
|
+
if target then
|
|
99
|
+
TARGET = target
|
|
100
|
+
end
|
|
101
|
+
local resolvedTarget = TARGET
|
|
102
|
+
if not resolvedTarget then
|
|
103
|
+
warn("usePx(): no valid target to observe")
|
|
104
|
+
return nil
|
|
105
|
+
end
|
|
106
|
+
local signal = if resolvedTarget:IsA("Camera") then resolvedTarget:GetPropertyChangedSignal("ViewportSize") else resolvedTarget:GetPropertyChangedSignal("AbsoluteSize")
|
|
107
|
+
local connection = signal:Connect(calculateScale)
|
|
108
|
+
calculateScale()
|
|
109
|
+
return function()
|
|
110
|
+
connection:Disconnect()
|
|
111
|
+
end
|
|
112
|
+
end, {})
|
|
113
|
+
end
|
|
114
|
+
return {
|
|
115
|
+
usePx = usePx,
|
|
116
|
+
px = px,
|
|
117
|
+
}
|
|
@@ -8,6 +8,10 @@ export default class Renders extends Rules {
|
|
|
8
8
|
render: Vide.Node;
|
|
9
9
|
}>;
|
|
10
10
|
constructor();
|
|
11
|
+
/**
|
|
12
|
+
* Entry point for mounting renders.
|
|
13
|
+
* Decides render strategy based on props.
|
|
14
|
+
*/
|
|
11
15
|
protected renderMount(this: AppForge, props: Types.Props.Main): Vide.Node;
|
|
12
16
|
private renderNames;
|
|
13
17
|
private collectByGroup;
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
-- Compiled with roblox-ts v3.0.0
|
|
2
2
|
local TS = _G[script]
|
|
3
|
-
-- Services
|
|
4
3
|
-- Packages
|
|
5
|
-
local usePx = TS.import(script, TS.getModule(script, "@rbxts", "loners-pretty-vide-utils").out).usePx
|
|
6
4
|
local _vide = TS.import(script, TS.getModule(script, "@rbxts", "vide").src)
|
|
7
5
|
local apply = _vide.apply
|
|
8
6
|
local create = _vide.create
|
|
9
7
|
-- Types
|
|
10
8
|
-- Components
|
|
11
9
|
local AppRegistry = TS.import(script, script.Parent.Parent, "decorator").AppRegistry
|
|
10
|
+
-- Hooks
|
|
11
|
+
local usePx = TS.import(script, script.Parent.Parent, "hooks", "usePx").usePx
|
|
12
12
|
-- Classes
|
|
13
13
|
local Rules = TS.import(script, script.Parent, "rules").default
|
|
14
14
|
local Renders
|
|
@@ -35,22 +35,28 @@ do
|
|
|
35
35
|
local render = _binding.render
|
|
36
36
|
local forge = _binding.forge
|
|
37
37
|
if not forge.__px then
|
|
38
|
+
local _debug = forge.debug
|
|
38
39
|
local _result = config
|
|
39
40
|
if _result ~= nil then
|
|
40
|
-
_result = _result.px
|
|
41
|
+
_result = _result.px
|
|
41
42
|
end
|
|
43
|
+
_debug:logTag("px", "global", "Initializing px scaling", _result)
|
|
42
44
|
local _result_1 = config
|
|
43
45
|
if _result_1 ~= nil then
|
|
44
|
-
_result_1 = _result_1.px.
|
|
46
|
+
_result_1 = _result_1.px.target
|
|
45
47
|
end
|
|
46
48
|
local _result_2 = config
|
|
47
49
|
if _result_2 ~= nil then
|
|
48
|
-
_result_2 = _result_2.px.
|
|
50
|
+
_result_2 = _result_2.px.resolution
|
|
49
51
|
end
|
|
50
|
-
|
|
52
|
+
local _result_3 = config
|
|
53
|
+
if _result_3 ~= nil then
|
|
54
|
+
_result_3 = _result_3.px.minScale
|
|
55
|
+
end
|
|
56
|
+
usePx(_result_1, _result_2, _result_3)
|
|
51
57
|
forge.__px = true
|
|
52
58
|
else
|
|
53
|
-
|
|
59
|
+
forge.debug:logTag("px", "global", "Skipped duplicate px initialization")
|
|
54
60
|
end
|
|
55
61
|
if render then
|
|
56
62
|
local _condition = render.name
|
|
@@ -58,34 +64,35 @@ do
|
|
|
58
64
|
_condition = render.group
|
|
59
65
|
end
|
|
60
66
|
if _condition ~= "" and _condition then
|
|
67
|
+
forge.debug:logTag("render", "global", "Rendering group by name", render)
|
|
61
68
|
return forge:renderGroupByName(props)
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
end
|
|
69
|
+
end
|
|
70
|
+
local _value = render.names and render.group
|
|
71
|
+
if _value ~= "" and _value then
|
|
72
|
+
forge.debug:logTag("render", "global", "Rendering group by names", render)
|
|
73
|
+
return forge:renderGroupByNames(props)
|
|
74
|
+
end
|
|
75
|
+
local _value_1 = render.name
|
|
76
|
+
if _value_1 ~= "" and _value_1 then
|
|
77
|
+
forge.debug:logTag("render", render.name, "Rendering single app")
|
|
78
|
+
return forge:renderApp(props)
|
|
79
|
+
end
|
|
80
|
+
if render.names then
|
|
81
|
+
forge.debug:logTag("render", "global", "Rendering multiple apps", render.names)
|
|
82
|
+
return forge:renderApps(props)
|
|
83
|
+
end
|
|
84
|
+
local _value_2 = render.group
|
|
85
|
+
if _value_2 ~= "" and _value_2 then
|
|
86
|
+
forge.debug:logTag("render", "global", "Rendering group", render.group)
|
|
87
|
+
return forge:renderGroup(props)
|
|
82
88
|
end
|
|
83
89
|
end
|
|
90
|
+
forge.debug:logTag("render", "global", "Rendering all apps")
|
|
84
91
|
return self:renderAll(props)
|
|
85
92
|
end
|
|
86
93
|
function Renders:renderNames(props, names, forge)
|
|
87
94
|
if #names == 0 then
|
|
88
|
-
error("No app names provided to
|
|
95
|
+
error("No app names provided to render", 2)
|
|
89
96
|
end
|
|
90
97
|
-- ▼ ReadonlyArray.map ▼
|
|
91
98
|
local _newValue = table.create(#names)
|
|
@@ -141,22 +148,21 @@ do
|
|
|
141
148
|
return _result
|
|
142
149
|
end
|
|
143
150
|
function Renders:renderApp(props)
|
|
144
|
-
local
|
|
145
|
-
local forge = _binding.forge
|
|
146
|
-
local render = _binding.render
|
|
147
|
-
local _name = render
|
|
151
|
+
local _name = props.render
|
|
148
152
|
if _name ~= nil then
|
|
149
153
|
_name = _name.name
|
|
150
154
|
end
|
|
151
155
|
local name = _name
|
|
152
156
|
if not (name ~= "" and name) then
|
|
153
|
-
error("
|
|
157
|
+
error("renderApp requires an app name", 2)
|
|
154
158
|
end
|
|
155
159
|
local appClass = AppRegistry[name]
|
|
156
160
|
if not appClass then
|
|
157
|
-
error(`App "{name}" not registered
|
|
161
|
+
error(`App "{name}" not registered`, 2)
|
|
158
162
|
end
|
|
159
|
-
|
|
163
|
+
self.debug:time("render", name)
|
|
164
|
+
if not (self.loaded[name] ~= nil) then
|
|
165
|
+
self.debug:logTag("render", name, "Creating render instance")
|
|
160
166
|
local render = appClass.constructor.new(props, name):render()
|
|
161
167
|
apply(render)({
|
|
162
168
|
Name = "Render",
|
|
@@ -169,19 +175,18 @@ do
|
|
|
169
175
|
Size = UDim2.fromScale(1, 1),
|
|
170
176
|
[0] = render,
|
|
171
177
|
})
|
|
172
|
-
local _loaded =
|
|
178
|
+
local _loaded = self.loaded
|
|
173
179
|
local _arg1 = {
|
|
174
180
|
container = container,
|
|
175
181
|
render = render,
|
|
176
182
|
}
|
|
177
183
|
_loaded[name] = _arg1
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
if not element then
|
|
181
|
-
error(`Failed to create instance for app "{name}"`)
|
|
184
|
+
else
|
|
185
|
+
self.debug:logTag("render", name, "Reusing existing render instance")
|
|
182
186
|
end
|
|
183
187
|
self:renderRules(name, props)
|
|
184
|
-
|
|
188
|
+
self.debug:timeEnd("render", name)
|
|
189
|
+
return self.loaded[name].container
|
|
185
190
|
end
|
|
186
191
|
function Renders:renderApps(props)
|
|
187
192
|
local _names = props.render
|
|
@@ -190,7 +195,7 @@ do
|
|
|
190
195
|
end
|
|
191
196
|
local names = _names
|
|
192
197
|
if not names then
|
|
193
|
-
error("
|
|
198
|
+
error("renderApps requires app names", 2)
|
|
194
199
|
end
|
|
195
200
|
return self:renderNames(props, names, self)
|
|
196
201
|
end
|
|
@@ -201,7 +206,7 @@ do
|
|
|
201
206
|
end
|
|
202
207
|
local group = _group
|
|
203
208
|
if not (group ~= "" and group) then
|
|
204
|
-
error("
|
|
209
|
+
error("renderGroup requires a group", 2)
|
|
205
210
|
end
|
|
206
211
|
local groups = self:normalizeGroups(group)
|
|
207
212
|
return self:renderNames(props, self:collectByGroup(groups), self)
|
|
@@ -211,7 +216,7 @@ do
|
|
|
211
216
|
local group = _binding.group
|
|
212
217
|
local name = _binding.name
|
|
213
218
|
if not (group ~= "" and group) or not (name ~= "" and name) then
|
|
214
|
-
error("Invalid renderGroupByName")
|
|
219
|
+
error("Invalid renderGroupByName call", 2)
|
|
215
220
|
end
|
|
216
221
|
local groups = self:normalizeGroups(group)
|
|
217
222
|
return self:renderNames(props, self:collectByGroup(groups, function(n)
|
|
@@ -223,7 +228,7 @@ do
|
|
|
223
228
|
local group = _binding.group
|
|
224
229
|
local names = _binding.names
|
|
225
230
|
if not (group ~= "" and group) or not names then
|
|
226
|
-
error("Invalid renderGroupByNames")
|
|
231
|
+
error("Invalid renderGroupByNames call", 2)
|
|
227
232
|
end
|
|
228
233
|
local groups = self:normalizeGroups(group)
|
|
229
234
|
return self:renderNames(props, self:collectByGroup(groups, function(n)
|
|
@@ -6,36 +6,43 @@ local AppRegistry = TS.import(script, script.Parent.Parent.Parent, "decorator").
|
|
|
6
6
|
local function ExclusiveGroupRule(entry, forge)
|
|
7
7
|
local _entry = entry
|
|
8
8
|
local entryApp = AppRegistry[_entry]
|
|
9
|
-
local
|
|
10
|
-
if
|
|
11
|
-
|
|
12
|
-
if
|
|
13
|
-
|
|
9
|
+
local _group = entryApp
|
|
10
|
+
if _group ~= nil then
|
|
11
|
+
_group = _group.rules
|
|
12
|
+
if _group ~= nil then
|
|
13
|
+
_group = _group.exclusiveGroup
|
|
14
14
|
end
|
|
15
15
|
end
|
|
16
|
-
|
|
16
|
+
local group = _group
|
|
17
|
+
if not (group ~= "" and group) then
|
|
17
18
|
return nil
|
|
18
19
|
end
|
|
19
|
-
local
|
|
20
|
-
|
|
21
|
-
if not entrySource then
|
|
20
|
+
local entryVisible = forge:getSource(entry)()
|
|
21
|
+
if not entryVisible then
|
|
22
22
|
return nil
|
|
23
23
|
end
|
|
24
|
+
forge.debug:logTag("rules", entry, "Exclusive group activated", group)
|
|
24
25
|
-- ▼ ReadonlyMap.forEach ▼
|
|
25
26
|
local _callback = function(app, name)
|
|
26
27
|
if name == entry then
|
|
27
28
|
return nil
|
|
28
29
|
end
|
|
29
|
-
local
|
|
30
|
-
if
|
|
31
|
-
|
|
30
|
+
local _result = app.rules
|
|
31
|
+
if _result ~= nil then
|
|
32
|
+
_result = _result.exclusiveGroup
|
|
32
33
|
end
|
|
33
|
-
if
|
|
34
|
+
if _result ~= group then
|
|
34
35
|
return nil
|
|
35
36
|
end
|
|
36
|
-
|
|
37
|
-
|
|
37
|
+
local visible = forge:getSource(name)()
|
|
38
|
+
if not visible then
|
|
39
|
+
return nil
|
|
38
40
|
end
|
|
41
|
+
forge.debug:logTag("rules", entry, "Closing app due to exclusive group", {
|
|
42
|
+
closed = name,
|
|
43
|
+
group = group,
|
|
44
|
+
})
|
|
45
|
+
forge:close(name, false)
|
|
39
46
|
end
|
|
40
47
|
for _k, _v in AppRegistry do
|
|
41
48
|
_callback(_v, _k, AppRegistry)
|
|
@@ -25,36 +25,40 @@ do
|
|
|
25
25
|
local _name = name
|
|
26
26
|
local appClass = AppRegistry[_name]
|
|
27
27
|
if not appClass then
|
|
28
|
-
error(`
|
|
28
|
+
error(`renderRules: App "{name}" not registered`, 2)
|
|
29
29
|
end
|
|
30
|
-
local
|
|
31
|
-
if
|
|
32
|
-
|
|
30
|
+
local rules = appClass.rules
|
|
31
|
+
if not rules then
|
|
32
|
+
return nil
|
|
33
33
|
end
|
|
34
|
-
|
|
34
|
+
-- Parent Anchor
|
|
35
|
+
local _condition = rules.parent
|
|
35
36
|
if _condition ~= "" and _condition then
|
|
36
|
-
_condition = not
|
|
37
|
+
_condition = not rules.detach
|
|
37
38
|
end
|
|
38
39
|
if _condition ~= "" and _condition then
|
|
39
|
-
self:
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
_result_1 = _result_1.index
|
|
40
|
+
self.debug:logTag("rules", name, "Applying parent anchor", {
|
|
41
|
+
parent = rules.parent,
|
|
42
|
+
})
|
|
43
|
+
self:anchor(name, rules.parent, props)
|
|
44
44
|
end
|
|
45
|
-
|
|
46
|
-
|
|
45
|
+
-- Index
|
|
46
|
+
if rules.index ~= nil then
|
|
47
|
+
self.debug:logTag("rules", name, "Applying ZIndex", rules.index)
|
|
48
|
+
self:index(name, rules.index)
|
|
47
49
|
end
|
|
48
50
|
end
|
|
49
51
|
function Rules:checkRules(name)
|
|
50
52
|
local _processing = self.processing
|
|
51
53
|
local _name = name
|
|
52
54
|
if _processing[_name] ~= nil then
|
|
55
|
+
self.debug:logTag("rules", name, "Skipped rule processing (cycle detected)")
|
|
53
56
|
return nil
|
|
54
57
|
end
|
|
55
58
|
local _processing_1 = self.processing
|
|
56
59
|
local _name_1 = name
|
|
57
60
|
_processing_1[_name_1] = true
|
|
61
|
+
self.debug:logTag("rules", name, "Evaluating rules")
|
|
58
62
|
TS.try(function()
|
|
59
63
|
ParentRule(name, self)
|
|
60
64
|
ExclusiveGroupRule(name, self)
|
|
@@ -4,7 +4,7 @@ local TS = _G[script]
|
|
|
4
4
|
-- Components
|
|
5
5
|
local AppRegistry = TS.import(script, script.Parent.Parent.Parent, "decorator").AppRegistry
|
|
6
6
|
local function ParentRule(entry, forge)
|
|
7
|
-
local
|
|
7
|
+
local entryVisible = forge:getSource(entry)()
|
|
8
8
|
-- ▼ ReadonlyMap.forEach ▼
|
|
9
9
|
local _callback = function(app, name)
|
|
10
10
|
local rules = app.rules
|
|
@@ -14,10 +14,14 @@ local function ParentRule(entry, forge)
|
|
|
14
14
|
if name == entry then
|
|
15
15
|
return nil
|
|
16
16
|
end
|
|
17
|
-
local
|
|
18
|
-
if
|
|
19
|
-
|
|
17
|
+
local childVisible = forge:getSource(name)()
|
|
18
|
+
if entryVisible or not childVisible then
|
|
19
|
+
return nil
|
|
20
20
|
end
|
|
21
|
+
forge.debug:logTag("rules", entry, "Closing child app (parent closed)", {
|
|
22
|
+
child = name,
|
|
23
|
+
})
|
|
24
|
+
forge:close(name, false)
|
|
21
25
|
end
|
|
22
26
|
for _k, _v in AppRegistry do
|
|
23
27
|
_callback(_v, _k, AppRegistry)
|
package/out/vide/context.d.ts
CHANGED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export type DebugTag = string;
|
|
2
|
+
type LogFn = (level: "DEBUG" | "PERF", message: string, data?: unknown, traceback?: string) => void;
|
|
3
|
+
export default class Debugger {
|
|
4
|
+
private readonly log;
|
|
5
|
+
private enabled;
|
|
6
|
+
private timers;
|
|
7
|
+
constructor(log: LogFn);
|
|
8
|
+
enable(tag: DebugTag): void;
|
|
9
|
+
disable(tag: DebugTag): void;
|
|
10
|
+
isEnabled(tag: DebugTag): boolean;
|
|
11
|
+
logTag(tag: DebugTag, app: AppNames, message: string, data?: unknown): void;
|
|
12
|
+
time(tag: DebugTag, app: AppNames): void;
|
|
13
|
+
timeEnd(tag: DebugTag, app: AppNames): void;
|
|
14
|
+
}
|
|
15
|
+
export {};
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
-- Compiled with roblox-ts v3.0.0
|
|
2
|
+
local TS = _G[script]
|
|
3
|
+
-- Services
|
|
4
|
+
local RunService = TS.import(script, TS.getModule(script, "@rbxts", "services")).RunService
|
|
5
|
+
local Debugger
|
|
6
|
+
do
|
|
7
|
+
Debugger = setmetatable({}, {
|
|
8
|
+
__tostring = function()
|
|
9
|
+
return "Debugger"
|
|
10
|
+
end,
|
|
11
|
+
})
|
|
12
|
+
Debugger.__index = Debugger
|
|
13
|
+
function Debugger.new(...)
|
|
14
|
+
local self = setmetatable({}, Debugger)
|
|
15
|
+
return self:constructor(...) or self
|
|
16
|
+
end
|
|
17
|
+
function Debugger:constructor(log)
|
|
18
|
+
self.log = log
|
|
19
|
+
self.enabled = {}
|
|
20
|
+
self.timers = {}
|
|
21
|
+
end
|
|
22
|
+
function Debugger:enable(tag)
|
|
23
|
+
local _enabled = self.enabled
|
|
24
|
+
local _tag = tag
|
|
25
|
+
_enabled[_tag] = true
|
|
26
|
+
end
|
|
27
|
+
function Debugger:disable(tag)
|
|
28
|
+
local _enabled = self.enabled
|
|
29
|
+
local _tag = tag
|
|
30
|
+
_enabled[_tag] = nil
|
|
31
|
+
end
|
|
32
|
+
function Debugger:isEnabled(tag)
|
|
33
|
+
local _enabled = self.enabled
|
|
34
|
+
local _tag = tag
|
|
35
|
+
return _enabled[_tag] ~= nil
|
|
36
|
+
end
|
|
37
|
+
function Debugger:logTag(tag, app, message, data)
|
|
38
|
+
if not RunService:IsStudio() then
|
|
39
|
+
return nil
|
|
40
|
+
end
|
|
41
|
+
local _enabled = self.enabled
|
|
42
|
+
local _tag = tag
|
|
43
|
+
if not (_enabled[_tag] ~= nil) then
|
|
44
|
+
return nil
|
|
45
|
+
end
|
|
46
|
+
self.log("DEBUG", `[{tag}][{app}] {message}`, data, debug.traceback(nil, 3))
|
|
47
|
+
end
|
|
48
|
+
function Debugger:time(tag, app)
|
|
49
|
+
if not RunService:IsStudio() then
|
|
50
|
+
return nil
|
|
51
|
+
end
|
|
52
|
+
local _enabled = self.enabled
|
|
53
|
+
local _tag = tag
|
|
54
|
+
if not (_enabled[_tag] ~= nil) then
|
|
55
|
+
return nil
|
|
56
|
+
end
|
|
57
|
+
local _timers = self.timers
|
|
58
|
+
local _arg0 = `{tag}:{app}`
|
|
59
|
+
local _arg1 = os.clock()
|
|
60
|
+
_timers[_arg0] = _arg1
|
|
61
|
+
end
|
|
62
|
+
function Debugger:timeEnd(tag, app)
|
|
63
|
+
if not RunService:IsStudio() then
|
|
64
|
+
return nil
|
|
65
|
+
end
|
|
66
|
+
local _enabled = self.enabled
|
|
67
|
+
local _tag = tag
|
|
68
|
+
if not (_enabled[_tag] ~= nil) then
|
|
69
|
+
return nil
|
|
70
|
+
end
|
|
71
|
+
local key = `{tag}:{app}`
|
|
72
|
+
local start = self.timers[key]
|
|
73
|
+
if start == nil then
|
|
74
|
+
return nil
|
|
75
|
+
end
|
|
76
|
+
self.timers[key] = nil
|
|
77
|
+
local elapsed = os.clock() - start
|
|
78
|
+
self.log("PERF", `[{tag}][{app}] {string.format("%.3fms", elapsed * 1000)}`)
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
return {
|
|
82
|
+
default = Debugger,
|
|
83
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
-- Compiled with roblox-ts v3.0.0
|
|
2
|
+
local TS = _G[script]
|
|
3
|
+
-- debug/logger.ts
|
|
4
|
+
local RunService = TS.import(script, TS.getModule(script, "@rbxts", "services")).RunService
|
|
5
|
+
local Logger
|
|
6
|
+
do
|
|
7
|
+
Logger = setmetatable({}, {
|
|
8
|
+
__tostring = function()
|
|
9
|
+
return "Logger"
|
|
10
|
+
end,
|
|
11
|
+
})
|
|
12
|
+
Logger.__index = Logger
|
|
13
|
+
function Logger.new(...)
|
|
14
|
+
local self = setmetatable({}, Logger)
|
|
15
|
+
return self:constructor(...) or self
|
|
16
|
+
end
|
|
17
|
+
function Logger:constructor(scope)
|
|
18
|
+
self.scope = scope
|
|
19
|
+
end
|
|
20
|
+
function Logger:log(level, message, data, traceback)
|
|
21
|
+
if not RunService:IsStudio() then
|
|
22
|
+
return nil
|
|
23
|
+
end
|
|
24
|
+
local prefix = `[{self.scope}][{level}]`
|
|
25
|
+
if data ~= nil then
|
|
26
|
+
print(prefix, message, data)
|
|
27
|
+
else
|
|
28
|
+
print(prefix, message)
|
|
29
|
+
end
|
|
30
|
+
if traceback ~= "" and traceback then
|
|
31
|
+
print(traceback)
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
return {
|
|
36
|
+
default = Logger,
|
|
37
|
+
}
|
package/out/vide/decorator.d.ts
CHANGED
|
@@ -2,7 +2,15 @@ import Vide from "@rbxts/vide";
|
|
|
2
2
|
import type Types from "./types";
|
|
3
3
|
import type AppForge from ".";
|
|
4
4
|
export declare const AppRegistry: Map<string, Types.AppRegistry.Static>;
|
|
5
|
+
/**
|
|
6
|
+
* Registers a Vide App with AppForge.
|
|
7
|
+
*
|
|
8
|
+
* This runs at definition time and validates static configuration.
|
|
9
|
+
*/
|
|
5
10
|
export declare function App<N extends AppNames>(props: Types.AppRegistry.Props<N>): <T extends new (props: Types.Props.Main, name: AppNames) => Args>(constructor: T) => T;
|
|
11
|
+
/**
|
|
12
|
+
* Base class for all AppForge Apps.
|
|
13
|
+
*/
|
|
6
14
|
export declare abstract class Args {
|
|
7
15
|
readonly forge: AppForge;
|
|
8
16
|
readonly props: Types.Props.Class;
|
package/out/vide/decorator.luau
CHANGED
|
@@ -1,15 +1,40 @@
|
|
|
1
1
|
-- Compiled with roblox-ts v3.0.0
|
|
2
2
|
local TS = _G[script]
|
|
3
3
|
-- Packages
|
|
4
|
-
local px = TS.import(script, TS.getModule(script, "@rbxts", "loners-pretty-vide-utils").out).px
|
|
5
4
|
-- Types
|
|
5
|
+
-- Hooks
|
|
6
|
+
local px = TS.import(script, script.Parent, "hooks", "usePx").px
|
|
7
|
+
-- Debug
|
|
8
|
+
local Logger = TS.import(script, script.Parent, "debug", "logger").default
|
|
9
|
+
local logger = Logger.new("AppRegistry")
|
|
6
10
|
local AppRegistry = {}
|
|
11
|
+
--[[
|
|
12
|
+
*
|
|
13
|
+
* Registers a Vide App with AppForge.
|
|
14
|
+
*
|
|
15
|
+
* This runs at definition time and validates static configuration.
|
|
16
|
+
|
|
17
|
+
]]
|
|
7
18
|
local function App(props)
|
|
8
19
|
return function(constructor)
|
|
9
20
|
local _name = props.name
|
|
10
21
|
if AppRegistry[_name] ~= nil then
|
|
11
|
-
|
|
22
|
+
logger:log("ERROR", "Duplicate App name detected", {
|
|
23
|
+
name = props.name,
|
|
24
|
+
})
|
|
25
|
+
error(`Duplicate registered App name "{props.name}". ` .. `App names must be globally unique.`, 2)
|
|
12
26
|
end
|
|
27
|
+
local _value = props.name
|
|
28
|
+
if not (_value ~= "" and _value) then
|
|
29
|
+
logger:log("ERROR", "Attempted to register App without a name", props)
|
|
30
|
+
error("App registration failed: missing app name", 2)
|
|
31
|
+
end
|
|
32
|
+
logger:log("DEBUG", "Registering App", {
|
|
33
|
+
name = props.name,
|
|
34
|
+
renderGroup = props.renderGroup,
|
|
35
|
+
visible = props.visible,
|
|
36
|
+
rules = props.rules,
|
|
37
|
+
})
|
|
13
38
|
local _name_1 = props.name
|
|
14
39
|
local _arg1 = {
|
|
15
40
|
constructor = constructor,
|
|
@@ -21,6 +46,11 @@ local function App(props)
|
|
|
21
46
|
return constructor
|
|
22
47
|
end
|
|
23
48
|
end
|
|
49
|
+
--[[
|
|
50
|
+
*
|
|
51
|
+
* Base class for all AppForge Apps.
|
|
52
|
+
|
|
53
|
+
]]
|
|
24
54
|
local Args
|
|
25
55
|
do
|
|
26
56
|
Args = {}
|
|
@@ -28,13 +58,20 @@ do
|
|
|
28
58
|
local _binding = props
|
|
29
59
|
local forge = _binding.forge
|
|
30
60
|
self.forge = forge
|
|
61
|
+
self.name = name
|
|
31
62
|
local _object = table.clone(props.props)
|
|
32
63
|
setmetatable(_object, nil)
|
|
33
64
|
_object.px = px
|
|
34
65
|
_object.forge = forge
|
|
35
66
|
self.props = _object
|
|
36
|
-
|
|
37
|
-
|
|
67
|
+
local src = forge:getSource(name)
|
|
68
|
+
if not src then
|
|
69
|
+
logger:log("ERROR", "Missing visibility source for App", {
|
|
70
|
+
name = name,
|
|
71
|
+
})
|
|
72
|
+
error(`Failed to retrieve visibility source for app "{name}"`, 2)
|
|
73
|
+
end
|
|
74
|
+
self.source = src
|
|
38
75
|
end
|
|
39
76
|
end
|
|
40
77
|
return {
|
|
@@ -2,10 +2,14 @@
|
|
|
2
2
|
local TS = _G[script]
|
|
3
3
|
-- Components
|
|
4
4
|
local Contexts = TS.import(script, script.Parent.Parent, "context").default
|
|
5
|
+
-- Debug
|
|
6
|
+
local Logger = TS.import(script, script.Parent.Parent, "debug", "logger").default
|
|
7
|
+
local logger = Logger.new("useAppContext")
|
|
5
8
|
local default = function()
|
|
6
9
|
local context = Contexts.App()
|
|
7
10
|
if not context then
|
|
8
|
-
|
|
11
|
+
logger:log("ERROR", "Failed to retrieve App context")
|
|
12
|
+
error(`Failed to retrieve App Props for Vide\n{debug.traceback()}`, 2)
|
|
9
13
|
end
|
|
10
14
|
return context
|
|
11
15
|
end
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
type EventLike<T extends Callback = Callback> = {
|
|
2
|
+
Connect(callback: T): ConnectionLike;
|
|
3
|
+
} | {
|
|
4
|
+
connect(callback: T): ConnectionLike;
|
|
5
|
+
} | {
|
|
6
|
+
subscribe(callback: T): ConnectionLike;
|
|
7
|
+
};
|
|
8
|
+
type ConnectionLike = {
|
|
9
|
+
Disconnect(): void;
|
|
10
|
+
} | {
|
|
11
|
+
disconnect(): void;
|
|
12
|
+
} | (() => void);
|
|
13
|
+
/**
|
|
14
|
+
* Subscribes to an event-like object and auto-cleans up.
|
|
15
|
+
*/
|
|
16
|
+
export declare function useEventListener<T extends EventLike>(event: T, listener: T extends EventLike<infer U> ? U : never): ReturnType<T>;
|
|
17
|
+
export {};
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
-- Compiled with roblox-ts v3.0.0
|
|
2
|
+
local TS = _G[script]
|
|
3
|
+
local cleanup = TS.import(script, TS.getModule(script, "@rbxts", "vide").src).cleanup
|
|
4
|
+
-- Debug
|
|
5
|
+
local Logger = TS.import(script, script.Parent.Parent, "debug", "logger").default
|
|
6
|
+
local logger = Logger.new("useEventListener")
|
|
7
|
+
local connect = function(event, callback)
|
|
8
|
+
local _event = event
|
|
9
|
+
if typeof(_event) == "RBXScriptSignal" then
|
|
10
|
+
local connection
|
|
11
|
+
connection = event:Connect(function(...)
|
|
12
|
+
local args = { ... }
|
|
13
|
+
if connection.Connected then
|
|
14
|
+
return callback(unpack(args))
|
|
15
|
+
end
|
|
16
|
+
end)
|
|
17
|
+
return connection
|
|
18
|
+
elseif event.Connect ~= nil then
|
|
19
|
+
return event:Connect(callback)
|
|
20
|
+
elseif event.connect ~= nil then
|
|
21
|
+
return event:connect(callback)
|
|
22
|
+
elseif event.subscribe ~= nil then
|
|
23
|
+
return event:subscribe(callback)
|
|
24
|
+
end
|
|
25
|
+
logger:log("ERROR", "Unsupported event-like object", event)
|
|
26
|
+
error("Event-like object does not have a supported connect method.", 2)
|
|
27
|
+
end
|
|
28
|
+
local disconnect = function(connection)
|
|
29
|
+
local _connection = connection
|
|
30
|
+
if type(_connection) == "function" then
|
|
31
|
+
connection()
|
|
32
|
+
else
|
|
33
|
+
local _connection_1 = connection
|
|
34
|
+
local _condition = typeof(_connection_1) == "RBXScriptConnection"
|
|
35
|
+
if not _condition then
|
|
36
|
+
_condition = connection.Disconnect ~= nil
|
|
37
|
+
end
|
|
38
|
+
if _condition then
|
|
39
|
+
connection:Disconnect()
|
|
40
|
+
elseif connection.disconnect ~= nil then
|
|
41
|
+
connection:disconnect()
|
|
42
|
+
else
|
|
43
|
+
logger:log("WARN", "Unsupported connection-like object during cleanup", connection)
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
--[[
|
|
48
|
+
*
|
|
49
|
+
* Subscribes to an event-like object and auto-cleans up.
|
|
50
|
+
|
|
51
|
+
]]
|
|
52
|
+
local function useEventListener(event, listener)
|
|
53
|
+
local connection = connect(event, listener)
|
|
54
|
+
cleanup(function()
|
|
55
|
+
return disconnect(connection)
|
|
56
|
+
end)
|
|
57
|
+
return connection
|
|
58
|
+
end
|
|
59
|
+
return {
|
|
60
|
+
useEventListener = useEventListener,
|
|
61
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export declare const px: ((value: number) => number) & {
|
|
2
|
+
scale: (value: number) => number;
|
|
3
|
+
even: (value: number) => number;
|
|
4
|
+
floor: (value: number) => number;
|
|
5
|
+
ceil: (value: number) => number;
|
|
6
|
+
};
|
|
7
|
+
/**
|
|
8
|
+
* Initializes global px scaling.
|
|
9
|
+
* Must be called exactly once.
|
|
10
|
+
*/
|
|
11
|
+
export declare function usePx(target?: GuiObject | Camera, baseResolution?: Vector2, minScale?: number): void;
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
-- Compiled with roblox-ts v3.0.0
|
|
2
|
+
local TS = _G[script]
|
|
3
|
+
-- Services
|
|
4
|
+
local Workspace = TS.import(script, TS.getModule(script, "@rbxts", "services")).Workspace
|
|
5
|
+
-- Packages
|
|
6
|
+
local source = TS.import(script, TS.getModule(script, "@rbxts", "vide").src).source
|
|
7
|
+
-- Helpers
|
|
8
|
+
local useEventListener = TS.import(script, script.Parent, "useEventListener").useEventListener
|
|
9
|
+
-- Debug
|
|
10
|
+
local Logger = TS.import(script, script.Parent.Parent, "debug", "logger").default
|
|
11
|
+
local logger = Logger.new("usePx")
|
|
12
|
+
--* Default reference resolution for px calculations
|
|
13
|
+
local BASE_RESOLUTION = source(Vector2.new(1920, 1080))
|
|
14
|
+
--* Minimum allowed scale to prevent unreadable UI
|
|
15
|
+
local MIN_SCALE = source(0.5)
|
|
16
|
+
--* 0 = width-based, 1 = height-based
|
|
17
|
+
local DOMINANT_AXIS = 0.5
|
|
18
|
+
local TARGET = source(Workspace.CurrentCamera)
|
|
19
|
+
local SCALE = source(1)
|
|
20
|
+
local INITIALIZED = false
|
|
21
|
+
local function callable(callback, object)
|
|
22
|
+
return setmetatable(object, {
|
|
23
|
+
__call = function(_, ...)
|
|
24
|
+
local args = { ... }
|
|
25
|
+
return callback(unpack(args))
|
|
26
|
+
end,
|
|
27
|
+
})
|
|
28
|
+
end
|
|
29
|
+
local px = callable(function(value)
|
|
30
|
+
return math.round(value * SCALE())
|
|
31
|
+
end, {
|
|
32
|
+
scale = function(value)
|
|
33
|
+
return value * SCALE()
|
|
34
|
+
end,
|
|
35
|
+
even = function(value)
|
|
36
|
+
return math.round(value * SCALE() * 0.5) * 2
|
|
37
|
+
end,
|
|
38
|
+
floor = function(value)
|
|
39
|
+
return math.floor(value * SCALE())
|
|
40
|
+
end,
|
|
41
|
+
ceil = function(value)
|
|
42
|
+
return math.ceil(value * SCALE())
|
|
43
|
+
end,
|
|
44
|
+
})
|
|
45
|
+
local function calculateScale()
|
|
46
|
+
local target = TARGET()
|
|
47
|
+
if not target then
|
|
48
|
+
return nil
|
|
49
|
+
end
|
|
50
|
+
local size = if target:IsA("Camera") then target.ViewportSize elseif target:IsA("GuiObject") then target.AbsoluteSize else nil
|
|
51
|
+
if not size then
|
|
52
|
+
return nil
|
|
53
|
+
end
|
|
54
|
+
local res = BASE_RESOLUTION()
|
|
55
|
+
if res.X <= 0 or res.Y <= 0 then
|
|
56
|
+
return nil
|
|
57
|
+
end
|
|
58
|
+
local min = MIN_SCALE()
|
|
59
|
+
local width = math.log(size.X / res.X, 2)
|
|
60
|
+
local height = math.log(size.Y / res.Y, 2)
|
|
61
|
+
local centered = width + (height - width) * DOMINANT_AXIS
|
|
62
|
+
local scale = 2 ^ centered
|
|
63
|
+
SCALE(math.max(scale, min))
|
|
64
|
+
end
|
|
65
|
+
--[[
|
|
66
|
+
*
|
|
67
|
+
* Initializes global px scaling.
|
|
68
|
+
* Must be called exactly once.
|
|
69
|
+
|
|
70
|
+
]]
|
|
71
|
+
local function usePx(target, baseResolution, minScale)
|
|
72
|
+
if INITIALIZED then
|
|
73
|
+
logger:log("WARN", "usePx() called more than once")
|
|
74
|
+
return nil
|
|
75
|
+
end
|
|
76
|
+
INITIALIZED = true
|
|
77
|
+
logger:log("DEBUG", "Initializing px scaling", {
|
|
78
|
+
target = target,
|
|
79
|
+
baseResolution = baseResolution,
|
|
80
|
+
minScale = minScale,
|
|
81
|
+
})
|
|
82
|
+
if baseResolution then
|
|
83
|
+
BASE_RESOLUTION(baseResolution)
|
|
84
|
+
end
|
|
85
|
+
if minScale ~= nil then
|
|
86
|
+
MIN_SCALE(minScale)
|
|
87
|
+
end
|
|
88
|
+
if target then
|
|
89
|
+
TARGET(target)
|
|
90
|
+
end
|
|
91
|
+
local resolvedTarget = TARGET()
|
|
92
|
+
if not resolvedTarget then
|
|
93
|
+
logger:log("WARN", "usePx(): no valid target to observe")
|
|
94
|
+
return nil
|
|
95
|
+
end
|
|
96
|
+
local signal = if resolvedTarget:IsA("Camera") then resolvedTarget:GetPropertyChangedSignal("ViewportSize") else resolvedTarget:GetPropertyChangedSignal("AbsoluteSize")
|
|
97
|
+
useEventListener(signal, calculateScale)
|
|
98
|
+
calculateScale()
|
|
99
|
+
end
|
|
100
|
+
return {
|
|
101
|
+
usePx = usePx,
|
|
102
|
+
px = px,
|
|
103
|
+
}
|
package/out/vide/index.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import Vide from "@rbxts/vide";
|
|
2
2
|
import Renders from "./classes/renders";
|
|
3
3
|
import Types from "./types";
|
|
4
|
+
import { Logger, Debugger } from "./debug";
|
|
4
5
|
type Destructor = () => void;
|
|
5
6
|
type Loaded = {
|
|
6
7
|
container: Vide.Node;
|
|
@@ -8,17 +9,19 @@ type Loaded = {
|
|
|
8
9
|
anchor?: Vide.Node;
|
|
9
10
|
};
|
|
10
11
|
export default class AppForge extends Renders {
|
|
12
|
+
readonly logger: Logger;
|
|
13
|
+
readonly debug: Debugger;
|
|
11
14
|
protected sources: Map<string, Vide.Source<boolean>>;
|
|
12
15
|
protected loaded: Map<string, Loaded>;
|
|
13
16
|
protected innerMount?: Destructor;
|
|
14
17
|
protected __px: boolean;
|
|
15
18
|
constructor();
|
|
16
|
-
protected createSource(name: AppNames):
|
|
19
|
+
protected createSource(name: AppNames): void;
|
|
20
|
+
getSource(name: AppNames): Vide.Source<boolean>;
|
|
17
21
|
isLoaded(name: AppNames): boolean;
|
|
18
22
|
bind(name: AppNames, value: Vide.Source<boolean>): void;
|
|
19
23
|
anchor(name: AppNames, anchorName: AppNames, props: Types.Props.Main): void;
|
|
20
24
|
index(name: AppNames, index: number): void;
|
|
21
|
-
getSource(name: AppNames): Vide.Source<boolean>;
|
|
22
25
|
set(name: AppNames, value: boolean, rules?: boolean): void;
|
|
23
26
|
open(name: AppNames, rules?: boolean): void;
|
|
24
27
|
close(name: AppNames, rules?: boolean): void;
|
package/out/vide/init.luau
CHANGED
|
@@ -14,6 +14,10 @@ local untrack = _vide.untrack
|
|
|
14
14
|
local Renders = TS.import(script, script, "classes", "renders").default
|
|
15
15
|
-- Helpers
|
|
16
16
|
local AppRegistry = TS.import(script, script, "decorator").AppRegistry
|
|
17
|
+
-- Debug
|
|
18
|
+
local _debug = TS.import(script, script, "debug")
|
|
19
|
+
local Logger = _debug.Logger
|
|
20
|
+
local Debugger = _debug.Debugger
|
|
17
21
|
local AppForge
|
|
18
22
|
do
|
|
19
23
|
local super = Renders
|
|
@@ -30,6 +34,10 @@ do
|
|
|
30
34
|
end
|
|
31
35
|
function AppForge:constructor()
|
|
32
36
|
super.constructor(self)
|
|
37
|
+
self.logger = Logger.new("AppForge")
|
|
38
|
+
self.debug = Debugger.new(function(level, msg, data, trace)
|
|
39
|
+
return self.logger:log(level, msg, data, trace)
|
|
40
|
+
end)
|
|
33
41
|
self.sources = {}
|
|
34
42
|
self.loaded = {}
|
|
35
43
|
self.__px = false
|
|
@@ -46,22 +54,48 @@ do
|
|
|
46
54
|
local _name = name
|
|
47
55
|
local app = AppRegistry[_name]
|
|
48
56
|
if not app then
|
|
49
|
-
|
|
57
|
+
self.logger:log("ERROR", "App not registered while creating source", {
|
|
58
|
+
name = name,
|
|
59
|
+
})
|
|
60
|
+
return nil
|
|
50
61
|
end
|
|
51
62
|
local _sources = self.sources
|
|
52
63
|
local _name_1 = name
|
|
53
64
|
if _sources[_name_1] ~= nil then
|
|
54
65
|
return nil
|
|
55
66
|
end
|
|
56
|
-
local
|
|
67
|
+
local _debug_1 = self.debug
|
|
57
68
|
local _exp = name
|
|
69
|
+
local _object = {}
|
|
70
|
+
local _left = "default"
|
|
58
71
|
local _condition = app.visible
|
|
59
72
|
if _condition == nil then
|
|
60
73
|
_condition = false
|
|
61
74
|
end
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
75
|
+
_object[_left] = _condition
|
|
76
|
+
_debug_1:logTag("state", _exp, "Creating visibility source", _object)
|
|
77
|
+
local _sources_1 = self.sources
|
|
78
|
+
local _exp_1 = name
|
|
79
|
+
local _condition_1 = app.visible
|
|
80
|
+
if _condition_1 == nil then
|
|
81
|
+
_condition_1 = false
|
|
82
|
+
end
|
|
83
|
+
local _arg1 = source(_condition_1)
|
|
84
|
+
_sources_1[_exp_1] = _arg1
|
|
85
|
+
end
|
|
86
|
+
function AppForge:getSource(name)
|
|
87
|
+
local _sources = self.sources
|
|
88
|
+
local _name = name
|
|
89
|
+
if not (_sources[_name] ~= nil) then
|
|
90
|
+
self:createSource(name)
|
|
91
|
+
end
|
|
92
|
+
local _sources_1 = self.sources
|
|
93
|
+
local _name_1 = name
|
|
94
|
+
local src = _sources_1[_name_1]
|
|
95
|
+
if not src then
|
|
96
|
+
error(`AppForge invariant broken: missing visibility source for {name}`, 2)
|
|
97
|
+
end
|
|
98
|
+
return src
|
|
65
99
|
end
|
|
66
100
|
function AppForge:isLoaded(name)
|
|
67
101
|
local _loaded = self.loaded
|
|
@@ -70,6 +104,7 @@ do
|
|
|
70
104
|
end
|
|
71
105
|
function AppForge:bind(name, value)
|
|
72
106
|
if not RunService:IsRunning() then
|
|
107
|
+
self.debug:logTag("state", name, "Binding external visibility source")
|
|
73
108
|
local _sources = self.sources
|
|
74
109
|
local _name = name
|
|
75
110
|
local _value = value
|
|
@@ -81,17 +116,26 @@ do
|
|
|
81
116
|
end)
|
|
82
117
|
end)
|
|
83
118
|
else
|
|
84
|
-
|
|
119
|
+
self.logger:log("WARN", "forge.bind called while game is running", {
|
|
120
|
+
name = name,
|
|
121
|
+
})
|
|
85
122
|
end
|
|
86
123
|
end
|
|
87
124
|
function AppForge:anchor(name, anchorName, props)
|
|
88
125
|
if name == anchorName then
|
|
89
|
-
|
|
126
|
+
self.logger:log("ERROR", "Attempted to anchor app to itself", {
|
|
127
|
+
name = name,
|
|
128
|
+
})
|
|
129
|
+
return nil
|
|
90
130
|
end
|
|
91
131
|
local _anchorName = anchorName
|
|
92
132
|
local anchorApp = AppRegistry[_anchorName]
|
|
93
133
|
if not anchorApp then
|
|
94
|
-
|
|
134
|
+
self.logger:log("ERROR", "Anchor parent not registered", {
|
|
135
|
+
child = name,
|
|
136
|
+
parent = anchorName,
|
|
137
|
+
})
|
|
138
|
+
return nil
|
|
95
139
|
end
|
|
96
140
|
local _loaded = self.loaded
|
|
97
141
|
local _name = name
|
|
@@ -101,11 +145,17 @@ do
|
|
|
101
145
|
end
|
|
102
146
|
local render = _render
|
|
103
147
|
if not render then
|
|
104
|
-
|
|
148
|
+
self.debug:logTag("rules", name, "Anchor skipped (child not rendered yet)", {
|
|
149
|
+
parent = anchorName,
|
|
150
|
+
})
|
|
151
|
+
return nil
|
|
105
152
|
end
|
|
153
|
+
self.debug:logTag("rules", name, "Anchoring to parent", {
|
|
154
|
+
parent = anchorName,
|
|
155
|
+
})
|
|
106
156
|
local anchor = anchorApp.constructor.new(props, anchorName):render()
|
|
107
|
-
for _,
|
|
108
|
-
|
|
157
|
+
for _, child in anchor:GetDescendants() do
|
|
158
|
+
child:Destroy()
|
|
109
159
|
end
|
|
110
160
|
apply(anchor)({
|
|
111
161
|
Name = "Anchor",
|
|
@@ -116,7 +166,7 @@ do
|
|
|
116
166
|
local _name_1 = name
|
|
117
167
|
local prev = _loaded_1[_name_1]
|
|
118
168
|
if not prev then
|
|
119
|
-
error(`
|
|
169
|
+
error(`AppForge invariant broken: missing loaded app for {name}`, 2)
|
|
120
170
|
end
|
|
121
171
|
apply(prev.container)({
|
|
122
172
|
[0] = anchor,
|
|
@@ -133,22 +183,19 @@ do
|
|
|
133
183
|
local _name = name
|
|
134
184
|
local loaded = _loaded[_name]
|
|
135
185
|
if not loaded then
|
|
136
|
-
|
|
186
|
+
self.logger:log("WARN", "ZIndex skipped (app not loaded)", {
|
|
187
|
+
name = name,
|
|
188
|
+
index = index,
|
|
189
|
+
})
|
|
190
|
+
return nil
|
|
137
191
|
end
|
|
192
|
+
self.debug:logTag("rules", name, "Applying ZIndex", {
|
|
193
|
+
index = index,
|
|
194
|
+
})
|
|
138
195
|
apply(loaded.container)({
|
|
139
196
|
ZIndex = index,
|
|
140
197
|
})
|
|
141
198
|
end
|
|
142
|
-
function AppForge:getSource(name)
|
|
143
|
-
local _sources = self.sources
|
|
144
|
-
local _name = name
|
|
145
|
-
if not (_sources[_name] ~= nil) then
|
|
146
|
-
self:createSource(name)
|
|
147
|
-
end
|
|
148
|
-
local _sources_1 = self.sources
|
|
149
|
-
local _name_1 = name
|
|
150
|
-
return _sources_1[_name_1]
|
|
151
|
-
end
|
|
152
199
|
function AppForge:set(name, value, rules)
|
|
153
200
|
if rules == nil then
|
|
154
201
|
rules = true
|
|
@@ -162,10 +209,21 @@ do
|
|
|
162
209
|
local _name_1 = name
|
|
163
210
|
src = _sources_1[_name_1]
|
|
164
211
|
end
|
|
165
|
-
if src
|
|
212
|
+
if not src then
|
|
213
|
+
self.logger:log("ERROR", "Failed to set visibility (missing source)", {
|
|
214
|
+
name = name,
|
|
215
|
+
})
|
|
216
|
+
return nil
|
|
217
|
+
end
|
|
218
|
+
local prev = src()
|
|
219
|
+
if prev == value then
|
|
166
220
|
return nil
|
|
167
221
|
end
|
|
168
222
|
src(value)
|
|
223
|
+
self.debug:logTag("state", name, "Visibility changed", {
|
|
224
|
+
from = prev,
|
|
225
|
+
to = value,
|
|
226
|
+
})
|
|
169
227
|
if rules then
|
|
170
228
|
self:checkRules(name)
|
|
171
229
|
end
|
|
@@ -189,6 +247,7 @@ do
|
|
|
189
247
|
self:set(name, not self:getSource(name)(), rules)
|
|
190
248
|
end
|
|
191
249
|
function AppForge:story(props)
|
|
250
|
+
self.debug:logTag("lifecycle", "story", "Creating story mount")
|
|
192
251
|
local Container = create("Frame")({
|
|
193
252
|
Name = "Story Container",
|
|
194
253
|
BackgroundTransparency = 1,
|
|
@@ -202,6 +261,7 @@ do
|
|
|
202
261
|
return Container
|
|
203
262
|
end
|
|
204
263
|
function AppForge:mount(callback, props, target)
|
|
264
|
+
self.debug:logTag("lifecycle", "mount", "Mounting AppForge")
|
|
205
265
|
local Container = callback()
|
|
206
266
|
self.innerMount = mount(function()
|
|
207
267
|
apply(Container)({
|
|
@@ -212,6 +272,7 @@ do
|
|
|
212
272
|
return self.innerMount
|
|
213
273
|
end
|
|
214
274
|
function AppForge:unMount()
|
|
275
|
+
self.debug:logTag("lifecycle", "unmount", "Unmounting AppForge")
|
|
215
276
|
local _result = self.innerMount
|
|
216
277
|
if _result ~= nil then
|
|
217
278
|
_result()
|
package/out/vide/types.d.ts
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rbxts/app-forge",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.2-alpha.1",
|
|
4
4
|
"description": "An App Manager for Vide",
|
|
5
5
|
"main": "out/init.lua",
|
|
6
6
|
"types": "out/index.d.ts",
|
|
@@ -38,11 +38,17 @@
|
|
|
38
38
|
"@rbxts/set-timeout": "^1.1.2"
|
|
39
39
|
},
|
|
40
40
|
"peerDependencies": {
|
|
41
|
-
"@rbxts/loners-pretty-react-hooks": "^0.2.9",
|
|
42
|
-
"@rbxts/loners-pretty-vide-utils": "^0.1.7",
|
|
43
41
|
"@rbxts/react": "^17.3.7-ts.1",
|
|
44
42
|
"@rbxts/vide": "^0.5.7"
|
|
45
43
|
},
|
|
44
|
+
"peerDependenciesMeta": {
|
|
45
|
+
"@rbxts/react": {
|
|
46
|
+
"optional": true
|
|
47
|
+
},
|
|
48
|
+
"@rbxts/vide": {
|
|
49
|
+
"optional": true
|
|
50
|
+
}
|
|
51
|
+
},
|
|
46
52
|
"devDependencies": {
|
|
47
53
|
"@biomejs/biome": "^2.3.7",
|
|
48
54
|
"@rbxts/compiler-types": "3.0.0-types.0",
|