@rbxts/toucan-debugger 0.0.0-stage → 1.0.0

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Over
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,7 @@
1
+ import type { Theme } from './themes';
2
+ export declare const Collapsible: import("@rbxts/toucan").Component<{
3
+ toggleButton: GuiButton;
4
+ chevron: ImageLabel;
5
+ body: GuiObject;
6
+ }>;
7
+ export declare function updateCollapsible(theme: Theme): void;
@@ -0,0 +1,47 @@
1
+ -- Compiled with roblox-ts v3.0.0
2
+ local TS = _G[script]
3
+ local _toucan = TS.import(script, TS.getModule(script, "@rbxts", "toucan").out)
4
+ local component = _toucan.component
5
+ local query = _toucan.query
6
+ local Collapsible = component("Collapsible")
7
+ -- TODO: This is temporary - use `useEvent` or another approach.
8
+ local Done = component("Done")
9
+ local function updateCollapsible(theme)
10
+ query(Collapsible):forEach(function(e, _param)
11
+ local toggleButton = _param.toggleButton
12
+ local chevron = _param.chevron
13
+ local body = _param.body
14
+ local _exp = body:GetChildren()
15
+ -- ▼ ReadonlyArray.some ▼
16
+ local _result = false
17
+ local _callback = function(c)
18
+ return c:IsA("GuiObject")
19
+ end
20
+ for _k, _v in _exp do
21
+ if _callback(_v, _k - 1, _exp) then
22
+ _result = true
23
+ break
24
+ end
25
+ end
26
+ -- ▲ ReadonlyArray.some ▲
27
+ local hasContent = _result
28
+ chevron.Visible = hasContent
29
+ body.Visible = if hasContent then body.Visible else false
30
+ if not e:has(Done) then
31
+ -- Start closed.
32
+ body.Visible = false
33
+ toggleButton.MouseButton1Click:Connect(function()
34
+ if not hasContent then
35
+ return nil
36
+ end
37
+ body.Visible = not body.Visible
38
+ chevron.Image = if body.Visible then theme.icons.chevronDown else theme.icons.chevronRight
39
+ end)
40
+ e:set(Done)
41
+ end
42
+ end)
43
+ end
44
+ return {
45
+ updateCollapsible = updateCollapsible,
46
+ Collapsible = Collapsible,
47
+ }
@@ -0,0 +1,4 @@
1
+ import type { Theme } from './themes';
2
+ export declare const EntryFor: import("@rbxts/toucan").Component<undefined>;
3
+ export declare function drawEntityEntries(theme: Theme): void;
4
+ export declare function bindEntityEntryCleanup(): void;
@@ -0,0 +1,97 @@
1
+ -- Compiled with roblox-ts v3.0.0
2
+ local TS = _G[script]
3
+ local _toucan = TS.import(script, TS.getModule(script, "@rbxts", "toucan").out)
4
+ local Builtin = _toucan.Builtin
5
+ local component = _toucan.component
6
+ local pair = _toucan.pair
7
+ local query = _toucan.query
8
+ local _instance = TS.import(script, script.Parent, "instance")
9
+ local Frame = _instance.Frame
10
+ local ScrollingFrame = _instance.ScrollingFrame
11
+ local _entityEntry = TS.import(script, script.Parent, "ui", "explorer", "entityEntry")
12
+ local entityEntry = _entityEntry.default
13
+ local NoEntry = _entityEntry.NoEntry
14
+ local EntityList = TS.import(script, script.Parent, "ui", "explorer", "entityList").EntityList
15
+ local EntryFor = component("EntryFor")
16
+ local seen = {}
17
+ local function canPlaceEntry(e)
18
+ local _condition = not e:has(NoEntry)
19
+ if _condition then
20
+ local _id = e.id
21
+ _condition = not (seen[_id] ~= nil)
22
+ end
23
+ return _condition
24
+ end
25
+ local function drawEntityEntries(theme)
26
+ local placeEntriesDeep
27
+ placeEntriesDeep = function(e, _param)
28
+ local parentEntt = _param[1]
29
+ local parentInst = _param[2]
30
+ local _binding = entityEntry(e, theme)
31
+ local _binding_1 = _binding[1]
32
+ local entryEntt = _binding_1[1]
33
+ local entryInst = _binding_1[2]
34
+ local body = _binding[2]
35
+ entryEntt:set(pair(Builtin.ChildOf, parentEntt))
36
+ entryEntt:set(pair(EntryFor, e))
37
+ entryInst.Parent = parentInst
38
+ local _id = e.id
39
+ seen[_id] = true
40
+ local _exp = e:children()
41
+ -- ▼ ReadonlyArray.filter ▼
42
+ local _newValue = {}
43
+ local _callback = function(c)
44
+ return canPlaceEntry(c)
45
+ end
46
+ local _length = 0
47
+ for _k, _v in _exp do
48
+ if _callback(_v, _k - 1, _exp) == true then
49
+ _length += 1
50
+ _newValue[_length] = _v
51
+ end
52
+ end
53
+ -- ▲ ReadonlyArray.filter ▲
54
+ -- ▼ ReadonlyArray.forEach ▼
55
+ local _callback_1 = function(c)
56
+ return placeEntriesDeep(c, { entryEntt, body })
57
+ end
58
+ for _k, _v in _newValue do
59
+ _callback_1(_v, _k - 1, _newValue)
60
+ end
61
+ -- ▲ ReadonlyArray.forEach ▲
62
+ end
63
+ local _binding = query(ScrollingFrame):with(EntityList):collect()[1]
64
+ local containerEntt = _binding[1]
65
+ local containerInst = _binding[2]
66
+ local enttKinds = { Builtin.Entity, Builtin.Component, Builtin.Resource }
67
+ -- ▼ ReadonlyArray.forEach ▼
68
+ local _callback = function(enttKind)
69
+ query(enttKind):filter(function(e)
70
+ return canPlaceEntry(e) and e:parent() == nil
71
+ end):forEach(function(e)
72
+ return placeEntriesDeep(e, { containerEntt, containerInst })
73
+ end)
74
+ end
75
+ for _k, _v in enttKinds do
76
+ _callback(_v, _k - 1, enttKinds)
77
+ end
78
+ -- ▲ ReadonlyArray.forEach ▲
79
+ end
80
+ local function bindEntityEntryCleanup()
81
+ EntryFor:onRemoved(function(entry, _v, despawned)
82
+ if despawned then
83
+ return nil
84
+ end
85
+ local frame = entry:get(Frame)
86
+ if not frame then
87
+ return nil
88
+ end
89
+ frame:Destroy()
90
+ entry:despawn()
91
+ end)
92
+ end
93
+ return {
94
+ drawEntityEntries = drawEntityEntries,
95
+ bindEntityEntryCleanup = bindEntityEntryCleanup,
96
+ EntryFor = EntryFor,
97
+ }
package/out/index.d.ts ADDED
@@ -0,0 +1,23 @@
1
+ import type { Scheduler } from '@rbxts/toucan';
2
+ import { type Theme } from './themes';
3
+ export { type Theme, themes } from './themes';
4
+ /**
5
+ * Spawns a visual debugger.
6
+ *
7
+ * It is currently very early in development and cannot observe the server.
8
+ * It can however...
9
+ * - List every entity-like along its id and type icon;
10
+ * - Measure system runtimes (they're under schedules).
11
+ *
12
+ * @remarks
13
+ * Throws if used on the server.
14
+ *
15
+ * @example
16
+ * ```ts
17
+ * import toucanDebugger, { themes } from '@rbxts/toucan-debugger'
18
+ *
19
+ * // Passing a theme is optional — defaults to black.
20
+ * scheduler().usePlugin(toucanDebugger, themes.black).run()
21
+ * ```
22
+ */
23
+ export default function toucanDebugger(scheduler: Scheduler, theme?: Theme): void;
package/out/init.luau ADDED
@@ -0,0 +1,57 @@
1
+ -- Compiled with roblox-ts v3.0.0
2
+ local TS = _G[script]
3
+ local exports = {}
4
+ local _services = TS.import(script, TS.getModule(script, "@rbxts", "services"))
5
+ local Players = _services.Players
6
+ local RunService = _services.RunService
7
+ local updateCollapsible = TS.import(script, script, "collapsible").updateCollapsible
8
+ local _drawEntityEntries = TS.import(script, script, "drawEntityEntries")
9
+ local bindEntityEntryCleanup = _drawEntityEntries.bindEntityEntryCleanup
10
+ local drawEntityEntries = _drawEntityEntries.drawEntityEntries
11
+ local themes = TS.import(script, script, "themes").themes
12
+ local explorerPanel = TS.import(script, script, "ui").default
13
+ local updateRuntimeLabel = TS.import(script, script, "updateRuntimeLabel").updateRuntimeLabel
14
+ exports.themes = TS.import(script, script, "themes").themes
15
+ local function setupDebuggerPanel(parent, theme)
16
+ explorerPanel(parent, theme)
17
+ end
18
+ --[[
19
+ *
20
+ * Spawns a visual debugger.
21
+ *
22
+ * It is currently very early in development and cannot observe the server.
23
+ * It can however...
24
+ * - List every entity-like along its id and type icon;
25
+ * - Measure system runtimes (they're under schedules).
26
+ *
27
+ * @remarks
28
+ * Throws if used on the server.
29
+ *
30
+ * @example
31
+ * ```ts
32
+ * import toucanDebugger, { themes } from '@rbxts/toucan-debugger'
33
+ *
34
+ * // Passing a theme is optional — defaults to black.
35
+ * scheduler().usePlugin(toucanDebugger, themes.black).run()
36
+ * ```
37
+
38
+ ]]
39
+ local function toucanDebugger(scheduler, theme)
40
+ if theme == nil then
41
+ theme = themes.black
42
+ end
43
+ if RunService:IsServer() then
44
+ error("Toucan's debugger can only be used on the client")
45
+ end
46
+ local client = Players.LocalPlayer
47
+ local playerGui = client:WaitForChild("PlayerGui")
48
+ scheduler:useSystem("preStartup", setupDebuggerPanel, {
49
+ args = { playerGui, theme },
50
+ }):useSystem("preStartup", bindEntityEntryCleanup):useSystemChain("last", { { drawEntityEntries, {
51
+ args = { theme },
52
+ } }, { updateCollapsible, {
53
+ args = { theme },
54
+ } }, updateRuntimeLabel })
55
+ end
56
+ exports.default = toucanDebugger
57
+ return exports
@@ -0,0 +1,63 @@
1
+ import { type Component, type Entity } from '@rbxts/toucan';
2
+ export declare const Frame: Component<Frame>;
3
+ export declare const ScreenGui: Component<ScreenGui>;
4
+ export declare const ScrollingFrame: Component<ScrollingFrame>;
5
+ export declare const ImageButton: Component<ImageButton>;
6
+ export declare const ImageLabel: Component<ImageLabel>;
7
+ export declare const TextLabel: Component<TextLabel>;
8
+ export declare const UIPadding: Component<UIPadding>;
9
+ export declare const UIListLayout: Component<UIListLayout>;
10
+ export declare const UIFlexItem: Component<UIFlexItem>;
11
+ export declare const UISizeConstraint: Component<UISizeConstraint>;
12
+ export declare const UIAspectRatioConstraint: Component<UIAspectRatioConstraint>;
13
+ export declare const UICorner: Component<UICorner>;
14
+ export declare const UIStroke: Component<UIStroke>;
15
+ declare const SUPPORTED_INSTANCES: {
16
+ Frame: Component<Frame>;
17
+ ScreenGui: Component<ScreenGui>;
18
+ ScrollingFrame: Component<ScrollingFrame>;
19
+ ImageButton: Component<ImageButton>;
20
+ ImageLabel: Component<ImageLabel>;
21
+ TextLabel: Component<TextLabel>;
22
+ UIPadding: Component<UIPadding>;
23
+ UIListLayout: Component<UIListLayout>;
24
+ UIFlexItem: Component<UIFlexItem>;
25
+ UISizeConstraint: Component<UISizeConstraint>;
26
+ UIAspectRatioConstraint: Component<UIAspectRatioConstraint>;
27
+ UICorner: Component<UICorner>;
28
+ UIStroke: Component<UIStroke>;
29
+ };
30
+ type SupportedInstances = typeof SUPPORTED_INSTANCES;
31
+ export type EntityInstance<I extends Instance = Instance> = [Entity, I];
32
+ type Child = EntityInstance | Entity | false | undefined;
33
+ type InstanceProperties<T extends Instance> = Partial<WritableInstanceProperties<T>> & {
34
+ child?: Child;
35
+ children?: Child[];
36
+ callback?: (entity: Entity, instance: T) => void;
37
+ Name?: string;
38
+ };
39
+ /**
40
+ * Spawns an entity paired with a new instance of the given `className`, automatically setting the corresponding component and parenting any children.
41
+ *
42
+ * Giving the instance a `Name` will also give its entity a `Label` of the same name.
43
+ *
44
+ * @remarks
45
+ * Not every instance is supported, as there needs to be a corresponding component for it. If there's an instance you need that isn't supported, consider contributing.
46
+ *
47
+ * @example
48
+ * let textLabel: TextLabel
49
+ *
50
+ * const [btnEntt, btnInst] = entityFor('ImageButton', {
51
+ * Size: new UDim2(0, 100, 0, 40),
52
+ * Parent: playerGui,
53
+ * child: entityFor('TextLabel', {
54
+ * Text: 'Click me',
55
+ * callback: (entt, inst) => {
56
+ * entt.set(SpecialLabel)
57
+ * textLabel = inst
58
+ * },
59
+ * }),
60
+ * })
61
+ */
62
+ export declare function entityFor<T extends keyof SupportedInstances>(className: T, properties?: InstanceProperties<CreatableInstances[T]>): EntityInstance<CreatableInstances[T]>;
63
+ export {};
@@ -0,0 +1,138 @@
1
+ -- Compiled with roblox-ts v3.0.0
2
+ local TS = _G[script]
3
+ local _toucan = TS.import(script, TS.getModule(script, "@rbxts", "toucan").out)
4
+ local Builtin = _toucan.Builtin
5
+ local component = _toucan.component
6
+ local EntityLike = _toucan.EntityLike
7
+ local entity = _toucan.entity
8
+ local pair = _toucan.pair
9
+ local Frame = component("Frame")
10
+ local ScreenGui = component("ScreenGui")
11
+ local ScrollingFrame = component("ScrollingFrame")
12
+ local ImageButton = component("ImageButton")
13
+ local ImageLabel = component("ImageLabel")
14
+ local TextLabel = component("TextLabel")
15
+ local UIPadding = component("UIPadding")
16
+ local UIListLayout = component("UIListLayout")
17
+ local UIFlexItem = component("UIFlexItem")
18
+ local UISizeConstraint = component("UISizeConstraint")
19
+ local UIAspectRatioConstraint = component("UIAspectRatioConstraint")
20
+ local UICorner = component("UICorner")
21
+ local UIStroke = component("UIStroke")
22
+ local SUPPORTED_INSTANCES = {
23
+ Frame = Frame,
24
+ ScreenGui = ScreenGui,
25
+ ScrollingFrame = ScrollingFrame,
26
+ ImageButton = ImageButton,
27
+ ImageLabel = ImageLabel,
28
+ TextLabel = TextLabel,
29
+ UIPadding = UIPadding,
30
+ UIListLayout = UIListLayout,
31
+ UIFlexItem = UIFlexItem,
32
+ UISizeConstraint = UISizeConstraint,
33
+ UIAspectRatioConstraint = UIAspectRatioConstraint,
34
+ UICorner = UICorner,
35
+ UIStroke = UIStroke,
36
+ }
37
+ local RESERVED_PROPERTIES = { "child", "children", "callback" }
38
+ --[[
39
+ *
40
+ * Spawns an entity paired with a new instance of the given `className`, automatically setting the corresponding component and parenting any children.
41
+ *
42
+ * Giving the instance a `Name` will also give its entity a `Label` of the same name.
43
+ *
44
+ * @remarks
45
+ * Not every instance is supported, as there needs to be a corresponding component for it. If there's an instance you need that isn't supported, consider contributing.
46
+ *
47
+ * @example
48
+ * let textLabel: TextLabel
49
+ *
50
+ * const [btnEntt, btnInst] = entityFor('ImageButton', {
51
+ * Size: new UDim2(0, 100, 0, 40),
52
+ * Parent: playerGui,
53
+ * child: entityFor('TextLabel', {
54
+ * Text: 'Click me',
55
+ * callback: (entt, inst) => {
56
+ * entt.set(SpecialLabel)
57
+ * textLabel = inst
58
+ * },
59
+ * }),
60
+ * })
61
+
62
+ ]]
63
+ local function entityFor(className, properties)
64
+ if properties == nil then
65
+ properties = {}
66
+ end
67
+ local _result = properties
68
+ if _result ~= nil then
69
+ _result = _result.Name
70
+ end
71
+ local _condition = _result
72
+ if _condition == nil then
73
+ _condition = className
74
+ end
75
+ local entt = entity(_condition)
76
+ local inst = Instance.new(className)
77
+ entt:set(SUPPORTED_INSTANCES[className], inst)
78
+ for k, v in pairs(properties) do
79
+ if table.find(RESERVED_PROPERTIES, k) ~= nil then
80
+ continue
81
+ end
82
+ inst[k] = v
83
+ end
84
+ local parentChild = function(child, entt, inst)
85
+ if not child then
86
+ return nil
87
+ end
88
+ if TS.instanceof(child, EntityLike) then
89
+ child:set(pair(Builtin.ChildOf, entt))
90
+ else
91
+ local _binding = child
92
+ local childEntt = _binding[1]
93
+ local childInst = _binding[2]
94
+ childEntt:set(pair(Builtin.ChildOf, entt))
95
+ childInst.Parent = inst
96
+ end
97
+ end
98
+ local _result_1 = properties
99
+ if _result_1 ~= nil then
100
+ _result_1 = _result_1.child
101
+ end
102
+ parentChild(_result_1, entt, inst)
103
+ local _result_2 = properties
104
+ if _result_2 ~= nil then
105
+ _result_2 = _result_2.children
106
+ end
107
+ local _condition_1 = _result_2
108
+ if _condition_1 == nil then
109
+ _condition_1 = {}
110
+ end
111
+ for _, child in _condition_1 do
112
+ parentChild(child, entt, inst)
113
+ end
114
+ local _result_3 = properties
115
+ if _result_3 ~= nil then
116
+ _result_3 = _result_3.callback
117
+ if _result_3 ~= nil then
118
+ _result_3(entt, inst)
119
+ end
120
+ end
121
+ return { entt, inst }
122
+ end
123
+ return {
124
+ entityFor = entityFor,
125
+ Frame = Frame,
126
+ ScreenGui = ScreenGui,
127
+ ScrollingFrame = ScrollingFrame,
128
+ ImageButton = ImageButton,
129
+ ImageLabel = ImageLabel,
130
+ TextLabel = TextLabel,
131
+ UIPadding = UIPadding,
132
+ UIListLayout = UIListLayout,
133
+ UIFlexItem = UIFlexItem,
134
+ UISizeConstraint = UISizeConstraint,
135
+ UIAspectRatioConstraint = UIAspectRatioConstraint,
136
+ UICorner = UICorner,
137
+ UIStroke = UIStroke,
138
+ }
@@ -0,0 +1,3 @@
1
+ import type { Theme } from '.';
2
+ declare const blackTheme: Theme;
3
+ export default blackTheme;
@@ -0,0 +1,50 @@
1
+ -- Compiled with roblox-ts v3.0.0
2
+ local FONT_INTER = "rbxassetid://12187365364"
3
+ local blackTheme = {
4
+ colors = {
5
+ content = Color3.fromRGB(255, 255, 255),
6
+ contentMuted = Color3.fromRGB(130, 130, 130),
7
+ contentInverse = Color3.fromRGB(0, 0, 0),
8
+ surface1 = Color3.fromRGB(0, 0, 0),
9
+ surface2 = Color3.fromRGB(16, 16, 16),
10
+ surface3 = Color3.fromRGB(24, 24, 24),
11
+ border = Color3.fromRGB(64, 64, 64),
12
+ borderMuted = Color3.fromRGB(32, 32, 32),
13
+ client = Color3.fromRGB(40, 140, 255),
14
+ server = Color3.fromRGB(40, 255, 55),
15
+ err = Color3.fromRGB(255, 40, 40),
16
+ },
17
+ icons = {
18
+ chevronRight = "rbxassetid://84839472552140",
19
+ chevronRightBold = "rbxassetid://78446529333772",
20
+ chevronDown = "rbxassetid://123273356220210",
21
+ chevronDownBold = "rbxassetid://71570100619926",
22
+ entity = "rbxassetid://117106064006931",
23
+ component = "rbxassetid://132097048540989",
24
+ system = "rbxassetid://104146467001055",
25
+ schedule = "rbxassetid://73049254764861",
26
+ plugin = "rbxassetid://85799610981034",
27
+ },
28
+ fonts = {
29
+ black = Font.new(FONT_INTER, Enum.FontWeight.Heavy),
30
+ bold = Font.new(FONT_INTER, Enum.FontWeight.Bold),
31
+ regular = Font.new(FONT_INTER, Enum.FontWeight.Regular),
32
+ extraLightItalic = Font.new(FONT_INTER, Enum.FontWeight.ExtraLight, Enum.FontStyle.Italic),
33
+ },
34
+ fontSizes = {
35
+ tiny = 10,
36
+ small = 12,
37
+ large = 16,
38
+ huge = 36,
39
+ },
40
+ paddingSizes = {
41
+ small = UDim.new(nil, 3),
42
+ medium = UDim.new(nil, 6),
43
+ large = UDim.new(nil, 9),
44
+ },
45
+ explorerIndent = 20,
46
+ }
47
+ local default = blackTheme
48
+ return {
49
+ default = default,
50
+ }
@@ -0,0 +1,49 @@
1
+ export declare const ROBLOX_TOPBAR_HEIGHT = 58;
2
+ export interface Theme {
3
+ colors: {
4
+ content: Color3;
5
+ contentMuted: Color3;
6
+ contentInverse: Color3;
7
+ surface1: Color3;
8
+ surface2: Color3;
9
+ surface3: Color3;
10
+ border: Color3;
11
+ borderMuted: Color3;
12
+ client: Color3;
13
+ server: Color3;
14
+ err: Color3;
15
+ };
16
+ icons: {
17
+ chevronRight: string;
18
+ chevronRightBold: string;
19
+ chevronDown: string;
20
+ chevronDownBold: string;
21
+ entity: string;
22
+ component: string;
23
+ system: string;
24
+ schedule: string;
25
+ plugin: string;
26
+ };
27
+ fonts: {
28
+ black: Font;
29
+ bold: Font;
30
+ regular: Font;
31
+ extraLightItalic: Font;
32
+ };
33
+ fontSizes: {
34
+ tiny: number;
35
+ small: number;
36
+ large: number;
37
+ huge: number;
38
+ };
39
+ paddingSizes: {
40
+ small: UDim;
41
+ medium: UDim;
42
+ large: UDim;
43
+ };
44
+ explorerIndent: number;
45
+ }
46
+ export declare const themes: {
47
+ black: Theme;
48
+ white: Theme;
49
+ };
@@ -0,0 +1,13 @@
1
+ -- Compiled with roblox-ts v3.0.0
2
+ local TS = _G[script]
3
+ local blackTheme = TS.import(script, script, "black").default
4
+ local whiteTheme = TS.import(script, script, "white").default
5
+ local ROBLOX_TOPBAR_HEIGHT = 58
6
+ local themes = {
7
+ black = blackTheme,
8
+ white = whiteTheme,
9
+ }
10
+ return {
11
+ ROBLOX_TOPBAR_HEIGHT = ROBLOX_TOPBAR_HEIGHT,
12
+ themes = themes,
13
+ }
@@ -0,0 +1,3 @@
1
+ import type { Theme } from '.';
2
+ declare const whiteTheme: Theme;
3
+ export default whiteTheme;
@@ -0,0 +1,50 @@
1
+ -- Compiled with roblox-ts v3.0.0
2
+ local FONT_INTER = "rbxassetid://12187365364"
3
+ local whiteTheme = {
4
+ colors = {
5
+ content = Color3.fromRGB(0, 0, 0),
6
+ contentMuted = Color3.fromRGB(125, 125, 125),
7
+ contentInverse = Color3.fromRGB(255, 255, 255),
8
+ surface1 = Color3.fromRGB(255, 255, 255),
9
+ surface2 = Color3.fromRGB(240, 240, 240),
10
+ surface3 = Color3.fromRGB(230, 230, 230),
11
+ border = Color3.fromRGB(190, 190, 190),
12
+ borderMuted = Color3.fromRGB(223, 223, 223),
13
+ client = Color3.fromRGB(0, 110, 230),
14
+ server = Color3.fromRGB(0, 170, 30),
15
+ err = Color3.fromRGB(220, 30, 30),
16
+ },
17
+ icons = {
18
+ chevronRight = "rbxassetid://84839472552140",
19
+ chevronRightBold = "rbxassetid://78446529333772",
20
+ chevronDown = "rbxassetid://123273356220210",
21
+ chevronDownBold = "rbxassetid://71570100619926",
22
+ entity = "rbxassetid://117106064006931",
23
+ component = "rbxassetid://132097048540989",
24
+ system = "rbxassetid://104146467001055",
25
+ schedule = "rbxassetid://73049254764861",
26
+ plugin = "rbxassetid://85799610981034",
27
+ },
28
+ fonts = {
29
+ black = Font.new(FONT_INTER, Enum.FontWeight.Heavy),
30
+ bold = Font.new(FONT_INTER, Enum.FontWeight.Bold),
31
+ regular = Font.new(FONT_INTER, Enum.FontWeight.Regular),
32
+ extraLightItalic = Font.new(FONT_INTER, Enum.FontWeight.ExtraLight, Enum.FontStyle.Italic),
33
+ },
34
+ fontSizes = {
35
+ tiny = 10,
36
+ small = 12,
37
+ large = 16,
38
+ huge = 36,
39
+ },
40
+ paddingSizes = {
41
+ small = UDim.new(nil, 3),
42
+ medium = UDim.new(nil, 6),
43
+ large = UDim.new(nil, 9),
44
+ },
45
+ explorerIndent = 20,
46
+ }
47
+ local default = whiteTheme
48
+ return {
49
+ default = default,
50
+ }
@@ -0,0 +1,3 @@
1
+ import { type EntityInstance } from '../../instance';
2
+ import type { Theme } from '../../themes';
3
+ export default function dashboard(theme: Theme): EntityInstance<Frame>;
@@ -0,0 +1,14 @@
1
+ -- Compiled with roblox-ts v3.0.0
2
+ local TS = _G[script]
3
+ local entityFor = TS.import(script, script.Parent.Parent.Parent, "instance").entityFor
4
+ local function dashboard(theme)
5
+ return entityFor("Frame", {
6
+ Name = "dashboard",
7
+ AutomaticSize = Enum.AutomaticSize.Y,
8
+ BackgroundColor3 = theme.colors.surface1,
9
+ LayoutOrder = 0,
10
+ })
11
+ end
12
+ return {
13
+ default = dashboard,
14
+ }
@@ -0,0 +1,5 @@
1
+ import { type EntityLike } from '@rbxts/toucan';
2
+ import { type EntityInstance } from '../../instance';
3
+ import type { Theme } from '../../themes';
4
+ export declare const NoEntry: import("@rbxts/toucan").Component<undefined>;
5
+ export default function entityEntry(entity: EntityLike, theme: Theme): [EntityInstance<Frame>, Frame];
@@ -0,0 +1,182 @@
1
+ -- Compiled with roblox-ts v3.0.0
2
+ local TS = _G[script]
3
+ local _toucan = TS.import(script, TS.getModule(script, "@rbxts", "toucan").out)
4
+ local Builtin = _toucan.Builtin
5
+ local component = _toucan.component
6
+ local pair = _toucan.pair
7
+ local Collapsible = TS.import(script, script.Parent.Parent.Parent, "collapsible").Collapsible
8
+ local entityFor = TS.import(script, script.Parent.Parent.Parent, "instance").entityFor
9
+ local RuntimeLabelFor = TS.import(script, script.Parent.Parent.Parent, "updateRuntimeLabel").RuntimeLabelFor
10
+ local HEADER_HEIGHT = 20
11
+ local NoEntry = component("NoEntry")
12
+ local function getEntityIcon(entity, theme)
13
+ if entity:has(Builtin.Component) then
14
+ return theme.icons.component
15
+ end
16
+ if entity:has(Builtin.System) then
17
+ return theme.icons.system
18
+ end
19
+ if entity:has(Builtin.Schedule) then
20
+ return theme.icons.schedule
21
+ end
22
+ if entity:has(Builtin.Plugin) then
23
+ return theme.icons.plugin
24
+ end
25
+ return theme.icons.entity
26
+ end
27
+ local function makeHeader(entity, theme, callback)
28
+ local chevron
29
+ local header = entityFor("ImageButton", {
30
+ Name = "header",
31
+ Size = UDim2.new(1, 0, 0, HEADER_HEIGHT),
32
+ BackgroundColor3 = theme.colors.surface2,
33
+ LayoutOrder = 0,
34
+ children = { entityFor("UIListLayout", {
35
+ FillDirection = Enum.FillDirection.Horizontal,
36
+ SortOrder = Enum.SortOrder.LayoutOrder,
37
+ Padding = theme.paddingSizes.medium,
38
+ }), entityFor("UIPadding", {
39
+ PaddingBottom = theme.paddingSizes.small,
40
+ PaddingTop = theme.paddingSizes.small,
41
+ PaddingLeft = theme.paddingSizes.small,
42
+ PaddingRight = theme.paddingSizes.small,
43
+ }), entityFor("UICorner", {
44
+ CornerRadius = theme.paddingSizes.medium,
45
+ }), entityFor("UIStroke", {
46
+ Color = theme.colors.border,
47
+ }), entityFor("Frame", {
48
+ Name = "left",
49
+ Size = UDim2.fromScale(0, 1),
50
+ BackgroundTransparency = 1,
51
+ children = { entityFor("UIListLayout", {
52
+ FillDirection = Enum.FillDirection.Horizontal,
53
+ SortOrder = Enum.SortOrder.LayoutOrder,
54
+ Padding = theme.paddingSizes.medium,
55
+ }), entityFor("UIFlexItem", {
56
+ FlexMode = Enum.UIFlexMode.Fill,
57
+ }), entityFor("ImageLabel", {
58
+ Name = "icon",
59
+ Image = getEntityIcon(entity, theme),
60
+ ImageColor3 = theme.colors.contentMuted,
61
+ Size = UDim2.fromScale(0, 1),
62
+ BackgroundTransparency = 1,
63
+ LayoutOrder = 0,
64
+ child = entityFor("UIAspectRatioConstraint", {
65
+ AspectType = Enum.AspectType.ScaleWithParentSize,
66
+ DominantAxis = Enum.DominantAxis.Height,
67
+ }),
68
+ }), entityFor("TextLabel", {
69
+ Name = "id",
70
+ Text = `#{entity.id}`,
71
+ FontFace = theme.fonts.regular,
72
+ TextSize = theme.fontSizes.small,
73
+ TextXAlignment = Enum.TextXAlignment.Left,
74
+ TextColor3 = theme.colors.contentMuted,
75
+ Size = UDim2.new(0, 33.5, 1, 0),
76
+ BackgroundTransparency = 1,
77
+ LayoutOrder = 1,
78
+ }), entityFor("TextLabel", {
79
+ Name = "label",
80
+ Text = entity:toString(),
81
+ FontFace = theme.fonts.regular,
82
+ TextSize = theme.fontSizes.small,
83
+ TextXAlignment = Enum.TextXAlignment.Left,
84
+ TextColor3 = theme.colors.content,
85
+ Size = UDim2.new(0, 40, 1, 0),
86
+ AutomaticSize = Enum.AutomaticSize.X,
87
+ BackgroundTransparency = 1,
88
+ LayoutOrder = 2,
89
+ }), entity:has(Builtin.System) and entityFor("TextLabel", {
90
+ Name = "runtime",
91
+ Text = "?",
92
+ FontFace = theme.fonts.extraLightItalic,
93
+ TextSize = theme.fontSizes.tiny,
94
+ TextXAlignment = Enum.TextXAlignment.Left,
95
+ TextYAlignment = Enum.TextYAlignment.Bottom,
96
+ TextColor3 = theme.colors.contentMuted,
97
+ Size = UDim2.new(0, 35, 1, 0),
98
+ BackgroundTransparency = 1,
99
+ LayoutOrder = 3,
100
+ callback = function(e)
101
+ return e:set(pair(RuntimeLabelFor, entity))
102
+ end,
103
+ }) },
104
+ }), entityFor("ImageLabel", {
105
+ Name = "chevron",
106
+ Size = UDim2.fromScale(0, 1),
107
+ Position = UDim2.fromScale(0.8, 0),
108
+ BackgroundTransparency = 1,
109
+ Image = theme.icons.chevronRight,
110
+ callback = function(_e, inst)
111
+ chevron = inst
112
+ end,
113
+ child = entityFor("UIAspectRatioConstraint", {
114
+ AspectType = Enum.AspectType.ScaleWithParentSize,
115
+ DominantAxis = Enum.DominantAxis.Height,
116
+ }),
117
+ }) },
118
+ })
119
+ callback(header[2], chevron)
120
+ return header
121
+ end
122
+ local function entityEntry(entity, theme)
123
+ local header
124
+ local chevron
125
+ local body
126
+ local entry = entityFor("Frame", {
127
+ Name = `entityEntry: {entity}`,
128
+ Size = UDim2.fromScale(0, 0),
129
+ AutomaticSize = Enum.AutomaticSize.Y,
130
+ Transparency = 1,
131
+ children = { entityFor("UIListLayout", {
132
+ FillDirection = Enum.FillDirection.Vertical,
133
+ SortOrder = Enum.SortOrder.LayoutOrder,
134
+ Padding = theme.paddingSizes.medium,
135
+ }), makeHeader(entity, theme, function(hdr, chv)
136
+ header = hdr
137
+ chevron = chv
138
+ end), entityFor("Frame", {
139
+ Name = "body",
140
+ Size = UDim2.fromScale(1, 0),
141
+ AutomaticSize = Enum.AutomaticSize.Y,
142
+ Transparency = 1,
143
+ LayoutOrder = 1,
144
+ callback = function(_e, inst)
145
+ body = inst
146
+ end,
147
+ children = { entityFor("UIListLayout", {
148
+ FillDirection = Enum.FillDirection.Vertical,
149
+ HorizontalFlex = Enum.UIFlexAlignment.Fill,
150
+ SortOrder = Enum.SortOrder.LayoutOrder,
151
+ Padding = theme.paddingSizes.medium,
152
+ }), entityFor("UIPadding", {
153
+ PaddingLeft = UDim.new(0, theme.explorerIndent),
154
+ }) },
155
+ }) },
156
+ })
157
+ entry[1]:set(Collapsible, {
158
+ toggleButton = header,
159
+ chevron = chevron,
160
+ body = body,
161
+ })
162
+ -- Without this, `drawEntities` would add entries infinitely, crashing the game.
163
+ local addNoEntryRecursive
164
+ addNoEntryRecursive = function(e)
165
+ e:set(NoEntry)
166
+ local _exp = e:children()
167
+ -- ▼ ReadonlyArray.forEach ▼
168
+ local _callback = function(c)
169
+ return addNoEntryRecursive(c)
170
+ end
171
+ for _k, _v in _exp do
172
+ _callback(_v, _k - 1, _exp)
173
+ end
174
+ -- ▲ ReadonlyArray.forEach ▲
175
+ end
176
+ addNoEntryRecursive(entry[1])
177
+ return { entry, body }
178
+ end
179
+ return {
180
+ default = entityEntry,
181
+ NoEntry = NoEntry,
182
+ }
@@ -0,0 +1,4 @@
1
+ import { type EntityInstance } from '../../instance';
2
+ import type { Theme } from '../../themes';
3
+ export declare const EntityList: import("@rbxts/toucan").Component<undefined>;
4
+ export default function entityList(theme: Theme): EntityInstance<ScrollingFrame>;
@@ -0,0 +1,36 @@
1
+ -- Compiled with roblox-ts v3.0.0
2
+ local TS = _G[script]
3
+ local component = TS.import(script, TS.getModule(script, "@rbxts", "toucan").out).component
4
+ local entityFor = TS.import(script, script.Parent.Parent.Parent, "instance").entityFor
5
+ local EntityList = component("EntityList")
6
+ local function entityList(theme)
7
+ return entityFor("ScrollingFrame", {
8
+ Name = "entityList",
9
+ CanvasSize = UDim2.fromScale(0, 0),
10
+ AutomaticCanvasSize = Enum.AutomaticSize.Y,
11
+ ScrollBarThickness = 0,
12
+ BackgroundColor3 = theme.colors.surface1,
13
+ BorderColor3 = theme.colors.border,
14
+ LayoutOrder = 1,
15
+ callback = function(e)
16
+ return e:set(EntityList)
17
+ end,
18
+ children = { entityFor("UIFlexItem", {
19
+ FlexMode = Enum.UIFlexMode.Fill,
20
+ }), entityFor("UIListLayout", {
21
+ FillDirection = Enum.FillDirection.Vertical,
22
+ HorizontalFlex = Enum.UIFlexAlignment.Fill,
23
+ SortOrder = Enum.SortOrder.LayoutOrder,
24
+ Padding = theme.paddingSizes.medium,
25
+ }), entityFor("UIPadding", {
26
+ PaddingLeft = theme.paddingSizes.medium,
27
+ PaddingRight = theme.paddingSizes.medium,
28
+ PaddingTop = theme.paddingSizes.large,
29
+ PaddingBottom = theme.paddingSizes.large,
30
+ }) },
31
+ })
32
+ end
33
+ return {
34
+ default = entityList,
35
+ EntityList = EntityList,
36
+ }
@@ -0,0 +1,3 @@
1
+ import { type EntityInstance } from '../../instance';
2
+ import type { Theme } from '../../themes';
3
+ export default function explorer(theme: Theme): EntityInstance<Frame>;
@@ -0,0 +1,22 @@
1
+ -- Compiled with roblox-ts v3.0.0
2
+ local TS = _G[script]
3
+ local entityFor = TS.import(script, script.Parent.Parent, "instance").entityFor
4
+ local dashboard = TS.import(script, script, "dashboard").default
5
+ local entityList = TS.import(script, script, "entityList").default
6
+ local function explorer(theme)
7
+ return entityFor("Frame", {
8
+ Name = "explorer",
9
+ Size = UDim2.new(1, 0, 1, -35),
10
+ Transparency = 1,
11
+ LayoutOrder = 1,
12
+ children = { entityFor("UIFlexItem", {
13
+ FlexMode = Enum.UIFlexMode.Fill,
14
+ }), entityFor("UIListLayout", {
15
+ HorizontalFlex = Enum.UIFlexAlignment.Fill,
16
+ SortOrder = Enum.SortOrder.LayoutOrder,
17
+ }), dashboard(theme), entityList(theme) },
18
+ })
19
+ end
20
+ return {
21
+ default = explorer,
22
+ }
@@ -0,0 +1,3 @@
1
+ import { type EntityInstance } from '../instance';
2
+ import type { Theme } from '../themes';
3
+ export default function footer(theme: Theme): EntityInstance<Frame>;
@@ -0,0 +1,15 @@
1
+ -- Compiled with roblox-ts v3.0.0
2
+ local TS = _G[script]
3
+ local entityFor = TS.import(script, script.Parent.Parent, "instance").entityFor
4
+ local function footer(theme)
5
+ return entityFor("Frame", {
6
+ Name = "footer",
7
+ Size = UDim2.new(1, 0, 0, 25),
8
+ BackgroundColor3 = theme.colors.surface1,
9
+ BorderColor3 = theme.colors.border,
10
+ LayoutOrder = 2,
11
+ })
12
+ end
13
+ return {
14
+ default = footer,
15
+ }
@@ -0,0 +1,3 @@
1
+ import { type EntityInstance } from '../instance';
2
+ import { type Theme } from '../themes';
3
+ export default function hero(theme: Theme): EntityInstance<Frame>;
@@ -0,0 +1,28 @@
1
+ -- Compiled with roblox-ts v3.0.0
2
+ local TS = _G[script]
3
+ local entityFor = TS.import(script, script.Parent.Parent, "instance").entityFor
4
+ local ROBLOX_TOPBAR_HEIGHT = TS.import(script, script.Parent.Parent, "themes").ROBLOX_TOPBAR_HEIGHT
5
+ local function hero(theme)
6
+ return entityFor("Frame", {
7
+ Name = "hero",
8
+ Size = UDim2.new(1, 0, 0, ROBLOX_TOPBAR_HEIGHT),
9
+ BackgroundTransparency = 1,
10
+ LayoutOrder = 0,
11
+ children = { entityFor("UIPadding", {
12
+ PaddingRight = theme.paddingSizes.medium,
13
+ }), entityFor("TextLabel", {
14
+ Name = "title",
15
+ Text = "Toucan",
16
+ FontFace = theme.fonts.black,
17
+ Size = UDim2.new(1, 0, 1, 0),
18
+ TextXAlignment = Enum.TextXAlignment.Right,
19
+ TextYAlignment = Enum.TextYAlignment.Bottom,
20
+ TextColor3 = theme.colors.contentInverse,
21
+ TextSize = theme.fontSizes.huge,
22
+ BackgroundTransparency = 1,
23
+ }) },
24
+ })
25
+ end
26
+ return {
27
+ default = hero,
28
+ }
@@ -0,0 +1,2 @@
1
+ import type { Theme } from '../themes';
2
+ export default function explorerPanel(parent: Instance, theme: Theme): void;
@@ -0,0 +1,29 @@
1
+ -- Compiled with roblox-ts v3.0.0
2
+ local TS = _G[script]
3
+ local entityFor = TS.import(script, script.Parent, "instance").entityFor
4
+ local explorer = TS.import(script, script, "explorer").default
5
+ local footer = TS.import(script, script, "footer").default
6
+ local hero = TS.import(script, script, "hero").default
7
+ local function explorerPanel(parent, theme)
8
+ entityFor("ScreenGui", {
9
+ Name = "toucanExplorer",
10
+ Parent = parent,
11
+ IgnoreGuiInset = true,
12
+ children = { entityFor("Frame", {
13
+ Name = "panel",
14
+ Position = UDim2.fromScale(0, 0),
15
+ Size = UDim2.new(0, 300, 1, 0),
16
+ BackgroundColor3 = theme.colors.surface2,
17
+ BorderColor3 = theme.colors.border,
18
+ BackgroundTransparency = 0.15,
19
+ children = { entityFor("UIListLayout", {
20
+ FillDirection = Enum.FillDirection.Vertical,
21
+ HorizontalFlex = Enum.UIFlexAlignment.Fill,
22
+ SortOrder = Enum.SortOrder.LayoutOrder,
23
+ }), hero(theme), explorer(theme), footer(theme) },
24
+ }) },
25
+ })
26
+ end
27
+ return {
28
+ default = explorerPanel,
29
+ }
@@ -0,0 +1,2 @@
1
+ export declare const RuntimeLabelFor: import("@rbxts/toucan").Component<undefined>;
2
+ export declare function updateRuntimeLabel(): void;
@@ -0,0 +1,37 @@
1
+ -- Compiled with roblox-ts v3.0.0
2
+ local TS = _G[script]
3
+ local _toucan = TS.import(script, TS.getModule(script, "@rbxts", "toucan").out)
4
+ local Builtin = _toucan.Builtin
5
+ local component = _toucan.component
6
+ local pair = _toucan.pair
7
+ local query = _toucan.query
8
+ local TextLabel = TS.import(script, script.Parent, "instance").TextLabel
9
+ local UPDATE_INTERVAL = 0.5
10
+ local RuntimeLabelFor = component("RuntimeLabelFor")
11
+ local lastRun = 0
12
+ local function formatRuntime(ms)
13
+ if ms >= 1 then
14
+ return `{math.round(ms * 10) / 10}ms`
15
+ end
16
+ local us = ms * 1000
17
+ local usRounded = math.round(us * 10) / 10
18
+ local showDecimal = usRounded < 10
19
+ return if showDecimal then `{usRounded}µs` else `{math.round(us)}µs`
20
+ end
21
+ local function updateRuntimeLabel()
22
+ local now = os.clock()
23
+ -- TODO: use something like `useThrottle` once its plugin gets published.
24
+ if now - lastRun < UPDATE_INTERVAL then
25
+ return nil
26
+ end
27
+ lastRun = now
28
+ query(TextLabel):with(pair(RuntimeLabelFor, Builtin.Wildcard)):forEach(function(e, label)
29
+ local systemEntt = e:targetOf(RuntimeLabelFor)
30
+ local system = systemEntt:get(Builtin.System)
31
+ label.Text = formatRuntime(system.runtimeMs)
32
+ end)
33
+ end
34
+ return {
35
+ updateRuntimeLabel = updateRuntimeLabel,
36
+ RuntimeLabelFor = RuntimeLabelFor,
37
+ }
package/package.json CHANGED
@@ -1,6 +1,40 @@
1
1
  {
2
- "name": "@rbxts/toucan-debugger",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
2
+ "name": "@rbxts/toucan-debugger",
3
+ "version": "1.0.0",
4
+ "description": "",
5
+ "main": "out/init.lua",
6
+ "scripts": {
7
+ "setup": "bun i && bun i --cwd test && rokit install",
8
+ "build": "rbxtsc",
9
+ "dev": "concurrently -n \"LIB,TEST,ROJO\" -c \"blue,magenta,yellow\" \"rbxtsc -w\" \"rbxtsc -p test -w\" \"rojo serve test/default.project.json\"",
10
+ "prepublishOnly": "npm run build",
11
+ "lint": "biome check --write .",
12
+ "format": "biome format --write .",
13
+ "predev": "rbxtsc && rbxtsc -p test",
14
+ "preinstall": "npx only-allow bun"
15
+ },
16
+ "keywords": [],
17
+ "author": "",
18
+ "license": "ISC",
19
+ "type": "commonjs",
20
+ "types": "out/index.d.ts",
21
+ "files": [
22
+ "out",
23
+ "!**/*.tsbuildinfo"
24
+ ],
25
+ "publishConfig": {
26
+ "access": "public"
27
+ },
28
+ "devDependencies": {
29
+ "@biomejs/biome": "2.5.0",
30
+ "@rbxts/compiler-types": "^3.0.0-types.0",
31
+ "@rbxts/types": "^1.0.959",
32
+ "concurrently": "^10.0.3",
33
+ "roblox-ts": "^3.0.0",
34
+ "typescript": "^5.9.3"
35
+ },
36
+ "dependencies": {
37
+ "@rbxts/services": "^1.6.0",
38
+ "@rbxts/toucan": "0.6.0"
39
+ }
40
+ }
package/README.md DELETED
@@ -1,3 +0,0 @@
1
- # Temporary Holding Version
2
-
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.