@rbxts/app-forge 0.7.1 → 0.7.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.
@@ -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
+ }
@@ -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
@@ -1,7 +1,7 @@
1
1
  declare const Contexts: {
2
2
  readonly App: import("@rbxts/vide").Context<{
3
3
  forge: import(".").default;
4
- px: typeof import("@rbxts/loners-pretty-vide-utils").px;
4
+ px: typeof import("./hooks/usePx").px;
5
5
  } | undefined>;
6
6
  };
7
7
  export default Contexts;
@@ -1,8 +1,9 @@
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
6
7
  local AppRegistry = {}
7
8
  local function App(props)
8
9
  return function(constructor)
@@ -1,5 +1,5 @@
1
1
  declare const _default: () => {
2
2
  forge: import("..").default;
3
- px: typeof import("@rbxts/loners-pretty-vide-utils").px;
3
+ px: typeof import("./usePx").px;
4
4
  };
5
5
  export default _default;
@@ -0,0 +1,22 @@
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. The subscription is automatically
15
+ * disconnected when the scope cleans up.
16
+ *
17
+ * @param event The event-like object to subscribe to.
18
+ * @param listener The listener to subscribe with.
19
+ * @returns The connection object.
20
+ */
21
+ export declare function useEventListener<T extends EventLike>(event: T, listener: T extends EventLike<infer U> ? U : never): ReturnType<T>;
22
+ export {};
@@ -0,0 +1,67 @@
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
+ local connect = function(event, callback)
5
+ local _event = event
6
+ if typeof(_event) == "RBXScriptSignal" then
7
+ -- With deferred events, a "hard disconnect" is necessary to avoid causing
8
+ -- state updates after a component unmounts. Use 'Connected' to check if
9
+ -- the connection is still valid before invoking the callback.
10
+ -- https://devforum.roblox.com/t/deferred-engine-events/2276564/99
11
+ local connection
12
+ connection = event:Connect(function(...)
13
+ local args = { ... }
14
+ if connection.Connected then
15
+ return callback(unpack(args))
16
+ end
17
+ end)
18
+ return connection
19
+ elseif event.Connect ~= nil then
20
+ return event:Connect(callback)
21
+ elseif event.connect ~= nil then
22
+ return event:connect(callback)
23
+ elseif event.subscribe ~= nil then
24
+ return event:subscribe(callback)
25
+ else
26
+ error("Event-like object does not have a supported connect method.")
27
+ end
28
+ end
29
+ local disconnect = function(connection)
30
+ local _connection = connection
31
+ if type(_connection) == "function" then
32
+ connection()
33
+ else
34
+ local _connection_1 = connection
35
+ local _condition = typeof(_connection_1) == "RBXScriptConnection"
36
+ if not _condition then
37
+ _condition = connection.Disconnect ~= nil
38
+ end
39
+ if _condition then
40
+ connection:Disconnect()
41
+ elseif connection.disconnect ~= nil then
42
+ connection:disconnect()
43
+ else
44
+ error("Connection-like object does not have a supported disconnect method.")
45
+ end
46
+ end
47
+ end
48
+ --[[
49
+ *
50
+ * Subscribes to an event-like object. The subscription is automatically
51
+ * disconnected when the scope cleans up.
52
+ *
53
+ * @param event The event-like object to subscribe to.
54
+ * @param listener The listener to subscribe with.
55
+ * @returns The connection object.
56
+
57
+ ]]
58
+ local function useEventListener(event, listener)
59
+ local connection = connect(event, listener)
60
+ cleanup(function()
61
+ return disconnect(connection)
62
+ end)
63
+ return connection
64
+ end
65
+ return {
66
+ useEventListener = useEventListener,
67
+ }
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Scaled pixel unit helper.
3
+ *
4
+ * - `px(12)` → rounded scaled pixels
5
+ * - `px.scale(12)` → raw scaled value
6
+ * - `px.even(12)` → even pixel values (useful for strokes/borders)
7
+ */
8
+ export declare const px: ((value: number) => number) & {
9
+ scale: (value: number) => number;
10
+ even: (value: number) => number;
11
+ floor: (value: number) => number;
12
+ ceil: (value: number) => number;
13
+ };
14
+ /**
15
+ * Initializes global px scaling.
16
+ *
17
+ * Should be called exactly once at app mount.
18
+ */
19
+ export declare function usePx(target?: GuiObject | Camera, baseResolution?: Vector2, minScale?: number): void;
@@ -0,0 +1,120 @@
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
+ --* Default reference resolution for px calculations
10
+ local BASE_RESOLUTION = source(Vector2.new(1920, 1080))
11
+ --* Minimum allowed scale to prevent unreadable UI
12
+ local MIN_SCALE = source(0.5)
13
+ --[[
14
+ *
15
+ * Interpolates between width- and height-based scaling.
16
+ * 0 = width-driven, 1 = height-driven
17
+
18
+ ]]
19
+ local DOMINANT_AXIS = 0.5
20
+ local TARGET = source(Workspace.CurrentCamera)
21
+ local SCALE = source(1)
22
+ local INITIALIZED = false
23
+ --[[
24
+ *
25
+ * Assigns a call signature to an object.
26
+
27
+ ]]
28
+ local function callable(callback, object)
29
+ return setmetatable(object, {
30
+ __call = function(_, ...)
31
+ local args = { ... }
32
+ return callback(unpack(args))
33
+ end,
34
+ })
35
+ end
36
+ --[[
37
+ *
38
+ * Scaled pixel unit helper.
39
+ *
40
+ * - `px(12)` → rounded scaled pixels
41
+ * - `px.scale(12)` → raw scaled value
42
+ * - `px.even(12)` → even pixel values (useful for strokes/borders)
43
+
44
+ ]]
45
+ local px = callable(function(value)
46
+ return math.round(value * SCALE())
47
+ end, {
48
+ scale = function(value)
49
+ return value * SCALE()
50
+ end,
51
+ even = function(value)
52
+ return math.round(value * SCALE() * 0.5) * 2
53
+ end,
54
+ floor = function(value)
55
+ return math.floor(value * SCALE())
56
+ end,
57
+ ceil = function(value)
58
+ return math.ceil(value * SCALE())
59
+ end,
60
+ })
61
+ --[[
62
+ *
63
+ * Recalculates the current scale factor based on the target size.
64
+
65
+ ]]
66
+ local function calculateScale()
67
+ local target = TARGET()
68
+ if not target then
69
+ return nil
70
+ end
71
+ local size = if target:IsA("Camera") then target.ViewportSize elseif target:IsA("GuiObject") then target.AbsoluteSize else nil
72
+ if not size then
73
+ return nil
74
+ end
75
+ local res = BASE_RESOLUTION()
76
+ if res.X <= 0 or res.Y <= 0 then
77
+ return nil
78
+ end
79
+ local min = MIN_SCALE()
80
+ local width = math.log(size.X / res.X, 2)
81
+ local height = math.log(size.Y / res.Y, 2)
82
+ local centered = width + (height - width) * DOMINANT_AXIS
83
+ local scale = 2 ^ centered
84
+ SCALE(math.max(scale, min))
85
+ end
86
+ --[[
87
+ *
88
+ * Initializes global px scaling.
89
+ *
90
+ * Should be called exactly once at app mount.
91
+
92
+ ]]
93
+ local function usePx(target, baseResolution, minScale)
94
+ if INITIALIZED then
95
+ warn("usePx() may only be called once")
96
+ return nil
97
+ end
98
+ INITIALIZED = true
99
+ if baseResolution then
100
+ BASE_RESOLUTION(baseResolution)
101
+ end
102
+ if minScale ~= nil then
103
+ MIN_SCALE(minScale)
104
+ end
105
+ if target then
106
+ TARGET(target)
107
+ end
108
+ local resolvedTarget = TARGET()
109
+ if not resolvedTarget then
110
+ warn("usePx(): no valid target to observe")
111
+ return nil
112
+ end
113
+ local signal = if resolvedTarget:IsA("Camera") then resolvedTarget:GetPropertyChangedSignal("ViewportSize") else resolvedTarget:GetPropertyChangedSignal("AbsoluteSize")
114
+ useEventListener(signal, calculateScale)
115
+ calculateScale()
116
+ end
117
+ return {
118
+ usePx = usePx,
119
+ px = px,
120
+ }
@@ -26,7 +26,7 @@ declare namespace Types {
26
26
 
27
27
  type Class = AppProps & {
28
28
  forge: AppForge;
29
- px: typeof import("@rbxts/loners-pretty-vide-utils").px;
29
+ px: typeof import("./hooks/usePx").px;
30
30
  };
31
31
  }
32
32
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rbxts/app-forge",
3
- "version": "0.7.1",
3
+ "version": "0.7.2",
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",