@el4cteo/rbx-studio-mcp 0.3.8 → 0.4.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/package.json +1 -1
- package/plugin/src/Config.luau +1 -1
- package/plugin/src/Console.luau +273 -0
- package/plugin/src/Visuals.luau +1281 -838
package/package.json
CHANGED
package/plugin/src/Config.luau
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
|
|
10
10
|
local Config = {}
|
|
11
11
|
|
|
12
|
-
Config.PLUGIN_VERSION = "0.
|
|
12
|
+
Config.PLUGIN_VERSION = "0.4.0"
|
|
13
13
|
|
|
14
14
|
-- Fingerprint of plugin/src, stamped in by scripts/build-plugin.mjs. The server
|
|
15
15
|
-- computes the same hash from its own copy of the sources and compares, so a
|
package/plugin/src/Console.luau
CHANGED
|
@@ -18,12 +18,49 @@
|
|
|
18
18
|
Instances, and colour spans do the same job in a fraction of the code.
|
|
19
19
|
]]
|
|
20
20
|
|
|
21
|
+
local TweenService = game:GetService("TweenService")
|
|
22
|
+
|
|
21
23
|
local ThemePicker = require(script.Parent.ThemePicker)
|
|
22
24
|
local Themes = require(script.Parent.Themes)
|
|
23
25
|
local Visuals = require(script.Parent.Visuals)
|
|
24
26
|
|
|
25
27
|
local Console = {}
|
|
26
28
|
|
|
29
|
+
--[[
|
|
30
|
+
The clear wipe: an old CRT being switched off.
|
|
31
|
+
|
|
32
|
+
A button that empties the screen and leaves no trace is indistinguishable
|
|
33
|
+
from one that crashed the panel. The receipt row answers that in words; this
|
|
34
|
+
answers it in the two hundred milliseconds before anyone has read the words.
|
|
35
|
+
|
|
36
|
+
It is an OVERLAY and nothing else. The log is cleared for real at the same
|
|
37
|
+
instant, underneath, so nothing about the console's state waits on an
|
|
38
|
+
animation -- a line that arrives mid-wipe lands in the fresh log and is
|
|
39
|
+
simply revealed when the overlay goes. What collapses is a still copy of the
|
|
40
|
+
text that was on screen, which is the honest thing to animate: it is a
|
|
41
|
+
picture of what was erased.
|
|
42
|
+
|
|
43
|
+
Three phases, and the timings are the whole effect. The picture holds and
|
|
44
|
+
then whips shut to a line; the line sits a moment; the line whips to a point
|
|
45
|
+
and is gone. Easing into each collapse rather than out of it is what makes it
|
|
46
|
+
read as a tube discharging rather than as a panel being resized.
|
|
47
|
+
]]
|
|
48
|
+
local CRT_COLLAPSE = 0.16
|
|
49
|
+
local CRT_HOLD = 0.07
|
|
50
|
+
local CRT_BLINK = 0.17
|
|
51
|
+
local CRT_SHUT = TweenInfo.new(CRT_COLLAPSE, Enum.EasingStyle.Quart, Enum.EasingDirection.In)
|
|
52
|
+
local CRT_SETTLE = TweenInfo.new(0.05, Enum.EasingStyle.Quad, Enum.EasingDirection.Out)
|
|
53
|
+
local CRT_OUT = TweenInfo.new(CRT_BLINK, Enum.EasingStyle.Quart, Enum.EasingDirection.In)
|
|
54
|
+
|
|
55
|
+
--[[
|
|
56
|
+
Explicit depths, because a plugin widget draws with ZIndexBehavior.Global
|
|
57
|
+
and a child does not inherit its parent's. Above the log, the band and the
|
|
58
|
+
hover readout; below the preset drawer, which must stay clickable.
|
|
59
|
+
]]
|
|
60
|
+
local Z_CRT = 20
|
|
61
|
+
local Z_CRT_TEXT = 21
|
|
62
|
+
local Z_CRT_LINE = 22
|
|
63
|
+
|
|
27
64
|
--[[
|
|
28
65
|
Ring buffer bound, counted in logged rows rather than in rendered lines.
|
|
29
66
|
|
|
@@ -209,6 +246,24 @@ type State = {
|
|
|
209
246
|
clients: number,
|
|
210
247
|
-- Set while a repaint is already scheduled for the end of this frame.
|
|
211
248
|
dirty: boolean,
|
|
249
|
+
|
|
250
|
+
--[[
|
|
251
|
+
The clear wipe's overlay: an unclipped shell over the log region, the
|
|
252
|
+
collapsing screen inside it, a fixed window holding the still copy of
|
|
253
|
+
the text, and the bright line the picture discharges into.
|
|
254
|
+
]]
|
|
255
|
+
crt: Frame?,
|
|
256
|
+
crtScreen: Frame?,
|
|
257
|
+
crtWindow: Frame?,
|
|
258
|
+
crtGhost: TextLabel?,
|
|
259
|
+
crtLine: Frame?,
|
|
260
|
+
--[[
|
|
261
|
+
Bumped by every wipe, so the delayed halves of an earlier one know they
|
|
262
|
+
have been superseded and do nothing. `task.delay` hands back no handle to
|
|
263
|
+
cancel, and clearing twice inside four hundred milliseconds is a click
|
|
264
|
+
away.
|
|
265
|
+
]]
|
|
266
|
+
crtGeneration: number,
|
|
212
267
|
}
|
|
213
268
|
|
|
214
269
|
local state: State = {
|
|
@@ -232,6 +287,12 @@ local state: State = {
|
|
|
232
287
|
generation = 0,
|
|
233
288
|
clients = 1,
|
|
234
289
|
dirty = false,
|
|
290
|
+
crt = nil,
|
|
291
|
+
crtScreen = nil,
|
|
292
|
+
crtWindow = nil,
|
|
293
|
+
crtGhost = nil,
|
|
294
|
+
crtLine = nil,
|
|
295
|
+
crtGeneration = 0,
|
|
235
296
|
}
|
|
236
297
|
|
|
237
298
|
-- How long the session must be silent before the console says so. Long enough
|
|
@@ -656,7 +717,121 @@ function Console.setClients(count: number)
|
|
|
656
717
|
end
|
|
657
718
|
end
|
|
658
719
|
|
|
720
|
+
--[[
|
|
721
|
+
Plays the wipe over whatever is on screen right now.
|
|
722
|
+
|
|
723
|
+
Must be called BEFORE the log is emptied: the still copy it collapses is
|
|
724
|
+
read straight off the live label, scroll offset and all, so what discharges
|
|
725
|
+
is exactly the text the user was looking at rather than a re-render of it.
|
|
726
|
+
|
|
727
|
+
Silent when there is nothing to play it on -- a widget dragged down to a
|
|
728
|
+
sliver has no room for a picture to collapse, and a flourish that plays in
|
|
729
|
+
four pixels is a flicker, not an effect.
|
|
730
|
+
]]
|
|
731
|
+
local function crtWipe()
|
|
732
|
+
local shell = state.crt
|
|
733
|
+
local screen = state.crtScreen
|
|
734
|
+
local window = state.crtWindow
|
|
735
|
+
local ghost = state.crtGhost
|
|
736
|
+
local line = state.crtLine
|
|
737
|
+
local scroller = state.scroller
|
|
738
|
+
local label = state.label
|
|
739
|
+
if
|
|
740
|
+
shell == nil
|
|
741
|
+
or screen == nil
|
|
742
|
+
or window == nil
|
|
743
|
+
or ghost == nil
|
|
744
|
+
or line == nil
|
|
745
|
+
or scroller == nil
|
|
746
|
+
or label == nil
|
|
747
|
+
then
|
|
748
|
+
return
|
|
749
|
+
end
|
|
750
|
+
|
|
751
|
+
local height = scroller.AbsoluteSize.Y
|
|
752
|
+
if height < 24 then
|
|
753
|
+
return
|
|
754
|
+
end
|
|
755
|
+
|
|
756
|
+
state.crtGeneration += 1
|
|
757
|
+
local generation = state.crtGeneration
|
|
758
|
+
|
|
759
|
+
-- Read at play time rather than tracked, so the overlay lands on the log
|
|
760
|
+
-- wherever the band toggle and the widget's size have left it.
|
|
761
|
+
shell.Position = scroller.Position
|
|
762
|
+
shell.Size = scroller.Size
|
|
763
|
+
|
|
764
|
+
--[[
|
|
765
|
+
The still copy, offset by exactly as far as the log is scrolled.
|
|
766
|
+
|
|
767
|
+
A TextLabel cannot scroll its own text, so the window is a fixed-height
|
|
768
|
+
clip and the label inside it is pushed up by the canvas offset. That is
|
|
769
|
+
what makes the copy line up with the real log to the pixel instead of
|
|
770
|
+
snapping to the top of the buffer the moment the wipe starts.
|
|
771
|
+
]]
|
|
772
|
+
window.Size = UDim2.new(1, 0, 0, height)
|
|
773
|
+
ghost.Position = UDim2.new(0, 12, 0, 8 - scroller.CanvasPosition.Y)
|
|
774
|
+
ghost.Size = UDim2.new(1, -24, 0, 0)
|
|
775
|
+
ghost.TextColor3 = PALETTE.text
|
|
776
|
+
ghost.Text = label.Text
|
|
777
|
+
|
|
778
|
+
screen.BackgroundColor3 = PALETTE.background
|
|
779
|
+
screen.Size = UDim2.new(1, 0, 1, 0)
|
|
780
|
+
screen.Visible = true
|
|
781
|
+
|
|
782
|
+
line.BackgroundColor3 = PALETTE.text
|
|
783
|
+
line.Size = UDim2.new(1, 0, 0, 2)
|
|
784
|
+
line.BackgroundTransparency = 1
|
|
785
|
+
line.Visible = false
|
|
786
|
+
|
|
787
|
+
shell.Visible = true
|
|
788
|
+
|
|
789
|
+
-- The picture, whipping shut around its own middle.
|
|
790
|
+
TweenService:Create(screen, CRT_SHUT, { Size = UDim2.new(1, 0, 0, 2) }):Play()
|
|
791
|
+
|
|
792
|
+
task.delay(CRT_COLLAPSE, function()
|
|
793
|
+
if state.crtGeneration ~= generation then
|
|
794
|
+
return
|
|
795
|
+
end
|
|
796
|
+
|
|
797
|
+
--[[
|
|
798
|
+
The handover. The screen goes and the line arrives in the same frame,
|
|
799
|
+
a little over-thick, and settles -- which is the flash. Fading one
|
|
800
|
+
into the other instead just looks like a crossfade of two rectangles.
|
|
801
|
+
]]
|
|
802
|
+
screen.Visible = false
|
|
803
|
+
line.BackgroundTransparency = 0
|
|
804
|
+
line.Size = UDim2.new(1, 0, 0, 3)
|
|
805
|
+
line.Visible = true
|
|
806
|
+
TweenService:Create(line, CRT_SETTLE, { Size = UDim2.new(1, 0, 0, 2) }):Play()
|
|
807
|
+
|
|
808
|
+
task.delay(CRT_HOLD, function()
|
|
809
|
+
if state.crtGeneration ~= generation then
|
|
810
|
+
return
|
|
811
|
+
end
|
|
812
|
+
|
|
813
|
+
TweenService:Create(line, CRT_OUT, {
|
|
814
|
+
Size = UDim2.new(0, 0, 0, 2),
|
|
815
|
+
BackgroundTransparency = 0.35,
|
|
816
|
+
}):Play()
|
|
817
|
+
|
|
818
|
+
task.delay(CRT_BLINK, function()
|
|
819
|
+
if state.crtGeneration ~= generation then
|
|
820
|
+
return
|
|
821
|
+
end
|
|
822
|
+
shell.Visible = false
|
|
823
|
+
line.Visible = false
|
|
824
|
+
screen.Visible = true
|
|
825
|
+
end)
|
|
826
|
+
end)
|
|
827
|
+
end)
|
|
828
|
+
end
|
|
829
|
+
|
|
659
830
|
function Console.clear()
|
|
831
|
+
-- Before anything is emptied: the wipe collapses a copy of what is on
|
|
832
|
+
-- screen, and a moment later there is nothing on screen to copy.
|
|
833
|
+
crtWipe()
|
|
834
|
+
|
|
660
835
|
--[[
|
|
661
836
|
Says what it did, with a timestamp, like everything else in here.
|
|
662
837
|
|
|
@@ -691,6 +866,9 @@ end
|
|
|
691
866
|
visible without scrolling, however long the session has run.
|
|
692
867
|
]]
|
|
693
868
|
function Console.setStatus(status: string, meta: string)
|
|
869
|
+
-- Read before it is overwritten: the arrival flourish below is the one thing
|
|
870
|
+
-- here that cares whether this is a change or a repeat.
|
|
871
|
+
local previous = state.status
|
|
694
872
|
state.status = status
|
|
695
873
|
state.statusMeta = meta
|
|
696
874
|
|
|
@@ -718,6 +896,23 @@ function Console.setStatus(status: string, meta: string)
|
|
|
718
896
|
-- The wireframe takes the same colour as the status light, so the panel
|
|
719
897
|
-- reads as disconnected at a glance even with the header off screen.
|
|
720
898
|
Visuals.setTint(color)
|
|
899
|
+
|
|
900
|
+
-- The trace has no calls to plot until a session exists, so it waves while
|
|
901
|
+
-- the transport is reaching for one rather than sitting as a dead baseline
|
|
902
|
+
-- at exactly the moment somebody is watching to see whether it is alive.
|
|
903
|
+
Visuals.setConnecting(status == "connecting")
|
|
904
|
+
|
|
905
|
+
--[[
|
|
906
|
+
And it celebrates when the session finally lands.
|
|
907
|
+
|
|
908
|
+
Only on the transition. `setStatus` is also how a theme switch and a
|
|
909
|
+
port change repaint the header, and both replay the current status --
|
|
910
|
+
which would fire the flourish again for an event that already happened,
|
|
911
|
+
several times a session, until it read as noise rather than as news.
|
|
912
|
+
]]
|
|
913
|
+
if status == "connected" and previous ~= "connected" then
|
|
914
|
+
Visuals.celebrate()
|
|
915
|
+
end
|
|
721
916
|
end
|
|
722
917
|
|
|
723
918
|
--[[
|
|
@@ -958,6 +1153,84 @@ function Console.mount(parent: Instance, handlers: Handlers)
|
|
|
958
1153
|
label.Parent = scroller
|
|
959
1154
|
state.label = label
|
|
960
1155
|
|
|
1156
|
+
--[[
|
|
1157
|
+
The clear wipe's overlay, built once and hidden.
|
|
1158
|
+
|
|
1159
|
+
Four nested pieces, and each one earns its place. `crt` is unclipped and
|
|
1160
|
+
covers the log region, so the line can stay full width while the screen
|
|
1161
|
+
inside it closes. `screen` is the clip that actually collapses, anchored
|
|
1162
|
+
to its own middle so it shuts toward the centre rather than rolling up
|
|
1163
|
+
from the top. `window` is a fixed-height clip that does not move with it,
|
|
1164
|
+
which is what holds the copied text still while the screen closes over
|
|
1165
|
+
it. `ghost` is the copy.
|
|
1166
|
+
]]
|
|
1167
|
+
local crt = Instance.new("Frame")
|
|
1168
|
+
crt.Name = "ClearWipe"
|
|
1169
|
+
crt.BackgroundTransparency = 1
|
|
1170
|
+
crt.BorderSizePixel = 0
|
|
1171
|
+
crt.Visible = false
|
|
1172
|
+
crt.ZIndex = Z_CRT
|
|
1173
|
+
crt.Parent = root
|
|
1174
|
+
state.crt = crt
|
|
1175
|
+
|
|
1176
|
+
local crtScreen = Instance.new("Frame")
|
|
1177
|
+
crtScreen.Name = "Screen"
|
|
1178
|
+
crtScreen.AnchorPoint = Vector2.new(0.5, 0.5)
|
|
1179
|
+
crtScreen.Position = UDim2.fromScale(0.5, 0.5)
|
|
1180
|
+
crtScreen.Size = UDim2.new(1, 0, 1, 0)
|
|
1181
|
+
themed(crtScreen, "BackgroundColor3", "background")
|
|
1182
|
+
crtScreen.BorderSizePixel = 0
|
|
1183
|
+
crtScreen.ClipsDescendants = true
|
|
1184
|
+
crtScreen.ZIndex = Z_CRT
|
|
1185
|
+
crtScreen.Parent = crt
|
|
1186
|
+
state.crtScreen = crtScreen
|
|
1187
|
+
|
|
1188
|
+
local crtWindow = Instance.new("Frame")
|
|
1189
|
+
crtWindow.Name = "Window"
|
|
1190
|
+
crtWindow.AnchorPoint = Vector2.new(0.5, 0.5)
|
|
1191
|
+
crtWindow.Position = UDim2.fromScale(0.5, 0.5)
|
|
1192
|
+
crtWindow.Size = UDim2.new(1, 0, 1, 0)
|
|
1193
|
+
crtWindow.BackgroundTransparency = 1
|
|
1194
|
+
crtWindow.BorderSizePixel = 0
|
|
1195
|
+
crtWindow.ClipsDescendants = true
|
|
1196
|
+
crtWindow.ZIndex = Z_CRT_TEXT
|
|
1197
|
+
crtWindow.Parent = crtScreen
|
|
1198
|
+
state.crtWindow = crtWindow
|
|
1199
|
+
|
|
1200
|
+
-- Every text property the log's own label has, because the copy has to be
|
|
1201
|
+
-- indistinguishable from it for the frame before it starts moving.
|
|
1202
|
+
local crtGhost = Instance.new("TextLabel")
|
|
1203
|
+
crtGhost.Name = "Ghost"
|
|
1204
|
+
crtGhost.BackgroundTransparency = 1
|
|
1205
|
+
crtGhost.Font = Enum.Font.Code
|
|
1206
|
+
crtGhost.TextSize = 12
|
|
1207
|
+
crtGhost.LineHeight = 1.25
|
|
1208
|
+
themed(crtGhost, "TextColor3", "text")
|
|
1209
|
+
crtGhost.RichText = true
|
|
1210
|
+
crtGhost.TextWrapped = true
|
|
1211
|
+
crtGhost.AutomaticSize = Enum.AutomaticSize.Y
|
|
1212
|
+
crtGhost.Size = UDim2.new(1, -24, 0, 0)
|
|
1213
|
+
crtGhost.TextXAlignment = Enum.TextXAlignment.Left
|
|
1214
|
+
crtGhost.TextYAlignment = Enum.TextYAlignment.Top
|
|
1215
|
+
crtGhost.Text = ""
|
|
1216
|
+
crtGhost.ZIndex = Z_CRT_TEXT
|
|
1217
|
+
crtGhost.Parent = crtWindow
|
|
1218
|
+
state.crtGhost = crtGhost
|
|
1219
|
+
|
|
1220
|
+
-- The line the picture discharges into. Outside `screen`, so it keeps its
|
|
1221
|
+
-- width while the screen closes to nothing behind it.
|
|
1222
|
+
local crtLine = Instance.new("Frame")
|
|
1223
|
+
crtLine.Name = "Line"
|
|
1224
|
+
crtLine.AnchorPoint = Vector2.new(0.5, 0.5)
|
|
1225
|
+
crtLine.Position = UDim2.fromScale(0.5, 0.5)
|
|
1226
|
+
crtLine.Size = UDim2.new(1, 0, 0, 2)
|
|
1227
|
+
themed(crtLine, "BackgroundColor3", "text")
|
|
1228
|
+
crtLine.BorderSizePixel = 0
|
|
1229
|
+
crtLine.Visible = false
|
|
1230
|
+
crtLine.ZIndex = Z_CRT_LINE
|
|
1231
|
+
crtLine.Parent = crt
|
|
1232
|
+
state.crtLine = crtLine
|
|
1233
|
+
|
|
961
1234
|
-- Status bar ------------------------------------------------------------
|
|
962
1235
|
local footer = Instance.new("Frame")
|
|
963
1236
|
footer.AnchorPoint = Vector2.new(0, 1)
|