pulse-rb 1.2.24
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 +225 -0
- package/adapters/linoria.lua +233 -0
- package/adapters/windui.llms.txt +366 -0
- package/adapters/windui.lua +505 -0
- package/bin/rb.js +2 -0
- package/dist/index.js +3285 -0
- package/package.json +59 -0
- package/pulse/dev/debuggui.lua +1206 -0
- package/pulse/dev/devconfig.lua +81 -0
- package/pulse/dev/ui/9_DevPanel.lua +384 -0
- package/pulse/helpers/aim.lua +193 -0
- package/pulse/helpers/cache.lua +68 -0
- package/pulse/helpers/cleaner.lua +110 -0
- package/pulse/helpers/conn.lua +33 -0
- package/pulse/helpers/cooldown.lua +47 -0
- package/pulse/helpers/draw.lua +122 -0
- package/pulse/helpers/hitbox.lua +63 -0
- package/pulse/helpers/input.lua +24 -0
- package/pulse/helpers/log.lua +228 -0
- package/pulse/helpers/loop.lua +58 -0
- package/pulse/helpers/memory.lua +48 -0
- package/pulse/helpers/narrate.lua +160 -0
- package/pulse/helpers/notify.lua +51 -0
- package/pulse/helpers/perf.lua +48 -0
- package/pulse/helpers/remote.lua +128 -0
- package/pulse/helpers/restore.lua +59 -0
- package/pulse/helpers/store.lua +39 -0
- package/pulse/helpers/team.lua +83 -0
- package/pulse/helpers/testmode.lua +80 -0
- package/pulse/helpers/trace.lua +111 -0
- package/pulse/helpers/track.lua +85 -0
- package/pulse/helpers/vec.lua +52 -0
- package/pulse/helpers/world.lua +51 -0
- package/pulse/runtime.lua +343 -0
- package/pulse/ui/linoria_settings.lua +55 -0
- package/pulse/ui/windui_settings.lua +87 -0
- package/templates/AGENTS.md +177 -0
- package/templates/CLAUDE.md +424 -0
- package/templates/deploy_config.example +17 -0
- package/templates/example_esp.rblua +69 -0
- package/templates/example_fov.rblua +20 -0
- package/templates/example_speed.rblua +25 -0
- package/templates/gitignore +4 -0
- package/templates/globals.lua +66 -0
- package/templates/layout.rblua +28 -0
- package/templates/module.lua +7 -0
- package/templates/module.rblua +32 -0
- package/templates/page_home.rblua +9 -0
- package/templates/remote.lua +6 -0
- package/templates/remotes.lua +14 -0
- package/vscode/language-configuration.json +35 -0
- package/vscode/package.json +35 -0
- package/vscode/src/extension.js +397 -0
- package/vscode/syntaxes/rblua.tmLanguage.json +126 -0
|
@@ -0,0 +1,1206 @@
|
|
|
1
|
+
-- ── Pulse Dev Overlay ────────────────────────────────────────────────────────
|
|
2
|
+
-- Injected automatically in --dev builds. Available in every project that
|
|
3
|
+
-- uses the rb framework — no project-level files needed.
|
|
4
|
+
--
|
|
5
|
+
-- LOG tab : scrollable log with level toggles, search, copy, ERR↑ jump.
|
|
6
|
+
-- FEAT tab : lists every Linoria toggle so you can enable/disable features.
|
|
7
|
+
-- CFG tab : snapshot of all Linoria Toggles + Options + Monitor values.
|
|
8
|
+
--
|
|
9
|
+
-- Toggle via _PulseDevGuiToggle() or the Debug page in the Linoria menu.
|
|
10
|
+
|
|
11
|
+
-- ── Cleanup any previous instance ────────────────────────────────────────────
|
|
12
|
+
local function _destroyOld(parent)
|
|
13
|
+
pcall(function()
|
|
14
|
+
local old = parent and parent:FindFirstChild("PulseDevLog")
|
|
15
|
+
if old then old:Destroy() end
|
|
16
|
+
end)
|
|
17
|
+
end
|
|
18
|
+
pcall(function() _destroyOld(game:GetService("CoreGui")) end)
|
|
19
|
+
pcall(function() _destroyOld(game:GetService("Players").LocalPlayer.PlayerGui) end)
|
|
20
|
+
|
|
21
|
+
-- ── GUI parent ────────────────────────────────────────────────────────────────
|
|
22
|
+
local _guiParent
|
|
23
|
+
pcall(function() _guiParent = game:GetService("CoreGui") end)
|
|
24
|
+
if not _guiParent then
|
|
25
|
+
_guiParent = game:GetService("Players").LocalPlayer.PlayerGui
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
-- ── Linoria global accessors (use _G[] not rawget — respects metatables) ─────
|
|
29
|
+
local function _getToggles() local ok, t = pcall(function() return _G.Toggles end); return (ok and t) or nil end
|
|
30
|
+
local function _getOptions() local ok, t = pcall(function() return _G.Options end); return (ok and t) or nil end
|
|
31
|
+
|
|
32
|
+
-- ── Palette ───────────────────────────────────────────────────────────────────
|
|
33
|
+
local P = {
|
|
34
|
+
bg = Color3.fromRGB(12, 12, 19),
|
|
35
|
+
bg2 = Color3.fromRGB(17, 17, 26),
|
|
36
|
+
bg3 = Color3.fromRGB(22, 22, 34),
|
|
37
|
+
header = Color3.fromRGB(20, 20, 32),
|
|
38
|
+
filter = Color3.fromRGB(16, 16, 24),
|
|
39
|
+
border = Color3.fromRGB(50, 50, 75),
|
|
40
|
+
text = Color3.fromRGB(195, 195, 215),
|
|
41
|
+
dim = Color3.fromRGB(85, 85, 108),
|
|
42
|
+
tag = Color3.fromRGB(135, 135, 205),
|
|
43
|
+
active = Color3.fromRGB(80, 80, 175),
|
|
44
|
+
on = Color3.fromRGB(60, 175, 100),
|
|
45
|
+
off = Color3.fromRGB(175, 60, 60),
|
|
46
|
+
section = Color3.fromRGB(30, 30, 48),
|
|
47
|
+
TRC = Color3.fromRGB(72, 72, 90),
|
|
48
|
+
DBG = Color3.fromRGB(70, 175, 155),
|
|
49
|
+
INF = Color3.fromRGB(170, 195, 220),
|
|
50
|
+
WRN = Color3.fromRGB(215, 165, 45),
|
|
51
|
+
ERR = Color3.fromRGB(215, 65, 65),
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
local LEVEL_HEX = {
|
|
55
|
+
TRC = "#484858", DBG = "#46AF9B",
|
|
56
|
+
INF = "#AAC3DC", WRN = "#D7A52D", ERR = "#D74141",
|
|
57
|
+
}
|
|
58
|
+
local TAG_HEX = "#8787CD"
|
|
59
|
+
local TS_HEX = "#555570"
|
|
60
|
+
local DATA_HEX = "#555570"
|
|
61
|
+
local MSG_HEX = "#C3C3D7"
|
|
62
|
+
|
|
63
|
+
local WIN_W, WIN_H = 760, 430
|
|
64
|
+
local HDR_H = 30
|
|
65
|
+
local FLT_H = 28
|
|
66
|
+
local LOG_H = WIN_H - HDR_H - FLT_H
|
|
67
|
+
local BODY_H = WIN_H - HDR_H
|
|
68
|
+
local FONT = Enum.Font.Code
|
|
69
|
+
local TAB_FONT = Enum.Font.GothamBold
|
|
70
|
+
local FS = 13
|
|
71
|
+
local ENTRY_H = 18
|
|
72
|
+
|
|
73
|
+
-- ── Helpers ───────────────────────────────────────────────────────────────────
|
|
74
|
+
local function _esc(s)
|
|
75
|
+
return tostring(s):gsub("&","&"):gsub("<","<"):gsub(">",">")
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
local function _mkFrame(props)
|
|
79
|
+
local f = Instance.new("Frame")
|
|
80
|
+
for k, v in pairs(props) do f[k] = v end
|
|
81
|
+
return f
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
local function _mkText(props)
|
|
85
|
+
local t = Instance.new("TextLabel")
|
|
86
|
+
t.BackgroundTransparency = 1
|
|
87
|
+
t.TextXAlignment = Enum.TextXAlignment.Left
|
|
88
|
+
t.Font = FONT
|
|
89
|
+
for k, v in pairs(props) do t[k] = v end
|
|
90
|
+
return t
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
local function _mkBtn(props, onClick)
|
|
94
|
+
local b = Instance.new("TextButton")
|
|
95
|
+
b.BackgroundTransparency = 1
|
|
96
|
+
b.Font = FONT
|
|
97
|
+
for k, v in pairs(props) do b[k] = v end
|
|
98
|
+
if onClick then b.MouseButton1Click:Connect(onClick) end
|
|
99
|
+
return b
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
local function _copyText(text)
|
|
103
|
+
local ok = false
|
|
104
|
+
if not ok then pcall(function() setclipboard(text); ok = true end) end
|
|
105
|
+
if not ok then pcall(function() toclipboard(text); ok = true end) end
|
|
106
|
+
if not ok then pcall(function() writeclipboard(text); ok = true end) end
|
|
107
|
+
if not ok then pcall(function() Clipboard:set(text); ok = true end) end
|
|
108
|
+
return ok
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
local function _mkTabBtn(text, xOffset, active)
|
|
112
|
+
local btn = _mkBtn({
|
|
113
|
+
Text = text,
|
|
114
|
+
Size = UDim2.new(0, 40, 0, 22),
|
|
115
|
+
Position = UDim2.new(1, xOffset, 0.5, -11),
|
|
116
|
+
TextColor3 = active and P.active or P.dim,
|
|
117
|
+
TextSize = 13,
|
|
118
|
+
Font = TAB_FONT,
|
|
119
|
+
BackgroundColor3 = P.bg2,
|
|
120
|
+
BackgroundTransparency = 0,
|
|
121
|
+
Parent = nil,
|
|
122
|
+
}, nil)
|
|
123
|
+
Instance.new("UICorner", btn).CornerRadius = UDim.new(0, 4)
|
|
124
|
+
local s = Instance.new("UIStroke", btn)
|
|
125
|
+
s.Color = active and P.active or P.border
|
|
126
|
+
s.Thickness = 1
|
|
127
|
+
return btn, s
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
-- Forward declarations — used before their bodies are defined below.
|
|
131
|
+
local _rerender
|
|
132
|
+
local _enableMouseForOverlay
|
|
133
|
+
|
|
134
|
+
-- ── Build window ─────────────────────────────────────────────────────────────
|
|
135
|
+
local _gui = Instance.new("ScreenGui")
|
|
136
|
+
_gui.Name = "PulseDevLog"
|
|
137
|
+
_gui.ResetOnSpawn = false
|
|
138
|
+
_gui.IgnoreGuiInset = true
|
|
139
|
+
_gui.ZIndexBehavior = Enum.ZIndexBehavior.Sibling
|
|
140
|
+
_gui.DisplayOrder = 999
|
|
141
|
+
_gui.Parent = _guiParent
|
|
142
|
+
|
|
143
|
+
local _win = _mkFrame({
|
|
144
|
+
Name = "Win",
|
|
145
|
+
Size = UDim2.new(0, WIN_W, 0, WIN_H),
|
|
146
|
+
Position = UDim2.new(1, -(WIN_W + 10), 0, 80),
|
|
147
|
+
BackgroundColor3 = P.bg,
|
|
148
|
+
BorderSizePixel = 0,
|
|
149
|
+
Visible = false,
|
|
150
|
+
Parent = _gui,
|
|
151
|
+
})
|
|
152
|
+
Instance.new("UIStroke", _win).Color = P.border
|
|
153
|
+
|
|
154
|
+
-- ── Header bar ────────────────────────────────────────────────────────────────
|
|
155
|
+
local _hdr = _mkFrame({
|
|
156
|
+
Size = UDim2.new(1, 0, 0, HDR_H),
|
|
157
|
+
BackgroundColor3 = P.header,
|
|
158
|
+
BorderSizePixel = 0,
|
|
159
|
+
Parent = _win,
|
|
160
|
+
})
|
|
161
|
+
Instance.new("UIStroke", _hdr).Color = P.border
|
|
162
|
+
|
|
163
|
+
_mkText({
|
|
164
|
+
Text = "◉ PULSE DEV",
|
|
165
|
+
Size = UDim2.new(0, 130, 1, 0),
|
|
166
|
+
Position = UDim2.new(0, 8, 0, 0),
|
|
167
|
+
TextColor3 = P.active,
|
|
168
|
+
TextSize = 13,
|
|
169
|
+
Font = Enum.Font.GothamBold,
|
|
170
|
+
Parent = _hdr,
|
|
171
|
+
})
|
|
172
|
+
|
|
173
|
+
local _monLabel = _mkText({
|
|
174
|
+
Text = "",
|
|
175
|
+
Size = UDim2.new(0, 300, 1, 0),
|
|
176
|
+
Position = UDim2.new(0, 145, 0, 0),
|
|
177
|
+
TextColor3 = P.dim,
|
|
178
|
+
TextSize = 12,
|
|
179
|
+
TextTruncate = Enum.TextTruncate.AtEnd,
|
|
180
|
+
Parent = _hdr,
|
|
181
|
+
})
|
|
182
|
+
|
|
183
|
+
-- ── Header: action buttons and tab buttons (right side) ───────────────────────
|
|
184
|
+
-- From right: ✕ SAVE CLR [CFG] [FEAT] [LOG]
|
|
185
|
+
|
|
186
|
+
local _btnClose = _mkBtn({
|
|
187
|
+
Text = "x",
|
|
188
|
+
Size = UDim2.new(0, 28, 0, 22),
|
|
189
|
+
Position = UDim2.new(1, -32, 0.5, -11),
|
|
190
|
+
TextColor3 = P.ERR,
|
|
191
|
+
TextSize = 14,
|
|
192
|
+
Font = Enum.Font.GothamBold,
|
|
193
|
+
BackgroundColor3 = P.bg2,
|
|
194
|
+
BackgroundTransparency = 0,
|
|
195
|
+
Parent = _hdr,
|
|
196
|
+
}, function() _win.Visible = false end)
|
|
197
|
+
Instance.new("UICorner", _btnClose).CornerRadius = UDim.new(0, 4)
|
|
198
|
+
do local s = Instance.new("UIStroke", _btnClose); s.Color = P.ERR; s.Thickness = 1 end
|
|
199
|
+
|
|
200
|
+
local _btnSave = _mkBtn({
|
|
201
|
+
Text = "SAVE",
|
|
202
|
+
Size = UDim2.new(0, 44, 0, 22),
|
|
203
|
+
Position = UDim2.new(1, -82, 0.5, -11),
|
|
204
|
+
TextColor3 = P.dim,
|
|
205
|
+
TextSize = 12,
|
|
206
|
+
BackgroundColor3 = P.bg2,
|
|
207
|
+
BackgroundTransparency = 0,
|
|
208
|
+
Parent = _hdr,
|
|
209
|
+
}, function() Pulse.Log.save("pulse_dev.log") end)
|
|
210
|
+
Instance.new("UICorner", _btnSave).CornerRadius = UDim.new(0, 4)
|
|
211
|
+
|
|
212
|
+
local _btnClear = _mkBtn({
|
|
213
|
+
Text = "CLR",
|
|
214
|
+
Size = UDim2.new(0, 36, 0, 22),
|
|
215
|
+
Position = UDim2.new(1, -124, 0.5, -11),
|
|
216
|
+
TextColor3 = P.dim,
|
|
217
|
+
TextSize = 12,
|
|
218
|
+
BackgroundColor3 = P.bg2,
|
|
219
|
+
BackgroundTransparency = 0,
|
|
220
|
+
Parent = _hdr,
|
|
221
|
+
}, function() Pulse.Log.clear(); _rerender() end)
|
|
222
|
+
Instance.new("UICorner", _btnClear).CornerRadius = UDim.new(0, 4)
|
|
223
|
+
|
|
224
|
+
-- Tab buttons: LOG / FEAT / CFG
|
|
225
|
+
local _mode = "log"
|
|
226
|
+
|
|
227
|
+
local _btnTabCfg, _cfgStroke = _mkTabBtn("CFG", -168, false)
|
|
228
|
+
local _btnTabFeat, _featStroke = _mkTabBtn("FEAT", -214, false)
|
|
229
|
+
local _btnTabLog, _logStroke = _mkTabBtn("LOG", -260, true)
|
|
230
|
+
_btnTabCfg.Parent = _hdr
|
|
231
|
+
_btnTabFeat.Parent = _hdr
|
|
232
|
+
_btnTabLog.Parent = _hdr
|
|
233
|
+
-- click handlers wired after panels are built
|
|
234
|
+
|
|
235
|
+
-- ── Filter bar (LOG view) ─────────────────────────────────────────────────────
|
|
236
|
+
local _flt = _mkFrame({
|
|
237
|
+
Size = UDim2.new(1, 0, 0, FLT_H),
|
|
238
|
+
Position = UDim2.new(0, 0, 0, HDR_H),
|
|
239
|
+
BackgroundColor3 = P.filter,
|
|
240
|
+
BorderSizePixel = 0,
|
|
241
|
+
Parent = _win,
|
|
242
|
+
})
|
|
243
|
+
Instance.new("UIStroke", _flt).Color = P.border
|
|
244
|
+
|
|
245
|
+
local _showLevels = { TRC=false, DBG=true, INF=true, WRN=true, ERR=true }
|
|
246
|
+
local _lvlBtns = {}
|
|
247
|
+
local LEVELS = { "TRC", "DBG", "INF", "WRN", "ERR" }
|
|
248
|
+
local function _lvlColor(lbl) return P[lbl] or P.text end
|
|
249
|
+
|
|
250
|
+
for i, lbl in ipairs(LEVELS) do
|
|
251
|
+
local on = _showLevels[lbl]
|
|
252
|
+
local btn = _mkBtn({
|
|
253
|
+
Text = lbl,
|
|
254
|
+
Size = UDim2.new(0, 36, 0, 20),
|
|
255
|
+
Position = UDim2.new(0, 6 + (i-1) * 40, 0.5, -10),
|
|
256
|
+
TextColor3 = on and _lvlColor(lbl) or P.dim,
|
|
257
|
+
TextSize = 11,
|
|
258
|
+
Font = Enum.Font.GothamBold,
|
|
259
|
+
BackgroundColor3 = P.bg,
|
|
260
|
+
BackgroundTransparency = on and 0.6 or 0.85,
|
|
261
|
+
Parent = _flt,
|
|
262
|
+
}, nil)
|
|
263
|
+
Instance.new("UICorner", btn).CornerRadius = UDim.new(0, 4)
|
|
264
|
+
local stroke = Instance.new("UIStroke", btn)
|
|
265
|
+
stroke.Color = on and _lvlColor(lbl) or P.border
|
|
266
|
+
stroke.Thickness = 1
|
|
267
|
+
btn.MouseButton1Click:Connect(function()
|
|
268
|
+
_showLevels[lbl] = not _showLevels[lbl]
|
|
269
|
+
local v = _showLevels[lbl]
|
|
270
|
+
btn.TextColor3 = v and _lvlColor(lbl) or P.dim
|
|
271
|
+
btn.BackgroundTransparency = v and 0.6 or 0.85
|
|
272
|
+
stroke.Color = v and _lvlColor(lbl) or P.border
|
|
273
|
+
end)
|
|
274
|
+
_lvlBtns[lbl] = btn
|
|
275
|
+
end
|
|
276
|
+
|
|
277
|
+
local _btnJumpErr = _mkBtn({
|
|
278
|
+
Text = "ERR↑",
|
|
279
|
+
Size = UDim2.new(0, 40, 0, 20),
|
|
280
|
+
Position = UDim2.new(0, 210, 0.5, -10),
|
|
281
|
+
TextColor3 = P.ERR,
|
|
282
|
+
TextSize = 11,
|
|
283
|
+
Font = Enum.Font.GothamBold,
|
|
284
|
+
BackgroundColor3 = P.bg2,
|
|
285
|
+
BackgroundTransparency = 0,
|
|
286
|
+
Parent = _flt,
|
|
287
|
+
}, nil)
|
|
288
|
+
Instance.new("UICorner", _btnJumpErr).CornerRadius = UDim.new(0, 4)
|
|
289
|
+
Instance.new("UIStroke", _btnJumpErr).Color = P.ERR
|
|
290
|
+
|
|
291
|
+
_mkText({
|
|
292
|
+
Text = "search:", Size = UDim2.new(0, 50, 1, 0),
|
|
293
|
+
Position = UDim2.new(0, 257, 0, 0), TextColor3 = P.dim, TextSize = 12, Parent = _flt,
|
|
294
|
+
})
|
|
295
|
+
|
|
296
|
+
local _tagInput = Instance.new("TextBox")
|
|
297
|
+
_tagInput.PlaceholderText = "tag or message…"
|
|
298
|
+
_tagInput.PlaceholderColor3 = P.dim
|
|
299
|
+
_tagInput.Text = ""
|
|
300
|
+
_tagInput.Size = UDim2.new(0, 190, 0, 20)
|
|
301
|
+
_tagInput.Position = UDim2.new(0, 309, 0.5, -10)
|
|
302
|
+
_tagInput.BackgroundColor3 = P.bg2
|
|
303
|
+
_tagInput.BorderSizePixel = 0
|
|
304
|
+
_tagInput.TextColor3 = P.tag
|
|
305
|
+
_tagInput.Font = FONT
|
|
306
|
+
_tagInput.TextSize = 12
|
|
307
|
+
_tagInput.TextXAlignment = Enum.TextXAlignment.Left
|
|
308
|
+
_tagInput.TextEditable = true
|
|
309
|
+
_tagInput.ClearTextOnFocus = false
|
|
310
|
+
_tagInput.Parent = _flt
|
|
311
|
+
do
|
|
312
|
+
local p = Instance.new("UIPadding", _tagInput); p.PaddingLeft = UDim.new(0, 6)
|
|
313
|
+
Instance.new("UICorner", _tagInput).CornerRadius = UDim.new(0, 4)
|
|
314
|
+
Instance.new("UIStroke", _tagInput).Color = P.border
|
|
315
|
+
end
|
|
316
|
+
|
|
317
|
+
local _countLabel = _mkText({
|
|
318
|
+
Text = "0 entries", Size = UDim2.new(0, 110, 1, 0),
|
|
319
|
+
Position = UDim2.new(1, -116, 0, 0), TextColor3 = P.dim, TextSize = 11,
|
|
320
|
+
TextXAlignment = Enum.TextXAlignment.Right, Parent = _flt,
|
|
321
|
+
})
|
|
322
|
+
|
|
323
|
+
local _btnCopyAll = _mkBtn({
|
|
324
|
+
Text = "COPY", Size = UDim2.new(0, 44, 0, 20),
|
|
325
|
+
Position = UDim2.new(0, 506, 0.5, -10), TextColor3 = P.dim, TextSize = 12,
|
|
326
|
+
BackgroundColor3 = P.bg2, BackgroundTransparency = 0, Parent = _flt,
|
|
327
|
+
}, function()
|
|
328
|
+
local all = Pulse.Log.getNewEntries(0)
|
|
329
|
+
local q = _tagInput.Text:lower():gsub("^%s+",""):gsub("%s+$","")
|
|
330
|
+
local lines = {}
|
|
331
|
+
for _, e in ipairs(all) do
|
|
332
|
+
if _showLevels[e.lbl] then
|
|
333
|
+
local pass = q == "" or e.tag:lower():find(q,1,true) or e.msg:lower():find(q,1,true)
|
|
334
|
+
if pass then lines[#lines+1] = e.line end
|
|
335
|
+
end
|
|
336
|
+
end
|
|
337
|
+
local feedback
|
|
338
|
+
if #lines == 0 then
|
|
339
|
+
feedback = "nothing visible"
|
|
340
|
+
elseif _copyText(table.concat(lines, "\n")) then
|
|
341
|
+
feedback = #lines .. " copied!"
|
|
342
|
+
else
|
|
343
|
+
feedback = "no clipboard fn"
|
|
344
|
+
end
|
|
345
|
+
_countLabel.Text = feedback
|
|
346
|
+
task.delay(1.5, function()
|
|
347
|
+
if _countLabel and _countLabel.Parent then _countLabel.Text = _totalShown .. " shown" end
|
|
348
|
+
end)
|
|
349
|
+
end)
|
|
350
|
+
Instance.new("UICorner", _btnCopyAll).CornerRadius = UDim.new(0, 4)
|
|
351
|
+
|
|
352
|
+
-- ── Log scroll area ───────────────────────────────────────────────────────────
|
|
353
|
+
local _scroll = Instance.new("ScrollingFrame")
|
|
354
|
+
_scroll.Size = UDim2.new(1, 0, 0, LOG_H)
|
|
355
|
+
_scroll.Position = UDim2.new(0, 0, 0, HDR_H + FLT_H)
|
|
356
|
+
_scroll.BackgroundColor3 = P.bg2
|
|
357
|
+
_scroll.BorderSizePixel = 0
|
|
358
|
+
_scroll.ScrollBarThickness = 5
|
|
359
|
+
_scroll.ScrollBarImageColor3 = P.border
|
|
360
|
+
_scroll.CanvasSize = UDim2.new(0, 0, 0, 0)
|
|
361
|
+
_scroll.AutomaticCanvasSize = Enum.AutomaticSize.Y
|
|
362
|
+
_scroll.Parent = _win
|
|
363
|
+
|
|
364
|
+
local _list = Instance.new("UIListLayout", _scroll)
|
|
365
|
+
_list.SortOrder = Enum.SortOrder.LayoutOrder; _list.Padding = UDim.new(0, 0)
|
|
366
|
+
|
|
367
|
+
local _pad = Instance.new("UIPadding", _scroll)
|
|
368
|
+
_pad.PaddingLeft = UDim.new(0, 4); _pad.PaddingRight = UDim.new(0, 6)
|
|
369
|
+
|
|
370
|
+
-- ── Entry rendering ───────────────────────────────────────────────────────────
|
|
371
|
+
local MAX_LABELS = 400
|
|
372
|
+
local _labels = {}
|
|
373
|
+
local _lastId = 0
|
|
374
|
+
local _totalShown = 0
|
|
375
|
+
|
|
376
|
+
-- One-line header (no data — data lives in the expanded panel)
|
|
377
|
+
local function _entryHeaderText(e)
|
|
378
|
+
local hasData = e.data ~= nil
|
|
379
|
+
local dot = hasData and ('<font color="'..DATA_HEX..'"> ●</font>') or ""
|
|
380
|
+
return string.format(
|
|
381
|
+
'<font color="%s">%s</font> <font color="%s">%s</font> <font color="%s">%s</font> %s%s',
|
|
382
|
+
TS_HEX, _esc(e.ts),
|
|
383
|
+
LEVEL_HEX[e.lbl] or MSG_HEX, "["..e.lbl.."]",
|
|
384
|
+
TAG_HEX, _esc(e.tag),
|
|
385
|
+
'<font color="'..MSG_HEX..'">'.. _esc(e.msg) .."</font>",
|
|
386
|
+
dot
|
|
387
|
+
)
|
|
388
|
+
end
|
|
389
|
+
|
|
390
|
+
-- Plain-text value formatter for the expanded data panel
|
|
391
|
+
local function _fmtVal(v)
|
|
392
|
+
if type(v) == "number" then
|
|
393
|
+
return (math.floor(v) == v) and tostring(math.floor(v)) or string.format("%.4g", v)
|
|
394
|
+
elseif type(v) == "string" then return '"' .. v .. '"'
|
|
395
|
+
else return tostring(v)
|
|
396
|
+
end
|
|
397
|
+
end
|
|
398
|
+
|
|
399
|
+
-- Legacy: still used by COPY ALL
|
|
400
|
+
local function _entryRichText(e)
|
|
401
|
+
local dataStr = ""
|
|
402
|
+
if e.data ~= nil then
|
|
403
|
+
if type(e.data) == "table" then
|
|
404
|
+
local ok, s = pcall(function() return game:GetService("HttpService"):JSONEncode(e.data) end)
|
|
405
|
+
dataStr = ok and (" <font color=\""..DATA_HEX.."\">".. _esc(s) .."</font>") or ""
|
|
406
|
+
else
|
|
407
|
+
dataStr = " <font color=\""..DATA_HEX.."\">" .. _esc(tostring(e.data)) .. "</font>"
|
|
408
|
+
end
|
|
409
|
+
end
|
|
410
|
+
return string.format(
|
|
411
|
+
'<font color="%s">%s</font> <font color="%s">%s</font> <font color="%s">%s</font> %s%s',
|
|
412
|
+
TS_HEX, _esc(e.ts),
|
|
413
|
+
LEVEL_HEX[e.lbl] or MSG_HEX, "["..e.lbl.."]",
|
|
414
|
+
TAG_HEX, _esc(e.tag),
|
|
415
|
+
'<font color="'..MSG_HEX..'">'.. _esc(e.msg) .."</font>",
|
|
416
|
+
dataStr
|
|
417
|
+
)
|
|
418
|
+
end
|
|
419
|
+
|
|
420
|
+
local function _entryPassesFilter(e)
|
|
421
|
+
if not _showLevels[e.lbl] then return false end
|
|
422
|
+
local q = _tagInput.Text:lower():gsub("^%s+",""):gsub("%s+$","")
|
|
423
|
+
if q == "" then return true end
|
|
424
|
+
return e.tag:lower():find(q,1,true) ~= nil or e.msg:lower():find(q,1,true) ~= nil
|
|
425
|
+
end
|
|
426
|
+
|
|
427
|
+
local function _addEntry(e)
|
|
428
|
+
if not _entryPassesFilter(e) then return end
|
|
429
|
+
|
|
430
|
+
-- ── Outer container — grows when expanded ──────────────────────────────
|
|
431
|
+
local container = _mkFrame({
|
|
432
|
+
BackgroundTransparency = 1,
|
|
433
|
+
Size = UDim2.new(1, -10, 0, 0),
|
|
434
|
+
AutomaticSize = Enum.AutomaticSize.Y,
|
|
435
|
+
LayoutOrder = e.id,
|
|
436
|
+
Parent = _scroll,
|
|
437
|
+
})
|
|
438
|
+
local _innerList = Instance.new("UIListLayout", container)
|
|
439
|
+
_innerList.SortOrder = Enum.SortOrder.LayoutOrder
|
|
440
|
+
_innerList.Padding = UDim.new(0, 0)
|
|
441
|
+
|
|
442
|
+
-- ── Header row (one-line, click to expand) ─────────────────────────────
|
|
443
|
+
local header = _mkFrame({
|
|
444
|
+
BackgroundTransparency = 1,
|
|
445
|
+
Size = UDim2.new(1, 0, 0, ENTRY_H),
|
|
446
|
+
LayoutOrder = 0,
|
|
447
|
+
Parent = container,
|
|
448
|
+
})
|
|
449
|
+
|
|
450
|
+
local indicator = _mkText({
|
|
451
|
+
Text = "▶",
|
|
452
|
+
Size = UDim2.new(0, 12, 1, 0),
|
|
453
|
+
Position = UDim2.new(0, 2, 0, 0),
|
|
454
|
+
TextColor3 = P.dim,
|
|
455
|
+
TextSize = 10,
|
|
456
|
+
Font = Enum.Font.GothamBold,
|
|
457
|
+
Parent = header,
|
|
458
|
+
})
|
|
459
|
+
|
|
460
|
+
local summary = _mkText({
|
|
461
|
+
Text = _entryHeaderText(e),
|
|
462
|
+
Size = UDim2.new(1, -14, 1, 0),
|
|
463
|
+
Position = UDim2.new(0, 14, 0, 0),
|
|
464
|
+
TextColor3 = P.text,
|
|
465
|
+
TextSize = FS,
|
|
466
|
+
RichText = true,
|
|
467
|
+
TextTruncate = Enum.TextTruncate.AtEnd,
|
|
468
|
+
TextYAlignment = Enum.TextYAlignment.Center,
|
|
469
|
+
Parent = header,
|
|
470
|
+
})
|
|
471
|
+
|
|
472
|
+
-- ── Expanded panel ─────────────────────────────────────────────────────
|
|
473
|
+
local expPanel = _mkFrame({
|
|
474
|
+
BackgroundColor3 = P.bg3,
|
|
475
|
+
BorderSizePixel = 0,
|
|
476
|
+
Size = UDim2.new(1, 0, 0, 0), -- height=0 when collapsed
|
|
477
|
+
AutomaticSize = Enum.AutomaticSize.None,
|
|
478
|
+
ClipsDescendants = true,
|
|
479
|
+
LayoutOrder = 1,
|
|
480
|
+
Parent = container,
|
|
481
|
+
})
|
|
482
|
+
Instance.new("UIStroke", expPanel).Color = P.border
|
|
483
|
+
|
|
484
|
+
local _expList = Instance.new("UIListLayout", expPanel)
|
|
485
|
+
_expList.SortOrder = Enum.SortOrder.LayoutOrder
|
|
486
|
+
_expList.Padding = UDim.new(0, 1)
|
|
487
|
+
|
|
488
|
+
local _expPad = Instance.new("UIPadding", expPanel)
|
|
489
|
+
_expPad.PaddingLeft = UDim.new(0, 14)
|
|
490
|
+
_expPad.PaddingRight = UDim.new(0, 8)
|
|
491
|
+
_expPad.PaddingTop = UDim.new(0, 3)
|
|
492
|
+
_expPad.PaddingBottom = UDim.new(0, 4)
|
|
493
|
+
|
|
494
|
+
-- Full message (non-truncated, wraps)
|
|
495
|
+
_mkText({
|
|
496
|
+
Text = string.format("[%s] [%s] [%s] %s", _esc(e.ts), e.lbl, _esc(e.tag), _esc(e.msg)),
|
|
497
|
+
Size = UDim2.new(1, -52, 0, 0),
|
|
498
|
+
AutomaticSize = Enum.AutomaticSize.Y,
|
|
499
|
+
TextColor3 = P.text,
|
|
500
|
+
TextSize = FS,
|
|
501
|
+
TextWrapped = true,
|
|
502
|
+
LayoutOrder = 1,
|
|
503
|
+
Parent = expPanel,
|
|
504
|
+
})
|
|
505
|
+
|
|
506
|
+
-- Data rows (one per key, sorted)
|
|
507
|
+
if e.data ~= nil then
|
|
508
|
+
local order = 2
|
|
509
|
+
if type(e.data) == "table" then
|
|
510
|
+
local keys = {}
|
|
511
|
+
for k in pairs(e.data) do keys[#keys+1] = k end
|
|
512
|
+
table.sort(keys, function(a,b) return tostring(a) < tostring(b) end)
|
|
513
|
+
for _, k in ipairs(keys) do
|
|
514
|
+
_mkText({
|
|
515
|
+
Text = tostring(k) .. " = " .. _fmtVal(e.data[k]),
|
|
516
|
+
Size = UDim2.new(1, 0, 0, ENTRY_H - 2),
|
|
517
|
+
TextColor3 = P.dim,
|
|
518
|
+
TextSize = FS - 1,
|
|
519
|
+
LayoutOrder = order,
|
|
520
|
+
Parent = expPanel,
|
|
521
|
+
})
|
|
522
|
+
order = order + 1
|
|
523
|
+
end
|
|
524
|
+
else
|
|
525
|
+
_mkText({
|
|
526
|
+
Text = _fmtVal(e.data),
|
|
527
|
+
Size = UDim2.new(1, 0, 0, ENTRY_H - 2),
|
|
528
|
+
TextColor3 = P.dim,
|
|
529
|
+
TextSize = FS - 1,
|
|
530
|
+
LayoutOrder = 2,
|
|
531
|
+
Parent = expPanel,
|
|
532
|
+
})
|
|
533
|
+
end
|
|
534
|
+
end
|
|
535
|
+
|
|
536
|
+
-- Copy button row
|
|
537
|
+
local copyRow = _mkFrame({
|
|
538
|
+
BackgroundTransparency = 1,
|
|
539
|
+
Size = UDim2.new(1, 0, 0, 22),
|
|
540
|
+
LayoutOrder = 99,
|
|
541
|
+
Parent = expPanel,
|
|
542
|
+
})
|
|
543
|
+
local copyBtn = _mkBtn({
|
|
544
|
+
Text = "COPY",
|
|
545
|
+
Size = UDim2.new(0, 44, 0, 18),
|
|
546
|
+
Position = UDim2.new(0, 0, 0.5, -9),
|
|
547
|
+
TextColor3 = P.dim,
|
|
548
|
+
TextSize = 11,
|
|
549
|
+
BackgroundColor3 = P.bg2,
|
|
550
|
+
BackgroundTransparency = 0,
|
|
551
|
+
Parent = copyRow,
|
|
552
|
+
}, function()
|
|
553
|
+
_copyText(e.line)
|
|
554
|
+
copyBtn.Text = "✓"
|
|
555
|
+
task.delay(0.8, function() if copyBtn and copyBtn.Parent then copyBtn.Text = "COPY" end end)
|
|
556
|
+
end)
|
|
557
|
+
Instance.new("UICorner", copyBtn).CornerRadius = UDim.new(0, 3)
|
|
558
|
+
|
|
559
|
+
-- ── Toggle expand/collapse ─────────────────────────────────────────────
|
|
560
|
+
local _expanded = false
|
|
561
|
+
local function _toggle()
|
|
562
|
+
_expanded = not _expanded
|
|
563
|
+
if _expanded then
|
|
564
|
+
expPanel.AutomaticSize = Enum.AutomaticSize.Y
|
|
565
|
+
indicator.Text = "▼"
|
|
566
|
+
indicator.TextColor3 = P.active
|
|
567
|
+
else
|
|
568
|
+
expPanel.AutomaticSize = Enum.AutomaticSize.None
|
|
569
|
+
expPanel.Size = UDim2.new(1, 0, 0, 0)
|
|
570
|
+
indicator.Text = "▶"
|
|
571
|
+
indicator.TextColor3 = P.dim
|
|
572
|
+
end
|
|
573
|
+
end
|
|
574
|
+
|
|
575
|
+
-- Whole header row is the click target (Frame.InputBegan; TextLabels pass input through)
|
|
576
|
+
header.InputBegan:Connect(function(inp)
|
|
577
|
+
if inp.UserInputType == Enum.UserInputType.MouseButton1 then _toggle() end
|
|
578
|
+
end)
|
|
579
|
+
|
|
580
|
+
_labels[#_labels+1] = { container = container, e = e }
|
|
581
|
+
_totalShown = _totalShown + 1
|
|
582
|
+
if #_labels > MAX_LABELS then
|
|
583
|
+
local old = table.remove(_labels, 1)
|
|
584
|
+
if old and old.container and old.container.Parent then old.container:Destroy() end
|
|
585
|
+
end
|
|
586
|
+
end
|
|
587
|
+
|
|
588
|
+
local function _scrollToBottom()
|
|
589
|
+
task.wait()
|
|
590
|
+
local maxY = math.max(0, _scroll.AbsoluteCanvasSize.Y - _scroll.AbsoluteWindowSize.Y)
|
|
591
|
+
_scroll.CanvasPosition = Vector2.new(0, maxY)
|
|
592
|
+
end
|
|
593
|
+
|
|
594
|
+
local function _isAtBottom()
|
|
595
|
+
local maxY = _scroll.AbsoluteCanvasSize.Y - _scroll.AbsoluteWindowSize.Y
|
|
596
|
+
return _scroll.CanvasPosition.Y >= maxY - 20
|
|
597
|
+
end
|
|
598
|
+
|
|
599
|
+
_rerender = function()
|
|
600
|
+
for _, item in ipairs(_labels) do
|
|
601
|
+
if item and item.container and item.container.Parent then item.container:Destroy() end
|
|
602
|
+
end
|
|
603
|
+
_labels = {}; _totalShown = 0; _lastId = 0
|
|
604
|
+
local all = Pulse.Log.getNewEntries(0)
|
|
605
|
+
for _, e in ipairs(all) do
|
|
606
|
+
_lastId = math.max(_lastId, e.id)
|
|
607
|
+
_addEntry(e)
|
|
608
|
+
end
|
|
609
|
+
_countLabel.Text = _totalShown .. " shown"
|
|
610
|
+
_scrollToBottom()
|
|
611
|
+
end
|
|
612
|
+
|
|
613
|
+
-- ── Features panel (FEAT tab) ─────────────────────────────────────────────────
|
|
614
|
+
local _featPanel = _mkFrame({
|
|
615
|
+
Size = UDim2.new(1, 0, 0, BODY_H),
|
|
616
|
+
Position = UDim2.new(0, 0, 0, HDR_H),
|
|
617
|
+
BackgroundColor3 = P.bg,
|
|
618
|
+
BorderSizePixel = 0,
|
|
619
|
+
Visible = false,
|
|
620
|
+
Parent = _win,
|
|
621
|
+
})
|
|
622
|
+
|
|
623
|
+
local _featScroll = Instance.new("ScrollingFrame")
|
|
624
|
+
_featScroll.Size = UDim2.new(1, 0, 1, -32)
|
|
625
|
+
_featScroll.Position = UDim2.new(0, 0, 0, 32)
|
|
626
|
+
_featScroll.BackgroundColor3 = P.bg2
|
|
627
|
+
_featScroll.BorderSizePixel = 0
|
|
628
|
+
_featScroll.ScrollBarThickness = 5
|
|
629
|
+
_featScroll.ScrollBarImageColor3 = P.border
|
|
630
|
+
_featScroll.CanvasSize = UDim2.new(0, 0, 0, 0)
|
|
631
|
+
_featScroll.AutomaticCanvasSize = Enum.AutomaticSize.Y
|
|
632
|
+
_featScroll.Parent = _featPanel
|
|
633
|
+
|
|
634
|
+
Instance.new("UIListLayout", _featScroll).SortOrder = Enum.SortOrder.LayoutOrder
|
|
635
|
+
do
|
|
636
|
+
local p = Instance.new("UIPadding", _featScroll)
|
|
637
|
+
p.PaddingLeft = UDim.new(0, 6); p.PaddingRight = UDim.new(0, 6); p.PaddingTop = UDim.new(0, 4)
|
|
638
|
+
end
|
|
639
|
+
|
|
640
|
+
local _featBar = _mkFrame({
|
|
641
|
+
Size = UDim2.new(1, 0, 0, 30), BackgroundColor3 = P.filter,
|
|
642
|
+
BorderSizePixel = 0, Parent = _featPanel,
|
|
643
|
+
})
|
|
644
|
+
Instance.new("UIStroke", _featBar).Color = P.border
|
|
645
|
+
|
|
646
|
+
_mkText({
|
|
647
|
+
Text = "Feature Toggles", Size = UDim2.new(1, -140, 1, 0),
|
|
648
|
+
Position = UDim2.new(0, 8, 0, 0), TextColor3 = P.dim, TextSize = 12,
|
|
649
|
+
Parent = _featBar,
|
|
650
|
+
})
|
|
651
|
+
|
|
652
|
+
local _featStatusLabel = _mkText({
|
|
653
|
+
Text = "", Size = UDim2.new(0, 100, 1, 0),
|
|
654
|
+
Position = UDim2.new(1, -300, 0, 0), TextColor3 = P.dim, TextSize = 11,
|
|
655
|
+
TextXAlignment = Enum.TextXAlignment.Right, Parent = _featBar,
|
|
656
|
+
})
|
|
657
|
+
|
|
658
|
+
local _btnEnableAll = _mkBtn({
|
|
659
|
+
Text = "ALL ON", Size = UDim2.new(0, 56, 0, 20),
|
|
660
|
+
Position = UDim2.new(1, -130, 0.5, -10), TextColor3 = P.on, TextSize = 11,
|
|
661
|
+
BackgroundColor3 = P.bg2, BackgroundTransparency = 0, Parent = _featBar,
|
|
662
|
+
}, nil)
|
|
663
|
+
Instance.new("UICorner", _btnEnableAll).CornerRadius = UDim.new(0, 4)
|
|
664
|
+
|
|
665
|
+
local _btnDisableAll = _mkBtn({
|
|
666
|
+
Text = "ALL OFF", Size = UDim2.new(0, 56, 0, 20),
|
|
667
|
+
Position = UDim2.new(1, -68, 0.5, -10), TextColor3 = P.off, TextSize = 11,
|
|
668
|
+
BackgroundColor3 = P.bg2, BackgroundTransparency = 0, Parent = _featBar,
|
|
669
|
+
}, nil)
|
|
670
|
+
Instance.new("UICorner", _btnDisableAll).CornerRadius = UDim.new(0, 4)
|
|
671
|
+
|
|
672
|
+
local _featRows = {}
|
|
673
|
+
|
|
674
|
+
local function _buildFeatRows()
|
|
675
|
+
for _, r in ipairs(_featRows) do
|
|
676
|
+
if r.row and r.row.Parent then r.row:Destroy() end
|
|
677
|
+
end
|
|
678
|
+
_featRows = {}
|
|
679
|
+
|
|
680
|
+
local toggles = _getToggles()
|
|
681
|
+
if not toggles or next(toggles) == nil then
|
|
682
|
+
local hint = _mkText({
|
|
683
|
+
Text = "No feature toggles registered yet — open the main menu once, then switch back to FEAT.",
|
|
684
|
+
Size = UDim2.new(1, -12, 0, 60), Position = UDim2.new(0, 6, 0, 6),
|
|
685
|
+
TextColor3 = P.dim, TextSize = 12, TextWrapped = true,
|
|
686
|
+
TextYAlignment = Enum.TextYAlignment.Top, LayoutOrder = 0, Parent = _featScroll,
|
|
687
|
+
})
|
|
688
|
+
_featRows[1] = { row = hint, id = "__hint" }
|
|
689
|
+
_featStatusLabel.Text = "not ready"
|
|
690
|
+
return
|
|
691
|
+
end
|
|
692
|
+
|
|
693
|
+
local ids = {}
|
|
694
|
+
for id in pairs(toggles) do ids[#ids+1] = id end
|
|
695
|
+
table.sort(ids)
|
|
696
|
+
|
|
697
|
+
for order, id in ipairs(ids) do
|
|
698
|
+
local toggle = toggles[id]
|
|
699
|
+
local val = toggle.Value
|
|
700
|
+
|
|
701
|
+
local row = _mkFrame({
|
|
702
|
+
Size = UDim2.new(1, 0, 0, 26), BackgroundColor3 = P.bg3,
|
|
703
|
+
BorderSizePixel = 0, LayoutOrder = order, Parent = _featScroll,
|
|
704
|
+
})
|
|
705
|
+
|
|
706
|
+
local nameLabel = _mkText({
|
|
707
|
+
Text = id, Size = UDim2.new(1, -80, 1, 0), Position = UDim2.new(0, 8, 0, 0),
|
|
708
|
+
TextColor3 = val and P.text or P.dim, TextSize = 12,
|
|
709
|
+
TextTruncate = Enum.TextTruncate.AtEnd, Parent = row,
|
|
710
|
+
})
|
|
711
|
+
|
|
712
|
+
local stateBtn = _mkBtn({
|
|
713
|
+
Text = val and "ON" or "OFF",
|
|
714
|
+
Size = UDim2.new(0, 52, 0, 20),
|
|
715
|
+
Position = UDim2.new(1, -58, 0.5, -10),
|
|
716
|
+
TextColor3 = val and P.on or P.off,
|
|
717
|
+
TextSize = 11, Font = Enum.Font.GothamBold,
|
|
718
|
+
BackgroundColor3 = P.bg2, BackgroundTransparency = 0,
|
|
719
|
+
Parent = row,
|
|
720
|
+
}, nil)
|
|
721
|
+
Instance.new("UICorner", stateBtn).CornerRadius = UDim.new(0, 4)
|
|
722
|
+
local stateStroke = Instance.new("UIStroke", stateBtn)
|
|
723
|
+
stateStroke.Color = val and P.on or P.off
|
|
724
|
+
stateStroke.Thickness = 1
|
|
725
|
+
|
|
726
|
+
local rowEntry = { row = row, id = id, nameLabel = nameLabel, stateBtn = stateBtn, stateStroke = stateStroke }
|
|
727
|
+
_featRows[#_featRows+1] = rowEntry
|
|
728
|
+
|
|
729
|
+
local function _flipToggle()
|
|
730
|
+
local t = _getToggles()
|
|
731
|
+
if not t or not t[id] then return end
|
|
732
|
+
local newVal = not t[id].Value
|
|
733
|
+
pcall(function() t[id]:SetValue(newVal) end)
|
|
734
|
+
stateBtn.Text = newVal and "ON" or "OFF"
|
|
735
|
+
stateBtn.TextColor3 = newVal and P.on or P.off
|
|
736
|
+
stateStroke.Color = newVal and P.on or P.off
|
|
737
|
+
nameLabel.TextColor3 = newVal and P.text or P.dim
|
|
738
|
+
Pulse.Log.debug("feat-panel", (newVal and "enabled" or "disabled") .. ": " .. id)
|
|
739
|
+
end
|
|
740
|
+
|
|
741
|
+
stateBtn.MouseButton1Click:Connect(_flipToggle)
|
|
742
|
+
row.InputBegan:Connect(function(inp)
|
|
743
|
+
if inp.UserInputType == Enum.UserInputType.MouseButton1 then _flipToggle() end
|
|
744
|
+
end)
|
|
745
|
+
end
|
|
746
|
+
|
|
747
|
+
_featStatusLabel.Text = #ids .. " toggles"
|
|
748
|
+
end
|
|
749
|
+
|
|
750
|
+
_btnEnableAll.MouseButton1Click:Connect(function()
|
|
751
|
+
local toggles = _getToggles()
|
|
752
|
+
if not toggles then return end
|
|
753
|
+
for _, r in ipairs(_featRows) do
|
|
754
|
+
if r.id ~= "__hint" then
|
|
755
|
+
pcall(function() toggles[r.id]:SetValue(true) end)
|
|
756
|
+
if r.stateBtn then r.stateBtn.Text = "ON"; r.stateBtn.TextColor3 = P.on end
|
|
757
|
+
if r.stateStroke then r.stateStroke.Color = P.on end
|
|
758
|
+
if r.nameLabel then r.nameLabel.TextColor3 = P.text end
|
|
759
|
+
end
|
|
760
|
+
end
|
|
761
|
+
Pulse.Log.info("feat-panel", "all features enabled")
|
|
762
|
+
end)
|
|
763
|
+
|
|
764
|
+
_btnDisableAll.MouseButton1Click:Connect(function()
|
|
765
|
+
local toggles = _getToggles()
|
|
766
|
+
if not toggles then return end
|
|
767
|
+
for _, r in ipairs(_featRows) do
|
|
768
|
+
if r.id ~= "__hint" then
|
|
769
|
+
pcall(function() toggles[r.id]:SetValue(false) end)
|
|
770
|
+
if r.stateBtn then r.stateBtn.Text = "OFF"; r.stateBtn.TextColor3 = P.off end
|
|
771
|
+
if r.stateStroke then r.stateStroke.Color = P.off end
|
|
772
|
+
if r.nameLabel then r.nameLabel.TextColor3 = P.dim end
|
|
773
|
+
end
|
|
774
|
+
end
|
|
775
|
+
Pulse.Log.info("feat-panel", "all features disabled")
|
|
776
|
+
end)
|
|
777
|
+
|
|
778
|
+
-- ── Config snapshot panel (CFG tab) ───────────────────────────────────────────
|
|
779
|
+
-- Shows all Toggles + Options current values + Monitor runtime values.
|
|
780
|
+
-- Use COPY to grab a plain-text snapshot for bug reports.
|
|
781
|
+
|
|
782
|
+
local _cfgPanel = _mkFrame({
|
|
783
|
+
Size = UDim2.new(1, 0, 0, BODY_H),
|
|
784
|
+
Position = UDim2.new(0, 0, 0, HDR_H),
|
|
785
|
+
BackgroundColor3 = P.bg,
|
|
786
|
+
BorderSizePixel = 0,
|
|
787
|
+
Visible = false,
|
|
788
|
+
Parent = _win,
|
|
789
|
+
})
|
|
790
|
+
|
|
791
|
+
local _cfgScroll = Instance.new("ScrollingFrame")
|
|
792
|
+
_cfgScroll.Size = UDim2.new(1, 0, 1, -32)
|
|
793
|
+
_cfgScroll.Position = UDim2.new(0, 0, 0, 32)
|
|
794
|
+
_cfgScroll.BackgroundColor3 = P.bg2
|
|
795
|
+
_cfgScroll.BorderSizePixel = 0
|
|
796
|
+
_cfgScroll.ScrollBarThickness = 5
|
|
797
|
+
_cfgScroll.ScrollBarImageColor3 = P.border
|
|
798
|
+
_cfgScroll.CanvasSize = UDim2.new(0, 0, 0, 0)
|
|
799
|
+
_cfgScroll.AutomaticCanvasSize = Enum.AutomaticSize.Y
|
|
800
|
+
_cfgScroll.Parent = _cfgPanel
|
|
801
|
+
Instance.new("UIListLayout", _cfgScroll).SortOrder = Enum.SortOrder.LayoutOrder
|
|
802
|
+
do
|
|
803
|
+
local p = Instance.new("UIPadding", _cfgScroll)
|
|
804
|
+
p.PaddingLeft = UDim.new(0, 6); p.PaddingRight = UDim.new(0, 6); p.PaddingTop = UDim.new(0, 4)
|
|
805
|
+
end
|
|
806
|
+
|
|
807
|
+
local _cfgBar = _mkFrame({
|
|
808
|
+
Size = UDim2.new(1, 0, 0, 30), BackgroundColor3 = P.filter,
|
|
809
|
+
BorderSizePixel = 0, Parent = _cfgPanel,
|
|
810
|
+
})
|
|
811
|
+
Instance.new("UIStroke", _cfgBar).Color = P.border
|
|
812
|
+
|
|
813
|
+
_mkText({
|
|
814
|
+
Text = "Config Snapshot", Size = UDim2.new(1, -160, 1, 0),
|
|
815
|
+
Position = UDim2.new(0, 8, 0, 0), TextColor3 = P.dim, TextSize = 12,
|
|
816
|
+
Parent = _cfgBar,
|
|
817
|
+
})
|
|
818
|
+
|
|
819
|
+
local _cfgCopyLabel = _mkText({
|
|
820
|
+
Text = "", Size = UDim2.new(0, 80, 1, 0),
|
|
821
|
+
Position = UDim2.new(1, -160, 0, 0), TextColor3 = P.dim, TextSize = 11,
|
|
822
|
+
TextXAlignment = Enum.TextXAlignment.Right, Parent = _cfgBar,
|
|
823
|
+
})
|
|
824
|
+
|
|
825
|
+
local _btnCfgRefresh = _mkBtn({
|
|
826
|
+
Text = "REFRESH", Size = UDim2.new(0, 64, 0, 20),
|
|
827
|
+
Position = UDim2.new(1, -130, 0.5, -10), TextColor3 = P.dim, TextSize = 11,
|
|
828
|
+
BackgroundColor3 = P.bg2, BackgroundTransparency = 0, Parent = _cfgBar,
|
|
829
|
+
}, nil) -- wired below
|
|
830
|
+
Instance.new("UICorner", _btnCfgRefresh).CornerRadius = UDim.new(0, 4)
|
|
831
|
+
|
|
832
|
+
local _btnCfgCopy = _mkBtn({
|
|
833
|
+
Text = "COPY", Size = UDim2.new(0, 44, 0, 20),
|
|
834
|
+
Position = UDim2.new(1, -60, 0.5, -10), TextColor3 = P.dim, TextSize = 11,
|
|
835
|
+
BackgroundColor3 = P.bg2, BackgroundTransparency = 0, Parent = _cfgBar,
|
|
836
|
+
}, nil) -- wired below
|
|
837
|
+
Instance.new("UICorner", _btnCfgCopy).CornerRadius = UDim.new(0, 4)
|
|
838
|
+
|
|
839
|
+
local _cfgRows = {} -- { row = Frame } list for cleanup
|
|
840
|
+
|
|
841
|
+
local function _addCfgSection(title, order)
|
|
842
|
+
local row = _mkFrame({
|
|
843
|
+
Size = UDim2.new(1, 0, 0, 22), BackgroundColor3 = P.section,
|
|
844
|
+
BorderSizePixel = 0, LayoutOrder = order, Parent = _cfgScroll,
|
|
845
|
+
})
|
|
846
|
+
_mkText({
|
|
847
|
+
Text = title, Size = UDim2.new(1, -8, 1, 0), Position = UDim2.new(0, 8, 0, 0),
|
|
848
|
+
TextColor3 = P.active, TextSize = 11, Font = Enum.Font.GothamBold, Parent = row,
|
|
849
|
+
})
|
|
850
|
+
_cfgRows[#_cfgRows+1] = { row = row }
|
|
851
|
+
return order + 1
|
|
852
|
+
end
|
|
853
|
+
|
|
854
|
+
local function _addCfgRow(key, value, isOn, order)
|
|
855
|
+
local row = _mkFrame({
|
|
856
|
+
Size = UDim2.new(1, 0, 0, 22), BackgroundColor3 = P.bg3,
|
|
857
|
+
BorderSizePixel = 0, LayoutOrder = order, Parent = _cfgScroll,
|
|
858
|
+
})
|
|
859
|
+
_mkText({
|
|
860
|
+
Text = tostring(key), Size = UDim2.new(1, -90, 1, 0), Position = UDim2.new(0, 8, 0, 0),
|
|
861
|
+
TextColor3 = P.text, TextSize = 11, TextTruncate = Enum.TextTruncate.AtEnd, Parent = row,
|
|
862
|
+
})
|
|
863
|
+
local valColor = P.dim
|
|
864
|
+
if isOn == true then valColor = P.on end
|
|
865
|
+
if isOn == false then valColor = P.off end
|
|
866
|
+
_mkText({
|
|
867
|
+
Text = tostring(value), Size = UDim2.new(0, 80, 1, 0),
|
|
868
|
+
Position = UDim2.new(1, -88, 0, 0),
|
|
869
|
+
TextColor3 = valColor, TextSize = 11,
|
|
870
|
+
TextXAlignment = Enum.TextXAlignment.Right,
|
|
871
|
+
TextTruncate = Enum.TextTruncate.AtEnd,
|
|
872
|
+
Parent = row,
|
|
873
|
+
})
|
|
874
|
+
_cfgRows[#_cfgRows+1] = { row = row }
|
|
875
|
+
return order + 1
|
|
876
|
+
end
|
|
877
|
+
|
|
878
|
+
local function _buildCfgContent()
|
|
879
|
+
for _, r in ipairs(_cfgRows) do
|
|
880
|
+
if r.row and r.row.Parent then r.row:Destroy() end
|
|
881
|
+
end
|
|
882
|
+
_cfgRows = {}
|
|
883
|
+
|
|
884
|
+
local order = 1
|
|
885
|
+
local copyLines = {}
|
|
886
|
+
|
|
887
|
+
-- Toggles section
|
|
888
|
+
local toggles = _getToggles()
|
|
889
|
+
order = _addCfgSection("TOGGLES", order)
|
|
890
|
+
copyLines[#copyLines+1] = "=== TOGGLES ==="
|
|
891
|
+
if toggles and next(toggles) then
|
|
892
|
+
local ids = {}
|
|
893
|
+
for id in pairs(toggles) do ids[#ids+1] = id end
|
|
894
|
+
table.sort(ids)
|
|
895
|
+
for _, id in ipairs(ids) do
|
|
896
|
+
local val = toggles[id].Value
|
|
897
|
+
order = _addCfgRow(id, val and "ON" or "OFF", val, order)
|
|
898
|
+
copyLines[#copyLines+1] = id .. ": " .. (val and "ON" or "OFF")
|
|
899
|
+
end
|
|
900
|
+
else
|
|
901
|
+
order = _addCfgRow("(not ready)", "open menu first", nil, order)
|
|
902
|
+
copyLines[#copyLines+1] = "(not ready)"
|
|
903
|
+
end
|
|
904
|
+
|
|
905
|
+
-- Options section
|
|
906
|
+
local options = _getOptions()
|
|
907
|
+
order = _addCfgSection("OPTIONS", order)
|
|
908
|
+
copyLines[#copyLines+1] = ""
|
|
909
|
+
copyLines[#copyLines+1] = "=== OPTIONS ==="
|
|
910
|
+
if options and next(options) then
|
|
911
|
+
local ids = {}
|
|
912
|
+
for id in pairs(options) do ids[#ids+1] = id end
|
|
913
|
+
table.sort(ids)
|
|
914
|
+
for _, id in ipairs(ids) do
|
|
915
|
+
local val = options[id].Value
|
|
916
|
+
order = _addCfgRow(id, tostring(val), nil, order)
|
|
917
|
+
copyLines[#copyLines+1] = id .. ": " .. tostring(val)
|
|
918
|
+
end
|
|
919
|
+
else
|
|
920
|
+
order = _addCfgRow("(not ready)", "open menu first", nil, order)
|
|
921
|
+
copyLines[#copyLines+1] = "(not ready)"
|
|
922
|
+
end
|
|
923
|
+
|
|
924
|
+
-- Monitor section
|
|
925
|
+
order = _addCfgSection("MONITOR (runtime)", order)
|
|
926
|
+
copyLines[#copyLines+1] = ""
|
|
927
|
+
copyLines[#copyLines+1] = "=== MONITOR ==="
|
|
928
|
+
local monitors = Pulse.Monitor.getAll()
|
|
929
|
+
local mkeys = {}
|
|
930
|
+
for k in pairs(monitors) do mkeys[#mkeys+1] = k end
|
|
931
|
+
table.sort(mkeys)
|
|
932
|
+
if #mkeys > 0 then
|
|
933
|
+
for _, k in ipairs(mkeys) do
|
|
934
|
+
order = _addCfgRow(k, tostring(monitors[k]), nil, order)
|
|
935
|
+
copyLines[#copyLines+1] = k .. ": " .. tostring(monitors[k])
|
|
936
|
+
end
|
|
937
|
+
else
|
|
938
|
+
order = _addCfgRow("(no monitor values)", "", nil, order)
|
|
939
|
+
end
|
|
940
|
+
|
|
941
|
+
return table.concat(copyLines, "\n")
|
|
942
|
+
end
|
|
943
|
+
|
|
944
|
+
local _cfgCopyBuffer = ""
|
|
945
|
+
|
|
946
|
+
_btnCfgRefresh.MouseButton1Click:Connect(function()
|
|
947
|
+
_cfgCopyBuffer = _buildCfgContent()
|
|
948
|
+
_cfgCopyLabel.Text = "refreshed"
|
|
949
|
+
task.delay(1.2, function() if _cfgCopyLabel and _cfgCopyLabel.Parent then _cfgCopyLabel.Text = "" end end)
|
|
950
|
+
end)
|
|
951
|
+
|
|
952
|
+
_btnCfgCopy.MouseButton1Click:Connect(function()
|
|
953
|
+
if _cfgCopyBuffer == "" then _cfgCopyBuffer = _buildCfgContent() end
|
|
954
|
+
local feedback
|
|
955
|
+
if _copyText(_cfgCopyBuffer) then
|
|
956
|
+
feedback = "copied!"
|
|
957
|
+
else
|
|
958
|
+
feedback = "no clipboard fn"
|
|
959
|
+
end
|
|
960
|
+
_cfgCopyLabel.Text = feedback
|
|
961
|
+
task.delay(1.5, function() if _cfgCopyLabel and _cfgCopyLabel.Parent then _cfgCopyLabel.Text = "" end end)
|
|
962
|
+
end)
|
|
963
|
+
|
|
964
|
+
-- ── Tab switching ─────────────────────────────────────────────────────────────
|
|
965
|
+
-- Tools are now in the WindUI Dev tab (pulse/dev/ui/9_DevPanel.lua).
|
|
966
|
+
local _allTabBtns = {
|
|
967
|
+
{ btn = _btnTabLog, stroke = _logStroke, tab = "log" },
|
|
968
|
+
{ btn = _btnTabFeat, stroke = _featStroke, tab = "feat" },
|
|
969
|
+
{ btn = _btnTabCfg, stroke = _cfgStroke, tab = "cfg" },
|
|
970
|
+
}
|
|
971
|
+
|
|
972
|
+
local function _setTab(tab)
|
|
973
|
+
_mode = tab
|
|
974
|
+
local isLog = tab == "log"
|
|
975
|
+
local isFeat = tab == "feat"
|
|
976
|
+
local isCfg = tab == "cfg"
|
|
977
|
+
|
|
978
|
+
_flt.Visible = isLog
|
|
979
|
+
_scroll.Visible = isLog
|
|
980
|
+
_featPanel.Visible = isFeat
|
|
981
|
+
_cfgPanel.Visible = isCfg
|
|
982
|
+
_btnClear.Visible = isLog
|
|
983
|
+
_btnSave.Visible = isLog
|
|
984
|
+
|
|
985
|
+
for _, t in ipairs(_allTabBtns) do
|
|
986
|
+
local active = t.tab == tab
|
|
987
|
+
t.btn.TextColor3 = active and P.active or P.dim
|
|
988
|
+
t.stroke.Color = active and P.active or P.border
|
|
989
|
+
end
|
|
990
|
+
|
|
991
|
+
if isFeat then _buildFeatRows() end
|
|
992
|
+
if isLog then _rerender() end
|
|
993
|
+
if isCfg then _cfgCopyBuffer = _buildCfgContent() end
|
|
994
|
+
|
|
995
|
+
_enableMouseForOverlay()
|
|
996
|
+
end
|
|
997
|
+
|
|
998
|
+
_btnTabLog.MouseButton1Click:Connect(function() _setTab("log") end)
|
|
999
|
+
_btnTabFeat.MouseButton1Click:Connect(function() _setTab("feat") end)
|
|
1000
|
+
_btnTabCfg.MouseButton1Click:Connect(function() _setTab("cfg") end)
|
|
1001
|
+
|
|
1002
|
+
-- ── Shared UIS reference ─────────────────────────────────────────────────────
|
|
1003
|
+
local _UIS = game:GetService("UserInputService")
|
|
1004
|
+
|
|
1005
|
+
-- ── Mouse cursor ──────────────────────────────────────────────────────────────
|
|
1006
|
+
-- Ensure the cursor is visible and unlocked while the overlay is open.
|
|
1007
|
+
-- Games can hide/lock the cursor; without this the overlay is unclickable.
|
|
1008
|
+
_enableMouseForOverlay = function()
|
|
1009
|
+
pcall(function() _UIS.MouseIconEnabled = true end)
|
|
1010
|
+
pcall(function() _UIS.MouseBehavior = Enum.MouseBehavior.Default end)
|
|
1011
|
+
end
|
|
1012
|
+
|
|
1013
|
+
-- ── Floating anchor (TanStack-style, draggable corner toggle) ─────────────────
|
|
1014
|
+
-- Click to open/close the dev window. Drag to reposition. Insert key also toggles.
|
|
1015
|
+
|
|
1016
|
+
local _anchor = _mkFrame({
|
|
1017
|
+
Name = "DevAnchor",
|
|
1018
|
+
Size = UDim2.new(0, 72, 0, 26),
|
|
1019
|
+
Position = UDim2.new(1, -82, 1, -36),
|
|
1020
|
+
BackgroundColor3 = P.bg,
|
|
1021
|
+
BorderSizePixel = 0,
|
|
1022
|
+
ZIndex = 10,
|
|
1023
|
+
Parent = _gui,
|
|
1024
|
+
})
|
|
1025
|
+
Instance.new("UICorner", _anchor).CornerRadius = UDim.new(0, 7)
|
|
1026
|
+
local _anchorStroke = Instance.new("UIStroke", _anchor)
|
|
1027
|
+
_anchorStroke.Color = P.border
|
|
1028
|
+
_anchorStroke.Thickness = 1
|
|
1029
|
+
|
|
1030
|
+
local _anchorBtn = _mkBtn({
|
|
1031
|
+
Text = "◉ DEV",
|
|
1032
|
+
Size = UDim2.new(1, 0, 1, 0),
|
|
1033
|
+
TextColor3 = P.dim,
|
|
1034
|
+
TextSize = 12,
|
|
1035
|
+
Font = Enum.Font.GothamBold,
|
|
1036
|
+
BackgroundTransparency = 1,
|
|
1037
|
+
ZIndex = 11,
|
|
1038
|
+
Parent = _anchor,
|
|
1039
|
+
}, nil)
|
|
1040
|
+
|
|
1041
|
+
local function _updateAnchorState()
|
|
1042
|
+
local open = _win.Visible
|
|
1043
|
+
_anchorBtn.TextColor3 = open and P.active or P.dim
|
|
1044
|
+
_anchorStroke.Color = open and P.active or P.border
|
|
1045
|
+
end
|
|
1046
|
+
|
|
1047
|
+
local _anchorDragging, _anchorDragStart, _anchorPosStart = false, nil, nil
|
|
1048
|
+
local _anchorMoved = false
|
|
1049
|
+
|
|
1050
|
+
-- Wire to _anchorBtn (TextButton), not _anchor (Frame) — TextButton consumes input,
|
|
1051
|
+
-- so Frame.InputBegan never fires when clicking the button area.
|
|
1052
|
+
_anchorBtn.InputBegan:Connect(function(inp)
|
|
1053
|
+
if inp.UserInputType == Enum.UserInputType.MouseButton1 then
|
|
1054
|
+
_anchorDragging = true
|
|
1055
|
+
_anchorMoved = false
|
|
1056
|
+
_anchorDragStart = inp.Position
|
|
1057
|
+
_anchorPosStart = _anchor.Position
|
|
1058
|
+
end
|
|
1059
|
+
end)
|
|
1060
|
+
|
|
1061
|
+
_anchorBtn.InputEnded:Connect(function(inp)
|
|
1062
|
+
if inp.UserInputType == Enum.UserInputType.MouseButton1 then
|
|
1063
|
+
_anchorDragging = false
|
|
1064
|
+
if not _anchorMoved then
|
|
1065
|
+
_win.Visible = not _win.Visible
|
|
1066
|
+
if _win.Visible then _setTab(_mode) end
|
|
1067
|
+
_updateAnchorState()
|
|
1068
|
+
end
|
|
1069
|
+
_anchorMoved = false
|
|
1070
|
+
end
|
|
1071
|
+
end)
|
|
1072
|
+
|
|
1073
|
+
_UIS.InputChanged:Connect(function(inp)
|
|
1074
|
+
if _anchorDragging and inp.UserInputType == Enum.UserInputType.MouseMovement then
|
|
1075
|
+
local d = inp.Position - _anchorDragStart
|
|
1076
|
+
if math.abs(d.X) > 4 or math.abs(d.Y) > 4 then _anchorMoved = true end
|
|
1077
|
+
if _anchorMoved then
|
|
1078
|
+
_anchor.Position = UDim2.new(
|
|
1079
|
+
_anchorPosStart.X.Scale, _anchorPosStart.X.Offset + d.X,
|
|
1080
|
+
_anchorPosStart.Y.Scale, _anchorPosStart.Y.Offset + d.Y
|
|
1081
|
+
)
|
|
1082
|
+
end
|
|
1083
|
+
end
|
|
1084
|
+
end)
|
|
1085
|
+
|
|
1086
|
+
-- Insert key toggles the dev window
|
|
1087
|
+
_UIS.InputBegan:Connect(function(inp, processed)
|
|
1088
|
+
if not processed and inp.KeyCode == Enum.KeyCode.Insert then
|
|
1089
|
+
_win.Visible = not _win.Visible
|
|
1090
|
+
if _win.Visible then _setTab(_mode) end
|
|
1091
|
+
_updateAnchorState()
|
|
1092
|
+
end
|
|
1093
|
+
end)
|
|
1094
|
+
|
|
1095
|
+
-- ── Window dragging ───────────────────────────────────────────────────────────
|
|
1096
|
+
local _dragging, _dragStart, _winStart = false, nil, nil
|
|
1097
|
+
|
|
1098
|
+
_hdr.InputBegan:Connect(function(inp)
|
|
1099
|
+
if inp.UserInputType == Enum.UserInputType.MouseButton1 then
|
|
1100
|
+
_dragging = true; _dragStart = inp.Position; _winStart = _win.Position
|
|
1101
|
+
end
|
|
1102
|
+
end)
|
|
1103
|
+
_hdr.InputEnded:Connect(function(inp)
|
|
1104
|
+
if inp.UserInputType == Enum.UserInputType.MouseButton1 then _dragging = false end
|
|
1105
|
+
end)
|
|
1106
|
+
_UIS.InputChanged:Connect(function(inp)
|
|
1107
|
+
if _dragging and inp.UserInputType == Enum.UserInputType.MouseMovement then
|
|
1108
|
+
local d = inp.Position - _dragStart
|
|
1109
|
+
-- Clamp Y so the header can't be dragged behind the TopBar (~36 px inset)
|
|
1110
|
+
local newY = math.max(36, _winStart.Y.Offset + d.Y)
|
|
1111
|
+
_win.Position = UDim2.new(_winStart.X.Scale, _winStart.X.Offset + d.X, _winStart.Y.Scale, newY)
|
|
1112
|
+
end
|
|
1113
|
+
end)
|
|
1114
|
+
|
|
1115
|
+
-- ── Update loop ───────────────────────────────────────────────────────────────
|
|
1116
|
+
task.spawn(function()
|
|
1117
|
+
while _gui.Parent do
|
|
1118
|
+
task.wait(0.25)
|
|
1119
|
+
|
|
1120
|
+
if _win.Visible and _mode == "log" then
|
|
1121
|
+
local atBottom = _isAtBottom()
|
|
1122
|
+
local newEntries = Pulse.Log.getNewEntries(_lastId)
|
|
1123
|
+
local added = 0
|
|
1124
|
+
for _, e in ipairs(newEntries) do
|
|
1125
|
+
_lastId = math.max(_lastId, e.id)
|
|
1126
|
+
_addEntry(e)
|
|
1127
|
+
added = added + 1
|
|
1128
|
+
end
|
|
1129
|
+
if added > 0 then
|
|
1130
|
+
_countLabel.Text = _totalShown .. " shown"
|
|
1131
|
+
if atBottom then _scrollToBottom() end
|
|
1132
|
+
end
|
|
1133
|
+
end
|
|
1134
|
+
|
|
1135
|
+
-- Keep anchor button state in sync (open=blue, closed=dim)
|
|
1136
|
+
_updateAnchorState()
|
|
1137
|
+
|
|
1138
|
+
-- Always update monitor bar (visible in all tabs)
|
|
1139
|
+
if _win.Visible then
|
|
1140
|
+
local monitors = Pulse.Monitor.getAll()
|
|
1141
|
+
local parts = {}
|
|
1142
|
+
for k, v in pairs(monitors) do parts[#parts+1] = k .. ":" .. tostring(v) end
|
|
1143
|
+
table.sort(parts)
|
|
1144
|
+
_monLabel.Text = table.concat(parts, " ")
|
|
1145
|
+
end
|
|
1146
|
+
|
|
1147
|
+
if _win.Visible and _mode == "feat" then
|
|
1148
|
+
local toggles = _getToggles()
|
|
1149
|
+
if toggles and next(toggles) then
|
|
1150
|
+
if #_featRows == 0 or (_featRows[1] and _featRows[1].id == "__hint") then
|
|
1151
|
+
_buildFeatRows()
|
|
1152
|
+
else
|
|
1153
|
+
for _, r in ipairs(_featRows) do
|
|
1154
|
+
if r.id ~= "__hint" and r.stateBtn and toggles[r.id] then
|
|
1155
|
+
local val = toggles[r.id].Value
|
|
1156
|
+
r.stateBtn.Text = val and "ON" or "OFF"
|
|
1157
|
+
r.stateBtn.TextColor3 = val and P.on or P.off
|
|
1158
|
+
if r.stateStroke then r.stateStroke.Color = val and P.on or P.off end
|
|
1159
|
+
if r.nameLabel then r.nameLabel.TextColor3 = val and P.text or P.dim end
|
|
1160
|
+
end
|
|
1161
|
+
end
|
|
1162
|
+
end
|
|
1163
|
+
end
|
|
1164
|
+
end
|
|
1165
|
+
end
|
|
1166
|
+
end)
|
|
1167
|
+
|
|
1168
|
+
-- ── Wire remaining handlers ───────────────────────────────────────────────────
|
|
1169
|
+
_btnJumpErr.MouseButton1Click:Connect(function()
|
|
1170
|
+
local lastErrIdx = nil
|
|
1171
|
+
for i, item in ipairs(_labels) do
|
|
1172
|
+
if item.e.lbl == "ERR" then lastErrIdx = i end
|
|
1173
|
+
end
|
|
1174
|
+
if lastErrIdx then
|
|
1175
|
+
task.wait()
|
|
1176
|
+
local y = (lastErrIdx - 1) * ENTRY_H
|
|
1177
|
+
local maxY = math.max(0, _scroll.AbsoluteCanvasSize.Y - _scroll.AbsoluteWindowSize.Y)
|
|
1178
|
+
_scroll.CanvasPosition = Vector2.new(0, math.min(math.max(0, y - LOG_H / 2), maxY))
|
|
1179
|
+
end
|
|
1180
|
+
end)
|
|
1181
|
+
|
|
1182
|
+
_tagInput:GetPropertyChangedSignal("Text"):Connect(function()
|
|
1183
|
+
task.delay(0.15, _rerender)
|
|
1184
|
+
end)
|
|
1185
|
+
|
|
1186
|
+
for _, lbl in ipairs(LEVELS) do
|
|
1187
|
+
_lvlBtns[lbl].MouseButton1Click:Connect(function()
|
|
1188
|
+
task.delay(0.05, _rerender)
|
|
1189
|
+
end)
|
|
1190
|
+
end
|
|
1191
|
+
|
|
1192
|
+
-- ── Public API ────────────────────────────────────────────────────────────────
|
|
1193
|
+
_PulseDevGuiToggle = function()
|
|
1194
|
+
_win.Visible = not _win.Visible
|
|
1195
|
+
if _win.Visible then _setTab(_mode) end
|
|
1196
|
+
end
|
|
1197
|
+
|
|
1198
|
+
_PulseDevGuiShow = function(v)
|
|
1199
|
+
_win.Visible = v
|
|
1200
|
+
if v then _setTab(_mode) end
|
|
1201
|
+
end
|
|
1202
|
+
|
|
1203
|
+
_PulseDevGuiOpenFeat = function()
|
|
1204
|
+
_win.Visible = true
|
|
1205
|
+
_setTab("feat")
|
|
1206
|
+
end
|